├── .gitignore ├── LICENSE ├── Readme.md ├── c ├── CMakeLists.txt └── src │ ├── CMakeLists.txt │ ├── helper │ ├── CMakeLists.txt │ ├── common.c │ ├── common.h │ ├── handler.c │ ├── handler.h │ ├── parameters.c │ ├── parameters.h │ ├── utils.c │ └── utils.h │ └── rawrtc-terminal.c ├── screenshot.png ├── signaling ├── Readme.md ├── requirements.txt └── server.py └── web ├── app.css ├── app.js ├── favicon.ico ├── index.html ├── package.json ├── sdp.js └── webrtc-rawrtc.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | 34 | # Var 35 | /var 36 | 37 | # Build folder 38 | /c/build* 39 | 40 | # NodeJS 41 | /web/node_modules 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, David Bardiau and Lennart Grahl 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # rawrtc-terminal-demo 2 | 3 | A browser terminal that uses WebRTC to punch through NATs. 4 | 5 | ![RAWRTC Terminal Demo Screenshot][screenshot] 6 | 7 | The frontend uses the amazing [xterm.js][xterm-js]. 8 | 9 | ## Introduction 10 | 11 | This demo includes two tightly coupled applications and an optional signalling 12 | server: 13 | 14 | 1. The [web terminal][web-terminal] which can be opened in any modern browser 15 | that supports WebRTC and WebRTC data channels. It represents a browser-based 16 | terminal GUI that needs to be used in combination with the 17 | RAWRTC terminal application. 18 | 2. The [RAWRTC terminal][rawrtc-terminal] application can be used to make a 19 | terminal (or any other program that reads from `stdin` and outputs data to 20 | `stdout`) accessible by using RAWRTC's data channel implementation. IO will 21 | be relayed from/to the web terminal frontend. 22 | 3. A very basic WebSocket signalling server written for Python 3.4+ which can 23 | be used instead of copy & pasting the signalling data from one peer to the 24 | other. Follow the [signalling server's readme][signalling-readme] for 25 | instructions on how to set up the server. 26 | 27 | In the following sections, we will describe how to build the RAWRTC terminal 28 | backend followed by [usage instructions](#usage) for the combination of the two 29 | applications. 30 | 31 | ## Prerequisites 32 | 33 | The following dependencies are required: 34 | 35 | * [cmake][cmake] >= 3.2 36 | * [RAWRTC][rawrtc] 37 | 38 | ### Meson (Alternative Build System) 39 | 40 | ~~If you want to use Meson instead of CMake, you have to install both the Meson 41 | build system and Ninja.~~ Use CMake for now. Meson will be updated later. 42 | 43 | * [meson][meson] 44 | * [ninja][ninja] 45 | 46 | ## Build 47 | 48 | The following instruction will use a custom *prefix* to avoid installing 49 | the necessary dependencies and this library system-wide. 50 | 51 | ### Package Configuration Path 52 | 53 | Make sure you have [set up the package configuration path][rawrtc-pkg-path] to 54 | be able to find the RAWRTC library and its dependencies. 55 | 56 | ### Compile 57 | 58 | #### Meson 59 | 60 | Will be added later. Use Cmake for now. 61 | 62 | #### CMake 63 | 64 | cd /c 65 | mkdir build && cd build 66 | cmake -DCMAKE_INSTALL_PREFIX=${PWD}/prefix .. 67 | make install 68 | 69 | ## Run 70 | 71 | Ensure you have [set up the library path][rawrtc-lib-path] (`LD_LIBRARY_PATH` 72 | environment variable) to be able to find the RAWRTC shared library and its 73 | dependency libraries. In addition, update the `PATH` environment variable 74 | to find the newly built binary: 75 | 76 | export PATH=${PWD}/prefix/bin:${PATH} 77 | 78 | Note: We assume that you are in the `build` directory. 79 | 80 | Now you should be able to print usage information of the RAWRTC terminal 81 | application by invoking 82 | 83 | rawrtc-terminal 84 | 85 | which will output 86 | 87 | Usage: rawrtc-terminal <0|1 (ice-role)> [] [] [] 88 | [ ...] 89 | 90 | Below is a description for the various arguments: 91 | 92 | #### ice-role 93 | 94 | Determines the ICE role to be used by the ICE transport, where `0` means 95 | *controlled* and `1` means *controlling*. For now, the web terminal will 96 | always take the *controlling* role, thus you must use `0` for the RAWRTC 97 | terminal application. 98 | 99 | #### sctp-port 100 | 101 | The port number the internal SCTP stack is supposed to use. Defaults to `5000`. 102 | 103 | Note: It doesn't matter which port you choose unless you want to be able to 104 | debug SCTP messages. In this case, it's easier to distinguish the peers by 105 | their port numbers. 106 | 107 | #### shell 108 | 109 | The binary the data channel's incoming messages will be piped into (`stdin`) and 110 | whose output (`stdout`) will be sent over the data channel to the other peer. 111 | Defaults to `bash`. 112 | 113 | #### ws-uri 114 | 115 | If supplied and set to a valid WebSocket URI, the signalling server and the 116 | WebSocket path supplied in the URI will be used to exchange signalling data. 117 | The URI is split into three parts: `ws:////` 118 | 119 | * *hostname-or-ip*: The hostname or IP of the WebSocket server. 120 | * *channel*: A channel name known to both peers. The server buffers and 121 | relays data on a channel from ICE role `0` to `1` and vice versa. 122 | * *ice-role*: The chosen ICE role of the peer. 123 | 124 | If not supplied or not a valid WebSocket URI, the copy & paste mode will be 125 | used. 126 | 127 | #### ice-candidate-type 128 | 129 | If supplied, one or more specific ICE candidate types will be enabled and all 130 | other ICE candidate types will be disabled. Can be one of the following 131 | strings: 132 | 133 | * *host* 134 | * *srflx* 135 | * *prflx* 136 | * *relay* 137 | 138 | Note that this has no effect on the gathering policy. The candidates will be 139 | gathered but they will simply be ignored by the tool. 140 | 141 | If not supplied, all ICE candidate types are enabled. 142 | 143 | ### Usage 144 | 145 | Before we can go ahead, we need to choose between two modes: 146 | 147 | * **Copy & Paste mode**: Signalling data will be exchanged using copy & paste. 148 | This is the default mode. 149 | * **WebSocket mode**: In this mode, the various parameters will be exchanged 150 | using a simple WebSocket-based signalling server that relays data. The mode 151 | can be activated by supplying a valid WebSocket URI which has been explained 152 | in the [`ws-uri` argument description](#ws-uri). 153 | 154 | 1. Open the [web terminal][web-terminal] in a WebRTC data channel capable 155 | browser. 156 | 2. Start the RAWRTC terminal application. 157 | 3. Exchange the signalling data: 158 | * In **Copy & Paste mode**, copy the JSON blob after `Local Parameters:` 159 | from the RAWRTC terminal application into the web terminal. Copy the web 160 | terminal's JSON blob into the RAWRTC terminal application. Press *Enter* 161 | in the RAWRTC terminal application. 162 | * In **WebSocket mode**, supply the RAWRTC terminal's WebSocket URI as an 163 | argument when starting the application and paste the web terminal's 164 | WebSocket URI into the web terminal. 165 | 4. Done! Enjoy your WebRTC remote terminal. 166 | 167 | [screenshot]: screenshot.png "RAWRTC Terminal Demo Screenshot" 168 | [xterm-js]: https://github.com/sourcelair/xterm.js 169 | 170 | [cmake]: https://cmake.org 171 | [rawrtc]: https://github.com/rawrtc/rawrtc 172 | [meson]: https://github.com/mesonbuild/meson 173 | [ninja]: https://ninja-build.org 174 | 175 | [web-terminal]: web/index.html 176 | [rawrtc-terminal]: ./c/src/rawrtc-terminal.c 177 | [signalling-readme]: ./signaling/Readme.md 178 | [rawrtc-pkg-path]: https://github.com/rawrtc/rawrtc#package-configuration-path 179 | [rawrtc-lib-path]: https://github.com/rawrtc/rawrtc#run 180 | -------------------------------------------------------------------------------- /c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project 2 | cmake_minimum_required(VERSION 3.2) 3 | project(rawrtc-terminal-demo 4 | VERSION 0.0.1) 5 | set(PROJECT_DESCRIPTION 6 | "A browser terminal that punches through NATs using the force of RAWRTC") 7 | set(PROJECT_URL 8 | "https://github.com/rawrtc/rawrtc-terminal-demo") 9 | 10 | # Debug build type as default 11 | if (NOT CMAKE_BUILD_TYPE) 12 | message(STATUS "No build type selected, using DEBUG") 13 | set(CMAKE_BUILD_TYPE "DEBUG") 14 | endif() 15 | 16 | # Enable verbose output in DEBUG mode 17 | if (${CMAKE_BUILD_TYPE} MATCHES "DEBUG") 18 | message(STATUS "enabling verbose outout") 19 | set(CMAKE_VERBOSE_MAKEFILE on) 20 | endif() 21 | 22 | # Use pkg-config 23 | find_package(PkgConfig REQUIRED) 24 | 25 | # Dependency list 26 | set(rawrtc_terminal_DEP_LIBRARIES) 27 | 28 | # Dependency: libre 29 | pkg_check_modules(LIB_RE REQUIRED "libre >= 0.5.0") 30 | include_directories(${LIB_RE_STATIC_INCLUDE_DIRS} ${LIB_RE_STATIC_INCLUDEDIR}) 31 | link_directories(${LIB_RE_STATIC_LIBRARY_DIRS}) 32 | list(APPEND rawrtc_terminal_DEP_LIBRARIES ${LIB_RE_STATIC_LIBRARIES}) 33 | 34 | # Dependency: librawrtc 35 | pkg_check_modules(LIB_RAWRTC REQUIRED "librawrtc >= 0.0.1") 36 | include_directories(${LIB_RAWRTC_INCLUDE_DIRS} ${LIB_RAWRTC_STATIC_INCLUDEDIR}) 37 | link_directories(${LIB_RAWRTC_LIBRARY_DIRS}) 38 | list(APPEND rawrtc_terminal_DEP_LIBRARIES ${LIB_RAWRTC_LIBRARIES}) 39 | 40 | # Dependency: lutil (forkpty) 41 | list(APPEND rawrtc_terminal_DEP_LIBRARIES "util") 42 | 43 | # Walk through subdirectories 44 | add_subdirectory(src) 45 | -------------------------------------------------------------------------------- /c/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Walk through subdirectories 2 | add_subdirectory(helper) 3 | 4 | # rawrtc-terminal 5 | add_executable(rawrtc-terminal 6 | rawrtc-terminal.c) 7 | target_link_libraries(rawrtc-terminal 8 | ${rawrtc_terminal_DEP_LIBRARIES} 9 | rawrtc-helper) 10 | install(TARGETS rawrtc-terminal 11 | DESTINATION bin) 12 | -------------------------------------------------------------------------------- /c/src/helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Helper sources 2 | set(rawrtc_HELPER 3 | common.c 4 | handler.c 5 | parameters.c 6 | utils.c) 7 | 8 | # Setup helper library for linker 9 | add_library(rawrtc-helper STATIC ${rawrtc_HELPER}) 10 | target_link_libraries(rawrtc-helper 11 | ${rawrtc_terminal_DEP_LIBRARIES}) 12 | -------------------------------------------------------------------------------- /c/src/helper/common.c: -------------------------------------------------------------------------------- 1 | #include // strerror 2 | #include 3 | #include "common.h" 4 | 5 | #define DEBUG_MODULE "helper-common" 6 | #define DEBUG_LEVEL 7 7 | #include 8 | 9 | /* 10 | * Ignore success code list. 11 | */ 12 | enum rawrtc_code const ignore_success[] = {RAWRTC_CODE_SUCCESS}; 13 | size_t const ignore_success_length = ARRAY_SIZE(ignore_success); 14 | 15 | /* 16 | * Function to be called before exiting. 17 | */ 18 | void before_exit() { 19 | // Close 20 | rawrtc_close(true); 21 | 22 | // Check memory leaks 23 | tmr_debug(); 24 | mem_debug(); 25 | } 26 | 27 | /* 28 | * Exit on error code. 29 | */ 30 | void exit_on_error( 31 | enum rawrtc_code const code, 32 | enum rawrtc_code const ignore[], 33 | size_t const n_ignore, 34 | char const* const file, 35 | uint32_t const line 36 | ) { 37 | size_t i; 38 | 39 | // Ignore? 40 | for (i = 0; i < n_ignore; ++i) { 41 | if (code == ignore[i]) { 42 | return; 43 | } 44 | } 45 | 46 | // Handle 47 | switch (code) { 48 | case RAWRTC_CODE_SUCCESS: 49 | return; 50 | case RAWRTC_CODE_NOT_IMPLEMENTED: 51 | DEBUG_WARNING("Not implemented in %s %"PRIu32"\n", 52 | file, line); 53 | return; 54 | default: 55 | DEBUG_WARNING("Error in %s %"PRIu32" (%d): %s\n", 56 | file, line, code, rawrtc_code_to_str(code)); 57 | before_exit(); 58 | exit((int) code); 59 | } 60 | } 61 | 62 | /* 63 | * Exit on POSIX error code. 64 | */ 65 | void exit_on_posix_error( 66 | int code, 67 | char const* const file, 68 | uint32_t line 69 | ) { 70 | if (code != 0) { 71 | DEBUG_WARNING("Error in %s %"PRIu32" (%d): %s\n", file, line, code, strerror(code)); 72 | before_exit(); 73 | exit(code); 74 | } 75 | } 76 | 77 | /* 78 | * Exit with a custom error message. 79 | */ 80 | void exit_with_error( 81 | char const* const file, 82 | uint32_t line, 83 | char const* const formatter, 84 | ... 85 | ) { 86 | char* message; 87 | 88 | // Format message 89 | va_list ap; 90 | va_start(ap, formatter); 91 | re_vsdprintf(&message, formatter, ap); 92 | va_end(ap); 93 | 94 | // Print message 95 | DEBUG_WARNING("%s %"PRIu32": %s\n", file, line, message); 96 | 97 | // Un-reference & bye 98 | mem_deref(message); 99 | before_exit(); 100 | exit(1); 101 | } 102 | 103 | /* 104 | * Check if the ICE candidate type is enabled. 105 | */ 106 | bool ice_candidate_type_enabled( 107 | struct client* const client, 108 | enum rawrtc_ice_candidate_type const type 109 | ) { 110 | char const* const type_str = rawrtc_ice_candidate_type_to_str(type); 111 | size_t i; 112 | 113 | // All enabled? 114 | if (client->n_ice_candidate_types == 0) { 115 | return true; 116 | } 117 | 118 | // Specifically enabled? 119 | for (i = 0; i < client->n_ice_candidate_types; ++i) { 120 | if (str_cmp(client->ice_candidate_types[i], type_str) == 0) { 121 | return true; 122 | } 123 | } 124 | 125 | // Nope 126 | return false; 127 | } 128 | 129 | /* 130 | * Print ICE candidate information. 131 | */ 132 | void print_ice_candidate( 133 | struct rawrtc_ice_candidate* const candidate, 134 | char const * const url, // read-only 135 | struct client* const client 136 | ) { 137 | if (candidate) { 138 | enum rawrtc_code const ignore[] = {RAWRTC_CODE_NO_VALUE}; 139 | enum rawrtc_code error; 140 | char* foundation; 141 | enum rawrtc_ice_protocol protocol; 142 | uint32_t priority; 143 | char* ip; 144 | uint16_t port; 145 | enum rawrtc_ice_candidate_type type; 146 | enum rawrtc_ice_tcp_candidate_type tcp_type; 147 | char const* tcp_type_str = "N/A"; 148 | char* related_address = NULL; 149 | uint16_t related_port = 0; 150 | bool is_enabled; 151 | 152 | // Get candidate information 153 | EOE(rawrtc_ice_candidate_get_foundation(&foundation, candidate)); 154 | EOE(rawrtc_ice_candidate_get_protocol(&protocol, candidate)); 155 | EOE(rawrtc_ice_candidate_get_priority(&priority, candidate)); 156 | EOE(rawrtc_ice_candidate_get_ip(&ip, candidate)); 157 | EOE(rawrtc_ice_candidate_get_port(&port, candidate)); 158 | EOE(rawrtc_ice_candidate_get_type(&type, candidate)); 159 | error = rawrtc_ice_candidate_get_tcp_type(&tcp_type, candidate); 160 | switch (error) { 161 | case RAWRTC_CODE_SUCCESS: 162 | tcp_type_str = rawrtc_ice_tcp_candidate_type_to_str(tcp_type); 163 | break; 164 | case RAWRTC_CODE_NO_VALUE: 165 | break; 166 | default: 167 | EOE(error); 168 | break; 169 | } 170 | EOEIGN(rawrtc_ice_candidate_get_related_address(&related_address, candidate), ignore); 171 | EOEIGN(rawrtc_ice_candidate_get_related_port(&related_port, candidate), ignore); 172 | is_enabled = ice_candidate_type_enabled(client, type); 173 | 174 | // Print candidate 175 | dbg_printf( 176 | is_enabled ? DBG_INFO : DBG_DEBUG, 177 | "(%s) ICE gatherer local candidate: foundation=%s, protocol=%s, priority=%"PRIu32"" 178 | ", ip=%s, port=%"PRIu16", type=%s, tcp-type=%s, related-address=%s," 179 | "related-port=%"PRIu16"; URL: %s; %s\n", 180 | client->name, foundation, rawrtc_ice_protocol_to_str(protocol), priority, ip, port, 181 | rawrtc_ice_candidate_type_to_str(type), tcp_type_str, 182 | related_address ? related_address : "N/A", related_port, url ? url : "N/A", 183 | is_enabled ? "enabled" : "disabled"); 184 | 185 | // Unreference 186 | mem_deref(related_address); 187 | mem_deref(ip); 188 | mem_deref(foundation); 189 | } else { 190 | DEBUG_INFO("(%s) ICE gatherer last local candidate\n", client->name); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /c/src/helper/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | enum { 5 | PARAMETERS_MAX_LENGTH = 8192, 6 | }; 7 | 8 | /* 9 | * SCTP parameters that need to be negotiated. 10 | */ 11 | struct sctp_parameters { 12 | struct rawrtc_sctp_capabilities* capabilities; 13 | uint16_t port; 14 | }; 15 | 16 | /* 17 | * Client structure. Can be extended. 18 | */ 19 | struct client { 20 | char* name; 21 | char** ice_candidate_types; 22 | size_t n_ice_candidate_types; 23 | }; 24 | 25 | /* 26 | * Data channel helper structure. Can be extended. 27 | */ 28 | struct data_channel_helper { 29 | struct le le; 30 | struct rawrtc_data_channel* channel; 31 | char* label; 32 | struct client* client; 33 | void* arg; 34 | }; 35 | 36 | /* 37 | * Ignore success code list. 38 | */ 39 | extern enum rawrtc_code const ignore_success[]; 40 | extern size_t const ignore_success_length; 41 | 42 | /* 43 | * Helper macros for exiting with error messages. 44 | */ 45 | #define EOE(code) exit_on_error(code, ignore_success, ignore_success_length, __FILE__, __LINE__) 46 | #define EOEIGN(code, ignore) exit_on_error(code, ignore, ARRAY_SIZE(ignore), __FILE__, __LINE__) 47 | #define EOR(code) exit_on_posix_error(code, __FILE__, __LINE__) 48 | #define EOP(code) exit_on_posix_error((code == -1) ? errno : 0, __FILE__, __LINE__) 49 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (__GNUC__ >= 3) 50 | #define EWE(...) exit_with_error(__FILE__, __LINE__, __VA_ARGS__) 51 | #elif defined(__GNUC__) 52 | #define EWE(args...) exit_with_error(__FILE__, __LINE__, args) 53 | #endif 54 | 55 | /* 56 | * Function to be called before exiting. 57 | */ 58 | void before_exit(); 59 | 60 | /* 61 | * Exit on error code. 62 | */ 63 | void exit_on_error( 64 | enum rawrtc_code const code, 65 | enum rawrtc_code const ignore[], 66 | size_t const n_ignore, 67 | char const* const file, 68 | uint32_t const line 69 | ); 70 | 71 | /* 72 | * Exit on POSIX error code. 73 | */ 74 | void exit_on_posix_error( 75 | int code, 76 | char const* const file, 77 | uint32_t line 78 | ); 79 | 80 | /* 81 | * Exit with a custom error message. 82 | */ 83 | void exit_with_error( 84 | char const* const file, 85 | uint32_t line, 86 | char const* const formatter, 87 | ... 88 | ); 89 | 90 | /* 91 | * Check if the ICE candidate type is enabled. 92 | */ 93 | bool ice_candidate_type_enabled( 94 | struct client* const client, 95 | enum rawrtc_ice_candidate_type const type 96 | ); 97 | 98 | /* 99 | * Print ICE candidate information. 100 | */ 101 | void print_ice_candidate( 102 | struct rawrtc_ice_candidate* const candidate, 103 | char const * const url, // read-only 104 | struct client* const client 105 | ); 106 | -------------------------------------------------------------------------------- /c/src/helper/handler.c: -------------------------------------------------------------------------------- 1 | #include // strlen 2 | #include 3 | #include "common.h" 4 | #include "utils.h" 5 | #include "handler.h" 6 | 7 | #define DEBUG_MODULE "helper-handler" 8 | #define DEBUG_LEVEL 7 9 | #include 10 | 11 | /* 12 | * Print the ICE gatherer's state. 13 | */ 14 | void default_ice_gatherer_state_change_handler( 15 | enum rawrtc_ice_gatherer_state const state, // read-only 16 | void* const arg // will be casted to `struct client*` 17 | ) { 18 | struct client* const client = arg; 19 | char const * const state_name = rawrtc_ice_gatherer_state_to_name(state); 20 | (void) arg; 21 | DEBUG_PRINTF("(%s) ICE gatherer state: %s\n", client->name, state_name); 22 | } 23 | 24 | /* 25 | * Print the ICE gatherer's error event. 26 | */ 27 | void default_ice_gatherer_error_handler( 28 | struct rawrtc_ice_candidate* const host_candidate, // read-only, nullable 29 | char const * const url, // read-only 30 | uint16_t const error_code, // read-only 31 | char const * const error_text, // read-only 32 | void* const arg // will be casted to `struct client*` 33 | ) { 34 | struct client* const client = arg; 35 | (void) host_candidate; (void) error_code; (void) arg; 36 | DEBUG_PRINTF("(%s) ICE gatherer error, URL: %s, reason: %s\n", client->name, url, error_text); 37 | } 38 | 39 | /* 40 | * Print the newly gatherered local candidate. 41 | */ 42 | void default_ice_gatherer_local_candidate_handler( 43 | struct rawrtc_ice_candidate* const candidate, 44 | char const * const url, // read-only 45 | void* const arg // will be casted to `struct client*` 46 | ) { 47 | struct client* const client = arg; 48 | (void) candidate; (void) arg; 49 | print_ice_candidate(candidate, url, client); 50 | } 51 | 52 | /* 53 | * Print the ICE transport's state. 54 | */ 55 | void default_ice_transport_state_change_handler( 56 | enum rawrtc_ice_transport_state const state, 57 | void* const arg // will be casted to `struct client*` 58 | ) { 59 | struct client* const client = arg; 60 | char const * const state_name = rawrtc_ice_transport_state_to_name(state); 61 | (void) arg; 62 | DEBUG_PRINTF("(%s) ICE transport state: %s\n", client->name, state_name); 63 | } 64 | 65 | /* 66 | * Print the ICE candidate pair change event. 67 | */ 68 | void default_ice_transport_candidate_pair_change_handler( 69 | struct rawrtc_ice_candidate* const local, // read-only 70 | struct rawrtc_ice_candidate* const remote, // read-only 71 | void* const arg // will be casted to `struct client*` 72 | ) { 73 | struct client* const client = arg; 74 | (void) local; (void) remote; 75 | DEBUG_PRINTF("(%s) ICE transport candidate pair change\n", client->name); 76 | } 77 | 78 | /* 79 | * Print the DTLS transport's state. 80 | */ 81 | void default_dtls_transport_state_change_handler( 82 | enum rawrtc_dtls_transport_state const state, // read-only 83 | void* const arg // will be casted to `struct client*` 84 | ) { 85 | struct client* const client = arg; 86 | char const * const state_name = rawrtc_dtls_transport_state_to_name(state); 87 | DEBUG_PRINTF("(%s) DTLS transport state change: %s\n", client->name, state_name); 88 | } 89 | 90 | /* 91 | * Print the DTLS transport's error event. 92 | */ 93 | void default_dtls_transport_error_handler( 94 | /* TODO: error.message (probably from OpenSSL) */ 95 | void* const arg // will be casted to `struct client*` 96 | ) { 97 | struct client* const client = arg; 98 | // TODO: Print error message 99 | DEBUG_PRINTF("(%s) DTLS transport error: %s\n", client->name, "???"); 100 | } 101 | 102 | /* 103 | * Print the SCTP transport's state. 104 | */ 105 | void default_sctp_transport_state_change_handler( 106 | enum rawrtc_sctp_transport_state const state, 107 | void* const arg // will be casted to `struct client*` 108 | ) { 109 | struct client* const client = arg; 110 | char const * const state_name = rawrtc_sctp_transport_state_to_name(state); 111 | DEBUG_PRINTF("(%s) SCTP transport state change: %s\n", client->name, state_name); 112 | } 113 | 114 | /* 115 | * Print the newly created data channel's parameter. 116 | */ 117 | void default_data_channel_handler( 118 | struct rawrtc_data_channel* const channel, // read-only, MUST be referenced when used 119 | void* const arg // will be casted to `struct client*` 120 | ) { 121 | struct client* const client = arg; 122 | struct rawrtc_data_channel_parameters* parameters; 123 | enum rawrtc_code const ignore[] = {RAWRTC_CODE_NO_VALUE}; 124 | char* label = NULL; 125 | 126 | // Get data channel label and protocol 127 | EOE(rawrtc_data_channel_get_parameters(¶meters, channel)); 128 | EOEIGN(rawrtc_data_channel_parameters_get_label(&label, parameters), ignore); 129 | DEBUG_INFO("(%s) New data channel instance: %s\n", client->name, label ? label : "N/A"); 130 | mem_deref(label); 131 | mem_deref(parameters); 132 | } 133 | 134 | /* 135 | * Print the data channel open event. 136 | */ 137 | void default_data_channel_open_handler( 138 | void* const arg // will be casted to `struct data_channel_helper*` 139 | ) { 140 | struct data_channel_helper* const channel = arg; 141 | struct client* const client = channel->client; 142 | DEBUG_PRINTF("(%s) Data channel open: %s\n", client->name, channel->label); 143 | } 144 | 145 | /* 146 | * Print the data channel buffered amount low event. 147 | */ 148 | void default_data_channel_buffered_amount_low_handler( 149 | void* const arg // will be casted to `struct data_channel_helper*` 150 | ) { 151 | struct data_channel_helper* const channel = arg; 152 | struct client* const client = channel->client; 153 | DEBUG_PRINTF("(%s) Data channel buffered amount low: %s\n", client->name, channel->label); 154 | } 155 | 156 | /* 157 | * Print the data channel error event. 158 | */ 159 | void default_data_channel_error_handler( 160 | void* const arg // will be casted to `struct data_channel_helper*` 161 | ) { 162 | struct data_channel_helper* const channel = arg; 163 | struct client* const client = channel->client; 164 | DEBUG_PRINTF("(%s) Data channel error: %s\n", client->name, channel->label); 165 | } 166 | 167 | /* 168 | * Print the data channel close event. 169 | */ 170 | void default_data_channel_close_handler( 171 | void* const arg // will be casted to `struct data_channel_helper*` 172 | ) { 173 | struct data_channel_helper* const channel = arg; 174 | struct client* const client = channel->client; 175 | DEBUG_PRINTF("(%s) Data channel closed: %s\n", client->name, channel->label); 176 | } 177 | 178 | /* 179 | * Stop the main loop. 180 | */ 181 | void default_signal_handler( 182 | int sig 183 | ) { 184 | DEBUG_INFO("Got signal: %d, terminating...\n", sig); 185 | re_cancel(); 186 | } 187 | -------------------------------------------------------------------------------- /c/src/helper/handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "common.h" 4 | 5 | /* 6 | * Print the ICE gatherer's state. 7 | */ 8 | void default_ice_gatherer_state_change_handler( 9 | enum rawrtc_ice_gatherer_state const state, // read-only 10 | void* const arg 11 | ); 12 | 13 | /* 14 | * Print the ICE gatherer's error event. 15 | */ 16 | void default_ice_gatherer_error_handler( 17 | struct rawrtc_ice_candidate* const host_candidate, // read-only, nullable 18 | char const * const url, // read-only 19 | uint16_t const error_code, // read-only 20 | char const * const error_text, // read-only 21 | void* const arg // will be casted to `struct client*` 22 | ); 23 | 24 | /* 25 | * Print the newly gatherered local candidate. 26 | * Will print local parameters on stdout in case the client is not 27 | * used in loopback mode. 28 | */ 29 | void default_ice_gatherer_local_candidate_handler( 30 | struct rawrtc_ice_candidate* const candidate, 31 | char const * const url, // read-only 32 | void* const arg // will be casted to `struct client*` 33 | ); 34 | 35 | /* 36 | * Print the ICE transport's state. 37 | */ 38 | void default_ice_transport_state_change_handler( 39 | enum rawrtc_ice_transport_state const state, 40 | void* const arg // will be casted to `struct client*` 41 | ); 42 | 43 | /* 44 | * Print the ICE candidate pair change event. 45 | */ 46 | void default_ice_transport_candidate_pair_change_handler( 47 | struct rawrtc_ice_candidate* const local, // read-only 48 | struct rawrtc_ice_candidate* const remote, // read-only 49 | void* const arg // will be casted to `struct client*` 50 | ); 51 | 52 | /* 53 | * Print the DTLS transport's state. 54 | */ 55 | void default_dtls_transport_state_change_handler( 56 | enum rawrtc_dtls_transport_state const state, // read-only 57 | void* const arg // will be casted to `struct client*` 58 | ); 59 | 60 | /* 61 | * Print the DTLS transport's error event. 62 | */ 63 | void default_dtls_transport_error_handler( 64 | /* TODO: error.message (probably from OpenSSL) */ 65 | void* const arg // will be casted to `struct client*` 66 | ); 67 | 68 | /* 69 | * Print the SCTP transport's state. 70 | */ 71 | void default_sctp_transport_state_change_handler( 72 | enum rawrtc_sctp_transport_state const state, 73 | void* const arg // will be casted to `struct client*` 74 | ); 75 | 76 | /* 77 | * Print the newly created data channel's parameter. 78 | */ 79 | void default_data_channel_handler( 80 | struct rawrtc_data_channel* const data_channel, // read-only, MUST be referenced when used 81 | void* const arg // will be casted to `struct data_channel_helper*` 82 | ); 83 | 84 | /* 85 | * Print the data channel open event. 86 | */ 87 | void default_data_channel_open_handler( 88 | void* const arg // will be casted to `struct data_channel_helper*` 89 | ); 90 | 91 | /* 92 | * Print the data channel buffered amount low event. 93 | */ 94 | void default_data_channel_buffered_amount_low_handler( 95 | void* const arg // will be casted to `struct data_channel_helper*` 96 | ); 97 | 98 | /* 99 | * Print the data channel error event. 100 | */ 101 | void default_data_channel_error_handler( 102 | void* const arg // will be casted to `struct data_channel_helper*` 103 | ); 104 | 105 | /* 106 | * Print the data channel close event. 107 | */ 108 | void default_data_channel_close_handler( 109 | void* const arg // will be casted to `struct data_channel_helper*` 110 | ); 111 | 112 | /* 113 | * Stop the main loop. 114 | */ 115 | void default_signal_handler( 116 | int sig 117 | ); 118 | -------------------------------------------------------------------------------- /c/src/helper/parameters.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | #include "utils.h" 5 | #include "parameters.h" 6 | 7 | //#define DEBUG_MODULE "helper-parameters" 8 | //#define DEBUG_LEVEL 7 9 | //#include 10 | 11 | /* 12 | * Set ICE parameters in dictionary. 13 | */ 14 | void set_ice_parameters( 15 | struct rawrtc_ice_parameters* const parameters, 16 | struct odict* const dict 17 | ) { 18 | char* username_fragment; 19 | char* password; 20 | bool ice_lite; 21 | 22 | // Get values 23 | EOE(rawrtc_ice_parameters_get_username_fragment(&username_fragment, parameters)); 24 | EOE(rawrtc_ice_parameters_get_password(&password, parameters)); 25 | EOE(rawrtc_ice_parameters_get_ice_lite(&ice_lite, parameters)); 26 | 27 | // Set ICE parameters 28 | EOR(odict_entry_add(dict, "usernameFragment", ODICT_STRING, username_fragment)); 29 | EOR(odict_entry_add(dict, "password", ODICT_STRING, password)); 30 | EOR(odict_entry_add(dict, "iceLite", ODICT_BOOL, ice_lite)); 31 | 32 | // Un-reference values 33 | mem_deref(password); 34 | mem_deref(username_fragment); 35 | } 36 | 37 | /* 38 | * Set ICE candidates in dictionary. 39 | */ 40 | void set_ice_candidates( 41 | struct rawrtc_ice_candidates* const parameters, 42 | struct odict* const array 43 | ) { 44 | size_t i; 45 | struct odict* node; 46 | 47 | // Set ICE candidates 48 | for (i = 0; i < parameters->n_candidates; ++i) { 49 | enum rawrtc_code error; 50 | struct rawrtc_ice_candidate* const candidate = parameters->candidates[i]; 51 | char* foundation; 52 | uint32_t priority; 53 | char* ip; 54 | enum rawrtc_ice_protocol protocol; 55 | uint16_t port; 56 | enum rawrtc_ice_candidate_type type; 57 | enum rawrtc_ice_tcp_candidate_type tcp_type = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE; 58 | char* related_address = NULL; 59 | uint16_t related_port = 0; 60 | char* key; 61 | 62 | // Create object 63 | EOR(odict_alloc(&node, 16)); 64 | 65 | // Get values 66 | EOE(rawrtc_ice_candidate_get_foundation(&foundation, candidate)); 67 | EOE(rawrtc_ice_candidate_get_priority(&priority, candidate)); 68 | EOE(rawrtc_ice_candidate_get_ip(&ip, candidate)); 69 | EOE(rawrtc_ice_candidate_get_protocol(&protocol, candidate)); 70 | EOE(rawrtc_ice_candidate_get_port(&port, candidate)); 71 | EOE(rawrtc_ice_candidate_get_type(&type, candidate)); 72 | error = rawrtc_ice_candidate_get_tcp_type(&tcp_type, candidate); 73 | EOE(error == RAWRTC_CODE_NO_VALUE ? RAWRTC_CODE_SUCCESS : error); 74 | error = rawrtc_ice_candidate_get_related_address(&related_address, candidate); 75 | EOE(error == RAWRTC_CODE_NO_VALUE ? RAWRTC_CODE_SUCCESS : error); 76 | error = rawrtc_ice_candidate_get_related_port(&related_port, candidate); 77 | EOE(error == RAWRTC_CODE_NO_VALUE ? RAWRTC_CODE_SUCCESS : error); 78 | 79 | // Set ICE candidate values 80 | EOR(odict_entry_add(node, "foundation", ODICT_STRING, foundation)); 81 | EOR(odict_entry_add(node, "priority", ODICT_INT, (int64_t) priority)); 82 | EOR(odict_entry_add(node, "ip", ODICT_STRING, ip)); 83 | EOR(odict_entry_add(node, "protocol", ODICT_STRING, rawrtc_ice_protocol_to_str(protocol))); 84 | EOR(odict_entry_add(node, "port", ODICT_INT, (int64_t) port)); 85 | EOR(odict_entry_add(node, "type", ODICT_STRING, rawrtc_ice_candidate_type_to_str(type))); 86 | if (protocol == RAWRTC_ICE_PROTOCOL_TCP) { 87 | EOR(odict_entry_add(node, "tcpType", ODICT_STRING, 88 | rawrtc_ice_tcp_candidate_type_to_str(tcp_type))); 89 | } 90 | if (related_address) { 91 | EOR(odict_entry_add(node, "relatedAddress", ODICT_STRING, related_address)); 92 | } 93 | if (related_port) { 94 | EOR(odict_entry_add(node, "relatedPort", ODICT_INT, (int64_t) related_port)); 95 | } 96 | 97 | // Add to array 98 | EOE(rawrtc_sdprintf(&key, "%zu", i)); 99 | EOR(odict_entry_add(array, key, ODICT_OBJECT, node)); 100 | 101 | // Un-reference values 102 | mem_deref(key); 103 | mem_deref(related_address); 104 | mem_deref(ip); 105 | mem_deref(foundation); 106 | mem_deref(node); 107 | } 108 | } 109 | 110 | /* 111 | * Set DTLS parameters in dictionary. 112 | */ 113 | void set_dtls_parameters( 114 | struct rawrtc_dtls_parameters* const parameters, 115 | struct odict* const dict 116 | ) { 117 | enum rawrtc_dtls_role role; 118 | struct odict* array; 119 | struct odict* node; 120 | struct rawrtc_dtls_fingerprints* fingerprints; 121 | size_t i; 122 | 123 | // Get and set DTLS role 124 | EOE(rawrtc_dtls_parameters_get_role(&role, parameters)); 125 | EOR(odict_entry_add(dict, "role", ODICT_STRING, rawrtc_dtls_role_to_str(role))); 126 | 127 | // Create array 128 | EOR(odict_alloc(&array, 16)); 129 | 130 | // Get and set fingerprints 131 | EOE(rawrtc_dtls_parameters_get_fingerprints(&fingerprints, parameters)); 132 | for (i = 0; i < fingerprints->n_fingerprints; ++i) { 133 | struct rawrtc_dtls_fingerprint* const fingerprint = 134 | fingerprints->fingerprints[i]; 135 | enum rawrtc_certificate_sign_algorithm sign_algorithm; 136 | char* value; 137 | char* key; 138 | 139 | // Create object 140 | EOR(odict_alloc(&node, 16)); 141 | 142 | // Get values 143 | EOE(rawrtc_dtls_fingerprint_get_sign_algorithm(&sign_algorithm, fingerprint)); 144 | EOE(rawrtc_dtls_fingerprint_get_value(&value, fingerprint)); 145 | 146 | // Set fingerprint values 147 | EOR(odict_entry_add(node, "algorithm", ODICT_STRING, 148 | rawrtc_certificate_sign_algorithm_to_str(sign_algorithm))); 149 | EOR(odict_entry_add(node, "value", ODICT_STRING, value)); 150 | 151 | // Add to array 152 | EOE(rawrtc_sdprintf(&key, "%zu", i)); 153 | EOR(odict_entry_add(array, key, ODICT_OBJECT, node)); 154 | 155 | // Un-reference values 156 | mem_deref(key); 157 | mem_deref(value); 158 | mem_deref(node); 159 | } 160 | 161 | // Un-reference fingerprints 162 | mem_deref(fingerprints); 163 | 164 | // Add array to object 165 | EOR(odict_entry_add(dict, "fingerprints", ODICT_ARRAY, array)); 166 | mem_deref(array); 167 | } 168 | 169 | /* 170 | * Set SCTP parameters in dictionary. 171 | */ 172 | void set_sctp_parameters( 173 | struct rawrtc_sctp_transport* const transport, 174 | struct sctp_parameters* const parameters, 175 | struct odict* const dict 176 | ) { 177 | uint64_t max_message_size; 178 | uint16_t port; 179 | 180 | // Get values 181 | EOE(rawrtc_sctp_capabilities_get_max_message_size(&max_message_size, parameters->capabilities)); 182 | EOE(rawrtc_sctp_transport_get_port(&port, transport)); 183 | 184 | // Ensure maximum message size fits into int64 185 | if (max_message_size > INT64_MAX) { 186 | EOE(RAWRTC_CODE_INSUFFICIENT_SPACE); 187 | } 188 | 189 | // Set ICE parameters 190 | EOR(odict_entry_add(dict, "maxMessageSize", ODICT_INT, (int64_t) max_message_size)); 191 | EOR(odict_entry_add(dict, "port", ODICT_INT, (int64_t) port)); 192 | } 193 | 194 | /* 195 | * Get ICE parameters from dictionary. 196 | */ 197 | enum rawrtc_code get_ice_parameters( 198 | struct rawrtc_ice_parameters** const parametersp, 199 | struct odict* const dict 200 | ) { 201 | enum rawrtc_code error = RAWRTC_CODE_SUCCESS; 202 | char* username_fragment; 203 | char* password; 204 | bool ice_lite; 205 | 206 | // Get ICE parameters 207 | error |= dict_get_entry(&username_fragment, dict, "usernameFragment", ODICT_STRING, true); 208 | error |= dict_get_entry(&password, dict, "password", ODICT_STRING, true); 209 | error |= dict_get_entry(&ice_lite, dict, "iceLite", ODICT_BOOL, true); 210 | if (error) { 211 | return error; 212 | } 213 | 214 | // Create ICE parameters instance 215 | return rawrtc_ice_parameters_create(parametersp, username_fragment, password, ice_lite); 216 | } 217 | 218 | static void ice_candidates_destroy( 219 | void* arg 220 | ) { 221 | struct rawrtc_ice_candidates* const candidates = arg; 222 | size_t i; 223 | 224 | // Un-reference each item 225 | for (i = 0; i < candidates->n_candidates; ++i) { 226 | mem_deref(candidates->candidates[i]); 227 | } 228 | } 229 | 230 | /* 231 | * Get ICE candidates from dictionary. 232 | * Filter by enabled ICE candidate types if `client` argument is set to 233 | * non-NULL. 234 | */ 235 | enum rawrtc_code get_ice_candidates( 236 | struct rawrtc_ice_candidates** const candidatesp, 237 | struct odict* const dict, 238 | struct client* const client 239 | ) { 240 | size_t n; 241 | struct rawrtc_ice_candidates* candidates; 242 | enum rawrtc_code error = RAWRTC_CODE_SUCCESS; 243 | struct le* le; 244 | 245 | // Get length 246 | n = list_count(&dict->lst); 247 | 248 | // Allocate & set length immediately 249 | // Note: We allocate more than we need in case ICE candidate types are being filtered but... meh 250 | candidates = mem_zalloc(sizeof(*candidates) + (sizeof(struct rawrtc_ice_candidate*) * n), 251 | ice_candidates_destroy); 252 | if (!candidates) { 253 | EWE("No memory to allocate ICE candidates array"); 254 | } 255 | candidates->n_candidates = 0; 256 | 257 | // Get ICE candidates 258 | for (le = list_head(&dict->lst); le != NULL; le = le->next) { 259 | struct odict* const node = ((struct odict_entry*) le->data)->u.odict; 260 | char const* type_str = NULL; 261 | enum rawrtc_ice_candidate_type type; 262 | char* foundation; 263 | uint32_t priority; 264 | char* ip; 265 | char const* protocol_str = NULL; 266 | enum rawrtc_ice_protocol protocol; 267 | uint16_t port; 268 | char const* tcp_type_str = NULL; 269 | enum rawrtc_ice_tcp_candidate_type tcp_type = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE; 270 | char* related_address = NULL; 271 | uint16_t related_port = 0; 272 | struct rawrtc_ice_candidate* candidate; 273 | 274 | // Get ICE candidate 275 | error |= dict_get_entry(&type_str, node, "type", ODICT_STRING, true); 276 | error |= rawrtc_str_to_ice_candidate_type(&type, type_str); 277 | error |= dict_get_entry(&foundation, node, "foundation", ODICT_STRING, true); 278 | error |= dict_get_uint32(&priority, node, "priority", true); 279 | error |= dict_get_entry(&ip, node, "ip", ODICT_STRING, true); 280 | error |= dict_get_entry(&protocol_str, node, "protocol", ODICT_STRING, true); 281 | error |= rawrtc_str_to_ice_protocol(&protocol, protocol_str); 282 | error |= dict_get_uint16(&port, node, "port", true); 283 | if (protocol == RAWRTC_ICE_PROTOCOL_TCP) { 284 | error |= dict_get_entry(&tcp_type_str, node, "tcpType", ODICT_STRING, true); 285 | error |= rawrtc_str_to_ice_tcp_candidate_type(&tcp_type, tcp_type_str); 286 | } 287 | dict_get_entry(&related_address, node, "relatedAddress", ODICT_STRING, false); 288 | dict_get_uint16(&related_port, node, "relatedPort", false); 289 | if (error) { 290 | goto out; 291 | } 292 | 293 | // Create and add ICE candidate 294 | error = rawrtc_ice_candidate_create( 295 | &candidate, foundation, priority, ip, protocol, port, type, 296 | tcp_type, related_address, related_port); 297 | if (error) { 298 | goto out; 299 | } 300 | 301 | // Print ICE candidate 302 | print_ice_candidate(candidate, NULL, client); 303 | 304 | // Store if ICE candidate type enabled 305 | if (ice_candidate_type_enabled(client, type)) { 306 | candidates->candidates[candidates->n_candidates++] = candidate; 307 | } else { 308 | mem_deref(candidate); 309 | } 310 | } 311 | 312 | out: 313 | if (error) { 314 | mem_deref(candidates); 315 | } else { 316 | // Set pointer 317 | *candidatesp = candidates; 318 | } 319 | return error; 320 | } 321 | 322 | static void dtls_fingerprints_destroy( 323 | void* arg 324 | ) { 325 | struct rawrtc_dtls_fingerprints* const fingerprints = arg; 326 | size_t i; 327 | 328 | // Un-reference each item 329 | for (i = 0; i < fingerprints->n_fingerprints; ++i) { 330 | mem_deref(fingerprints->fingerprints[i]); 331 | } 332 | } 333 | 334 | /* 335 | * Get DTLS parameters from dictionary. 336 | */ 337 | enum rawrtc_code get_dtls_parameters( 338 | struct rawrtc_dtls_parameters** const parametersp, 339 | struct odict* const dict 340 | ) { 341 | size_t n; 342 | struct rawrtc_dtls_parameters* parameters = NULL; 343 | struct rawrtc_dtls_fingerprints* fingerprints; 344 | enum rawrtc_code error; 345 | char const* role_str = NULL; 346 | enum rawrtc_dtls_role role; 347 | struct odict* node; 348 | struct le* le; 349 | size_t i; 350 | 351 | // Get fingerprints array and length 352 | error = dict_get_entry(&node, dict, "fingerprints", ODICT_ARRAY, true); 353 | if (error) { 354 | return error; 355 | } 356 | n = list_count(&node->lst); 357 | 358 | // Allocate & set length immediately 359 | fingerprints = mem_zalloc( 360 | sizeof(*fingerprints) + (sizeof(struct rawrtc_dtls_fingerprints*) * n), 361 | dtls_fingerprints_destroy); 362 | if (!fingerprints) { 363 | EWE("No memory to allocate DTLS fingerprint array"); 364 | } 365 | fingerprints->n_fingerprints = n; 366 | 367 | // Get role 368 | error |= dict_get_entry(&role_str, dict, "role", ODICT_STRING, true); 369 | error |= rawrtc_str_to_dtls_role(&role, role_str); 370 | if (error) { 371 | role = RAWRTC_DTLS_ROLE_AUTO; 372 | } 373 | 374 | // Get fingerprints 375 | for (le = list_head(&node->lst), i = 0; le != NULL; le = le->next, ++i) { 376 | node = ((struct odict_entry*) le->data)->u.odict; 377 | char* algorithm_str = NULL; 378 | enum rawrtc_certificate_sign_algorithm algorithm; 379 | char* value; 380 | 381 | // Get fingerprint 382 | error |= dict_get_entry(&algorithm_str, node, "algorithm", ODICT_STRING, true); 383 | error |= rawrtc_str_to_certificate_sign_algorithm(&algorithm, algorithm_str); 384 | error |= dict_get_entry(&value, node, "value", ODICT_STRING, true); 385 | if (error) { 386 | goto out; 387 | } 388 | 389 | // Create and add fingerprint 390 | error = rawrtc_dtls_fingerprint_create(&fingerprints->fingerprints[i], algorithm, value); 391 | if (error) { 392 | goto out; 393 | } 394 | } 395 | 396 | // Create DTLS parameters 397 | error = rawrtc_dtls_parameters_create( 398 | ¶meters, role, fingerprints->fingerprints, fingerprints->n_fingerprints); 399 | 400 | out: 401 | mem_deref(fingerprints); 402 | 403 | if (error) { 404 | mem_deref(parameters); 405 | } else { 406 | // Set pointer 407 | *parametersp = parameters; 408 | } 409 | return error; 410 | } 411 | 412 | /* 413 | * Get SCTP parameters from dictionary. 414 | */ 415 | enum rawrtc_code get_sctp_parameters( 416 | struct sctp_parameters* const parameters, 417 | struct odict* const dict 418 | ) { 419 | enum rawrtc_code error; 420 | uint64_t max_message_size; 421 | 422 | // Get maximum message size 423 | error = dict_get_entry(&max_message_size, dict, "maxMessageSize", ODICT_INT, true); 424 | if (error) { 425 | return error; 426 | } 427 | 428 | // Get port 429 | error = dict_get_uint16(¶meters->port, dict, "port", false); 430 | if (error && error != RAWRTC_CODE_NO_VALUE) { 431 | // Note: Nothing to do in NO VALUE case as port has been set to 0 by default 432 | return error; 433 | } 434 | 435 | // Create SCTP capabilities instance 436 | return rawrtc_sctp_capabilities_create(¶meters->capabilities, max_message_size); 437 | } 438 | -------------------------------------------------------------------------------- /c/src/helper/parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "common.h" 4 | 5 | /* 6 | * Set ICE parameters in dictionary. 7 | */ 8 | void set_ice_parameters( 9 | struct rawrtc_ice_parameters* const parameters, 10 | struct odict* const dict 11 | ); 12 | 13 | /* 14 | * Set ICE candidates in dictionary. 15 | */ 16 | void set_ice_candidates( 17 | struct rawrtc_ice_candidates* const parameters, 18 | struct odict* const array 19 | ); 20 | 21 | /* 22 | * Set DTLS parameters in dictionary. 23 | */ 24 | void set_dtls_parameters( 25 | struct rawrtc_dtls_parameters* const parameters, 26 | struct odict* const dict 27 | ); 28 | 29 | /* 30 | * Set SCTP parameters in dictionary. 31 | */ 32 | void set_sctp_parameters( 33 | struct rawrtc_sctp_transport* const transport, 34 | struct sctp_parameters* const parameters, 35 | struct odict* const dict 36 | ); 37 | 38 | /* 39 | * Get ICE parameters from dictionary. 40 | */ 41 | enum rawrtc_code get_ice_parameters( 42 | struct rawrtc_ice_parameters** const parametersp, 43 | struct odict* const dict 44 | ); 45 | 46 | /* 47 | * Get ICE candidates from dictionary. 48 | * Filter by enabled ICE candidate types if `client` argument is set to 49 | * non-NULL. 50 | */ 51 | enum rawrtc_code get_ice_candidates( 52 | struct rawrtc_ice_candidates** const candidatesp, 53 | struct odict* const dict, 54 | struct client* const client 55 | ); 56 | 57 | /* 58 | * Get DTLS parameters from dictionary. 59 | */ 60 | enum rawrtc_code get_dtls_parameters( 61 | struct rawrtc_dtls_parameters** const parametersp, 62 | struct odict* const dict 63 | ); 64 | 65 | /* 66 | * Get SCTP parameters from dictionary. 67 | */ 68 | enum rawrtc_code get_sctp_parameters( 69 | struct sctp_parameters* const parameters, 70 | struct odict* const dict 71 | ); 72 | -------------------------------------------------------------------------------- /c/src/helper/utils.c: -------------------------------------------------------------------------------- 1 | #include // strtol 2 | #include // strlen 3 | #include 4 | #include 5 | #include "common.h" 6 | #include "utils.h" 7 | 8 | #define DEBUG_MODULE "helper-utils" 9 | #define DEBUG_LEVEL 7 10 | #include 11 | 12 | /* 13 | * Convert string to uint16. 14 | */ 15 | bool str_to_uint16( 16 | uint16_t* const numberp, 17 | char* const str 18 | ) { 19 | char* end; 20 | unsigned long number = strtoul(str, &end, 10); 21 | 22 | // Check result (this function is insane, srsly...) 23 | if (*end != '\0' || (number == ULONG_MAX && errno == ERANGE)) { 24 | return false; 25 | } 26 | 27 | // Check bounds 28 | #if (ULONG_MAX > UINT16_MAX) 29 | if (number > UINT16_MAX) { 30 | return false; 31 | } 32 | #endif 33 | 34 | // Done 35 | *numberp = (uint16_t) number; 36 | return true; 37 | } 38 | 39 | /* 40 | * Get a dictionary entry and store it in `*valuep`. 41 | */ 42 | enum rawrtc_code dict_get_entry( 43 | void* const valuep, 44 | struct odict* const parent, 45 | char* const key, 46 | enum odict_type const type, 47 | bool required 48 | ) { 49 | struct odict_entry const * entry; 50 | 51 | // Check arguments 52 | if (!valuep || !parent || !key) { 53 | return RAWRTC_CODE_INVALID_ARGUMENT; 54 | } 55 | 56 | // Do lookup 57 | entry = odict_lookup(parent, key); 58 | 59 | // Check for entry 60 | if (!entry) { 61 | if (required) { 62 | DEBUG_WARNING("'%s' missing\n", key); 63 | return RAWRTC_CODE_INVALID_ARGUMENT; 64 | } else { 65 | return RAWRTC_CODE_NO_VALUE; 66 | } 67 | } 68 | 69 | // Check for type 70 | if (entry->type != type) { 71 | DEBUG_WARNING("'%s' is of different type than expected\n", key); 72 | return RAWRTC_CODE_INVALID_ARGUMENT; 73 | } 74 | 75 | // Set value according to type 76 | switch (type) { 77 | case ODICT_OBJECT: 78 | case ODICT_ARRAY: 79 | *((struct odict** const) valuep) = entry->u.odict; 80 | break; 81 | case ODICT_STRING: 82 | *((char** const) valuep) = entry->u.str; 83 | break; 84 | case ODICT_INT: 85 | *((int64_t* const) valuep) = entry->u.integer; 86 | break; 87 | case ODICT_DOUBLE: 88 | *((double* const) valuep) = entry->u.dbl; 89 | break; 90 | case ODICT_BOOL: 91 | *((bool* const) valuep) = entry->u.boolean; 92 | break; 93 | case ODICT_NULL: 94 | *((char** const) valuep) = NULL; // meh! 95 | break; 96 | default: 97 | return RAWRTC_CODE_INVALID_ARGUMENT; 98 | } 99 | 100 | // Done 101 | return RAWRTC_CODE_SUCCESS; 102 | } 103 | 104 | /* 105 | * Get a uint32 entry and store it in `*valuep`. 106 | */ 107 | enum rawrtc_code dict_get_uint32( 108 | uint32_t* const valuep, 109 | struct odict* const parent, 110 | char* const key, 111 | bool required 112 | ) { 113 | int64_t value; 114 | 115 | // Check arguments 116 | if (!valuep || !parent || !key) { 117 | return RAWRTC_CODE_INVALID_ARGUMENT; 118 | } 119 | 120 | // Get int64_t 121 | enum rawrtc_code error = dict_get_entry(&value, parent, key, ODICT_INT, required); 122 | if (error) { 123 | return error; 124 | } 125 | 126 | // Check bounds 127 | if (value < 0 || value > UINT32_MAX) { 128 | return RAWRTC_CODE_INVALID_ARGUMENT; 129 | } 130 | 131 | // Set value & done 132 | *valuep = (uint32_t) value; 133 | return RAWRTC_CODE_SUCCESS; 134 | } 135 | 136 | /* 137 | * Get a uint16 entry and store it in `*valuep`. 138 | */ 139 | enum rawrtc_code dict_get_uint16( 140 | uint16_t* const valuep, 141 | struct odict* const parent, 142 | char* const key, 143 | bool required 144 | ) { 145 | int64_t value; 146 | 147 | // Check arguments 148 | if (!valuep || !parent || !key) { 149 | return RAWRTC_CODE_INVALID_ARGUMENT; 150 | } 151 | 152 | // Get int64_t 153 | enum rawrtc_code error = dict_get_entry(&value, parent, key, ODICT_INT, required); 154 | if (error) { 155 | return error; 156 | } 157 | 158 | // Check bounds 159 | if (value < 0 || value > UINT16_MAX) { 160 | return RAWRTC_CODE_INVALID_ARGUMENT; 161 | } 162 | 163 | // Set value & done 164 | *valuep = (uint16_t) value; 165 | return RAWRTC_CODE_SUCCESS; 166 | } 167 | 168 | /* 169 | * Get JSON from stdin and parse it to a dictionary. 170 | */ 171 | enum rawrtc_code get_json_stdin( 172 | struct odict** const dictp // de-referenced 173 | ) { 174 | char buffer[PARAMETERS_MAX_LENGTH]; 175 | size_t length; 176 | 177 | // Get message from stdin 178 | if (!fgets((char*) buffer, PARAMETERS_MAX_LENGTH, stdin)) { 179 | EWE("Error polling stdin"); 180 | } 181 | length = strlen(buffer); 182 | 183 | // Exit? 184 | if (length == 1 && buffer[0] == '\n') { 185 | return RAWRTC_CODE_NO_VALUE; 186 | } 187 | 188 | // Decode JSON 189 | EOR(json_decode_odict(dictp, 16, buffer, length, 3)); 190 | return RAWRTC_CODE_SUCCESS; 191 | } 192 | 193 | /* 194 | * Get the ICE role from a string. 195 | */ 196 | enum rawrtc_code get_ice_role( 197 | enum rawrtc_ice_role* const rolep, // de-referenced 198 | char const* const str 199 | ) { 200 | // Get ICE role 201 | switch (str[0]) { 202 | case '0': 203 | *rolep = RAWRTC_ICE_ROLE_CONTROLLED; 204 | return RAWRTC_CODE_SUCCESS; 205 | case '1': 206 | *rolep = RAWRTC_ICE_ROLE_CONTROLLING; 207 | return RAWRTC_CODE_SUCCESS; 208 | default: 209 | return RAWRTC_CODE_INVALID_ARGUMENT; 210 | } 211 | } 212 | 213 | static void data_channel_helper_destroy( 214 | void* arg 215 | ) { 216 | struct data_channel_helper* const channel = arg; 217 | 218 | // Unset handler argument & handlers of the channel 219 | EOE(rawrtc_data_channel_unset_handlers(channel->channel)); 220 | 221 | // Remove from list 222 | list_unlink(&channel->le); 223 | 224 | // Un-reference 225 | mem_deref(channel->arg); 226 | mem_deref(channel->label); 227 | mem_deref(channel->channel); 228 | } 229 | 230 | /* 231 | * Create a data channel helper instance from parameters. 232 | */ 233 | void data_channel_helper_create_from_channel( 234 | struct data_channel_helper** const channel_helperp, // de-referenced 235 | struct rawrtc_data_channel* channel, 236 | struct client* const client, 237 | void* const arg // nullable 238 | ) { 239 | enum rawrtc_code error; 240 | struct rawrtc_data_channel_parameters* parameters; 241 | char* label; 242 | 243 | // Allocate 244 | struct data_channel_helper* const channel_helper = 245 | mem_zalloc(sizeof(*channel_helper), data_channel_helper_destroy); 246 | if (!channel_helper) { 247 | EOE(RAWRTC_CODE_NO_MEMORY); 248 | return; 249 | } 250 | 251 | // Get parameters 252 | EOE(rawrtc_data_channel_get_parameters(¶meters, channel)); 253 | 254 | // Get & set label 255 | error = rawrtc_data_channel_parameters_get_label(&label, parameters); 256 | switch (error) { 257 | case RAWRTC_CODE_SUCCESS: 258 | EOE(rawrtc_strdup(&channel_helper->label, label)); 259 | mem_deref(label); 260 | break; 261 | case RAWRTC_CODE_NO_VALUE: 262 | EOE(rawrtc_strdup(&channel_helper->label, "N/A")); 263 | break; 264 | default: 265 | EOE(error); 266 | } 267 | 268 | // Set fields 269 | channel_helper->client = client; 270 | channel_helper->channel = channel; 271 | channel_helper->arg = mem_ref(arg); 272 | 273 | // Set pointer 274 | *channel_helperp = channel_helper; 275 | 276 | // Un-reference & done 277 | mem_deref(parameters); 278 | } 279 | -------------------------------------------------------------------------------- /c/src/helper/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "common.h" 4 | 5 | /* 6 | * Convert string to uint16. 7 | */ 8 | bool str_to_uint16( 9 | uint16_t* const numberp, 10 | char* const str 11 | ); 12 | 13 | /* 14 | * Get a dictionary entry and store it in `*valuep`. 15 | */ 16 | enum rawrtc_code dict_get_entry( 17 | void* const valuep, 18 | struct odict* const parent, 19 | char* const key, 20 | enum odict_type const type, 21 | bool required 22 | ); 23 | 24 | /* 25 | * Get a uint32 entry and store it in `*valuep`. 26 | */ 27 | enum rawrtc_code dict_get_uint32( 28 | uint32_t* const valuep, 29 | struct odict* const parent, 30 | char* const key, 31 | bool required 32 | ); 33 | 34 | /* 35 | * Get a uint16 entry and store it in `*valuep`. 36 | */ 37 | enum rawrtc_code dict_get_uint16( 38 | uint16_t* const valuep, 39 | struct odict* const parent, 40 | char* const key, 41 | bool required 42 | ); 43 | 44 | /* 45 | * Get JSON from stdin and parse it to a dictionary. 46 | */ 47 | enum rawrtc_code get_json_stdin( 48 | struct odict** const dictp // de-referenced 49 | ); 50 | 51 | /* 52 | * Get the ICE role from a string. 53 | */ 54 | enum rawrtc_code get_ice_role( 55 | enum rawrtc_ice_role* const rolep, // de-referenced 56 | char const* const str 57 | ); 58 | 59 | /* 60 | * Create a data channel helper instance from parameters. 61 | */ 62 | void data_channel_helper_create_from_channel( 63 | struct data_channel_helper** const channel_helperp, // de-referenced 64 | struct rawrtc_data_channel* channel, 65 | struct client* const client, 66 | void* const arg // nullable 67 | ); 68 | -------------------------------------------------------------------------------- /c/src/rawrtc-terminal.c: -------------------------------------------------------------------------------- 1 | #include // memcpy 2 | #include // STDIN_FILENO, STDOUT_FILENO, close, execvp, read, write 3 | #include // USHRT_MAX 4 | #include // SIGTERM, kill 5 | #include // setenv 6 | #include // ioctl, struct winsize 7 | #include // forkpty 8 | #include 9 | #include "helper/utils.h" 10 | #include "helper/handler.h" 11 | #include "helper/parameters.h" 12 | 13 | #define DEBUG_MODULE "rawrtc-terminal" 14 | #define DEBUG_LEVEL 7 15 | #include 16 | 17 | enum { 18 | PIPE_READ_BUFFER = 4096 19 | }; 20 | 21 | // Control message types 22 | enum { 23 | CONTROL_MESSAGE_WINDOW_SIZE_TYPE = 0 24 | }; 25 | 26 | // Control message lengths 27 | enum { 28 | CONTROL_MESSAGE_WINDOW_SIZE_LENGTH = 5 29 | }; 30 | 31 | static char const ws_uri_regex[] = "ws:[^]*"; 32 | 33 | struct parameters { 34 | struct rawrtc_ice_parameters* ice_parameters; 35 | struct rawrtc_ice_candidates* ice_candidates; 36 | struct rawrtc_dtls_parameters* dtls_parameters; 37 | struct sctp_parameters sctp_parameters; 38 | }; 39 | 40 | // Note: Shadows struct client 41 | struct terminal_client { 42 | char* name; 43 | char** ice_candidate_types; 44 | size_t n_ice_candidate_types; 45 | char* shell; 46 | char* ws_uri; 47 | struct rawrtc_ice_gather_options* gather_options; 48 | enum rawrtc_ice_role role; 49 | struct dnsc* dns_client; 50 | struct http_cli* http_client; 51 | struct websock* ws_socket; 52 | struct rawrtc_certificate* certificate; 53 | struct rawrtc_ice_gatherer* gatherer; 54 | struct rawrtc_ice_transport* ice_transport; 55 | struct rawrtc_dtls_transport* dtls_transport; 56 | struct rawrtc_sctp_transport* sctp_transport; 57 | struct rawrtc_data_transport* data_transport; 58 | struct websock_conn* ws_connection; 59 | struct list data_channels; 60 | struct parameters local_parameters; 61 | struct parameters remote_parameters; 62 | }; 63 | 64 | struct terminal_client_channel { 65 | pid_t pid; 66 | int pty; 67 | }; 68 | 69 | static void client_start_transports( 70 | struct terminal_client* const client 71 | ); 72 | 73 | static void client_stop( 74 | struct terminal_client* const client 75 | ); 76 | 77 | static void client_apply_parameters( 78 | struct terminal_client* const client 79 | ); 80 | 81 | static enum rawrtc_code client_decode_parameters( 82 | struct parameters* const parametersp, 83 | struct odict* const dict, 84 | struct terminal_client* const client 85 | ); 86 | 87 | static struct odict* client_encode_parameters( 88 | struct terminal_client* const client 89 | ); 90 | 91 | /* 92 | * Print the WS close event. 93 | */ 94 | static void ws_close_handler( 95 | int err, 96 | void* arg 97 | ) { 98 | struct terminal_client* const client = arg; 99 | DEBUG_PRINTF("(%s) WS connection closed, reason: %m\n", client->name, err); 100 | } 101 | 102 | /* 103 | * Receive the JSON encoded remote parameters, parse and apply them. 104 | */ 105 | static void ws_receive_handler( 106 | struct websock_hdr const* header, 107 | struct mbuf* buffer, 108 | void* arg 109 | ) { 110 | struct terminal_client* const client = arg; 111 | enum rawrtc_code error; 112 | struct odict* dict; 113 | (void) header; 114 | DEBUG_PRINTF("(%s) WS message of %zu bytes received\n", client->name, mbuf_get_left(buffer)); 115 | 116 | // Check opcode 117 | if (header->opcode != WEBSOCK_TEXT) { 118 | DEBUG_NOTICE("(%s) Unexpected opcode (%u) in WS message\n", client->name, header->opcode); 119 | return; 120 | } 121 | 122 | // Decode JSON 123 | error = rawrtc_error_to_code(json_decode_odict( 124 | &dict, 16, (char*) mbuf_buf(buffer), mbuf_get_left(buffer), 3)); 125 | if (error) { 126 | DEBUG_WARNING("(%s) Invalid remote parameters\n", client->name); 127 | return; 128 | } 129 | 130 | // Decode parameters 131 | if (client_decode_parameters(&client->remote_parameters, dict, client) == RAWRTC_CODE_SUCCESS) { 132 | // Set parameters & start transports 133 | client_apply_parameters(client); 134 | client_start_transports(client); 135 | 136 | // Close WS connection 137 | EOR(websock_close(client->ws_connection, WEBSOCK_NORMAL_CLOSURE, NULL)); 138 | client->ws_connection = mem_deref(client->ws_connection); 139 | } 140 | 141 | // Un-reference 142 | mem_deref(dict); 143 | } 144 | 145 | /* 146 | * Send the JSON encoded local parameters to the other peer. 147 | */ 148 | static void ws_established_handler( 149 | void* arg 150 | ) { 151 | struct terminal_client* const client = arg; 152 | struct odict* dict; 153 | DEBUG_PRINTF("(%s) WS connection established\n", client->name); 154 | 155 | // Encode parameters 156 | dict = client_encode_parameters(client); 157 | 158 | // Send as JSON 159 | DEBUG_INFO("(%s) Sending local parameters\n", client->name); 160 | EOR(websock_send(client->ws_connection, WEBSOCK_TEXT, "%H", json_encode_odict, dict)); 161 | 162 | // Un-reference 163 | mem_deref(dict); 164 | } 165 | 166 | /* 167 | * Parse the JSON encoded remote parameters and apply them. 168 | */ 169 | static void stdin_receive_handler( 170 | int flags, 171 | void* arg 172 | ) { 173 | struct terminal_client* const client = arg; 174 | struct odict* dict = NULL; 175 | enum rawrtc_code error; 176 | (void) flags; 177 | 178 | // Get dict from JSON 179 | error = get_json_stdin(&dict); 180 | if (error) { 181 | goto out; 182 | } 183 | 184 | // Decode parameters 185 | if (client_decode_parameters(&client->remote_parameters, dict, client) == RAWRTC_CODE_SUCCESS) { 186 | // Set parameters & start transports 187 | client_apply_parameters(client); 188 | client_start_transports(client); 189 | } 190 | 191 | out: 192 | // Un-reference 193 | mem_deref(dict); 194 | 195 | // Exit? 196 | if (error == RAWRTC_CODE_NO_VALUE) { 197 | DEBUG_NOTICE("Exiting\n"); 198 | 199 | // Stop client & bye 200 | client_stop(client); 201 | before_exit(); 202 | exit(0); 203 | } 204 | } 205 | 206 | /* 207 | * Print the JSON encoded local parameters for the other peer. 208 | */ 209 | static void print_local_parameters( 210 | struct terminal_client* const client 211 | ) { 212 | struct odict* dict; 213 | 214 | // Encode parameters 215 | dict = client_encode_parameters(client); 216 | 217 | // Print as JSON 218 | DEBUG_INFO("Local Parameters:\n%H\n", json_encode_odict, dict); 219 | 220 | // Un-reference 221 | mem_deref(dict); 222 | } 223 | 224 | /* 225 | * Print the local candidate. Open a connection to the WS server in 226 | * case all candidates have been gathered. 227 | */ 228 | static void ice_gatherer_local_candidate_handler( 229 | struct rawrtc_ice_candidate* const candidate, 230 | char const * const url, // read-only 231 | void* const arg 232 | ) { 233 | struct terminal_client* const client = arg; 234 | 235 | // Print local candidate 236 | default_ice_gatherer_local_candidate_handler(candidate, url, arg); 237 | 238 | // Print or send local parameters (if last candidate) 239 | if (!candidate) { 240 | if (client->ws_socket) { 241 | EOR(websock_connect( 242 | &client->ws_connection, client->ws_socket, client->http_client, 243 | client->ws_uri, 30000, 244 | ws_established_handler, ws_receive_handler, ws_close_handler, 245 | client, NULL)); 246 | } else { 247 | print_local_parameters(client); 248 | } 249 | } 250 | } 251 | 252 | /* 253 | * Write the received data channel message's data to the PTY (or handle 254 | * a control message). 255 | */ 256 | void data_channel_message_handler( 257 | struct mbuf* const buffer, 258 | enum rawrtc_data_channel_message_flag const flags, 259 | void* const arg // will be casted to `struct data_channel_helper*` 260 | ) { 261 | struct data_channel_helper* const channel = arg; 262 | struct terminal_client_channel* const client_channel = channel->arg; 263 | struct terminal_client* const client = 264 | (struct terminal_client* const) channel->client; 265 | size_t const length = mbuf_get_left(buffer); 266 | (void) flags; 267 | DEBUG_PRINTF("(%s.%s) Received %zu bytes\n", client->name, channel->label, length); 268 | 269 | if (flags & RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_BINARY) { 270 | uint_fast8_t type; 271 | 272 | // Check size 273 | if (length < 1) { 274 | DEBUG_WARNING("(%s.%s) Invalid control message of size %zu\n", 275 | client->name, channel->label, length); 276 | return; 277 | } 278 | 279 | // Get type 280 | type = mbuf_read_u8(buffer); 281 | 282 | // Handle control message 283 | switch (type) { 284 | case CONTROL_MESSAGE_WINDOW_SIZE_TYPE: 285 | // Check size 286 | if (length < CONTROL_MESSAGE_WINDOW_SIZE_LENGTH) { 287 | DEBUG_WARNING("(%s.%s) Invalid window size message of size %zu\n", 288 | client->name, channel->label, length); 289 | return; 290 | } 291 | { 292 | uint_fast16_t columns; 293 | uint_fast16_t rows; 294 | struct winsize window_size = {0}; 295 | 296 | // Get window size 297 | columns = ntohs(mbuf_read_u16(buffer)); 298 | rows = ntohs(mbuf_read_u16(buffer)); 299 | 300 | // Check window size 301 | #if (UINT16_MAX > USHRT_MAX) 302 | if (columns > USHRT_MAX || rows > USHRT_MAX) { 303 | DEBUG_WARNING("(%s.%s) Invalid window size value\n", 304 | client->name, channel->label); 305 | return; 306 | } 307 | #endif 308 | 309 | // Set window size 310 | window_size.ws_col = (unsigned short) columns; 311 | window_size.ws_row = (unsigned short) rows; 312 | 313 | // Apply window size 314 | DEBUG_PRINTF("(%s.%s) Resizing terminal to %"PRIuFAST16" columns and " 315 | "%"PRIuFAST16" rows\n", client->name, channel->label, columns, rows); 316 | EOP(ioctl(client_channel->pty, TIOCSWINSZ, &window_size)); 317 | } 318 | 319 | break; 320 | default: 321 | DEBUG_WARNING("(%s.%s) Unknown control message %"PRIuFAST8"\n", 322 | client->name, channel->label, length); 323 | break; 324 | } 325 | } else { 326 | // Write into PTY 327 | // TODO: Handle EAGAIN? 328 | DEBUG_PRINTF("(%s.%s) Piping %zu bytes into process...\n", 329 | client->name, channel->label, length); 330 | EOP(write(client_channel->pty, mbuf_buf(buffer), length)); 331 | DEBUG_PRINTF("(%s.%s) ... completed!\n", client->name, channel->label); 332 | } 333 | } 334 | 335 | /* 336 | * Stop the PTY. 337 | */ 338 | static void stop_process( 339 | struct terminal_client_channel* const channel 340 | ) { 341 | // Close PTY (if not already closed) 342 | if (channel->pty != -1) { 343 | // Stop listening on PTY 344 | fd_close(channel->pty); 345 | EOP(close(channel->pty)); 346 | 347 | // Invalidate PTY 348 | channel->pty = -1; 349 | } 350 | 351 | // Stop process (if not already stopped) 352 | if (channel->pid != -1) { 353 | // Terminate process 354 | EOP(kill(channel->pid, SIGTERM)); 355 | 356 | // Invalidate process 357 | channel->pid = -1; 358 | } 359 | } 360 | 361 | /* 362 | * Stop the forked process on error event. 363 | */ 364 | static void data_channel_error_handler( 365 | void* const arg // will be casted to `struct data_channel_helper*` 366 | ) { 367 | struct data_channel_helper* const channel = arg; 368 | struct terminal_client_channel* const client_channel = channel->arg; 369 | 370 | // Print error event 371 | default_data_channel_error_handler(arg); 372 | 373 | // Stop forked process 374 | if (client_channel->pid != -1) { 375 | DEBUG_INFO("(%s.%s) Stopping process\n", channel->client->name, channel->label); 376 | } 377 | stop_process(client_channel); 378 | } 379 | 380 | /* 381 | * Stop the forked process on close event. 382 | */ 383 | void data_channel_close_handler( 384 | void* const arg // will be casted to `struct data_channel_helper*` 385 | ) { 386 | struct data_channel_helper* const channel = arg; 387 | struct terminal_client_channel* const client_channel = channel->arg; 388 | 389 | // Print close event 390 | default_data_channel_close_handler(arg); 391 | 392 | // Stop forked process 393 | if (client_channel->pid != -1) { 394 | DEBUG_INFO("(%s.%s) Stopping process\n", channel->client->name, channel->label); 395 | } 396 | stop_process(client_channel); 397 | } 398 | 399 | /* 400 | * Send the PTY's data on the data channel. 401 | */ 402 | static void pty_read_handler( 403 | int flags, 404 | void* arg 405 | ) { 406 | struct data_channel_helper* const channel = arg; 407 | struct terminal_client_channel* const client_channel = channel->arg; 408 | struct terminal_client* const client = 409 | (struct terminal_client* const) channel->client; 410 | ssize_t length; 411 | (void) flags; 412 | 413 | // Create buffer 414 | struct mbuf* const buffer = mbuf_alloc(PIPE_READ_BUFFER); 415 | 416 | // Read from PTY into buffer 417 | // TODO: Handle EAGAIN? 418 | DEBUG_PRINTF("(%s.%s) Reading from process...\n", client->name, channel->label); 419 | length = read(client_channel->pty, mbuf_buf(buffer), mbuf_get_space(buffer)); 420 | if (length == -1) { 421 | switch (errno) { 422 | case EIO: 423 | // This happens when invoking 'exit' or similar commands 424 | length = 0; 425 | break; 426 | default: 427 | EOR(errno); 428 | break; 429 | } 430 | } 431 | mbuf_set_end(buffer, (size_t) length); 432 | DEBUG_PRINTF("(%s.%s) ... read %zu bytes\n", 433 | client->name, channel->label, mbuf_get_left(buffer)); 434 | 435 | // Process terminated? 436 | if (length == 0) { 437 | // Stop listening 438 | if (client_channel->pid != -1) { 439 | DEBUG_INFO("(%s.%s) Stopping process\n", channel->client->name, channel->label); 440 | } 441 | stop_process(client_channel); 442 | 443 | // Close data channel 444 | EOE(rawrtc_data_channel_close(channel->channel)); 445 | 446 | // Unreference helper 447 | mem_deref(channel); 448 | } else { 449 | // Send the buffer 450 | DEBUG_PRINTF("(%s.%s) Sending %zu bytes\n", client->name, channel->label, length); 451 | EOE(rawrtc_data_channel_send(channel->channel, buffer, false)); 452 | } 453 | 454 | // Clean up 455 | mem_deref(buffer); 456 | } 457 | 458 | /* 459 | * Fork and start the process on open event. 460 | */ 461 | static void data_channel_open_handler( 462 | void* const arg // will be casted to `struct data_channel_helper*` 463 | ) { 464 | struct data_channel_helper* const channel = arg; 465 | struct terminal_client_channel* const client_channel = channel->arg; 466 | struct terminal_client* const client = 467 | (struct terminal_client* const) channel->client; 468 | pid_t pid; 469 | int pty; 470 | 471 | // Print open event 472 | default_data_channel_open_handler(arg); 473 | 474 | // Fork to pseudo-terminal 475 | // TODO: Check bounds (PID_MAX < INT_MAX...) 476 | // TODO: Fix leaking FDs 477 | DEBUG_INFO("(%s) Starting process for data channel %s\n", 478 | channel->client->name, channel->label); 479 | pid = (pid_t) forkpty(&pty, NULL, NULL, NULL); 480 | EOP(pid); 481 | 482 | // Child process 483 | if (pid == 0) { 484 | char* const args[] = {client->shell, NULL}; 485 | 486 | // Make it colourful! 487 | EOP(setenv("TERM", "xterm-256color", 1)); 488 | 489 | // Run terminal 490 | EOP(execvp(args[0], args)); 491 | EWE("Child process returned!\n"); 492 | } 493 | 494 | // Set fields 495 | client_channel->pid = pid; 496 | client_channel->pty = pty; 497 | 498 | // Listen on PTY 499 | EOR(fd_listen(client_channel->pty, FD_READ, pty_read_handler, channel)); 500 | } 501 | 502 | static void terminal_client_channel_destroy( 503 | void* arg 504 | ) { 505 | struct terminal_client_channel* const client_channel = arg; 506 | 507 | // Stop process 508 | stop_process(client_channel); 509 | } 510 | 511 | /* 512 | * Handle the newly created data channel. 513 | */ 514 | static void data_channel_handler( 515 | struct rawrtc_data_channel* const channel, // read-only, MUST be referenced when used 516 | void* const arg // will be casted to `struct client*` 517 | ) { 518 | struct terminal_client* const client = arg; 519 | struct terminal_client_channel* client_channel; 520 | struct data_channel_helper* channel_helper; 521 | 522 | // Print channel 523 | default_data_channel_handler(channel, arg); 524 | 525 | // Create terminal client channel instance 526 | client_channel = mem_zalloc(sizeof(*client_channel), terminal_client_channel_destroy); 527 | if (!client_channel) { 528 | EOE(RAWRTC_CODE_NO_MEMORY); 529 | return; 530 | } 531 | 532 | // Set fields 533 | client_channel->pid = -1; 534 | client_channel->pty = -1; 535 | 536 | // Create data channel helper instance 537 | // Note: In this case we need to reference the channel because we have not created it 538 | data_channel_helper_create_from_channel(&channel_helper, mem_ref(channel), arg, client_channel); 539 | mem_deref(client_channel); 540 | 541 | // Add to list 542 | list_append(&client->data_channels, &channel_helper->le, channel_helper); 543 | 544 | // Set handler argument & handlers 545 | EOE(rawrtc_data_channel_set_arg(channel, channel_helper)); 546 | EOE(rawrtc_data_channel_set_open_handler(channel, data_channel_open_handler)); 547 | EOE(rawrtc_data_channel_set_buffered_amount_low_handler( 548 | channel, default_data_channel_buffered_amount_low_handler)); 549 | EOE(rawrtc_data_channel_set_error_handler(channel, data_channel_error_handler)); 550 | EOE(rawrtc_data_channel_set_close_handler(channel, data_channel_close_handler)); 551 | EOE(rawrtc_data_channel_set_message_handler(channel, data_channel_message_handler)); 552 | } 553 | 554 | static void client_init( 555 | struct terminal_client* const client 556 | ) { 557 | struct rawrtc_certificate* certificates[1]; 558 | 559 | if (client->ws_uri) { 560 | // Create DNS client 561 | EOR(dnsc_alloc(&client->dns_client, NULL, NULL, 0)); 562 | 563 | // Create HTTP client 564 | EOR(http_client_alloc(&client->http_client, client->dns_client)); 565 | 566 | // Create WS Socket 567 | EOR(websock_alloc(&client->ws_socket, NULL, client)); 568 | } 569 | 570 | // Generate certificates 571 | EOE(rawrtc_certificate_generate(&client->certificate, NULL)); 572 | certificates[0] = client->certificate; 573 | 574 | // Create ICE gatherer 575 | EOE(rawrtc_ice_gatherer_create( 576 | &client->gatherer, client->gather_options, 577 | default_ice_gatherer_state_change_handler, default_ice_gatherer_error_handler, 578 | ice_gatherer_local_candidate_handler, client)); 579 | 580 | // Create ICE transport 581 | EOE(rawrtc_ice_transport_create( 582 | &client->ice_transport, client->gatherer, 583 | default_ice_transport_state_change_handler, 584 | default_ice_transport_candidate_pair_change_handler, client)); 585 | 586 | // Create DTLS transport 587 | EOE(rawrtc_dtls_transport_create( 588 | &client->dtls_transport, client->ice_transport, certificates, ARRAY_SIZE(certificates), 589 | default_dtls_transport_state_change_handler, default_dtls_transport_error_handler, 590 | client)); 591 | 592 | // Create SCTP transport 593 | EOE(rawrtc_sctp_transport_create( 594 | &client->sctp_transport, client->dtls_transport, 595 | client->local_parameters.sctp_parameters.port, 596 | data_channel_handler, default_sctp_transport_state_change_handler, client)); 597 | 598 | // Get data transport 599 | EOE(rawrtc_sctp_transport_get_data_transport( 600 | &client->data_transport, client->sctp_transport)); 601 | } 602 | 603 | static void client_start_gathering( 604 | struct terminal_client* const client 605 | ) { 606 | // Start gathering 607 | EOE(rawrtc_ice_gatherer_gather(client->gatherer, NULL)); 608 | } 609 | 610 | static void client_start_transports( 611 | struct terminal_client* const client 612 | ) { 613 | struct parameters* const remote_parameters = &client->remote_parameters; 614 | DEBUG_INFO("(%s) Starting transports\n", client->name); 615 | 616 | // Start ICE transport 617 | EOE(rawrtc_ice_transport_start( 618 | client->ice_transport, client->gatherer, remote_parameters->ice_parameters, 619 | client->role)); 620 | 621 | // Start DTLS transport 622 | EOE(rawrtc_dtls_transport_start( 623 | client->dtls_transport, remote_parameters->dtls_parameters)); 624 | 625 | // Start SCTP transport 626 | EOE(rawrtc_sctp_transport_start( 627 | client->sctp_transport, remote_parameters->sctp_parameters.capabilities, 628 | remote_parameters->sctp_parameters.port)); 629 | } 630 | 631 | static void parameters_destroy( 632 | struct parameters* const parameters 633 | ) { 634 | // Un-reference 635 | parameters->ice_parameters = mem_deref(parameters->ice_parameters); 636 | parameters->ice_candidates = mem_deref(parameters->ice_candidates); 637 | parameters->dtls_parameters = mem_deref(parameters->dtls_parameters); 638 | if (parameters->sctp_parameters.capabilities) { 639 | parameters->sctp_parameters.capabilities = 640 | mem_deref(parameters->sctp_parameters.capabilities); 641 | } 642 | } 643 | 644 | static void client_stop( 645 | struct terminal_client* const client 646 | ) { 647 | DEBUG_INFO("(%s) Stopping transports\n", client->name); 648 | 649 | // Clear data channels 650 | list_flush(&client->data_channels); 651 | 652 | // Stop all transports & gatherer 653 | EOE(rawrtc_sctp_transport_stop(client->sctp_transport)); 654 | EOE(rawrtc_dtls_transport_stop(client->dtls_transport)); 655 | EOE(rawrtc_ice_transport_stop(client->ice_transport)); 656 | EOE(rawrtc_ice_gatherer_close(client->gatherer)); 657 | 658 | // Close WS connection 659 | if (client->ws_connection) { 660 | EOR(websock_close(client->ws_connection, WEBSOCK_GOING_AWAY, NULL)); 661 | } 662 | 663 | // Stop listening on STDIN 664 | fd_close(STDIN_FILENO); 665 | 666 | // Un-reference & close 667 | parameters_destroy(&client->remote_parameters); 668 | parameters_destroy(&client->local_parameters); 669 | client->ws_connection = mem_deref(client->ws_connection); 670 | client->data_transport = mem_deref(client->data_transport); 671 | client->sctp_transport = mem_deref(client->sctp_transport); 672 | client->dtls_transport = mem_deref(client->dtls_transport); 673 | client->ice_transport = mem_deref(client->ice_transport); 674 | client->gatherer = mem_deref(client->gatherer); 675 | client->certificate = mem_deref(client->certificate); 676 | client->ws_socket = mem_deref(client->ws_socket); 677 | client->http_client = mem_deref(client->http_client); 678 | client->dns_client = mem_deref(client->dns_client); 679 | client->gather_options = mem_deref(client->gather_options); 680 | client->ws_uri = mem_deref(client->ws_uri); 681 | client->shell = mem_deref(client->shell); 682 | } 683 | 684 | static void client_apply_parameters( 685 | struct terminal_client* const client 686 | ) { 687 | struct parameters* const remote_parameters = &client->remote_parameters; 688 | DEBUG_INFO("(%s) Applying remote parameters\n", client->name); 689 | 690 | // Set remote ICE candidates 691 | EOE(rawrtc_ice_transport_set_remote_candidates( 692 | client->ice_transport, remote_parameters->ice_candidates->candidates, 693 | remote_parameters->ice_candidates->n_candidates)); 694 | } 695 | 696 | static enum rawrtc_code client_decode_parameters( 697 | struct parameters* const parametersp, 698 | struct odict* const dict, 699 | struct terminal_client* const client 700 | ) { 701 | enum rawrtc_code error = RAWRTC_CODE_SUCCESS; 702 | struct odict* node; 703 | struct parameters parameters = {0}; 704 | 705 | // Decode nodes 706 | error |= dict_get_entry(&node, dict, "iceParameters", ODICT_OBJECT, true); 707 | error |= get_ice_parameters(¶meters.ice_parameters, node); 708 | error |= dict_get_entry(&node, dict, "iceCandidates", ODICT_ARRAY, true); 709 | error |= get_ice_candidates(¶meters.ice_candidates, node, (struct client* const) client); 710 | error |= dict_get_entry(&node, dict, "dtlsParameters", ODICT_OBJECT, true); 711 | error |= get_dtls_parameters(¶meters.dtls_parameters, node); 712 | error |= dict_get_entry(&node, dict, "sctpParameters", ODICT_OBJECT, true); 713 | error |= get_sctp_parameters(¶meters.sctp_parameters, node); 714 | 715 | // Ok? 716 | if (error) { 717 | DEBUG_WARNING("(%s) Invalid remote parameters\n", client->name); 718 | goto out; 719 | } 720 | 721 | out: 722 | if (error) { 723 | // Un-reference 724 | mem_deref(parameters.sctp_parameters.capabilities); 725 | mem_deref(parameters.dtls_parameters); 726 | mem_deref(parameters.ice_candidates); 727 | mem_deref(parameters.ice_parameters); 728 | } else { 729 | // Copy parameters 730 | memcpy(parametersp, ¶meters, sizeof(parameters)); 731 | } 732 | 733 | return error; 734 | } 735 | 736 | static void client_get_parameters( 737 | struct terminal_client* const client 738 | ) { 739 | struct parameters* const local_parameters = &client->local_parameters; 740 | 741 | // Get local ICE parameters 742 | EOE(rawrtc_ice_gatherer_get_local_parameters( 743 | &local_parameters->ice_parameters, client->gatherer)); 744 | 745 | // Get local ICE candidates 746 | EOE(rawrtc_ice_gatherer_get_local_candidates( 747 | &local_parameters->ice_candidates, client->gatherer)); 748 | 749 | // Get local DTLS parameters 750 | EOE(rawrtc_dtls_transport_get_local_parameters( 751 | &local_parameters->dtls_parameters, client->dtls_transport)); 752 | 753 | // Get local SCTP parameters 754 | EOE(rawrtc_sctp_transport_get_capabilities( 755 | &local_parameters->sctp_parameters.capabilities)); 756 | EOE(rawrtc_sctp_transport_get_port( 757 | &local_parameters->sctp_parameters.port, client->sctp_transport)); 758 | } 759 | 760 | static struct odict* client_encode_parameters( 761 | struct terminal_client* const client 762 | ) { 763 | struct odict* dict; 764 | struct odict* node; 765 | 766 | // Get local parameters 767 | client_get_parameters(client); 768 | 769 | // Create dict 770 | EOR(odict_alloc(&dict, 16)); 771 | 772 | // Create nodes 773 | EOR(odict_alloc(&node, 16)); 774 | set_ice_parameters(client->local_parameters.ice_parameters, node); 775 | EOR(odict_entry_add(dict, "iceParameters", ODICT_OBJECT, node)); 776 | mem_deref(node); 777 | EOR(odict_alloc(&node, 16)); 778 | set_ice_candidates(client->local_parameters.ice_candidates, node); 779 | EOR(odict_entry_add(dict, "iceCandidates", ODICT_ARRAY, node)); 780 | mem_deref(node); 781 | EOR(odict_alloc(&node, 16)); 782 | set_dtls_parameters(client->local_parameters.dtls_parameters, node); 783 | EOR(odict_entry_add(dict, "dtlsParameters", ODICT_OBJECT, node)); 784 | mem_deref(node); 785 | EOR(odict_alloc(&node, 16)); 786 | set_sctp_parameters(client->sctp_transport, &client->local_parameters.sctp_parameters, node); 787 | EOR(odict_entry_add(dict, "sctpParameters", ODICT_OBJECT, node)); 788 | mem_deref(node); 789 | 790 | // Done 791 | return dict; 792 | } 793 | 794 | static void exit_with_usage(char* program) { 795 | DEBUG_WARNING("Usage: %s <0|1 (ice-role)> [] [] [] " 796 | "[ ...]", program); 797 | exit(1); 798 | } 799 | 800 | int main(int argc, char* argv[argc + 1]) { 801 | char** ice_candidate_types = NULL; 802 | size_t n_ice_candidate_types = 0; 803 | enum rawrtc_ice_role role; 804 | struct rawrtc_ice_gather_options* gather_options; 805 | char* const stun_google_com_urls[] = {"stun:stun.l.google.com:19302", 806 | "stun:stun1.l.google.com:19302"}; 807 | char* const turn_threema_ch_urls[] = {"turn:turn.threema.ch:443"}; 808 | struct terminal_client client = {0}; 809 | (void) client.ice_candidate_types; (void) client.n_ice_candidate_types; 810 | 811 | // Initialise 812 | EOE(rawrtc_init(true)); 813 | 814 | // Debug 815 | dbg_init(DBG_DEBUG, DBG_ALL); 816 | DEBUG_PRINTF("Init\n"); 817 | 818 | // Check arguments length 819 | if (argc < 2) { 820 | exit_with_usage(argv[0]); 821 | } 822 | 823 | // Get ICE role 824 | if (get_ice_role(&role, argv[1])) { 825 | exit_with_usage(argv[0]); 826 | } 827 | 828 | // Get WS URI (optional) 829 | if (argc >= 3 && re_regex(argv[2], strlen(argv[2]), ws_uri_regex, NULL) == 0) { 830 | EOE(rawrtc_sdprintf(&client.ws_uri, argv[2])); 831 | DEBUG_PRINTF("Using mode: WebSocket\n"); 832 | } else { 833 | DEBUG_PRINTF("Using mode: Copy & Paste\n"); 834 | } 835 | 836 | // Get shell (optional) 837 | if (argc >= 4) { 838 | EOE(rawrtc_sdprintf(&client.shell, argv[3])); 839 | } else { 840 | EOE(rawrtc_sdprintf(&client.shell, "bash")); 841 | } 842 | DEBUG_PRINTF("Using process: %s\n", client.shell); 843 | 844 | // Get SCTP port (optional) 845 | if (argc >= 5 && !str_to_uint16(&client.local_parameters.sctp_parameters.port, argv[4])) { 846 | exit_with_usage(argv[0]); 847 | } 848 | 849 | // Get enabled ICE candidate types to be added (optional) 850 | if (argc >= 6) { 851 | ice_candidate_types = &argv[5]; 852 | n_ice_candidate_types = (size_t) argc - 5; 853 | } 854 | 855 | // Create ICE gather options 856 | EOE(rawrtc_ice_gather_options_create(&gather_options, RAWRTC_ICE_GATHER_POLICY_ALL)); 857 | 858 | // Add ICE servers to ICE gather options 859 | EOE(rawrtc_ice_gather_options_add_server( 860 | gather_options, stun_google_com_urls, ARRAY_SIZE(stun_google_com_urls), 861 | NULL, NULL, RAWRTC_ICE_CREDENTIAL_TYPE_NONE)); 862 | EOE(rawrtc_ice_gather_options_add_server( 863 | gather_options, turn_threema_ch_urls, ARRAY_SIZE(turn_threema_ch_urls), 864 | "threema-angular", "Uv0LcCq3kyx6EiRwQW5jVigkhzbp70CjN2CJqzmRxG3UGIdJHSJV6tpo7Gj7YnGB", 865 | RAWRTC_ICE_CREDENTIAL_TYPE_PASSWORD)); 866 | 867 | // Set client fields 868 | client.name = "A"; 869 | client.ice_candidate_types = ice_candidate_types; 870 | client.n_ice_candidate_types = n_ice_candidate_types; 871 | client.gather_options = gather_options; 872 | client.role = role; 873 | list_init(&client.data_channels); 874 | 875 | // Setup client 876 | client_init(&client); 877 | 878 | // Start gathering 879 | client_start_gathering(&client); 880 | 881 | // Listen on stdin 882 | EOR(fd_listen(STDIN_FILENO, FD_READ, stdin_receive_handler, &client)); 883 | 884 | // Start main loop 885 | // TODO: Wrap re_main? 886 | EOR(re_main(default_signal_handler)); 887 | 888 | // Stop client & bye 889 | client_stop(&client); 890 | before_exit(); 891 | return 0; 892 | } 893 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawrtc/rawrtc-terminal-demo/5eb446d9e641f578807ed3916e82e6b432f86aee/screenshot.png -------------------------------------------------------------------------------- /signaling/Readme.md: -------------------------------------------------------------------------------- 1 | # So basic, it doesn't even have a name 2 | 3 | The most basic signalling server you can get. 4 | 5 | Don't even think about using this in production code. We recommend 6 | [SaltyRTC][saltyrtc] for secure production signalling. 7 | 8 | ## Prerequisites 9 | 10 | * Python 3.4+ 11 | * [pip][pip] 12 | 13 | We recommend using [venv][venv] to create an isolated Python environment: 14 | 15 | python3 -m venv signaling-venv 16 | 17 | You can switch into the created virtual environment *signaling-venv* by running 18 | this command: 19 | 20 | source signaling-venv/bin/activate 21 | 22 | While the virtual environment is active, all packages installed using 23 | `pip` will be installed into this environment. 24 | 25 | To deactivate the virtual environment, just run: 26 | 27 | deactivate 28 | 29 | If you want easier handling of your virtualenvs, you might also want to 30 | take a look at [virtualenvwrapper][virtualenvwrapper]. 31 | 32 | ## Installation 33 | 34 | To install the dependencies, activate the virtual environment and run 35 | 36 | pip install -r requirements.txt 37 | 38 | ## Usage 39 | 40 | Run the server with the following command: 41 | 42 | python server.py 43 | 44 | [saltyrtc]: https://saltyrtc.org 45 | [pip]: https://pip.pypa.io/en/stable/installing 46 | [venv]: https://docs.python.org/3/library/venv.html 47 | [virtualenvwrapper]: https://virtualenvwrapper.readthedocs.io/ 48 | -------------------------------------------------------------------------------- /signaling/requirements.txt: -------------------------------------------------------------------------------- 1 | asyncio>=3.4.3 2 | logbook>=1.0.0,<2 3 | websockets>=3.2,<4 -------------------------------------------------------------------------------- /signaling/server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import logbook 4 | import logbook.more 5 | import websockets 6 | 7 | 8 | __author__ = 'Lennart Grahl' 9 | 10 | PING_INTERVAL = 5.0 11 | PING_TIMEOUT = 10.0 12 | 13 | 14 | class SignalingError(Exception): 15 | pass 16 | 17 | 18 | class MessageFlowError(SignalingError): 19 | pass 20 | 21 | 22 | class PathClient: 23 | def __init__(self, loop, path, connection, slot): 24 | self.loop = loop 25 | self.connection = connection 26 | self.name = '{}.{}'.format(path.log.name, slot) 27 | self.log = logbook.Logger(self.name) 28 | self.slot = slot 29 | if self.connection.open: 30 | self.log.debug('Open') 31 | 32 | def __repr__(self): 33 | return '<{} at {}>'.format(self.name, hex(id(self))) 34 | 35 | @property 36 | def open(self): 37 | return self.connection.open 38 | 39 | def close(self, code=1000, reason=''): 40 | if self.connection.open: 41 | self.log.debug('Closing') 42 | self.loop.create_task(self.connection.close(code=code, reason=reason)) 43 | 44 | def receive(self): 45 | return self.connection.recv() 46 | 47 | def send(self, message): 48 | return self.connection.send(message) 49 | 50 | def ping(self): 51 | return self.connection.ping() 52 | 53 | 54 | class Path: 55 | def __init__(self, loop, path): 56 | self.name = 'path.{}'.format(path) 57 | self.loop = loop 58 | self.log = logbook.Logger(self.name) 59 | self.slots = { 60 | 0: asyncio.Future(), 61 | 1: asyncio.Future(), 62 | } 63 | 64 | def __repr__(self): 65 | return '<{} at {}>'.format(self.name, hex(id(self))) 66 | 67 | def wait_other_client(self, client): 68 | other_slot = 0 if client.slot is 1 else 1 69 | return asyncio.shield(self.slots[other_slot], loop=self.loop) 70 | 71 | def register_client(self, client): 72 | # Unregister previous client 73 | if self.slots[client.slot].done(): 74 | self.unregister_client(self.slots[client.slot].result()) 75 | self.slots[client.slot] = asyncio.Future() 76 | 77 | # Store 78 | self.slots[client.slot].set_result(client) 79 | self.log.info('Registered client {}', client) 80 | 81 | def unregister_client(self, client): 82 | # Slot occupied? 83 | if self.slots[client.slot].done(): 84 | # Already replaced? 85 | current_client = self.slots[client.slot].result() 86 | if current_client != client: 87 | self.log.warning('Different client {}', client) 88 | return 89 | else: 90 | self.log.warning('Slot {} is empty', client.slot) 91 | return 92 | 93 | # Close 94 | client.close() 95 | 96 | # Remove 97 | self.slots[client.slot] = asyncio.Future() 98 | self.log.info('Unregistered client {}', client) 99 | 100 | 101 | class Server: 102 | def __init__(self, loop): 103 | self.loop = loop 104 | self.log = logbook.Logger('server') 105 | self.paths = {} 106 | 107 | @asyncio.coroutine 108 | def handler(self, connection, path): 109 | # Get slot from path 110 | try: 111 | _, path = path.split('/', maxsplit=1) 112 | path, slot = path.rsplit('/', maxsplit=1) 113 | slot = int(slot) 114 | if slot > 1: 115 | raise ValueError() 116 | except ValueError: 117 | raise SignalingError('Invalid path: {}'.format(path)) 118 | 119 | # Get path instance 120 | path = self.paths.setdefault(path, Path(self.loop, path)) 121 | self.log.debug('Using path {}', path) 122 | 123 | # Create client instance 124 | client = PathClient(self.loop, path, connection, slot) 125 | 126 | # Register client 127 | path.register_client(client) 128 | 129 | # Handle client until disconnected or an exception occurred 130 | try: 131 | yield from self.handle_client(path, client) 132 | except websockets.ConnectionClosed: 133 | self.log.info('Connection closed to {}', client) 134 | except SignalingError as exc: 135 | self.log.notice('Closing due to protocol error: {}', exc) 136 | yield from client.close(code=1002) 137 | except Exception as exc: 138 | self.log.exception('Closing due to exception:', exc) 139 | yield from client.close(code=1011) 140 | 141 | # Unregister client 142 | path.unregister_client(client) 143 | 144 | @asyncio.coroutine 145 | def handle_client(self, path, client): 146 | # Wait until complete 147 | tasks = [self.keep_alive(client), self.channel(path, client)] 148 | tasks = [self.loop.create_task(coroutine) for coroutine in tasks] 149 | done, pending = yield from asyncio.wait( 150 | tasks, loop=self.loop, return_when=asyncio.FIRST_EXCEPTION) 151 | for task in done: 152 | exc = task.exception() 153 | 154 | # Cancel pending tasks 155 | for pending_task in pending: 156 | self.log.debug('Cancelling task {}', pending_task) 157 | pending_task.cancel() 158 | 159 | # Raise (or re-raise) 160 | if exc is None: 161 | self.log.error('Task {} returned unexpectedly', task) 162 | raise SignalingError('A task returned unexpectedly') 163 | else: 164 | raise exc 165 | 166 | @asyncio.coroutine 167 | def channel(self, path, client): 168 | try: 169 | while True: 170 | # Receive message 171 | message = yield from client.receive() 172 | length = len(message) 173 | self.log.info('Received {} bytes from {}', length, client) 174 | self.log.debug('<< {}', message) 175 | 176 | # Wait for other client 177 | other_client_future = path.wait_other_client(client) 178 | if not other_client_future.done(): 179 | self.log.info('Client {} is waiting for other client', client) 180 | other_client = yield from other_client_future 181 | 182 | # Send to other client 183 | self.log.info('Sending {} bytes to {}', length, other_client) 184 | self.log.debug('>> {}', message) 185 | yield from other_client.send(message) 186 | except asyncio.CancelledError: 187 | self.log.debug('Channel cancelled') 188 | 189 | @asyncio.coroutine 190 | def keep_alive(self, client): 191 | try: 192 | while True: 193 | self.log.debug('Ping to {}', client) 194 | try: 195 | # Send ping 196 | yield from asyncio.wait_for(client.ping(), PING_TIMEOUT) 197 | except asyncio.TimeoutError: 198 | raise SignalingError('Ping timeout') 199 | else: 200 | self.log.debug('Pong from {}', client) 201 | 202 | # Wait 203 | yield from asyncio.sleep(PING_INTERVAL) 204 | except asyncio.CancelledError: 205 | self.log.debug('Ping cancelled') 206 | 207 | 208 | def main(): 209 | loop = asyncio.get_event_loop() 210 | server = Server(loop) 211 | ws_server = websockets.serve(server.handler, port=9765) 212 | loop.run_until_complete(ws_server) 213 | try: 214 | loop.run_forever() 215 | except KeyboardInterrupt: 216 | pass 217 | 218 | if __name__ == '__main__': 219 | logging_handler = logbook.more.ColorizedStderrHandler() 220 | with logging_handler.applicationbound(): 221 | main() 222 | 223 | -------------------------------------------------------------------------------- /web/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, body { 8 | height: 100%; 9 | } 10 | 11 | body, input, button, textarea { 12 | font: 1em sans-serif; 13 | } 14 | 15 | body { 16 | color: #e6e1dc; 17 | background: #2b2b2b; 18 | } 19 | 20 | .red { 21 | border-color: #da4939 !important; 22 | } 23 | .orange { 24 | border-color: #ffc66d !important; 25 | } 26 | .green { 27 | border-color: #a5c261 !important; 28 | } 29 | 30 | #wrapper { 31 | height: 100%; 32 | display: flex; 33 | flex-direction: column; 34 | } 35 | 36 | #status-bar { 37 | border-bottom: solid .2em transparent; 38 | } 39 | 40 | #navigation { 41 | display: flex; 42 | flex-wrap: wrap; 43 | flex-shrink: 0; 44 | } 45 | #navigation > * { 46 | color: #fff; 47 | background: #2b2b2b; 48 | border: .1em solid #2b2b2b; 49 | padding: .6em 1em; 50 | cursor: pointer; 51 | } 52 | #navigation > .active { 53 | background: #477cb4; 54 | } 55 | 56 | #content { 57 | flex: 1; 58 | } 59 | #content > * { 60 | width: 100%; 61 | order: 1; 62 | display: none; 63 | } 64 | #content > .show { 65 | display: flex; 66 | } 67 | 68 | #connection { 69 | padding: .1em 0; 70 | text-align: center; 71 | flex-direction: column; 72 | } 73 | #paste-here { 74 | padding: 1em; 75 | margin: 1em; 76 | font-size: 1.2em; 77 | border: dashed .2em #e6e1dc; 78 | cursor: pointer; 79 | } 80 | #paste-here.done { 81 | cursor: default; 82 | } 83 | #connection .parameters { 84 | text-align: left; 85 | margin: 1em; 86 | font-size: .8em; 87 | white-space: pre-wrap; 88 | word-wrap: break-word; 89 | } 90 | 91 | .terminal { 92 | height: 100%; 93 | } 94 | 95 | /* Hack to remove that ugly scrollbar fragment */ 96 | .xterm-viewport { 97 | visibility: hidden; 98 | } 99 | 100 | /* Fix height of rows */ 101 | .xterm-rows > div { 102 | height: 21px; 103 | } 104 | -------------------------------------------------------------------------------- /web/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //noinspection JSUnusedLocalSymbols 4 | window.addEventListener('load', (event) => { 5 | // Control message types 6 | let messageType = { 7 | 'windowSize': 0 8 | }; 9 | 10 | // DOM elements 11 | let status = document.getElementById('status-bar'); 12 | let content = document.getElementById('content'); 13 | let connectionLabel = document.getElementById('l-connection'); 14 | let connectionTab = document.getElementById('connection'); 15 | let newTerminalLabel = document.getElementById('l-add'); 16 | let paste = document.getElementById('paste-here'); 17 | let localParameters = document.getElementById('local-parameters'); 18 | let remoteParameters = document.getElementById('remote-parameters'); 19 | let pasteInnerText = paste.innerText; 20 | 21 | class WebTerminalPeer { 22 | constructor(resetEventHandler) { 23 | this.terminals = []; 24 | this.peer = null; 25 | this.connected = false; 26 | this.previousPasteEventHandler = null; 27 | this.createPeerConnection(); 28 | this.resetEventHandler = resetEventHandler; 29 | 30 | // Connection tab events 31 | //noinspection JSUnusedLocalSymbols 32 | connectionLabel.onclick = (event) => { 33 | // Show content 34 | WebTerminalPeer.switchTab(connectionLabel, connectionTab); 35 | }; 36 | 37 | // Create terminal on request 38 | //noinspection JSUnusedLocalSymbols 39 | newTerminalLabel.onclick = (event) => { 40 | if (this.connected) { 41 | this.createTerminal(); 42 | } 43 | }; 44 | } 45 | 46 | static beautifyParameters(node, parameters) { 47 | if (!parameters) { 48 | parameters = JSON.parse(node.innerText); 49 | } 50 | 51 | // Parse and beautify 52 | node.innerText = JSON.stringify(parameters, null, 2); 53 | } 54 | 55 | static switchTab(label, section, terminal) { 56 | // Make current label inactive 57 | let currentLabel = document.querySelector('#navigation > .active'); 58 | currentLabel.classList.remove('active'); 59 | 60 | // Make new label active 61 | label.classList.add('active'); 62 | 63 | // Make current tab inactive 64 | let currentSection = document.querySelector('#content > .show'); 65 | currentSection.classList.remove('show'); 66 | 67 | // Make new tab active 68 | section.classList.add('show'); 69 | 70 | // Fit terminal (if any) 71 | if (terminal) { 72 | WebTerminalPeer.fitTerminal(terminal); 73 | } 74 | } 75 | 76 | static sendResizeMessage(dc, geometry) { 77 | console.log('Resize to cols: ' + geometry.cols + ', rows: ' + geometry.rows); 78 | 79 | // Prepare control message 80 | let buffer = new ArrayBuffer(5); 81 | let view = new DataView(buffer); 82 | view.setUint8(0, messageType.windowSize); 83 | view.setUint16(1, geometry.cols); 84 | view.setUint16(3, geometry.rows); 85 | 86 | // Send control message 87 | dc.send(buffer); 88 | } 89 | 90 | static fitTerminal(terminal) { 91 | // Space above 92 | let above = Math.ceil(content.getBoundingClientRect().top); 93 | 94 | // Calculate height & width 95 | let height = window.innerHeight - above; 96 | let width = window.innerWidth; 97 | 98 | // Calculate columns & rows 99 | let subjectRow = terminal.rowContainer.firstElementChild; 100 | let contentBuffer = subjectRow.innerHTML; 101 | subjectRow.style.display = 'inline'; 102 | subjectRow.innerHTML = 'W'; 103 | let characterWidth = subjectRow.getBoundingClientRect().width; 104 | subjectRow.style.display = ''; 105 | let characterHeight = parseInt(subjectRow.offsetHeight); 106 | subjectRow.innerHTML = contentBuffer; 107 | let rows = parseInt(height / characterHeight); 108 | let columns = parseInt(width / characterWidth); 109 | 110 | // Apply geometry 111 | terminal.resize(columns, rows); 112 | } 113 | 114 | reset() { 115 | // Update status 116 | status.className = 'red'; 117 | 118 | // Tear down all terminals 119 | this.removeTerminals(); 120 | 121 | // Reset parameters 122 | localParameters.innerHTML = ''; 123 | remoteParameters.innerHTML = ''; 124 | 125 | // Reset paste element 126 | paste.setAttribute('contenteditable', 'true'); 127 | if (this.previousPasteEventHandler) { 128 | paste.onpaste = this.previousPasteEventHandler; 129 | } 130 | paste.className = ''; 131 | paste.innerText = pasteInnerText; 132 | 133 | // Call handler 134 | console.info('Reset'); 135 | if (this.resetEventHandler) { 136 | this.resetEventHandler(); 137 | } 138 | } 139 | 140 | createPeerConnection() { 141 | // Create peer 142 | let peer = new ControllingPeer(); 143 | peer.createPeerConnection(); 144 | 145 | // Bind peer connection events 146 | //noinspection JSUnusedLocalSymbols 147 | peer.pc.oniceconnectionstatechange = (event) => { 148 | let state = peer.pc.iceConnectionState; 149 | console.log('ICE connection state changed to:', state); 150 | 151 | // Connected, yay! 152 | if (state == 'connected' || state == 'completed') { 153 | this.connected = true; 154 | status.className = 'green'; 155 | } 156 | 157 | // Warn (if disconnected) 158 | if (state == 'disconnected' || state == 'checking') { 159 | this.connected = false; 160 | status.className = 'orange'; 161 | } 162 | 163 | // Reset (if failed) 164 | if (state == 'failed') { 165 | this.connected = false; 166 | this.reset(); 167 | } 168 | }; 169 | 170 | // Create ignore-me data channel 171 | // Note: This channel is not going to be used, it simply exists to be able to create 172 | // an offer that includes data channel parameters. 173 | peer.createDataChannel(peer.pc.createDataChannel('ignore-me', { 174 | ordered: true, 175 | id: 0, 176 | negotiated: true 177 | })); 178 | 179 | // Apply local parameters 180 | peer.getLocalParameters() 181 | .then((parameters) => { 182 | console.log('Local parameters:', parameters); 183 | localParameters.innerText = JSON.stringify(parameters); 184 | }); 185 | 186 | // Done 187 | this.peer = peer; 188 | } 189 | 190 | parseWSURIOrParameters(text) { 191 | // Stop catching paste events 192 | paste.setAttribute('contenteditable', 'false'); 193 | this.previousPasteEventHandler = paste.onpaste; 194 | paste.onpaste = (event) => { 195 | event.preventDefault(); 196 | }; 197 | 198 | // Remove current selections (or we'll get an error when selecting) 199 | window.getSelection().removeAllRanges(); 200 | 201 | // Parse 202 | if (text.startsWith('ws://')) { 203 | // WebSocket URI 204 | paste.innerText = 'Connecting to WebSocket URI: ' + text; 205 | paste.classList.add('done'); 206 | paste.classList.add('orange'); 207 | this.startWS(text); 208 | } else { 209 | // Parse parameters 210 | let parameters = JSON.parse(text); 211 | 212 | // Copy & paste mode 213 | let parametersElement = localParameters; 214 | if (document.selection) { 215 | let range = document.body.createTextRange(); 216 | range.moveToElementText(parametersElement); 217 | range.select(); 218 | } else if (window.getSelection) { 219 | let range = document.createRange(); 220 | range.selectNode(parametersElement); 221 | window.getSelection().addRange(range); 222 | } 223 | 224 | // Try copying to clipboard 225 | let copied = false; 226 | try { 227 | copied = document.execCommand('copy'); 228 | } catch (err) {} 229 | 230 | // Un-select if copied 231 | if (copied) { 232 | window.getSelection().removeAllRanges(); 233 | paste.classList.add('done'); 234 | paste.classList.add('green'); 235 | paste.innerText = 'Parameters copied to clipboard! Paste them in the RAWRTC terminal ' + 236 | 'application.'; 237 | } else { 238 | paste.classList.add('done'); 239 | paste.classList.add('orange'); 240 | paste.innerText = 'Copy & paste the selected parameters in the RAWRTC terminal ' + 241 | 'application.'; 242 | } 243 | 244 | // Set remote parameters 245 | this.setRemoteParameters(parameters); 246 | } 247 | } 248 | 249 | setRemoteParameters(parameters) { 250 | // Beautify local and remote parameters 251 | WebTerminalPeer.beautifyParameters(localParameters); 252 | WebTerminalPeer.beautifyParameters(remoteParameters, parameters); 253 | 254 | // Set remote parameters 255 | console.log('Remote parameters:', parameters); 256 | this.peer.setRemoteParameters(parameters) 257 | .catch((error) => { 258 | console.error(error); 259 | }); 260 | } 261 | 262 | startWS(uri) { 263 | // Beautify local parameters 264 | WebTerminalPeer.beautifyParameters(localParameters); 265 | 266 | // Create WebSocket connection 267 | let ws = new WebSocket(uri); 268 | 269 | // Bind WebSocket events 270 | //noinspection JSUnusedLocalSymbols 271 | ws.onopen = (event) => { 272 | console.log('WS connection open'); 273 | 274 | // Send local parameters 275 | this.peer.getLocalParameters().then((parameters) => { 276 | console.info('Sending local parameters'); 277 | ws.send(JSON.stringify(parameters)); 278 | }); 279 | }; 280 | ws.onerror = (event) => { 281 | console.log('WS connection error:', event); 282 | }; 283 | //noinspection JSUnusedLocalSymbols 284 | ws.onclose = (event) => { 285 | console.log('WS connection closed'); 286 | }; 287 | ws.onmessage = (event) => { 288 | let length = event.data.size || event.data.byteLength || event.data.length; 289 | console.log('WS message of', length, 'bytes received'); 290 | 291 | // Parse remote parameters 292 | let parameters = JSON.parse(event.data); 293 | paste.className = 'green'; 294 | paste.classList.remove('orange'); 295 | paste.classList.add('green'); 296 | paste.innerText = 'Received parameters from WebSocket URI: ' + uri; 297 | this.setRemoteParameters(parameters); 298 | 299 | // Close WebSocket connection 300 | ws.close(); 301 | }; 302 | } 303 | 304 | createTerminal(dc) { 305 | let id = this.terminals.length; 306 | 307 | // Create data channel (if needed) 308 | if (!dc) { 309 | // Create terminal data channel 310 | dc = this.peer.createDataChannel(this.peer.pc.createDataChannel('terminal-' + id, { 311 | ordered: true, 312 | })); 313 | } 314 | 315 | // Update UI 316 | let parent = newTerminalLabel.parentNode; 317 | 318 | // Create new tab 319 | let section = document.createElement('section'); 320 | section.id = 'terminal-' + id; 321 | section.className = 'terminal'; 322 | let label = document.createElement('label'); 323 | label.id = 'l-terminal-' + id; 324 | label.innerHTML = 'Terminal ' + (id + 1); 325 | //noinspection JSUnusedLocalSymbols 326 | label.onclick = (event) => { 327 | // Show section 328 | WebTerminalPeer.switchTab(label, section, terminal); 329 | 330 | // Focus terminal 331 | terminal.focus(); 332 | }; 333 | 334 | // Insert elements 335 | parent.insertBefore(label, newTerminalLabel); 336 | content.appendChild(section); 337 | 338 | // Create terminal 339 | let terminal = new Terminal(); 340 | let resizeTimeout; 341 | 342 | // Bind data channel events 343 | //noinspection JSUnusedLocalSymbols 344 | dc.onopen = (event) => { 345 | console.log('Data channel "' + dc.label + '" open'); 346 | 347 | // Open terminal 348 | terminal.open(section); 349 | }; 350 | //noinspection JSUnusedLocalSymbols 351 | dc.onclose = (event) => { 352 | console.log('Data channel "' + dc.label + '" closed'); 353 | 354 | // Remove terminal 355 | this.removeTerminal(id); 356 | }; 357 | dc.onmessage = (event) => { 358 | let length = event.data.size || event.data.byteLength || event.data.length; 359 | console.info('Received', length, 'bytes over data channel "' + dc.label + '"'); 360 | 361 | // Write to terminal 362 | terminal.write(event.data); 363 | }; 364 | 365 | // Bind terminal events 366 | terminal.on('data', (data) => { 367 | // Send over data channel 368 | console.log('Sending', data.length, 'bytes over data channel "' + dc.label + '"'); 369 | dc.send(data); 370 | }); 371 | terminal.on('resize', function(geometry) { 372 | clearTimeout(resizeTimeout); 373 | resizeTimeout = setTimeout(() => { 374 | WebTerminalPeer.sendResizeMessage(dc, geometry); 375 | }, 100); 376 | }); 377 | 378 | // Fit terminal on resize 379 | //noinspection JSUnusedLocalSymbols 380 | window.addEventListener('resize', (event) => { 381 | WebTerminalPeer.fitTerminal(terminal); 382 | }); 383 | 384 | // Switch to the terminal 385 | // Note: The timeout is a workaround to avoid some timing crap 386 | setTimeout(() => { 387 | WebTerminalPeer.switchTab(label, section, terminal); 388 | }, 1); 389 | 390 | // Add to terminals 391 | this.terminals.push({ 392 | id: id, 393 | terminal: terminal, 394 | label: label, 395 | section: section 396 | }); 397 | } 398 | 399 | removeTerminal(id) { 400 | let terminal = this.terminals[id]; 401 | 402 | // Destroy terminal 403 | terminal.terminal.destroy(); 404 | 405 | // Update UI 406 | let active = terminal.label.classList.contains('active'); 407 | 408 | // Set active tab (if the terminal tab was active) 409 | if (active) { 410 | WebTerminalPeer.switchTab(connectionLabel, connectionTab); 411 | } 412 | 413 | // Remove elements 414 | terminal.label.parentNode.removeChild(terminal.label); 415 | if (terminal.section.parentNode) { 416 | terminal.section.parentNode.removeChild(terminal.section); 417 | } else { 418 | let section = document.getElementById('terminal-' + id); 419 | section.parentNode.removeChild(section); 420 | } 421 | 422 | // Invalidate terminal 423 | this.terminals[id] = null; 424 | } 425 | 426 | removeTerminals() { 427 | // Remove all terminals 428 | for (let i = 0; i < this.terminals.length; ++i) { 429 | if (this.terminals[i]) { 430 | this.removeTerminal(i); 431 | } 432 | } 433 | 434 | // Reset list 435 | this.terminals = []; 436 | } 437 | } 438 | 439 | let start = () => { 440 | // Create peer and make peer globally available 441 | let peer = new WebTerminalPeer(() => { 442 | console.info('Restart'); 443 | start(); 444 | }); 445 | //noinspection JSUndefinedPropertyAssignment 446 | window.peer = peer; 447 | 448 | // Autofocus paste area 449 | paste.focus(); 450 | 451 | // Catch pasted data 452 | paste.onpaste = (event) => { 453 | event.preventDefault(); 454 | 455 | // If no clipboard data is available, do nothing. 456 | if (!event.clipboardData) { 457 | return; 458 | } 459 | 460 | if (event.clipboardData.types) { 461 | // Loop the data store in type and display it 462 | for (let i = 0; i < event.clipboardData.types.length; ++i) { 463 | let type = event.clipboardData.types[i]; 464 | let value = event.clipboardData.getData(type); 465 | if (type == 'text/plain') { 466 | peer.parseWSURIOrParameters(value); 467 | break; 468 | } 469 | } 470 | 471 | } else { 472 | // Look for access to data if types array is missing 473 | let text = event.clipboardData.getData('text/plain'); 474 | peer.parseWSURIOrParameters(text); 475 | } 476 | }; 477 | }; 478 | 479 | // Start 480 | console.info('Start'); 481 | start(); 482 | }); 483 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawrtc/rawrtc-terminal-demo/5eb446d9e641f578807ed3916e82e6b432f86aee/web/favicon.ico -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | RAWRTC Web Terminal Demo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 | 30 | 31 |
32 |
33 |
34 | Paste a WebSocket URI or the remote parameters here. 35 |
36 | 37 | Local Parameters 38 |

39 | 
40 |                 Remote Parameters
41 |                 

42 |             
43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawrtc-terminal-demo", 3 | "version": "0.1.0", 4 | "description": "RAWRTC Web Terminal Demo", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/rawrtc/rawrtc-terminal-demo.git" 9 | }, 10 | "keywords": [ 11 | "rawrtc", 12 | "terminal", 13 | "demo", 14 | "webrtc", 15 | "ortc", 16 | "data", 17 | "channels" 18 | ], 19 | "author": "David Bardiau ", 20 | "contributors": [{ 21 | "name": "Lennart Grahl", 22 | "email": "lennart.grahl@gmail.com" 23 | }], 24 | "license": "BSD 2-Clause", 25 | "bugs": { 26 | "url": "https://github.com/rawrtc/rawrtc-terminal-demo/issues" 27 | }, 28 | "homepage": "https://github.com/rawrtc/rawrtc-terminal-demo#readme", 29 | "dependencies": { 30 | "webrtc-adapter": "^3.2.0", 31 | "xterm": "^2.4.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/sdp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Philipp Hancke 3 | * License: MIT 4 | * Source: https://github.com/fippo/sdp 5 | * 6 | * Extended by Lennart Grahl 7 | */ 8 | 9 | /* eslint-env node */ 10 | 'use strict'; 11 | 12 | // SDP helpers. 13 | var SDPUtils = {}; 14 | 15 | // Generate an alphanumeric identifier for cname or mids. 16 | // TODO: use UUIDs instead? https://gist.github.com/jed/982883 17 | SDPUtils.generateIdentifier = function() { 18 | return Math.random().toString(36).substr(2, 10); 19 | }; 20 | 21 | // The RTCP CNAME used by all peerconnections from the same JS. 22 | SDPUtils.localCName = SDPUtils.generateIdentifier(); 23 | 24 | // Splits SDP into lines, dealing with both CRLF and LF. 25 | SDPUtils.splitLines = function(blob) { 26 | return blob.trim().split('\n').map(function(line) { 27 | return line.trim(); 28 | }); 29 | }; 30 | // Splits SDP into sessionpart and mediasections. Ensures CRLF. 31 | SDPUtils.splitSections = function(blob) { 32 | var parts = blob.split('\nm='); 33 | return parts.map(function(part, index) { 34 | return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; 35 | }); 36 | }; 37 | 38 | // Returns lines that start with a certain prefix. 39 | SDPUtils.matchPrefix = function(blob, prefix) { 40 | return SDPUtils.splitLines(blob).filter(function(line) { 41 | return line.indexOf(prefix) === 0; 42 | }); 43 | }; 44 | 45 | // Parses an ICE candidate line. Sample input: 46 | // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 47 | // rport 55996" 48 | SDPUtils.parseCandidate = function(line) { 49 | var parts; 50 | // Parse both variants. 51 | if (line.indexOf('a=candidate:') === 0) { 52 | parts = line.substring(12).split(' '); 53 | } else { 54 | parts = line.substring(10).split(' '); 55 | } 56 | 57 | var candidate = { 58 | foundation: parts[0], 59 | component: parts[1], 60 | protocol: parts[2].toLowerCase(), 61 | priority: parseInt(parts[3], 10), 62 | ip: parts[4], 63 | port: parseInt(parts[5], 10), 64 | // skip parts[6] == 'typ' 65 | type: parts[7] 66 | }; 67 | 68 | for (var i = 8; i < parts.length; i += 2) { 69 | switch (parts[i]) { 70 | case 'raddr': 71 | candidate.relatedAddress = parts[i + 1]; 72 | break; 73 | case 'rport': 74 | candidate.relatedPort = parseInt(parts[i + 1], 10); 75 | break; 76 | case 'tcptype': 77 | candidate.tcpType = parts[i + 1]; 78 | break; 79 | default: // Unknown extensions are silently ignored. 80 | break; 81 | } 82 | } 83 | return candidate; 84 | }; 85 | 86 | // Translates a candidate object into SDP candidate attribute. 87 | SDPUtils.writeCandidate = function(candidate) { 88 | var sdp = []; 89 | sdp.push(candidate.foundation); 90 | sdp.push(candidate.component); 91 | sdp.push(candidate.protocol.toUpperCase()); 92 | sdp.push(candidate.priority); 93 | sdp.push(candidate.ip); 94 | sdp.push(candidate.port); 95 | 96 | var type = candidate.type; 97 | sdp.push('typ'); 98 | sdp.push(type); 99 | if (type !== 'host' && candidate.relatedAddress && 100 | candidate.relatedPort) { 101 | sdp.push('raddr'); 102 | sdp.push(candidate.relatedAddress); // was: relAddr 103 | sdp.push('rport'); 104 | sdp.push(candidate.relatedPort); // was: relPort 105 | } 106 | if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { 107 | sdp.push('tcptype'); 108 | sdp.push(candidate.tcpType); 109 | } 110 | return 'candidate:' + sdp.join(' '); 111 | }; 112 | 113 | // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: 114 | // a=rtpmap:111 opus/48000/2 115 | SDPUtils.parseRtpMap = function(line) { 116 | var parts = line.substr(9).split(' '); 117 | var parsed = { 118 | payloadType: parseInt(parts.shift(), 10) // was: id 119 | }; 120 | 121 | parts = parts[0].split('/'); 122 | 123 | parsed.name = parts[0]; 124 | parsed.clockRate = parseInt(parts[1], 10); // was: clockrate 125 | // was: channels 126 | parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; 127 | return parsed; 128 | }; 129 | 130 | // Generate an a=rtpmap line from RTCRtpCodecCapability or 131 | // RTCRtpCodecParameters. 132 | SDPUtils.writeRtpMap = function(codec) { 133 | var pt = codec.payloadType; 134 | if (codec.preferredPayloadType !== undefined) { 135 | pt = codec.preferredPayloadType; 136 | } 137 | return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + 138 | (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; 139 | }; 140 | 141 | // Parses an a=extmap line (headerextension from RFC 5285). Sample input: 142 | // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset 143 | SDPUtils.parseExtmap = function(line) { 144 | var parts = line.substr(9).split(' '); 145 | return { 146 | id: parseInt(parts[0], 10), 147 | uri: parts[1] 148 | }; 149 | }; 150 | 151 | // Generates a=extmap line from RTCRtpHeaderExtensionParameters or 152 | // RTCRtpHeaderExtension. 153 | SDPUtils.writeExtmap = function(headerExtension) { 154 | return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + 155 | ' ' + headerExtension.uri + '\r\n'; 156 | }; 157 | 158 | // Parses an ftmp line, returns dictionary. Sample input: 159 | // a=fmtp:96 vbr=on;cng=on 160 | // Also deals with vbr=on; cng=on 161 | SDPUtils.parseFmtp = function(line) { 162 | var parsed = {}; 163 | var kv; 164 | var parts = line.substr(line.indexOf(' ') + 1).split(';'); 165 | for (var j = 0; j < parts.length; j++) { 166 | kv = parts[j].trim().split('='); 167 | parsed[kv[0].trim()] = kv[1]; 168 | } 169 | return parsed; 170 | }; 171 | 172 | // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. 173 | SDPUtils.writeFmtp = function(codec) { 174 | var line = ''; 175 | var pt = codec.payloadType; 176 | if (codec.preferredPayloadType !== undefined) { 177 | pt = codec.preferredPayloadType; 178 | } 179 | if (codec.parameters && Object.keys(codec.parameters).length) { 180 | var params = []; 181 | Object.keys(codec.parameters).forEach(function(param) { 182 | params.push(param + '=' + codec.parameters[param]); 183 | }); 184 | line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; 185 | } 186 | return line; 187 | }; 188 | 189 | // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: 190 | // a=rtcp-fb:98 nack rpsi 191 | SDPUtils.parseRtcpFb = function(line) { 192 | var parts = line.substr(line.indexOf(' ') + 1).split(' '); 193 | return { 194 | type: parts.shift(), 195 | parameter: parts.join(' ') 196 | }; 197 | }; 198 | // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. 199 | SDPUtils.writeRtcpFb = function(codec) { 200 | var lines = ''; 201 | var pt = codec.payloadType; 202 | if (codec.preferredPayloadType !== undefined) { 203 | pt = codec.preferredPayloadType; 204 | } 205 | if (codec.rtcpFeedback && codec.rtcpFeedback.length) { 206 | // FIXME: special handling for trr-int? 207 | codec.rtcpFeedback.forEach(function(fb) { 208 | lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + 209 | (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + 210 | '\r\n'; 211 | }); 212 | } 213 | return lines; 214 | }; 215 | 216 | // Parses an RFC 5576 ssrc media attribute. Sample input: 217 | // a=ssrc:3735928559 cname:something 218 | SDPUtils.parseSsrcMedia = function(line) { 219 | var sp = line.indexOf(' '); 220 | var parts = { 221 | ssrc: parseInt(line.substr(7, sp - 7), 10) 222 | }; 223 | var colon = line.indexOf(':', sp); 224 | if (colon > -1) { 225 | parts.attribute = line.substr(sp + 1, colon - sp - 1); 226 | parts.value = line.substr(colon + 1); 227 | } else { 228 | parts.attribute = line.substr(sp + 1); 229 | } 230 | return parts; 231 | }; 232 | 233 | // Extracts SCTP capabilities from SDP media section or sessionpart. 234 | SDPUtils.getSctpCapabilities = function(mediaSection, sessionpart) { 235 | var lines = SDPUtils.splitLines(mediaSection); 236 | // Search in session part, too. 237 | lines = lines.concat(SDPUtils.splitLines(sessionpart)); 238 | var maxMessageSize = lines.filter(function(line) { 239 | return line.indexOf('a=max-message-size:') === 0; 240 | }); 241 | // TODO: Use 65536 once browsers have fixed their implementations, 242 | // see: https://lgrahl.de/articles/demystifying-webrtc-dc-size-limit.html 243 | maxMessageSize = maxMessageSize.length ? parseInt(maxMessageSize[0].substr(19)) : 16384; 244 | return { 245 | maxMessageSize: maxMessageSize 246 | }; 247 | }; 248 | 249 | // Serializes SCTP capabilities to SDP. 250 | SDPUtils.writeSctpCapabilities = function(capabilities) { 251 | return 'a=max-message-size:' + capabilities.maxMessageSize + '\r\n'; // (03) 252 | }; 253 | 254 | // Extracts SCTP port from SDP media section or sessionpart. 255 | SDPUtils.getSctpPort = function(mediaSection, sessionpart) { 256 | var lines = SDPUtils.splitLines(mediaSection); 257 | // Search in session part, too. 258 | lines = lines.concat(SDPUtils.splitLines(sessionpart)); 259 | var port = lines.filter(function(line) { 260 | return line.indexOf('a=sctp-port:') === 0; 261 | }); 262 | port = port.length ? parseInt(port[0].substr(12), 10) : 5000; 263 | return port; 264 | }; 265 | 266 | // Serializes SCTP port to SDP. 267 | SDPUtils.writeSctpPort = function(port) { 268 | // TODO: Enable (chromium can't cope with it) 269 | // return 'a=sctp-port:' + (port ? port : 5000) + '\r\n'; // (03) 270 | return ''; 271 | }; 272 | 273 | // Extracts DTLS parameters from SDP media section or sessionpart. 274 | // FIXME: for consistency with other functions this should only 275 | // get the fingerprint line as input. See also getIceParameters. 276 | SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { 277 | var lines = SDPUtils.splitLines(mediaSection); 278 | // Search in session part, too. 279 | lines = lines.concat(SDPUtils.splitLines(sessionpart)); 280 | var fpLine = lines.filter(function(line) { 281 | return line.indexOf('a=fingerprint:') === 0; 282 | })[0].substr(14); 283 | // Note: a=setup line is ignored since we use the 'auto' role. 284 | var dtlsParameters = { 285 | role: 'auto', 286 | fingerprints: [{ 287 | algorithm: fpLine.split(' ')[0], 288 | value: fpLine.split(' ')[1] 289 | }] 290 | }; 291 | return dtlsParameters; 292 | }; 293 | 294 | // Serializes DTLS parameters to SDP. 295 | SDPUtils.writeDtlsParameters = function(params, setupType) { 296 | var sdp = 'a=setup:' + setupType + '\r\n'; 297 | params.fingerprints.forEach(function(fp) { 298 | sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; 299 | }); 300 | return sdp; 301 | }; 302 | // Parses ICE information from SDP media section or sessionpart. 303 | // FIXME: for consistency with other functions this should only 304 | // get the ice-ufrag and ice-pwd lines as input. 305 | SDPUtils.getIceParameters = function(mediaSection, sessionpart) { 306 | var lines = SDPUtils.splitLines(mediaSection); 307 | // Search in session part, too. 308 | lines = lines.concat(SDPUtils.splitLines(sessionpart)); 309 | var iceParameters = { 310 | usernameFragment: lines.filter(function(line) { 311 | return line.indexOf('a=ice-ufrag:') === 0; 312 | })[0].substr(12), 313 | password: lines.filter(function(line) { 314 | return line.indexOf('a=ice-pwd:') === 0; 315 | })[0].substr(10) 316 | }; 317 | return iceParameters; 318 | }; 319 | 320 | // Serializes ICE parameters to SDP. 321 | SDPUtils.writeIceParameters = function(params) { 322 | return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 323 | 'a=ice-pwd:' + params.password + '\r\n'; 324 | }; 325 | 326 | // Parses the SDP media section and returns RTCRtpParameters. 327 | SDPUtils.parseRtpParameters = function(mediaSection) { 328 | var description = { 329 | codecs: [], 330 | headerExtensions: [], 331 | fecMechanisms: [], 332 | rtcp: [] 333 | }; 334 | var lines = SDPUtils.splitLines(mediaSection); 335 | var mline = lines[0].split(' '); 336 | for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] 337 | var pt = mline[i]; 338 | var rtpmapline = SDPUtils.matchPrefix( 339 | mediaSection, 'a=rtpmap:' + pt + ' ')[0]; 340 | if (rtpmapline) { 341 | var codec = SDPUtils.parseRtpMap(rtpmapline); 342 | var fmtps = SDPUtils.matchPrefix( 343 | mediaSection, 'a=fmtp:' + pt + ' '); 344 | // Only the first a=fmtp: is considered. 345 | codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; 346 | codec.rtcpFeedback = SDPUtils.matchPrefix( 347 | mediaSection, 'a=rtcp-fb:' + pt + ' ') 348 | .map(SDPUtils.parseRtcpFb); 349 | description.codecs.push(codec); 350 | // parse FEC mechanisms from rtpmap lines. 351 | switch (codec.name.toUpperCase()) { 352 | case 'RED': 353 | case 'ULPFEC': 354 | description.fecMechanisms.push(codec.name.toUpperCase()); 355 | break; 356 | default: // only RED and ULPFEC are recognized as FEC mechanisms. 357 | break; 358 | } 359 | } 360 | } 361 | SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { 362 | description.headerExtensions.push(SDPUtils.parseExtmap(line)); 363 | }); 364 | // FIXME: parse rtcp. 365 | return description; 366 | }; 367 | 368 | // Generates parts of the SDP media section describing the capabilities / 369 | // parameters. 370 | SDPUtils.writeRtpDescription = function(kind, caps) { 371 | var sdp = ''; 372 | 373 | // Build the mline. 374 | sdp += 'm=' + kind + ' '; 375 | sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. 376 | sdp += ' UDP/TLS/RTP/SAVPF '; 377 | sdp += caps.codecs.map(function(codec) { 378 | if (codec.preferredPayloadType !== undefined) { 379 | return codec.preferredPayloadType; 380 | } 381 | return codec.payloadType; 382 | }).join(' ') + '\r\n'; 383 | 384 | sdp += 'c=IN IP4 0.0.0.0\r\n'; 385 | sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; 386 | 387 | // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. 388 | caps.codecs.forEach(function(codec) { 389 | sdp += SDPUtils.writeRtpMap(codec); 390 | sdp += SDPUtils.writeFmtp(codec); 391 | sdp += SDPUtils.writeRtcpFb(codec); 392 | }); 393 | var maxptime = 0; 394 | caps.codecs.forEach(function(codec) { 395 | if (codec.maxptime > maxptime) { 396 | maxptime = codec.maxptime; 397 | } 398 | }); 399 | if (maxptime > 0) { 400 | sdp += 'a=maxptime:' + maxptime + '\r\n'; 401 | } 402 | sdp += 'a=rtcp-mux\r\n'; 403 | 404 | caps.headerExtensions.forEach(function(extension) { 405 | sdp += SDPUtils.writeExtmap(extension); 406 | }); 407 | // FIXME: write fecMechanisms. 408 | return sdp; 409 | }; 410 | 411 | // Parses the SDP media section and returns an array of 412 | // RTCRtpEncodingParameters. 413 | SDPUtils.parseRtpEncodingParameters = function(mediaSection) { 414 | var encodingParameters = []; 415 | var description = SDPUtils.parseRtpParameters(mediaSection); 416 | var hasRed = description.fecMechanisms.indexOf('RED') !== -1; 417 | var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; 418 | 419 | // filter a=ssrc:... cname:, ignore PlanB-msid 420 | var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') 421 | .map(function(line) { 422 | return SDPUtils.parseSsrcMedia(line); 423 | }) 424 | .filter(function(parts) { 425 | return parts.attribute === 'cname'; 426 | }); 427 | var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; 428 | var secondarySsrc; 429 | 430 | var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') 431 | .map(function(line) { 432 | var parts = line.split(' '); 433 | parts.shift(); 434 | return parts.map(function(part) { 435 | return parseInt(part, 10); 436 | }); 437 | }); 438 | if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { 439 | secondarySsrc = flows[0][1]; 440 | } 441 | 442 | description.codecs.forEach(function(codec) { 443 | if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { 444 | var encParam = { 445 | ssrc: primarySsrc, 446 | codecPayloadType: parseInt(codec.parameters.apt, 10), 447 | rtx: { 448 | payloadType: codec.payloadType, 449 | ssrc: secondarySsrc 450 | } 451 | }; 452 | encodingParameters.push(encParam); 453 | if (hasRed) { 454 | encParam = JSON.parse(JSON.stringify(encParam)); 455 | encParam.fec = { 456 | ssrc: secondarySsrc, 457 | mechanism: hasUlpfec ? 'red+ulpfec' : 'red' 458 | }; 459 | encodingParameters.push(encParam); 460 | } 461 | } 462 | }); 463 | if (encodingParameters.length === 0 && primarySsrc) { 464 | encodingParameters.push({ 465 | ssrc: primarySsrc 466 | }); 467 | } 468 | 469 | // we support both b=AS and b=TIAS but interpret AS as TIAS. 470 | var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); 471 | if (bandwidth.length) { 472 | if (bandwidth[0].indexOf('b=TIAS:') === 0) { 473 | bandwidth = parseInt(bandwidth[0].substr(7), 10); 474 | } else if (bandwidth[0].indexOf('b=AS:') === 0) { 475 | bandwidth = parseInt(bandwidth[0].substr(5), 10); 476 | } 477 | encodingParameters.forEach(function(params) { 478 | params.maxBitrate = bandwidth; 479 | }); 480 | } 481 | return encodingParameters; 482 | }; 483 | 484 | SDPUtils.writeSessionBoilerplate = function() { 485 | // FIXME: sess-id should be an NTP timestamp. 486 | return 'v=0\r\n' + 487 | 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + 488 | 's=-\r\n' + 489 | 't=0 0\r\n'; 490 | }; 491 | 492 | SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { 493 | var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); 494 | 495 | // Map ICE parameters (ufrag, pwd) to SDP. 496 | sdp += SDPUtils.writeIceParameters( 497 | transceiver.iceGatherer.getLocalParameters()); 498 | 499 | // Map DTLS parameters to SDP. 500 | sdp += SDPUtils.writeDtlsParameters( 501 | transceiver.dtlsTransport.getLocalParameters(), 502 | type === 'offer' ? 'actpass' : 'active'); 503 | 504 | sdp += 'a=mid:' + transceiver.mid + '\r\n'; 505 | 506 | if (transceiver.rtpSender && transceiver.rtpReceiver) { 507 | sdp += 'a=sendrecv\r\n'; 508 | } else if (transceiver.rtpSender) { 509 | sdp += 'a=sendonly\r\n'; 510 | } else if (transceiver.rtpReceiver) { 511 | sdp += 'a=recvonly\r\n'; 512 | } else { 513 | sdp += 'a=inactive\r\n'; 514 | } 515 | 516 | // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. 517 | if (transceiver.rtpSender) { 518 | var msid = 'msid:' + stream.id + ' ' + 519 | transceiver.rtpSender.track.id + '\r\n'; 520 | sdp += 'a=' + msid; 521 | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + 522 | ' ' + msid; 523 | } 524 | // FIXME: this should be written by writeRtpDescription. 525 | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + 526 | ' cname:' + SDPUtils.localCName + '\r\n'; 527 | return sdp; 528 | }; 529 | 530 | // Gets the direction from the mediaSection or the sessionpart. 531 | SDPUtils.getDirection = function(mediaSection, sessionpart) { 532 | // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. 533 | var lines = SDPUtils.splitLines(mediaSection); 534 | for (var i = 0; i < lines.length; i++) { 535 | switch (lines[i]) { 536 | case 'a=sendrecv': 537 | case 'a=sendonly': 538 | case 'a=recvonly': 539 | case 'a=inactive': 540 | return lines[i].substr(2); 541 | default: 542 | // FIXME: What should happen here? 543 | } 544 | } 545 | if (sessionpart) { 546 | return SDPUtils.getDirection(sessionpart); 547 | } 548 | return 'sendrecv'; 549 | }; 550 | -------------------------------------------------------------------------------- /web/webrtc-rawrtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Peer { 4 | constructor() { 5 | this.pc = null; 6 | this.localMid = null; 7 | this.localCandidates = []; 8 | this.localParameters = null; 9 | this.localDescription = null; 10 | this.remoteParameters = null; 11 | this.remoteDescription = null; 12 | var _waitGatheringComplete = {}; 13 | _waitGatheringComplete.promise = new Promise((resolve, reject) => { 14 | _waitGatheringComplete.resolve = resolve; 15 | _waitGatheringComplete.reject = reject; 16 | }); 17 | this._waitGatheringComplete = _waitGatheringComplete; 18 | this.dc = {} 19 | } 20 | 21 | createPeerConnection() { 22 | if (this.pc) { 23 | console.warn('RTCPeerConnection already created'); 24 | return this.pc; 25 | } 26 | 27 | var self = this; 28 | 29 | // Create peer connection 30 | var pc = new RTCPeerConnection({ 31 | iceServers: [{ 32 | urls: 'stun:stun.l.google.com:19302' 33 | }] 34 | }); 35 | 36 | // Bind peer connection events 37 | pc.onnegotiationneeded = function(event) { 38 | console.log('Negotiation needed') 39 | }; 40 | pc.onicecandidate = function(event) { 41 | if (event.candidate) { 42 | console.log('Gathered candidate:', event.candidate); 43 | self.localCandidates.push(event.candidate); 44 | } else { 45 | console.log('Gathering complete'); 46 | self._waitGatheringComplete.resolve(); 47 | } 48 | }; 49 | pc.onicecandidateerror = function(event) { 50 | console.error('ICE candidate error:', event.errorText); 51 | }; 52 | pc.onsignalingstatechange = function(event) { 53 | console.log('Signaling state changed to:', pc.signalingState); 54 | }; 55 | pc.oniceconnectionstatechange = function(event) { 56 | console.log('ICE connection state changed to:', pc.iceConnectionState); 57 | }; 58 | pc.onicegatheringstatechange = function(event) { 59 | console.log('ICE gathering state changed to:', pc.iceGatheringState); 60 | }; 61 | pc.onconnectionstatechange = function(event) { 62 | console.log('Connection state changed to:', pc.connectionState); 63 | }; 64 | pc.ondatachannel = function(event) { 65 | self.createDataChannel(event.channel); 66 | }; 67 | 68 | this.pc = pc; 69 | return pc; 70 | } 71 | 72 | createDataChannel(dc) { 73 | // Create data channel 74 | dc = (typeof dc !== 'undefined') ? dc : this.pc.createDataChannel('example-channel', { 75 | ordered: true 76 | }); 77 | 78 | // Bind data channel events 79 | dc.onopen = function(event) { 80 | console.log('Data channel', dc.label, '(', dc.id, ')', 'open'); 81 | }; 82 | dc.onbufferedamountlow = function(event) { 83 | console.log('Data channel', dc.label, '(', dc.id, ')', 'buffered amount low'); 84 | }; 85 | dc.onerror = function(event) { 86 | console.error('Data channel', dc.label, '(', dc.id, ')', 'error:', event); 87 | }; 88 | dc.onclose = function(event) { 89 | console.log('Data channel', dc.label, '(', dc.id, ')', 'closed'); 90 | }; 91 | dc.onmessage = function(event) { 92 | var length = event.data.size || event.data.byteLength || event.data.length; 93 | console.info('Data channel', dc.label, '(', dc.id, ')', 'message size:', length, 94 | 'content:\n' + event.data); 95 | }; 96 | 97 | // Store channel 98 | this.dc[dc.label] = dc; 99 | return dc; 100 | } 101 | 102 | getLocalParameters() { 103 | return new Promise((resolve, reject) => { 104 | var error; 105 | var self = this; 106 | 107 | if (!this.localDescription) { 108 | error = 'Must create offer/answer'; 109 | console.error(error); 110 | reject(error); 111 | return; 112 | } 113 | 114 | // Initialise parameters 115 | var parameters = { 116 | iceParameters: null, 117 | iceCandidates: [], 118 | dtlsParameters: null, 119 | sctpParameters: null, 120 | }; 121 | 122 | // Split sections 123 | var sections = SDPUtils.splitSections(this.localDescription.sdp); 124 | var session = sections.shift(); 125 | 126 | // Go through media sections 127 | sections.forEach(function(mediaSection, sdpMLineIndex) { 128 | // TODO: Ignore anything else but data transports 129 | 130 | // Get mid 131 | // TODO: This breaks with multiple transceivers 132 | if (!self.localMid) { 133 | var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); 134 | if (mid.length > 0) { 135 | self.localMid = mid[0].substr(6); 136 | } 137 | } 138 | 139 | // Get ICE parameters 140 | if (!parameters.iceParameters) { 141 | parameters.iceParameters = SDPUtils.getIceParameters(mediaSection, session); 142 | } 143 | 144 | // Get DTLS parameters 145 | if (!parameters.dtlsParameters) { 146 | parameters.dtlsParameters = SDPUtils.getDtlsParameters(mediaSection, session); 147 | } 148 | 149 | // Get SCTP parameters 150 | if (!parameters.sctpParameters) { 151 | parameters.sctpParameters = SDPUtils.getSctpCapabilities(mediaSection, session); 152 | parameters.sctpParameters.port = SDPUtils.getSctpPort(mediaSection, session); 153 | } 154 | }); 155 | 156 | // ICE lite parameter 157 | if (!parameters.iceParameters 158 | || !parameters.dtlsParameters 159 | || !parameters.sctpParameters) { 160 | error = 'Could not retrieve required parameters from local description'; 161 | console.error(error); 162 | reject(error); 163 | return; 164 | } 165 | parameters.iceParameters.iceLite = 166 | SDPUtils.matchPrefix(session, 'a=ice-lite').length > 0; 167 | 168 | // Get ICE candidates 169 | this._waitGatheringComplete.promise.then(() => { 170 | // Add ICE candidates 171 | for (var sdpCandidate of self.localCandidates) { 172 | var candidate = SDPUtils.parseCandidate(sdpCandidate.candidate); 173 | parameters.iceCandidates.push(candidate); 174 | } 175 | 176 | // Add ICE candidate complete sentinel 177 | // parameters.iceCandidates.push({complete: true}); // TODO 178 | 179 | // Done 180 | resolve(parameters); 181 | }); 182 | }); 183 | } 184 | 185 | setRemoteParameters(parameters, type, localMid = null) { 186 | return new Promise((resolve, reject) => { 187 | if (this.remoteDescription) { 188 | resolve(this.remoteDescription); 189 | return; 190 | } 191 | 192 | if (!this.pc) { 193 | console.error('Must create RTCPeerConnection instance'); 194 | return; 195 | } 196 | 197 | if (!localMid) { 198 | localMid = this.localMid; 199 | } 200 | this.remoteParameters = parameters; 201 | 202 | // Translate DTLS role 203 | // TODO: This somehow didn't make it into SDPUtils 204 | var setupType; 205 | switch (parameters.dtlsParameters.role) { 206 | case 'client': 207 | setupType = 'active'; 208 | break; 209 | case 'server': 210 | setupType = 'passive'; 211 | break; 212 | default: 213 | // We map 'offer' to 'controlling' and 'answer' to 'controlled', 214 | // so rawrtc will take 'server' if offering and 'client' if answering 215 | // as specified by the ORTC spec 216 | switch (type) { 217 | case 'offer': 218 | // WebRTC requires actpass in offer 219 | setupType = 'actpass'; 220 | break; 221 | case 'answer': 222 | setupType = 'active'; 223 | break; 224 | } 225 | break; 226 | } 227 | 228 | // Write session section 229 | var sdp = SDPUtils.writeSessionBoilerplate(); 230 | sdp += 'a=group:BUNDLE ' + localMid + '\r\n'; 231 | sdp += 'a=ice-options:trickle\r\n'; 232 | if (parameters.iceParameters.iceLite) { 233 | sdp += 'a=ice-lite\r\n'; 234 | } 235 | 236 | // Write media section 237 | // TODO: Replace 238 | // sdp += 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n'; // (03) 239 | sdp += 'm=application 9 DTLS/SCTP ' + parameters.sctpParameters.port + '\r\n'; // (01) 240 | sdp += 'c=IN IP4 0.0.0.0\r\n'; 241 | sdp += 'a=mid:' + localMid + '\r\n'; 242 | sdp += 'a=sendrecv\r\n'; 243 | 244 | // SCTP part 245 | sdp += SDPUtils.writeSctpCapabilities(parameters.sctpParameters); 246 | sdp += SDPUtils.writeSctpPort(parameters.sctpParameters.port); 247 | sdp += 'a=sctpmap:' + parameters.sctpParameters.port + ' webrtc-datachannel 65535\r\n'; // (01) 248 | 249 | // DTLS part 250 | sdp += SDPUtils.writeDtlsParameters(parameters.dtlsParameters, setupType); 251 | 252 | // ICE part 253 | sdp += 'a=connection:new\r\n'; // (03) 254 | sdp += SDPUtils.writeIceParameters(parameters.iceParameters); 255 | 256 | // Done 257 | console.log('Remote description:\n' + sdp); 258 | 259 | // Set remote description 260 | this.pc.setRemoteDescription({type: type, sdp: sdp}) 261 | .then(() => { 262 | console.log('Actual remote description:\n' + this.pc.remoteDescription.sdp); 263 | this.remoteDescription = this.pc.remoteDescription; 264 | 265 | // Add ICE candidates 266 | for (var iceCandidate of parameters.iceCandidates) { 267 | // Add component which ORTC doesn't have 268 | // Note: We choose RTP as it doesn't actually matter for us 269 | iceCandidate.component = 1; // RTP 270 | 271 | // Create 272 | var candidate = new RTCIceCandidate({ 273 | candidate: SDPUtils.writeCandidate(iceCandidate), 274 | sdpMLineIndex: 0, // TODO: Fix 275 | sdpMid: localMid // TODO: Fix 276 | }); 277 | 278 | // Add 279 | console.log(candidate.candidate); 280 | this.pc.addIceCandidate(candidate) 281 | .then(() => { 282 | console.log('Added remote candidate', candidate); 283 | }); 284 | } 285 | 286 | // It's trickle ICE, no need to wait for candidates to be added 287 | resolve(); 288 | }) 289 | .catch((error) => { 290 | reject(error); 291 | }); 292 | }); 293 | } 294 | 295 | start() {} 296 | } 297 | 298 | class ControllingPeer extends Peer { 299 | getLocalParameters() { 300 | return new Promise((resolve, reject) => { 301 | if (!this.pc) { 302 | var error = 'Must create RTCPeerConnection instance'; 303 | console.error(error); 304 | reject(error); 305 | return; 306 | } 307 | 308 | var getLocalParameters = () => { 309 | // Return parameters 310 | super.getLocalParameters() 311 | .then((parameters) => { 312 | this.localParameters = parameters; 313 | resolve(parameters); 314 | }) 315 | .catch((error) => { 316 | reject(error); 317 | }); 318 | }; 319 | 320 | // Create offer 321 | if (!this.localDescription) { 322 | this.pc.createOffer() 323 | .then((description) => { 324 | return this.pc.setLocalDescription(description); 325 | }) 326 | .then(() => { 327 | console.log('Local description:\n' + this.pc.localDescription.sdp); 328 | this.localDescription = this.pc.localDescription; 329 | getLocalParameters(); 330 | }) 331 | .catch((error) => { 332 | reject(error); 333 | }); 334 | } else { 335 | getLocalParameters(); 336 | } 337 | }); 338 | } 339 | 340 | setRemoteParameters(parameters, localMid = null) { 341 | return super.setRemoteParameters(parameters, 'answer', localMid); 342 | } 343 | } 344 | 345 | class ControlledPeer extends Peer { 346 | getLocalParameters() { 347 | return new Promise((resolve, reject) => { 348 | var error; 349 | 350 | if (!this.pc) { 351 | error = 'Must create RTCPeerConnection instance'; 352 | console.error(error); 353 | reject(error); 354 | return; 355 | } 356 | if (!this.remoteDescription) { 357 | error = 'Must have remote description'; 358 | console.error(error); 359 | reject(error); 360 | return; 361 | } 362 | 363 | var getLocalParameters = () => { 364 | // Return parameters 365 | super.getLocalParameters() 366 | .then((parameters) => { 367 | resolve(parameters); 368 | }) 369 | .catch((error) => { 370 | reject(error); 371 | }); 372 | }; 373 | 374 | // Create answer 375 | if (!this.localDescription) { 376 | this.pc.createAnswer() 377 | .then((description) => { 378 | return this.pc.setLocalDescription(description); 379 | }) 380 | .then(() => { 381 | console.log('Local description:\n' + this.pc.localDescription.sdp); 382 | this.localDescription = this.pc.localDescription; 383 | getLocalParameters(); 384 | }); 385 | } else { 386 | getLocalParameters(); 387 | } 388 | }); 389 | } 390 | 391 | setRemoteParameters(parameters, localMid = null) { 392 | return super.setRemoteParameters(parameters, 'offer', localMid); 393 | } 394 | } 395 | --------------------------------------------------------------------------------