main #6
@ -40,14 +40,14 @@ cd apple-libressl-sdk
|
|||||||
git checkout fce8ffe30c9939fa1d71076745d9b3c811b8a90e
|
git checkout fce8ffe30c9939fa1d71076745d9b3c811b8a90e
|
||||||
```
|
```
|
||||||
|
|
||||||
**Webview for windodows**
|
**Webview for windows**
|
||||||
```bash
|
```bash
|
||||||
# Windows needs the WebView2 SDK while mac and linux are OS implementations
|
# 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
|
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"
|
powershell -Command "Expand-Archive -Path webview2.zip -DestinationPath webview2-tmp -Force"
|
||||||
xcopy /E /I /Y webview2-tmp\build\native\include webview2\include\
|
xcopy /E /I /Y webview2-tmp\build\native\include webview2\include\
|
||||||
mkdir webview2\lib\x64
|
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
|
rmdir /S /Q webview2-tmp
|
||||||
del webview2.zip
|
del webview2.zip
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "lockfree_queue.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -14,61 +15,7 @@ namespace EngineManager {
|
|||||||
|
|
||||||
// thread communication
|
// thread communication
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class LockFreeQueue {
|
using LockFreeQueue = ::LockFreeQueue<T>;
|
||||||
private:
|
|
||||||
struct Node {
|
|
||||||
std::atomic<T*> data{nullptr};
|
|
||||||
std::atomic<Node*> next{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
std::atomic<Node*> head_{nullptr};
|
|
||||||
std::atomic<Node*> 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// thread pool stealing
|
// thread pool stealing
|
||||||
class ThreadPool {
|
class ThreadPool {
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
#pragma comment(lib, "ws2_32.lib")
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
#else
|
#else
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
@ -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<bool>* cancel) {
|
||||||
std::string key = make_key(host, port, is_ssl);
|
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);
|
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<int>(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);
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to connect to %s:%d", host.c_str(), port);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
closesocket(sock);
|
closesocket(sock);
|
||||||
@ -165,8 +226,22 @@ std::string ConnectionPool::make_key(const std::string& host, int port, bool is_
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ConnectionPool::is_socket_alive(socket_t socket) const {
|
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<int>(socket) + 1, &rfds, nullptr, nullptr, &tv);
|
||||||
|
#endif
|
||||||
|
if (sel < 0) return false;
|
||||||
|
if (sel == 0) return true;
|
||||||
char test_byte;
|
char test_byte;
|
||||||
int result = ::recv(socket, &test_byte, 1, MSG_PEEK);
|
int result = ::recv(socket, &test_byte, 1, MSG_PEEK);
|
||||||
|
if (result == 0) return false;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (result == SOCKET_ERROR) {
|
if (result == SOCKET_ERROR) {
|
||||||
@ -179,5 +254,5 @@ bool ConnectionPool::is_socket_alive(socket_t socket) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@ -31,7 +32,8 @@ public:
|
|||||||
ConnectionPool() = default;
|
ConnectionPool() = default;
|
||||||
~ConnectionPool();
|
~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<bool>* cancel = nullptr);
|
||||||
|
|
||||||
// returns connection to pool for reuse
|
// returns connection to pool for reuse
|
||||||
void release_connection(const std::string& host, int port, socket_t socket, bool is_ssl = false);
|
void release_connection(const std::string& host, int port, socket_t socket, bool is_ssl = false);
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -299,12 +300,50 @@ namespace HttpRequestWrapper {
|
|||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Destroying HTTP request client");
|
kinc_log(KINC_LOG_LEVEL_INFO, "Destroying HTTP request client");
|
||||||
#endif
|
#endif
|
||||||
|
should_abort_ = true;
|
||||||
|
int s = active_socket_.exchange(-1);
|
||||||
|
if (s >= 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
::shutdown(static_cast<SOCKET>(s), SD_BOTH);
|
||||||
|
#else
|
||||||
|
::shutdown(s, SHUT_RDWR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (request_future_.valid()) {
|
||||||
|
request_future_.wait();
|
||||||
|
}
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
cleanupSSL();
|
cleanupSSL();
|
||||||
#endif
|
#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) {
|
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<SOCKET>(s), SD_BOTH);
|
||||||
|
#else
|
||||||
|
::shutdown(s, SHUT_RDWR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
request_future_.wait();
|
||||||
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||||
|
|
||||||
method_ = method;
|
method_ = method;
|
||||||
@ -350,10 +389,11 @@ namespace HttpRequestWrapper {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
request_data_ = data;
|
request_data_ = data;
|
||||||
|
should_abort_ = false;
|
||||||
|
|
||||||
if (async_) {
|
if (async_) {
|
||||||
// use thread pool
|
// use thread pool
|
||||||
GlobalThreadPool::getInstance().enqueue([this]() {
|
request_future_ = GlobalThreadPool::getInstance().enqueue([this]() {
|
||||||
this->performRequest();
|
this->performRequest();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -369,6 +409,16 @@ namespace HttpRequestWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
should_abort_ = true;
|
||||||
|
int s = active_socket_.exchange(-1);
|
||||||
|
if (s >= 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
::shutdown(static_cast<SOCKET>(s), SD_BOTH);
|
||||||
|
#else
|
||||||
|
::shutdown(s, SHUT_RDWR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
setReadyState(DONE);
|
setReadyState(DONE);
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request aborted");
|
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request aborted");
|
||||||
@ -437,6 +487,26 @@ namespace HttpRequestWrapper {
|
|||||||
on_load_end_.Reset(isolate_, callback);
|
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() {
|
void HttpRequestClient::performRequest() {
|
||||||
active_operation_ = true;
|
active_operation_ = true;
|
||||||
|
|
||||||
@ -497,7 +567,8 @@ namespace HttpRequestWrapper {
|
|||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Acquiring connection for HTTP request");
|
kinc_log(KINC_LOG_LEVEL_INFO, "Acquiring connection for HTTP request");
|
||||||
#endif
|
#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<int>(sock);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
response_.error = "Failed to acquire connection";
|
response_.error = "Failed to acquire connection";
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
@ -523,27 +594,36 @@ namespace HttpRequestWrapper {
|
|||||||
::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", host.c_str(), port);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", host.c_str(), port);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
if (is_https) {
|
if (is_https) {
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Performing SSL handshake");
|
kinc_log(KINC_LOG_LEVEL_INFO, "Performing SSL handshake");
|
||||||
|
#endif
|
||||||
ssl_socket_ = static_cast<int>(sock);
|
ssl_socket_ = static_cast<int>(sock);
|
||||||
if (!performSSLHandshake(static_cast<int>(sock), host)) {
|
if (!performSSLHandshake(static_cast<int>(sock), host)) {
|
||||||
response_.error = "SSL handshake failed";
|
response_.error = "SSL handshake failed";
|
||||||
|
active_socket_ = -1;
|
||||||
closesocket(sock);
|
closesocket(sock);
|
||||||
setReadyState(DONE);
|
setReadyState(DONE);
|
||||||
handleError(response_.error);
|
handleError(response_.error);
|
||||||
active_operation_ = false;
|
active_operation_ = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake completed successfully");
|
kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake completed successfully");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string request_data = formatHttpRequest(method_, path, host, request_data_);
|
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, "Sending HTTP request (%zu bytes):", request_data.length());
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Request preview: %.200s", request_data.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Request preview: %.200s", request_data.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
int bytes_sent = 0;
|
int bytes_sent = 0;
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
@ -565,20 +645,34 @@ namespace HttpRequestWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request sent successfully");
|
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request sent successfully");
|
||||||
|
#endif
|
||||||
|
|
||||||
char buffer[4096];
|
char buffer[4096];
|
||||||
int bytes_received;
|
int bytes_received;
|
||||||
std::string response_data;
|
std::string response_data;
|
||||||
int total_bytes = 0;
|
int total_bytes = 0;
|
||||||
|
bool body_complete = false;
|
||||||
|
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Starting to receive HTTP response");
|
kinc_log(KINC_LOG_LEVEL_INFO, "Starting to receive HTTP response");
|
||||||
|
#endif
|
||||||
while (true) {
|
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
|
#ifdef WITH_SSL
|
||||||
if (is_https) {
|
if (is_https) {
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Calling sslRead for %zu bytes", sizeof(buffer) - 1);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Calling sslRead for %zu bytes", sizeof(buffer) - 1);
|
||||||
|
#endif
|
||||||
bytes_received = sslRead(buffer, sizeof(buffer) - 1);
|
bytes_received = sslRead(buffer, sizeof(buffer) - 1);
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "sslRead returned %d bytes", bytes_received);
|
kinc_log(KINC_LOG_LEVEL_INFO, "sslRead returned %d bytes", bytes_received);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
|
bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
|
||||||
}
|
}
|
||||||
@ -587,16 +681,21 @@ namespace HttpRequestWrapper {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (bytes_received <= 0) {
|
if (bytes_received <= 0) {
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Breaking from response reading loop, bytes_received: %d", bytes_received);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Breaking from response reading loop, bytes_received: %d", bytes_received);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer[bytes_received] = '\0';
|
response_data.append(buffer, bytes_received);
|
||||||
response_data += buffer;
|
|
||||||
total_bytes += bytes_received;
|
total_bytes += bytes_received;
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes (total: %d)", bytes_received, total_bytes);
|
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);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Finished receiving data, total bytes: %d", total_bytes);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
if (is_https) {
|
if (is_https) {
|
||||||
@ -604,24 +703,40 @@ namespace HttpRequestWrapper {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// return connection to pool for reuse instead of closing
|
active_socket_ = -1;
|
||||||
g_connection_pool.release_connection(host, port, sock, is_https);
|
|
||||||
|
|
||||||
if (bytes_received == SOCKET_ERROR) {
|
if (bytes_received == SOCKET_ERROR) {
|
||||||
response_.error = "Failed to receive response";
|
response_.error = "Failed to receive response";
|
||||||
|
closesocket(sock);
|
||||||
setReadyState(DONE);
|
setReadyState(DONE);
|
||||||
handleError(response_.error);
|
handleError(response_.error);
|
||||||
active_operation_ = false;
|
active_operation_ = false;
|
||||||
return;
|
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, "Raw HTTP response data (%zu bytes):", response_data.length());
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Response preview: %.200s", response_data.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Response preview: %.200s", response_data.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
parseHttpResponse(response_data);
|
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, "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());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %.200s", response_.responseText.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
setReadyState(HEADERS_RECEIVED);
|
setReadyState(HEADERS_RECEIVED);
|
||||||
|
|
||||||
@ -661,6 +776,7 @@ namespace HttpRequestWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestClient::handleError(const std::string& error) {
|
void HttpRequestClient::handleError(const std::string& error) {
|
||||||
|
if (should_abort_.load()) return; // abort() already fired its event
|
||||||
std::lock_guard<std::mutex> lock(event_queue_mutex_);
|
std::lock_guard<std::mutex> lock(event_queue_mutex_);
|
||||||
event_queue_.push(Event(HTTP_ERROR, error));
|
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) {
|
bool HttpRequestClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path, bool& is_https) {
|
||||||
if (url.find("://") == std::string::npos) {
|
if (url.find("://") == std::string::npos) {
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Detected local file path: %s", url.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Detected local file path: %s", url.c_str());
|
||||||
|
#endif
|
||||||
host = "localhost";
|
host = "localhost";
|
||||||
port = 0;
|
port = 0;
|
||||||
path = url;
|
path = url;
|
||||||
@ -841,7 +959,9 @@ namespace HttpRequestWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (url.substr(0, 7) == "file://") {
|
if (url.substr(0, 7) == "file://") {
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Detected file:// URL: %s", url.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Detected file:// URL: %s", url.c_str());
|
||||||
|
#endif
|
||||||
host = "localhost";
|
host = "localhost";
|
||||||
port = 0; // Special port to indicate local file
|
port = 0; // Special port to indicate local file
|
||||||
path = url.substr(7); // Remove "file://" prefix
|
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::regex url_regex(R"(^(https?):\/\/([^\/\s:]+)(?::(\d+))?(\/[^\s]*)?(?:\?[^\s]*)?(?:#[^\s]*)?$)");
|
||||||
std::smatch matches;
|
std::smatch matches;
|
||||||
|
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Parsing HTTP URL: %s", url.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Parsing HTTP URL: %s", url.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!std::regex_match(url, matches, url_regex)) {
|
if (!std::regex_match(url, matches, url_regex)) {
|
||||||
// fallback parsing for simpler URLs
|
// fallback parsing for simpler URLs
|
||||||
@ -900,8 +1022,10 @@ namespace HttpRequestWrapper {
|
|||||||
port = is_https ? 443 : 80;
|
port = is_https ? 443 : 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "URL parsed successfully - scheme: %s, host: %s, port: %d, path: %s",
|
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());
|
scheme.c_str(), host.c_str(), port, path.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -911,7 +1035,7 @@ namespace HttpRequestWrapper {
|
|||||||
|
|
||||||
request << method << " " << path << " HTTP/1.1\r\n";
|
request << method << " " << path << " HTTP/1.1\r\n";
|
||||||
request << "Host: " << host << "\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 << "User-Agent: LNXCORE/1.0\r\n";
|
||||||
request << "Accept: */*\r\n";
|
request << "Accept: */*\r\n";
|
||||||
request << "Accept-Encoding: identity\r\n";
|
request << "Accept-Encoding: identity\r\n";
|
||||||
@ -935,8 +1059,10 @@ namespace HttpRequestWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string request_str = request.str();
|
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, "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());
|
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;
|
return request_str;
|
||||||
}
|
}
|
||||||
@ -1011,11 +1137,15 @@ namespace HttpRequestWrapper {
|
|||||||
|
|
||||||
response_.headers[header_name] = header_value;
|
response_.headers[header_name] = header_value;
|
||||||
header_count++;
|
header_count++;
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Header: %s = %s", header_name.c_str(), header_value.c_str());
|
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);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed %d headers", header_count);
|
||||||
|
#endif
|
||||||
|
|
||||||
std::ostringstream body_stream;
|
std::ostringstream body_stream;
|
||||||
std::string body_line;
|
std::string body_line;
|
||||||
@ -1030,12 +1160,16 @@ namespace HttpRequestWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response_.responseText = body_stream.str();
|
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());
|
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()) {
|
if (!response_.responseText.empty()) {
|
||||||
std::string preview = response_.responseText.substr(0, 100);
|
std::string preview = response_.responseText.substr(0, 100);
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %s", preview.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %s", preview.c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestClient::loadLocalFile(const std::string& path) {
|
void HttpRequestClient::loadLocalFile(const std::string& path) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <future>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
@ -101,7 +102,9 @@ namespace HttpRequestWrapper {
|
|||||||
bool with_credentials_;
|
bool with_credentials_;
|
||||||
bool async_;
|
bool async_;
|
||||||
std::thread request_thread_;
|
std::thread request_thread_;
|
||||||
std::atomic<bool> should_abort_;
|
std::atomic<bool> should_abort_{false};
|
||||||
|
std::future<void> request_future_;
|
||||||
|
std::atomic<int> active_socket_{-1};
|
||||||
|
|
||||||
v8::Global<v8::Function> on_ready_state_change_;
|
v8::Global<v8::Function> on_ready_state_change_;
|
||||||
v8::Global<v8::Function> on_load_;
|
v8::Global<v8::Function> on_load_;
|
||||||
|
|||||||
@ -59,6 +59,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
result = *data;
|
result = *data;
|
||||||
|
next->data.store(nullptr);
|
||||||
delete data;
|
delete data;
|
||||||
|
|
||||||
head_.store(next);
|
head_.store(next);
|
||||||
|
|||||||
134
Sources/main.cpp
134
Sources/main.cpp
@ -38,6 +38,10 @@
|
|||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_RTC
|
||||||
|
#include "rtc_v8_bindings.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_BENCHMARK
|
#ifdef WITH_BENCHMARK
|
||||||
#include "BENCHMARK/websocket_benchmark.h"
|
#include "BENCHMARK/websocket_benchmark.h"
|
||||||
extern "C" void websocket_run_benchmark();
|
extern "C" void websocket_run_benchmark();
|
||||||
@ -1325,9 +1329,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int runt_get_samples_per_second_fast(Local<Object> receiver) {
|
int runt_get_samples_per_second_fast(Local<Object> receiver) {
|
||||||
int rate = kinc_a2_samples_per_second();
|
return kinc_a2_samples_per_second();
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Samples per second: %d Hz.", rate);
|
|
||||||
return rate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void runt_get_samples_per_second(const FunctionCallbackInfo<Value> &args) {
|
void runt_get_samples_per_second(const FunctionCallbackInfo<Value> &args) {
|
||||||
@ -2296,11 +2298,14 @@ namespace {
|
|||||||
HandleScope scope(args.GetIsolate());
|
HandleScope scope(args.GetIsolate());
|
||||||
String::Utf8Value utf8_path(isolate, args[0]);
|
String::Utf8Value utf8_path(isolate, args[0]);
|
||||||
|
|
||||||
|
if (args.Length() < 2 || !args[1]->IsArrayBuffer()) return;
|
||||||
|
|
||||||
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[1]);
|
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[1]);
|
||||||
std::shared_ptr<BackingStore> content = buffer->GetBackingStore();
|
std::shared_ptr<BackingStore> content = buffer->GetBackingStore();
|
||||||
|
|
||||||
bool hasLengthArg = args.Length() > 2 && !args[2]->IsNullOrUndefined();
|
bool hasLengthArg = args.Length() > 2 && !args[2]->IsNullOrUndefined();
|
||||||
int byteLength = hasLengthArg ? args[2]->ToInt32(isolate->GetCurrentContext()).ToLocalChecked()->Value() : (int)content->ByteLength();
|
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();
|
if (byteLength > (int)content->ByteLength()) byteLength = (int)content->ByteLength();
|
||||||
|
|
||||||
#ifdef KINC_WINDOWS
|
#ifdef KINC_WINDOWS
|
||||||
@ -2314,6 +2319,112 @@ namespace {
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void runt_file_write_bytes(const FunctionCallbackInfo<Value> &args) {
|
||||||
|
HandleScope scope(args.GetIsolate());
|
||||||
|
String::Utf8Value utf8_path(isolate, args[0]);
|
||||||
|
|
||||||
|
if (args.Length() < 2 || !args[1]->IsArrayBuffer()) return;
|
||||||
|
|
||||||
|
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[1]);
|
||||||
|
std::shared_ptr<BackingStore> 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<Value> &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<ArrayBuffer> buffer = ArrayBuffer::New(isolate, length);
|
||||||
|
std::shared_ptr<BackingStore> 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<Value> &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) {
|
int sys_command(const char *cmd) {
|
||||||
#ifdef KINC_WINDOWS
|
#ifdef KINC_WINDOWS
|
||||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, cmd, -1, NULL, 0);
|
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, "beginFace", runt_begin_face);
|
||||||
SET_FUNCTION(runt, "end", runt_end);
|
SET_FUNCTION(runt, "end", runt_end);
|
||||||
SET_FUNCTION(runt, "fileSaveBytes", runt_file_save_bytes);
|
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, "sysCommand", runt_sys_command);
|
||||||
SET_FUNCTION(runt, "savePath", runt_save_path);
|
SET_FUNCTION(runt, "savePath", runt_save_path);
|
||||||
SET_FUNCTION(runt, "getArgCount", runt_get_arg_count);
|
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, "socketSetBlocking", v8_runt_socket_set_blocking);
|
||||||
SET_FUNCTION(runt, "socketSelect", v8_runt_socket_select);
|
SET_FUNCTION(runt, "socketSelect", v8_runt_socket_select);
|
||||||
SET_FUNCTION(runt, "socketIsConnected", v8_runt_socket_is_connected);
|
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
|
#ifdef WITH_SSL
|
||||||
SET_FUNCTION(runt, "socketEnableSsl", v8_runt_socket_enable_ssl);
|
SET_FUNCTION(runt, "socketEnableSsl", v8_runt_socket_enable_ssl);
|
||||||
#endif
|
#endif
|
||||||
@ -3214,6 +3332,9 @@ namespace {
|
|||||||
WebSocketWrapper::processEvents();
|
WebSocketWrapper::processEvents();
|
||||||
HttpRequestWrapper::processEvents();
|
HttpRequestWrapper::processEvents();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_RTC
|
||||||
|
runt_rtc_process_events_native();
|
||||||
|
#endif
|
||||||
|
|
||||||
execute_timers();
|
execute_timers();
|
||||||
EngineManager::AsyncEngine::instance().process_events();
|
EngineManager::AsyncEngine::instance().process_events();
|
||||||
@ -3455,9 +3576,13 @@ namespace {
|
|||||||
handle_exception(&try_catch);
|
handle_exception(&try_catch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_RTC
|
||||||
|
v8_runt_rtc_shutdown_all();
|
||||||
|
#endif
|
||||||
#ifdef WITH_NETWORKING
|
#ifdef WITH_NETWORKING
|
||||||
WebSocketWrapper::cleanup();
|
WebSocketWrapper::cleanup();
|
||||||
HttpRequestWrapper::cleanup();
|
HttpRequestWrapper::cleanup();
|
||||||
|
GlobalThreadPool::shutdown();
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_VIEWPORT
|
#ifdef WITH_VIEWPORT
|
||||||
if (viewport_server_mode) {
|
if (viewport_server_mode) {
|
||||||
@ -4115,6 +4240,9 @@ int kickstart(int argc, char **argv) {
|
|||||||
bind_httprequest_class(isolate, global_context);
|
bind_httprequest_class(isolate, global_context);
|
||||||
WEBSOCKET_BIND_V8(isolate, global_context);
|
WEBSOCKET_BIND_V8(isolate, global_context);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_RTC
|
||||||
|
bind_rtc_bridge(isolate, global_context);
|
||||||
|
#endif
|
||||||
|
|
||||||
start_runt(snapshot_found ? NULL : code);
|
start_runt(snapshot_found ? NULL : code);
|
||||||
|
|
||||||
|
|||||||
232
Sources/rtc_bridge.cpp
Normal file
232
Sources/rtc_bridge.cpp
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
#include "rtc_bridge.h"
|
||||||
|
|
||||||
|
#include <rtc/rtc.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <bcrypt.h>
|
||||||
|
#else
|
||||||
|
#include <cstdio>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::mutex *g_rtc_mutex = new std::mutex;
|
||||||
|
static std::deque<RtcEvent> *g_rtc_events = new std::deque<RtcEvent>;
|
||||||
|
|
||||||
|
static void rtc_push(RtcEvent &&ev) {
|
||||||
|
std::lock_guard<std::mutex> lock(*g_rtc_mutex);
|
||||||
|
g_rtc_events->push_back(std::move(ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool runt_rtc_poll_event(RtcEvent &out) {
|
||||||
|
std::lock_guard<std::mutex> 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<std::string> &iceServers) {
|
||||||
|
rtcConfiguration conf;
|
||||||
|
memset(&conf, 0, sizeof(conf));
|
||||||
|
std::vector<const char *> 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<PUCHAR>(out),
|
||||||
|
static_cast<ULONG>(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<size_t>(len), f);
|
||||||
|
fclose(f);
|
||||||
|
return n == static_cast<size_t>(len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
54
Sources/rtc_bridge.h
Normal file
54
Sources/rtc_bridge.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<char> data;
|
||||||
|
bool isString = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
int runt_rtc_init();
|
||||||
|
void runt_rtc_cleanup();
|
||||||
|
|
||||||
|
int runt_rtc_create_peer_connection(const std::vector<std::string> &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);
|
||||||
607
Sources/rtc_v8_bindings.cpp
Normal file
607
Sources/rtc_v8_bindings.cpp
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
#include "rtc_v8_bindings.h"
|
||||||
|
#include "rtc_bridge.h"
|
||||||
|
|
||||||
|
#include <kinc/log.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace v8;
|
||||||
|
|
||||||
|
static Isolate *g_rtc_isolate = nullptr;
|
||||||
|
static std::map<int, Persistent<Function>> &g_desc_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_cand_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_state_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_gathering_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_dc_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_open_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_closed_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_error_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
static std::map<int, Persistent<Function>> &g_msg_cbs = *new std::map<int, Persistent<Function>>;
|
||||||
|
|
||||||
|
static std::map<int, std::vector<int>> &g_pc_dcs = *new std::map<int, std::vector<int>>;
|
||||||
|
static std::map<int, int> &g_dc_to_pc = *new std::map<int, int>;
|
||||||
|
static Global<Context> *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<Value> &args,
|
||||||
|
std::map<int, Persistent<Function>> &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<Function>());
|
||||||
|
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<Function> &cb, int argc, Local<Value> *argv) {
|
||||||
|
if (cb.IsEmpty()) return;
|
||||||
|
Isolate *isolate = g_rtc_isolate;
|
||||||
|
Local<Context> ctx = isolate->GetCurrentContext();
|
||||||
|
Local<Function> 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<String> 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<Value> &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<Value> &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<int> 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<int> 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<Value> &args) {
|
||||||
|
Isolate *isolate = args.GetIsolate();
|
||||||
|
std::vector<std::string> iceServers;
|
||||||
|
if (args.Length() >= 1 && args[0]->IsArray()) {
|
||||||
|
Local<Array> arr = args[0].As<Array>();
|
||||||
|
Local<Context> ctx = isolate->GetCurrentContext();
|
||||||
|
for (uint32_t i = 0; i < arr->Length(); i++) {
|
||||||
|
Local<Value> 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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<ArrayBuffer> buf = args[1].As<ArrayBuffer>();
|
||||||
|
ret = runt_rtc_send_message(
|
||||||
|
id, static_cast<const char *>(buf->GetBackingStore()->Data()),
|
||||||
|
(int)buf->ByteLength());
|
||||||
|
} else if (args[1]->IsArrayBufferView()) {
|
||||||
|
Local<ArrayBufferView> view = args[1].As<ArrayBufferView>();
|
||||||
|
Local<ArrayBuffer> buf = view->Buffer();
|
||||||
|
const char *data =
|
||||||
|
static_cast<const char *>(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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &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<Value> &args) {
|
||||||
|
rtc_store_cb(args, g_msg_cbs,
|
||||||
|
"runt_rtc_set_message_callback requires (id, fn)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void v8_runt_rtc_random_bytes(const FunctionCallbackInfo<Value> &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<ArrayBuffer> buf = ArrayBuffer::New(isolate, len);
|
||||||
|
if (!runt_rtc_random_bytes(
|
||||||
|
static_cast<char *>(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<Value> 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<Value> 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<Value> 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<Value> 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<Value> 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<Value> 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<Value> 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<char>(std::move(ev.data));
|
||||||
|
auto backing = ArrayBuffer::NewBackingStore(
|
||||||
|
payload->data(), payload->size(),
|
||||||
|
[](void *, size_t, void *deleter_data) {
|
||||||
|
delete static_cast<std::vector<char> *>(deleter_data);
|
||||||
|
},
|
||||||
|
payload);
|
||||||
|
arg = ArrayBuffer::New(isolate, std::move(backing));
|
||||||
|
}
|
||||||
|
Local<Value> 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<Context> ctx = Local<Context>::New(g_rtc_isolate, *g_rtc_context);
|
||||||
|
Context::Scope context_scope(ctx);
|
||||||
|
rtc_drain_events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind_rtc_bridge(Isolate *isolate, const Global<Context> &context) {
|
||||||
|
Locker locker{isolate};
|
||||||
|
Isolate::Scope isolate_scope(isolate);
|
||||||
|
HandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<Context> current_context = Local<Context>::New(isolate, context);
|
||||||
|
Context::Scope context_scope(current_context);
|
||||||
|
Local<Object> global = current_context->Global();
|
||||||
|
|
||||||
|
g_rtc_isolate = isolate;
|
||||||
|
if (g_rtc_context == nullptr) {
|
||||||
|
g_rtc_context = new Global<Context>(isolate, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> runt_obj;
|
||||||
|
Local<Value> 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<Function> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Sources/rtc_v8_bindings.h
Normal file
9
Sources/rtc_v8_bindings.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <v8.h>
|
||||||
|
|
||||||
|
void bind_rtc_bridge(v8::Isolate *isolate, const v8::Global<v8::Context> &context);
|
||||||
|
|
||||||
|
void v8_runt_rtc_shutdown_all();
|
||||||
|
|
||||||
|
void runt_rtc_process_events_native();
|
||||||
@ -44,6 +44,7 @@ public:
|
|||||||
bool isConnected;
|
bool isConnected;
|
||||||
bool isBound;
|
bool isBound;
|
||||||
bool isListening;
|
bool isListening;
|
||||||
|
bool connectInProgress;
|
||||||
std::string lastError;
|
std::string lastError;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -59,6 +60,7 @@ public:
|
|||||||
void* ssl_context_handle_;
|
void* ssl_context_handle_;
|
||||||
bool ssl_context_initialized_;
|
bool ssl_context_initialized_;
|
||||||
std::vector<char> ssl_buffer_;
|
std::vector<char> ssl_buffer_;
|
||||||
|
std::vector<char> ssl_send_buffer_;
|
||||||
#else
|
#else
|
||||||
SSL* ssl;
|
SSL* ssl;
|
||||||
SSL_CTX* ssl_ctx;
|
SSL_CTX* ssl_ctx;
|
||||||
@ -67,7 +69,7 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
RunTSocket(int socket_id) : id(socket_id), isBlocking(true), isConnected(false),
|
RunTSocket(int socket_id) : id(socket_id), isBlocking(true), isConnected(false),
|
||||||
isBound(false), isListening(false) {
|
isBound(false), isListening(false), connectInProgress(false) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
socket_fd = INVALID_SOCKET;
|
socket_fd = INVALID_SOCKET;
|
||||||
#else
|
#else
|
||||||
@ -245,6 +247,14 @@ extern "C" int runt_socket_create() {
|
|||||||
int no_sigpipe = 1;
|
int no_sigpipe = 1;
|
||||||
setsockopt(socket->socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&no_sigpipe, sizeof(no_sigpipe));
|
setsockopt(socket->socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&no_sigpipe, sizeof(no_sigpipe));
|
||||||
#endif
|
#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);
|
g_sockets[socket_id] = std::move(socket);
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
@ -399,6 +409,20 @@ extern "C" bool runt_socket_connect(int socket_id, const char* hostname, int por
|
|||||||
connected = true;
|
connected = true;
|
||||||
break;
|
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);
|
freeaddrinfo(result);
|
||||||
@ -413,7 +437,7 @@ extern "C" bool runt_socket_connect(int socket_id, const char* hostname, int por
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket->isConnected = true;
|
socket->isConnected = !socket->connectInProgress;
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", hostname, port);
|
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", hostname, port);
|
||||||
#endif
|
#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;
|
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;
|
SecBufferDesc message_desc;
|
||||||
SecBuffer message_buffers[4];
|
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) {
|
if (status != SEC_E_OK) {
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel encrypt failed: 0x%x", status);
|
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel encrypt failed: 0x%x", status);
|
||||||
delete[] encrypt_buffer;
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int total_encrypted = message_buffers[0].cbBuffer + message_buffers[1].cbBuffer + message_buffers[2].cbBuffer;
|
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);
|
int sent = send(socket->socket_fd, encrypt_buffer, total_encrypted, 0);
|
||||||
|
|
||||||
delete[] encrypt_buffer;
|
|
||||||
|
|
||||||
if (sent != total_encrypted) {
|
if (sent != total_encrypted) {
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send encrypted SSL data");
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send encrypted SSL data");
|
||||||
return -1;
|
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);
|
int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0);
|
||||||
if (received <= 0) {
|
if (received <= 0) {
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
|
if (received < 0 && WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
||||||
if (received == 0) {
|
if (received == 0) {
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server");
|
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server");
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
socket->isConnected = false;
|
||||||
|
return -2;
|
||||||
} else {
|
} else {
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data for SSL read");
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data for SSL read");
|
||||||
return -1;
|
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);
|
int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0);
|
||||||
if (received <= 0) {
|
if (received <= 0) {
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
|
if (received < 0 && WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
||||||
if (received == 0) {
|
if (received == 0) {
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server (no buffered data)");
|
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server (no buffered data)");
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
socket->isConnected = false;
|
||||||
|
return -2;
|
||||||
} else {
|
} else {
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data");
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data");
|
||||||
return -1;
|
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) {
|
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
|
||||||
return 0;
|
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);
|
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_read failed: %d", ssl_error);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -755,6 +785,143 @@ extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data)
|
|||||||
return received;
|
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<RunTSocket>(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) {
|
extern "C" void runt_socket_close(int socket_id) {
|
||||||
auto it = g_sockets.find(socket_id);
|
auto it = g_sockets.find(socket_id);
|
||||||
if (it == g_sockets.end()) return;
|
if (it == g_sockets.end()) return;
|
||||||
@ -771,7 +938,41 @@ extern "C" bool runt_socket_is_connected(int socket_id) {
|
|||||||
if (it == g_sockets.end()) return false;
|
if (it == g_sockets.end()) return false;
|
||||||
|
|
||||||
auto& socket = it->second;
|
auto& socket = it->second;
|
||||||
return socket->isValid() && socket->isConnected;
|
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) {
|
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);
|
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);
|
int ret = SSL_connect(socket->ssl);
|
||||||
|
fcntl(socket->socket_fd, F_SETFL, flags);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
int ssl_error = SSL_get_error(socket->ssl, ret);
|
int ssl_error = SSL_get_error(socket->ssl, ret);
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_connect failed: %d", ssl_error);
|
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_connect failed: %d", ssl_error);
|
||||||
|
|||||||
@ -15,6 +15,13 @@ int runt_socket_accept(int socket_id);
|
|||||||
bool runt_socket_connect(int socket_id, const char* hostname, int port);
|
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_send(int socket_id, const char* data, int length);
|
||||||
int runt_socket_recv(int socket_id, int max_length, char** out_data);
|
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
|
#ifdef WITH_SSL
|
||||||
bool runt_socket_enable_ssl(int socket_id);
|
bool runt_socket_enable_ssl(int socket_id);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -235,6 +235,141 @@ void v8_runt_socket_is_connected(const FunctionCallbackInfo<Value>& args) {
|
|||||||
args.GetReturnValue().Set(Boolean::New(isolate, connected));
|
args.GetReturnValue().Set(Boolean::New(isolate, connected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void v8_runt_udp_create(const FunctionCallbackInfo<Value>& 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<Value>& 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<Value>& 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<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[3]);
|
||||||
|
std::shared_ptr<BackingStore> backing =
|
||||||
|
buffer->GetBackingStore();
|
||||||
|
sent = runt_udp_sendto(socket_id, *host, port,
|
||||||
|
static_cast<const char*>(backing->Data()),
|
||||||
|
static_cast<int>(backing->ByteLength()));
|
||||||
|
} else if (args[3]->IsArrayBufferView()) {
|
||||||
|
Local<ArrayBufferView> view =
|
||||||
|
Local<ArrayBufferView>::Cast(args[3]);
|
||||||
|
Local<ArrayBuffer> buffer = view->Buffer();
|
||||||
|
std::shared_ptr<BackingStore> backing =
|
||||||
|
buffer->GetBackingStore();
|
||||||
|
const char* data =
|
||||||
|
static_cast<const char*>(backing->Data())
|
||||||
|
+ view->ByteOffset();
|
||||||
|
sent = runt_udp_sendto(socket_id, *host, port, data,
|
||||||
|
static_cast<int>(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<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
Local<Context> 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<BackingStore> backing =
|
||||||
|
ArrayBuffer::NewBackingStore(data, received,
|
||||||
|
[](void* data, size_t length, void* deleter_data) {
|
||||||
|
delete[] static_cast<char*>(data);
|
||||||
|
}, nullptr);
|
||||||
|
Local<ArrayBuffer> buffer =
|
||||||
|
ArrayBuffer::New(isolate, std::move(backing));
|
||||||
|
|
||||||
|
Local<Object> 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
|
#ifdef WITH_SSL
|
||||||
void v8_runt_socket_enable_ssl(const FunctionCallbackInfo<Value>& args) {
|
void v8_runt_socket_enable_ssl(const FunctionCallbackInfo<Value>& args) {
|
||||||
Isolate* isolate = args.GetIsolate();
|
Isolate* isolate = args.GetIsolate();
|
||||||
@ -302,6 +437,18 @@ void bind_socket_bridge(Isolate* isolate, const v8::Global<v8::Context>& context
|
|||||||
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_is_connected").ToLocalChecked(),
|
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_is_connected").ToLocalChecked(),
|
||||||
Function::New(current_context, v8_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
|
#ifdef WITH_SSL
|
||||||
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_enable_ssl").ToLocalChecked(),
|
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_enable_ssl").ToLocalChecked(),
|
||||||
Function::New(current_context, v8_runt_socket_enable_ssl).ToLocalChecked());
|
Function::New(current_context, v8_runt_socket_enable_ssl).ToLocalChecked());
|
||||||
|
|||||||
@ -15,6 +15,10 @@ void v8_runt_socket_close(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|||||||
void v8_runt_socket_set_blocking(const v8::FunctionCallbackInfo<v8::Value>& args);
|
void v8_runt_socket_set_blocking(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
void v8_runt_socket_select(const v8::FunctionCallbackInfo<v8::Value>& args);
|
void v8_runt_socket_select(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
void v8_runt_socket_is_connected(const v8::FunctionCallbackInfo<v8::Value>& args);
|
void v8_runt_socket_is_connected(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
void v8_runt_udp_create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
void v8_runt_udp_bind(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
void v8_runt_udp_sendto(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
void v8_runt_udp_recvfrom(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
void v8_runt_socket_enable_ssl(const v8::FunctionCallbackInfo<v8::Value>& args);
|
void v8_runt_socket_enable_ssl(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -101,11 +101,19 @@ namespace WebSocketWrapper {
|
|||||||
|
|
||||||
void processEvents() {
|
void processEvents() {
|
||||||
try {
|
try {
|
||||||
|
std::vector<int> closed_ids;
|
||||||
for (auto& [id, client] : active_websockets) {
|
for (auto& [id, client] : active_websockets) {
|
||||||
if (client) {
|
if (client) {
|
||||||
client->processEvents();
|
client->processEvents();
|
||||||
|
if (client->getReadyState() == CLOSED
|
||||||
|
&& !client->hasPendingEvents()) {
|
||||||
|
closed_ids.push_back(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for (int id : closed_ids) {
|
||||||
|
active_websockets.erase(id);
|
||||||
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in global processEvents");
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in global processEvents");
|
||||||
@ -120,137 +128,9 @@ namespace WebSocketWrapper {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimpleWebSocketClient {
|
|
||||||
private:
|
|
||||||
SOCKET socket_;
|
|
||||||
std::thread* thread_;
|
|
||||||
std::atomic<bool> connected_{false};
|
|
||||||
std::atomic<bool> 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<int>(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<int>(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<uint8_t> frame;
|
|
||||||
frame.push_back(0x81);
|
|
||||||
|
|
||||||
if (data.length() < 126) {
|
|
||||||
frame.push_back(0x80 | static_cast<uint8_t>(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<int>(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<Context>* global_context, const std::string& url)
|
WebSocketClient::WebSocketClient(Isolate* isolate, Global<Context>* global_context, const std::string& url)
|
||||||
: isolate_(isolate), global_context_(global_context), url_(url), ready_state_(CONNECTING),
|
: 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 WITH_SSL
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
, ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1)
|
, ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1)
|
||||||
@ -278,21 +158,35 @@ namespace WebSocketWrapper {
|
|||||||
ready_state_ = CONNECTING;
|
ready_state_ = CONNECTING;
|
||||||
|
|
||||||
// start connection using thread pool
|
// start connection using thread pool
|
||||||
GlobalThreadPool::getInstance().enqueue([this, host, port, path]() {
|
connect_future_ = GlobalThreadPool::getInstance().enqueue([this, host, port, path]() {
|
||||||
try {
|
try {
|
||||||
|
if (ready_state_ == CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
#ifdef DEBUG_NETWORK
|
#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");
|
host.c_str(), port, path.c_str(), is_ssl_ ? "yes" : "no");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (connectToServer(host, port, path)) {
|
if (connectToServer(host, port, path)) {
|
||||||
ready_state_ = OPEN;
|
if (ready_state_ == OPEN) {
|
||||||
handleOpen();
|
|
||||||
|
|
||||||
messageLoop();
|
messageLoop();
|
||||||
} else {
|
} else {
|
||||||
ready_state_ = CLOSED;
|
if (is_ssl_) {
|
||||||
|
cleanupWebSocketSSL();
|
||||||
|
}
|
||||||
|
if (ws_ >= 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
closesocket((SOCKET)ws_);
|
||||||
|
#else
|
||||||
|
::close(ws_);
|
||||||
|
#endif
|
||||||
|
ws_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
handleError("Connection failed");
|
handleError("Connection failed");
|
||||||
|
ready_state_ = CLOSED;
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ready_state_ = CLOSED;
|
ready_state_ = CLOSED;
|
||||||
@ -304,9 +198,39 @@ namespace WebSocketWrapper {
|
|||||||
if (ready_state_ == OPEN) {
|
if (ready_state_ == OPEN) {
|
||||||
close();
|
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
|
#ifdef WITH_SSL
|
||||||
cleanupWSL();
|
cleanupWSL();
|
||||||
#endif
|
#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) {
|
bool WebSocketClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path) {
|
||||||
@ -338,7 +262,7 @@ namespace WebSocketWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws_ == nullptr) {
|
if (ws_ < 0) {
|
||||||
handleError("WebSocket: No socket available for sending");
|
handleError("WebSocket: No socket available for sending");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -353,7 +277,7 @@ namespace WebSocketWrapper {
|
|||||||
// create and send WebSocket frame
|
// create and send WebSocket frame
|
||||||
std::vector<uint8_t> frame = createWebSocketFrame(data);
|
std::vector<uint8_t> frame = createWebSocketFrame(data);
|
||||||
|
|
||||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
int sock = ws_;
|
||||||
int sendResult;
|
int sendResult;
|
||||||
|
|
||||||
if (is_ssl_ && isSSLReady()) {
|
if (is_ssl_ && isSSLReady()) {
|
||||||
@ -388,7 +312,7 @@ namespace WebSocketWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws_ == nullptr) {
|
if (ws_ < 0) {
|
||||||
handleError("WebSocket: No socket available for sending");
|
handleError("WebSocket: No socket available for sending");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -403,7 +327,7 @@ namespace WebSocketWrapper {
|
|||||||
// binary frame with opcode 0x02
|
// binary frame with opcode 0x02
|
||||||
std::vector<uint8_t> frame = createWebSocketBinaryFrame(data);
|
std::vector<uint8_t> frame = createWebSocketBinaryFrame(data);
|
||||||
|
|
||||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
int sock = ws_;
|
||||||
int sendResult;
|
int sendResult;
|
||||||
|
|
||||||
if (is_ssl_ && isSSLReady()) {
|
if (is_ssl_ && isSSLReady()) {
|
||||||
@ -434,8 +358,17 @@ namespace WebSocketWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ready_state_ == OPEN && ws_ >= 0) {
|
||||||
|
std::vector<uint8_t> frame = createCloseFrame(code, reason);
|
||||||
|
if (is_ssl_ && isSSLReady()) {
|
||||||
|
webSocketSSLSend(frame.data(), static_cast<int>(frame.size()));
|
||||||
|
} else {
|
||||||
|
int sock = ws_;
|
||||||
|
::send(sock, reinterpret_cast<const char*>(frame.data()), static_cast<int>(frame.size()), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ready_state_ = CLOSING;
|
ready_state_ = CLOSING;
|
||||||
ready_state_ = CLOSED;
|
|
||||||
handleClose(code, reason);
|
handleClose(code, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,16 +390,6 @@ namespace WebSocketWrapper {
|
|||||||
on_close_.Reset(isolate_, callback);
|
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) {
|
void WebSocketClient::handleError(const std::string& error) {
|
||||||
try {
|
try {
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
@ -479,8 +402,8 @@ namespace WebSocketWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketClient::handleClose(int code, const std::string& reason) {
|
void WebSocketClient::handleClose(int code, const std::string& reason) {
|
||||||
ready_state_ = CLOSED;
|
|
||||||
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
||||||
|
ready_state_ = CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process events on main thread with lock free queue and batching
|
// process events on main thread with lock free queue and batching
|
||||||
@ -929,6 +852,8 @@ namespace WebSocketWrapper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws_ = sock;
|
||||||
|
|
||||||
sockaddr_in addr{};
|
sockaddr_in addr{};
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
@ -945,6 +870,7 @@ namespace WebSocketWrapper {
|
|||||||
#else
|
#else
|
||||||
close(sock);
|
close(sock);
|
||||||
#endif
|
#endif
|
||||||
|
ws_ = -1;
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to resolve host %s", host.c_str());
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to resolve host %s", host.c_str());
|
||||||
#endif
|
#endif
|
||||||
@ -961,6 +887,7 @@ namespace WebSocketWrapper {
|
|||||||
#else
|
#else
|
||||||
close(sock);
|
close(sock);
|
||||||
#endif
|
#endif
|
||||||
|
ws_ = -1;
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to connect to %s:%d", host.c_str(), port);
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to connect to %s:%d", host.c_str(), port);
|
||||||
#endif
|
#endif
|
||||||
@ -971,7 +898,7 @@ namespace WebSocketWrapper {
|
|||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: TCP connected to %s:%d", host.c_str(), port);
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: TCP connected to %s:%d", host.c_str(), port);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ws_ = reinterpret_cast<void*>(static_cast<intptr_t>(sock));
|
ws_ = sock;
|
||||||
|
|
||||||
if (is_ssl_ && port == 443) {
|
if (is_ssl_ && port == 443) {
|
||||||
if (!initializeWebSocketSSL(sock, host)) {
|
if (!initializeWebSocketSSL(sock, host)) {
|
||||||
@ -980,7 +907,7 @@ namespace WebSocketWrapper {
|
|||||||
#else
|
#else
|
||||||
close(sock);
|
close(sock);
|
||||||
#endif
|
#endif
|
||||||
ws_ = nullptr;
|
ws_ = -1;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
@ -995,7 +922,7 @@ namespace WebSocketWrapper {
|
|||||||
#else
|
#else
|
||||||
close(sock);
|
close(sock);
|
||||||
#endif
|
#endif
|
||||||
ws_ = nullptr;
|
ws_ = -1;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1003,8 +930,10 @@ namespace WebSocketWrapper {
|
|||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake completed successfully");
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake completed successfully");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (ready_state_ == CONNECTING) {
|
||||||
ready_state_ = OPEN;
|
ready_state_ = OPEN;
|
||||||
fireOpenEvent();
|
fireOpenEvent();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1060,9 +989,15 @@ namespace WebSocketWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char buffer[SocketOptimization::SMALL_BUFFER_SIZE];
|
char buffer[SocketOptimization::SMALL_BUFFER_SIZE];
|
||||||
|
std::string response;
|
||||||
|
for (int attempt = 0; attempt < 64
|
||||||
|
&& response.find("\r\n\r\n") == std::string::npos; ++attempt) {
|
||||||
int bytesReceived;
|
int bytesReceived;
|
||||||
if (is_ssl_ && isSSLReady()) {
|
if (is_ssl_ && isSSLReady()) {
|
||||||
bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1);
|
bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1);
|
||||||
|
if (bytesReceived == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
|
bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
|
||||||
}
|
}
|
||||||
@ -1073,9 +1008,15 @@ namespace WebSocketWrapper {
|
|||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
response.append(buffer, bytesReceived);
|
||||||
|
}
|
||||||
|
|
||||||
buffer[bytesReceived] = '\0';
|
if (response.find("\r\n\r\n") == std::string::npos) {
|
||||||
std::string response(buffer);
|
#ifdef DEBUG_NETWORK
|
||||||
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Handshake response never completed");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received handshake response");
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received handshake response");
|
||||||
@ -1155,8 +1096,8 @@ namespace WebSocketWrapper {
|
|||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed: %d - %s", code, reason.c_str());
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed: %d - %s", code, reason.c_str());
|
||||||
#endif
|
#endif
|
||||||
ready_state_ = CLOSED;
|
|
||||||
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
||||||
|
ready_state_ = CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete frame helper for ring buffer messageLoop
|
// complete frame helper for ring buffer messageLoop
|
||||||
@ -1186,14 +1127,23 @@ namespace WebSocketWrapper {
|
|||||||
close_reason = std::string(payload.begin() + 2, payload.end());
|
close_reason = std::string(payload.begin() + 2, payload.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ready_state_ == OPEN) {
|
||||||
|
std::vector<uint8_t> echoFrame = createCloseFrame(close_code, "");
|
||||||
|
if (is_ssl_ && isSSLReady()) {
|
||||||
|
webSocketSSLSend(echoFrame.data(), static_cast<int>(echoFrame.size()));
|
||||||
|
} else {
|
||||||
|
int sock = ws_;
|
||||||
|
::send(sock, reinterpret_cast<const char*>(echoFrame.data()), static_cast<int>(echoFrame.size()), 0);
|
||||||
|
}
|
||||||
fireCloseEvent(close_code, close_reason);
|
fireCloseEvent(close_code, close_reason);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x9: // ping frame
|
case 0x9: // ping frame
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
||||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
int sock = ws_;
|
||||||
if (is_ssl_ && isSSLReady()) {
|
if (is_ssl_ && isSSLReady()) {
|
||||||
webSocketSSLSend(pongFrame.data(), static_cast<int>(pongFrame.size()));
|
webSocketSSLSend(pongFrame.data(), static_cast<int>(pongFrame.size()));
|
||||||
} else {
|
} else {
|
||||||
@ -1213,14 +1163,14 @@ namespace WebSocketWrapper {
|
|||||||
|
|
||||||
// following uWebSockets
|
// following uWebSockets
|
||||||
void WebSocketClient::messageLoop() {
|
void WebSocketClient::messageLoop() {
|
||||||
if (ws_ == nullptr) {
|
if (ws_ < 0) {
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: No socket available for message loop");
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: No socket available for message loop");
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
int sock = ws_;
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Starting message loop");
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Starting message loop");
|
||||||
#endif
|
#endif
|
||||||
@ -1273,6 +1223,9 @@ namespace WebSocketWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bytesReceived <= 0) {
|
if (bytesReceived <= 0) {
|
||||||
|
if (bytesReceived == 0 && is_ssl_ && isSSLReady()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed by server");
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed by server");
|
||||||
#endif
|
#endif
|
||||||
@ -1357,6 +1310,10 @@ namespace WebSocketWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ready_state_ == OPEN) {
|
||||||
|
fireCloseEvent(1006, "");
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_NETWORK
|
#ifdef DEBUG_NETWORK
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message loop completed");
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message loop completed");
|
||||||
#endif
|
#endif
|
||||||
@ -1464,7 +1421,7 @@ namespace WebSocketWrapper {
|
|||||||
case 0x9:
|
case 0x9:
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
||||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
int sock = ws_;
|
||||||
|
|
||||||
int sendResult;
|
int sendResult;
|
||||||
if (is_ssl_ && isSSLReady()) {
|
if (is_ssl_ && isSSLReady()) {
|
||||||
@ -1634,9 +1591,13 @@ namespace WebSocketWrapper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// TODO: SHA1
|
std::vector<uint8_t> result(SHA_DIGEST_LENGTH, 0);
|
||||||
kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: SHA1 not implemented for this platform");
|
SHA1(reinterpret_cast<const unsigned char*>(data.data()),
|
||||||
return std::vector<uint8_t>(20, 0);
|
data.size(), result.data());
|
||||||
|
#ifdef DEBUG_NETWORK
|
||||||
|
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SHA1 hash computed successfully");
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1709,12 +1670,24 @@ namespace WebSocketWrapper {
|
|||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
if (ssl_initialized_) {
|
ssl_socket_ = sock;
|
||||||
return true;
|
|
||||||
}
|
if (!initWSL()) {
|
||||||
// TODO: OpenSSL
|
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to initialize SSL context");
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not supported on this platform");
|
|
||||||
return false;
|
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
|
#endif
|
||||||
#else
|
#else
|
||||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not compiled in");
|
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
|
// validate buffer before sending
|
||||||
if (outbuffers[0].cbBuffer < 0 || outbuffers[0].cbBuffer > 65536) {
|
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);
|
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 (status == SEC_E_OK) {
|
||||||
|
if (!handshake_buffer.empty()) {
|
||||||
|
ssl_buffer_.insert(ssl_buffer_.end(), handshake_buffer.begin(),
|
||||||
|
handshake_buffer.end());
|
||||||
|
}
|
||||||
ssl_context_initialized_ = true;
|
ssl_context_initialized_ = true;
|
||||||
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel WebSocket SSL handshake completed successfully");
|
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel WebSocket SSL handshake completed successfully");
|
||||||
return true;
|
return true;
|
||||||
@ -2649,6 +2626,160 @@ void runt_websocket_set_onclose(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Native WebSocket class implementation for V8
|
// Native WebSocket class implementation for V8
|
||||||
|
static void dispatchWebSocketEvent(const FunctionCallbackInfo<Value>& args, const char* type, const char* handler) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
Local<Object> self = args.Data().As<Object>();
|
||||||
|
|
||||||
|
Local<Object> event;
|
||||||
|
if (args.Length() > 0 && args[0]->IsObject()) {
|
||||||
|
event = args[0].As<Object>();
|
||||||
|
} 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<Value> argv[1] = { event };
|
||||||
|
Local<Value> fn = self->Get(context, String::NewFromUtf8(isolate, handler).ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (fn->IsFunction()) {
|
||||||
|
TryCatch try_catch(isolate);
|
||||||
|
fn.As<Function>()->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<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (listenersVal->IsObject()) {
|
||||||
|
Local<Value> arr = listenersVal.As<Object>()->Get(context, String::NewFromUtf8(isolate, type).ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (arr->IsArray()) {
|
||||||
|
Local<Array> listeners = arr.As<Array>();
|
||||||
|
uint32_t length = listeners->Length();
|
||||||
|
std::vector<Local<Function>> snapshot;
|
||||||
|
snapshot.reserve(length);
|
||||||
|
for (uint32_t i = 0; i < length; ++i) {
|
||||||
|
Local<Value> listener = listeners->Get(context, i).ToLocalChecked();
|
||||||
|
if (listener->IsFunction()) {
|
||||||
|
snapshot.push_back(listener.As<Function>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Local<Function>& 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<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
Local<Object> self = args.Data().As<Object>();
|
||||||
|
|
||||||
|
Local<Value> 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<Value>& args) {
|
||||||
|
dispatchWebSocketEvent(args, "message", "onmessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WebSocketDispatchError(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
dispatchWebSocketEvent(args, "error", "onerror");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WebSocketDispatchClose(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
dispatchWebSocketEvent(args, "close", "onclose");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WebSocketReadyStateGetter(Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||||
|
Isolate* isolate = info.GetIsolate();
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
int state = WebSocketWrapper::CLOSED;
|
||||||
|
Local<Value> 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<Name> property, Local<Value> value, const PropertyCallbackInfo<void>& info) {}
|
||||||
|
|
||||||
|
static void WebSocketAddEventListener(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
Local<Object> self = args.Holder();
|
||||||
|
|
||||||
|
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return;
|
||||||
|
|
||||||
|
Local<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (!listenersVal->IsObject()) return;
|
||||||
|
Local<Object> listeners = listenersVal.As<Object>();
|
||||||
|
|
||||||
|
Local<Value> arr = listeners->Get(context, args[0]).ToLocalChecked();
|
||||||
|
Local<Array> listenerList;
|
||||||
|
if (arr->IsArray()) {
|
||||||
|
listenerList = arr.As<Array>();
|
||||||
|
for (uint32_t i = 0; i < listenerList->Length(); ++i) {
|
||||||
|
Local<Value> 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<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
Local<Object> self = args.Holder();
|
||||||
|
|
||||||
|
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return;
|
||||||
|
|
||||||
|
Local<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (!listenersVal->IsObject()) return;
|
||||||
|
|
||||||
|
Local<Value> arr = listenersVal.As<Object>()->Get(context, args[0]).ToLocalChecked();
|
||||||
|
if (!arr->IsArray()) return;
|
||||||
|
Local<Array> listenerList = arr.As<Array>();
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < listenerList->Length(); ++i) {
|
||||||
|
Local<Value> listener = listenerList->Get(context, i).ToLocalChecked();
|
||||||
|
if (listener->StrictEquals(args[1])) {
|
||||||
|
Local<Value> splice = listenerList->Get(context, String::NewFromUtf8(isolate, "splice").ToLocalChecked()).ToLocalChecked();
|
||||||
|
Local<Value> spliceArgs[2] = { Integer::New(isolate, i), Integer::New(isolate, 1) };
|
||||||
|
splice.As<Function>()->Call(context, listenerList, 2, spliceArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
||||||
Isolate* isolate = args.GetIsolate();
|
Isolate* isolate = args.GetIsolate();
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
@ -2672,7 +2803,6 @@ static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
|||||||
Local<Object> instance = args.This();
|
Local<Object> instance = args.This();
|
||||||
instance->Set(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked(), Integer::New(isolate, wsId));
|
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, "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, "protocol").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
|
||||||
instance->Set(context, String::NewFromUtf8(isolate, "extensions").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());
|
instance->Set(context, String::NewFromUtf8(isolate, "binaryType").ToLocalChecked(), String::NewFromUtf8(isolate, "blob").ToLocalChecked());
|
||||||
@ -2681,6 +2811,15 @@ static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
|||||||
instance->Set(context, String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), Null(isolate));
|
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, "onerror").ToLocalChecked(), Null(isolate));
|
||||||
instance->Set(context, String::NewFromUtf8(isolate, "onclose").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);
|
args.GetReturnValue().Set(instance);
|
||||||
}
|
}
|
||||||
@ -2745,8 +2884,6 @@ static void WebSocketClose(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
if (readyState == 2 || readyState == 3) return;
|
if (readyState == 2 || readyState == 3) return;
|
||||||
|
|
||||||
self->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 2));
|
|
||||||
|
|
||||||
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
|
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
|
||||||
int wsId = idVal->Int32Value(context).FromJust();
|
int wsId = idVal->Int32Value(context).FromJust();
|
||||||
|
|
||||||
@ -2883,10 +3020,15 @@ void bind_websocket_class(Isolate* isolate, const Global<Context>& context) {
|
|||||||
Local<FunctionTemplate> wsTpl = FunctionTemplate::New(isolate, WebSocketConstructor);
|
Local<FunctionTemplate> wsTpl = FunctionTemplate::New(isolate, WebSocketConstructor);
|
||||||
wsTpl->SetClassName(String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked());
|
wsTpl->SetClassName(String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked());
|
||||||
wsTpl->InstanceTemplate()->SetInternalFieldCount(1);
|
wsTpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
wsTpl->InstanceTemplate()->SetNativeDataProperty(
|
||||||
|
String::NewFromUtf8(isolate, "readyState").ToLocalChecked(),
|
||||||
|
WebSocketReadyStateGetter, WebSocketReadyStateSetter);
|
||||||
|
|
||||||
Local<ObjectTemplate> proto = wsTpl->PrototypeTemplate();
|
Local<ObjectTemplate> proto = wsTpl->PrototypeTemplate();
|
||||||
proto->Set(isolate, "send", FunctionTemplate::New(isolate, WebSocketSend));
|
proto->Set(isolate, "send", FunctionTemplate::New(isolate, WebSocketSend));
|
||||||
proto->Set(isolate, "close", FunctionTemplate::New(isolate, WebSocketClose));
|
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<Function> wsFunc = wsTpl->GetFunction(current_context).ToLocalChecked();
|
Local<Function> wsFunc = wsTpl->GetFunction(current_context).ToLocalChecked();
|
||||||
wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CONNECTING").ToLocalChecked(), Integer::New(isolate, 0));
|
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>& context) {
|
|||||||
wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CLOSED").ToLocalChecked(), Integer::New(isolate, 3));
|
wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CLOSED").ToLocalChecked(), Integer::New(isolate, 3));
|
||||||
|
|
||||||
global->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc);
|
global->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc);
|
||||||
|
|
||||||
|
Local<Value> windowVal = global->Get(current_context, String::NewFromUtf8(isolate, "window").ToLocalChecked()).ToLocalChecked();
|
||||||
|
if (windowVal->IsObject()) {
|
||||||
|
windowVal.As<Object>()->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <future>
|
||||||
#include <v8.h>
|
#include <v8.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ namespace WebSocketWrapper {
|
|||||||
void processEvents();
|
void processEvents();
|
||||||
|
|
||||||
ReadyState getReadyState() const { return ready_state_; }
|
ReadyState getReadyState() const { return ready_state_; }
|
||||||
|
bool hasPendingEvents() const { return !event_queue_.empty(); }
|
||||||
const std::string& getUrl() const { return url_; }
|
const std::string& getUrl() const { return url_; }
|
||||||
const std::string& getProtocol() const { return protocol_; }
|
const std::string& getProtocol() const { return protocol_; }
|
||||||
int getBufferedAmount() const { return buffered_amount_; }
|
int getBufferedAmount() const { return buffered_amount_; }
|
||||||
@ -83,7 +85,7 @@ namespace WebSocketWrapper {
|
|||||||
v8::Global<v8::Context>* global_context_;
|
v8::Global<v8::Context>* global_context_;
|
||||||
std::string url_;
|
std::string url_;
|
||||||
std::string protocol_;
|
std::string protocol_;
|
||||||
ReadyState ready_state_;
|
std::atomic<ReadyState> ready_state_;
|
||||||
bool is_ssl_;
|
bool is_ssl_;
|
||||||
int buffered_amount_;
|
int buffered_amount_;
|
||||||
|
|
||||||
@ -94,9 +96,9 @@ namespace WebSocketWrapper {
|
|||||||
v8::Global<v8::Function> on_close_;
|
v8::Global<v8::Function> on_close_;
|
||||||
|
|
||||||
LockFreeQueue<WebSocketEvent> event_queue_;
|
LockFreeQueue<WebSocketEvent> event_queue_;
|
||||||
|
std::future<void> connect_future_;
|
||||||
|
|
||||||
void* ws_;
|
int ws_;
|
||||||
void* ssl_ws_;
|
|
||||||
|
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -131,8 +133,6 @@ namespace WebSocketWrapper {
|
|||||||
std::string base64Encode(const std::string& data);
|
std::string base64Encode(const std::string& data);
|
||||||
|
|
||||||
// called from worker thread
|
// called from worker thread
|
||||||
void handleOpen();
|
|
||||||
void handleMessage(const std::string& message);
|
|
||||||
void handleError(const std::string& error);
|
void handleError(const std::string& error);
|
||||||
void handleClose(int code, const std::string& reason);
|
void handleClose(int code, const std::string& reason);
|
||||||
|
|
||||||
|
|||||||
43
kfile.js
43
kfile.js
@ -13,6 +13,7 @@ let flags = {
|
|||||||
with_webview: true,
|
with_webview: true,
|
||||||
with_uws: false,
|
with_uws: false,
|
||||||
with_ssl: true,
|
with_ssl: true,
|
||||||
|
with_rtc: true,
|
||||||
debug_network: false
|
debug_network: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,10 +123,45 @@ if (flags.with_viewport && (platform === Platform.Windows || platform === Platfo
|
|||||||
project.addFile('Sources/viewport_server.h');
|
project.addFile('Sources/viewport_server.h');
|
||||||
project.addFile('Sources/viewport_server.cpp');
|
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) {
|
if (flags.with_networking && platform !== Platform.Windows) {
|
||||||
|
// 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('ssl');
|
||||||
project.addLib('crypto');
|
project.addLib('crypto');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (flags.with_worker) {
|
if (flags.with_worker) {
|
||||||
project.addDefine('WITH_WORKER');
|
project.addDefine('WITH_WORKER');
|
||||||
@ -142,7 +178,7 @@ if (platform === Platform.Windows) {
|
|||||||
project.addLib(libdir + 'v8_monolith');
|
project.addLib(libdir + 'v8_monolith');
|
||||||
if (flags.with_webview) {
|
if (flags.with_webview) {
|
||||||
project.addIncludeDir('webview2/include');
|
project.addIncludeDir('webview2/include');
|
||||||
project.addLib(root + '/webview2/lib/x64/WebView2Loader');
|
project.addLib(root + '/webview2/lib/x64/WebView2LoaderStatic');
|
||||||
}
|
}
|
||||||
if (!flags.release) {
|
if (!flags.release) {
|
||||||
project.addDefine('_HAS_ITERATOR_DEBUGGING=0');
|
project.addDefine('_HAS_ITERATOR_DEBUGGING=0');
|
||||||
@ -175,10 +211,15 @@ else if (platform === Platform.OSX) {
|
|||||||
project.addLinkerFlag('-framework AppKit');
|
project.addLinkerFlag('-framework AppKit');
|
||||||
}
|
}
|
||||||
if (flags.with_networking) {
|
if (flags.with_networking) {
|
||||||
|
if (flags.with_rtc) {
|
||||||
|
project.addIncludeDir('/opt/homebrew/opt/openssl@3/include');
|
||||||
|
}
|
||||||
|
else {
|
||||||
project.addIncludeDir('apple-libressl-sdk/include');
|
project.addIncludeDir('apple-libressl-sdk/include');
|
||||||
project.addLinkerFlag('-L' + root + '/apple-libressl-sdk/lib');
|
project.addLinkerFlag('-L' + root + '/apple-libressl-sdk/lib');
|
||||||
project.addLinkerFlag('-lssl');
|
project.addLinkerFlag('-lssl');
|
||||||
project.addLinkerFlag('-lcrypto');
|
project.addLinkerFlag('-lcrypto');
|
||||||
|
}
|
||||||
project.addLinkerFlag('-framework Security');
|
project.addLinkerFlag('-framework Security');
|
||||||
project.addLinkerFlag('-framework SystemConfiguration');
|
project.addLinkerFlag('-framework SystemConfiguration');
|
||||||
}
|
}
|
||||||
|
|||||||
21
libdatachannel/LICENSE
Normal file
21
libdatachannel/LICENSE
Normal file
@ -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.
|
||||||
57
libdatachannel/include/rtc/av1rtppacketizer.hpp
Normal file
57
libdatachannel/include/rtc/av1rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<binary> extractTemporalUnitObus(const binary &data);
|
||||||
|
|
||||||
|
std::vector<binary> fragment(binary data) override;
|
||||||
|
std::vector<binary> fragmentObu(const binary &data);
|
||||||
|
|
||||||
|
const Packetization mPacketization;
|
||||||
|
const size_t mMaxFragmentSize;
|
||||||
|
|
||||||
|
std::unique_ptr<binary> 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 */
|
||||||
77
libdatachannel/include/rtc/candidate.hpp
Normal file
77
libdatachannel/include/rtc/candidate.hpp
Normal file
@ -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 <string>
|
||||||
|
|
||||||
|
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<string> address() const;
|
||||||
|
optional<uint16_t> 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<string> 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
|
||||||
61
libdatachannel/include/rtc/channel.hpp
Normal file
61
libdatachannel/include/rtc/channel.hpp
Normal file
@ -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 <atomic>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
struct Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT Channel : private CheshireCat<impl::Channel> {
|
||||||
|
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<void()> callback);
|
||||||
|
void onClosed(std::function<void()> callback);
|
||||||
|
void onError(std::function<void(string error)> callback);
|
||||||
|
|
||||||
|
void onMessage(std::function<void(message_variant data)> callback);
|
||||||
|
void onMessage(std::function<void(binary data)> binaryCallback,
|
||||||
|
std::function<void(string data)> stringCallback);
|
||||||
|
|
||||||
|
void onBufferedAmountLow(std::function<void()> callback);
|
||||||
|
void setBufferedAmountLowThreshold(size_t amount);
|
||||||
|
|
||||||
|
void resetCallbacks();
|
||||||
|
|
||||||
|
// Extended API
|
||||||
|
optional<message_variant> receive(); // only if onMessage unset
|
||||||
|
optional<message_variant> peek(); // only if onMessage unset
|
||||||
|
size_t availableAmount() const; // total size available to receive
|
||||||
|
void onAvailable(std::function<void()> callback);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Channel(impl_ptr<impl::Channel> impl);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif // RTC_CHANNEL_H
|
||||||
85
libdatachannel/include/rtc/common.hpp
Normal file
85
libdatachannel/include/rtc/common.hpp
Normal file
@ -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 <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<byte>;
|
||||||
|
using message_variant = variant<binary, string>;
|
||||||
|
|
||||||
|
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
|
||||||
130
libdatachannel/include/rtc/configuration.hpp
Normal file
130
libdatachannel/include/rtc/configuration.hpp
Normal file
@ -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 <vector>
|
||||||
|
|
||||||
|
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<string> username;
|
||||||
|
optional<string> 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<IceServer> iceServers;
|
||||||
|
optional<ProxyServer> proxyServer; // libnice only
|
||||||
|
optional<string> 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<size_t> mtu;
|
||||||
|
|
||||||
|
// Local maximum message size for Data Channels
|
||||||
|
optional<size_t> maxMessageSize;
|
||||||
|
|
||||||
|
// Certificates and private keys
|
||||||
|
optional<string> certificatePemFile;
|
||||||
|
optional<string> keyPemFile;
|
||||||
|
optional<string> keyPemPass;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef RTC_ENABLE_WEBSOCKET
|
||||||
|
|
||||||
|
struct WebSocketConfiguration {
|
||||||
|
bool disableTlsVerification = false; // if true, don't verify the TLS certificate
|
||||||
|
optional<ProxyServer> proxyServer; // only non-authenticated http supported for now
|
||||||
|
std::vector<string> protocols;
|
||||||
|
optional<std::chrono::milliseconds> tcpConnectionTimeout; // zero to disable
|
||||||
|
optional<std::chrono::milliseconds> connectionTimeout; // zero to disable
|
||||||
|
optional<std::chrono::milliseconds> pingInterval; // zero to disable
|
||||||
|
optional<int> maxOutstandingPings;
|
||||||
|
optional<string> caCertificatePemFile;
|
||||||
|
optional<string> certificatePemFile;
|
||||||
|
optional<string> keyPemFile;
|
||||||
|
optional<string> keyPemPass;
|
||||||
|
optional<size_t> maxMessageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WebSocketServerConfiguration {
|
||||||
|
uint16_t port = 8080;
|
||||||
|
bool enableTls = false;
|
||||||
|
optional<string> certificatePemFile;
|
||||||
|
optional<string> keyPemFile;
|
||||||
|
optional<string> keyPemPass;
|
||||||
|
optional<string> bindAddress;
|
||||||
|
optional<std::chrono::milliseconds> connectionTimeout;
|
||||||
|
optional<size_t> maxMessageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
80
libdatachannel/include/rtc/datachannel.hpp
Normal file
80
libdatachannel/include/rtc/datachannel.hpp
Normal file
@ -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 <type_traits>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
struct DataChannel;
|
||||||
|
struct PeerConnection;
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT DataChannel final : private CheshireCat<impl::DataChannel>, public Channel {
|
||||||
|
public:
|
||||||
|
DataChannel(impl_ptr<impl::DataChannel> impl);
|
||||||
|
~DataChannel() override;
|
||||||
|
|
||||||
|
optional<uint16_t> stream() const;
|
||||||
|
optional<uint16_t> 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 <typename Buffer> bool sendBuffer(const Buffer &buf);
|
||||||
|
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CheshireCat<impl::DataChannel>::impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Buffer> std::pair<const byte *, size_t> to_bytes(const Buffer &buf) {
|
||||||
|
using T = typename std::remove_pointer<decltype(buf.data())>::type;
|
||||||
|
using E = typename std::conditional<std::is_void<T>::value, byte, T>::type;
|
||||||
|
return std::make_pair(static_cast<const byte *>(static_cast<const void *>(buf.data())),
|
||||||
|
buf.size() * sizeof(E));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Buffer> bool DataChannel::sendBuffer(const Buffer &buf) {
|
||||||
|
auto [bytes, size] = to_bytes(buf);
|
||||||
|
return send(bytes, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Iterator> 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
|
||||||
107
libdatachannel/include/rtc/dependencydescriptor.hpp
Normal file
107
libdatachannel/include/rtc/dependencydescriptor.hpp
Normal file
@ -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 <bitset>
|
||||||
|
|
||||||
|
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<DecodeTargetIndication> decodeTargetIndications;
|
||||||
|
std::vector<int> frameDiffs;
|
||||||
|
std::vector<int> chainDiffs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FrameDependencyStructure {
|
||||||
|
int templateIdOffset = 0;
|
||||||
|
int decodeTargetCount = 0;
|
||||||
|
int chainCount = 0;
|
||||||
|
std::vector<int> decodeTargetProtectedBy;
|
||||||
|
std::vector<RenderResolution> resolutions;
|
||||||
|
std::vector<FrameDependencyTemplate> templates;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DependencyDescriptor {
|
||||||
|
bool startOfFrame = true;
|
||||||
|
bool endOfFrame = true;
|
||||||
|
int frameNumber = 0;
|
||||||
|
FrameDependencyTemplate dependencyTemplate;
|
||||||
|
std::optional<RenderResolution> resolution;
|
||||||
|
std::optional<uint32_t> 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
|
||||||
401
libdatachannel/include/rtc/description.hpp
Normal file
401
libdatachannel/include/rtc/description.hpp
Normal file
@ -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 <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<string> iceOptions() const;
|
||||||
|
optional<string> iceUfrag() const;
|
||||||
|
optional<string> icePwd() const;
|
||||||
|
optional<CertificateFingerprint> 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<string> attributes() const;
|
||||||
|
void addAttribute(string attr);
|
||||||
|
void removeAttribute(const string &attr);
|
||||||
|
|
||||||
|
std::vector<Candidate> candidates() const;
|
||||||
|
std::vector<Candidate> extractCandidates();
|
||||||
|
bool hasCandidate(const Candidate &candidate) const;
|
||||||
|
void addCandidate(Candidate candidate);
|
||||||
|
void addCandidates(std::vector<Candidate> 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<RidAttribute> attributes);
|
||||||
|
|
||||||
|
[[nodiscard]] const string &rid() const;
|
||||||
|
[[nodiscard]] const std::vector<RidAttribute> &attributes() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
string mRid;
|
||||||
|
std::vector<RidAttribute> 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<RidAttribute> 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<string> 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<Rid> 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<int> 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<string> mAttributes;
|
||||||
|
std::map<int, ExtMap> mExtMaps;
|
||||||
|
|
||||||
|
private:
|
||||||
|
string mType;
|
||||||
|
string mProtocol;
|
||||||
|
string mDescription;
|
||||||
|
string mMid;
|
||||||
|
std::vector<Rid> 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<uint16_t> sctpPort() const;
|
||||||
|
optional<size_t> maxMessageSize() const;
|
||||||
|
|
||||||
|
virtual void parseSdpLine(string_view line) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual string generateSdpLines(string_view eol) const override;
|
||||||
|
|
||||||
|
optional<uint16_t> mSctpPort;
|
||||||
|
optional<size_t> 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<string> name, optional<string> msid = nullopt,
|
||||||
|
optional<string> trackId = nullopt);
|
||||||
|
void removeSSRC(uint32_t ssrc);
|
||||||
|
void replaceSSRC(uint32_t old, uint32_t ssrc, optional<string> name,
|
||||||
|
optional<string> msid = nullopt, optional<string> trackID = nullopt);
|
||||||
|
bool hasSSRC(uint32_t ssrc) const;
|
||||||
|
void clearSSRCs();
|
||||||
|
std::vector<uint32_t> getSSRCs() const;
|
||||||
|
optional<std::string> getCNameForSsrc(uint32_t ssrc) const;
|
||||||
|
|
||||||
|
void addRtxSSRC(SSRC primarySsrc, SSRC rtxSsrc, optional<string> cname = nullopt);
|
||||||
|
void removeRtxSSRC(SSRC primarySsrc);
|
||||||
|
optional<SSRC> getRtxSsrcForSsrc(SSRC primarySsrc) const;
|
||||||
|
optional<SSRC> 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<string> rtcpFbs;
|
||||||
|
std::vector<string> fmtps;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool hasPayloadType(int payloadType) const;
|
||||||
|
std::vector<int> 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<int> 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<int> mOrderedPayloadTypes;
|
||||||
|
std::map<int, RtpMap> mRtpMaps;
|
||||||
|
std::vector<uint32_t> mSsrcs;
|
||||||
|
std::map<uint32_t, string> mCNameMap;
|
||||||
|
std::map<SSRC, SSRC> 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<string> profile = std::nullopt);
|
||||||
|
void addOpusCodec(int payloadType, optional<string> profile = DEFAULT_OPUS_AUDIO_PROFILE);
|
||||||
|
void addPCMACodec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addPCMUCodec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addAACCodec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addG722Codec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
|
||||||
|
[[deprecated("Use addAACCodec")]] inline void
|
||||||
|
addAacCodec(int payloadType, optional<string> 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<string> profile = std::nullopt);
|
||||||
|
|
||||||
|
void addH264Codec(int payloadType, optional<string> profile = DEFAULT_H264_VIDEO_PROFILE);
|
||||||
|
void addH265Codec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addVP8Codec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addVP9Codec(int payloadType, optional<string> profile = std::nullopt);
|
||||||
|
void addAV1Codec(int payloadType, optional<string> 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<unsigned int> 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 *, Application *> media(int index);
|
||||||
|
variant<const Media *, const Application *> 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<Candidate> defaultCandidate() const;
|
||||||
|
shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
|
||||||
|
void removeApplication();
|
||||||
|
|
||||||
|
Type mType;
|
||||||
|
|
||||||
|
// Session-level attributes
|
||||||
|
Role mRole;
|
||||||
|
string mUsername;
|
||||||
|
string mSessionId;
|
||||||
|
std::vector<string> mIceOptions;
|
||||||
|
optional<string> mIceUfrag, mIcePwd;
|
||||||
|
optional<CertificateFingerprint> mFingerprint;
|
||||||
|
std::vector<string> mAttributes; // other attributes
|
||||||
|
|
||||||
|
// Entries
|
||||||
|
std::vector<shared_ptr<Entry>> mEntries;
|
||||||
|
shared_ptr<Application> mApplication;
|
||||||
|
|
||||||
|
// Candidates
|
||||||
|
std::vector<Candidate> 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
|
||||||
44
libdatachannel/include/rtc/frameinfo.hpp
Normal file
44
libdatachannel/include/rtc/frameinfo.hpp
Normal file
@ -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 <chrono>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
struct RTC_CPP_EXPORT FrameInfo {
|
||||||
|
FrameInfo(uint32_t timestamp) : timestamp(timestamp) {};
|
||||||
|
template<typename Period = std::ratio<1>> FrameInfo(std::chrono::duration<double, Period> timestamp) : timestampSeconds(timestamp) {}
|
||||||
|
|
||||||
|
[[deprecated]] FrameInfo(uint8_t payloadType, uint32_t timestamp) : timestamp(timestamp), payloadType(payloadType) {};
|
||||||
|
|
||||||
|
uint32_t timestamp = 0;
|
||||||
|
uint8_t payloadType = 0;
|
||||||
|
|
||||||
|
optional<std::chrono::duration<double>> 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<uint64_t> absCaptureTimeNtp;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif // RTC_FRAMEINFO_H
|
||||||
66
libdatachannel/include/rtc/global.hpp
Normal file
66
libdatachannel/include/rtc/global.hpp
Normal file
@ -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 <chrono>
|
||||||
|
#include <future>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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<void(LogLevel level, string message)> 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<size_t> recvBufferSize; // in bytes
|
||||||
|
optional<size_t> sendBufferSize; // in bytes
|
||||||
|
optional<size_t> maxChunksOnQueue; // in chunks
|
||||||
|
optional<size_t> initialCongestionWindow; // in MTUs
|
||||||
|
optional<size_t> maxBurst; // in MTUs
|
||||||
|
optional<unsigned int> congestionControlModule; // 0: RFC2581, 1: HSTCP, 2: H-TCP, 3: RTCC
|
||||||
|
optional<std::chrono::milliseconds> delayedSackTime;
|
||||||
|
optional<std::chrono::milliseconds> minRetransmitTimeout;
|
||||||
|
optional<std::chrono::milliseconds> maxRetransmitTimeout;
|
||||||
|
optional<std::chrono::milliseconds> initialRetransmitTimeout;
|
||||||
|
optional<unsigned int> maxRetransmitAttempts;
|
||||||
|
optional<std::chrono::milliseconds> heartbeatInterval;
|
||||||
|
};
|
||||||
|
|
||||||
|
RTC_CPP_EXPORT void SetSctpSettings(SctpSettings s);
|
||||||
|
|
||||||
|
// Optional global preload and cleanup
|
||||||
|
RTC_CPP_EXPORT bool Preload();
|
||||||
|
RTC_CPP_EXPORT std::shared_future<void> Cleanup();
|
||||||
|
|
||||||
|
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, LogLevel level);
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
42
libdatachannel/include/rtc/h264rtpdepacketizer.hpp
Normal file
42
libdatachannel/include/rtc/h264rtpdepacketizer.hpp
Normal file
@ -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 */
|
||||||
57
libdatachannel/include/rtc/h264rtppacketizer.hpp
Normal file
57
libdatachannel/include/rtc/h264rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
// For backward compatibility, do not use
|
||||||
|
[[deprecated]] H264RtpPacketizer(
|
||||||
|
shared_ptr<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<binary> fragment(binary data) override;
|
||||||
|
std::vector<NalUnit> 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 */
|
||||||
194
libdatachannel/include/rtc/h265nalunit.hpp
Normal file
194
libdatachannel/include/rtc/h265nalunit.hpp
Normal file
@ -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 <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<binary> GenerateFragments(const std::vector<H265NalUnit> &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 <typename Iterator>
|
||||||
|
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<H265NalUnitFragment> generateFragments(size_t maxFragmentSize) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const H265NalUnitHeader *header() const {
|
||||||
|
assert(size() >= H265_NAL_HEADER_SIZE);
|
||||||
|
return reinterpret_cast<const H265NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
|
||||||
|
H265NalUnitHeader *header() {
|
||||||
|
assert(size() >= H265_NAL_HEADER_SIZE);
|
||||||
|
return reinterpret_cast<H265NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// NAL unit fragment
|
||||||
|
struct RTC_CPP_EXPORT H265NalUnitFragment : H265NalUnit {
|
||||||
|
[[deprecated]] static std::vector<shared_ptr<H265NalUnitFragment>> fragmentsFrom(shared_ptr<H265NalUnit> 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<H265NalUnitHeader *>(data()); }
|
||||||
|
|
||||||
|
const H265NalUnitHeader *fragmentIndicator() const {
|
||||||
|
return reinterpret_cast<const H265NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
|
||||||
|
H265NalUnitFragmentHeader *fragmentHeader() {
|
||||||
|
return reinterpret_cast<H265NalUnitFragmentHeader *>(data() + H265_NAL_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const H265NalUnitFragmentHeader *fragmentHeader() const {
|
||||||
|
return reinterpret_cast<const H265NalUnitFragmentHeader *>(data() + H265_NAL_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[deprecated]] RTC_CPP_EXPORT H265NalUnits : public std::vector<shared_ptr<H265NalUnit>> {
|
||||||
|
public:
|
||||||
|
static const uint16_t defaultMaximumFragmentSize =
|
||||||
|
uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6
|
||||||
|
|
||||||
|
std::vector<shared_ptr<binary>> generateFragments(uint16_t maxFragmentSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif /* RTC_ENABLE_MEDIA */
|
||||||
|
|
||||||
|
#endif /* RTC_NAL_UNIT_H */
|
||||||
45
libdatachannel/include/rtc/h265rtpdepacketizer.hpp
Normal file
45
libdatachannel/include/rtc/h265rtpdepacketizer.hpp
Normal file
@ -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 <set>
|
||||||
|
|
||||||
|
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
|
||||||
55
libdatachannel/include/rtc/h265rtppacketizer.hpp
Normal file
55
libdatachannel/include/rtc/h265rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
// For backward compatibility, do not use
|
||||||
|
[[deprecated]] H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<binary> fragment(binary data) override;
|
||||||
|
std::vector<H265NalUnit> 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 */
|
||||||
47
libdatachannel/include/rtc/iceudpmuxlistener.hpp
Normal file
47
libdatachannel/include/rtc/iceudpmuxlistener.hpp
Normal file
@ -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<impl::IceUdpMuxListener> {
|
||||||
|
public:
|
||||||
|
IceUdpMuxListener(uint16_t port, optional<string> bindAddress = nullopt);
|
||||||
|
~IceUdpMuxListener();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
uint16_t port() const;
|
||||||
|
|
||||||
|
void OnUnhandledStunRequest(std::function<void(IceUdpMuxRequest)> callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CheshireCat<impl::IceUdpMuxListener>::impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
60
libdatachannel/include/rtc/mediahandler.hpp
Normal file
60
libdatachannel/include/rtc/mediahandler.hpp
Normal file
@ -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 <vector>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT MediaHandler : public std::enable_shared_from_this<MediaHandler> {
|
||||||
|
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<SSRC>& targetSSRCs, bool retransmit, const message_callback &send);
|
||||||
|
virtual bool requestBitrate(unsigned int bitrate, const message_callback &send);
|
||||||
|
|
||||||
|
void addToChain(shared_ptr<MediaHandler> handler);
|
||||||
|
void setNext(shared_ptr<MediaHandler> handler);
|
||||||
|
shared_ptr<MediaHandler> next();
|
||||||
|
shared_ptr<const MediaHandler> next() const;
|
||||||
|
shared_ptr<MediaHandler> last(); // never null
|
||||||
|
shared_ptr<const MediaHandler> 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<MediaHandler> mNext;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif // RTC_MEDIA_HANDLER_H
|
||||||
100
libdatachannel/include/rtc/message.hpp
Normal file
100
libdatachannel/include/rtc/message.hpp
Normal file
@ -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 <functional>
|
||||||
|
|
||||||
|
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 <typename Iterator>
|
||||||
|
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> reliability;
|
||||||
|
shared_ptr<FrameInfo> frameInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
using message_ptr = shared_ptr<Message>;
|
||||||
|
using message_callback = std::function<void(message_ptr message)>;
|
||||||
|
using message_vector = std::vector<message_ptr>;
|
||||||
|
|
||||||
|
inline size_t message_size_func(const message_ptr &m) {
|
||||||
|
return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
|
||||||
|
unsigned int stream = 0, shared_ptr<Reliability> reliability = nullptr) {
|
||||||
|
auto message = std::make_shared<Message>(begin, end, type);
|
||||||
|
message->stream = stream;
|
||||||
|
message->reliability = reliability;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
message_ptr make_message(Iterator begin, Iterator end, shared_ptr<FrameInfo> frameInfo) {
|
||||||
|
auto message = std::make_shared<Message>(begin, end);
|
||||||
|
message->frameInfo = frameInfo;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibiity, do not use
|
||||||
|
template <typename Iterator>
|
||||||
|
[[deprecated]] message_ptr make_message(Iterator begin, Iterator end, Message::Type type,
|
||||||
|
unsigned int stream, shared_ptr<FrameInfo> frameInfo) {
|
||||||
|
auto message = std::make_shared<Message>(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> reliability = nullptr);
|
||||||
|
|
||||||
|
RTC_CPP_EXPORT message_ptr make_message(binary &&data, Message::Type type = Message::Binary,
|
||||||
|
unsigned int stream = 0,
|
||||||
|
shared_ptr<Reliability> reliability = nullptr);
|
||||||
|
|
||||||
|
RTC_CPP_EXPORT message_ptr make_message(binary &&data, shared_ptr<FrameInfo> 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
|
||||||
197
libdatachannel/include/rtc/nalunit.hpp
Normal file
197
libdatachannel/include/rtc/nalunit.hpp
Normal file
@ -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 <vector>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
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<binary> GenerateFragments(const std::vector<NalUnit> &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 <typename Iterator> 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<NalUnitFragmentA> generateFragments(size_t maxFragmentSize) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const NalUnitHeader *header() const {
|
||||||
|
assert(size() >= 1);
|
||||||
|
return reinterpret_cast<const NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
|
||||||
|
NalUnitHeader *header() {
|
||||||
|
assert(size() >= 1);
|
||||||
|
return reinterpret_cast<NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Nal unit fragment A
|
||||||
|
struct RTC_CPP_EXPORT NalUnitFragmentA : NalUnit {
|
||||||
|
// For backward compatibility, do not use
|
||||||
|
[[deprecated]] static std::vector<shared_ptr<NalUnitFragmentA>>
|
||||||
|
fragmentsFrom(shared_ptr<NalUnit> 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<NalUnitHeader *>(data()); }
|
||||||
|
|
||||||
|
const NalUnitHeader *fragmentIndicator() const {
|
||||||
|
return reinterpret_cast<const NalUnitHeader *>(data());
|
||||||
|
}
|
||||||
|
|
||||||
|
NalUnitFragmentHeader *fragmentHeader() {
|
||||||
|
return reinterpret_cast<NalUnitFragmentHeader *>(fragmentIndicator() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NalUnitFragmentHeader *fragmentHeader() const {
|
||||||
|
return reinterpret_cast<const NalUnitFragmentHeader *>(fragmentIndicator() + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, do not use
|
||||||
|
class [[deprecated]] RTC_CPP_EXPORT NalUnits : public std::vector<shared_ptr<NalUnit>> {
|
||||||
|
public:
|
||||||
|
static const uint16_t defaultMaximumFragmentSize =
|
||||||
|
uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6
|
||||||
|
|
||||||
|
std::vector<shared_ptr<binary>> generateFragments(uint16_t maxFragmentSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif /* RTC_ENABLE_MEDIA */
|
||||||
|
|
||||||
|
#endif /* RTC_NAL_UNIT_H */
|
||||||
61
libdatachannel/include/rtc/pacinghandler.hpp
Normal file
61
libdatachannel/include/rtc/pacinghandler.hpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Sean DuBois <sean@siobud.com>
|
||||||
|
*
|
||||||
|
* 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 <atomic>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
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<void()> callback);
|
||||||
|
|
||||||
|
void outgoing(message_vector &messages, const message_callback &send) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<bool> mHaveScheduled = false;
|
||||||
|
|
||||||
|
double mBytesPerSecond;
|
||||||
|
double mBudget;
|
||||||
|
|
||||||
|
std::chrono::milliseconds mSendInterval;
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> mLastRun;
|
||||||
|
|
||||||
|
size_t mMaxQueueAmount;
|
||||||
|
|
||||||
|
std::mutex mParamsMutex;
|
||||||
|
std::mutex mMutex;
|
||||||
|
std::queue<message_ptr> 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
|
||||||
142
libdatachannel/include/rtc/peerconnection.hpp
Normal file
142
libdatachannel/include/rtc/peerconnection.hpp
Normal file
@ -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 <chrono>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
struct PeerConnection;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RTC_CPP_EXPORT DataChannelInit {
|
||||||
|
Reliability reliability = {};
|
||||||
|
bool negotiated = false;
|
||||||
|
optional<uint16_t> id = nullopt;
|
||||||
|
string protocol = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RTC_CPP_EXPORT LocalDescriptionInit {
|
||||||
|
optional<string> iceUfrag;
|
||||||
|
optional<string> icePwd;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT PeerConnection final : CheshireCat<impl::PeerConnection> {
|
||||||
|
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<Description> localDescription() const;
|
||||||
|
optional<Description> remoteDescription() const;
|
||||||
|
size_t remoteMaxMessageSize() const;
|
||||||
|
optional<string> localAddress() const;
|
||||||
|
optional<string> 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<IceServer> additionalIceServers = {});
|
||||||
|
void setRemoteDescription(Description description);
|
||||||
|
void addRemoteCandidate(Candidate candidate);
|
||||||
|
|
||||||
|
// For specific use cases only
|
||||||
|
Description createOffer();
|
||||||
|
Description createAnswer();
|
||||||
|
|
||||||
|
void setMediaHandler(shared_ptr<MediaHandler> handler);
|
||||||
|
shared_ptr<MediaHandler> getMediaHandler();
|
||||||
|
|
||||||
|
[[nodiscard]] shared_ptr<DataChannel> createDataChannel(string label,
|
||||||
|
DataChannelInit init = {});
|
||||||
|
void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback);
|
||||||
|
|
||||||
|
[[nodiscard]] shared_ptr<Track> addTrack(Description::Media description);
|
||||||
|
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
|
||||||
|
|
||||||
|
void onLocalDescription(std::function<void(Description description)> callback);
|
||||||
|
void onLocalCandidate(std::function<void(Candidate candidate)> callback);
|
||||||
|
void onStateChange(std::function<void(State state)> callback);
|
||||||
|
void onIceStateChange(std::function<void(IceState state)> callback);
|
||||||
|
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||||
|
void onSignalingStateChange(std::function<void(SignalingState state)> callback);
|
||||||
|
|
||||||
|
void resetCallbacks();
|
||||||
|
CertificateFingerprint remoteFingerprint();
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
void clearStats();
|
||||||
|
size_t bytesSent();
|
||||||
|
size_t bytesReceived();
|
||||||
|
optional<std::chrono::milliseconds> 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
|
||||||
41
libdatachannel/include/rtc/plihandler.hpp
Normal file
41
libdatachannel/include/rtc/plihandler.hpp
Normal file
@ -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 <unordered_map>
|
||||||
|
|
||||||
|
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<void(void)> onPli);
|
||||||
|
|
||||||
|
void incoming(message_vector &messages, const message_callback &send) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unordered_map<uint64_t, uint8_t> mFirSSRCSeqNumberMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // RTC_ENABLE_MEDIA
|
||||||
|
|
||||||
|
#endif // RTC_PLI_RESPONDER_H
|
||||||
43
libdatachannel/include/rtc/reliability.hpp
Normal file
43
libdatachannel/include/rtc/reliability.hpp
Normal file
@ -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 <chrono>
|
||||||
|
|
||||||
|
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<std::chrono::milliseconds> maxPacketLifeTime;
|
||||||
|
|
||||||
|
// Maximum number of retransmissions that are attempted
|
||||||
|
optional<unsigned int> 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<int, std::chrono::milliseconds> rexmit = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
35
libdatachannel/include/rtc/rembhandler.hpp
Normal file
35
libdatachannel/include/rtc/rembhandler.hpp
Normal file
@ -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<unsigned int> 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<void(unsigned int)> onRemb);
|
||||||
|
|
||||||
|
void incoming(message_vector &messages, const message_callback &send) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // RTC_ENABLE_MEDIA
|
||||||
|
|
||||||
|
#endif // RTC_REMB_RESPONDER_H
|
||||||
581
libdatachannel/include/rtc/rtc.h
Normal file
581
libdatachannel/include/rtc/rtc.h
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
53
libdatachannel/include/rtc/rtc.hpp
Normal file
53
libdatachannel/include/rtc/rtc.hpp
Normal file
@ -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
|
||||||
36
libdatachannel/include/rtc/rtcpapphandler.hpp
Normal file
36
libdatachannel/include/rtc/rtcpapphandler.hpp
Normal file
@ -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<const RtcpAppName, uint8_t, binary> 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<void(const RtcpAppName name, uint8_t subtype, binary data)> onApp);
|
||||||
|
|
||||||
|
void incoming(message_vector &messages, const message_callback &send) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif // RTC_ENABLE_MEDIA
|
||||||
|
|
||||||
|
#endif // RTC_RTCP_APP_HANDLER_H
|
||||||
89
libdatachannel/include/rtc/rtcpnackresponder.hpp
Normal file
89
libdatachannel/include/rtc/rtcpnackresponder.hpp
Normal file
@ -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 <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
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<SSRC> mRtxSsrc;
|
||||||
|
std::unordered_map<int, int> 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<Element> next = nullptr);
|
||||||
|
const message_ptr packet;
|
||||||
|
const uint16_t sequenceNumber;
|
||||||
|
/// Pointer to newer element
|
||||||
|
shared_ptr<Element> next = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Oldest packet in storage
|
||||||
|
shared_ptr<Element> oldest = nullptr;
|
||||||
|
/// Newest packet in storage
|
||||||
|
shared_ptr<Element> newest = nullptr;
|
||||||
|
/// Inner storage
|
||||||
|
std::unordered_map<uint16_t, shared_ptr<Element>> 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<Storage> mStorage;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif /* RTC_ENABLE_MEDIA */
|
||||||
|
|
||||||
|
#endif /* RTC_RTCP_NACK_RESPONDER_H */
|
||||||
95
libdatachannel/include/rtc/rtcpreceivingsession.hpp
Normal file
95
libdatachannel/include/rtc/rtcpreceivingsession.hpp
Normal file
@ -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 <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<SSRC>& 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<SSRC>& 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<unsigned int> 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<uint8_t, uint8_t> mRtxToPrimaryPtMap; // RTX PT -> primary PT
|
||||||
|
SSRC mRtxPrimarySsrc = 0;
|
||||||
|
bool mRtxEnabled = false;
|
||||||
|
|
||||||
|
bool mSupportsRfc5104Fir = false;
|
||||||
|
std::unordered_map<SSRC, uint32_t> mRfc5104FirCmdNums; // SSRC -> seq no mapping
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif // RTC_ENABLE_MEDIA
|
||||||
|
|
||||||
|
#endif // RTC_RTCP_RECEIVING_SESSION_H
|
||||||
49
libdatachannel/include/rtc/rtcpsrreporter.hpp
Normal file
49
libdatachannel/include/rtc/rtcpsrreporter.hpp
Normal file
@ -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 <chrono>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT RtcpSrReporter final : public MediaHandler {
|
||||||
|
public:
|
||||||
|
RtcpSrReporter(shared_ptr<RtpPacketizationConfig> 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<RtpPacketizationConfig> 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 */
|
||||||
407
libdatachannel/include/rtc/rtp.hpp
Normal file
407
libdatachannel/include/rtc/rtp.hpp
Normal file
@ -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 <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<uint8_t> 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<std::vector<uint8_t>> 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<RtcpFirFci> &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<uint16_t> 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<char, 4> 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
|
||||||
79
libdatachannel/include/rtc/rtpdepacketizer.hpp
Normal file
79
libdatachannel/include/rtc/rtpdepacketizer.hpp
Normal file
@ -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 <set>
|
||||||
|
|
||||||
|
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<FrameInfo> 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<message_ptr, sequence_cmp>;
|
||||||
|
|
||||||
|
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 <uint32_t DEFAULT_CLOCK_RATE>
|
||||||
|
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 */
|
||||||
141
libdatachannel/include/rtc/rtppacketizationconfig.hpp
Normal file
141
libdatachannel/include/rtc/rtppacketizationconfig.hpp
Normal file
@ -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<std::string> mid;
|
||||||
|
|
||||||
|
// RID Extension Header
|
||||||
|
uint8_t ridId = 0;
|
||||||
|
optional<std::string> rid;
|
||||||
|
|
||||||
|
// Dependency Descriptor Extension Header
|
||||||
|
uint8_t dependencyDescriptorId = 0;
|
||||||
|
|
||||||
|
optional<DependencyDescriptorContext> 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<const VideoLayersAllocation> 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 */
|
||||||
106
libdatachannel/include/rtc/rtppacketizer.hpp
Normal file
106
libdatachannel/include/rtc/rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> 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<RtpPacketizationConfig> rtpConfig;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Fragment data into payloads
|
||||||
|
/// Default implementation returns data as a single payload
|
||||||
|
/// @param data Input data
|
||||||
|
virtual std::vector<binary> 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> frameInfo = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const auto RtpHeaderSize = 12;
|
||||||
|
static const auto RtpExtHeaderCvoSize = 8;
|
||||||
|
|
||||||
|
uint32_t videoLayersAllocationInitialPacketCount = 0;
|
||||||
|
|
||||||
|
bool shouldEmitVideoLayersAllocation(shared_ptr<FrameInfo> frameInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic audio RTP packetizer
|
||||||
|
template <uint32_t DEFAULT_CLOCK_RATE>
|
||||||
|
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<RtpPacketizationConfig> 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<RtpPacketizer> packetizer)
|
||||||
|
: mPacketizer(std::move(packetizer)) {}
|
||||||
|
|
||||||
|
inline void outgoing(message_vector &messages, const message_callback &send) {
|
||||||
|
return mPacketizer->outgoing(messages, send);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
shared_ptr<RtpPacketizer> 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 */
|
||||||
70
libdatachannel/include/rtc/track.hpp
Normal file
70
libdatachannel/include/rtc/track.hpp
Normal file
@ -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 <vector>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
class Track;
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT Track final : private CheshireCat<impl::Track>, public Channel {
|
||||||
|
public:
|
||||||
|
Track(impl_ptr<impl::Track> 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<void(binary data, FrameInfo info)> callback);
|
||||||
|
|
||||||
|
bool requestKeyframe(SSRC ssrc=0, bool retransmit=false);
|
||||||
|
bool requestKeyframe(const std::vector<SSRC>& 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<MediaHandler> handler);
|
||||||
|
void chainMediaHandler(shared_ptr<MediaHandler> handler);
|
||||||
|
shared_ptr<MediaHandler> getMediaHandler();
|
||||||
|
|
||||||
|
// Deprecated, use setMediaHandler() and getMediaHandler()
|
||||||
|
inline void setRtcpHandler(shared_ptr<MediaHandler> handler) { setMediaHandler(handler); }
|
||||||
|
inline shared_ptr<MediaHandler> getRtcpHandler() { return getMediaHandler(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CheshireCat<impl::Track>::impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
174
libdatachannel/include/rtc/utils.hpp
Normal file
174
libdatachannel/include/rtc/utils.hpp
Normal file
@ -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 <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
// overloaded helper
|
||||||
|
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
|
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
// weak_ptr bind helper
|
||||||
|
template <typename F, typename T, typename... Args> 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<decltype(bound(args...))>(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope_guard helper
|
||||||
|
class scope_guard final {
|
||||||
|
public:
|
||||||
|
scope_guard(std::function<void()> 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<void()> function;
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback with built-in synchronization
|
||||||
|
template <typename... Args> 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<void(Args...)> 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<void(Args...)> 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<void(Args...)> func) { callback = std::move(func); }
|
||||||
|
virtual bool call(Args... args) const {
|
||||||
|
if (!callback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
callback(std::move(args)...);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void(Args...)> callback;
|
||||||
|
mutable std::recursive_mutex mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback with built-in synchronization and replay of the last missed call
|
||||||
|
template <typename... Args>
|
||||||
|
class synchronized_stored_callback final : public synchronized_callback<Args...> {
|
||||||
|
public:
|
||||||
|
template <typename... CArgs>
|
||||||
|
synchronized_stored_callback(CArgs &&...cargs)
|
||||||
|
: synchronized_callback<Args...>(std::forward<CArgs>(cargs)...) {}
|
||||||
|
~synchronized_stored_callback() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set(std::function<void(Args...)> func) {
|
||||||
|
synchronized_callback<Args...>::set(func);
|
||||||
|
if (func && stored) {
|
||||||
|
std::apply(func, std::move(*stored));
|
||||||
|
stored.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool call(Args... args) const {
|
||||||
|
if (!synchronized_callback<Args...>::call(args...))
|
||||||
|
stored.emplace(std::move(args)...);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutable std::optional<std::tuple<Args...>> stored;
|
||||||
|
};
|
||||||
|
|
||||||
|
// pimpl base class
|
||||||
|
template <typename T> using impl_ptr = std::shared_ptr<T>;
|
||||||
|
template <typename T> class CheshireCat {
|
||||||
|
public:
|
||||||
|
CheshireCat(impl_ptr<T> impl) : mImpl(std::move(impl)) {}
|
||||||
|
template <typename... Args>
|
||||||
|
CheshireCat(Args... args) : mImpl(std::make_shared<T>(std::forward<Args>(args)...)) {}
|
||||||
|
CheshireCat(CheshireCat<T> &&cc) { *this = std::move(cc); }
|
||||||
|
CheshireCat(const CheshireCat<T> &) = delete;
|
||||||
|
|
||||||
|
virtual ~CheshireCat() = default;
|
||||||
|
|
||||||
|
CheshireCat &operator=(CheshireCat<T> &&cc) {
|
||||||
|
mImpl = std::move(cc.mImpl);
|
||||||
|
return *this;
|
||||||
|
};
|
||||||
|
CheshireCat &operator=(const CheshireCat<T> &) = delete;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
impl_ptr<T> impl() { return mImpl; }
|
||||||
|
impl_ptr<const T> impl() const { return mImpl; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
impl_ptr<T> mImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
// case-insensitive string comparator using std::tolower
|
||||||
|
class case_insensitive_less : public std::less<std::string> {
|
||||||
|
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<unsigned char>(a)) <
|
||||||
|
std::tolower(static_cast<unsigned char>(b));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
9
libdatachannel/include/rtc/version.h
Normal file
9
libdatachannel/include/rtc/version.h
Normal file
@ -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
|
||||||
50
libdatachannel/include/rtc/video_layers_allocation.hpp
Normal file
50
libdatachannel/include/rtc/video_layers_allocation.hpp
Normal file
@ -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 <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<uint32_t> targetBitratesKbps; // per temporal layer, cumulative, cannot be empty
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RtpStream {
|
||||||
|
std::vector<SpatialLayer> spatialLayers;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<RtpStream> 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 */
|
||||||
34
libdatachannel/include/rtc/vp8rtpdepacketizer.hpp
Normal file
34
libdatachannel/include/rtc/vp8rtpdepacketizer.hpp
Normal file
@ -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 */
|
||||||
42
libdatachannel/include/rtc/vp8rtppacketizer.hpp
Normal file
42
libdatachannel/include/rtc/vp8rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<binary> fragment(binary frame) override;
|
||||||
|
|
||||||
|
const size_t mMaxFragmentSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif /* RTC_ENABLE_MEDIA */
|
||||||
|
|
||||||
|
#endif /* RTC_VP8_RTP_PACKETIZER_H */
|
||||||
34
libdatachannel/include/rtc/vp9rtpdepacketizer.hpp
Normal file
34
libdatachannel/include/rtc/vp9rtpdepacketizer.hpp
Normal file
@ -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 */
|
||||||
41
libdatachannel/include/rtc/vp9rtppacketizer.hpp
Normal file
41
libdatachannel/include/rtc/vp9rtppacketizer.hpp
Normal file
@ -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<RtpPacketizationConfig> rtpConfig,
|
||||||
|
size_t maxFragmentSize = DefaultMaxFragmentSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<binary> fragment(binary frame) override;
|
||||||
|
|
||||||
|
const size_t mMaxFragmentSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif /* RTC_ENABLE_MEDIA */
|
||||||
|
|
||||||
|
#endif /* RTC_VP9_RTP_PACKETIZER_H */
|
||||||
72
libdatachannel/include/rtc/websocket.hpp
Normal file
72
libdatachannel/include/rtc/websocket.hpp
Normal file
@ -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 <map>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
struct WebSocket;
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
class RTC_CPP_EXPORT WebSocket final : private CheshireCat<impl::WebSocket>, public Channel {
|
||||||
|
public:
|
||||||
|
enum class State : int {
|
||||||
|
Connecting = 0,
|
||||||
|
Open = 1,
|
||||||
|
Closing = 2,
|
||||||
|
Closed = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
using Configuration = WebSocketConfiguration;
|
||||||
|
|
||||||
|
using Headers = std::map<string, string, case_insensitive_less>;
|
||||||
|
|
||||||
|
WebSocket();
|
||||||
|
WebSocket(Configuration config);
|
||||||
|
WebSocket(impl_ptr<impl::WebSocket> 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<string> remoteAddress() const;
|
||||||
|
optional<string> path() const;
|
||||||
|
std::multimap<string, string, case_insensitive_less> requestHeaders() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CheshireCat<impl::WebSocket>::impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &out, WebSocket::State state);
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // RTC_WEBSOCKET_H
|
||||||
48
libdatachannel/include/rtc/websocketserver.hpp
Normal file
48
libdatachannel/include/rtc/websocketserver.hpp
Normal file
@ -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<impl::WebSocketServer> {
|
||||||
|
public:
|
||||||
|
using Configuration = WebSocketServerConfiguration;
|
||||||
|
|
||||||
|
WebSocketServer();
|
||||||
|
WebSocketServer(Configuration config);
|
||||||
|
~WebSocketServer();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
uint16_t port() const;
|
||||||
|
|
||||||
|
void onClient(std::function<void(shared_ptr<WebSocket>)> callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CheshireCat<impl::WebSocketServer>::impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // RTC_WEBSOCKET_H
|
||||||
BIN
libdatachannel/lib/MbedTLS.lib
Normal file
BIN
libdatachannel/lib/MbedTLS.lib
Normal file
Binary file not shown.
BIN
libdatachannel/lib/datachannel-static.lib
Normal file
BIN
libdatachannel/lib/datachannel-static.lib
Normal file
Binary file not shown.
BIN
libdatachannel/lib/juice-static.lib
Normal file
BIN
libdatachannel/lib/juice-static.lib
Normal file
Binary file not shown.
BIN
libdatachannel/lib/libMbedTLS.a
Normal file
BIN
libdatachannel/lib/libMbedTLS.a
Normal file
Binary file not shown.
BIN
libdatachannel/lib/libdatachannel-static.a
Normal file
BIN
libdatachannel/lib/libdatachannel-static.a
Normal file
Binary file not shown.
BIN
libdatachannel/lib/libjuice-static.a
Normal file
BIN
libdatachannel/lib/libjuice-static.a
Normal file
Binary file not shown.
BIN
libdatachannel/lib/libsrtp2.a
Normal file
BIN
libdatachannel/lib/libsrtp2.a
Normal file
Binary file not shown.
BIN
libdatachannel/lib/libusrsctp.a
Normal file
BIN
libdatachannel/lib/libusrsctp.a
Normal file
Binary file not shown.
BIN
libdatachannel/lib/usrsctp.lib
Normal file
BIN
libdatachannel/lib/usrsctp.lib
Normal file
Binary file not shown.
Reference in New Issue
Block a user