blob: 2c52d0d50e9512558d14df53768faadde43a174c [file] [log] [blame]
#include "server.h"
#include "../main/config.h"
#include <rawrtc/config.h>
#include <rawrtc/ice_gather_options.h>
#include <rawrtc/ice_server.h>
#include <rawrtcc/code.h>
#include <rawrtcc/utils.h>
#include <re.h>
#include <string.h> // strlen
#define DEBUG_MODULE "ice-server"
//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
#include <rawrtcc/debug.h>
/*
* ICE server URL-related regular expressions.
*/
static char const ice_server_url_regex[] = "[a-z]+:[^?]+[^]*";
static char const ice_server_host_port_regex[] = "[^:]+[:]*[0-9]*";
static char const ice_server_host_port_ipv6_regex[] = "\\[[0-9a-f:]+\\][:]*[0-9]*";
static char const ice_server_transport_regex[] = "\\?transport=[a-z]+";
/*
* Valid ICE server schemes.
*
* Note: Update `ice_server_scheme_type_mapping`,
* `ice_server_scheme_secure_mapping` and
* `ice_server_scheme_port_mapping` if changed.
*/
static char const* const ice_server_schemes[] = {
"stun",
"stuns",
"turn",
"turns",
};
static size_t const ice_server_schemes_length = ARRAY_SIZE(ice_server_schemes);
/*
* ICE server scheme to server type mapping.
*/
static enum rawrtc_ice_server_type ice_server_scheme_type_mapping[] = {
RAWRTC_ICE_SERVER_TYPE_STUN,
RAWRTC_ICE_SERVER_TYPE_STUN,
RAWRTC_ICE_SERVER_TYPE_TURN,
RAWRTC_ICE_SERVER_TYPE_TURN,
};
/*
* ICE server scheme to secure mapping.
*/
static bool ice_server_scheme_secure_mapping[] = {
false,
true,
false,
true,
};
/*
* ICE server scheme to default port mapping.
*/
static uint_fast16_t ice_server_scheme_port_mapping[] = {
3478,
5349,
3478,
5349,
};
/*
* Valid ICE server transports.
*
* Note: Update `ice_server_transport_normal_transport_mapping` and
* `ice_server_transport_secure_transport_mapping` if changed.
*/
static char const* const ice_server_transports[] = {
"udp",
"tcp",
};
static size_t const ice_server_transports_length = ARRAY_SIZE(ice_server_transports);
/*
* ICE server transport to non-secure transport mapping.
*/
static enum rawrtc_ice_server_transport ice_server_transport_normal_transport_mapping[] = {
RAWRTC_ICE_SERVER_TRANSPORT_UDP,
RAWRTC_ICE_SERVER_TRANSPORT_TCP,
};
/*
* ICE server transport to secure transport mapping.
*/
static enum rawrtc_ice_server_transport ice_server_transport_secure_transport_mapping[] = {
RAWRTC_ICE_SERVER_TRANSPORT_DTLS,
RAWRTC_ICE_SERVER_TRANSPORT_TLS,
};
/*
* Parse ICE server's transport.
*/
static enum rawrtc_code decode_ice_server_transport(
enum rawrtc_ice_server_transport* const transportp, // de-referenced, not checked
struct pl* const query, // not checked
bool const secure) {
enum rawrtc_code error;
struct pl transport;
size_t i;
// Decode transport
error =
rawrtc_error_to_code(re_regex(query->p, query->l, ice_server_transport_regex, &transport));
if (error) {
return error;
}
// Translate transport to ICE server transport
for (i = 0; i < ice_server_transports_length; ++i) {
if (pl_strcmp(&transport, ice_server_transports[i]) == 0) {
if (!secure) {
*transportp = ice_server_transport_normal_transport_mapping[i];
} else {
*transportp = ice_server_transport_secure_transport_mapping[i];
}
return RAWRTC_CODE_SUCCESS;
}
}
// Not found
return RAWRTC_CODE_INVALID_ARGUMENT;
}
/*
* Parse an ICE scheme to an ICE server type, 'secure' flag and
* default port.
*/
static enum rawrtc_code decode_ice_server_scheme(
enum rawrtc_ice_server_type* const typep, // de-referenced, not checked
bool* const securep, // de-referenced, not checked
uint_fast16_t* const portp, // de-referenced, not checked
struct pl* const scheme // not checked
) {
size_t i;
// Translate scheme to ICE server type (and set if secure)
for (i = 0; i < ice_server_schemes_length; ++i) {
if (pl_strcmp(scheme, ice_server_schemes[i]) == 0) {
// Set values
*typep = ice_server_scheme_type_mapping[i];
*securep = ice_server_scheme_secure_mapping[i];
*portp = ice_server_scheme_port_mapping[i];
// Done
return RAWRTC_CODE_SUCCESS;
}
}
// Not found
return RAWRTC_CODE_INVALID_ARGUMENT;
}
/*
* Parse an ICE server URL according to RFC 7064 and RFC 7065
* (although the `transport` part is inaccurate for RFC 7064 but it
* seems useful)
*/
static enum rawrtc_code decode_ice_server_url(
struct rawrtc_ice_server_url* const url // not checked
) {
enum rawrtc_code error;
struct pl scheme;
struct pl host_port;
struct pl query;
bool secure;
struct pl port_pl;
uint_fast16_t port;
// Decode URL
error = rawrtc_error_to_code(
re_regex(url->url, strlen(url->url), ice_server_url_regex, &scheme, &host_port, &query));
if (error) {
DEBUG_WARNING("Invalid ICE server URL: %s\n", url->url);
goto out;
}
// TODO: Can scheme or host be NULL?
// Get server type, secure flag and default port from scheme
error = decode_ice_server_scheme(&url->type, &secure, &port, &scheme);
if (error) {
DEBUG_WARNING("Invalid scheme in ICE server URL (%s): %r\n", url->url, &scheme);
goto out;
}
// Set default address
sa_set_in(&url->resolved_address, INADDR_ANY, (uint16_t) port);
// Decode host: Either IPv4 or IPv6 including the port (if any)
// Try IPv6 first, then normal hostname/IPv4.
error = rawrtc_error_to_code(re_regex(
host_port.p, host_port.l, ice_server_host_port_ipv6_regex, &url->host, NULL, &port_pl));
if (error) {
error = rawrtc_error_to_code(re_regex(
host_port.p, host_port.l, ice_server_host_port_regex, &url->host, NULL, &port_pl));
if (error) {
DEBUG_WARNING(
"Invalid host or port in ICE server URL (%s): %r\n", url->url, &host_port);
goto out;
}
// Try decoding IPv4
sa_set(&url->resolved_address, &url->host, (uint16_t) port);
} else {
// Try decoding IPv6
error = rawrtc_error_to_code(sa_set(&url->resolved_address, &url->host, (uint16_t) port));
if (error) {
DEBUG_WARNING(
"Invalid IPv6 address in ICE server URL (%s): %r\n", url->url, &host_port);
goto out;
}
}
// Decode port (if any)
if (pl_isset(&port_pl)) {
uint_fast32_t port_u32;
// Get port
port_u32 = pl_u32(&port_pl);
if (port_u32 == 0 || port_u32 > UINT16_MAX) {
DEBUG_WARNING(
"Invalid port number in ICE server URL (%s): %" PRIu32 "\n", url->url, port_u32);
error = RAWRTC_CODE_INVALID_ARGUMENT;
goto out;
}
// Set port
sa_set_port(&url->resolved_address, (uint16_t) port_u32);
}
// Translate transport (if any) & secure flag to ICE server transport
if (pl_isset(&query)) {
error = decode_ice_server_transport(&url->transport, &query, secure);
if (error) {
DEBUG_WARNING("Invalid transport in ICE server URL (%s): %r\n", url->url, &query);
goto out;
}
} else {
// Set default transport (depending on secure flag)
if (secure) {
url->transport = rawrtc_default_config.ice_server_secure_transport;
} else {
url->transport = rawrtc_default_config.ice_server_normal_transport;
}
}
// Done
error = RAWRTC_CODE_SUCCESS;
out:
return error;
}
/*
* Destructor for URLs of the ICE gatherer.
*/
static void rawrtc_ice_server_url_destroy(void* arg) {
struct rawrtc_ice_server_url* const url = arg;
// Remove from list
list_unlink(&url->le);
// Un-reference
mem_deref(url->url);
}
/*
* Copy a URL for the ICE gatherer.
*/
static enum rawrtc_code rawrtc_ice_server_url_create(
struct rawrtc_ice_server_url** const urlp, // de-referenced
char* const url_s // copied
) {
struct rawrtc_ice_server_url* url;
enum rawrtc_code error;
// Check arguments
if (!urlp || !url_s) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Allocate
url = mem_zalloc(sizeof(*url), rawrtc_ice_server_url_destroy);
if (!url) {
return RAWRTC_CODE_NO_MEMORY;
}
// Copy URL
error = rawrtc_strdup(&url->url, url_s);
if (error) {
goto out;
}
// Parse URL
// Note: `url->host` points inside `url->url`, so we MUST have copied the URL first.
error = decode_ice_server_url(url);
if (error) {
goto out;
}
// Done
error = RAWRTC_CODE_SUCCESS;
out:
if (error) {
mem_deref(url);
} else {
// Set pointer
*urlp = url;
}
return error;
}
/*
* Destructor for an existing ICE server.
*/
static void rawrtc_ice_server_destroy(void* arg) {
struct rawrtc_ice_server* const server = arg;
// Un-reference
list_flush(&server->urls);
mem_deref(server->username);
mem_deref(server->credential);
}
/*
* Create an ICE server.
*/
enum rawrtc_code rawrtc_ice_server_create(
struct rawrtc_ice_server** const serverp, // de-referenced
char* const* const urls, // copied
size_t const n_urls,
char* const username, // nullable, copied
char* const credential, // nullable, copied
enum rawrtc_ice_credential_type const credential_type) {
struct rawrtc_ice_server* server;
enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
size_t i;
// Check arguments
if (!serverp || !urls) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Allocate
server = mem_zalloc(sizeof(*server), rawrtc_ice_server_destroy);
if (!server) {
return RAWRTC_CODE_NO_MEMORY;
}
// Copy URLs to list
list_init(&server->urls);
for (i = 0; i < n_urls; ++i) {
struct rawrtc_ice_server_url* url;
// Ensure URLs aren't null
if (!urls[i]) {
error = RAWRTC_CODE_INVALID_ARGUMENT;
goto out;
}
// Copy URL
error = rawrtc_ice_server_url_create(&url, urls[i]);
if (error) {
goto out;
}
// Append URL to list
list_append(&server->urls, &url->le, url);
}
// Set fields
if (credential_type != RAWRTC_ICE_CREDENTIAL_TYPE_NONE) {
if (username) {
error = rawrtc_strdup(&server->username, username);
if (error) {
goto out;
}
}
if (credential) {
error = rawrtc_strdup(&server->credential, credential);
if (error) {
goto out;
}
}
}
server->credential_type = credential_type; // TODO: Validation needed in case TOKEN is used?
out:
if (error) {
mem_deref(server);
} else {
// Set pointer
*serverp = server;
}
return error;
}
/*
* Copy an ICE server.
*/
enum rawrtc_code rawrtc_ice_server_copy(
struct rawrtc_ice_server** const serverp, // de-referenced
struct rawrtc_ice_server* const source_server) {
size_t n_urls;
char** urls = NULL;
struct le* le;
size_t i;
enum rawrtc_code error;
// Check arguments
if (!serverp || !source_server) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Create temporary ICE server URL array
n_urls = list_count(&source_server->urls);
if (n_urls > 0) {
urls = mem_alloc(sizeof(char*) * n_urls, NULL);
if (!urls) {
return RAWRTC_CODE_NO_MEMORY;
}
}
// Copy ICE server URL (str) pointers
for (le = list_head(&source_server->urls), i = 0; le != NULL; le = le->next, ++i) {
struct rawrtc_ice_server_url* const url = le->data;
urls[i] = url->url;
}
// Copy
error = rawrtc_ice_server_create(
serverp, urls, n_urls, source_server->username, source_server->credential,
source_server->credential_type);
if (error) {
goto out;
}
out:
// Un-reference
mem_deref(urls);
return error;
}