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/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',
+])