diff --git a/README.md b/README.md index 78e8198..03c8f50 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ cd apple-libressl-sdk git checkout fce8ffe30c9939fa1d71076745d9b3c811b8a90e ``` -**Webview for windodows** +**Webview for windows** ```bash # Windows needs the WebView2 SDK while mac and linux are OS implementations curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2 -o webview2.zip powershell -Command "Expand-Archive -Path webview2.zip -DestinationPath webview2-tmp -Force" xcopy /E /I /Y webview2-tmp\build\native\include webview2\include\ mkdir webview2\lib\x64 -copy webview2-tmp\build\native\x64\WebView2Loader.dll.lib webview2\lib\x64\WebView2Loader.lib +copy webview2-tmp\build\native\x64\WebView2LoaderStatic.lib webview2\lib\x64\WebView2LoaderStatic.lib rmdir /S /Q webview2-tmp del webview2.zip ``` diff --git a/Sources/async_engine.h b/Sources/async_engine.h index 2ab7584..651f94d 100644 --- a/Sources/async_engine.h +++ b/Sources/async_engine.h @@ -1,5 +1,6 @@ #pragma once +#include "lockfree_queue.h" #include #include #include @@ -14,61 +15,7 @@ namespace EngineManager { // thread communication template -class LockFreeQueue { -private: - struct Node { - std::atomic data{nullptr}; - std::atomic next{nullptr}; - }; - - std::atomic head_{nullptr}; - std::atomic tail_{nullptr}; - -public: - LockFreeQueue() { - Node* dummy = new Node; - head_.store(dummy); - tail_.store(dummy); - } - - ~LockFreeQueue() { - while (Node* const old_head = head_.load()) { - head_.store(old_head->next); - delete old_head->data.load(); - delete old_head; - } - } - - void push(T item) { - Node* new_node = new Node; - T* data = new T(std::move(item)); - new_node->data.store(data); - - Node* prev_tail = tail_.exchange(new_node); - prev_tail->next.store(new_node); - } - - bool try_pop(T& result) { - Node* head = head_.load(); - Node* next = head->next.load(); - - if (next == nullptr) return false; - - T* data = next->data.load(); - if (data == nullptr) return false; - - result = *data; - delete data; - head_.store(next); - delete head; - return true; - } - - bool empty() const { - Node* head = head_.load(); - return (head->next.load() == nullptr); - } -}; +using LockFreeQueue = ::LockFreeQueue; // thread pool stealing class ThreadPool { diff --git a/Sources/connection_pool.cpp b/Sources/connection_pool.cpp index 3823950..b3b7078 100644 --- a/Sources/connection_pool.cpp +++ b/Sources/connection_pool.cpp @@ -7,6 +7,8 @@ #pragma comment(lib, "ws2_32.lib") #else #include +#include +#include #include #include #include @@ -34,7 +36,8 @@ ConnectionPool::~ConnectionPool() { } } -socket_t ConnectionPool::acquire_connection(const std::string& host, int port, bool is_ssl) { +socket_t ConnectionPool::acquire_connection(const std::string& host, int port, bool is_ssl, + std::atomic* cancel) { std::string key = make_key(host, port, is_ssl); { @@ -83,7 +86,65 @@ socket_t ConnectionPool::acquire_connection(const std::string& host, int port, b memcpy(&server_addr.sin_addr, host_entry->h_addr, host_entry->h_length); - if (::connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { +#ifdef _WIN32 + u_long nb_mode = 1; + ioctlsocket(sock, FIONBIO, &nb_mode); +#else + int old_flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, old_flags | O_NONBLOCK); +#endif + + bool connected = (::connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0); + if (!connected) { +#ifdef _WIN32 + int connect_err = WSAGetLastError(); + bool in_progress = (connect_err == WSAEWOULDBLOCK || connect_err == WSAEINPROGRESS); +#else + bool in_progress = (errno == EINPROGRESS); +#endif + auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(15000); + while (in_progress) { + if (cancel != nullptr && cancel->load()) { + in_progress = false; + break; + } + if (std::chrono::steady_clock::now() >= deadline) { + in_progress = false; + break; + } + fd_set wfds, efds; + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(sock, &wfds); + FD_SET(sock, &efds); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 50000; +#ifdef _WIN32 + int sel = ::select(0, nullptr, &wfds, &efds, &tv); +#else + int sel = ::select(static_cast(sock) + 1, nullptr, &wfds, &efds, &tv); +#endif + if (sel > 0) { + int so_err = 0; + socklen_t so_len = sizeof(so_err); + getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&so_err, &so_len); + connected = (so_err == 0); + break; + } + if (sel < 0) break; + } + } + +#ifdef _WIN32 + nb_mode = 0; + ioctlsocket(sock, FIONBIO, &nb_mode); +#else + fcntl(sock, F_SETFL, old_flags); +#endif + + if (!connected) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to connect to %s:%d", host.c_str(), port); #ifdef _WIN32 closesocket(sock); @@ -165,9 +226,23 @@ std::string ConnectionPool::make_key(const std::string& host, int port, bool is_ } bool ConnectionPool::is_socket_alive(socket_t socket) const { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(socket, &rfds); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; +#ifdef _WIN32 + int sel = ::select(0, &rfds, nullptr, nullptr, &tv); +#else + int sel = ::select(static_cast(socket) + 1, &rfds, nullptr, nullptr, &tv); +#endif + if (sel < 0) return false; + if (sel == 0) return true; char test_byte; int result = ::recv(socket, &test_byte, 1, MSG_PEEK); - + if (result == 0) return false; + #ifdef _WIN32 if (result == SOCKET_ERROR) { int error = WSAGetLastError(); @@ -178,6 +253,6 @@ bool ConnectionPool::is_socket_alive(socket_t socket) const { return (errno == EWOULDBLOCK || errno == EAGAIN); } #endif - - return true; + + return false; } diff --git a/Sources/connection_pool.h b/Sources/connection_pool.h index 8054f6a..3ac407f 100644 --- a/Sources/connection_pool.h +++ b/Sources/connection_pool.h @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -31,7 +32,8 @@ public: ConnectionPool() = default; ~ConnectionPool(); - socket_t acquire_connection(const std::string& host, int port, bool is_ssl = false); + socket_t acquire_connection(const std::string& host, int port, bool is_ssl = false, + std::atomic* cancel = nullptr); // returns connection to pool for reuse void release_connection(const std::string& host, int port, socket_t socket, bool is_ssl = false); diff --git a/Sources/httprequest.cpp b/Sources/httprequest.cpp index 1bd59bf..e0ca230 100644 --- a/Sources/httprequest.cpp +++ b/Sources/httprequest.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -299,14 +300,52 @@ namespace HttpRequestWrapper { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Destroying HTTP request client"); #endif + should_abort_ = true; + int s = active_socket_.exchange(-1); + if (s >= 0) { + #ifdef _WIN32 + ::shutdown(static_cast(s), SD_BOTH); + #else + ::shutdown(s, SHUT_RDWR); + #endif + } + if (request_future_.valid()) { + request_future_.wait(); + } #ifdef WITH_SSL cleanupSSL(); #endif + if (isolate_ != nullptr) { + v8::Locker locker{isolate_}; + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + on_ready_state_change_.Reset(); + on_load_.Reset(); + on_error_.Reset(); + on_timeout_.Reset(); + on_abort_.Reset(); + on_progress_.Reset(); + on_load_start_.Reset(); + on_load_end_.Reset(); + } } void HttpRequestClient::open(const std::string& method, const std::string& url, bool async) { + if (request_future_.valid()) { + should_abort_ = true; + int s = active_socket_.exchange(-1); + if (s >= 0) { + #ifdef _WIN32 + ::shutdown(static_cast(s), SD_BOTH); + #else + ::shutdown(s, SHUT_RDWR); + #endif + } + request_future_.wait(); + } + std::lock_guard lock(state_mutex_); - + method_ = method; url_ = url; async_ = async; @@ -350,10 +389,11 @@ namespace HttpRequestWrapper { #endif request_data_ = data; - + should_abort_ = false; + if (async_) { // use thread pool - GlobalThreadPool::getInstance().enqueue([this]() { + request_future_ = GlobalThreadPool::getInstance().enqueue([this]() { this->performRequest(); }); } else { @@ -364,11 +404,21 @@ namespace HttpRequestWrapper { void HttpRequestClient::abort() { std::lock_guard lock(state_mutex_); - + if (ready_state_ == DONE) { return; } - + + should_abort_ = true; + int s = active_socket_.exchange(-1); + if (s >= 0) { + #ifdef _WIN32 + ::shutdown(static_cast(s), SD_BOTH); + #else + ::shutdown(s, SHUT_RDWR); + #endif + } + setReadyState(DONE); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request aborted"); @@ -437,6 +487,26 @@ namespace HttpRequestWrapper { on_load_end_.Reset(isolate_, callback); } + static size_t response_end(const std::string& response, const std::string& method) { + size_t header_end = response.find("\r\n\r\n"); + if (header_end == std::string::npos) return std::string::npos; + std::string lower(response, 0, header_end); + for (char& c : lower) c = (char)std::tolower((unsigned char)c); + int status = atoi(lower.c_str() + lower.find(' ') + 1); + if (status == 204 || status == 304 || method == "HEAD" || method == "head") { + return header_end + 4; + } + size_t pos = lower.find("content-length:"); + if (pos != std::string::npos) { + return header_end + 4 + strtoull(lower.c_str() + pos + 15, nullptr, 10); + } + if (lower.find("transfer-encoding:") != std::string::npos) { + size_t term = response.find("0\r\n\r\n", header_end + 4); + return term == std::string::npos ? std::string::npos : term + 5; + } + return std::string::npos; + } + void HttpRequestClient::performRequest() { active_operation_ = true; @@ -497,7 +567,8 @@ namespace HttpRequestWrapper { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Acquiring connection for HTTP request"); #endif - SOCKET sock = g_connection_pool.acquire_connection(host, port, is_https); + SOCKET sock = g_connection_pool.acquire_connection(host, port, is_https, &should_abort_); + active_socket_ = static_cast(sock); if (sock == INVALID_SOCKET) { response_.error = "Failed to acquire connection"; #ifdef DEBUG_NETWORK @@ -523,27 +594,36 @@ namespace HttpRequestWrapper { ::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); #endif } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", host.c_str(), port); + #endif #ifdef WITH_SSL if (is_https) { + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Performing SSL handshake"); + #endif ssl_socket_ = static_cast(sock); if (!performSSLHandshake(static_cast(sock), host)) { response_.error = "SSL handshake failed"; + active_socket_ = -1; closesocket(sock); setReadyState(DONE); handleError(response_.error); active_operation_ = false; return; } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake completed successfully"); + #endif } #endif std::string request_data = formatHttpRequest(method_, path, host, request_data_); + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Sending HTTP request (%zu bytes):", request_data.length()); kinc_log(KINC_LOG_LEVEL_INFO, "Request preview: %.200s", request_data.c_str()); + #endif int bytes_sent = 0; #ifdef WITH_SSL @@ -565,20 +645,34 @@ namespace HttpRequestWrapper { return; } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request sent successfully"); + #endif char buffer[4096]; int bytes_received; std::string response_data; int total_bytes = 0; + bool body_complete = false; + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Starting to receive HTTP response"); + #endif while (true) { + size_t end = response_end(response_data, method_); + if (end != std::string::npos && response_data.size() >= end) { + body_complete = true; + break; + } #ifdef WITH_SSL if (is_https) { + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Calling sslRead for %zu bytes", sizeof(buffer) - 1); + #endif bytes_received = sslRead(buffer, sizeof(buffer) - 1); + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "sslRead returned %d bytes", bytes_received); + #endif } else { bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0); } @@ -587,16 +681,21 @@ namespace HttpRequestWrapper { #endif if (bytes_received <= 0) { + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Breaking from response reading loop, bytes_received: %d", bytes_received); + #endif break; } - buffer[bytes_received] = '\0'; - response_data += buffer; + response_data.append(buffer, bytes_received); total_bytes += bytes_received; + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes (total: %d)", bytes_received, total_bytes); + #endif } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Finished receiving data, total bytes: %d", total_bytes); + #endif #ifdef WITH_SSL if (is_https) { @@ -604,24 +703,40 @@ namespace HttpRequestWrapper { } #endif - // return connection to pool for reuse instead of closing - g_connection_pool.release_connection(host, port, sock, is_https); - + active_socket_ = -1; + if (bytes_received == SOCKET_ERROR) { response_.error = "Failed to receive response"; + closesocket(sock); setReadyState(DONE); handleError(response_.error); active_operation_ = false; return; } + + if (should_abort_.load()) { + closesocket(sock); + active_operation_ = false; + return; + } + + if (body_complete) { + g_connection_pool.release_connection(host, port, sock, is_https); + } else { + closesocket(sock); + } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Raw HTTP response data (%zu bytes):", response_data.length()); kinc_log(KINC_LOG_LEVEL_INFO, "Response preview: %.200s", response_data.c_str()); + #endif parseHttpResponse(response_data); + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Parsed response - status: %d, body length: %zu", response_.status, response_.responseText.length()); kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %.200s", response_.responseText.c_str()); + #endif setReadyState(HEADERS_RECEIVED); @@ -661,6 +776,7 @@ namespace HttpRequestWrapper { } void HttpRequestClient::handleError(const std::string& error) { + if (should_abort_.load()) return; // abort() already fired its event std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_ERROR, error)); } @@ -832,7 +948,9 @@ namespace HttpRequestWrapper { bool HttpRequestClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path, bool& is_https) { if (url.find("://") == std::string::npos) { + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Detected local file path: %s", url.c_str()); + #endif host = "localhost"; port = 0; path = url; @@ -841,7 +959,9 @@ namespace HttpRequestWrapper { } if (url.substr(0, 7) == "file://") { + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Detected file:// URL: %s", url.c_str()); + #endif host = "localhost"; port = 0; // Special port to indicate local file path = url.substr(7); // Remove "file://" prefix @@ -852,7 +972,9 @@ namespace HttpRequestWrapper { std::regex url_regex(R"(^(https?):\/\/([^\/\s:]+)(?::(\d+))?(\/[^\s]*)?(?:\?[^\s]*)?(?:#[^\s]*)?$)"); std::smatch matches; + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Parsing HTTP URL: %s", url.c_str()); + #endif if (!std::regex_match(url, matches, url_regex)) { // fallback parsing for simpler URLs @@ -900,8 +1022,10 @@ namespace HttpRequestWrapper { port = is_https ? 443 : 80; } - kinc_log(KINC_LOG_LEVEL_INFO, "URL parsed successfully - scheme: %s, host: %s, port: %d, path: %s", + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_INFO, "URL parsed successfully - scheme: %s, host: %s, port: %d, path: %s", scheme.c_str(), host.c_str(), port, path.c_str()); + #endif return true; } @@ -911,7 +1035,7 @@ namespace HttpRequestWrapper { request << method << " " << path << " HTTP/1.1\r\n"; request << "Host: " << host << "\r\n"; - request << "Connection: close\r\n"; + request << "Connection: keep-alive\r\n"; request << "User-Agent: LNXCORE/1.0\r\n"; request << "Accept: */*\r\n"; request << "Accept-Encoding: identity\r\n"; @@ -935,8 +1059,10 @@ namespace HttpRequestWrapper { } std::string request_str = request.str(); + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Formatted HTTP request (%zu bytes):", request_str.length()); kinc_log(KINC_LOG_LEVEL_INFO, "Request headers: %s", request_str.substr(0, request_str.find("\r\n\r\n")).c_str()); + #endif return request_str; } @@ -1011,11 +1137,15 @@ namespace HttpRequestWrapper { response_.headers[header_name] = header_value; header_count++; + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Header: %s = %s", header_name.c_str(), header_value.c_str()); + #endif } } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Parsed %d headers", header_count); + #endif std::ostringstream body_stream; std::string body_line; @@ -1030,12 +1160,16 @@ namespace HttpRequestWrapper { } response_.responseText = body_stream.str(); + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Parsed body: %zu lines, %zu bytes", body_lines, response_.responseText.length()); + #endif + #ifdef DEBUG_NETWORK if (!response_.responseText.empty()) { std::string preview = response_.responseText.substr(0, 100); kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %s", preview.c_str()); } + #endif } void HttpRequestClient::loadLocalFile(const std::string& path) { @@ -2394,4 +2528,4 @@ void bind_httprequest_class(Isolate* isolate, const Global& context) { global->Set(current_context, String::NewFromUtf8(isolate, "XMLHttpRequest").ToLocalChecked(), xhrFunc); } -#endif \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/httprequest.h b/Sources/httprequest.h index cd727ab..20e720c 100644 --- a/Sources/httprequest.h +++ b/Sources/httprequest.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #ifdef WITH_SSL @@ -101,7 +102,9 @@ namespace HttpRequestWrapper { bool with_credentials_; bool async_; std::thread request_thread_; - std::atomic should_abort_; + std::atomic should_abort_{false}; + std::future request_future_; + std::atomic active_socket_{-1}; v8::Global on_ready_state_change_; v8::Global on_load_; diff --git a/Sources/lockfree_queue.h b/Sources/lockfree_queue.h index 66303f4..a06274e 100644 --- a/Sources/lockfree_queue.h +++ b/Sources/lockfree_queue.h @@ -59,6 +59,7 @@ public: } result = *data; + next->data.store(nullptr); delete data; head_.store(next); diff --git a/Sources/main.cpp b/Sources/main.cpp index 264b991..42723a0 100644 --- a/Sources/main.cpp +++ b/Sources/main.cpp @@ -38,6 +38,10 @@ #include "websocket.h" #endif +#ifdef WITH_RTC +#include "rtc_v8_bindings.h" +#endif + #ifdef WITH_BENCHMARK #include "BENCHMARK/websocket_benchmark.h" extern "C" void websocket_run_benchmark(); @@ -1325,9 +1329,7 @@ namespace { } int runt_get_samples_per_second_fast(Local receiver) { - int rate = kinc_a2_samples_per_second(); - kinc_log(KINC_LOG_LEVEL_INFO, "Samples per second: %d Hz.", rate); - return rate; + return kinc_a2_samples_per_second(); } void runt_get_samples_per_second(const FunctionCallbackInfo &args) { @@ -2296,11 +2298,14 @@ namespace { HandleScope scope(args.GetIsolate()); String::Utf8Value utf8_path(isolate, args[0]); + if (args.Length() < 2 || !args[1]->IsArrayBuffer()) return; + Local buffer = Local::Cast(args[1]); std::shared_ptr content = buffer->GetBackingStore(); bool hasLengthArg = args.Length() > 2 && !args[2]->IsNullOrUndefined(); int byteLength = hasLengthArg ? args[2]->ToInt32(isolate->GetCurrentContext()).ToLocalChecked()->Value() : (int)content->ByteLength(); + if (byteLength < 0) byteLength = 0; if (byteLength > (int)content->ByteLength()) byteLength = (int)content->ByteLength(); #ifdef KINC_WINDOWS @@ -2314,6 +2319,112 @@ namespace { fclose(file); } + void runt_file_write_bytes(const FunctionCallbackInfo &args) { + HandleScope scope(args.GetIsolate()); + String::Utf8Value utf8_path(isolate, args[0]); + + if (args.Length() < 2 || !args[1]->IsArrayBuffer()) return; + + Local buffer = Local::Cast(args[1]); + std::shared_ptr content = buffer->GetBackingStore(); + + int64_t offset = 0; + if (args.Length() > 2 && !args[2]->IsNullOrUndefined()) { + offset = (int64_t)args[2]->ToNumber(isolate->GetCurrentContext()).ToLocalChecked()->Value(); + } + + bool hasLengthArg = args.Length() > 3 && !args[3]->IsNullOrUndefined(); + int byteLength = hasLengthArg ? args[3]->ToInt32(isolate->GetCurrentContext()).ToLocalChecked()->Value() : (int)content->ByteLength(); + if (byteLength < 0) byteLength = 0; + if (byteLength > (int)content->ByteLength()) byteLength = (int)content->ByteLength(); + + #ifdef KINC_WINDOWS + MultiByteToWideChar(CP_UTF8, 0, *utf8_path, -1, temp_wstring, 1024); + FILE *file = _wfopen(temp_wstring, L"r+b"); + if (file == nullptr) file = _wfopen(temp_wstring, L"w+b"); + #else + FILE *file = fopen(*utf8_path, "r+b"); + if (file == nullptr) file = fopen(*utf8_path, "w+b"); + #endif + if (file == nullptr) { + args.GetReturnValue().Set(Boolean::New(isolate, false)); + return; + } + #ifdef KINC_WINDOWS + _fseeki64(file, offset, SEEK_SET); + #else + fseeko(file, offset, SEEK_SET); + #endif + size_t written = fwrite(content->Data(), 1, byteLength, file); + fclose(file); + args.GetReturnValue().Set(Boolean::New(isolate, written == (size_t)byteLength)); + } + + void runt_file_read_bytes(const FunctionCallbackInfo &args) { + HandleScope scope(args.GetIsolate()); + String::Utf8Value utf8_path(isolate, args[0]); + + int64_t offset = 0; + if (args.Length() > 1 && !args[1]->IsNullOrUndefined()) { + offset = (int64_t)args[1]->ToNumber(isolate->GetCurrentContext()).ToLocalChecked()->Value(); + } + int length = 0; + if (args.Length() > 2 && !args[2]->IsNullOrUndefined()) { + length = args[2]->ToInt32(isolate->GetCurrentContext()).ToLocalChecked()->Value(); + } + if (offset < 0 || length <= 0) { + args.GetReturnValue().Set(ArrayBuffer::New(isolate, 0)); + return; + } + + Local buffer = ArrayBuffer::New(isolate, length); + std::shared_ptr content = buffer->GetBackingStore(); + memset(content->Data(), 0, length); + + #ifdef KINC_WINDOWS + MultiByteToWideChar(CP_UTF8, 0, *utf8_path, -1, temp_wstring, 1024); + FILE *file = _wfopen(temp_wstring, L"rb"); + #else + FILE *file = fopen(*utf8_path, "rb"); + #endif + if (file != nullptr) { + #ifdef KINC_WINDOWS + _fseeki64(file, offset, SEEK_SET); + #else + fseeko(file, offset, SEEK_SET); + #endif + fread(content->Data(), 1, length, file); + fclose(file); + } + + args.GetReturnValue().Set(buffer); + } + + void runt_file_size(const FunctionCallbackInfo &args) { + HandleScope scope(args.GetIsolate()); + String::Utf8Value utf8_path(isolate, args[0]); + int64_t size = -1; + + #ifdef KINC_WINDOWS + MultiByteToWideChar(CP_UTF8, 0, *utf8_path, -1, temp_wstring, 1024); + FILE *file = _wfopen(temp_wstring, L"rb"); + #else + FILE *file = fopen(*utf8_path, "rb"); + #endif + if (file != nullptr) { + #ifdef KINC_WINDOWS + _fseeki64(file, 0, SEEK_END); + size = _ftelli64(file); + #else + fseeko(file, 0, SEEK_END); + size = (int64_t)ftello(file); + #endif + fclose(file); + } + + args.GetReturnValue().Set(Number::New(isolate, (double)size)); + } + int sys_command(const char *cmd) { #ifdef KINC_WINDOWS int wlen = MultiByteToWideChar(CP_UTF8, 0, cmd, -1, NULL, 0); @@ -2913,6 +3024,9 @@ namespace { SET_FUNCTION(runt, "beginFace", runt_begin_face); SET_FUNCTION(runt, "end", runt_end); SET_FUNCTION(runt, "fileSaveBytes", runt_file_save_bytes); + SET_FUNCTION(runt, "fileWriteBytes", runt_file_write_bytes); + SET_FUNCTION(runt, "fileReadBytes", runt_file_read_bytes); + SET_FUNCTION(runt, "fileSize", runt_file_size); SET_FUNCTION(runt, "sysCommand", runt_sys_command); SET_FUNCTION(runt, "savePath", runt_save_path); SET_FUNCTION(runt, "getArgCount", runt_get_arg_count); @@ -2934,6 +3048,10 @@ namespace { SET_FUNCTION(runt, "socketSetBlocking", v8_runt_socket_set_blocking); SET_FUNCTION(runt, "socketSelect", v8_runt_socket_select); SET_FUNCTION(runt, "socketIsConnected", v8_runt_socket_is_connected); + SET_FUNCTION(runt, "udpCreate", v8_runt_udp_create); + SET_FUNCTION(runt, "udpBind", v8_runt_udp_bind); + SET_FUNCTION(runt, "udpSendTo", v8_runt_udp_sendto); + SET_FUNCTION(runt, "udpRecvFrom", v8_runt_udp_recvfrom); #ifdef WITH_SSL SET_FUNCTION(runt, "socketEnableSsl", v8_runt_socket_enable_ssl); #endif @@ -3214,6 +3332,9 @@ namespace { WebSocketWrapper::processEvents(); HttpRequestWrapper::processEvents(); #endif + #ifdef WITH_RTC + runt_rtc_process_events_native(); + #endif execute_timers(); EngineManager::AsyncEngine::instance().process_events(); @@ -3454,10 +3575,14 @@ namespace { if (!func->Call(context, context->Global(), 0, NULL).ToLocal(&result)) { handle_exception(&try_catch); } - + + #ifdef WITH_RTC + v8_runt_rtc_shutdown_all(); + #endif #ifdef WITH_NETWORKING WebSocketWrapper::cleanup(); HttpRequestWrapper::cleanup(); + GlobalThreadPool::shutdown(); #endif #ifdef WITH_VIEWPORT if (viewport_server_mode) { @@ -4115,6 +4240,9 @@ int kickstart(int argc, char **argv) { bind_httprequest_class(isolate, global_context); WEBSOCKET_BIND_V8(isolate, global_context); #endif + #ifdef WITH_RTC + bind_rtc_bridge(isolate, global_context); + #endif start_runt(snapshot_found ? NULL : code); diff --git a/Sources/rtc_bridge.cpp b/Sources/rtc_bridge.cpp new file mode 100644 index 0000000..0ef05c7 --- /dev/null +++ b/Sources/rtc_bridge.cpp @@ -0,0 +1,232 @@ +#include "rtc_bridge.h" + +#include + +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +static std::mutex *g_rtc_mutex = new std::mutex; +static std::deque *g_rtc_events = new std::deque; + +static void rtc_push(RtcEvent &&ev) { + std::lock_guard lock(*g_rtc_mutex); + g_rtc_events->push_back(std::move(ev)); +} + +bool runt_rtc_poll_event(RtcEvent &out) { + std::lock_guard lock(*g_rtc_mutex); + if (g_rtc_events->empty()) return false; + out = std::move(g_rtc_events->front()); + g_rtc_events->pop_front(); + return true; +} + +static void RTC_API rtc_description_cb(int pc, const char *sdp, const char *type, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::LocalDescription; + ev.id = pc; + ev.s1 = sdp != nullptr ? sdp : ""; + ev.s2 = type != nullptr ? type : ""; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_candidate_cb(int pc, const char *cand, const char *mid, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::LocalCandidate; + ev.id = pc; + ev.s1 = cand != nullptr ? cand : ""; + ev.s2 = mid != nullptr ? mid : ""; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_open_cb(int id, void *ptr); +static void RTC_API rtc_closed_cb(int id, void *ptr); +static void RTC_API rtc_error_cb(int id, const char *error, void *ptr); +static void RTC_API rtc_message_cb(int id, const char *message, int size, void *ptr); + +static void RTC_API rtc_state_cb(int pc, rtcState state, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::StateChange; + ev.id = pc; + ev.arg = (int)state; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_gathering_cb(int pc, rtcGatheringState state, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::GatheringState; + ev.id = pc; + ev.arg = (int)state; + rtc_push(std::move(ev)); +} + +static void rtc_register_dc_callbacks(int dc) { + rtcSetOpenCallback(dc, rtc_open_cb); + rtcSetClosedCallback(dc, rtc_closed_cb); + rtcSetErrorCallback(dc, rtc_error_cb); + rtcSetMessageCallback(dc, rtc_message_cb); +} + +static void RTC_API rtc_datachannel_cb(int pc, int dc, void *ptr) { + rtc_register_dc_callbacks(dc); + RtcEvent ev; + ev.type = RtcEventType::DataChannel; + ev.id = pc; + ev.arg = dc; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_open_cb(int id, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::Open; + ev.id = id; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_closed_cb(int id, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::Closed; + ev.id = id; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_error_cb(int id, const char *error, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::Error; + ev.id = id; + ev.s1 = error != nullptr ? error : ""; + rtc_push(std::move(ev)); +} + +static void RTC_API rtc_message_cb(int id, const char *message, int size, void *ptr) { + RtcEvent ev; + ev.type = RtcEventType::Message; + ev.id = id; + if (size < 0) { + ev.isString = true; + ev.s1 = message != nullptr ? message : ""; + } else { + ev.data.assign(message, message + size); + } + rtc_push(std::move(ev)); +} + +int runt_rtc_init() { + return rtcPreload() ? 0 : -1; +} + +void runt_rtc_cleanup() { + rtcCleanup(); +} + +int runt_rtc_create_peer_connection(const std::vector &iceServers) { + rtcConfiguration conf; + memset(&conf, 0, sizeof(conf)); + std::vector servers(iceServers.size()); + for (size_t i = 0; i < iceServers.size(); i++) { + servers[i] = iceServers[i].c_str(); + } + conf.iceServers = servers.data(); + conf.iceServersCount = (int)servers.size(); + int pc = rtcCreatePeerConnection(&conf); + if (pc < 0) return pc; + rtcSetLocalDescriptionCallback(pc, rtc_description_cb); + rtcSetLocalCandidateCallback(pc, rtc_candidate_cb); + rtcSetStateChangeCallback(pc, rtc_state_cb); + rtcSetGatheringStateChangeCallback(pc, rtc_gathering_cb); + rtcSetDataChannelCallback(pc, rtc_datachannel_cb); + return pc; +} + +void runt_rtc_close_peer_connection(int pc) { + rtcClosePeerConnection(pc); +} + +void runt_rtc_delete_peer_connection(int pc) { + rtcDeletePeerConnection(pc); +} + +void runt_rtc_set_local_description(int pc, const char *type) { + rtcSetLocalDescription(pc, type); +} + +int runt_rtc_set_remote_description(int pc, const char *sdp, const char *type) { + return rtcSetRemoteDescription(pc, sdp, type); +} + +int runt_rtc_add_remote_candidate(int pc, const char *cand, const char *mid) { + return rtcAddRemoteCandidate(pc, cand, mid); +} + +std::string runt_rtc_get_local_description(int pc) { + int size = rtcGetLocalDescription(pc, nullptr, 0); + if (size <= 0) return ""; + std::string buf(size, '\0'); + rtcGetLocalDescription(pc, buf.data(), size); + return std::string(buf.data(), strnlen(buf.data(), buf.size())); +} + +std::string runt_rtc_get_local_description_type(int pc) { + char buf[64]; + int size = rtcGetLocalDescriptionType(pc, buf, sizeof(buf)); + if (size <= 0) return ""; + return std::string(buf, strnlen(buf, sizeof(buf))); +} + +int runt_rtc_create_data_channel(int pc, const char *label, bool unordered, + int maxRetransmits) { + rtcDataChannelInit init; + memset(&init, 0, sizeof(init)); + init.reliability.unordered = unordered; + init.reliability.unreliable = maxRetransmits >= 0; + init.reliability.maxRetransmits = + maxRetransmits >= 0 ? (unsigned int)maxRetransmits : 0; + int dc = rtcCreateDataChannelEx(pc, label, &init); + if (dc < 0) return dc; + rtc_register_dc_callbacks(dc); + return dc; +} + +void runt_rtc_delete_data_channel(int dc) { + rtcDeleteDataChannel(dc); +} + +int runt_rtc_send_message(int id, const char *data, int size) { + return rtcSendMessage(id, data, size); +} + +void runt_rtc_close(int id) { + rtcClose(id); +} + +void runt_rtc_delete(int id) { + rtcDelete(id); +} + +bool runt_rtc_is_open(int id) { + return rtcIsOpen(id); +} + +int runt_rtc_get_buffered_amount(int id) { + return rtcGetBufferedAmount(id); +} + +bool runt_rtc_random_bytes(char *out, int len) { +#ifdef _WIN32 + return BCryptGenRandom(nullptr, reinterpret_cast(out), + static_cast(len), + BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0; +#else + FILE *f = fopen("/dev/urandom", "rb"); + if (f == nullptr) return false; + size_t n = fread(out, 1, static_cast(len), f); + fclose(f); + return n == static_cast(len); +#endif +} diff --git a/Sources/rtc_bridge.h b/Sources/rtc_bridge.h new file mode 100644 index 0000000..c98eb66 --- /dev/null +++ b/Sources/rtc_bridge.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +enum class RtcEventType { + LocalDescription, // id=pc, s1=sdp, s2=type + LocalCandidate, // id=pc, s1=cand, s2=mid + StateChange, // id=pc, arg=rtcState + GatheringState, // id=pc, arg=rtcGatheringState + DataChannel, // id=pc, arg=new dc id + Open, // id=dc + Closed, // id=dc + Error, // id=dc, s1=error + Message // id=dc, data=payload, isString=text frame +}; + +struct RtcEvent { + RtcEventType type; + int id; + int arg = 0; + std::string s1; + std::string s2; + std::vector data; + bool isString = false; +}; + +int runt_rtc_init(); +void runt_rtc_cleanup(); + +int runt_rtc_create_peer_connection(const std::vector &iceServers); +void runt_rtc_close_peer_connection(int pc); +void runt_rtc_delete_peer_connection(int pc); +void runt_rtc_set_local_description(int pc, const char *type); +int runt_rtc_set_remote_description(int pc, const char *sdp, const char *type); +int runt_rtc_add_remote_candidate(int pc, const char *cand, const char *mid); +std::string runt_rtc_get_local_description(int pc); +std::string runt_rtc_get_local_description_type(int pc); + +int runt_rtc_create_data_channel(int pc, const char *label, bool unordered, + int maxRetransmits); +void runt_rtc_delete_data_channel(int dc); + +int runt_rtc_send_message(int id, const char *data, int size); +void runt_rtc_close(int id); +void runt_rtc_delete(int id); +bool runt_rtc_is_open(int id); +int runt_rtc_get_buffered_amount(int id); + +bool runt_rtc_random_bytes(char *out, int len); + +bool runt_rtc_poll_event(RtcEvent &out); diff --git a/Sources/rtc_v8_bindings.cpp b/Sources/rtc_v8_bindings.cpp new file mode 100644 index 0000000..e970e61 --- /dev/null +++ b/Sources/rtc_v8_bindings.cpp @@ -0,0 +1,607 @@ +#include "rtc_v8_bindings.h" +#include "rtc_bridge.h" + +#include + +#include +#include +#include +#include +#include + +using namespace v8; + +static Isolate *g_rtc_isolate = nullptr; +static std::map> &g_desc_cbs = *new std::map>; +static std::map> &g_cand_cbs = *new std::map>; +static std::map> &g_state_cbs = *new std::map>; +static std::map> &g_gathering_cbs = *new std::map>; +static std::map> &g_dc_cbs = *new std::map>; +static std::map> &g_open_cbs = *new std::map>; +static std::map> &g_closed_cbs = *new std::map>; +static std::map> &g_error_cbs = *new std::map>; +static std::map> &g_msg_cbs = *new std::map>; + +static std::map> &g_pc_dcs = *new std::map>; +static std::map &g_dc_to_pc = *new std::map; +static Global *g_rtc_context = nullptr; + +static void rtc_throw(Isolate *isolate, const char *msg) { + isolate->ThrowException( + Exception::TypeError(String::NewFromUtf8(isolate, msg).ToLocalChecked())); +} + +static bool rtc_store_cb(const FunctionCallbackInfo &args, + std::map> &cbs, + const char *name) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { + rtc_throw(isolate, name); + return false; + } + int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + cbs[id].Reset(isolate, args[1].As()); + return true; +} + +static void rtc_drop_cbs(int id) { + g_desc_cbs.erase(id); + g_cand_cbs.erase(id); + g_state_cbs.erase(id); + g_gathering_cbs.erase(id); + g_dc_cbs.erase(id); + g_open_cbs.erase(id); + g_closed_cbs.erase(id); + g_error_cbs.erase(id); + g_msg_cbs.erase(id); +} + +static void rtc_track_dc(int pc, int dc) { + g_pc_dcs[pc].push_back(dc); + g_dc_to_pc[dc] = pc; +} + +static void rtc_forget_dc(int dc) { + auto it = g_dc_to_pc.find(dc); + if (it == g_dc_to_pc.end()) return; + auto &dcs = g_pc_dcs[it->second]; + dcs.erase(std::remove(dcs.begin(), dcs.end(), dc), dcs.end()); + g_dc_to_pc.erase(it); +} + +static void rtc_call(Persistent &cb, int argc, Local *argv) { + if (cb.IsEmpty()) return; + Isolate *isolate = g_rtc_isolate; + Local ctx = isolate->GetCurrentContext(); + Local fn = cb.Get(isolate); + TryCatch try_catch(isolate); + fn->Call(ctx, Null(isolate), argc, argv).IsEmpty(); + if (try_catch.HasCaught()) { + String::Utf8Value err(isolate, try_catch.Exception()); + kinc_log(KINC_LOG_LEVEL_WARNING, "runt_rtc callback threw: %s", + *err != nullptr ? *err : "unknown"); + } +} + +static Local rtc_str(Isolate *isolate, const std::string &s) { + return String::NewFromUtf8(isolate, s.data(), NewStringType::kNormal, + (int)s.size()) + .ToLocalChecked(); +} + +void v8_runt_rtc_init(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + g_rtc_isolate = isolate; + args.GetReturnValue().Set(Integer::New(isolate, runt_rtc_init())); +} + +void v8_runt_rtc_cleanup(const FunctionCallbackInfo &args) { + runt_rtc_cleanup(); + for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs, + &g_gathering_cbs, &g_dc_cbs, &g_open_cbs, + &g_closed_cbs, &g_error_cbs, &g_msg_cbs}) { + for (auto &kv : *m) kv.second.Reset(); + m->clear(); + } + g_pc_dcs.clear(); + g_dc_to_pc.clear(); + RtcEvent ev; + while (runt_rtc_poll_event(ev)) {} + args.GetReturnValue().Set(Undefined(args.GetIsolate())); +} + +void v8_runt_rtc_shutdown_all() { + std::set dcs; + for (auto &kv : g_dc_to_pc) dcs.insert(kv.first); + for (auto &kv : g_msg_cbs) dcs.insert(kv.first); + for (auto &kv : g_open_cbs) dcs.insert(kv.first); + for (auto &kv : g_closed_cbs) dcs.insert(kv.first); + for (auto &kv : g_error_cbs) dcs.insert(kv.first); + for (int dc : dcs) runt_rtc_delete_data_channel(dc); + + std::set pcs; + for (auto &kv : g_pc_dcs) pcs.insert(kv.first); + for (auto &kv : g_desc_cbs) pcs.insert(kv.first); + for (auto &kv : g_cand_cbs) pcs.insert(kv.first); + for (auto &kv : g_state_cbs) pcs.insert(kv.first); + for (auto &kv : g_gathering_cbs) pcs.insert(kv.first); + for (auto &kv : g_dc_cbs) pcs.insert(kv.first); + for (int pc : pcs) runt_rtc_delete_peer_connection(pc); + + for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs, + &g_gathering_cbs, &g_dc_cbs, &g_open_cbs, + &g_closed_cbs, &g_error_cbs, &g_msg_cbs}) { + for (auto &kv : *m) kv.second.Reset(); + m->clear(); + } + g_pc_dcs.clear(); + g_dc_to_pc.clear(); + RtcEvent ev; + while (runt_rtc_poll_event(ev)) {} + runt_rtc_cleanup(); +} + +void v8_runt_rtc_create_peer_connection(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + std::vector iceServers; + if (args.Length() >= 1 && args[0]->IsArray()) { + Local arr = args[0].As(); + Local ctx = isolate->GetCurrentContext(); + for (uint32_t i = 0; i < arr->Length(); i++) { + Local el = arr->Get(ctx, i).ToLocalChecked(); + if (el->IsString()) { + String::Utf8Value s(isolate, el); + iceServers.push_back(*s); + } + } + } + int pc = runt_rtc_create_peer_connection(iceServers); + args.GetReturnValue().Set(Integer::New(isolate, pc)); +} + +void v8_runt_rtc_close_peer_connection(const FunctionCallbackInfo &args) { + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); + runt_rtc_close_peer_connection(pc); +} + +void v8_runt_rtc_delete_peer_connection(const FunctionCallbackInfo &args) { + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); + runt_rtc_delete_peer_connection(pc); + rtc_drop_cbs(pc); + auto it = g_pc_dcs.find(pc); + if (it != g_pc_dcs.end()) { + for (int dc : it->second) { + rtc_drop_cbs(dc); + g_dc_to_pc.erase(dc); + } + g_pc_dcs.erase(it); + } +} + +void v8_runt_rtc_set_local_description(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + const char *type = nullptr; + if (args.Length() >= 2 && args[1]->IsString()) { + String::Utf8Value typeStr(isolate, args[1]); + type = *typeStr; + } + runt_rtc_set_local_description(pc, type); +} + +void v8_runt_rtc_set_remote_description(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || + !args[2]->IsString()) { + rtc_throw(isolate, + "runt_rtc_set_remote_description requires (pc, sdp, type)"); + return; + } + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + String::Utf8Value sdp(isolate, args[1]); + String::Utf8Value type(isolate, args[2]); + int ret = runt_rtc_set_remote_description(pc, *sdp, *type); + args.GetReturnValue().Set(Integer::New(isolate, ret)); +} + +void v8_runt_rtc_add_remote_candidate(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || + !args[2]->IsString()) { + rtc_throw(isolate, + "runt_rtc_add_remote_candidate requires (pc, cand, mid)"); + return; + } + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + String::Utf8Value cand(isolate, args[1]); + String::Utf8Value mid(isolate, args[2]); + int ret = runt_rtc_add_remote_candidate(pc, *cand, *mid); + args.GetReturnValue().Set(Integer::New(isolate, ret)); +} + +void v8_runt_rtc_get_local_description(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + args.GetReturnValue().Set( + rtc_str(isolate, runt_rtc_get_local_description(pc))); +} + +void v8_runt_rtc_get_local_description_type( + const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + args.GetReturnValue().Set( + rtc_str(isolate, runt_rtc_get_local_description_type(pc))); +} + +void v8_runt_rtc_create_data_channel(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) { + rtc_throw(isolate, "runt_rtc_create_data_channel requires (pc, label)"); + return; + } + int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + String::Utf8Value label(isolate, args[1]); + bool unordered = false; + int maxRetransmits = -1; + if (args.Length() >= 3 && args[2]->IsBoolean()) { + unordered = args[2]->BooleanValue(isolate); + } + if (args.Length() >= 4 && args[3]->IsNumber()) { + maxRetransmits = + args[3]->Int32Value(isolate->GetCurrentContext()).FromJust(); + } + int dc = runt_rtc_create_data_channel(pc, *label, unordered, + maxRetransmits); + if (dc >= 0) rtc_track_dc(pc, dc); + args.GetReturnValue().Set(Integer::New(isolate, dc)); +} + +void v8_runt_rtc_delete_data_channel(const FunctionCallbackInfo &args) { + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int dc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); + runt_rtc_delete_data_channel(dc); + rtc_drop_cbs(dc); + rtc_forget_dc(dc); +} + +void v8_runt_rtc_send_message(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 2 || !args[0]->IsNumber()) { + rtc_throw(isolate, "runt_rtc_send_message requires (id, data)"); + return; + } + int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + int ret = -1; + if (args[1]->IsArrayBuffer()) { + Local buf = args[1].As(); + ret = runt_rtc_send_message( + id, static_cast(buf->GetBackingStore()->Data()), + (int)buf->ByteLength()); + } else if (args[1]->IsArrayBufferView()) { + Local view = args[1].As(); + Local buf = view->Buffer(); + const char *data = + static_cast(buf->GetBackingStore()->Data()) + + view->ByteOffset(); + ret = runt_rtc_send_message(id, data, (int)view->ByteLength()); + } else if (args[1]->IsString()) { + String::Utf8Value s(isolate, args[1]); + ret = runt_rtc_send_message(id, *s, -1); + } else { + rtc_throw(isolate, + "runt_rtc_send_message data must be String or ArrayBuffer"); + return; + } + args.GetReturnValue().Set(Integer::New(isolate, ret)); +} + +void v8_runt_rtc_close(const FunctionCallbackInfo &args) { + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); + runt_rtc_close(id); +} + +void v8_runt_rtc_delete(const FunctionCallbackInfo &args) { + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); + runt_rtc_delete(id); + rtc_drop_cbs(id); + rtc_forget_dc(id); +} + +void v8_runt_rtc_is_open(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + args.GetReturnValue().Set(Boolean::New(isolate, runt_rtc_is_open(id))); +} + +void v8_runt_rtc_get_buffered_amount(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) return; + int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + args.GetReturnValue().Set( + Integer::New(isolate, runt_rtc_get_buffered_amount(id))); +} + +void v8_runt_rtc_set_local_description_callback( + const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_desc_cbs, + "runt_rtc_set_local_description_callback requires (pc, fn)"); +} + +void v8_runt_rtc_set_local_candidate_callback( + const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_cand_cbs, + "runt_rtc_set_local_candidate_callback requires (pc, fn)"); +} + +void v8_runt_rtc_set_state_change_callback( + const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_state_cbs, + "runt_rtc_set_state_change_callback requires (pc, fn)"); +} + +void v8_runt_rtc_set_gathering_state_callback( + const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_gathering_cbs, + "runt_rtc_set_gathering_state_callback requires (pc, fn)"); +} + +void v8_runt_rtc_set_data_channel_callback( + const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_dc_cbs, + "runt_rtc_set_data_channel_callback requires (pc, fn)"); +} + +void v8_runt_rtc_set_open_callback(const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_open_cbs, + "runt_rtc_set_open_callback requires (id, fn)"); +} + +void v8_runt_rtc_set_closed_callback(const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_closed_cbs, + "runt_rtc_set_closed_callback requires (id, fn)"); +} + +void v8_runt_rtc_set_error_callback(const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_error_cbs, + "runt_rtc_set_error_callback requires (id, fn)"); +} + +void v8_runt_rtc_set_message_callback(const FunctionCallbackInfo &args) { + rtc_store_cb(args, g_msg_cbs, + "runt_rtc_set_message_callback requires (id, fn)"); +} + +void v8_runt_rtc_random_bytes(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + if (args.Length() < 1 || !args[0]->IsNumber()) { + rtc_throw(isolate, "runt_rtc_random_bytes requires (len)"); + return; + } + int len = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); + if (len <= 0) { + args.GetReturnValue().Set(Null(isolate)); + return; + } + Local buf = ArrayBuffer::New(isolate, len); + if (!runt_rtc_random_bytes( + static_cast(buf->GetBackingStore()->Data()), len)) { + args.GetReturnValue().Set(Null(isolate)); + return; + } + args.GetReturnValue().Set(buf); +} + +static void rtc_drain_events() { + Isolate *isolate = g_rtc_isolate; + RtcEvent ev; + while (runt_rtc_poll_event(ev)) { + switch (ev.type) { + case RtcEventType::LocalDescription: { + auto it = g_desc_cbs.find(ev.id); + if (it != g_desc_cbs.end()) { + Local argv[2] = {rtc_str(isolate, ev.s1), + rtc_str(isolate, ev.s2)}; + rtc_call(it->second, 2, argv); + } + break; + } + case RtcEventType::LocalCandidate: { + auto it = g_cand_cbs.find(ev.id); + if (it != g_cand_cbs.end()) { + Local argv[2] = {rtc_str(isolate, ev.s1), + rtc_str(isolate, ev.s2)}; + rtc_call(it->second, 2, argv); + } + break; + } + case RtcEventType::StateChange: { + auto it = g_state_cbs.find(ev.id); + if (it != g_state_cbs.end()) { + Local argv[1] = {Integer::New(isolate, ev.arg)}; + rtc_call(it->second, 1, argv); + } + break; + } + case RtcEventType::GatheringState: { + auto it = g_gathering_cbs.find(ev.id); + if (it != g_gathering_cbs.end()) { + Local argv[1] = {Integer::New(isolate, ev.arg)}; + rtc_call(it->second, 1, argv); + } + break; + } + case RtcEventType::DataChannel: { + rtc_track_dc(ev.id, ev.arg); + auto it = g_dc_cbs.find(ev.id); + if (it != g_dc_cbs.end()) { + Local argv[1] = {Integer::New(isolate, ev.arg)}; + rtc_call(it->second, 1, argv); + } + break; + } + case RtcEventType::Open: { + auto it = g_open_cbs.find(ev.id); + if (it != g_open_cbs.end()) { + rtc_call(it->second, 0, nullptr); + } + break; + } + case RtcEventType::Closed: { + auto it = g_closed_cbs.find(ev.id); + if (it != g_closed_cbs.end()) { + rtc_call(it->second, 0, nullptr); + } + rtc_drop_cbs(ev.id); + rtc_forget_dc(ev.id); + break; + } + case RtcEventType::Error: { + auto it = g_error_cbs.find(ev.id); + if (it != g_error_cbs.end()) { + Local argv[1] = {rtc_str(isolate, ev.s1)}; + rtc_call(it->second, 1, argv); + } + break; + } + case RtcEventType::Message: { + auto it = g_msg_cbs.find(ev.id); + if (it != g_msg_cbs.end()) { + Local arg; + if (ev.isString) { + arg = rtc_str(isolate, ev.s1); + } else { + // move the payload into the backing store (no copy) + auto *payload = new std::vector(std::move(ev.data)); + auto backing = ArrayBuffer::NewBackingStore( + payload->data(), payload->size(), + [](void *, size_t, void *deleter_data) { + delete static_cast *>(deleter_data); + }, + payload); + arg = ArrayBuffer::New(isolate, std::move(backing)); + } + Local argv[1] = {arg}; + rtc_call(it->second, 1, argv); + } + break; + } + } + } +} + +void runt_rtc_process_events_native() { + if (g_rtc_isolate == nullptr || g_rtc_context == nullptr) return; + Locker locker{g_rtc_isolate}; + Isolate::Scope isolate_scope(g_rtc_isolate); + HandleScope handle_scope(g_rtc_isolate); + Local ctx = Local::New(g_rtc_isolate, *g_rtc_context); + Context::Scope context_scope(ctx); + rtc_drain_events(); +} + +void bind_rtc_bridge(Isolate *isolate, const Global &context) { + Locker locker{isolate}; + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + + Local current_context = Local::New(isolate, context); + Context::Scope context_scope(current_context); + Local global = current_context->Global(); + + g_rtc_isolate = isolate; + if (g_rtc_context == nullptr) { + g_rtc_context = new Global(isolate, context); + } + + Local runt_obj; + Local existing_runt = + global->Get(current_context, + String::NewFromUtf8(isolate, "RunT").ToLocalChecked()) + .ToLocalChecked(); + if (existing_runt->IsObject()) { + runt_obj = existing_runt->ToObject(current_context).ToLocalChecked(); + } else { + runt_obj = Object::New(isolate); + global->Set(current_context, + String::NewFromUtf8(isolate, "RunT").ToLocalChecked(), + runt_obj); + } + + struct RtcBinding { + const char *global_name; + const char *runt_name; + FunctionCallback fn; + }; + static const RtcBinding bindings[] = { + {"runt_rtc_init", "rtcInit", v8_runt_rtc_init}, + {"runt_rtc_cleanup", "rtcCleanup", v8_runt_rtc_cleanup}, + {"runt_rtc_create_peer_connection", "rtcCreatePeerConnection", + v8_runt_rtc_create_peer_connection}, + {"runt_rtc_close_peer_connection", "rtcClosePeerConnection", + v8_runt_rtc_close_peer_connection}, + {"runt_rtc_delete_peer_connection", "rtcDeletePeerConnection", + v8_runt_rtc_delete_peer_connection}, + {"runt_rtc_set_local_description", "rtcSetLocalDescription", + v8_runt_rtc_set_local_description}, + {"runt_rtc_set_remote_description", "rtcSetRemoteDescription", + v8_runt_rtc_set_remote_description}, + {"runt_rtc_add_remote_candidate", "rtcAddRemoteCandidate", + v8_runt_rtc_add_remote_candidate}, + {"runt_rtc_get_local_description", "rtcGetLocalDescription", + v8_runt_rtc_get_local_description}, + {"runt_rtc_get_local_description_type", "rtcGetLocalDescriptionType", + v8_runt_rtc_get_local_description_type}, + {"runt_rtc_create_data_channel", "rtcCreateDataChannel", + v8_runt_rtc_create_data_channel}, + {"runt_rtc_delete_data_channel", "rtcDeleteDataChannel", + v8_runt_rtc_delete_data_channel}, + {"runt_rtc_send_message", "rtcSendMessage", v8_runt_rtc_send_message}, + {"runt_rtc_close", "rtcClose", v8_runt_rtc_close}, + {"runt_rtc_delete", "rtcDelete", v8_runt_rtc_delete}, + {"runt_rtc_is_open", "rtcIsOpen", v8_runt_rtc_is_open}, + {"runt_rtc_get_buffered_amount", "rtcGetBufferedAmount", + v8_runt_rtc_get_buffered_amount}, + {"runt_rtc_set_local_description_callback", + "rtcSetLocalDescriptionCallback", + v8_runt_rtc_set_local_description_callback}, + {"runt_rtc_set_local_candidate_callback", + "rtcSetLocalCandidateCallback", + v8_runt_rtc_set_local_candidate_callback}, + {"runt_rtc_set_state_change_callback", "rtcSetStateChangeCallback", + v8_runt_rtc_set_state_change_callback}, + {"runt_rtc_set_gathering_state_callback", "rtcSetGatheringStateCallback", + v8_runt_rtc_set_gathering_state_callback}, + {"runt_rtc_set_data_channel_callback", "rtcSetDataChannelCallback", + v8_runt_rtc_set_data_channel_callback}, + {"runt_rtc_set_open_callback", "rtcSetOpenCallback", + v8_runt_rtc_set_open_callback}, + {"runt_rtc_set_closed_callback", "rtcSetClosedCallback", + v8_runt_rtc_set_closed_callback}, + {"runt_rtc_set_error_callback", "rtcSetErrorCallback", + v8_runt_rtc_set_error_callback}, + {"runt_rtc_set_message_callback", "rtcSetMessageCallback", + v8_runt_rtc_set_message_callback}, + {"runt_rtc_random_bytes", "rtcRandomBytes", + v8_runt_rtc_random_bytes}, + }; + + for (const RtcBinding &b : bindings) { + Local fn = + Function::New(current_context, b.fn).ToLocalChecked(); + global->Set(current_context, + String::NewFromUtf8(isolate, b.global_name).ToLocalChecked(), + fn); + runt_obj->Set(current_context, + String::NewFromUtf8(isolate, b.runt_name).ToLocalChecked(), + fn); + } +} diff --git a/Sources/rtc_v8_bindings.h b/Sources/rtc_v8_bindings.h new file mode 100644 index 0000000..20c0b23 --- /dev/null +++ b/Sources/rtc_v8_bindings.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void bind_rtc_bridge(v8::Isolate *isolate, const v8::Global &context); + +void v8_runt_rtc_shutdown_all(); + +void runt_rtc_process_events_native(); diff --git a/Sources/socket_bridge.cpp b/Sources/socket_bridge.cpp index bb1a9ad..f3cad3c 100644 --- a/Sources/socket_bridge.cpp +++ b/Sources/socket_bridge.cpp @@ -44,6 +44,7 @@ public: bool isConnected; bool isBound; bool isListening; + bool connectInProgress; std::string lastError; #ifdef _WIN32 @@ -58,7 +59,8 @@ public: void* ssl_cred_handle_; void* ssl_context_handle_; bool ssl_context_initialized_; - std::vector ssl_buffer_; + std::vector ssl_buffer_; + std::vector ssl_send_buffer_; #else SSL* ssl; SSL_CTX* ssl_ctx; @@ -67,7 +69,7 @@ public: #endif RunTSocket(int socket_id) : id(socket_id), isBlocking(true), isConnected(false), - isBound(false), isListening(false) { + isBound(false), isListening(false), connectInProgress(false) { #ifdef _WIN32 socket_fd = INVALID_SOCKET; #else @@ -245,6 +247,14 @@ extern "C" int runt_socket_create() { int no_sigpipe = 1; setsockopt(socket->socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&no_sigpipe, sizeof(no_sigpipe)); #endif +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket->socket_fd, FIONBIO, &mode); +#else + int flags = fcntl(socket->socket_fd, F_GETFL, 0); + fcntl(socket->socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif + socket->isBlocking = false; g_sockets[socket_id] = std::move(socket); #ifdef DEBUG_NETWORK @@ -399,6 +409,20 @@ extern "C" bool runt_socket_connect(int socket_id, const char* hostname, int por connected = true; break; } +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS) { + connected = true; + socket->connectInProgress = true; + break; + } +#else + if (errno == EINPROGRESS || errno == EWOULDBLOCK) { + connected = true; + socket->connectInProgress = true; + break; + } +#endif } freeaddrinfo(result); @@ -413,7 +437,7 @@ extern "C" bool runt_socket_connect(int socket_id, const char* hostname, int por return false; } - socket->isConnected = true; + socket->isConnected = !socket->connectInProgress; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", hostname, port); #endif @@ -447,7 +471,8 @@ extern "C" int runt_socket_send(int socket_id, const char* data, int length) { } int total_size = stream_sizes.cbHeader + length + stream_sizes.cbTrailer; - char* encrypt_buffer = new char[total_size]; + socket->ssl_send_buffer_.resize(total_size); + char* encrypt_buffer = socket->ssl_send_buffer_.data(); SecBufferDesc message_desc; SecBuffer message_buffers[4]; @@ -479,15 +504,12 @@ extern "C" int runt_socket_send(int socket_id, const char* data, int length) { if (status != SEC_E_OK) { kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel encrypt failed: 0x%x", status); - delete[] encrypt_buffer; return -1; } int total_encrypted = message_buffers[0].cbBuffer + message_buffers[1].cbBuffer + message_buffers[2].cbBuffer; int sent = send(socket->socket_fd, encrypt_buffer, total_encrypted, 0); - delete[] encrypt_buffer; - if (sent != total_encrypted) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send encrypted SSL data"); return -1; @@ -644,11 +666,13 @@ extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data) int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { delete[] buffer; + if (received < 0 && WSAGetLastError() == WSAEWOULDBLOCK) return 0; if (received == 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server"); #endif - return 0; + socket->isConnected = false; + return -2; } else { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data for SSL read"); return -1; @@ -669,11 +693,13 @@ extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data) int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { delete[] buffer; + if (received < 0 && WSAGetLastError() == WSAEWOULDBLOCK) return 0; if (received == 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server (no buffered data)"); #endif - return 0; + socket->isConnected = false; + return -2; } else { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data"); return -1; @@ -691,6 +717,10 @@ extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data) if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) { return 0; } + if (ssl_error == SSL_ERROR_ZERO_RETURN) { + socket->isConnected = false; + return -2; + } kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_read failed: %d", ssl_error); return -1; } @@ -755,6 +785,143 @@ extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data) return received; } +extern "C" int runt_udp_create() { + if (!initialize_networking()) { + kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to initialize networking"); + return -1; + } + + int socket_id = g_next_socket_id++; + auto socket = std::make_unique(socket_id); + +#ifdef _WIN32 + socket->socket_fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (socket->socket_fd == INVALID_SOCKET) { + kinc_log(KINC_LOG_LEVEL_ERROR, "UDP socket creation failed: %d", + WSAGetLastError()); + return -1; + } +#else + socket->socket_fd = ::socket(AF_INET, SOCK_DGRAM, 0); + if (socket->socket_fd < 0) { + kinc_log(KINC_LOG_LEVEL_ERROR, "UDP socket creation failed: %s", + strerror(errno)); + return -1; + } +#endif + + int reuse = 1; + setsockopt(socket->socket_fd, SOL_SOCKET, SO_REUSEADDR, + (const char*)&reuse, sizeof(reuse)); + + int sndbuf = 256 * 1024; + int rcvbuf = 256 * 1024; + setsockopt(socket->socket_fd, SOL_SOCKET, SO_SNDBUF, + (const char*)&sndbuf, sizeof(sndbuf)); + setsockopt(socket->socket_fd, SOL_SOCKET, SO_RCVBUF, + (const char*)&rcvbuf, sizeof(rcvbuf)); + +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket->socket_fd, FIONBIO, &mode); +#else + int flags = fcntl(socket->socket_fd, F_GETFL, 0); + fcntl(socket->socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif + socket->isBlocking = false; + + g_sockets[socket_id] = std::move(socket); + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_INFO, "UDP socket created: ID %d", socket_id); + #endif + return socket_id; +} + +extern "C" bool runt_udp_bind(int socket_id, const char* address, + int port) { + return runt_socket_bind(socket_id, address, port); +} + +extern "C" int runt_udp_sendto(int socket_id, const char* host, int port, + const char* data, int length) { + auto it = g_sockets.find(socket_id); + if (it == g_sockets.end()) return -1; + + auto& socket = it->second; + if (!socket->isValid()) return -1; + + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) { + struct addrinfo hints, *result; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + if (getaddrinfo(host, nullptr, &hints, &result) != 0) { + kinc_log(KINC_LOG_LEVEL_ERROR, + "UDP resolve failed: %s", host); + return -1; + } + addr.sin_addr = ((sockaddr_in*)result->ai_addr)->sin_addr; + freeaddrinfo(result); + } + +#ifdef _WIN32 + int sent = sendto(socket->socket_fd, data, length, 0, + (sockaddr*)&addr, sizeof(addr)); + if (sent < 0) { + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) return 0; + kinc_log(KINC_LOG_LEVEL_ERROR, "UDP sendto failed: %d", error); + return -1; + } +#else + int sent = sendto(socket->socket_fd, data, length, MSG_NOSIGNAL, + (sockaddr*)&addr, sizeof(addr)); + if (sent < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; + kinc_log(KINC_LOG_LEVEL_ERROR, "UDP sendto failed: %s", + strerror(errno)); + return -1; + } +#endif + return sent; +} + +extern "C" int runt_udp_recvfrom(int socket_id, int max_length, + char** out_data, char* out_host, int out_host_len, + int* out_port) { + auto it = g_sockets.find(socket_id); + if (it == g_sockets.end()) return -1; + + auto& socket = it->second; + if (!socket->isValid()) return -1; + + char* buffer = new char[max_length]; + sockaddr_in from; + socklen_t fromlen = sizeof(from); + + int received = recvfrom(socket->socket_fd, buffer, max_length, 0, + (sockaddr*)&from, &fromlen); + if (received < 0) { + delete[] buffer; +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) return 0; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; +#endif + return -1; + } + + inet_ntop(AF_INET, &from.sin_addr, out_host, out_host_len); + *out_port = ntohs(from.sin_port); + *out_data = buffer; + return received; +} + extern "C" void runt_socket_close(int socket_id) { auto it = g_sockets.find(socket_id); if (it == g_sockets.end()) return; @@ -767,11 +934,45 @@ extern "C" void runt_socket_close(int socket_id) { } extern "C" bool runt_socket_is_connected(int socket_id) { - auto it = g_sockets.find(socket_id); - if (it == g_sockets.end()) return false; - - auto& socket = it->second; - return socket->isValid() && socket->isConnected; + auto it = g_sockets.find(socket_id); + if (it == g_sockets.end()) return false; + + auto& socket = it->second; + if (!socket->isValid()) return false; + if (socket->isConnected) return true; + if (!socket->connectInProgress) return false; + + fd_set wfds, efds; + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(socket->socket_fd, &wfds); + FD_SET(socket->socket_fd, &efds); + struct timeval tv = {0, 0}; + + int sel; +#ifdef _WIN32 + sel = ::select(0, nullptr, &wfds, &efds, &tv); +#else + sel = ::select(socket->socket_fd + 1, nullptr, &wfds, &efds, &tv); +#endif + if (sel <= 0 || (!FD_ISSET(socket->socket_fd, &wfds) && + !FD_ISSET(socket->socket_fd, &efds))) { + return false; + } + + int err = 0; + socklen_t errlen = sizeof(err); + if (getsockopt(socket->socket_fd, SOL_SOCKET, SO_ERROR, (char*)&err, &errlen) != 0) { + socket->connectInProgress = false; + return false; + } + + socket->connectInProgress = false; + if (err == 0) { + socket->isConnected = true; + return true; + } + return false; } extern "C" void runt_socket_set_blocking(int socket_id, bool blocking) { @@ -925,7 +1126,10 @@ extern "C" bool runt_socket_enable_ssl(int socket_id) { SSL_set_fd(socket->ssl, socket->socket_fd); + int flags = fcntl(socket->socket_fd, F_GETFL, 0); + fcntl(socket->socket_fd, F_SETFL, flags & ~O_NONBLOCK); int ret = SSL_connect(socket->ssl); + fcntl(socket->socket_fd, F_SETFL, flags); if (ret <= 0) { int ssl_error = SSL_get_error(socket->ssl, ret); kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_connect failed: %d", ssl_error); diff --git a/Sources/socket_bridge.h b/Sources/socket_bridge.h index 464a956..89cd17e 100644 --- a/Sources/socket_bridge.h +++ b/Sources/socket_bridge.h @@ -15,6 +15,13 @@ int runt_socket_accept(int socket_id); bool runt_socket_connect(int socket_id, const char* hostname, int port); int runt_socket_send(int socket_id, const char* data, int length); int runt_socket_recv(int socket_id, int max_length, char** out_data); + +int runt_udp_create(); +bool runt_udp_bind(int socket_id, const char* address, int port); +int runt_udp_sendto(int socket_id, const char* host, int port, + const char* data, int length); +int runt_udp_recvfrom(int socket_id, int max_length, char** out_data, + char* out_host, int out_host_len, int* out_port); #ifdef WITH_SSL bool runt_socket_enable_ssl(int socket_id); #endif diff --git a/Sources/socket_v8_bindings.cpp b/Sources/socket_v8_bindings.cpp index 71235af..c9cdc62 100644 --- a/Sources/socket_v8_bindings.cpp +++ b/Sources/socket_v8_bindings.cpp @@ -235,6 +235,141 @@ void v8_runt_socket_is_connected(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(Boolean::New(isolate, connected)); } +void v8_runt_udp_create(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + int socket_id = runt_udp_create(); + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_udp_create returned: %d", + socket_id); + #endif + + args.GetReturnValue().Set(Integer::New(isolate, socket_id)); +} + +void v8_runt_udp_bind(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.Length() < 3 || !args[0]->IsInt32() || !args[1]->IsString() + || !args[2]->IsInt32()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, "Wrong arguments") + .ToLocalChecked())); + return; + } + + int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()) + .FromJust(); + String::Utf8Value address(isolate, args[1]); + int port = args[2]->Int32Value(isolate->GetCurrentContext()) + .FromJust(); + + bool result = runt_udp_bind(socket_id, *address, port); + args.GetReturnValue().Set(Boolean::New(isolate, result)); +} + +void v8_runt_udp_sendto(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.Length() < 4 || !args[0]->IsInt32() || !args[1]->IsString() + || !args[2]->IsInt32()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, "Wrong arguments") + .ToLocalChecked())); + return; + } + + int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()) + .FromJust(); + String::Utf8Value host(isolate, args[1]); + int port = args[2]->Int32Value(isolate->GetCurrentContext()) + .FromJust(); + int sent = -1; + + if (args[3]->IsArrayBuffer()) { + Local buffer = Local::Cast(args[3]); + std::shared_ptr backing = + buffer->GetBackingStore(); + sent = runt_udp_sendto(socket_id, *host, port, + static_cast(backing->Data()), + static_cast(backing->ByteLength())); + } else if (args[3]->IsArrayBufferView()) { + Local view = + Local::Cast(args[3]); + Local buffer = view->Buffer(); + std::shared_ptr backing = + buffer->GetBackingStore(); + const char* data = + static_cast(backing->Data()) + + view->ByteOffset(); + sent = runt_udp_sendto(socket_id, *host, port, data, + static_cast(view->ByteLength())); + } else if (args[3]->IsString()) { + String::Utf8Value str_data(isolate, args[3]); + if (*str_data == nullptr) { + args.GetReturnValue().Set(Integer::New(isolate, -1)); + return; + } + sent = runt_udp_sendto(socket_id, *host, port, *str_data, + str_data.length()); + } else { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, + "Data must be string, ArrayBuffer, or TypedArray") + .ToLocalChecked())); + return; + } + + args.GetReturnValue().Set(Integer::New(isolate, sent)); +} + +void v8_runt_udp_recvfrom(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.Length() < 2 || !args[0]->IsInt32() || !args[1]->IsInt32()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, "Wrong arguments") + .ToLocalChecked())); + return; + } + + int socket_id = args[0]->Int32Value(context).FromJust(); + int max_length = args[1]->Int32Value(context).FromJust(); + + char* data = nullptr; + char host[64] = {0}; + int port = 0; + int received = runt_udp_recvfrom(socket_id, max_length, &data, + host, sizeof(host), &port); + + if (received > 0 && data) { + std::unique_ptr backing = + ArrayBuffer::NewBackingStore(data, received, + [](void* data, size_t length, void* deleter_data) { + delete[] static_cast(data); + }, nullptr); + Local buffer = + ArrayBuffer::New(isolate, std::move(backing)); + + Local obj = Object::New(isolate); + obj->Set(context, + String::NewFromUtf8(isolate, "data").ToLocalChecked(), + buffer); + obj->Set(context, + String::NewFromUtf8(isolate, "host").ToLocalChecked(), + String::NewFromUtf8(isolate, host).ToLocalChecked()); + obj->Set(context, + String::NewFromUtf8(isolate, "port").ToLocalChecked(), + Integer::New(isolate, port)); + args.GetReturnValue().Set(obj); + } else if (received < 0) { + args.GetReturnValue().Set(Integer::New(isolate, -1)); + } else { + args.GetReturnValue().Set(Null(isolate)); + } +} + #ifdef WITH_SSL void v8_runt_socket_enable_ssl(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); @@ -302,6 +437,18 @@ void bind_socket_bridge(Isolate* isolate, const v8::Global& context global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_is_connected").ToLocalChecked(), Function::New(current_context, v8_runt_socket_is_connected).ToLocalChecked()); + global->Set(current_context, String::NewFromUtf8(isolate, "runt_udp_create").ToLocalChecked(), + Function::New(current_context, v8_runt_udp_create).ToLocalChecked()); + + global->Set(current_context, String::NewFromUtf8(isolate, "runt_udp_bind").ToLocalChecked(), + Function::New(current_context, v8_runt_udp_bind).ToLocalChecked()); + + global->Set(current_context, String::NewFromUtf8(isolate, "runt_udp_sendto").ToLocalChecked(), + Function::New(current_context, v8_runt_udp_sendto).ToLocalChecked()); + + global->Set(current_context, String::NewFromUtf8(isolate, "runt_udp_recvfrom").ToLocalChecked(), + Function::New(current_context, v8_runt_udp_recvfrom).ToLocalChecked()); + #ifdef WITH_SSL global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_enable_ssl").ToLocalChecked(), Function::New(current_context, v8_runt_socket_enable_ssl).ToLocalChecked()); diff --git a/Sources/socket_v8_bindings.h b/Sources/socket_v8_bindings.h index ccf92d3..a0627af 100644 --- a/Sources/socket_v8_bindings.h +++ b/Sources/socket_v8_bindings.h @@ -15,6 +15,10 @@ void v8_runt_socket_close(const v8::FunctionCallbackInfo& args); void v8_runt_socket_set_blocking(const v8::FunctionCallbackInfo& args); void v8_runt_socket_select(const v8::FunctionCallbackInfo& args); void v8_runt_socket_is_connected(const v8::FunctionCallbackInfo& args); +void v8_runt_udp_create(const v8::FunctionCallbackInfo& args); +void v8_runt_udp_bind(const v8::FunctionCallbackInfo& args); +void v8_runt_udp_sendto(const v8::FunctionCallbackInfo& args); +void v8_runt_udp_recvfrom(const v8::FunctionCallbackInfo& args); #ifdef WITH_SSL void v8_runt_socket_enable_ssl(const v8::FunctionCallbackInfo& args); #endif diff --git a/Sources/websocket.cpp b/Sources/websocket.cpp index fcaf918..e76cd02 100644 --- a/Sources/websocket.cpp +++ b/Sources/websocket.cpp @@ -101,11 +101,19 @@ namespace WebSocketWrapper { void processEvents() { try { + std::vector closed_ids; for (auto& [id, client] : active_websockets) { if (client) { client->processEvents(); + if (client->getReadyState() == CLOSED + && !client->hasPendingEvents()) { + closed_ids.push_back(id); + } } } + for (int id : closed_ids) { + active_websockets.erase(id); + } } catch (...) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in global processEvents"); @@ -120,137 +128,9 @@ namespace WebSocketWrapper { return id; } - class SimpleWebSocketClient { - private: - SOCKET socket_; - std::thread* thread_; - std::atomic connected_{false}; - std::atomic should_stop_{false}; - std::string host_; - int port_; - std::string path_; - - public: - SimpleWebSocketClient(const std::string& url) : socket_(INVALID_SOCKET), thread_(nullptr) { - parseUrl(url); - } - - ~SimpleWebSocketClient() { - disconnect(); - } - - bool connect() { - socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (socket_ == INVALID_SOCKET) { - return false; - } - - SocketOptimization::optimizeWebSocket(static_cast(socket_)); - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port_); - inet_pton(AF_INET, host_.c_str(), &addr.sin_addr); - - if (::connect(socket_, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { - closesocket(socket_); - socket_ = INVALID_SOCKET; - return false; - } - - std::string handshake = - "GET " + path_ + " HTTP/1.1\r\n" - "Host: " + host_ + ":" + std::to_string(port_) + "\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n\r\n"; - - if (send(socket_, handshake.c_str(), static_cast(handshake.length()), 0) == SOCKET_ERROR) { - closesocket(socket_); - socket_ = INVALID_SOCKET; - return false; - } - - char buffer[SocketOptimization::SMALL_BUFFER_SIZE]; - int bytes = recv(socket_, buffer, sizeof(buffer) - 1, 0); - if (bytes > 0) { - buffer[bytes] = '\0'; - if (strstr(buffer, "101 Switching Protocols")) { - connected_ = true; - return true; - } - } - - closesocket(socket_); - socket_ = INVALID_SOCKET; - return false; - } - - void disconnect() { - should_stop_ = true; - if (socket_ != INVALID_SOCKET) { - closesocket(socket_); - socket_ = INVALID_SOCKET; - } - if (thread_ && thread_->joinable()) { - thread_->join(); - delete thread_; - thread_ = nullptr; - } - connected_ = false; - } - - bool isConnected() const { - return connected_; - } - - void sendText(const std::string& data) { - if (!connected_ || socket_ == INVALID_SOCKET) return; - - // text frame format FIN + text frame - std::vector frame; - frame.push_back(0x81); - - if (data.length() < 126) { - frame.push_back(0x80 | static_cast(data.length())); // MASK + length - } else { - // TODO: support longer messages - return; - } - - // adds simplified masking key - uint8_t mask[4] = {0x12, 0x34, 0x56, 0x78}; - frame.insert(frame.end(), mask, mask + 4); - - // adds the masked payload - for (size_t i = 0; i < data.length(); i++) { - frame.push_back(data[i] ^ mask[i % 4]); - } - - send(socket_, (char*)frame.data(), static_cast(frame.size()), 0); - } - - private: - void parseUrl(const std::string& url) { - std::regex url_regex(R"(^wss?://([^:/]+)(?::(\d+))?(/.*)?$)"); - std::smatch matches; - - if (std::regex_match(url, matches, url_regex)) { - host_ = matches[1].str(); - port_ = matches[2].matched ? std::stoi(matches[2].str()) : 80; - path_ = matches[3].matched ? matches[3].str() : "/"; - } else { - host_ = "localhost"; - port_ = 80; - path_ = "/"; - } - } - }; - WebSocketClient::WebSocketClient(Isolate* isolate, Global* global_context, const std::string& url) : isolate_(isolate), global_context_(global_context), url_(url), ready_state_(CONNECTING), - is_ssl_(false), buffered_amount_(0), ws_(nullptr), ssl_ws_(nullptr) + is_ssl_(false), buffered_amount_(0), ws_(-1) #ifdef WITH_SSL #ifdef _WIN32 , ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1) @@ -278,21 +158,35 @@ namespace WebSocketWrapper { ready_state_ = CONNECTING; // start connection using thread pool - GlobalThreadPool::getInstance().enqueue([this, host, port, path]() { + connect_future_ = GlobalThreadPool::getInstance().enqueue([this, host, port, path]() { try { + if (ready_state_ == CLOSED) { + return; + } #ifdef DEBUG_NETWORK - kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket connecting to %s:%d%s (SSL: %s)", + kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket connecting to %s:%d%s (SSL: %s)", host.c_str(), port, path.c_str(), is_ssl_ ? "yes" : "no"); #endif - + if (connectToServer(host, port, path)) { - ready_state_ = OPEN; - handleOpen(); - - messageLoop(); + if (ready_state_ == OPEN) { + messageLoop(); + } else { + if (is_ssl_) { + cleanupWebSocketSSL(); + } + if (ws_ >= 0) { + #ifdef _WIN32 + closesocket((SOCKET)ws_); + #else + ::close(ws_); + #endif + ws_ = -1; + } + } } else { - ready_state_ = CLOSED; handleError("Connection failed"); + ready_state_ = CLOSED; } } catch (...) { ready_state_ = CLOSED; @@ -304,9 +198,39 @@ namespace WebSocketWrapper { if (ready_state_ == OPEN) { close(); } + ready_state_ = CLOSED; + if (ws_ >= 0) { + #ifdef _WIN32 + ::shutdown((SOCKET)ws_, SD_BOTH); + #else + ::shutdown(ws_, SHUT_RDWR); + #endif + } + if (connect_future_.valid()) { + connect_future_.wait(); + } + if (ws_ >= 0) { + #ifdef _WIN32 + closesocket((SOCKET)ws_); + #else + ::close(ws_); + #endif + ws_ = -1; + } #ifdef WITH_SSL cleanupWSL(); #endif + if (isolate_ != nullptr) { + v8::Locker locker{isolate_}; + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + on_open_.Reset(); + on_message_.Reset(); + on_error_.Reset(); + on_close_.Reset(); + } + WebSocketEvent ev; + while (event_queue_.try_pop(ev)) {} } bool WebSocketClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path) { @@ -338,7 +262,7 @@ namespace WebSocketWrapper { return; } - if (ws_ == nullptr) { + if (ws_ < 0) { handleError("WebSocket: No socket available for sending"); return; } @@ -353,7 +277,7 @@ namespace WebSocketWrapper { // create and send WebSocket frame std::vector frame = createWebSocketFrame(data); - int sock = static_cast(reinterpret_cast(ws_)); + int sock = ws_; int sendResult; if (is_ssl_ && isSSLReady()) { @@ -388,7 +312,7 @@ namespace WebSocketWrapper { return; } - if (ws_ == nullptr) { + if (ws_ < 0) { handleError("WebSocket: No socket available for sending"); return; } @@ -403,7 +327,7 @@ namespace WebSocketWrapper { // binary frame with opcode 0x02 std::vector frame = createWebSocketBinaryFrame(data); - int sock = static_cast(reinterpret_cast(ws_)); + int sock = ws_; int sendResult; if (is_ssl_ && isSSLReady()) { @@ -434,8 +358,17 @@ namespace WebSocketWrapper { return; } + if (ready_state_ == OPEN && ws_ >= 0) { + std::vector frame = createCloseFrame(code, reason); + if (is_ssl_ && isSSLReady()) { + webSocketSSLSend(frame.data(), static_cast(frame.size())); + } else { + int sock = ws_; + ::send(sock, reinterpret_cast(frame.data()), static_cast(frame.size()), 0); + } + } + ready_state_ = CLOSING; - ready_state_ = CLOSED; handleClose(code, reason); } @@ -457,16 +390,6 @@ namespace WebSocketWrapper { on_close_.Reset(isolate_, callback); } - // event handlers called from worker thread that queue events safely - void WebSocketClient::handleOpen() { - event_queue_.push(WebSocketEvent(EVENT_OPEN)); - } - - void WebSocketClient::handleMessage(const std::string& message) { - // push directly to lock free queue - event_queue_.push(WebSocketEvent(EVENT_MESSAGE, message)); - } - void WebSocketClient::handleError(const std::string& error) { try { #ifdef DEBUG_NETWORK @@ -479,8 +402,8 @@ namespace WebSocketWrapper { } void WebSocketClient::handleClose(int code, const std::string& reason) { - ready_state_ = CLOSED; event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason)); + ready_state_ = CLOSED; } // process events on main thread with lock free queue and batching @@ -928,6 +851,8 @@ namespace WebSocketWrapper { #endif return false; } + + ws_ = sock; sockaddr_in addr{}; addr.sin_family = AF_INET; @@ -945,6 +870,7 @@ namespace WebSocketWrapper { #else close(sock); #endif + ws_ = -1; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to resolve host %s", host.c_str()); #endif @@ -961,6 +887,7 @@ namespace WebSocketWrapper { #else close(sock); #endif + ws_ = -1; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to connect to %s:%d", host.c_str(), port); #endif @@ -971,7 +898,7 @@ namespace WebSocketWrapper { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: TCP connected to %s:%d", host.c_str(), port); #endif - ws_ = reinterpret_cast(static_cast(sock)); + ws_ = sock; if (is_ssl_ && port == 443) { if (!initializeWebSocketSSL(sock, host)) { @@ -980,7 +907,7 @@ namespace WebSocketWrapper { #else close(sock); #endif - ws_ = nullptr; + ws_ = -1; return false; } #ifdef DEBUG_NETWORK @@ -995,7 +922,7 @@ namespace WebSocketWrapper { #else close(sock); #endif - ws_ = nullptr; + ws_ = -1; return false; } @@ -1003,9 +930,11 @@ namespace WebSocketWrapper { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake completed successfully"); #endif - ready_state_ = OPEN; - fireOpenEvent(); - + if (ready_state_ == CONNECTING) { + ready_state_ = OPEN; + fireOpenEvent(); + } + return true; } catch (const std::exception& e) { @@ -1060,23 +989,35 @@ namespace WebSocketWrapper { } char buffer[SocketOptimization::SMALL_BUFFER_SIZE]; - int bytesReceived; - if (is_ssl_ && isSSLReady()) { - bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1); - } else { - bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0); + std::string response; + for (int attempt = 0; attempt < 64 + && response.find("\r\n\r\n") == std::string::npos; ++attempt) { + int bytesReceived; + if (is_ssl_ && isSSLReady()) { + bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1); + if (bytesReceived == 0) { + continue; + } + } else { + bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0); + } + + if (bytesReceived <= 0) { + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive handshake response"); + #endif + return false; + } + response.append(buffer, bytesReceived); } - - if (bytesReceived <= 0) { + + if (response.find("\r\n\r\n") == std::string::npos) { #ifdef DEBUG_NETWORK - kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive handshake response"); + kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Handshake response never completed"); #endif return false; } - buffer[bytesReceived] = '\0'; - std::string response(buffer); - #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received handshake response"); #endif @@ -1155,8 +1096,8 @@ namespace WebSocketWrapper { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed: %d - %s", code, reason.c_str()); #endif - ready_state_ = CLOSED; event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason)); + ready_state_ = CLOSED; } // complete frame helper for ring buffer messageLoop @@ -1186,14 +1127,23 @@ namespace WebSocketWrapper { close_reason = std::string(payload.begin() + 2, payload.end()); } } - fireCloseEvent(close_code, close_reason); + if (ready_state_ == OPEN) { + std::vector echoFrame = createCloseFrame(close_code, ""); + if (is_ssl_ && isSSLReady()) { + webSocketSSLSend(echoFrame.data(), static_cast(echoFrame.size())); + } else { + int sock = ws_; + ::send(sock, reinterpret_cast(echoFrame.data()), static_cast(echoFrame.size()), 0); + } + fireCloseEvent(close_code, close_reason); + } } break; case 0x9: // ping frame { std::vector pongFrame = createPongFrame(payload); - int sock = static_cast(reinterpret_cast(ws_)); + int sock = ws_; if (is_ssl_ && isSSLReady()) { webSocketSSLSend(pongFrame.data(), static_cast(pongFrame.size())); } else { @@ -1213,14 +1163,14 @@ namespace WebSocketWrapper { // following uWebSockets void WebSocketClient::messageLoop() { - if (ws_ == nullptr) { + if (ws_ < 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: No socket available for message loop"); #endif return; } - int sock = static_cast(reinterpret_cast(ws_)); + int sock = ws_; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Starting message loop"); #endif @@ -1271,8 +1221,11 @@ namespace WebSocketWrapper { } else { bytesReceived = recv(sock, reinterpret_cast(writePtr), static_cast(contiguousSpace), 0); } - + if (bytesReceived <= 0) { + if (bytesReceived == 0 && is_ssl_ && isSSLReady()) { + continue; + } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed by server"); #endif @@ -1357,6 +1310,10 @@ namespace WebSocketWrapper { } } + if (ready_state_ == OPEN) { + fireCloseEvent(1006, ""); + } + #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message loop completed"); #endif @@ -1464,7 +1421,7 @@ namespace WebSocketWrapper { case 0x9: { std::vector pongFrame = createPongFrame(payload); - int sock = static_cast(reinterpret_cast(ws_)); + int sock = ws_; int sendResult; if (is_ssl_ && isSSLReady()) { @@ -1634,9 +1591,13 @@ namespace WebSocketWrapper { return result; } #else - // TODO: SHA1 - kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: SHA1 not implemented for this platform"); - return std::vector(20, 0); + std::vector result(SHA_DIGEST_LENGTH, 0); + SHA1(reinterpret_cast(data.data()), + data.size(), result.data()); + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SHA1 hash computed successfully"); + #endif + return result; #endif } @@ -1709,12 +1670,24 @@ namespace WebSocketWrapper { #endif return true; #else - if (ssl_initialized_) { - return true; + ssl_socket_ = sock; + + if (!initWSL()) { + kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to initialize SSL context"); + return false; } - // TODO: OpenSSL - kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not supported on this platform"); - return false; + + if (!performWSLHandshake(sock, host)) { + kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL handshake failed"); + cleanupWSL(); + return false; + } + + ssl_initialized_ = true; + #ifdef DEBUG_NETWORK + kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL/TLS initialized successfully"); + #endif + return true; #endif #else kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not compiled in"); @@ -1964,7 +1937,7 @@ namespace WebSocketWrapper { } } - if (status == SEC_I_CONTINUE_NEEDED && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) { + if ((status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) { // validate buffer before sending if (outbuffers[0].cbBuffer < 0 || outbuffers[0].cbBuffer > 65536) { kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid WebSocket SSL handshake buffer size: %d bytes (corrupted data)", outbuffers[0].cbBuffer); @@ -2024,6 +1997,10 @@ namespace WebSocketWrapper { } if (status == SEC_E_OK) { + if (!handshake_buffer.empty()) { + ssl_buffer_.insert(ssl_buffer_.end(), handshake_buffer.begin(), + handshake_buffer.end()); + } ssl_context_initialized_ = true; kinc_log(KINC_LOG_LEVEL_INFO, "SChannel WebSocket SSL handshake completed successfully"); return true; @@ -2649,6 +2626,160 @@ void runt_websocket_set_onclose(const FunctionCallbackInfo& args) { } // Native WebSocket class implementation for V8 +static void dispatchWebSocketEvent(const FunctionCallbackInfo& args, const char* type, const char* handler) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Local context = isolate->GetCurrentContext(); + Local self = args.Data().As(); + + Local event; + if (args.Length() > 0 && args[0]->IsObject()) { + event = args[0].As(); + } else { + event = Object::New(isolate); + } + event->Set(context, String::NewFromUtf8(isolate, "type").ToLocalChecked(), String::NewFromUtf8(isolate, type).ToLocalChecked()); + event->Set(context, String::NewFromUtf8(isolate, "target").ToLocalChecked(), self); + event->Set(context, String::NewFromUtf8(isolate, "currentTarget").ToLocalChecked(), self); + + Local argv[1] = { event }; + Local fn = self->Get(context, String::NewFromUtf8(isolate, handler).ToLocalChecked()).ToLocalChecked(); + if (fn->IsFunction()) { + TryCatch try_catch(isolate); + fn.As()->Call(context, self, 1, argv); + if (try_catch.HasCaught()) { + String::Utf8Value err(isolate, try_catch.Exception()); + kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket %s handler threw: %s", type, + *err != nullptr ? *err : "unknown"); + } + } + + Local listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked(); + if (listenersVal->IsObject()) { + Local arr = listenersVal.As()->Get(context, String::NewFromUtf8(isolate, type).ToLocalChecked()).ToLocalChecked(); + if (arr->IsArray()) { + Local listeners = arr.As(); + uint32_t length = listeners->Length(); + std::vector> snapshot; + snapshot.reserve(length); + for (uint32_t i = 0; i < length; ++i) { + Local listener = listeners->Get(context, i).ToLocalChecked(); + if (listener->IsFunction()) { + snapshot.push_back(listener.As()); + } + } + for (Local& listener : snapshot) { + TryCatch try_catch(isolate); + listener->Call(context, self, 1, argv); + if (try_catch.HasCaught()) { + String::Utf8Value err(isolate, try_catch.Exception()); + kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket %s listener threw: %s", type, + *err != nullptr ? *err : "unknown"); + } + } + } + } +} + +static void WebSocketDispatchOpen(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Local context = isolate->GetCurrentContext(); + Local self = args.Data().As(); + + Local idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked(); + if (idVal->IsNumber()) { + int wsId = idVal->Int32Value(context).FromJust(); + auto it = WebSocketWrapper::active_websockets.find(wsId); + if (it != WebSocketWrapper::active_websockets.end()) { + self->Set(context, String::NewFromUtf8(isolate, "protocol").ToLocalChecked(), + String::NewFromUtf8(isolate, it->second->getProtocol().c_str()).ToLocalChecked()); + } + } + dispatchWebSocketEvent(args, "open", "onopen"); +} + +static void WebSocketDispatchMessage(const FunctionCallbackInfo& args) { + dispatchWebSocketEvent(args, "message", "onmessage"); +} + +static void WebSocketDispatchError(const FunctionCallbackInfo& args) { + dispatchWebSocketEvent(args, "error", "onerror"); +} + +static void WebSocketDispatchClose(const FunctionCallbackInfo& args) { + dispatchWebSocketEvent(args, "close", "onclose"); +} + +static void WebSocketReadyStateGetter(Local property, const PropertyCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + int state = WebSocketWrapper::CLOSED; + Local idVal = info.Holder()->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked(); + if (idVal->IsNumber()) { + auto it = WebSocketWrapper::active_websockets.find(idVal->Int32Value(context).FromJust()); + if (it != WebSocketWrapper::active_websockets.end()) { + state = it->second->getReadyState(); + } + } + info.GetReturnValue().Set(Integer::New(isolate, state)); +} + +static void WebSocketReadyStateSetter(Local property, Local value, const PropertyCallbackInfo& info) {} + +static void WebSocketAddEventListener(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Local context = isolate->GetCurrentContext(); + Local self = args.Holder(); + + if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return; + + Local listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked(); + if (!listenersVal->IsObject()) return; + Local listeners = listenersVal.As(); + + Local arr = listeners->Get(context, args[0]).ToLocalChecked(); + Local listenerList; + if (arr->IsArray()) { + listenerList = arr.As(); + for (uint32_t i = 0; i < listenerList->Length(); ++i) { + Local existing = listenerList->Get(context, i).ToLocalChecked(); + if (existing->StrictEquals(args[1])) return; + } + } else { + listenerList = Array::New(isolate); + listeners->Set(context, args[0], listenerList); + } + listenerList->Set(context, listenerList->Length(), args[1]); +} + +static void WebSocketRemoveEventListener(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Local context = isolate->GetCurrentContext(); + Local self = args.Holder(); + + if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return; + + Local listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked(); + if (!listenersVal->IsObject()) return; + + Local arr = listenersVal.As()->Get(context, args[0]).ToLocalChecked(); + if (!arr->IsArray()) return; + Local listenerList = arr.As(); + + for (uint32_t i = 0; i < listenerList->Length(); ++i) { + Local listener = listenerList->Get(context, i).ToLocalChecked(); + if (listener->StrictEquals(args[1])) { + Local splice = listenerList->Get(context, String::NewFromUtf8(isolate, "splice").ToLocalChecked()).ToLocalChecked(); + Local spliceArgs[2] = { Integer::New(isolate, i), Integer::New(isolate, 1) }; + splice.As()->Call(context, listenerList, 2, spliceArgs); + break; + } + } +} + static void WebSocketConstructor(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); @@ -2672,7 +2803,6 @@ static void WebSocketConstructor(const FunctionCallbackInfo& args) { Local instance = args.This(); instance->Set(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked(), Integer::New(isolate, wsId)); instance->Set(context, String::NewFromUtf8(isolate, "url").ToLocalChecked(), args[0]); - instance->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 0)); instance->Set(context, String::NewFromUtf8(isolate, "protocol").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "extensions").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "binaryType").ToLocalChecked(), String::NewFromUtf8(isolate, "blob").ToLocalChecked()); @@ -2681,6 +2811,15 @@ static void WebSocketConstructor(const FunctionCallbackInfo& args) { instance->Set(context, String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "onerror").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "onclose").ToLocalChecked(), Null(isolate)); + instance->Set(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked(), Object::New(isolate)); + + auto it = WebSocketWrapper::active_websockets.find(wsId); + if (it != WebSocketWrapper::active_websockets.end()) { + it->second->setOnOpen(Function::New(context, WebSocketDispatchOpen, instance).ToLocalChecked()); + it->second->setOnMessage(Function::New(context, WebSocketDispatchMessage, instance).ToLocalChecked()); + it->second->setOnError(Function::New(context, WebSocketDispatchError, instance).ToLocalChecked()); + it->second->setOnClose(Function::New(context, WebSocketDispatchClose, instance).ToLocalChecked()); + } args.GetReturnValue().Set(instance); } @@ -2745,8 +2884,6 @@ static void WebSocketClose(const FunctionCallbackInfo& args) { if (readyState == 2 || readyState == 3) return; - self->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 2)); - Local idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked(); int wsId = idVal->Int32Value(context).FromJust(); @@ -2883,10 +3020,15 @@ void bind_websocket_class(Isolate* isolate, const Global& context) { Local wsTpl = FunctionTemplate::New(isolate, WebSocketConstructor); wsTpl->SetClassName(String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked()); wsTpl->InstanceTemplate()->SetInternalFieldCount(1); + wsTpl->InstanceTemplate()->SetNativeDataProperty( + String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), + WebSocketReadyStateGetter, WebSocketReadyStateSetter); Local proto = wsTpl->PrototypeTemplate(); proto->Set(isolate, "send", FunctionTemplate::New(isolate, WebSocketSend)); proto->Set(isolate, "close", FunctionTemplate::New(isolate, WebSocketClose)); + proto->Set(isolate, "addEventListener", FunctionTemplate::New(isolate, WebSocketAddEventListener)); + proto->Set(isolate, "removeEventListener", FunctionTemplate::New(isolate, WebSocketRemoveEventListener)); Local wsFunc = wsTpl->GetFunction(current_context).ToLocalChecked(); wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CONNECTING").ToLocalChecked(), Integer::New(isolate, 0)); @@ -2895,6 +3037,11 @@ void bind_websocket_class(Isolate* isolate, const Global& context) { wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CLOSED").ToLocalChecked(), Integer::New(isolate, 3)); global->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc); + + Local windowVal = global->Get(current_context, String::NewFromUtf8(isolate, "window").ToLocalChecked()).ToLocalChecked(); + if (windowVal->IsObject()) { + windowVal.As()->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc); + } } #endif diff --git a/Sources/websocket.h b/Sources/websocket.h index e810c5c..332660c 100644 --- a/Sources/websocket.h +++ b/Sources/websocket.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ namespace WebSocketWrapper { void processEvents(); ReadyState getReadyState() const { return ready_state_; } + bool hasPendingEvents() const { return !event_queue_.empty(); } const std::string& getUrl() const { return url_; } const std::string& getProtocol() const { return protocol_; } int getBufferedAmount() const { return buffered_amount_; } @@ -83,7 +85,7 @@ namespace WebSocketWrapper { v8::Global* global_context_; std::string url_; std::string protocol_; - ReadyState ready_state_; + std::atomic ready_state_; bool is_ssl_; int buffered_amount_; @@ -94,9 +96,9 @@ namespace WebSocketWrapper { v8::Global on_close_; LockFreeQueue event_queue_; - - void* ws_; - void* ssl_ws_; + std::future connect_future_; + + int ws_; #ifdef WITH_SSL #ifdef _WIN32 @@ -131,8 +133,6 @@ namespace WebSocketWrapper { std::string base64Encode(const std::string& data); // called from worker thread - void handleOpen(); - void handleMessage(const std::string& message); void handleError(const std::string& error); void handleClose(int code, const std::string& reason); diff --git a/kfile.js b/kfile.js index 97e3080..42b917a 100644 --- a/kfile.js +++ b/kfile.js @@ -13,6 +13,7 @@ let flags = { with_webview: true, with_uws: false, with_ssl: true, + with_rtc: true, debug_network: false }; @@ -122,9 +123,44 @@ if (flags.with_viewport && (platform === Platform.Windows || platform === Platfo project.addFile('Sources/viewport_server.h'); project.addFile('Sources/viewport_server.cpp'); } + +if (flags.with_rtc && (platform === Platform.Windows || platform === Platform.Linux || platform === Platform.OSX)) { + project.addDefine('WITH_RTC'); + project.addDefine('RTC_STATIC'); + project.addFile('Sources/rtc_bridge.h'); + project.addFile('Sources/rtc_bridge.cpp'); + project.addFile('Sources/rtc_v8_bindings.h'); + project.addFile('Sources/rtc_v8_bindings.cpp'); + project.addIncludeDir('libdatachannel/include'); + if (platform === Platform.Windows) { + project.addLib(root + '/libdatachannel/lib/datachannel-static'); + project.addLib(root + '/libdatachannel/lib/juice-static'); + project.addLib(root + '/libdatachannel/lib/usrsctp'); + project.addLib(root + '/libdatachannel/lib/MbedTLS'); + project.addLib('bcrypt'); + } + else { + project.addLinkerFlag(root + '/libdatachannel/lib/libdatachannel-static.a'); + project.addLinkerFlag(root + '/libdatachannel/lib/libjuice-static.a'); + project.addLinkerFlag(root + '/libdatachannel/lib/libusrsctp.a'); + project.addLinkerFlag(root + '/libdatachannel/lib/libsrtp2.a'); + project.addLinkerFlag(root + '/libdatachannel/lib/libMbedTLS.a'); + if (platform === Platform.OSX) { + const odir = '/opt/homebrew/opt/openssl@3/lib'; + project.addLinkerFlag(path.join(odir, 'libssl.a')); + project.addLinkerFlag(path.join(odir, 'libcrypto.a')); + } + } +} + if (flags.with_networking && platform !== Platform.Windows) { - project.addLib('ssl'); - project.addLib('crypto'); + // On macOS with RTC, libdatachannel's static OpenSSL must be the only + // TLS impl; adding -lssl/-lcrypto would bind some EVP symbols to the + // system LibreSSL dylibs and crash inside srtp_init. + if (!(flags.with_rtc && platform === Platform.OSX)) { + project.addLib('ssl'); + project.addLib('crypto'); + } } if (flags.with_worker) { @@ -142,7 +178,7 @@ if (platform === Platform.Windows) { project.addLib(libdir + 'v8_monolith'); if (flags.with_webview) { project.addIncludeDir('webview2/include'); - project.addLib(root + '/webview2/lib/x64/WebView2Loader'); + project.addLib(root + '/webview2/lib/x64/WebView2LoaderStatic'); } if (!flags.release) { project.addDefine('_HAS_ITERATOR_DEBUGGING=0'); @@ -175,10 +211,15 @@ else if (platform === Platform.OSX) { project.addLinkerFlag('-framework AppKit'); } if (flags.with_networking) { - project.addIncludeDir('apple-libressl-sdk/include'); - project.addLinkerFlag('-L' + root + '/apple-libressl-sdk/lib'); - project.addLinkerFlag('-lssl'); - project.addLinkerFlag('-lcrypto'); + if (flags.with_rtc) { + project.addIncludeDir('/opt/homebrew/opt/openssl@3/include'); + } + else { + project.addIncludeDir('apple-libressl-sdk/include'); + project.addLinkerFlag('-L' + root + '/apple-libressl-sdk/lib'); + project.addLinkerFlag('-lssl'); + project.addLinkerFlag('-lcrypto'); + } project.addLinkerFlag('-framework Security'); project.addLinkerFlag('-framework SystemConfiguration'); } diff --git a/libdatachannel/LICENSE b/libdatachannel/LICENSE new file mode 100644 index 0000000..3d76a56 --- /dev/null +++ b/libdatachannel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 RandomityGuy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libdatachannel/include/rtc/av1rtppacketizer.hpp b/libdatachannel/include/rtc/av1rtppacketizer.hpp new file mode 100644 index 0000000..c2b2b79 --- /dev/null +++ b/libdatachannel/include/rtc/av1rtppacketizer.hpp @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2023 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_AV1_RTP_PACKETIZER_H +#define RTC_AV1_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "nalunit.hpp" +#include "rtppacketizer.hpp" + +namespace rtc { + +// RTP packetization of AV1 payload +class RTC_CPP_EXPORT AV1RtpPacketizer final : public RtpPacketizer { +public: + inline static const uint32_t ClockRate = VideoClockRate; + [[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate; + + // Define how OBUs are seperated in a AV1 Sample + enum class Packetization { + Obu = RTC_OBU_PACKETIZED_OBU, + TemporalUnit = RTC_OBU_PACKETIZED_TEMPORAL_UNIT, + }; + + // Constructs AV1 payload packetizer with given RTP configuration. + // @note RTP configuration is used in packetization process which may change some configuration + // properties such as sequence number. + AV1RtpPacketizer(Packetization packetization, shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + +private: + static std::vector extractTemporalUnitObus(const binary &data); + + std::vector fragment(binary data) override; + std::vector fragmentObu(const binary &data); + + const Packetization mPacketization; + const size_t mMaxFragmentSize; + + std::unique_ptr mSequenceHeader; +}; + +// For backward compatibility, do not use +using AV1PacketizationHandler [[deprecated("Add AV1RtpPacketizer directly")]] = PacketizationHandler; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_AV1_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/candidate.hpp b/libdatachannel/include/rtc/candidate.hpp new file mode 100644 index 0000000..00ca20d --- /dev/null +++ b/libdatachannel/include/rtc/candidate.hpp @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_CANDIDATE_H +#define RTC_CANDIDATE_H + +#include "common.hpp" + +#include + +namespace rtc { + +class RTC_CPP_EXPORT Candidate { +public: + enum class Family { Unresolved, Ipv4, Ipv6 }; + enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed }; + enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown }; + + Candidate(); + Candidate(string candidate); + Candidate(string candidate, string mid); + + void hintMid(string mid); + void changeAddress(string addr); + void changeAddress(string addr, uint16_t port); + void changeAddress(string addr, string service); + + enum class ResolveMode { Simple, Lookup }; + bool resolve(ResolveMode mode = ResolveMode::Simple); + + Type type() const; + TransportType transportType() const; + uint32_t priority() const; + string candidate() const; + string mid() const; + operator string() const; + + bool operator==(const Candidate &other) const; + bool operator!=(const Candidate &other) const; + + bool isResolved() const; + Family family() const; + optional address() const; + optional port() const; + +private: + void parse(string candidate); + + string mFoundation; + uint32_t mComponent, mPriority; + string mTypeString, mTransportString; + Type mType; + TransportType mTransportType; + string mNode, mService; + string mTail; + + optional mMid; + + // Extracted on resolution + Family mFamily; + string mAddress; + uint16_t mPort; +}; + +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const Candidate &candidate); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const Candidate::Type &type); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, + const Candidate::TransportType &transportType); + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/channel.hpp b/libdatachannel/include/rtc/channel.hpp new file mode 100644 index 0000000..384279d --- /dev/null +++ b/libdatachannel/include/rtc/channel.hpp @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2019-2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_CHANNEL_H +#define RTC_CHANNEL_H + +#include "common.hpp" + +#include +#include + +namespace rtc { + +namespace impl { +struct Channel; +} + +class RTC_CPP_EXPORT Channel : private CheshireCat { +public: + virtual ~Channel(); + + virtual void close() = 0; + virtual bool send(message_variant data) = 0; // returns false if buffered + virtual bool send(const byte *data, size_t size) = 0; + + virtual bool isOpen() const = 0; + virtual bool isClosed() const = 0; + virtual size_t maxMessageSize() const; // max message size in a call to send + virtual size_t bufferedAmount() const; // total size buffered to send + + void onOpen(std::function callback); + void onClosed(std::function callback); + void onError(std::function callback); + + void onMessage(std::function callback); + void onMessage(std::function binaryCallback, + std::function stringCallback); + + void onBufferedAmountLow(std::function callback); + void setBufferedAmountLowThreshold(size_t amount); + + void resetCallbacks(); + + // Extended API + optional receive(); // only if onMessage unset + optional peek(); // only if onMessage unset + size_t availableAmount() const; // total size available to receive + void onAvailable(std::function callback); + +protected: + Channel(impl_ptr impl); +}; + +} // namespace rtc + +#endif // RTC_CHANNEL_H diff --git a/libdatachannel/include/rtc/common.hpp b/libdatachannel/include/rtc/common.hpp new file mode 100644 index 0000000..371f18f --- /dev/null +++ b/libdatachannel/include/rtc/common.hpp @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_COMMON_H +#define RTC_COMMON_H + +#ifdef RTC_STATIC +#define RTC_CPP_EXPORT +#else // dynamic library +#ifdef _WIN32 +#ifdef RTC_EXPORTS +#define RTC_CPP_EXPORT __declspec(dllexport) // building the library +#else +#define RTC_CPP_EXPORT __declspec(dllimport) // using the library +#endif +#else // not WIN32 +#define RTC_CPP_EXPORT +#endif +#endif + +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0602 // Windows 8 +#endif +#ifdef _MSC_VER +#pragma warning(disable : 4251) // disable "X needs to have dll-interface..." +#endif +#endif + +#ifndef RTC_ENABLE_WEBSOCKET +#define RTC_ENABLE_WEBSOCKET 1 +#endif + +#ifndef RTC_ENABLE_MEDIA +#define RTC_ENABLE_MEDIA 1 +#endif + +#include "rtc.h" // for C API defines + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rtc { + +using std::byte; +using std::nullopt; +using std::optional; +using std::shared_ptr; +using std::string; +using std::string_view; +using std::unique_ptr; +using std::variant; +using std::weak_ptr; + +using binary = std::vector; +using message_variant = variant; + +using std::int16_t; +using std::int32_t; +using std::int64_t; +using std::int8_t; +using std::ptrdiff_t; +using std::size_t; +using std::uint16_t; +using std::uint32_t; +using std::uint64_t; +using std::uint8_t; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/configuration.hpp b/libdatachannel/include/rtc/configuration.hpp new file mode 100644 index 0000000..d02fa7e --- /dev/null +++ b/libdatachannel/include/rtc/configuration.hpp @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_ICE_CONFIGURATION_H +#define RTC_ICE_CONFIGURATION_H + +#include "common.hpp" + +#include + +namespace rtc { + +struct RTC_CPP_EXPORT IceServer { + enum class Type { Stun, Turn }; + enum class RelayType { TurnUdp, TurnTcp, TurnTls }; + + // Any type + IceServer(const string &url); + + // STUN + IceServer(string hostname_, uint16_t port_); + IceServer(string hostname_, string service_); + + // TURN + IceServer(string hostname_, uint16_t port, string username_, string password_, + RelayType relayType_ = RelayType::TurnUdp); + IceServer(string hostname_, string service_, string username_, string password_, + RelayType relayType_ = RelayType::TurnUdp); + + string hostname; + uint16_t port; + Type type; + string username; + string password; + RelayType relayType; +}; + +struct RTC_CPP_EXPORT ProxyServer { + enum class Type { Http, Socks5 }; + + ProxyServer(const string &url); + + ProxyServer(Type type_, string hostname_, uint16_t port_); + ProxyServer(Type type_, string hostname_, uint16_t port_, string username_, string password_); + + Type type; + string hostname; + uint16_t port; + optional username; + optional password; +}; + +enum class CertificateType { + Default = RTC_CERTIFICATE_DEFAULT, // ECDSA + Ecdsa = RTC_CERTIFICATE_ECDSA, + Rsa = RTC_CERTIFICATE_RSA +}; + +enum class TransportPolicy { All = RTC_TRANSPORT_POLICY_ALL, Relay = RTC_TRANSPORT_POLICY_RELAY }; + +struct RTC_CPP_EXPORT Configuration { + // ICE settings + std::vector iceServers; + optional proxyServer; // libnice only + optional bindAddress; // libjuice only, default any + + // Options + CertificateType certificateType = CertificateType::Default; + TransportPolicy iceTransportPolicy = TransportPolicy::All; + bool enableIceTcp = false; + bool enableIceUdpMux = false; // libjuice only + bool disableAutoNegotiation = false; + bool disableAutoGathering = false; + bool forceMediaTransport = false; + bool disableFingerprintVerification = false; + + // Port range + uint16_t portRangeBegin = 1024; + uint16_t portRangeEnd = 65535; + + // Network MTU + optional mtu; + + // Local maximum message size for Data Channels + optional maxMessageSize; + + // Certificates and private keys + optional certificatePemFile; + optional keyPemFile; + optional keyPemPass; +}; + +#ifdef RTC_ENABLE_WEBSOCKET + +struct WebSocketConfiguration { + bool disableTlsVerification = false; // if true, don't verify the TLS certificate + optional proxyServer; // only non-authenticated http supported for now + std::vector protocols; + optional tcpConnectionTimeout; // zero to disable + optional connectionTimeout; // zero to disable + optional pingInterval; // zero to disable + optional maxOutstandingPings; + optional caCertificatePemFile; + optional certificatePemFile; + optional keyPemFile; + optional keyPemPass; + optional maxMessageSize; +}; + +struct WebSocketServerConfiguration { + uint16_t port = 8080; + bool enableTls = false; + optional certificatePemFile; + optional keyPemFile; + optional keyPemPass; + optional bindAddress; + optional connectionTimeout; + optional maxMessageSize; +}; + +#endif + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/datachannel.hpp b/libdatachannel/include/rtc/datachannel.hpp new file mode 100644 index 0000000..0e83a97 --- /dev/null +++ b/libdatachannel/include/rtc/datachannel.hpp @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_DATA_CHANNEL_H +#define RTC_DATA_CHANNEL_H + +#include "channel.hpp" +#include "common.hpp" +#include "reliability.hpp" + +#include + +namespace rtc { + +namespace impl { + +struct DataChannel; +struct PeerConnection; + +} // namespace impl + +class RTC_CPP_EXPORT DataChannel final : private CheshireCat, public Channel { +public: + DataChannel(impl_ptr impl); + ~DataChannel() override; + + optional stream() const; + optional id() const; + string label() const; + string protocol() const; + Reliability reliability() const; + + bool isOpen(void) const override; + bool isClosed(void) const override; + size_t maxMessageSize() const override; + + void close(void) override; + bool send(message_variant data) override; + bool send(const byte *data, size_t size) override; + template bool sendBuffer(const Buffer &buf); + template bool sendBuffer(Iterator first, Iterator last); + +private: + using CheshireCat::impl; +}; + +template std::pair to_bytes(const Buffer &buf) { + using T = typename std::remove_pointer::type; + using E = typename std::conditional::value, byte, T>::type; + return std::make_pair(static_cast(static_cast(buf.data())), + buf.size() * sizeof(E)); +} + +template bool DataChannel::sendBuffer(const Buffer &buf) { + auto [bytes, size] = to_bytes(buf); + return send(bytes, size); +} + +template bool DataChannel::sendBuffer(Iterator first, Iterator last) { + size_t size = 0; + for (Iterator it = first; it != last; ++it) + size += it->size(); + + binary buffer(size); + byte *pos = buffer.data(); + for (Iterator it = first; it != last; ++it) { + auto [bytes, len] = to_bytes(*it); + pos = std::copy(bytes, bytes + len, pos); + } + return send(std::move(buffer)); +} + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/dependencydescriptor.hpp b/libdatachannel/include/rtc/dependencydescriptor.hpp new file mode 100644 index 0000000..59ddcdf --- /dev/null +++ b/libdatachannel/include/rtc/dependencydescriptor.hpp @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2024 Shigemasa Watanabe (Wandbox) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_DEPENDENCY_DESCRIPTOR_H +#define RTC_DEPENDENCY_DESCRIPTOR_H + +#include "common.hpp" + +#include + +namespace rtc { + +struct BitWriter { + static BitWriter fromSizeBits(byte *buf, size_t offsetBits, size_t sizeBits); + static BitWriter fromNull(); + + size_t getWrittenBits() const; + + bool write(uint64_t v, size_t bits); + // Write non-symmetric unsigned encoded integer + // ref: https://aomediacodec.github.io/av1-rtp-spec/#a82-syntax + bool writeNonSymmetric(uint64_t v, uint64_t n); + +private: + size_t writePartialByte(uint8_t *p, size_t offset, uint64_t v, size_t bits); + +private: + byte *mBuf = nullptr; + size_t mInitialOffset = 0; + size_t mOffset = 0; + size_t mSize = 0; +}; + +enum class DecodeTargetIndication { + NotPresent = 0, + Discardable = 1, + Switch = 2, + Required = 3, +}; + +struct RenderResolution { + int width = 0; + int height = 0; +}; + +struct FrameDependencyTemplate { + int spatialId = 0; + int temporalId = 0; + std::vector decodeTargetIndications; + std::vector frameDiffs; + std::vector chainDiffs; +}; + +struct FrameDependencyStructure { + int templateIdOffset = 0; + int decodeTargetCount = 0; + int chainCount = 0; + std::vector decodeTargetProtectedBy; + std::vector resolutions; + std::vector templates; +}; + +struct DependencyDescriptor { + bool startOfFrame = true; + bool endOfFrame = true; + int frameNumber = 0; + FrameDependencyTemplate dependencyTemplate; + std::optional resolution; + std::optional activeDecodeTargetsBitmask; + bool structureAttached; +}; + +struct DependencyDescriptorContext { + DependencyDescriptor descriptor; + std::bitset<32> activeChains; + FrameDependencyStructure structure; +}; + +// Write dependency descriptor to RTP Header Extension +// Dependency descriptor specification is here: +// https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension +class DependencyDescriptorWriter { +public: + explicit DependencyDescriptorWriter(const DependencyDescriptorContext& context); + size_t getSizeBits() const; + size_t getSize() const; + void writeTo(byte *buf, size_t sizeBytes) const; + +private: + void doWriteTo(BitWriter &writer) const; + void writeBits(BitWriter &writer, uint64_t v, size_t bits) const; + void writeNonSymmetric(BitWriter &writer, uint64_t v, uint64_t n) const; + +private: + const FrameDependencyStructure &mStructure; + std::bitset<32> mActiveChains; + const DependencyDescriptor &mDescriptor; +}; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/description.hpp b/libdatachannel/include/rtc/description.hpp new file mode 100644 index 0000000..573190d --- /dev/null +++ b/libdatachannel/include/rtc/description.hpp @@ -0,0 +1,401 @@ +/** + * Copyright (c) 2019-2020 Paul-Louis Ageneau + * Copyright (c) 2020 Staz Modrzynski + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_DESCRIPTION_H +#define RTC_DESCRIPTION_H + +#include "candidate.hpp" +#include "common.hpp" +#include "rtp.hpp" + +#include +#include +#include + +namespace rtc { + +const string DEFAULT_OPUS_AUDIO_PROFILE = + "minptime=10;maxaveragebitrate=96000;stereo=1;sprop-stereo=1;useinbandfec=1"; + +// Use Constrained Baseline profile Level 3.1 (necessary for Firefox) +// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#Supported_video_codecs +// TODO: Should be 42E0 but 42C0 appears to be more compatible. Investigate this. +const string DEFAULT_H264_VIDEO_PROFILE = + "profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1"; + +struct CertificateFingerprint { + enum class Algorithm { Sha1, Sha224, Sha256, Sha384, Sha512 }; + static string AlgorithmIdentifier(Algorithm algorithm); + static size_t AlgorithmSize(Algorithm algorithm); + + bool isValid() const; + + Algorithm algorithm; + string value; +}; + +class RTC_CPP_EXPORT Description { +public: + enum class Type { Unspec, Offer, Answer, Pranswer, Rollback }; + enum class Role { ActPass, Passive, Active }; + + enum class Direction { + SendOnly = RTC_DIRECTION_SENDONLY, + RecvOnly = RTC_DIRECTION_RECVONLY, + SendRecv = RTC_DIRECTION_SENDRECV, + Inactive = RTC_DIRECTION_INACTIVE, + Unknown = RTC_DIRECTION_UNKNOWN + }; + + Description(const string &sdp, Type type = Type::Unspec, Role role = Role::ActPass); + Description(const string &sdp, string typeString); + + Type type() const; + string typeString() const; + Role role() const; + string bundleMid() const; + std::vector iceOptions() const; + optional iceUfrag() const; + optional icePwd() const; + optional fingerprint() const; + bool ended() const; + + void hintType(Type type); + void addIceOption(string option); + void removeIceOption(const string &option); + void setIceAttribute(string ufrag, string pwd); + void setFingerprint(CertificateFingerprint f); + + std::vector attributes() const; + void addAttribute(string attr); + void removeAttribute(const string &attr); + + std::vector candidates() const; + std::vector extractCandidates(); + bool hasCandidate(const Candidate &candidate) const; + void addCandidate(Candidate candidate); + void addCandidates(std::vector candidates); + void endCandidates(); + + operator string() const; + string generateSdp(string_view eol = "\r\n") const; + string generateApplicationSdp(string_view eol = "\r\n") const; + + class RTC_CPP_EXPORT RidAttribute { + public: + RidAttribute(string name, string value); + + [[nodiscard]] const string &name() const; + [[nodiscard]] const string &value() const; + + void value(string value); + + private: + string mName; + string mValue; + }; + + class RTC_CPP_EXPORT Rid { + public: + explicit Rid(string rid); + Rid(string rid, std::vector attributes); + + [[nodiscard]] const string &rid() const; + [[nodiscard]] const std::vector &attributes() const; + + private: + string mRid; + std::vector mAttributes; + }; + + class RTC_CPP_EXPORT RidBuilder { + public: + explicit RidBuilder(string rid); + + // https://datatracker.ietf.org/doc/html/rfc8851#name-arid-restrictions + RidBuilder& max_width(uint32_t value); + RidBuilder& max_height(uint32_t value); + RidBuilder& max_fps(uint32_t value); + RidBuilder& max_br(uint32_t value); + + RidBuilder& custom(string key, string value); + + // One time use only + [[nodiscard]] Rid build(); + + private: + string mRid; + std::vector mAttributes; + + RidBuilder& saveAttribute(string key, string value); + }; + + class RTC_CPP_EXPORT Entry { + public: + virtual ~Entry() = default; + + virtual string type() const; + virtual string protocol() const; + virtual string description() const; + virtual string mid() const; + + Direction direction() const; + void setDirection(Direction dir); + + bool isRemoved() const; + void markRemoved(); + + std::vector attributes() const; + void addAttribute(string attr); + void removeAttribute(const string &attr); + void addRid(string rid); // Just the name + void addRid(Rid rid); // With RFC 8851 attributes + + std::vector rids() const; + + struct RTC_CPP_EXPORT ExtMap { + static int parseId(string_view description); + + ExtMap(int id, string uri, Direction direction = Direction::Unknown); + ExtMap(string_view description); + + void setDescription(string_view description); + + int id; + string uri; + string attributes; + Direction direction = Direction::Unknown; + }; + + std::vector extIds(); + ExtMap *extMap(int id); + const ExtMap *extMap(int id) const; + void addExtMap(ExtMap map); + void removeExtMap(int id); + + operator string() const; + string generateSdp(string_view eol = "\r\n", string_view addr = "0.0.0.0", + uint16_t port = 9) const; + + virtual void parseSdpLine(string_view line); + + protected: + Entry(const string &mline, string mid, Direction dir = Direction::Unknown); + + virtual string generateSdpLines(string_view eol) const; + + std::vector mAttributes; + std::map mExtMaps; + + private: + string mType; + string mProtocol; + string mDescription; + string mMid; + std::vector mRids; + Direction mDirection; + bool mIsRemoved; + }; + + struct RTC_CPP_EXPORT Application : public Entry { + public: + Application(string mid = "data"); + Application(const string &mline, string mid); + virtual ~Application() = default; + + Application reciprocate() const; + + void setSctpPort(uint16_t port); + void hintSctpPort(uint16_t port); + void setMaxMessageSize(size_t size); + + optional sctpPort() const; + optional maxMessageSize() const; + + virtual void parseSdpLine(string_view line) override; + + private: + virtual string generateSdpLines(string_view eol) const override; + + optional mSctpPort; + optional mMaxMessageSize; + }; + + // Media (non-data) + class RTC_CPP_EXPORT Media : public Entry { + public: + Media(const string &mline, string mid, Direction dir = Direction::SendOnly); + Media(const string &sdp); + virtual ~Media() = default; + + string description() const override; + Media reciprocate() const; + + void addSSRC(uint32_t ssrc, optional name, optional msid = nullopt, + optional trackId = nullopt); + void removeSSRC(uint32_t ssrc); + void replaceSSRC(uint32_t old, uint32_t ssrc, optional name, + optional msid = nullopt, optional trackID = nullopt); + bool hasSSRC(uint32_t ssrc) const; + void clearSSRCs(); + std::vector getSSRCs() const; + optional getCNameForSsrc(uint32_t ssrc) const; + + void addRtxSSRC(SSRC primarySsrc, SSRC rtxSsrc, optional cname = nullopt); + void removeRtxSSRC(SSRC primarySsrc); + optional getRtxSsrcForSsrc(SSRC primarySsrc) const; + optional getSsrcForRtxSsrc(SSRC rtxSsrc) const; + + int bitrate() const; + void setBitrate(int bitrate); + + struct RTC_CPP_EXPORT RtpMap { + static int parsePayloadType(string_view description); + + explicit RtpMap(int payloadType); + RtpMap(string_view description); + + void setDescription(string_view description); + + void addFeedback(string fb); + void removeFeedback(const string &str); + bool hasFeedback(const string &str) const; + + void addParameter(string p); + void removeParameter(const string &str); + + int payloadType; + string format; + int clockRate; + string encParams; + + std::vector rtcpFbs; + std::vector fmtps; + }; + + bool hasPayloadType(int payloadType) const; + std::vector payloadTypes() const; + RtpMap *rtpMap(int payloadType); + const RtpMap *rtpMap(int payloadType) const; + void addRtpMap(RtpMap map); + void removeRtpMap(int payloadType); + void removeFormat(const string &format); + + void addRtxCodec(int payloadType, int origPayloadType, unsigned int clockRate); + + optional getRtxPayloadType(int primaryPayloadType) const; + + bool isRtxEnabled() const; + void disableRtx(); + + virtual void parseSdpLine(string_view line) override; + + private: + virtual string generateSdpLines(string_view eol) const override; + + int mBas = -1; + + std::vector mOrderedPayloadTypes; + std::map mRtpMaps; + std::vector mSsrcs; + std::map mCNameMap; + std::map mSsrcToRtxSsrc; // primary_ssrc -> rtx_ssrc + }; + + class RTC_CPP_EXPORT Audio : public Media { + public: + Audio(string mid = "audio", Direction dir = Direction::SendOnly); + + void addAudioCodec(int payloadType, string codec, optional profile = std::nullopt); + void addOpusCodec(int payloadType, optional profile = DEFAULT_OPUS_AUDIO_PROFILE); + void addPCMACodec(int payloadType, optional profile = std::nullopt); + void addPCMUCodec(int payloadType, optional profile = std::nullopt); + void addAACCodec(int payloadType, optional profile = std::nullopt); + void addG722Codec(int payloadType, optional profile = std::nullopt); + + [[deprecated("Use addAACCodec")]] inline void + addAacCodec(int payloadType, optional profile = std::nullopt) { + addAACCodec(payloadType, std::move(profile)); + }; + }; + + class RTC_CPP_EXPORT Video : public Media { + public: + Video(string mid = "video", Direction dir = Direction::SendOnly); + + void addVideoCodec(int payloadType, string codec, optional profile = std::nullopt); + + void addH264Codec(int payloadType, optional profile = DEFAULT_H264_VIDEO_PROFILE); + void addH265Codec(int payloadType, optional profile = std::nullopt); + void addVP8Codec(int payloadType, optional profile = std::nullopt); + void addVP9Codec(int payloadType, optional profile = std::nullopt); + void addAV1Codec(int payloadType, optional profile = std::nullopt); + }; + + // Adds RTX payload type mappings for SDP negotiation (codec level only). + // For per-stream RTX, also call Media::addRtxSSRC() once SSRCs are known. + // Set audio=true to also apply RTX to audio media streams. + void addRtx(optional clockRate = nullopt, bool audio = false); + + bool hasApplication() const; + bool hasAudioOrVideo() const; + bool hasMid(string_view mid) const; + + int addMedia(Media media); + int addMedia(Application application); + int addApplication(string mid = "data"); + int addVideo(string mid = "video", Direction dir = Direction::SendOnly); + int addAudio(string mid = "audio", Direction dir = Direction::SendOnly); + void clearMedia(); + + variant media(int index); + variant media(int index) const; + int mediaCount() const; + + const Application *application() const; + Application *application(); + + static Type stringToType(const string &typeString); + static string typeToString(Type type); + + string sessionId() const; + +private: + optional defaultCandidate() const; + shared_ptr createEntry(string mline, string mid, Direction dir); + void removeApplication(); + + Type mType; + + // Session-level attributes + Role mRole; + string mUsername; + string mSessionId; + std::vector mIceOptions; + optional mIceUfrag, mIcePwd; + optional mFingerprint; + std::vector mAttributes; // other attributes + + // Entries + std::vector> mEntries; + shared_ptr mApplication; + + // Candidates + std::vector mCandidates; + bool mEnded = false; +}; + +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const Description &description); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, Description::Type type); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, Description::Role role); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const Description::Direction &direction); + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/frameinfo.hpp b/libdatachannel/include/rtc/frameinfo.hpp new file mode 100644 index 0000000..2747e9f --- /dev/null +++ b/libdatachannel/include/rtc/frameinfo.hpp @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2019-2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_FRAMEINFO_H +#define RTC_FRAMEINFO_H + +#include "common.hpp" + +#include + +namespace rtc { + +struct RTC_CPP_EXPORT FrameInfo { + FrameInfo(uint32_t timestamp) : timestamp(timestamp) {}; + template> FrameInfo(std::chrono::duration timestamp) : timestampSeconds(timestamp) {} + + [[deprecated]] FrameInfo(uint8_t payloadType, uint32_t timestamp) : timestamp(timestamp), payloadType(payloadType) {}; + + uint32_t timestamp = 0; + uint8_t payloadType = 0; + + optional> timestampSeconds; + + bool isKeyFrame = false; // Set by the application + + /// Absolute capture timestamp in NTP-format (high 32 bits seconds since + /// the NTP epoch 1900-01-01 UTC; low 32 bits fractional seconds). When + /// set alongside an RtpPacketizationConfig with absCaptureTimeId > 0, + /// the RtpPacketizer writes the 8-byte form of the abs-capture-time RTP + /// header extension on every packet of this frame. Receivers use this + /// to recover the source's capture wallclock and compute glass-to-glass + /// latency. + /// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time + optional absCaptureTimeNtp; +}; + +} // namespace rtc + +#endif // RTC_FRAMEINFO_H diff --git a/libdatachannel/include/rtc/global.hpp b/libdatachannel/include/rtc/global.hpp new file mode 100644 index 0000000..47b6133 --- /dev/null +++ b/libdatachannel/include/rtc/global.hpp @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2020-2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_GLOBAL_H +#define RTC_GLOBAL_H + +#include "common.hpp" + +#include +#include +#include + +namespace rtc { + +#ifdef None +#undef None +#endif + +enum class LogLevel { // Don't change, it must match plog severity + None = 0, + Fatal = 1, + Error = 2, + Warning = 3, + Info = 4, + Debug = 5, + Verbose = 6 +}; + +typedef std::function LogCallback; + +RTC_CPP_EXPORT void InitLogger(LogLevel level, LogCallback callback = nullptr); + +RTC_CPP_EXPORT void SetThreadPoolSize(unsigned int count); // 0: hardware concurrency + +struct SctpSettings { + // For the following settings, not set means optimized default + optional recvBufferSize; // in bytes + optional sendBufferSize; // in bytes + optional maxChunksOnQueue; // in chunks + optional initialCongestionWindow; // in MTUs + optional maxBurst; // in MTUs + optional congestionControlModule; // 0: RFC2581, 1: HSTCP, 2: H-TCP, 3: RTCC + optional delayedSackTime; + optional minRetransmitTimeout; + optional maxRetransmitTimeout; + optional initialRetransmitTimeout; + optional maxRetransmitAttempts; + optional heartbeatInterval; +}; + +RTC_CPP_EXPORT void SetSctpSettings(SctpSettings s); + +// Optional global preload and cleanup +RTC_CPP_EXPORT bool Preload(); +RTC_CPP_EXPORT std::shared_future Cleanup(); + +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, LogLevel level); + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/h264rtpdepacketizer.hpp b/libdatachannel/include/rtc/h264rtpdepacketizer.hpp new file mode 100644 index 0000000..19bbe97 --- /dev/null +++ b/libdatachannel/include/rtc/h264rtpdepacketizer.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H264_RTP_DEPACKETIZER_H +#define RTC_H264_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "message.hpp" +#include "nalunit.hpp" +#include "rtp.hpp" +#include "rtpdepacketizer.hpp" + +namespace rtc { + +/// RTP depacketization for H264 +class RTC_CPP_EXPORT H264RtpDepacketizer final : public VideoRtpDepacketizer { +public: + using Separator = NalUnit::Separator; + + H264RtpDepacketizer(Separator separator = Separator::StartSequence); + ~H264RtpDepacketizer(); + +private: + message_ptr reassemble(message_buffer &buffer) override; + void addSeparator(binary &frame); + + const NalUnit::Separator mSeparator; +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif /* RTC_H264_RTP_DEPACKETIZER_H */ diff --git a/libdatachannel/include/rtc/h264rtppacketizer.hpp b/libdatachannel/include/rtc/h264rtppacketizer.hpp new file mode 100644 index 0000000..ffc5f2e --- /dev/null +++ b/libdatachannel/include/rtc/h264rtppacketizer.hpp @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * Copyright (c) 2023 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H264_RTP_PACKETIZER_H +#define RTC_H264_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "nalunit.hpp" +#include "rtppacketizer.hpp" + +namespace rtc { + +/// RTP packetization for H264 +class RTC_CPP_EXPORT H264RtpPacketizer final : public RtpPacketizer { +public: + using Separator = NalUnit::Separator; + + inline static const uint32_t ClockRate = VideoClockRate; + [[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate; + + /// Constructs h264 payload packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration + /// properties such as sequence number. + /// @param separator NAL unit separator + /// @param rtpConfig RTP configuration + /// @param maxFragmentSize maximum size of one NALU fragment + H264RtpPacketizer(Separator separator, shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + + // For backward compatibility, do not use + [[deprecated]] H264RtpPacketizer( + shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + +private: + std::vector fragment(binary data) override; + std::vector splitFrame(const binary &frame); + + const Separator mSeparator; + const size_t mMaxFragmentSize; +}; + +// For backward compatibility, do not use +using H264PacketizationHandler [[deprecated("Add H264RtpPacketizer directly")]] = PacketizationHandler; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_H264_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/h265nalunit.hpp b/libdatachannel/include/rtc/h265nalunit.hpp new file mode 100644 index 0000000..ad40cf0 --- /dev/null +++ b/libdatachannel/include/rtc/h265nalunit.hpp @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2023 Zita Liao (Dolby) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H265_NAL_UNIT_H +#define RTC_H265_NAL_UNIT_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "nalunit.hpp" + +#include +#include + +namespace rtc { + +#pragma pack(push, 1) + +#define H265_FU_HEADER_SIZE 1 +/// Nalu header +struct RTC_CPP_EXPORT H265NalUnitHeader { + /* + * nal_unit_header( ) { + * forbidden_zero_bit f(1) + * nal_unit_type u(6) + * nuh_layer_id u(6) + * nuh_temporal_id_plus1 u(3) + } + */ + uint8_t _first = 0; // high byte of header + uint8_t _second = 0; // low byte of header + + bool forbiddenBit() const { return _first >> 7; } + uint8_t unitType() const { return (_first & 0b0111'1110) >> 1; } + uint8_t nuhLayerId() const { return ((_first & 0x1) << 5) | ((_second & 0b1111'1000) >> 3); } + uint8_t nuhTempIdPlus1() const { return _second & 0b111; } + + void setForbiddenBit(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setUnitType(uint8_t type) { _first = (_first & 0b1000'0001) | ((type & 0b11'1111) << 1); } + void setNuhLayerId(uint8_t nuhLayerId) { + _first = (_first & 0b1111'1110) | ((nuhLayerId & 0b10'0000) >> 5); + _second = (_second & 0b0000'0111) | ((nuhLayerId & 0b01'1111) << 3); + } + void setNuhTempIdPlus1(uint8_t nuhTempIdPlus1) { + _second = (_second & 0b1111'1000) | (nuhTempIdPlus1 & 0b111); + } +}; + +/// Nalu fragment header +struct RTC_CPP_EXPORT H265NalUnitFragmentHeader { + /* + * +---------------+ + * |0|1|2|3|4|5|6|7| + * +-+-+-+-+-+-+-+-+ + * |S|E| FuType | + * +---------------+ + */ + uint8_t _first = 0; + + bool isStart() const { return _first >> 7; } + bool isEnd() const { return (_first >> 6) & 0x01; } + uint8_t unitType() const { return _first & 0b11'1111; } + + void setStart(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setEnd(bool isSet) { _first = (_first & 0b1011'1111) | (isSet << 6); } + void setUnitType(uint8_t type) { _first = (_first & 0b1100'0000) | (type & 0b11'1111); } +}; + +#pragma pack(pop) + +struct H265NalUnitFragment; + +/// NAL unit +struct RTC_CPP_EXPORT H265NalUnit : NalUnit { + static std::vector GenerateFragments(const std::vector &nalus, + size_t maxFragmentSize); + + H265NalUnit(const H265NalUnit &unit) = default; + H265NalUnit(size_t size, bool includingHeader = true) + : NalUnit(size, includingHeader, NalUnit::Type::H265) {} + H265NalUnit(binary &&data) : NalUnit(std::move(data)) {} + H265NalUnit() : NalUnit(NalUnit::Type::H265) {} + + template + H265NalUnit(Iterator begin_, Iterator end_) : NalUnit(begin_, end_) {} + + bool forbiddenBit() const { return header()->forbiddenBit(); } + uint8_t unitType() const { return header()->unitType(); } + uint8_t nuhLayerId() const { return header()->nuhLayerId(); } + uint8_t nuhTempIdPlus1() const { return header()->nuhTempIdPlus1(); } + + binary payload() const { + assert(size() >= H265_NAL_HEADER_SIZE); + return {begin() + H265_NAL_HEADER_SIZE, end()}; + } + + void setForbiddenBit(bool isSet) { header()->setForbiddenBit(isSet); } + void setUnitType(uint8_t type) { header()->setUnitType(type); } + void setNuhLayerId(uint8_t nuhLayerId) { header()->setNuhLayerId(nuhLayerId); } + void setNuhTempIdPlus1(uint8_t nuhTempIdPlus1) { header()->setNuhTempIdPlus1(nuhTempIdPlus1); } + + void setPayload(binary payload) { + assert(size() >= H265_NAL_HEADER_SIZE); + erase(begin() + H265_NAL_HEADER_SIZE, end()); + insert(end(), payload.begin(), payload.end()); + } + + std::vector generateFragments(size_t maxFragmentSize) const; + +protected: + const H265NalUnitHeader *header() const { + assert(size() >= H265_NAL_HEADER_SIZE); + return reinterpret_cast(data()); + } + + H265NalUnitHeader *header() { + assert(size() >= H265_NAL_HEADER_SIZE); + return reinterpret_cast(data()); + } +}; + +/// NAL unit fragment +struct RTC_CPP_EXPORT H265NalUnitFragment : H265NalUnit { + [[deprecated]] static std::vector> fragmentsFrom(shared_ptr nalu, + uint16_t maxFragmentSize); + + enum class FragmentType { Start, Middle, End }; + + H265NalUnitFragment(FragmentType type, bool forbiddenBit, uint8_t nuhLayerId, + uint8_t nuhTempIdPlus1, uint8_t unitType, binary data); + + uint8_t unitType() const { return fragmentHeader()->unitType(); } + + binary payload() const { + assert(size() >= H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE); + return {begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE, end()}; + } + + FragmentType type() const { + if (fragmentHeader()->isStart()) { + return FragmentType::Start; + } else if (fragmentHeader()->isEnd()) { + return FragmentType::End; + } else { + return FragmentType::Middle; + } + } + + void setUnitType(uint8_t type) { fragmentHeader()->setUnitType(type); } + + void setPayload(binary payload) { + assert(size() >= H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE); + erase(begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE, end()); + insert(end(), payload.begin(), payload.end()); + } + + void setFragmentType(FragmentType type); + +protected: + const uint8_t nal_type_fu = 49; + + H265NalUnitHeader *fragmentIndicator() { return reinterpret_cast(data()); } + + const H265NalUnitHeader *fragmentIndicator() const { + return reinterpret_cast(data()); + } + + H265NalUnitFragmentHeader *fragmentHeader() { + return reinterpret_cast(data() + H265_NAL_HEADER_SIZE); + } + + const H265NalUnitFragmentHeader *fragmentHeader() const { + return reinterpret_cast(data() + H265_NAL_HEADER_SIZE); + } +}; + +class [[deprecated]] RTC_CPP_EXPORT H265NalUnits : public std::vector> { +public: + static const uint16_t defaultMaximumFragmentSize = + uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6 + + std::vector> generateFragments(uint16_t maxFragmentSize); +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_NAL_UNIT_H */ diff --git a/libdatachannel/include/rtc/h265rtpdepacketizer.hpp b/libdatachannel/include/rtc/h265rtpdepacketizer.hpp new file mode 100644 index 0000000..0f48f02 --- /dev/null +++ b/libdatachannel/include/rtc/h265rtpdepacketizer.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020-2024 Paul-Louis Ageneau + * Copyright (c) 2024 Robert Edmonds + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H265_RTP_DEPACKETIZER_H +#define RTC_H265_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "h265nalunit.hpp" +#include "message.hpp" +#include "rtp.hpp" +#include "rtpdepacketizer.hpp" + +#include + +namespace rtc { + +/// RTP depacketization for H265 +class RTC_CPP_EXPORT H265RtpDepacketizer final : public VideoRtpDepacketizer { +public: + using Separator = NalUnit::Separator; + + H265RtpDepacketizer(Separator separator = Separator::StartSequence); + ~H265RtpDepacketizer(); + +private: + message_ptr reassemble(message_buffer &buffer); + void addSeparator(binary &frame); + + const NalUnit::Separator mSeparator; +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_H265_RTP_DEPACKETIZER_H diff --git a/libdatachannel/include/rtc/h265rtppacketizer.hpp b/libdatachannel/include/rtc/h265rtppacketizer.hpp new file mode 100644 index 0000000..e788bac --- /dev/null +++ b/libdatachannel/include/rtc/h265rtppacketizer.hpp @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2023 Zita Liao (Dolby) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H265_RTP_PACKETIZER_H +#define RTC_H265_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "h265nalunit.hpp" +#include "rtppacketizer.hpp" + +namespace rtc { + +// RTP packetization for H265 +class RTC_CPP_EXPORT H265RtpPacketizer final : public RtpPacketizer { +public: + using Separator = NalUnit::Separator; + + inline static const uint32_t ClockRate = VideoClockRate; + [[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate; + + // Constructs h265 payload packetizer with given RTP configuration. + // @note RTP configuration is used in packetization process which may change some configuration + // properties such as sequence number. + // @param separator NAL unit separator + // @param rtpConfig RTP configuration + // @param maxFragmentSize maximum size of one NALU fragment + H265RtpPacketizer(Separator separator, shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + + // For backward compatibility, do not use + [[deprecated]] H265RtpPacketizer(shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + +private: + std::vector fragment(binary data) override; + std::vector splitFrame(const binary &frame); + + const NalUnit::Separator mSeparator; + const size_t mMaxFragmentSize; +}; + +// For backward compatibility, do not use +using H265PacketizationHandler [[deprecated("Add H265RtpPacketizer directly")]] = PacketizationHandler; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_H265_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/iceudpmuxlistener.hpp b/libdatachannel/include/rtc/iceudpmuxlistener.hpp new file mode 100644 index 0000000..df72c4d --- /dev/null +++ b/libdatachannel/include/rtc/iceudpmuxlistener.hpp @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2025 Alex Potsides + * Copyright (c) 2025 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_ICE_UDP_MUX_LISTENER_H +#define RTC_ICE_UDP_MUX_LISTENER_H + +#include "common.hpp" + +namespace rtc { + +namespace impl { + +struct IceUdpMuxListener; + +} // namespace impl + +struct IceUdpMuxRequest { // TODO change name + string localUfrag; + string remoteUfrag; + string remoteAddress; + uint16_t remotePort; +}; + +class RTC_CPP_EXPORT IceUdpMuxListener final : private CheshireCat { +public: + IceUdpMuxListener(uint16_t port, optional bindAddress = nullopt); + ~IceUdpMuxListener(); + + void stop(); + + uint16_t port() const; + + void OnUnhandledStunRequest(std::function callback); + +private: + using CheshireCat::impl; +}; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/mediahandler.hpp b/libdatachannel/include/rtc/mediahandler.hpp new file mode 100644 index 0000000..32b9310 --- /dev/null +++ b/libdatachannel/include/rtc/mediahandler.hpp @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_MEDIA_HANDLER_H +#define RTC_MEDIA_HANDLER_H + +#include "common.hpp" +#include "description.hpp" +#include "message.hpp" + +#include + +namespace rtc { + +class RTC_CPP_EXPORT MediaHandler : public std::enable_shared_from_this { +public: + MediaHandler(); + virtual ~MediaHandler(); + + /// Called when a media is added or updated + /// @param desc Description of the media + virtual void media([[maybe_unused]] const Description::Media &desc) {} + + /// Called when there is traffic coming from the peer + /// @param messages Incoming messages from the peer, can be modified by the handler + /// @param send Send callback to send messages back to the peer + virtual void incoming([[maybe_unused]] message_vector &messages, [[maybe_unused]] const message_callback &send) {} + + /// Called when there is traffic that needs to be sent to the peer + /// @param messages Outgoing messages to the peer, can be modified by the handler + /// @param send Send callback to send messages back to the peer + virtual void outgoing([[maybe_unused]] message_vector &messages, [[maybe_unused]] const message_callback &send) {} + + virtual bool requestKeyframe(const std::vector& targetSSRCs, bool retransmit, const message_callback &send); + virtual bool requestBitrate(unsigned int bitrate, const message_callback &send); + + void addToChain(shared_ptr handler); + void setNext(shared_ptr handler); + shared_ptr next(); + shared_ptr next() const; + shared_ptr last(); // never null + shared_ptr last() const; // never null + + void mediaChain(const Description::Media &desc); + void incomingChain(message_vector &messages, const message_callback &send); + void outgoingChain(message_vector &messages, const message_callback &send); + +private: + shared_ptr mNext; +}; + +} // namespace rtc + +#endif // RTC_MEDIA_HANDLER_H diff --git a/libdatachannel/include/rtc/message.hpp b/libdatachannel/include/rtc/message.hpp new file mode 100644 index 0000000..52a7dbe --- /dev/null +++ b/libdatachannel/include/rtc/message.hpp @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2019-2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_MESSAGE_H +#define RTC_MESSAGE_H + +#include "common.hpp" +#include "frameinfo.hpp" +#include "reliability.hpp" + +#include + +namespace rtc { + +struct RTC_CPP_EXPORT Message : binary { + enum Type { Binary, String, Control, Reset }; + + Message(const Message &message) = default; + Message(size_t size, Type type_ = Binary) : binary(size), type(type_) {} + + template + Message(Iterator begin_, Iterator end_, Type type_ = Binary) + : binary(begin_, end_), type(type_) {} + + Message(binary &&data, Type type_ = Binary) : binary(std::move(data)), type(type_) {} + + Type type; + unsigned int stream = 0; // Stream id (SCTP stream or SSRC) + unsigned int dscp = 0; // Differentiated Services Code Point + shared_ptr reliability; + shared_ptr frameInfo; +}; + +using message_ptr = shared_ptr; +using message_callback = std::function; +using message_vector = std::vector; + +inline size_t message_size_func(const message_ptr &m) { + return m->type == Message::Binary || m->type == Message::String ? m->size() : 0; +} + +template +message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary, + unsigned int stream = 0, shared_ptr reliability = nullptr) { + auto message = std::make_shared(begin, end, type); + message->stream = stream; + message->reliability = reliability; + return message; +} + +template +message_ptr make_message(Iterator begin, Iterator end, shared_ptr frameInfo) { + auto message = std::make_shared(begin, end); + message->frameInfo = frameInfo; + return message; +} + +// For backward compatibiity, do not use +template +[[deprecated]] message_ptr make_message(Iterator begin, Iterator end, Message::Type type, + unsigned int stream, shared_ptr frameInfo) { + auto message = std::make_shared(begin, end, type); + message->stream = stream; + message->frameInfo = frameInfo; + return message; +} + +RTC_CPP_EXPORT message_ptr make_message(size_t size, Message::Type type = Message::Binary, + unsigned int stream = 0, + shared_ptr reliability = nullptr); + +RTC_CPP_EXPORT message_ptr make_message(binary &&data, Message::Type type = Message::Binary, + unsigned int stream = 0, + shared_ptr reliability = nullptr); + +RTC_CPP_EXPORT message_ptr make_message(binary &&data, shared_ptr frameInfo); + +RTC_CPP_EXPORT message_ptr make_message(size_t size, message_ptr orig); + +RTC_CPP_EXPORT message_ptr make_message(message_variant data); + +#if RTC_ENABLE_MEDIA + +// Reconstructs a message_ptr from an opaque rtcMessage pointer that +// was allocated by rtcCreateOpaqueMessage(). +message_ptr make_message_from_opaque_ptr(rtcMessage *&&message); + +#endif + +RTC_CPP_EXPORT message_variant to_variant(Message &&message); +RTC_CPP_EXPORT message_variant to_variant(const Message &message); + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/nalunit.hpp b/libdatachannel/include/rtc/nalunit.hpp new file mode 100644 index 0000000..ca8af8c --- /dev/null +++ b/libdatachannel/include/rtc/nalunit.hpp @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_NAL_UNIT_H +#define RTC_NAL_UNIT_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" + +#include +#include + +namespace rtc { + +#pragma pack(push, 1) + +/// Nalu header +struct RTC_CPP_EXPORT NalUnitHeader { + uint8_t _first = 0; + + bool forbiddenBit() const { return _first >> 7; } + uint8_t nri() const { return _first >> 5 & 0x03; } + uint8_t idc() const { return _first & 0x60; } + uint8_t unitType() const { return _first & 0x1F; } + + void setForbiddenBit(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setNRI(uint8_t nri) { _first = (_first & 0x9F) | ((nri & 0x03) << 5); } + void setUnitType(uint8_t type) { _first = (_first & 0xE0) | (type & 0x1F); } +}; + +/// Nalu fragment header +struct RTC_CPP_EXPORT NalUnitFragmentHeader { + uint8_t _first = 0; + + bool isStart() const { return _first >> 7; } + bool reservedBit6() const { return (_first >> 5) & 0x01; } + bool isEnd() const { return (_first >> 6) & 0x01; } + uint8_t unitType() const { return _first & 0x1F; } + + void setStart(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setEnd(bool isSet) { _first = (_first & 0xBF) | (isSet << 6); } + void setReservedBit6(bool isSet) { _first = (_first & 0xDF) | (isSet << 5); } + void setUnitType(uint8_t type) { _first = (_first & 0xE0) | (type & 0x1F); } +}; + +#pragma pack(pop) + +enum NalUnitStartSequenceMatch { + NUSM_noMatch, + NUSM_firstZero, + NUSM_secondZero, + NUSM_thirdZero, + NUSM_shortMatch, + NUSM_longMatch +}; + +static const size_t H264_NAL_HEADER_SIZE = 1; +static const size_t H265_NAL_HEADER_SIZE = 2; + +struct NalUnitFragmentA; + +/// NAL unit +struct RTC_CPP_EXPORT NalUnit : binary { + static std::vector GenerateFragments(const std::vector &nalus, + size_t maxFragmentSize); + + enum class Separator { + Length = RTC_NAL_SEPARATOR_LENGTH, // first 4 bytes are NAL unit length + LongStartSequence = RTC_NAL_SEPARATOR_LONG_START_SEQUENCE, // 0x00, 0x00, 0x00, 0x01 + ShortStartSequence = RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE, // 0x00, 0x00, 0x01 + StartSequence = RTC_NAL_SEPARATOR_START_SEQUENCE, // LongStartSequence or ShortStartSequence + }; + + static NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match, + std::byte _byte, Separator separator); + + enum class Type { H264, H265 }; + + NalUnit(const NalUnit &unit) = default; + NalUnit(size_t size, bool includingHeader = true, Type type = Type::H264) + : binary(size + (includingHeader ? 0 + : (type == Type::H264 ? H264_NAL_HEADER_SIZE + : H265_NAL_HEADER_SIZE))) {} + NalUnit(binary &&data) : binary(std::move(data)) {} + NalUnit(Type type = Type::H264) + : binary(type == Type::H264 ? H264_NAL_HEADER_SIZE : H265_NAL_HEADER_SIZE) {} + template NalUnit(Iterator begin_, Iterator end_) : binary(begin_, end_) {} + + bool forbiddenBit() const { return header()->forbiddenBit(); } + uint8_t nri() const { return header()->nri(); } + uint8_t unitType() const { return header()->unitType(); } + + binary payload() const { + assert(size() >= 1); + return {begin() + 1, end()}; + } + + void setForbiddenBit(bool isSet) { header()->setForbiddenBit(isSet); } + void setNRI(uint8_t nri) { header()->setNRI(nri); } + void setUnitType(uint8_t type) { header()->setUnitType(type); } + + void setPayload(binary payload) { + assert(size() >= 1); + erase(begin() + 1, end()); + insert(end(), payload.begin(), payload.end()); + } + + std::vector generateFragments(size_t maxFragmentSize) const; + +protected: + const NalUnitHeader *header() const { + assert(size() >= 1); + return reinterpret_cast(data()); + } + + NalUnitHeader *header() { + assert(size() >= 1); + return reinterpret_cast(data()); + } +}; + +/// Nal unit fragment A +struct RTC_CPP_EXPORT NalUnitFragmentA : NalUnit { + // For backward compatibility, do not use + [[deprecated]] static std::vector> + fragmentsFrom(shared_ptr nalu, uint16_t maxFragmentSize); + + enum class FragmentType { Start, Middle, End }; + + NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri, uint8_t unitType, + binary data); + + uint8_t unitType() const { return fragmentHeader()->unitType(); } + + binary payload() const { + assert(size() >= 2); + return {begin() + 2, end()}; + } + + FragmentType type() const { + if (fragmentHeader()->isStart()) { + return FragmentType::Start; + } else if (fragmentHeader()->isEnd()) { + return FragmentType::End; + } else { + return FragmentType::Middle; + } + } + + void setUnitType(uint8_t type) { fragmentHeader()->setUnitType(type); } + + void setPayload(binary payload) { + assert(size() >= 2); + erase(begin() + 2, end()); + insert(end(), payload.begin(), payload.end()); + } + + void setFragmentType(FragmentType type); + +protected: + const uint8_t nal_type_fu_A = 28; + + NalUnitHeader *fragmentIndicator() { return reinterpret_cast(data()); } + + const NalUnitHeader *fragmentIndicator() const { + return reinterpret_cast(data()); + } + + NalUnitFragmentHeader *fragmentHeader() { + return reinterpret_cast(fragmentIndicator() + 1); + } + + const NalUnitFragmentHeader *fragmentHeader() const { + return reinterpret_cast(fragmentIndicator() + 1); + } +}; + +// For backward compatibility, do not use +class [[deprecated]] RTC_CPP_EXPORT NalUnits : public std::vector> { +public: + static const uint16_t defaultMaximumFragmentSize = + uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6 + + std::vector> generateFragments(uint16_t maxFragmentSize); +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_NAL_UNIT_H */ diff --git a/libdatachannel/include/rtc/pacinghandler.hpp b/libdatachannel/include/rtc/pacinghandler.hpp new file mode 100644 index 0000000..60313d7 --- /dev/null +++ b/libdatachannel/include/rtc/pacinghandler.hpp @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 Sean DuBois + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_PACING_HANDLER_H +#define RTC_PACING_HANDLER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "utils.hpp" + +#include +#include + +namespace rtc { + +// Paced sending of RTP packets. It takes a stream of RTP packets that can have an uneven bitrate +// and delivers them in a smoother manner by sending a fixed size of them on an interval +class RTC_CPP_EXPORT PacingHandler : public MediaHandler { +public: + PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval, size_t maxQueueSize=0); + + void setBitrate(double bitsPerSecond); + + void setMaxQueueAmount(size_t maxQueueAmount); + + void onOverflow(std::function callback); + + void outgoing(message_vector &messages, const message_callback &send) override; + +private: + std::atomic mHaveScheduled = false; + + double mBytesPerSecond; + double mBudget; + + std::chrono::milliseconds mSendInterval; + std::chrono::time_point mLastRun; + + size_t mMaxQueueAmount; + + std::mutex mParamsMutex; + std::mutex mMutex; + std::queue mRtpBuffer; + + synchronized_callback<> mOverflowCallback; + + void schedule(const message_callback &send, std::chrono::milliseconds scheduleInterval); + void run(const message_callback &send); +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_PACING_HANDLER_H diff --git a/libdatachannel/include/rtc/peerconnection.hpp b/libdatachannel/include/rtc/peerconnection.hpp new file mode 100644 index 0000000..f7a76a1 --- /dev/null +++ b/libdatachannel/include/rtc/peerconnection.hpp @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_PEER_CONNECTION_H +#define RTC_PEER_CONNECTION_H + +#include "candidate.hpp" +#include "common.hpp" +#include "configuration.hpp" +#include "datachannel.hpp" +#include "description.hpp" +#include "reliability.hpp" +#include "track.hpp" + +#include +#include + +namespace rtc { + +namespace impl { + +struct PeerConnection; + +} + +struct RTC_CPP_EXPORT DataChannelInit { + Reliability reliability = {}; + bool negotiated = false; + optional id = nullopt; + string protocol = ""; +}; + +struct RTC_CPP_EXPORT LocalDescriptionInit { + optional iceUfrag; + optional icePwd; +}; + +class RTC_CPP_EXPORT PeerConnection final : CheshireCat { +public: + enum class State : int { + New = RTC_NEW, + Connecting = RTC_CONNECTING, + Connected = RTC_CONNECTED, + Disconnected = RTC_DISCONNECTED, + Failed = RTC_FAILED, + Closed = RTC_CLOSED + }; + + enum class IceState : int { + New = RTC_ICE_NEW, + Checking = RTC_ICE_CHECKING, + Connected = RTC_ICE_CONNECTED, + Completed = RTC_ICE_COMPLETED, + Failed = RTC_ICE_FAILED, + Disconnected = RTC_ICE_DISCONNECTED, + Closed = RTC_ICE_CLOSED + }; + + enum class GatheringState : int { + New = RTC_GATHERING_NEW, + InProgress = RTC_GATHERING_INPROGRESS, + Complete = RTC_GATHERING_COMPLETE + }; + + enum class SignalingState : int { + Stable = RTC_SIGNALING_STABLE, + HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER, + HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER, + HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER, + HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER, + }; + + PeerConnection(); + PeerConnection(Configuration config); + ~PeerConnection(); + + void close(); + + const Configuration *config() const; + State state() const; + IceState iceState() const; + GatheringState gatheringState() const; + SignalingState signalingState() const; + bool negotiationNeeded() const; + bool hasMedia() const; + optional localDescription() const; + optional remoteDescription() const; + size_t remoteMaxMessageSize() const; + optional localAddress() const; + optional remoteAddress() const; + uint16_t maxDataChannelId() const; + bool getSelectedCandidatePair(Candidate *local, Candidate *remote); + + void setLocalDescription(Description::Type type = Description::Type::Unspec, LocalDescriptionInit init = {}); + void gatherLocalCandidates(std::vector additionalIceServers = {}); + void setRemoteDescription(Description description); + void addRemoteCandidate(Candidate candidate); + + // For specific use cases only + Description createOffer(); + Description createAnswer(); + + void setMediaHandler(shared_ptr handler); + shared_ptr getMediaHandler(); + + [[nodiscard]] shared_ptr createDataChannel(string label, + DataChannelInit init = {}); + void onDataChannel(std::function dataChannel)> callback); + + [[nodiscard]] shared_ptr addTrack(Description::Media description); + void onTrack(std::function track)> callback); + + void onLocalDescription(std::function callback); + void onLocalCandidate(std::function callback); + void onStateChange(std::function callback); + void onIceStateChange(std::function callback); + void onGatheringStateChange(std::function callback); + void onSignalingStateChange(std::function callback); + + void resetCallbacks(); + CertificateFingerprint remoteFingerprint(); + + // Stats + void clearStats(); + size_t bytesSent(); + size_t bytesReceived(); + optional rtt(); +}; + +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, PeerConnection::State state); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, PeerConnection::IceState state); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, PeerConnection::GatheringState state); +RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, PeerConnection::SignalingState state); + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/plihandler.hpp b/libdatachannel/include/rtc/plihandler.hpp new file mode 100644 index 0000000..91eeaf9 --- /dev/null +++ b/libdatachannel/include/rtc/plihandler.hpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2023 Arda Cinar + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_PLI_RESPONDER_H +#define RTC_PLI_RESPONDER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "utils.hpp" + +#include + +namespace rtc { + +/// Responds to PLI and FIR messages sent by the receiver. The sender should respond to these +/// messages by sending an intra. +class RTC_CPP_EXPORT PliHandler final : public MediaHandler { + rtc::synchronized_callback<> mOnPli; + +public: + /// Constructs the PLIResponder object to notify whenever a new intra frame is requested + /// @param onPli The callback that gets called whenever an intra frame is requested by the receiver + PliHandler(std::function onPli); + + void incoming(message_vector &messages, const message_callback &send) override; + +protected: + std::unordered_map mFirSSRCSeqNumberMap; +}; + +} + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_PLI_RESPONDER_H diff --git a/libdatachannel/include/rtc/reliability.hpp b/libdatachannel/include/rtc/reliability.hpp new file mode 100644 index 0000000..df63b93 --- /dev/null +++ b/libdatachannel/include/rtc/reliability.hpp @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RELIABILITY_H +#define RTC_RELIABILITY_H + +#include "common.hpp" + +#include + +namespace rtc { + +struct Reliability { + // It true, the channel does not enforce message ordering and out-of-order delivery is allowed + bool unordered = false; + + // If both maxPacketLifeTime or maxRetransmits are unset, the channel is reliable. + // If either maxPacketLifeTime or maxRetransmits is set, the channel is unreliable. + // (The settings are exclusive so both maxPacketLifetime and maxRetransmits must not be set.) + + // Time window during which transmissions and retransmissions may occur + optional maxPacketLifeTime; + + // Maximum number of retransmissions that are attempted + optional maxRetransmits; + + // For backward compatibility, do not use + enum class Type { Reliable = 0, Rexmit, Timed }; + union { + Type typeDeprecated = Type::Reliable; + [[deprecated("Use maxPacketLifeTime or maxRetransmits")]] Type type; + }; + variant rexmit = 0; +}; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/rembhandler.hpp b/libdatachannel/include/rtc/rembhandler.hpp new file mode 100644 index 0000000..6f98878 --- /dev/null +++ b/libdatachannel/include/rtc/rembhandler.hpp @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024 Vladimir Voronin + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_REMB_RESPONDER_H +#define RTC_REMB_RESPONDER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "utils.hpp" + +namespace rtc { + +/// Responds to REMB messages sent by the receiver. +class RTC_CPP_EXPORT RembHandler final : public MediaHandler { + rtc::synchronized_callback mOnRemb; + +public: + /// Constructs the RembResponder object to notify whenever a bitrate + /// @param onRemb The callback that gets called whenever a bitrate by the receiver + RembHandler(std::function onRemb); + + void incoming(message_vector &messages, const message_callback &send) override; +}; + +} + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_REMB_RESPONDER_H diff --git a/libdatachannel/include/rtc/rtc.h b/libdatachannel/include/rtc/rtc.h new file mode 100644 index 0000000..dddcaa5 --- /dev/null +++ b/libdatachannel/include/rtc/rtc.h @@ -0,0 +1,581 @@ +/** + * Copyright (c) 2019-2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_C_API +#define RTC_C_API + +#include "version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef RTC_STATIC +#define RTC_C_EXPORT +#else // dynamic library +#ifdef _WIN32 +#ifdef RTC_EXPORTS +#define RTC_C_EXPORT __declspec(dllexport) // building the library +#else +#define RTC_C_EXPORT __declspec(dllimport) // using the library +#endif +#else // not WIN32 +#define RTC_C_EXPORT +#endif +#endif + +#ifndef RTC_ENABLE_WEBSOCKET +#define RTC_ENABLE_WEBSOCKET 1 +#endif + +#ifndef RTC_ENABLE_MEDIA +#define RTC_ENABLE_MEDIA 1 +#endif + +#define RTC_DEFAULT_MTU 1280 // IPv6 minimum guaranteed MTU + +#if RTC_ENABLE_MEDIA +#define RTC_DEFAULT_MAX_FRAGMENT_SIZE ((uint16_t)(RTC_DEFAULT_MTU - 12 - 8 - 40)) // SRTP/UDP/IPv6 +#define RTC_DEFAULT_MAX_STORED_PACKET_COUNT 512 +// Deprecated, do not use +#define RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE RTC_DEFAULT_MAX_FRAGMENT_SIZE +#define RTC_DEFAULT_MAXIMUM_PACKET_COUNT_FOR_NACK_CACHE RTC_DEFAULT_MAX_STORED_PACKET_COUNT +#endif + +#ifdef _WIN32 +#ifdef CAPI_STDCALL +#define RTC_API __stdcall +#else +#define RTC_API +#endif +#else // not WIN32 +#define RTC_API +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define RTC_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define RTC_DEPRECATED __declspec(deprecated) +#else +#define DEPRECATED +#endif + +// libdatachannel C API + +typedef enum { + RTC_NEW = 0, + RTC_CONNECTING = 1, + RTC_CONNECTED = 2, + RTC_DISCONNECTED = 3, + RTC_FAILED = 4, + RTC_CLOSED = 5 +} rtcState; + +typedef enum { + RTC_ICE_NEW = 0, + RTC_ICE_CHECKING = 1, + RTC_ICE_CONNECTED = 2, + RTC_ICE_COMPLETED = 3, + RTC_ICE_FAILED = 4, + RTC_ICE_DISCONNECTED = 5, + RTC_ICE_CLOSED = 6 +} rtcIceState; + +typedef enum { + RTC_GATHERING_NEW = 0, + RTC_GATHERING_INPROGRESS = 1, + RTC_GATHERING_COMPLETE = 2 +} rtcGatheringState; + +typedef enum { + RTC_SIGNALING_STABLE = 0, + RTC_SIGNALING_HAVE_LOCAL_OFFER = 1, + RTC_SIGNALING_HAVE_REMOTE_OFFER = 2, + RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3, + RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4, +} rtcSignalingState; + +typedef enum { // Don't change, it must match plog severity + RTC_LOG_NONE = 0, + RTC_LOG_FATAL = 1, + RTC_LOG_ERROR = 2, + RTC_LOG_WARNING = 3, + RTC_LOG_INFO = 4, + RTC_LOG_DEBUG = 5, + RTC_LOG_VERBOSE = 6 +} rtcLogLevel; + +typedef enum { + RTC_CERTIFICATE_DEFAULT = 0, // ECDSA + RTC_CERTIFICATE_ECDSA = 1, + RTC_CERTIFICATE_RSA = 2, +} rtcCertificateType; + +typedef enum { + // video + RTC_CODEC_H264 = 0, + RTC_CODEC_VP8 = 1, + RTC_CODEC_VP9 = 2, + RTC_CODEC_H265 = 3, + RTC_CODEC_AV1 = 4, + + // audio + RTC_CODEC_OPUS = 128, + RTC_CODEC_PCMU = 129, + RTC_CODEC_PCMA = 130, + RTC_CODEC_AAC = 131, + RTC_CODEC_G722 = 132, +} rtcCodec; + +typedef enum { + RTC_DIRECTION_UNKNOWN = 0, + RTC_DIRECTION_SENDONLY = 1, + RTC_DIRECTION_RECVONLY = 2, + RTC_DIRECTION_SENDRECV = 3, + RTC_DIRECTION_INACTIVE = 4 +} rtcDirection; + +typedef enum { RTC_TRANSPORT_POLICY_ALL = 0, RTC_TRANSPORT_POLICY_RELAY = 1 } rtcTransportPolicy; + +#define RTC_ERR_SUCCESS 0 +#define RTC_ERR_INVALID -1 // invalid argument +#define RTC_ERR_FAILURE -2 // runtime error +#define RTC_ERR_NOT_AVAIL -3 // element not available +#define RTC_ERR_TOO_SMALL -4 // buffer too small + +typedef void(RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message); +typedef void(RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type, + void *ptr); +typedef void(RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid, + void *ptr); +typedef void(RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr); +typedef void(RTC_API *rtcIceStateChangeCallbackFunc)(int pc, rtcIceState state, void *ptr); +typedef void(RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr); +typedef void(RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr); +typedef void(RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr); +typedef void(RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr); +typedef void(RTC_API *rtcOpenCallbackFunc)(int id, void *ptr); +typedef void(RTC_API *rtcClosedCallbackFunc)(int id, void *ptr); +typedef void(RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr); +typedef void(RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr); +typedef void *(RTC_API *rtcInterceptorCallbackFunc)(int pc, const char *message, int size, + void *ptr); +typedef void(RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr); +typedef void(RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr); +typedef void(RTC_API *rtcPliHandlerCallbackFunc)(int tr, void *ptr); +typedef void(RTC_API *rtcRembHandlerCallbackFunc)(int tr, unsigned int bitrate, void *ptr); + +typedef struct { + uint32_t timestamp; + uint8_t payloadType; + double timestampSeconds; // negative means not available +} rtcFrameInfo; + +typedef void(RTC_API *rtcFrameCallbackFunc)(int tr, const char *data, int size, + const rtcFrameInfo *info, void *ptr); + +// Log + +// NULL cb on the first call will log to stdout +RTC_C_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); + +// User pointer +RTC_C_EXPORT void rtcSetUserPointer(int id, void *ptr); +RTC_C_EXPORT void *rtcGetUserPointer(int i); + +// PeerConnection + +typedef struct { + const char **iceServers; + int iceServersCount; + const char *proxyServer; // libnice only + const char *bindAddress; // libjuice only, NULL means any + rtcCertificateType certificateType; + const char *certificatePemFile; // NULL for autogenerated certificate + const char *keyPemFile; // NULL for autogenerated certificate + const char *keyPemPass; // NULL if no pass + rtcTransportPolicy iceTransportPolicy; + bool enableIceTcp; + bool enableIceUdpMux; // libjuice only + bool disableAutoNegotiation; + bool forceMediaTransport; + uint16_t portRangeBegin; // 0 means automatic + uint16_t portRangeEnd; // 0 means automatic + int mtu; // <= 0 means automatic + int maxMessageSize; // <= 0 means default +} rtcConfiguration; + +RTC_C_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id +RTC_C_EXPORT int rtcClosePeerConnection(int pc); +RTC_C_EXPORT int rtcDeletePeerConnection(int pc); + +RTC_C_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb); +RTC_C_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb); +RTC_C_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb); +RTC_C_EXPORT int rtcSetIceStateChangeCallback(int pc, rtcIceStateChangeCallbackFunc cb); +RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb); +RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb); + +RTC_C_EXPORT int rtcSetLocalDescription(int pc, const char *type); // type may be NULL +RTC_C_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type); +RTC_C_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid); + +RTC_C_EXPORT int rtcGetLocalDescription(int pc, char *buffer, int size); +RTC_C_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size); + +RTC_C_EXPORT int rtcGetLocalDescriptionType(int pc, char *buffer, int size); +RTC_C_EXPORT int rtcGetRemoteDescriptionType(int pc, char *buffer, int size); + +// For specific use cases only +RTC_C_EXPORT int rtcCreateOffer(int pc, char *buffer, int size); +RTC_C_EXPORT int rtcCreateAnswer(int pc, char *buffer, int size); + +RTC_C_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size); +RTC_C_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size); + +RTC_C_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, + int remoteSize); + +RTC_C_EXPORT bool rtcIsNegotiationNeeded(int pc); + +RTC_C_EXPORT int rtcGetMaxDataChannelStream(int pc); +RTC_C_EXPORT int rtcGetRemoteMaxMessageSize(int pc); + +// DataChannel, Track, and WebSocket common API + +RTC_C_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb); +RTC_C_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb); +RTC_C_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb); +RTC_C_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb); +RTC_C_EXPORT int rtcSendMessage(int id, const char *data, int size); +RTC_C_EXPORT int rtcClose(int id); +RTC_C_EXPORT int rtcDelete(int id); +RTC_C_EXPORT bool rtcIsOpen(int id); +RTC_C_EXPORT bool rtcIsClosed(int id); + +RTC_C_EXPORT int rtcMaxMessageSize(int id); +RTC_C_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send +RTC_C_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount); +RTC_C_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb); + +// DataChannel, Track, and WebSocket common extended API + +RTC_C_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive +RTC_C_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb); +RTC_C_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size); + +// DataChannel + +typedef struct { + bool unordered; + bool unreliable; + unsigned int maxPacketLifeTime; // ignored if reliable + unsigned int maxRetransmits; // ignored if reliable +} rtcReliability; + +typedef struct { + rtcReliability reliability; + const char *protocol; // empty string if NULL + bool negotiated; + bool manualStream; + uint16_t stream; // numeric ID 0-65534, ignored if manualStream is false +} rtcDataChannelInit; + +RTC_C_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb); +RTC_C_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id +RTC_C_EXPORT int rtcCreateDataChannelEx(int pc, const char *label, + const rtcDataChannelInit *init); // returns dc id +RTC_C_EXPORT int rtcDeleteDataChannel(int dc); + +RTC_C_EXPORT int rtcGetDataChannelStream(int dc); +RTC_C_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size); +RTC_C_EXPORT int rtcGetDataChannelProtocol(int dc, char *buffer, int size); +RTC_C_EXPORT int rtcGetDataChannelReliability(int dc, rtcReliability *reliability); + +// Track + +typedef struct { + rtcDirection direction; + rtcCodec codec; + int payloadType; + uint32_t ssrc; + const char *mid; + const char *name; // optional + const char *msid; // optional + const char *trackId; // optional, track ID used in MSID + const char *profile; // optional, codec profile +} rtcTrackInit; + +RTC_C_EXPORT int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb); +RTC_C_EXPORT int rtcAddTrack(int pc, const char *mediaDescriptionSdp); // returns tr id +RTC_C_EXPORT int rtcAddTrackEx(int pc, const rtcTrackInit *init); // returns tr id +RTC_C_EXPORT int rtcDeleteTrack(int tr); + +RTC_C_EXPORT int rtcGetTrackDescription(int tr, char *buffer, int size); +RTC_C_EXPORT int rtcGetTrackMid(int tr, char *buffer, int size); +RTC_C_EXPORT int rtcGetTrackDirection(int tr, rtcDirection *direction); + +RTC_C_EXPORT int rtcRequestKeyframe(int tr); +RTC_C_EXPORT int rtcRequestBitrate(int tr, unsigned int bitrate); + +RTC_C_EXPORT int rtcSetFrameCallback(int tr, rtcFrameCallbackFunc cb); + +#if RTC_ENABLE_MEDIA + +// Media + +// Define how OBUs are packetizied in a AV1 Sample +typedef enum { + RTC_OBU_PACKETIZED_OBU = 0, + RTC_OBU_PACKETIZED_TEMPORAL_UNIT = 1, +} rtcObuPacketization; + +// Define how NAL units are separated in a H264/H265 sample +typedef enum { + RTC_NAL_SEPARATOR_LENGTH = 0, // first 4 bytes are NAL unit length + RTC_NAL_SEPARATOR_LONG_START_SEQUENCE = 1, // 0x00, 0x00, 0x00, 0x01 + RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE = 2, // 0x00, 0x00, 0x01 + RTC_NAL_SEPARATOR_START_SEQUENCE = 3, // long or short start sequence +} rtcNalUnitSeparator; + +typedef struct { + uint32_t ssrc; + const char *cname; + uint8_t payloadType; + uint32_t clockRate; + uint16_t sequenceNumber; + uint32_t timestamp; + + // H264, H265, AV1 + uint16_t maxFragmentSize; // Maximum fragment size, 0 means default + + // H264/H265 only + rtcNalUnitSeparator nalSeparator; // NAL unit separator + + // AV1 only + rtcObuPacketization obuPacketization; // OBU paketization for AV1 samples + + uint8_t playoutDelayId; + uint16_t playoutDelayMin; + uint16_t playoutDelayMax; + + uint8_t colorSpaceId; + uint8_t colorChromaSitingHorz; + uint8_t colorChromaSitingVert; + uint8_t colorRange; + uint8_t colorPrimaries; + uint8_t colorTransfer; + uint8_t colorMatrix; +} rtcPacketizerInit; + +// Deprecated, do not use +typedef rtcPacketizerInit rtcPacketizationHandlerInit; + +typedef struct { + uint32_t ssrc; + const char *name; // optional + const char *msid; // optional + const char *trackId; // optional, track ID used in MSID +} rtcSsrcForTypeInit; + +// Opaque type used (via rtcMessage*) to reference an rtc::Message +typedef void *rtcMessage; + +// Allocate a new opaque message. +// Must be explicitly freed by rtcDeleteOpaqueMessage() unless +// explicitly returned by a media interceptor callback; +RTC_C_EXPORT rtcMessage *rtcCreateOpaqueMessage(void *data, int size); +RTC_C_EXPORT void rtcDeleteOpaqueMessage(rtcMessage *msg); + +// Set MediaInterceptor on peer connection +RTC_C_EXPORT int rtcSetMediaInterceptorCallback(int id, rtcInterceptorCallbackFunc cb); + +// Set a packetizer on track +RTC_C_EXPORT int rtcSetH264Packetizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetH265Packetizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetAV1Packetizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetVP8Packetizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetVP9Packetizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetOpusPacketizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetAACPacketizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetPCMUPacketizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetPCMAPacketizer(int tr, const rtcPacketizerInit *init); +RTC_C_EXPORT int rtcSetG722Packetizer(int tr, const rtcPacketizerInit *init); + +// Set a depacketizer on track +RTC_C_EXPORT int rtcSetH264Depacketizer(int tr, rtcNalUnitSeparator nalSeparator); +RTC_C_EXPORT int rtcSetH265Depacketizer(int tr, rtcNalUnitSeparator nalSeparator); +RTC_C_EXPORT int rtcSetVP8Depacketizer(int tr); +RTC_C_EXPORT int rtcSetVP9Depacketizer(int tr); +RTC_C_EXPORT int rtcSetOpusDepacketizer(int tr); +RTC_C_EXPORT int rtcSetAACDepacketizer(int tr); +RTC_C_EXPORT int rtcSetPCMUDepacketizer(int tr); +RTC_C_EXPORT int rtcSetPCMADepacketizer(int tr); +RTC_C_EXPORT int rtcSetG722Depacketizer(int tr); + +// Deprecated, do not use +RTC_DEPRECATED static inline int +rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { + return rtcSetH264Packetizer(tr, init); +} +RTC_DEPRECATED static inline int +rtcSetH265PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { + return rtcSetH265Packetizer(tr, init); +} +RTC_DEPRECATED static inline int +rtcSetAV1PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { + return rtcSetAV1Packetizer(tr, init); +} +RTC_DEPRECATED static inline int +rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { + return rtcSetOpusPacketizer(tr, init); +} +RTC_DEPRECATED static inline int +rtcSetAACPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { + return rtcSetAACPacketizer(tr, init); +} + +// Chain RtcpReceivingSession on track +RTC_C_EXPORT int rtcChainRtcpReceivingSession(int tr); + +// Chain RtcpSrReporter on track +RTC_C_EXPORT int rtcChainRtcpSrReporter(int tr); + +// Chain RtcpNackResponder on track +RTC_C_EXPORT int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount); + +// Chain PliHandler on track +RTC_C_EXPORT int rtcChainPliHandler(int tr, rtcPliHandlerCallbackFunc cb); + +// Chain RembHandler on track +RTC_C_EXPORT int rtcChainRembHandler(int tr, rtcRembHandlerCallbackFunc cb); + +// Chain PacingHandler on track +RTC_C_EXPORT int rtcChainPacingHandler(int tr, double bitsPerSecond, int sendIntervalMs); + +// Transform seconds to timestamp using track's clock rate, result is written to timestamp +RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp); + +// Transform timestamp to seconds using track's clock rate, result is written to seconds +RTC_C_EXPORT int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds); + +// Get current timestamp, result is written to timestamp +RTC_C_EXPORT int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp); + +// Set RTP timestamp for track identified by given id +RTC_C_EXPORT int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp); + +// Get timestamp of last RTCP SR, result is written to timestamp +RTC_C_EXPORT int rtcGetLastTrackSenderReportTimestamp(int id, uint32_t *timestamp); + +// Get sync timestamps from receiving RTCP session +RTC_C_EXPORT int rtcGetTrackRtcpSyncTimestamps(int tr, uint64_t *rtpTimestamp, uint64_t *ntpTimestamp); + +// Get all available payload types for given codec and stores them in buffer, does nothing if +// buffer is NULL +RTC_C_EXPORT int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size); + +// Get all SSRCs for given track +RTC_C_EXPORT int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count); + +// Get CName for SSRC +RTC_C_EXPORT int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize); + +// Get all SSRCs for given media type in given SDP +RTC_C_EXPORT int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize); + +// Set SSRC for given media type in given SDP +RTC_C_EXPORT int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize, + rtcSsrcForTypeInit *init); + +// For backward compatibility, do not use +RTC_C_EXPORT RTC_DEPRECATED int rtcSetNeedsToSendRtcpSr(int id); + +#endif // RTC_ENABLE_MEDIA + +#if RTC_ENABLE_WEBSOCKET + +// WebSocket + +typedef struct { + bool disableTlsVerification; // if true, don't verify the TLS certificate + const char *proxyServer; // only non-authenticated http supported for now + const char **protocols; + int protocolsCount; + int tcpConnectionTimeoutMs; // in milliseconds, 0 means default, < 0 means disabled + int connectionTimeoutMs; // in milliseconds, 0 means default, < 0 means disabled + int pingIntervalMs; // in milliseconds, 0 means default, < 0 means disabled + int maxOutstandingPings; // 0 means default, < 0 means disabled + int maxMessageSize; // <= 0 means default +} rtcWsConfiguration; + +RTC_C_EXPORT int rtcCreateWebSocket(const char *url); // returns ws id +RTC_C_EXPORT int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config); +RTC_C_EXPORT int rtcDeleteWebSocket(int ws); + +RTC_C_EXPORT int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size); +RTC_C_EXPORT int rtcGetWebSocketPath(int ws, char *buffer, int size); + +// WebSocketServer + +typedef void(RTC_API *rtcWebSocketClientCallbackFunc)(int wsserver, int ws, void *ptr); + +typedef struct { + uint16_t port; // 0 means automatic selection + bool enableTls; // if true, enable TLS (WSS) + const char *certificatePemFile; // NULL for autogenerated certificate + const char *keyPemFile; // NULL for autogenerated certificate + const char *keyPemPass; // NULL if no pass + const char *bindAddress; // NULL for any + int connectionTimeoutMs; // in milliseconds, 0 means default, < 0 means disabled + int maxMessageSize; // <= 0 means default +} rtcWsServerConfiguration; + +RTC_C_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config, + rtcWebSocketClientCallbackFunc cb); // returns wsserver id +RTC_C_EXPORT int rtcDeleteWebSocketServer(int wsserver); + +RTC_C_EXPORT int rtcGetWebSocketServerPort(int wsserver); + +#endif + +// Global settings + +// Note: Applied when threads are spawned +RTC_C_EXPORT int rtcSetThreadPoolSize(unsigned int count); + +typedef struct { + int recvBufferSize; // in bytes, <= 0 means optimized default + int sendBufferSize; // in bytes, <= 0 means optimized default + int maxChunksOnQueue; // in chunks, <= 0 means optimized default + int initialCongestionWindow; // in MTUs, <= 0 means optimized default + int maxBurst; // in MTUs, 0 means optimized default, < 0 means disabled + int congestionControlModule; // 0: RFC2581 (default), 1: HSTCP, 2: H-TCP, 3: RTCC + int delayedSackTimeMs; // in milliseconds, 0 means optimized default, < 0 means disabled + int minRetransmitTimeoutMs; // in milliseconds, <= 0 means optimized default + int maxRetransmitTimeoutMs; // in milliseconds, <= 0 means optimized default + int initialRetransmitTimeoutMs; // in milliseconds, <= 0 means optimized default + int maxRetransmitAttempts; // number of retransmissions, <= 0 means optimized default + int heartbeatIntervalMs; // in milliseconds, <= 0 means optimized default +} rtcSctpSettings; + +// Note: SCTP settings apply to newly-created PeerConnections only +RTC_C_EXPORT int rtcSetSctpSettings(const rtcSctpSettings *settings); + +// Optional global preload and cleanup +RTC_C_EXPORT bool rtcPreload(void); +RTC_C_EXPORT void rtcCleanup(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/libdatachannel/include/rtc/rtc.hpp b/libdatachannel/include/rtc/rtc.hpp new file mode 100644 index 0000000..2d2a106 --- /dev/null +++ b/libdatachannel/include/rtc/rtc.hpp @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2019 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// C API +#include "rtc.h" + +// C++ API +#include "common.hpp" +#include "global.hpp" +// +#include "datachannel.hpp" +#include "iceudpmuxlistener.hpp" +#include "peerconnection.hpp" +#include "track.hpp" + +#if RTC_ENABLE_WEBSOCKET + +// WebSocket +#include "websocket.hpp" +#include "websocketserver.hpp" + +#endif // RTC_ENABLE_WEBSOCKET + +#if RTC_ENABLE_MEDIA + +// Media +#include "av1rtppacketizer.hpp" +#include "dependencydescriptor.hpp" +#include "rtppacketizer.hpp" +#include "rtpdepacketizer.hpp" +#include "h264rtppacketizer.hpp" +#include "h264rtpdepacketizer.hpp" +#include "h265rtppacketizer.hpp" +#include "h265rtpdepacketizer.hpp" +#include "vp8rtppacketizer.hpp" +#include "vp8rtpdepacketizer.hpp" +#include "vp9rtppacketizer.hpp" +#include "vp9rtpdepacketizer.hpp" +#include "mediahandler.hpp" +#include "rtcpapphandler.hpp" +#include "plihandler.hpp" +#include "rembhandler.hpp" +#include "pacinghandler.hpp" +#include "rtcpnackresponder.hpp" +#include "rtcpreceivingsession.hpp" +#include "rtcpsrreporter.hpp" + +#endif // RTC_ENABLE_MEDIA diff --git a/libdatachannel/include/rtc/rtcpapphandler.hpp b/libdatachannel/include/rtc/rtcpapphandler.hpp new file mode 100644 index 0000000..30dbef6 --- /dev/null +++ b/libdatachannel/include/rtc/rtcpapphandler.hpp @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2026 Apple Inc + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTCP_APP_HANDLER_H +#define RTC_RTCP_APP_HANDLER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "rtp.hpp" +#include "utils.hpp" + +namespace rtc { + +/// Handles RTCP APP packets (payload type 204) as defined in RFC 3550 section 6.7. +class RTC_CPP_EXPORT RtcpAppHandler final : public MediaHandler { + rtc::synchronized_callback mOnApp; + +public: + /// Constructs the RtcpAppHandler to notify when RTCP APP packets are received. + /// @param onApp Callback invoked with (name, subtype, application-dependent data) + RtcpAppHandler(std::function onApp); + + void incoming(message_vector &messages, const message_callback &send) override; +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_RTCP_APP_HANDLER_H diff --git a/libdatachannel/include/rtc/rtcpnackresponder.hpp b/libdatachannel/include/rtc/rtcpnackresponder.hpp new file mode 100644 index 0000000..045d956 --- /dev/null +++ b/libdatachannel/include/rtc/rtcpnackresponder.hpp @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTCP_NACK_RESPONDER_H +#define RTC_RTCP_NACK_RESPONDER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "description.hpp" +#include "rtp.hpp" + +#include +#include +#include + +namespace rtc { + +class RTC_CPP_EXPORT RtcpNackResponder final : public MediaHandler { +public: + static const size_t DefaultMaxSize = 512; + + RtcpNackResponder(size_t maxSize = DefaultMaxSize); + + void media(const Description::Media &desc) override; + void incoming(message_vector &messages, const message_callback &send) override; + void outgoing(message_vector &messages, const message_callback &send) override; + +private: + message_ptr wrapInRtx(const message_ptr &original); + + // RTX state populated by media() from SDP inspection + optional mRtxSsrc; + std::unordered_map mRtxPayloadTypeMap; // original PT -> RTX PT + uint16_t mRtxSequenceNumber = 0; + bool mRtxEnabled = false; + std::mutex mMutex; + + // Packet storage + class RTC_CPP_EXPORT Storage { + + /// Packet storage element + struct RTC_CPP_EXPORT Element { + Element(message_ptr packet, uint16_t sequenceNumber, shared_ptr next = nullptr); + const message_ptr packet; + const uint16_t sequenceNumber; + /// Pointer to newer element + shared_ptr next = nullptr; + }; + + private: + /// Oldest packet in storage + shared_ptr oldest = nullptr; + /// Newest packet in storage + shared_ptr newest = nullptr; + /// Inner storage + std::unordered_map> storage{}; + std::mutex mutex; + + /// Maximum storage size + const size_t maxSize; + + /// Returns current size + size_t size(); + + public: + Storage(size_t _maxSize); + + /// Returns packet with given sequence number + message_ptr get(uint16_t sequenceNumber); + + /// Stores packet + /// @param packet Packet + void store(message_ptr packet); + }; + + const shared_ptr mStorage; +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_RTCP_NACK_RESPONDER_H */ diff --git a/libdatachannel/include/rtc/rtcpreceivingsession.hpp b/libdatachannel/include/rtc/rtcpreceivingsession.hpp new file mode 100644 index 0000000..e1e25f2 --- /dev/null +++ b/libdatachannel/include/rtc/rtcpreceivingsession.hpp @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTCP_RECEIVING_SESSION_H +#define RTC_RTCP_RECEIVING_SESSION_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "description.hpp" +#include "mediahandler.hpp" +#include "message.hpp" +#include "rtp.hpp" + +#include +#include +#include +#include + +#define RTP_SEQ_MOD (1<<16) + +namespace rtc { + +// An RtcpSession can be plugged into a Track to handle the whole RTCP session +class RTC_CPP_EXPORT RtcpReceivingSession : public MediaHandler { +public: + RtcpReceivingSession() = default; + virtual ~RtcpReceivingSession() = default; + + void media(const Description::Media &desc) override; + void incoming(message_vector &messages, const message_callback &send) override; + bool requestKeyframe(const std::vector& targetSSRCs, bool retransmit, const message_callback &send) override; + bool requestBitrate(unsigned int bitrate, const message_callback &send) override; + + // For backward compatibility + [[deprecated("Use Track::requestKeyframe()")]] inline bool requestKeyframe() { return false; }; + [[deprecated("Use Track::requestBitrate()")]] inline void requestBitrate(unsigned int) {}; + + struct SyncTimestamps { + uint64_t rtpTimestamp; + uint64_t ntpTimestamp; + }; + + SyncTimestamps getSyncTimestamps(); + +protected: + void pushREMB(const message_callback &send, unsigned int bitrate); + void pushRR(const message_callback &send,unsigned int lastSrDelay); + void pushPLI(const message_callback &send); + void pushFIR(const message_callback &send, const std::vector& targetSSRCs, bool retransmit); + + void initSeq(uint16_t seq); + bool updateSeq(uint16_t seq); + + SSRC mSsrc = 0; + uint32_t mGreatestSeqNo = 0; + uint16_t mMaxSeq = 0; // highest seq. number seen + uint32_t mCycles = 0; // shifted count of seq. number cycles + uint32_t mBaseSeq = 0; // base seq number + uint32_t mBadSeq = 0; // last 'bad' seq number + 1 + uint32_t mProbation = 0; // sequ. packets till source is valid + uint32_t mReceived = 0; // packets received + uint32_t mExpectedPrior = 0; // packet expected at last interval + uint32_t mReceivedPrior = 0; // packet received at last interval + uint32_t mTransit = 0; // relative trans time for prev pkt + uint32_t mJitter = 0; + + SyncTimestamps mSyncTimestamps{0,0}; + + std::atomic mRequestedBitrate = 0; + std::mutex mSyncMutex; + std::mutex mMutex; + + message_ptr unwrapRtx(const message_ptr &rtxPacket); + + // RTX state populated by media() from SDP inspection + std::unordered_map mRtxToPrimaryPtMap; // RTX PT -> primary PT + SSRC mRtxPrimarySsrc = 0; + bool mRtxEnabled = false; + + bool mSupportsRfc5104Fir = false; + std::unordered_map mRfc5104FirCmdNums; // SSRC -> seq no mapping +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif // RTC_RTCP_RECEIVING_SESSION_H diff --git a/libdatachannel/include/rtc/rtcpsrreporter.hpp b/libdatachannel/include/rtc/rtcpsrreporter.hpp new file mode 100644 index 0000000..70f05c2 --- /dev/null +++ b/libdatachannel/include/rtc/rtcpsrreporter.hpp @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTCP_SR_REPORTER_H +#define RTC_RTCP_SR_REPORTER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "rtp.hpp" +#include "rtppacketizationconfig.hpp" + +#include + +namespace rtc { + +class RTC_CPP_EXPORT RtcpSrReporter final : public MediaHandler { +public: + RtcpSrReporter(shared_ptr rtpConfig); + ~RtcpSrReporter(); + + uint32_t lastReportedTimestamp() const; + [[deprecated]] void setNeedsToReport(); + + void outgoing(message_vector &messages, const message_callback &send) override; + + // TODO: remove this + const shared_ptr rtpConfig; + +private: + void addToReport(RtpHeader *header, size_t size); + message_ptr getSenderReport(uint32_t timestamp); + + uint32_t mPacketCount = 0; + uint32_t mPayloadOctets = 0; + uint32_t mLastReportedTimestamp = 0; + std::chrono::steady_clock::time_point mLastReportTime; +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_RTCP_SR_REPORTER_H */ diff --git a/libdatachannel/include/rtc/rtp.hpp b/libdatachannel/include/rtc/rtp.hpp new file mode 100644 index 0000000..9e4c3ea --- /dev/null +++ b/libdatachannel/include/rtc/rtp.hpp @@ -0,0 +1,407 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020 Paul-Louis Ageneau + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTP_HPP +#define RTC_RTP_HPP + +#include "common.hpp" + +#include +#include + +namespace rtc { + +typedef uint32_t SSRC; + +RTC_CPP_EXPORT bool IsRtcp(const binary &data); + +#pragma pack(push, 1) + +struct RTC_CPP_EXPORT RtpExtensionHeader { + uint16_t _profileSpecificId; + uint16_t _headerLength; + + [[nodiscard]] uint16_t profileSpecificId() const; + [[nodiscard]] uint16_t headerLength() const; + + [[nodiscard]] size_t getSize() const; + [[nodiscard]] const char *getBody() const; + [[nodiscard]] char *getBody(); + + void setProfileSpecificId(uint16_t profileSpecificId); + void setHeaderLength(uint16_t headerLength); + + void clearBody(); + size_t writeCurrentVideoOrientation(bool twoByteHeader, size_t offset, uint8_t id, + uint8_t value); + size_t writeOneByteHeader(size_t offset, uint8_t id, const byte *value, size_t size); + size_t writeTwoByteHeader(size_t offset, uint8_t id, const byte *value, size_t size); + size_t writeHeader(bool twoByteHeader, size_t offset, uint8_t id, const byte *value, + size_t size); +}; + +struct RTC_CPP_EXPORT RtpHeader { + uint8_t _first; + uint8_t _payloadType; + uint16_t _seqNumber; + uint32_t _timestamp; + SSRC _ssrc; + // The following field is SSRC _csrc[] + + [[nodiscard]] uint8_t version() const; + [[nodiscard]] bool padding() const; + [[nodiscard]] bool extension() const; + [[nodiscard]] uint8_t csrcCount() const; + [[nodiscard]] uint8_t marker() const; + [[nodiscard]] uint8_t payloadType() const; + [[nodiscard]] uint16_t seqNumber() const; + [[nodiscard]] uint32_t timestamp() const; + [[nodiscard]] uint32_t ssrc() const; + + [[nodiscard]] size_t getSize() const; + [[nodiscard]] size_t getExtensionHeaderSize() const; + [[nodiscard]] const RtpExtensionHeader *getExtensionHeader() const; + [[nodiscard]] RtpExtensionHeader *getExtensionHeader(); + [[nodiscard]] const char *getBody() const; + [[nodiscard]] char *getBody(); + + void log() const; + + void preparePacket(); + void setSeqNumber(uint16_t newSeqNo); + void setPayloadType(uint8_t newPayloadType); + void setSsrc(uint32_t in_ssrc); + void setMarker(bool marker); + void setTimestamp(uint32_t i); + void setExtension(bool extension); +}; + +struct RTC_CPP_EXPORT RtcpReportBlock { + SSRC _ssrc; + uint32_t _fractionLostAndPacketsLost; // fraction lost is 8-bit, packets lost is 24-bit + uint16_t _seqNoCycles; + uint16_t _highestSeqNo; + uint32_t _jitter; + uint32_t _lastReport; + uint32_t _delaySinceLastReport; + + [[nodiscard]] uint16_t seqNoCycles() const; + [[nodiscard]] uint16_t highestSeqNo() const; + [[nodiscard]] uint32_t extendedHighestSeqNo() const; + [[nodiscard]] uint32_t jitter() const; + [[nodiscard]] uint32_t delaySinceSR() const; + + [[nodiscard]] SSRC getSSRC() const; + [[nodiscard]] uint32_t getNTPOfSR() const; + [[nodiscard]] uint8_t getFractionLost() const; + [[nodiscard]] unsigned int getPacketsLostCount() const; + + void preparePacket(SSRC in_ssrc, uint8_t fraction, unsigned int totalPacketsLost, + uint16_t highestSeqNo, uint16_t seqNoCycles, uint32_t jitter, + uint64_t lastSR_NTP, uint64_t lastSR_DELAY); + void setSSRC(SSRC in_ssrc); + void setPacketsLost(uint8_t fractionLost, unsigned int packetsLostCount); + void setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles); + void setJitter(uint32_t jitter); + void setNTPOfSR(uint64_t ntp); + void setDelaySinceSR(uint32_t sr); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpHeader { + uint8_t _first; + uint8_t _payloadType; + uint16_t _length; + + [[nodiscard]] uint8_t version() const; + [[nodiscard]] bool padding() const; + [[nodiscard]] uint8_t reportCount() const; + [[nodiscard]] uint8_t payloadType() const; + [[nodiscard]] uint16_t length() const; + [[nodiscard]] size_t lengthInBytes() const; + + void prepareHeader(uint8_t payloadType, uint8_t reportCount, uint16_t length); + void setPayloadType(uint8_t type); + void setReportCount(uint8_t count); + void setLength(uint16_t length); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpFbHeader { + RtcpHeader header; + + SSRC _packetSender; + SSRC _mediaSource; + + [[nodiscard]] SSRC packetSenderSSRC() const; + [[nodiscard]] SSRC mediaSourceSSRC() const; + + void setPacketSenderSSRC(SSRC ssrc); + void setMediaSourceSSRC(SSRC ssrc); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpSr { + RtcpHeader header; + + SSRC _senderSSRC; + uint64_t _ntpTimestamp; + uint32_t _rtpTimestamp; + uint32_t _packetCount; + uint32_t _octetCount; + + RtcpReportBlock _reportBlocks; + + [[nodiscard]] static unsigned int Size(unsigned int reportCount); + + [[nodiscard]] uint64_t ntpTimestamp() const; + [[nodiscard]] uint32_t rtpTimestamp() const; + [[nodiscard]] uint32_t packetCount() const; + [[nodiscard]] uint32_t octetCount() const; + [[nodiscard]] uint32_t senderSSRC() const; + + [[nodiscard]] const RtcpReportBlock *getReportBlock(int num) const; // nullptr if out-of-bounds + [[nodiscard]] RtcpReportBlock *getReportBlock(int num); // nullptr if out-of-bounds + [[nodiscard]] unsigned int size(unsigned int reportCount); + [[nodiscard]] size_t getSize() const; + + void preparePacket(SSRC senderSSRC, uint8_t reportCount); + void setNtpTimestamp(uint64_t ts); + void setRtpTimestamp(uint32_t ts); + void setOctetCount(uint32_t ts); + void setPacketCount(uint32_t ts); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpSdesItem { + uint8_t type; + + uint8_t _length; + char _text[1]; + + [[nodiscard]] static unsigned int Size(uint8_t textLength); + + [[nodiscard]] string text() const; + [[nodiscard]] uint8_t length() const; + + void setText(string text); +}; + +struct RTC_CPP_EXPORT RtcpSdesChunk { + SSRC _ssrc; + RtcpSdesItem _items; + + [[nodiscard]] static unsigned int Size(const std::vector textLengths); + + [[nodiscard]] SSRC ssrc() const; + + void setSSRC(SSRC ssrc); + + // Get item at given index + // All items with index < num must be valid, otherwise this function has undefined behaviour + // (use safelyCountChunkSize() to check if chunk is valid). + [[nodiscard]] const RtcpSdesItem *getItem(int num) const; + [[nodiscard]] RtcpSdesItem *getItem(int num); + + // Get size of chunk + // All items must be valid, otherwise this function has undefined behaviour (use + // safelyCountChunkSize() to check if chunk is valid) + [[nodiscard]] unsigned int getSize() const; + + long safelyCountChunkSize(size_t maxChunkSize) const; +}; + +struct RTC_CPP_EXPORT RtcpSdes { + RtcpHeader header; + RtcpSdesChunk _chunks; + + [[nodiscard]] static unsigned int Size(const std::vector> lengths); + + bool isValid() const; + + // Returns number of chunks in this packet + // Returns 0 if packet is invalid + unsigned int chunksCount() const; + + // Get chunk at given index + // All chunks (and their items) with index < `num` must be valid, otherwise this function has + // undefined behaviour (use `isValid` to check if chunk is valid). + const RtcpSdesChunk *getChunk(int num) const; + RtcpSdesChunk *getChunk(int num); + + void preparePacket(uint8_t chunkCount); +}; + +struct RTC_CPP_EXPORT RtcpRr { + RtcpHeader header; + + SSRC _senderSSRC; + RtcpReportBlock _reportBlocks; + + [[nodiscard]] static size_t SizeWithReportBlocks(uint8_t reportCount); + + SSRC senderSSRC() const; + bool isSenderReport(); + bool isReceiverReport(); + + [[nodiscard]] RtcpReportBlock *getReportBlock(int num); // nullptr if out-of-bounds + [[nodiscard]] const RtcpReportBlock *getReportBlock(int num) const; // nullptr if out-of-bounds + [[nodiscard]] size_t getSize() const; + + void preparePacket(SSRC senderSSRC, uint8_t reportCount); + void setSenderSSRC(SSRC ssrc); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpRemb { + RtcpFbHeader header; + + char _id[4]; // Unique identifier ('R' 'E' 'M' 'B') + uint32_t _bitrate; // Num SSRC, Br Exp, Br Mantissa (bit mask) + SSRC _ssrcs[1]; + + [[nodiscard]] static size_t SizeWithSSRCs(int count); + + [[nodiscard]] unsigned int getSize() const; + + void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int in_bitrate); + void setBitrate(unsigned int numSSRC, unsigned int in_bitrate); + SSRC getSSRC(int num) const; + void setSSRC(int num, SSRC newSsrc); + bool hasValidId() const; + int getSSRCCount() const; + unsigned int getBitrate() const; + + // Deprecated + [[deprecated("use setSSRC")]] void setSsrc(int num, SSRC newSssrc); + [[deprecated("use getSSRCCount")]] unsigned int getNumSSRC() const; + [[deprecated("use getSSRCCount")]] unsigned int getNumSSRC(); + unsigned int getBitrate(); +}; + +struct RTC_CPP_EXPORT RtcpPli { + RtcpFbHeader header; + + [[nodiscard]] static unsigned int Size(); + + void preparePacket(SSRC messageSSRC); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpFirFci { + uint32_t _ssrc; + uint8_t _seqNo; + uint8_t _dummy1; + uint16_t _dummy2; + + void preparePacket(SSRC ssrc, uint8_t seqNo); + + [[nodiscard]] SSRC getSSRC() const; + [[nodiscard]] uint8_t getSeqNo() const; +}; + +struct RTC_CPP_EXPORT RtcpFir { + RtcpFbHeader header; + RtcpFirFci _fcis[1]; + + [[nodiscard]] static size_t SizeWithFcis(int count); + + int getFciCount() const; + [[nodiscard]] const RtcpFirFci *getFci(int num) const; // nullptr if out-of-bounds + void preparePacket(SSRC senderSSRC, const std::vector &fcis); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtcpNackPart { + uint16_t _pid; + uint16_t _blp; + + uint16_t pid(); + uint16_t blp(); + + void setPid(uint16_t pid); + void setBlp(uint16_t blp); + + std::vector getSequenceNumbers(); +}; + +struct RTC_CPP_EXPORT RtcpNack { + RtcpFbHeader header; + RtcpNackPart parts[1]; + + [[nodiscard]] static unsigned int Size(unsigned int discreteSeqNoCount); + + [[nodiscard]] unsigned int getSeqNoCount(); + + void preparePacket(SSRC ssrc, unsigned int discreteSeqNoCount); + + /** + * Add a packet to the list of missing packets. + * @param fciCount The number of FCI fields that are present in this packet. + * Let the number start at zero and let this function grow the number. + * @param fciPID The seq no of the active FCI. It will be initialized automatically, and will + * change automatically. + * @param missingPacket The seq no of the missing packet. This will be added to the queue. + * @return true if the packet has grown, false otherwise. + */ + bool addMissingPacket(unsigned int *fciCount, uint16_t *fciPID, uint16_t missingPacket); +}; + +typedef std::array RtcpAppName; + +struct RTC_CPP_EXPORT RtcpApp { + RtcpHeader header; + + SSRC _ssrc; + char _name[4]; + char _data[1]; + + [[nodiscard]] static size_t SizeWithData(size_t dataLength); + + [[nodiscard]] SSRC ssrc() const; + [[nodiscard]] uint8_t subtype() const; + [[nodiscard]] RtcpAppName name() const; + [[nodiscard]] const char *data() const; + [[nodiscard]] size_t dataSize() const; + + void preparePacket(SSRC ssrc, const RtcpAppName &name, uint8_t subtype, size_t dataLength); + void setSSRC(SSRC ssrc); + void setName(const RtcpAppName &name); + + void log() const; +}; + +struct RTC_CPP_EXPORT RtpRtx { + RtpHeader header; + + [[nodiscard]] const char *getBody() const; + [[nodiscard]] char *getBody(); + [[nodiscard]] size_t getBodySize(size_t totalSize) const; + [[nodiscard]] size_t getSize() const; + [[nodiscard]] uint16_t getOriginalSeqNo() const; + + // Returns the new size of the packet + size_t normalizePacket(size_t totalSize, SSRC originalSSRC, uint8_t originalPayloadType); + + size_t copyTo(RtpHeader *dest, size_t totalSize, uint8_t originalPayloadType); +}; + +#pragma pack(pop) + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/rtpdepacketizer.hpp b/libdatachannel/include/rtc/rtpdepacketizer.hpp new file mode 100644 index 0000000..0d56819 --- /dev/null +++ b/libdatachannel/include/rtc/rtpdepacketizer.hpp @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTP_DEPACKETIZER_H +#define RTC_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "message.hpp" + +#include + +namespace rtc { + +// Base RTP depacketizer class +class RTC_CPP_EXPORT RtpDepacketizer : public MediaHandler { +public: + RtpDepacketizer(); + RtpDepacketizer(uint32_t clockRate); + virtual ~RtpDepacketizer(); + + virtual void incoming(message_vector &messages, const message_callback &send) override; + +protected: + shared_ptr createFrameInfo(uint32_t timestamp, uint8_t payloadType) const; + +private: + const uint32_t mClockRate; +}; + +// Base class for video RTP depacketizer +class RTC_CPP_EXPORT VideoRtpDepacketizer : public RtpDepacketizer { +public: + inline static const uint32_t ClockRate = 90000; + + VideoRtpDepacketizer(); + virtual ~VideoRtpDepacketizer(); + +protected: + struct sequence_cmp { + bool operator()(message_ptr a, message_ptr b) const; + }; + using message_buffer = std::set; + + virtual message_ptr reassemble(message_buffer &messages) = 0; + +private: + void incoming(message_vector &messages, const message_callback &send) override; + + message_buffer mBuffer; +}; + +// Generic audio RTP depacketizer +template +class RTC_CPP_EXPORT AudioRtpDepacketizer final : public RtpDepacketizer { +public: + inline static const uint32_t DefaultClockRate = DEFAULT_CLOCK_RATE; + + AudioRtpDepacketizer(uint32_t clockRate = DefaultClockRate) : RtpDepacketizer(clockRate) {} +}; + +// Audio RTP depacketizers +using OpusRtpDepacketizer = AudioRtpDepacketizer<48000>; +using AACRtpDepacketizer = AudioRtpDepacketizer<48000>; +using PCMARtpDepacketizer = AudioRtpDepacketizer<8000>; +using PCMURtpDepacketizer = AudioRtpDepacketizer<8000>; +using G722RtpDepacketizer = AudioRtpDepacketizer<8000>; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_RTP_DEPACKETIZER_H */ diff --git a/libdatachannel/include/rtc/rtppacketizationconfig.hpp b/libdatachannel/include/rtc/rtppacketizationconfig.hpp new file mode 100644 index 0000000..238b919 --- /dev/null +++ b/libdatachannel/include/rtc/rtppacketizationconfig.hpp @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTP_PACKETIZATION_CONFIG_H +#define RTC_RTP_PACKETIZATION_CONFIG_H + +#if RTC_ENABLE_MEDIA + +#include "dependencydescriptor.hpp" +#include "rtp.hpp" + +namespace rtc { + +struct RTC_CPP_EXPORT VideoLayersAllocation; + +// RTP configuration used in packetization process +class RTC_CPP_EXPORT RtpPacketizationConfig { +public: + SSRC ssrc; + std::string cname; + uint8_t payloadType; + uint32_t clockRate; + uint8_t videoOrientationId; + + // current sequence number + uint16_t sequenceNumber; + + // current timestamp + uint32_t timestamp; + + // start timestamp + uint32_t startTimestamp; + + /// Current video orientation + /// + /// Bit# 7 6 5 4 3 2 1 0 + /// Definition 0 0 0 0 C F R1 R0 + /// + /// C + /// 0 - Front-facing camera (use this if unsure) + /// 1 - Back-facing camera + /// + /// F + /// 0 - No Flip + /// 1 - Horizontal flip + /// + /// R1 R0 - CW rotation that receiver must apply + /// 0 - 0 degrees + /// 1 - 90 degrees + /// 2 - 180 degrees + /// 3 - 270 degrees + uint8_t videoOrientation = 0; + + // MID Extension Header + uint8_t midId = 0; + optional mid; + + // RID Extension Header + uint8_t ridId = 0; + optional rid; + + // Dependency Descriptor Extension Header + uint8_t dependencyDescriptorId = 0; + + optional dependencyDescriptorContext; + // the negotiated ID of the playout delay header extension + // https://webrtc.googlesource.com/src/+/main/docs/native-code/rtp-hdrext/playout-delay/README.md + uint8_t playoutDelayId = 0; + + // Minimum/maxiumum playout delay, in 10ms intervals. A value of 10 would equal a 100ms delay + uint16_t playoutDelayMin = 0; + uint16_t playoutDelayMax = 0; + + // Google Video Layers Allocation for simulcast + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00 + // + // The negotiated extension id + uint8_t videoLayersAllocationId = 0; + // Stream index, unique per RID/SSRC + uint8_t videoLayersAllocationStreamIndex = 0; + // Shared data about layers + std::shared_ptr videoLayersAllocationStreams; + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/color-space/ + uint8_t colorSpaceId = 0; // the negotiated ID of color space header extension + uint8_t colorChromaSitingHorz = 0; // unspecified + uint8_t colorChromaSitingVert = 0; // unspecified + uint8_t colorRange = 2; // full range + uint8_t colorPrimaries = 1; // BT.709-6 + uint8_t colorTransfer = 1; // BT.709-6 + uint8_t colorMatrix = 1; // BT.709-6 + + // abs-capture-time RTP header extension. When absCaptureTimeId > 0 and an + // outgoing FrameInfo carries an absCaptureTimeNtp value, the RtpPacketizer + // writes the 8-byte (shortened) form of the abs-capture-time extension into + // every packet for that frame. The receiver can recover the source's + // capture wallclock and compute true glass-to-glass latency. + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time + uint8_t absCaptureTimeId = 0; + + /// Construct RTP configuration used in packetization process + /// @param ssrc SSRC of source + /// @param cname CNAME of source + /// @param payloadType Payload type of source + /// @param clockRate Clock rate of source used in timestamps + /// nullopt) + /// @param videoOrientationId Video orientation (see above) + RtpPacketizationConfig(SSRC ssrc, std::string cname, uint8_t payloadType, uint32_t clockRate, + uint8_t videoOrientationId = 0); + + RtpPacketizationConfig(const RtpPacketizationConfig &) = delete; + + /// Convert timestamp to seconds + /// @param timestamp Timestamp + /// @param clockRate Clock rate for timestamp calculation + static double getSecondsFromTimestamp(uint32_t timestamp, uint32_t clockRate); + + /// Convert timestamp to seconds + /// @param timestamp Timestamp + double timestampToSeconds(uint32_t timestamp); + + /// Convert seconds to timestamp + /// @param seconds Number of seconds + /// @param clockRate Clock rate for timestamp calculation + static uint32_t getTimestampFromSeconds(double seconds, uint32_t clockRate); + + /// Convert seconds to timestamp + /// @param seconds Number of seconds + uint32_t secondsToTimestamp(double seconds); +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_RTP_PACKETIZATION_CONFIG_H */ diff --git a/libdatachannel/include/rtc/rtppacketizer.hpp b/libdatachannel/include/rtc/rtppacketizer.hpp new file mode 100644 index 0000000..df3fcc1 --- /dev/null +++ b/libdatachannel/include/rtc/rtppacketizer.hpp @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_RTP_PACKETIZER_H +#define RTC_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "mediahandler.hpp" +#include "message.hpp" +#include "rtppacketizationconfig.hpp" + +namespace rtc { + +/// RTP packetizer +class RTC_CPP_EXPORT RtpPacketizer : public MediaHandler { +public: + /// Default maximum fragment size (for video packetizers) + inline static const size_t DefaultMaxFragmentSize = RTC_DEFAULT_MAX_FRAGMENT_SIZE; + + /// Clock rate for video in RTP + inline static const uint32_t VideoClockRate = 90 * 1000; + + /// Constructs packetizer with given RTP configuration + /// @note RTP configuration is used in packetization process which may change some configuration + /// properties such as sequence number. + /// @param rtpConfig RTP configuration + RtpPacketizer(shared_ptr rtpConfig); + virtual ~RtpPacketizer(); + + virtual void media(const Description::Media &desc) override; + virtual void outgoing(message_vector &messages, const message_callback &send) override; + + /// RTP packetization config + const shared_ptr rtpConfig; + +protected: + /// Fragment data into payloads + /// Default implementation returns data as a single payload + /// @param data Input data + virtual std::vector fragment(binary data); + + /// Creates an RTP packet for a payload + /// @note This function increases the sequence number. + /// @param payload RTP payload + /// @param mark Set marker flag in RTP packet if true + virtual message_ptr packetize(const binary &payload, bool mark, shared_ptr frameInfo = nullptr); + +private: + static const auto RtpHeaderSize = 12; + static const auto RtpExtHeaderCvoSize = 8; + + uint32_t videoLayersAllocationInitialPacketCount = 0; + + bool shouldEmitVideoLayersAllocation(shared_ptr frameInfo); +}; + +// Generic audio RTP packetizer +template +class RTC_CPP_EXPORT AudioRtpPacketizer final : public RtpPacketizer { +public: + inline static const uint32_t DefaultClockRate = DEFAULT_CLOCK_RATE; + inline static const uint32_t defaultClockRate [[deprecated("Use DefaultClockRate")]] = + DEFAULT_CLOCK_RATE; // for backward compatibility + + AudioRtpPacketizer(shared_ptr rtpConfig) + : RtpPacketizer(std::move(rtpConfig)) {} +}; + +// Audio RTP packetizers +using OpusRtpPacketizer = AudioRtpPacketizer<48000>; +using AACRtpPacketizer = AudioRtpPacketizer<48000>; +using PCMARtpPacketizer = AudioRtpPacketizer<8000>; +using PCMURtpPacketizer = AudioRtpPacketizer<8000>; +using G722RtpPacketizer = AudioRtpPacketizer<8000>; + +// Dummy wrapper for backward compatibility, do not use +class RTC_CPP_EXPORT PacketizationHandler final : public MediaHandler { +public: + PacketizationHandler(shared_ptr packetizer) + : mPacketizer(std::move(packetizer)) {} + + inline void outgoing(message_vector &messages, const message_callback &send) { + return mPacketizer->outgoing(messages, send); + } + +private: + shared_ptr mPacketizer; +}; + +// Audio packetization handlers for backward compatibility, do not use +using OpusPacketizationHandler [[deprecated("Add OpusRtpPacketizer directly")]] = + PacketizationHandler; +using AACPacketizationHandler [[deprecated("Add AACRtpPacketizer directly")]] = + PacketizationHandler; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/track.hpp b/libdatachannel/include/rtc/track.hpp new file mode 100644 index 0000000..f06d78f --- /dev/null +++ b/libdatachannel/include/rtc/track.hpp @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_TRACK_H +#define RTC_TRACK_H + +#include "channel.hpp" +#include "common.hpp" +#include "description.hpp" +#include "mediahandler.hpp" + +#include + +namespace rtc { + +namespace impl { + +class Track; + +} // namespace impl + +class RTC_CPP_EXPORT Track final : private CheshireCat, public Channel { +public: + Track(impl_ptr impl); + ~Track() override; + + string mid() const; + Description::Direction direction() const; + Description::Media description() const; + + void setDescription(Description::Media description); + + void close(void) override; + bool send(message_variant data) override; + bool send(const byte *data, size_t size) override; + + bool isOpen(void) const override; + bool isClosed(void) const override; + size_t maxMessageSize() const override; + + void sendFrame(binary data, FrameInfo info); + void sendFrame(const byte *data, size_t size, FrameInfo info); + void onFrame(std::function callback); + + bool requestKeyframe(SSRC ssrc=0, bool retransmit=false); + bool requestKeyframe(const std::vector& targetSSRCs, bool retransmit=false); + bool requestBitrate(unsigned int bitrate); + bool sendRtcpApp(uint32_t ssrc, const RtcpAppName &name, uint8_t subtype, + const binary &data = binary{}); + + void setMediaHandler(shared_ptr handler); + void chainMediaHandler(shared_ptr handler); + shared_ptr getMediaHandler(); + + // Deprecated, use setMediaHandler() and getMediaHandler() + inline void setRtcpHandler(shared_ptr handler) { setMediaHandler(handler); } + inline shared_ptr getRtcpHandler() { return getMediaHandler(); } + +private: + using CheshireCat::impl; +}; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/utils.hpp b/libdatachannel/include/rtc/utils.hpp new file mode 100644 index 0000000..9ec20b1 --- /dev/null +++ b/libdatachannel/include/rtc/utils.hpp @@ -0,0 +1,174 @@ +/** + * Copyright (c) 2019-2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_UTILS_H +#define RTC_UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rtc { + +// overloaded helper +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +// weak_ptr bind helper +template auto weak_bind(F &&f, T *t, Args &&..._args) { + return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&...args) { + if (auto shared_this = weak_this.lock()) + return bound(args...); + else + return static_cast(false); + }; +} + +// scope_guard helper +class scope_guard final { +public: + scope_guard(std::function func) : function(std::move(func)) {} + scope_guard(scope_guard &&other) = delete; + scope_guard(const scope_guard &) = delete; + void operator=(const scope_guard &) = delete; + + ~scope_guard() { + if (function) + function(); + } + +private: + std::function function; +}; + +// callback with built-in synchronization +template class synchronized_callback { +public: + synchronized_callback() = default; + synchronized_callback(synchronized_callback &&cb) { *this = std::move(cb); } + synchronized_callback(const synchronized_callback &cb) { *this = cb; } + synchronized_callback(std::function func) { *this = std::move(func); } + virtual ~synchronized_callback() { *this = nullptr; } + + synchronized_callback &operator=(synchronized_callback &&cb) { + std::scoped_lock lock(mutex, cb.mutex); + set(std::exchange(cb.callback, nullptr)); + return *this; + } + + synchronized_callback &operator=(const synchronized_callback &cb) { + std::scoped_lock lock(mutex, cb.mutex); + set(cb.callback); + return *this; + } + + synchronized_callback &operator=(std::function func) { + std::lock_guard lock(mutex); + set(std::move(func)); + return *this; + } + + bool operator()(Args... args) const { + std::lock_guard lock(mutex); + return call(std::move(args)...); + } + + operator bool() const { + std::lock_guard lock(mutex); + return callback ? true : false; + } + +protected: + virtual void set(std::function func) { callback = std::move(func); } + virtual bool call(Args... args) const { + if (!callback) + return false; + + callback(std::move(args)...); + return true; + } + + std::function callback; + mutable std::recursive_mutex mutex; +}; + +// callback with built-in synchronization and replay of the last missed call +template +class synchronized_stored_callback final : public synchronized_callback { +public: + template + synchronized_stored_callback(CArgs &&...cargs) + : synchronized_callback(std::forward(cargs)...) {} + ~synchronized_stored_callback() {} + +private: + void set(std::function func) { + synchronized_callback::set(func); + if (func && stored) { + std::apply(func, std::move(*stored)); + stored.reset(); + } + } + + bool call(Args... args) const { + if (!synchronized_callback::call(args...)) + stored.emplace(std::move(args)...); + + return true; + } + + mutable std::optional> stored; +}; + +// pimpl base class +template using impl_ptr = std::shared_ptr; +template class CheshireCat { +public: + CheshireCat(impl_ptr impl) : mImpl(std::move(impl)) {} + template + CheshireCat(Args... args) : mImpl(std::make_shared(std::forward(args)...)) {} + CheshireCat(CheshireCat &&cc) { *this = std::move(cc); } + CheshireCat(const CheshireCat &) = delete; + + virtual ~CheshireCat() = default; + + CheshireCat &operator=(CheshireCat &&cc) { + mImpl = std::move(cc.mImpl); + return *this; + }; + CheshireCat &operator=(const CheshireCat &) = delete; + +protected: + impl_ptr impl() { return mImpl; } + impl_ptr impl() const { return mImpl; } + +private: + impl_ptr mImpl; +}; + +// case-insensitive string comparator using std::tolower +class case_insensitive_less : public std::less { +public: + bool operator()(const std::string &lhs, const std::string &rhs) const { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), + [](auto a, auto b) { + return std::tolower(static_cast(a)) < + std::tolower(static_cast(b)); + }); + } +}; + +} // namespace rtc + +#endif diff --git a/libdatachannel/include/rtc/version.h b/libdatachannel/include/rtc/version.h new file mode 100644 index 0000000..1c339bf --- /dev/null +++ b/libdatachannel/include/rtc/version.h @@ -0,0 +1,9 @@ +#ifndef RTC_VERSION_H +#define RTC_VERSION_H + +#define RTC_VERSION_MAJOR 0 +#define RTC_VERSION_MINOR 24 +#define RTC_VERSION_PATCH 4 +#define RTC_VERSION "0.24.4" + +#endif diff --git a/libdatachannel/include/rtc/video_layers_allocation.hpp b/libdatachannel/include/rtc/video_layers_allocation.hpp new file mode 100644 index 0000000..31590bc --- /dev/null +++ b/libdatachannel/include/rtc/video_layers_allocation.hpp @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_VIDEO_LAYERS_ALLOCATION_H +#define RTC_VIDEO_LAYERS_ALLOCATION_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" + +#include +#include +#include + +namespace rtc { + +// Google Video Layers Allocation for simulcast +// +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00 + +struct RTC_CPP_EXPORT VideoLayersAllocation { + struct SpatialLayer { + uint16_t width = 0; + uint16_t height = 0; + uint8_t fps = 0; + std::vector targetBitratesKbps; // per temporal layer, cumulative, cannot be empty + }; + + struct RtpStream { + std::vector spatialLayers; + }; + + std::vector rtpStreams; // up to 4 streams + + /// Generate the wire format for Google Video Layers Allocation RTP header extension + /// @param streamIndex The RTP stream index (0-3) for this packet's stream + /// @return Binary payload for the RTP header extension, empty if allocation is invalid + binary generate(uint8_t streamIndex) const; +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_VIDEO_LAYERS_ALLOCATION_H */ diff --git a/libdatachannel/include/rtc/vp8rtpdepacketizer.hpp b/libdatachannel/include/rtc/vp8rtpdepacketizer.hpp new file mode 100644 index 0000000..beeea4c --- /dev/null +++ b/libdatachannel/include/rtc/vp8rtpdepacketizer.hpp @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_VP8_RTP_DEPACKETIZER_H +#define RTC_VP8_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "message.hpp" +#include "rtpdepacketizer.hpp" + +namespace rtc { + +/// RTP depacketization for VP8 +class RTC_CPP_EXPORT VP8RtpDepacketizer final : public VideoRtpDepacketizer { +public: + VP8RtpDepacketizer(); + ~VP8RtpDepacketizer(); + +private: + message_ptr reassemble(message_buffer &buffer) override; +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif /* RTC_VP8_RTP_DEPACKETIZER_H */ diff --git a/libdatachannel/include/rtc/vp8rtppacketizer.hpp b/libdatachannel/include/rtc/vp8rtppacketizer.hpp new file mode 100644 index 0000000..a8f121a --- /dev/null +++ b/libdatachannel/include/rtc/vp8rtppacketizer.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2026 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_VP8_RTP_PACKETIZER_H +#define RTC_VP8_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "rtppacketizer.hpp" + +namespace rtc { + +/// RTP packetization for VP8 +class RTC_CPP_EXPORT VP8RtpPacketizer final : public RtpPacketizer { +public: + inline static const uint32_t ClockRate = VideoClockRate; + [[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate; + + /// Constructs VP8 payload packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration + /// properties such as sequence number. + /// @param rtpConfig RTP configuration + /// @param maxFragmentSize maximum size of one packet payload + VP8RtpPacketizer(shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + +private: + std::vector fragment(binary frame) override; + + const size_t mMaxFragmentSize; +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_VP8_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/vp9rtpdepacketizer.hpp b/libdatachannel/include/rtc/vp9rtpdepacketizer.hpp new file mode 100644 index 0000000..853936a --- /dev/null +++ b/libdatachannel/include/rtc/vp9rtpdepacketizer.hpp @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_VP9_RTP_DEPACKETIZER_H +#define RTC_VP9_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "message.hpp" +#include "rtpdepacketizer.hpp" + +namespace rtc { + +/// RTP depacketization for VP9 +class RTC_CPP_EXPORT VP9RtpDepacketizer final : public VideoRtpDepacketizer { +public: + VP9RtpDepacketizer(); + ~VP9RtpDepacketizer(); + +private: + message_ptr reassemble(message_buffer &buffer) override; +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif /* RTC_VP9_RTP_DEPACKETIZER_H */ diff --git a/libdatachannel/include/rtc/vp9rtppacketizer.hpp b/libdatachannel/include/rtc/vp9rtppacketizer.hpp new file mode 100644 index 0000000..85637f2 --- /dev/null +++ b/libdatachannel/include/rtc/vp9rtppacketizer.hpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_VP9_RTP_PACKETIZER_H +#define RTC_VP9_RTP_PACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "rtppacketizer.hpp" + +namespace rtc { + +/// RTP packetization for VP9 +class RTC_CPP_EXPORT VP9RtpPacketizer final : public RtpPacketizer { +public: + inline static const uint32_t ClockRate = VideoClockRate; + + /// Constructs VP9 payload packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration + /// properties such as sequence number. + /// @param rtpConfig RTP configuration + /// @param maxFragmentSize maximum size of one packet payload + VP9RtpPacketizer(shared_ptr rtpConfig, + size_t maxFragmentSize = DefaultMaxFragmentSize); + +private: + std::vector fragment(binary frame) override; + + const size_t mMaxFragmentSize; +}; + +} // namespace rtc + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTC_VP9_RTP_PACKETIZER_H */ diff --git a/libdatachannel/include/rtc/websocket.hpp b/libdatachannel/include/rtc/websocket.hpp new file mode 100644 index 0000000..cbb72e6 --- /dev/null +++ b/libdatachannel/include/rtc/websocket.hpp @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2020-2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_WEBSOCKET_H +#define RTC_WEBSOCKET_H + +#if RTC_ENABLE_WEBSOCKET + +#include "channel.hpp" +#include "common.hpp" +#include "configuration.hpp" + +#include + +namespace rtc { + +namespace impl { + +struct WebSocket; + +} // namespace impl + +class RTC_CPP_EXPORT WebSocket final : private CheshireCat, public Channel { +public: + enum class State : int { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3, + }; + + using Configuration = WebSocketConfiguration; + + using Headers = std::map; + + WebSocket(); + WebSocket(Configuration config); + WebSocket(impl_ptr impl); + ~WebSocket() override; + + State readyState() const; + + bool isOpen() const override; + bool isClosed() const override; + size_t maxMessageSize() const override; + + void open(const string &url, const Headers &headers = {}); + void close() override; + void forceClose(); + bool send(const message_variant data) override; + bool send(const byte *data, size_t size) override; + + optional remoteAddress() const; + optional path() const; + std::multimap requestHeaders() const; + +private: + using CheshireCat::impl; +}; + +std::ostream &operator<<(std::ostream &out, WebSocket::State state); + +} // namespace rtc + +#endif + +#endif // RTC_WEBSOCKET_H diff --git a/libdatachannel/include/rtc/websocketserver.hpp b/libdatachannel/include/rtc/websocketserver.hpp new file mode 100644 index 0000000..9bb1c0d --- /dev/null +++ b/libdatachannel/include/rtc/websocketserver.hpp @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2021 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_WEBSOCKETSERVER_H +#define RTC_WEBSOCKETSERVER_H + +#if RTC_ENABLE_WEBSOCKET + +#include "common.hpp" +#include "configuration.hpp" +#include "websocket.hpp" + +namespace rtc { + +namespace impl { + +struct WebSocketServer; + +} + +class RTC_CPP_EXPORT WebSocketServer final : private CheshireCat { +public: + using Configuration = WebSocketServerConfiguration; + + WebSocketServer(); + WebSocketServer(Configuration config); + ~WebSocketServer(); + + void stop(); + + uint16_t port() const; + + void onClient(std::function)> callback); + +private: + using CheshireCat::impl; +}; + +} // namespace rtc + +#endif + +#endif // RTC_WEBSOCKET_H diff --git a/libdatachannel/lib/MbedTLS.lib b/libdatachannel/lib/MbedTLS.lib new file mode 100644 index 0000000..ec317b4 Binary files /dev/null and b/libdatachannel/lib/MbedTLS.lib differ diff --git a/libdatachannel/lib/datachannel-static.lib b/libdatachannel/lib/datachannel-static.lib new file mode 100644 index 0000000..d294505 Binary files /dev/null and b/libdatachannel/lib/datachannel-static.lib differ diff --git a/libdatachannel/lib/juice-static.lib b/libdatachannel/lib/juice-static.lib new file mode 100644 index 0000000..5aaba49 Binary files /dev/null and b/libdatachannel/lib/juice-static.lib differ diff --git a/libdatachannel/lib/libMbedTLS.a b/libdatachannel/lib/libMbedTLS.a new file mode 100644 index 0000000..06a4998 Binary files /dev/null and b/libdatachannel/lib/libMbedTLS.a differ diff --git a/libdatachannel/lib/libdatachannel-static.a b/libdatachannel/lib/libdatachannel-static.a new file mode 100644 index 0000000..e1e14ad Binary files /dev/null and b/libdatachannel/lib/libdatachannel-static.a differ diff --git a/libdatachannel/lib/libjuice-static.a b/libdatachannel/lib/libjuice-static.a new file mode 100644 index 0000000..1226558 Binary files /dev/null and b/libdatachannel/lib/libjuice-static.a differ diff --git a/libdatachannel/lib/libsrtp2.a b/libdatachannel/lib/libsrtp2.a new file mode 100644 index 0000000..ebf78fe Binary files /dev/null and b/libdatachannel/lib/libsrtp2.a differ diff --git a/libdatachannel/lib/libusrsctp.a b/libdatachannel/lib/libusrsctp.a new file mode 100644 index 0000000..abf7227 Binary files /dev/null and b/libdatachannel/lib/libusrsctp.a differ diff --git a/libdatachannel/lib/usrsctp.lib b/libdatachannel/lib/usrsctp.lib new file mode 100644 index 0000000..733ccbb Binary files /dev/null and b/libdatachannel/lib/usrsctp.lib differ