├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── cmake └── Modules │ ├── FindGLIB.cmake │ ├── FindLibNice.cmake │ ├── FindSpdlog.cmake │ └── FindUsrSCTP.cmake ├── examples ├── README.md ├── site-api.py ├── static │ ├── adapter.js │ ├── demo.css │ ├── index.html │ └── jquery-3.0.0.min.js └── websocket_client │ ├── CMakeLists.txt │ ├── WebSocketWrapper.cpp │ ├── WebSocketWrapper.hpp │ ├── easywsclient.cpp │ ├── easywsclient.hpp │ ├── frag_bunny.mp4 │ ├── json │ ├── json-forwards.h │ └── json.h │ ├── jsoncpp.cpp │ └── testclient.cpp ├── include └── rtcdcpp │ ├── Chunk.hpp │ ├── ChunkQueue.hpp │ ├── DTLSWrapper.hpp │ ├── DataChannel.hpp │ ├── Logging.hpp │ ├── NiceWrapper.hpp │ ├── PeerConnection.hpp │ ├── RTCCertificate.hpp │ ├── SCTPWrapper.hpp │ └── librtcdcpp.h └── src ├── DTLSWrapper.cpp ├── DataChannel.cpp ├── Logging.cpp ├── NiceWrapper.cpp ├── PeerConnection.cpp ├── RTCCertificate.cpp ├── SCTPWrapper.cpp └── librtcdcpp.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 150 3 | Standard: Cpp11 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /BUILD/ 3 | /examples/.env/ 4 | *.so 5 | spdlog/ 6 | CMakeCache.txt 7 | CMakeFiles/ 8 | Makefile 9 | cmake_install.cmake 10 | testclient 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(librtcdcpp 4 | VERSION 1.0.0 5 | LANGUAGES CXX) 6 | 7 | option(DISABLE_SPDLOG "Disable Spdlog") 8 | 9 | set(CMAKE_CXX_STANDARD 14) 10 | set(CMAKE_MACOSX_RPATH 1) 11 | 12 | # Custom CMake modules 13 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules") 14 | 15 | # Find packages 16 | set(THREADS_PREFER_PTHREAD_FLAG ON) 17 | find_package(Threads REQUIRED) 18 | find_package(OpenSSL REQUIRED) 19 | find_package(LibNice REQUIRED) 20 | find_package(UsrSCTP REQUIRED) 21 | 22 | set(LIB_HEADERS 23 | include/rtcdcpp/Chunk.hpp 24 | include/rtcdcpp/ChunkQueue.hpp 25 | include/rtcdcpp/DataChannel.hpp 26 | include/rtcdcpp/DTLSWrapper.hpp 27 | include/rtcdcpp/Logging.hpp 28 | include/rtcdcpp/NiceWrapper.hpp 29 | include/rtcdcpp/PeerConnection.hpp 30 | include/rtcdcpp/RTCCertificate.hpp 31 | include/rtcdcpp/SCTPWrapper.hpp) 32 | 33 | set(LIB_SOURCES 34 | src/DataChannel.cpp 35 | src/DTLSWrapper.cpp 36 | src/Logging.cpp 37 | src/NiceWrapper.cpp 38 | src/PeerConnection.cpp 39 | src/RTCCertificate.cpp 40 | src/SCTPWrapper.cpp) 41 | 42 | add_library(rtcdcpp SHARED 43 | ${LIB_HEADERS} 44 | ${LIB_SOURCES}) 45 | 46 | if (DISABLE_SPDLOG) 47 | message(STATUS "Spdlog is disabled. Use stubbed out logging") 48 | target_compile_definitions(rtcdcpp PUBLIC -DSPDLOG_DISABLED) 49 | else () 50 | find_package(Spdlog REQUIRED) 51 | target_link_libraries(rtcdcpp PUBLIC Gabime::Spdlog) 52 | endif () 53 | 54 | target_include_directories(rtcdcpp 55 | PUBLIC 56 | ${PROJECT_SOURCE_DIR}/include) 57 | 58 | target_link_libraries(rtcdcpp 59 | PUBLIC 60 | LibNice::LibNice 61 | SctpLab::UsrSCTP 62 | OpenSSL::SSL 63 | Threads::Threads) 64 | 65 | # Declare a namespaced alias for used in other projects 66 | add_library(LibRtcdcpp::LibRtcdcpp ALIAS rtcdcpp) 67 | 68 | # Build examples 69 | add_subdirectory(examples/websocket_client) 70 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | librtcdcpp - A Simple WebRTC DataChannels Library 2 | ================================================= 3 | 4 | librtcdcpp is a simple C++ implementation of the WebRTC DataChannels API. 5 | 6 | It was originally written by [Andrew Gault](https://github.com/abgault) and [Nick Chadwick](https://github.com/chadnickbok), and was inspired in no small part by [librtcdc](https://github.com/xhs/librtcdc) 7 | 8 | Its goal is to be the easiest way to build native WebRTC DataChannels apps across PC/Mac/Linux/iOS/Android. 9 | 10 | Why 11 | --- 12 | 13 | Because building the WebRTC libraries from Chromium can be a real PITA, and slimming it down to just DataChannels can be really tough. 14 | 15 | 16 | Dependencies 17 | ------------ 18 | 19 | - libnice - https://github.com/libnice/libnice 20 | - usrsctp - https://github.com/sctplab/usrsctp 21 | - openssl - https://www.openssl.org/ 22 | - spdlog - https://github.com/gabime/spdlog. Header-only. Optional. 23 | 24 | Building 25 | -------- 26 | 27 | On Linux: 28 | 29 | **TODO**: deb and rpm packages 30 | 31 | ./configure 32 | make 33 | sudo make install 34 | 35 | On Mac: 36 | 37 | **TODO**: homebrew integration 38 | 39 | brew install ... 40 | ./configure 41 | make 42 | sudo make install 43 | 44 | 45 | On Windows: 46 | 47 | **TODO**: Visual studio integration, or a script like that jsoncpp library does 48 | 49 | - We recommend you just copy-paste the cpp and hpp files into your own project and go from there 50 | 51 | 52 | Licensing 53 | --------- 54 | 55 | BSD style - see the accompanying LICENSE file for more information 56 | -------------------------------------------------------------------------------- /cmake/Modules/FindGLIB.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Glib and its components (gio, gobject etc) 2 | # Once done, this will define 3 | # 4 | # GLIB_FOUND - system has Glib 5 | # GLIB_INCLUDE_DIRS - the Glib include directories 6 | # GLIB_LIBRARIES - link these to use Glib 7 | # 8 | # Optionally, the COMPONENTS keyword can be passed to find_package() 9 | # and Glib components can be looked for. Currently, the following 10 | # components can be used, and they define the following variables if 11 | # found: 12 | # 13 | # gio: GLIB_GIO_LIBRARIES 14 | # gobject: GLIB_GOBJECT_LIBRARIES 15 | # gmodule: GLIB_GMODULE_LIBRARIES 16 | # gthread: GLIB_GTHREAD_LIBRARIES 17 | # 18 | # Note that the respective _INCLUDE_DIR variables are not set, since 19 | # all headers are in the same directory as GLIB_INCLUDE_DIRS. 20 | # 21 | # Copyright (C) 2012 Raphael Kubo da Costa 22 | # 23 | # Redistribution and use in source and binary forms, with or without 24 | # modification, are permitted provided that the following conditions 25 | # are met: 26 | # 1. Redistributions of source code must retain the above copyright 27 | # notice, this list of conditions and the following disclaimer. 28 | # 2. Redistributions in binary form must reproduce the above copyright 29 | # notice, this list of conditions and the following disclaimer in the 30 | # documentation and/or other materials provided with the distribution. 31 | # 32 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS 33 | # IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 34 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 35 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS 36 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 37 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 38 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 39 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 40 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 41 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 42 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | find_package(PkgConfig) 45 | pkg_check_modules(PC_GLIB QUIET glib-2.0) 46 | 47 | find_library(GLIB_LIBRARIES 48 | NAMES glib-2.0 49 | HINTS ${PC_GLIB_LIBDIR} 50 | ${PC_GLIB_LIBRARY_DIRS} 51 | ) 52 | 53 | # Files in glib's main include path may include glibconfig.h, which, 54 | # for some odd reason, is normally in $LIBDIR/glib-2.0/include. 55 | get_filename_component(_GLIB_LIBRARY_DIR ${GLIB_LIBRARIES} PATH) 56 | find_path(GLIBCONFIG_INCLUDE_DIR 57 | NAMES glibconfig.h 58 | HINTS ${PC_LIBDIR} ${PC_LIBRARY_DIRS} ${_GLIB_LIBRARY_DIR} 59 | ${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS} 60 | PATH_SUFFIXES glib-2.0/include 61 | ) 62 | 63 | find_path(GLIB_INCLUDE_DIR 64 | NAMES glib.h 65 | HINTS ${PC_GLIB_INCLUDEDIR} 66 | ${PC_GLIB_INCLUDE_DIRS} 67 | PATH_SUFFIXES glib-2.0 68 | ) 69 | 70 | set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR} ${GLIBCONFIG_INCLUDE_DIR}) 71 | 72 | # Version detection 73 | if (EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h") 74 | file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS) 75 | string(REGEX MATCH "#define GLIB_MAJOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") 76 | set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}") 77 | string(REGEX MATCH "#define GLIB_MINOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") 78 | set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}") 79 | string(REGEX MATCH "#define GLIB_MICRO_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") 80 | set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}") 81 | set(GLIB_VERSION "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}") 82 | endif () 83 | 84 | # Additional Glib components. We only look for libraries, as not all of them 85 | # have corresponding headers and all headers are installed alongside the main 86 | # glib ones. 87 | foreach (_component ${GLIB_FIND_COMPONENTS}) 88 | if (${_component} STREQUAL "gio") 89 | find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR}) 90 | set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES) 91 | elseif (${_component} STREQUAL "gobject") 92 | find_library(GLIB_GOBJECT_LIBRARIES NAMES gobject-2.0 HINTS ${_GLIB_LIBRARY_DIR}) 93 | set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GOBJECT_LIBRARIES) 94 | elseif (${_component} STREQUAL "gmodule") 95 | find_library(GLIB_GMODULE_LIBRARIES NAMES gmodule-2.0 HINTS ${_GLIB_LIBRARY_DIR}) 96 | set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GMODULE_LIBRARIES) 97 | elseif (${_component} STREQUAL "gthread") 98 | find_library(GLIB_GTHREAD_LIBRARIES NAMES gthread-2.0 HINTS ${_GLIB_LIBRARY_DIR}) 99 | set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GTHREAD_LIBRARIES) 100 | elseif (${_component} STREQUAL "gio-unix") 101 | # gio-unix is compiled as part of the gio library, but the include paths 102 | # are separate from the shared glib ones. Since this is currently only used 103 | # by WebKitGTK+ we don't go to extraordinary measures beyond pkg-config. 104 | pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0) 105 | endif () 106 | endforeach () 107 | 108 | include(FindPackageHandleStandardArgs) 109 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLIB REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS} 110 | VERSION_VAR GLIB_VERSION) 111 | 112 | mark_as_advanced( 113 | GLIBCONFIG_INCLUDE_DIR 114 | GLIB_GIO_LIBRARIES 115 | GLIB_GIO_UNIX_LIBRARIES 116 | GLIB_GMODULE_LIBRARIES 117 | GLIB_GOBJECT_LIBRARIES 118 | GLIB_GTHREAD_LIBRARIES 119 | GLIB_INCLUDE_DIR 120 | GLIB_INCLUDE_DIRS 121 | GLIB_LIBRARIES 122 | ) 123 | -------------------------------------------------------------------------------- /cmake/Modules/FindLibNice.cmake: -------------------------------------------------------------------------------- 1 | if (NOT TARGET LibNice::LibNice) 2 | find_package(PkgConfig) 3 | pkg_check_modules(PC_LIBNICE nice) 4 | set(LIBNICE_DEFINITIONS ${PC_LIBNICE_CFLAGS_OTHER}) 5 | 6 | find_path(LIBNICE_INCLUDE_DIR nice/agent.h 7 | HINTS ${PC_LIBNICE_INCLUDEDIR} ${PC_LIBNICE_INCLUDE_DIRS} 8 | PATH_SUFFICES libnice) 9 | find_library(LIBNICE_LIBRARY NAMES nice libnice 10 | HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS}) 11 | 12 | include(FindPackageHandleStandardArgs) 13 | find_package_handle_standard_args(Libnice DEFAULT_MSG 14 | LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR) 15 | mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY) 16 | 17 | set(LIBNICE_LIBRARIES ${LIBNICE_LIBRARY}) 18 | set(LIBNICE_INCLUDE_DIRS ${LIBNICE_INCLUDE_DIR}) 19 | 20 | find_package(GLIB REQUIRED COMPONENTS gio gobject gmodule gthread) 21 | 22 | list(APPEND LIBNICE_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS}) 23 | list(APPEND LIBNICE_LIBRARIES ${GLIB_GOBJECT_LIBRARIES} ${GLIB_LIBRARIES}) 24 | 25 | if (LIBNICE_FOUND) 26 | add_library(LibNice::LibNice UNKNOWN IMPORTED) 27 | set_target_properties(LibNice::LibNice PROPERTIES 28 | IMPORTED_LOCATION "${LIBNICE_LIBRARY}" 29 | INTERFACE_COMPILE_DEFINITIONS "_REENTRANT" 30 | INTERFACE_INCLUDE_DIRECTORIES "${LIBNICE_INCLUDE_DIRS}" 31 | INTERFACE_LINK_LIBRARIES "${LIBNICE_LIBRARIES}" 32 | IMPORTED_LINK_INTERFACE_LANGUAGES "C") 33 | endif () 34 | endif () 35 | -------------------------------------------------------------------------------- /cmake/Modules/FindSpdlog.cmake: -------------------------------------------------------------------------------- 1 | if (NOT TARGET Gabime::Spdlog) 2 | include(FindPackageHandleStandardArgs) 3 | find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h) 4 | find_package_handle_standard_args(Spdlog DEFAULT_MSG SPDLOG_INCLUDE_DIR) 5 | add_library(spdlog INTERFACE) 6 | target_include_directories(spdlog INTERFACE ${SPDLOG_INCLUDE_DIR}) 7 | add_library(Gabime::Spdlog ALIAS spdlog) 8 | endif () 9 | -------------------------------------------------------------------------------- /cmake/Modules/FindUsrSCTP.cmake: -------------------------------------------------------------------------------- 1 | # Simple libnice cmake find 2 | 3 | if (NOT TARGET SctpLab::UsrSCTP) 4 | set(USRSCTP_DEFINITIONS INET INET6) 5 | find_path(USRSCTP_INCLUDE_DIR usrsctp.h PATH_SUFFICES usrsctp) 6 | find_library(USRSCTP_LIBRARY NAMES usrsctp libusrsctp) 7 | 8 | include(FindPackageHandleStandardArgs) 9 | find_package_handle_standard_args(Usrsctp DEFAULT_MSG USRSCTP_LIBRARY USRSCTP_INCLUDE_DIR) 10 | 11 | mark_as_advanced(USRSCTP_INCLUDE_DIR USRSCTP_LIBRARY) 12 | 13 | set(USRSCTP_LIBRARIES ${USRSCTP_LIBRARY}) 14 | set(USRSCTP_INCLUDE_DIRS ${USRSCTP_INCLUDE_DIR}) 15 | 16 | if (USRSCTP_FOUND) 17 | add_library(SctpLab::UsrSCTP UNKNOWN IMPORTED) 18 | set_target_properties(SctpLab::UsrSCTP PROPERTIES 19 | IMPORTED_LOCATION "${USRSCTP_LIBRARY}" 20 | INTERFACE_COMPILE_DEFINITIONS "${USRSCTP_DEFINITIONS}" 21 | INTERFACE_INCLUDE_DIRECTORIES "${USRSCTP_INCLUDE_DIRS}" 22 | IMPORTED_LINK_INTERFACE_LANGUAGES "C") 23 | endif () 24 | endif () 25 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Running the Demo 4 | ---------------- 5 | 6 | To run the demo, first run: 7 | 8 | cd examples 9 | python site-api.py 10 | 11 | This should start a web server on localhost:5000 12 | 13 | Open http://localhost:5000 14 | Enter channel name "test" 15 | Click "connect" - This should show up in the python console 16 | 17 | Then, run 18 | 19 | build/examples/websocket_client/testclient - its important that this be started after the web browser has connected to the test channel. 20 | 21 | You should then see a whole heap of ICE messages, followed by a "Hello from native code" 22 | -------------------------------------------------------------------------------- /examples/site-api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Sets up a basic site that can allow two browsers to connect to each 3 | # other via WebRTC DataChannels, sending connection events via WebSockets. 4 | 5 | 6 | from flask import Flask, send_from_directory 7 | from flask_sockets import Sockets 8 | import json 9 | 10 | app = Flask(__name__) 11 | sockets = Sockets(app) 12 | 13 | channels = {} 14 | 15 | @sockets.route('/channel/') 16 | def channel_socket(ws, name): 17 | if name in channels: 18 | channels[name].append(ws) 19 | else: 20 | channels[name] = [ws] 21 | 22 | print("Got new websocket on channel", name) 23 | 24 | ws.send(json.dumps({"type": "hello", "msg": "From the server"})) 25 | 26 | while not ws.closed: 27 | message = ws.receive() 28 | print("Got msg:", message) 29 | 30 | if message is None: 31 | continue 32 | 33 | for other_ws in channels[name]: 34 | if ws is not other_ws: 35 | other_ws.send(message) 36 | 37 | channels[name].remove(ws) 38 | for other_ws in channels[name]: 39 | other_ws.send(json.dumps({"type": "client_disconnected", "msg": {}})) 40 | 41 | 42 | @app.route('/static/') 43 | def send_static(path): 44 | return app.send_from_directory('static', path) 45 | 46 | 47 | @app.route('/') 48 | def serve_site(): 49 | return app.send_static_file("index.html") 50 | 51 | 52 | if __name__ == "__main__": 53 | from gevent import pywsgi 54 | from geventwebsocket.handler import WebSocketHandler 55 | server = pywsgi.WSGIServer(('', 5000), app, handler_class=WebSocketHandler) 56 | server.serve_forever() 57 | -------------------------------------------------------------------------------- /examples/static/demo.css: -------------------------------------------------------------------------------- 1 | 2 | #logs_container { 3 | display: flex; 4 | flex-direction: row; 5 | width: 100%; 6 | } 7 | 8 | 9 | #reliable_logs { 10 | flex: 1; 11 | } 12 | 13 | #datachannel_logs { 14 | flex: 1; 15 | } 16 | -------------------------------------------------------------------------------- /examples/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebRTC DataChannels Demo Site 5 | 6 | 7 | 8 | 239 | 240 | 241 | 242 |

WebRTC DataChannels Demo

243 | 244 |
245 | 246 | 247 | 248 |
249 | 250 |
251 |
252 |

WebSocket Logs

253 |
    254 |
255 |
256 |
257 |

DataChannel Logs

258 | 259 | 260 | 261 |
    262 |
263 |
264 |
265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /examples/websocket_client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(testclient 2 | json/json.h 3 | json/json-forwards.h 4 | easywsclient.cpp 5 | easywsclient.hpp 6 | jsoncpp.cpp 7 | testclient.cpp 8 | WebSocketWrapper.cpp 9 | WebSocketWrapper.hpp) 10 | 11 | target_link_libraries(testclient rtcdcpp) 12 | -------------------------------------------------------------------------------- /examples/websocket_client/WebSocketWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "WebSocketWrapper.hpp" 2 | #include 3 | #include 4 | 5 | using namespace rtcdcpp; 6 | 7 | WebSocketWrapper::WebSocketWrapper(std::string url) : url(url), send_queue() { ; } 8 | 9 | WebSocketWrapper::~WebSocketWrapper() { delete this->ws; } 10 | 11 | bool WebSocketWrapper::Initialize() { 12 | this->ws = WebSocket::from_url(this->url); 13 | return this->ws ? true : false; 14 | } 15 | 16 | void WebSocketWrapper::SetOnMessage(std::function onMessage) { this->onMessage = onMessage; } 17 | 18 | void WebSocketWrapper::Start() { 19 | this->stopping = false; 20 | this->send_loop = std::thread(&WebSocketWrapper::Loop, this); 21 | } 22 | 23 | void WebSocketWrapper::Loop() { 24 | while (!this->stopping) { 25 | this->ws->poll(); 26 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 27 | if (!this->send_queue.empty()) { 28 | ChunkPtr chunk = this->send_queue.wait_and_pop(); 29 | std::string msg(reinterpret_cast(chunk->Data()), chunk->Length()); 30 | this->ws->send(msg); 31 | this->ws->poll(); 32 | } 33 | this->ws->dispatch(this->onMessage); 34 | } 35 | } 36 | 37 | void WebSocketWrapper::Send(std::string msg) { this->send_queue.push(std::shared_ptr(new Chunk((const void*)msg.c_str(), msg.length()))); } 38 | 39 | void WebSocketWrapper::Close() { this->stopping = true; this->send_loop.join(); } 40 | -------------------------------------------------------------------------------- /examples/websocket_client/WebSocketWrapper.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple libwebsockets C++ wrapper 3 | */ 4 | 5 | #include "easywsclient.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | using easywsclient::WebSocket; 21 | 22 | class WebSocketWrapper { 23 | public: 24 | WebSocketWrapper(std::string url); 25 | virtual ~WebSocketWrapper(); 26 | 27 | bool Initialize(); 28 | void Start(); 29 | void Send(std::string); 30 | void Close(); 31 | 32 | void SetOnMessage(std::function); 33 | void SetOnClose(std::function); 34 | void SetOnError(std::function); 35 | 36 | private: 37 | void Loop(); 38 | bool stopping; 39 | WebSocket::pointer ws; 40 | std::string url; 41 | rtcdcpp::ChunkQueue send_queue; 42 | std::function onMessage; 43 | std::thread send_loop; 44 | }; 45 | -------------------------------------------------------------------------------- /examples/websocket_client/easywsclient.cpp: -------------------------------------------------------------------------------- 1 | 2 | #ifdef _WIN32 3 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 4 | #define _CRT_SECURE_NO_WARNINGS // _CRT_SECURE_NO_WARNINGS for sscanf errors in MSVC2013 Express 5 | #endif 6 | #ifndef WIN32_LEAN_AND_MEAN 7 | #define WIN32_LEAN_AND_MEAN 8 | #endif 9 | #include 10 | #include 11 | #include 12 | #pragma comment(lib, "ws2_32") 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #ifndef _SSIZE_T_DEFINED 19 | typedef int ssize_t; 20 | #define _SSIZE_T_DEFINED 21 | #endif 22 | #ifndef _SOCKET_T_DEFINED 23 | typedef SOCKET socket_t; 24 | #define _SOCKET_T_DEFINED 25 | #endif 26 | #ifndef snprintf 27 | #define snprintf _snprintf_s 28 | #endif 29 | #if _MSC_VER >= 1600 30 | // vs2010 or later 31 | #include 32 | #else 33 | typedef __int8 int8_t; 34 | typedef unsigned __int8 uint8_t; 35 | typedef __int32 int32_t; 36 | typedef unsigned __int32 uint32_t; 37 | typedef __int64 int64_t; 38 | typedef unsigned __int64 uint64_t; 39 | #endif 40 | #define socketerrno WSAGetLastError() 41 | #define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS 42 | #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK 43 | #else 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #ifndef _SOCKET_T_DEFINED 56 | typedef int socket_t; 57 | #define _SOCKET_T_DEFINED 58 | #endif 59 | #ifndef INVALID_SOCKET 60 | #define INVALID_SOCKET (-1) 61 | #endif 62 | #ifndef SOCKET_ERROR 63 | #define SOCKET_ERROR (-1) 64 | #endif 65 | #define closesocket(s) ::close(s) 66 | #include 67 | #define socketerrno errno 68 | #define SOCKET_EAGAIN_EINPROGRESS EAGAIN 69 | #define SOCKET_EWOULDBLOCK EWOULDBLOCK 70 | #endif 71 | 72 | #include 73 | #include 74 | 75 | #include "easywsclient.hpp" 76 | 77 | using easywsclient::Callback_Imp; 78 | using easywsclient::BytesCallback_Imp; 79 | 80 | namespace { // private module-only namespace 81 | 82 | socket_t hostname_connect(const std::string& hostname, int port) { 83 | struct addrinfo hints; 84 | struct addrinfo* result; 85 | struct addrinfo* p; 86 | int ret; 87 | socket_t sockfd = INVALID_SOCKET; 88 | char sport[16]; 89 | memset(&hints, 0, sizeof(hints)); 90 | hints.ai_family = AF_UNSPEC; 91 | hints.ai_socktype = SOCK_STREAM; 92 | snprintf(sport, 16, "%d", port); 93 | if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0) { 94 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); 95 | return 1; 96 | } 97 | for (p = result; p != NULL; p = p->ai_next) { 98 | sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); 99 | if (sockfd == INVALID_SOCKET) { 100 | continue; 101 | } 102 | if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) { 103 | break; 104 | } 105 | closesocket(sockfd); 106 | sockfd = INVALID_SOCKET; 107 | } 108 | freeaddrinfo(result); 109 | return sockfd; 110 | } 111 | 112 | class _DummyWebSocket : public easywsclient::WebSocket { 113 | public: 114 | void poll(int timeout) {} 115 | void send(const std::string& message) {} 116 | void sendBinary(const std::string& message) {} 117 | void sendBinary(const std::vector& message) {} 118 | void sendPing() {} 119 | void close() {} 120 | readyStateValues getReadyState() const { return CLOSED; } 121 | void _dispatch(Callback_Imp& callable) {} 122 | void _dispatchBinary(BytesCallback_Imp& callable) {} 123 | }; 124 | 125 | class _RealWebSocket : public easywsclient::WebSocket { 126 | public: 127 | // http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol 128 | // 129 | // 0 1 2 3 130 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 131 | // +-+-+-+-+-------+-+-------------+-------------------------------+ 132 | // |F|R|R|R| opcode|M| Payload len | Extended payload length | 133 | // |I|S|S|S| (4) |A| (7) | (16/64) | 134 | // |N|V|V|V| |S| | (if payload len==126/127) | 135 | // | |1|2|3| |K| | | 136 | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 137 | // | Extended payload length continued, if payload len == 127 | 138 | // + - - - - - - - - - - - - - - - +-------------------------------+ 139 | // | |Masking-key, if MASK set to 1 | 140 | // +-------------------------------+-------------------------------+ 141 | // | Masking-key (continued) | Payload Data | 142 | // +-------------------------------- - - - - - - - - - - - - - - - + 143 | // : Payload Data continued ... : 144 | // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 145 | // | Payload Data continued ... | 146 | // +---------------------------------------------------------------+ 147 | struct wsheader_type { 148 | unsigned header_size; 149 | bool fin; 150 | bool mask; 151 | enum opcode_type { 152 | CONTINUATION = 0x0, 153 | TEXT_FRAME = 0x1, 154 | BINARY_FRAME = 0x2, 155 | CLOSE = 8, 156 | PING = 9, 157 | PONG = 0xa, 158 | } opcode; 159 | int N0; 160 | uint64_t N; 161 | uint8_t masking_key[4]; 162 | }; 163 | 164 | std::vector rxbuf; 165 | std::vector txbuf; 166 | std::vector receivedData; 167 | 168 | socket_t sockfd; 169 | readyStateValues readyState; 170 | bool useMask; 171 | 172 | _RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask) {} 173 | 174 | readyStateValues getReadyState() const { return readyState; } 175 | 176 | void poll(int timeout) { // timeout in milliseconds 177 | if (readyState == CLOSED) { 178 | if (timeout > 0) { 179 | timeval tv = {timeout / 1000, (timeout % 1000) * 1000}; 180 | select(0, NULL, NULL, NULL, &tv); 181 | } 182 | return; 183 | } 184 | if (timeout != 0) { 185 | fd_set rfds; 186 | fd_set wfds; 187 | timeval tv = {timeout / 1000, (timeout % 1000) * 1000}; 188 | FD_ZERO(&rfds); 189 | FD_ZERO(&wfds); 190 | FD_SET(sockfd, &rfds); 191 | if (txbuf.size()) { 192 | FD_SET(sockfd, &wfds); 193 | } 194 | select(sockfd + 1, &rfds, &wfds, 0, timeout > 0 ? &tv : 0); 195 | } 196 | while (true) { 197 | // FD_ISSET(0, &rfds) will be true 198 | int N = rxbuf.size(); 199 | ssize_t ret; 200 | rxbuf.resize(N + 1500); 201 | ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0); 202 | if (false) { 203 | } else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { 204 | rxbuf.resize(N); 205 | break; 206 | } else if (ret <= 0) { 207 | rxbuf.resize(N); 208 | closesocket(sockfd); 209 | readyState = CLOSED; 210 | fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); 211 | break; 212 | } else { 213 | rxbuf.resize(N + ret); 214 | } 215 | } 216 | while (txbuf.size()) { 217 | int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0); 218 | if (false) { 219 | } // ?? 220 | else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { 221 | break; 222 | } else if (ret <= 0) { 223 | closesocket(sockfd); 224 | readyState = CLOSED; 225 | fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); 226 | break; 227 | } else { 228 | txbuf.erase(txbuf.begin(), txbuf.begin() + ret); 229 | } 230 | } 231 | if (!txbuf.size() && readyState == CLOSING) { 232 | closesocket(sockfd); 233 | readyState = CLOSED; 234 | } 235 | } 236 | 237 | // Callable must have signature: void(const std::string & message). 238 | // Should work with C functions, C++ functors, and C++11 std::function and 239 | // lambda: 240 | // template 241 | // void dispatch(Callable callable) 242 | virtual void _dispatch(Callback_Imp& callable) { 243 | struct CallbackAdapter : public BytesCallback_Imp 244 | // Adapt void(const std::string&) to void(const std::string&) 245 | { 246 | Callback_Imp& callable; 247 | CallbackAdapter(Callback_Imp& callable) : callable(callable) {} 248 | void operator()(const std::vector& message) { 249 | std::string stringMessage(message.begin(), message.end()); 250 | callable(stringMessage); 251 | } 252 | }; 253 | CallbackAdapter bytesCallback(callable); 254 | _dispatchBinary(bytesCallback); 255 | } 256 | 257 | virtual void _dispatchBinary(BytesCallback_Imp& callable) { 258 | // TODO: consider acquiring a lock on rxbuf... 259 | while (true) { 260 | wsheader_type ws; 261 | if (rxbuf.size() < 2) { 262 | return; /* Need at least 2 */ 263 | } 264 | const uint8_t* data = (uint8_t*)&rxbuf[0]; // peek, but don't consume 265 | ws.fin = (data[0] & 0x80) == 0x80; 266 | ws.opcode = (wsheader_type::opcode_type)(data[0] & 0x0f); 267 | ws.mask = (data[1] & 0x80) == 0x80; 268 | ws.N0 = (data[1] & 0x7f); 269 | ws.header_size = 2 + (ws.N0 == 126 ? 2 : 0) + (ws.N0 == 127 ? 8 : 0) + (ws.mask ? 4 : 0); 270 | if (rxbuf.size() < ws.header_size) { 271 | return; /* Need: ws.header_size - rxbuf.size() */ 272 | } 273 | int i = 0; 274 | if (ws.N0 < 126) { 275 | ws.N = ws.N0; 276 | i = 2; 277 | } else if (ws.N0 == 126) { 278 | ws.N = 0; 279 | ws.N |= ((uint64_t)data[2]) << 8; 280 | ws.N |= ((uint64_t)data[3]) << 0; 281 | i = 4; 282 | } else if (ws.N0 == 127) { 283 | ws.N = 0; 284 | ws.N |= ((uint64_t)data[2]) << 56; 285 | ws.N |= ((uint64_t)data[3]) << 48; 286 | ws.N |= ((uint64_t)data[4]) << 40; 287 | ws.N |= ((uint64_t)data[5]) << 32; 288 | ws.N |= ((uint64_t)data[6]) << 24; 289 | ws.N |= ((uint64_t)data[7]) << 16; 290 | ws.N |= ((uint64_t)data[8]) << 8; 291 | ws.N |= ((uint64_t)data[9]) << 0; 292 | i = 10; 293 | } 294 | if (ws.mask) { 295 | ws.masking_key[0] = ((uint8_t)data[i + 0]) << 0; 296 | ws.masking_key[1] = ((uint8_t)data[i + 1]) << 0; 297 | ws.masking_key[2] = ((uint8_t)data[i + 2]) << 0; 298 | ws.masking_key[3] = ((uint8_t)data[i + 3]) << 0; 299 | } else { 300 | ws.masking_key[0] = 0; 301 | ws.masking_key[1] = 0; 302 | ws.masking_key[2] = 0; 303 | ws.masking_key[3] = 0; 304 | } 305 | if (rxbuf.size() < ws.header_size + ws.N) { 306 | return; /* Need: ws.header_size+ws.N - rxbuf.size() */ 307 | } 308 | 309 | // We got a whole message, now do something with it: 310 | if (false) { 311 | } else if (ws.opcode == wsheader_type::TEXT_FRAME || ws.opcode == wsheader_type::BINARY_FRAME || ws.opcode == wsheader_type::CONTINUATION) { 312 | if (ws.mask) { 313 | for (size_t i = 0; i != ws.N; ++i) { 314 | rxbuf[i + ws.header_size] ^= ws.masking_key[i & 0x3]; 315 | } 316 | } 317 | receivedData.insert(receivedData.end(), rxbuf.begin() + ws.header_size, rxbuf.begin() + ws.header_size + (size_t)ws.N); // just feed 318 | if (ws.fin) { 319 | callable((const std::vector)receivedData); 320 | receivedData.erase(receivedData.begin(), receivedData.end()); 321 | std::vector().swap(receivedData); // free memory 322 | } 323 | } else if (ws.opcode == wsheader_type::PING) { 324 | if (ws.mask) { 325 | for (size_t i = 0; i != ws.N; ++i) { 326 | rxbuf[i + ws.header_size] ^= ws.masking_key[i & 0x3]; 327 | } 328 | } 329 | std::string data(rxbuf.begin() + ws.header_size, rxbuf.begin() + ws.header_size + (size_t)ws.N); 330 | sendData(wsheader_type::PONG, data.size(), data.begin(), data.end()); 331 | } else if (ws.opcode == wsheader_type::PONG) { 332 | } else if (ws.opcode == wsheader_type::CLOSE) { 333 | close(); 334 | } else { 335 | fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); 336 | close(); 337 | } 338 | 339 | rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size + (size_t)ws.N); 340 | } 341 | } 342 | 343 | void sendPing() { 344 | std::string empty; 345 | sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end()); 346 | } 347 | 348 | void send(const std::string& message) { sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end()); } 349 | 350 | void sendBinary(const std::string& message) { sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); } 351 | 352 | void sendBinary(const std::vector& message) { sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); } 353 | 354 | template 355 | void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) { 356 | // TODO: 357 | // Masking key should (must) be derived from a high quality random 358 | // number generator, to mitigate attacks on non-WebSocket friendly 359 | // middleware: 360 | const uint8_t masking_key[4] = {0x12, 0x34, 0x56, 0x78}; 361 | // TODO: consider acquiring a lock on txbuf... 362 | if (readyState == CLOSING || readyState == CLOSED) { 363 | return; 364 | } 365 | std::vector header; 366 | header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0); 367 | header[0] = 0x80 | type; 368 | if (false) { 369 | } else if (message_size < 126) { 370 | header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0); 371 | if (useMask) { 372 | header[2] = masking_key[0]; 373 | header[3] = masking_key[1]; 374 | header[4] = masking_key[2]; 375 | header[5] = masking_key[3]; 376 | } 377 | } else if (message_size < 65536) { 378 | header[1] = 126 | (useMask ? 0x80 : 0); 379 | header[2] = (message_size >> 8) & 0xff; 380 | header[3] = (message_size >> 0) & 0xff; 381 | if (useMask) { 382 | header[4] = masking_key[0]; 383 | header[5] = masking_key[1]; 384 | header[6] = masking_key[2]; 385 | header[7] = masking_key[3]; 386 | } 387 | } else { // TODO: run coverage testing here 388 | header[1] = 127 | (useMask ? 0x80 : 0); 389 | header[2] = (message_size >> 56) & 0xff; 390 | header[3] = (message_size >> 48) & 0xff; 391 | header[4] = (message_size >> 40) & 0xff; 392 | header[5] = (message_size >> 32) & 0xff; 393 | header[6] = (message_size >> 24) & 0xff; 394 | header[7] = (message_size >> 16) & 0xff; 395 | header[8] = (message_size >> 8) & 0xff; 396 | header[9] = (message_size >> 0) & 0xff; 397 | if (useMask) { 398 | header[10] = masking_key[0]; 399 | header[11] = masking_key[1]; 400 | header[12] = masking_key[2]; 401 | header[13] = masking_key[3]; 402 | } 403 | } 404 | // N.B. - txbuf will keep growing until it can be transmitted over the socket: 405 | txbuf.insert(txbuf.end(), header.begin(), header.end()); 406 | txbuf.insert(txbuf.end(), message_begin, message_end); 407 | if (useMask) { 408 | for (size_t i = 0; i != message_size; ++i) { 409 | *(txbuf.end() - message_size + i) ^= masking_key[i & 0x3]; 410 | } 411 | } 412 | } 413 | 414 | void close() { 415 | if (readyState == CLOSING || readyState == CLOSED) { 416 | return; 417 | } 418 | readyState = CLOSING; 419 | uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key 420 | std::vector header(closeFrame, closeFrame + 6); 421 | txbuf.insert(txbuf.end(), header.begin(), header.end()); 422 | } 423 | }; 424 | 425 | easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) { 426 | char host[128]; 427 | int port; 428 | char path[128]; 429 | if (url.size() >= 128) { 430 | fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str()); 431 | return NULL; 432 | } 433 | if (origin.size() >= 200) { 434 | fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str()); 435 | return NULL; 436 | } 437 | if (false) { 438 | } else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) { 439 | } else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) { 440 | port = 80; 441 | } else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) { 442 | path[0] = '\0'; 443 | } else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) { 444 | port = 80; 445 | path[0] = '\0'; 446 | } else { 447 | fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str()); 448 | return NULL; 449 | } 450 | fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path); 451 | socket_t sockfd = hostname_connect(host, port); 452 | if (sockfd == INVALID_SOCKET) { 453 | fprintf(stderr, "Unable to connect to %s:%d\n", host, port); 454 | return NULL; 455 | } 456 | { 457 | // XXX: this should be done non-blocking, 458 | char line[256]; 459 | int status; 460 | int i; 461 | snprintf(line, 256, "GET /%s HTTP/1.1\r\n", path); 462 | ::send(sockfd, line, strlen(line), 0); 463 | if (port == 80) { 464 | snprintf(line, 256, "Host: %s\r\n", host); 465 | ::send(sockfd, line, strlen(line), 0); 466 | } else { 467 | snprintf(line, 256, "Host: %s:%d\r\n", host, port); 468 | ::send(sockfd, line, strlen(line), 0); 469 | } 470 | snprintf(line, 256, "Upgrade: websocket\r\n"); 471 | ::send(sockfd, line, strlen(line), 0); 472 | snprintf(line, 256, "Connection: Upgrade\r\n"); 473 | ::send(sockfd, line, strlen(line), 0); 474 | if (!origin.empty()) { 475 | snprintf(line, 256, "Origin: %s\r\n", origin.c_str()); 476 | ::send(sockfd, line, strlen(line), 0); 477 | } 478 | snprintf(line, 256, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); 479 | ::send(sockfd, line, strlen(line), 0); 480 | snprintf(line, 256, "Sec-WebSocket-Version: 13\r\n"); 481 | ::send(sockfd, line, strlen(line), 0); 482 | snprintf(line, 256, "\r\n"); 483 | ::send(sockfd, line, strlen(line), 0); 484 | for (i = 0; i < 2 || (i < 255 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i) { 485 | if (recv(sockfd, line + i, 1, 0) == 0) { 486 | return NULL; 487 | } 488 | } 489 | line[i] = 0; 490 | if (i == 255) { 491 | fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); 492 | return NULL; 493 | } 494 | if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { 495 | fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); 496 | return NULL; 497 | } 498 | // TODO: verify response headers, 499 | while (true) { 500 | for (i = 0; i < 2 || (i < 255 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i) { 501 | if (recv(sockfd, line + i, 1, 0) == 0) { 502 | return NULL; 503 | } 504 | } 505 | if (line[0] == '\r' && line[1] == '\n') { 506 | break; 507 | } 508 | } 509 | } 510 | int flag = 1; 511 | setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)); // Disable Nagle's algorithm 512 | #ifdef _WIN32 513 | u_long on = 1; 514 | ioctlsocket(sockfd, FIONBIO, &on); 515 | #else 516 | fcntl(sockfd, F_SETFL, O_NONBLOCK); 517 | #endif 518 | fprintf(stderr, "Connected to: %s\n", url.c_str()); 519 | return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask)); 520 | } 521 | 522 | } // end of module-only namespace 523 | 524 | namespace easywsclient { 525 | 526 | WebSocket::pointer WebSocket::create_dummy() { 527 | static pointer dummy = pointer(new _DummyWebSocket); 528 | return dummy; 529 | } 530 | 531 | WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) { return ::from_url(url, true, origin); } 532 | 533 | WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) { return ::from_url(url, false, origin); } 534 | 535 | } // namespace easywsclient 536 | -------------------------------------------------------------------------------- /examples/websocket_client/easywsclient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD 2 | #define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD 3 | 4 | // This code comes from: 5 | // https://github.com/dhbaird/easywsclient 6 | // 7 | // To get the latest version: 8 | // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp 9 | // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp 10 | 11 | #include 12 | #include 13 | 14 | namespace easywsclient { 15 | 16 | struct Callback_Imp { 17 | virtual void operator()(const std::string& message) = 0; 18 | }; 19 | struct BytesCallback_Imp { 20 | virtual void operator()(const std::vector& message) = 0; 21 | }; 22 | 23 | class WebSocket { 24 | public: 25 | typedef WebSocket* pointer; 26 | typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues; 27 | 28 | // Factories: 29 | static pointer create_dummy(); 30 | static pointer from_url(const std::string& url, const std::string& origin = std::string()); 31 | static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string()); 32 | 33 | // Interfaces: 34 | virtual ~WebSocket() {} 35 | virtual void poll(int timeout = 0) = 0; // timeout in milliseconds 36 | virtual void send(const std::string& message) = 0; 37 | virtual void sendBinary(const std::string& message) = 0; 38 | virtual void sendBinary(const std::vector& message) = 0; 39 | virtual void sendPing() = 0; 40 | virtual void close() = 0; 41 | virtual readyStateValues getReadyState() const = 0; 42 | 43 | template 44 | void dispatch(Callable callable) 45 | // For callbacks that accept a string argument. 46 | { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers 47 | struct _Callback : public Callback_Imp { 48 | Callable& callable; 49 | _Callback(Callable& callable) : callable(callable) {} 50 | void operator()(const std::string& message) { callable(message); } 51 | }; 52 | _Callback callback(callable); 53 | _dispatch(callback); 54 | } 55 | 56 | template 57 | void dispatchBinary(Callable callable) 58 | // For callbacks that accept a std::vector argument. 59 | { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers 60 | struct _Callback : public BytesCallback_Imp { 61 | Callable& callable; 62 | _Callback(Callable& callable) : callable(callable) {} 63 | void operator()(const std::vector& message) { callable(message); } 64 | }; 65 | _Callback callback(callable); 66 | _dispatchBinary(callback); 67 | } 68 | 69 | protected: 70 | virtual void _dispatch(Callback_Imp& callable) = 0; 71 | virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0; 72 | }; 73 | 74 | } // namespace easywsclient 75 | 76 | #endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */ 77 | -------------------------------------------------------------------------------- /examples/websocket_client/frag_bunny.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadnickbok/librtcdcpp/2fe92c38b48b6acf1217a8a812c2408310dec437/examples/websocket_client/frag_bunny.mp4 -------------------------------------------------------------------------------- /examples/websocket_client/json/json-forwards.h: -------------------------------------------------------------------------------- 1 | /// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/). 2 | /// It is intended to be used with #include "json/json-forwards.h" 3 | /// This header provides forward declaration for all JsonCpp types. 4 | 5 | // ////////////////////////////////////////////////////////////////////// 6 | // Beginning of content of file: LICENSE 7 | // ////////////////////////////////////////////////////////////////////// 8 | 9 | /* 10 | The JsonCpp library's source code, including accompanying documentation, 11 | tests and demonstration applications, are licensed under the following 12 | conditions... 13 | 14 | The author (Baptiste Lepilleur) explicitly disclaims copyright in all 15 | jurisdictions which recognize such a disclaimer. In such jurisdictions, 16 | this software is released into the Public Domain. 17 | 18 | In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 19 | 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is 20 | released under the terms of the MIT License (see below). 21 | 22 | In jurisdictions which recognize Public Domain property, the user of this 23 | software may choose to accept it either as 1) Public Domain, 2) under the 24 | conditions of the MIT License (see below), or 3) under the terms of dual 25 | Public Domain/MIT License conditions described here, as they choose. 26 | 27 | The MIT License is about as close to Public Domain as a license can get, and is 28 | described in clear, concise terms at: 29 | 30 | http://en.wikipedia.org/wiki/MIT_License 31 | 32 | The full text of the MIT License follows: 33 | 34 | ======================================================================== 35 | Copyright (c) 2007-2010 Baptiste Lepilleur 36 | 37 | Permission is hereby granted, free of charge, to any person 38 | obtaining a copy of this software and associated documentation 39 | files (the "Software"), to deal in the Software without 40 | restriction, including without limitation the rights to use, copy, 41 | modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 52 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 53 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 54 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | ======================================================================== 57 | (END LICENSE TEXT) 58 | 59 | The MIT license is compatible with both the GPL and commercial 60 | software, affording one all of the rights of Public Domain with the 61 | minor nuisance of being required to keep the above copyright notice 62 | and license text in the source code. Note also that by accepting the 63 | Public Domain "license" you can re-license your copy using whatever 64 | license you like. 65 | 66 | */ 67 | 68 | // ////////////////////////////////////////////////////////////////////// 69 | // End of content of file: LICENSE 70 | // ////////////////////////////////////////////////////////////////////// 71 | 72 | #ifndef JSON_FORWARD_AMALGATED_H_INCLUDED 73 | #define JSON_FORWARD_AMALGATED_H_INCLUDED 74 | /// If defined, indicates that the source file is amalgated 75 | /// to prevent private header inclusion. 76 | #define JSON_IS_AMALGAMATION 77 | 78 | // ////////////////////////////////////////////////////////////////////// 79 | // Beginning of content of file: include/json/config.h 80 | // ////////////////////////////////////////////////////////////////////// 81 | 82 | // Copyright 2007-2010 Baptiste Lepilleur 83 | // Distributed under MIT license, or public domain if desired and 84 | // recognized in your jurisdiction. 85 | // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE 86 | 87 | #ifndef JSON_CONFIG_H_INCLUDED 88 | #define JSON_CONFIG_H_INCLUDED 89 | #include 90 | #include //typdef String 91 | 92 | /// If defined, indicates that json library is embedded in CppTL library. 93 | //# define JSON_IN_CPPTL 1 94 | 95 | /// If defined, indicates that json may leverage CppTL library 96 | //# define JSON_USE_CPPTL 1 97 | /// If defined, indicates that cpptl vector based map should be used instead of 98 | /// std::map 99 | /// as Value container. 100 | //# define JSON_USE_CPPTL_SMALLMAP 1 101 | 102 | // If non-zero, the library uses exceptions to report bad input instead of C 103 | // assertion macros. The default is to use exceptions. 104 | #ifndef JSON_USE_EXCEPTION 105 | #define JSON_USE_EXCEPTION 1 106 | #endif 107 | 108 | /// If defined, indicates that the source file is amalgated 109 | /// to prevent private header inclusion. 110 | /// Remarks: it is automatically defined in the generated amalgated header. 111 | // #define JSON_IS_AMALGAMATION 112 | 113 | #ifdef JSON_IN_CPPTL 114 | #include 115 | #ifndef JSON_USE_CPPTL 116 | #define JSON_USE_CPPTL 1 117 | #endif 118 | #endif 119 | 120 | #ifdef JSON_IN_CPPTL 121 | #define JSON_API CPPTL_API 122 | #elif defined(JSON_DLL_BUILD) 123 | #if defined(_MSC_VER) || defined(__MINGW32__) 124 | #define JSON_API __declspec(dllexport) 125 | #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING 126 | #endif // if defined(_MSC_VER) 127 | #elif defined(JSON_DLL) 128 | #if defined(_MSC_VER) || defined(__MINGW32__) 129 | #define JSON_API __declspec(dllimport) 130 | #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING 131 | #endif // if defined(_MSC_VER) 132 | #endif // ifdef JSON_IN_CPPTL 133 | #if !defined(JSON_API) 134 | #define JSON_API 135 | #endif 136 | 137 | // If JSON_NO_INT64 is defined, then Json only support C++ "int" type for 138 | // integer 139 | // Storages, and 64 bits integer support is disabled. 140 | // #define JSON_NO_INT64 1 141 | 142 | #if defined(_MSC_VER) // MSVC 143 | #if _MSC_VER <= 1200 // MSVC 6 144 | // Microsoft Visual Studio 6 only support conversion from __int64 to double 145 | // (no conversion from unsigned __int64). 146 | #define JSON_USE_INT64_DOUBLE_CONVERSION 1 147 | // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' 148 | // characters in the debug information) 149 | // All projects I've ever seen with VS6 were using this globally (not bothering 150 | // with pragma push/pop). 151 | #pragma warning(disable : 4786) 152 | #endif // MSVC 6 153 | 154 | #if _MSC_VER >= 1500 // MSVC 2008 155 | /// Indicates that the following function is deprecated. 156 | #define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) 157 | #endif 158 | 159 | #endif // defined(_MSC_VER) 160 | 161 | // In c++11 the override keyword allows you to explicity define that a function 162 | // is intended to override the base-class version. This makes the code more 163 | // managable and fixes a set of common hard-to-find bugs. 164 | #if __cplusplus >= 201103L 165 | #define JSONCPP_OVERRIDE override 166 | #elif defined(_MSC_VER) && _MSC_VER > 1600 167 | #define JSONCPP_OVERRIDE override 168 | #else 169 | #define JSONCPP_OVERRIDE 170 | #endif 171 | 172 | #ifndef JSON_HAS_RVALUE_REFERENCES 173 | 174 | #if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 175 | #define JSON_HAS_RVALUE_REFERENCES 1 176 | #endif // MSVC >= 2010 177 | 178 | #ifdef __clang__ 179 | #if __has_feature(cxx_rvalue_references) 180 | #define JSON_HAS_RVALUE_REFERENCES 1 181 | #endif // has_feature 182 | 183 | #elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) 184 | #if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) 185 | #define JSON_HAS_RVALUE_REFERENCES 1 186 | #endif // GXX_EXPERIMENTAL 187 | 188 | #endif // __clang__ || __GNUC__ 189 | 190 | #endif // not defined JSON_HAS_RVALUE_REFERENCES 191 | 192 | #ifndef JSON_HAS_RVALUE_REFERENCES 193 | #define JSON_HAS_RVALUE_REFERENCES 0 194 | #endif 195 | 196 | #ifdef __clang__ 197 | #elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) 198 | #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) 199 | #define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) 200 | #elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) 201 | #define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) 202 | #endif // GNUC version 203 | #endif // __clang__ || __GNUC__ 204 | 205 | #if !defined(JSONCPP_DEPRECATED) 206 | #define JSONCPP_DEPRECATED(message) 207 | #endif // if !defined(JSONCPP_DEPRECATED) 208 | 209 | #if __GNUC__ >= 6 210 | #define JSON_USE_INT64_DOUBLE_CONVERSION 1 211 | #endif 212 | 213 | #if !defined(JSON_IS_AMALGAMATION) 214 | 215 | #include "version.h" 216 | 217 | #if JSONCPP_USING_SECURE_MEMORY 218 | #include "allocator.h" //typedef Allocator 219 | #endif 220 | 221 | #endif // if !defined(JSON_IS_AMALGAMATION) 222 | 223 | namespace Json { 224 | typedef int Int; 225 | typedef unsigned int UInt; 226 | #if defined(JSON_NO_INT64) 227 | typedef int LargestInt; 228 | typedef unsigned int LargestUInt; 229 | #undef JSON_HAS_INT64 230 | #else // if defined(JSON_NO_INT64) 231 | // For Microsoft Visual use specific types as long long is not supported 232 | #if defined(_MSC_VER) // Microsoft Visual Studio 233 | typedef __int64 Int64; 234 | typedef unsigned __int64 UInt64; 235 | #else // if defined(_MSC_VER) // Other platforms, use long long 236 | typedef long long int Int64; 237 | typedef unsigned long long int UInt64; 238 | #endif // if defined(_MSC_VER) 239 | typedef Int64 LargestInt; 240 | typedef UInt64 LargestUInt; 241 | #define JSON_HAS_INT64 242 | #endif // if defined(JSON_NO_INT64) 243 | #if JSONCPP_USING_SECURE_MEMORY 244 | #define JSONCPP_STRING std::basic_string, Json::SecureAllocator> 245 | #define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator> 246 | #define JSONCPP_OSTREAM std::basic_ostream> 247 | #define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator> 248 | #define JSONCPP_ISTREAM std::istream 249 | #else 250 | #define JSONCPP_STRING std::string 251 | #define JSONCPP_OSTRINGSTREAM std::ostringstream 252 | #define JSONCPP_OSTREAM std::ostream 253 | #define JSONCPP_ISTRINGSTREAM std::istringstream 254 | #define JSONCPP_ISTREAM std::istream 255 | #endif // if JSONCPP_USING_SECURE_MEMORY 256 | } // end namespace Json 257 | 258 | #endif // JSON_CONFIG_H_INCLUDED 259 | 260 | // ////////////////////////////////////////////////////////////////////// 261 | // End of content of file: include/json/config.h 262 | // ////////////////////////////////////////////////////////////////////// 263 | 264 | // ////////////////////////////////////////////////////////////////////// 265 | // Beginning of content of file: include/json/forwards.h 266 | // ////////////////////////////////////////////////////////////////////// 267 | 268 | // Copyright 2007-2010 Baptiste Lepilleur 269 | // Distributed under MIT license, or public domain if desired and 270 | // recognized in your jurisdiction. 271 | // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE 272 | 273 | #ifndef JSON_FORWARDS_H_INCLUDED 274 | #define JSON_FORWARDS_H_INCLUDED 275 | 276 | #if !defined(JSON_IS_AMALGAMATION) 277 | #include "config.h" 278 | #endif // if !defined(JSON_IS_AMALGAMATION) 279 | 280 | namespace Json { 281 | 282 | // writer.h 283 | class FastWriter; 284 | class StyledWriter; 285 | 286 | // reader.h 287 | class Reader; 288 | 289 | // features.h 290 | class Features; 291 | 292 | // value.h 293 | typedef unsigned int ArrayIndex; 294 | class StaticString; 295 | class Path; 296 | class PathArgument; 297 | class Value; 298 | class ValueIteratorBase; 299 | class ValueIterator; 300 | class ValueConstIterator; 301 | 302 | } // namespace Json 303 | 304 | #endif // JSON_FORWARDS_H_INCLUDED 305 | 306 | // ////////////////////////////////////////////////////////////////////// 307 | // End of content of file: include/json/forwards.h 308 | // ////////////////////////////////////////////////////////////////////// 309 | 310 | #endif // ifndef JSON_FORWARD_AMALGATED_H_INCLUDED 311 | -------------------------------------------------------------------------------- /examples/websocket_client/testclient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple WebRTC test client. 3 | */ 4 | 5 | #include "WebSocketWrapper.hpp" 6 | #include "json/json.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace rtcdcpp; 16 | 17 | void send_loop(std::shared_ptr dc) { 18 | std::ifstream bunnyFile; 19 | bunnyFile.open("frag_bunny.mp4", std::ios_base::in | std::ios_base::binary); 20 | 21 | char buf[100 * 1024]; 22 | 23 | while (bunnyFile.good()) { 24 | bunnyFile.read(buf, 100 * 1024); 25 | int nRead = bunnyFile.gcount(); 26 | if (nRead > 0) { 27 | dc->SendBinary((const uint8_t *)buf, nRead); 28 | std::this_thread::sleep_for(std::chrono::seconds(1)); 29 | } 30 | 31 | std::cout << "Sent message of size " << std::to_string(nRead) << std::endl; 32 | } 33 | } 34 | 35 | int main() { 36 | #ifndef SPDLOG_DISABLED 37 | auto console_sink = std::make_shared(); 38 | spdlog::logger("rtcdcpp.PeerConnection", console_sink); 39 | spdlog::logger("rtcdcpp.SCTP", console_sink); 40 | spdlog::logger("rtcdcpp.Nice", console_sink); 41 | spdlog::logger("rtcdcpp.DTLS", console_sink); 42 | spdlog::set_level(spdlog::level::debug); 43 | #endif 44 | 45 | WebSocketWrapper ws("ws://localhost:5000/channel/test"); 46 | std::shared_ptr pc; 47 | std::shared_ptr dc; 48 | 49 | if (!ws.Initialize()) { 50 | std::cout << "WebSocket connection failed\n"; 51 | return 0; 52 | } 53 | 54 | RTCConfiguration config; 55 | config.ice_servers.emplace_back(RTCIceServer{"stun3.l.google.com", 19302}); 56 | 57 | bool running = true; 58 | 59 | ChunkQueue messages; 60 | 61 | std::function onMessage = [&messages](std::string msg) { 62 | messages.push(std::shared_ptr(new Chunk((const void *)msg.c_str(), msg.length()))); 63 | }; 64 | 65 | std::function onLocalIceCandidate = [&ws](PeerConnection::IceCandidate candidate) { 66 | Json::Value jsonCandidate; 67 | jsonCandidate["type"] = "candidate"; 68 | jsonCandidate["msg"]["candidate"] = candidate.candidate; 69 | jsonCandidate["msg"]["sdpMid"] = candidate.sdpMid; 70 | jsonCandidate["msg"]["sdpMLineIndex"] = candidate.sdpMLineIndex; 71 | 72 | Json::StreamWriterBuilder wBuilder; 73 | ws.Send(Json::writeString(wBuilder, jsonCandidate)); 74 | }; 75 | 76 | std::function channel)> onDataChannel = [&dc](std::shared_ptr channel) { 77 | std::cout << "Hey cool, got a data channel\n"; 78 | dc = channel; 79 | std::thread send_thread = std::thread(send_loop, channel); 80 | send_thread.detach(); 81 | }; 82 | 83 | ws.SetOnMessage(onMessage); 84 | ws.Start(); 85 | ws.Send("{\"type\": \"client_connected\", \"msg\": {}}"); 86 | 87 | Json::Reader reader; 88 | Json::StreamWriterBuilder msgBuilder; 89 | 90 | while (running) { 91 | ChunkPtr cur_msg = messages.wait_and_pop(); 92 | std::string msg((const char *)cur_msg->Data(), cur_msg->Length()); 93 | std::cout << msg << "\n"; 94 | Json::Value root; 95 | if (reader.parse(msg, root)) { 96 | std::cout << "Got msg of type: " << root["type"] << "\n"; 97 | if (root["type"] == "offer") { 98 | std::cout << "Time to get the rtc party started\n"; 99 | pc = std::make_shared(config, onLocalIceCandidate, onDataChannel); 100 | 101 | pc->ParseOffer(root["msg"]["sdp"].asString()); 102 | Json::Value answer; 103 | answer["type"] = "answer"; 104 | answer["msg"]["sdp"] = pc->GenerateAnswer(); 105 | answer["msg"]["type"] = "answer"; 106 | 107 | std::cout << "Sending Answer: " << answer << "\n"; 108 | ws.Send(Json::writeString(msgBuilder, answer)); 109 | } else if (root["type"] == "candidate") { 110 | pc->SetRemoteIceCandidate("a=" + root["msg"]["candidate"].asString()); 111 | } 112 | } else { 113 | std::cout << "Json parse failed" 114 | << "\n"; 115 | } 116 | } 117 | 118 | ws.Close(); 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /include/rtcdcpp/Chunk.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | namespace rtcdcpp { 37 | 38 | // Utility class for passing messages around 39 | class Chunk { 40 | private: 41 | size_t len{0}; 42 | uint8_t *data{nullptr}; 43 | 44 | public: 45 | // TODO memory pool? 46 | // XXX should we just use a vector? 47 | 48 | // Makes a copy of data 49 | Chunk(const void *dataToCopy, size_t dataLen) : len(dataLen), data(new uint8_t[len]) { memcpy(data, dataToCopy, dataLen); } 50 | 51 | // Copy constructor 52 | Chunk(const Chunk &other) : len(other.len), data(new uint8_t[len]) { memcpy(data, other.data, other.len); } 53 | 54 | // Assignment operator 55 | Chunk &operator=(const Chunk &other) { 56 | if (data) { 57 | len = 0; 58 | delete[] data; 59 | } 60 | len = other.len; 61 | data = new uint8_t[len]; 62 | memcpy(data, other.data, other.len); 63 | return *this; 64 | } 65 | 66 | ~Chunk() { delete[] data; } 67 | 68 | size_t Size() const { return len; } 69 | size_t Length() const { return Size(); } 70 | uint8_t *Data() const { return data; } 71 | }; 72 | 73 | using ChunkPtr = std::shared_ptr; 74 | } 75 | -------------------------------------------------------------------------------- /include/rtcdcpp/ChunkQueue.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Simple blocking thread-safe queue. 30 | */ 31 | 32 | #pragma once 33 | 34 | #include "Chunk.hpp" 35 | 36 | #include 37 | #include 38 | 39 | namespace rtcdcpp { 40 | 41 | /** 42 | * Thread-Safe Queue of DataChunks 43 | */ 44 | class ChunkQueue { 45 | private: 46 | mutable std::mutex mut; 47 | std::queue chunk_queue; 48 | std::condition_variable data_cond; 49 | bool stopping; 50 | 51 | public: 52 | ChunkQueue() : chunk_queue(), stopping(false) {} 53 | 54 | void Stop() { 55 | std::lock_guard lock(mut); 56 | stopping = true; 57 | data_cond.notify_all(); 58 | } 59 | 60 | void push(ChunkPtr chunk) { 61 | std::lock_guard lock(mut); 62 | if (stopping) { 63 | return; 64 | } 65 | chunk_queue.push(chunk); 66 | data_cond.notify_one(); 67 | } 68 | 69 | ChunkPtr wait_and_pop() { 70 | std::unique_lock lock(mut); 71 | while (!stopping && chunk_queue.empty()) { 72 | data_cond.wait(lock); 73 | } 74 | 75 | if (stopping) { 76 | return ChunkPtr(); 77 | } 78 | 79 | ChunkPtr res = chunk_queue.front(); 80 | chunk_queue.pop(); 81 | return res; 82 | } 83 | 84 | bool empty() const { 85 | std::lock_guard lock(mut); 86 | return chunk_queue.empty(); 87 | } 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /include/rtcdcpp/DTLSWrapper.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | /** 31 | * Wrapper around OpenSSL DTLS. 32 | */ 33 | 34 | #include "ChunkQueue.hpp" 35 | #include "PeerConnection.hpp" 36 | #include "Logging.hpp" 37 | 38 | #include 39 | 40 | #include 41 | 42 | namespace rtcdcpp { 43 | 44 | class DTLSWrapper { 45 | public: 46 | DTLSWrapper(PeerConnection *peer_connection); 47 | virtual ~DTLSWrapper(); 48 | 49 | const RTCCertificate *certificate() { return certificate_; } 50 | 51 | bool Initialize(); 52 | void Start(); 53 | void Stop(); 54 | 55 | void EncryptData(ChunkPtr chunk); 56 | void DecryptData(ChunkPtr chunk); 57 | 58 | void SetEncryptedCallback(std::function); 59 | void SetDecryptedCallback(std::function); 60 | 61 | private: 62 | PeerConnection *peer_connection; 63 | const RTCCertificate *certificate_; 64 | 65 | std::atomic should_stop; 66 | 67 | ChunkQueue encrypt_queue; 68 | ChunkQueue decrypt_queue; 69 | 70 | std::thread encrypt_thread; 71 | std::thread decrypt_thread; 72 | 73 | void RunEncrypt(); 74 | void RunDecrypt(); 75 | 76 | // SSL Context 77 | std::mutex ssl_mutex; 78 | SSL_CTX *ctx; 79 | SSL *ssl; 80 | BIO *in_bio, *out_bio; 81 | 82 | bool handshake_complete; 83 | 84 | std::function decrypted_callback; 85 | std::function encrypted_callback; 86 | 87 | std::shared_ptr logger = GetLogger("rtcdcpp.DTLS"); 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /include/rtcdcpp/DataChannel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * WebRTC DataChannel. 30 | */ 31 | 32 | #pragma once 33 | 34 | #include "Chunk.hpp" 35 | #include 36 | #include 37 | 38 | namespace rtcdcpp { 39 | 40 | // SCTP PPID Types 41 | #define PPID_CONTROL 50 42 | #define PPID_STRING 51 43 | #define PPID_BINARY 53 44 | #define PPID_STRING_EMPTY 56 45 | #define PPID_BINARY_EMPTY 57 46 | 47 | // DataChannel Control Types 48 | #define DC_TYPE_OPEN 0x03 49 | #define DC_TYPE_ACK 0x02 50 | 51 | // Channel types 52 | #define DATA_CHANNEL_RELIABLE 0x00 53 | #define DATA_CHANNEL_RELIABLE_UNORDERED 0x80 54 | #define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT 0x01 55 | #define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED 0x81 56 | #define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED 0x02 57 | #define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED 0x82 58 | 59 | typedef struct { 60 | uint8_t msg_type; 61 | uint8_t chan_type; 62 | uint16_t priority; 63 | uint32_t reliability; 64 | uint16_t label_len; 65 | uint16_t protocol_len; 66 | char *label; 67 | char *protocol; 68 | } dc_open_msg; 69 | 70 | typedef struct { uint8_t msg_type; } dc_open_ack; 71 | 72 | class PeerConnection; 73 | 74 | class DataChannel { 75 | friend class PeerConnection; 76 | 77 | private: 78 | PeerConnection *pc; 79 | uint16_t stream_id; 80 | uint8_t chan_type; 81 | std::string label; 82 | std::string protocol; 83 | 84 | // TODO: Priority field 85 | 86 | std::function open_cb; 87 | std::function str_msg_cb; 88 | // std::function data, int len)> bin_msg_cb; 89 | std::function bin_msg_cb; 90 | std::function closed_cb; 91 | std::function error_cb; 92 | 93 | void OnOpen(); 94 | void OnStringMsg(std::string msg); 95 | void OnBinaryMsg(ChunkPtr msg); 96 | void OnClosed(); 97 | void OnError(std::string description); 98 | 99 | public: 100 | DataChannel(PeerConnection *pc, uint16_t stream_id, uint8_t chan_type, std::string label, std::string protocol); 101 | virtual ~DataChannel(); 102 | 103 | /** 104 | * Get the Stream ID for the DataChannel. 105 | * XXX: Stream IDs *are* unique. 106 | */ 107 | uint16_t GetStreamID(); 108 | 109 | /** 110 | * Get the channel type. 111 | */ 112 | uint8_t GetChannelType(); 113 | 114 | /** 115 | * Get the label for the DataChannel. 116 | * XXX: Labels are *not* unique. 117 | */ 118 | std::string GetLabel(); 119 | 120 | /** 121 | * Get the protocol for the DataChannel. 122 | */ 123 | std::string GetProtocol(); 124 | 125 | /** 126 | * Cleanly close the DataChannel. 127 | */ 128 | void Close(); 129 | 130 | /** 131 | * Send calls return false if the DataChannel is no longer operational, 132 | * ie. an error or close event has been detected. 133 | */ 134 | bool SendString(std::string msg); 135 | bool SendBinary(const uint8_t *msg, int len); 136 | 137 | // Callbacks 138 | 139 | /** 140 | * Called when the remote peer 'acks' our data channel 141 | * This is only called when we were the peer who created the data channel. 142 | * Receiving this message means its 'safe' to send messages, but messages 143 | * can be sent before this is received (its just unknown if they'll arrive). 144 | */ 145 | void SetOnOpen(std::function open_cb); 146 | 147 | /** 148 | * Called when we receive a string. 149 | */ 150 | void SetOnStringMsgCallback(std::function recv_str_cb); 151 | 152 | /** 153 | * Called when we receive a binary blob. 154 | */ 155 | void SetOnBinaryMsgCallback(std::function msg_binary_cb); 156 | 157 | /** 158 | * Called when the DataChannel has been cleanly closed. 159 | * NOT called after the Close() method has been called 160 | * NOT called after an error has been received. 161 | */ 162 | void SetOnClosedCallback(std::function close_cb); 163 | 164 | /** 165 | * Called when there has been an error in the underlying transport and the 166 | * data channel is no longer valid. 167 | */ 168 | void SetOnErrorCallback(std::function error_cb); 169 | }; 170 | } 171 | -------------------------------------------------------------------------------- /include/rtcdcpp/Logging.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | 32 | #ifndef SPDLOG_DISABLED 33 | #include 34 | #include "spdlog/sinks/stdout_color_sinks.h" 35 | #include 36 | #endif 37 | 38 | namespace rtcdcpp { 39 | 40 | #ifndef SPDLOG_DISABLED 41 | 42 | typedef spdlog::logger Logger; 43 | 44 | #else 45 | 46 | class Logger { 47 | public: 48 | 49 | Logger() = default; 50 | 51 | Logger(const Logger &) = delete; 52 | void operator=(const Logger &) = delete; 53 | Logger(Logger &&) = delete; 54 | void operator=(Logger &&) = delete; 55 | 56 | template 57 | void trace(const char *fmt, const Args &... args) {} 58 | template 59 | void debug(const char *fmt, const Args &... args) {} 60 | template 61 | void info(const char *fmt, const Args &... args) {} 62 | template 63 | void warn(const char *fmt, const Args &... args) {} 64 | template 65 | void error(const char *fmt, const Args &... args) {} 66 | template 67 | void critical(const char *fmt, const Args &... args) {} 68 | 69 | template 70 | void trace(const T &) {} 71 | template 72 | void debug(const T &) {} 73 | template 74 | void info(const T &) {} 75 | template 76 | void warn(const T &) {} 77 | template 78 | void error(const T &) {} 79 | template 80 | void critical(const T &) {} 81 | }; 82 | 83 | #define SPDLOG_TRACE(logger, ...) 84 | #define SPDLOG_DEBUG(logger, ...) 85 | 86 | #endif 87 | 88 | std::shared_ptr GetLogger(const std::string &logger_name); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /include/rtcdcpp/NiceWrapper.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | /** 31 | * Wrapper around libnice and NiceAgent. 32 | */ 33 | 34 | #include "ChunkQueue.hpp" 35 | #include "PeerConnection.hpp" 36 | #include "Logging.hpp" 37 | 38 | #include 39 | 40 | extern "C" { 41 | #include 42 | } 43 | 44 | namespace rtcdcpp { 45 | 46 | /** 47 | * Nice Wrapper broh. 48 | */ 49 | class NiceWrapper { 50 | public: 51 | // TODO: Remove reference to handler 52 | // TODO: Add callback for candidates 53 | NiceWrapper(PeerConnection *peer_connection); 54 | virtual ~NiceWrapper(); 55 | 56 | // Setup libnice 57 | bool Initialize(); 58 | 59 | // Start sending packets XXX: recv just happens once candidates are set 60 | void StartSendLoop(); 61 | 62 | // Shutdown nice and stop the send thread 63 | void Stop(); 64 | 65 | // Parse the remote SDP 66 | void ParseRemoteSDP(std::string remote_sdp); 67 | 68 | // void SetRemoteCredentials(std::string username, std::string password); 69 | 70 | // Generate the local SDP 71 | std::string GenerateLocalSDP(); 72 | 73 | // Add a single remote ice candidate (supports trickling) 74 | bool SetRemoteIceCandidate(std::string candidate_sdp); 75 | 76 | // Set the remote ice candidates 77 | bool SetRemoteIceCandidates(std::vector candidate_sdps); 78 | 79 | // Callback to call when we receive local ice candidates 80 | // void SetLocalCandidatesCallback(std::vector candidate_sdps); 81 | 82 | // Callback to call when we receive remote data 83 | void SetDataReceivedCallback(std::function); 84 | 85 | // Send data over the nice channel 86 | void SendData(ChunkPtr chunk); 87 | 88 | private: 89 | PeerConnection *peer_connection; 90 | int packets_sent; 91 | 92 | std::unique_ptr agent; 93 | std::unique_ptr loop; 94 | uint32_t stream_id; 95 | std::mutex send_lock; 96 | 97 | bool gathering_done; 98 | bool negotiation_done; 99 | 100 | ChunkQueue send_queue; 101 | 102 | std::function data_received_callback; 103 | 104 | // Send data thread 105 | void SendLoop(); 106 | std::thread send_thread; 107 | std::thread g_main_loop_thread; 108 | std::atomic should_stop; 109 | 110 | // Callback methods 111 | void OnStateChange(uint32_t stream_id, uint32_t component_id, uint32_t state); 112 | void OnGatheringDone(); 113 | void OnCandidate(std::string candidate); 114 | void OnSelectedPair(); 115 | void OnDataReceived(const uint8_t *buf, int len); 116 | void OnIceReady(); 117 | void LogMessage(const gchar *message); 118 | 119 | // Helper functions 120 | friend void candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer user_data); 121 | friend void component_state_changed(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer user_data); 122 | friend void new_local_candidate(NiceAgent *agent, NiceCandidate *candidate, gpointer user_data); 123 | friend void new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id, NiceCandidate *lcandidate, NiceCandidate *rcandidate, 124 | gpointer user_data); 125 | friend void data_received(NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data); 126 | friend void nice_log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); 127 | 128 | std::shared_ptr logger = GetLogger("rtcdcpp.Nice"); 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /include/rtcdcpp/PeerConnection.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "ChunkQueue.hpp" 31 | #include "DataChannel.hpp" 32 | #include "RTCCertificate.hpp" 33 | #include "Logging.hpp" 34 | #include 35 | #include 36 | 37 | namespace rtcdcpp { 38 | 39 | class NiceWrapper; 40 | class DTLSWrapper; 41 | class SCTPWrapper; 42 | 43 | struct RTCIceServer { 44 | std::string hostname; 45 | int port; 46 | }; 47 | 48 | std::ostream &operator<<(std::ostream &os, const RTCIceServer &ice_server); 49 | 50 | struct RTCConfiguration { 51 | std::vector ice_servers; 52 | std::pair ice_port_range; 53 | std::string ice_ufrag; 54 | std::string ice_pwd; 55 | std::vector certificates; 56 | }; 57 | 58 | class PeerConnection { 59 | public: 60 | struct IceCandidate { 61 | IceCandidate(const std::string &candidate, const std::string &sdpMid, int sdpMLineIndex) 62 | : candidate(candidate), sdpMid(sdpMid), sdpMLineIndex(sdpMLineIndex) {} 63 | std::string candidate; 64 | std::string sdpMid; 65 | int sdpMLineIndex; 66 | }; 67 | 68 | using IceCandidateCallbackPtr = std::function; 69 | using DataChannelCallbackPtr = std::function channel)>; 70 | 71 | PeerConnection(const RTCConfiguration &config, IceCandidateCallbackPtr icCB, DataChannelCallbackPtr dcCB); 72 | 73 | virtual ~PeerConnection(); 74 | 75 | const RTCConfiguration &config() { return config_; } 76 | 77 | /** 78 | * 79 | * Parse Offer SDP 80 | */ 81 | void ParseOffer(std::string offer_sdp); 82 | 83 | /** 84 | * Generate Answer SDP 85 | */ 86 | std::string GenerateAnswer(); 87 | 88 | /** 89 | * Handle remote ICE Candidate. 90 | * Supports trickle ice candidates. 91 | */ 92 | bool SetRemoteIceCandidate(std::string candidate_sdp); 93 | 94 | /** 95 | * Handle remote ICE Candidates. 96 | * TODO: Handle trickle ice candidates. 97 | */ 98 | bool SetRemoteIceCandidates(std::vector candidate_sdps); 99 | 100 | /** 101 | * Create a new data channel with the given label. 102 | * Only callable once RTCConnectedCallback has been called. 103 | * TODO: Handle creating data channels before generating SDP, so that the 104 | * data channel is created as part of the connection process. 105 | */ 106 | // std::shared_ptr CreateDataChannel(std::string label); 107 | 108 | /** 109 | * Notify when remote party creates a DataChannel. 110 | * XXX: This is *not* a callback saying that a call to CreateDataChannel 111 | * has succeeded. This is a call saying the remote party wants to 112 | * create a new data channel. 113 | */ 114 | // void SetDataChannelCreatedCallback(DataChannelCallbackPtr cb); 115 | 116 | // TODO: Error callbacks 117 | 118 | void SendStrMsg(std::string msg, uint16_t sid); 119 | void SendBinaryMsg(const uint8_t *data, int len, uint16_t sid); 120 | 121 | /* Internal Callback Handlers */ 122 | void OnLocalIceCandidate(std::string &ice_candidate); 123 | void OnIceReady(); 124 | void OnDTLSHandshakeDone(); 125 | void OnSCTPMsgReceived(ChunkPtr chunk, uint16_t sid, uint32_t ppid); 126 | 127 | private: 128 | RTCConfiguration config_; 129 | const IceCandidateCallbackPtr ice_candidate_cb; 130 | const DataChannelCallbackPtr new_channel_cb; 131 | 132 | std::string mid; 133 | 134 | enum Role { Client, Server } role = Client; 135 | 136 | std::atomic iceReady{false}; 137 | std::unique_ptr nice; 138 | std::unique_ptr dtls; 139 | std::unique_ptr sctp; 140 | 141 | std::map> data_channels; 142 | std::shared_ptr GetChannel(uint16_t sid); 143 | 144 | /** 145 | * Constructor helper 146 | * Initialize the RTC connection. 147 | * Allocates all internal structures and configs, and starts ICE gathering. 148 | */ 149 | bool Initialize(); 150 | 151 | // DataChannel message parsing 152 | void HandleNewDataChannel(ChunkPtr chunk, uint16_t sid); 153 | void HandleStringMessage(ChunkPtr chunk, uint16_t sid); 154 | void HandleBinaryMessage(ChunkPtr chunk, uint16_t sid); 155 | 156 | std::shared_ptr logger = GetLogger("rtcdcpp.PeerConnection"); 157 | 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /include/rtcdcpp/RTCCertificate.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | /** 31 | * Wrapper around OpenSSL Certs. 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | namespace rtcdcpp { 40 | 41 | #define SHA256_FINGERPRINT_SIZE (95 + 1) 42 | 43 | class RTCCertificate { 44 | public: 45 | static RTCCertificate GenerateCertificate(std::string common_name, int days); 46 | 47 | RTCCertificate(std::string cert_pem, std::string pkey_pem); 48 | 49 | const std::string &fingerprint() const { return fingerprint_; } 50 | 51 | protected: 52 | friend class DTLSWrapper; 53 | 54 | X509 *x509() const { return x509_.get(); } 55 | EVP_PKEY *evp_pkey() const { return evp_pkey_.get(); } 56 | 57 | private: 58 | RTCCertificate(std::shared_ptr x509, std::shared_ptr evp_pkey); 59 | 60 | std::shared_ptr x509_; 61 | std::shared_ptr evp_pkey_; 62 | std::string fingerprint_; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /include/rtcdcpp/SCTPWrapper.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | /** 31 | * Wrapper around usrsctp. 32 | */ 33 | 34 | #include "ChunkQueue.hpp" 35 | #include "PeerConnection.hpp" 36 | 37 | #include 38 | 39 | #include 40 | 41 | namespace rtcdcpp { 42 | 43 | #define MAX_OUT_STREAM 256 44 | #define MAX_IN_STREAM 256 45 | 46 | class SCTPWrapper { 47 | public: 48 | using MsgReceivedCallbackPtr = std::function; 49 | using DTLSEncryptCallbackPtr = std::function; 50 | 51 | SCTPWrapper(DTLSEncryptCallbackPtr dtlsEncryptCB, MsgReceivedCallbackPtr msgReceivedCB); 52 | virtual ~SCTPWrapper(); 53 | 54 | bool Initialize(); 55 | void Start(); 56 | void Stop(); 57 | // int GetStreamCursor(); 58 | // void SetStreamCursor(int i); 59 | 60 | // Handle a decrypted SCTP packet 61 | void DTLSForSCTP(ChunkPtr chunk); 62 | 63 | // Send a message to the remote connection 64 | // Note, this will cause 1+ DTLSEncrypt callback calls 65 | void GSForSCTP(ChunkPtr chunk, uint16_t sid, uint32_t ppid); 66 | 67 | private: 68 | // PeerConnection *peer_connection; 69 | bool started{false}; 70 | struct socket *sock; 71 | uint16_t local_port; 72 | uint16_t remote_port; 73 | int stream_cursor; 74 | 75 | bool connectSentData{false}; 76 | std::mutex connectMtx; 77 | std::condition_variable connectCV; 78 | 79 | ChunkQueue send_queue; 80 | ChunkQueue recv_queue; 81 | 82 | const DTLSEncryptCallbackPtr dtlsEncryptCallback; 83 | const MsgReceivedCallbackPtr msgReceivedCallback; 84 | 85 | std::atomic should_stop{false}; 86 | std::thread recv_thread; 87 | std::thread connect_thread; 88 | 89 | void RunConnect(); 90 | void RecvLoop(); 91 | 92 | // SCTP has output a packet ready for DTLS 93 | int OnSCTPForDTLS(void *data, size_t len, uint8_t tos, uint8_t set_df); 94 | 95 | // SCTP has received a packet for GameSurge 96 | int OnSCTPForGS(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, struct sctp_rcvinfo recv_info, int flags); 97 | 98 | void OnMsgReceived(const uint8_t *data, size_t len, int ppid, int sid); 99 | void OnNotification(union sctp_notification *notify, size_t len); 100 | 101 | // usrsctp callbacks 102 | static int _OnSCTPForDTLS(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df); 103 | static void _DebugLog(const char *format, ...); 104 | static int _OnSCTPForGS(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, struct sctp_rcvinfo recv_info, int flags, 105 | void *user_data); 106 | 107 | std::shared_ptr logger = GetLogger("rtcdcpp.SCTP"); 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /include/rtcdcpp/librtcdcpp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * C Wrapper around the C++ Classes. 30 | */ 31 | -------------------------------------------------------------------------------- /src/DTLSWrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Simple wrapper around OpenSSL DTLS. 30 | */ 31 | 32 | #include "rtcdcpp/DTLSWrapper.hpp" 33 | #include "rtcdcpp/RTCCertificate.hpp" 34 | 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | namespace rtcdcpp { 42 | 43 | using namespace std; 44 | 45 | DTLSWrapper::DTLSWrapper(PeerConnection *peer_connection) 46 | : peer_connection(peer_connection), certificate_(nullptr), handshake_complete(false), should_stop(false) { 47 | if (peer_connection->config().certificates.size() != 1) { 48 | throw std::runtime_error("At least one and only one certificate has to be set"); 49 | } 50 | certificate_ = &peer_connection->config().certificates.front(); 51 | this->decrypted_callback = [](ChunkPtr x) { ; }; 52 | this->encrypted_callback = [](ChunkPtr x) { ; }; 53 | } 54 | 55 | DTLSWrapper::~DTLSWrapper() { 56 | Stop(); 57 | 58 | // NOTE: We intentionally do NOT free the BIO's manually 59 | 60 | if (ssl) { 61 | if (SSL_shutdown(ssl) == 0) { 62 | SSL_shutdown(ssl); 63 | } 64 | SSL_free(ssl); 65 | ssl = nullptr; 66 | } 67 | if (ctx) { 68 | SSL_CTX_free(ctx); 69 | ctx = nullptr; 70 | } 71 | } 72 | 73 | static int verify_peer_certificate(int ok, X509_STORE_CTX *ctx) { 74 | // XXX: This function should ask the user if they trust the cert 75 | return 1; 76 | } 77 | 78 | bool DTLSWrapper::Initialize() { 79 | SSL_library_init(); 80 | OpenSSL_add_all_algorithms(); 81 | 82 | ctx = SSL_CTX_new(DTLS_method()); 83 | if (!ctx) { 84 | return false; 85 | } 86 | 87 | if (SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH") != 1) { 88 | return false; 89 | } 90 | 91 | SSL_CTX_set_read_ahead(ctx, 1); 92 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_peer_certificate); 93 | SSL_CTX_use_PrivateKey(ctx, certificate_->evp_pkey()); 94 | SSL_CTX_use_certificate(ctx, certificate_->x509()); 95 | 96 | if (SSL_CTX_check_private_key(ctx) != 1) { 97 | return false; 98 | } 99 | 100 | ssl = SSL_new(ctx); 101 | if (!ssl) { 102 | return false; 103 | } 104 | 105 | in_bio = BIO_new(BIO_s_mem()); 106 | if (!in_bio) { 107 | return false; 108 | } 109 | BIO_set_mem_eof_return(in_bio, -1); 110 | 111 | out_bio = BIO_new(BIO_s_mem()); 112 | if (!out_bio) { 113 | return false; 114 | } 115 | BIO_set_mem_eof_return(out_bio, -1); 116 | 117 | SSL_set_bio(ssl, in_bio, out_bio); 118 | 119 | std::shared_ptr ecdh = std::shared_ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), EC_KEY_free); 120 | SSL_set_options(ssl, SSL_OP_SINGLE_ECDH_USE); 121 | SSL_set_tmp_ecdh(ssl, ecdh.get()); 122 | 123 | return true; 124 | } 125 | 126 | void DTLSWrapper::Start() { 127 | SPDLOG_TRACE(logger, "Start(): Starting handshake - {}", std::this_thread::get_id()); 128 | 129 | // XXX: We can never be the server (sdp always returns active, not passive) 130 | SSL_set_connect_state(ssl); 131 | uint8_t buf[4192]; 132 | SSL_do_handshake(ssl); 133 | while (BIO_ctrl_pending(out_bio) > 0) { 134 | // XXX: This is not actually valid (buf + offset send after) 135 | int nbytes = BIO_read(out_bio, buf, sizeof(buf)); 136 | if (nbytes > 0) { 137 | SPDLOG_TRACE(logger, "Start(): Sending handshake bytes {}", nbytes); 138 | this->encrypted_callback(std::make_shared(buf, nbytes)); 139 | } 140 | } 141 | 142 | // std::cerr << "DTLS: handshake started, start encrypt/decrypt threads" << std::endl; 143 | this->encrypt_thread = std::thread(&DTLSWrapper::RunEncrypt, this); 144 | this->decrypt_thread = std::thread(&DTLSWrapper::RunDecrypt, this); 145 | } 146 | 147 | void DTLSWrapper::Stop() { 148 | this->should_stop = true; 149 | 150 | encrypt_queue.Stop(); 151 | if (this->encrypt_thread.joinable()) { 152 | this->encrypt_thread.join(); 153 | } 154 | 155 | decrypt_queue.Stop(); 156 | if (this->decrypt_thread.joinable()) { 157 | this->decrypt_thread.join(); 158 | } 159 | } 160 | 161 | void DTLSWrapper::SetEncryptedCallback(std::function encrypted_callback) { this->encrypted_callback = encrypted_callback; } 162 | 163 | void DTLSWrapper::SetDecryptedCallback(std::function decrypted_callback) { this->decrypted_callback = decrypted_callback; } 164 | 165 | void DTLSWrapper::DecryptData(ChunkPtr chunk) { this->decrypt_queue.push(chunk); } 166 | 167 | void DTLSWrapper::RunDecrypt() { 168 | SPDLOG_TRACE(logger, "RunDecrypt()"); 169 | 170 | bool should_notify = false; 171 | while (!should_stop) { 172 | int read_bytes = 0; 173 | uint8_t buf[2048] = {0}; 174 | ChunkPtr chunk = this->decrypt_queue.wait_and_pop(); 175 | if (!chunk) { 176 | return; 177 | } 178 | size_t cur_len = chunk->Length(); 179 | 180 | { 181 | std::lock_guard lock(this->ssl_mutex); 182 | 183 | // std::cout << "DTLS: Decrypting data of size - " << chunk->Length() << std::endl; 184 | BIO_write(in_bio, chunk->Data(), (int)chunk->Length()); 185 | read_bytes = SSL_read(ssl, buf, sizeof(buf)); 186 | 187 | if (!handshake_complete) { 188 | if (BIO_ctrl_pending(out_bio)) { 189 | uint8_t out_buf[2048]; 190 | int send_bytes = 0; 191 | while (BIO_ctrl_pending(out_bio) > 0) { 192 | send_bytes += BIO_read(out_bio, out_buf + send_bytes, sizeof(out_buf) - send_bytes); 193 | } 194 | if (send_bytes > 0) { 195 | this->encrypted_callback(std::make_shared(out_buf, send_bytes)); 196 | } 197 | } 198 | 199 | if (SSL_is_init_finished(ssl)) { 200 | handshake_complete = true; 201 | should_notify = true; 202 | } 203 | } 204 | } 205 | 206 | // std::cerr << "Read this many bytes " << read_bytes << std::endl; 207 | if (read_bytes > 0) { 208 | // std::cerr << "DTLS: Calling decrypted callback with data of size: " << read_bytes << std::endl; 209 | this->decrypted_callback(std::make_shared(buf, read_bytes)); 210 | } else { 211 | // TODO: SSL error checking 212 | } 213 | 214 | if (should_notify) { 215 | // std::cerr << "DTLS: handshake is done" << std::endl; 216 | should_notify = false; 217 | peer_connection->OnDTLSHandshakeDone(); 218 | } 219 | } 220 | } 221 | 222 | void DTLSWrapper::EncryptData(ChunkPtr chunk) { this->encrypt_queue.push(chunk); } 223 | 224 | void DTLSWrapper::RunEncrypt() { 225 | SPDLOG_TRACE(logger, "RunEncrypt()"); 226 | while (!this->should_stop) { 227 | ChunkPtr chunk = this->encrypt_queue.wait_and_pop(); 228 | if (!chunk) { 229 | return; 230 | } 231 | 232 | // std::cerr << "DTLS: Encrypting message of len - " << chunk->Length() << std::endl; 233 | { 234 | std::lock_guard lock(this->ssl_mutex); 235 | uint8_t buf[2048] = {0}; 236 | if (SSL_write(ssl, chunk->Data(), (int)chunk->Length()) != chunk->Length()) { 237 | // TODO: Error handling 238 | } 239 | 240 | int nbytes = 0; 241 | while (BIO_ctrl_pending(out_bio) > 0) { 242 | nbytes += BIO_read(out_bio, buf + nbytes, 2048 - nbytes); 243 | } 244 | 245 | if (nbytes > 0) { 246 | // std::cerr << "DTLS: Calling the encrypted data cb" << std::endl; 247 | this->encrypted_callback(std::make_shared(buf, nbytes)); 248 | } 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/DataChannel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * DataChannel. 30 | */ 31 | 32 | #include 33 | 34 | #include "rtcdcpp/DataChannel.hpp" 35 | #include "rtcdcpp/PeerConnection.hpp" 36 | 37 | namespace rtcdcpp { 38 | 39 | DataChannel::DataChannel(PeerConnection *pc, uint16_t stream_id, uint8_t chan_type, std::string label, std::string protocol) 40 | : pc(pc), stream_id(stream_id), chan_type(chan_type), label(label), protocol(protocol) { 41 | // XXX: Default-noop callbacks 42 | open_cb = []() { ; }; // XXX: I love and hate that this is valid c++ 43 | str_msg_cb = [](std::string x) { ; }; 44 | bin_msg_cb = [](ChunkPtr data) { ; }; 45 | closed_cb = []() { ; }; 46 | error_cb = [](std::string x) { ; }; 47 | } 48 | 49 | DataChannel::~DataChannel() { ; } 50 | 51 | uint16_t DataChannel::GetStreamID() { return this->stream_id; } 52 | 53 | uint8_t DataChannel::GetChannelType() { return this->chan_type; } 54 | 55 | std::string DataChannel::GetLabel() { return this->label; } 56 | 57 | std::string DataChannel::GetProtocol() { return this->protocol; } 58 | 59 | /** 60 | * TODO: Close the DataChannel. 61 | */ 62 | void Close() { ; } 63 | 64 | bool DataChannel::SendString(std::string msg) { 65 | std::cerr << "DC: Sending string: " << msg << std::endl; 66 | this->pc->SendStrMsg(msg, this->stream_id); 67 | return true; 68 | } 69 | 70 | // TODO Take a shared_ptr to datachunk 71 | bool DataChannel::SendBinary(const uint8_t *msg, int len) { 72 | std::cerr << "DC: Sending binary of len - " << len << std::endl; 73 | this->pc->SendBinaryMsg(msg, len, this->stream_id); 74 | std::cerr << "DC: Binary sent" << std::endl; 75 | return true; 76 | } 77 | 78 | void DataChannel::SetOnOpen(std::function open_cb) { this->open_cb = open_cb; } 79 | 80 | void DataChannel::SetOnStringMsgCallback(std::function str_msg_cb) { this->str_msg_cb = str_msg_cb; } 81 | 82 | void DataChannel::SetOnBinaryMsgCallback(std::function bin_msg_cb) { this->bin_msg_cb = bin_msg_cb; } 83 | 84 | void DataChannel::SetOnClosedCallback(std::function closed_cb) { this->closed_cb = closed_cb; } 85 | 86 | void DataChannel::SetOnErrorCallback(std::function error_cb) { this->error_cb = error_cb; } 87 | 88 | void DataChannel::OnOpen() { 89 | if (this->open_cb) { 90 | this->open_cb(); 91 | } 92 | } 93 | 94 | void DataChannel::OnStringMsg(std::string msg) { 95 | if (this->str_msg_cb) { 96 | this->str_msg_cb(msg); 97 | } 98 | } 99 | 100 | void DataChannel::OnBinaryMsg(ChunkPtr msg) { 101 | if (this->bin_msg_cb) { 102 | this->bin_msg_cb(msg); 103 | } 104 | } 105 | 106 | void DataChannel::OnClosed() { 107 | if (this->closed_cb) { 108 | this->closed_cb(); 109 | } 110 | } 111 | 112 | void DataChannel::OnError(std::string description) { 113 | if (this->error_cb) { 114 | this->error_cb(description); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Logging.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "rtcdcpp/Logging.hpp" 29 | 30 | namespace rtcdcpp { 31 | 32 | #ifndef SPDLOG_DISABLED 33 | 34 | #include 35 | 36 | std::shared_ptr GetLogger(const std::string &logger_name) { 37 | auto logger = spdlog::get(logger_name); 38 | //spdlog::set_level(spdlog::level::trace); // Set global log level to debug 39 | 40 | if (logger) { 41 | return logger; 42 | } 43 | return spdlog::stdout_color_mt(logger_name); 44 | } 45 | 46 | #else 47 | 48 | std::shared_ptr GetLogger(const std::string &logger) { 49 | return std::make_shared(); 50 | } 51 | 52 | #endif 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/NiceWrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Basic implementation of libnice stuff. 30 | */ 31 | 32 | #include "rtcdcpp/NiceWrapper.hpp" 33 | 34 | #include 35 | 36 | #include 37 | 38 | void ReplaceAll(std::string &s, const std::string &search, const std::string &replace) { 39 | size_t pos = 0; 40 | while ((pos = s.find(search, pos)) != std::string::npos) { 41 | s.replace(pos, search.length(), replace); 42 | pos += replace.length(); 43 | } 44 | } 45 | 46 | namespace rtcdcpp { 47 | 48 | using namespace std; 49 | 50 | NiceWrapper::NiceWrapper(PeerConnection *peer_connection) 51 | : peer_connection(peer_connection), stream_id(0), should_stop(false), send_queue(), agent(NULL, nullptr), loop(NULL, nullptr), packets_sent(0) { 52 | data_received_callback = [](ChunkPtr x) { ; }; 53 | nice_debug_disable(true); 54 | } 55 | 56 | NiceWrapper::~NiceWrapper() { Stop(); } 57 | 58 | void new_local_candidate(NiceAgent *agent, NiceCandidate *candidate, gpointer user_data) { 59 | NiceWrapper *nice = (NiceWrapper *)user_data; 60 | gchar *cand = nice_agent_generate_local_candidate_sdp(agent, candidate); 61 | std::string cand_str(cand); 62 | nice->OnCandidate(cand_str); 63 | g_free(cand); 64 | } 65 | 66 | void NiceWrapper::OnCandidate(std::string candidate) { 67 | SPDLOG_DEBUG(logger, "On candidate: {}", candidate); 68 | this->peer_connection->OnLocalIceCandidate(candidate); 69 | } 70 | 71 | void candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer user_data) { 72 | NiceWrapper *nice = (NiceWrapper *)user_data; 73 | nice->OnGatheringDone(); 74 | } 75 | 76 | // TODO: Callback for this 77 | void NiceWrapper::OnGatheringDone() { 78 | SPDLOG_DEBUG(logger, "ICE: candidate gathering done"); 79 | std::string empty_candidate(""); 80 | this->peer_connection->OnLocalIceCandidate(empty_candidate); 81 | } 82 | 83 | // TODO: Callbacks on failure 84 | void component_state_changed(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer user_data) { 85 | NiceWrapper *nice = (NiceWrapper *)user_data; 86 | nice->OnStateChange(stream_id, component_id, state); 87 | } 88 | 89 | void NiceWrapper::OnStateChange(uint32_t stream_id, uint32_t component_id, uint32_t state) { 90 | switch (state) { 91 | case (NICE_COMPONENT_STATE_DISCONNECTED): 92 | SPDLOG_TRACE(logger, "ICE: DISCONNECTED"); 93 | break; 94 | case (NICE_COMPONENT_STATE_GATHERING): 95 | SPDLOG_TRACE(logger, "ICE: GATHERING"); 96 | break; 97 | case (NICE_COMPONENT_STATE_CONNECTING): 98 | SPDLOG_TRACE(logger, "ICE: CONNECTING"); 99 | break; 100 | case (NICE_COMPONENT_STATE_CONNECTED): 101 | SPDLOG_TRACE(logger, "ICE: CONNECTED"); 102 | break; 103 | case (NICE_COMPONENT_STATE_READY): 104 | SPDLOG_TRACE(logger, "ICE: READY"); 105 | this->OnIceReady(); 106 | break; 107 | case (NICE_COMPONENT_STATE_FAILED): 108 | SPDLOG_TRACE(logger, "ICE FAILED: stream_id={} - component_id={}", stream_id, component_id); 109 | break; 110 | default: 111 | SPDLOG_TRACE(logger, "ICE: Unknown state: {}", state); 112 | break; 113 | } 114 | } 115 | 116 | // TODO: Turn this into a callback 117 | void NiceWrapper::OnIceReady() { this->peer_connection->OnIceReady(); } 118 | 119 | void new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id, NiceCandidate *lcandidate, NiceCandidate *rcandidate, 120 | gpointer user_data) { 121 | GetLogger("librtcpp.Nice")->error("ICE: new selected pair"); 122 | NiceWrapper *nice = (NiceWrapper *)user_data; 123 | nice->OnSelectedPair(); 124 | } 125 | 126 | void NiceWrapper::OnSelectedPair() { SPDLOG_TRACE(logger, "OnSelectedPair"); } 127 | 128 | void data_received(NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data) { 129 | NiceWrapper *nice = (NiceWrapper *)user_data; 130 | nice->OnDataReceived((const uint8_t *)buf, len); 131 | } 132 | 133 | void NiceWrapper::OnDataReceived(const uint8_t *buf, int len) { 134 | SPDLOG_TRACE(logger, "Nice data IN: {}", len); 135 | this->data_received_callback(std::make_shared(buf, len)); 136 | } 137 | 138 | void nice_log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { 139 | NiceWrapper *nice = (NiceWrapper *)user_data; 140 | nice->LogMessage(message); 141 | } 142 | 143 | void NiceWrapper::LogMessage(const gchar *message) { SPDLOG_TRACE(logger, "libnice: {}", message); } 144 | 145 | bool NiceWrapper::Initialize() { 146 | auto config = peer_connection->config(); 147 | 148 | int log_flags = G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION; 149 | g_log_set_handler(NULL, (GLogLevelFlags)log_flags, nice_log_handler, this); 150 | this->loop = std::unique_ptr(g_main_loop_new(NULL, FALSE), g_main_loop_unref); 151 | if (!this->loop) { 152 | SPDLOG_TRACE(logger, "Failed to initialize GMainLoop"); 153 | } 154 | 155 | this->agent = std::unique_ptr(nice_agent_new(g_main_loop_get_context(loop.get()), NICE_COMPATIBILITY_RFC5245), 156 | g_object_unref); 157 | if (!this->agent) { 158 | SPDLOG_TRACE(logger, "Failed to initialize nice agent"); 159 | return false; 160 | } 161 | 162 | this->g_main_loop_thread = std::thread(g_main_loop_run, this->loop.get()); 163 | 164 | g_object_set(G_OBJECT(agent.get()), "upnp", FALSE, NULL); 165 | g_object_set(G_OBJECT(agent.get()), "controlling-mode", 0, NULL); 166 | 167 | if (config.ice_servers.size() > 1) { 168 | throw std::invalid_argument("Only up to one ICE server is currently supported"); 169 | } 170 | 171 | for (auto ice_server : config.ice_servers) { 172 | struct hostent *stun_host = gethostbyname(ice_server.hostname.c_str()); 173 | if (stun_host == nullptr) { 174 | logger->warn("Failed to lookup host for server: {}", ice_server); 175 | } else { 176 | in_addr *address = (in_addr *)stun_host->h_addr; 177 | const char *ip_address = inet_ntoa(*address); 178 | 179 | g_object_set(G_OBJECT(agent.get()), "stun-server", ip_address, NULL); 180 | } 181 | 182 | if (ice_server.port > 0) { 183 | g_object_set(G_OBJECT(agent.get()), "stun-server-port", ice_server.port, NULL); 184 | } else { 185 | logger->error("stun port empty"); 186 | } 187 | } 188 | 189 | g_signal_connect(G_OBJECT(agent.get()), "candidate-gathering-done", G_CALLBACK(candidate_gathering_done), this); 190 | g_signal_connect(G_OBJECT(agent.get()), "component-state-changed", G_CALLBACK(component_state_changed), this); 191 | g_signal_connect(G_OBJECT(agent.get()), "new-candidate-full", G_CALLBACK(new_local_candidate), this); 192 | g_signal_connect(G_OBJECT(agent.get()), "new-selected-pair", G_CALLBACK(new_selected_pair), this); 193 | 194 | // TODO: Learn more about nice streams 195 | this->stream_id = nice_agent_add_stream(agent.get(), 1); 196 | if (this->stream_id == 0) { 197 | return false; 198 | } 199 | 200 | nice_agent_set_stream_name(agent.get(), this->stream_id, "application"); 201 | 202 | if (!config.ice_ufrag.empty() && !config.ice_pwd.empty()) { 203 | nice_agent_set_local_credentials(agent.get(), this->stream_id, config.ice_ufrag.c_str(), config.ice_pwd.c_str()); 204 | } 205 | 206 | if (config.ice_port_range.first != 0 || config.ice_port_range.second != 0) { 207 | nice_agent_set_port_range(agent.get(), this->stream_id, 1, config.ice_port_range.first, config.ice_port_range.second); 208 | } 209 | 210 | return (bool)nice_agent_attach_recv(agent.get(), this->stream_id, 1, g_main_loop_get_context(loop.get()), data_received, this); 211 | } 212 | 213 | void NiceWrapper::StartSendLoop() { this->send_thread = std::thread(&NiceWrapper::SendLoop, this); } 214 | 215 | void NiceWrapper::Stop() { 216 | this->should_stop = true; 217 | 218 | send_queue.Stop(); 219 | if (this->send_thread.joinable()) { 220 | this->send_thread.join(); 221 | } 222 | 223 | g_main_loop_quit(this->loop.get()); 224 | 225 | if (this->g_main_loop_thread.joinable()) { 226 | this->g_main_loop_thread.join(); 227 | } 228 | } 229 | 230 | void NiceWrapper::ParseRemoteSDP(std::string remote_sdp) { 231 | string crfree_remote_sdp = remote_sdp; 232 | 233 | // TODO: Improve this. This is needed because otherwise libnice will wrongly take the '\r' as part of ice-ufrag/password. 234 | ReplaceAll(crfree_remote_sdp, "\r\n", "\n"); 235 | 236 | int rc = nice_agent_parse_remote_sdp(this->agent.get(), crfree_remote_sdp.c_str()); 237 | 238 | if (rc < 0) { 239 | throw std::runtime_error("ParseRemoteSDP: " + std::string(strerror(rc))); 240 | } else { 241 | logger->info("ICE: Added {} Candidates", rc); 242 | } 243 | 244 | if (!nice_agent_gather_candidates(agent.get(), this->stream_id)) { 245 | throw std::runtime_error("ParseRemoteSDP: Error gathering candidates!"); 246 | } 247 | } 248 | 249 | void NiceWrapper::SendData(ChunkPtr chunk) { 250 | if (this->stream_id == 0) { 251 | SPDLOG_TRACE(logger, "ICE: ERROR sending data to unitialized nice context"); 252 | return; 253 | } 254 | 255 | this->send_queue.push(chunk); 256 | } 257 | 258 | // Pull items off the send queue and call nice_agent_send 259 | void NiceWrapper::SendLoop() { 260 | while (!this->should_stop) { 261 | ChunkPtr chunk = send_queue.wait_and_pop(); 262 | if (!chunk) { 263 | return; 264 | } 265 | size_t cur_len = chunk->Length(); 266 | int result = 0; 267 | // std::cerr << "ICE: Sending data of len " << cur_len << std::endl; 268 | SPDLOG_TRACE(logger, "Nice data OUT: {}", cur_len); 269 | result = nice_agent_send(this->agent.get(), this->stream_id, 1, (guint)cur_len, (const char *)chunk->Data()); 270 | if (result != cur_len) { 271 | SPDLOG_TRACE(logger, "ICE: Failed to send data of len - {}", cur_len); 272 | SPDLOG_TRACE(logger, "ICE: Failed send result - {}", result); 273 | } else { 274 | // std::cerr << "ICE: Data sent " << cur_len << std::endl; 275 | } 276 | } 277 | } 278 | 279 | std::string NiceWrapper::GenerateLocalSDP() { 280 | std::stringstream nice_sdp; 281 | std::stringstream result; 282 | std::string line; 283 | 284 | gchar *raw_sdp = nice_agent_generate_local_sdp(agent.get()); 285 | nice_sdp << raw_sdp; 286 | 287 | while (std::getline(nice_sdp, line)) { 288 | if (g_str_has_prefix(line.c_str(), "a=ice-ufrag:") || g_str_has_prefix(line.c_str(), "a=ice-pwd:")) { 289 | result << line << "\r\n"; 290 | } 291 | } 292 | g_free(raw_sdp); 293 | return result.str(); 294 | } 295 | 296 | bool NiceWrapper::SetRemoteIceCandidate(string candidate_sdp) { 297 | GSList *list = NULL; 298 | NiceCandidate *rcand = nice_agent_parse_remote_candidate_sdp(this->agent.get(), this->stream_id, candidate_sdp.c_str()); 299 | 300 | if (rcand == NULL) { 301 | SPDLOG_TRACE(logger, "failed to parse remote candidate"); 302 | return false; 303 | } 304 | list = g_slist_append(list, rcand); 305 | 306 | bool success = (nice_agent_set_remote_candidates(this->agent.get(), this->stream_id, 1, list) > 0); 307 | 308 | g_slist_free_full(list, (GDestroyNotify)&nice_candidate_free); 309 | 310 | return success; 311 | } 312 | 313 | bool NiceWrapper::SetRemoteIceCandidates(vector candidate_sdps) { 314 | GSList *list = NULL; 315 | for (auto candidate_sdp : candidate_sdps) { 316 | NiceCandidate *rcand = nice_agent_parse_remote_candidate_sdp(this->agent.get(), this->stream_id, candidate_sdp.c_str()); 317 | 318 | if (rcand == NULL) { 319 | SPDLOG_TRACE(logger, "failed to parse remote candidate"); 320 | return false; 321 | } 322 | list = g_slist_append(list, rcand); 323 | } 324 | 325 | bool success = (nice_agent_set_remote_candidates(this->agent.get(), this->stream_id, 1, list) > 0); 326 | 327 | g_slist_free_full(list, (GDestroyNotify)&nice_candidate_free); 328 | 329 | return success; 330 | } 331 | 332 | void NiceWrapper::SetDataReceivedCallback(std::function data_received_callback) { 333 | this->data_received_callback = data_received_callback; 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/PeerConnection.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * RTC Handler. 30 | */ 31 | 32 | #include "rtcdcpp/PeerConnection.hpp" 33 | #include "rtcdcpp/DTLSWrapper.hpp" 34 | #include "rtcdcpp/NiceWrapper.hpp" 35 | #include "rtcdcpp/SCTPWrapper.hpp" 36 | 37 | #include 38 | 39 | #define SESSION_ID_SIZE 16 40 | 41 | namespace rtcdcpp { 42 | 43 | using namespace std; 44 | 45 | std::ostream &operator<<(std::ostream &os, const RTCIceServer &ice_server) { return os << ice_server.hostname << ":" << ice_server.port; } 46 | 47 | PeerConnection::PeerConnection(const RTCConfiguration &config, IceCandidateCallbackPtr icCB, DataChannelCallbackPtr dcCB) 48 | : config_(config), ice_candidate_cb(icCB), new_channel_cb(dcCB) { 49 | if (config_.certificates.empty()) { 50 | config_.certificates.push_back(RTCCertificate::GenerateCertificate("rtcdcpp", 365)); 51 | } 52 | if (!Initialize()) { 53 | throw runtime_error("Could not initialize"); 54 | } 55 | } 56 | 57 | PeerConnection::~PeerConnection() { 58 | sctp->Stop(); 59 | dtls->Stop(); 60 | nice->Stop(); 61 | } 62 | 63 | bool PeerConnection::Initialize() { 64 | this->nice = make_unique(this); 65 | this->dtls = make_unique(this); 66 | this->sctp = make_unique( 67 | std::bind(&DTLSWrapper::EncryptData, dtls.get(), std::placeholders::_1), 68 | std::bind(&PeerConnection::OnSCTPMsgReceived, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); 69 | 70 | if (!dtls->Initialize()) { 71 | logger->error("DTLS failure"); 72 | return false; 73 | } 74 | SPDLOG_DEBUG(logger, "RTC: dtls initialized"); 75 | 76 | if (!nice->Initialize()) { 77 | logger->error("Nice failure"); 78 | return false; 79 | } 80 | SPDLOG_DEBUG(logger, "RTC: nice initialized"); 81 | 82 | if (!sctp->Initialize()) { 83 | logger->error("sctp failure"); 84 | return false; 85 | } 86 | SPDLOG_DEBUG(logger, "RTC: sctp initialized"); 87 | 88 | nice->SetDataReceivedCallback(std::bind(&DTLSWrapper::DecryptData, dtls.get(), std::placeholders::_1)); 89 | dtls->SetDecryptedCallback(std::bind(&SCTPWrapper::DTLSForSCTP, sctp.get(), std::placeholders::_1)); 90 | dtls->SetEncryptedCallback(std::bind(&NiceWrapper::SendData, nice.get(), std::placeholders::_1)); 91 | nice->StartSendLoop(); 92 | return true; 93 | } 94 | 95 | void PeerConnection::ParseOffer(std::string offer_sdp) { 96 | std::stringstream ss(offer_sdp); 97 | std::string line; 98 | 99 | while (std::getline(ss, line)) { 100 | if (g_str_has_prefix(line.c_str(), "a=setup:")) { 101 | std::size_t pos = line.find(":") + 1; 102 | std::string setup = line.substr(pos); 103 | if (setup == "active" && this->role == Client) { 104 | this->role = Server; 105 | } else if (setup == "passive" && this->role == Server) { 106 | this->role = Client; 107 | } else { // actpass 108 | // nothing to do 109 | } 110 | } else if (g_str_has_prefix(line.c_str(), "a=mid:")) { 111 | std::size_t pos = line.find(":") + 1; 112 | std::size_t end = line.find("\r"); 113 | this->mid = line.substr(pos, end - pos); 114 | } 115 | } 116 | nice->ParseRemoteSDP(offer_sdp); 117 | } 118 | 119 | std::string random_session_id() { 120 | const static char *numbers = "0123456789"; 121 | srand((unsigned)time(nullptr)); 122 | std::stringstream result; 123 | 124 | for (int i = 0; i < SESSION_ID_SIZE; ++i) { 125 | int r = rand() % 10; 126 | result << numbers[r]; 127 | } 128 | return result.str(); 129 | } 130 | 131 | std::string PeerConnection::GenerateAnswer() { 132 | std::stringstream sdp; 133 | std::string session_id = random_session_id(); 134 | SPDLOG_TRACE(logger, "Generating Answer SDP: session_id={}", session_id); 135 | 136 | sdp << "v=0\r\n"; 137 | sdp << "o=- " << session_id << " 2 IN IP4 0.0.0.0\r\n"; // Session ID 138 | sdp << "s=-\r\n"; 139 | sdp << "t=0 0\r\n"; 140 | sdp << "a=msid-semantic: WMS\r\n"; 141 | sdp << "m=application 9 DTLS/SCTP 5000\r\n"; // XXX: hardcoded port 142 | sdp << "c=IN IP4 0.0.0.0\r\n"; 143 | sdp << this->nice->GenerateLocalSDP(); 144 | sdp << "a=fingerprint:sha-256 " << dtls->certificate()->fingerprint() << "\r\n"; 145 | sdp << "a=ice-options:trickle\r\n"; 146 | sdp << "a=setup:" << (this->role == Client ? "active" : "passive") << "\r\n"; 147 | sdp << "a=mid:" << this->mid << "\r\n"; 148 | sdp << "a=sctpmap:5000 webrtc-datachannel 1024\r\n"; 149 | 150 | return sdp.str(); 151 | } 152 | 153 | bool PeerConnection::SetRemoteIceCandidate(string candidate_sdp) { return this->nice->SetRemoteIceCandidate(candidate_sdp); } 154 | 155 | bool PeerConnection::SetRemoteIceCandidates(vector candidate_sdps) { return this->nice->SetRemoteIceCandidates(candidate_sdps); } 156 | 157 | void PeerConnection::OnLocalIceCandidate(std::string &ice_candidate) { 158 | if (this->ice_candidate_cb) { 159 | if (ice_candidate.size() > 2) { 160 | ice_candidate = ice_candidate.substr(2); 161 | } 162 | IceCandidate candidate(ice_candidate, this->mid, 0); 163 | this->ice_candidate_cb(candidate); 164 | } 165 | } 166 | 167 | void PeerConnection::OnIceReady() { 168 | SPDLOG_TRACE(logger, "OnIceReady(): Time to ping DTLS"); 169 | if (!iceReady) { 170 | iceReady = true; 171 | this->dtls->Start(); 172 | } else { 173 | // TODO work out 174 | logger->warn("OnIceReady(): Called twice!!"); 175 | } 176 | } 177 | 178 | void PeerConnection::OnDTLSHandshakeDone() { 179 | SPDLOG_TRACE(logger, "OnDTLSHandshakeDone(): Time to get the SCTP party started"); 180 | this->sctp->Start(); 181 | } 182 | 183 | // Matches DataChannel onmessage 184 | void PeerConnection::OnSCTPMsgReceived(ChunkPtr chunk, uint16_t sid, uint32_t ppid) { 185 | SPDLOG_TRACE(logger, "OnSCTPMsgReceived(): Handling an sctp message"); 186 | if (ppid == PPID_CONTROL) { 187 | SPDLOG_TRACE(logger, "Control PPID"); 188 | if (chunk->Data()[0] == DC_TYPE_OPEN) { 189 | SPDLOG_TRACE(logger, "New channel time!"); 190 | HandleNewDataChannel(chunk, sid); 191 | } else if (chunk->Data()[0] == DC_TYPE_ACK) { 192 | SPDLOG_TRACE(logger, "DC ACK"); 193 | // HandleDataChannelAck(chunk, sid); XXX: Don't care right now 194 | } else { 195 | SPDLOG_TRACE(logger, "Unknown msg_type for ppid control: {}", chunk->Data()[0]); 196 | } 197 | } else if ((ppid == PPID_STRING) || (ppid == PPID_STRING_EMPTY)) { 198 | SPDLOG_TRACE(logger, "String msg"); 199 | HandleStringMessage(chunk, sid); 200 | } else if ((ppid == PPID_BINARY) || (ppid == PPID_BINARY_EMPTY)) { 201 | SPDLOG_TRACE(logger, "Binary msg"); 202 | HandleBinaryMessage(chunk, sid); 203 | } else { 204 | logger->error("Unknown ppid={}", ppid); 205 | } 206 | } 207 | 208 | std::shared_ptr PeerConnection::GetChannel(uint16_t sid) { 209 | auto iter = data_channels.find(sid); 210 | if (iter != data_channels.end()) { 211 | return data_channels[sid]; 212 | } 213 | 214 | return std::shared_ptr(); 215 | } 216 | 217 | void PeerConnection::HandleNewDataChannel(ChunkPtr chunk, uint16_t sid) { 218 | uint8_t *raw_msg = chunk->Data(); 219 | dc_open_msg open_msg; 220 | open_msg.chan_type = raw_msg[1]; 221 | open_msg.priority = (raw_msg[2] << 8) + raw_msg[3]; 222 | open_msg.reliability = (raw_msg[4] << 24) + (raw_msg[5] << 16) + (raw_msg[6] << 8) + raw_msg[7]; 223 | open_msg.label_len = (raw_msg[8] << 8) + raw_msg[9]; 224 | open_msg.protocol_len = (raw_msg[10] << 8) + raw_msg[11]; 225 | 226 | std::string label(reinterpret_cast(raw_msg + 12), open_msg.label_len); 227 | std::string protocol(reinterpret_cast(raw_msg + 12 + open_msg.label_len), open_msg.protocol_len); 228 | 229 | SPDLOG_DEBUG(logger, "Creating channel with sid: {}, chan_type: {}, label: {}, protocol: {}", sid, open_msg.chan_type, label, protocol); 230 | 231 | // TODO: Support overriding an existing channel 232 | auto new_channel = std::make_shared(this, sid, open_msg.chan_type, label, protocol); 233 | 234 | data_channels[sid] = new_channel; 235 | 236 | if (this->new_channel_cb) { 237 | this->new_channel_cb(new_channel); 238 | } else { 239 | logger->warn("No new channel callback, ignoring new channel"); 240 | } 241 | } 242 | 243 | void PeerConnection::HandleStringMessage(ChunkPtr chunk, uint16_t sid) { 244 | auto cur_channel = GetChannel(sid); 245 | if (!cur_channel) { 246 | logger->warn("Received msg on unknown channel: {}", sid); 247 | return; 248 | } 249 | 250 | std::string cur_msg(reinterpret_cast(chunk->Data()), chunk->Length()); 251 | 252 | cur_channel->OnStringMsg(cur_msg); 253 | } 254 | 255 | void PeerConnection::HandleBinaryMessage(ChunkPtr chunk, uint16_t sid) { 256 | auto cur_channel = GetChannel(sid); 257 | if (!cur_channel) { 258 | logger->warn("Received binary msg on unknown channel: {}", sid); 259 | return; 260 | } 261 | 262 | cur_channel->OnBinaryMsg(chunk); 263 | } 264 | 265 | void PeerConnection::SendStrMsg(std::string str_msg, uint16_t sid) { 266 | auto cur_msg = std::make_shared((const uint8_t *)str_msg.c_str(), str_msg.size()); 267 | this->sctp->GSForSCTP(cur_msg, sid, PPID_STRING); 268 | } 269 | 270 | void PeerConnection::SendBinaryMsg(const uint8_t *data, int len, uint16_t sid) { 271 | auto cur_msg = std::make_shared(data, len); 272 | this->sctp->GSForSCTP(cur_msg, sid, PPID_BINARY); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/RTCCertificate.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Simple wrapper around OpenSSL Certs. 30 | */ 31 | 32 | #include "rtcdcpp/RTCCertificate.hpp" 33 | 34 | #include 35 | 36 | namespace rtcdcpp { 37 | 38 | using namespace std; 39 | 40 | static std::shared_ptr GenerateX509(std::shared_ptr evp_pkey, const std::string &common_name, int days) { 41 | std::shared_ptr null_result; 42 | 43 | std::shared_ptr x509(X509_new(), X509_free); 44 | std::shared_ptr serial_number(BN_new(), BN_free); 45 | std::shared_ptr name(X509_NAME_new(), X509_NAME_free); 46 | 47 | if (!x509 || !serial_number || !name) { 48 | return null_result; 49 | } 50 | 51 | if (!X509_set_pubkey(x509.get(), evp_pkey.get())) { 52 | return null_result; 53 | } 54 | 55 | if (!BN_pseudo_rand(serial_number.get(), 64, 0, 0)) { 56 | return null_result; 57 | } 58 | 59 | ASN1_INTEGER *asn1_serial_number = X509_get_serialNumber(x509.get()); 60 | if (!asn1_serial_number) { 61 | return null_result; 62 | } 63 | 64 | if (!BN_to_ASN1_INTEGER(serial_number.get(), asn1_serial_number)) { 65 | return null_result; 66 | } 67 | 68 | if (!X509_set_version(x509.get(), 0L)) { 69 | return null_result; 70 | } 71 | 72 | if (!X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, (unsigned char *)common_name.c_str(), -1, -1, 0)) { 73 | return null_result; 74 | } 75 | 76 | if (!X509_set_subject_name(x509.get(), name.get()) || !X509_set_issuer_name(x509.get(), name.get())) { 77 | return null_result; 78 | } 79 | 80 | if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 0) || !X509_gmtime_adj(X509_get_notAfter(x509.get()), days * 24 * 3600)) { 81 | return null_result; 82 | } 83 | 84 | if (!X509_sign(x509.get(), evp_pkey.get(), EVP_sha1())) { 85 | return null_result; 86 | } 87 | 88 | return x509; 89 | } 90 | 91 | static std::string GenerateFingerprint(std::shared_ptr x509) { 92 | unsigned int len; 93 | unsigned char buf[4096] = {0}; 94 | if (!X509_digest(x509.get(), EVP_sha256(), buf, &len)) { 95 | throw std::runtime_error("GenerateFingerprint(): X509_digest error"); 96 | } 97 | 98 | if (len > SHA256_FINGERPRINT_SIZE) { 99 | throw std::runtime_error("GenerateFingerprint(): fingerprint size too large for buffer!"); 100 | } 101 | 102 | int offset = 0; 103 | char fp[SHA256_FINGERPRINT_SIZE]; 104 | memset(fp, 0, SHA256_FINGERPRINT_SIZE); 105 | for (unsigned int i = 0; i < len; ++i) { 106 | snprintf(fp + offset, 4, "%02X:", buf[i]); 107 | offset += 3; 108 | } 109 | fp[offset - 1] = '\0'; 110 | return std::string(fp); 111 | } 112 | 113 | RTCCertificate RTCCertificate::GenerateCertificate(std::string common_name, int days) { 114 | std::shared_ptr pkey(EVP_PKEY_new(), EVP_PKEY_free); 115 | RSA *rsa = RSA_new(); 116 | 117 | std::shared_ptr exponent(BN_new(), BN_free); 118 | 119 | if (!pkey || !rsa || !exponent) { 120 | throw std::runtime_error("GenerateCertificate: !pkey || !rsa || !exponent"); 121 | } 122 | 123 | if (!BN_set_word(exponent.get(), RSA_F4) 124 | || !RSA_generate_key_ex(rsa, 2048, exponent.get(), NULL) 125 | || !EVP_PKEY_assign_RSA(pkey.get(), rsa) 126 | ){ 127 | throw std::runtime_error("GenerateCertificate: Error generating key"); 128 | } 129 | auto cert = GenerateX509(pkey, common_name, days); 130 | 131 | if (!cert) { 132 | throw std::runtime_error("GenerateCertificate: Error in GenerateX509"); 133 | } 134 | return RTCCertificate(cert, pkey); 135 | } 136 | 137 | RTCCertificate::RTCCertificate(std::string cert_pem, std::string pkey_pem) { 138 | /* x509 */ 139 | BIO *bio = BIO_new(BIO_s_mem()); 140 | BIO_write(bio, cert_pem.c_str(), (int)cert_pem.length()); 141 | 142 | x509_ = std::shared_ptr(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free); 143 | BIO_free(bio); 144 | if (!x509_) { 145 | throw std::invalid_argument("Could not read cert_pem"); 146 | } 147 | 148 | /* evp_pkey */ 149 | bio = BIO_new(BIO_s_mem()); 150 | BIO_write(bio, pkey_pem.c_str(), (int)pkey_pem.length()); 151 | 152 | evp_pkey_ = std::shared_ptr(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free); 153 | BIO_free(bio); 154 | 155 | if (!evp_pkey_) { 156 | throw std::invalid_argument("Could not read pkey_pem"); 157 | } 158 | 159 | fingerprint_ = GenerateFingerprint(x509_); 160 | } 161 | 162 | RTCCertificate::RTCCertificate(std::shared_ptr x509, std::shared_ptr evp_pkey) 163 | : x509_(x509), evp_pkey_(evp_pkey), fingerprint_(GenerateFingerprint(x509_)) {} 164 | } 165 | -------------------------------------------------------------------------------- /src/SCTPWrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Wrapper around usrsctp/ 30 | */ 31 | 32 | #include "rtcdcpp/SCTPWrapper.hpp" 33 | 34 | #include 35 | 36 | namespace rtcdcpp { 37 | 38 | using namespace std; 39 | 40 | SCTPWrapper::SCTPWrapper(DTLSEncryptCallbackPtr dtlsEncryptCB, MsgReceivedCallbackPtr msgReceivedCB) 41 | : local_port(5000), // XXX: Hard-coded for now 42 | remote_port(5000), 43 | stream_cursor(0), 44 | dtlsEncryptCallback(dtlsEncryptCB), 45 | msgReceivedCallback(msgReceivedCB) {} 46 | 47 | SCTPWrapper::~SCTPWrapper() { 48 | Stop(); 49 | 50 | int tries = 0; 51 | while (usrsctp_finish() != 0 && tries < 5) { 52 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 53 | } 54 | } 55 | 56 | static uint16_t interested_events[] = {SCTP_ASSOC_CHANGE, SCTP_PEER_ADDR_CHANGE, SCTP_REMOTE_ERROR, SCTP_SEND_FAILED, 57 | SCTP_SENDER_DRY_EVENT, SCTP_SHUTDOWN_EVENT, SCTP_ADAPTATION_INDICATION, SCTP_PARTIAL_DELIVERY_EVENT, 58 | SCTP_AUTHENTICATION_EVENT, SCTP_STREAM_RESET_EVENT, SCTP_ASSOC_RESET_EVENT, SCTP_STREAM_CHANGE_EVENT, 59 | SCTP_SEND_FAILED_EVENT}; 60 | 61 | // TODO: error callbacks 62 | void SCTPWrapper::OnNotification(union sctp_notification *notify, size_t len) { 63 | if (notify->sn_header.sn_length != (uint32_t)len) { 64 | logger->error("OnNotification(len={}) invalid length: {}", len, notify->sn_header.sn_length); 65 | return; 66 | } 67 | 68 | switch (notify->sn_header.sn_type) { 69 | case SCTP_ASSOC_CHANGE: 70 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_ASSOC_CHANGE)"); 71 | break; 72 | case SCTP_PEER_ADDR_CHANGE: 73 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_PEER_ADDR_CHANGE)"); 74 | break; 75 | case SCTP_REMOTE_ERROR: 76 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_REMOTE_ERROR)"); 77 | break; 78 | case SCTP_SEND_FAILED_EVENT: 79 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_SEND_FAILED_EVENT)"); 80 | break; 81 | case SCTP_SHUTDOWN_EVENT: 82 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_SHUTDOWN_EVENT)"); 83 | break; 84 | case SCTP_ADAPTATION_INDICATION: 85 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_ADAPTATION_INDICATION)"); 86 | break; 87 | case SCTP_PARTIAL_DELIVERY_EVENT: 88 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_PARTIAL_DELIVERY_EVENT)"); 89 | break; 90 | case SCTP_AUTHENTICATION_EVENT: 91 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_AUTHENTICATION_EVENT)"); 92 | break; 93 | case SCTP_SENDER_DRY_EVENT: 94 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_SENDER_DRY_EVENT)"); 95 | break; 96 | case SCTP_NOTIFICATIONS_STOPPED_EVENT: 97 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_NOTIFICATIONS_STOPPED_EVENT)"); 98 | break; 99 | case SCTP_STREAM_RESET_EVENT: 100 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_STREAM_RESET_EVENT)"); 101 | break; 102 | case SCTP_ASSOC_RESET_EVENT: 103 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_ASSOC_RESET_EVENT)"); 104 | break; 105 | case SCTP_STREAM_CHANGE_EVENT: 106 | SPDLOG_TRACE(logger, "OnNotification(type=SCTP_STREAM_CHANGE_EVENT)"); 107 | break; 108 | default: 109 | SPDLOG_TRACE(logger, "OnNotification(type={} (unknown))", notify->sn_header.sn_type); 110 | break; 111 | } 112 | } 113 | 114 | int SCTPWrapper::_OnSCTPForDTLS(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) { 115 | if (sctp_ptr) { 116 | return static_cast(sctp_ptr)->OnSCTPForDTLS(data, len, tos, set_df); 117 | } else { 118 | return -1; 119 | } 120 | } 121 | 122 | int SCTPWrapper::OnSCTPForDTLS(void *data, size_t len, uint8_t tos, uint8_t set_df) { 123 | SPDLOG_TRACE(logger, "Data ready. len={}, tos={}, set_df={}", len, tos, set_df); 124 | this->dtlsEncryptCallback(std::make_shared(data, len)); 125 | 126 | { 127 | unique_lock l(connectMtx); 128 | this->connectSentData = true; 129 | connectCV.notify_one(); 130 | } 131 | 132 | return 0; // success 133 | } 134 | 135 | void SCTPWrapper::_DebugLog(const char *format, ...) { 136 | va_list ap; 137 | va_start(ap, format); 138 | // std::string msg = Util::FormatString(format, ap); 139 | char msg[1024 * 16]; 140 | vsprintf(msg, format, ap); 141 | GetLogger("librtcpp.SCTP")->trace("SCTP: msg={}", msg); 142 | va_end(ap); 143 | } 144 | 145 | int SCTPWrapper::_OnSCTPForGS(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, struct sctp_rcvinfo recv_info, int flags, 146 | void *user_data) { 147 | if (user_data) { 148 | return static_cast(user_data)->OnSCTPForGS(sock, addr, data, len, recv_info, flags); 149 | } else { 150 | return -1; 151 | } 152 | } 153 | 154 | int SCTPWrapper::OnSCTPForGS(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, struct sctp_rcvinfo recv_info, int flags) { 155 | if (len == 0) { 156 | return -1; 157 | } 158 | 159 | SPDLOG_TRACE(logger, "Data received. stream={}, len={}, SSN={}, TSN={}, PPID={}", 160 | len, 161 | recv_info.rcv_sid, 162 | recv_info.rcv_ssn, 163 | recv_info.rcv_tsn, 164 | ntohl(recv_info.rcv_ppid)); 165 | 166 | if (flags & MSG_NOTIFICATION) { 167 | OnNotification((union sctp_notification *)data, len); 168 | } else { 169 | std::cout << "Got msg of size: " << len << "\n"; 170 | OnMsgReceived((const uint8_t *)data, len, recv_info.rcv_sid, ntohl(recv_info.rcv_ppid)); 171 | } 172 | free(data); 173 | return 0; 174 | } 175 | 176 | void SCTPWrapper::OnMsgReceived(const uint8_t *data, size_t len, int ppid, int sid) { 177 | this->msgReceivedCallback(std::make_shared(data, len), ppid, sid); 178 | } 179 | 180 | bool SCTPWrapper::Initialize() { 181 | usrsctp_init(0, &SCTPWrapper::_OnSCTPForDTLS, &SCTPWrapper::_DebugLog); 182 | usrsctp_sysctl_set_sctp_ecn_enable(0); 183 | usrsctp_register_address(this); 184 | 185 | sock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SCTPWrapper::_OnSCTPForGS, NULL, 0, this); 186 | if (!sock) { 187 | logger->error("Could not create usrsctp_socket. errno={}", errno); 188 | return false; 189 | } 190 | 191 | struct linger linger_opt; 192 | linger_opt.l_onoff = 1; 193 | linger_opt.l_linger = 0; 194 | if (usrsctp_setsockopt(this->sock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt)) == -1) { 195 | logger->error("Could not set socket options for SO_LINGER. errno={}", errno); 196 | return false; 197 | } 198 | 199 | struct sctp_paddrparams peer_param; 200 | memset(&peer_param, 0, sizeof(peer_param)); 201 | peer_param.spp_flags = SPP_PMTUD_DISABLE; 202 | peer_param.spp_pathmtu = 1200; // XXX: Does this need to match the actual MTU? 203 | if (usrsctp_setsockopt(this->sock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_param, sizeof(peer_param)) == -1) { 204 | logger->error("Could not set socket options for SCTP_PEER_ADDR_PARAMS. errno={}", errno); 205 | return false; 206 | } 207 | 208 | struct sctp_assoc_value av; 209 | av.assoc_id = SCTP_ALL_ASSOC; 210 | av.assoc_value = 1; 211 | if (usrsctp_setsockopt(this->sock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)) == -1) { 212 | logger->error("Could not set socket options for SCTP_ENABLE_STREAM_RESET. errno={}", errno); 213 | return false; 214 | } 215 | 216 | uint32_t nodelay = 1; 217 | if (usrsctp_setsockopt(this->sock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)) == -1) { 218 | logger->error("Could not set socket options for SCTP_NODELAY. errno={}", errno); 219 | return false; 220 | } 221 | 222 | /* Enable the events of interest */ 223 | struct sctp_event event; 224 | memset(&event, 0, sizeof(event)); 225 | event.se_assoc_id = SCTP_ALL_ASSOC; 226 | event.se_on = 1; 227 | int num_events = sizeof(interested_events) / sizeof(uint16_t); 228 | for (int i = 0; i < num_events; i++) { 229 | event.se_type = interested_events[i]; 230 | if (usrsctp_setsockopt(this->sock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) == -1) { 231 | logger->error("Could not set socket options for SCTP_EVENT {}. errno={}", i, errno); 232 | return false; 233 | } 234 | } 235 | 236 | struct sctp_initmsg init_msg; 237 | memset(&init_msg, 0, sizeof(init_msg)); 238 | init_msg.sinit_num_ostreams = MAX_OUT_STREAM; 239 | init_msg.sinit_max_instreams = MAX_IN_STREAM; 240 | if (usrsctp_setsockopt(this->sock, IPPROTO_SCTP, SCTP_INITMSG, &init_msg, sizeof(init_msg)) == -1) { 241 | logger->error("Could not set socket options for SCTP_INITMSG. errno={}", errno); 242 | return false; 243 | } 244 | 245 | struct sockaddr_conn sconn; 246 | sconn.sconn_family = AF_CONN; 247 | sconn.sconn_port = htons(remote_port); 248 | sconn.sconn_addr = (void *)this; 249 | #ifdef HAVE_SCONN_LEN 250 | sconn.sconn_len = sizeof(struct sockaddr_conn); 251 | #endif 252 | 253 | if (usrsctp_bind(this->sock, (struct sockaddr *)&sconn, sizeof(sconn)) == -1) { 254 | logger->error("Could not usrsctp_bind. errno={}", errno); 255 | return false; 256 | } 257 | 258 | return true; 259 | } 260 | 261 | void SCTPWrapper::Start() { 262 | if (started) { 263 | logger->error("Start() - already started!"); 264 | return; 265 | } 266 | 267 | SPDLOG_TRACE(logger, "Start()"); 268 | started = true; 269 | 270 | this->recv_thread = std::thread(&SCTPWrapper::RecvLoop, this); 271 | this->connect_thread = std::thread(&SCTPWrapper::RunConnect, this); 272 | } 273 | 274 | void SCTPWrapper::Stop() { 275 | this->should_stop = true; 276 | 277 | send_queue.Stop(); 278 | recv_queue.Stop(); 279 | 280 | connectCV.notify_one(); // unblock the recv thread in case we never connected 281 | if (this->recv_thread.joinable()) { 282 | this->recv_thread.join(); 283 | } 284 | 285 | if (this->connect_thread.joinable()) { 286 | this->connect_thread.join(); 287 | } 288 | 289 | if (sock) { 290 | usrsctp_shutdown(sock, SHUT_RDWR); 291 | usrsctp_close(sock); 292 | sock = nullptr; 293 | } 294 | } 295 | 296 | void SCTPWrapper::DTLSForSCTP(ChunkPtr chunk) { this->recv_queue.push(chunk); } 297 | 298 | // Send a message to the remote connection 299 | void SCTPWrapper::GSForSCTP(ChunkPtr chunk, uint16_t sid, uint32_t ppid) { 300 | struct sctp_sendv_spa spa = {0}; 301 | 302 | // spa.sendv_flags = SCTP_SEND_SNDINFO_VALID | SCTP_SEND_PRINFO_VALID; 303 | spa.sendv_flags = SCTP_SEND_SNDINFO_VALID; 304 | 305 | spa.sendv_sndinfo.snd_sid = sid; 306 | // spa.sendv_sndinfo.snd_flags = SCTP_EOR | SCTP_UNORDERED; 307 | spa.sendv_sndinfo.snd_flags = SCTP_EOR; 308 | spa.sendv_sndinfo.snd_ppid = htonl(ppid); 309 | 310 | // spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX; 311 | // spa.sendv_prinfo.pr_value = 0; 312 | 313 | int tries = 0; 314 | while (tries < 5) { 315 | if (usrsctp_sendv(this->sock, chunk->Data(), chunk->Length(), NULL, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0) < 0) { 316 | logger->error("FAILED to send, try: {}", tries); 317 | tries += 1; 318 | std::this_thread::sleep_for(std::chrono::seconds(tries)); 319 | } else { 320 | return; 321 | } 322 | } 323 | //tried about 5 times and still no luck 324 | throw std::runtime_error("Send failed"); 325 | } 326 | 327 | void SCTPWrapper::RecvLoop() { 328 | // Util::SetThreadName("SCTP-RecvLoop"); 329 | // NDC ndc("SCTP-RecvLoop"); 330 | 331 | SPDLOG_TRACE(logger, "RunRecv()"); 332 | 333 | { 334 | // We need to wait for the connect thread to send some data 335 | unique_lock l(connectMtx); 336 | while (!this->connectSentData && !this->should_stop) { 337 | connectCV.wait_for(l, chrono::milliseconds(100)); 338 | } 339 | } 340 | 341 | SPDLOG_DEBUG(logger, "RunRecv() sent_data=true"); 342 | 343 | while (!this->should_stop) { 344 | ChunkPtr chunk = this->recv_queue.wait_and_pop(); 345 | if (!chunk) { 346 | return; 347 | } 348 | SPDLOG_DEBUG(logger, "RunRecv() Handling packet of len - {}", chunk->Length()); 349 | usrsctp_conninput(this, chunk->Data(), chunk->Length(), 0); 350 | } 351 | } 352 | 353 | void SCTPWrapper::RunConnect() { 354 | // Util::SetThreadName("SCTP-Connect"); 355 | SPDLOG_TRACE(logger, "RunConnect() port={}", remote_port); 356 | 357 | struct sockaddr_conn sconn; 358 | sconn.sconn_family = AF_CONN; 359 | sconn.sconn_port = htons(remote_port); 360 | sconn.sconn_addr = (void *)this; 361 | #ifdef HAVE_SCONN_LEN 362 | sconn.sconn_len = sizeof((void *)this); 363 | #endif 364 | 365 | // Blocks until connection succeeds/fails 366 | int connect_result = usrsctp_connect(sock, (struct sockaddr *)&sconn, sizeof sconn); 367 | 368 | if ((connect_result < 0) && (errno != EINPROGRESS)) { 369 | SPDLOG_DEBUG(logger, "Connection failed. errno={}", errno); 370 | should_stop = true; 371 | 372 | { 373 | // Unblock the recv thread 374 | unique_lock l(connectMtx); 375 | connectCV.notify_one(); 376 | } 377 | 378 | // TODO let the world know we failed :( 379 | 380 | } else { 381 | SPDLOG_DEBUG(logger, "Connected on port {}", remote_port); 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/librtcdcpp.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017, Andrew Gault, Nick Chadwick and Guillaume Egles. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * C Wrapper around the C++ classes. 30 | */ 31 | --------------------------------------------------------------------------------