blob: b4da9eb4d3a7bc2ff288932691f3b1328e6193a5 [file] [log] [blame]
#include "description.h"
#include "../dtls_fingerprint/fingerprint.h"
#include "../dtls_parameters/parameters.h"
#include "../peer_connection/connection.h"
#include "../peer_connection_configuration/configuration.h"
#include "../peer_connection_description/description.h"
#include "../peer_connection_ice_candidate/candidate.h"
#include <rawrtc/certificate.h>
#include <rawrtc/config.h>
#include <rawrtc/dtls_fingerprint.h>
#include <rawrtc/dtls_parameters.h>
#include <rawrtc/dtls_transport.h>
#include <rawrtc/ice_parameters.h>
#include <rawrtc/peer_connection_description.h>
#include <rawrtc/peer_connection_ice_candidate.h>
#include <rawrtcc/code.h>
#include <rawrtcc/utils.h>
#include <rawrtcdc/data_transport.h>
#include <rawrtcdc/sctp_capabilities.h>
#include <rawrtcdc/sctp_transport.h>
#include <re.h>
#include <string.h> // strlen
#define DEBUG_MODULE "peer-connection-description"
//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
#include <rawrtcc/debug.h>
// Constants
static uint16_t const discard_port = 9;
static char const sdp_application_dtls_sctp_regex[] = "application [0-9]+ [^ ]+";
static char const* const sdp_application_dtls_sctp_variants[] = {
"DTLS/SCTP",
"UDP/DTLS/SCTP",
"TCP/DTLS/SCTP",
};
static size_t const sdp_application_dtls_sctp_variants_length =
ARRAY_SIZE(sdp_application_dtls_sctp_variants);
static char const sdp_group_regex[] = "group:BUNDLE [^]+";
static char const sdp_mid_regex[] = "mid:[^]+";
static char const sdp_ice_options_trickle[] = "ice-options:trickle";
static char const sdp_ice_username_fragment_regex[] = "ice-ufrag:[^]+";
static char const sdp_ice_password_regex[] = "ice-pwd:[^]+";
static char const sdp_ice_lite[] = "ice-lite";
static char const sdp_dtls_role_regex[] = "setup:[^]+";
static enum rawrtc_dtls_role const map_enum_dtls_role[] = {
RAWRTC_DTLS_ROLE_AUTO,
RAWRTC_DTLS_ROLE_CLIENT,
RAWRTC_DTLS_ROLE_SERVER,
};
static char const* const map_str_dtls_role[] = {
"actpass",
"active",
"passive",
};
static size_t const map_dtls_role_length = ARRAY_SIZE(map_enum_dtls_role);
static char const sdp_dtls_fingerprint_regex[] = "fingerprint:[^ ]+ [^]+";
static char const sdp_sctp_port_sctmap_regex[] = "sctpmap:[0-9]+[^]*";
static char const sdp_sctp_port_regex[] = "sctp-port:[0-9]+";
static char const sdp_sctp_maximum_message_size_regex[] = "max-message-size:[0-9]+";
static char const sdp_ice_end_of_candidates[] = "end-of-candidates";
static char const sdp_ice_candidate_head[] = "candidate:";
static size_t const sdp_ice_candidate_head_length = ARRAY_SIZE(sdp_ice_candidate_head);
// Candidate line
struct candidate_line {
struct le le;
struct pl line;
};
/*
* Set session boilerplate
*/
static enum rawrtc_code set_session_boilerplate(
struct mbuf* const sdp, // not checked
char const* const version, // not checked
uint32_t const id) {
int err;
// Write session boilerplate
err = mbuf_write_str(sdp, "v=0\r\n");
err |=
mbuf_printf(sdp, "o=sdpartanic-rawrtc-%s %" PRIu32 " 1 IN IP4 127.0.0.1\r\n", version, id);
err |= mbuf_write_str(sdp, "s=-\r\n");
err |= mbuf_write_str(sdp, "t=0 0\r\n");
// Done
return rawrtc_error_to_code(err);
}
/*
* Set session attributes on SDP.
*/
static enum rawrtc_code set_session_attributes(
struct mbuf* const sdp, // not checked
bool const trickle_ice,
char const* const bundled_mids) {
int err = 0;
// Trickle ICE
if (trickle_ice) {
err = mbuf_write_str(sdp, "a=ice-options:trickle\r\n");
}
// WebRTC identity not supported as of now
// Bundle media (we currently only support a single SCTP transport and nothing else)
if (bundled_mids) {
err |= mbuf_printf(sdp, "a=group:BUNDLE %s\r\n", bundled_mids);
}
// Done
return rawrtc_error_to_code(err);
}
/*
* Get general attributes from an SDP line.
*/
static enum rawrtc_code get_general_attributes(
char** const bundled_midsp, // de-referenced, not checked
char** const midp, // de-referenced, not checked
struct pl* const line // not checked
) {
enum rawrtc_code error;
struct pl value;
// Bundle groups
if (!re_regex(line->p, line->l, sdp_group_regex, &value)) {
// Check if there is more than one group
if (pl_strchr(&value, ' ')) {
DEBUG_WARNING("Only one bundle group is supported\n");
error = RAWRTC_CODE_NOT_IMPLEMENTED;
return error;
}
// Copy group
error = rawrtc_error_to_code(pl_strdup(bundled_midsp, &value));
if (error) {
DEBUG_WARNING("Couldn't copy bundle group\n");
return error;
}
}
// Media line identification tag
if (!re_regex(line->p, line->l, sdp_mid_regex, &value)) {
// Copy 'mid'
error = rawrtc_error_to_code(pl_strdup(midp, &value));
if (error) {
DEBUG_WARNING("Couldn't copy 'mid'\n");
return error;
}
}
// Done
return RAWRTC_CODE_NO_VALUE;
}
/*
* Add ICE attributes to SDP media line.
*/
static enum rawrtc_code add_ice_attributes(
struct mbuf* const sdp, // not checked
struct rawrtc_peer_connection_context* const context // not checked
) {
enum rawrtc_code error;
struct rawrtc_ice_parameters* parameters;
char* username_fragment = NULL;
char* password = NULL;
int err;
// Get ICE parameters
error = rawrtc_ice_gatherer_get_local_parameters(&parameters, context->ice_gatherer);
if (error) {
return error;
}
// Get values
error = rawrtc_ice_parameters_get_username_fragment(&username_fragment, parameters);
error |= rawrtc_ice_parameters_get_password(&password, parameters);
if (error) {
goto out;
}
// Set username fragment and password
err = mbuf_printf(sdp, "a=ice-ufrag:%s\r\n", username_fragment);
err |= mbuf_printf(sdp, "a=ice-pwd:%s\r\n", password);
error = rawrtc_error_to_code(err);
out:
mem_deref(password);
mem_deref(username_fragment);
mem_deref(parameters);
return error;
}
/*
* Get ICE attributes from SDP line.
*/
static enum rawrtc_code get_ice_attributes(
bool* const trickle_icep, // de-referenced, not checked
char** const username_fragmentp, // de-referenced, not checked
char** const passwordp, // de-referenced, not checked
bool* const ice_litep, // de-referenced, not checked
struct pl* const line // not checked
) {
struct pl value;
// ICE options trickle
if (pl_strcmp(line, sdp_ice_options_trickle) == 0) {
*trickle_icep = true;
return RAWRTC_CODE_SUCCESS;
}
// ICE username fragment
if (!re_regex(line->p, line->l, sdp_ice_username_fragment_regex, &value)) {
return rawrtc_sdprintf(username_fragmentp, "%r", &value);
}
// ICE password
if (!re_regex(line->p, line->l, sdp_ice_password_regex, &value)) {
return rawrtc_sdprintf(passwordp, "%r", &value);
}
// ICE lite
if (pl_strcmp(line, sdp_ice_lite) == 0) {
*ice_litep = true;
return RAWRTC_CODE_SUCCESS;
}
// Done
return RAWRTC_CODE_NO_VALUE;
}
/*
* Add DTLS fingerprint attributes to SDP media line.
*/
static enum rawrtc_code add_dtls_fingerprint_attributes(
struct mbuf* const sdp, // not checked
struct rawrtc_dtls_parameters* const parameters // not checked
) {
enum rawrtc_code error;
struct rawrtc_dtls_fingerprints* fingerprints;
size_t i;
char* value = NULL;
// Get fingerprints
error = rawrtc_dtls_parameters_get_fingerprints(&fingerprints, parameters);
if (error) {
return error;
}
// Add fingerprints
for (i = 0; i < fingerprints->n_fingerprints; ++i) {
struct rawrtc_dtls_fingerprint* const fingerprint = fingerprints->fingerprints[i];
enum rawrtc_certificate_sign_algorithm sign_algorithm;
// Get sign algorithm
error = rawrtc_dtls_fingerprint_get_sign_algorithm(&sign_algorithm, fingerprint);
if (error) {
goto out;
}
// Get fingerprint value
error = rawrtc_dtls_fingerprint_get_value(&value, fingerprint);
if (error) {
goto out;
}
// Add fingerprint attribute
error = rawrtc_error_to_code(mbuf_printf(
sdp, "a=fingerprint:%s %s\r\n",
rawrtc_certificate_sign_algorithm_to_str(sign_algorithm), value));
if (error) {
goto out;
}
}
// Done
error = RAWRTC_CODE_SUCCESS;
out:
// Un-reference
mem_deref(value);
mem_deref(fingerprints);
return error;
}
/*
* Get DTLS fingerprint attribute from an SDP line.
*/
static enum rawrtc_code get_dtls_fingerprint_attributes(
struct rawrtc_dtls_fingerprint** const fingerprintp, // de-referenced, not checked
struct pl* const line // not checked
) {
struct pl algorithm_pl;
struct pl value_pl;
enum rawrtc_code error;
char* algorithm_str = NULL;
char* value_str = NULL;
enum rawrtc_certificate_sign_algorithm algorithm;
// Parse DTLS fingerprint
if (re_regex(line->p, line->l, sdp_dtls_fingerprint_regex, &algorithm_pl, &value_pl)) {
return RAWRTC_CODE_NO_VALUE;
}
// Copy certificate sign algorithm and value to string
error = rawrtc_sdprintf(&algorithm_str, "%r", &algorithm_pl);
if (error) {
goto out;
}
error = rawrtc_sdprintf(&value_str, "%r", &value_pl);
if (error) {
goto out;
}
// Convert certificate sign algorithm
error = rawrtc_str_to_certificate_sign_algorithm(&algorithm, algorithm_str);
if (error) {
// This is allowed to fail, some people still use SHA-1 and we don't support it. But there
// may be further fingerprints.
DEBUG_WARNING("Unsupported certificate sign algorithm: %r\n", &algorithm_pl);
error = RAWRTC_CODE_NO_VALUE;
goto out;
}
// Create DTLS fingerprint
error = rawrtc_dtls_fingerprint_create(fingerprintp, algorithm, value_str);
if (error) {
goto out;
}
out:
// Un-reference
mem_deref(value_str);
mem_deref(algorithm_str);
return error;
}
/*
* Add DTLS transport attributes to SDP media line.
*/
static enum rawrtc_code add_dtls_attributes(
struct mbuf* const sdp, // not checked
struct rawrtc_peer_connection_context* const context, // not checked
bool const offering) {
enum rawrtc_code error;
struct rawrtc_dtls_parameters* parameters;
enum rawrtc_dtls_role role;
char const* setup_str;
// Get DTLS parameters
error = rawrtc_dtls_transport_get_local_parameters(&parameters, context->dtls_transport);
if (error) {
return error;
}
// Get DTLS role
error = rawrtc_dtls_parameters_get_role(&role, parameters);
if (error) {
goto out;
}
// Add setup attribute
if (offering) {
// Note: When offering, we MUST use 'actpass' as specified in JSEP
setup_str = "actpass";
} else {
switch (role) {
case RAWRTC_DTLS_ROLE_AUTO:
setup_str = "active";
break;
case RAWRTC_DTLS_ROLE_CLIENT:
setup_str = "active";
break;
case RAWRTC_DTLS_ROLE_SERVER:
setup_str = "passive";
break;
default:
error = RAWRTC_CODE_INVALID_STATE;
goto out;
}
}
error = rawrtc_error_to_code(mbuf_printf(sdp, "a=setup:%s\r\n", setup_str));
if (error) {
goto out;
}
// Add fingerprints
error = add_dtls_fingerprint_attributes(sdp, parameters);
if (error) {
goto out;
}
// Add (D)TLS ID
error = rawrtc_error_to_code(mbuf_printf(sdp, "a=tls-id:%s\r\n", context->dtls_id));
if (error) {
goto out;
}
out:
mem_deref(parameters);
return error;
}
/*
* Get DTLS transport attribute from an SDP line.
*/
static enum rawrtc_code get_dtls_attributes(
enum rawrtc_dtls_role* const rolep, // de-referenced, not checked
struct list* const fingerprints, // not checked
struct pl* const line // not checked
) {
enum rawrtc_code error;
struct pl role_pl;
struct rawrtc_dtls_fingerprint* fingerprint;
// DTLS role
if (!re_regex(line->p, line->l, sdp_dtls_role_regex, &role_pl)) {
size_t i;
for (i = 0; i < map_dtls_role_length; ++i) {
if (pl_strcmp(&role_pl, map_str_dtls_role[i]) == 0) {
*rolep = map_enum_dtls_role[i];
return RAWRTC_CODE_SUCCESS;
}
}
}
// DTLS fingerprint
error = get_dtls_fingerprint_attributes(&fingerprint, line);
if (!error) {
list_append(fingerprints, &fingerprint->le, fingerprint);
}
return error;
}
/*
* Add SCTP transport attributes to SDP session.
*/
static enum rawrtc_code add_sctp_attributes(
struct mbuf* const sdp, // not checked
struct rawrtc_sctp_transport* const transport, // not checked
struct rawrtc_peer_connection_context* const context, // not checked
bool const offering,
char const* const remote_media_line,
char const* const mid,
bool const sctp_sdp_05) {
enum rawrtc_code error;
uint16_t sctp_port;
uint16_t sctp_n_streams;
int err;
// Get SCTP port
error = rawrtc_sctp_transport_get_port(&sctp_port, transport);
if (error) {
return error;
}
// Get SCTP #streams
error = rawrtc_sctp_transport_get_n_streams(&sctp_n_streams, transport);
if (error) {
return error;
}
// Add media section
if (remote_media_line) {
// Just repeat the remote media line.
err = mbuf_printf(sdp, "m=%s\r\n", remote_media_line);
} else {
if (!sctp_sdp_05) {
// Note: We choose UDP here although communication may still happen over ICE-TCP
// candidates.
// See also: https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-25#section-12.2
err = mbuf_printf(
sdp, "m=application %" PRIu16 " UDP/DTLS/SCTP webrtc-datachannel\r\n",
discard_port);
} else {
err = mbuf_printf(
sdp, "m=application %" PRIu16 " DTLS/SCTP %" PRIu16 "\r\n", discard_port,
sctp_port);
}
}
// Add dummy 'c'-line
err |= mbuf_write_str(sdp, "c=IN IP4 0.0.0.0\r\n");
// Add 'mid' line (if any)
if (mid) {
err |= mbuf_printf(sdp, "a=mid:%s\r\n", mid);
}
// Add direction line
err |= mbuf_write_str(sdp, "a=sendrecv\r\n");
if (err) {
return rawrtc_error_to_code(err);
}
// Add ICE attributes
error = add_ice_attributes(sdp, context);
if (error) {
return error;
}
// Add DTLS attributes
error = add_dtls_attributes(sdp, context, offering);
if (error) {
return error;
}
// Set attributes
if (!sctp_sdp_05) {
// Set SCTP port
// Note: Last time I checked, Chrome wasn't able to cope with this
err = mbuf_printf(sdp, "a=sctp-port:%" PRIu16 "\r\n", sctp_port);
} else {
// Set SCTP port, upper layer protocol and number of streams
err = mbuf_printf(
sdp, "a=sctpmap:%" PRIu16 " webrtc-datachannel %" PRIu16 "\r\n", sctp_port,
sctp_n_streams);
}
if (err) {
return rawrtc_error_to_code(err);
}
// Set maximum message size
// Note: This isn't part of the 05 version but Firefox can only parse 'max-message-size' but
// doesn't understand the old 'sctpmap' one (from 06 to 21).
err = mbuf_write_str(sdp, "a=max-message-size:0\r\n");
error = rawrtc_error_to_code(err);
if (error) {
return error;
}
// Done
return RAWRTC_CODE_SUCCESS;
}
/*
* Get SCTP transport attributes from an SDP line.
*/
static enum rawrtc_code get_sctp_attributes(
uint16_t* const portp, // de-referenced, not checked
uint64_t* const max_message_sizep, // de-referenced, not checked
struct pl* const line // not checked
) {
struct pl port_pl;
uint32_t port;
struct pl max_message_size_pl;
// SCTP port (from 'sctpmap' or 'sctp-port')
if (!re_regex(line->p, line->l, sdp_sctp_port_sctmap_regex, &port_pl, NULL) ||
!re_regex(line->p, line->l, sdp_sctp_port_regex, &port_pl)) {
port = pl_u32(&port_pl);
// Validate port
if (port == 0 || port > UINT16_MAX) {
DEBUG_WARNING("Invalid SCTP port: %" PRIu32 "\n", port);
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Set port & done
*portp = (uint16_t) port;
return RAWRTC_CODE_SUCCESS;
}
// SCTP maximum message size
// Note: Theoretically, there's another approach as part of 'sctmap' which has been deprecated
// but I doubt anyone ever implemented that.
if (!re_regex(line->p, line->l, sdp_sctp_maximum_message_size_regex, &max_message_size_pl)) {
*max_message_sizep = pl_u64(&max_message_size_pl);
return RAWRTC_CODE_SUCCESS;
}
// Done
return RAWRTC_CODE_NO_VALUE;
}
/*
* Get an ICE candidate from the description.
*/
static enum rawrtc_code get_ice_candidate_attributes(
struct list* const candidate_lines, // not checked
bool* const end_of_candidatesp, // de-referenced, not checked
struct pl* const line // not checked
) {
bool add_candidate_line = false;
struct pl* use_line = NULL;
// ICE candidate
if (line->l >= sdp_ice_candidate_head_length) {
struct pl candidate_pl = {
.p = line->p,
.l = sdp_ice_candidate_head_length - 1,
};
if (pl_strcmp(&candidate_pl, sdp_ice_candidate_head) == 0) {
add_candidate_line = true;
use_line = line;
}
}
// End of candidates
if (!add_candidate_line && pl_strcmp(line, sdp_ice_end_of_candidates) == 0) {
add_candidate_line = true;
use_line = NULL;
*end_of_candidatesp = true;
}
// Create candidate line (if any)
if (add_candidate_line) {
struct candidate_line* const candidate_line = mem_zalloc(sizeof(*candidate_line), NULL);
if (!candidate_line) {
DEBUG_WARNING("Unable to create candidate line, no memory\n");
return RAWRTC_CODE_NO_MEMORY;
}
// Set fields
// Warning: The line is NOT copied - it's just a pointer to some memory provided by
// the caller!
if (use_line) {
candidate_line->line = *use_line;
}
// Add candidate line to list & done
list_append(candidate_lines, &candidate_line->le, candidate_line);
return RAWRTC_CODE_SUCCESS;
} else {
return RAWRTC_CODE_NO_VALUE;
}
}
/*
* Destructor for an existing peer connection description.
*/
static void rawrtc_peer_connection_description_destroy(void* arg) {
struct rawrtc_peer_connection_description* const description = arg;
// Un-reference
mem_deref(description->sdp);
mem_deref(description->sctp_capabilities);
mem_deref(description->dtls_parameters);
mem_deref(description->ice_parameters);
list_flush(&description->ice_candidates);
mem_deref(description->mid);
mem_deref(description->remote_media_line);
mem_deref(description->bundled_mids);
mem_deref(description->connection);
}
/*
* Create a description by creating an offer or answer.
*/
enum rawrtc_code rawrtc_peer_connection_description_create_internal(
struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
struct rawrtc_peer_connection* const connection,
bool const offering) {
struct rawrtc_peer_connection_context* context;
struct rawrtc_peer_connection_description* remote_description;
struct rawrtc_peer_connection_description* local_description;
enum rawrtc_code error;
struct mbuf* sdp = NULL;
enum rawrtc_data_transport_type data_transport_type;
void* data_transport = NULL;
// Check arguments
if (!descriptionp || !connection) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Get context
context = &connection->context;
// Ensure a data transport has been set (otherwise, there would be nothing to do)
if (!context->data_transport) {
DEBUG_WARNING("No data transport set\n");
return RAWRTC_CODE_NO_VALUE;
}
// Ensure a remote description is available (when answering)
remote_description = connection->remote_description;
if (!offering && !remote_description) {
DEBUG_WARNING("No remote description set\n");
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Allocate
local_description =
mem_zalloc(sizeof(*local_description), rawrtc_peer_connection_description_destroy);
if (!local_description) {
return RAWRTC_CODE_NO_MEMORY;
}
// Set initial values
local_description->connection = mem_ref(connection); // Warning: Circular reference
local_description->end_of_candidates = false;
if (offering) {
local_description->type = RAWRTC_SDP_TYPE_OFFER;
local_description->trickle_ice = true;
error =
rawrtc_strdup(&local_description->bundled_mids, RAWRTC_PEER_CONNECTION_DESCRIPTION_MID);
if (error) {
goto out;
}
local_description->media_line_index = 0; // Since we only support one media line...
error = rawrtc_strdup(&local_description->mid, RAWRTC_PEER_CONNECTION_DESCRIPTION_MID);
if (error) {
goto out;
}
local_description->sctp_sdp_05 = connection->configuration->sctp_sdp_05;
} else {
local_description->type = RAWRTC_SDP_TYPE_ANSWER;
local_description->trickle_ice = remote_description->trickle_ice;
local_description->bundled_mids = mem_ref(remote_description->bundled_mids);
local_description->remote_media_line = mem_ref(remote_description->remote_media_line);
local_description->media_line_index = remote_description->media_line_index;
local_description->mid = mem_ref(remote_description->mid);
local_description->sctp_sdp_05 = remote_description->sctp_sdp_05;
}
// Create buffer for local description
sdp = mbuf_alloc(RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_SIZE);
if (!sdp) {
error = RAWRTC_CODE_NO_MEMORY;
goto out;
}
// Set session boilerplate
error = set_session_boilerplate(sdp, RAWRTC_VERSION, rand_u32());
if (error) {
goto out;
}
// Set session attributes
error = set_session_attributes(
sdp, local_description->trickle_ice, local_description->bundled_mids);
if (error) {
goto out;
}
// Get data transport
error = rawrtc_data_transport_get_transport(
&data_transport_type, &data_transport, context->data_transport);
if (error) {
return error;
}
// Add data transport
switch (data_transport_type) {
case RAWRTC_DATA_TRANSPORT_TYPE_SCTP:
// Add SCTP transport
error = add_sctp_attributes(
sdp, data_transport, context, offering, local_description->remote_media_line,
local_description->mid, local_description->sctp_sdp_05);
if (error) {
goto out;
}
break;
default:
error = RAWRTC_CODE_UNKNOWN_ERROR;
goto out;
}
// Reference SDP
local_description->sdp = mem_ref(sdp);
// Debug
DEBUG_PRINTF(
"Description (internal):\n%H\n", rawrtc_peer_connection_description_debug,
local_description);
out:
mem_deref(data_transport);
mem_deref(sdp);
if (error) {
mem_deref(local_description);
} else {
// Set pointer & done
*descriptionp = local_description;
}
return error;
}
/*
* Add an ICE candidate to the description.
*/
enum rawrtc_code rawrtc_peer_connection_description_add_candidate(
struct rawrtc_peer_connection_description* const description,
struct rawrtc_peer_connection_ice_candidate* const candidate // nullable
) {
enum rawrtc_code error;
// Check arguments
if (!description) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// Write candidate or end of candidates indication
if (candidate) {
char* candidate_sdp;
// Already written?
if (description->end_of_candidates) {
return RAWRTC_CODE_INVALID_STATE;
}
// Get candidate SDP
error = rawrtc_peer_connection_ice_candidate_get_sdp(&candidate_sdp, candidate);
if (error) {
return error;
}
// TODO: We would have to get the associated 'mid', media line index and username fragment
// as well and...
//
// * inject the candidate at the correct place (compare 'mid' or line index), and
// * compare the username fragment against the one that's currently active (once we
// support ICE restarts).
// Write candidate to SDP
// Note: We only have one media line, so it should be fine to append this to the end
error = rawrtc_error_to_code(mbuf_printf(description->sdp, "a=%s\r\n", candidate_sdp));
if (error) {
DEBUG_WARNING(
"Couldn't write candidate to description, reason: %s\n", rawrtc_code_to_str(error));
mem_deref(candidate_sdp);
return error;
}
// Debug
DEBUG_PRINTF("Added candidate line: %s\n", candidate_sdp);
mem_deref(candidate_sdp);
} else {
// Already written?
if (description->end_of_candidates) {
DEBUG_WARNING("End of candidates has already been written\n");
return RAWRTC_CODE_SUCCESS;
}
// Write end of candidates into SDP
error = rawrtc_error_to_code(mbuf_write_str(description->sdp, "a=end-of-candidates\r\n"));
if (error) {
return error;
}
description->end_of_candidates = true;
// Debug
DEBUG_PRINTF(
"Description (end-of-candidates):\n%H\n", rawrtc_peer_connection_description_debug,
description);
}
// Done
return RAWRTC_CODE_SUCCESS;
}
// Helper for parsing SDP attributes
#define HANDLE_ATTRIBUTE(code) \
error = code; \
if (error == RAWRTC_CODE_SUCCESS) { \
break; \
} else if (error != RAWRTC_CODE_NO_VALUE) { \
goto out; \
break; \
}
/*
* Create a description by parsing it from SDP.
* `*descriptionp` must be unreferenced.
*/
enum rawrtc_code rawrtc_peer_connection_description_create(
struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
enum rawrtc_sdp_type const type,
char const* const sdp) {
enum rawrtc_code error;
struct rawrtc_peer_connection_description* remote_description;
char const* cursor;
bool media_line = false;
struct le* le;
// ICE parameters
char* ice_username_fragment = NULL;
char* ice_password = NULL;
bool ice_lite = false;
// DTLS parameters
enum rawrtc_dtls_role dtls_role = RAWRTC_DTLS_ROLE_AUTO;
struct list dtls_fingerprints = LIST_INIT;
// SCTP capabilities
uint64_t sctp_max_message_size = RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_MAX_MESSAGE_SIZE;
// ICE candidate lines (temporarily stored, so it can be parsed later)
struct list ice_candidate_lines = LIST_INIT;
// Check arguments
if (!descriptionp || !sdp) {
return RAWRTC_CODE_INVALID_ARGUMENT;
}
// We only accept 'offer' or 'answer' at the moment
// TODO: Handle the other ones as well
if (type != RAWRTC_SDP_TYPE_OFFER && type != RAWRTC_SDP_TYPE_ANSWER) {
DEBUG_WARNING("Only 'offer' or 'answer' descriptions can be handled at the moment\n");
return RAWRTC_CODE_NOT_IMPLEMENTED;
}
// Allocate
remote_description =
mem_zalloc(sizeof(*remote_description), rawrtc_peer_connection_description_destroy);
if (!remote_description) {
return RAWRTC_CODE_NO_MEMORY;
}
// Set fields to initial values
remote_description->type = type;
remote_description->trickle_ice = false;
remote_description->media_line_index = 0; // Since we only support one media line...
remote_description->sctp_sdp_05 = true;
list_init(&remote_description->ice_candidates);
remote_description->sctp_port = RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_SCTP_PORT;
// Find required session and media attributes
cursor = sdp;
while (*cursor != '\0') {
struct pl line;
char sdp_type;
// Ignore lines beginning with '\r' or '\n'
if (*cursor == '\r' || *cursor == '\n') {
++cursor;
continue;
}
// Find next line or end of string
for (line.p = cursor, line.l = 0; *cursor != '\r' && *cursor != '\n' && *cursor != '\0';
++cursor, ++line.l) {
}
// Get line type and move line cursor to value
if (line.l < 2) {
DEBUG_WARNING("Invalid SDP line: %r\n", &line);
break;
}
sdp_type = *line.p;
pl_advance(&line, 2);
// Are we interested in this line?
switch (sdp_type) {
case 'a': {
// Be aware we're using a macro here which does the following:
//
// * if the function returns 'success', break (and therefore don't continue
// parsing other attributes on this line).
// * if the function returns 'no value', do nothing (and therefore continue parsing
// other attributes on this line).
// * if the function returns anything else (which indicates an error), set 'error'
// and jump to 'out'.
HANDLE_ATTRIBUTE(get_general_attributes(
&remote_description->bundled_mids, &remote_description->mid, &line));
HANDLE_ATTRIBUTE(get_ice_attributes(
&remote_description->trickle_ice, &ice_username_fragment, &ice_password,
&ice_lite, &line));
HANDLE_ATTRIBUTE(get_dtls_attributes(&dtls_role, &dtls_fingerprints, &line));
HANDLE_ATTRIBUTE(get_sctp_attributes(
&remote_description->sctp_port, &sctp_max_message_size, &line));
HANDLE_ATTRIBUTE(get_ice_candidate_attributes(
&ice_candidate_lines, &remote_description->end_of_candidates, &line));
break;
}
case 'm': {
struct pl application;
size_t i;
// Ensure amount of media lines is exactly one
if (media_line) {
DEBUG_WARNING("Unable to handle more than one media line\n");
error = RAWRTC_CODE_NOT_IMPLEMENTED;
goto out;
}
// Parse media line
if (re_regex(line.p, line.l, sdp_application_dtls_sctp_regex, NULL, &application)) {
DEBUG_WARNING("Unsupport media line: %r\n", &line);
error = RAWRTC_CODE_NOT_IMPLEMENTED;
goto out;
}
// Check if the application matches some kind of DTLS/SCTP variant (ugh...)
for (i = 0; i < sdp_application_dtls_sctp_variants_length; ++i) {
if (pl_strcmp(&application, sdp_application_dtls_sctp_variants[i]) == 0) {
media_line = true;
}
}
if (!media_line) {
DEBUG_WARNING("Unsupported application on media line: %r\n", &application);
error = RAWRTC_CODE_NOT_IMPLEMENTED;
goto out;
}
// Copy media line
error = rawrtc_sdprintf(&remote_description->remote_media_line, "%r", &line);
if (error) {
goto out;
}
// Done
break;
}
default:
DEBUG_PRINTF(
"Ignoring %s line: %c=%r\n", media_line ? "media" : "session", sdp_type, &line);
break;
}
}
// Return 'no value' in case there was no media line
if (!media_line) {
error = RAWRTC_CODE_NO_VALUE;
goto out;
}
// Create ICE parameters (if possible)
if (ice_username_fragment && ice_password) {
error = rawrtc_ice_parameters_create(
&remote_description->ice_parameters, ice_username_fragment, ice_password, ice_lite);
if (error) {
goto out;
}
}
// Create DTLS parameters (if possible)
if (!list_isempty(&dtls_fingerprints)) {
error = rawrtc_dtls_parameters_create_internal(
&remote_description->dtls_parameters, dtls_role, &dtls_fingerprints);
if (error) {
goto out;
}
}
// Create SCTP capabilities
error = rawrtc_sctp_capabilities_create(
&remote_description->sctp_capabilities, sctp_max_message_size);
if (error) {
goto out;
}
// Late parsing of ICE candidates.
// Note: This is required since the 'mid' and the username fragment may be parsed after a
// candidate has been found.
for (le = list_head(&ice_candidate_lines); le != NULL; le = le->next) {
struct candidate_line* const candidate_line = le->data;
// Create ICE candidate
struct rawrtc_peer_connection_ice_candidate* candidate;
error = rawrtc_peer_connection_ice_candidate_create_internal(
&candidate, &candidate_line->line, remote_description->mid,
&remote_description->media_line_index, ice_username_fragment);
if (error) {
goto out;
}
// Add ICE candidate to the list
DEBUG_PRINTF("Adding ICE candidate to description\n");
list_append(&remote_description->ice_candidates, &candidate->le, candidate);
}
// Copy SDP
remote_description->sdp = mbuf_alloc(strlen(sdp));
if (!remote_description->sdp) {
error = RAWRTC_CODE_NO_MEMORY;
goto out;
}
mbuf_write_str(remote_description->sdp, sdp);
// Debug
DEBUG_PRINTF(
"Description (parsed):\n%H\n", rawrtc_peer_connection_description_debug,
remote_description);
// Done
error = RAWRTC_CODE_SUCCESS;
out:
// Un-reference
list_flush(&ice_candidate_lines);
list_flush(&dtls_fingerprints);
mem_deref(ice_password);
mem_deref(ice_username_fragment);
if (error) {
mem_deref(remote_description);
} else {
// Set pointer & done
*descriptionp = remote_description;
}
return error;
}