Squashed 'third_party/rawrtc/rawrtc/' content from commit aa3ae4b24
Change-Id: I38a655a4259b62f591334e90a1315bd4e7e4d8ec
git-subtree-dir: third_party/rawrtc/rawrtc
git-subtree-split: aa3ae4b247275cc6e69c30613b3a4ba7fdc82d1b
diff --git a/src/certificate/certificate.c b/src/certificate/certificate.c
new file mode 100644
index 0000000..9f68b8e
--- /dev/null
+++ b/src/certificate/certificate.c
@@ -0,0 +1,899 @@
+#include "certificate.h"
+#include "../utils/utils.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/config.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <limits.h> // INT_MAX, LONG_MAX
+#include <string.h> // strlen
+
+#define DEBUG_MODULE "certificate"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Default certificate options.
+ */
+struct rawrtc_certificate_options rawrtc_default_certificate_options = {
+ .key_type = RAWRTC_CERTIFICATE_KEY_TYPE_EC,
+ .common_name = "anonymous@rawrtc.org",
+ .valid_until = 3600 * 24 * 30, // 30 days
+ .sign_algorithm = RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256,
+ .named_curve = "prime256v1",
+ .modulus_length = 3072,
+};
+
+/*
+ * Print and flush the OpenSSL error queue.
+ */
+static int print_openssl_error(char const* message, size_t length, void* arg) {
+ (void) message;
+ (void) length;
+ (void) arg;
+ DEBUG_WARNING("%b", message, length);
+
+ // 1 to continue outputting the error queue
+ return 1;
+}
+
+/*
+ * Generates an n-bit RSA key pair.
+ * Caller must call `EVP_PKEY_free(*keyp)` when done.
+ */
+static enum rawrtc_code generate_key_rsa(
+ EVP_PKEY** const keyp, // de-referenced
+ uint_fast32_t const modulus_length) {
+ enum rawrtc_code error = RAWRTC_CODE_UNKNOWN_ERROR;
+ EVP_PKEY* key = NULL;
+ RSA* rsa = NULL;
+ BIGNUM* bn = NULL;
+
+ // Check arguments
+ if (!keyp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#if (UINT_FAST32_MAX > INT_MAX)
+ if (modulus_length > INT_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+
+ // Create an empty EVP_PKEY structure
+ key = EVP_PKEY_new();
+ if (!key) {
+ DEBUG_WARNING("Could not create EVP_PKEY structure\n");
+ goto out;
+ }
+
+ // Initialise RSA structure
+ rsa = RSA_new();
+ if (!rsa) {
+ DEBUG_WARNING("Could not initialise RSA structure\n");
+ goto out;
+ }
+
+ // Allocate BIGNUM
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)
+ bn = BN_secure_new();
+#else
+ bn = BN_new();
+#endif
+ if (!bn) {
+ DEBUG_WARNING("Could not allocate BIGNUM\n");
+ goto out;
+ }
+
+ // Generate RSA key pair and store it in the RSA structure
+ BN_set_word(bn, RSA_F4);
+ if (!RSA_generate_key_ex(rsa, (int) modulus_length, bn, NULL)) {
+ DEBUG_WARNING("Could not generate RSA key pair\n");
+ goto out;
+ }
+
+ // Store the generated RSA key pair in the EVP_PKEY structure
+ if (!EVP_PKEY_set1_RSA(key, rsa)) {
+ DEBUG_WARNING("Could not assign RSA key pair to EVP_PKEY structure\n");
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (rsa) {
+ RSA_free(rsa);
+ }
+ if (bn) {
+ BN_free(bn);
+ }
+ if (error) {
+ if (key) {
+ EVP_PKEY_free(key);
+ }
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ *keyp = key;
+ }
+ return error;
+}
+
+/*
+ * Generates an ECC key pair.
+ * Caller must call `EVP_PKEY_free(*keyp)` when done.
+ */
+static enum rawrtc_code generate_key_ecc(
+ EVP_PKEY** const keyp, // de-referenced
+ char* const named_curve) {
+ enum rawrtc_code error = RAWRTC_CODE_UNKNOWN_ERROR;
+ EVP_PKEY* key = NULL;
+ int curve_group_nid;
+ EC_KEY* ecc = NULL;
+
+ // Check arguments
+ if (!keyp || !named_curve) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Create an empty EVP_PKEY structure
+ key = EVP_PKEY_new();
+ if (!key) {
+ DEBUG_WARNING("Could not create EVP_PKEY structure\n");
+ goto out;
+ }
+
+ // Get NID of named curve
+ curve_group_nid = OBJ_txt2nid(named_curve);
+ if (curve_group_nid == NID_undef) {
+ DEBUG_WARNING("Could not determine group NID of named curve: %s\n", named_curve);
+ goto out;
+ }
+
+ // Initialise EC structure for named curve
+ ecc = EC_KEY_new_by_curve_name(curve_group_nid);
+ if (!ecc) {
+ DEBUG_WARNING("Could not initialise EC structure for named curve\n");
+ goto out;
+ }
+
+ // This is needed to correctly sign the certificate
+ EC_KEY_set_asn1_flag(ecc, OPENSSL_EC_NAMED_CURVE);
+
+ // Generate the ECC key pair and store it in the EC structure
+ if (!EC_KEY_generate_key(ecc)) {
+ DEBUG_WARNING("Could not generate ECC key pair\n");
+ goto out;
+ }
+
+ // Store the generated ECC key pair in the EVP_PKEY structure
+ if (!EVP_PKEY_assign_EC_KEY(key, ecc)) {
+ DEBUG_WARNING("Could not assign ECC key pair to EVP_PKEY structure\n");
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ if (ecc) {
+ EC_KEY_free(ecc);
+ }
+ if (key) {
+ EVP_PKEY_free(key);
+ }
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ *keyp = key;
+ }
+ return error;
+}
+
+/*
+ * Generates a self-signed certificate.
+ * Caller must call `X509_free(*certificatep)` when done.
+ */
+static enum rawrtc_code generate_self_signed_certificate(
+ X509** const certificatep, // de-referenced
+ EVP_PKEY* const key,
+ char* const common_name,
+ uint_fast32_t const valid_until,
+ enum rawrtc_certificate_sign_algorithm const sign_algorithm) {
+ enum rawrtc_code error = RAWRTC_CODE_UNKNOWN_ERROR;
+ X509* certificate = NULL;
+ X509_NAME* name = NULL;
+ EVP_MD const* sign_function;
+
+ // Check arguments
+ if (!certificatep || !key || !common_name) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#if (UINT_FAST32_MAX > LONG_MAX)
+ if (valid_until > LONG_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+
+ // Get sign function
+ sign_function = rawrtc_get_sign_function(sign_algorithm);
+ if (!sign_function) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate and initialise x509 structure
+ certificate = X509_new();
+ if (!certificate) {
+ DEBUG_WARNING("Could not initialise x509 structure\n");
+ goto out;
+ }
+
+ // Set x509 version
+ // Note: '2' maps to version 3
+ if (!X509_set_version(certificate, 2)) {
+ DEBUG_WARNING("Could not set x509 version\n");
+ goto out;
+ }
+
+ // Set the serial number randomly (doesn't need to be unique as we are self-signing)
+ if (!ASN1_INTEGER_set(X509_get_serialNumber(certificate), rand_u32())) {
+ DEBUG_WARNING("Could not set x509 serial number\n");
+ goto out;
+ }
+
+ // Create an empty X509_NAME structure
+ name = X509_NAME_new();
+ if (!name) {
+ DEBUG_WARNING("Could not create x509_NAME structure\n");
+ goto out;
+ }
+
+ // Set common name field on X509_NAME structure
+ if (!X509_NAME_add_entry_by_txt(
+ name, "CN", MBSTRING_ASC, (uint8_t*) common_name, (int) strlen(common_name), -1, 0)) {
+ DEBUG_WARNING("Could not apply common name (%s) on certificate\n", common_name);
+ goto out;
+ }
+
+ // Set issuer and subject name
+ if (!X509_set_issuer_name(certificate, name) || !X509_set_subject_name(certificate, name)) {
+ DEBUG_WARNING("Could not set issuer name on certificate\n");
+ goto out;
+ }
+
+ // Certificate is valid from now (-1 day) until whatever has been provided in parameters
+ if (!X509_gmtime_adj(X509_get_notBefore(certificate), -3600 * 24) ||
+ !X509_gmtime_adj(X509_get_notAfter(certificate), (long) valid_until)) {
+ DEBUG_WARNING("Could not apply lifetime range to certificate\n");
+ goto out;
+ }
+
+ // Set public key of certificate
+ if (!X509_set_pubkey(certificate, key)) {
+ DEBUG_WARNING("Could not set public key to certificate\n");
+ goto out;
+ }
+
+ // Sign the certificate
+ if (!X509_sign(certificate, key, sign_function)) {
+ DEBUG_WARNING("Could not sign the certificate\n");
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (name) {
+ X509_NAME_free(name);
+ }
+ if (error) {
+ if (certificate) {
+ X509_free(certificate);
+ }
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ *certificatep = certificate;
+ }
+ return error;
+}
+
+/*
+ * Destructor for existing certificate options.
+ */
+static void rawrtc_certificate_options_destroy(void* arg) {
+ struct rawrtc_certificate_options* const options = arg;
+
+ // Un-reference
+ mem_deref(options->named_curve);
+ mem_deref(options->common_name);
+}
+
+/*
+ * Create certificate options.
+ *
+ * All arguments but `key_type` are optional. Sane and safe default
+ * values will be applied, don't worry!
+ *
+ * `*optionsp` must be unreferenced.
+ *
+ * If `common_name` is `NULL` the default common name will be applied.
+ * If `valid_until` is `0` the default certificate lifetime will be
+ * applied.
+ * If the key type is `ECC` and `named_curve` is `NULL`, the default
+ * named curve will be used.
+ * If the key type is `RSA` and `modulus_length` is `0`, the default
+ * amount of bits will be used. The same applies to the
+ * `sign_algorithm` if it has been set to `NONE`.
+ */
+enum rawrtc_code rawrtc_certificate_options_create(
+ struct rawrtc_certificate_options** const optionsp, // de-referenced
+ enum rawrtc_certificate_key_type const key_type,
+ char* common_name, // nullable, copied
+ uint_fast32_t valid_until,
+ enum rawrtc_certificate_sign_algorithm sign_algorithm,
+ char* named_curve, // nullable, copied, ignored for RSA
+ uint_fast32_t modulus_length // ignored for ECC
+) {
+ struct rawrtc_certificate_options* options;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!optionsp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#if (UINT_FAST32_MAX > LONG_MAX)
+ if (valid_until > LONG_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+#if (UINT_FAST32_MAX > INT_MAX)
+ if (modulus_length > INT_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+
+ // Set defaults
+ if (!common_name) {
+ common_name = rawrtc_default_certificate_options.common_name;
+ }
+ if (!valid_until) {
+ valid_until = rawrtc_default_certificate_options.valid_until;
+ }
+
+ // Check sign algorithm/set default
+ // Note: We say 'no' to SHA1 intentionally
+ // Note: SHA-384 and SHA-512 are currently not supported (needs to be added to libre)
+ switch (sign_algorithm) {
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_NONE:
+ sign_algorithm = rawrtc_default_certificate_options.sign_algorithm;
+ break;
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256:
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set defaults depending on key type
+ switch (key_type) {
+ case RAWRTC_CERTIFICATE_KEY_TYPE_RSA:
+ // Unset ECC vars
+ named_curve = NULL;
+
+ // Prevent user from being stupid
+ if (modulus_length < RAWRTC_MODULUS_LENGTH_MIN) {
+ modulus_length = rawrtc_default_certificate_options.modulus_length;
+ }
+
+ break;
+
+ case RAWRTC_CERTIFICATE_KEY_TYPE_EC:
+ // Unset RSA vars
+ modulus_length = 0;
+
+ // Set default named curve (if required)
+ if (!named_curve) {
+ named_curve = rawrtc_default_certificate_options.named_curve;
+ }
+
+ break;
+
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Allocate
+ options = mem_zalloc(sizeof(*options), rawrtc_certificate_options_destroy);
+ if (!options) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/copy
+ options->key_type = key_type;
+ if (common_name) {
+ error = rawrtc_strdup(&options->common_name, common_name);
+ if (error) {
+ goto out;
+ }
+ }
+ options->valid_until = valid_until;
+ options->sign_algorithm = sign_algorithm;
+ if (named_curve) {
+ error = rawrtc_strdup(&options->named_curve, named_curve);
+ if (error) {
+ goto out;
+ }
+ }
+ options->modulus_length = modulus_length;
+
+out:
+ if (error) {
+ mem_deref(options);
+ } else {
+ // Set pointer
+ *optionsp = options;
+ }
+ return error;
+}
+
+/*
+ * Destructor for existing certificate.
+ */
+static void rawrtc_certificate_destroy(void* arg) {
+ struct rawrtc_certificate* const certificate = arg;
+
+ // Free
+ if (certificate->certificate) {
+ X509_free(certificate->certificate);
+ }
+ if (certificate->key) {
+ EVP_PKEY_free(certificate->key);
+ }
+}
+
+/*
+ * Create and generate a self-signed certificate.
+ *
+ * Sane and safe default options will be applied if `options` is
+ * `NULL`.
+ *
+ * `*certificatep` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_certificate_generate(
+ struct rawrtc_certificate** const certificatep,
+ struct rawrtc_certificate_options* options // nullable
+) {
+ struct rawrtc_certificate* certificate;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!certificatep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Default options
+ if (!options) {
+ options = &rawrtc_default_certificate_options;
+ }
+
+ // Allocate
+ certificate = mem_zalloc(sizeof(*certificate), rawrtc_certificate_destroy);
+ if (!certificate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Generate key pair
+ switch (options->key_type) {
+ case RAWRTC_CERTIFICATE_KEY_TYPE_RSA:
+ error = generate_key_rsa(&certificate->key, options->modulus_length);
+ break;
+ case RAWRTC_CERTIFICATE_KEY_TYPE_EC:
+ error = generate_key_ecc(&certificate->key, options->named_curve);
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+ if (error) {
+ goto out;
+ }
+
+ // Generate certificate
+ error = generate_self_signed_certificate(
+ &certificate->certificate, certificate->key, options->common_name, options->valid_until,
+ options->sign_algorithm);
+ if (error) {
+ goto out;
+ }
+
+ // Set key type
+ certificate->key_type = options->key_type;
+
+out:
+ if (error) {
+ mem_deref(certificate);
+ } else {
+ // Set pointer
+ *certificatep = certificate;
+ }
+ return error;
+}
+
+/*
+ * Copy a certificate.
+ * References the x509 certificate and private key.
+ */
+enum rawrtc_code rawrtc_certificate_copy(
+ struct rawrtc_certificate** const certificatep, // de-referenced
+ struct rawrtc_certificate* const source_certificate) {
+ enum rawrtc_code error = RAWRTC_CODE_UNKNOWN_ERROR;
+ struct rawrtc_certificate* certificate;
+
+ // Check arguments
+ if (!certificatep || !source_certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ certificate = mem_zalloc(sizeof(*certificate), rawrtc_certificate_destroy);
+ if (!certificate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Increment reference count of certificate and private key, copy the pointers
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ if (!X509_up_ref(source_certificate->certificate)) {
+ goto out;
+ }
+#else
+ if (!CRYPTO_add(&source_certificate->certificate->references, 1, CRYPTO_LOCK_X509)) {
+ goto out;
+ }
+#endif
+ certificate->certificate = source_certificate->certificate;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ if (!EVP_PKEY_up_ref(source_certificate->key)) {
+ goto out;
+ }
+#else
+ if (!CRYPTO_add(&source_certificate->key->references, 1, CRYPTO_LOCK_EVP_PKEY)) {
+ goto out;
+ }
+#endif
+ certificate->key = source_certificate->key;
+ certificate->key_type = source_certificate->key_type;
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ mem_deref(certificate);
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ // Set pointer
+ *certificatep = certificate;
+ }
+ return error;
+}
+
+static enum rawrtc_code what_to_encode(
+ enum rawrtc_certificate_encode const to_encode,
+ bool* encode_certificatep, // de-referenced
+ bool* encode_keyp // de-referenced
+) {
+ *encode_certificatep = false;
+ *encode_keyp = false;
+
+ // What to encode?
+ switch (to_encode) {
+ case RAWRTC_CERTIFICATE_ENCODE_CERTIFICATE:
+ *encode_certificatep = true;
+ break;
+ case RAWRTC_CERTIFICATE_ENCODE_PRIVATE_KEY:
+ *encode_keyp = true;
+ break;
+ case RAWRTC_CERTIFICATE_ENCODE_BOTH:
+ *encode_certificatep = true;
+ *encode_keyp = true;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get PEM of the certificate and/or the private key if requested.
+ * *pemp will NOT be null-terminated!
+ */
+enum rawrtc_code rawrtc_certificate_get_pem(
+ char** const pemp, // de-referenced
+ size_t* const pem_lengthp, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_encode const to_encode) {
+ bool encode_certificate;
+ bool encode_key;
+ enum rawrtc_code error;
+ BIO* bio = NULL;
+ char* pem = NULL;
+ uint64_t length;
+
+ // Check arguments
+ if (!pemp || !certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // What to encode?
+ error = what_to_encode(to_encode, &encode_certificate, &encode_key);
+ if (error) {
+ return error;
+ }
+ error = RAWRTC_CODE_UNKNOWN_ERROR;
+
+ // Create bio structure
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)
+ bio = BIO_new(BIO_s_secmem());
+#else
+ bio = BIO_new(BIO_s_mem());
+#endif
+
+ // Write certificate
+ if (encode_certificate && !PEM_write_bio_X509(bio, certificate->certificate)) {
+ goto out;
+ }
+
+ // Write private key (if requested)
+ if (encode_key && !PEM_write_bio_PrivateKey(bio, certificate->key, NULL, NULL, 0, 0, NULL)) {
+ goto out;
+ }
+
+ // Allocate buffer
+ length = BIO_number_written(bio);
+#if (UINT64_MAX > INT_MAX)
+ if (length > INT_MAX) {
+ goto out;
+ }
+#endif
+ pem = mem_alloc(length, NULL);
+ if (!pem) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+
+ // Copy to buffer
+ if (BIO_read(bio, pem, (int) length) < (int) length) {
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (bio) {
+ BIO_free(bio);
+ }
+ if (error) {
+ mem_deref(pem);
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ // Set pointers
+ *pemp = pem;
+ *pem_lengthp = length;
+ }
+ return error;
+}
+
+/*
+ * Get DER of the certificate and/or the private key if requested.
+ * *derp will NOT be null-terminated!
+ */
+enum rawrtc_code rawrtc_certificate_get_der(
+ uint8_t** const derp, // de-referenced
+ size_t* const der_lengthp, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_encode const to_encode) {
+ bool encode_certificate;
+ bool encode_key;
+ enum rawrtc_code error;
+ int length_certificate = 0;
+ int length_key = 0;
+ size_t length;
+ uint8_t* der = NULL;
+ uint8_t* der_i2d;
+
+ // Check arguments
+ if (!derp || !certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // What to encode?
+ error = what_to_encode(to_encode, &encode_certificate, &encode_key);
+ if (error) {
+ return error;
+ }
+ error = RAWRTC_CODE_UNKNOWN_ERROR;
+
+ // Allocate buffer
+ if (encode_certificate) {
+ length_certificate = i2d_X509(certificate->certificate, NULL);
+ if (length_certificate < 1) {
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+ }
+ if (encode_key) {
+ length_key = i2d_PrivateKey(certificate->key, NULL);
+ if (length_key < 1) {
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+ }
+ length = (size_t)(length_certificate + length_key);
+ der = mem_alloc(length, NULL);
+ if (!der) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+ der_i2d = der;
+
+ // Write certificate
+ if (encode_certificate && i2d_X509(certificate->certificate, &der_i2d) < length_certificate) {
+ goto out;
+ }
+
+ // Write private key (if requested)
+ if (encode_key && i2d_PrivateKey(certificate->key, &der_i2d) < length_key) {
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ mem_deref(der);
+ ERR_print_errors_cb(print_openssl_error, NULL);
+ } else {
+ // Set pointers
+ *derp = der;
+ *der_lengthp = length;
+ }
+ return error;
+}
+
+/*
+ * Get certificate's fingerprint.
+ * Caller must ensure that `buffer` has space for
+ * `RAWRTC_FINGERPRINT_MAX_SIZE_HEX` bytes
+ */
+enum rawrtc_code rawrtc_certificate_get_fingerprint(
+ char** const fingerprint, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_sign_algorithm const algorithm) {
+ EVP_MD const* sign_function;
+ uint8_t bytes_buffer[RAWRTC_FINGERPRINT_MAX_SIZE_HEX];
+ uint_least32_t length;
+
+ // Check arguments
+ if (!fingerprint || !certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get sign function for algorithm
+ sign_function = rawrtc_get_sign_function(algorithm);
+ if (!sign_function) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Generate certificate fingerprint
+ if (!X509_digest(certificate->certificate, sign_function, bytes_buffer, &length)) {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+ if (length < 1) {
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+
+ // Convert bytes to hex
+ return rawrtc_bin_to_colon_hex(fingerprint, bytes_buffer, (size_t) length);
+}
+
+/*
+ * Copy and append a certificate to a list.
+ */
+static enum rawrtc_code copy_and_append_certificate(
+ struct list* const certificate_list, // de-referenced, not checked
+ struct rawrtc_certificate* const certificate // copied
+) {
+ enum rawrtc_code error;
+ struct rawrtc_certificate* copied_certificate;
+
+ // Check arguments
+ if (!certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Copy certificate
+ // Note: Copying is needed as the 'le' element cannot be associated to multiple lists
+ error = rawrtc_certificate_copy(&copied_certificate, certificate);
+ if (error) {
+ return error;
+ }
+
+ // Append to list
+ list_append(certificate_list, &copied_certificate->le, copied_certificate);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Copy an array of certificates to a list.
+ * Warning: The list will be flushed on error.
+ */
+enum rawrtc_code rawrtc_certificate_array_to_list(
+ struct list* const certificate_list, // de-referenced, copied into
+ struct rawrtc_certificate* const certificates[], // copied (each item)
+ size_t const n_certificates) {
+ size_t i;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!certificate_list || !certificates) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Append and reference certificates
+ for (i = 0; i < n_certificates; ++i) {
+ error = copy_and_append_certificate(certificate_list, certificates[i]);
+ if (error) {
+ goto out;
+ }
+ }
+
+out:
+ if (error) {
+ list_flush(certificate_list);
+ }
+ return error;
+}
+
+/*
+ * Copy a certificate list.
+ * Warning: The destination list will be flushed on error.
+ */
+enum rawrtc_code rawrtc_certificate_list_copy(
+ struct list* const destination_list, // de-referenced, copied into
+ struct list* const source_list // de-referenced, copied (each item)
+) {
+ struct le* le;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!destination_list || !source_list) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Append and reference certificates
+ for (le = list_head(source_list); le != NULL; le = le->next) {
+ struct rawrtc_certificate* const certificate = le->data;
+ error = copy_and_append_certificate(destination_list, certificate);
+ if (error) {
+ goto out;
+ }
+ }
+
+out:
+ if (error) {
+ list_flush(destination_list);
+ }
+ return error;
+}
diff --git a/src/certificate/certificate.h b/src/certificate/certificate.h
new file mode 100644
index 0000000..4409928
--- /dev/null
+++ b/src/certificate/certificate.h
@@ -0,0 +1,90 @@
+#pragma once
+#include <rawrtc/certificate.h>
+#include <re.h>
+#include <openssl/evp.h> // EVP_*
+#include <openssl/x509.h> // X509
+
+/*
+ * Maximum digest size of certificate fingerprint.
+ */
+enum {
+ RAWRTC_MODULUS_LENGTH_MIN = 1024,
+ RAWRTC_FINGERPRINT_MAX_SIZE = EVP_MAX_MD_SIZE,
+ RAWRTC_FINGERPRINT_MAX_SIZE_HEX = (EVP_MAX_MD_SIZE * 2),
+};
+
+/*
+ * Certificate options.
+ */
+struct rawrtc_certificate_options {
+ enum rawrtc_certificate_key_type key_type;
+ char* common_name; // copied
+ uint_fast32_t valid_until;
+ enum rawrtc_certificate_sign_algorithm sign_algorithm;
+ char* named_curve; // nullable, copied, ignored for RSA
+ uint_fast32_t modulus_length; // ignored for ECC
+};
+
+/*
+ * Certificate.
+ */
+struct rawrtc_certificate {
+ struct le le;
+ X509* certificate;
+ EVP_PKEY* key;
+ enum rawrtc_certificate_key_type key_type;
+};
+
+extern struct rawrtc_certificate_options rawrtc_default_certificate_options;
+
+enum rawrtc_code rawrtc_certificate_copy(
+ struct rawrtc_certificate** const certificatep, // de-referenced
+ struct rawrtc_certificate* const source_certificate);
+
+enum rawrtc_code rawrtc_certificate_get_pem(
+ char** const pemp, // de-referenced
+ size_t* const pem_lengthp, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_encode const to_encode);
+
+enum rawrtc_code rawrtc_certificate_get_der(
+ uint8_t** const derp, // de-referenced
+ size_t* const der_lengthp, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_encode const to_encode);
+
+enum rawrtc_code rawrtc_certificate_get_fingerprint(
+ char** const fingerprint, // de-referenced
+ struct rawrtc_certificate* const certificate,
+ enum rawrtc_certificate_sign_algorithm const algorithm);
+
+enum rawrtc_code rawrtc_certificate_array_to_list(
+ struct list* const certificate_list, // de-referenced, copied into
+ struct rawrtc_certificate* const certificates[], // copied (each item)
+ size_t const n_certificates);
+
+enum rawrtc_code rawrtc_certificate_list_copy(
+ struct list* const destination_list, // de-referenced, copied into
+ struct list* const source_list // de-referenced, copied (each item)
+);
+
+enum tls_keytype rawrtc_certificate_key_type_to_tls_keytype(
+ const enum rawrtc_certificate_key_type type);
+
+enum rawrtc_code rawrtc_tls_keytype_to_certificate_key_type(
+ enum rawrtc_certificate_key_type* const typep, // de-referenced
+ enum tls_keytype const re_type);
+
+enum rawrtc_code rawrtc_certificate_sign_algorithm_to_tls_fingerprint(
+ enum tls_fingerprint* const fingerprintp, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const algorithm);
+
+enum rawrtc_code rawrtc_tls_fingerprint_to_certificate_sign_algorithm(
+ enum rawrtc_certificate_sign_algorithm* const algorithmp, // de-referenced
+ enum tls_fingerprint re_algorithm);
+
+EVP_MD const* rawrtc_get_sign_function(enum rawrtc_certificate_sign_algorithm type);
+
+enum rawrtc_code rawrtc_get_sign_algorithm_length(
+ size_t* const sizep, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const type);
diff --git a/src/certificate/meson.build b/src/certificate/meson.build
new file mode 100644
index 0000000..9d0e932
--- /dev/null
+++ b/src/certificate/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'certificate.c',
+ 'utils.c',
+])
diff --git a/src/certificate/utils.c b/src/certificate/utils.c
new file mode 100644
index 0000000..e9ab58c
--- /dev/null
+++ b/src/certificate/utils.c
@@ -0,0 +1,177 @@
+#include "certificate.h"
+#include <rawrtc/certificate.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Translate a certificate key type to the corresponding re type.
+ */
+enum tls_keytype rawrtc_certificate_key_type_to_tls_keytype(
+ enum rawrtc_certificate_key_type const type) {
+ // No conversion needed
+ return (enum tls_keytype) type;
+}
+
+/*
+ * Translate a re key type to the corresponding rawrtc type.
+ */
+enum rawrtc_code rawrtc_tls_keytype_to_certificate_key_type(
+ enum rawrtc_certificate_key_type* const typep, // de-referenced
+ enum tls_keytype const re_type) {
+ // Check arguments
+ if (!typep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert ice_cand_type
+ switch (re_type) {
+ case TLS_KEYTYPE_RSA:
+ *typep = RAWRTC_CERTIFICATE_KEY_TYPE_RSA;
+ return RAWRTC_CODE_SUCCESS;
+ case TLS_KEYTYPE_EC:
+ *typep = RAWRTC_CERTIFICATE_KEY_TYPE_EC;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
+
+/*
+ * Translate a certificate sign algorithm to the corresponding re fingerprint algorithm.
+ */
+enum rawrtc_code rawrtc_certificate_sign_algorithm_to_tls_fingerprint(
+ enum tls_fingerprint* const fingerprintp, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const algorithm) {
+ switch (algorithm) {
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_NONE:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA384:
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA512:
+ // Note: SHA-384 and SHA-512 are currently not supported (needs to be added to re)
+ return RAWRTC_CODE_UNSUPPORTED_ALGORITHM;
+ default:
+ break;
+ }
+
+ // No conversion needed
+ *fingerprintp = (enum tls_fingerprint) algorithm;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Translate a re fingerprint algorithm to the corresponding rawrtc algorithm.
+ */
+enum rawrtc_code rawrtc_tls_fingerprint_to_certificate_sign_algorithm(
+ enum rawrtc_certificate_sign_algorithm* const algorithmp, // de-referenced
+ enum tls_fingerprint re_algorithm) {
+ // Check arguments
+ if (!algorithmp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert ice_cand_type
+ // Note: SHA-384 and SHA-512 are currently not supported (needs to be added to libre)
+ switch (re_algorithm) {
+ case TLS_FINGERPRINT_SHA256:
+ *algorithmp = RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
+
+static enum rawrtc_certificate_sign_algorithm const map_enum_certificate_sign_algorithm[] = {
+ RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256,
+ RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA384,
+ RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA512,
+};
+
+static char const* const map_str_certificate_sign_algorithm[] = {
+ "sha-256",
+ "sha-384",
+ "sha-512",
+};
+
+static size_t const map_certificate_sign_algorithm_length =
+ ARRAY_SIZE(map_enum_certificate_sign_algorithm);
+
+/*
+ * Translate a certificate sign algorithm to str.
+ */
+char const* rawrtc_certificate_sign_algorithm_to_str(
+ enum rawrtc_certificate_sign_algorithm const algorithm) {
+ size_t i;
+
+ for (i = 0; i < map_certificate_sign_algorithm_length; ++i) {
+ if (map_enum_certificate_sign_algorithm[i] == algorithm) {
+ return map_str_certificate_sign_algorithm[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to a certificate sign algorithm (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_certificate_sign_algorithm(
+ enum rawrtc_certificate_sign_algorithm* const algorithmp, // de-referenced
+ char const* const str) {
+ size_t i;
+
+ // Check arguments
+ if (!algorithmp || !str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_certificate_sign_algorithm_length; ++i) {
+ if (str_casecmp(map_str_certificate_sign_algorithm[i], str) == 0) {
+ *algorithmp = map_enum_certificate_sign_algorithm[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Get the EVP_MD* structure for a certificate sign algorithm type.
+ */
+EVP_MD const* rawrtc_get_sign_function(enum rawrtc_certificate_sign_algorithm const type) {
+ switch (type) {
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256:
+ return EVP_sha256();
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA384:
+ return EVP_sha384();
+ case RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA512:
+ return EVP_sha512();
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * Get the length of the fingerprint to a certificate sign algorithm type.
+ */
+enum rawrtc_code rawrtc_get_sign_algorithm_length(
+ size_t* const sizep, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const type) {
+ EVP_MD const* sign_function;
+ int size;
+
+ // Get sign algorithm function
+ sign_function = rawrtc_get_sign_function(type);
+ if (!sign_function) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get length
+ size = EVP_MD_size(sign_function);
+ if (size < 1) {
+ return RAWRTC_CODE_UNSUPPORTED_ALGORITHM;
+ }
+
+ // Set size
+ *sizep = (size_t) size;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/diffie_hellman_parameters/meson.build b/src/diffie_hellman_parameters/meson.build
new file mode 100644
index 0000000..7505db0
--- /dev/null
+++ b/src/diffie_hellman_parameters/meson.build
@@ -0,0 +1 @@
+sources += files('parameters.c')
diff --git a/src/diffie_hellman_parameters/parameters.c b/src/diffie_hellman_parameters/parameters.c
new file mode 100644
index 0000000..7783e59
--- /dev/null
+++ b/src/diffie_hellman_parameters/parameters.c
@@ -0,0 +1,190 @@
+#include "parameters.h"
+#include <rawrtc/config.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <openssl/bio.h> // BIO_new_mem_buf
+#include <openssl/dh.h> // DH, DH_check_params
+#include <openssl/err.h> // ERR_clear_error
+#include <openssl/pem.h> // PEM_read_bio_DHparams
+#include <openssl/ssl.h> // SSL_CTX_set_tmp_dh, SSL_CTX_set_ecdh_auto
+#include <limits.h> // INT_MAX, LONG_MAX
+
+#define DEBUG_MODULE "diffie-hellman-parameters"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Apply Diffie-Hellman parameters on an OpenSSL context.
+ */
+static enum rawrtc_code set_dh_parameters(
+ struct ssl_ctx_st* const ssl_context, // not checked
+ DH const* const dh // not checked
+) {
+ int codes;
+
+ // Check that the parameters are "likely enough to be valid"
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL || defined(OPENSSL_IS_BORINGSSL)
+ if (!DH_check(dh, &codes)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#else
+ if (!DH_check_params(dh, &codes)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+ if (codes) {
+#if defined(DH_CHECK_P_NOT_PRIME)
+ if (codes & DH_CHECK_P_NOT_PRIME) {
+ DEBUG_WARNING("set_dh_parameters: p is not prime\n");
+ }
+#endif
+#if defined(DH_CHECK_P_NOT_SAFE_PRIME)
+ if (codes & DH_CHECK_P_NOT_SAFE_PRIME) {
+ DEBUG_WARNING("set_dh_parameters: p is not safe prime\n");
+ }
+#endif
+#if defined(DH_UNABLE_TO_CHECK_GENERATOR)
+ if (codes & DH_UNABLE_TO_CHECK_GENERATOR) {
+ DEBUG_WARNING("set_dh_parameters: generator g cannot be checked\n");
+ }
+#endif
+#if defined(DH_NOT_SUITABLE_GENERATOR)
+ if (codes & DH_NOT_SUITABLE_GENERATOR) {
+ DEBUG_WARNING("set_dh_parameters: generator g is not suitable\n");
+ }
+#endif
+#if defined(DH_CHECK_Q_NOT_PRIME)
+ if (codes & DH_CHECK_Q_NOT_PRIME) {
+ DEBUG_WARNING("set_dh_parameters: q is not prime\n");
+ }
+#endif
+#if defined(DH_CHECK_INVALID_Q_VALUE)
+ if (codes & DH_CHECK_INVALID_Q_VALUE) {
+ DEBUG_WARNING("set_dh_parameters: q is invalid\n");
+ }
+#endif
+#if defined(DH_CHECK_INVALID_J_VALUE)
+ if (codes & DH_CHECK_INVALID_J_VALUE) {
+ DEBUG_WARNING("set_dh_parameters: j is invalid\n");
+ }
+#endif
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Apply Diffie-Hellman parameters
+ if (!SSL_CTX_set_tmp_dh(ssl_context, dh)) {
+ DEBUG_WARNING("set_dh_parameters: set_tmp_dh failed\n");
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set Diffie-Hellman parameters on an OpenSSL context using DER encoding.
+ */
+enum rawrtc_code rawrtc_set_dh_parameters_der(
+ struct tls* const tls, uint8_t const* const der, size_t const der_size) {
+ struct ssl_ctx_st* const ssl_context = tls_openssl_context(tls);
+ DH* dh = NULL;
+ enum rawrtc_code error = RAWRTC_CODE_UNKNOWN_ERROR;
+
+ // Check arguments
+ if (!ssl_context || !der || der_size == 0 || der_size > LONG_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Decode PKCS#3 Diffie-Hellman parameters
+ dh = d2i_DHparams(NULL, (unsigned char const**) &der, der_size);
+ if (!dh) {
+ goto out;
+ }
+
+ // Apply Diffie-Hellman parameters
+ error = set_dh_parameters(ssl_context, dh);
+ if (error) {
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (dh) {
+ DH_free(dh);
+ }
+ if (error) {
+ ERR_clear_error();
+ }
+ return error;
+}
+
+/**
+ * Set Diffie-Hellman parameters on an OpenSSL context using PEM encoding.
+ */
+enum rawrtc_code rawrtc_set_dh_parameters_pem(
+ struct tls* const tls, char const* const pem, size_t const pem_size) {
+ struct ssl_ctx_st* const ssl_context = tls_openssl_context(tls);
+ BIO* bio = NULL;
+ DH* dh = NULL;
+ enum rawrtc_code error = RAWRTC_CODE_NO_MEMORY;
+
+ // Check arguments
+ if (!ssl_context || !pem || pem_size == 0 || pem_size > INT_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Create memory sink
+ bio = BIO_new_mem_buf(pem, (int) pem_size);
+ if (!bio) {
+ goto out;
+ }
+
+ // Read Diffie-Hellman parameters into memory sink
+ dh = PEM_read_bio_DHparams(bio, NULL, 0, NULL);
+ if (!dh)
+ goto out;
+
+ // Apply Diffie-Hellman parameters
+ error = set_dh_parameters(ssl_context, dh);
+ if (error) {
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (dh) {
+ DH_free(dh);
+ }
+ if (bio) {
+ BIO_free(bio);
+ }
+ if (error) {
+ ERR_clear_error();
+ }
+ return error;
+}
+
+/*
+ * Enable elliptic-curve Diffie-Hellman on an OpenSSL context.
+ */
+enum rawrtc_code rawrtc_enable_ecdh(struct tls* const tls) {
+ struct ssl_ctx_st* const ssl_context = tls_openssl_context(tls);
+
+ // Check arguments
+ if (!ssl_context) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Enable elliptic-curve Diffie-Hellman
+ if (!SSL_CTX_set_ecdh_auto(ssl_context, (long) 1)) {
+ DEBUG_WARNING("set_dh_params: set_ecdh_auto failed\n");
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/diffie_hellman_parameters/parameters.h b/src/diffie_hellman_parameters/parameters.h
new file mode 100644
index 0000000..fecf924
--- /dev/null
+++ b/src/diffie_hellman_parameters/parameters.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+enum rawrtc_code rawrtc_set_dh_parameters_der(
+ struct tls* const tls, uint8_t const* const der, size_t const der_size);
+
+enum rawrtc_code rawrtc_set_dh_parameters_pem(
+ struct tls* const tls, char const* const pem, size_t const pem_size);
+
+enum rawrtc_code rawrtc_enable_ecdh(struct tls* const tls);
diff --git a/src/dtls_fingerprint/attributes.c b/src/dtls_fingerprint/attributes.c
new file mode 100644
index 0000000..dc52659
--- /dev/null
+++ b/src/dtls_fingerprint/attributes.c
@@ -0,0 +1,38 @@
+#include "fingerprint.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the DTLS certificate fingerprint's sign algorithm.
+ */
+enum rawrtc_code rawrtc_dtls_fingerprint_get_sign_algorithm(
+ enum rawrtc_certificate_sign_algorithm* const sign_algorithmp, // de-referenced
+ struct rawrtc_dtls_fingerprint* const fingerprint) {
+ // Check arguments
+ if (!sign_algorithmp || !fingerprint) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set sign algorithm
+ *sign_algorithmp = fingerprint->algorithm;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the DTLS certificate's fingerprint value.
+ * `*valuep` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_dtls_fingerprint_get_value(
+ char** const valuep, // de-referenced
+ struct rawrtc_dtls_fingerprint* const fingerprint) {
+ // Check arguments
+ if (!valuep || !fingerprint) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *valuep = mem_ref(fingerprint->value);
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/dtls_fingerprint/fingerprint.c b/src/dtls_fingerprint/fingerprint.c
new file mode 100644
index 0000000..bfa3b0a
--- /dev/null
+++ b/src/dtls_fingerprint/fingerprint.c
@@ -0,0 +1,74 @@
+#include "fingerprint.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+/*
+ * Destructor for an existing DTLS fingerprint instance.
+ */
+static void rawrtc_dtls_fingerprint_destroy(void* arg) {
+ struct rawrtc_dtls_fingerprint* const fingerprint = arg;
+
+ // Un-reference
+ mem_deref(fingerprint->value);
+}
+
+/*
+ * Create a new DTLS fingerprint instance.
+ * `*fingerprintp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_dtls_fingerprint_create(
+ struct rawrtc_dtls_fingerprint** const fingerprintp, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const algorithm,
+ char* const value // copied
+) {
+ struct rawrtc_dtls_fingerprint* fingerprint;
+ enum rawrtc_code error;
+
+ // Allocate
+ fingerprint = mem_zalloc(sizeof(*fingerprint), rawrtc_dtls_fingerprint_destroy);
+ if (!fingerprint) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/copy
+ fingerprint->algorithm = algorithm;
+ error = rawrtc_strdup(&fingerprint->value, value);
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(fingerprint);
+ } else {
+ // Set pointer
+ *fingerprintp = fingerprint;
+ }
+ return error;
+}
+
+/*
+ * Create a new DTLS fingerprint instance without any value.
+ * The caller MUST set the `value` field after creation.
+ */
+enum rawrtc_code rawrtc_dtls_fingerprint_create_empty(
+ struct rawrtc_dtls_fingerprint** const fingerprintp, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const algorithm) {
+ struct rawrtc_dtls_fingerprint* fingerprint;
+
+ // Allocate
+ fingerprint = mem_zalloc(sizeof(*fingerprint), rawrtc_dtls_fingerprint_destroy);
+ if (!fingerprint) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/copy
+ fingerprint->algorithm = algorithm;
+
+ // Set pointer
+ *fingerprintp = fingerprint;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/dtls_fingerprint/fingerprint.h b/src/dtls_fingerprint/fingerprint.h
new file mode 100644
index 0000000..30b788b
--- /dev/null
+++ b/src/dtls_fingerprint/fingerprint.h
@@ -0,0 +1,14 @@
+#pragma once
+#include <rawrtc/certificate.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+struct rawrtc_dtls_fingerprint {
+ struct le le;
+ enum rawrtc_certificate_sign_algorithm algorithm;
+ char* value; // copied
+};
+
+enum rawrtc_code rawrtc_dtls_fingerprint_create_empty(
+ struct rawrtc_dtls_fingerprint** const fingerprintp, // de-referenced
+ enum rawrtc_certificate_sign_algorithm const algorithm);
diff --git a/src/dtls_fingerprint/meson.build b/src/dtls_fingerprint/meson.build
new file mode 100644
index 0000000..2e32bbf
--- /dev/null
+++ b/src/dtls_fingerprint/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'attributes.c',
+ 'fingerprint.c',
+])
diff --git a/src/dtls_parameters/attributes.c b/src/dtls_parameters/attributes.c
new file mode 100644
index 0000000..e8a6a30
--- /dev/null
+++ b/src/dtls_parameters/attributes.c
@@ -0,0 +1,39 @@
+#include "parameters.h"
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtc/dtls_parameters.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the DTLS parameter's role value.
+ */
+enum rawrtc_code rawrtc_dtls_parameters_get_role(
+ enum rawrtc_dtls_role* rolep, // de-referenced
+ struct rawrtc_dtls_parameters* const parameters) {
+ // Check arguments
+ if (!rolep || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *rolep = parameters->role;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the DTLS parameter's fingerprint array.
+ * `*fingerprintsp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_dtls_parameters_get_fingerprints(
+ struct rawrtc_dtls_fingerprints** const fingerprintsp, // de-referenced
+ struct rawrtc_dtls_parameters* const parameters) {
+ // Check arguments
+ if (!fingerprintsp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set pointer (and reference)
+ *fingerprintsp = mem_ref(parameters->fingerprints);
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/dtls_parameters/meson.build b/src/dtls_parameters/meson.build
new file mode 100644
index 0000000..8710eb0
--- /dev/null
+++ b/src/dtls_parameters/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'parameters.c',
+ 'utils.c',
+])
diff --git a/src/dtls_parameters/parameters.c b/src/dtls_parameters/parameters.c
new file mode 100644
index 0000000..32dc5a2
--- /dev/null
+++ b/src/dtls_parameters/parameters.c
@@ -0,0 +1,173 @@
+#include "parameters.h"
+#include "../dtls_fingerprint/fingerprint.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/dtls_parameters.h>
+#include <rawrtc/dtls_transport.h>
+#include <re.h>
+
+/*
+ * Destructor for an existing DTLS parameter's fingerprints instance.
+ */
+static void rawrtc_dtls_parameters_fingerprints_destroy(void* arg) {
+ struct rawrtc_dtls_fingerprints* const fingerprints = arg;
+ size_t i;
+
+ // Un-reference each item
+ for (i = 0; i < fingerprints->n_fingerprints; ++i) {
+ mem_deref(fingerprints->fingerprints[i]);
+ }
+}
+
+/*
+ * Destructor for an existing DTLS parameters instance.
+ */
+static void rawrtc_dtls_parameters_destroy(void* arg) {
+ struct rawrtc_dtls_parameters* const parameters = arg;
+
+ // Un-reference
+ mem_deref(parameters->fingerprints);
+}
+
+/*
+ * Common code to allocate a DTLS parameters instance.
+ */
+static enum rawrtc_code rawrtc_dtls_parameters_allocate(
+ struct rawrtc_dtls_parameters** const parametersp, // de-referenced
+ enum rawrtc_dtls_role const role,
+ size_t const n_fingerprints) {
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+ struct rawrtc_dtls_parameters* parameters;
+ size_t fingerprints_size;
+
+ // Allocate parameters
+ parameters = mem_zalloc(sizeof(*parameters), rawrtc_dtls_parameters_destroy);
+ if (!parameters) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set role
+ parameters->role = role;
+
+ // Allocate fingerprints array & set length immediately
+ fingerprints_size = sizeof(*parameters) * n_fingerprints;
+ parameters->fingerprints = mem_zalloc(
+ sizeof(*parameters) + fingerprints_size, rawrtc_dtls_parameters_fingerprints_destroy);
+ if (!parameters->fingerprints) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+ parameters->fingerprints->n_fingerprints = n_fingerprints;
+
+out:
+ if (error) {
+ mem_deref(parameters);
+ } else {
+ // Set pointer
+ *parametersp = parameters;
+ }
+ return error;
+}
+
+/*
+ * Create a new DTLS parameters instance.
+ * `*parametersp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_dtls_parameters_create(
+ struct rawrtc_dtls_parameters** const parametersp, // de-referenced
+ enum rawrtc_dtls_role const role,
+ struct rawrtc_dtls_fingerprint* const fingerprints[], // referenced (each item)
+ size_t const n_fingerprints) {
+ struct rawrtc_dtls_parameters* parameters;
+ enum rawrtc_code error;
+ size_t i;
+
+ // Check arguments
+ if (!parametersp || !fingerprints || n_fingerprints < 1) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Create parameters
+ error = rawrtc_dtls_parameters_allocate(¶meters, role, n_fingerprints);
+ if (error) {
+ goto out;
+ }
+
+ // Reference and set each fingerprint
+ for (i = 0; i < n_fingerprints; ++i) {
+ // Null?
+ if (!fingerprints[i]) {
+ error = RAWRTC_CODE_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ // Check algorithm
+ if (fingerprints[i]->algorithm == RAWRTC_CERTIFICATE_SIGN_ALGORITHM_NONE) {
+ error = RAWRTC_CODE_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ // Reference and set fingerprint
+ parameters->fingerprints->fingerprints[i] = mem_ref(fingerprints[i]);
+ }
+
+out:
+ if (error) {
+ mem_deref(parameters);
+ } else {
+ // Set pointer
+ *parametersp = parameters;
+ }
+ return error;
+}
+
+/*
+ * Create parameters from the internal vars of a DTLS transport
+ * instance.
+ */
+enum rawrtc_code rawrtc_dtls_parameters_create_internal(
+ struct rawrtc_dtls_parameters** const parametersp, // de-referenced
+ enum rawrtc_dtls_role const role,
+ struct list* const fingerprints) {
+ size_t n_fingerprints;
+ struct rawrtc_dtls_parameters* parameters;
+ enum rawrtc_code error;
+ struct le* le;
+ size_t i;
+
+ // Check arguments
+ if (!parametersp || !fingerprints) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get fingerprints length
+ n_fingerprints = list_count(fingerprints);
+
+ // Create parameters
+ error = rawrtc_dtls_parameters_allocate(¶meters, role, n_fingerprints);
+ if (error) {
+ goto out;
+ }
+
+ // Reference and set each fingerprint
+ for (le = list_head(fingerprints), i = 0; le != NULL; le = le->next, ++i) {
+ struct rawrtc_dtls_fingerprint* const fingerprint = le->data;
+
+ // Check algorithm
+ if (fingerprint->algorithm == RAWRTC_CERTIFICATE_SIGN_ALGORITHM_NONE) {
+ error = RAWRTC_CODE_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ // Reference and set fingerprint
+ parameters->fingerprints->fingerprints[i] = mem_ref(fingerprint);
+ }
+
+out:
+ if (error) {
+ mem_deref(parameters);
+ } else {
+ // Set pointer
+ *parametersp = parameters;
+ }
+ return error;
+}
diff --git a/src/dtls_parameters/parameters.h b/src/dtls_parameters/parameters.h
new file mode 100644
index 0000000..ac018a2
--- /dev/null
+++ b/src/dtls_parameters/parameters.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtc/dtls_parameters.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+struct rawrtc_dtls_parameters {
+ enum rawrtc_dtls_role role;
+ struct rawrtc_dtls_fingerprints* fingerprints;
+};
+
+enum rawrtc_code rawrtc_dtls_parameters_create_internal(
+ struct rawrtc_dtls_parameters** const parametersp, // de-referenced
+ enum rawrtc_dtls_role const role,
+ struct list* const fingerprints);
+
+int rawrtc_dtls_parameters_debug(
+ struct re_printf* const pf, struct rawrtc_dtls_parameters const* const parameters);
diff --git a/src/dtls_parameters/utils.c b/src/dtls_parameters/utils.c
new file mode 100644
index 0000000..e79e4c5
--- /dev/null
+++ b/src/dtls_parameters/utils.c
@@ -0,0 +1,40 @@
+#include "parameters.h"
+#include "../dtls_fingerprint/fingerprint.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtc/dtls_transport.h>
+#include <re.h>
+
+/*
+ * Print debug information for DTLS parameters.
+ */
+int rawrtc_dtls_parameters_debug(
+ struct re_printf* const pf, struct rawrtc_dtls_parameters const* const parameters) {
+ int err = 0;
+ struct rawrtc_dtls_fingerprints* fingerprints;
+ size_t i;
+
+ // Check arguments
+ if (!parameters) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, " DTLS Parameters <%p>:\n", parameters);
+
+ // Role
+ err |= re_hprintf(pf, " role=%s\n", rawrtc_dtls_role_to_str(parameters->role));
+
+ // Fingerprints
+ fingerprints = parameters->fingerprints;
+ err |= re_hprintf(pf, " Fingerprints <%p>:\n", fingerprints);
+ for (i = 0; i < fingerprints->n_fingerprints; ++i) {
+ // Fingerprint
+ err |= re_hprintf(
+ pf, " algorithm=%s value=%s\n",
+ rawrtc_certificate_sign_algorithm_to_str(fingerprints->fingerprints[i]->algorithm),
+ fingerprints->fingerprints[i]->value);
+ }
+
+ // Done
+ return err;
+}
diff --git a/src/dtls_transport/attributes.c b/src/dtls_transport/attributes.c
new file mode 100644
index 0000000..411e611
--- /dev/null
+++ b/src/dtls_transport/attributes.c
@@ -0,0 +1,40 @@
+#include "transport.h"
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Check for an existing data transport (on top of DTLS).
+ */
+enum rawrtc_code rawrtc_dtls_transport_have_data_transport(
+ bool* const have_data_transportp, // de-referenced
+ struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!have_data_transportp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check if a receive handler has been set.
+ if (transport->receive_handler) {
+ *have_data_transportp = true;
+ } else {
+ *have_data_transportp = false;
+ }
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the current state of the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_get_state(
+ enum rawrtc_dtls_transport_state* const statep, // de-referenced
+ struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!statep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state & done
+ *statep = transport->state;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/dtls_transport/external.c b/src/dtls_transport/external.c
new file mode 100644
index 0000000..2f56096
--- /dev/null
+++ b/src/dtls_transport/external.c
@@ -0,0 +1,64 @@
+#include "transport.h"
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/external.h>
+
+/*
+ * Get external DTLS role.
+ */
+enum rawrtc_code rawrtc_dtls_transport_get_external_role(
+ enum rawrtc_external_dtls_role* const rolep, // de-referenced
+ struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!rolep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert role
+ switch (transport->role) {
+ case RAWRTC_DTLS_ROLE_AUTO:
+ // Unable to convert in this state
+ return RAWRTC_CODE_INVALID_STATE;
+ case RAWRTC_DTLS_ROLE_CLIENT:
+ *rolep = RAWRTC_EXTERNAL_DTLS_ROLE_CLIENT;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_DTLS_ROLE_SERVER:
+ *rolep = RAWRTC_EXTERNAL_DTLS_ROLE_SERVER;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+}
+
+/*
+ * Convert DTLS transport state to external DTLS transport state.
+ */
+enum rawrtc_code rawrtc_dtls_transport_get_external_state(
+ enum rawrtc_external_dtls_transport_state* const statep, // de-referenced
+ struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!statep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert DTLS transport state to external DTLS transport state
+ switch (transport->state) {
+ case RAWRTC_DTLS_TRANSPORT_STATE_NEW:
+ *statep = RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_NEW_OR_CONNECTING;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING:
+ *statep = RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_NEW_OR_CONNECTING;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED:
+ *statep = RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CONNECTED;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_DTLS_TRANSPORT_STATE_CLOSED:
+ *statep = RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CLOSED_OR_FAILED;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_DTLS_TRANSPORT_STATE_FAILED:
+ *statep = RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CLOSED_OR_FAILED;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+}
diff --git a/src/dtls_transport/meson.build b/src/dtls_transport/meson.build
new file mode 100644
index 0000000..3bc7451
--- /dev/null
+++ b/src/dtls_transport/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+ 'attributes.c',
+ 'external.c',
+ 'transport.c',
+ 'utils.c',
+])
diff --git a/src/dtls_transport/transport.c b/src/dtls_transport/transport.c
new file mode 100644
index 0000000..8219697
--- /dev/null
+++ b/src/dtls_transport/transport.c
@@ -0,0 +1,1059 @@
+#include "transport.h"
+#include "../certificate/certificate.h"
+#include "../diffie_hellman_parameters/parameters.h"
+#include "../dtls_fingerprint/fingerprint.h"
+#include "../dtls_parameters/parameters.h"
+#include "../ice_candidate/helper.h"
+#include "../ice_gatherer/gatherer.h"
+#include "../ice_transport/transport.h"
+#include "../main/config.h"
+#include "../utils/utils.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/config.h>
+#include <rawrtc/dtls_fingerprint.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/message_buffer.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+#include <string.h> // memcmp
+
+#define DEBUG_MODULE "dtls-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Embedded DH parameters in DER encoding (bits: 2048)
+ */
+uint8_t const rawrtc_default_dh_parameters[] = {
+ 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xaa, 0x4c, 0x1f, 0x1e, 0xc9, 0xed, 0xfe,
+ 0x5c, 0x50, 0x2d, 0xff, 0xf4, 0x95, 0xf4, 0x80, 0x69, 0xcf, 0xc3, 0x84, 0x29, 0x87, 0xd5, 0x2c,
+ 0x4f, 0xf6, 0x9e, 0x88, 0xa2, 0x5b, 0x61, 0xd2, 0x7d, 0x78, 0x97, 0xce, 0x47, 0x39, 0x9d, 0xc0,
+ 0x95, 0x14, 0x98, 0x1f, 0xa9, 0xa3, 0x42, 0x93, 0x58, 0x49, 0x3d, 0xad, 0xeb, 0x6c, 0x3d, 0x79,
+ 0x2d, 0x27, 0x94, 0x67, 0x4c, 0xdc, 0x94, 0x31, 0xbf, 0xc1, 0x00, 0x9d, 0x96, 0x4a, 0x91, 0xa7,
+ 0x4f, 0xab, 0x48, 0x44, 0xcc, 0x54, 0x1a, 0x4e, 0x2a, 0x8e, 0xa1, 0x81, 0x4b, 0xeb, 0xea, 0xc3,
+ 0xba, 0xd6, 0x03, 0xfb, 0xf2, 0x9a, 0x48, 0x1f, 0xc8, 0xba, 0x73, 0x89, 0x86, 0x25, 0x2e, 0xba,
+ 0x10, 0x80, 0x2a, 0xeb, 0xf9, 0xe2, 0x28, 0xf1, 0xcf, 0x85, 0x0d, 0xeb, 0x2f, 0x61, 0x51, 0x11,
+ 0xe1, 0xe7, 0x82, 0xe5, 0xa7, 0x5d, 0x71, 0x0a, 0xef, 0x8a, 0xe1, 0x97, 0x48, 0x41, 0xac, 0xd7,
+ 0xc5, 0xf7, 0xce, 0xd5, 0xcd, 0x66, 0x1e, 0x6b, 0x0e, 0x82, 0x4e, 0x77, 0x5d, 0x89, 0x3b, 0xe2,
+ 0x94, 0x7a, 0x10, 0xee, 0x5b, 0x5d, 0x36, 0x07, 0x29, 0x8b, 0x06, 0xb6, 0x49, 0x1e, 0x17, 0x17,
+ 0x57, 0xc8, 0xc1, 0x80, 0x24, 0x15, 0x22, 0x9c, 0xb8, 0x59, 0x55, 0x08, 0x41, 0x67, 0x07, 0xca,
+ 0xa8, 0x54, 0x1a, 0xd1, 0xb7, 0x91, 0x2f, 0x41, 0x78, 0xc0, 0xcd, 0x2f, 0x07, 0x49, 0x4b, 0xb9,
+ 0x05, 0xf4, 0xea, 0x72, 0x3a, 0xcf, 0x04, 0x69, 0xcb, 0x5b, 0xe4, 0xcb, 0x4f, 0x72, 0x40, 0xe4,
+ 0x56, 0x1f, 0xca, 0xee, 0x33, 0x2b, 0x29, 0x1a, 0x80, 0xda, 0x01, 0x3f, 0x03, 0xa6, 0xbf, 0x32,
+ 0x02, 0x6c, 0xfb, 0xb1, 0xb5, 0x81, 0xda, 0x32, 0x6f, 0xa1, 0x4b, 0x9f, 0x42, 0x2e, 0x17, 0xc9,
+ 0x95, 0x30, 0xda, 0x16, 0xb7, 0x9a, 0x7c, 0xf4, 0x83, 0x02, 0x01, 0x02,
+};
+size_t const rawrtc_default_dh_parameters_length = ARRAY_SIZE(rawrtc_default_dh_parameters);
+
+/*
+ * List of default DTLS cipher suites.
+ */
+char const* rawrtc_default_dtls_cipher_suites[] = {
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
+ "ECDHE-RSA-CHACHA20-POLY1305",
+ "ECDHE-ECDSA-AES128-GCM-SHA256", // recommended
+ "ECDHE-RSA-AES128-GCM-SHA256",
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-AES256-GCM-SHA384",
+ "DHE-RSA-AES128-GCM-SHA256",
+ "DHE-RSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-AES128-SHA256",
+ "ECDHE-RSA-AES128-SHA256",
+ "ECDHE-ECDSA-AES128-SHA", // required
+ "ECDHE-RSA-AES256-SHA384",
+ "ECDHE-RSA-AES128-SHA",
+ "ECDHE-ECDSA-AES256-SHA384",
+ "ECDHE-ECDSA-AES256-SHA",
+ "ECDHE-RSA-AES256-SHA",
+ "DHE-RSA-AES128-SHA256",
+ "DHE-RSA-AES128-SHA",
+ "DHE-RSA-AES256-SHA256",
+ "DHE-RSA-AES256-SHA",
+};
+size_t const rawrtc_default_dtls_cipher_suites_length =
+ ARRAY_SIZE(rawrtc_default_dtls_cipher_suites);
+
+/*
+ * Handle outgoing buffered DTLS messages.
+ */
+static bool dtls_outgoing_buffer_handler(
+ struct mbuf* const buffer, void* const context, void* const arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ enum rawrtc_code error;
+ (void) context;
+
+ // Send
+ error = rawrtc_dtls_transport_send(transport, buffer);
+ if (error) {
+ DEBUG_WARNING("Could not send buffered packet, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Continue iterating through message queue
+ return true;
+}
+
+/*
+ * Change the state of the ICE transport.
+ * Will call the corresponding handler.
+ * Caller MUST ensure that the same state is not set twice.
+ */
+static void set_state(
+ struct rawrtc_dtls_transport* const transport, enum rawrtc_dtls_transport_state const state) {
+ // Closed or failed: Remove connection
+ if (state == RAWRTC_DTLS_TRANSPORT_STATE_CLOSED ||
+ state == RAWRTC_DTLS_TRANSPORT_STATE_FAILED) {
+ // Remove connection
+ transport->connection = mem_deref(transport->connection);
+
+ // Remove self from ICE transport (if attached)
+ transport->ice_transport->dtls_transport = NULL;
+ }
+
+ // Set state
+ transport->state = state;
+
+ // Connected?
+ if (state == RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+ // Send buffered outgoing DTLS messages
+ enum rawrtc_code const error = rawrtc_message_buffer_clear(
+ &transport->buffered_messages_out, dtls_outgoing_buffer_handler, transport);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not send buffered messages, reason: %s\n", rawrtc_code_to_str(error));
+ }
+ }
+
+ // Call handler (if any)
+ if (transport->state_change_handler) {
+ transport->state_change_handler(state, transport->arg);
+ }
+}
+
+/*
+ * Check if the state is 'closed' or 'failed'.
+ */
+static bool is_closed(struct rawrtc_dtls_transport* const transport // not checked
+) {
+ switch (transport->state) {
+ case RAWRTC_DTLS_TRANSPORT_STATE_CLOSED:
+ case RAWRTC_DTLS_TRANSPORT_STATE_FAILED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * DTLS connection closed handler.
+ */
+static void close_handler(int err, void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ enum rawrtc_code error;
+
+ // Closed?
+ if (!is_closed(transport)) {
+ DEBUG_INFO("DTLS connection closed, reason: %m\n", err);
+
+ // Set to failed if not closed normally
+ if (err != ECONNRESET) {
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_FAILED);
+ }
+
+ // Stop
+ error = rawrtc_dtls_transport_stop(transport);
+ if (error) {
+ DEBUG_WARNING(
+ "DTLS connection closed, could not stop transport: %s\n",
+ rawrtc_code_to_str(error));
+ }
+ } else {
+ DEBUG_PRINTF(
+ "DTLS connection closed (but state is already closed anyway), reason: %m\n", err);
+ }
+}
+
+/*
+ * Handle incoming DTLS messages.
+ */
+static void dtls_receive_handler(struct mbuf* buffer, void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ enum rawrtc_code error;
+
+ // Check state
+ if (is_closed(transport)) {
+ DEBUG_PRINTF("Ignoring incoming DTLS message, transport is closed\n");
+ return;
+ }
+
+ // Handle (if receive handler exists and connected)
+ // Note: Checking for 'connected' state ensures that no data will be received before the
+ // fingerprints have been verified.
+ if (transport->receive_handler && transport->state == RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+ transport->receive_handler(buffer, transport->receive_handler_arg);
+ return;
+ }
+
+ // Buffer message
+ error = rawrtc_message_buffer_append(&transport->buffered_messages_in, buffer, NULL);
+ if (error) {
+ DEBUG_WARNING("Could not buffer incoming packet, reason: %s\n", rawrtc_code_to_str(error));
+ } else {
+ DEBUG_PRINTF("Buffered incoming packet of size %zu\n", mbuf_get_left(buffer));
+ }
+}
+
+/*
+ * Either called by a DTLS connection established event or by the
+ * `start` method of the DTLS transport.
+ * The caller MUST make sure that remote parameters are available and
+ * that the state is NOT 'closed' or 'failed'!
+ */
+static void verify_certificate(struct rawrtc_dtls_transport* const transport // not checked
+) {
+ size_t i;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+ bool valid = false;
+ enum tls_fingerprint algorithm;
+ uint8_t expected_fingerprint[RAWRTC_FINGERPRINT_MAX_SIZE];
+ uint8_t actual_fingerprint[RAWRTC_FINGERPRINT_MAX_SIZE];
+
+ // Verify the peer's certificate
+ // TODO: Fix this. Testing the fingerprint alone is okay for now though.
+ // error = rawrtc_error_to_code(tls_peer_verify(transport->connection));
+ // if (error) {
+ // goto out;
+ // }
+ // DEBUG_PRINTF("Peer's certificate verified\n");
+
+ // Check if *any* of the fingerprints provided matches
+ // Note: We don't verify the peer's certificate since it will almost always
+ // be self-signed.
+ for (i = 0; i < transport->remote_parameters->fingerprints->n_fingerprints; ++i) {
+ struct rawrtc_dtls_fingerprint* const fingerprint =
+ transport->remote_parameters->fingerprints->fingerprints[i];
+ size_t length;
+ size_t bytes_written;
+
+ // Get algorithm
+ error = rawrtc_certificate_sign_algorithm_to_tls_fingerprint(
+ &algorithm, fingerprint->algorithm);
+ if (error) {
+ if (error == RAWRTC_CODE_UNSUPPORTED_ALGORITHM) {
+ continue;
+ }
+ goto out;
+ }
+
+ // Get algorithm digest size
+ error = rawrtc_get_sign_algorithm_length(&length, fingerprint->algorithm);
+ if (error) {
+ if (error == RAWRTC_CODE_UNSUPPORTED_ALGORITHM) {
+ continue;
+ }
+ goto out;
+ }
+
+ // Convert hex-encoded value to binary
+ error = rawrtc_colon_hex_to_bin(
+ &bytes_written, expected_fingerprint, length, fingerprint->value);
+ if (error) {
+ if (error == RAWRTC_CODE_INSUFFICIENT_SPACE) {
+ DEBUG_WARNING("Hex-encoded fingerprint exceeds buffer size!\n");
+ } else {
+ DEBUG_WARNING(
+ "Could not convert hex-encoded fingerprint to binary, reason: %s\n",
+ rawrtc_code_to_str(error));
+ }
+ continue;
+ }
+
+ // Validate length
+ if (bytes_written != length) {
+ DEBUG_WARNING(
+ "Hex-encoded fingerprint should have been %zu bytes but was %zu bytes\n", length,
+ bytes_written);
+ continue;
+ }
+
+ // Get remote fingerprint
+ error = rawrtc_error_to_code(tls_peer_fingerprint(
+ transport->connection, algorithm, actual_fingerprint, sizeof(actual_fingerprint)));
+ if (error) {
+ goto out;
+ }
+
+ // Compare fingerprints
+ if (memcmp(expected_fingerprint, actual_fingerprint, length) == 0) {
+ DEBUG_PRINTF("Peer's certificate fingerprint is valid\n");
+ valid = true;
+ }
+ }
+
+out:
+ if (error || !valid) {
+ DEBUG_WARNING("Verifying certificate failed, reason: %s\n", rawrtc_code_to_str(error));
+ if (!is_closed(transport)) {
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_FAILED);
+ }
+
+ // Stop
+ error = rawrtc_dtls_transport_stop(transport);
+ if (error) {
+ DEBUG_WARNING(
+ "DTLS connection closed, could not stop transport: %s\n",
+ rawrtc_code_to_str(error));
+ }
+ } else {
+ // Connected
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED);
+ }
+}
+
+/*
+ * Handle DTLS connection established event.
+ */
+static void establish_handler(void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+
+ // Check state
+ if (is_closed(transport)) {
+ DEBUG_WARNING("Ignoring established DTLS connection, transport is closed\n");
+ return;
+ }
+
+ // Connection established
+ // Note: State is either 'NEW', 'CONNECTING' or 'FAILED' here
+ DEBUG_INFO("DTLS connection established\n");
+ transport->connection_established = true;
+
+ // Verify certificate & fingerprint (if remote parameters are available)
+ if (transport->remote_parameters) {
+ verify_certificate(transport);
+ }
+}
+
+/*
+ * Handle incoming DTLS connection.
+ */
+static void connect_handler(const struct sa* peer, void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ bool role_is_server;
+ bool have_connection;
+ int err;
+ (void) peer;
+
+ // Check state
+ if (is_closed(transport)) {
+ DEBUG_PRINTF("Ignoring incoming DTLS connection, transport is closed\n");
+ return;
+ }
+
+ // Update role if "auto"
+ if (transport->role == RAWRTC_DTLS_ROLE_AUTO) {
+ DEBUG_PRINTF("Switching role 'auto' -> 'server'\n");
+ transport->role = RAWRTC_DTLS_ROLE_SERVER;
+ }
+
+ // Accept?
+ role_is_server = transport->role == RAWRTC_DTLS_ROLE_SERVER;
+ have_connection = transport->connection != NULL;
+ if (role_is_server && !have_connection) {
+ // Set state to connecting (if not already set)
+ if (transport->state != RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING) {
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING);
+ }
+
+ // Accept and create connection
+ DEBUG_PRINTF("Accepting incoming DTLS connection from %J\n", peer);
+ err = dtls_accept(
+ &transport->connection, transport->context, transport->socket, establish_handler,
+ dtls_receive_handler, close_handler, transport);
+ if (err) {
+ DEBUG_WARNING("Could not accept incoming DTLS connection, reason: %m\n", err);
+ }
+ } else {
+ if (have_connection) {
+ DEBUG_WARNING("Incoming DTLS connect but we already have a connection\n");
+ }
+ if (!role_is_server) {
+ DEBUG_WARNING("Incoming DTLS connect but role is 'client'\n");
+ }
+ }
+}
+
+/*
+ * Initiate a DTLS connect.
+ */
+static enum rawrtc_code do_connect(
+ struct rawrtc_dtls_transport* const transport, const struct sa* const peer) {
+ // Connect
+ DEBUG_PRINTF("Starting DTLS connection to %J\n", peer);
+ return rawrtc_error_to_code(dtls_connect(
+ &transport->connection, transport->context, transport->socket, peer, establish_handler,
+ dtls_receive_handler, close_handler, transport));
+}
+
+/*
+ * Handle outgoing DTLS messages.
+ */
+static int send_handler(
+ struct tls_conn* tc, struct sa const* original_destination, struct mbuf* buffer, void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ struct trice* const ice = transport->ice_transport->gatherer->ice;
+ bool closed = is_closed(transport);
+ struct ice_candpair* candidate_pair;
+ struct udp_sock* udp_socket;
+ int err;
+ (void) tc;
+ (void) original_destination;
+
+ // Note: No need to check if closed as only non-application data may be sent if the
+ // transport is already closed.
+
+ // Get candidate pair with highest priority
+ // Note: This ignores whatever is nominated
+ // TODO: Should we rather use the nominated candidate pair?
+ candidate_pair = list_ledata(list_head(trice_validl(ice)));
+ if (!candidate_pair) {
+ if (!closed) {
+ DEBUG_WARNING("Cannot send message, no valid candidate pair\n");
+ }
+ return ECONNRESET;
+ }
+
+ // Get local candidate's UDP socket
+ // TODO: What about TCP?
+ udp_socket = trice_lcand_sock(ice, candidate_pair->lcand);
+ if (!udp_socket) {
+ if (!closed) {
+ DEBUG_WARNING("Cannot send message, selected candidate pair has no socket\n");
+ }
+ return ECONNRESET;
+ }
+
+ // Send
+ // TODO: Is destination correct?
+ DEBUG_PRINTF(
+ "Sending DTLS message (%zu bytes) to %J (originally: %J) from %J\n", mbuf_get_left(buffer),
+ &candidate_pair->rcand->attr.addr, original_destination, &candidate_pair->lcand->attr.addr);
+ err = udp_send(udp_socket, &candidate_pair->rcand->attr.addr, buffer);
+ if (err) {
+ DEBUG_WARNING("Could not send, error: %m\n", err);
+ }
+ return err;
+}
+
+/*
+ * Handle MTU queries.
+ */
+static size_t mtu_handler(struct tls_conn* tc, void* arg) {
+ (void) tc;
+ (void) arg;
+ // TODO: Choose a sane value.
+ return 1400;
+}
+
+/*
+ * Handle received UDP messages.
+ */
+static bool udp_receive_handler(struct mbuf* const buffer, void* const context, void* const arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ struct sa* source = context;
+ struct sa const* peer;
+
+ // TODO: This handler should be moved into ICE transport
+ // https://tools.ietf.org/search/rfc7983#section-7
+
+ // Update remote peer address (if changed and connection exists)
+ if (transport->connection) {
+ // TODO: It would be cleaner to check if source is in our list of remote candidates
+
+ // TODO: SCTP - Retest path MTU and reset congestion state to the initial state
+ // https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-5
+
+ // Update if changed
+ peer = dtls_peer(transport->connection);
+ if (!sa_cmp(peer, source, SA_ALL)) {
+ DEBUG_PRINTF("Remote changed its peer address from %J to %J\n", peer, source);
+ dtls_set_peer(transport->connection, source);
+ }
+ }
+
+ // Decrypt & receive
+ // Note: No need to check if the transport is already closed as the messages will re-appear in
+ // the `dtls_receive_handler`.
+ dtls_receive(transport->socket, source, buffer);
+
+ // Continue iterating through message queue
+ return true;
+}
+
+/*
+ * Handle received UDP messages (UDP receive helper).
+ */
+static bool udp_receive_helper(struct sa* source, struct mbuf* buffer, void* arg) {
+ // Receive
+ udp_receive_handler(buffer, source, arg);
+
+ // Handled
+ return true;
+}
+
+/*
+ * Destructor for an existing DTLS transport.
+ */
+static void rawrtc_dtls_transport_destroy(void* arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ struct le* le;
+
+ // Stop transport
+ // TODO: Check effects in case transport has been destroyed due to error in create
+ rawrtc_dtls_transport_stop(transport);
+
+ // TODO: Remove once ICE transport and DTLS transport have been separated properly
+ for (le = list_head(&transport->ice_transport->gatherer->local_candidates); le != NULL;
+ le = le->next) {
+ struct rawrtc_candidate_helper* const candidate_helper = le->data;
+ mem_deref(candidate_helper->udp_helper);
+ // TODO: Be aware that UDP packets go to nowhere now...
+ }
+
+ // Un-reference
+ mem_deref(transport->connection);
+ mem_deref(transport->socket);
+ mem_deref(transport->context);
+ list_flush(&transport->fingerprints);
+ list_flush(&transport->buffered_messages_out);
+ list_flush(&transport->buffered_messages_in);
+ mem_deref(transport->remote_parameters);
+ list_flush(&transport->certificates);
+ mem_deref(transport->ice_transport);
+}
+
+/*
+ * Create a new DTLS transport (internal)
+ */
+enum rawrtc_code rawrtc_dtls_transport_create_internal(
+ struct rawrtc_dtls_transport** const transportp, // de-referenced
+ struct rawrtc_ice_transport* const ice_transport, // referenced
+ struct list* certificates, // de-referenced, copied (shallow)
+ rawrtc_dtls_transport_state_change_handler const state_change_handler, // nullable
+ rawrtc_dtls_transport_error_handler const error_handler, // nullable
+ void* const arg // nullable
+) {
+ struct rawrtc_dtls_transport* transport;
+ enum rawrtc_code error;
+ struct le* le;
+ struct rawrtc_certificate* certificate;
+ uint8_t* certificate_der;
+ size_t certificate_der_length;
+
+ // Check arguments
+ if (!transportp || !ice_transport || !certificates) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Check certificates expiration dates
+
+ // Check ICE transport state
+ if (ice_transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Check if another DTLS transport is associated to the ICE transport
+ if (ice_transport->dtls_transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ transport = mem_zalloc(sizeof(*transport), rawrtc_dtls_transport_destroy);
+ if (!transport) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ transport->state = RAWRTC_DTLS_TRANSPORT_STATE_NEW; // TODO: Raise state (delayed)?
+ transport->ice_transport = mem_ref(ice_transport);
+ transport->certificates = *certificates;
+ transport->state_change_handler = state_change_handler;
+ transport->error_handler = error_handler;
+ transport->arg = arg;
+ transport->role = RAWRTC_DTLS_ROLE_AUTO;
+ transport->connection_established = false;
+ list_init(&transport->buffered_messages_in);
+ list_init(&transport->buffered_messages_out);
+ list_init(&transport->fingerprints);
+
+ // Create (D)TLS context
+ DEBUG_PRINTF("Creating DTLS context\n");
+ error = rawrtc_error_to_code(tls_alloc(&transport->context, TLS_METHOD_DTLS, NULL, NULL));
+ if (error) {
+ goto out;
+ }
+
+ // Get DER encoded certificate of choice
+ // TODO: Which certificate should we use?
+ certificate = list_ledata(list_head(&transport->certificates));
+ error = rawrtc_certificate_get_der(
+ &certificate_der, &certificate_der_length, certificate, RAWRTC_CERTIFICATE_ENCODE_BOTH);
+ if (error) {
+ goto out;
+ }
+
+ // Set certificate
+ DEBUG_PRINTF("Setting certificate on DTLS context\n");
+ error = rawrtc_error_to_code(tls_set_certificate_der(
+ transport->context, rawrtc_certificate_key_type_to_tls_keytype(certificate->key_type),
+ certificate_der, certificate_der_length, NULL, 0));
+ mem_deref(certificate_der);
+ if (error) {
+ goto out;
+ }
+
+ // Set Diffie-Hellman parameters
+ // TODO: Get whether to apply DH parameters from config
+ // TODO: Get DH params from config
+ DEBUG_PRINTF("Setting DH parameters on DTLS context\n");
+ error = rawrtc_set_dh_parameters_der(
+ transport->context, rawrtc_default_dh_parameters, rawrtc_default_dh_parameters_length);
+ if (error) {
+ goto out;
+ }
+
+ // Enable elliptic-curve Diffie-Hellman
+ // TODO: Get whether to enable ECDH from config
+ DEBUG_PRINTF("Enabling ECDH on DTLS context\n");
+ error = rawrtc_enable_ecdh(transport->context);
+ if (error) {
+ goto out;
+ }
+
+ // Set cipher suites
+ // TODO: Get cipher suites from config
+ DEBUG_PRINTF("Setting cipher suites on DTLS context\n");
+ error = rawrtc_error_to_code(tls_set_ciphers(
+ transport->context, rawrtc_default_dtls_cipher_suites,
+ rawrtc_default_dtls_cipher_suites_length));
+ if (error) {
+ goto out;
+ }
+
+ // Send client certificate (client) / request client certificate (server)
+ tls_set_verify_client(transport->context);
+
+ // Create DTLS socket
+ DEBUG_PRINTF("Creating DTLS socket\n");
+ error = rawrtc_error_to_code(dtls_socketless(
+ &transport->socket, 1, connect_handler, send_handler, mtu_handler, transport));
+ if (error) {
+ goto out;
+ }
+
+ // Attach to existing candidate pairs
+ for (le = list_head(trice_validl(ice_transport->gatherer->ice)); le != NULL; le = le->next) {
+ struct ice_candpair* candidate_pair = le->data;
+ error = rawrtc_dtls_transport_add_candidate_pair(transport, candidate_pair);
+ if (error) {
+ DEBUG_WARNING(
+ "DTLS transport could not attach to candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+ }
+
+ // Attach to ICE transport
+ // Note: We cannot reference ourselves here as that would introduce a cyclic reference
+ ice_transport->dtls_transport = transport;
+
+out:
+ if (error) {
+ mem_deref(transport);
+ } else {
+ // Set pointer
+ *transportp = transport;
+ }
+ return error;
+}
+
+/*
+ * Create a new DTLS transport.
+ * `*transport` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_dtls_transport_create(
+ struct rawrtc_dtls_transport** const transportp, // de-referenced
+ struct rawrtc_ice_transport* const ice_transport, // referenced
+ struct rawrtc_certificate* const certificates[], // copied (each item)
+ size_t const n_certificates,
+ rawrtc_dtls_transport_state_change_handler const state_change_handler, // nullable
+ rawrtc_dtls_transport_error_handler const error_handler, // nullable
+ void* const arg // nullable
+) {
+ enum rawrtc_code error;
+ struct list certificates_list = LIST_INIT;
+
+ // Append and reference certificates
+ error = rawrtc_certificate_array_to_list(&certificates_list, certificates, n_certificates);
+ if (error) {
+ return error;
+ }
+
+ // Create DTLS transport
+ return rawrtc_dtls_transport_create_internal(
+ transportp, ice_transport, &certificates_list, state_change_handler, error_handler, arg);
+}
+
+/*
+ * Let the DTLS transport attach itself to a candidate pair.
+ * TODO: Separate ICE transport and DTLS transport properly (like data transport)
+ */
+enum rawrtc_code rawrtc_dtls_transport_add_candidate_pair(
+ struct rawrtc_dtls_transport* const transport, struct ice_candpair* const candidate_pair) {
+ enum rawrtc_code error;
+ struct rawrtc_candidate_helper* candidate_helper = NULL;
+
+ // Check arguments
+ if (!transport || !candidate_pair) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (is_closed(transport)) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // TODO: Check if already attached
+
+ // Find candidate helper
+ error = rawrtc_candidate_helper_find(
+ &candidate_helper, &transport->ice_transport->gatherer->local_candidates,
+ candidate_pair->lcand);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not find matching candidate helper for candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Receive buffered packets
+ error = rawrtc_message_buffer_clear(
+ &transport->ice_transport->gatherer->buffered_messages, udp_receive_handler, transport);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not handle buffered packets on candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Attach this transport's receive handler
+ error = rawrtc_candidate_helper_set_receive_handler(
+ candidate_helper, udp_receive_helper, transport);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not find matching candidate helper for candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Do connect (if client and no connection)
+ if (transport->role == RAWRTC_DTLS_ROLE_CLIENT && !transport->connection) {
+ error = do_connect(transport, &candidate_pair->rcand->attr.addr);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not start DTLS connection for candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+ }
+
+out:
+ if (!error) {
+ DEBUG_PRINTF("Attached DTLS transport to candidate pair\n");
+ }
+ return error;
+}
+
+/*
+ * Start the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_start(
+ struct rawrtc_dtls_transport* const transport,
+ struct rawrtc_dtls_parameters* const remote_parameters // referenced
+) {
+ enum rawrtc_code error;
+ enum rawrtc_ice_role ice_role;
+
+ // Check arguments
+ if (!transport || !remote_parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Validate parameters
+ if (remote_parameters->fingerprints->n_fingerprints < 1) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ // Note: Checking for 'remote_parameters' ensures that 'start' is not called twice
+ if (transport->remote_parameters || is_closed(transport)) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set state to connecting (if not already set)
+ if (transport->state != RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING) {
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING);
+ }
+
+ // Get ICE role
+ error = rawrtc_ice_transport_get_role(&ice_role, transport->ice_transport);
+ if (error) {
+ return error;
+ }
+
+ // Determine role
+ if (remote_parameters->role == RAWRTC_DTLS_ROLE_AUTO) {
+ switch (ice_role) {
+ case RAWRTC_ICE_ROLE_CONTROLLED:
+ transport->role = RAWRTC_DTLS_ROLE_CLIENT;
+ DEBUG_PRINTF("Switching role 'auto' -> 'client'\n");
+ break;
+ case RAWRTC_ICE_ROLE_CONTROLLING:
+ transport->role = RAWRTC_DTLS_ROLE_SERVER;
+ DEBUG_PRINTF("Switching role 'auto' -> 'server'\n");
+ break;
+ default:
+ // Cannot continue if ICE transport role is unknown
+ DEBUG_WARNING("ICE role must be set before DTLS transport can be started!\n");
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+ } else if (remote_parameters->role == RAWRTC_DTLS_ROLE_SERVER) {
+ transport->role = RAWRTC_DTLS_ROLE_CLIENT;
+ DEBUG_PRINTF("Switching role 'server' -> 'client'\n");
+ } else {
+ transport->role = RAWRTC_DTLS_ROLE_SERVER;
+ DEBUG_PRINTF("Switching role 'client' -> 'server'\n");
+ }
+
+ // Connect (if client)
+ if (transport->role == RAWRTC_DTLS_ROLE_CLIENT) {
+ struct ice_candpair* candidate_pair;
+
+ // Reset existing connections
+ if (transport->connection) {
+ // Note: This is needed as ORTC requires us to reset previous DTLS connections
+ // if the remote role is 'server'
+ DEBUG_PRINTF("Resetting DTLS connection\n");
+ transport->connection = mem_deref(transport->connection);
+ transport->connection_established = false;
+ }
+
+ // Get selected candidate pair
+ candidate_pair =
+ list_ledata(list_head(trice_validl(transport->ice_transport->gatherer->ice)));
+
+ // Do connect (if we have a valid candidate pair)
+ if (candidate_pair) {
+ error = do_connect(transport, &candidate_pair->rcand->attr.addr);
+ if (error) {
+ goto out;
+ }
+ }
+ } else {
+ // Verify certificate & fingerprint (if connection is established)
+ if (transport->connection_established) {
+ verify_certificate(transport);
+ }
+ }
+
+out:
+ if (error) {
+ transport->connection = mem_deref(transport->connection);
+ } else {
+ // Set remote parameters
+ transport->remote_parameters = mem_ref(remote_parameters);
+ }
+ return error;
+}
+
+/*
+ * Pipe buffered messages into the data receive handler that has a
+ * different signature.
+ */
+static bool intermediate_receive_handler(
+ struct mbuf* const buffer, void* const context, void* const arg) {
+ struct rawrtc_dtls_transport* const transport = arg;
+ (void) context;
+
+ // Pipe into the actual receive handler
+ if (transport->receive_handler) {
+ transport->receive_handler(buffer, transport->receive_handler_arg);
+ } else {
+ DEBUG_WARNING("No receive handler, discarded %zu bytes\n", mbuf_get_left(buffer));
+ }
+
+ // Continue iterating through message queue
+ return true;
+}
+
+/*
+ * Set a data transport on the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_set_data_transport(
+ struct rawrtc_dtls_transport* const transport,
+ rawrtc_dtls_transport_receive_handler const receive_handler,
+ void* const arg) {
+ enum rawrtc_code error;
+ bool have_data_transport;
+
+ // Check arguments
+ if (!transport || !receive_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check for existing data transport
+ error = rawrtc_dtls_transport_have_data_transport(&have_data_transport, transport);
+ if (error) {
+ return error;
+ }
+ if (have_data_transport) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set handler
+ transport->receive_handler = receive_handler;
+ transport->receive_handler_arg = arg;
+
+ // Receive buffered messages
+ error = rawrtc_message_buffer_clear(
+ &transport->buffered_messages_in, intermediate_receive_handler, transport);
+ if (error) {
+ return error;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Remove an existing data transport from the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_clear_data_transport(
+ struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Clear buffered messages (?)
+
+ // Clear handler and argument
+ transport->receive_handler = NULL;
+ transport->receive_handler_arg = NULL;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Send a data message over the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_send(
+ struct rawrtc_dtls_transport* const transport, struct mbuf* const buffer) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !buffer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (is_closed(transport)) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Connected?
+ if (transport->state == RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+ return rawrtc_error_to_code(dtls_send(transport->connection, buffer));
+ }
+
+ // Buffer message
+ error = rawrtc_message_buffer_append(&transport->buffered_messages_out, buffer, NULL);
+ if (error) {
+ DEBUG_WARNING("Could not buffer outgoing packet, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Buffered message
+ DEBUG_PRINTF("Buffered outgoing packet of size %zu\n", mbuf_get_left(buffer));
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Stop and close the DTLS transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_stop(struct rawrtc_dtls_transport* const transport) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (is_closed(transport)) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Update state
+ set_state(transport, RAWRTC_DTLS_TRANSPORT_STATE_CLOSED);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get local DTLS parameters of a transport.
+ */
+enum rawrtc_code rawrtc_dtls_transport_get_local_parameters(
+ struct rawrtc_dtls_parameters** const parametersp, // de-referenced
+ struct rawrtc_dtls_transport* const transport) {
+ // TODO: Get config from struct
+ enum rawrtc_certificate_sign_algorithm const algorithm = rawrtc_default_config.sign_algorithm;
+ struct le* le;
+ struct rawrtc_dtls_fingerprint* fingerprint;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!parametersp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (is_closed(transport)) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Lazy-create fingerprints
+ if (list_isempty(&transport->fingerprints)) {
+ for (le = list_head(&transport->certificates); le != NULL; le = le->next) {
+ struct rawrtc_certificate* certificate = le->data;
+
+ // Create fingerprint
+ error = rawrtc_dtls_fingerprint_create_empty(&fingerprint, algorithm);
+ if (error) {
+ return error;
+ }
+
+ // Get and set fingerprint of certificate
+ error = rawrtc_certificate_get_fingerprint(&fingerprint->value, certificate, algorithm);
+ if (error) {
+ return error;
+ }
+
+ // Append fingerprint
+ list_append(&transport->fingerprints, &fingerprint->le, fingerprint);
+ }
+ }
+
+ // Create and return DTLS parameters instance
+ return rawrtc_dtls_parameters_create_internal(
+ parametersp, transport->role, &transport->fingerprints);
+}
diff --git a/src/dtls_transport/transport.h b/src/dtls_transport/transport.h
new file mode 100644
index 0000000..a954d0a
--- /dev/null
+++ b/src/dtls_transport/transport.h
@@ -0,0 +1,68 @@
+#pragma once
+#include <rawrtc/dtls_parameters.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/external.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Handle inbound application data.
+ */
+typedef void (*rawrtc_dtls_transport_receive_handler)(struct mbuf* const buffer, void* const arg);
+
+struct rawrtc_dtls_transport {
+ enum rawrtc_dtls_transport_state state;
+ struct rawrtc_ice_transport* ice_transport; // referenced
+ struct list certificates; // deep-copied
+ rawrtc_dtls_transport_state_change_handler state_change_handler; // nullable
+ rawrtc_dtls_transport_error_handler error_handler; // nullable
+ void* arg; // nullable
+ struct rawrtc_dtls_parameters* remote_parameters; // referenced
+ enum rawrtc_dtls_role role;
+ bool connection_established;
+ struct list buffered_messages_in;
+ struct list buffered_messages_out;
+ struct list fingerprints;
+ struct tls* context;
+ struct dtls_sock* socket;
+ struct tls_conn* connection;
+ rawrtc_dtls_transport_receive_handler receive_handler;
+ void* receive_handler_arg;
+};
+
+enum rawrtc_code rawrtc_dtls_transport_create_internal(
+ struct rawrtc_dtls_transport** const transportp, // de-referenced
+ struct rawrtc_ice_transport* const ice_transport, // referenced
+ struct list* certificates, // de-referenced, copied (shallow)
+ rawrtc_dtls_transport_state_change_handler const state_change_handler, // nullable
+ rawrtc_dtls_transport_error_handler const error_handler, // nullable
+ void* const arg // nullable
+);
+
+enum rawrtc_code rawrtc_dtls_transport_add_candidate_pair(
+ struct rawrtc_dtls_transport* const transport, struct ice_candpair* const candidate_pair);
+
+enum rawrtc_code rawrtc_dtls_transport_have_data_transport(
+ bool* const have_data_transportp, // de-referenced
+ struct rawrtc_dtls_transport* const transport);
+
+enum rawrtc_code rawrtc_dtls_transport_set_data_transport(
+ struct rawrtc_dtls_transport* const transport,
+ rawrtc_dtls_transport_receive_handler const receive_handler,
+ void* const arg);
+
+enum rawrtc_code rawrtc_dtls_transport_clear_data_transport(
+ struct rawrtc_dtls_transport* const transport);
+
+enum rawrtc_code rawrtc_dtls_transport_send(
+ struct rawrtc_dtls_transport* const transport, struct mbuf* const buffer);
+
+enum rawrtc_code rawrtc_dtls_transport_get_external_role(
+ enum rawrtc_external_dtls_role* const rolep, // de-referenced
+ struct rawrtc_dtls_transport* const transport);
+
+enum rawrtc_code rawrtc_dtls_transport_get_external_state(
+ enum rawrtc_external_dtls_transport_state* const statep, // de-referenced
+ struct rawrtc_dtls_transport* const transport);
diff --git a/src/dtls_transport/utils.c b/src/dtls_transport/utils.c
new file mode 100644
index 0000000..7b96fc5
--- /dev/null
+++ b/src/dtls_transport/utils.c
@@ -0,0 +1,75 @@
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the corresponding name for an ICE transport state.
+ */
+char const* rawrtc_dtls_transport_state_to_name(enum rawrtc_dtls_transport_state const state) {
+ switch (state) {
+ case RAWRTC_DTLS_TRANSPORT_STATE_NEW:
+ return "new";
+ case RAWRTC_DTLS_TRANSPORT_STATE_CONNECTING:
+ return "connecting";
+ case RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED:
+ return "connected";
+ case RAWRTC_DTLS_TRANSPORT_STATE_CLOSED:
+ return "closed";
+ case RAWRTC_DTLS_TRANSPORT_STATE_FAILED:
+ return "failed";
+ default:
+ return "???";
+ }
+}
+
+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[] = {
+ "auto",
+ "client",
+ "server",
+};
+
+static size_t const map_dtls_role_length = ARRAY_SIZE(map_enum_dtls_role);
+
+/*
+ * Translate a DTLS role to str.
+ */
+char const* rawrtc_dtls_role_to_str(enum rawrtc_dtls_role const role) {
+ size_t i;
+
+ for (i = 0; i < map_dtls_role_length; ++i) {
+ if (map_enum_dtls_role[i] == role) {
+ return map_str_dtls_role[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to a DTLS role (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_dtls_role(
+ enum rawrtc_dtls_role* const rolep, // de-referenced
+ char const* const str) {
+ size_t i;
+
+ // Check arguments
+ if (!rolep || !str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_dtls_role_length; ++i) {
+ if (str_casecmp(map_str_dtls_role[i], str) == 0) {
+ *rolep = map_enum_dtls_role[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
diff --git a/src/ice_candidate/attributes.c b/src/ice_candidate/attributes.c
new file mode 100644
index 0000000..822c770
--- /dev/null
+++ b/src/ice_candidate/attributes.c
@@ -0,0 +1,296 @@
+#include "candidate.h"
+#include <rawrtc/ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Get the ICE candidate's foundation.
+ * `*foundationp` will be set to a copy of the foundation that must be
+ * unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_foundation(
+ char** const foundationp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ // Check arguments
+ if (!candidate || !foundationp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set copied foundation
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ return rawrtc_strdup(foundationp, candidate->candidate.raw_candidate->foundation);
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ return rawrtc_strdup(
+ foundationp, candidate->candidate.local_candidate->attr.foundation);
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ return rawrtc_strdup(
+ foundationp, candidate->candidate.remote_candidate->attr.foundation);
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+}
+
+/*
+ * Get the ICE candidate's priority.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_priority(
+ uint32_t* const priorityp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ // Check arguments
+ if (!candidate || !priorityp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set priority
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ *priorityp = candidate->candidate.raw_candidate->priority;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ *priorityp = candidate->candidate.local_candidate->attr.prio;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ *priorityp = candidate->candidate.remote_candidate->attr.prio;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+}
+
+/*
+ * Get the ICE candidate's IP address.
+ * `*ipp` will be set to a copy of the IP address that must be
+ * unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_ip(
+ char** const ipp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ // Check arguments
+ if (!candidate || !ipp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set copied IP address
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ return rawrtc_strdup(ipp, candidate->candidate.raw_candidate->ip);
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ return rawrtc_sdprintf(ipp, "%j", &candidate->candidate.local_candidate->attr.addr);
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ return rawrtc_sdprintf(ipp, "%j", &candidate->candidate.remote_candidate->attr.addr);
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+}
+
+/*
+ * Get the ICE candidate's protocol.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_protocol(
+ enum rawrtc_ice_protocol* const protocolp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ int ipproto;
+
+ // Check arguments
+ if (!candidate || !protocolp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set protocol
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ *protocolp = candidate->candidate.raw_candidate->protocol;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ ipproto = candidate->candidate.local_candidate->attr.proto;
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ ipproto = candidate->candidate.remote_candidate->attr.proto;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+ return rawrtc_ipproto_to_ice_protocol(protocolp, ipproto);
+}
+
+/*
+ * Get the ICE candidate's port.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_port(
+ uint16_t* const portp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ // Check arguments
+ if (!candidate || !portp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set port
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ *portp = candidate->candidate.raw_candidate->port;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ *portp = sa_port(&candidate->candidate.local_candidate->attr.addr);
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ *portp = sa_port(&candidate->candidate.remote_candidate->attr.addr);
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+}
+
+/*
+ * Get the ICE candidate's type.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_type(
+ enum rawrtc_ice_candidate_type* typep, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ // Check arguments
+ if (!candidate || !typep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set type
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ *typep = candidate->candidate.raw_candidate->type;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ return rawrtc_ice_cand_type_to_ice_candidate_type(
+ typep, candidate->candidate.local_candidate->attr.type);
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ return rawrtc_ice_cand_type_to_ice_candidate_type(
+ typep, candidate->candidate.remote_candidate->attr.type);
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+}
+
+/*
+ * Get the ICE candidate's TCP type.
+ * Return `RAWRTC_CODE_NO_VALUE` in case the protocol is not TCP.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_tcp_type(
+ enum rawrtc_ice_tcp_candidate_type* typep, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ struct ice_cand_attr* re_candidate;
+
+ // Check arguments
+ if (!candidate || !typep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set type/get re candidate
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ *typep = candidate->candidate.raw_candidate->tcp_type;
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ re_candidate = &candidate->candidate.local_candidate->attr;
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ re_candidate = &candidate->candidate.remote_candidate->attr;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set type from re candidate if TCP
+ if (re_candidate->proto == IPPROTO_TCP) {
+ return rawrtc_ice_tcptype_to_ice_tcp_candidate_type(typep, re_candidate->tcptype);
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the ICE candidate's related IP address.
+ * `*related_address` will be set to a copy of the related address that
+ * must be unreferenced.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no related address exists.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_related_address(
+ char** const related_addressp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ struct ice_cand_attr* re_candidate = NULL;
+
+ // Check arguments
+ if (!candidate || !related_addressp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set copied related IP address/get re candidate
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ if (candidate->candidate.raw_candidate->related_address) {
+ return rawrtc_strdup(
+ related_addressp, candidate->candidate.raw_candidate->related_address);
+ }
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ re_candidate = &candidate->candidate.local_candidate->attr;
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ re_candidate = &candidate->candidate.remote_candidate->attr;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set copied related IP address from re candidate
+ if (re_candidate && sa_isset(&re_candidate->rel_addr, SA_ADDR)) {
+ return rawrtc_sdprintf(
+ related_addressp, "%j", &candidate->candidate.local_candidate->attr.rel_addr);
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the ICE candidate's related IP address' port.
+ * `*related_portp` will be set to a copy of the related address'
+ * port.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no related port exists.
+ */
+enum rawrtc_code rawrtc_ice_candidate_get_related_port(
+ uint16_t* const related_portp, // de-referenced
+ struct rawrtc_ice_candidate* const candidate) {
+ struct ice_cand_attr* re_candidate = NULL;
+
+ // Check arguments
+ if (!candidate || !related_portp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set port
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ if (candidate->candidate.raw_candidate->related_address) {
+ *related_portp = candidate->candidate.raw_candidate->related_port;
+ return RAWRTC_CODE_SUCCESS;
+ }
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ re_candidate = &candidate->candidate.local_candidate->attr;
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ re_candidate = &candidate->candidate.remote_candidate->attr;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set copied related IP address' port from re candidate
+ if (re_candidate && sa_isset(&re_candidate->rel_addr, SA_PORT)) {
+ *related_portp = sa_port(&candidate->candidate.local_candidate->attr.rel_addr);
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
diff --git a/src/ice_candidate/candidate.c b/src/ice_candidate/candidate.c
new file mode 100644
index 0000000..b5db1b0
--- /dev/null
+++ b/src/ice_candidate/candidate.c
@@ -0,0 +1,274 @@
+#include "candidate.h"
+#include <rawrtc/config.h>
+#include <rawrtc/ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+
+#define DEBUG_MODULE "ice-candidate"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Calculate the ICE candidate priority.
+ *
+ * We prefer:
+ *
+ * 1. UDP over TCP, then
+ * 2. IPv6 over IPv4, then
+ * 3. Older candidates over newer candidates.
+ *
+ * TODO: We should follow ICE Dual-Stack Recommendations.
+ * See: https://tools.ietf.org/html/rfc8421#section-4
+ */
+uint32_t rawrtc_ice_candidate_calculate_priority(
+ uint32_t const n_candidates,
+ enum ice_cand_type const candidate_type,
+ int const protocol,
+ int const address_family,
+ enum ice_tcptype const tcp_type) {
+ uint16_t const age =
+ n_candidates > (1 << 13) ? (uint16_t) 0 : (uint16_t)((1 << 13) - n_candidates);
+ uint16_t const is_udp = protocol == IPPROTO_UDP ? (uint16_t) 1 : (uint16_t) 0;
+ uint16_t const is_ipv6 = address_family == AF_INET6 ? (uint16_t) 1 : (uint16_t) 0;
+ (void) tcp_type;
+ // TODO: Set correct component ID
+ return ice_cand_calc_prio(candidate_type, age | is_ipv6 << 14 | is_udp << 15, 1);
+}
+
+/*
+ * Destructor for an existing ICE candidate.
+ */
+static void rawrtc_ice_candidate_raw_destroy(void* arg) {
+ struct rawrtc_ice_candidate_raw* const candidate = arg;
+
+ // Un-reference
+ mem_deref(candidate->related_address);
+ mem_deref(candidate->ip);
+ mem_deref(candidate->foundation);
+}
+
+/*
+ * Create a raw ICE candidate (pending candidate).
+ */
+static enum rawrtc_code rawrtc_ice_candidate_raw_create(
+ struct rawrtc_ice_candidate_raw** const candidatep, // de-referenced
+ struct pl* const foundation, // copied
+ uint32_t const priority,
+ struct pl* const ip, // copied
+ enum rawrtc_ice_protocol const protocol,
+ uint16_t const port,
+ enum rawrtc_ice_candidate_type const type,
+ enum rawrtc_ice_tcp_candidate_type const tcp_type,
+ struct pl* const related_address, // copied, nullable
+ uint16_t const related_port) {
+ struct rawrtc_ice_candidate_raw* candidate;
+ enum rawrtc_code error;
+
+ // Allocate
+ candidate = mem_zalloc(sizeof(*candidate), rawrtc_ice_candidate_raw_destroy);
+ if (!candidate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/copy
+ error = rawrtc_error_to_code(pl_strdup(&candidate->foundation, foundation));
+ if (error) {
+ goto out;
+ }
+ candidate->priority = priority;
+ error = rawrtc_error_to_code(pl_strdup(&candidate->ip, ip));
+ if (error) {
+ goto out;
+ }
+ candidate->protocol = protocol;
+ candidate->port = port;
+ candidate->type = type;
+ candidate->tcp_type = tcp_type;
+ if (pl_isset(related_address)) {
+ error = rawrtc_error_to_code(pl_strdup(&candidate->related_address, related_address));
+ if (error) {
+ goto out;
+ }
+ }
+ candidate->related_port = related_port;
+
+out:
+ if (error) {
+ mem_deref(candidate);
+ } else {
+ // Set pointer
+ *candidatep = candidate;
+ DEBUG_PRINTF("Created candidate (raw): %r\n", ip);
+ }
+ return error;
+}
+
+/*
+ * Destructor for an existing ICE candidate.
+ */
+static void rawrtc_ice_candidate_destroy(void* arg) {
+ struct rawrtc_ice_candidate* const candidate = arg;
+
+ // Un-reference
+ switch (candidate->storage_type) {
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RAW:
+ mem_deref(candidate->candidate.raw_candidate);
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_LCAND:
+ mem_deref(candidate->candidate.local_candidate);
+ break;
+ case RAWRTC_ICE_CANDIDATE_STORAGE_RCAND:
+ mem_deref(candidate->candidate.remote_candidate);
+ break;
+ }
+}
+
+/*
+ * Create an ICE candidate (pl variant).
+ */
+enum rawrtc_code rawrtc_ice_candidate_create_internal(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct pl* const foundation, // copied
+ uint32_t const priority,
+ struct pl* const ip, // copied
+ enum rawrtc_ice_protocol const protocol,
+ uint16_t const port,
+ enum rawrtc_ice_candidate_type const type,
+ enum rawrtc_ice_tcp_candidate_type const tcp_type,
+ struct pl* const related_address, // copied, nullable
+ uint16_t const related_port) {
+ struct rawrtc_ice_candidate* candidate;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!candidatep || !pl_isset(foundation) || !pl_isset(ip)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ candidate = mem_zalloc(sizeof(*candidate), rawrtc_ice_candidate_destroy);
+ if (!candidate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set storage type
+ candidate->storage_type = RAWRTC_ICE_CANDIDATE_STORAGE_RAW;
+
+ // Create raw candidate
+ error = rawrtc_ice_candidate_raw_create(
+ &candidate->candidate.raw_candidate, foundation, priority, ip, protocol, port, type,
+ tcp_type, related_address, related_port);
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(candidate);
+ } else {
+ // Set pointer
+ *candidatep = candidate;
+ }
+ return error;
+}
+
+/*
+ * Create an ICE candidate.
+ * `*candidatep` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_candidate_create(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ char* const foundation, // copied
+ uint32_t const priority,
+ char* const ip, // copied
+ enum rawrtc_ice_protocol const protocol,
+ uint16_t const port,
+ enum rawrtc_ice_candidate_type const type,
+ enum rawrtc_ice_tcp_candidate_type const tcp_type,
+ char* const related_address, // copied, nullable
+ uint16_t const related_port) {
+ struct pl foundation_pl;
+ struct pl ip_pl;
+ struct pl related_address_pl = PL_INIT;
+
+ // Check arguments
+ if (!foundation || !ip) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert str to pl
+ pl_set_str(&foundation_pl, foundation);
+ pl_set_str(&ip_pl, ip);
+ if (related_address) {
+ pl_set_str(&related_address_pl, related_address);
+ }
+
+ // Create ICE candidate
+ return rawrtc_ice_candidate_create_internal(
+ candidatep, &foundation_pl, priority, &ip_pl, protocol, port, type, tcp_type,
+ &related_address_pl, related_port);
+}
+
+/*
+ * Create an ICE candidate instance from an existing local candidate.
+ */
+enum rawrtc_code rawrtc_ice_candidate_create_from_local_candidate(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct ice_lcand* const local_candidate // referenced
+) {
+ struct rawrtc_ice_candidate* candidate;
+
+ // Check arguments
+ if (!candidatep || !local_candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ candidate = mem_zalloc(sizeof(*candidate), rawrtc_ice_candidate_destroy);
+ if (!candidate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set storage type and reference local candidate
+ candidate->storage_type = RAWRTC_ICE_CANDIDATE_STORAGE_LCAND;
+ candidate->candidate.local_candidate = mem_ref(local_candidate);
+
+ // Set pointer
+ *candidatep = candidate;
+ DEBUG_PRINTF(
+ "Created candidate (lcand): %J\n", &candidate->candidate.local_candidate->attr.addr);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Create an ICE candidate instance from an existing remote candidate.
+ */
+enum rawrtc_code rawrtc_ice_candidate_create_from_remote_candidate(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct ice_rcand* const remote_candidate // referenced
+) {
+ struct rawrtc_ice_candidate* candidate;
+
+ // Check arguments
+ if (!candidatep || !remote_candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ candidate = mem_zalloc(sizeof(*candidate), rawrtc_ice_candidate_destroy);
+ if (!candidate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set storage type and reference remote candidate
+ candidate->storage_type = RAWRTC_ICE_CANDIDATE_STORAGE_RCAND;
+ candidate->candidate.remote_candidate = mem_ref(remote_candidate);
+
+ // Set pointer
+ *candidatep = candidate;
+ DEBUG_PRINTF(
+ "Created candidate (rcand): %j\n", &candidate->candidate.remote_candidate->attr.addr);
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_candidate/candidate.h b/src/ice_candidate/candidate.h
new file mode 100644
index 0000000..3dec216
--- /dev/null
+++ b/src/ice_candidate/candidate.h
@@ -0,0 +1,86 @@
+#pragma once
+#include "../ice_candidate/candidate.h"
+#include <rawrtc/ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * ICE candidate storage type (internal).
+ */
+enum rawrtc_ice_candidate_storage {
+ RAWRTC_ICE_CANDIDATE_STORAGE_RAW,
+ RAWRTC_ICE_CANDIDATE_STORAGE_LCAND,
+ RAWRTC_ICE_CANDIDATE_STORAGE_RCAND,
+};
+
+/*
+ * Raw ICE candidate (pending candidate).
+ */
+struct rawrtc_ice_candidate_raw {
+ char* foundation; // copied
+ uint32_t priority;
+ char* ip; // copied
+ enum rawrtc_ice_protocol protocol;
+ uint16_t port;
+ enum rawrtc_ice_candidate_type type;
+ enum rawrtc_ice_tcp_candidate_type tcp_type;
+ char* related_address; // copied, nullable
+ uint16_t related_port;
+};
+
+struct rawrtc_ice_candidate {
+ enum rawrtc_ice_candidate_storage storage_type;
+ union {
+ struct rawrtc_ice_candidate_raw* raw_candidate;
+ struct ice_lcand* local_candidate;
+ struct ice_rcand* remote_candidate;
+ } candidate;
+};
+
+// Note: Cannot be public until it uses fixed size types in signature (stdint)
+uint32_t rawrtc_ice_candidate_calculate_priority(
+ uint32_t const n_candidates,
+ enum ice_cand_type const candidate_type,
+ int const protocol,
+ int const address_family,
+ enum ice_tcptype const tcp_type);
+
+enum rawrtc_code rawrtc_ice_candidate_create_internal(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct pl* const foundation, // copied
+ uint32_t const priority,
+ struct pl* const ip, // copied
+ enum rawrtc_ice_protocol const protocol,
+ uint16_t const port,
+ enum rawrtc_ice_candidate_type const type,
+ enum rawrtc_ice_tcp_candidate_type const tcp_type,
+ struct pl* const related_address, // copied, nullable
+ uint16_t const related_port);
+
+enum rawrtc_code rawrtc_ice_candidate_create_from_local_candidate(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct ice_lcand* const local_candidate // referenced
+);
+
+enum rawrtc_code rawrtc_ice_candidate_create_from_remote_candidate(
+ struct rawrtc_ice_candidate** const candidatep, // de-referenced
+ struct ice_rcand* const remote_candidate // referenced
+);
+
+int rawrtc_ice_candidate_debug(
+ struct re_printf* const pf, struct rawrtc_ice_candidate* const candidate);
+
+enum ice_cand_type rawrtc_ice_candidate_type_to_ice_cand_type(
+ enum rawrtc_ice_candidate_type const type);
+
+enum rawrtc_code rawrtc_ice_cand_type_to_ice_candidate_type(
+ enum rawrtc_ice_candidate_type* const typep, // de-referenced
+ const enum ice_cand_type re_type);
+
+enum ice_tcptype rawrtc_ice_tcp_candidate_type_to_ice_tcptype(
+ const enum rawrtc_ice_tcp_candidate_type type);
+
+enum rawrtc_code rawrtc_ice_tcptype_to_ice_tcp_candidate_type(
+ enum rawrtc_ice_tcp_candidate_type* const typep, // de-referenced
+ const enum ice_tcptype re_type);
diff --git a/src/ice_candidate/helper.c b/src/ice_candidate/helper.c
new file mode 100644
index 0000000..ff74c71
--- /dev/null
+++ b/src/ice_candidate/helper.c
@@ -0,0 +1,209 @@
+#include "helper.h"
+#include "../ice_gatherer/gatherer.h"
+#include "../ice_server/server.h"
+#include <rawrtc/main.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Destructor for an existing candidate helper.
+ */
+static void rawrtc_candidate_helper_destroy(void* arg) {
+ struct rawrtc_candidate_helper* const local_candidate = arg;
+
+ // Un-reference
+ list_flush(&local_candidate->stun_sessions);
+ mem_deref(local_candidate->udp_helper);
+ mem_deref(local_candidate->candidate);
+ mem_deref(local_candidate->gatherer);
+}
+
+/*
+ * Create a candidate helper.
+ */
+enum rawrtc_code rawrtc_candidate_helper_create(
+ struct rawrtc_candidate_helper** const candidate_helperp, // de-referenced
+ struct rawrtc_ice_gatherer* gatherer,
+ struct ice_lcand* const candidate,
+ udp_helper_recv_h* const receive_handler,
+ void* const arg) {
+ struct rawrtc_candidate_helper* candidate_helper;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!candidate_helperp || !gatherer || !candidate || !receive_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Create candidate helper
+ candidate_helper = mem_zalloc(sizeof(*candidate_helper), rawrtc_candidate_helper_destroy);
+ if (!candidate_helper) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ candidate_helper->gatherer = mem_ref(gatherer);
+ candidate_helper->candidate = mem_ref(candidate);
+ candidate_helper->srflx_pending_count = 0;
+ candidate_helper->relay_pending_count = 0;
+
+ // Set receive handler
+ error = rawrtc_candidate_helper_set_receive_handler(candidate_helper, receive_handler, arg);
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(candidate_helper);
+ } else {
+ // Set pointer
+ *candidate_helperp = candidate_helper;
+ }
+ return error;
+}
+
+/*
+ * Set a candidate helper's receive handler.
+ */
+enum rawrtc_code rawrtc_candidate_helper_set_receive_handler(
+ struct rawrtc_candidate_helper* const candidate_helper,
+ udp_helper_recv_h* const receive_handler,
+ void* const arg) {
+ enum rawrtc_code error;
+ struct udp_helper* udp_helper;
+ struct udp_sock* udp_socket;
+
+ // Check arguments
+ if (!candidate_helper || !receive_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get local candidate's UDP socket
+ udp_socket = trice_lcand_sock(candidate_helper->gatherer->ice, candidate_helper->candidate);
+ if (!udp_socket) {
+ return RAWRTC_CODE_NO_SOCKET;
+ }
+
+ // Create UDP helper
+ error = rawrtc_error_to_code(udp_register_helper(
+ &udp_helper, udp_socket, RAWRTC_LAYER_DTLS_SRTP_STUN, NULL, receive_handler, arg));
+ if (error) {
+ return error;
+ }
+
+ // Unset current helper (if any) and set new helper
+ mem_deref(candidate_helper->udp_helper);
+ candidate_helper->udp_helper = udp_helper;
+
+ // TODO: What about TCP helpers?
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Find a specific candidate helper by re candidate.
+ */
+enum rawrtc_code rawrtc_candidate_helper_find(
+ struct rawrtc_candidate_helper** const candidate_helperp,
+ struct list* const candidate_helpers,
+ struct ice_lcand* re_candidate) {
+ struct le* le;
+
+ // Check arguments
+ if (!candidate_helperp || !candidate_helpers || !re_candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Lookup candidate helper
+ for (le = list_head(candidate_helpers); le != NULL; le = le->next) {
+ struct rawrtc_candidate_helper* const candidate_helper = le->data;
+ if (candidate_helper->candidate->us == re_candidate->us) {
+ // Found
+ *candidate_helperp = candidate_helper;
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ // Not found
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+static void rawrtc_candidate_helper_stun_session_destroy(void* arg) {
+ struct rawrtc_candidate_helper_stun_session* const session = arg;
+
+ // Remove from list
+ list_unlink(&session->le);
+
+ // Un-reference
+ mem_deref(session->url);
+ mem_deref(session->stun_keepalive);
+ mem_deref(session->candidate_helper);
+}
+
+/*
+ * Create a STUN session.
+ */
+enum rawrtc_code rawrtc_candidate_helper_stun_session_create(
+ struct rawrtc_candidate_helper_stun_session** const sessionp, // de-referenced
+ struct rawrtc_ice_server_url* const url) {
+ struct rawrtc_candidate_helper_stun_session* session;
+
+ // Check arguments
+ if (!sessionp || !url) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ session = mem_zalloc(sizeof(*session), rawrtc_candidate_helper_stun_session_destroy);
+ if (!session) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ session->url = mem_ref(url);
+ session->pending = true;
+
+ // Set pointer & done
+ *sessionp = session;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add a STUN session to a candidate helper.
+ */
+enum rawrtc_code rawrtc_candidate_helper_stun_session_add(
+ struct rawrtc_candidate_helper_stun_session* const session,
+ struct rawrtc_candidate_helper* const candidate_helper,
+ struct stun_keepalive* const stun_keepalive) {
+ // Check arguments
+ if (!session || !candidate_helper || !stun_keepalive) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set fields/reference
+ session->candidate_helper = mem_ref(candidate_helper);
+ session->stun_keepalive = mem_ref(stun_keepalive);
+
+ // Append to STUN sessions
+ list_append(&candidate_helper->stun_sessions, &session->le, session);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Remove STUN sessions list handler (for candidate helper lists).
+ */
+bool rawrtc_candidate_helper_remove_stun_sessions_handler(struct le* le, void* arg) {
+ struct rawrtc_candidate_helper* const candidate_helper = le->data;
+ (void) arg;
+
+ // Flush STUN sessions
+ list_flush(&candidate_helper->stun_sessions);
+
+ return false; // continue traversing
+}
diff --git a/src/ice_candidate/helper.h b/src/ice_candidate/helper.h
new file mode 100644
index 0000000..9446574
--- /dev/null
+++ b/src/ice_candidate/helper.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "../ice_server/server.h"
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Local candidate helper.
+ */
+struct rawrtc_candidate_helper {
+ struct le le;
+ struct rawrtc_ice_gatherer* gatherer;
+ struct ice_lcand* candidate;
+ struct udp_helper* udp_helper;
+ uint_fast8_t srflx_pending_count;
+ struct list stun_sessions;
+ uint_fast8_t relay_pending_count;
+};
+
+/*
+ * STUN keep-alive session.
+ */
+struct rawrtc_candidate_helper_stun_session {
+ struct le le;
+ struct rawrtc_candidate_helper* candidate_helper;
+ struct stun_keepalive* stun_keepalive;
+ struct rawrtc_ice_server_url* url;
+ bool pending;
+};
+
+enum rawrtc_code rawrtc_candidate_helper_create(
+ struct rawrtc_candidate_helper** const candidate_helperp, // de-referenced
+ struct rawrtc_ice_gatherer* gatherer,
+ struct ice_lcand* const candidate,
+ udp_helper_recv_h* const receive_handler,
+ void* const arg);
+
+enum rawrtc_code rawrtc_candidate_helper_set_receive_handler(
+ struct rawrtc_candidate_helper* const candidate_helper,
+ udp_helper_recv_h* const receive_handler,
+ void* const arg);
+
+enum rawrtc_code rawrtc_candidate_helper_find(
+ struct rawrtc_candidate_helper** const candidate_helperp,
+ struct list* const candidate_helpers,
+ struct ice_lcand* re_candidate);
+
+enum rawrtc_code rawrtc_candidate_helper_stun_session_create(
+ struct rawrtc_candidate_helper_stun_session** const sessionp, // de-referenced
+ struct rawrtc_ice_server_url* const url);
+
+enum rawrtc_code rawrtc_candidate_helper_stun_session_add(
+ struct rawrtc_candidate_helper_stun_session* const session,
+ struct rawrtc_candidate_helper* const candidate_helper,
+ struct stun_keepalive* const stun_keepalive);
+
+bool rawrtc_candidate_helper_remove_stun_sessions_handler(struct le* le, void* arg);
diff --git a/src/ice_candidate/meson.build b/src/ice_candidate/meson.build
new file mode 100644
index 0000000..abd4455
--- /dev/null
+++ b/src/ice_candidate/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+ 'attributes.c',
+ 'candidate.c',
+ 'helper.c',
+ 'utils.c',
+])
diff --git a/src/ice_candidate/utils.c b/src/ice_candidate/utils.c
new file mode 100644
index 0000000..a6bee78
--- /dev/null
+++ b/src/ice_candidate/utils.c
@@ -0,0 +1,476 @@
+#include "candidate.h"
+#include <rawrtc/ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <netinet/in.h> // IPPROTO_UDP, IPPROTO_TCP
+
+/*
+ * Translate an ICE candidate type to the corresponding re type.
+ */
+enum ice_cand_type rawrtc_ice_candidate_type_to_ice_cand_type(
+ enum rawrtc_ice_candidate_type const type) {
+ // No conversion needed
+ return (enum ice_cand_type) type;
+}
+
+/*
+ * Translate a re ICE candidate type to the corresponding rawrtc type.
+ */
+enum rawrtc_code rawrtc_ice_cand_type_to_ice_candidate_type(
+ enum rawrtc_ice_candidate_type* const typep, // de-referenced
+ enum ice_cand_type const re_type) {
+ // Check arguments
+ if (!typep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert ice_cand_type
+ switch (re_type) {
+ case ICE_CAND_TYPE_HOST:
+ *typep = RAWRTC_ICE_CANDIDATE_TYPE_HOST;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_CAND_TYPE_SRFLX:
+ *typep = RAWRTC_ICE_CANDIDATE_TYPE_SRFLX;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_CAND_TYPE_PRFLX:
+ *typep = RAWRTC_ICE_CANDIDATE_TYPE_PRFLX;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_CAND_TYPE_RELAY:
+ *typep = RAWRTC_ICE_CANDIDATE_TYPE_RELAY;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
+
+/*
+ * Translate an ICE TCP candidate type to the corresponding re type.
+ */
+enum ice_tcptype rawrtc_ice_tcp_candidate_type_to_ice_tcptype(
+ enum rawrtc_ice_tcp_candidate_type const type) {
+ // No conversion needed
+ return (enum ice_tcptype) type;
+}
+
+/*
+ * Translate a re ICE TCP candidate type to the corresponding rawrtc type.
+ */
+enum rawrtc_code rawrtc_ice_tcptype_to_ice_tcp_candidate_type(
+ enum rawrtc_ice_tcp_candidate_type* const typep, // de-referenced
+ enum ice_tcptype const re_type) {
+ // Check arguments
+ if (!typep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert ice_cand_type
+ switch (re_type) {
+ case ICE_TCP_ACTIVE:
+ *typep = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_TCP_PASSIVE:
+ *typep = RAWRTC_ICE_TCP_CANDIDATE_TYPE_PASSIVE;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_TCP_SO:
+ *typep = RAWRTC_ICE_TCP_CANDIDATE_TYPE_SO;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
+
+/*
+ * Translate a protocol to the corresponding IPPROTO_*.
+ */
+int rawrtc_ice_protocol_to_ipproto(enum rawrtc_ice_protocol const protocol) {
+ // No conversion needed
+ return (int) protocol;
+}
+
+/*
+ * Translate a IPPROTO_* to the corresponding protocol.
+ */
+enum rawrtc_code rawrtc_ipproto_to_ice_protocol(
+ enum rawrtc_ice_protocol* const protocolp, // de-referenced
+ int const ipproto) {
+ // Check arguments
+ if (!protocolp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert IPPROTO_*
+ switch (ipproto) {
+ case IPPROTO_UDP:
+ *protocolp = RAWRTC_ICE_PROTOCOL_UDP;
+ return RAWRTC_CODE_SUCCESS;
+ case IPPROTO_TCP:
+ *protocolp = RAWRTC_ICE_PROTOCOL_TCP;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
+
+static enum rawrtc_ice_protocol const map_enum_ice_protocol[] = {
+ RAWRTC_ICE_PROTOCOL_UDP,
+ RAWRTC_ICE_PROTOCOL_TCP,
+};
+
+static char const* const map_str_ice_protocol[] = {
+ "udp",
+ "tcp",
+};
+
+static size_t const map_ice_protocol_length = ARRAY_SIZE(map_enum_ice_protocol);
+
+/*
+ * Translate an ICE protocol to str.
+ */
+char const* rawrtc_ice_protocol_to_str(enum rawrtc_ice_protocol const protocol) {
+ size_t i;
+
+ for (i = 0; i < map_ice_protocol_length; ++i) {
+ if (map_enum_ice_protocol[i] == protocol) {
+ return map_str_ice_protocol[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a pl to an ICE protocol (case-insensitive).
+ */
+enum rawrtc_code rawrtc_pl_to_ice_protocol(
+ enum rawrtc_ice_protocol* const protocolp, // de-referenced
+ struct pl const* const pl) {
+ size_t i;
+
+ // Check arguments
+ if (!protocolp || !pl_isset(pl)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_ice_protocol_length; ++i) {
+ if (pl_strcasecmp(pl, map_str_ice_protocol[i]) == 0) {
+ *protocolp = map_enum_ice_protocol[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Translate a str to an ICE protocol (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_ice_protocol(
+ enum rawrtc_ice_protocol* const protocolp, // de-referenced
+ char const* const str) {
+ struct pl pl;
+
+ // Check arguments
+ if (!str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert str to pl
+ pl_set_str(&pl, str);
+ return rawrtc_pl_to_ice_protocol(protocolp, &pl);
+}
+
+static enum rawrtc_ice_candidate_type const map_enum_ice_candidate_type[] = {
+ RAWRTC_ICE_CANDIDATE_TYPE_HOST,
+ RAWRTC_ICE_CANDIDATE_TYPE_SRFLX,
+ RAWRTC_ICE_CANDIDATE_TYPE_PRFLX,
+ RAWRTC_ICE_CANDIDATE_TYPE_RELAY,
+};
+
+static char const* const map_str_ice_candidate_type[] = {
+ "host",
+ "srflx",
+ "prflx",
+ "relay",
+};
+
+static size_t const map_ice_candidate_type_length = ARRAY_SIZE(map_enum_ice_candidate_type);
+
+/*
+ * Translate an ICE candidate type to str.
+ */
+char const* rawrtc_ice_candidate_type_to_str(enum rawrtc_ice_candidate_type const type) {
+ size_t i;
+
+ for (i = 0; i < map_ice_candidate_type_length; ++i) {
+ if (map_enum_ice_candidate_type[i] == type) {
+ return map_str_ice_candidate_type[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a pl to an ICE candidate type (case-insensitive).
+ */
+enum rawrtc_code rawrtc_pl_to_ice_candidate_type(
+ enum rawrtc_ice_candidate_type* const typep, // de-referenced
+ struct pl const* const pl) {
+ size_t i;
+
+ // Check arguments
+ if (!typep || !pl_isset(pl)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_ice_candidate_type_length; ++i) {
+ if (pl_strcasecmp(pl, map_str_ice_candidate_type[i]) == 0) {
+ *typep = map_enum_ice_candidate_type[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Translate a str to an ICE candidate type (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_ice_candidate_type(
+ enum rawrtc_ice_candidate_type* const typep, // de-referenced
+ char const* const str) {
+ struct pl pl;
+
+ // Check arguments
+ if (!str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert str to pl
+ pl_set_str(&pl, str);
+ return rawrtc_pl_to_ice_candidate_type(typep, &pl);
+}
+
+static enum rawrtc_ice_tcp_candidate_type const map_enum_ice_tcp_candidate_type[] = {
+ RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE,
+ RAWRTC_ICE_TCP_CANDIDATE_TYPE_PASSIVE,
+ RAWRTC_ICE_TCP_CANDIDATE_TYPE_SO,
+};
+
+static char const* const map_str_ice_tcp_candidate_type[] = {
+ "active",
+ "passive",
+ "so",
+};
+
+static size_t const map_ice_tcp_candidate_type_length = ARRAY_SIZE(map_enum_ice_tcp_candidate_type);
+
+/*
+ * Translate an ICE TCP candidate type to str.
+ */
+char const* rawrtc_ice_tcp_candidate_type_to_str(enum rawrtc_ice_tcp_candidate_type const type) {
+ size_t i;
+
+ for (i = 0; i < map_ice_tcp_candidate_type_length; ++i) {
+ if (map_enum_ice_tcp_candidate_type[i] == type) {
+ return map_str_ice_tcp_candidate_type[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to an ICE TCP candidate type (case-insensitive).
+ */
+enum rawrtc_code rawrtc_pl_to_ice_tcp_candidate_type(
+ enum rawrtc_ice_tcp_candidate_type* const typep, // de-referenced
+ struct pl const* const pl) {
+ size_t i;
+
+ // Check arguments
+ if (!typep || !pl_isset(pl)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_ice_tcp_candidate_type_length; ++i) {
+ if (pl_strcasecmp(pl, map_str_ice_tcp_candidate_type[i]) == 0) {
+ *typep = map_enum_ice_tcp_candidate_type[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Translate a str to an ICE TCP candidate type (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_ice_tcp_candidate_type(
+ enum rawrtc_ice_tcp_candidate_type* const typep, // de-referenced
+ char const* const str) {
+ struct pl pl;
+
+ // Check arguments
+ if (!str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert str to pl
+ pl_set_str(&pl, str);
+ return rawrtc_pl_to_ice_tcp_candidate_type(typep, &pl);
+}
+
+static char const* const map_str_ice_candidate_storage[] = {
+ "raw",
+ "lcand",
+ "rcand",
+};
+
+static enum rawrtc_ice_candidate_storage const map_enum_ice_candidate_storage[] = {
+ RAWRTC_ICE_CANDIDATE_STORAGE_RAW,
+ RAWRTC_ICE_CANDIDATE_STORAGE_LCAND,
+ RAWRTC_ICE_CANDIDATE_STORAGE_RCAND,
+};
+
+static size_t const map_ice_candidate_storage_length = ARRAY_SIZE(map_enum_ice_candidate_storage);
+
+/*
+ * Translate an ICE candidate storage type to str.
+ */
+static char const* ice_candidate_storage_to_str(enum rawrtc_ice_candidate_storage const type) {
+ size_t i;
+
+ for (i = 0; i < map_ice_candidate_storage_length; ++i) {
+ if (map_enum_ice_candidate_storage[i] == type) {
+ return map_str_ice_candidate_storage[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Print debug information for an ICE candidate.
+ */
+int rawrtc_ice_candidate_debug(
+ struct re_printf* const pf, struct rawrtc_ice_candidate* const candidate) {
+ int err = 0;
+ enum rawrtc_code error;
+ char* foundation = NULL;
+ uint32_t priority;
+ char* ip = NULL;
+ enum rawrtc_ice_protocol protocol;
+ uint16_t port;
+ enum rawrtc_ice_candidate_type type;
+ enum rawrtc_ice_tcp_candidate_type tcp_type;
+ char* related_address = NULL;
+ uint16_t related_port;
+
+ // Check arguments
+ if (!candidate) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, " ICE Candidate <%p>:\n", candidate);
+
+ // Storage type
+ err |= re_hprintf(
+ pf, " storage_type=%s\n", ice_candidate_storage_to_str(candidate->storage_type));
+
+ // Foundation
+ error = rawrtc_ice_candidate_get_foundation(&foundation, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " foundation=\"%s\"\n", foundation);
+
+ // Priority
+ error = rawrtc_ice_candidate_get_priority(&priority, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " priority=%" PRIu32 "\n", priority);
+
+ // IP
+ error = rawrtc_ice_candidate_get_ip(&ip, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " ip=%s\n", ip);
+
+ // Protocol
+ error = rawrtc_ice_candidate_get_protocol(&protocol, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " protocol=%s\n", rawrtc_ice_protocol_to_str(protocol));
+
+ // Port
+ error = rawrtc_ice_candidate_get_port(&port, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " port=%" PRIu16 "\n", port);
+
+ // Type
+ error = rawrtc_ice_candidate_get_type(&type, candidate);
+ if (error) {
+ goto out;
+ }
+ err |= re_hprintf(pf, " type=%s\n", rawrtc_ice_candidate_type_to_str(type));
+
+ // TCP type (if any)
+ err |= re_hprintf(pf, " tcp_type=");
+ error = rawrtc_ice_candidate_get_tcp_type(&tcp_type, candidate);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ err |= re_hprintf(pf, "%s\n", rawrtc_ice_tcp_candidate_type_to_str(tcp_type));
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ err |= re_hprintf(pf, "n/a\n");
+ break;
+ default:
+ goto out;
+ }
+
+ // Related address (if any)
+ err |= re_hprintf(pf, " related_address=");
+ error = rawrtc_ice_candidate_get_related_address(&related_address, candidate);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ err |= re_hprintf(pf, "%s\n", related_address);
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ err |= re_hprintf(pf, "n/a\n");
+ break;
+ default:
+ goto out;
+ }
+
+ // Related port (if any)
+ err |= re_hprintf(pf, " related_port=");
+ error = rawrtc_ice_candidate_get_related_port(&related_port, candidate);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ err |= re_hprintf(pf, "%" PRIu16 "\n", related_port);
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ err |= re_hprintf(pf, "n/a\n");
+ break;
+ default:
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(related_address);
+ mem_deref(ip);
+ mem_deref(foundation);
+
+ // Translate error & done
+ if (!err && error) {
+ err = EINVAL;
+ }
+ return err;
+}
diff --git a/src/ice_gather_options/meson.build b/src/ice_gather_options/meson.build
new file mode 100644
index 0000000..1161f68
--- /dev/null
+++ b/src/ice_gather_options/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'options.c',
+ 'utils.c',
+])
diff --git a/src/ice_gather_options/options.c b/src/ice_gather_options/options.c
new file mode 100644
index 0000000..c2847ab
--- /dev/null
+++ b/src/ice_gather_options/options.c
@@ -0,0 +1,93 @@
+#include "options.h"
+#include "../ice_server/server.h"
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Destructor for an existing ICE gather options instance.
+ */
+static void rawrtc_ice_gather_options_destroy(void* arg) {
+ struct rawrtc_ice_gather_options* const options = arg;
+
+ // Un-reference
+ list_flush(&options->ice_servers);
+}
+
+/*
+ * Create a new ICE gather options instance.
+ * `*optionsp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gather_options_create(
+ struct rawrtc_ice_gather_options** const optionsp, // de-referenced
+ enum rawrtc_ice_gather_policy const gather_policy) {
+ struct rawrtc_ice_gather_options* options;
+
+ // Check arguments
+ if (!optionsp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ options = mem_zalloc(sizeof(*options), rawrtc_ice_gather_options_destroy);
+ if (!options) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ options->gather_policy = gather_policy;
+ list_init(&options->ice_servers);
+
+ // Set pointer and return
+ *optionsp = options;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add an ICE server instance to the gather options.
+ */
+enum rawrtc_code rawrtc_ice_gather_options_add_server_internal(
+ struct rawrtc_ice_gather_options* const options, struct rawrtc_ice_server* const server) {
+ // Check arguments
+ if (!options || !server) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Add to options
+ list_append(&options->ice_servers, &server->le, server);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add an ICE server to the gather options.
+ */
+enum rawrtc_code rawrtc_ice_gather_options_add_server(
+ struct rawrtc_ice_gather_options* const options,
+ 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;
+
+ // Check arguments
+ if (!options) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure there are less than 2^8 servers
+ // TODO: This check should be in some common location
+ if (list_count(&options->ice_servers) == UINT8_MAX) {
+ return RAWRTC_CODE_INSUFFICIENT_SPACE;
+ }
+
+ // Create ICE server
+ error = rawrtc_ice_server_create(&server, urls, n_urls, username, credential, credential_type);
+ if (error) {
+ return error;
+ }
+
+ // Add to options
+ return rawrtc_ice_gather_options_add_server_internal(options, server);
+}
diff --git a/src/ice_gather_options/options.h b/src/ice_gather_options/options.h
new file mode 100644
index 0000000..c43b9c8
--- /dev/null
+++ b/src/ice_gather_options/options.h
@@ -0,0 +1,16 @@
+#pragma once
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_server.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+struct rawrtc_ice_gather_options {
+ enum rawrtc_ice_gather_policy gather_policy;
+ struct list ice_servers;
+};
+
+enum rawrtc_code rawrtc_ice_gather_options_add_server_internal(
+ struct rawrtc_ice_gather_options* const configuration, struct rawrtc_ice_server* const server);
+
+int rawrtc_ice_gather_options_debug(
+ struct re_printf* const pf, struct rawrtc_ice_gather_options const* const options);
diff --git a/src/ice_gather_options/utils.c b/src/ice_gather_options/utils.c
new file mode 100644
index 0000000..ae51caa
--- /dev/null
+++ b/src/ice_gather_options/utils.c
@@ -0,0 +1,87 @@
+#include "options.h"
+#include "../ice_server/server.h"
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_server.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+static enum rawrtc_ice_gather_policy const map_enum_ice_gather_policy[] = {
+ RAWRTC_ICE_GATHER_POLICY_ALL,
+ RAWRTC_ICE_GATHER_POLICY_NOHOST,
+ RAWRTC_ICE_GATHER_POLICY_RELAY,
+};
+
+static char const* const map_str_ice_gather_policy[] = {
+ "all",
+ "nohost",
+ "relay",
+};
+
+static size_t const map_ice_gather_policy_length = ARRAY_SIZE(map_enum_ice_gather_policy);
+
+/*
+ * Translate an ICE gather policy to str.
+ */
+char const* rawrtc_ice_gather_policy_to_str(enum rawrtc_ice_gather_policy const policy) {
+ size_t i;
+
+ for (i = 0; i < map_ice_gather_policy_length; ++i) {
+ if (map_enum_ice_gather_policy[i] == policy) {
+ return map_str_ice_gather_policy[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to an ICE gather policy (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_ice_gather_policy(
+ enum rawrtc_ice_gather_policy* const policyp, // de-referenced
+ char const* const str) {
+ size_t i;
+
+ // Check arguments
+ if (!policyp || !str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_ice_gather_policy_length; ++i) {
+ if (str_casecmp(map_str_ice_gather_policy[i], str) == 0) {
+ *policyp = map_enum_ice_gather_policy[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Print debug information for the ICE gather options.
+ */
+int rawrtc_ice_gather_options_debug(
+ struct re_printf* const pf, struct rawrtc_ice_gather_options const* const options) {
+ int err = 0;
+ struct le* le;
+
+ // Check arguments
+ if (!options) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, "----- ICE Gather Options <%p> -----\n", options);
+
+ // Gather policy
+ err |= re_hprintf(
+ pf, " gather_policy=%s\n", rawrtc_ice_gather_policy_to_str(options->gather_policy));
+
+ // ICE servers
+ for (le = list_head(&options->ice_servers); le != NULL; le = le->next) {
+ struct rawrtc_ice_server* const server = le->data;
+ err |= re_hprintf(pf, "%H", rawrtc_ice_server_debug, server);
+ }
+
+ // Done
+ return err;
+}
diff --git a/src/ice_gatherer/attributes.c b/src/ice_gatherer/attributes.c
new file mode 100644
index 0000000..92c6fb5
--- /dev/null
+++ b/src/ice_gatherer/attributes.c
@@ -0,0 +1,19 @@
+#include "gatherer.h"
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtcc/code.h>
+
+/*
+ * Get the current state of an ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_state(
+ enum rawrtc_ice_gatherer_state* const statep, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!statep || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ *statep = gatherer->state;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_gatherer/gatherer.c b/src/ice_gatherer/gatherer.c
new file mode 100644
index 0000000..51002fe
--- /dev/null
+++ b/src/ice_gatherer/gatherer.c
@@ -0,0 +1,1073 @@
+#include "gatherer.h"
+#include "../ice_candidate/candidate.h"
+#include "../ice_candidate/helper.h"
+#include "../ice_gather_options/options.h"
+#include "../ice_server/address.h"
+#include "../ice_server/resolver.h"
+#include "../ice_server/server.h"
+#include "../main/config.h"
+#include <rawrtc/config.h>
+#include <rawrtc/ice_candidate.h>
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_parameters.h>
+#include <rawrtc/main.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/message_buffer.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+#include <string.h> // memcpy
+#include <sys/socket.h> // AF_INET, AF_INET6
+
+#define DEBUG_MODULE "ice-gatherer"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#define RAWRTC_DEBUG_ICE_GATHERER 0 // TODO: Remove
+#include <rawrtcc/debug.h>
+
+/*
+ * Destructor for an existing ICE gatherer.
+ */
+static void rawrtc_ice_gatherer_destroy(void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+
+ // Close gatherer
+ // TODO: Check effects in case transport has been destroyed due to error in create
+ rawrtc_ice_gatherer_close(gatherer);
+
+ // Un-reference
+ mem_deref(gatherer->dns_client);
+ mem_deref(gatherer->ice);
+ list_flush(&gatherer->local_candidates);
+ list_flush(&gatherer->buffered_messages);
+ list_flush(&gatherer->url_resolvers);
+ list_flush(&gatherer->url_addresses);
+ mem_deref(gatherer->options);
+}
+
+/*
+ * Create a new ICE gatherer.
+ * `*gathererp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_create(
+ struct rawrtc_ice_gatherer** const gathererp, // de-referenced
+ struct rawrtc_ice_gather_options* const options, // referenced
+ rawrtc_ice_gatherer_state_change_handler const state_change_handler, // nullable
+ rawrtc_ice_gatherer_error_handler const error_handler, // nullable
+ rawrtc_ice_gatherer_local_candidate_handler const local_candidate_handler, // nullable
+ void* const arg // nullable
+) {
+ struct rawrtc_ice_gatherer* gatherer;
+ int err;
+ struct sa dns_servers[RAWRTC_ICE_GATHERER_DNS_SERVERS] = {0};
+ uint32_t n_dns_servers = ARRAY_SIZE(dns_servers);
+ uint32_t i;
+
+ // Check arguments
+ if (!gathererp || !options) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ gatherer = mem_zalloc(sizeof(*gatherer), rawrtc_ice_gatherer_destroy);
+ if (!gatherer) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ gatherer->state = RAWRTC_ICE_GATHERER_STATE_NEW; // TODO: Raise state (delayed)?
+ gatherer->options = mem_ref(options);
+ gatherer->state_change_handler = state_change_handler;
+ gatherer->error_handler = error_handler;
+ gatherer->local_candidate_handler = local_candidate_handler;
+ gatherer->arg = arg;
+ list_init(&gatherer->url_addresses);
+ list_init(&gatherer->url_resolvers);
+ list_init(&gatherer->buffered_messages);
+ list_init(&gatherer->local_candidates);
+
+ // Generate random username fragment and password for ICE
+ rand_str(gatherer->ice_username_fragment, sizeof(gatherer->ice_username_fragment));
+ rand_str(gatherer->ice_password, sizeof(gatherer->ice_password));
+
+ // Set ICE configuration and create trice instance
+ // TODO: Get from config
+ gatherer->ice_config.nom = ICE_NOMINATION_AGGRESSIVE;
+ gatherer->ice_config.debug = RAWRTC_DEBUG_ICE_GATHERER ? true : false;
+ gatherer->ice_config.trace = RAWRTC_DEBUG_ICE_GATHERER ? true : false;
+ gatherer->ice_config.ansi = true;
+ gatherer->ice_config.enable_prflx = true;
+ gatherer->ice_config.optimize_loopback_pairing = true;
+ err = trice_alloc(
+ &gatherer->ice, &gatherer->ice_config, ICE_ROLE_UNKNOWN, gatherer->ice_username_fragment,
+ gatherer->ice_password);
+ if (err) {
+ DEBUG_WARNING("Unable to create trickle ICE instance, reason: %m\n", err);
+ goto out;
+ }
+
+ // Get local DNS servers
+ err = dns_srv_get(NULL, 0, dns_servers, &n_dns_servers);
+ if (err) {
+ DEBUG_WARNING("Unable to retrieve local DNS servers, reason: %m\n", err);
+ goto out;
+ }
+
+ // Print local DNS servers
+ if (n_dns_servers == 0) {
+ DEBUG_NOTICE("No DNS servers found\n");
+ }
+ for (i = 0; i < n_dns_servers; ++i) {
+ DEBUG_PRINTF("DNS server: %j\n", &dns_servers[i]);
+ }
+
+ // Create DNS client (for resolving ICE server IPs)
+ err = dnsc_alloc(&gatherer->dns_client, NULL, dns_servers, n_dns_servers);
+ if (err) {
+ DEBUG_WARNING("Unable to create DNS client instance, reason: %m\n", err);
+ goto out;
+ }
+
+ // Done
+ DEBUG_PRINTF("ICE gatherer created:\n%H", rawrtc_ice_gather_options_debug, gatherer->options);
+
+out:
+ if (err) {
+ mem_deref(gatherer);
+ } else {
+ // Set pointer
+ *gathererp = gatherer;
+ }
+ return rawrtc_error_to_code(err);
+}
+
+/*
+ * Change the state of the ICE gatherer.
+ * Will call the corresponding handler.
+ * TODO: https://github.com/w3c/ortc/issues/606
+ */
+static void set_state(
+ struct rawrtc_ice_gatherer* const gatherer, enum rawrtc_ice_gatherer_state const state) {
+ // Set state
+ gatherer->state = state;
+
+ // Call handler (if any)
+ if (gatherer->state_change_handler) {
+ gatherer->state_change_handler(state, gatherer->arg);
+ }
+}
+
+/*
+ * Close the ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_close(struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Already closed?
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // TODO: Stop ICE transport
+
+ // Stop timeout timer
+ tmr_cancel(&gatherer->timeout_timer);
+
+ // Remove STUN sessions from local candidate helpers
+ // Note: Needed to purge remaining references to the gatherer so it can be free'd.
+ list_apply(
+ &gatherer->local_candidates, true, rawrtc_candidate_helper_remove_stun_sessions_handler,
+ NULL);
+
+ // Flush local candidate helpers
+ list_flush(&gatherer->local_candidates);
+
+ // Remove ICE server URL resolvers
+ list_flush(&gatherer->url_resolvers);
+
+ // Remove ICE server URL addresses
+ list_flush(&gatherer->url_addresses);
+
+ // Stop ICE checklist (if running)
+ trice_checklist_stop(gatherer->ice);
+
+ // Remove ICE agent
+ gatherer->ice = mem_deref(gatherer->ice);
+
+ // Set state to closed and return
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_CLOSED);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Handle received UDP messages.
+ */
+static bool udp_receive_handler(struct sa* source, struct mbuf* buffer, void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ enum rawrtc_code error;
+
+ // Allocate context and copy source address
+ void* const context = mem_zalloc(sizeof(*source), NULL);
+ if (!context) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+ memcpy(context, source, sizeof(*source));
+
+ // Buffer message
+ error = rawrtc_message_buffer_append(&gatherer->buffered_messages, buffer, context);
+ if (error) {
+ goto out;
+ }
+
+ // Done
+ DEBUG_PRINTF("Buffered UDP packet of size %zu\n", mbuf_get_left(buffer));
+
+out:
+ if (error) {
+ DEBUG_WARNING("Could not buffer UDP packet, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Un-reference
+ mem_deref(context);
+
+ // Handled
+ return true;
+}
+
+/*
+ * Announce a local candidate.
+ */
+static enum rawrtc_code announce_candidate(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct ice_lcand* const re_candidate, // nullable
+ char const* const url // nullable
+) {
+ enum rawrtc_code error;
+
+ // Don't announce in the completed state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_COMPLETE) {
+ DEBUG_PRINTF("Not announcing candidate, gathering state is complete\n");
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Create ICE candidate
+ if (gatherer->local_candidate_handler) {
+ struct rawrtc_ice_candidate* ice_candidate = NULL;
+
+ // Create ICE candidate
+ if (re_candidate) {
+ error = rawrtc_ice_candidate_create_from_local_candidate(&ice_candidate, re_candidate);
+ if (error) {
+ return error;
+ }
+ }
+
+ // Call candidate handler and un-reference
+ gatherer->local_candidate_handler(ice_candidate, url, gatherer->arg);
+ mem_deref(ice_candidate);
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Check if the gathering process is complete.
+ */
+static void check_gathering_complete(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ bool const force_complete) {
+ struct le* le;
+ enum rawrtc_code error;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return;
+ }
+
+ // Check or force completion?
+ if (!force_complete) {
+ // Ensure no URL resolvers are in flight
+ if (!list_isempty(&gatherer->url_resolvers)) {
+ struct rawrtc_ice_server_url_resolver* const resolver =
+ list_head(&gatherer->url_resolvers)->data;
+ (void) resolver;
+ DEBUG_PRINTF(
+ "Gathering still in progress, resolving URL (%s [%s])\n", resolver->url->url,
+ dns_rr_typename(resolver->dns_type));
+ return;
+ }
+
+ // Ensure every local candidate has no pending srflx/relay candidates
+ for (le = list_head(&gatherer->local_candidates); le != NULL; le = le->next) {
+ struct rawrtc_candidate_helper* const candidate = le->data;
+
+ // Check counters
+ if (candidate->srflx_pending_count > 0 || candidate->relay_pending_count > 0) {
+ // Nope
+ DEBUG_PRINTF(
+ "Gathering still in progress at candidate %j, #srflx=%" PRIuFAST8
+ ", #relay=%" PRIuFAST8 "\n",
+ &candidate->candidate->attr.addr, candidate->srflx_pending_count,
+ candidate->relay_pending_count);
+ return;
+ }
+ }
+ }
+
+ // Stop timeout timer
+ tmr_cancel(&gatherer->timeout_timer);
+
+ // TODO: Skip the remaining code below when using continuous gathering
+
+ // Announce candidate gathering complete
+ error = announce_candidate(gatherer, NULL, NULL);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not announce end-of-candidates, reason: %s\n", rawrtc_code_to_str(error));
+
+ // This should never happen, so close on failure
+ rawrtc_ice_gatherer_close(gatherer);
+ return;
+ }
+
+ // Update state & done
+ if (gatherer->state != RAWRTC_ICE_GATHERER_STATE_COMPLETE) {
+ DEBUG_PRINTF("Gathering complete:\n%H", trice_debug, gatherer->ice);
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_COMPLETE);
+ }
+}
+
+/*
+ * Find an existing local candidate.
+ * TODO: This should probably be moved into a PR towards rew
+ */
+static struct ice_lcand* find_candidate(
+ struct trice* const ice,
+ enum ice_cand_type type, // set to -1 if it should not be checked
+ unsigned const component_id, // set to 0 if it should not be checked
+ int const protocol,
+ struct sa const* const address, // nullable
+ enum sa_flag const address_flag,
+ struct sa const* base_address, // nullable
+ enum sa_flag const base_address_flags) {
+ struct le* le;
+
+ // Check arguments
+ if (!ice) {
+ return NULL;
+ }
+
+ // If base address and address have an identical IP, ignore the base address and the type
+ if (address && base_address && sa_cmp(address, base_address, SA_ADDR)) {
+ base_address = NULL;
+ type = (enum ice_cand_type) - 1;
+ }
+
+ for (le = list_head(trice_lcandl(ice)); le != NULL; le = le->next) {
+ struct ice_lcand* candidate = le->data;
+
+ // Check type (if requested)
+ if (type != (enum ice_cand_type) - 1 && type != candidate->attr.type) {
+ continue;
+ }
+
+ // Check component id (if requested)
+ if (component_id && candidate->attr.compid != component_id) {
+ continue;
+ }
+
+ // Check protocol
+ if (candidate->attr.proto != protocol) {
+ continue;
+ }
+
+ // Check address
+ if (address && !sa_cmp(&candidate->attr.addr, address, address_flag)) {
+ continue;
+ }
+
+ // Check base address
+ if (base_address && !sa_cmp(&candidate->base_addr, base_address, base_address_flags)) {
+ continue;
+ }
+
+ // Found
+ return candidate;
+ }
+
+ // Not found
+ return NULL;
+}
+
+/*
+ * Gather relay candidates on an ICE server.
+ */
+static enum rawrtc_code gather_relay_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ // Check ICE server is enabled for TURN
+ if (server_address->url->type != RAWRTC_ICE_SERVER_TYPE_TURN) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // TODO: Create TURN request
+ (void) candidate;
+ DEBUG_NOTICE(
+ "TODO: Gather relay candidates using server %J (%s)\n", &server_address->address,
+ server_address->url->url);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Handle gathered server reflexive candidate.
+ */
+static void reflexive_candidate_handler(
+ int err,
+ struct sa const* address, // not checked
+ void* arg // not checked
+) {
+ struct rawrtc_candidate_helper_stun_session* const session = arg;
+ struct rawrtc_candidate_helper* const candidate = session->candidate_helper;
+ struct rawrtc_ice_gatherer* const gatherer = candidate->gatherer;
+ struct ice_lcand* const re_candidate = candidate->candidate;
+ struct ice_lcand* re_other_candidate;
+ uint32_t priority;
+ struct ice_lcand* srflx_candidate;
+ enum rawrtc_code error;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return;
+ }
+
+ // Error?
+ if (err) {
+ DEBUG_NOTICE("STUN request failed, reason: %m\n", err);
+ goto out;
+ }
+
+ // Check if a local candidate with the same base and same attributes (apart from the port)
+ // exists
+ re_other_candidate = find_candidate(
+ gatherer->ice, ICE_CAND_TYPE_SRFLX, re_candidate->attr.compid, re_candidate->attr.proto,
+ address, SA_ADDR, &re_candidate->attr.addr, SA_ALL);
+ if (re_other_candidate) {
+ DEBUG_PRINTF(
+ "Ignoring server reflexive candidate with same base %J and public IP %j (%s)"
+ "\n",
+ &re_candidate->attr.addr, address, session->url->url);
+ goto out;
+ }
+
+ // Add server reflexive candidate
+ // TODO: Using the candidate's protocol, TCP type and component id correct?
+ priority = rawrtc_ice_candidate_calculate_priority(
+ list_count(trice_lcandl(gatherer->ice)), ICE_CAND_TYPE_SRFLX, re_candidate->attr.proto,
+ sa_af(address), re_candidate->attr.tcptype);
+ err = trice_lcand_add(
+ &srflx_candidate, gatherer->ice, re_candidate->attr.compid, re_candidate->attr.proto,
+ priority, address, &re_candidate->attr.addr, ICE_CAND_TYPE_SRFLX, &re_candidate->attr.addr,
+ re_candidate->attr.tcptype, NULL, RAWRTC_LAYER_ICE);
+ if (err) {
+ DEBUG_WARNING("Could not add server reflexive candidate, reason: %m\n", err);
+ goto out;
+ }
+ DEBUG_PRINTF(
+ "Added %s server reflexive candidate for interface %j (%s)\n",
+ net_proto2name(srflx_candidate->attr.proto), address, session->url->url);
+
+ // Announce candidate to handler
+ error = announce_candidate(gatherer, srflx_candidate, session->url->url);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not announce server reflexive candidate, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+
+out:
+ // Decrease counter & check if done gathering
+ if (session->pending) {
+ --candidate->srflx_pending_count;
+ session->pending = false;
+ }
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Gather server reflexive candidates on an ICE server.
+ */
+static enum rawrtc_code gather_reflexive_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ enum rawrtc_code error;
+ struct ice_lcand* const re_candidate = candidate->candidate;
+ struct ice_cand_attr* const attribute = &candidate->candidate->attr;
+ int const af = sa_af(&attribute->addr);
+ enum rawrtc_ice_candidate_type type;
+ char const* type_str;
+ struct rawrtc_candidate_helper_stun_session* session = NULL;
+ struct stun_conf stun_config = {
+ // TODO: Make this configurable!
+ .rto = STUN_DEFAULT_RTO, // 500ms
+ .rc = 3, // Send at: 0ms, 500ms, 1500ms
+ .rm = 6, // Additional wait: 3000ms
+ .ti = 4500, // Total timeout: 4500ms
+ .tos = 0x00,
+ };
+ struct stun_keepalive* stun_keepalive = NULL;
+
+ // Ignore IPv6 addresses
+ // Note: If you have a use case for IPv6 server reflexive candidates, let me know.
+ if (af == AF_INET6) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Ensure the candidate's IP version matches the server address's IP version
+ if (af != sa_af(&server_address->address)) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Convert ICE candidate type
+ error = rawrtc_ice_cand_type_to_ice_candidate_type(&type, attribute->type);
+ if (error) {
+ goto out;
+ }
+ type_str = rawrtc_ice_candidate_type_to_str(type);
+ (void) type_str;
+
+ // TODO: Handle TCP/TLS/DTLS transports
+
+ // Create STUN session
+ error = rawrtc_candidate_helper_stun_session_create(&session, server_address->url);
+ if (error) {
+ goto out;
+ }
+
+ // Create STUN keep-alive session
+ // TODO: We're using the candidate's protocol which conflicts with the ICE server URL transport
+ DEBUG_PRINTF(
+ "Creating STUN request for %s %s candidate %J using ICE server %J (%s)\n",
+ net_proto2name(attribute->proto), type_str, &attribute->addr, &server_address->address,
+ server_address->url->url);
+ error = rawrtc_error_to_code(stun_keepalive_alloc(
+ &stun_keepalive, re_candidate->attr.proto, re_candidate->us, RAWRTC_LAYER_STUN,
+ &server_address->address, &stun_config, reflexive_candidate_handler, session));
+ if (error) {
+ goto out;
+ }
+
+ // Add the STUN session to the candidate
+ error = rawrtc_candidate_helper_stun_session_add(session, candidate, stun_keepalive);
+ if (error) {
+ goto out;
+ }
+
+ // Increase counter, start the STUN session & done
+ ++candidate->srflx_pending_count;
+ stun_keepalive_enable(stun_keepalive, rawrtc_default_config.stun_keepalive_interval);
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ DEBUG_WARNING("Could not create STUN request, reason: %s\n", rawrtc_code_to_str(error));
+ mem_deref(session);
+ }
+
+ // Un-reference & done
+ mem_deref(stun_keepalive);
+ return error;
+}
+
+/*
+ * Gather server reflexive and relay candidates using a specific ICE
+ * server.
+ */
+static void gather_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ struct sa* const address = &candidate->candidate->attr.addr;
+ int af;
+ enum rawrtc_code error;
+
+ // Skip IPv4, IPv6 (server [!] addresses)?
+ // TODO: Get config from struct
+ af = sa_af(&server_address->address);
+ if ((!rawrtc_default_config.ipv6_enable && af == AF_INET6) ||
+ (!rawrtc_default_config.ipv4_enable && af == AF_INET)) {
+ DEBUG_PRINTF(
+ "Ignoring ICE server address %j (family disabled)\n", &server_address->address);
+ return;
+ }
+
+ // Ignore 'any', loopback and link-local server (!) addresses
+ if (sa_is_any(&server_address->address) || sa_is_loopback(&server_address->address) ||
+ sa_is_linklocal(&server_address->address)) {
+ DEBUG_NOTICE("Ignoring ICE server address %j\n", &server_address->address);
+ return;
+ }
+
+ // Ignore loopback and link-local candidate (!) addresses (there is no mapped NAT address since
+ // the addresses aren't reachable from outside of the local network)
+ if (sa_is_linklocal(address) || sa_is_loopback(address)) {
+ return;
+ }
+
+ // Gather reflexive candidates
+ error = gather_reflexive_candidates(candidate, server_address);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not gather server reflexive candidates, reason: %s", rawrtc_code_to_str(error));
+ // Note: Considered non-critical, continuing
+ }
+
+ // Gather relay candidates
+ error = gather_relay_candidates(candidate, server_address);
+ if (error) {
+ DEBUG_WARNING("Could not gather relay candidates, reason: %s", rawrtc_code_to_str(error));
+ // Note: Considered non-critical, continuing
+ }
+}
+
+/*
+ * Gather server reflexive and relay candidates using a newly resolved
+ * ICE server URL address.
+ */
+static void gather_candidates_using_server(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_ice_server_url_address* const address // not checked
+) {
+ struct le* le;
+ for (le = list_head(&gatherer->local_candidates); le != NULL; le = le->next) {
+ struct rawrtc_candidate_helper* const candidate = le->data;
+
+ // Gather candidates
+ gather_candidates(candidate, address);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Gather server reflexive candidates of a local candidate using
+ * already resolved ICE servers.
+ */
+static void gather_candidates_using_resolved_servers(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_candidate_helper* const candidate // not checked
+) {
+ struct le* le;
+ for (le = list_head(&gatherer->url_addresses); le != NULL; le = le->next) {
+ struct rawrtc_ice_server_url_address* const address = le->data;
+
+ // Gather candidates
+ gather_candidates(candidate, address);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Add local candidate, gather server reflexive and relay candidates.
+ */
+static enum rawrtc_code add_candidate(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct sa const* const address, // not checked
+ enum rawrtc_ice_protocol const protocol,
+ enum ice_tcptype const tcp_type) {
+ uint32_t priority;
+ int const ipproto = rawrtc_ice_protocol_to_ipproto(protocol);
+ struct ice_lcand* re_candidate;
+ int err;
+ struct rawrtc_candidate_helper* candidate;
+ enum rawrtc_code error;
+
+ // Add host candidate
+ priority = rawrtc_ice_candidate_calculate_priority(
+ list_count(trice_lcandl(gatherer->ice)), ICE_CAND_TYPE_HOST, ipproto, sa_af(address),
+ tcp_type);
+ // TODO: Set component id properly
+ err = trice_lcand_add(
+ &re_candidate, gatherer->ice, 1, ipproto, priority, address, NULL, ICE_CAND_TYPE_HOST, NULL,
+ tcp_type, NULL, RAWRTC_LAYER_ICE);
+ if (err) {
+ DEBUG_WARNING("Could not add host candidate, reason: %m\n", err);
+ return rawrtc_error_to_code(err);
+ }
+
+ // Create candidate helper (attaches receive handler)
+ error = rawrtc_candidate_helper_create(
+ &candidate, gatherer, re_candidate, udp_receive_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING("Could not create candidate helper, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Add to local candidates list
+ list_append(&gatherer->local_candidates, &candidate->le, candidate);
+ DEBUG_PRINTF(
+ "Added %s host candidate for interface %j\n", rawrtc_ice_protocol_to_str(protocol),
+ address);
+
+ // Announce host candidate to handler
+ error = announce_candidate(gatherer, re_candidate, NULL);
+ if (error) {
+ DEBUG_WARNING("Could not announce host candidate, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Gather server reflexive and relay candidates
+ gather_candidates_using_resolved_servers(gatherer, candidate);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Local interfaces callback.
+ * TODO: Consider ICE gather policy
+ * TODO: https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-01
+ */
+static bool interface_handler(
+ char const* interface, // not checked
+ struct sa const* address, // not checked
+ void* arg // not checked
+) {
+ int af;
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+ (void) interface;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return true; // Don't continue gathering
+ }
+
+ // Ignore 'any' address
+ if (sa_is_any(address)) {
+ DEBUG_PRINTF("Ignoring gathered 'any' address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Ignore loopback address
+ // TODO: Make this configurable
+ if (sa_is_loopback(address)) {
+ DEBUG_PRINTF("Ignoring gathered loopback address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Ignore link-local address
+ // TODO: Make this configurable
+ if (sa_is_linklocal(address)) {
+ DEBUG_PRINTF("Ignoring gathered link-local address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Skip IPv4, IPv6?
+ // TODO: Get config from struct
+ af = sa_af(address);
+ if ((!rawrtc_default_config.ipv6_enable && af == AF_INET6) ||
+ (!rawrtc_default_config.ipv4_enable && af == AF_INET)) {
+ DEBUG_PRINTF("Ignoring gathered address %j (family disabled)\n", address);
+ return false; // Continue gathering
+ }
+
+ // TODO: Ignore interfaces gathered twice
+
+ DEBUG_PRINTF("Gathered local interface %j\n", address);
+
+ // Add UDP candidate
+ if (rawrtc_default_config.udp_enable) {
+ error = add_candidate(gatherer, address, RAWRTC_ICE_PROTOCOL_UDP, ICE_TCP_ACTIVE);
+ if (error) {
+ DEBUG_WARNING("Could not add candidate, reason: %s", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return true; // Don't continue gathering
+ }
+ }
+
+ // Add TCP candidate
+ if (rawrtc_default_config.tcp_enable) {
+ // TODO
+ // add_candidate(gatherer, address, RAWRTC_ICE_PROTOCOL_TCP, ICE_TCP_SO);
+ DEBUG_WARNING("TODO: Add TCP host candidate for interface %j\n", address);
+ }
+
+out:
+ if (error) {
+ // Close and don't continue gathering
+ rawrtc_ice_gatherer_close(gatherer);
+ return true;
+ } else {
+ return false; // Continue gathering
+ }
+}
+
+/*
+ * ICE server URL address resolved handler.
+ */
+static bool ice_server_url_address_result_handler(
+ struct rawrtc_ice_server_url_address* const address, // not checked, referenced
+ void* const arg // not checked
+) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ DEBUG_INFO("Resolved URL %s to address %J\n", address->url->url, &address->address);
+
+ // Append to list of URL addresses
+ list_append(&gatherer->url_addresses, &address->le, mem_ref(address));
+
+ // Gather on the newly created address
+ gather_candidates_using_server(gatherer, address);
+
+ // Done, stop traversing, one address per family is sufficient
+ return true;
+}
+
+/*
+ * Resolve ICE server IP addresses.
+ */
+static enum rawrtc_code resolve_ice_server_addresses(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_ice_gather_options* const options // not checked
+) {
+ struct le* le;
+
+ // Remove all ICE server URL resolvers
+ // Note: This will cancel pending URL resolve processes
+ list_flush(&gatherer->url_resolvers);
+
+ // Remove all resolved ICE server URL addresses
+ list_flush(&gatherer->url_addresses);
+
+ for (le = list_head(&options->ice_servers); le != NULL; le = le->next) {
+ struct rawrtc_ice_server* const ice_server = le->data;
+ struct le* url_le;
+ enum rawrtc_code error;
+
+ for (url_le = list_head(&ice_server->urls); url_le != NULL; url_le = url_le->next) {
+ struct rawrtc_ice_server_url* const url = url_le->data;
+ // URL already resolved (decoded IP address)?
+ if (!sa_is_any(&url->resolved_address)) {
+ struct rawrtc_ice_server_url_address* address;
+
+ // Create URL address from resolved URL
+ error = rawrtc_ice_server_url_address_create(&address, url, &url->resolved_address);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to create ICE server URL address, reason: %s\n",
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL addresses
+ list_append(&gatherer->url_addresses, &address->le, address);
+ }
+ } else {
+ // Create URL resolver for A record (if enabled)
+ if (rawrtc_default_config.ipv4_enable) {
+ struct rawrtc_ice_server_url_resolver* resolver;
+ error = rawrtc_ice_server_url_resolver_create(
+ &resolver, gatherer->dns_client, DNS_TYPE_A, url,
+ ice_server_url_address_result_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to query A record for URL %s, reason: %s\n", url->url,
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL resolvers
+ list_append(&gatherer->url_resolvers, &resolver->le, resolver);
+ }
+ }
+
+ // Create URL resolver for AAAA record (if enabled)
+ if (rawrtc_default_config.ipv6_enable) {
+ struct rawrtc_ice_server_url_resolver* resolver;
+ error = rawrtc_ice_server_url_resolver_create(
+ &resolver, gatherer->dns_client, DNS_TYPE_AAAA, url,
+ ice_server_url_address_result_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to query AAAA record for URL %s, reason: %s\n", url->url,
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL resolvers
+ list_append(&gatherer->url_resolvers, &resolver->le, resolver);
+ }
+ }
+ }
+ }
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Gathering timeout handler.
+ * Note: This timeout has no effect when using continuous gathering.
+ */
+static void gather_timeout_handler(void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+
+ // Force gathering complete
+ check_gathering_complete(gatherer, true);
+}
+
+/*
+ * Start gathering using an ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_gather(
+ struct rawrtc_ice_gatherer* const gatherer,
+ struct rawrtc_ice_gather_options* options // referenced, nullable
+) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+ if (!options) {
+ options = gatherer->options;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Already gathering?
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_GATHERING) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Resolve ICE server IP addresses
+ error = resolve_ice_server_addresses(gatherer, options);
+ if (error) {
+ return error;
+ }
+
+ // Update state
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_GATHERING);
+
+ // Start timeout timer
+ // TODO: Make the timeout configurable
+ tmr_start(&gatherer->timeout_timer, 6000, gather_timeout_handler, gatherer);
+
+ // Start gathering host candidates
+ if (options->gather_policy != RAWRTC_ICE_GATHER_POLICY_NOHOST) {
+ net_if_apply(interface_handler, gatherer);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get local ICE parameters of an ICE gatherer.
+ * `*parametersp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_local_parameters(
+ struct rawrtc_ice_parameters** const parametersp, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!parametersp || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Create and return ICE parameters instance
+ return rawrtc_ice_parameters_create(
+ parametersp, gatherer->ice_username_fragment, gatherer->ice_password, false);
+}
+
+/*
+ * Destructor for an existing local candidates array.
+ */
+static void rawrtc_ice_gatherer_local_candidates_destroy(void* arg) {
+ struct rawrtc_ice_candidates* const candidates = arg;
+ size_t i;
+
+ // Un-reference each item
+ for (i = 0; i < candidates->n_candidates; ++i) {
+ mem_deref(candidates->candidates[i]);
+ }
+}
+
+/*
+ * Get local ICE candidates of an ICE gatherer.
+ * `*candidatesp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_local_candidates(
+ struct rawrtc_ice_candidates** const candidatesp, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ size_t n;
+ struct rawrtc_ice_candidates* candidates;
+ struct le* le;
+ size_t i;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!candidatesp || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get length
+ n = list_count(trice_lcandl(gatherer->ice));
+
+ // Allocate & set length immediately
+ candidates = mem_zalloc(
+ sizeof(*candidates) + (sizeof(struct rawrtc_ice_candidate*) * n),
+ rawrtc_ice_gatherer_local_candidates_destroy);
+ if (!candidates) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+ candidates->n_candidates = n;
+
+ // Copy each ICE candidate
+ for (le = list_head(trice_lcandl(gatherer->ice)), i = 0; le != NULL; le = le->next, ++i) {
+ struct ice_lcand* re_candidate = le->data;
+
+ // Create ICE candidate
+ error = rawrtc_ice_candidate_create_from_local_candidate(
+ &candidates->candidates[i], re_candidate);
+ if (error) {
+ goto out;
+ }
+ }
+
+out:
+ if (error) {
+ mem_deref(candidates);
+ } else {
+ // Set pointers
+ *candidatesp = candidates;
+ }
+ return error;
+}
diff --git a/src/ice_gatherer/gatherer.h b/src/ice_gatherer/gatherer.h
new file mode 100644
index 0000000..7f2d447
--- /dev/null
+++ b/src/ice_gatherer/gatherer.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <rawrtc/ice_gatherer.h>
+#include <re.h>
+#include <rew.h>
+
+enum {
+ RAWRTC_ICE_GATHERER_DNS_SERVERS = 10,
+ RAWRTC_ICE_USERNAME_FRAGMENT_LENGTH = 16,
+ RAWRTC_ICE_PASSWORD_LENGTH = 32,
+};
+
+struct rawrtc_ice_gatherer {
+ enum rawrtc_ice_gatherer_state state;
+ struct rawrtc_ice_gather_options* options; // referenced
+ rawrtc_ice_gatherer_state_change_handler state_change_handler; // nullable
+ rawrtc_ice_gatherer_error_handler error_handler; // nullable
+ rawrtc_ice_gatherer_local_candidate_handler local_candidate_handler; // nullable
+ void* arg; // nullable
+ struct tmr timeout_timer;
+ struct list url_addresses;
+ struct list url_resolvers;
+ struct list buffered_messages; // TODO: Can this be added to the candidates list?
+ struct list local_candidates; // TODO: Hash list instead?
+ char ice_username_fragment[RAWRTC_ICE_USERNAME_FRAGMENT_LENGTH + 1];
+ char ice_password[RAWRTC_ICE_PASSWORD_LENGTH + 1];
+ struct trice* ice;
+ struct trice_conf ice_config;
+ struct dnsc* dns_client;
+};
diff --git a/src/ice_gatherer/meson.build b/src/ice_gatherer/meson.build
new file mode 100644
index 0000000..1276f93
--- /dev/null
+++ b/src/ice_gatherer/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'gatherer.c',
+ 'utils.c',
+])
diff --git a/src/ice_gatherer/utils.c b/src/ice_gatherer/utils.c
new file mode 100644
index 0000000..982a5cb
--- /dev/null
+++ b/src/ice_gatherer/utils.c
@@ -0,0 +1,19 @@
+#include <rawrtc/ice_gatherer.h>
+
+/*
+ * Get the corresponding name for an ICE gatherer state.
+ */
+char const* rawrtc_ice_gatherer_state_to_name(enum rawrtc_ice_gatherer_state const state) {
+ switch (state) {
+ case RAWRTC_ICE_GATHERER_STATE_NEW:
+ return "new";
+ case RAWRTC_ICE_GATHERER_STATE_GATHERING:
+ return "gathering";
+ case RAWRTC_ICE_GATHERER_STATE_COMPLETE:
+ return "complete";
+ case RAWRTC_ICE_GATHERER_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
diff --git a/src/ice_parameters/attributes.c b/src/ice_parameters/attributes.c
new file mode 100644
index 0000000..a4bf66f
--- /dev/null
+++ b/src/ice_parameters/attributes.c
@@ -0,0 +1,54 @@
+#include "parameters.h"
+#include <rawrtc/ice_parameters.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the ICE parameter's username fragment value.
+ * `*username_fragmentp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_parameters_get_username_fragment(
+ char** const username_fragmentp, // de-referenced
+ struct rawrtc_ice_parameters* const parameters) {
+ // Check arguments
+ if (!username_fragmentp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set pointer (and reference)
+ *username_fragmentp = mem_ref(parameters->username_fragment);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the ICE parameter's password value.
+ * `*passwordp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_parameters_get_password(
+ char** const passwordp, // de-referenced
+ struct rawrtc_ice_parameters* const parameters) {
+ // Check arguments
+ if (!passwordp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set pointer (and reference)
+ *passwordp = mem_ref(parameters->password);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the ICE parameter's ICE lite value.
+ */
+enum rawrtc_code rawrtc_ice_parameters_get_ice_lite(
+ bool* const ice_litep, // de-referenced
+ struct rawrtc_ice_parameters* const parameters) {
+ // Check arguments
+ if (!ice_litep || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *ice_litep = parameters->ice_lite;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_parameters/meson.build b/src/ice_parameters/meson.build
new file mode 100644
index 0000000..8710eb0
--- /dev/null
+++ b/src/ice_parameters/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'parameters.c',
+ 'utils.c',
+])
diff --git a/src/ice_parameters/parameters.c b/src/ice_parameters/parameters.c
new file mode 100644
index 0000000..e36cffb
--- /dev/null
+++ b/src/ice_parameters/parameters.c
@@ -0,0 +1,60 @@
+#include "parameters.h"
+#include <rawrtc/ice_parameters.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+/*
+ * Destructor for an existing ICE parameters instance.
+ */
+static void rawrtc_ice_parameters_destroy(void* arg) {
+ struct rawrtc_ice_parameters* const parameters = arg;
+
+ // Un-reference
+ mem_deref(parameters->username_fragment);
+ mem_deref(parameters->password);
+}
+
+/*
+ * Create a new ICE parameters instance.
+ * `*parametersp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_parameters_create(
+ struct rawrtc_ice_parameters** const parametersp, // de-referenced
+ char* const username_fragment, // copied
+ char* const password, // copied
+ bool const ice_lite) {
+ struct rawrtc_ice_parameters* parameters;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!parametersp || !username_fragment || !password) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ parameters = mem_zalloc(sizeof(*parameters), rawrtc_ice_parameters_destroy);
+ if (!parameters) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/copy
+ error = rawrtc_strdup(¶meters->username_fragment, username_fragment);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_strdup(¶meters->password, password);
+ if (error) {
+ goto out;
+ }
+ parameters->ice_lite = ice_lite;
+
+out:
+ if (error) {
+ mem_deref(parameters);
+ } else {
+ // Set pointer
+ *parametersp = parameters;
+ }
+ return error;
+}
diff --git a/src/ice_parameters/parameters.h b/src/ice_parameters/parameters.h
new file mode 100644
index 0000000..a425ca9
--- /dev/null
+++ b/src/ice_parameters/parameters.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <re.h>
+
+struct rawrtc_ice_parameters {
+ char* username_fragment; // copied
+ char* password; // copied
+ bool ice_lite;
+};
+
+int rawrtc_ice_parameters_debug(
+ struct re_printf* const pf, struct rawrtc_ice_parameters const* const parameters);
diff --git a/src/ice_parameters/utils.c b/src/ice_parameters/utils.c
new file mode 100644
index 0000000..53bfdb7
--- /dev/null
+++ b/src/ice_parameters/utils.c
@@ -0,0 +1,29 @@
+#include "parameters.h"
+#include <re.h>
+
+/*
+ * Print debug information for ICE parameters.
+ */
+int rawrtc_ice_parameters_debug(
+ struct re_printf* const pf, struct rawrtc_ice_parameters const* const parameters) {
+ int err = 0;
+
+ // Check arguments
+ if (!parameters) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, " ICE Parameters <%p>:\n", parameters);
+
+ // Username fragment
+ err |= re_hprintf(pf, " username_fragment=\"%s\"\n", parameters->username_fragment);
+
+ // Password
+ err |= re_hprintf(pf, " password=\"%s\"\n", parameters->password);
+
+ // ICE lite
+ err |= re_hprintf(pf, " ice_lite=%s\n", parameters->ice_lite ? "yes" : "no");
+
+ // Done
+ return err;
+}
diff --git a/src/ice_server/address.c b/src/ice_server/address.c
new file mode 100644
index 0000000..a6fb176
--- /dev/null
+++ b/src/ice_server/address.c
@@ -0,0 +1,47 @@
+#include "address.h"
+#include "server.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Destructor for an ICE server URL address.
+ */
+static void rawrtc_ice_server_url_address_destroy(void* arg) {
+ struct rawrtc_ice_server_url_address* const address = arg;
+
+ // Remove from list
+ list_unlink(&address->le);
+
+ // Un-reference
+ mem_deref(address->url);
+}
+
+/*
+ * Create an ICE server URL address.
+ */
+enum rawrtc_code rawrtc_ice_server_url_address_create(
+ struct rawrtc_ice_server_url_address** const addressp, // de-referenced
+ struct rawrtc_ice_server_url* const url, // referenced
+ struct sa* const address // copied
+) {
+ struct rawrtc_ice_server_url_address* url_address;
+
+ // Check arguments
+ if (!addressp || !url || !address) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ url_address = mem_zalloc(sizeof(*url_address), rawrtc_ice_server_url_address_destroy);
+ if (!url_address) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ url_address->url = mem_ref(url);
+ url_address->address = *address;
+
+ // Set pointer & done
+ *addressp = url_address;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_server/address.h b/src/ice_server/address.h
new file mode 100644
index 0000000..d39fc25
--- /dev/null
+++ b/src/ice_server/address.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "server.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * ICE server URL's resolved address.
+ */
+struct rawrtc_ice_server_url_address {
+ struct le le;
+ struct rawrtc_ice_server_url* url; // referenced
+ struct sa address;
+};
+
+enum rawrtc_code rawrtc_ice_server_url_address_create(
+ struct rawrtc_ice_server_url_address** const addressp, // de-referenced
+ struct rawrtc_ice_server_url* const url, // referenced
+ struct sa* const address // copied
+);
diff --git a/src/ice_server/meson.build b/src/ice_server/meson.build
new file mode 100644
index 0000000..0135c72
--- /dev/null
+++ b/src/ice_server/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+ 'address.c',
+ 'resolver.c',
+ 'server.c',
+ 'utils.c',
+])
diff --git a/src/ice_server/resolver.c b/src/ice_server/resolver.c
new file mode 100644
index 0000000..65ed350
--- /dev/null
+++ b/src/ice_server/resolver.c
@@ -0,0 +1,182 @@
+#include "resolver.h"
+#include "address.h"
+#include "server.h"
+#include <rawrtc/config.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+#define DEBUG_MODULE "ice-server-url-resolver"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * DNS A or AAAA record handler.
+ */
+static bool dns_record_result_handler(struct dnsrr* resource_record, void* arg) {
+ struct rawrtc_ice_server_url_resolver* const resolver = arg;
+ struct rawrtc_ice_server_url* const url = resolver->url;
+ struct sa address;
+ enum rawrtc_code error;
+ struct rawrtc_ice_server_url_address* url_address;
+ bool stop;
+ DEBUG_PRINTF("DNS resource record: %H\n", dns_rr_print, resource_record);
+
+ // Set IP address
+ sa_cpy(&address, &url->resolved_address);
+ switch (resource_record->type) {
+ case DNS_TYPE_A:
+ // Set IPv4 address
+ sa_set_in(&address, resource_record->rdata.a.addr, sa_port(&address));
+ break;
+
+ case DNS_TYPE_AAAA:
+ // Set IPv6 address
+ sa_set_in6(&address, resource_record->rdata.aaaa.addr, sa_port(&address));
+ break;
+
+ default:
+ DEBUG_WARNING(
+ "Invalid DNS resource record, expected A/AAAA record, got: %H\n", dns_rr_print,
+ resource_record);
+ return true; // stop traversing
+ }
+
+ // Create URL address
+ error = rawrtc_ice_server_url_address_create(&url_address, url, &address);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to create ICE server URL address, reason: %s\n", rawrtc_code_to_str(error));
+ return true; // stop traversing
+ }
+
+ // Announce resolved IP address
+ stop = resolver->address_handler(url_address, resolver->arg);
+
+ // Un-reference
+ mem_deref(url_address);
+
+ // Done (continue or stop traversing)
+ return stop;
+}
+
+/*
+ * DNS query result handler.
+ */
+static void dns_query_handler(
+ int err,
+ struct dnshdr const* header,
+ struct list* answer_records,
+ struct list* authoritive_records,
+ struct list* additional_records,
+ void* arg) {
+ struct rawrtc_ice_server_url_resolver* const resolver = arg;
+ (void) header;
+ (void) authoritive_records;
+ (void) additional_records;
+
+ // Handle error (if any)
+ if (err) {
+ DEBUG_WARNING("Could not query DNS record for '%r', reason: %m\n", &resolver->url->host);
+ goto out;
+ } else if (header->rcode != 0) {
+ DEBUG_NOTICE(
+ "DNS record query for '%r' unsuccessful: %s (%" PRIu8 ")\n", &resolver->url->host,
+ dns_hdr_rcodename(header->rcode), header->rcode);
+ goto out;
+ }
+
+ // Unlink self from any list
+ list_unlink(&resolver->le);
+
+ // Handle A or AAAA record
+ dns_rrlist_apply2(
+ answer_records, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN, true,
+ dns_record_result_handler, resolver);
+
+out:
+ // Unlink & un-reference self
+ // Note: We're unlinking twice here since the above unlink may be skipped in an error case.
+ // This is perfectly safe.
+ list_unlink(&resolver->le);
+ mem_deref(resolver);
+}
+
+/*
+ * Destructor for an ICE server URL.
+ */
+static void rawrtc_ice_server_url_resolver_destroy(void* arg) {
+ struct rawrtc_ice_server_url_resolver* const resolver = arg;
+
+ // Remove from list
+ list_unlink(&resolver->le);
+
+ // Un-reference
+ mem_deref(resolver->dns_query);
+ mem_deref(resolver->url);
+}
+
+/*
+ * Create an ICE server URL resolver.
+ *
+ * Important: Once the handler has been called, the resolver will unlink
+ * from an associated list and un-reference itself.
+ */
+enum rawrtc_code rawrtc_ice_server_url_resolver_create(
+ struct rawrtc_ice_server_url_resolver** const resolverp, // de-referenced
+ struct dnsc* const dns_client,
+ uint_fast16_t const dns_type,
+ struct rawrtc_ice_server_url* const url, // referenced
+ rawrtc_ice_server_url_address_resolved_handler address_handler,
+ void* const arg) {
+ enum rawrtc_code error;
+ struct rawrtc_ice_server_url_resolver* resolver;
+ char* host_str;
+
+ // Check arguments
+ if (!resolverp || !dns_client || !url || !address_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ resolver = mem_zalloc(sizeof(*resolver), rawrtc_ice_server_url_resolver_destroy);
+ if (!resolver) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ resolver->url = mem_ref(url);
+ resolver->address_handler = address_handler;
+ resolver->arg = arg;
+ resolver->dns_type = dns_type;
+
+ // Copy URL to str
+ error = rawrtc_error_to_code(pl_strdup(&host_str, &url->host));
+ if (error) {
+ goto out;
+ }
+
+ // Query A or AAAA record
+ error = rawrtc_error_to_code(dnsc_query(
+ &resolver->dns_query, dns_client, host_str, (uint16_t) dns_type, DNS_CLASS_IN, true,
+ dns_query_handler, resolver));
+ if (error) {
+ goto out;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ // Un-reference
+ mem_deref(host_str);
+
+ if (error) {
+ mem_deref(resolver);
+ } else {
+ // Set pointer & done
+ *resolverp = resolver;
+ }
+
+ return error;
+}
diff --git a/src/ice_server/resolver.h b/src/ice_server/resolver.h
new file mode 100644
index 0000000..63e9c0f
--- /dev/null
+++ b/src/ice_server/resolver.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "address.h"
+#include "server.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * ICE server URL address resolved handler.
+ *
+ * `*resolverp` must be referenced if used.
+ *
+ * Return `true` if you want to continue receiving further addresses
+ * from the URL's address entry. Be aware that you will be offered at
+ * least one IPv4 address and one IPv6 address per URL (if available)
+ * even if you always return `false`.
+ */
+typedef bool (*rawrtc_ice_server_url_address_resolved_handler)(
+ struct rawrtc_ice_server_url_address* const address, void* const arg);
+
+/*
+ * ICE server URL resolver.
+ */
+struct rawrtc_ice_server_url_resolver {
+ struct le le;
+ struct rawrtc_ice_server_url* url; // referenced
+ rawrtc_ice_server_url_address_resolved_handler address_handler;
+ void* arg;
+ uint_fast16_t dns_type;
+ struct dns_query* dns_query;
+};
+
+enum rawrtc_code rawrtc_ice_server_url_resolver_create(
+ struct rawrtc_ice_server_url_resolver** const resolverp, // de-referenced
+ struct dnsc* const dns_client,
+ uint_fast16_t const dns_type,
+ struct rawrtc_ice_server_url* const url, // referenced
+ rawrtc_ice_server_url_address_resolved_handler address_handler,
+ void* const arg);
diff --git a/src/ice_server/server.c b/src/ice_server/server.c
new file mode 100644
index 0000000..2c52d0d
--- /dev/null
+++ b/src/ice_server/server.c
@@ -0,0 +1,449 @@
+#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;
+}
diff --git a/src/ice_server/server.h b/src/ice_server/server.h
new file mode 100644
index 0000000..da9c9a6
--- /dev/null
+++ b/src/ice_server/server.h
@@ -0,0 +1,49 @@
+#pragma once
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_server.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * ICE server type.
+ * Note: Update `ice_server_schemes` if changed.
+ */
+enum rawrtc_ice_server_type {
+ RAWRTC_ICE_SERVER_TYPE_STUN,
+ RAWRTC_ICE_SERVER_TYPE_TURN,
+};
+
+struct rawrtc_ice_server {
+ struct le le;
+ struct list urls; // deep-copied
+ char* username; // copied
+ char* credential; // copied
+ enum rawrtc_ice_credential_type credential_type;
+};
+
+/*
+ * ICE server URL. (list element)
+ */
+struct rawrtc_ice_server_url {
+ struct le le;
+ char* url; // copied
+ struct pl host; // points inside `url`
+ enum rawrtc_ice_server_type type;
+ enum rawrtc_ice_server_transport transport;
+ struct sa resolved_address;
+};
+
+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);
+
+enum rawrtc_code rawrtc_ice_server_copy(
+ struct rawrtc_ice_server** const serverp, // de-referenced
+ struct rawrtc_ice_server* const source_server);
+
+int rawrtc_ice_server_debug(
+ struct re_printf* const pf, struct rawrtc_ice_server const* const server);
diff --git a/src/ice_server/utils.c b/src/ice_server/utils.c
new file mode 100644
index 0000000..0b1ac90
--- /dev/null
+++ b/src/ice_server/utils.c
@@ -0,0 +1,103 @@
+#include "server.h"
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_server.h>
+#include <re.h>
+
+/*
+ * Get the corresponding name for an ICE server type.
+ */
+static char const* ice_server_type_to_name(enum rawrtc_ice_server_type const type) {
+ switch (type) {
+ case RAWRTC_ICE_SERVER_TYPE_STUN:
+ return "stun";
+ case RAWRTC_ICE_SERVER_TYPE_TURN:
+ return "turn";
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Get the corresponding name for an ICE server transport.
+ */
+static char const* ice_server_transport_to_name(enum rawrtc_ice_server_transport const transport) {
+ switch (transport) {
+ case RAWRTC_ICE_SERVER_TRANSPORT_UDP:
+ return "udp";
+ case RAWRTC_ICE_SERVER_TRANSPORT_TCP:
+ return "tcp";
+ case RAWRTC_ICE_SERVER_TRANSPORT_DTLS:
+ return "dtls";
+ case RAWRTC_ICE_SERVER_TRANSPORT_TLS:
+ return "tls";
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Get the corresponding name for an ICE credential type.
+ */
+static char const* ice_credential_type_to_name(enum rawrtc_ice_credential_type const type) {
+ switch (type) {
+ case RAWRTC_ICE_CREDENTIAL_TYPE_NONE:
+ return "n/a";
+ case RAWRTC_ICE_CREDENTIAL_TYPE_PASSWORD:
+ return "password";
+ case RAWRTC_ICE_CREDENTIAL_TYPE_TOKEN:
+ return "token";
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Print debug information for an ICE server.
+ */
+int rawrtc_ice_server_debug(
+ struct re_printf* const pf, struct rawrtc_ice_server const* const server) {
+ int err = 0;
+ struct le* le;
+
+ // Check arguments
+ if (!server) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, " ICE Server <%p>:\n", server);
+
+ // Credential type
+ err |= re_hprintf(
+ pf, " credential_type=%s\n", ice_credential_type_to_name(server->credential_type));
+ if (server->credential_type != RAWRTC_ICE_CREDENTIAL_TYPE_NONE) {
+ // Username
+ err |= re_hprintf(pf, " username=");
+ if (server->username) {
+ err |= re_hprintf(pf, "\"%s\"\n", server->username);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+
+ // Credential
+ err |= re_hprintf(pf, " credential=");
+ if (server->credential) {
+ err |= re_hprintf(pf, "\"%s\"\n", server->credential);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+ }
+
+ // URLs
+ for (le = list_head(&server->urls); le != NULL; le = le->next) {
+ struct rawrtc_ice_server_url* const url = le->data;
+
+ // URL, STUN/TURN, transport, currently gathering?
+ err |= re_hprintf(
+ pf, " URL=\"%s\" type=%s transport=%s resolved=%s\n", url->url,
+ ice_server_type_to_name(url->type), ice_server_transport_to_name(url->transport),
+ sa_is_any(&url->resolved_address) ? "no" : "yes");
+ }
+
+ // Done
+ return err;
+}
diff --git a/src/ice_transport/attributes.c b/src/ice_transport/attributes.c
new file mode 100644
index 0000000..36956f0
--- /dev/null
+++ b/src/ice_transport/attributes.c
@@ -0,0 +1,58 @@
+#include "transport.h"
+#include "../ice_gatherer/gatherer.h"
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Get the current ICE role of the ICE transport.
+ * Return `RAWRTC_CODE_NO_VALUE` code in case the ICE role has not been
+ * determined yet.
+ */
+enum rawrtc_code rawrtc_ice_transport_get_role(
+ enum rawrtc_ice_role* const rolep, // de-referenced
+ struct rawrtc_ice_transport* const transport) {
+ enum ice_role re_role;
+ enum rawrtc_code error;
+ enum rawrtc_ice_role role;
+
+ // Check arguments
+ if (!rolep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get libre role from ICE instance
+ re_role = trice_local_role(transport->gatherer->ice);
+
+ // Translate role
+ error = rawrtc_re_ice_role_to_ice_role(&role, re_role);
+ if (error) {
+ return error;
+ }
+
+ // Unknown?
+ if (re_role == ICE_ROLE_UNKNOWN) {
+ return RAWRTC_CODE_NO_VALUE;
+ } else {
+ // Set pointer
+ *rolep = role;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Get the current state of the ICE transport.
+ */
+enum rawrtc_code rawrtc_ice_transport_get_state(
+ enum rawrtc_ice_transport_state* const statep, // de-referenced
+ struct rawrtc_ice_transport* const transport) {
+ // Check arguments
+ if (!statep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state & done
+ *statep = transport->state;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_transport/meson.build b/src/ice_transport/meson.build
new file mode 100644
index 0000000..0227c31
--- /dev/null
+++ b/src/ice_transport/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'transport.c',
+ 'utils.c',
+])
diff --git a/src/ice_transport/transport.c b/src/ice_transport/transport.c
new file mode 100644
index 0000000..a6c914e
--- /dev/null
+++ b/src/ice_transport/transport.c
@@ -0,0 +1,579 @@
+#include "transport.h"
+#include "../dtls_transport/transport.h"
+#include "../ice_candidate/candidate.h"
+#include "../ice_candidate/helper.h"
+#include "../ice_gatherer/gatherer.h"
+#include "../ice_parameters/parameters.h"
+#include "../main/config.h"
+#include <rawrtc/config.h>
+#include <rawrtc/ice_candidate.h>
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+
+#define DEBUG_MODULE "ice-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Destructor for an existing ICE transport.
+ */
+static void rawrtc_ice_transport_destroy(void* arg) {
+ struct rawrtc_ice_transport* const transport = arg;
+
+ // Stop transport
+ // TODO: Check effects in case transport has been destroyed due to error in create
+ rawrtc_ice_transport_stop(transport);
+
+ // Un-reference
+ mem_deref(transport->stun_client);
+ mem_deref(transport->remote_parameters);
+ mem_deref(transport->gatherer);
+}
+
+/*
+ * Create a new ICE transport.
+ * `*transportp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_transport_create(
+ struct rawrtc_ice_transport** const transportp, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer, // referenced, nullable
+ rawrtc_ice_transport_state_change_handler const state_change_handler, // nullable
+ rawrtc_ice_transport_candidate_pair_change_handler const
+ candidate_pair_change_handler, // nullable
+ void* const arg // nullable
+) {
+ struct rawrtc_ice_transport* transport;
+ struct stun_conf stun_config = {
+ // TODO: Make this configurable!
+ .rto = 100, // 100ms
+ .rc = 7, // Send at: 0ms, 100ms, 300ms, 700ms, 1500ms, 3100ms, 6300ms
+ .rm = 60, // Additional wait: 60*100 -> 6000ms
+ .ti = 12300, // Timeout after: 12300ms
+ .tos = 0x00,
+ };
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transportp || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check ICE gatherer state
+ // TODO: Check if gatherer.component is RTCP -> invalid state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Allocate
+ transport = mem_zalloc(sizeof(*transport), rawrtc_ice_transport_destroy);
+ if (!transport) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ transport->state = RAWRTC_ICE_TRANSPORT_STATE_NEW; // TODO: Raise state (delayed)?
+ transport->gatherer = mem_ref(gatherer);
+ transport->state_change_handler = state_change_handler;
+ transport->candidate_pair_change_handler = candidate_pair_change_handler;
+ transport->arg = arg;
+ transport->remote_end_of_candidates = false;
+
+ // Create STUN client
+ error = rawrtc_error_to_code(stun_alloc(&transport->stun_client, &stun_config, NULL, NULL));
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(transport);
+ } else {
+ // Set pointer
+ *transportp = transport;
+ }
+ return error;
+}
+
+/*
+ * Change the state of the ICE transport.
+ * Will call the corresponding handler.
+ */
+static void set_state(
+ struct rawrtc_ice_transport* const transport, enum rawrtc_ice_transport_state const state) {
+ // Set state
+ transport->state = state;
+
+ // Call handler (if any)
+ if (transport->state_change_handler) {
+ transport->state_change_handler(state, transport->arg);
+ }
+}
+
+/*
+ * Check if the ICE checklist process is complete.
+ */
+static void check_ice_checklist_complete(
+ struct rawrtc_ice_transport* const transport // not checked
+) {
+ struct trice* const ice = transport->gatherer->ice;
+
+ // Completed all candidate pairs?
+ if (trice_checklist_iscompleted(ice)) {
+ struct le;
+
+ DEBUG_INFO("Checklist completed\n");
+ DEBUG_PRINTF("%H", trice_debug, ice);
+
+ // Stop the checklist
+ trice_checklist_stop(ice);
+
+ // Remove STUN and TURN sessions from local candidate helpers since the keep-alive
+ // mechanism now moves over to the peers themselves.
+ list_apply(
+ &transport->gatherer->local_candidates, true,
+ rawrtc_candidate_helper_remove_stun_sessions_handler, NULL);
+
+ // Start keep-alive for active candidate pairs
+ // TODO: Implement!
+ // start_keepalive(transport);
+
+ // Do we have one candidate pair that succeeded?
+ if (!list_isempty(trice_validl(ice))) {
+ // Have we received the remote end-of-candidates indication?
+ if (transport->remote_end_of_candidates) {
+ DEBUG_INFO("ICE connection completed\n");
+ set_state(transport, RAWRTC_ICE_TRANSPORT_STATE_COMPLETED);
+ }
+ } else {
+ // No, transition to failed
+ DEBUG_INFO("ICE connection failed\n");
+ set_state(transport, RAWRTC_ICE_TRANSPORT_STATE_FAILED);
+ }
+ }
+}
+
+/*
+ * ICE connection established callback.
+ */
+static void ice_established_handler(
+ struct ice_candpair* candidate_pair, struct stun_msg const* message, void* arg) {
+ struct rawrtc_ice_transport* const transport = arg;
+ enum rawrtc_code error;
+ (void) message;
+
+ DEBUG_PRINTF("Candidate pair established: %H\n", trice_candpair_debug, candidate_pair);
+
+ // Ignore if closed
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED) {
+ return;
+ }
+
+ // State: checking -> connected
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_CHECKING) {
+ DEBUG_INFO("ICE connection established\n");
+ set_state(transport, RAWRTC_ICE_TRANSPORT_STATE_CONNECTED);
+ }
+
+ // Ignore if completed or failed
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_COMPLETED ||
+ transport->state == RAWRTC_ICE_TRANSPORT_STATE_FAILED) {
+ return;
+ }
+
+ // Offer candidate pair to DTLS transport (if any)
+ // TODO: Offer to whatever transport lays above so we are SRTP/QUIC compatible
+ if (transport->dtls_transport) {
+ error = rawrtc_dtls_transport_add_candidate_pair(transport->dtls_transport, candidate_pair);
+ if (error) {
+ DEBUG_WARNING(
+ "DTLS transport could not attach to candidate pair, reason: %s\n",
+ rawrtc_code_to_str(error));
+
+ // Important: Removing a candidate pair can lead to segfaults due to STUN transaction
+ // timers looking up the pair. Don't do it!
+ }
+ }
+
+ // TODO: Call candidate_pair_change_handler (?)
+
+ // ICE checklist process complete?
+ check_ice_checklist_complete(transport);
+}
+
+/*
+ * ICE connection failed callback.
+ */
+static void ice_failed_handler(
+ int err, uint16_t stun_code, struct ice_candpair* candidate_pair, void* arg) {
+ struct rawrtc_ice_transport* const transport = arg;
+ (void) err;
+ (void) stun_code;
+ (void) candidate_pair;
+
+ DEBUG_PRINTF(
+ "Candidate pair failed: %H (%m %" PRIu16 ")\n", trice_candpair_debug, candidate_pair, err,
+ stun_code);
+
+ // Ignore if closed
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED) {
+ return;
+ }
+
+ // Ignore if completed or failed
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_COMPLETED ||
+ transport->state == RAWRTC_ICE_TRANSPORT_STATE_FAILED) {
+ return;
+ }
+
+ // ICE checklist process complete?
+ check_ice_checklist_complete(transport);
+
+ // Important: Removing the failed candidate pair can lead to segfaults due to STUN transaction
+ // timers looking up the pair. Don't do it!
+}
+
+/*
+ * Start the ICE transport.
+ * TODO https://github.com/w3c/ortc/issues/607
+ */
+enum rawrtc_code rawrtc_ice_transport_start(
+ struct rawrtc_ice_transport* const transport,
+ struct rawrtc_ice_gatherer* const gatherer, // referenced
+ struct rawrtc_ice_parameters* const remote_parameters, // referenced
+ enum rawrtc_ice_role const role) {
+ bool ice_transport_closed;
+ bool ice_gatherer_closed;
+ enum ice_role translated_role;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !gatherer || !remote_parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Validate parameters
+ if (!remote_parameters->username_fragment || !remote_parameters->password) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Handle ICE lite
+ if (remote_parameters->ice_lite) {
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+ }
+
+ // TODO: Check that components of ICE gatherer and ICE transport match
+
+ // Check state
+ ice_transport_closed = transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED;
+ ice_gatherer_closed = gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED;
+ if (ice_transport_closed || ice_gatherer_closed) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // TODO: Handle ICE restart when called again
+ if (transport->state != RAWRTC_ICE_TRANSPORT_STATE_NEW) {
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+ }
+
+ // Check if gatherer instance is different
+ // TODO https://github.com/w3c/ortc/issues/607
+ if (transport->gatherer != gatherer) {
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+ }
+
+ // Set role (abort if unknown or something entirely weird)
+ translated_role = rawrtc_ice_role_to_re_ice_role(role);
+ error = rawrtc_error_to_code(trice_set_role(transport->gatherer->ice, translated_role));
+ if (error) {
+ return error;
+ }
+
+ // New/first remote parameters?
+ if (transport->remote_parameters != remote_parameters) {
+ // Apply username fragment and password on trice
+ error = rawrtc_error_to_code(
+ trice_set_remote_ufrag(transport->gatherer->ice, remote_parameters->username_fragment));
+ if (error) {
+ return error;
+ }
+ error = rawrtc_error_to_code(
+ trice_set_remote_pwd(transport->gatherer->ice, remote_parameters->password));
+ if (error) {
+ return error;
+ }
+
+ // Replace
+ mem_deref(transport->remote_parameters);
+ transport->remote_parameters = mem_ref(remote_parameters);
+ }
+
+ // Set state to checking
+ // TODO: Get more states from trice
+ set_state(transport, RAWRTC_ICE_TRANSPORT_STATE_CHECKING);
+
+ // Start checklist (if remote candidates exist)
+ if (!list_isempty(trice_rcandl(transport->gatherer->ice))) {
+ // TODO: Get config from struct
+ DEBUG_INFO("Starting checklist due to start event\n");
+ error = rawrtc_error_to_code(trice_checklist_start(
+ transport->gatherer->ice, transport->stun_client, rawrtc_default_config.pacing_interval,
+ ice_established_handler, ice_failed_handler, transport));
+ if (error) {
+ return error;
+ }
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Stop and close the ICE transport.
+ */
+enum rawrtc_code rawrtc_ice_transport_stop(struct rawrtc_ice_transport* const transport) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Already closed?
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Stop ICE checklist (if running)
+ if (trice_checklist_isrunning(transport->gatherer->ice)) {
+ trice_checklist_stop(transport->gatherer->ice);
+ }
+
+ // TODO: Remove remote candidates, role, username fragment and password from rew
+
+ // TODO: Remove from RTCICETransportController (once we have it)
+
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add a remote candidate ot the ICE transport.
+ * Note: 'candidate' must be NULL to inform the transport that the
+ * remote site finished gathering.
+ */
+enum rawrtc_code rawrtc_ice_transport_add_remote_candidate(
+ struct rawrtc_ice_transport* const transport,
+ struct rawrtc_ice_candidate* candidate // nullable
+) {
+ struct ice_rcand* re_candidate = NULL;
+ enum rawrtc_code error;
+ char* ip = NULL;
+ uint16_t port;
+ struct sa address = {0};
+ int af;
+ enum rawrtc_ice_protocol protocol;
+ char* foundation = NULL;
+ uint32_t priority;
+ enum rawrtc_ice_candidate_type type;
+ enum rawrtc_ice_tcp_candidate_type tcp_type;
+ char* related_address = NULL;
+
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check ICE transport state
+ if (transport->state == RAWRTC_ICE_TRANSPORT_STATE_CLOSED ||
+ transport->state == RAWRTC_ICE_TRANSPORT_STATE_FAILED ||
+ transport->state == RAWRTC_ICE_TRANSPORT_STATE_COMPLETED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Remote site completed gathering?
+ if (!candidate) {
+ if (!transport->remote_end_of_candidates) {
+ DEBUG_PRINTF(
+ "Remote site gathering complete\n%H", trice_debug, transport->gatherer->ice);
+
+ // Transition to 'complete' if the checklist is done
+ // Note: 'completed' and 'failed' states are covered in checks above
+ if (transport->state != RAWRTC_ICE_TRANSPORT_STATE_NEW &&
+ !trice_checklist_isrunning(transport->gatherer->ice)) {
+ set_state(transport, RAWRTC_ICE_TRANSPORT_STATE_COMPLETED);
+ }
+
+ // Mark that we've received end-of-candidates
+ transport->remote_end_of_candidates = true;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // New remote candidate after end-of-candidates indication?
+ if (transport->remote_end_of_candidates) {
+ DEBUG_NOTICE("Tried to add a remote candidate after end-of-candidates\n");
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Get IP and port
+ error = rawrtc_ice_candidate_get_ip(&ip, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_port(&port, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_error_to_code(sa_set_str(&address, ip, port));
+ if (error) {
+ goto out;
+ }
+
+ // Skip IPv4, IPv6 if requested
+ // TODO: Get config from struct
+ af = sa_af(&address);
+ if ((!rawrtc_default_config.ipv6_enable && af == AF_INET6) ||
+ (!rawrtc_default_config.ipv4_enable && af == AF_INET)) {
+ DEBUG_PRINTF("Skipping remote candidate due to IP version: %J\n", &address);
+ goto out;
+ }
+
+ // Get protocol
+ error = rawrtc_ice_candidate_get_protocol(&protocol, candidate);
+ if (error) {
+ goto out;
+ }
+
+ // Skip UDP/TCP if requested
+ // TODO: Get config from struct
+ if ((!rawrtc_default_config.udp_enable && protocol == RAWRTC_ICE_PROTOCOL_UDP) ||
+ (!rawrtc_default_config.tcp_enable && protocol == RAWRTC_ICE_PROTOCOL_TCP)) {
+ DEBUG_PRINTF("Skipping remote candidate due to protocol: %J\n", &address);
+ goto out;
+ }
+
+ // Get necessary vars
+ error = rawrtc_ice_candidate_get_foundation(&foundation, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_protocol(&protocol, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_priority(&priority, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_type(&type, candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_tcp_type(&tcp_type, candidate);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ // Doesn't matter what we choose here, protocol is not TCP anyway
+ tcp_type = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE;
+ break;
+ default:
+ goto out;
+ }
+
+ // Add remote candidate
+ // TODO: Set correct component ID
+ error = rawrtc_error_to_code(trice_rcand_add(
+ &re_candidate, transport->gatherer->ice, 1, foundation,
+ rawrtc_ice_protocol_to_ipproto(protocol), priority, &address,
+ rawrtc_ice_candidate_type_to_ice_cand_type(type),
+ rawrtc_ice_tcp_candidate_type_to_ice_tcptype(tcp_type)));
+ if (error) {
+ goto out;
+ }
+
+ // Set related address (if any)
+ error = rawrtc_ice_candidate_get_related_address(&related_address, candidate);
+ if (!error) {
+ error = rawrtc_ice_candidate_get_related_port(&port, candidate);
+ if (!error) {
+ error = rawrtc_error_to_code(
+ sa_set_str(&re_candidate->attr.rel_addr, related_address, port));
+ if (error) {
+ goto out;
+ }
+ }
+ }
+ if (error && error != RAWRTC_CODE_NO_VALUE) {
+ goto out;
+ }
+
+ // TODO: Add TURN permission
+
+ // Done
+ DEBUG_PRINTF("Added remote candidate: %J\n", &address);
+ error = RAWRTC_CODE_SUCCESS;
+
+ // Start checklist (if not new, not started and not completed or failed)
+ // TODO: Get config from struct
+ if (transport->state != RAWRTC_ICE_TRANSPORT_STATE_NEW &&
+ transport->state != RAWRTC_ICE_TRANSPORT_STATE_COMPLETED &&
+ transport->state != RAWRTC_ICE_TRANSPORT_STATE_FAILED &&
+ !trice_checklist_isrunning(transport->gatherer->ice)) {
+ DEBUG_INFO("Starting checklist due to new remote candidate\n");
+ error = rawrtc_error_to_code(trice_checklist_start(
+ transport->gatherer->ice, transport->stun_client, rawrtc_default_config.pacing_interval,
+ ice_established_handler, ice_failed_handler, transport));
+ if (error) {
+ DEBUG_WARNING("Could not start checklist, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+ }
+
+out:
+ if (error) {
+ mem_deref(re_candidate); // TODO: Not entirely sure about that
+ }
+
+ // Free vars
+ mem_deref(related_address);
+ mem_deref(foundation);
+ mem_deref(ip);
+
+ return error;
+}
+
+/*
+ * Set the remote candidates on the ICE transport overwriting all
+ * existing remote candidates.
+ */
+enum rawrtc_code rawrtc_ice_transport_set_remote_candidates(
+ struct rawrtc_ice_transport* const transport,
+ struct rawrtc_ice_candidate* const candidates[], // referenced (each item)
+ size_t const n_candidates) {
+ size_t i;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !candidates) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Our implementation is incorrect here, it should remove
+ // previously added remote candidates and replace them. Fix this
+ // once we can handle an ICE restart.
+
+ // Add each remote candidate
+ for (i = 0; i < n_candidates; ++i) {
+ error = rawrtc_ice_transport_add_remote_candidate(transport, candidates[i]);
+ if (error) {
+ return error;
+ }
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_transport/transport.h b/src/ice_transport/transport.h
new file mode 100644
index 0000000..60d938b
--- /dev/null
+++ b/src/ice_transport/transport.h
@@ -0,0 +1,26 @@
+#pragma once
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_parameters.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <rew.h>
+
+struct rawrtc_ice_transport {
+ enum rawrtc_ice_transport_state state;
+ struct rawrtc_ice_gatherer* gatherer; // referenced
+ rawrtc_ice_transport_state_change_handler state_change_handler; // nullable
+ rawrtc_ice_transport_candidate_pair_change_handler candidate_pair_change_handler; // nullable
+ void* arg; // nullable
+ struct stun* stun_client;
+ struct rawrtc_ice_parameters* remote_parameters; // referenced
+ struct rawrtc_dtls_transport* dtls_transport; // referenced, nullable
+ bool remote_end_of_candidates;
+};
+
+enum ice_role rawrtc_ice_role_to_re_ice_role(enum rawrtc_ice_role const role);
+
+enum rawrtc_code rawrtc_re_ice_role_to_ice_role(
+ enum rawrtc_ice_role* const rolep, // de-referenced
+ enum ice_role const re_role);
diff --git a/src/ice_transport/utils.c b/src/ice_transport/utils.c
new file mode 100644
index 0000000..32969d8
--- /dev/null
+++ b/src/ice_transport/utils.c
@@ -0,0 +1,114 @@
+#include "transport.h"
+#include <rawrtc/ice_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <rew.h>
+
+/*
+ * Get the corresponding name for an ICE transport state.
+ */
+char const* rawrtc_ice_transport_state_to_name(enum rawrtc_ice_transport_state const state) {
+ switch (state) {
+ case RAWRTC_ICE_TRANSPORT_STATE_NEW:
+ return "new";
+ case RAWRTC_ICE_TRANSPORT_STATE_CHECKING:
+ return "checking";
+ case RAWRTC_ICE_TRANSPORT_STATE_CONNECTED:
+ return "connected";
+ case RAWRTC_ICE_TRANSPORT_STATE_COMPLETED:
+ return "completed";
+ case RAWRTC_ICE_TRANSPORT_STATE_DISCONNECTED:
+ return "disconnected";
+ case RAWRTC_ICE_TRANSPORT_STATE_FAILED:
+ return "failed";
+ case RAWRTC_ICE_TRANSPORT_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
+
+static enum rawrtc_ice_role const map_enum_ice_role[] = {
+ RAWRTC_ICE_ROLE_CONTROLLING,
+ RAWRTC_ICE_ROLE_CONTROLLED,
+};
+
+static char const* const map_str_ice_role[] = {
+ "controlling",
+ "controlled",
+};
+
+static size_t const map_ice_role_length = ARRAY_SIZE(map_enum_ice_role);
+
+/*
+ * Translate an ICE role to str.
+ */
+char const* rawrtc_ice_role_to_str(enum rawrtc_ice_role const role) {
+ size_t i;
+
+ for (i = 0; i < map_ice_role_length; ++i) {
+ if (map_enum_ice_role[i] == role) {
+ return map_str_ice_role[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to an ICE role (case-insensitive).
+ */
+enum rawrtc_code rawrtc_str_to_ice_role(
+ enum rawrtc_ice_role* const rolep, // de-referenced
+ char const* const str) {
+ size_t i;
+
+ // Check arguments
+ if (!rolep || !str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_ice_role_length; ++i) {
+ if (str_casecmp(map_str_ice_role[i], str) == 0) {
+ *rolep = map_enum_ice_role[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Translate an ICE role to the corresponding re type.
+ */
+enum ice_role rawrtc_ice_role_to_re_ice_role(enum rawrtc_ice_role const role) {
+ // No conversion needed
+ return (enum ice_role) role;
+}
+
+/*
+ * Translate a re ICE role to the corresponding rawrtc role.
+ */
+enum rawrtc_code rawrtc_re_ice_role_to_ice_role(
+ enum rawrtc_ice_role* const rolep, // de-referenced
+ enum ice_role const re_role) {
+ // Check arguments
+ if (!rolep) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Translate role
+ switch (re_role) {
+ case ICE_ROLE_CONTROLLING:
+ *rolep = RAWRTC_ICE_ROLE_CONTROLLING;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_ROLE_CONTROLLED:
+ *rolep = RAWRTC_ICE_ROLE_CONTROLLED;
+ return RAWRTC_CODE_SUCCESS;
+ case ICE_ROLE_UNKNOWN:
+ *rolep = RAWRTC_ICE_ROLE_UNKNOWN;
+ return RAWRTC_CODE_SUCCESS;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+}
diff --git a/src/main/config.c b/src/main/config.c
new file mode 100644
index 0000000..2817bf0
--- /dev/null
+++ b/src/main/config.c
@@ -0,0 +1,27 @@
+#include "config.h"
+#include <rawrtc/certificate.h>
+#include <rawrtc/ice_server.h>
+#include <re.h>
+
+/*
+ * Default rawrtc configuration.
+ */
+struct rawrtc_config rawrtc_default_config = {
+ .pacing_interval = 20,
+ .ipv4_enable = true,
+ .ipv6_enable = true,
+ .udp_enable = true,
+ .tcp_enable = false, // TODO: true by default
+ .sign_algorithm = RAWRTC_CERTIFICATE_SIGN_ALGORITHM_SHA256,
+ .ice_server_normal_transport = RAWRTC_ICE_SERVER_TRANSPORT_UDP,
+ .ice_server_secure_transport = RAWRTC_ICE_SERVER_TRANSPORT_TLS,
+ .stun_keepalive_interval = 25,
+ .stun_config =
+ {
+ .rto = STUN_DEFAULT_RTO,
+ .rc = STUN_DEFAULT_RC,
+ .rm = STUN_DEFAULT_RM,
+ .ti = STUN_DEFAULT_TI,
+ .tos = 0x00,
+ },
+};
diff --git a/src/main/config.h b/src/main/config.h
new file mode 100644
index 0000000..f00c2fd
--- /dev/null
+++ b/src/main/config.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <rawrtc/certificate.h>
+#include <rawrtc/ice_server.h>
+#include <re.h>
+
+struct rawrtc_config {
+ uint32_t pacing_interval;
+ bool ipv4_enable;
+ bool ipv6_enable;
+ bool udp_enable;
+ bool tcp_enable;
+ enum rawrtc_certificate_sign_algorithm sign_algorithm;
+ enum rawrtc_ice_server_transport ice_server_normal_transport;
+ enum rawrtc_ice_server_transport ice_server_secure_transport;
+ uint32_t stun_keepalive_interval;
+ struct stun_conf stun_config;
+};
+
+extern struct rawrtc_config rawrtc_default_config;
diff --git a/src/main/main.c b/src/main/main.c
new file mode 100644
index 0000000..aa25b20
--- /dev/null
+++ b/src/main/main.c
@@ -0,0 +1,75 @@
+#include "main.h"
+#include <rawrtc/config.h>
+#include <rawrtc/main.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/main.h>
+#include <re.h>
+
+#define DEBUG_MODULE "rawrtc-main"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+struct rawrtc_global rawrtc_global;
+
+/*
+ * Handle RAWRTCDC timer tick expired.
+ */
+static inline void rawrtcdc_timer_tick_expired_handler(void* arg) {
+ (void) arg;
+
+ // Restart timer
+ tmr_start(
+ &rawrtc_global.rawrtcdc_timer, (uint64_t) rawrtc_global.rawrtcdc_timer_interval,
+ rawrtcdc_timer_tick_expired_handler, NULL);
+
+ // Handle timer tick
+ rawrtcdc_timer_tick(rawrtc_global.rawrtcdc_timer_interval);
+}
+
+/*
+ * RAWRTCDC timer handler.
+ */
+static inline enum rawrtc_code rawrtcdc_timer_tick_handler(
+ bool const on, uint_fast16_t const interval) {
+ // Start or stop timer?
+ if (on) {
+ // Store interval, initialise & start timer
+ rawrtc_global.rawrtcdc_timer_interval = interval;
+ tmr_start(
+ &rawrtc_global.rawrtcdc_timer, (uint64_t) rawrtc_global.rawrtcdc_timer_interval,
+ rawrtcdc_timer_tick_expired_handler, NULL);
+ DEBUG_PRINTF("Started RAWRTCDC timer\n");
+ } else {
+ tmr_cancel(&rawrtc_global.rawrtcdc_timer);
+ DEBUG_PRINTF("Stopped RAWRTCDC timer\n");
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Initialise RAWRTC. Must be called before making a call to any other
+ * function.
+ *
+ * Note: In case `init_re` is not set to `true`, you MUST initialise
+ * re yourselves before calling this function.
+ */
+enum rawrtc_code rawrtc_init(bool const init_re) {
+ // Initialise timer
+ tmr_init(&rawrtc_global.rawrtcdc_timer);
+
+ // Initialise RAWRTCDC
+ return rawrtcdc_init(init_re, rawrtcdc_timer_tick_handler);
+}
+
+/*
+ * Close RAWRTC and free up all resources.
+ *
+ * Note: In case `close_re` is not set to `true`, you MUST close
+ * re yourselves.
+ */
+enum rawrtc_code rawrtc_close(bool const close_re) {
+ // Close RAWRTCDC
+ return rawrtcdc_close(close_re);
+}
diff --git a/src/main/main.h b/src/main/main.h
new file mode 100644
index 0000000..8b485d6
--- /dev/null
+++ b/src/main/main.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <re.h>
+
+extern struct rawrtc_global rawrtc_global;
+
+/*
+ * Global RAWRTC vars.
+ */
+struct rawrtc_global {
+ struct tmr rawrtcdc_timer;
+ uint_fast16_t rawrtcdc_timer_interval;
+};
diff --git a/src/main/meson.build b/src/main/meson.build
new file mode 100644
index 0000000..8e92d19
--- /dev/null
+++ b/src/main/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'config.c',
+ 'main.c',
+])
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..7c17410
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,25 @@
+sources = []
+
+subdir('certificate')
+subdir('diffie_hellman_parameters')
+subdir('dtls_fingerprint')
+subdir('dtls_parameters')
+subdir('dtls_transport')
+subdir('ice_candidate')
+subdir('ice_gather_options')
+subdir('ice_gatherer')
+subdir('ice_parameters')
+subdir('ice_server')
+subdir('ice_transport')
+subdir('main')
+subdir('peer_connection')
+subdir('peer_connection_configuration')
+subdir('peer_connection_description')
+subdir('peer_connection_ice_candidate')
+subdir('peer_connection_state')
+subdir('sctp_common')
+if get_option('sctp_redirect_transport')
+ subdir('sctp_redirect_transport')
+endif
+subdir('sctp_transport')
+subdir('utils')
diff --git a/src/peer_connection/attributes.c b/src/peer_connection/attributes.c
new file mode 100644
index 0000000..392bad1
--- /dev/null
+++ b/src/peer_connection/attributes.c
@@ -0,0 +1,492 @@
+#include "connection.h"
+#include "../peer_connection_description/description.h"
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtc/peer_connection.h>
+#include <rawrtc/peer_connection_state.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/data_channel.h>
+#include <re.h>
+
+/*
+ * Get local description.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no local description has been
+ * set. Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and
+ * `*descriptionp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_local_description(
+ struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!descriptionp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Reference description (if any)
+ if (connection->local_description) {
+ *descriptionp = mem_ref(connection->local_description);
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get remote description.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no remote description has been
+ * set. Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and
+ * `*descriptionp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_remote_description(
+ struct rawrtc_peer_connection_description** const descriptionp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!descriptionp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Reference description (if any)
+ if (connection->remote_description) {
+ *descriptionp = mem_ref(connection->remote_description);
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the current signalling state of a peer connection.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_signaling_state(
+ enum rawrtc_signaling_state* const statep, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!statep || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ *statep = connection->signaling_state;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the current ICE gathering state of a peer connection.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_ice_gathering_state(
+ enum rawrtc_ice_gatherer_state* const statep, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!statep || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ // Note: The W3C spec requires us to return 'new' in case no ICE gatherer exists.
+ // Note: Theoretically there's no 'closed' state on the peer connection variant. We ignore
+ // that here.
+ if (connection->context.ice_gatherer) {
+ return rawrtc_ice_gatherer_get_state(statep, connection->context.ice_gatherer);
+ } else {
+ *statep = RAWRTC_ICE_GATHERER_STATE_NEW;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Get the current ICE connection state of a peer connection.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_ice_connection_state(
+ enum rawrtc_ice_transport_state* const statep, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!statep || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ // Note: The W3C spec requires us to return 'new' in case no ICE transport exists.
+ if (connection->context.ice_transport) {
+ return rawrtc_ice_transport_get_state(statep, connection->context.ice_transport);
+ } else {
+ *statep = RAWRTC_ICE_TRANSPORT_STATE_NEW;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Get the current (peer) connection state of the peer connection.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_connection_state(
+ enum rawrtc_peer_connection_state* const statep, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!statep || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ *statep = connection->connection_state;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get indication whether the remote peer accepts trickled ICE
+ * candidates.
+ *
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no remote description has been
+ * set.
+ */
+enum rawrtc_code rawrtc_peer_connection_can_trickle_ice_candidates(
+ bool* const can_trickle_ice_candidatesp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!can_trickle_ice_candidatesp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set flag (if remote description set)
+ if (connection->remote_description) {
+ *can_trickle_ice_candidatesp = connection->remote_description->trickle_ice;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Unset the handler argument and all handlers of the peer connection.
+ */
+enum rawrtc_code rawrtc_peer_connection_unset_handlers(
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Unset handler argument
+ connection->arg = NULL;
+
+ // Unset all handlers
+ connection->data_channel_handler = NULL;
+ connection->connection_state_change_handler = NULL;
+ connection->ice_gathering_state_change_handler = NULL;
+ connection->ice_connection_state_change_handler = NULL;
+ connection->signaling_state_change_handler = NULL;
+ connection->local_candidate_error_handler = NULL;
+ connection->local_candidate_handler = NULL;
+ connection->negotiation_needed_handler = NULL;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the peer connection's negotiation needed handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_negotiation_needed_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_negotiation_needed_handler const negotiation_needed_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set negotiation needed handler & done
+ connection->negotiation_needed_handler = negotiation_needed_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's negotiation needed handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_negotiation_needed_handler(
+ rawrtc_negotiation_needed_handler* const negotiation_needed_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!negotiation_needed_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get negotiation needed handler (if any)
+ if (connection->negotiation_needed_handler) {
+ *negotiation_needed_handlerp = connection->negotiation_needed_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's ICE local candidate handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_local_candidate_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_peer_connection_local_candidate_handler const local_candidate_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set local candidate handler & done
+ connection->local_candidate_handler = local_candidate_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's ICE local candidate handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_local_candidate_handler(
+ rawrtc_peer_connection_local_candidate_handler* const
+ local_candidate_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!local_candidate_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get local candidate handler (if any)
+ if (connection->local_candidate_handler) {
+ *local_candidate_handlerp = connection->local_candidate_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's ICE local candidate error handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_local_candidate_error_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_peer_connection_local_candidate_error_handler const
+ local_candidate_error_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set local candidate error handler & done
+ connection->local_candidate_error_handler = local_candidate_error_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's ICE local candidate error handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_local_candidate_error_handler(
+ rawrtc_peer_connection_local_candidate_error_handler* const
+ local_candidate_error_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!local_candidate_error_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get local candidate error handler (if any)
+ if (connection->local_candidate_error_handler) {
+ *local_candidate_error_handlerp = connection->local_candidate_error_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's signaling state change handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_signaling_state_change_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_signaling_state_change_handler const signaling_state_change_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set signaling state change handler & done
+ connection->signaling_state_change_handler = signaling_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's signaling state change handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_signaling_state_change_handler(
+ rawrtc_signaling_state_change_handler* const signaling_state_change_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!signaling_state_change_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get signaling state change handler (if any)
+ if (connection->signaling_state_change_handler) {
+ *signaling_state_change_handlerp = connection->signaling_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's ice connection state change handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_ice_connection_state_change_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_ice_transport_state_change_handler const ice_connection_state_change_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set ice connection state change handler & done
+ connection->ice_connection_state_change_handler = ice_connection_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's ice connection state change handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_ice_connection_state_change_handler(
+ rawrtc_ice_transport_state_change_handler* const
+ ice_connection_state_change_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!ice_connection_state_change_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get ice connection state change handler (if any)
+ if (connection->ice_connection_state_change_handler) {
+ *ice_connection_state_change_handlerp = connection->ice_connection_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's ice gathering state change handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_ice_gathering_state_change_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_ice_gatherer_state_change_handler const ice_gathering_state_change_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set ice gathering state change handler & done
+ connection->ice_gathering_state_change_handler = ice_gathering_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's ice gathering state change handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_ice_gathering_state_change_handler(
+ rawrtc_ice_gatherer_state_change_handler* const
+ ice_gathering_state_change_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!ice_gathering_state_change_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get ice gathering state change handler (if any)
+ if (connection->ice_gathering_state_change_handler) {
+ *ice_gathering_state_change_handlerp = connection->ice_gathering_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's (peer) connection state change handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_connection_state_change_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_peer_connection_state_change_handler const connection_state_change_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set (peer) connection state change handler & done
+ connection->connection_state_change_handler = connection_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's (peer) connection state change handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_connection_state_change_handler(
+ rawrtc_peer_connection_state_change_handler* const
+ connection_state_change_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!connection_state_change_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get (peer) connection state change handler (if any)
+ if (connection->connection_state_change_handler) {
+ *connection_state_change_handlerp = connection->connection_state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the peer connection's data channel handler.
+ */
+enum rawrtc_code rawrtc_peer_connection_set_data_channel_handler(
+ struct rawrtc_peer_connection* const connection,
+ rawrtc_data_channel_handler const data_channel_handler // nullable
+) {
+ // Check arguments
+ if (!connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set data channel handler & done
+ connection->data_channel_handler = data_channel_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the peer connection's data channel handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_peer_connection_get_data_channel_handler(
+ rawrtc_data_channel_handler* const data_channel_handlerp, // de-referenced
+ struct rawrtc_peer_connection* const connection) {
+ // Check arguments
+ if (!data_channel_handlerp || !connection) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get data channel handler (if any)
+ if (connection->data_channel_handler) {
+ *data_channel_handlerp = connection->data_channel_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
diff --git a/src/peer_connection/connection.c b/src/peer_connection/connection.c
new file mode 100644
index 0000000..ceb1a43
--- /dev/null
+++ b/src/peer_connection/connection.c
@@ -0,0 +1,1465 @@
+#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(¤t->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(¤t->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;
+}
diff --git a/src/peer_connection/connection.h b/src/peer_connection/connection.h
new file mode 100644
index 0000000..ef7cff6
--- /dev/null
+++ b/src/peer_connection/connection.h
@@ -0,0 +1,49 @@
+#pragma once
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_transport.h>
+#include <rawrtc/peer_connection.h>
+#include <rawrtc/peer_connection_configuration.h>
+#include <rawrtc/peer_connection_description.h>
+#include <rawrtc/peer_connection_state.h>
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_transport.h>
+#include <re.h>
+
+enum {
+ RAWRTC_PEER_CONNECTION_SCTP_TRANSPORT_PORT = 5000,
+ RAWRTC_DTLS_ID_LENGTH = 32,
+};
+
+/*
+ * Peer connection context.
+ */
+struct rawrtc_peer_connection_context {
+ struct rawrtc_ice_gather_options* gather_options;
+ struct rawrtc_ice_gatherer* ice_gatherer;
+ struct rawrtc_ice_transport* ice_transport;
+ struct list certificates;
+ char dtls_id[RAWRTC_DTLS_ID_LENGTH + 1];
+ struct rawrtc_dtls_transport* dtls_transport;
+ struct rawrtc_data_transport* data_transport;
+};
+
+struct rawrtc_peer_connection {
+ enum rawrtc_peer_connection_state connection_state;
+ enum rawrtc_signaling_state signaling_state;
+ struct rawrtc_peer_connection_configuration* configuration; // referenced
+ rawrtc_negotiation_needed_handler negotiation_needed_handler; // nullable
+ rawrtc_peer_connection_local_candidate_handler local_candidate_handler; // nullable
+ rawrtc_peer_connection_local_candidate_error_handler local_candidate_error_handler; // nullable
+ rawrtc_signaling_state_change_handler signaling_state_change_handler; // nullable
+ rawrtc_ice_transport_state_change_handler ice_connection_state_change_handler; // nullable
+ rawrtc_ice_gatherer_state_change_handler ice_gathering_state_change_handler; // nullable
+ rawrtc_peer_connection_state_change_handler connection_state_change_handler; // nullable
+ rawrtc_data_channel_handler data_channel_handler; // nullable
+ enum rawrtc_data_transport_type data_transport_type;
+ struct rawrtc_peer_connection_description* local_description; // referenced
+ struct rawrtc_peer_connection_description* remote_description; // referenced
+ struct rawrtc_peer_connection_context context;
+ void* arg; // nullable
+};
diff --git a/src/peer_connection/meson.build b/src/peer_connection/meson.build
new file mode 100644
index 0000000..16bd198
--- /dev/null
+++ b/src/peer_connection/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'attributes.c',
+ 'connection.c',
+])
diff --git a/src/peer_connection_configuration/configuration.c b/src/peer_connection_configuration/configuration.c
new file mode 100644
index 0000000..c1f2b71
--- /dev/null
+++ b/src/peer_connection_configuration/configuration.c
@@ -0,0 +1,260 @@
+#include "configuration.h"
+#include "../certificate/certificate.h"
+#include "../ice_server/server.h"
+#include "../utils/utils.h"
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/peer_connection_configuration.h>
+#include <rawrtc/utils.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+#include <limits.h> // INT_MAX
+
+/*
+ * Destructor for an existing peer connection configuration.
+ */
+static void rawrtc_peer_connection_configuration_destroy(void* arg) {
+ struct rawrtc_peer_connection_configuration* const configuration = arg;
+
+ // Un-reference
+ list_flush(&configuration->certificates);
+ list_flush(&configuration->ice_servers);
+}
+
+/*
+ * Create a new peer connection configuration.
+ * `*configurationp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_create(
+ struct rawrtc_peer_connection_configuration** const configurationp, // de-referenced
+ enum rawrtc_ice_gather_policy const gather_policy) {
+ struct rawrtc_peer_connection_configuration* configuration;
+
+ // Check arguments
+ if (!configurationp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ configuration =
+ mem_zalloc(sizeof(*configuration), rawrtc_peer_connection_configuration_destroy);
+ if (!configuration) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ configuration->gather_policy = gather_policy;
+ list_init(&configuration->ice_servers);
+ list_init(&configuration->certificates);
+ configuration->sctp_sdp_05 = true;
+ configuration->sctp.send_buffer_length = 0;
+ configuration->sctp.receive_buffer_length = 0;
+ configuration->sctp.congestion_ctrl_algorithm = RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581;
+ configuration->sctp.mtu = 0;
+ configuration->sctp.mtu_discovery = false;
+
+ // Set pointer and return
+ *configurationp = configuration;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add an ICE server instance to the peer connection configuration.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_add_ice_server_internal(
+ struct rawrtc_peer_connection_configuration* const configuration,
+ struct rawrtc_ice_server* const server) {
+ // Check arguments
+ if (!configuration || !server) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Add to configuration
+ list_append(&configuration->ice_servers, &server->le, server);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Add an ICE server to the peer connection configuration.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_add_ice_server(
+ struct rawrtc_peer_connection_configuration* const configuration,
+ 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;
+
+ // Check arguments
+ if (!configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure there are less than 2^8 servers
+ // TODO: This check should be in some common location
+ if (list_count(&configuration->ice_servers) == UINT8_MAX) {
+ return RAWRTC_CODE_INSUFFICIENT_SPACE;
+ }
+
+ // Create ICE server
+ error = rawrtc_ice_server_create(&server, urls, n_urls, username, credential, credential_type);
+ if (error) {
+ return error;
+ }
+
+ // Add to configuration
+ return rawrtc_peer_connection_configuration_add_ice_server_internal(configuration, server);
+}
+
+/*
+ * Get ICE servers from the peer connection configuration.
+ * `*serversp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_get_ice_servers(
+ struct rawrtc_ice_servers** const serversp, // de-referenced
+ struct rawrtc_peer_connection_configuration* const configuration) {
+ // Check arguments
+ if (!serversp || !configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Hand out list as array
+ // Note: ICE servers handed out cannot be added to other lists
+ // without copying since the items are only referenced.
+ return rawrtc_list_to_array(
+ (struct rawrtc_array_container**) serversp, &configuration->ice_servers, true);
+}
+
+/*
+ * Add a certificate to the peer connection configuration to be used
+ * instead of an ephemerally generated one.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_add_certificate(
+ struct rawrtc_peer_connection_configuration* configuration,
+ struct rawrtc_certificate* const certificate // copied
+) {
+ enum rawrtc_code error;
+ struct rawrtc_certificate* certificate_copy;
+
+ // Check arguments
+ if (!configuration || !certificate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Copy certificate
+ // Note: Copying is needed as the 'le' element cannot be associated to multiple lists
+ error = rawrtc_certificate_copy(&certificate_copy, certificate);
+ if (error) {
+ return error;
+ }
+
+ // Append to list
+ list_append(&configuration->certificates, &certificate_copy->le, certificate_copy);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get certificates from the peer connection configuration.
+ * `*certificatesp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_get_certificates(
+ struct rawrtc_certificates** const certificatesp, // de-referenced
+ struct rawrtc_peer_connection_configuration* const configuration) {
+ // Check arguments
+ if (!certificatesp || !configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Hand out list as array
+ // Note: Certificates handed out cannot be added to other lists
+ // without copying since the items are only referenced.
+ return rawrtc_list_to_array(
+ (struct rawrtc_array_container**) certificatesp, &configuration->certificates, true);
+}
+
+/*
+ * Set whether to use legacy SDP for data channel parameter encoding.
+ * Note: Legacy SDP for data channels is on by default due to parsing problems in Chrome.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_set_sctp_sdp_05(
+ struct rawrtc_peer_connection_configuration* configuration, bool const on) {
+ // Check parameters
+ if (!configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set
+ configuration->sctp_sdp_05 = on;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's send and receive buffer length in bytes.
+ * If both values are zero, the default buffer length will be used. Otherwise,
+ * zero is invalid.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_set_sctp_buffer_length(
+ struct rawrtc_peer_connection_configuration* configuration,
+ uint32_t const send_buffer_length,
+ uint32_t const receive_buffer_length) {
+ // Check arguments
+ if (!configuration || send_buffer_length > INT_MAX || receive_buffer_length > INT_MAX ||
+ (send_buffer_length == 0 && receive_buffer_length != 0) ||
+ (send_buffer_length != 0 && receive_buffer_length == 0)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set length for send/receive buffer
+ configuration->sctp.send_buffer_length = send_buffer_length;
+ configuration->sctp.receive_buffer_length = receive_buffer_length;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's congestion control algorithm.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_set_sctp_congestion_ctrl_algorithm(
+ struct rawrtc_peer_connection_configuration* configuration,
+ enum rawrtc_sctp_transport_congestion_ctrl const algorithm) {
+ // Check arguments
+ if (!configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set
+ configuration->sctp.congestion_ctrl_algorithm = algorithm;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's maximum transmission unit (MTU).
+ * A value of zero indicates that the default MTU should be used.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_set_sctp_mtu(
+ struct rawrtc_peer_connection_configuration* configuration, uint32_t const mtu) {
+ // Check arguments
+ if (!configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set
+ configuration->sctp.mtu = mtu;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Enable or disable MTU discovery on the SCTP transport.
+ */
+enum rawrtc_code rawrtc_peer_connection_configuration_set_sctp_mtu_discovery(
+ struct rawrtc_peer_connection_configuration* configuration, bool const on) {
+ // Check arguments
+ if (!configuration) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set
+ configuration->sctp.mtu_discovery = on;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/peer_connection_configuration/configuration.h b/src/peer_connection_configuration/configuration.h
new file mode 100644
index 0000000..34fd7c1
--- /dev/null
+++ b/src/peer_connection_configuration/configuration.h
@@ -0,0 +1,24 @@
+#pragma once
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_server.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+
+struct rawrtc_peer_connection_configuration {
+ enum rawrtc_ice_gather_policy gather_policy;
+ struct list ice_servers;
+ struct list certificates;
+ bool sctp_sdp_05;
+ struct {
+ uint32_t send_buffer_length;
+ uint32_t receive_buffer_length;
+ enum rawrtc_sctp_transport_congestion_ctrl congestion_ctrl_algorithm;
+ uint32_t mtu;
+ bool mtu_discovery;
+ } sctp;
+};
+
+enum rawrtc_code rawrtc_peer_connection_configuration_add_ice_server_internal(
+ struct rawrtc_peer_connection_configuration* const configuration,
+ struct rawrtc_ice_server* const server);
diff --git a/src/peer_connection_configuration/meson.build b/src/peer_connection_configuration/meson.build
new file mode 100644
index 0000000..3d01bb6
--- /dev/null
+++ b/src/peer_connection_configuration/meson.build
@@ -0,0 +1 @@
+sources += files('configuration.c')
diff --git a/src/peer_connection_description/attributes.c b/src/peer_connection_description/attributes.c
new file mode 100644
index 0000000..60f9e0a
--- /dev/null
+++ b/src/peer_connection_description/attributes.c
@@ -0,0 +1,39 @@
+#include "description.h"
+#include <rawrtc/peer_connection_description.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+/*
+ * Get the SDP type of the description.
+ */
+enum rawrtc_code rawrtc_peer_connection_description_get_sdp_type(
+ enum rawrtc_sdp_type* const typep, // de-referenced
+ struct rawrtc_peer_connection_description* const description) {
+ // Check arguments
+ if (!typep || !description) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set SDP type
+ *typep = description->type;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the SDP of the description.
+ * `*sdpp` will be set to a copy of the SDP that must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_description_get_sdp(
+ char** const sdpp, // de-referenced
+ struct rawrtc_peer_connection_description* const description) {
+ // Check arguments
+ if (!sdpp || !description) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Copy SDP
+ return rawrtc_sdprintf(sdpp, "%b", description->sdp->buf, description->sdp->end);
+}
diff --git a/src/peer_connection_description/description.c b/src/peer_connection_description/description.c
new file mode 100644
index 0000000..b4da9eb
--- /dev/null
+++ b/src/peer_connection_description/description.c
@@ -0,0 +1,1091 @@
+#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(¶meters, 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(¶meters, 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;
+}
diff --git a/src/peer_connection_description/description.h b/src/peer_connection_description/description.h
new file mode 100644
index 0000000..eabf2e0
--- /dev/null
+++ b/src/peer_connection_description/description.h
@@ -0,0 +1,47 @@
+#pragma once
+#include <rawrtc/dtls_parameters.h>
+#include <rawrtc/ice_parameters.h>
+#include <rawrtc/peer_connection.h>
+#include <rawrtc/peer_connection_description.h>
+#include <rawrtc/peer_connection_ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/sctp_capabilities.h>
+#include <re.h>
+
+#define RAWRTC_PEER_CONNECTION_DESCRIPTION_MID "rawrtc-sctp-dc"
+
+struct rawrtc_peer_connection_description {
+ struct rawrtc_peer_connection* connection;
+ enum rawrtc_sdp_type type;
+ bool trickle_ice;
+ char* bundled_mids;
+ char* remote_media_line;
+ uint8_t media_line_index;
+ char* mid;
+ bool sctp_sdp_05;
+ bool end_of_candidates;
+ struct list ice_candidates;
+ struct rawrtc_ice_parameters* ice_parameters;
+ struct rawrtc_dtls_parameters* dtls_parameters;
+ struct rawrtc_sctp_capabilities* sctp_capabilities;
+ uint16_t sctp_port;
+ struct mbuf* sdp;
+};
+
+enum {
+ RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_SIZE = 1024,
+ RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_MAX_MESSAGE_SIZE = 65536,
+ RAWRTC_PEER_CONNECTION_DESCRIPTION_DEFAULT_SCTP_PORT = 5000,
+};
+
+enum rawrtc_code rawrtc_peer_connection_description_create_internal(
+ struct rawrtc_peer_connection_description** const descriptionp,
+ struct rawrtc_peer_connection* const connection,
+ bool const offering);
+
+enum rawrtc_code rawrtc_peer_connection_description_add_candidate(
+ struct rawrtc_peer_connection_description* const description,
+ struct rawrtc_peer_connection_ice_candidate* const candidate);
+
+int rawrtc_peer_connection_description_debug(
+ struct re_printf* const pf, struct rawrtc_peer_connection_description* const description);
diff --git a/src/peer_connection_description/meson.build b/src/peer_connection_description/meson.build
new file mode 100644
index 0000000..54c8f27
--- /dev/null
+++ b/src/peer_connection_description/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'description.c',
+ 'utils.c',
+])
diff --git a/src/peer_connection_description/utils.c b/src/peer_connection_description/utils.c
new file mode 100644
index 0000000..d05d13b
--- /dev/null
+++ b/src/peer_connection_description/utils.c
@@ -0,0 +1,149 @@
+#include "description.h"
+#include "../dtls_parameters/parameters.h"
+#include "../ice_parameters/parameters.h"
+#include "../peer_connection_ice_candidate/candidate.h"
+#include <rawrtc/peer_connection_description.h>
+#include <rawrtcc/code.h>
+#include <rawrtcdc/sctp_capabilities.h>
+#include <re.h>
+
+static enum rawrtc_sdp_type const map_enum_sdp_type[] = {
+ RAWRTC_SDP_TYPE_OFFER,
+ RAWRTC_SDP_TYPE_PROVISIONAL_ANSWER,
+ RAWRTC_SDP_TYPE_ANSWER,
+ RAWRTC_SDP_TYPE_ROLLBACK,
+};
+
+static char const* const map_str_sdp_type[] = {
+ "offer",
+ "pranswer",
+ "answer",
+ "rollback",
+};
+
+static size_t const map_sdp_type_length = ARRAY_SIZE(map_enum_sdp_type);
+
+/*
+ * Translate an SDP type to str.
+ */
+char const* rawrtc_sdp_type_to_str(enum rawrtc_sdp_type const type) {
+ size_t i;
+
+ for (i = 0; i < map_sdp_type_length; ++i) {
+ if (map_enum_sdp_type[i] == type) {
+ return map_str_sdp_type[i];
+ }
+ }
+
+ return "???";
+}
+
+/*
+ * Translate a str to an SDP type.
+ */
+enum rawrtc_code rawrtc_str_to_sdp_type(
+ enum rawrtc_sdp_type* const typep, // de-referenced
+ char const* const str) {
+ size_t i;
+
+ // Check arguments
+ if (!typep || !str) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < map_sdp_type_length; ++i) {
+ if (str_casecmp(map_str_sdp_type[i], str) == 0) {
+ *typep = map_enum_sdp_type[i];
+ return RAWRTC_CODE_SUCCESS;
+ }
+ }
+
+ return RAWRTC_CODE_NO_VALUE;
+}
+
+/*
+ * Print debug information for a peer connection description.
+ */
+int rawrtc_peer_connection_description_debug(
+ struct re_printf* const pf, struct rawrtc_peer_connection_description* const description) {
+ int err = 0;
+ struct le* le;
+
+ // Check arguments
+ if (!description) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, "----- Peer Connection Description <%p>\n", description);
+
+ // Print general fields
+ err |= re_hprintf(pf, " peer_connection=");
+ if (description->connection) {
+ err |= re_hprintf(pf, "%p\n", description->connection);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+ err |= re_hprintf(pf, " sdp_type=%s\n", rawrtc_sdp_type_to_str(description->type));
+ err |= re_hprintf(pf, " trickle_ice=%s\n", description->trickle_ice ? "yes" : "no");
+ err |= re_hprintf(pf, " bundled_mids=");
+ if (description->bundled_mids) {
+ err |= re_hprintf(pf, "\"%s\"\n", description->bundled_mids);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+ err |= re_hprintf(pf, " remote_media_line=");
+ if (description->remote_media_line) {
+ err |= re_hprintf(pf, "\"%s\"\n", description->remote_media_line);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+ err |= re_hprintf(pf, " media_line_index=%" PRIu8 "\n", description->media_line_index);
+ err |= re_hprintf(pf, " mid=");
+ if (description->mid) {
+ err |= re_hprintf(pf, "\"%s\"\n", description->mid);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+ err |= re_hprintf(pf, " sctp_sdp_05=%s\n", description->sctp_sdp_05 ? "yes" : "no");
+ err |=
+ re_hprintf(pf, " end_of_candidates=%s\n", description->end_of_candidates ? "yes" : "no");
+
+ // Print ICE parameters
+ if (description->ice_parameters) {
+ err |= re_hprintf(pf, "%H", rawrtc_ice_parameters_debug, description->ice_parameters);
+ } else {
+ err |= re_hprintf(pf, " ICE Parameters <n/a>\n");
+ }
+
+ // Print ICE candidates
+ le = list_head(&description->ice_candidates);
+ if (le) {
+ for (; le != NULL; le = le->next) {
+ struct rawrtc_peer_connection_ice_candidate* const candidate = le->data;
+ err |= re_hprintf(pf, "%H", rawrtc_peer_connection_ice_candidate_debug, candidate);
+ }
+ } else {
+ err |= re_hprintf(pf, " ICE Candidates <n/a>\n");
+ }
+
+ // Print DTLS parameters
+ if (description->dtls_parameters) {
+ err |= re_hprintf(pf, "%H", rawrtc_dtls_parameters_debug, description->dtls_parameters);
+ } else {
+ err |= re_hprintf(pf, " DTLS Parameters <n/a>\n");
+ }
+
+ // Print SCTP capabilities & port
+ if (description->sctp_capabilities) {
+ err |= re_hprintf(pf, "%H", rawrtc_sctp_capabilities_debug, description->sctp_capabilities);
+ } else {
+ err |= re_hprintf(pf, " SCTP Capabilities <n/a>\n");
+ }
+ err |= re_hprintf(pf, " sctp_port=%" PRIu16 "\n", description->sctp_port);
+
+ // Print SDP
+ err |= re_hprintf(pf, " sdp=\n%b", description->sdp->buf, description->sdp->end);
+
+ // Done
+ return err;
+}
diff --git a/src/peer_connection_ice_candidate/attributes.c b/src/peer_connection_ice_candidate/attributes.c
new file mode 100644
index 0000000..eed0125
--- /dev/null
+++ b/src/peer_connection_ice_candidate/attributes.c
@@ -0,0 +1,220 @@
+#include "candidate.h"
+#include <rawrtc/ice_candidate.h>
+#include <rawrtc/peer_connection_ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+/*
+ * Encode the ICE candidate into SDP.
+ * `*sdpp` will be set to a copy of the SDP attribute that must be
+ * unreferenced.
+ *
+ * Note: This is equivalent to the `candidate` attribute of the W3C
+ * WebRTC specification's `RTCIceCandidateInit`.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_get_sdp(
+ char** const sdpp, // de-referenced
+ struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ enum rawrtc_code error;
+ char* foundation = NULL;
+ uint16_t component_id = 1;
+ enum rawrtc_ice_protocol protocol;
+ char const* protocol_str;
+ uint32_t priority;
+ char* ip = NULL;
+ uint16_t port;
+ enum rawrtc_ice_candidate_type type;
+ char const* type_str;
+ struct mbuf* sdp = NULL;
+ char* related_address = NULL;
+ uint16_t related_port = 0;
+ enum rawrtc_ice_tcp_candidate_type tcp_type = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE;
+ char const* tcp_type_str;
+
+ // Check arguments
+ if (!sdpp || !candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get values for mandatory fields
+ error = rawrtc_ice_candidate_get_foundation(&foundation, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ // TODO: Get component ID from candidate/gatherer/transport
+ error = rawrtc_ice_candidate_get_protocol(&protocol, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_priority(&priority, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_ip(&ip, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_port(&port, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_type(&type, candidate->candidate);
+ if (error) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_related_address(&related_address, candidate->candidate);
+ if (error && error != RAWRTC_CODE_NO_VALUE) {
+ goto out;
+ }
+ error = rawrtc_ice_candidate_get_related_port(&related_port, candidate->candidate);
+ if (error && error != RAWRTC_CODE_NO_VALUE) {
+ goto out;
+ }
+ protocol_str = rawrtc_ice_protocol_to_str(protocol);
+ type_str = rawrtc_ice_candidate_type_to_str(type);
+
+ // Initialise SDP attribute buffer
+ sdp = mbuf_alloc(RAWRTC_PEER_CONNECTION_CANDIDATE_DEFAULT_SIZE);
+ if (!sdp) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+
+ // Encode candidate's mandatory fields
+ error = rawrtc_error_to_code(mbuf_printf(
+ sdp, "candidate:%s %" PRIu16 " %s %" PRIu32 " %s %" PRIu16 " typ %s", foundation,
+ component_id, protocol_str, priority, ip, port, type_str));
+ if (error) {
+ goto out;
+ }
+ if (related_address) {
+ error = rawrtc_error_to_code(mbuf_printf(sdp, " raddr %s", related_address));
+ if (error) {
+ goto out;
+ }
+ }
+ if (related_port > 0) {
+ error = rawrtc_error_to_code(mbuf_printf(sdp, " rport %" PRIu16, related_port));
+ if (error) {
+ goto out;
+ }
+ }
+
+ // Get value for 'tcptype' extension field and encode it (if available)
+ error = rawrtc_ice_candidate_get_tcp_type(&tcp_type, candidate->candidate);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ tcp_type_str = rawrtc_ice_tcp_candidate_type_to_str(tcp_type);
+ mbuf_printf(sdp, " tcptype %s", tcp_type_str);
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ break;
+ default:
+ goto out;
+ }
+
+ // Copy SDP attribute
+ error = rawrtc_sdprintf(sdpp, "%b", sdp->buf, sdp->end);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(related_address);
+ mem_deref(sdp);
+ mem_deref(ip);
+ mem_deref(foundation);
+ return error;
+}
+
+/*
+ * Get the media stream identification tag the ICE candidate is
+ * associated to.
+ * `*midp` will be set to a copy of the candidate's mid and must be
+ * unreferenced.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no 'mid' has been set.
+ * Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and `*midp* must
+ * be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_get_sdp_mid(
+ char** const midp, // de-referenced
+ struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ // Check arguments
+ if (!midp || !candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Copy mid (if any)
+ if (candidate->mid) {
+ return rawrtc_strdup(midp, candidate->mid);
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the media stream line index the ICE candidate is associated to.
+ * Return `RAWRTC_CODE_NO_VALUE` in case no media line index has been
+ * set.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_get_sdp_media_line_index(
+ uint8_t* const media_line_index, // de-referenced
+ struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ // Check arguments
+ if (!media_line_index || !candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set media line index (if any)
+ if (candidate->media_line_index >= 0 && candidate->media_line_index <= UINT8_MAX) {
+ *media_line_index = (uint8_t) candidate->media_line_index;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the username fragment the ICE candidate is associated to.
+ * `*username_fragmentp` will be set to a copy of the candidate's
+ * username fragment and must be unreferenced.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no username fragment has been
+ * set. Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and
+ * `*username_fragmentp* must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_get_username_fragment(
+ char** const username_fragmentp, // de-referenced
+ struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ // Check arguments
+ if (!username_fragmentp || !candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Copy username fragment (if any)
+ if (candidate->username_fragment) {
+ return rawrtc_strdup(username_fragmentp, candidate->username_fragment);
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the underlying ORTC ICE candidate from the ICE candidate.
+ * `*ortc_candidatep` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_get_ortc_candidate(
+ struct rawrtc_ice_candidate** const ortc_candidatep, // de-referenced
+ struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ // Check arguments
+ if (!ortc_candidatep || !candidate) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Reference ORTC ICE candidate
+ *ortc_candidatep = mem_ref(candidate->candidate);
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/peer_connection_ice_candidate/candidate.c b/src/peer_connection_ice_candidate/candidate.c
new file mode 100644
index 0000000..4f2c7fc
--- /dev/null
+++ b/src/peer_connection_ice_candidate/candidate.c
@@ -0,0 +1,252 @@
+#include "candidate.h"
+#include "../ice_candidate/candidate.h"
+#include <rawrtc/ice_candidate.h>
+#include <rawrtc/peer_connection_ice_candidate.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+static char const sdp_ice_candidate_regex[] =
+ "candidate:[^ ]+ [0-9]+ [^ ]+ [0-9]+ [^ ]+ [0-9]+ typ [^ ]+[^]*";
+static char const sdp_ice_candidate_related_address_regex[] = "[^]* raddr [^ ]+";
+static char const sdp_ice_candidate_related_port_regex[] = "[^]* rport [0-9]+";
+static char const sdp_ice_candidate_tcp_type_regex[] = "[^]* tcptype [^ ]+";
+
+/*
+ * Destructor for an existing peer connection.
+ */
+static void rawrtc_peer_connection_ice_candidate_destroy(void* arg) {
+ struct rawrtc_peer_connection_ice_candidate* const candidate = arg;
+
+ // Un-reference
+ mem_deref(candidate->username_fragment);
+ mem_deref(candidate->mid);
+ mem_deref(candidate->candidate);
+}
+
+/*
+ * Create a new ICE candidate from an existing (ORTC) ICE candidate.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_from_ortc_candidate(
+ struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced
+ struct rawrtc_ice_candidate* const ortc_candidate, // nullable
+ char* const mid, // nullable, referenced
+ uint8_t const* const media_line_index, // nullable, copied
+ char* const username_fragment // nullable, referenced
+) {
+ struct rawrtc_peer_connection_ice_candidate* candidate;
+
+ // Ensure either 'mid' or the media line index is present
+ if (!mid && !media_line_index) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ candidate = mem_zalloc(sizeof(*candidate), rawrtc_peer_connection_ice_candidate_destroy);
+ if (!candidate) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ candidate->candidate = mem_ref(ortc_candidate);
+ candidate->mid = mem_ref(mid);
+ candidate->media_line_index = (int16_t)(media_line_index ? *media_line_index : -1);
+ candidate->username_fragment = mem_ref(username_fragment);
+
+ // Set pointer & done
+ *candidatep = candidate;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Create a new ICE candidate from SDP (pl variant).
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_create_internal(
+ struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced
+ struct pl* const sdp,
+ char* const mid, // nullable, referenced
+ uint8_t const* const media_line_index, // nullable, copied
+ char* const username_fragment // nullable, referenced
+) {
+ enum rawrtc_code error;
+ struct pl optional;
+ uint32_t value_u32;
+
+ // Mandatory fields
+ struct pl foundation_pl;
+ struct pl component_id_pl;
+ struct pl protocol_pl;
+ struct pl priority_pl;
+ struct pl ip_pl;
+ struct pl port_pl;
+ struct pl type_pl;
+ uint32_t priority;
+ enum rawrtc_ice_protocol protocol;
+ uint16_t port;
+ enum rawrtc_ice_candidate_type type;
+
+ // Optional fields
+ struct pl related_address_pl = PL_INIT;
+ struct pl related_port_pl = PL_INIT;
+ struct pl tcp_type_pl = PL_INIT;
+ uint16_t related_port = 0;
+ enum rawrtc_ice_tcp_candidate_type tcp_type = RAWRTC_ICE_TCP_CANDIDATE_TYPE_ACTIVE;
+
+ // (ORTC) ICE candidate
+ struct rawrtc_ice_candidate* ortc_candidate;
+
+ // ICE candidate
+ struct rawrtc_peer_connection_ice_candidate* candidate;
+
+ // Check arguments
+ if (!candidatep || !sdp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure either 'mid' or the media line index is present
+ if (!mid && !media_line_index) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ if (pl_isset(sdp)) {
+ // Get mandatory ICE candidate fields
+ if (re_regex(
+ sdp->p, sdp->l, sdp_ice_candidate_regex, &foundation_pl, &component_id_pl,
+ &protocol_pl, &priority_pl, &ip_pl, &port_pl, &type_pl, &optional)) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get optional ICE candidate fields
+ re_regex(
+ optional.p, optional.l, sdp_ice_candidate_related_address_regex, NULL,
+ &related_address_pl);
+ re_regex(
+ optional.p, optional.l, sdp_ice_candidate_related_port_regex, NULL, &related_port_pl);
+ re_regex(optional.p, optional.l, sdp_ice_candidate_tcp_type_regex, NULL, &tcp_type_pl);
+
+ // Component ID
+ // TODO: Handle
+ (void) component_id_pl;
+
+ // Protocol
+ error = rawrtc_pl_to_ice_protocol(&protocol, &protocol_pl);
+ if (error) {
+ return error;
+ }
+
+ // Priority
+ priority = pl_u32(&priority_pl);
+
+ // Port
+ value_u32 = pl_u32(&port_pl);
+ if (value_u32 > UINT16_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+ port = (uint16_t) value_u32;
+
+ // Type
+ error = rawrtc_pl_to_ice_candidate_type(&type, &type_pl);
+ if (error) {
+ return error;
+ }
+
+ // Related port (if any)
+ if (pl_isset(&related_port_pl)) {
+ value_u32 = pl_u32(&related_port_pl);
+ if (value_u32 > UINT16_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+ related_port = (uint16_t) value_u32;
+ }
+
+ // TCP type (if any)
+ if (pl_isset(&tcp_type_pl)) {
+ error = rawrtc_pl_to_ice_tcp_candidate_type(&tcp_type, &tcp_type_pl);
+ if (error) {
+ return error;
+ }
+ }
+
+ // Create (ORTC) ICE candidate
+ error = rawrtc_ice_candidate_create_internal(
+ &ortc_candidate, &foundation_pl, priority, &ip_pl, protocol, port, type, tcp_type,
+ &related_address_pl, related_port);
+ if (error) {
+ return error;
+ }
+ } else {
+ ortc_candidate = NULL;
+ }
+
+ // Create ICE candidate
+ error = rawrtc_peer_connection_ice_candidate_from_ortc_candidate(
+ &candidate, ortc_candidate, mid, media_line_index, username_fragment);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(ortc_candidate);
+ if (!error) {
+ // Set pointer & done
+ *candidatep = candidate;
+ }
+ return error;
+}
+
+/*
+ * Create a new ICE candidate from SDP.
+ * `*candidatesp` must be unreferenced.
+ *
+ * Note: This is equivalent to creating an `RTCIceCandidate` from an
+ * `RTCIceCandidateInit` instance in the W3C WebRTC
+ * specification.
+ */
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_create(
+ struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced
+ char* const sdp,
+ char* const mid, // nullable, copied
+ uint8_t const* const media_line_index, // nullable, copied
+ char* const username_fragment // nullable, copied
+) {
+ struct pl sdp_pl;
+ enum rawrtc_code error;
+ char* mid_copy = NULL;
+ char* username_fragment_copy = NULL;
+
+ // Check arguments (not checked in the internal function)
+ if (!sdp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Convert SDP str to pl
+ pl_set_str(&sdp_pl, sdp);
+
+ // Copy arguments that will be referenced
+ if (mid) {
+ error = rawrtc_strdup(&mid_copy, mid);
+ if (error) {
+ goto out;
+ }
+ }
+ if (username_fragment) {
+ error = rawrtc_strdup(&username_fragment_copy, username_fragment);
+ if (error) {
+ goto out;
+ }
+ }
+
+ // Create ICE candidate
+ error = rawrtc_peer_connection_ice_candidate_create_internal(
+ candidatep, &sdp_pl, mid_copy, media_line_index, username_fragment_copy);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(username_fragment_copy);
+ mem_deref(mid_copy);
+ return error;
+}
diff --git a/src/peer_connection_ice_candidate/candidate.h b/src/peer_connection_ice_candidate/candidate.h
new file mode 100644
index 0000000..6fbd828
--- /dev/null
+++ b/src/peer_connection_ice_candidate/candidate.h
@@ -0,0 +1,32 @@
+#pragma once
+#include <rawrtc.h>
+
+enum {
+ RAWRTC_PEER_CONNECTION_CANDIDATE_DEFAULT_SIZE = 256,
+};
+
+struct rawrtc_peer_connection_ice_candidate {
+ struct le le;
+ struct rawrtc_ice_candidate* candidate;
+ char* mid;
+ int16_t media_line_index;
+ char* username_fragment;
+};
+
+int rawrtc_peer_connection_ice_candidate_debug(
+ struct re_printf* const pf, struct rawrtc_peer_connection_ice_candidate* const candidate);
+
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_from_ortc_candidate(
+ struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced
+ struct rawrtc_ice_candidate* const ortc_candidate, // nullable
+ char* const mid, // nullable, referenced
+ uint8_t const* const media_line_index, // nullable, copied
+ char* const username_fragment // nullable, referenced
+);
+
+enum rawrtc_code rawrtc_peer_connection_ice_candidate_create_internal(
+ struct rawrtc_peer_connection_ice_candidate** const candidatep, // de-referenced
+ struct pl* const sdp,
+ char* const mid, // nullable
+ uint8_t const* const media_line_index, // nullable
+ char* const username_fragment);
diff --git a/src/peer_connection_ice_candidate/meson.build b/src/peer_connection_ice_candidate/meson.build
new file mode 100644
index 0000000..6ff3140
--- /dev/null
+++ b/src/peer_connection_ice_candidate/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'candidate.c',
+ 'utils.c',
+])
diff --git a/src/peer_connection_ice_candidate/utils.c b/src/peer_connection_ice_candidate/utils.c
new file mode 100644
index 0000000..49cd11e
--- /dev/null
+++ b/src/peer_connection_ice_candidate/utils.c
@@ -0,0 +1,47 @@
+#include "candidate.h"
+#include "../ice_candidate/candidate.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Print debug information for an ICE candidate.
+ */
+int rawrtc_peer_connection_ice_candidate_debug(
+ struct re_printf* const pf, struct rawrtc_peer_connection_ice_candidate* const candidate) {
+ int err = 0;
+
+ // Check arguments
+ if (!candidate) {
+ return 0;
+ }
+
+ // ORTC ICE candidate
+ err |= re_hprintf(pf, "%H", rawrtc_ice_candidate_debug, candidate->candidate);
+
+ // Media line identification tag
+ err |= re_hprintf(pf, " mid=");
+ if (candidate->mid) {
+ err |= re_hprintf(pf, "\"%s\"\n", candidate->mid);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+
+ // Media line index
+ err |= re_hprintf(pf, " media_line_index=");
+ if (candidate->media_line_index >= 0 && candidate->media_line_index <= UINT8_MAX) {
+ err |= re_hprintf(pf, "%" PRId16 "\n", candidate->media_line_index);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+
+ // Username fragment
+ err |= re_hprintf(pf, " username_fragment=");
+ if (candidate->username_fragment) {
+ err |= re_hprintf(pf, "\"%s\"\n", candidate->username_fragment);
+ } else {
+ err |= re_hprintf(pf, "n/a\n");
+ }
+
+ // Done
+ return err;
+}
diff --git a/src/peer_connection_state/meson.build b/src/peer_connection_state/meson.build
new file mode 100644
index 0000000..55395eb
--- /dev/null
+++ b/src/peer_connection_state/meson.build
@@ -0,0 +1 @@
+sources += files('state.c')
diff --git a/src/peer_connection_state/state.c b/src/peer_connection_state/state.c
new file mode 100644
index 0000000..5aa274c
--- /dev/null
+++ b/src/peer_connection_state/state.c
@@ -0,0 +1,45 @@
+#include <rawrtc/peer_connection_state.h>
+
+/*
+ * Get the corresponding name for a signaling state.
+ */
+char const* rawrtc_signaling_state_to_name(enum rawrtc_signaling_state const state) {
+ switch (state) {
+ case RAWRTC_SIGNALING_STATE_STABLE:
+ return "stable";
+ case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER:
+ return "have-local-offer";
+ case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER:
+ return "have-remote-offer";
+ case RAWRTC_SIGNALING_STATE_HAVE_LOCAL_PROVISIONAL_ANSWER:
+ return "have-local-pranswer";
+ case RAWRTC_SIGNALING_STATE_HAVE_REMOTE_PROVISIONAL_ANSWER:
+ return "have-remote-pranswer";
+ case RAWRTC_SIGNALING_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Get the corresponding name for a peer connection state.
+ */
+char const* rawrtc_peer_connection_state_to_name(enum rawrtc_peer_connection_state const state) {
+ switch (state) {
+ case RAWRTC_PEER_CONNECTION_STATE_NEW:
+ return "new";
+ case RAWRTC_PEER_CONNECTION_STATE_CONNECTING:
+ return "connecting";
+ case RAWRTC_PEER_CONNECTION_STATE_CONNECTED:
+ return "connected";
+ case RAWRTC_PEER_CONNECTION_STATE_DISCONNECTED:
+ return "disconnected";
+ case RAWRTC_PEER_CONNECTION_STATE_CLOSED:
+ return "closed";
+ case RAWRTC_PEER_CONNECTION_STATE_FAILED:
+ return "failed";
+ default:
+ return "???";
+ }
+}
diff --git a/src/sctp_common/common.c b/src/sctp_common/common.c
new file mode 100644
index 0000000..b40bdac
--- /dev/null
+++ b/src/sctp_common/common.c
@@ -0,0 +1,112 @@
+#include "common.h"
+#include "../dtls_transport/transport.h"
+#include <rawrtc/config.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <rawrtcdc/external.h>
+#include <re.h>
+
+// Note: Although shared with the redirect transport, this name is accurate enough for both.
+#define DEBUG_MODULE "sctp-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * External DTLS role getter.
+ * Warning: `rolep` and `arg` will not be validated.
+ */
+enum rawrtc_code rawrtc_sctp_common_dtls_role_getter(
+ enum rawrtc_external_dtls_role* const rolep, // de-referenced, not checked
+ void* const arg // not checked
+) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+ return rawrtc_dtls_transport_get_external_role(rolep, dtls_transport);
+}
+
+/*
+ * Get the external DTLS transport state.
+ * Warning: `statep` and `arg` will not be validated.
+ */
+enum rawrtc_code rawrtc_sctp_common_dtls_transport_state_getter(
+ enum rawrtc_external_dtls_transport_state* const statep, // de-referenced, not checked
+ void* const arg // not checked
+) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+ return rawrtc_dtls_transport_get_external_state(statep, dtls_transport);
+}
+
+/*
+ * Outbound data handler of the SCTP transport.
+ * `buffer` will be a fake `mbuf` structure.
+ *
+ * Warning: `buffer` and `arg` will not be validated.
+ */
+enum rawrtc_code rawrtc_sctp_common_sctp_transport_outbound_handler(
+ struct mbuf* const buffer, // not checked
+ uint8_t const tos,
+ uint8_t const set_df,
+ void* const arg // not checked
+) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+ enum rawrtc_code error;
+
+ // TODO: Handle
+ (void) tos;
+ (void) set_df;
+
+ // Note: We only need to copy the buffer if we add it to the outgoing queue
+ if (dtls_transport->state == RAWRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+ // Send
+ error = rawrtc_dtls_transport_send(dtls_transport, buffer);
+ } else {
+ int err;
+ struct mbuf* copied_buffer;
+
+ // Get length
+ size_t const length = mbuf_get_left(buffer);
+
+ // Allocate
+ copied_buffer = mbuf_alloc(length);
+ if (!copied_buffer) {
+ DEBUG_WARNING("Could not create buffer for outgoing packet, no memory\n");
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Copy and set position
+ err = mbuf_write_mem(copied_buffer, mbuf_buf(buffer), length);
+ if (err) {
+ DEBUG_WARNING("Could not write to buffer, reason: %m\n", err);
+ mem_deref(copied_buffer);
+ return rawrtc_error_to_code(err);
+ }
+ mbuf_set_pos(copied_buffer, 0);
+
+ // Send (well, actually buffer...)
+ error = rawrtc_dtls_transport_send(dtls_transport, copied_buffer);
+ mem_deref(copied_buffer);
+ }
+
+ // Handle error & done
+ if (error) {
+ DEBUG_WARNING("Could not send packet, reason: %s\n", rawrtc_code_to_str(error));
+ }
+ return error;
+}
+
+/*
+ * Detach the SCTP transport from the DTLS transport and therefore
+ * don't feed any DTLS application data to the SCTP transport.
+ * Warning: `arg` will not be validated.
+ */
+void rawrtc_sctp_common_sctp_transport_detach_handler(void* const arg // not checked
+) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+
+ // Detach from DTLS transport
+ enum rawrtc_code error = rawrtc_dtls_transport_clear_data_transport(dtls_transport);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to detach from DTLS transport, reason: %s\n", rawrtc_code_to_str(error));
+ }
+}
diff --git a/src/sctp_common/common.h b/src/sctp_common/common.h
new file mode 100644
index 0000000..cf441f7
--- /dev/null
+++ b/src/sctp_common/common.h
@@ -0,0 +1,24 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <rawrtcdc/external.h>
+#include <re.h>
+
+enum rawrtc_code rawrtc_sctp_common_dtls_role_getter(
+ enum rawrtc_external_dtls_role* const rolep, // de-referenced, not checked
+ void* const arg // not checked
+);
+
+enum rawrtc_code rawrtc_sctp_common_dtls_transport_state_getter(
+ enum rawrtc_external_dtls_transport_state* const statep, // de-referenced, not checked
+ void* const arg // not checked
+);
+
+enum rawrtc_code rawrtc_sctp_common_sctp_transport_outbound_handler(
+ struct mbuf* const buffer, // not checked
+ uint8_t const tos,
+ uint8_t const set_df,
+ void* const arg // not checked
+);
+
+void rawrtc_sctp_common_sctp_transport_detach_handler(void* const arg // not checked
+);
diff --git a/src/sctp_common/meson.build b/src/sctp_common/meson.build
new file mode 100644
index 0000000..5ed4f3e
--- /dev/null
+++ b/src/sctp_common/meson.build
@@ -0,0 +1 @@
+sources += files('common.c')
diff --git a/src/sctp_redirect_transport/meson.build b/src/sctp_redirect_transport/meson.build
new file mode 100644
index 0000000..d516026
--- /dev/null
+++ b/src/sctp_redirect_transport/meson.build
@@ -0,0 +1 @@
+sources += files('transport.c')
diff --git a/src/sctp_redirect_transport/transport.c b/src/sctp_redirect_transport/transport.c
new file mode 100644
index 0000000..d0d022e
--- /dev/null
+++ b/src/sctp_redirect_transport/transport.c
@@ -0,0 +1,111 @@
+#include "../dtls_transport/transport.h"
+#include "../sctp_common/common.h"
+#include <rawrtc/config.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/sctp_redirect_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <rawrtcdc/sctp_redirect_transport.h>
+#include <re.h>
+
+#define DEBUG_MODULE "sctp-redirect-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Pass DTLS application data to the SCTP redirect transport as inbound
+ * data.
+ */
+static void sctp_redirect_transport_inbound_handler(
+ struct mbuf* const buffer, // not checked
+ void* const arg // not checked
+) {
+ struct rawrtc_sctp_redirect_transport* const transport = arg;
+
+ // Feed data
+ enum rawrtc_code const error = rawrtc_sctp_redirect_transport_feed_inbound(transport, buffer);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to feed data into the SCTP redirect transport, reason: %s\n",
+ rawrtc_code_to_str(error));
+ }
+}
+
+/*
+ * Destructor for an existing SCTP redirect transport.
+ */
+static void rawrtc_sctp_redirect_transport_destroy(void* const arg) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+
+ // Un-reference
+ mem_deref(dtls_transport);
+}
+
+/*
+ * Create an SCTP redirect transport.
+ * `*transportp` must be unreferenced.
+ *
+ * `port` defaults to `5000` if set to `0`.
+ * `redirect_ip` is the target IP SCTP packets will be redirected to
+ * and must be a IPv4 address.
+ * `redirect_port` is the target SCTP port packets will be redirected
+ * to.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_create(
+ struct rawrtc_sctp_redirect_transport** const transportp, // de-referenced
+ struct rawrtc_dtls_transport* const dtls_transport, // referenced
+ uint16_t const port, // zeroable
+ char* const redirect_ip, // copied
+ uint16_t const redirect_port,
+ rawrtc_sctp_redirect_transport_state_change_handler const state_change_handler, // nullable
+ void* const arg // nullable
+) {
+ enum rawrtc_code error;
+ bool have_data_transport;
+ struct rawrtc_sctp_redirect_transport* transport = NULL;
+
+ // Create SCTP transport context
+ struct rawrtc_sctp_transport_context context = {
+ .role_getter = NULL,
+ .state_getter = rawrtc_sctp_common_dtls_transport_state_getter,
+ .outbound_handler = rawrtc_sctp_common_sctp_transport_outbound_handler,
+ .detach_handler = rawrtc_sctp_common_sctp_transport_detach_handler,
+ .destroyed_handler = rawrtc_sctp_redirect_transport_destroy,
+ .arg = mem_ref(dtls_transport),
+ };
+
+ // Check if a data transport is already registered
+ error = rawrtc_dtls_transport_have_data_transport(&have_data_transport, dtls_transport);
+ if (error) {
+ goto out;
+ }
+ if (have_data_transport) {
+ error = RAWRTC_CODE_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ // Create SCTP redirect transport
+ error = rawrtc_sctp_redirect_transport_create_from_external(
+ &transport, &context, port, redirect_ip, redirect_port, state_change_handler, arg);
+ if (error) {
+ goto out;
+ }
+
+ // Attach to DTLS transport
+ DEBUG_PRINTF("Attaching as data transport\n");
+ error = rawrtc_dtls_transport_set_data_transport(
+ dtls_transport, sctp_redirect_transport_inbound_handler, transport);
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(transport);
+ mem_deref(dtls_transport);
+ } else {
+ // Set pointer
+ *transportp = transport;
+ }
+ return error;
+}
diff --git a/src/sctp_transport/meson.build b/src/sctp_transport/meson.build
new file mode 100644
index 0000000..d516026
--- /dev/null
+++ b/src/sctp_transport/meson.build
@@ -0,0 +1 @@
+sources += files('transport.c')
diff --git a/src/sctp_transport/transport.c b/src/sctp_transport/transport.c
new file mode 100644
index 0000000..c329d63
--- /dev/null
+++ b/src/sctp_transport/transport.c
@@ -0,0 +1,108 @@
+#include "../dtls_transport/transport.h"
+#include "../sctp_common/common.h"
+#include <rawrtc/config.h>
+#include <rawrtc/dtls_transport.h>
+#include <rawrtc/sctp_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/external.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+
+#define DEBUG_MODULE "sctp-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Pass DTLS application data to the SCTP transport as inbound data.
+ */
+static void sctp_transport_inbound_handler(
+ struct mbuf* const buffer, // not checked
+ void* const arg // not checked
+) {
+ struct rawrtc_sctp_transport* const transport = arg;
+
+ // Feed data
+ // TODO: What about ECN bits?
+ enum rawrtc_code const error = rawrtc_sctp_transport_feed_inbound(transport, buffer, 0x00);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to feed data into the SCTP transport, reason: %s\n", rawrtc_code_to_str(error));
+ }
+}
+
+/*
+ * Destructor for an existing SCTP transport.
+ */
+static void rawrtc_sctp_transport_destroy(void* const arg) {
+ struct rawrtc_dtls_transport* const dtls_transport = arg;
+
+ // Un-reference
+ mem_deref(dtls_transport);
+}
+
+/*
+ * Create an SCTP transport.
+ * `*transportp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_sctp_transport_create(
+ struct rawrtc_sctp_transport** const transportp, // de-referenced
+ struct rawrtc_dtls_transport* const dtls_transport, // referenced
+ uint16_t const port, // zeroable
+ rawrtc_data_channel_handler const data_channel_handler, // nullable
+ rawrtc_sctp_transport_state_change_handler const state_change_handler, // nullable
+ void* const arg // nullable
+) {
+ enum rawrtc_code error;
+ bool have_data_transport;
+ struct rawrtc_sctp_transport* transport = NULL;
+
+ // Create SCTP transport context
+ struct rawrtc_sctp_transport_context context = {
+ .role_getter = rawrtc_sctp_common_dtls_role_getter,
+ .state_getter = rawrtc_sctp_common_dtls_transport_state_getter,
+ .outbound_handler = rawrtc_sctp_common_sctp_transport_outbound_handler,
+ .detach_handler = rawrtc_sctp_common_sctp_transport_detach_handler,
+ .destroyed_handler = rawrtc_sctp_transport_destroy,
+ .trace_packets = false, // TODO: Make this configurable
+ .arg = mem_ref(dtls_transport),
+ };
+
+ // Check if a data transport is already registered
+ error = rawrtc_dtls_transport_have_data_transport(&have_data_transport, dtls_transport);
+ if (error) {
+ goto out;
+ }
+ if (have_data_transport) {
+ error = RAWRTC_CODE_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ // Create SCTP transport
+ error = rawrtc_sctp_transport_create_from_external(
+ &transport, &context, port, data_channel_handler, state_change_handler, arg);
+ if (error) {
+ goto out;
+ }
+
+ // TODO: Set MTU (1200|1280 (IPv4|IPv6) - UDP - DTLS (cipher suite dependent) - SCTP (12)
+
+ // Attach to DTLS transport
+ DEBUG_PRINTF("Attaching as data transport\n");
+ error = rawrtc_dtls_transport_set_data_transport(
+ dtls_transport, sctp_transport_inbound_handler, transport);
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(transport);
+ mem_deref(dtls_transport);
+ } else {
+ // Set pointer
+ *transportp = transport;
+ }
+ return error;
+}
diff --git a/src/utils/meson.build b/src/utils/meson.build
new file mode 100644
index 0000000..d82c551
--- /dev/null
+++ b/src/utils/meson.build
@@ -0,0 +1 @@
+sources += files('utils.c')
diff --git a/src/utils/utils.c b/src/utils/utils.c
new file mode 100644
index 0000000..26af2a9
--- /dev/null
+++ b/src/utils/utils.c
@@ -0,0 +1,163 @@
+#include "utils.h"
+#include <rawrtc/utils.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <stdarg.h> // va_*
+#include <stdio.h> // sprintf
+#include <string.h> // strlen
+
+/*
+ * Convert binary to hex string where each value is separated by a
+ * colon.
+ */
+enum rawrtc_code rawrtc_bin_to_colon_hex(
+ char** const destinationp, // de-referenced
+ uint8_t* const source,
+ size_t const length) {
+ char* hex_str;
+ char* hex_ptr;
+ size_t i;
+ int ret;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!destinationp || !source) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate hex string
+ hex_str = mem_zalloc(length > 0 ? (length * 3) : 1, NULL);
+ if (!hex_str) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Bin to hex
+ hex_ptr = hex_str;
+ for (i = 0; i < length; ++i) {
+ if (i > 0) {
+ *hex_ptr = ':';
+ ++hex_ptr;
+ }
+ ret = sprintf(hex_ptr, "%02X", source[i]);
+ if (ret != 2) {
+ error = RAWRTC_CODE_UNKNOWN_ERROR;
+ goto out;
+ } else {
+ hex_ptr += ret;
+ }
+ }
+
+out:
+ if (error) {
+ mem_deref(hex_str);
+ } else {
+ // Set pointer
+ *destinationp = hex_str;
+ }
+ return error;
+}
+
+/*
+ * Convert hex string with colon-separated hex values to binary.
+ */
+enum rawrtc_code rawrtc_colon_hex_to_bin(
+ size_t* const bytes_written, // de-referenced
+ uint8_t* const buffer, // written into
+ size_t const buffer_size,
+ char* source) {
+ size_t hex_length;
+ size_t bin_length;
+ size_t i;
+
+ // Check arguments
+ if (!bytes_written || !buffer || !source) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Validate length
+ hex_length = strlen(source);
+ if (hex_length > 0 && hex_length % 3 != 2) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Determine size
+ bin_length = hex_length > 0 ? (size_t)((hex_length + 1) / 3) : 0;
+ if (bin_length > buffer_size) {
+ return RAWRTC_CODE_INSUFFICIENT_SPACE;
+ }
+
+ // Hex to bin
+ for (i = 0; i < bin_length; ++i) {
+ if (i > 0) {
+ // Skip colon
+ ++source;
+ }
+ buffer[i] = ch_hex(*source) << 4;
+ ++source;
+ buffer[i] += ch_hex(*source);
+ ++source;
+ }
+
+ // Done
+ *bytes_written = bin_length;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Destructor for an existing array container that did reference each
+ * item.
+ */
+static void rawrtc_array_container_destroy(void* arg) {
+ struct rawrtc_array_container* const container = arg;
+ size_t i;
+
+ // Un-reference each item
+ for (i = 0; i < container->n_items; ++i) {
+ mem_deref(container->items[i]);
+ }
+}
+
+/*
+ * Convert a list to a dynamically allocated array container.
+ *
+ * If `reference` is set to `true`, each item in the list will be
+ * referenced and a destructor will be added that unreferences each
+ * item when unreferencing the array.
+ */
+enum rawrtc_code rawrtc_list_to_array(
+ struct rawrtc_array_container** containerp, // de-referenced
+ struct list const* const list,
+ bool reference) {
+ size_t n;
+ struct rawrtc_array_container* container;
+ struct le* le;
+ size_t i;
+
+ // Check arguments
+ if (!containerp || !list) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get list length
+ n = list_count(list);
+
+ // Allocate array & set length immediately
+ container = mem_zalloc(
+ sizeof(*container) + sizeof(void*) * n, reference ? rawrtc_array_container_destroy : NULL);
+ if (!container) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+ container->n_items = n;
+
+ // Copy pointer to each item
+ for (le = list_head(list), i = 0; le != NULL; le = le->next, ++i) {
+ if (reference) {
+ mem_ref(le->data);
+ }
+ container->items[i] = le->data;
+ }
+
+ // Set pointer & done
+ *containerp = container;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/utils/utils.h b/src/utils/utils.h
new file mode 100644
index 0000000..9557277
--- /dev/null
+++ b/src/utils/utils.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <rawrtc/utils.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+enum rawrtc_code rawrtc_bin_to_colon_hex(
+ char** const destinationp, // de-referenced
+ uint8_t* const source,
+ size_t const length);
+
+enum rawrtc_code rawrtc_colon_hex_to_bin(
+ size_t* const bytes_written, // de-referenced
+ uint8_t* const buffer, // written into
+ size_t const buffer_size,
+ char* source);
+
+enum rawrtc_code rawrtc_list_to_array(
+ struct rawrtc_array_container** containerp, // de-referenced
+ struct list const* const list,
+ bool reference);