mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
a74f9419a0 | |||
9d8394eddf | |||
978d3e4d09 | |||
becdaaa25b | |||
b6f2176be8 | |||
f7f83aa519 | |||
64e8957c54 | |||
f3b3208367 | |||
ed28460e80 | |||
7b5b12617d | |||
be04d8037e | |||
56198372fd | |||
29ffb34fe8 | |||
834ea9b041 | |||
9441f78494 | |||
3367eba4fe | |||
6507542a80 | |||
fea3297a57 | |||
f322ab00ec | |||
b6374b9d07 | |||
70fd54804d | |||
ff268aee60 | |||
91a5c608d7 | |||
682be73eab | |||
fd4a6fef7f | |||
05a06f47b0 | |||
8e3de8a07a | |||
dc065add0b | |||
e64d4049a6 | |||
cb3bc85474 | |||
7af3da7872 | |||
3c77d717d2 | |||
6f399945fe | |||
c8b14b1262 | |||
35d4455c4f | |||
7d21b4b42b | |||
24e9e06c5a | |||
443a19d8e7 | |||
83de743924 | |||
1dc1de4b86 | |||
8ca7722d48 | |||
3079072e63 | |||
982d1c10e1 | |||
50b22bbf3c | |||
93e153398f | |||
be6470d8bc | |||
8a92c97058 | |||
93da605230 |
21
.github/workflows/build.yml
vendored
Normal file
21
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Build and test
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -3,7 +3,7 @@
|
|||||||
url = https://github.com/SergiusTheBest/plog
|
url = https://github.com/SergiusTheBest/plog
|
||||||
[submodule "usrsctp"]
|
[submodule "usrsctp"]
|
||||||
path = deps/usrsctp
|
path = deps/usrsctp
|
||||||
url = https://github.com/paullouisageneau/usrsctp.git
|
url = https://github.com/sctplab/usrsctp.git
|
||||||
[submodule "deps/libjuice"]
|
[submodule "deps/libjuice"]
|
||||||
path = deps/libjuice
|
path = deps/libjuice
|
||||||
url = https://github.com/paullouisageneau/libjuice
|
url = https://github.com/paullouisageneau/libjuice
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required (VERSION 3.7)
|
cmake_minimum_required (VERSION 3.7)
|
||||||
project (libdatachannel
|
project (libdatachannel
|
||||||
DESCRIPTION "WebRTC Data Channels Library"
|
DESCRIPTION "WebRTC DataChannels Library"
|
||||||
VERSION 0.4.1
|
VERSION 0.4.8
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||||
@ -16,6 +16,12 @@ endif()
|
|||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
if (MSYS OR MINGW)
|
||||||
|
add_definitions(-DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(LIBDATACHANNEL_SOURCES
|
set(LIBDATACHANNEL_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
|
||||||
@ -32,6 +38,8 @@ set(LIBDATACHANNEL_SOURCES
|
|||||||
|
|
||||||
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
|
||||||
@ -58,10 +66,7 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
|||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||||
target_link_libraries(datachannel
|
target_link_libraries(datachannel Threads::Threads Usrsctp::UsrsctpStatic)
|
||||||
Threads::Threads
|
|
||||||
Usrsctp::UsrsctpStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
||||||
@ -72,10 +77,12 @@ target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
|||||||
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_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||||
target_link_libraries(datachannel-static
|
target_link_libraries(datachannel-static Threads::Threads Usrsctp::UsrsctpStatic)
|
||||||
Threads::Threads
|
|
||||||
Usrsctp::UsrsctpStatic
|
if(WIN32)
|
||||||
)
|
target_link_libraries(datachannel "wsock32" "ws2_32") # winsock2
|
||||||
|
target_link_libraries(datachannel-static "wsock32" "ws2_32") # winsock2
|
||||||
|
endif()
|
||||||
|
|
||||||
if (USE_GNUTLS)
|
if (USE_GNUTLS)
|
||||||
find_package(GnuTLS REQUIRED)
|
find_package(GnuTLS REQUIRED)
|
||||||
@ -138,6 +145,6 @@ add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
|
|||||||
set_target_properties(datachannel-answerer PROPERTIES
|
set_target_properties(datachannel-answerer PROPERTIES
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME datachannel)
|
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
|
||||||
target_link_libraries(datachannel-answerer datachannel)
|
target_link_libraries(datachannel-answerer datachannel)
|
||||||
|
|
||||||
|
9
Makefile
9
Makefile
@ -4,8 +4,8 @@ NAME=libdatachannel
|
|||||||
CXX=$(CROSS)g++
|
CXX=$(CROSS)g++
|
||||||
AR=$(CROSS)ar
|
AR=$(CROSS)ar
|
||||||
RM=rm -f
|
RM=rm -f
|
||||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
|
||||||
CXXFLAGS=-std=c++17
|
CXXFLAGS=-std=c++17
|
||||||
|
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
||||||
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
|
||||||
|
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
|||||||
# libdatachannel - C/C++ WebRTC DataChannels
|
# 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.
|
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
|
||||||
|
|
||||||
@ -79,11 +79,11 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
|
|||||||
### Observe the PeerConnection state
|
### 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.
|
||||||
|
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 25b71f18dc...7cb445b8ff
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: 8768f70504...aa10d60bc2
@ -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();
|
||||||
|
@ -44,27 +44,25 @@ public:
|
|||||||
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> sctpTransport);
|
||||||
|
@ -35,6 +35,7 @@ public:
|
|||||||
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
||||||
|
|
||||||
Description(const string &sdp, const string &typeString = "");
|
Description(const string &sdp, const string &typeString = "");
|
||||||
|
Description(const string &sdp, Type type);
|
||||||
Description(const string &sdp, Type type, Role role);
|
Description(const string &sdp, Type type, Role role);
|
||||||
|
|
||||||
Type type() const;
|
Type type() const;
|
||||||
@ -47,6 +48,7 @@ public:
|
|||||||
std::optional<size_t> maxMessageSize() const;
|
std::optional<size_t> maxMessageSize() const;
|
||||||
bool trickleEnabled() const;
|
bool trickleEnabled() const;
|
||||||
|
|
||||||
|
void hintType(Type type);
|
||||||
void setFingerprint(string fingerprint);
|
void setFingerprint(string fingerprint);
|
||||||
void setSctpPort(uint16_t port);
|
void setSctpPort(uint16_t port);
|
||||||
void setMaxMessageSize(size_t size);
|
void setMaxMessageSize(size_t size);
|
||||||
|
@ -19,6 +19,15 @@
|
|||||||
#ifndef RTC_INCLUDE_H
|
#ifndef RTC_INCLUDE_H
|
||||||
#define RTC_INCLUDE_H
|
#define RTC_INCLUDE_H
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#ifndef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0602
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -27,9 +36,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;
|
||||||
@ -44,8 +50,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
|
||||||
|
|
||||||
@ -53,29 +57,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...>;
|
||||||
@ -83,6 +64,7 @@ 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) {
|
||||||
|
55
include/rtc/log.hpp
Normal file
55
include/rtc/log.hpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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/Appenders/ColorConsoleAppender.h"
|
||||||
|
#include "plog/Log.h"
|
||||||
|
#include "plog/Logger.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
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -32,6 +32,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@ -95,6 +96,11 @@ private:
|
|||||||
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();
|
||||||
@ -118,6 +124,7 @@ private:
|
|||||||
std::recursive_mutex mInitMutex;
|
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;
|
||||||
|
@ -33,13 +33,13 @@ typedef enum {
|
|||||||
RTC_FAILED = 4,
|
RTC_FAILED = 4,
|
||||||
RTC_CLOSED = 5,
|
RTC_CLOSED = 5,
|
||||||
RTC_DESTROYING = 6 // internal
|
RTC_DESTROYING = 6 // internal
|
||||||
} rtc_state_t;
|
} rtcState;
|
||||||
|
|
||||||
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 +50,67 @@ 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;
|
||||||
|
} 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);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
// C++ API
|
// C++ API
|
||||||
#include "datachannel.hpp"
|
#include "datachannel.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
#include "peerconnection.hpp"
|
#include "peerconnection.hpp"
|
||||||
|
|
||||||
// C API
|
// C API
|
||||||
|
@ -22,8 +22,14 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
using std::array;
|
using std::array;
|
||||||
|
@ -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(); }
|
||||||
|
@ -21,6 +21,12 @@
|
|||||||
#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;
|
||||||
@ -77,6 +83,14 @@ 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)
|
if (mIsOpen.exchange(false) && mSctpTransport)
|
||||||
mSctpTransport->reset(mStream);
|
mSctpTransport->reset(mStream);
|
||||||
@ -131,8 +145,6 @@ 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 description = mPeerConnection->remoteDescription())
|
||||||
@ -142,13 +154,7 @@ size_t DataChannel::maxMessageSize() const {
|
|||||||
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; }
|
|
||||||
|
|
||||||
string DataChannel::protocol() const { return mProtocol; }
|
|
||||||
|
|
||||||
Reliability DataChannel::reliability() const { return *mReliability; }
|
|
||||||
|
|
||||||
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
||||||
mSctpTransport = sctpTransport;
|
mSctpTransport = sctpTransport;
|
||||||
|
@ -45,12 +45,13 @@ inline void trim_end(string &str) {
|
|||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
Description::Description(const string &sdp, const string &typeString)
|
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)
|
Description::Description(const string &sdp, Type type, Role role)
|
||||||
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) {
|
: mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
|
||||||
if (mType == Type::Answer && mRole == Role::ActPass)
|
hintType(type);
|
||||||
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
|
|
||||||
|
|
||||||
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
|
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||||
std::default_random_engine generator(seed);
|
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; }
|
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) {
|
void Description::setFingerprint(string fingerprint) {
|
||||||
mFingerprint.emplace(std::move(fingerprint));
|
mFingerprint.emplace(std::move(fingerprint));
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
// 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;
|
const char *err_pos = NULL;
|
||||||
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");
|
||||||
|
@ -19,15 +19,22 @@
|
|||||||
#include "icetransport.hpp"
|
#include "icetransport.hpp"
|
||||||
#include "configuration.hpp"
|
#include "configuration.hpp"
|
||||||
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
@ -109,9 +116,6 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
|||||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||||
: Description::Role::Active;
|
: Description::Role::Active;
|
||||||
mMid = description.mid();
|
mMid = description.mid();
|
||||||
// TODO
|
|
||||||
// mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
|
|
||||||
|
|
||||||
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
|
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
|
||||||
throw std::runtime_error("Failed to parse remote SDP");
|
throw std::runtime_error("Failed to parse remote SDP");
|
||||||
}
|
}
|
||||||
@ -241,11 +245,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;
|
||||||
|
@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
@ -32,7 +36,13 @@ using namespace std::placeholders;
|
|||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::weak_ptr;
|
using std::weak_ptr;
|
||||||
|
|
||||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsaData;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
||||||
|
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
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) {}
|
||||||
@ -43,12 +53,15 @@ PeerConnection::~PeerConnection() {
|
|||||||
mSctpTransport.reset();
|
mSctpTransport.reset();
|
||||||
mDtlsTransport.reset();
|
mDtlsTransport.reset();
|
||||||
mIceTransport.reset();
|
mIceTransport.reset();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::close() {
|
void PeerConnection::close() {
|
||||||
// Close DataChannels
|
// Close DataChannels
|
||||||
closeDataChannels();
|
closeDataChannels();
|
||||||
mDataChannels.clear();
|
|
||||||
|
|
||||||
// Close Transports
|
// Close Transports
|
||||||
for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
|
for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
|
||||||
@ -79,9 +92,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::setRemoteDescription(Description description) {
|
void PeerConnection::setRemoteDescription(Description description) {
|
||||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||||
|
|
||||||
auto remoteCandidates = description.extractCandidates();
|
auto remoteCandidates = description.extractCandidates();
|
||||||
|
|
||||||
|
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||||
mRemoteDescription.emplace(std::move(description));
|
mRemoteDescription.emplace(std::move(description));
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
@ -100,12 +114,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);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,19 +175,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
|
||||||
@ -209,108 +215,126 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
|||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||||
std::lock_guard lock(mInitMutex);
|
try {
|
||||||
if (auto transport = std::atomic_load(&mIceTransport))
|
std::lock_guard lock(mInitMutex);
|
||||||
return transport;
|
if (auto transport = std::atomic_load(&mIceTransport))
|
||||||
|
return transport;
|
||||||
|
|
||||||
auto transport = std::make_shared<IceTransport>(
|
auto transport = std::make_shared<IceTransport>(
|
||||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||||
[this](IceTransport::State state) {
|
[this](IceTransport::State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IceTransport::State::Connecting:
|
case IceTransport::State::Connecting:
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
break;
|
break;
|
||||||
case IceTransport::State::Failed:
|
case IceTransport::State::Failed:
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
break;
|
break;
|
||||||
case IceTransport::State::Connected:
|
case IceTransport::State::Connected:
|
||||||
initDtlsTransport();
|
initDtlsTransport();
|
||||||
break;
|
break;
|
||||||
case IceTransport::State::Disconnected:
|
case IceTransport::State::Disconnected:
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[this](IceTransport::GatheringState state) {
|
[this](IceTransport::GatheringState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IceTransport::GatheringState::InProgress:
|
case IceTransport::GatheringState::InProgress:
|
||||||
changeGatheringState(GatheringState::InProgress);
|
changeGatheringState(GatheringState::InProgress);
|
||||||
break;
|
break;
|
||||||
case IceTransport::GatheringState::Complete:
|
case IceTransport::GatheringState::Complete:
|
||||||
endLocalCandidates();
|
endLocalCandidates();
|
||||||
changeGatheringState(GatheringState::Complete);
|
changeGatheringState(GatheringState::Complete);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::atomic_store(&mIceTransport, transport);
|
std::atomic_store(&mIceTransport, transport);
|
||||||
return 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() {
|
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||||
std::lock_guard lock(mInitMutex);
|
try {
|
||||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
std::lock_guard lock(mInitMutex);
|
||||||
return transport;
|
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||||
|
return transport;
|
||||||
|
|
||||||
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, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
||||||
[this](DtlsTransport::State state) {
|
[this](DtlsTransport::State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DtlsTransport::State::Connected:
|
case DtlsTransport::State::Connected:
|
||||||
initSctpTransport();
|
initSctpTransport();
|
||||||
break;
|
break;
|
||||||
case DtlsTransport::State::Failed:
|
case DtlsTransport::State::Failed:
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
break;
|
break;
|
||||||
case DtlsTransport::State::Disconnected:
|
case DtlsTransport::State::Disconnected:
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::atomic_store(&mDtlsTransport, transport);
|
std::atomic_store(&mDtlsTransport, transport);
|
||||||
return 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() {
|
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||||
std::lock_guard lock(mInitMutex);
|
try {
|
||||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
std::lock_guard lock(mInitMutex);
|
||||||
return transport;
|
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||||
|
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, std::bind(&PeerConnection::forwardMessage, this, _1),
|
||||||
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||||
[this](SctpTransport::State state) {
|
[this](SctpTransport::State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case SctpTransport::State::Connected:
|
case SctpTransport::State::Connected:
|
||||||
changeState(State::Connected);
|
changeState(State::Connected);
|
||||||
openDataChannels();
|
openDataChannels();
|
||||||
break;
|
break;
|
||||||
case SctpTransport::State::Failed:
|
case SctpTransport::State::Failed:
|
||||||
remoteCloseDataChannels();
|
remoteCloseDataChannels();
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
break;
|
break;
|
||||||
case SctpTransport::State::Disconnected:
|
case SctpTransport::State::Disconnected:
|
||||||
remoteCloseDataChannels();
|
remoteCloseDataChannels();
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::atomic_store(&mSctpTransport, transport);
|
std::atomic_store(&mSctpTransport, transport);
|
||||||
return 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() {
|
void PeerConnection::endLocalCandidates() {
|
||||||
@ -334,14 +358,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);
|
||||||
@ -369,21 +386,46 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
|
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
|
||||||
|
if (auto channel = findDataChannel(stream))
|
||||||
|
channel->triggerBufferedAmount(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
|
||||||
|
const string &label,
|
||||||
|
const string &protocol,
|
||||||
|
const Reliability &reliability) {
|
||||||
|
// The active side must use streams with even identifiers, whereas the passive side must use
|
||||||
|
// streams with odd identifiers.
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
|
||||||
|
std::unique_lock lock(mDataChannelsMutex);
|
||||||
|
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);
|
||||||
shared_ptr<DataChannel> channel;
|
shared_ptr<DataChannel> channel;
|
||||||
if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) {
|
if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) {
|
||||||
channel = it->second.lock();
|
channel = it->second.lock();
|
||||||
if (!channel || channel->isClosed()) {
|
if (!channel || channel->isClosed()) {
|
||||||
mDataChannels.erase(it);
|
mDataChannels.erase(it);
|
||||||
channel = nullptr;
|
channel.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return channel;
|
||||||
if (channel)
|
|
||||||
channel->triggerBufferedAmount(amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::iterateDataChannels(
|
void PeerConnection::iterateDataChannels(
|
||||||
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
||||||
|
std::shared_lock lock(mDataChannelsMutex);
|
||||||
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();
|
||||||
|
532
src/rtc.cpp
532
src/rtc.cpp
@ -22,191 +22,82 @@
|
|||||||
|
|
||||||
#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
|
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
auto it = peerConnectionMap.find(id);
|
||||||
|
return it != peerConnectionMap.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); }
|
shared_ptr<DataChannel> getDataChannel(int id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
auto it = dataChannelMap.find(id);
|
||||||
|
return it != dataChannelMap.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
|
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
||||||
Configuration config;
|
std::lock_guard lock(mutex);
|
||||||
for (int i = 0; i < iceServersCount; ++i) {
|
|
||||||
config.iceServers.emplace_back(IceServer(string(iceServers[i])));
|
|
||||||
}
|
|
||||||
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);
|
||||||
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) {
|
if (peerConnectionMap.erase(pc) == 0)
|
||||||
auto it = peerConnectionMap.find(pc);
|
return false;
|
||||||
if (it == peerConnectionMap.end())
|
userPointerMap.erase(pc);
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) {
|
|
||||||
int dc = ++lastId;
|
|
||||||
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
|
|
||||||
dataChannelCallback(dc, getUserPointer(pc));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
|
bool eraseDataChannel(int dc) {
|
||||||
void *)) {
|
std::lock_guard lock(mutex);
|
||||||
auto it = peerConnectionMap.find(pc);
|
if (dataChannelMap.erase(dc) == 0)
|
||||||
if (it == peerConnectionMap.end())
|
return false;
|
||||||
return;
|
userPointerMap.erase(dc);
|
||||||
|
return true;
|
||||||
it->second->onLocalDescription([pc, descriptionCallback](const Description &description) {
|
|
||||||
descriptionCallback(string(description).c_str(), description.typeString().c_str(),
|
|
||||||
getUserPointer(pc));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetLocalCandidateCallback(int pc,
|
} // namespace
|
||||||
void (*candidateCallback)(const char *, const char *, void *)) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onLocalCandidate([pc, candidateCallback](const Candidate &candidate) {
|
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
|
||||||
candidateCallback(candidate.candidate().c_str(), candidate.mid().c_str(),
|
|
||||||
getUserPointer(pc));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onStateChange([pc, stateCallback](PeerConnection::State state) {
|
|
||||||
stateCallback(static_cast<rtc_state_t>(state), getUserPointer(pc));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetGatheringStateChangeCallback(int pc,
|
|
||||||
void (*gatheringStateCallback)(rtc_gathering_state_t state,
|
|
||||||
void *)) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onGatheringStateChange(
|
|
||||||
[pc, gatheringStateCallback](PeerConnection::GatheringState state) {
|
|
||||||
gatheringStateCallback(static_cast<rtc_gathering_state_t>(state), getUserPointer(pc));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
|
||||||
auto it = dataChannelMap.find(dc);
|
|
||||||
if (it == dataChannelMap.end())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!size)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
string label = it->second->label();
|
|
||||||
size = std::min(size_t(size - 1), label.size());
|
|
||||||
std::copy(label.data(), label.data() + size, buffer);
|
|
||||||
buffer[size] = '\0';
|
|
||||||
return size + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) {
|
|
||||||
auto it = dataChannelMap.find(dc);
|
|
||||||
if (it == dataChannelMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) {
|
|
||||||
auto it = dataChannelMap.find(dc);
|
|
||||||
if (it == dataChannelMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onError([dc, errorCallback](const string &error) {
|
|
||||||
errorCallback(error.c_str(), getUserPointer(dc));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) {
|
|
||||||
auto it = dataChannelMap.find(dc);
|
|
||||||
if (it == dataChannelMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onMessage(
|
|
||||||
[dc, messageCallback](const binary &b) {
|
|
||||||
messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
|
||||||
},
|
|
||||||
[dc, messageCallback](const string &s) {
|
|
||||||
messageCallback(s.c_str(), -1, getUserPointer(dc));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int rtcSendMessage(int dc, const char *data, int size) {
|
|
||||||
auto it = dataChannelMap.find(dc);
|
|
||||||
if (it == dataChannelMap.end())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (size >= 0) {
|
|
||||||
auto b = reinterpret_cast<const byte *>(data);
|
|
||||||
it->second->send(b, size);
|
|
||||||
return size;
|
|
||||||
} else {
|
|
||||||
string s(data);
|
|
||||||
it->second->send(s);
|
|
||||||
return s.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rtcSetUserPointer(int i, void *ptr) {
|
void rtcSetUserPointer(int i, void *ptr) {
|
||||||
if (ptr)
|
if (ptr)
|
||||||
@ -214,3 +105,340 @@ void rtcSetUserPointer(int i, void *ptr) {
|
|||||||
else
|
else
|
||||||
userPointerMap.erase(i);
|
userPointerMap.erase(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||||
|
Configuration c;
|
||||||
|
for (int i = 0; i < config->iceServersCount; ++i)
|
||||||
|
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||||
|
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onLocalDescription(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||||
|
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onLocalCandidate(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||||
|
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onStateChange(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||||
|
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onGatheringStateChange(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (auto addr = peerConnection->localAddress()) {
|
||||||
|
size = std::min(size_t(size - 1), addr->size());
|
||||||
|
std::copy(addr->data(), addr->data() + size, buffer);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (auto addr = peerConnection->remoteAddress()) {
|
||||||
|
size = std::min(size_t(size - 1), addr->size());
|
||||||
|
std::copy(addr->data(), addr->data() + size, buffer);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
string label = dataChannel->label();
|
||||||
|
size = std::min(size_t(size - 1), label.size());
|
||||||
|
std::copy(label.data(), label.data() + size, buffer);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetOpenCallback(int dc, openCallbackFunc 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 rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onClosed(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onError(
|
||||||
|
[dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onError(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetMessageCallback(int dc, messageCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onMessage(
|
||||||
|
[dc, cb](const binary &b) {
|
||||||
|
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
||||||
|
},
|
||||||
|
[dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onMessage(nullptr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSendMessage(int dc, const char *data, int size) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (size >= 0) {
|
||||||
|
auto b = reinterpret_cast<const byte *>(data);
|
||||||
|
CATCH(dataChannel->send(b, size));
|
||||||
|
return size;
|
||||||
|
} else {
|
||||||
|
string s(data);
|
||||||
|
CATCH(dataChannel->send(s));
|
||||||
|
return s.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetBufferedAmount(int dc) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(return int(dataChannel->bufferedAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetBufferedAmountLowThreshold(int dc, int amount) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(dataChannel->setBufferedAmountLowThreshold(size_t(amount)));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onBufferedAmountLow(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetAvailableAmount(int dc) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(return int(dataChannel->availableAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onOpen(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcReceiveMessage(int dc, char *buffer, int *size) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH({
|
||||||
|
auto message = dataChannel->receive();
|
||||||
|
if (!message)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return std::visit( //
|
||||||
|
overloaded{ //
|
||||||
|
[&](const binary &b) {
|
||||||
|
*size = std::min(*size, int(b.size()));
|
||||||
|
auto data = reinterpret_cast<const char *>(b.data());
|
||||||
|
std::copy(data, data + *size, buffer);
|
||||||
|
return *size;
|
||||||
|
},
|
||||||
|
[&](const string &s) {
|
||||||
|
int len = std::min(*size - 1, int(s.size()));
|
||||||
|
if (len >= 0) {
|
||||||
|
std::copy(s.data(), s.data() + len, buffer);
|
||||||
|
buffer[len] = '\0';
|
||||||
|
}
|
||||||
|
*size = -(len + 1);
|
||||||
|
return len + 1;
|
||||||
|
}},
|
||||||
|
*message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -23,7 +23,24 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#ifdef USE_JUICE
|
||||||
|
#ifndef __APPLE__
|
||||||
|
// libjuice enables Linux path MTU discovery or sets the DF flag
|
||||||
|
#define USE_PMTUD 1
|
||||||
|
#else
|
||||||
|
// Setting the DF flag is not available on Mac OS
|
||||||
|
#define USE_PMTUD 0
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef __linux__
|
||||||
|
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
|
||||||
|
// It should be safe to enable discovery for SCTP.
|
||||||
|
#define USE_PMTUD 1
|
||||||
|
#else
|
||||||
|
// Otherwise assume fragmentation
|
||||||
|
#define USE_PMTUD 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
@ -117,12 +134,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
std::to_string(errno));
|
std::to_string(errno));
|
||||||
|
|
||||||
struct sctp_paddrparams spp = {};
|
struct sctp_paddrparams spp = {};
|
||||||
#ifdef __linux__
|
#if USE_PMTUD
|
||||||
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE).
|
// Enabled SCTP path MTU discovery
|
||||||
// It should be safe to enable discovery for SCTP.
|
|
||||||
spp.spp_flags = SPP_PMTUD_ENABLE;
|
spp.spp_flags = SPP_PMTUD_ENABLE;
|
||||||
#else
|
#else
|
||||||
// Otherwise, fall back to a safe MTU value.
|
// Fall back to a safe MTU value.
|
||||||
spp.spp_flags = SPP_PMTUD_DISABLE;
|
spp.spp_flags = SPP_PMTUD_DISABLE;
|
||||||
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
|
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
|
||||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||||
@ -353,7 +369,7 @@ 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();
|
||||||
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";
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,9 +29,6 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "usrsctp.h"
|
#include "usrsctp.h"
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
189
test/capi.cpp
Normal file
189
test/capi.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
#include <unistd.h> // for sleep
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
rtcInitLogger(RTC_LOG_DEBUG);
|
||||||
|
|
||||||
|
rtcConfiguration config;
|
||||||
|
memset(&config, 0, sizeof(config));
|
||||||
|
// const char *iceServers[1] = {"stun:stun.l.google.com:19302"};
|
||||||
|
// config.iceServers = iceServers;
|
||||||
|
// config.iceServersCount = 1;
|
||||||
|
|
||||||
|
// Create peer 1
|
||||||
|
peer1 = createPeer(&config);
|
||||||
|
if (!peer1)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
// Create peer 2
|
||||||
|
peer2 = createPeer(&config);
|
||||||
|
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);
|
||||||
|
|
||||||
|
sleep(3);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (peer1->connected && peer2->connected) {
|
||||||
|
deletePeer(peer1);
|
||||||
|
deletePeer(peer2);
|
||||||
|
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");
|
||||||
|
}
|
129
test/connectivity.cpp
Normal file
129
test/connectivity.cpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* 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 config;
|
||||||
|
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||||
|
|
||||||
|
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 << "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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this_thread::sleep_for(3s);
|
||||||
|
|
||||||
|
if (auto addr = pc1->localAddress())
|
||||||
|
cout << "Local address 1: " << *addr << endl;
|
||||||
|
if (auto addr = pc1->remoteAddress())
|
||||||
|
cout << "Remote address 1: " << *addr << endl;
|
||||||
|
if (auto addr = pc2->localAddress())
|
||||||
|
cout << "Local address 2: " << *addr << endl;
|
||||||
|
if (auto addr = pc2->remoteAddress())
|
||||||
|
cout << "Remote address 2: " << *addr << endl;
|
||||||
|
|
||||||
|
if (!dc1->isOpen() || !dc2->isOpen())
|
||||||
|
throw runtime_error("DataChannel is not open");
|
||||||
|
|
||||||
|
pc1->close();
|
||||||
|
pc2->close();
|
||||||
|
|
||||||
|
this_thread::sleep_for(1s);
|
||||||
|
|
||||||
|
cout << "Success" << endl;
|
||||||
|
}
|
127
test/main.cpp
127
test/main.cpp
@ -16,120 +16,29 @@
|
|||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* 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>
|
|
||||||
|
|
||||||
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;
|
||||||
Configuration config;
|
test_connectivity();
|
||||||
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
std::cout << "*** Finished connectivity test" << std::endl;
|
||||||
// config.enableIceTcp = true;
|
} catch (const exception &e) {
|
||||||
|
std::cerr << "Connectivity test failed: " << e.what() << endl;
|
||||||
// TURN server example
|
return -1;
|
||||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
|
||||||
// IceServer::RelayType::TurnUdp);
|
|
||||||
// config.iceServers.push_back(turnServer);
|
|
||||||
|
|
||||||
auto pc1 = std::make_shared<PeerConnection>(config);
|
|
||||||
auto pc2 = std::make_shared<PeerConnection>(config);
|
|
||||||
|
|
||||||
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
|
||||||
auto pc2 = wpc2.lock();
|
|
||||||
if (!pc2)
|
|
||||||
return;
|
|
||||||
cout << "Description 1: " << sdp << endl;
|
|
||||||
pc2->setRemoteDescription(sdp);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
|
||||||
auto pc2 = wpc2.lock();
|
|
||||||
if (!pc2)
|
|
||||||
return;
|
|
||||||
cout << "Candidate 1: " << candidate << endl;
|
|
||||||
pc2->addRemoteCandidate(candidate);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
|
||||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
|
||||||
cout << "Gathering state 1: " << state << endl;
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
|
||||||
auto pc1 = wpc1.lock();
|
|
||||||
if (!pc1)
|
|
||||||
return;
|
|
||||||
cout << "Description 2: " << sdp << endl;
|
|
||||||
pc1->setRemoteDescription(sdp);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
|
||||||
auto pc1 = wpc1.lock();
|
|
||||||
if (!pc1)
|
|
||||||
return;
|
|
||||||
cout << "Candidate 2: " << candidate << endl;
|
|
||||||
pc1->addRemoteCandidate(candidate);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
|
||||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
|
||||||
cout << "Gathering state 2: " << state << endl;
|
|
||||||
});
|
|
||||||
|
|
||||||
shared_ptr<DataChannel> dc2;
|
|
||||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
|
||||||
cout << "Got a DataChannel with label: " << dc->label() << endl;
|
|
||||||
dc2 = dc;
|
|
||||||
dc2->onMessage([](const variant<binary, string> &message) {
|
|
||||||
if (holds_alternative<string>(message)) {
|
|
||||||
cout << "Received 2: " << get<string>(message) << endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dc2->send("Hello from 2");
|
|
||||||
});
|
|
||||||
|
|
||||||
auto dc1 = pc1->createDataChannel("test");
|
|
||||||
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
|
|
||||||
auto dc1 = wdc1.lock();
|
|
||||||
if (!dc1)
|
|
||||||
return;
|
|
||||||
cout << "DataChannel open: " << dc1->label() << endl;
|
|
||||||
dc1->send("Hello from 1");
|
|
||||||
});
|
|
||||||
dc1->onMessage([](const variant<binary, string> &message) {
|
|
||||||
if (holds_alternative<string>(message)) {
|
|
||||||
cout << "Received 1: " << get<string>(message) << endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this_thread::sleep_for(3s);
|
|
||||||
|
|
||||||
if (auto addr = pc1->localAddress())
|
|
||||||
cout << "Local address 1: " << *addr << endl;
|
|
||||||
if (auto addr = pc1->remoteAddress())
|
|
||||||
cout << "Remote address 1: " << *addr << endl;
|
|
||||||
if (auto addr = pc2->localAddress())
|
|
||||||
cout << "Local address 2: " << *addr << endl;
|
|
||||||
if (auto addr = pc2->remoteAddress())
|
|
||||||
cout << "Remote address 2: " << *addr << endl;
|
|
||||||
|
|
||||||
if (dc1->isOpen() && dc2->isOpen()) {
|
|
||||||
pc1->close();
|
|
||||||
pc2->close();
|
|
||||||
|
|
||||||
cout << "Success" << endl;
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
cout << "Failure" << endl;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
std::cout << "*** Running C API test..." << std::endl;
|
||||||
|
test_capi();
|
||||||
|
std::cout << "*** Finished C API test" << std::endl;
|
||||||
|
} catch (const exception &e) {
|
||||||
|
std::cerr << "C API test failed: " << e.what() << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -32,41 +33,36 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||||
// config.enableIceTcp = true;
|
|
||||||
|
|
||||||
// TURN Server example
|
|
||||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
|
||||||
// IceServer::RelayType::TurnUdp);
|
|
||||||
// config.iceServers.push_back(turnServer);
|
|
||||||
|
|
||||||
auto pc = std::make_shared<PeerConnection>(config);
|
auto pc = std::make_shared<PeerConnection>(config);
|
||||||
|
|
||||||
pc->onLocalDescription([](const Description &sdp) {
|
pc->onLocalDescription([](const Description &description) {
|
||||||
std::string s(sdp);
|
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
cout << string(description) << endl;
|
||||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
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(
|
pc->onStateChange(
|
||||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
cout << "[Gathering State: " << state << "]" << endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
shared_ptr<DataChannel> dc = nullptr;
|
shared_ptr<DataChannel> dc = nullptr;
|
||||||
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
|
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 = _dc;
|
||||||
|
|
||||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
|
||||||
|
|
||||||
dc->onMessage([](const variant<binary, string> &message) {
|
dc->onMessage([](const variant<binary, string> &message) {
|
||||||
if (holds_alternative<string>(message)) {
|
if (holds_alternative<string>(message)) {
|
||||||
cout << "[ Received: " << get<string>(message) << " ]" << endl;
|
cout << "[Received message: " << get<string>(message) << "]" << endl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -74,67 +70,61 @@ int main(int argc, char **argv) {
|
|||||||
bool exit = false;
|
bool exit = false;
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
cout << endl
|
cout << endl
|
||||||
|
<< "**********************************************************************************"
|
||||||
|
"*****"
|
||||||
<< endl
|
<< endl
|
||||||
<< "*************************************************************************" << endl
|
|
||||||
<< "* 0: Exit /"
|
<< "* 0: Exit /"
|
||||||
<< " 1: Enter Description /"
|
<< " 1: Enter remote description /"
|
||||||
<< " 2: Enter Candidate /"
|
<< " 2: Enter remote candidate /"
|
||||||
<< " 3: Send Message *" << endl
|
<< " 3: Send message *" << endl
|
||||||
<< " [Command]: ";
|
<< "[Command]: ";
|
||||||
|
|
||||||
int command;
|
int command = -1;
|
||||||
std::string sdp, candidate, message;
|
|
||||||
const char *a;
|
|
||||||
std::unique_ptr<Candidate> candidatePtr;
|
|
||||||
std::unique_ptr<Description> descPtr;
|
|
||||||
cin >> command;
|
cin >> command;
|
||||||
|
cin.ignore();
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 0:
|
case 0: {
|
||||||
exit = true;
|
exit = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 1:
|
case 1: {
|
||||||
// Parse Description
|
// Parse Description
|
||||||
cout << "[SDP]: ";
|
cout << "[Description]: ";
|
||||||
sdp = "";
|
string sdp, line;
|
||||||
while (sdp.length() == 0)
|
while (getline(cin, line) && !line.empty()) {
|
||||||
getline(cin, sdp);
|
sdp += line;
|
||||||
|
sdp += "\r\n";
|
||||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
}
|
||||||
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer,
|
std::cout << sdp;
|
||||||
Description::Role::Passive);
|
pc->setRemoteDescription(sdp);
|
||||||
pc->setRemoteDescription(*descPtr);
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 2:
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
cout << "[Candidate]: ";
|
cout << "[Candidate]: ";
|
||||||
candidate = "";
|
string candidate;
|
||||||
while (candidate.length() == 0)
|
getline(cin, candidate);
|
||||||
getline(cin, candidate);
|
pc->addRemoteCandidate(candidate);
|
||||||
|
|
||||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
|
||||||
pc->addRemoteCandidate(*candidatePtr);
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 3:
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if (!dc || !dc->isOpen()) {
|
if (!dc || !dc->isOpen()) {
|
||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cout << "[Message]: ";
|
cout << "[Message]: ";
|
||||||
message = "";
|
string message;
|
||||||
while (message.length() == 0)
|
getline(cin, message);
|
||||||
getline(cin, message);
|
|
||||||
dc->send(message);
|
dc->send(message);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default: {
|
||||||
cout << "** Invalid Command ** ";
|
cout << "** Invalid Command ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dc)
|
if (dc)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -32,106 +33,98 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||||
// config.enableIceTcp = true;
|
|
||||||
|
|
||||||
// TURN server example
|
|
||||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
|
||||||
// IceServer::RelayType::TurnUdp);
|
|
||||||
// config.iceServers.push_back(turnServer);
|
|
||||||
|
|
||||||
auto pc = std::make_shared<PeerConnection>(config);
|
auto pc = std::make_shared<PeerConnection>(config);
|
||||||
|
|
||||||
pc->onLocalDescription([](const Description &sdp) {
|
pc->onLocalDescription([](const Description &description) {
|
||||||
std::string s(sdp);
|
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
cout << string(description) << endl;
|
||||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
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(
|
pc->onStateChange(
|
||||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||||
|
|
||||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
cout << "[Gathering State: " << state << "]" << endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
auto dc = pc->createDataChannel("test");
|
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
|
||||||
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
|
|
||||||
|
|
||||||
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) {
|
dc->onMessage([](const variant<binary, string> &message) {
|
||||||
if (holds_alternative<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;
|
bool exit = false;
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
cout << endl
|
cout << endl
|
||||||
|
<< "**********************************************************************************"
|
||||||
|
"*****"
|
||||||
<< endl
|
<< endl
|
||||||
<< "*************************************************************************" << endl
|
|
||||||
<< "* 0: Exit /"
|
<< "* 0: Exit /"
|
||||||
<< " 1: Enter Description /"
|
<< " 1: Enter remote description /"
|
||||||
<< " 2: Enter Candidate /"
|
<< " 2: Enter remote candidate /"
|
||||||
<< " 3: Send Message *" << endl
|
<< " 3: Send message *" << endl
|
||||||
<< " [Command]: ";
|
<< "[Command]: ";
|
||||||
|
|
||||||
int command;
|
int command = -1;
|
||||||
std::string sdp, candidate, message;
|
|
||||||
const char *a;
|
|
||||||
std::unique_ptr<Candidate> candidatePtr;
|
|
||||||
std::unique_ptr<Description> descPtr;
|
|
||||||
cin >> command;
|
cin >> command;
|
||||||
|
cin.ignore();
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 0:
|
case 0: {
|
||||||
exit = true;
|
exit = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 1:
|
case 1: {
|
||||||
// Parse Description
|
// Parse Description
|
||||||
cout << "[SDP]: ";
|
cout << "[Description]: ";
|
||||||
sdp = "";
|
string sdp, line;
|
||||||
while (sdp.length() == 0)
|
while (getline(cin, line) && !line.empty()) {
|
||||||
getline(cin, sdp);
|
sdp += line;
|
||||||
|
sdp += "\r\n";
|
||||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
}
|
||||||
descPtr = std::make_unique<Description>(sdp);
|
pc->setRemoteDescription(sdp);
|
||||||
pc->setRemoteDescription(*descPtr);
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 2:
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
cout << "[Candidate]: ";
|
cout << "[Candidate]: ";
|
||||||
candidate = "";
|
string candidate;
|
||||||
while (candidate.length() == 0)
|
getline(cin, candidate);
|
||||||
getline(cin, candidate);
|
pc->addRemoteCandidate(candidate);
|
||||||
|
|
||||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
|
||||||
pc->addRemoteCandidate(*candidatePtr);
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 3:
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if (!dc->isOpen()) {
|
if (!dc->isOpen()) {
|
||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cout << "[Message]: ";
|
cout << "[Message]: ";
|
||||||
message = "";
|
string message;
|
||||||
while (message.length() == 0)
|
getline(cin, message);
|
||||||
getline(cin, message);
|
|
||||||
dc->send(message);
|
dc->send(message);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default: {
|
||||||
cout << "** Invalid Command ** ";
|
cout << "** Invalid Command ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dc)
|
if (dc)
|
||||||
|
Reference in New Issue
Block a user