blob: ceb1a437560552711c76065dcc6430d98647b5dd [file] [log] [blame]
#include "connection.h"
#include "../certificate/certificate.h"
#include "../dtls_transport/transport.h"
#include "../ice_gather_options/options.h"
#include "../ice_gatherer/gatherer.h"
#include "../ice_server/server.h"
#include "../peer_connection_configuration/configuration.h"
#include "../peer_connection_description/description.h"
#include "../peer_connection_ice_candidate/candidate.h"
#include <rawrtc/config.h>
#include <rawrtc/dtls_transport.h>
#include <rawrtc/ice_candidate.h>
#include <rawrtc/ice_gather_options.h>
#include <rawrtc/ice_gatherer.h>
#include <rawrtc/ice_parameters.h>
#include <rawrtc/ice_transport.h>
#include <rawrtc/peer_connection.h>
#include <rawrtc/peer_connection_description.h>
#include <rawrtc/peer_connection_state.h>
#include <rawrtcc/code.h>
#include <rawrtcc/utils.h>
#include <rawrtcdc/data_channel.h>
#include <rawrtcdc/data_channel_parameters.h>
#include <rawrtcdc/data_transport.h>
#include <rawrtcdc/sctp_transport.h>
#include <re.h>
#define DEBUG_MODULE "peer-connection"
//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
#include <rawrtcc/debug.h>
#include <src/peer_connection_configuration/configuration.h>
/*
* Change the signalling state.
* Will call the corresponding handler.
* Caller MUST ensure that the same state is not set twice.
*/
static void set_signaling_state(
struct rawrtc_peer_connection* const connection, // not checked
enum rawrtc_signaling_state const state) {
// Set state
connection->signaling_state = state;
// Call handler (if any)
if (connection->signaling_state_change_handler) {
connection->signaling_state_change_handler(state, connection->arg);
}
}
/*
* Change the connection state to a specific state.
* Will call the corresponding handler.
* Caller MUST ensure that the same state is not set twice.
*/
static void set_connection_state(
struct rawrtc_peer_connection* const connection, // not checked
enum rawrtc_peer_connection_state const state) {
// Set state
connection->connection_state = state;
// Call handler (if any)
if (connection->connection_state_change_handler) {
connection->connection_state_change_handler(state, connection->arg);
}
}
/*
* Update connection state.
* Will call the corresponding handler.
*/
static void update_connection_state(struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
enum rawrtc_ice_transport_state ice_transport_state = RAWRTC_ICE_TRANSPORT_STATE_NEW;
enum rawrtc_dtls_transport_state dtls_transport_state = RAWRTC_DTLS_TRANSPORT_STATE_NEW;
enum rawrtc_peer_connection_state connection_state;
// Nothing beats the closed state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return;
}
// Get ICE transport and DTLS transport states
if (connection->context.ice_transport) {
error =
rawrtc_ice_transport_get_state(&ice_transport_state, connection->context.ice_transport);
if (error) {
DEBUG_WARNING(
"Unable to get ICE transport state, reason: %s\n", rawrtc_error_to_code(error));
}
}
if (connection->context.dtls_transport) {
error = rawrtc_dtls_transport_get_state(
&dtls_transport_state, connection->context.dtls_transport);
if (error) {
DEBUG_WARNING(
"Unable to get DTLS transport state, reason: %s\n", rawrtc_error_to_code(error));
}
}
// Note: This follows the mindbogglingly confusing W3C spec description - it's just not
// super-obvious. We start with states that are easy to detect and remove more and more
// states from the equation.
// Failed: Any in the 'failed' state
if (ice_transport_state == RAWRTC_ICE_TRANSPORT_STATE_FAILED ||
dtls_transport_state == RAWRTC_DTLS_TRANSPORT_STATE_FAILED) {
connection_state = RAWRTC_PEER_CONNECTION_STATE_FAILED;
goto out;
}
// Connecting: Any in the 'connecting' or 'checking' state
if (ice_transport_state == RAWRTC_ICE_TRANSPORT_STATE_CHECKING ||
dtls_transport_state == RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING) {
connection_state = RAWRTC_PEER_CONNECTION_STATE_CONNECTING;
goto out;
}
// Disconnected: Any in the 'disconnected' state
if (ice_transport_state == RAWRTC_ICE_TRANSPORT_STATE_DISCONNECTED) {
connection_state = RAWRTC_PEER_CONNECTION_STATE_DISCONNECTED;
goto out;
}
// New: Any in 'new' or all in 'closed'
if (ice_transport_state == RAWRTC_ICE_TRANSPORT_STATE_NEW ||
dtls_transport_state == RAWRTC_DTLS_TRANSPORT_STATE_NEW ||
(ice_transport_state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED &&
dtls_transport_state == RAWRTC_DTLS_TRANSPORT_STATE_CLOSED)) {
connection_state = RAWRTC_PEER_CONNECTION_STATE_NEW;
goto out;
}
// Connected
connection_state = RAWRTC_PEER_CONNECTION_STATE_CONNECTED;
out:
// Debug
DEBUG_PRINTF(
"ICE (%s) + DTLS (%s) = PC %s\n", rawrtc_ice_transport_state_to_name(ice_transport_state),
rawrtc_dtls_transport_state_to_name(dtls_transport_state),
rawrtc_peer_connection_state_to_name(connection_state));
// Check if the state would change
if (connection->connection_state == connection_state) {
return;
}
// Set state
connection->connection_state = connection_state;
// Call handler (if any)
if (connection->connection_state_change_handler) {
connection->connection_state_change_handler(connection_state, connection->arg);
}
}
/*
* Start the SCTP transport.
*/
static enum rawrtc_code sctp_transport_start(
struct rawrtc_sctp_transport* const sctp_transport, // not checked
struct rawrtc_peer_connection* const connection, // not checked
struct rawrtc_peer_connection_description* const description // not checked
) {
enum rawrtc_code error;
// Start SCTP transport
error = rawrtc_sctp_transport_start(
sctp_transport, description->sctp_capabilities, description->sctp_port);
if (error) {
return error;
}
// Set MTU (if necessary)
if (connection->configuration->sctp.mtu != 0) {
error = rawrtc_sctp_transport_set_mtu(sctp_transport, connection->configuration->sctp.mtu);
if (error) {
return error;
}
}
// Enable path MTU discovery (if necessary)
if (connection->configuration->sctp.mtu_discovery) {
error = rawrtc_sctp_transport_enable_mtu_discovery(sctp_transport);
if (error) {
return error;
}
}
// Done
return RAWRTC_CODE_SUCCESS;
}
/*
* All the nasty SDP stuff has been done. Fire it all up - YAY!
*/
static enum rawrtc_code peer_connection_start(
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
struct rawrtc_peer_connection_context* const context = &connection->context;
struct rawrtc_peer_connection_description* description;
enum rawrtc_ice_role ice_role;
enum rawrtc_data_transport_type data_transport_type;
void* data_transport;
struct le* le;
// Check if it's too early to start
if (!connection->local_description || !connection->remote_description) {
return RAWRTC_CODE_NO_VALUE;
}
DEBUG_INFO("Local and remote description set, starting transports\n");
description = connection->remote_description;
// Determine ICE role
// TODO: Is this correct?
switch (description->type) {
case RAWRTC_SDP_TYPE_OFFER:
ice_role = RAWRTC_ICE_ROLE_CONTROLLED;
break;
case RAWRTC_SDP_TYPE_ANSWER:
ice_role = RAWRTC_ICE_ROLE_CONTROLLING;
break;
default:
DEBUG_WARNING(
"Cannot determine ICE role from SDP type %s, report this!\n",
rawrtc_sdp_type_to_str(description->type));
return RAWRTC_CODE_UNKNOWN_ERROR;
}
// Start ICE transport
error = rawrtc_ice_transport_start(
context->ice_transport, context->ice_gatherer, description->ice_parameters, ice_role);
if (error) {
return error;
}
// Get data transport
error = rawrtc_data_transport_get_transport(
&data_transport_type, &data_transport, context->data_transport);
if (error) {
return error;
}
// Start data transport
switch (data_transport_type) {
case RAWRTC_DATA_TRANSPORT_TYPE_SCTP: {
// Start DTLS transport
error =
rawrtc_dtls_transport_start(context->dtls_transport, description->dtls_parameters);
if (error) {
goto out;
}
// Start SCTP transport
error = sctp_transport_start(data_transport, connection, description);
if (error) {
goto out;
}
break;
}
default:
DEBUG_WARNING(
"Invalid data transport type: %s\n",
rawrtc_data_transport_type_to_str(data_transport_type));
error = RAWRTC_CODE_UNSUPPORTED_PROTOCOL;
goto out;
}
// Add remote ICE candidates
for (le = list_head(&description->ice_candidates); le != NULL; le = le->next) {
struct rawrtc_peer_connection_ice_candidate* const candidate = le->data;
error = rawrtc_peer_connection_add_ice_candidate(connection, candidate);
if (error) {
DEBUG_WARNING(
"Unable to add remote candidate, reason: %s\n", rawrtc_code_to_str(error));
// Note: Continuing here since other candidates may work
}
}
// Done
error = RAWRTC_CODE_SUCCESS;
out:
mem_deref(data_transport);
return error;
}
/*
* Remove all instances that have been created which are not
* associated to the peer connection.
*/
static void revert_context(
struct rawrtc_peer_connection_context* const new, // not checked
struct rawrtc_peer_connection_context* const current // not checked
) {
if (new->data_transport != current->data_transport) {
mem_deref(new->data_transport);
}
if (new->dtls_transport != current->dtls_transport) {
mem_deref(new->dtls_transport);
}
// TODO: This check is brittle...
if (!list_isempty(&new->certificates) && list_isempty(&current->certificates)) {
list_flush(&new->certificates);
}
if (new->ice_transport != current->ice_transport) {
mem_deref(new->ice_transport);
}
if (new->ice_gatherer != current->ice_gatherer) {
mem_deref(new->ice_gatherer);
}
if (new->gather_options != current->gather_options) {
mem_deref(new->gather_options);
}
}
/*
* Apply all instances on a peer connection.
* Return if anything inside the context has changed.
*/
static bool apply_context(
struct rawrtc_peer_connection_context* const new, // not checked
struct rawrtc_peer_connection_context* const current // not checked
) {
bool changed = false;
if (new->data_transport != current->data_transport) {
current->data_transport = new->data_transport;
changed = true;
}
if (new->dtls_transport != current->dtls_transport) {
current->dtls_transport = new->dtls_transport;
str_ncpy(current->dtls_id, new->dtls_id, RAWRTC_DTLS_ID_LENGTH + 1);
changed = true;
}
// TODO: This check is brittle...
if (!list_isempty(&new->certificates) && list_isempty(&current->certificates)) {
current->certificates = new->certificates;
changed = true;
}
if (new->ice_transport != current->ice_transport) {
current->ice_transport = new->ice_transport;
changed = true;
}
if (new->ice_gatherer != current->ice_gatherer) {
current->ice_gatherer = new->ice_gatherer;
changed = true;
}
if (new->gather_options != current->gather_options) {
current->gather_options = new->gather_options;
changed = true;
}
return changed;
}
/*
* Wrap an ORTC ICE candidate to a peer connection ICE candidate.
*/
static enum rawrtc_code local_ortc_candidate_to_candidate(
struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced, not checked
struct rawrtc_ice_candidate* const ortc_candidate, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
char* username_fragment;
struct rawrtc_peer_connection_ice_candidate* candidate;
// Copy username fragment (is going to be referenced later)
error =
rawrtc_strdup(&username_fragment, connection->context.ice_gatherer->ice_username_fragment);
if (error) {
DEBUG_WARNING(
"Unable to copy username fragment from ICE gatherer, reason: %s\n",
rawrtc_code_to_str(error));
return error;
}
// Create candidate
// Note: The local description will exist at this point since we start gathering when the
// local description is being set.
error = rawrtc_peer_connection_ice_candidate_from_ortc_candidate(
&candidate, ortc_candidate, connection->local_description->mid,
&connection->local_description->media_line_index, username_fragment);
if (error) {
goto out;
}
// Set pointer & done
*candidatep = candidate;
error = RAWRTC_CODE_SUCCESS;
out:
// Un-reference
mem_deref(username_fragment);
return error;
}
/*
* Add candidate to description and announce candidate.
*/
static void ice_gatherer_local_candidate_handler(
struct rawrtc_ice_candidate* const ortc_candidate, // nullable
char const* const url, // nullable
void* const arg) {
struct rawrtc_peer_connection* const connection = arg;
enum rawrtc_code error;
struct rawrtc_peer_connection_ice_candidate* candidate = NULL;
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_FAILED ||
connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
DEBUG_NOTICE(
"Ignoring candidate in the %s state\n",
rawrtc_peer_connection_state_to_name(connection->connection_state));
return;
}
// Wrap candidate (if any ORTC candidate)
if (ortc_candidate) {
error = local_ortc_candidate_to_candidate(&candidate, ortc_candidate, connection);
if (error) {
DEBUG_WARNING(
"Unable to create local candidate from ORTC candidate, reason: %s\n",
rawrtc_code_to_str(error));
return;
}
}
// Add candidate (or end-of-candidate) to description
error =
rawrtc_peer_connection_description_add_candidate(connection->local_description, candidate);
if (error) {
DEBUG_WARNING(
"Unable to add local candidate to local description, reason: %s\n",
rawrtc_code_to_str(error));
goto out;
}
// Call handler (if any)
if (connection->local_candidate_handler) {
connection->local_candidate_handler(candidate, url, connection->arg);
}
out:
// Un-reference
mem_deref(candidate);
}
/*
* Announce ICE gatherer error as ICE candidate error.
*/
static void ice_gatherer_error_handler(
struct rawrtc_ice_candidate* const ortc_candidate, // nullable
char const* const url,
uint16_t const error_code,
char const* const error_text,
void* const arg) {
struct rawrtc_peer_connection* const connection = arg;
enum rawrtc_code error;
struct rawrtc_peer_connection_ice_candidate* candidate = NULL;
// Wrap candidate (if any ORTC candidate)
if (ortc_candidate) {
error = local_ortc_candidate_to_candidate(&candidate, ortc_candidate, connection);
if (error) {
DEBUG_WARNING(
"Unable to create local candidate from ORTC candidate, reason: %s\n",
rawrtc_code_to_str(error));
return;
}
}
// Call handler (if any)
if (connection->local_candidate_error_handler) {
connection->local_candidate_error_handler(
candidate, url, error_code, error_text, connection->arg);
}
}
/*
* Filter ICE gatherer state and announce it.
*/
static void ice_gatherer_state_change_handler(
enum rawrtc_ice_gatherer_state const state, void* const arg) {
struct rawrtc_peer_connection* const connection = arg;
// The only difference to the ORTC gatherer states is that there's no 'closed' state.
if (state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
return;
}
// Call handler (if any)
if (connection->ice_gathering_state_change_handler) {
connection->ice_gathering_state_change_handler(state, connection->arg);
}
}
/*
* Lazy-create an ICE gatherer.
*/
static enum rawrtc_code get_ice_gatherer(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
struct rawrtc_ice_gather_options* options;
struct rawrtc_ice_gatherer* gatherer = NULL;
struct le* le;
// Already created?
if (context->ice_gatherer) {
return RAWRTC_CODE_SUCCESS;
}
// Create ICE gather options
error = rawrtc_ice_gather_options_create(&options, connection->configuration->gather_policy);
if (error) {
return error;
}
// Add ICE servers to gather options
for (le = list_head(&connection->configuration->ice_servers); le != NULL; le = le->next) {
struct rawrtc_ice_server* const source_server = le->data;
struct rawrtc_ice_server* server;
// Copy ICE server
error = rawrtc_ice_server_copy(&server, source_server);
if (error) {
goto out;
}
// Add ICE server to gather options
error = rawrtc_ice_gather_options_add_server_internal(options, server);
if (error) {
mem_deref(server);
goto out;
}
}
// Create ICE gatherer
error = rawrtc_ice_gatherer_create(
&gatherer, options, ice_gatherer_state_change_handler, ice_gatherer_error_handler,
ice_gatherer_local_candidate_handler, connection);
if (error) {
goto out;
}
out:
if (error) {
mem_deref(gatherer);
mem_deref(options);
} else {
// Set pointers & done
context->gather_options = options;
context->ice_gatherer = gatherer;
}
return error;
}
static void ice_transport_candidate_pair_change_handler(
struct rawrtc_ice_candidate* const local, // read-only
struct rawrtc_ice_candidate* const remote, // read-only
void* const arg // will be casted to `struct client*`
) {
(void) local;
(void) remote;
(void) arg;
// There's no handler that could potentially print this, so we print it here for debug purposes
DEBUG_PRINTF("ICE transport candidate pair change\n");
}
static void ice_transport_state_change_handler(
enum rawrtc_ice_transport_state const state, void* const arg) {
struct rawrtc_peer_connection* const connection = arg;
// Call handler (if any)
if (connection->ice_connection_state_change_handler) {
connection->ice_connection_state_change_handler(state, connection->arg);
}
// Update connection state
update_connection_state(connection);
}
/*
* Lazy-create an ICE transport.
*/
static enum rawrtc_code get_ice_transport(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
// Already created?
if (context->ice_transport) {
return RAWRTC_CODE_SUCCESS;
}
// Get ICE gatherer
error = get_ice_gatherer(context, connection);
if (error) {
return error;
}
// Create ICE transport
return rawrtc_ice_transport_create(
&context->ice_transport, context->ice_gatherer, ice_transport_state_change_handler,
ice_transport_candidate_pair_change_handler, connection);
}
/*
* Lazy-generate a certificate list.
*/
static enum rawrtc_code get_certificates(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection_configuration* const configuration // not checked
) {
enum rawrtc_code error;
struct rawrtc_certificate* certificate;
// Already created?
if (!list_isempty(&context->certificates)) {
return RAWRTC_CODE_SUCCESS;
}
// Certificates in the configuration? Copy them.
if (!list_isempty(&configuration->certificates)) {
return rawrtc_certificate_list_copy(&context->certificates, &configuration->certificates);
}
// Generate a certificate
error = rawrtc_certificate_generate(&certificate, NULL);
if (error) {
return error;
}
// Add certificate to the list
list_append(&context->certificates, &certificate->le, certificate);
return RAWRTC_CODE_SUCCESS;
}
static void dtls_transport_error_handler(
// TODO: error.message (probably from OpenSSL)
void* const arg) {
(void) arg;
// TODO: Print error message
DEBUG_WARNING("DTLS transport error: %s\n", "???");
}
static void dtls_transport_state_change_handler(
enum rawrtc_dtls_transport_state const state, void* const arg) {
struct rawrtc_peer_connection* connection = arg;
(void) state;
// Update connection state
update_connection_state(connection);
}
/*
* Lazy-create a DTLS transport.
*/
static enum rawrtc_code get_dtls_transport(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
struct list certificates = LIST_INIT;
// Already created?
if (context->dtls_transport) {
return RAWRTC_CODE_SUCCESS;
}
// Get ICE transport
error = get_ice_transport(context, connection);
if (error) {
return error;
}
// Get certificates
error = get_certificates(context, connection->configuration);
if (error) {
return error;
}
// Copy certificates list
error = rawrtc_certificate_list_copy(&certificates, &context->certificates);
if (error) {
return error;
}
// Generate random DTLS ID
rand_str(context->dtls_id, sizeof(context->dtls_id));
// Create DTLS transport
return rawrtc_dtls_transport_create_internal(
&context->dtls_transport, context->ice_transport, &certificates,
dtls_transport_state_change_handler, dtls_transport_error_handler, connection);
}
static void sctp_transport_state_change_handler(
enum rawrtc_sctp_transport_state const state, void* const arg) {
(void) arg;
(void) state;
// There's no handler that could potentially print this, so we print it here for debug purposes
DEBUG_PRINTF("SCTP transport state change: %s\n", rawrtc_sctp_transport_state_to_name(state));
}
/*
* Lazy-create an SCTP transport.
*/
static enum rawrtc_code get_sctp_transport(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
enum rawrtc_code error;
struct rawrtc_sctp_transport* sctp_transport;
// Get DTLS transport
error = get_dtls_transport(context, connection);
if (error) {
return error;
}
// Create SCTP transport
error = rawrtc_sctp_transport_create(
&sctp_transport, context->dtls_transport, RAWRTC_PEER_CONNECTION_SCTP_TRANSPORT_PORT,
connection->data_channel_handler, sctp_transport_state_change_handler, connection->arg);
if (error) {
return error;
}
// Set send/receive buffer length (if necessary)
if (connection->configuration->sctp.send_buffer_length != 0 &&
connection->configuration->sctp.receive_buffer_length != 0) {
error = rawrtc_sctp_transport_set_buffer_length(
sctp_transport, connection->configuration->sctp.send_buffer_length,
connection->configuration->sctp.receive_buffer_length);
if (error) {
goto out;
}
}
// Set congestion control algorithm (if necessary)
if (connection->configuration->sctp.congestion_ctrl_algorithm !=
RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581) {
error = rawrtc_sctp_transport_set_congestion_ctrl_algorithm(
sctp_transport, connection->configuration->sctp.congestion_ctrl_algorithm);
if (error) {
goto out;
}
}
// Get data transport
error = rawrtc_sctp_transport_get_data_transport(&context->data_transport, sctp_transport);
if (error) {
goto out;
}
out:
// Un-reference
// Note: As the data transport has a reference to the SCTP transport, we can
// still retrieve the reference later.
mem_deref(sctp_transport);
return error;
}
/*
* Lazy-create the requested data transport.
*/
static enum rawrtc_code get_data_transport(
struct rawrtc_peer_connection_context* const context, // not checked
struct rawrtc_peer_connection* const connection // not checked
) {
// Already created?
if (context->data_transport) {
return RAWRTC_CODE_SUCCESS;
}
// Create data transport depending on what we want to have
switch (connection->data_transport_type) {
case RAWRTC_DATA_TRANSPORT_TYPE_SCTP: {
return get_sctp_transport(context, connection);
}
default:
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
}
/*
* Destructor for an existing peer connection.
*/
static void rawrtc_peer_connection_destroy(void* arg) {
struct rawrtc_peer_connection* const connection = arg;
// Unset all handlers
rawrtc_peer_connection_unset_handlers(connection);
// Close peer connection
rawrtc_peer_connection_close(connection);
// Un-reference
mem_deref(connection->context.data_transport);
mem_deref(connection->context.dtls_transport);
list_flush(&connection->context.certificates);
mem_deref(connection->context.ice_transport);
mem_deref(connection->context.ice_gatherer);
mem_deref(connection->context.gather_options);
mem_deref(connection->remote_description);
mem_deref(connection->local_description);
mem_deref(connection->configuration);
}
/*
* Create a new peer connection.
* `*connectionp` must be unreferenced.
*/
enum rawrtc_code rawrtc_peer_connection_create(
struct rawrtc_peer_connection** const connectionp, // de-referenced
struct rawrtc_peer_connection_configuration* configuration, // referenced
rawrtc_negotiation_needed_handler const negotiation_needed_handler, // nullable
rawrtc_peer_connection_local_candidate_handler const local_candidate_handler, // nullable
rawrtc_peer_connection_local_candidate_error_handler const
local_candidate_error_handler, // nullable
rawrtc_signaling_state_change_handler const signaling_state_change_handler, // nullable
rawrtc_ice_transport_state_change_handler const
ice_connection_state_change_handler, // nullable
rawrtc_ice_gatherer_state_change_handler const ice_gathering_state_change_handler, // nullable
rawrtc_peer_connection_state_change_handler const connection_state_change_handler, // nullable
rawrtc_data_channel_handler const data_channel_handler, // nullable
void* const arg // nullable
) {
struct rawrtc_peer_connection* connection;
// Check arguments
if (!connectionp) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Allocate
connection = mem_zalloc(sizeof(*connection), rawrtc_peer_connection_destroy);
if (!connection) {
return RAWRTC_CODE_NO_MEMORY;
}
// Set fields/reference
connection->connection_state = RAWRTC_PEER_CONNECTION_STATE_NEW;
connection->signaling_state = RAWRTC_SIGNALING_STATE_STABLE;
connection->configuration = mem_ref(configuration);
connection->negotiation_needed_handler = negotiation_needed_handler;
connection->local_candidate_handler = local_candidate_handler;
connection->local_candidate_error_handler = local_candidate_error_handler;
connection->signaling_state_change_handler = signaling_state_change_handler;
connection->ice_connection_state_change_handler = ice_connection_state_change_handler;
connection->ice_gathering_state_change_handler = ice_gathering_state_change_handler;
connection->connection_state_change_handler = connection_state_change_handler;
connection->data_channel_handler = data_channel_handler;
connection->data_transport_type = RAWRTC_DATA_TRANSPORT_TYPE_SCTP;
connection->arg = arg;
// Set pointer & done
*connectionp = connection;
return RAWRTC_CODE_SUCCESS;
}
/*
* Close the peer connection. This will stop all underlying transports
* and results in a final 'closed' state.
*/
enum rawrtc_code rawrtc_peer_connection_close(struct rawrtc_peer_connection* const connection) {
enum rawrtc_code error;
// Check arguments
if (!connection) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_SUCCESS;
}
// Update signalling & connection state
// Note: We need to do this early or the 'closed' states when tearing down the transports may
// lead to surprising peer connection states such as 'connected' at the very end.
set_signaling_state(connection, RAWRTC_SIGNALING_STATE_CLOSED);
set_connection_state(connection, RAWRTC_PEER_CONNECTION_STATE_CLOSED);
// Stop data transport (if any)
if (connection->context.data_transport) {
enum rawrtc_data_transport_type data_transport_type;
void* data_transport;
// Get data transport
error = rawrtc_data_transport_get_transport(
&data_transport_type, &data_transport, connection->context.data_transport);
if (error) {
DEBUG_WARNING("Unable to get data transport, reason: %s\n", rawrtc_code_to_str(error));
} else {
// Stop transport
switch (data_transport_type) {
case RAWRTC_DATA_TRANSPORT_TYPE_SCTP: {
struct rawrtc_sctp_transport* const sctp_transport = data_transport;
error = rawrtc_sctp_transport_stop(sctp_transport);
if (error) {
DEBUG_WARNING(
"Unable to stop SCTP transport, reason: %s\n",
rawrtc_code_to_str(error));
}
break;
}
default:
DEBUG_WARNING(
"Invalid data transport type: %s\n",
rawrtc_data_transport_type_to_str(data_transport_type));
break;
}
// Un-reference
mem_deref(data_transport);
}
}
// Stop DTLS transport (if any)
if (connection->context.dtls_transport) {
error = rawrtc_dtls_transport_stop(connection->context.dtls_transport);
if (error) {
DEBUG_WARNING("Unable to stop DTLS transport, reason: %s\n", rawrtc_code_to_str(error));
}
}
// Stop ICE transport (if any)
if (connection->context.ice_transport) {
error = rawrtc_ice_transport_stop(connection->context.ice_transport);
if (error) {
DEBUG_WARNING("Unable to stop ICE transport, reason: %s\n", rawrtc_code_to_str(error));
}
}
// Close ICE gatherer (if any)
if (connection->context.ice_gatherer) {
error = rawrtc_ice_gatherer_close(connection->context.ice_gatherer);
if (error) {
DEBUG_WARNING("Unable to close ICE gatherer, reason: %s\n", rawrtc_code_to_str(error));
}
}
// Done
return RAWRTC_CODE_SUCCESS;
}
/*
* Create an offer.
* `*descriptionp` must be unreferenced.
*/
enum rawrtc_code rawrtc_peer_connection_create_offer(
struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
struct rawrtc_peer_connection* const connection,
bool const ice_restart) {
// Check arguments
if (!connection) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// TODO: Support ICE restart
if (ice_restart) {
DEBUG_WARNING("ICE restart currently not supported\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// TODO: Allow subsequent offers
if (connection->local_description) {
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Create description
return rawrtc_peer_connection_description_create_internal(descriptionp, connection, true);
}
/*
* Create an answer.
* `*descriptionp` must be unreferenced.
*/
enum rawrtc_code rawrtc_peer_connection_create_answer(
struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
struct rawrtc_peer_connection* const connection) {
// Check arguments
if (!connection) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// TODO: Allow subsequent answers
if (connection->local_description) {
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Create description
return rawrtc_peer_connection_description_create_internal(descriptionp, connection, false);
}
/*
* Set and apply the local description.
*/
enum rawrtc_code rawrtc_peer_connection_set_local_description(
struct rawrtc_peer_connection* const connection,
struct rawrtc_peer_connection_description* const description // referenced
) {
bool initial_description = true;
enum rawrtc_code error;
// Check arguments
if (!connection || !description) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// Ensure it has been created by the local peer connection.
if (description->connection != connection) {
// Yeah, sorry, nope, I'm not parsing all this SDP nonsense again just to check
// what kind of nasty things could have been done in the meantime.
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// TODO: Allow changing the local description
if (connection->local_description) {
initial_description = false;
(void) initial_description;
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// We only accept 'offer' or 'answer' at the moment
// TODO: Handle the other ones as well
if (description->type != RAWRTC_SDP_TYPE_OFFER && description->type != RAWRTC_SDP_TYPE_ANSWER) {
DEBUG_WARNING("Only 'offer' or 'answer' descriptions can be handled at the moment\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Check SDP type
DEBUG_PRINTF(
"Set local description: %s (local), %s (remote)\n",
rawrtc_sdp_type_to_str(description->type),
connection->remote_description
? rawrtc_sdp_type_to_str(connection->remote_description->type)
: "n/a");
if (connection->remote_description) {
switch (description->type) {
case RAWRTC_SDP_TYPE_OFFER:
// We have a remote description and get an offer. This requires renegotiation we
// currently don't support.
// TODO: Add support for this
DEBUG_WARNING("There's no support for renegotiation at the moment.\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
case RAWRTC_SDP_TYPE_ANSWER:
// We have a remote description and get an answer. Sanity-check that the remote
// description is an offer.
if (connection->remote_description->type != RAWRTC_SDP_TYPE_OFFER) {
DEBUG_WARNING(
"Got 'answer' but remote description is '%s'\n",
rawrtc_sdp_type_to_str(connection->remote_description->type));
return RAWRTC_CODE_INVALID_STATE;
}
break;
default:
DEBUG_WARNING("Unknown SDP type, please report this!\n");
return RAWRTC_CODE_UNKNOWN_ERROR;
}
} else {
switch (description->type) {
case RAWRTC_SDP_TYPE_OFFER:
// We have no remote description and get an offer. Fine.
break;
case RAWRTC_SDP_TYPE_ANSWER:
// We have no remote description and get an answer. Not going to work.
DEBUG_WARNING("Got 'answer' but have no remote description\n");
return RAWRTC_CODE_INVALID_STATE;
default:
DEBUG_WARNING("Unknown SDP type, please report this!\n");
return RAWRTC_CODE_UNKNOWN_ERROR;
}
}
// Remove reference to self
description->connection = mem_deref(description->connection);
// Set local description
connection->local_description = mem_ref(description);
// Start gathering (if initial description)
if (initial_description) {
error = rawrtc_ice_gatherer_gather(connection->context.ice_gatherer, NULL);
if (error) {
DEBUG_WARNING("Unable to start gathering, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
}
// Start peer connection if both description are set
error = peer_connection_start(connection);
if (error && error != RAWRTC_CODE_NO_VALUE) {
DEBUG_WARNING("Unable to start peer connection, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
// Update signalling state
switch (connection->signaling_state) {
case RAWRTC_SIGNALING_STATE_STABLE:
// Can only be an offer or it would not have been accepted
set_signaling_state(connection, RAWRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER);
break;
case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER:
// Update of the local offer, nothing to do
break;
case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER:
// Can only be an answer or it would not have been accepted
// Note: This may change once we accept PR answers
set_signaling_state(connection, RAWRTC_SIGNALING_STATE_STABLE);
break;
case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_PROVISIONAL_ANSWER:
// Impossible state
// Note: This may change once we accept PR answers
break;
case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_PROVISIONAL_ANSWER:
// Impossible state
// Note: This may change once we accept PR answers
break;
case RAWRTC_SIGNALING_STATE_CLOSED:
// Impossible state
break;
}
// Done
return RAWRTC_CODE_SUCCESS;
}
/*
* Set and apply the remote description.
*/
enum rawrtc_code rawrtc_peer_connection_set_remote_description(
struct rawrtc_peer_connection* const connection,
struct rawrtc_peer_connection_description* const description // referenced
) {
enum rawrtc_code error;
struct rawrtc_peer_connection_context context;
// Check arguments
if (!connection || !description) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// TODO: Allow changing the remote description
if (connection->remote_description) {
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// We only accept 'offer' or 'answer' at the moment
// TODO: Handle the other ones as well
if (description->type != RAWRTC_SDP_TYPE_OFFER && description->type != RAWRTC_SDP_TYPE_ANSWER) {
DEBUG_WARNING("Only 'offer' or 'answer' descriptions can be handled at the moment\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Check SDP type
DEBUG_PRINTF(
"Set remote description: %s (local), %s (remote)\n",
connection->local_description ? rawrtc_sdp_type_to_str(connection->local_description->type)
: "n/a",
rawrtc_sdp_type_to_str(description->type));
if (connection->local_description) {
switch (description->type) {
case RAWRTC_SDP_TYPE_OFFER:
// We have a local description and get an offer. This requires renegotiation we
// currently don't support.
// TODO: Add support for this
DEBUG_WARNING("There's no support for renegotiation at the moment.\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
case RAWRTC_SDP_TYPE_ANSWER:
// We have a local description and get an answer. Sanity-check that the local
// description is an offer.
if (connection->local_description->type != RAWRTC_SDP_TYPE_OFFER) {
DEBUG_WARNING(
"Got 'answer' but local description is '%s'\n",
rawrtc_sdp_type_to_str(connection->local_description->type));
return RAWRTC_CODE_INVALID_STATE;
}
break;
default:
DEBUG_WARNING("Unknown SDP type, please report this!\n");
return RAWRTC_CODE_UNKNOWN_ERROR;
}
} else {
switch (description->type) {
case RAWRTC_SDP_TYPE_OFFER:
// We have no local description and get an offer. Fine.
break;
case RAWRTC_SDP_TYPE_ANSWER:
// We have no local description and get an answer. Not going to work.
DEBUG_WARNING("Got 'answer' but have no local description\n");
return RAWRTC_CODE_INVALID_STATE;
default:
DEBUG_WARNING("Unknown SDP type, please report this!\n");
return RAWRTC_CODE_UNKNOWN_ERROR;
}
}
// No trickle ICE? Ensure we have all candidates
if (!description->trickle_ice && !description->end_of_candidates) {
DEBUG_NOTICE("No trickle ICE indicated but don't have all candidates\n");
// Note: We continue since we still accept further candidates.
}
// No remote media 'application' line?
if (!description->remote_media_line) {
DEBUG_WARNING("No remote media 'application' line for data channels found\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// No ICE parameters?
// Note: We either have valid ICE parameters or none at this point
if (!description->ice_parameters) {
DEBUG_WARNING("Required ICE parameters not present\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// No DTLS parameters?
// Note: We either have valid DTLS parameters or none at this point
if (!description->dtls_parameters) {
DEBUG_WARNING("Required DTLS parameters not present\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// No SCTP capabilities or port?
// Note: We either have valid SCTP capabilities or none at this point
if (!description->sctp_capabilities) {
DEBUG_WARNING("Required SCTP capabilities not present\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
if (description->sctp_port == 0) {
DEBUG_WARNING("Invalid SCTP port (0)\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Set remote description
connection->remote_description = mem_ref(description);
// Initialise context
context = connection->context;
// Create a data transport if we're answering
if (description->type == RAWRTC_SDP_TYPE_OFFER) {
// Get data transport
error = get_data_transport(&context, connection);
if (error) {
DEBUG_WARNING(
"Unable to create data transport, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
// Apply context
apply_context(&context, &connection->context);
}
// Start peer connection if both descriptions are set
error = peer_connection_start(connection);
if (error && error != RAWRTC_CODE_NO_VALUE) {
DEBUG_WARNING("Unable to start peer connection, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
// Update signalling state
switch (connection->signaling_state) {
case RAWRTC_SIGNALING_STATE_STABLE:
// Can only be an offer or it would not have been accepted
set_signaling_state(connection, RAWRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER);
break;
case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER:
// Can only be an answer or it would not have been accepted
// Note: This may change once we accept PR answers
set_signaling_state(connection, RAWRTC_SIGNALING_STATE_STABLE);
break;
case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER:
// Update of the remote offer, nothing to do
break;
case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_PROVISIONAL_ANSWER:
// Impossible state
// Note: This may change once we accept PR answers
break;
case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_PROVISIONAL_ANSWER:
// Impossible state
// Note: This may change once we accept PR answers
break;
case RAWRTC_SIGNALING_STATE_CLOSED:
// Impossible state
break;
}
// Done
return RAWRTC_CODE_SUCCESS;
}
/*
* Add an ICE candidate to the peer connection.
*/
enum rawrtc_code rawrtc_peer_connection_add_ice_candidate(
struct rawrtc_peer_connection* const connection,
struct rawrtc_peer_connection_ice_candidate* const candidate) {
enum rawrtc_code error;
struct rawrtc_peer_connection_description* description;
// Check arguments
if (!connection || !candidate) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// Ensure there's a remote description
description = connection->remote_description;
if (!description) {
return RAWRTC_CODE_INVALID_STATE;
}
// Note: We can be sure that either 'mid' or the media line index is present at this point.
// Check if the 'mid' matches (if any)
// TODO: Once we support further media lines, we need to look up the appropriate transport here
if (candidate->mid && description->mid && str_cmp(candidate->mid, description->mid) != 0) {
DEBUG_WARNING("No matching 'mid' in remote description\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check if the media line index matches (if any)
if (candidate->media_line_index >= 0 && candidate->media_line_index <= UINT8_MAX &&
((uint8_t) candidate->media_line_index) != description->media_line_index) {
DEBUG_WARNING("No matching media line index in remote description\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check if the username fragment matches (if any)
// TODO: This would need to be done across ICE generations
if (candidate->username_fragment) {
char* username_fragment;
bool matching;
// Get username fragment from the remote ICE parameters
error = rawrtc_ice_parameters_get_username_fragment(
&username_fragment, description->ice_parameters);
if (error) {
DEBUG_WARNING(
"Unable to retrieve username fragment, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
// Compare username fragments
matching = str_cmp(candidate->username_fragment, username_fragment) == 0;
mem_deref(username_fragment);
if (!matching) {
DEBUG_WARNING("Username fragments don't match\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
}
// Add ICE candidate
return rawrtc_ice_transport_add_remote_candidate(
connection->context.ice_transport, candidate->candidate);
}
/*
* Create a data channel on a peer connection.
* `*channelp` must be unreferenced.
*/
enum rawrtc_code rawrtc_peer_connection_create_data_channel(
struct rawrtc_data_channel** const channelp, // de-referenced
struct rawrtc_peer_connection* const connection,
struct rawrtc_data_channel_parameters* const parameters, // referenced
rawrtc_data_channel_open_handler const open_handler, // nullable
rawrtc_data_channel_buffered_amount_low_handler const buffered_amount_low_handler, // nullable
rawrtc_data_channel_error_handler const error_handler, // nullable
rawrtc_data_channel_close_handler const close_handler, // nullable
rawrtc_data_channel_message_handler const message_handler, // nullable
void* const arg // nullable
) {
enum rawrtc_code error;
struct rawrtc_peer_connection_context context;
struct rawrtc_data_channel* channel = NULL;
// Check arguments
if (!connection) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Check state
if (connection->connection_state == RAWRTC_PEER_CONNECTION_STATE_CLOSED) {
return RAWRTC_CODE_INVALID_STATE;
}
// Initialise context
context = connection->context;
// Get data transport (if no description has been set, yet)
if (!connection->local_description && !connection->remote_description) {
error = get_data_transport(&context, connection);
if (error) {
DEBUG_WARNING(
"Unable to create data transport, reason: %s\n", rawrtc_code_to_str(error));
return error;
}
}
// Create data channel
// TODO: Fix data channel cannot be created before transports have been started
error = rawrtc_data_channel_create(
&channel, context.data_transport, parameters, open_handler, buffered_amount_low_handler,
error_handler, close_handler, message_handler, arg);
if (error) {
goto out;
}
out:
if (error) {
// Un-reference
mem_deref(channel);
// Remove all newly created instances
revert_context(&context, &connection->context);
} else {
// Apply context
bool const negotiation_needed = apply_context(&context, &connection->context);
// Set pointer
*channelp = channel;
// Negotiation needed?
if (negotiation_needed) {
connection->negotiation_needed_handler(connection->arg);
}
}
return error;
}