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/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;
+    }
+}