Squashed 'third_party/rawrtc/rawrtc-data-channel/' content from commit 7b1b8d57c
Change-Id: I84850720e2b51961981d55f67238f4d282314fff
git-subtree-dir: third_party/rawrtc/rawrtc-data-channel
git-subtree-split: 7b1b8d57c6d07da18cc0de8bbca8cc5e8bd06eae
diff --git a/src/crc32c/crc32c.c b/src/crc32c/crc32c.c
new file mode 100644
index 0000000..d22550e
--- /dev/null
+++ b/src/crc32c/crc32c.c
@@ -0,0 +1,36 @@
+#include "crc32c.h"
+#include "software.h"
+#include "../main/main.h"
+#include <rawrtcdc/config.h>
+#include <re.h>
+
+#if RAWRTCDC_ENABLE_SSE42_CRC32C
+# include "sse42.h"
+#endif
+
+#define DEBUG_MODULE "crc32c"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Initialise CRC32-C.
+ */
+void rawrtc_crc32c_init(void) {
+#if RAWRTCDC_ENABLE_SSE42_CRC32C
+ if (rawrtc_crc32c_sse42_supported()) {
+ rawrtc_crc32c_init_sse42();
+ rawrtcdc_global.crc32c_handler = rawrtc_crc32c_sse42;
+ DEBUG_PRINTF("Initialised CRC32-C (sse42)\n");
+ return;
+ }
+#endif
+ rawrtcdc_global.crc32c_handler = rawrtc_crc32c_software;
+ DEBUG_PRINTF("Initialised CRC32-C (software)\n");
+}
+
+/*
+ * Compute CRC-32C using whatever method has been established.
+ */
+uint32_t rawrtc_crc32c(void const* buffer, size_t length) {
+ return rawrtcdc_global.crc32c_handler(buffer, length);
+}
diff --git a/src/crc32c/crc32c.h b/src/crc32c/crc32c.h
new file mode 100644
index 0000000..591817c
--- /dev/null
+++ b/src/crc32c/crc32c.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <re.h>
+
+void rawrtc_crc32c_init(void);
+
+uint32_t rawrtc_crc32c(void const* buffer, size_t length);
diff --git a/src/crc32c/meson.build b/src/crc32c/meson.build
new file mode 100644
index 0000000..190919a
--- /dev/null
+++ b/src/crc32c/meson.build
@@ -0,0 +1,4 @@
+sources += files('crc32c.c')
+if have_cpuid
+ sources += files('sse42.c')
+endif
diff --git a/src/crc32c/software.h b/src/crc32c/software.h
new file mode 100644
index 0000000..090d00a
--- /dev/null
+++ b/src/crc32c/software.h
@@ -0,0 +1,7 @@
+#pragma once
+#include <re.h>
+#include <usrsctp.h>
+
+static inline uint32_t rawrtc_crc32c_software(void const* buffer, size_t length) {
+ return usrsctp_crc32c((void*) buffer, length);
+}
diff --git a/src/crc32c/sse42.c b/src/crc32c/sse42.c
new file mode 100644
index 0000000..436479b
--- /dev/null
+++ b/src/crc32c/sse42.c
@@ -0,0 +1,260 @@
+/*
+ * crc32c.c -- compute CRC-32C using the Intel crc32 instruction
+ * Copyright (C) 2013 Mark Adler
+ * Version 1.1 1 Aug 2013 Mark Adler
+ *
+ * Use hardware CRC instruction on Intel SSE 4.2 processors. This computes a
+ * CRC-32C, *not* the CRC-32 used by Ethernet and zip, gzip, etc. A software
+ * version is provided as a fall-back, as well as for speed comparisons.
+ *
+ * (Modified for the use in RAWRTC. Software fallback has been stripped.)
+ *
+ *
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the author be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ * Mark Adler
+ * madler@alumni.caltech.edu
+ */
+#include "sse42.h"
+#include <re.h>
+
+/* CRC-32C (iSCSI) polynomial in reversed bit order. */
+#define POLY 0x82f63b78
+
+/* Multiply a matrix times a vector over the Galois field of two elements,
+ GF(2). Each element is a bit in an unsigned integer. mat must have at
+ least as many entries as the power of two for most significant one bit in
+ vec. */
+static inline uint32_t gf2_matrix_times(uint32_t* mat, uint32_t vec) {
+ uint32_t sum;
+
+ sum = 0;
+ while (vec) {
+ if (vec & 1) {
+ sum ^= *mat;
+ }
+ vec >>= 1;
+ mat++;
+ }
+ return sum;
+}
+
+/* Multiply a matrix by itself over GF(2). Both mat and square must have 32
+ rows. */
+static inline void gf2_matrix_square(uint32_t* square, uint32_t* mat) {
+ int n;
+
+ for (n = 0; n < 32; n++) {
+ square[n] = gf2_matrix_times(mat, mat[n]);
+ }
+}
+
+/* Construct an operator to apply len zeros to a crc. len must be a power of
+ two. If len is not a power of two, then the result is the same as for the
+ largest power of two less than len. The result for len == 0 is the same as
+ for len == 1. A version of this routine could be easily written for any
+ len, but that is not needed for this application. */
+static void crc32c_zeros_op(uint32_t* even, size_t len) {
+ int n;
+ uint32_t row;
+ uint32_t odd[32]; /* odd-power-of-two zeros operator */
+
+ /* put operator for one zero bit in odd */
+ odd[0] = POLY; /* CRC-32C polynomial */
+ row = 1;
+ for (n = 1; n < 32; n++) {
+ odd[n] = row;
+ row <<= 1;
+ }
+
+ /* put operator for two zero bits in even */
+ gf2_matrix_square(even, odd);
+
+ /* put operator for four zero bits in odd */
+ gf2_matrix_square(odd, even);
+
+ /* first square will put the operator for one zero byte (eight zero bits),
+ in even -- next square puts operator for two zero bytes in odd, and so
+ on, until len has been rotated down to zero */
+ do {
+ gf2_matrix_square(even, odd);
+ len >>= 1;
+ if (len == 0) {
+ return;
+ }
+ gf2_matrix_square(odd, even);
+ len >>= 1;
+ } while (len);
+
+ /* answer ended up in odd -- copy to even */
+ for (n = 0; n < 32; n++) {
+ even[n] = odd[n];
+ }
+}
+
+/* Take a length and build four lookup tables for applying the zeros operator
+ for that length, byte-by-byte on the operand. */
+static void crc32c_zeros(uint32_t zeros[][256], size_t len) {
+ uint32_t n;
+ uint32_t op[32];
+
+ crc32c_zeros_op(op, len);
+ for (n = 0; n < 256; n++) {
+ zeros[0][n] = gf2_matrix_times(op, n);
+ zeros[1][n] = gf2_matrix_times(op, n << 8);
+ zeros[2][n] = gf2_matrix_times(op, n << 16);
+ zeros[3][n] = gf2_matrix_times(op, n << 24);
+ }
+}
+
+/* Apply the zeros operator table to crc. */
+static inline uint32_t crc32c_shift(uint32_t zeros[][256], uint32_t crc) {
+ // clang-format off
+ return zeros[0][crc & 0xff] ^ zeros[1][(crc >> 8) & 0xff] ^
+ zeros[2][(crc >> 16) & 0xff] ^ zeros[3][crc >> 24];
+ // clang-format on
+}
+
+/* Block sizes for three-way parallel crc computation. LONG and SHORT must
+ both be powers of two. The associated string constants must be set
+ accordingly, for use in constructing the assembler instructions. */
+#define LONG 8192
+#define LONGx1 "8192"
+#define LONGx2 "16384"
+#define SHORT 256
+#define SHORTx1 "256"
+#define SHORTx2 "512"
+
+/* Tables for hardware crc that shift a crc by LONG and SHORT zeros. */
+static uint32_t crc32c_long[4][256];
+static uint32_t crc32c_short[4][256];
+
+/* Compute CRC-32C using the Intel hardware instruction. */
+static uint32_t crc32c_hw(uint32_t crc, const void* buf, size_t len) {
+ const unsigned char* next = buf;
+ const unsigned char* end;
+ uint64_t crc0, crc1, crc2; /* need to be 64 bits for crc32q */
+
+ /* pre-process the crc */
+ crc0 = crc ^ 0xffffffff;
+
+ /* compute the crc for up to seven leading bytes to bring the data pointer
+ to an eight-byte boundary */
+ while (len && ((uintptr_t) next & 7) != 0) {
+ __asm__("crc32b\t"
+ "(%1), %0"
+ : "=r"(crc0)
+ : "r"(next), "0"(crc0));
+ next++;
+ len--;
+ }
+
+ /* compute the crc on sets of LONG*3 bytes, executing three independent crc
+ instructions, each on LONG bytes -- this is optimized for the Nehalem,
+ Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a
+ throughput of one crc per cycle, but a latency of three cycles */
+ while (len >= LONG * 3) {
+ crc1 = 0;
+ crc2 = 0;
+ end = next + LONG;
+ do {
+ __asm__("crc32q\t"
+ "(%3), %0\n\t"
+ "crc32q\t" LONGx1 "(%3), %1\n\t"
+ "crc32q\t" LONGx2 "(%3), %2"
+ : "=r"(crc0), "=r"(crc1), "=r"(crc2)
+ : "r"(next), "0"(crc0), "1"(crc1), "2"(crc2));
+ next += 8;
+ } while (next < end);
+ crc0 = crc32c_shift(crc32c_long, (uint32_t) crc0) ^ crc1;
+ crc0 = crc32c_shift(crc32c_long, (uint32_t) crc0) ^ crc2;
+ next += LONG * 2;
+ len -= LONG * 3;
+ }
+
+ /* do the same thing, but now on SHORT*3 blocks for the remaining data less
+ than a LONG*3 block */
+ while (len >= SHORT * 3) {
+ crc1 = 0;
+ crc2 = 0;
+ end = next + SHORT;
+ do {
+ __asm__("crc32q\t"
+ "(%3), %0\n\t"
+ "crc32q\t" SHORTx1 "(%3), %1\n\t"
+ "crc32q\t" SHORTx2 "(%3), %2"
+ : "=r"(crc0), "=r"(crc1), "=r"(crc2)
+ : "r"(next), "0"(crc0), "1"(crc1), "2"(crc2));
+ next += 8;
+ } while (next < end);
+ crc0 = crc32c_shift(crc32c_short, (uint32_t) crc0) ^ crc1;
+ crc0 = crc32c_shift(crc32c_short, (uint32_t) crc0) ^ crc2;
+ next += SHORT * 2;
+ len -= SHORT * 3;
+ }
+
+ /* compute the crc on the remaining eight-byte units less than a SHORT*3
+ block */
+ end = next + (len - (len & 7));
+ while (next < end) {
+ __asm__("crc32q\t"
+ "(%1), %0"
+ : "=r"(crc0)
+ : "r"(next), "0"(crc0));
+ next += 8;
+ }
+ len &= 7;
+
+ /* compute the crc for up to seven trailing bytes */
+ while (len) {
+ __asm__("crc32b\t"
+ "(%1), %0"
+ : "=r"(crc0)
+ : "r"(next), "0"(crc0));
+ next++;
+ len--;
+ }
+
+ /* return a post-processed crc */
+ return (uint32_t) crc0 ^ 0xffffffff;
+}
+
+/*
+ * Probe for SSE 4.2 support.
+ */
+bool rawrtc_crc32c_sse42_supported(void) {
+ uint32_t eax, ecx;
+ eax = 1;
+ __asm__("cpuid" : "=c"(ecx) : "a"(eax) : "%ebx", "%edx");
+ return ((ecx >> 20) & 1) != 0;
+}
+
+/*
+ * Initialize tables for shifting CRCs.
+ */
+void rawrtc_crc32c_init_sse42(void) {
+ crc32c_zeros(crc32c_long, LONG);
+ crc32c_zeros(crc32c_short, SHORT);
+}
+
+/*
+ * Compute CRC-32C using hardware instructions.
+ */
+uint32_t rawrtc_crc32c_sse42(void const* buffer, size_t length) {
+ return crc32c_hw(0x00000000, buffer, length);
+}
diff --git a/src/crc32c/sse42.h b/src/crc32c/sse42.h
new file mode 100644
index 0000000..5365888
--- /dev/null
+++ b/src/crc32c/sse42.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <re.h>
+
+bool rawrtc_crc32c_sse42_supported(void);
+
+void rawrtc_crc32c_init_sse42(void);
+
+uint32_t rawrtc_crc32c_sse42(void const* buffer, size_t length);
diff --git a/src/data_channel/attributes.c b/src/data_channel/attributes.c
new file mode 100644
index 0000000..31abf35
--- /dev/null
+++ b/src/data_channel/attributes.c
@@ -0,0 +1,351 @@
+#include "channel.h"
+#include "../data_transport/transport.h"
+#include <rawrtcdc/config.h>
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_channel_parameters.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+#define DEBUG_MODULE "data-channel"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Get the current state of the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_get_state(
+ enum rawrtc_data_channel_state* const statep, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!statep || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state & done
+ *statep = channel->state;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the currently buffered amount (bytes) of outgoing application
+ * data of the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_get_buffered_amount(
+ uint64_t* const buffered_amountp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!buffered_amountp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Implement this!
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+}
+
+/*
+ * Set the data channel's buffered amount (bytes) low threshold for
+ * outgoing application data.
+ */
+enum rawrtc_code rawrtc_data_channel_set_buffered_amount_low_threshold(
+ struct rawrtc_data_channel* const channel, uint64_t const buffered_amount_low_threshold) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Implement this!
+ (void) buffered_amount_low_threshold;
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+}
+
+/*
+ * Get the data channel's buffered amount (bytes) low threshold for
+ * outgoing application data.
+ */
+enum rawrtc_code rawrtc_data_channel_get_buffered_amount_low_threshold(
+ uint64_t* const buffered_amount_low_thresholdp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!buffered_amount_low_thresholdp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Implement this!
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+}
+
+/*
+ * Unset the handler argument and all handlers of the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_unset_handlers(struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Unset handler argument
+ channel->arg = NULL;
+
+ // Unset all handlers
+ channel->message_handler = NULL;
+ channel->close_handler = NULL;
+ channel->error_handler = NULL;
+ channel->buffered_amount_low_handler = NULL;
+ channel->open_handler = NULL;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's parameters.
+ * `*parametersp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_data_channel_get_parameters(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!parametersp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set pointer & done
+ *parametersp = mem_ref(channel->parameters);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Enable or disable streamed delivery.
+ *
+ * Note: In case an incoming message is currently pending (there are
+ * queued chunks in the internal reassembly buffer), this will
+ * fail with a *still in use* error.
+ */
+enum rawrtc_code rawrtc_data_channel_set_streaming(
+ struct rawrtc_data_channel* const channel, bool const on) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (channel->state == RAWRTC_DATA_CHANNEL_STATE_CLOSING ||
+ channel->state == RAWRTC_DATA_CHANNEL_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Does anything change?
+ if ((on && channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_STREAMED) ||
+ (!on && !(channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_STREAMED))) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Let the transport know we want to enable/disable streaming
+ error = channel->transport->channel_set_streaming(channel, on);
+ if (error) {
+ return error;
+ }
+
+ // Enable/disable streaming & done
+ if (on) {
+ channel->flags |= RAWRTC_DATA_CHANNEL_FLAGS_STREAMED;
+ DEBUG_PRINTF("Enabled streaming mode\n");
+ } else {
+ channel->flags &= ~RAWRTC_DATA_CHANNEL_FLAGS_STREAMED;
+ DEBUG_PRINTF("Disabled streaming mode\n");
+ }
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the data channel's open handler.
+ */
+enum rawrtc_code rawrtc_data_channel_set_open_handler(
+ struct rawrtc_data_channel* const channel,
+ rawrtc_data_channel_open_handler const open_handler // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set open handler & done
+ channel->open_handler = open_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's open handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_data_channel_get_open_handler(
+ rawrtc_data_channel_open_handler* const open_handlerp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!open_handlerp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get open handler (if any)
+ if (channel->open_handler) {
+ *open_handlerp = channel->open_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the data channel's buffered amount low handler.
+ */
+enum rawrtc_code rawrtc_data_channel_set_buffered_amount_low_handler(
+ struct rawrtc_data_channel* const channel,
+ rawrtc_data_channel_buffered_amount_low_handler const buffered_amount_low_handler // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set buffered amount low handler & done
+ channel->buffered_amount_low_handler = buffered_amount_low_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's buffered amount low handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_data_channel_get_buffered_amount_low_handler(
+ rawrtc_data_channel_buffered_amount_low_handler* const
+ buffered_amount_low_handlerp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!buffered_amount_low_handlerp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get buffered amount low handler (if any)
+ if (channel->buffered_amount_low_handler) {
+ *buffered_amount_low_handlerp = channel->buffered_amount_low_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the data channel's error handler.
+ */
+enum rawrtc_code rawrtc_data_channel_set_error_handler(
+ struct rawrtc_data_channel* const channel,
+ rawrtc_data_channel_error_handler const error_handler // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set error handler & done
+ channel->error_handler = error_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's error handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_data_channel_get_error_handler(
+ rawrtc_data_channel_error_handler* const error_handlerp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!error_handlerp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get error handler (if any)
+ if (channel->error_handler) {
+ *error_handlerp = channel->error_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the data channel's close handler.
+ */
+enum rawrtc_code rawrtc_data_channel_set_close_handler(
+ struct rawrtc_data_channel* const channel,
+ rawrtc_data_channel_close_handler const close_handler // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set close handler & done
+ channel->close_handler = close_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's close handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_data_channel_get_close_handler(
+ rawrtc_data_channel_close_handler* const close_handlerp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!close_handlerp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get close handler (if any)
+ if (channel->close_handler) {
+ *close_handlerp = channel->close_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the data channel's message handler.
+ */
+enum rawrtc_code rawrtc_data_channel_set_message_handler(
+ struct rawrtc_data_channel* const channel,
+ rawrtc_data_channel_message_handler const message_handler // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set message handler & done
+ channel->message_handler = message_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the data channel's message handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_data_channel_get_message_handler(
+ rawrtc_data_channel_message_handler* const message_handlerp, // de-referenced
+ struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!message_handlerp || !channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get message handler (if any)
+ if (channel->message_handler) {
+ *message_handlerp = channel->message_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
diff --git a/src/data_channel/channel.c b/src/data_channel/channel.c
new file mode 100644
index 0000000..e134430
--- /dev/null
+++ b/src/data_channel/channel.c
@@ -0,0 +1,251 @@
+#include "channel.h"
+#include "../data_channel_parameters/parameters.h"
+#include "../data_transport/transport.h"
+#include <rawrtcdc/config.h>
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+#define DEBUG_MODULE "data-channel"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Change the state of the data channel.
+ * Will call the corresponding handler.
+ * Caller MUST ensure that the same state is not set twice.
+ */
+void rawrtc_data_channel_set_state(
+ struct rawrtc_data_channel* const channel, // not checked
+ enum rawrtc_data_channel_state const state) {
+ // Set state
+ // Note: Keep this here as it will prevent infinite recursion during closing/destroying
+ channel->state = state;
+ DEBUG_PRINTF(
+ "Data channel '%s' state changed to %s\n",
+ channel->parameters->label ? channel->parameters->label : "n/a",
+ rawrtc_data_channel_state_to_name(state));
+
+ // TODO: Clear options flag?
+
+ // Call transport handler (if any) and user application handler
+ switch (state) {
+ case RAWRTC_DATA_CHANNEL_STATE_OPEN:
+ // Call handler
+ if (channel->open_handler) {
+ channel->open_handler(channel->arg);
+ }
+ break;
+
+ case RAWRTC_DATA_CHANNEL_STATE_CLOSING:
+ // Nothing to do.
+ break;
+
+ case RAWRTC_DATA_CHANNEL_STATE_CLOSED:
+ // Call handler
+ if (channel->close_handler) {
+ channel->close_handler(channel->arg);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Destructor for an existing data channel.
+ */
+static void rawrtc_data_channel_destroy(void* arg) {
+ struct rawrtc_data_channel* const channel = arg;
+
+ // Unset all handlers
+ rawrtc_data_channel_unset_handlers(channel);
+
+ // Close channel
+ // Note: The function will ensure that the channel is not closed before it's initialised
+ rawrtc_data_channel_close(channel);
+
+ // Un-reference
+ mem_deref(channel->transport);
+ mem_deref(channel->transport_arg);
+ mem_deref(channel->parameters);
+}
+
+/*
+ * Create a data channel (internal).
+ */
+enum rawrtc_code rawrtc_data_channel_create_internal(
+ struct rawrtc_data_channel** const channelp, // de-referenced
+ struct rawrtc_data_transport* const transport, // referenced
+ 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
+ bool const call_handler) {
+ enum rawrtc_code error;
+ struct rawrtc_data_channel* channel;
+
+ // Check arguments
+ if (!channelp || !transport || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ channel = mem_zalloc(sizeof(*channel), rawrtc_data_channel_destroy);
+ if (!channel) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ channel->flags = 0;
+ channel->state = RAWRTC_DATA_CHANNEL_STATE_CONNECTING;
+ channel->transport = mem_ref(transport);
+ channel->parameters = mem_ref(parameters);
+ channel->open_handler = open_handler;
+ channel->buffered_amount_low_handler = buffered_amount_low_handler;
+ channel->error_handler = error_handler;
+ channel->close_handler = close_handler;
+ channel->message_handler = message_handler;
+ channel->arg = arg;
+
+ // Create data channel on transport
+ if (call_handler) {
+ error = transport->channel_create(transport, channel, parameters);
+ if (error) {
+ goto out;
+ }
+ } else {
+ error = RAWRTC_CODE_SUCCESS;
+ }
+
+ // Done
+ DEBUG_PRINTF(
+ "Created data channel: label=%s, type=%d, reliability-parameter=%" PRIu32 ", "
+ "protocol=%s, negotiated=%s, id=%" PRIu16 "\n",
+ parameters->label ? parameters->label : "n/a", parameters->channel_type,
+ parameters->reliability_parameter, parameters->protocol ? parameters->protocol : "n/a",
+ parameters->negotiated ? "yes" : "no", parameters->id);
+
+out:
+ if (error) {
+ mem_deref(channel);
+ } else {
+ // Update flags & set pointer
+ channel->flags |= RAWRTC_DATA_CHANNEL_FLAGS_INITIALIZED;
+ *channelp = channel;
+ }
+ return error;
+}
+
+/*
+ * Call the data channel handler (internal).
+ *
+ * Important: Data transport implementations SHALL call this function
+ * instead of calling the channel handler directly.
+ */
+void rawrtc_data_channel_call_channel_handler(
+ struct rawrtc_data_channel* const channel, // not checked
+ rawrtc_data_channel_handler const channel_handler, // nullable
+ void* const arg) {
+ // Call handler (if any)
+ if (channel_handler) {
+ channel_handler(channel, arg);
+ }
+}
+
+/*
+ * Create a data channel.
+ * `*channelp` must be unreferenced.
+ *
+ * Note: You should call `rawrtc_data_channel_set_streaming`
+ * directly after this function returned if you want to enable
+ * streamed delivery of data for this channel from the beginning
+ * of the first incoming message.
+ */
+enum rawrtc_code rawrtc_data_channel_create(
+ struct rawrtc_data_channel** const channelp, // de-referenced
+ struct rawrtc_data_transport* const transport, // referenced
+ 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 const error = rawrtc_data_channel_create_internal(
+ channelp, transport, parameters, open_handler, buffered_amount_low_handler, error_handler,
+ close_handler, message_handler, arg, true);
+
+ // Done
+ return error;
+}
+
+/*
+ * Set the argument of a data channel that is passed to the various
+ * handlers.
+ */
+enum rawrtc_code rawrtc_data_channel_set_arg(
+ struct rawrtc_data_channel* const channel,
+ void* const arg // nullable
+) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set handler argument & done
+ channel->arg = arg;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Send data via the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_send(
+ struct rawrtc_data_channel* const channel,
+ struct mbuf* const buffer, // nullable (if empty message), referenced
+ bool const is_binary) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ // TODO: Is this correct or can we send during `connecting` as well?
+ if (channel->state != RAWRTC_DATA_CHANNEL_STATE_OPEN) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Call handler
+ return channel->transport->channel_send(channel, buffer, is_binary);
+}
+
+/*
+ * Close the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_close(struct rawrtc_data_channel* const channel) {
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Don't close before the channel is initialised
+ // Note: This is needed as this function may be called in the destructor of the data channel
+ if (!(channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_INITIALIZED)) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Check state
+ if (channel->state == RAWRTC_DATA_CHANNEL_STATE_CLOSING ||
+ channel->state == RAWRTC_DATA_CHANNEL_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Close channel
+ DEBUG_PRINTF("Closing data channel: %s\n", channel->parameters->label);
+ return channel->transport->channel_close(channel);
+}
diff --git a/src/data_channel/channel.h b/src/data_channel/channel.h
new file mode 100644
index 0000000..c6e8e89
--- /dev/null
+++ b/src/data_channel/channel.h
@@ -0,0 +1,58 @@
+#pragma once
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_channel_parameters.h>
+#include <rawrtcdc/data_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Data channel flags.
+ */
+enum {
+ RAWRTC_DATA_CHANNEL_FLAGS_INITIALIZED = 1 << 0,
+ RAWRTC_DATA_CHANNEL_FLAGS_STREAMED = 1 << 1,
+};
+
+/*
+ * Data channel type unordered bit flag.
+ */
+enum {
+ RAWRTC_DATA_CHANNEL_TYPE_IS_UNORDERED = 0x80,
+};
+
+/*
+ * Data channel.
+ */
+struct rawrtc_data_channel {
+ uint_fast8_t flags;
+ enum rawrtc_data_channel_state state;
+ struct rawrtc_data_transport* transport; // referenced
+ void* transport_arg; // referenced
+ struct rawrtc_data_channel_parameters* parameters; // referenced
+ rawrtc_data_channel_open_handler open_handler; // nullable
+ rawrtc_data_channel_buffered_amount_low_handler buffered_amount_low_handler; // nullable
+ rawrtc_data_channel_error_handler error_handler; // nullable
+ rawrtc_data_channel_close_handler close_handler; // nullable
+ rawrtc_data_channel_message_handler message_handler; // nullable
+ void* arg; // nullable
+};
+
+void rawrtc_data_channel_set_state(
+ struct rawrtc_data_channel* const channel, enum rawrtc_data_channel_state const state);
+
+enum rawrtc_code rawrtc_data_channel_create_internal(
+ struct rawrtc_data_channel** const channelp, // de-referenced
+ struct rawrtc_data_transport* const transport, // referenced
+ 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
+ bool const call_handler);
+
+void rawrtc_data_channel_call_channel_handler(
+ struct rawrtc_data_channel* const channel, // not checked
+ rawrtc_data_channel_handler const channel_handler, // nullable
+ void* const arg);
diff --git a/src/data_channel/meson.build b/src/data_channel/meson.build
new file mode 100644
index 0000000..9ab3083
--- /dev/null
+++ b/src/data_channel/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'channel.c',
+ 'utils.c',
+])
diff --git a/src/data_channel/utils.c b/src/data_channel/utils.c
new file mode 100644
index 0000000..31ab581
--- /dev/null
+++ b/src/data_channel/utils.c
@@ -0,0 +1,19 @@
+#include <rawrtcdc/data_channel.h>
+
+/*
+ * Get the corresponding name for a data channel state.
+ */
+char const* rawrtc_data_channel_state_to_name(enum rawrtc_data_channel_state const state) {
+ switch (state) {
+ case RAWRTC_DATA_CHANNEL_STATE_CONNECTING:
+ return "connecting";
+ case RAWRTC_DATA_CHANNEL_STATE_OPEN:
+ return "open";
+ case RAWRTC_DATA_CHANNEL_STATE_CLOSING:
+ return "closing";
+ case RAWRTC_DATA_CHANNEL_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
diff --git a/src/data_channel_parameters/attributes.c b/src/data_channel_parameters/attributes.c
new file mode 100644
index 0000000..f09deb6
--- /dev/null
+++ b/src/data_channel_parameters/attributes.c
@@ -0,0 +1,138 @@
+#include "parameters.h"
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_channel_parameters.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the label from the data channel parameters.
+ * `*labelp` will be set to a copy of the parameter's label and must be
+ * unreferenced.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no label has been set.
+ * Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and `*parameters*
+ * must be unreferenced.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_label(
+ char** const labelp, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!labelp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ if (parameters->label) {
+ *labelp = mem_ref(parameters->label);
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the channel type from the data channel parameters.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_channel_type(
+ enum rawrtc_data_channel_type* const channel_typep, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!channel_typep || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *channel_typep = parameters->channel_type;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the reliability parameter from the data channel parameters.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case the channel type is
+ * `RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_*`. Otherwise,
+ * `RAWRTC_CODE_SUCCESS` will be returned.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_reliability_parameter(
+ uint32_t* const reliability_parameterp, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!reliability_parameterp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ switch (parameters->channel_type) {
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_ORDERED:
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_UNORDERED:
+ return RAWRTC_CODE_NO_VALUE;
+ default:
+ *reliability_parameterp = parameters->reliability_parameter;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Get the protocol from the data channel parameters.
+ * `*protocolp` will be set to a copy of the parameter's protocol and
+ * must be unreferenced.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case no protocol has been set.
+ * Otherwise, `RAWRTC_CODE_SUCCESS` will be returned and `*protocolp*
+ * must be unreferenced.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_protocol(
+ char** const protocolp, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!protocolp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ if (parameters->protocol) {
+ *protocolp = mem_ref(parameters->protocol);
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Get the 'negotiated' flag from the data channel parameters.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_negotiated(
+ bool* const negotiatedp, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!negotiatedp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *negotiatedp = parameters->negotiated;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the negotiated id from the data channel parameters.
+ *
+ * Return `RAWRTC_CODE_NO_VALUE` in case the 'negotiated' flag is set
+ * `false`. Otherwise, `RAWRTC_CODE_SUCCESS` will be returned.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_get_id(
+ uint16_t* const idp, // de-referenced
+ struct rawrtc_data_channel_parameters* const parameters) {
+ // Check arguments
+ if (!idp || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ if (parameters->negotiated) {
+ *idp = parameters->id;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
diff --git a/src/data_channel_parameters/meson.build b/src/data_channel_parameters/meson.build
new file mode 100644
index 0000000..c68f3a1
--- /dev/null
+++ b/src/data_channel_parameters/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'attributes.c',
+ 'parameters.c',
+])
diff --git a/src/data_channel_parameters/parameters.c b/src/data_channel_parameters/parameters.c
new file mode 100644
index 0000000..189850a
--- /dev/null
+++ b/src/data_channel_parameters/parameters.c
@@ -0,0 +1,152 @@
+#include "parameters.h"
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_channel_parameters.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+
+/*
+ * Destructor for existing data channel parameters.
+ */
+static void rawrtc_data_channel_parameters_destroy(void* arg) {
+ struct rawrtc_data_channel_parameters* const parameters = arg;
+
+ // Un-reference
+ mem_deref(parameters->label);
+ mem_deref(parameters->protocol);
+}
+
+/*
+ * Common code to create or copy parameters.
+ */
+static enum rawrtc_code data_parameters_create(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced
+ char* const label, // referenced, nullable
+ enum rawrtc_data_channel_type const channel_type,
+ uint32_t const reliability_parameter,
+ char* const protocol, // referenced, nullable
+ bool const negotiated,
+ uint16_t const id) {
+ struct rawrtc_data_channel_parameters* parameters;
+
+ // Check arguments
+ if (!parametersp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ parameters = mem_zalloc(sizeof(*parameters), rawrtc_data_channel_parameters_destroy);
+ if (!parameters) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ parameters->label = label;
+ parameters->protocol = protocol;
+ parameters->channel_type = channel_type;
+ parameters->negotiated = negotiated;
+ if (negotiated) {
+ parameters->id = id;
+ }
+
+ // Set reliability parameter
+ switch (channel_type) {
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_ORDERED:
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_UNORDERED:
+ parameters->reliability_parameter = 0;
+ break;
+ default:
+ parameters->reliability_parameter = reliability_parameter;
+ break;
+ }
+
+ // Set pointer & done
+ *parametersp = parameters;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Create data channel parameters (internal).
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_create_internal(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced
+ char* const label, // referenced, nullable
+ enum rawrtc_data_channel_type const channel_type,
+ uint32_t const reliability_parameter,
+ char* const protocol, // referenced, nullable
+ bool const negotiated,
+ uint16_t const id) {
+ enum rawrtc_code error;
+
+ // Create parameters
+ error = data_parameters_create(
+ parametersp, label, channel_type, reliability_parameter, protocol, negotiated, id);
+
+ if (!error) {
+ // Reference label & protocol
+ mem_ref(label);
+ mem_ref(protocol);
+ }
+
+ // Done
+ return error;
+}
+
+/*
+ * Create data channel parameters.
+ *
+ * For `RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_*`, the reliability parameter
+ * is being ignored.
+ *
+ * When using `RAWRTC_DATA_CHANNEL_TYPE_*_RETRANSMIT`, the reliability
+ * parameter specifies the number of times a retransmission occurs if
+ * not acknowledged before the message is being discarded.
+ *
+ * When using `RAWRTC_DATA_CHANNEL_TYPE_*_TIMED`, the reliability
+ * parameter specifies the time window in milliseconds during which
+ * (re-)transmissions may occur before the message is being discarded.
+ *
+ * `*parametersp` must be unreferenced.
+ *
+ * In case `negotiated` is set to `false`, the `id` is being ignored.
+ */
+enum rawrtc_code rawrtc_data_channel_parameters_create(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced
+ char const* const label, // copied, nullable
+ enum rawrtc_data_channel_type const channel_type,
+ uint32_t const reliability_parameter,
+ char const* const protocol, // copied, nullable
+ bool const negotiated,
+ uint16_t const id) {
+ char* copied_label;
+ char* copied_protocol;
+ enum rawrtc_code error;
+
+ // Copy label
+ if (label) {
+ rawrtc_strdup(&copied_label, label);
+ } else {
+ copied_label = NULL;
+ }
+
+ // Copy protocol
+ if (protocol) {
+ rawrtc_strdup(&copied_protocol, protocol);
+ } else {
+ copied_protocol = NULL;
+ }
+
+ // Create parameters
+ error = data_parameters_create(
+ parametersp, copied_label, channel_type, reliability_parameter, copied_protocol, negotiated,
+ id);
+
+ if (error) {
+ // Un-reference
+ mem_deref(copied_label);
+ mem_deref(copied_protocol);
+ }
+
+ // Done
+ return error;
+}
diff --git a/src/data_channel_parameters/parameters.h b/src/data_channel_parameters/parameters.h
new file mode 100644
index 0000000..500e0a9
--- /dev/null
+++ b/src/data_channel_parameters/parameters.h
@@ -0,0 +1,25 @@
+#pragma once
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Data channel parameters.
+ */
+struct rawrtc_data_channel_parameters {
+ char* label; // copied
+ enum rawrtc_data_channel_type channel_type;
+ uint32_t reliability_parameter; // contains either max_packet_lifetime or max_retransmit
+ char* protocol; // copied
+ bool negotiated;
+ uint16_t id;
+};
+
+enum rawrtc_code rawrtc_data_channel_parameters_create_internal(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced
+ char* const label, // referenced, nullable
+ enum rawrtc_data_channel_type const channel_type,
+ uint32_t const reliability_parameter,
+ char* const protocol, // referenced, nullable
+ bool const negotiated,
+ uint16_t const id);
diff --git a/src/data_transport/attributes.c b/src/data_transport/attributes.c
new file mode 100644
index 0000000..6888ab6
--- /dev/null
+++ b/src/data_transport/attributes.c
@@ -0,0 +1,25 @@
+#include "transport.h"
+#include <rawrtcdc/data_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Get the data transport's type and underlying transport reference.
+ * `*internal_transportp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_data_transport_get_transport(
+ enum rawrtc_data_transport_type* const typep, // de-referenced
+ void** const internal_transportp, // de-referenced
+ struct rawrtc_data_transport* const transport) {
+ // Check arguments
+ if (!typep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set type & transport
+ *typep = transport->type;
+ *internal_transportp = mem_ref(transport->transport);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/data_transport/meson.build b/src/data_transport/meson.build
new file mode 100644
index 0000000..0227c31
--- /dev/null
+++ b/src/data_transport/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'transport.c',
+ 'utils.c',
+])
diff --git a/src/data_transport/transport.c b/src/data_transport/transport.c
new file mode 100644
index 0000000..62be56d
--- /dev/null
+++ b/src/data_transport/transport.c
@@ -0,0 +1,57 @@
+#include "transport.h"
+#include <rawrtcdc/config.h>
+#include <rawrtcdc/data_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+#define DEBUG_MODULE "data-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Destructor for an existing data transport.
+ */
+static void rawrtc_data_transport_destroy(void* arg) {
+ struct rawrtc_data_transport* const transport = arg;
+
+ // Un-reference
+ mem_deref(transport->transport);
+}
+
+/*
+ * Create a data transport instance.
+ */
+enum rawrtc_code rawrtc_data_transport_create(
+ struct rawrtc_data_transport** const transportp, // de-referenced
+ enum rawrtc_data_transport_type const type,
+ void* const internal_transport, // referenced
+ rawrtc_data_transport_channel_create_handler const channel_create_handler,
+ rawrtc_data_transport_channel_close_handler const channel_close_handler,
+ rawrtc_data_transport_channel_send_handler const channel_send_handler,
+ rawrtc_data_transport_channel_set_streaming_handler const channel_set_streaming_handler) {
+ struct rawrtc_data_transport* transport;
+
+ // Check arguments
+ if (!transportp || !internal_transport || !channel_create_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ transport = mem_zalloc(sizeof(*transport), rawrtc_data_transport_destroy);
+ if (!transport) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ transport->type = type;
+ transport->transport = mem_ref(internal_transport);
+ transport->channel_create = channel_create_handler;
+ transport->channel_close = channel_close_handler;
+ transport->channel_send = channel_send_handler;
+ transport->channel_set_streaming = channel_set_streaming_handler;
+
+ // Set pointer & done
+ DEBUG_PRINTF("Created data transport of type %s\n", rawrtc_data_transport_type_to_str(type));
+ *transportp = transport;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/data_transport/transport.h b/src/data_transport/transport.h
new file mode 100644
index 0000000..3b5593e
--- /dev/null
+++ b/src/data_transport/transport.h
@@ -0,0 +1,57 @@
+#pragma once
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_channel_parameters.h>
+#include <rawrtcdc/data_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Create the data channel (transport handler).
+ */
+typedef enum rawrtc_code (*rawrtc_data_transport_channel_create_handler)(
+ struct rawrtc_data_transport* const transport,
+ struct rawrtc_data_channel* const channel, // referenced
+ struct rawrtc_data_channel_parameters const* const parameters // read-only
+);
+
+/*
+ * Close the data channel (transport handler).
+ */
+typedef enum rawrtc_code (*rawrtc_data_transport_channel_close_handler)(
+ struct rawrtc_data_channel* const channel);
+
+/*
+ * Send data via the data channel (transport handler).
+ */
+typedef enum rawrtc_code (*rawrtc_data_transport_channel_send_handler)(
+ struct rawrtc_data_channel* const channel,
+ struct mbuf* buffer, // nullable (if size 0), referenced
+ bool const is_binary);
+
+/*
+ * Check if the data transport allows changing the delivery mode
+ * (transport handler).
+ */
+typedef enum rawrtc_code (*rawrtc_data_transport_channel_set_streaming_handler)(
+ struct rawrtc_data_channel* const channel, bool const on);
+
+/**
+ * Generic data transport.
+ */
+struct rawrtc_data_transport {
+ enum rawrtc_data_transport_type type;
+ void* transport;
+ rawrtc_data_transport_channel_create_handler channel_create;
+ rawrtc_data_transport_channel_close_handler channel_close;
+ rawrtc_data_transport_channel_send_handler channel_send;
+ rawrtc_data_transport_channel_set_streaming_handler channel_set_streaming;
+};
+
+enum rawrtc_code rawrtc_data_transport_create(
+ struct rawrtc_data_transport** const transportp, // de-referenced
+ enum rawrtc_data_transport_type const type,
+ void* const internal_transport, // referenced
+ rawrtc_data_transport_channel_create_handler const channel_create_handler,
+ rawrtc_data_transport_channel_close_handler const channel_close_handler,
+ rawrtc_data_transport_channel_send_handler const channel_send_handler,
+ rawrtc_data_transport_channel_set_streaming_handler const channel_set_streaming_handler);
diff --git a/src/data_transport/utils.c b/src/data_transport/utils.c
new file mode 100644
index 0000000..1216eea
--- /dev/null
+++ b/src/data_transport/utils.c
@@ -0,0 +1,13 @@
+#include <rawrtcdc/data_transport.h>
+
+/*
+ * Translate a data transport type to str.
+ */
+char const* rawrtc_data_transport_type_to_str(enum rawrtc_data_transport_type const type) {
+ switch (type) {
+ case RAWRTC_DATA_TRANSPORT_TYPE_SCTP:
+ return "SCTP";
+ default:
+ return "???";
+ }
+}
diff --git a/src/main/main.c b/src/main/main.c
new file mode 100644
index 0000000..e17633e
--- /dev/null
+++ b/src/main/main.c
@@ -0,0 +1,79 @@
+#include "main.h"
+#include "../crc32c/crc32c.h"
+#include <rawrtcdc/main.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+#include <usrsctp.h> // usrsctp_handle_timers
+#include <limits.h> // INT_MAX
+
+/*
+ * Global RAWRTCDC settings.
+ */
+struct rawrtcdc_global rawrtcdc_global;
+
+/*
+ * Initialise RAWRTCDC. 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 rawrtcdc_init(bool const init_re, rawrtcdc_timer_handler const timer_handler) {
+ // Check arguments
+ if (!timer_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Initialise re (if requested)
+ if (init_re) {
+ if (libre_init()) {
+ return RAWRTC_CODE_INITIALISE_FAIL;
+ }
+ }
+
+ // Initialise CRC32-C
+ rawrtc_crc32c_init();
+
+ // Set timer handler
+ rawrtcdc_global.timer_handler = timer_handler;
+
+ // Set usrsctp initialised counter
+ rawrtcdc_global.usrsctp_initialized = 0;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Close RAWRTCDC and free up all resources.
+ *
+ * Note: In case `close_re` is not set to `true`, you MUST close
+ * re yourselves.
+ */
+enum rawrtc_code rawrtcdc_close(bool const close_re) {
+ // TODO: Close usrsctp if initialised
+
+ // Remove timer handler
+ rawrtcdc_global.timer_handler = NULL;
+
+ // Close re (if requested)
+ if (close_re) {
+ libre_close();
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Handle timer tick.
+ * `delta` contains the delta milliseconds passed between calls.
+ */
+void rawrtcdc_timer_tick(uint_fast16_t const delta) {
+ // Pass delta ms to usrsctp
+#if (UINT16_MAX > INT_MAX)
+ usrsctp_handle_timers(delta > INT_MAX ? INT_MAX : ((int) delta));
+#else
+ usrsctp_handle_timers((int) delta);
+#endif
+}
diff --git a/src/main/main.h b/src/main/main.h
new file mode 100644
index 0000000..faf0071
--- /dev/null
+++ b/src/main/main.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <rawrtcdc/main.h>
+#include <re.h>
+
+extern struct rawrtcdc_global rawrtcdc_global;
+
+/*
+ * CRC32-C handler to be used.
+ */
+typedef uint32_t (*rawrtc_crc32c_handler)(void const* buffer, size_t length);
+
+/*
+ * Global RAWRTCDC settings.
+ */
+struct rawrtcdc_global {
+ rawrtcdc_timer_handler timer_handler;
+ rawrtc_crc32c_handler crc32c_handler;
+ uint_fast32_t usrsctp_initialized;
+ size_t usrsctp_chunk_size;
+};
diff --git a/src/main/meson.build b/src/main/meson.build
new file mode 100644
index 0000000..b298885
--- /dev/null
+++ b/src/main/meson.build
@@ -0,0 +1 @@
+sources += files('main.c')
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..976bc9d
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,12 @@
+sources = []
+
+subdir('crc32c')
+subdir('data_channel')
+subdir('data_channel_parameters')
+subdir('data_transport')
+subdir('main')
+subdir('sctp_capabilities')
+if get_option('sctp_redirect_transport')
+ subdir('sctp_redirect_transport')
+endif
+subdir('sctp_transport')
diff --git a/src/sctp_capabilities/attributes.c b/src/sctp_capabilities/attributes.c
new file mode 100644
index 0000000..f84ff70
--- /dev/null
+++ b/src/sctp_capabilities/attributes.c
@@ -0,0 +1,23 @@
+#include "capabilities.h"
+#include <rawrtcdc/sctp_capabilities.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the SCTP parameter's maximum message size value.
+ *
+ * Note: A value of `0` indicates that the implementation supports
+ * receiving messages of arbitrary size.
+ */
+enum rawrtc_code rawrtc_sctp_capabilities_get_max_message_size(
+ uint64_t* const max_message_sizep, // de-referenced
+ struct rawrtc_sctp_capabilities* const capabilities) {
+ // Check arguments
+ if (!capabilities) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set value
+ *max_message_sizep = capabilities->max_message_size;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/sctp_capabilities/capabilities.c b/src/sctp_capabilities/capabilities.c
new file mode 100644
index 0000000..8775760
--- /dev/null
+++ b/src/sctp_capabilities/capabilities.c
@@ -0,0 +1,53 @@
+#include "capabilities.h"
+#include <rawrtcdc/sctp_capabilities.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Print debug information for SCTP capabilities.
+ */
+int rawrtc_sctp_capabilities_debug(
+ struct re_printf* const pf, struct rawrtc_sctp_capabilities const* const capabilities) {
+ int err = 0;
+
+ // Check arguments
+ if (!capabilities) {
+ return 0;
+ }
+
+ err |= re_hprintf(pf, " SCTP Capabilities <%p>:\n", capabilities);
+
+ // Maximum message size
+ err |= re_hprintf(pf, " max_message_size=%" PRIu64 "\n", capabilities->max_message_size);
+
+ // Done
+ return err;
+}
+
+/*
+ * Create a new SCTP transport capabilities instance.
+ * `*capabilitiesp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_sctp_capabilities_create(
+ struct rawrtc_sctp_capabilities** const capabilitiesp, // de-referenced
+ uint64_t const max_message_size) {
+ struct rawrtc_sctp_capabilities* capabilities;
+
+ // Check arguments
+ if (!capabilitiesp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate capabilities
+ capabilities = mem_zalloc(sizeof(*capabilities), NULL);
+ if (!capabilities) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ capabilities->max_message_size = max_message_size;
+
+ // Set pointer & done
+ *capabilitiesp = capabilities;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/sctp_capabilities/capabilities.h b/src/sctp_capabilities/capabilities.h
new file mode 100644
index 0000000..09bdfe6
--- /dev/null
+++ b/src/sctp_capabilities/capabilities.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <re.h>
+
+struct rawrtc_sctp_capabilities {
+ uint64_t max_message_size;
+};
diff --git a/src/sctp_capabilities/meson.build b/src/sctp_capabilities/meson.build
new file mode 100644
index 0000000..db2b827
--- /dev/null
+++ b/src/sctp_capabilities/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+ 'attributes.c',
+ 'capabilities.c',
+])
diff --git a/src/sctp_redirect_transport/attributes.c b/src/sctp_redirect_transport/attributes.c
new file mode 100644
index 0000000..2a1d163
--- /dev/null
+++ b/src/sctp_redirect_transport/attributes.c
@@ -0,0 +1,36 @@
+#include "transport.h"
+#include <rawrtcdc/sctp_redirect_transport.h>
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/*
+ * Get the state of the SCTP redirect transport.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_get_state(
+ enum rawrtc_sctp_redirect_transport_state* const statep, // de-referenced
+ struct rawrtc_sctp_redirect_transport* const transport) {
+ // Check arguments
+ if (!statep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state & done
+ *statep = transport->state;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the redirected local SCTP port of the SCTP redirect transport.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_get_port(
+ uint16_t* const portp, // de-referenced
+ struct rawrtc_sctp_redirect_transport* const transport) {
+ // Check arguments
+ if (!portp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set port & done
+ *portp = transport->local_port;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/sctp_redirect_transport/meson.build b/src/sctp_redirect_transport/meson.build
new file mode 100644
index 0000000..0227c31
--- /dev/null
+++ b/src/sctp_redirect_transport/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'transport.c',
+ 'utils.c',
+])
diff --git a/src/sctp_redirect_transport/transport.c b/src/sctp_redirect_transport/transport.c
new file mode 100644
index 0000000..2867235
--- /dev/null
+++ b/src/sctp_redirect_transport/transport.c
@@ -0,0 +1,449 @@
+#include "transport.h"
+#include "../crc32c/crc32c.h"
+#include <rawrtcdc/config.h>
+#include <rawrtcdc/external.h>
+#include <rawrtcdc/sctp_redirect_transport.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <errno.h>
+#include <netinet/in.h> // IPPROTO_RAW, ntohs, htons
+#include <string.h> // memset
+#include <sys/socket.h> // AF_INET, SOCK_RAW, sendto, recvfrom
+#include <sys/types.h>
+#include <unistd.h> // close
+
+#define DEBUG_MODULE "redirect-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+/*
+ * Patch local and remote port in the SCTP packet header.
+ */
+static void patch_sctp_header(
+ struct mbuf* const buffer, uint16_t const source, uint16_t const destination) {
+ size_t const start = buffer->pos;
+ int err;
+ uint32_t checksum;
+
+ // Patch source port
+ err = mbuf_write_u16(buffer, htons(source));
+ if (err) {
+ DEBUG_WARNING("Could not patch source port, reason: %m\n", err);
+ return;
+ }
+
+ // Patch destination port
+ err = mbuf_write_u16(buffer, htons(destination));
+ if (err) {
+ DEBUG_WARNING("Could not patch destination port, reason: %m\n", err);
+ return;
+ }
+
+ // Skip verification tag
+ mbuf_advance(buffer, 4);
+
+ // Reset checksum field to '0' and rewind back
+ memset(mbuf_buf(buffer), 0, 4);
+ mbuf_set_pos(buffer, start);
+ // Recalculate checksum
+ checksum = rawrtc_crc32c(mbuf_buf(buffer), mbuf_get_left(buffer));
+ // Advance to checksum field, set it and rewind back
+ mbuf_advance(buffer, 8);
+ err = mbuf_write_u32(buffer, checksum);
+ if (err) {
+ DEBUG_WARNING("Could not patch checksum, reason: %m\n", err);
+ return;
+ }
+ mbuf_set_pos(buffer, start);
+}
+
+/*
+ * Handle outgoing messages (that came in from the raw socket).
+ */
+static void redirect_from_raw(
+ int flags,
+ void* arg // not checked
+) {
+ struct rawrtc_sctp_redirect_transport* const transport = arg;
+ struct mbuf* const buffer = transport->buffer;
+ enum rawrtc_code error;
+ struct sockaddr_in from_address;
+ socklen_t address_length;
+ ssize_t length;
+ struct sa from = {0};
+ size_t header_length;
+ uint16_t source;
+ uint16_t destination;
+
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_DETACHED) {
+ DEBUG_PRINTF("Ignoring SCTP packet ready event, transport is detached\n");
+ return;
+ }
+
+ if ((flags & FD_READ) == FD_READ) {
+ // Receive
+ address_length = sizeof(from_address);
+ length = recvfrom(
+ transport->socket, mbuf_buf(buffer), mbuf_get_space(buffer), 0,
+ (struct sockaddr*) &from_address, &address_length);
+ if (length == -1) {
+ switch (errno) {
+ case EAGAIN:
+#if (defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ break;
+ default:
+ DEBUG_WARNING("Unable to receive raw message: %m\n", errno);
+ break;
+ }
+ goto out;
+ }
+ mbuf_set_end(buffer, (size_t) length);
+
+ // Check address
+ error = rawrtc_error_to_code(sa_set_sa(&from, (struct sockaddr*) &from_address));
+ if (error) {
+ DEBUG_WARNING("Invalid sender address: %m\n", error);
+ goto out;
+ }
+ DEBUG_PRINTF("Received %zu bytes via RAW from %j\n", mbuf_get_left(buffer), &from);
+ if (!sa_isset(&from, SA_ADDR) && !sa_cmp(&transport->redirect_address, &from, SA_ADDR)) {
+ DEBUG_WARNING("Ignoring data from unknown address");
+ goto out;
+ }
+
+ // Skip IPv4 header
+ header_length = (size_t)(mbuf_read_u8(buffer) & 0xf);
+ mbuf_advance(buffer, -1);
+ DEBUG_PRINTF("IPv4 header length: %zu\n", header_length);
+ mbuf_advance(buffer, header_length * 4);
+
+ // Read source and destination port
+ source = ntohs(mbuf_read_u16(buffer));
+ destination = ntohs(mbuf_read_u16(buffer));
+ sa_set_port(&from, source);
+ (void) destination;
+ DEBUG_PRINTF("Raw from %J to %" PRIu16 "\n", &from, destination);
+ mbuf_advance(buffer, -4);
+
+ // Is this from the correct source?
+ if (source != sa_port(&transport->redirect_address)) {
+ DEBUG_PRINTF("Ignored data from different source port %" PRIu16 "\n", source);
+ goto out;
+ }
+
+ // Update SCTP header with changed ports
+ patch_sctp_header(buffer, transport->local_port, transport->remote_port);
+
+ // Pass to outbound handler
+ error = transport->context.outbound_handler(buffer, 0x00, 0x00, transport->context.arg);
+ if (error) {
+ DEBUG_WARNING("Could not send packet, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+ }
+
+out:
+ // Rewind buffer
+ mbuf_rewind(buffer);
+}
+
+/*
+ * Change the state of the SCTP redirect transport.
+ * Caller MUST ensure that the same state is not set twice.
+ */
+static void set_state(
+ struct rawrtc_sctp_redirect_transport* const transport, // not checked
+ enum rawrtc_sctp_redirect_transport_state const state) {
+ // Closed?
+ if (state == RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_CLOSED) {
+ // Stop listening and close raw socket
+ if (transport->socket != -1) {
+ fd_close(transport->socket);
+ if (close(transport->socket)) {
+ DEBUG_WARNING("Closing raw socket failed: %m\n", errno);
+ }
+ }
+
+ // Mark as detached & detach from DTLS transport
+ transport->flags |= RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_DETACHED;
+ if (transport->context.detach_handler) {
+ transport->context.detach_handler(transport->context.arg);
+ }
+ }
+
+ // Set state
+ transport->state = state;
+
+ // Call handler (if any)
+ if (transport->state_change_handler) {
+ transport->state_change_handler(state, transport->arg);
+ }
+}
+
+static enum rawrtc_code validate_context(
+ struct rawrtc_sctp_transport_context* const context // not checked
+) {
+ // Ensure the context contains all necessary callbacks
+ if (!context->state_getter || !context->outbound_handler || !context->detach_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ } else {
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Destructor for an existing SCTP redirect transport.
+ */
+static void rawrtc_sctp_redirect_transport_destroy(void* arg) {
+ struct rawrtc_sctp_redirect_transport* const transport = arg;
+
+ // Stop transport
+ rawrtc_sctp_redirect_transport_stop(transport);
+
+ // Call 'destroyed' handler (if fully initialised)
+ if (transport->flags & RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_INITIALIZED &&
+ transport->context.destroyed_handler) {
+ transport->context.destroyed_handler(transport->context.arg);
+ }
+
+ // Un-reference
+ mem_deref(transport->buffer);
+}
+
+/*
+ * Create an SCTP redirect transport from an external DTLS transport.
+ * `*transportp` must be unreferenced.
+ *
+ * `redirect_ip` must be a IPv4 address.
+ *
+ * Important: The redirect transport requires to be run inside re's
+ * event loop (`re_main`).
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_create_from_external(
+ struct rawrtc_sctp_redirect_transport** const transportp, // de-referenced
+ struct rawrtc_sctp_transport_context* const context, // copied
+ 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;
+ struct sa redirect_address;
+ enum rawrtc_external_dtls_transport_state dtls_transport_state;
+ struct rawrtc_sctp_redirect_transport* transport;
+
+ // Check arguments
+ if (!transportp || !context || !redirect_ip || redirect_port == 0) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure it's an IPv4 address
+ error = rawrtc_error_to_code(sa_set_str(&redirect_address, redirect_ip, redirect_port));
+ if (error) {
+ return error;
+ }
+ if (sa_af(&redirect_address) != AF_INET) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure the context contains all necessary callbacks
+ error = validate_context(context);
+ if (error) {
+ return error;
+ }
+
+ // Check DTLS transport state
+ error = context->state_getter(&dtls_transport_state, context->arg);
+ if (error) {
+ DEBUG_WARNING(
+ "Getting external DTLS transport state failed: %s\n", rawrtc_code_to_str(error));
+ return RAWRTC_CODE_EXTERNAL_ERROR;
+ }
+ if (dtls_transport_state == RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CLOSED_OR_FAILED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Allocate
+ transport = mem_zalloc(sizeof(*transport), rawrtc_sctp_redirect_transport_destroy);
+ if (!transport) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ transport->context = *context;
+ transport->flags = 0;
+ transport->state = RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_NEW;
+ transport->local_port = port > 0 ? port : RAWRTC_SCTP_REDIRECT_TRANSPORT_DEFAULT_PORT;
+ transport->redirect_address = redirect_address;
+ transport->state_change_handler = state_change_handler;
+ transport->arg = arg;
+
+ // Create buffer
+ transport->buffer = mbuf_alloc(RAWRTC_SCTP_REDIRECT_TRANSPORT_RAW_SOCKET_RECEIVE_SIZE);
+ if (!transport->buffer) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+
+ // Create raw socket
+ transport->socket = socket(AF_INET, SOCK_RAW, IPPROTO_SCTP);
+ if (transport->socket == -1) {
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Set non-blocking
+ error = rawrtc_error_to_code(net_sockopt_blocking_set(transport->socket, false));
+ if (error) {
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(transport);
+ } else {
+ // Set pointer & mark as initialised
+ *transportp = transport;
+ transport->flags |= RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_INITIALIZED;
+ }
+ return error;
+}
+
+/*
+ * Start an SCTP redirect transport.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_start(
+ struct rawrtc_sctp_redirect_transport* const transport,
+ struct rawrtc_sctp_capabilities const* const remote_capabilities, // copied
+ uint16_t remote_port // zeroable
+) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !remote_capabilities) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_NEW) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set default port (if 0)
+ if (remote_port == 0) {
+ remote_port = transport->local_port;
+ }
+
+ // Store remote port
+ transport->remote_port = remote_port;
+
+ // Listen on raw socket
+ error =
+ rawrtc_error_to_code(fd_listen(transport->socket, FD_READ, redirect_from_raw, transport));
+ if (error) {
+ goto out;
+ }
+
+ // Update state & done
+ set_state(transport, RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_OPEN);
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ // Stop listening on raw socket
+ fd_close(transport->socket);
+ }
+ return error;
+}
+
+/*
+ * Stop and close the SCTP redirect transport.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_stop(
+ struct rawrtc_sctp_redirect_transport* const transport) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->flags & RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_DETACHED ||
+ transport->state == RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Update state
+ set_state(transport, RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_CLOSED);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Feed inbound data to the SCTP redirect transport (that will be sent
+ * out via the raw socket).
+ *
+ * `buffer` contains the data to be fed to the raw transport. Since
+ * the data is not going to be referenced, you can pass a *fake* `mbuf`
+ * structure that hasn't been allocated with `mbuf_alloc` to avoid
+ * copying.
+ *
+ * Return `RAWRTC_CODE_INVALID_STATE` in case the transport is closed.
+ * In case the buffer could not be sent due to the raw socket's buffer
+ * being too full, `RAWRTC_CODE_TRY_AGAIN_LATER` will be returned. You
+ * can safely ignore this code since SCTP will retransmit data on a
+ * reliable stream.
+ * Otherwise, `RAWRTC_CODE_SUCCESS` is being returned.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_feed_inbound(
+ struct rawrtc_sctp_redirect_transport* const transport, struct mbuf* const buffer) {
+ ssize_t length;
+
+ // Check arguments
+ if (!transport || !buffer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_DETACHED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_OPEN) {
+ // Note: Silently ignore inbound packets received as long as the transport is not open.
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Update SCTP header with changed ports
+ patch_sctp_header(buffer, transport->local_port, sa_port(&transport->redirect_address));
+
+ // Send over raw socket
+ DEBUG_PRINTF(
+ "Redirecting message (%zu bytes) to %J\n", mbuf_get_left(buffer),
+ &transport->redirect_address);
+ length = sendto(
+ transport->socket, mbuf_buf(buffer), mbuf_get_left(buffer), 0,
+ &transport->redirect_address.u.sa, transport->redirect_address.len);
+ if (length == -1) {
+ switch (errno) {
+ case EAGAIN:
+#if (defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ // We're just dropping the packets in this case. SCTP will retransmit eventually.
+ return RAWRTC_CODE_TRY_AGAIN_LATER;
+ default:
+ DEBUG_WARNING("Unable to redirect message: %m\n", errno);
+ return rawrtc_error_to_code(errno);
+ }
+ }
+
+ // Advance buffer & done
+ mbuf_advance(buffer, length);
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/sctp_redirect_transport/transport.h b/src/sctp_redirect_transport/transport.h
new file mode 100644
index 0000000..f5998ce
--- /dev/null
+++ b/src/sctp_redirect_transport/transport.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <rawrtcdc/external.h>
+#include <rawrtcdc/sctp_redirect_transport.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+
+enum {
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_DEFAULT_PORT = 5000,
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_RAW_SOCKET_RECEIVE_SIZE = 8192,
+};
+
+/*
+ * SCTP redirect transport flags.
+ */
+enum {
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_INITIALIZED = 1 << 0,
+ // The detached flag is virtually identical to the 'closed' state but is applied before the
+ // detach handler is being called. Thus, any other functions should check for the detached flag
+ // instead of checking for the 'closed' state since that is being set at a later stage.
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_FLAGS_DETACHED = 1 << 1,
+};
+
+/*
+ * SCTP redirect transport.
+ */
+struct rawrtc_sctp_redirect_transport {
+ struct rawrtc_sctp_transport_context context;
+ uint_fast8_t flags;
+ enum rawrtc_sctp_redirect_transport_state state;
+ uint16_t local_port;
+ uint16_t remote_port;
+ struct sa redirect_address;
+ rawrtc_sctp_redirect_transport_state_change_handler state_change_handler; // nullable
+ void* arg; // nullable
+ struct mbuf* buffer;
+ int socket;
+};
diff --git a/src/sctp_redirect_transport/utils.c b/src/sctp_redirect_transport/utils.c
new file mode 100644
index 0000000..478ce7f
--- /dev/null
+++ b/src/sctp_redirect_transport/utils.c
@@ -0,0 +1,18 @@
+#include <rawrtcdc/sctp_redirect_transport.h>
+
+/*
+ * Get the corresponding name for an SCTP redirect transport state.
+ */
+char const* rawrtc_sctp_redirect_transport_state_to_name(
+ enum rawrtc_sctp_redirect_transport_state const state) {
+ switch (state) {
+ case RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_NEW:
+ return "new";
+ case RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_OPEN:
+ return "open";
+ case RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
diff --git a/src/sctp_transport/attributes.c b/src/sctp_transport/attributes.c
new file mode 100644
index 0000000..6bc582c
--- /dev/null
+++ b/src/sctp_transport/attributes.c
@@ -0,0 +1,139 @@
+#include "transport.h"
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/sctp_capabilities.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+
+/*
+ * Get the current state of the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_state(
+ enum rawrtc_sctp_transport_state* const statep, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!statep || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state & done
+ *statep = transport->state;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the local port of the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_port(
+ uint16_t* const portp, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!portp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set port & done
+ *portp = transport->port;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the number of streams allocated for the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_n_streams(
+ uint16_t* const n_streamsp, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!n_streamsp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set #streams & done
+ *n_streamsp = (uint16_t) transport->n_channels;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the local SCTP transport capabilities (static).
+ * `*capabilitiesp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_capabilities(
+ struct rawrtc_sctp_capabilities** const capabilitiesp // de-referenced
+) {
+ return rawrtc_sctp_capabilities_create(capabilitiesp, RAWRTC_SCTP_TRANSPORT_MAX_MESSAGE_SIZE);
+}
+
+/*
+ * Set the SCTP transport's data channel handler.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_data_channel_handler(
+ struct rawrtc_sctp_transport* const transport,
+ rawrtc_data_channel_handler const data_channel_handler // nullable
+) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set data channel handler & done
+ transport->data_channel_handler = data_channel_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the SCTP transport's data channel handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_data_channel_handler(
+ rawrtc_data_channel_handler* const data_channel_handlerp, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!data_channel_handlerp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get message handler (if any)
+ if (transport->data_channel_handler) {
+ *data_channel_handlerp = transport->data_channel_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
+
+/*
+ * Set the SCTP transport's state change handler.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_state_change_handler(
+ struct rawrtc_sctp_transport* const transport,
+ rawrtc_sctp_transport_state_change_handler const state_change_handler // nullable
+) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set data channel handler & done
+ transport->state_change_handler = state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the SCTP transport's state change handler.
+ * Returns `RAWRTC_CODE_NO_VALUE` in case no handler has been set.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_state_change_handler(
+ rawrtc_sctp_transport_state_change_handler* const state_change_handlerp, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!state_change_handlerp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get message handler (if any)
+ if (transport->state_change_handler) {
+ *state_change_handlerp = transport->state_change_handler;
+ return RAWRTC_CODE_SUCCESS;
+ } else {
+ return RAWRTC_CODE_NO_VALUE;
+ }
+}
diff --git a/src/sctp_transport/meson.build b/src/sctp_transport/meson.build
new file mode 100644
index 0000000..0227c31
--- /dev/null
+++ b/src/sctp_transport/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'transport.c',
+ 'utils.c',
+])
diff --git a/src/sctp_transport/transport.c b/src/sctp_transport/transport.c
new file mode 100644
index 0000000..46e79b2
--- /dev/null
+++ b/src/sctp_transport/transport.c
@@ -0,0 +1,3328 @@
+#include "transport.h"
+#include "../crc32c/crc32c.h"
+#include "../data_channel/channel.h"
+#include "../data_channel_parameters/parameters.h"
+#include "../data_transport/transport.h"
+#include "../main/main.h"
+#include "../sctp_capabilities/capabilities.h"
+#include <rawrtcdc/config.h>
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/data_transport.h>
+#include <rawrtcdc/external.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/message_buffer.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <usrsctp.h>
+#include <errno.h> // errno
+#include <limits.h> // INT_MAX
+#include <netinet/in.h> // IPPROTO_SCTP, htons, ntohs
+#include <stdio.h> // fopen, ...
+#include <string.h> // memcpy, strlen
+#include <sys/socket.h> // AF_CONN, SOCK_STREAM, linger
+
+#define DEBUG_MODULE "sctp-transport"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#include <rawrtcc/debug.h>
+
+// TODO: Get from config
+uint8_t checksum_flags = RAWRTC_SCTP_TRANSPORT_CHECKSUM_ENABLE_ALL;
+
+// SCTP outgoing message context (needed when buffering)
+struct send_context {
+ unsigned int info_type;
+ union {
+ struct sctp_sndinfo sndinfo;
+ struct sctp_sendv_spa spa;
+ } info;
+ int flags;
+};
+
+// Events to subscribe to
+static uint16_t const sctp_events[] = {
+ SCTP_ASSOC_CHANGE,
+ // SCTP_PEER_ADDR_CHANGE,
+ // SCTP_REMOTE_ERROR,
+ SCTP_PARTIAL_DELIVERY_EVENT,
+ SCTP_SEND_FAILED_EVENT,
+ SCTP_SENDER_DRY_EVENT,
+ SCTP_SHUTDOWN_EVENT,
+ // SCTP_ADAPTATION_INDICATION,
+ SCTP_STREAM_CHANGE_EVENT,
+ SCTP_STREAM_RESET_EVENT,
+};
+static size_t const sctp_events_length = ARRAY_SIZE(sctp_events);
+
+static enum rawrtc_code channel_context_create(
+ struct rawrtc_sctp_data_channel_context** const contextp, // de-referenced, not checked
+ uint16_t const sid,
+ bool const can_send_unordered);
+
+static bool channel_registered(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel // not checked
+);
+
+static void channel_register(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel, // referenced, not checked
+ struct rawrtc_sctp_data_channel_context* const context, // referenced, not checked
+ bool const raise_event);
+
+static enum rawrtc_code sctp_transport_send(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct mbuf* const buffer, // not checked
+ void* const info, // not checked
+ socklen_t const info_size,
+ unsigned int const info_type,
+ int const flags);
+
+/*
+ * Parse a data channel open message.
+ */
+static enum rawrtc_code data_channel_open_message_parse(
+ struct rawrtc_data_channel_parameters** const parametersp, // de-referenced, not checked
+ uint_fast16_t* const priorityp, // de-referenced, not checked
+ uint16_t const id,
+ struct mbuf* const buffer // not checked
+) {
+ uint_fast8_t channel_type;
+ uint_fast16_t priority;
+ uint_fast32_t reliability_parameter;
+ uint_fast16_t label_length;
+ uint_fast16_t protocol_length;
+ int err;
+ char* label = NULL;
+ char* protocol = NULL;
+ enum rawrtc_code error;
+
+ // Check length
+ // Note: -1 because we've already removed the message type
+ if (mbuf_get_left(buffer) < (RAWRTC_DCEP_MESSAGE_OPEN_BASE_SIZE - 1)) {
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+
+ // Get fields
+ channel_type = mbuf_read_u8(buffer);
+ priority = ntohs(mbuf_read_u16(buffer));
+ reliability_parameter = ntohl(mbuf_read_u32(buffer));
+ label_length = ntohs(mbuf_read_u16(buffer));
+ protocol_length = ntohs(mbuf_read_u16(buffer));
+
+ // Validate channel type
+ switch (channel_type) {
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_ORDERED:
+ case RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_UNORDERED:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_RETRANSMIT:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_RETRANSMIT:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_TIMED:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_TIMED:
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+
+ // Get label
+#if (UINT_FAST16_MAX > SIZE_MAX)
+ if (label_length > SIZE_MAX) {
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+#endif
+ if (mbuf_get_left(buffer) < label_length) {
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+ if (label_length > 0) {
+ err = mbuf_strdup(buffer, &label, label_length);
+ if (err) {
+ error = rawrtc_error_to_code(err);
+ goto out;
+ }
+ }
+
+ // Get protocol
+#if (UINT_FAST16_MAX > SIZE_MAX)
+ if (protocol_length > SIZE_MAX) {
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+#endif
+ if (mbuf_get_left(buffer) < protocol_length) {
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+ if (protocol_length > 0) {
+ err = mbuf_strdup(buffer, &protocol, protocol_length);
+ if (err) {
+ error = rawrtc_error_to_code(err);
+ goto out;
+ }
+ }
+
+ // Create data channel parameters
+ error = rawrtc_data_channel_parameters_create_internal(
+ parametersp, label, (enum rawrtc_data_channel_type) channel_type,
+ (uint32_t) reliability_parameter, protocol, false, id);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(label);
+ mem_deref(protocol);
+
+ if (!error) {
+ // Set priority value
+ *priorityp = priority;
+ }
+ return error;
+}
+
+/*
+ * Create a data channel open message.
+ */
+static enum rawrtc_code data_channel_open_message_create(
+ struct mbuf** const bufferp, // de-referenced, not checked
+ struct rawrtc_data_channel_parameters const* const parameters // not checked
+) {
+ size_t label_length;
+ size_t protocol_length;
+ struct mbuf* buffer;
+ int err;
+
+ // Get length of label and protocol
+ label_length = parameters->label ? strlen(parameters->label) : 0;
+ protocol_length = parameters->protocol ? strlen(parameters->protocol) : 0;
+
+ // Check string length
+#if (SIZE_MAX > UINT16_MAX)
+ if (label_length > UINT16_MAX || protocol_length > UINT16_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+#endif
+
+ // Allocate
+ buffer = mbuf_alloc(RAWRTC_DCEP_MESSAGE_OPEN_BASE_SIZE + label_length + protocol_length);
+ if (!buffer) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ err = mbuf_write_u8(buffer, RAWRTC_DCEP_MESSAGE_TYPE_OPEN);
+ err |= mbuf_write_u8(buffer, parameters->channel_type);
+ err |= mbuf_write_u16(buffer, htons(RAWRTC_DCEP_CHANNEL_PRIORITY_NORMAL)); // TODO: Ok?
+ err |= mbuf_write_u32(buffer, htonl(parameters->reliability_parameter));
+ err |= mbuf_write_u16(buffer, htons((uint16_t) label_length));
+ err |= mbuf_write_u16(buffer, htons((uint16_t) protocol_length));
+ if (parameters->label) {
+ err |= mbuf_write_mem(buffer, (uint8_t*) parameters->label, label_length);
+ }
+ if (parameters->protocol) {
+ err |= mbuf_write_mem(buffer, (uint8_t*) parameters->protocol, protocol_length);
+ }
+
+ if (err) {
+ mem_deref(buffer);
+ return rawrtc_error_to_code(err);
+ } else {
+ // Set position
+ mbuf_set_pos(buffer, 0);
+
+ // Set pointer & done
+ *bufferp = buffer;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Create a data channel ack message.
+ */
+static enum rawrtc_code data_channel_ack_message_create(
+ struct mbuf** const bufferp // de-referenced, not checked
+) {
+ int err;
+
+ // Allocate
+ struct mbuf* const buffer = mbuf_alloc(RAWRTC_DCEP_MESSAGE_ACK_BASE_SIZE);
+ if (!buffer) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ err = mbuf_write_u8(buffer, RAWRTC_DCEP_MESSAGE_TYPE_ACK);
+
+ if (err) {
+ mem_deref(buffer);
+ return rawrtc_error_to_code(err);
+ } else {
+ // Set position
+ mbuf_set_pos(buffer, 0);
+
+ // Set pointer & done
+ *bufferp = buffer;
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Dump an SCTP packet into a trace file.
+ */
+static void trace_packet(
+ struct rawrtc_sctp_transport* const transport,
+ void* const buffer,
+ size_t const length,
+ int const direction) {
+ char* dump_buffer;
+
+ // Have trace handle?
+ if (!transport->trace_handle) {
+ return;
+ }
+
+ // Trace (if trace handle)
+ dump_buffer = usrsctp_dumppacket(buffer, length, direction);
+ if (dump_buffer) {
+ fprintf(transport->trace_handle, "%s", dump_buffer);
+ usrsctp_freedumpbuffer(dump_buffer);
+ fflush(transport->trace_handle);
+ }
+}
+
+/*
+ * Send a deferred SCTP message.
+ */
+static bool sctp_send_deferred_message(
+ struct mbuf* const buffer, void* const context, void* const arg) {
+ struct rawrtc_sctp_transport* const transport = arg;
+ struct send_context* const send_context = context;
+ enum rawrtc_code error;
+ void* info;
+ socklen_t info_size;
+
+ // Determine info pointer and info size
+ switch (send_context->info_type) {
+ case SCTP_SENDV_SNDINFO:
+ info = (void*) &send_context->info.sndinfo;
+ info_size = sizeof(send_context->info.sndinfo);
+ break;
+ case SCTP_SENDV_SPA:
+ info = (void*) &send_context->info.spa;
+ info_size = sizeof(send_context->info.spa);
+ break;
+ default:
+ error = RAWRTC_CODE_INVALID_STATE;
+ goto out;
+ }
+
+ // Try sending
+ DEBUG_PRINTF("Sending deferred message\n");
+ error = sctp_transport_send(
+ transport, buffer, info, info_size, send_context->info_type, send_context->flags);
+ switch (error) {
+ case RAWRTC_CODE_TRY_AGAIN_LATER:
+ // Stop iterating through message queue
+ return false;
+ case RAWRTC_CODE_MESSAGE_TOO_LONG:
+ DEBUG_WARNING("Incorrect message size guess, report this!\n");
+ default:
+ goto out;
+ break;
+ }
+
+out:
+ if (error) {
+ DEBUG_WARNING("Could not send buffered message, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Continue iterating through message queue
+ return true;
+}
+
+/*
+ * Send all deferred messages.
+ */
+static enum rawrtc_code sctp_send_deferred_messages(
+ struct rawrtc_sctp_transport* const transport // not checked
+) {
+ // Send buffered outgoing SCTP packets
+ return rawrtc_message_buffer_clear(
+ &transport->buffered_messages_outgoing, sctp_send_deferred_message, transport);
+}
+
+/*
+ * Create outgoing message context for buffering SCTP messages.
+ */
+static enum rawrtc_code message_send_context_create(
+ struct send_context** const contextp, // de-referenced, not checked
+ void* const info, // not checked
+ unsigned int const info_type,
+ int const flags) {
+ enum rawrtc_code error;
+ struct send_context* context;
+
+ // Allocate context
+ context = mem_zalloc(sizeof(*context), NULL);
+ if (!context) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set context fields
+ context->info_type = info_type;
+ context->flags = flags;
+
+ // Copy info data (if any)
+ if (info_type != SCTP_SENDV_NOINFO && info) {
+ // Copy info data according to type
+ // Note: info_size will be ignored for buffered messages
+ switch (info_type) {
+ case SCTP_SENDV_SNDINFO:
+ memcpy(&context->info.sndinfo, info, sizeof(context->info.sndinfo));
+ break;
+ case SCTP_SENDV_SPA:
+ memcpy(&context->info.spa, info, sizeof(context->info.spa));
+ break;
+ default:
+ error = RAWRTC_CODE_INVALID_STATE;
+ goto out;
+ }
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ mem_deref(context);
+ } else {
+ // Set pointer
+ *contextp = context;
+ }
+
+ return error;
+}
+
+/*
+ * Send a message via the SCTP transport.
+ */
+static enum rawrtc_code sctp_send(
+ struct rawrtc_sctp_transport* const transport,
+ struct mbuf* const buffer,
+ void* const info,
+ socklen_t const info_size,
+ unsigned int const info_type,
+ int const flags) {
+ struct send_context* context;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !buffer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Clear buffered amount low flag
+ transport->flags &= ~RAWRTC_SCTP_TRANSPORT_FLAGS_BUFFERED_AMOUNT_LOW;
+
+ // Send directly (if connected and no outstanding messages)
+ if (transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED &&
+ list_isempty(&transport->buffered_messages_outgoing)) {
+ // Try sending
+ DEBUG_PRINTF("Message queue is empty, sending directly\n");
+ error = sctp_transport_send(transport, buffer, info, info_size, info_type, flags);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+ case RAWRTC_CODE_TRY_AGAIN_LATER:
+ DEBUG_PRINTF("Need to buffer message and wait for a write request\n");
+ break;
+ case RAWRTC_CODE_MESSAGE_TOO_LONG:
+ DEBUG_WARNING("Incorrect message size guess, report this!\n");
+ return error;
+ default:
+ return error;
+ }
+ }
+
+ // Create message context (for buffering)
+ error = message_send_context_create(&context, info, info_type, flags);
+ if (error) {
+ goto out;
+ }
+
+ // Buffer message
+ error = rawrtc_message_buffer_append(&transport->buffered_messages_outgoing, buffer, context);
+ if (error) {
+ goto out;
+ }
+ DEBUG_PRINTF("Buffered outgoing message of size %zu\n", mbuf_get_left(buffer));
+
+out:
+ // Un-reference
+ mem_deref(context);
+
+ return error;
+}
+
+/*
+ * Send an SCTP message on the data channel.
+ * TODO: Add EOR marking and some kind of an id (does ndata provide that?)
+ */
+static enum rawrtc_code send_message(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel, // nullable (if DCEP message)
+ struct rawrtc_sctp_data_channel_context* const context, // not checked
+ struct mbuf* const buffer, // not checked
+ uint_fast32_t const ppid) {
+ struct sctp_sendv_spa spa = {0};
+ enum rawrtc_code error;
+
+ // Set stream identifier, protocol identifier and flags
+ spa.sendv_sndinfo.snd_sid = context->sid;
+ spa.sendv_sndinfo.snd_flags = SCTP_EOR; // TODO: Update signature
+ spa.sendv_sndinfo.snd_ppid = htonl((uint32_t) ppid);
+ spa.sendv_flags = SCTP_SEND_SNDINFO_VALID;
+
+ // Set ordered/unordered and partial reliability policy
+ if (ppid != RAWRTC_SCTP_TRANSPORT_PPID_DCEP) {
+ // Check channel
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Unordered?
+ if (channel->parameters->channel_type & RAWRTC_DATA_CHANNEL_TYPE_IS_UNORDERED &&
+ context->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED) {
+ spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
+ }
+
+ // Partial reliability policy
+ switch (ppid) {
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_RETRANSMIT:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_RETRANSMIT:
+ // Set amount of retransmissions
+ spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX;
+ spa.sendv_prinfo.pr_value = channel->parameters->reliability_parameter;
+ spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+ break;
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_TIMED:
+ case RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_TIMED:
+ // Set TTL
+ spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL;
+ spa.sendv_prinfo.pr_value = channel->parameters->reliability_parameter;
+ spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+ break;
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+
+ // Send message
+ DEBUG_PRINTF("Sending message with SID %" PRIu16 ", PPID: %" PRIu32 "\n", context->sid, ppid);
+ error = sctp_send(transport, buffer, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
+ if (error) {
+ DEBUG_WARNING("Unable to send message, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Change the states of all data channels.
+ * Caller MUST ensure that the same state is not set twice.
+ */
+static void set_data_channel_states(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ enum rawrtc_data_channel_state const to_state,
+ enum rawrtc_data_channel_state const* const from_state // optional current state
+) {
+ uint_fast16_t i;
+
+ // Set state on all data channels
+ for (i = 0; i < transport->n_channels; ++i) {
+ struct rawrtc_data_channel* const channel = transport->channels[i];
+ if (!channel) {
+ continue;
+ }
+
+ // Update state
+ if (!from_state || channel->state == *from_state) {
+ rawrtc_data_channel_set_state(channel, to_state);
+ }
+ }
+}
+
+/*
+ * Close all data channels.
+ * Warning: This will not use the closing procedure, use `channel_close_handler` instead.
+ */
+static void close_data_channels(struct rawrtc_sctp_transport* const transport // not checked
+) {
+ uint_fast16_t i;
+
+ // Set state on all data channels
+ for (i = 0; i < transport->n_channels; ++i) {
+ struct rawrtc_data_channel* const channel = transport->channels[i];
+ if (!channel) {
+ continue;
+ }
+
+ // Update state
+ DEBUG_PRINTF("Closing (non-graceful) channel with SID %" PRIu16 "\n", i);
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_CLOSED);
+
+ // Remove from transport
+ transport->channels[i] = mem_deref(channel);
+ }
+}
+
+/*
+ * Change the state of the SCTP transport.
+ * Will call the corresponding handler.
+ * Caller MUST ensure that the same state is not set twice.
+ */
+static void set_state(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ enum rawrtc_sctp_transport_state const state) {
+ // Closed?
+ if (state == RAWRTC_SCTP_TRANSPORT_STATE_CLOSED) {
+ DEBUG_INFO("SCTP connection closed\n");
+
+ // Close all data channels
+ close_data_channels(transport);
+
+ // Mark as detached & detach from DTLS transport
+ transport->flags |= RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED;
+ if (transport->context.detach_handler) {
+ transport->context.detach_handler(transport->context.arg);
+ }
+
+ // Close socket and deregister transport
+ if (transport->socket) {
+ usrsctp_close(transport->socket);
+ usrsctp_deregister_address(transport);
+ transport->socket = NULL;
+ }
+
+ // Close trace file (if any)
+ if (transport->trace_handle) {
+ if (fclose(transport->trace_handle)) {
+ DEBUG_WARNING("Could not close trace file, reason: %m\n", errno);
+ }
+ }
+ }
+
+ // Set state
+ transport->state = state;
+
+ // Connected?
+ // Note: This needs to be done after the state has been updated because it uses the
+ // send function which checks the state.
+ if (state == RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ enum rawrtc_code error;
+ enum rawrtc_data_channel_state const from_channel_state =
+ RAWRTC_DATA_CHANNEL_STATE_CONNECTING;
+ DEBUG_INFO("SCTP connection established\n");
+
+ // Send deferred messages
+ error = sctp_send_deferred_messages(transport);
+ if (error && error != RAWRTC_CODE_STOP_ITERATION) {
+ DEBUG_WARNING(
+ "Could not send deferred messages, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Open waiting channels
+ // Note: This call must be above calling the state handler to prevent the user from
+ // being able to close the transport before the data channels are being opened.
+ set_data_channel_states(transport, RAWRTC_DATA_CHANNEL_STATE_OPEN, &from_channel_state);
+ }
+
+ // Call handler (if any)
+ if (transport->state_change_handler) {
+ transport->state_change_handler(state, transport->arg);
+ }
+}
+
+/*
+ * Reset the outgoing stream of a data channel.
+ * Note: This function will only return an error in case the stream could not be reset properly
+ * In this case, the channel will be closed and removed from the transport immediately.
+ */
+static enum rawrtc_code reset_outgoing_stream(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel // not checked
+) {
+ struct sctp_reset_streams* reset_streams = NULL;
+ size_t length;
+ enum rawrtc_code error;
+
+ // Get context
+ struct rawrtc_sctp_data_channel_context* const context = channel->transport_arg;
+
+ // Check if there are pending outgoing messages
+ if (!list_isempty(&transport->buffered_messages_outgoing)) {
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_STREAM_RESET;
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Calculate length
+ length = sizeof(*reset_streams) + sizeof(uint16_t);
+
+ // Allocate
+ reset_streams = mem_zalloc(length, NULL);
+ if (!reset_streams) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+
+ // Set fields
+ reset_streams->srs_flags = SCTP_STREAM_RESET_OUTGOING;
+ reset_streams->srs_number_streams = 1;
+ reset_streams->srs_stream_list[0] = context->sid;
+
+ // Reset stream
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_RESET_STREAMS, reset_streams,
+ (socklen_t) length)) {
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Done
+ DEBUG_PRINTF("Outgoing stream %" PRIu16 " reset procedure started\n", context->sid);
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ // Un-reference
+ mem_deref(reset_streams);
+
+ if (error) {
+ // Improper closing
+ DEBUG_WARNING(
+ "Could not reset outgoing stream %" PRIu16 ", reason: %s, closing channel "
+ "improperly\n",
+ context->sid, rawrtc_code_to_str(error));
+
+ // Close
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_CLOSED);
+
+ // Sanity check
+ if (!channel_registered(transport, channel)) {
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+
+ // Remove from transport
+ transport->channels[context->sid] = mem_deref(channel);
+ }
+ return error;
+}
+
+#if DEBUG_LEVEL >= 7
+/*
+ * Print debug information for an SCTP association change event.
+ */
+static int debug_association_change_event(
+ struct re_printf* const pf, struct sctp_assoc_change* const event) {
+ int err = 0;
+ uint_fast32_t length;
+ uint_fast32_t i;
+
+ switch (event->sac_state) {
+ case SCTP_COMM_UP:
+ err |= re_hprintf(pf, "SCTP_COMM_UP");
+ break;
+ case SCTP_COMM_LOST:
+ err |= re_hprintf(pf, "SCTP_COMM_LOST");
+ break;
+ case SCTP_RESTART:
+ err |= re_hprintf(pf, "SCTP_RESTART");
+ break;
+ case SCTP_SHUTDOWN_COMP:
+ err |= re_hprintf(pf, "SCTP_SHUTDOWN_COMP");
+ break;
+ case SCTP_CANT_STR_ASSOC:
+ err |= re_hprintf(pf, "SCTP_CANT_STR_ASSOC");
+ break;
+ default:
+ err |= re_hprintf(pf, "???");
+ break;
+ }
+ err |= re_hprintf(
+ pf, ", streams (in/out) = (%" PRIu16 "/%" PRIu16 ")", event->sac_inbound_streams,
+ event->sac_outbound_streams);
+ length = event->sac_length - sizeof(*event);
+ if (length > 0) {
+ switch (event->sac_state) {
+ case SCTP_COMM_UP:
+ case SCTP_RESTART:
+ err |= re_hprintf(pf, ", supports");
+ for (i = 0; i < length; ++i) {
+ switch (event->sac_info[i]) {
+ case SCTP_ASSOC_SUPPORTS_PR:
+ err |= re_hprintf(pf, " PR");
+ break;
+ case SCTP_ASSOC_SUPPORTS_AUTH:
+ err |= re_hprintf(pf, " AUTH");
+ break;
+ case SCTP_ASSOC_SUPPORTS_ASCONF:
+ err |= re_hprintf(pf, " ASCONF");
+ break;
+ case SCTP_ASSOC_SUPPORTS_MULTIBUF:
+ err |= re_hprintf(pf, " MULTIBUF");
+ break;
+ case SCTP_ASSOC_SUPPORTS_RE_CONFIG:
+ err |= re_hprintf(pf, " RE-CONFIG");
+ break;
+ default:
+ err |= re_hprintf(pf, " ??? (0x%02x)", event->sac_info[i]);
+ break;
+ }
+ }
+ break;
+ case SCTP_COMM_LOST:
+ case SCTP_CANT_STR_ASSOC:
+ err |= re_hprintf(pf, ", ABORT =");
+ for (i = 0; i < length; ++i) {
+ err |= re_hprintf(pf, " 0x%02x", event->sac_info[i]);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ err |= re_hprintf(pf, "\n");
+ return err;
+}
+
+/*
+ * Print debug information for an SCTP partial delivery event.
+ */
+static int debug_partial_delivery_event(
+ struct re_printf* const pf, struct sctp_pdapi_event* const event) {
+ int err = 0;
+
+ switch (event->pdapi_indication) {
+ case SCTP_PARTIAL_DELIVERY_ABORTED:
+ re_hprintf(pf, "Partial delivery aborted ");
+ break;
+ default:
+ re_hprintf(pf, "??? ");
+ break;
+ }
+ err |= re_hprintf(pf, "(flags = %x) ", event->pdapi_flags);
+ err |= re_hprintf(pf, "stream = %" PRIu32 " ", event->pdapi_stream);
+ err |= re_hprintf(pf, "sn = %" PRIu32, event->pdapi_seq);
+ err |= re_hprintf(pf, "\n");
+ return err;
+}
+
+/*
+ * Print debug information for an SCTP send failed event.
+ */
+static int debug_send_failed_event(
+ struct re_printf* const pf, struct sctp_send_failed_event* const event) {
+ int err = 0;
+
+ if (event->ssfe_flags & SCTP_DATA_UNSENT) {
+ err |= re_hprintf(pf, "Unsent ");
+ }
+ if (event->ssfe_flags & SCTP_DATA_SENT) {
+ err |= re_hprintf(pf, "Sent ");
+ }
+ if (event->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) {
+ err |= re_hprintf(pf, "(flags = %x) ", event->ssfe_flags);
+ }
+ err |= re_hprintf(
+ pf,
+ "message with PPID %" PRIu32 ", SID = %" PRIu16 ", flags: 0x%04x due to error = 0x%08x\n",
+ ntohl(event->ssfe_info.snd_ppid), event->ssfe_info.snd_sid, event->ssfe_info.snd_flags,
+ event->ssfe_error);
+ return err;
+}
+
+/*
+ * Print debug information for an SCTP stream reset event.
+ */
+static int debug_stream_reset_event(
+ struct re_printf* const pf, struct sctp_stream_reset_event* const event) {
+ int err = 0;
+ uint_fast32_t length;
+ uint_fast32_t i;
+
+ // Get #sid's
+ length = (event->strreset_length - sizeof(*event)) / sizeof(uint16_t);
+
+ err |= re_hprintf(pf, "flags = %x, ", event->strreset_flags);
+ if (event->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+ if (event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+ err |= re_hprintf(pf, "incoming/");
+ }
+ err |= re_hprintf(pf, "incoming ");
+ }
+ if (event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+ err |= re_hprintf(pf, "outgoing ");
+ }
+ err |= re_hprintf(pf, "stream ids = ");
+ for (i = 0; i < length; ++i) {
+ if (i > 0) {
+ err |= re_hprintf(pf, ", ");
+ }
+ err |= re_hprintf(pf, "%" PRIu16, event->strreset_stream_list[i]);
+ }
+ err |= re_hprintf(pf, "\n");
+ return err;
+}
+#endif
+
+/*
+ * Handle SCTP association change event.
+ */
+static void handle_association_change_event(
+ struct rawrtc_sctp_transport* const transport, struct sctp_assoc_change* const event) {
+#if DEBUG_LEVEL >= 7
+ // Print debug output for event
+ DEBUG_PRINTF("Association change: %H", debug_association_change_event, event);
+#endif
+
+ // Handle state
+ switch (event->sac_state) {
+ case SCTP_COMM_UP:
+ // Connected
+ if (transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING) {
+ set_state(transport, RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED);
+ }
+ break;
+ case SCTP_RESTART:
+ // TODO: Handle?
+ break;
+ case SCTP_CANT_STR_ASSOC:
+ case SCTP_SHUTDOWN_COMP:
+ case SCTP_COMM_LOST:
+ // Disconnected
+ if (!(transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED) &&
+ transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CLOSED) {
+ set_state(transport, RAWRTC_SCTP_TRANSPORT_STATE_CLOSED);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Handle SCTP partial delivery event.
+ */
+static void handle_partial_delivery_event(
+ struct rawrtc_sctp_transport* const transport, struct sctp_pdapi_event* const event) {
+ uint16_t sid;
+ struct rawrtc_data_channel* channel;
+ struct rawrtc_sctp_data_channel_context* context;
+
+#if DEBUG_LEVEL >= 7
+ // Print debug output for event
+ DEBUG_PRINTF("Partial delivery event: %H", debug_partial_delivery_event, event);
+#endif
+
+ // Validate stream ID
+ if (event->pdapi_stream >= UINT16_MAX) {
+ DEBUG_WARNING(
+ "Invalid stream id in partial delivery event: %" PRIu32 "\n", event->pdapi_stream);
+ return;
+ }
+ sid = (uint16_t) event->pdapi_stream;
+
+ // Check if channel exists
+ // TODO: Need to check if channel is open?
+ if (sid >= transport->n_channels || !transport->channels[sid]) {
+ DEBUG_NOTICE("No channel registered for sid %" PRIu16 "\n", sid);
+ return;
+ }
+
+ // Get channel and context
+ channel = transport->channels[sid];
+ context = channel->transport_arg;
+
+ // Clear pending incoming message flag
+ context->flags &= ~RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+
+ // Abort pending message
+ if (context->buffer_inbound) {
+ DEBUG_NOTICE(
+ "Abort partially delivered message of %zu bytes\n",
+ mbuf_get_left(context->buffer_inbound));
+ context->buffer_inbound = mem_deref(context->buffer_inbound);
+
+ // Sanity-check
+ if (channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_STREAMED) {
+ DEBUG_WARNING("We deliver partially but there was a buffered message?!\n");
+ }
+ }
+
+ // Pass abort notification to handler
+ if (channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_STREAMED) {
+ enum rawrtc_data_channel_message_flag const message_flags =
+ RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_ABORTED;
+ if (channel->message_handler) {
+ channel->message_handler(NULL, message_flags, channel->arg);
+ } else {
+ DEBUG_NOTICE("No message handler, message abort notification has been discarded\n");
+ }
+ }
+}
+
+/*
+ * Handle SCTP send failed event.
+ */
+static void handle_send_failed_event(
+ struct rawrtc_sctp_transport* const transport, struct sctp_send_failed_event* const event) {
+#if DEBUG_LEVEL >= 7
+ // Print debug output for event
+ DEBUG_PRINTF("Send failed event: %H", debug_send_failed_event, event);
+#endif
+ (void) transport;
+ (void) event;
+}
+
+/*
+ * Raise buffered amount low on a data channel.
+ */
+static void raise_buffered_amount_low_event(
+ struct rawrtc_data_channel* const channel // not checked
+) {
+ // Check for event handler
+ if (channel->buffered_amount_low_handler) {
+ // Get context
+ struct rawrtc_sctp_data_channel_context* const context = channel->transport_arg;
+ (void) context;
+
+ // Raise event
+ DEBUG_PRINTF(
+ "Raising buffered amount low event on channel with SID %" PRIu16 "\n", context->sid);
+ channel->buffered_amount_low_handler(channel->arg);
+ }
+}
+
+/*
+ * Handle SCTP sender dry (no outstanding data) event.
+ */
+static void handle_sender_dry_event(
+ struct rawrtc_sctp_transport* const transport, struct sctp_sender_dry_event* const event) {
+ uint_fast16_t i;
+ uint_fast16_t stop;
+ (void) event;
+
+ // If there are outstanding messages, don't raise an event
+ if (!list_isempty(&transport->buffered_messages_outgoing)) {
+ DEBUG_PRINTF("Pending messages, ignoring sender dry event\n");
+ return;
+ }
+
+ // Set buffered amount low
+ transport->flags |= RAWRTC_SCTP_TRANSPORT_FLAGS_BUFFERED_AMOUNT_LOW;
+
+ // Reset counter if #channels has been reduced
+ if (transport->current_channel_sid >= transport->n_channels) {
+ i = 0;
+ } else {
+ i = transport->current_channel_sid;
+ }
+
+ // Raise event on each data channel
+ stop = i;
+ do {
+ struct rawrtc_data_channel* const channel = transport->channels[i];
+ if (channel) {
+ struct rawrtc_sctp_data_channel_context* const context = channel->transport_arg;
+
+ // Handle flags
+ if (context->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_STREAM_RESET) {
+ // Reset pending outgoing stream
+ // TODO: This should probably be handled earlier but requires having separate
+ // lists for each data channel to be sure that the stream is not reset before
+ // all pending outgoing messages of that channel have been sent.
+ reset_outgoing_stream(transport, channel);
+ context->flags &= ~RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_STREAM_RESET;
+ } else {
+ // Raise event
+ raise_buffered_amount_low_event(transport->channels[i]);
+ }
+ }
+
+ // Update/wrap
+ // Note: uint16 is sufficient here as the maximum number of channels is
+ // 65534, so 65535 will still fit
+ i = (i + 1) % transport->n_channels;
+
+ // Stop if the flag has been cleared
+ if (!(transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_BUFFERED_AMOUNT_LOW)) {
+ break;
+ }
+ } while (i != stop);
+
+ // Update current channel SID
+ transport->current_channel_sid = i;
+}
+
+/*
+ * Handle stream reset event (data channel closed).
+ */
+static void handle_stream_reset_event(
+ struct rawrtc_sctp_transport* const transport, struct sctp_stream_reset_event* const event) {
+ uint_fast32_t length;
+ uint_fast32_t i;
+
+ // Get #sid's
+ length = (event->strreset_length - sizeof(*event)) / sizeof(uint16_t);
+
+#if DEBUG_LEVEL >= 7
+ // Print debug output for event
+ DEBUG_PRINTF("Stream reset event: %H", debug_stream_reset_event, event, length);
+#endif
+
+ // Ignore denied/failed events
+ if (event->strreset_flags & SCTP_STREAM_RESET_DENIED ||
+ event->strreset_flags & SCTP_STREAM_RESET_FAILED) {
+ return;
+ }
+
+ // Handle stream resets
+ for (i = 0; i < length; ++i) {
+ uint_fast16_t const sid = (uint_fast16_t) event->strreset_stream_list[i];
+ struct rawrtc_data_channel* channel;
+ struct rawrtc_sctp_data_channel_context* context;
+
+ // Check if channel exists
+ if (sid >= transport->n_channels || !transport->channels[sid]) {
+ DEBUG_NOTICE("No channel registered for sid %" PRIuFAST16 "\n", sid);
+ continue;
+ }
+
+ // Get channel and context
+ channel = transport->channels[sid];
+ context = channel->transport_arg;
+
+ // Incoming stream reset
+ if (event->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+ // Note: Assuming that we can be sure that all pending incoming messages have been
+ // received at that point.
+
+ // Set flag
+ channel->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_INCOMING_STREAM_RESET;
+
+ // Reset outgoing stream (if needed)
+ if (channel->state != RAWRTC_DATA_CHANNEL_STATE_CLOSING &&
+ channel->state != RAWRTC_DATA_CHANNEL_STATE_CLOSED) {
+ if (reset_outgoing_stream(transport, channel)) {
+ // Error, channel has been closed automatically
+ continue;
+ }
+
+ // Set to closing
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_CLOSING);
+ }
+ }
+
+ // Outgoing stream reset (this is raised from our own stream reset)
+ if (event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+ // Set flag
+ channel->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_OUTGOING_STREAM_RESET;
+ }
+
+ // Close if both incoming and outgoing stream has been reset
+ if (channel->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_INCOMING_STREAM_RESET &&
+ channel->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_OUTGOING_STREAM_RESET) {
+ // Set to closed
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_CLOSED);
+
+ // Remove from transport
+ transport->channels[context->sid] = mem_deref(channel);
+ }
+ }
+}
+
+/*
+ * Handle SCTP notification.
+ */
+static void handle_notification(
+ struct rawrtc_sctp_transport* const transport, struct mbuf* const buffer) {
+ union sctp_notification* const notification = (union sctp_notification*) buffer->buf;
+
+ // TODO: Are all of these checks necessary or can we reduce that?
+#if (SIZE_MAX > UINT32_MAX)
+ if (buffer->end > UINT32_MAX) {
+ return;
+ }
+#endif
+#if (UINT32_MAX > SIZE_MAX)
+ if (notification->sn_header.sn_length > SIZE_MAX) {
+ return;
+ }
+#endif
+ if (notification->sn_header.sn_length != buffer->end) {
+ return;
+ }
+
+ // Handle notification by type
+ switch (notification->sn_header.sn_type) {
+ case SCTP_ASSOC_CHANGE:
+ handle_association_change_event(transport, ¬ification->sn_assoc_change);
+ break;
+ case SCTP_PARTIAL_DELIVERY_EVENT:
+ handle_partial_delivery_event(transport, ¬ification->sn_pdapi_event);
+ break;
+ case SCTP_SEND_FAILED_EVENT:
+ handle_send_failed_event(transport, ¬ification->sn_send_failed_event);
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ handle_sender_dry_event(transport, ¬ification->sn_sender_dry_event);
+ break;
+ case SCTP_SHUTDOWN_EVENT:
+ // TODO: Stop sending (this is a bit tricky to implement, so skipping for now)
+ // handle_shutdown_event(transport, ¬ification->sn_shutdown_event);
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ // TODO: Handle
+ DEBUG_WARNING("TODO: HANDLE STREAM CHANGE\n");
+ // handle_stream_change_event(transport, ...);
+ break;
+ case SCTP_STREAM_RESET_EVENT:
+ handle_stream_reset_event(transport, ¬ification->sn_strreset_event);
+ break;
+ default:
+ DEBUG_WARNING(
+ "Unexpected notification event: %" PRIu16 "\n", notification->sn_header.sn_type);
+ break;
+ }
+}
+
+/*
+ * Handle outgoing SCTP messages.
+ */
+static int sctp_packet_handler(
+ void* arg, void* buffer, size_t length, uint8_t tos, uint8_t set_df) {
+ struct rawrtc_sctp_transport* const transport = arg;
+ struct mbuf mbuffer;
+ enum rawrtc_code error;
+
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED) {
+ DEBUG_PRINTF("Ignoring SCTP packet ready event, transport is detached\n");
+ goto out;
+ }
+
+ // Calculate CRC32-C checksum
+ if (!(checksum_flags & RAWRTC_SCTP_TRANSPORT_CHECKSUM_DISABLE_OUTBOUND)) {
+ if (length >= sizeof(struct sctp_common_header)) {
+ struct sctp_common_header* const header = buffer;
+ // Note: The resulting checksum will be in network byte order
+ header->crc32c = rawrtc_crc32c(buffer, length);
+ } else {
+ DEBUG_WARNING("Outbound packet too short (%zu bytes), please report this!\n", length);
+ goto out;
+ }
+ }
+
+ // Trace (if trace handle)
+ // Note: No need to check if NULL as the function does it for us
+ trace_packet(transport, buffer, length, SCTP_DUMP_OUTBOUND);
+
+ // Note: We want to avoid copying if necessary. If the outbound handler needs to queue the data,
+ // it is required to copy it.
+ mbuffer.buf = buffer;
+ mbuffer.pos = 0;
+ mbuffer.size = length;
+ mbuffer.end = length;
+
+ // Pass to outbound handler
+ error = transport->context.outbound_handler(&mbuffer, tos, set_df, transport->context.arg);
+ if (error) {
+ DEBUG_WARNING("Could not send packet, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+out:
+ // Note: The return code is irrelevant for usrsctp.
+ return 0;
+}
+
+/*
+ * Handle data channel ack message.
+ */
+static void handle_data_channel_ack_message(
+ struct rawrtc_sctp_transport* const transport, struct sctp_rcvinfo* const info) {
+ struct rawrtc_sctp_data_channel_context* context;
+
+ // Get channel and context
+ struct rawrtc_data_channel* const channel = transport->channels[info->rcv_sid];
+ if (!channel) {
+ DEBUG_WARNING("Received ack on an invalid channel with SID %" PRIu16 "\n", info->rcv_sid);
+ goto error;
+ }
+ context = channel->transport_arg;
+
+ // TODO: We should probably track the state and close the channel if an ack is being received
+ // on an already negotiated channel. For now, we only check that the ack is the first
+ // message received (which is fair enough but may not be 100% correct in the future).
+ if (context->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED) {
+ DEBUG_WARNING(
+ "Received ack but channel %" PRIu16 " is already negotiated\n", info->rcv_sid);
+ goto error;
+ }
+
+ // Messages may now be sent unordered
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED;
+ return;
+
+error:
+ // TODO: Reset stream with SID on error
+ return;
+}
+
+/*
+ * Handle data channel open message.
+ */
+static void handle_data_channel_open_message(
+ struct rawrtc_sctp_transport* const transport,
+ struct mbuf* const buffer_in,
+ struct sctp_rcvinfo* const info) {
+ enum rawrtc_code error;
+ enum rawrtc_external_dtls_role dtls_role;
+ struct rawrtc_data_channel_parameters* parameters;
+ uint_fast16_t priority;
+ struct rawrtc_data_transport* data_transport = NULL;
+ struct rawrtc_data_channel* channel = NULL;
+ struct rawrtc_sctp_data_channel_context* context = NULL;
+ struct mbuf* buffer_out = NULL;
+
+ // Get DTLS role
+ error = transport->context.role_getter(&dtls_role, transport->context.arg);
+ if (error) {
+ DEBUG_WARNING("Getting external DTLS role failed: %s\n", rawrtc_code_to_str(error));
+ return;
+ }
+
+ // Check SID corresponds to other peer's role
+ switch (dtls_role) {
+ case RAWRTC_EXTERNAL_DTLS_ROLE_CLIENT:
+ // Other peer must have chosen an odd SID
+ if (info->rcv_sid % 2 != 1) {
+ DEBUG_WARNING("Other peer incorrectly chose an even SID\n");
+ return;
+ }
+ break;
+ case RAWRTC_EXTERNAL_DTLS_ROLE_SERVER:
+ // Other peer must have chosen an even SID
+ if (info->rcv_sid % 2 != 0) {
+ DEBUG_WARNING("Other peer incorrectly chose an odd SID\n");
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+
+ // Check if slot is occupied
+ if (transport->channels[info->rcv_sid]) {
+ DEBUG_WARNING("Other peer chose already occupied SID %" PRIu16 "\n", info->rcv_sid);
+ return;
+ }
+
+ // Get parameters from data channel open message
+ error = data_channel_open_message_parse(¶meters, &priority, info->rcv_sid, buffer_in);
+ if (error) {
+ DEBUG_WARNING("Unable to parse DCEP open message, reason: %s\n", rawrtc_code_to_str(error));
+ return;
+ }
+
+ // Get data transport
+ error = rawrtc_sctp_transport_get_data_transport(&data_transport, transport);
+ if (error) {
+ DEBUG_WARNING("Unable to get data transport, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Create data channel
+ error = rawrtc_data_channel_create_internal(
+ &channel, data_transport, parameters, NULL, NULL, NULL, NULL, NULL, NULL, false);
+ if (error) {
+ DEBUG_WARNING("Unable to create data channel, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Allocate context to be used as an argument for the data channel handlers
+ error = channel_context_create(&context, info->rcv_sid, true);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to create data channel context, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // TODO: Store priority for SCTP ndata,
+ // see https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.4
+ (void) priority;
+
+ // Create ack message
+ buffer_out = NULL;
+ error = data_channel_ack_message_create(&buffer_out);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to create data channel ack message, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Send message
+ DEBUG_PRINTF(
+ "Sending data channel ack message for channel with SID %" PRIu16 "\n", context->sid);
+ error = send_message(transport, NULL, context, buffer_out, RAWRTC_SCTP_TRANSPORT_PPID_DCEP);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to send data channel ack message, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Register data channel
+ channel_register(transport, channel, context, true);
+
+ // TODO: Reset stream with SID on error
+
+out:
+ mem_deref(buffer_out);
+ mem_deref(context);
+ mem_deref(channel);
+ mem_deref(data_transport);
+ mem_deref(parameters);
+}
+
+/*
+ * Buffer incoming messages
+ *
+ * Return `RAWRTC_CODE_SUCCESS` in case the message is complete and
+ * should be handled. Otherwise, return `RAWRTC_CODE_NO_VALUE`.
+ */
+static enum rawrtc_code buffer_message_received_raise_complete(
+ struct mbuf** const buffer_inboundp, // de-referenced, not checked
+ struct sctp_rcvinfo* const info_inboundp, // de-referenced, not checked
+ struct mbuf* const message_buffer, // not checked
+ struct sctp_rcvinfo* const info, // not checked
+ int const flags) {
+ bool const complete = (flags & MSG_EOR) &&
+ info->rcv_ppid != RAWRTC_SCTP_TRANSPORT_PPID_UTF16_PARTIAL &&
+ info->rcv_ppid != RAWRTC_SCTP_TRANSPORT_PPID_BINARY_PARTIAL;
+ enum rawrtc_code error;
+
+ // Reference buffer and copy receive info (if first)
+ if (*buffer_inboundp == NULL) {
+ // Reference & set buffer
+ *buffer_inboundp = mem_ref(message_buffer);
+
+ // Copy receive info
+ memcpy(info_inboundp, info, sizeof(*info));
+
+ // Complete?
+ if (complete) {
+ DEBUG_PRINTF(
+ "Incoming message of size %zu is already complete\n",
+ mbuf_get_left(message_buffer));
+ error = RAWRTC_CODE_SUCCESS;
+ goto out;
+ }
+
+ // Clear headroom (if any)
+ if ((*buffer_inboundp)->pos > 0) {
+ error = rawrtc_error_to_code(mbuf_shift(*buffer_inboundp, -(*buffer_inboundp)->pos));
+ if (error) {
+ goto out;
+ }
+ }
+
+ // Skip to end (for upcoming chunks)
+ mbuf_skip_to_end(*buffer_inboundp);
+ }
+
+ // Copy message into existing buffer
+ error = rawrtc_error_to_code(
+ mbuf_write_mem(*buffer_inboundp, mbuf_buf(message_buffer), mbuf_get_left(message_buffer)));
+ if (error) {
+ goto out;
+ }
+ DEBUG_PRINTF("Buffered incoming message chunk of size %zu\n", mbuf_get_left(message_buffer));
+
+ // Stop (if not last message)
+ if (!complete) {
+ error = RAWRTC_CODE_NO_VALUE;
+ goto out;
+ }
+
+ // Set position & done
+ mbuf_set_pos(*buffer_inboundp, 0);
+ DEBUG_PRINTF("Merged incoming message chunks to size %zu\n", mbuf_get_left(*buffer_inboundp));
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error && error != RAWRTC_CODE_NO_VALUE) {
+ // Discard the message
+ *buffer_inboundp = mem_deref(*buffer_inboundp);
+ }
+ return error;
+}
+
+/*
+ * Handle incoming application data messages.
+ */
+static void handle_application_message(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct mbuf* const buffer, // not checked
+ struct sctp_rcvinfo* info, // not checked
+ int const flags) {
+ enum rawrtc_code error;
+ struct rawrtc_sctp_data_channel_context* context = NULL;
+ enum rawrtc_data_channel_message_flag message_flags = (enum rawrtc_data_channel_message_flag) 0;
+
+ // Get channel and context
+ struct rawrtc_data_channel* const channel = transport->channels[info->rcv_sid];
+ if (!channel) {
+ DEBUG_WARNING(
+ "Received application message on an invalid channel with SID %" PRIu16 "\n",
+ info->rcv_sid);
+ error = RAWRTC_CODE_INVALID_MESSAGE;
+ goto out;
+ }
+ context = channel->transport_arg;
+
+ // Messages may now be sent unordered
+ // TODO: Should we update this flag before or after the message has been received completely
+ // (EOR)? Guessing: Once first chunk has been received.
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED;
+
+ // Handle empty / Buffer if partial delivery is off / deliver directly
+ if (info->rcv_ppid == RAWRTC_SCTP_TRANSPORT_PPID_UTF16_EMPTY ||
+ info->rcv_ppid == RAWRTC_SCTP_TRANSPORT_PPID_BINARY_EMPTY) {
+ // Incomplete empty message?
+ if (!(flags & MSG_EOR)) {
+ DEBUG_WARNING("Empty but incomplete message\n");
+ error = RAWRTC_CODE_INVALID_MESSAGE;
+ goto out;
+ }
+
+ // Reference (because we un-reference at the end and copy info)
+ context->buffer_inbound = mem_ref(buffer);
+
+ // Let the buffer appear to be empty
+ mbuf_skip_to_end(context->buffer_inbound);
+
+ // Empty message is complete
+ context->flags &= ~RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+ message_flags |= RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_COMPLETE;
+
+ } else if (!(channel->flags & RAWRTC_DATA_CHANNEL_FLAGS_STREAMED)) {
+ // Buffer message (if needed) and get complete message (if any)
+ error = buffer_message_received_raise_complete(
+ &context->buffer_inbound, &context->info_inbound, buffer, info, flags);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ // Message buffered, early return here
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+ return;
+ default:
+ DEBUG_WARNING(
+ "Could not buffer/complete application message, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ break;
+ }
+
+ // Update info pointer
+ info = &context->info_inbound;
+
+ // Message is complete
+ context->flags &= ~RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+ message_flags |= RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_COMPLETE;
+ } else {
+ // Partial delivery on, pass buffer directly
+ context->buffer_inbound = mem_ref(buffer);
+
+ // Complete?
+ if (flags & MSG_EOR) {
+ context->flags &= ~RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+ message_flags |= RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_COMPLETE;
+ } else {
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE;
+ }
+ }
+
+ // Handle application message
+ switch (info->rcv_ppid) {
+ case RAWRTC_SCTP_TRANSPORT_PPID_UTF16:
+ case RAWRTC_SCTP_TRANSPORT_PPID_UTF16_EMPTY:
+ case RAWRTC_SCTP_TRANSPORT_PPID_UTF16_PARTIAL:
+ message_flags |= RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_STRING;
+ break;
+ case RAWRTC_SCTP_TRANSPORT_PPID_BINARY:
+ case RAWRTC_SCTP_TRANSPORT_PPID_BINARY_EMPTY:
+ case RAWRTC_SCTP_TRANSPORT_PPID_BINARY_PARTIAL:
+ message_flags |= RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_BINARY;
+ break;
+ default:
+ DEBUG_WARNING(
+ "Ignored incoming message with unknown PPID: %" PRIu32 "\n", info->rcv_ppid);
+ error = RAWRTC_CODE_INVALID_MESSAGE;
+ goto out;
+ break;
+ }
+
+ // Pass message to handler
+ if (channel->message_handler) {
+ channel->message_handler(context->buffer_inbound, message_flags, channel->arg);
+ } else {
+ DEBUG_NOTICE(
+ "No message handler, message of %zu bytes has been discarded\n",
+ mbuf_get_left(context->buffer_inbound));
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to handle application message, reason: %s\n", rawrtc_code_to_str(error));
+
+ // TODO: Reset stream with SID
+ // Note: Correct behaviour of pending inbound message flag depends on closing the
+ // channel on error here!
+ }
+
+ // Un-reference
+ if (context) {
+ context->buffer_inbound = mem_deref(context->buffer_inbound);
+ }
+}
+
+/*
+ * Handle incoming DCEP control message.
+ */
+static void handle_dcep_message(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct mbuf* const buffer, // not checked
+ struct sctp_rcvinfo* info, // not checked
+ int const flags) {
+ enum rawrtc_code error;
+ uint_fast16_t message_type;
+
+ // Buffer message (if needed) and get complete message (if any)
+ error = buffer_message_received_raise_complete(
+ &transport->buffer_dcep_inbound, &transport->info_dcep_inbound, buffer, info, flags);
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ break;
+ case RAWRTC_CODE_NO_VALUE:
+ // Message buffered, early return here
+ return;
+ default:
+ DEBUG_WARNING(
+ "Could not buffer/complete DCEP message, reason: %s\n", rawrtc_code_to_str(error));
+ goto out;
+ break;
+ }
+
+ // Update info pointer
+ info = &transport->info_dcep_inbound;
+
+ // Handle by message type
+ // Note: There MUST be at least a byte present in the buffer as SCTP cannot handle empty
+ // messages.
+ message_type = mbuf_read_u8(transport->buffer_dcep_inbound);
+ switch (message_type) {
+ case RAWRTC_DCEP_MESSAGE_TYPE_ACK:
+ DEBUG_PRINTF(
+ "Received data channel ack message for channel with SID %" PRIu16 "\n",
+ info->rcv_sid);
+ handle_data_channel_ack_message(transport, info);
+ break;
+ case RAWRTC_DCEP_MESSAGE_TYPE_OPEN:
+ DEBUG_PRINTF(
+ "Received data channel open message for channel with SID %" PRIu16 "\n",
+ info->rcv_sid);
+ handle_data_channel_open_message(transport, transport->buffer_dcep_inbound, info);
+ break;
+ default:
+ DEBUG_WARNING(
+ "Ignored incoming DCEP control message with unknown type: %" PRIu16 "\n",
+ message_type);
+ break;
+ }
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ DEBUG_WARNING("Unable to handle DCEP message, reason: %s\n", rawrtc_code_to_str(error));
+
+ // TODO: Close channel?
+ }
+
+ // Un-reference
+ transport->buffer_dcep_inbound = mem_deref(transport->buffer_dcep_inbound);
+}
+
+/*
+ * Handle incoming data message.
+ */
+static void data_receive_handler(
+ struct rawrtc_sctp_transport* const transport,
+ struct mbuf* const buffer,
+ struct sctp_rcvinfo* const info,
+ int const flags) {
+ // Convert PPID first
+ info->rcv_ppid = ntohl(info->rcv_ppid);
+ DEBUG_PRINTF(
+ "Received message with SID %" PRIu16 ", PPID: %" PRIu32 "\n", info->rcv_sid,
+ info->rcv_ppid);
+
+ // Handle by PPID
+ if (info->rcv_ppid == RAWRTC_SCTP_TRANSPORT_PPID_DCEP) {
+ handle_dcep_message(transport, buffer, info, flags);
+ } else {
+ handle_application_message(transport, buffer, info, flags);
+ }
+}
+
+/*
+ * Handle usrsctp read event.
+ */
+static int read_event_handler(struct rawrtc_sctp_transport* const transport // not checked
+) {
+ struct mbuf* buffer;
+ ssize_t length;
+ int ignore_events = RAWRTC_SCTP_EVENT_NONE;
+ struct sctp_rcvinfo info = {0};
+ socklen_t info_length = sizeof(info);
+ unsigned int info_type = 0;
+ int flags = 0;
+
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED) {
+ DEBUG_PRINTF("Ignoring read event, transport is detached\n");
+ return RAWRTC_SCTP_EVENT_ALL;
+ }
+
+ // TODO: Get next message size
+
+ // Create buffer
+ buffer = mbuf_alloc(rawrtcdc_global.usrsctp_chunk_size);
+ if (!buffer) {
+ DEBUG_WARNING("Cannot allocate buffer, no memory");
+ // TODO: This needs to be handled in a better way, otherwise it's probably going
+ // to cause another read call which calls this handler again resulting in an infinite
+ // loop.
+ return RAWRTC_SCTP_EVENT_NONE;
+ }
+
+ // Receive notification or data
+ length = usrsctp_recvv(
+ transport->socket, buffer->buf, buffer->size, NULL, NULL, &info, &info_length, &info_type,
+ &flags);
+ if (length < 0) {
+ switch (errno) {
+ case EAGAIN:
+#if (defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ ignore_events = SCTP_EVENT_READ;
+ break;
+ default:
+ // Handle error
+ DEBUG_WARNING("SCTP receive failed, reason: %m\n", errno);
+ // TODO: What now? Close?
+ break;
+ }
+ goto out;
+ }
+
+ // Update buffer position and end
+ mbuf_set_end(buffer, (size_t) length);
+
+ // Handle notification
+ if (flags & MSG_NOTIFICATION) {
+ handle_notification(transport, buffer);
+ goto out;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ DEBUG_WARNING("Ignored incoming data before state 'connected'\n");
+ goto out;
+ }
+
+ // Have info?
+ if (info_type != SCTP_RECVV_RCVINFO) {
+ DEBUG_WARNING("Cannot handle incoming data without SCTP rcvfinfo\n");
+ goto out;
+ }
+
+ // Pass data to handler
+ data_receive_handler(transport, buffer, &info, flags);
+
+out:
+ // Un-reference
+ mem_deref(buffer);
+
+ // Done
+ return ignore_events;
+}
+
+/*
+ * Handle usrsctp write event.
+ */
+static int write_event_handler(struct rawrtc_sctp_transport* const transport // not checked
+) {
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED) {
+ DEBUG_PRINTF("Ignoring write event, transport is detached\n");
+ return RAWRTC_SCTP_EVENT_ALL;
+ }
+
+ // Send all deferred messages (if not already sending)
+ // TODO: Check if this flag is really necessary
+ if (!(transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_SENDING_IN_PROGRESS)) {
+ enum rawrtc_code error;
+
+ // Send
+ transport->flags |= RAWRTC_SCTP_TRANSPORT_FLAGS_SENDING_IN_PROGRESS;
+ error = sctp_send_deferred_messages(transport);
+ transport->flags &= ~RAWRTC_SCTP_TRANSPORT_FLAGS_SENDING_IN_PROGRESS;
+ switch (error) {
+ case RAWRTC_CODE_SUCCESS:
+ case RAWRTC_CODE_STOP_ITERATION:
+ // We either sent all pending messages or could not send all messages, so there's
+ // no reason to react to further write events in this iteration
+ return SCTP_EVENT_WRITE;
+ default:
+ // TODO: What now? Close?
+ DEBUG_WARNING(
+ "Could not send deferred messages, reason: %s\n", rawrtc_code_to_str(error));
+ return SCTP_EVENT_WRITE;
+ }
+ } else {
+ DEBUG_WARNING("Sending still in progress!\n");
+ // TODO: Is this correct?
+ return SCTP_EVENT_WRITE;
+ }
+}
+
+/*
+ * Handle usrsctp error event.
+ */
+static bool error_event_handler(struct rawrtc_sctp_transport* const transport // not checked
+) {
+ // TODO: If we want to do anything with the DTLS transport, we need to check if we are
+ // detached already.
+
+ // Closed?
+ if (transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CLOSED) {
+ DEBUG_PRINTF("Ignoring error event, transport is closed\n");
+ return RAWRTC_SCTP_EVENT_ALL;
+ }
+
+ // TODO: What am I supposed to do with this information?
+ DEBUG_WARNING("TODO: Handle SCTP error event\n");
+
+ // Continue handling events
+ // TODO: Probably depends on the error, right?
+ return RAWRTC_SCTP_EVENT_NONE;
+}
+
+/*
+ * usrsctp event handler helper.
+ */
+static void upcall_handler_helper(struct socket* socket, void* arg, int flags) {
+ int events = usrsctp_get_events(socket);
+ struct rawrtc_sctp_transport* const transport = arg;
+ int ignore_events = RAWRTC_SCTP_EVENT_NONE;
+ (void) flags; // TODO: What does this indicate?
+
+ // TODO: This loop may lead to long blocking and is unfair to normal fds.
+ // It's a compromise because scheduling repetitive timers in re's event loop seems to
+ // be slow.
+ while (events) {
+ // TODO: This should work but it doesn't because usrsctp keeps switching from read to write
+ // events endlessly for some reason. So, we need to discard previous events.
+ // ignore_events = RAWRTC_SCTP_EVENT_NONE;
+
+ // Handle error event
+ if (events & SCTP_EVENT_ERROR) {
+ ignore_events |= error_event_handler(transport);
+ }
+
+ // Handle read event
+ if (events & SCTP_EVENT_READ) {
+ ignore_events |= read_event_handler(transport);
+ }
+
+ // Handle write event
+ if (events & SCTP_EVENT_WRITE) {
+ ignore_events |= write_event_handler(transport);
+ }
+
+ // Get upcoming events and remove events that should be ignored
+ events = usrsctp_get_events(socket) & ~ignore_events;
+ }
+}
+
+/*
+ * Destructor for an existing SCTP data channel array.
+ */
+static void data_channels_destroy(void* arg) {
+ struct rawrtc_sctp_transport* const transport = arg;
+ uint_fast16_t i;
+
+ // Un-reference all members
+ for (i = 0; i < transport->n_channels; ++i) {
+ mem_deref(transport->channels[i]);
+ }
+}
+
+/*
+ * Create SCTP data channel array.
+ *
+ * Warning: Will not pre-fill stream IDs of the members!
+ */
+static enum rawrtc_code data_channels_alloc(
+ struct rawrtc_data_channel*** channelsp, // de-referenced
+ uint_fast16_t const n_channels,
+ uint_fast16_t const n_channels_previously) {
+ size_t i;
+ struct rawrtc_data_channel** channels;
+
+ // Check arguments
+ if (!channelsp) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocated before and #channels is decreasing?
+ if (n_channels_previously > 0 && n_channels < n_channels_previously) {
+ // Ensure we're not removing active data channels
+ for (i = 0; i < n_channels_previously; ++i) {
+ if ((*channelsp)[i]) {
+ return RAWRTC_CODE_STILL_IN_USE;
+ }
+ }
+ }
+
+ // Allocate
+ channels = mem_reallocarray(*channelsp, n_channels, sizeof(*channels), data_channels_destroy);
+ if (!channels) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Initialise
+ // Note: We can safely multiply 'n_channels' with size of the struct as 'mem_reallocarray'
+ // ensures that it does not overflow (returns NULL).
+ if (n_channels > n_channels_previously) {
+ struct rawrtc_data_channel** channels_offset = channels + n_channels_previously;
+ memset(channels_offset, 0, (n_channels - n_channels_previously) * sizeof(*channels));
+ }
+
+ // Set pointer & done
+ *channelsp = channels;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+static enum rawrtc_code validate_context(
+ struct rawrtc_sctp_transport_context* const context // not checked
+) {
+ // Ensure the context contains all necessary callbacks
+ if (!context->role_getter || !context->state_getter || !context->outbound_handler ||
+ !context->detach_handler) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ } else {
+ return RAWRTC_CODE_SUCCESS;
+ }
+}
+
+/*
+ * Destructor for an existing SCTP transport.
+ */
+static void rawrtc_sctp_transport_destroy(void* arg) {
+ struct rawrtc_sctp_transport* const transport = arg;
+
+ // Stop transport
+ // TODO: Check effects in case transport has been destroyed due to error in create
+ rawrtc_sctp_transport_stop(transport);
+
+ // Call 'destroyed' handler (if fully initialised)
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_INITIALIZED &&
+ transport->context.destroyed_handler) {
+ transport->context.destroyed_handler(transport->context.arg);
+ }
+
+ // Un-reference
+ mem_deref(transport->channels);
+ mem_deref(transport->buffer_dcep_inbound);
+ list_flush(&transport->buffered_messages_outgoing);
+
+ // Decrease in-use counter
+ --rawrtcdc_global.usrsctp_initialized;
+
+ // Close usrsctp (if needed)
+ if (rawrtcdc_global.usrsctp_initialized == 0) {
+ // Cancel timer
+ enum rawrtc_code error = rawrtcdc_global.timer_handler(false, 0);
+ if (error) {
+ DEBUG_WARNING("Unable to cancel timer, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Close
+ usrsctp_finish();
+ DEBUG_PRINTF("Closed usrsctp\n");
+ }
+}
+
+/*
+ * Create an SCTP transport from an external DTLS transport.
+ * `*transportp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_sctp_transport_create_from_external(
+ struct rawrtc_sctp_transport** const transportp, // de-referenced
+ struct rawrtc_sctp_transport_context* const context, // copied
+ uint16_t 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;
+ enum rawrtc_external_dtls_transport_state dtls_transport_state;
+ uint_fast16_t n_channels;
+ struct rawrtc_sctp_transport* transport;
+ struct sctp_assoc_value av;
+ struct linger linger_option;
+ struct sctp_event sctp_event = {0};
+ size_t i;
+ int option_value;
+ struct sockaddr_conn peer = {0};
+
+ // Check arguments
+ if (!transportp || !context) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure the context contains all necessary callbacks
+ error = validate_context(context);
+ if (error) {
+ return error;
+ }
+
+ // Check DTLS transport state
+ error = context->state_getter(&dtls_transport_state, context->arg);
+ if (error) {
+ DEBUG_WARNING(
+ "Getting external DTLS transport state failed: %s\n", rawrtc_code_to_str(error));
+ return RAWRTC_CODE_EXTERNAL_ERROR;
+ }
+ if (dtls_transport_state == RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CLOSED_OR_FAILED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set number of channels
+ // TODO: Get from config or remove this option
+ n_channels = RAWRTC_SCTP_TRANSPORT_DEFAULT_NUMBER_OF_STREAMS;
+
+ // Set default port (if 0)
+ if (port == 0) {
+ port = RAWRTC_SCTP_TRANSPORT_DEFAULT_PORT;
+ }
+
+ // Initialise usrsctp (if needed)
+ if (rawrtcdc_global.usrsctp_initialized == 0) {
+ DEBUG_PRINTF("Initialising usrsctp\n");
+ usrsctp_init(0, sctp_packet_handler, dbg_info);
+
+ // TODO: Debugging depending on options
+#if DEBUG_LEVEL >= 7 && defined(SCTP_DEBUG)
+ usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+
+ // Do not send ABORTs in response to INITs (1).
+ // Do not send ABORTs for received Out of the Blue packets (2).
+ usrsctp_sysctl_set_sctp_blackhole(2);
+
+ // Disable the Explicit Congestion Notification extension
+ // TODO: Do we want to re-enable this?
+ usrsctp_sysctl_set_sctp_ecn_enable(0);
+
+ // Disable the Address Reconfiguration extension
+ usrsctp_sysctl_set_sctp_asconf_enable(0);
+
+ // Disable the Authentication extension
+ usrsctp_sysctl_set_sctp_auth_enable(0);
+
+ // Disable the NR-SACK extension (not standardised)
+ usrsctp_sysctl_set_sctp_nrsack_enable(0);
+
+ // Disable the Packet Drop Report extension (not standardised)
+ usrsctp_sysctl_set_sctp_pktdrop_enable(0);
+
+ // Enable the Partial Reliability extension
+ usrsctp_sysctl_set_sctp_pr_enable(1);
+
+ // Set amount of incoming streams
+ usrsctp_sysctl_set_sctp_nr_incoming_streams_default((uint32_t) n_channels);
+
+ // Set amount of outgoing streams
+ usrsctp_sysctl_set_sctp_nr_outgoing_streams_default((uint32_t) n_channels);
+
+ // Enable interleaving messages for different streams (incoming)
+ // See: https://tools.ietf.org/html/rfc6458#section-8.1.20
+ usrsctp_sysctl_set_sctp_default_frag_interleave(2);
+
+ // Disable default CRC32-C checksum calculation
+ // Note: We may or may not calculate and verify the checksum depending on the
+ // configuration.
+ usrsctp_enable_crc32c_offload();
+ }
+
+ // Allocate
+ transport = mem_zalloc(sizeof(*transport), rawrtc_sctp_transport_destroy);
+ if (!transport) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Increase in-use counter
+ // Note: This needs to be below allocation to ensure the counter is decreased properly on error
+ ++rawrtcdc_global.usrsctp_initialized;
+
+ // Set fields/reference
+ transport->context = *context;
+ transport->flags = 0;
+ transport->state = RAWRTC_SCTP_TRANSPORT_STATE_NEW; // TODO: Raise state (delayed)?
+ transport->port = port;
+ transport->data_channel_handler = data_channel_handler;
+ transport->state_change_handler = state_change_handler;
+ transport->arg = arg;
+ list_init(&transport->buffered_messages_outgoing);
+
+ // Start timer (if needed)
+ if (rawrtcdc_global.usrsctp_initialized == 1) {
+ error = rawrtcdc_global.timer_handler(
+ true, (uint_fast16_t) RAWRTC_SCTP_TRANSPORT_TIMER_TIMEOUT);
+ if (error) {
+ DEBUG_WARNING("Scheduling timer failed: %s\n", rawrtc_code_to_str(error));
+ error = RAWRTC_CODE_EXTERNAL_ERROR;
+ goto out;
+ }
+ }
+
+ // Allocate channel array
+ error = data_channels_alloc(&transport->channels, n_channels, 0);
+ if (error) {
+ goto out;
+ }
+ transport->n_channels = n_channels;
+ transport->current_channel_sid = 0;
+
+ // Create packet tracer
+ // TODO: Debug mode only, filename set by debug options
+ if (context->trace_packets) {
+ char trace_handle_id[8];
+ char* trace_handle_name;
+
+ // Create trace handle ID
+ rand_str(trace_handle_id, sizeof(trace_handle_id));
+ error = rawrtc_sdprintf(&trace_handle_name, "trace-sctp-%s.hex", trace_handle_id);
+ if (error) {
+ DEBUG_WARNING("Could create trace file name, reason: %s\n", rawrtc_code_to_str(error));
+ } else {
+ // Open trace file
+ transport->trace_handle = fopen(trace_handle_name, "w");
+ mem_deref(trace_handle_name);
+ if (!transport->trace_handle) {
+ DEBUG_WARNING("Could not open trace file, reason: %m\n", errno);
+ } else {
+ DEBUG_INFO("Using trace handle id: %s\n", trace_handle_id);
+ }
+ }
+ }
+
+ // Create SCTP socket
+ DEBUG_PRINTF("Creating SCTP socket\n");
+ transport->socket = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL);
+ if (!transport->socket) {
+ DEBUG_WARNING("Could not create socket, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Register instance
+ usrsctp_register_address(transport);
+
+ // Make socket non-blocking
+ if (usrsctp_set_non_blocking(transport->socket, 1)) {
+ DEBUG_WARNING("Could not set to non-blocking, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Set event callback
+ if (usrsctp_set_upcall(transport->socket, upcall_handler_helper, transport)) {
+ DEBUG_WARNING("Could not set event callback (upcall), reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Determine chunk size
+ if (rawrtcdc_global.usrsctp_initialized == 1) {
+ socklen_t option_size = sizeof(int); // PD point is int according to spec
+ if (usrsctp_getsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_PARTIAL_DELIVERY_POINT, &option_value,
+ &option_size)) {
+ DEBUG_WARNING("Could not retrieve partial delivery point, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Check value
+ if (option_size != sizeof(int) || option_value < 1) {
+ DEBUG_WARNING("Invalid partial delivery point value: %d\n", option_value);
+ error = RAWRTC_CODE_INITIALISE_FAIL;
+ goto out;
+ }
+
+ // Store value
+ rawrtcdc_global.usrsctp_chunk_size = (size_t) option_value;
+ DEBUG_PRINTF("Chunk size: %zu\n", rawrtcdc_global.usrsctp_chunk_size);
+ }
+
+ // Enable the Stream Reconfiguration extension
+ av.assoc_id = SCTP_ALL_ASSOC;
+ av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av,
+ sizeof(struct sctp_assoc_value))) {
+ DEBUG_WARNING("Could not enable stream reconfiguration extension, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // We want info
+ option_value = 1;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_RECVRCVINFO, &option_value,
+ sizeof(option_value))) {
+ DEBUG_WARNING("Could not set info option, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // TODO: Enable interleaving messages for different streams (outgoing)
+ // This needs some work: https://github.com/rawrtc/rawrtc-data-channel/issues/14
+ // https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08#section-4.3.1
+ //
+ // av.assoc_id = SCTP_ALL_ASSOC;
+ // av.assoc_value = 1;
+ // if (usrsctp_setsockopt(transport->socket, IPPROTO_SCTP, SCTP_INTERLEAVING_SUPPORTED,
+ // &av, sizeof(struct sctp_assoc_value))) {
+ // DEBUG_WARNING("Could not enable ndata, reason: %m\n", errno);
+ // error = rawrtc_error_to_code(errno);
+ // goto out;
+ // }
+
+ // Discard pending packets when closing
+ // (so we don't get a callback when the transport is already free'd)
+ // TODO: Find a way to use graceful shutdown instead, otherwise the other peer would raise an
+ // error indication (https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.2)
+ linger_option.l_onoff = 1;
+ linger_option.l_linger = 0;
+ if (usrsctp_setsockopt(
+ transport->socket, SOL_SOCKET, SO_LINGER, &linger_option, sizeof(linger_option))) {
+ DEBUG_WARNING("Could not set linger options, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Set no delay option (disable nagle)
+ option_value = 1;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_NODELAY, &option_value, sizeof(option_value))) {
+ DEBUG_WARNING("Could not set no-delay, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Set explicit EOR
+ option_value = 1;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &option_value,
+ sizeof(option_value))) {
+ DEBUG_WARNING("Could not enable explicit EOR, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // Subscribe to SCTP event notifications
+ sctp_event.se_assoc_id = SCTP_ALL_ASSOC;
+ sctp_event.se_on = 1;
+ for (i = 0; i < sctp_events_length; ++i) {
+ sctp_event.se_type = sctp_events[i];
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_EVENT, &sctp_event, sizeof(sctp_event))) {
+ DEBUG_WARNING("Could not subscribe to event notification, reason: %m", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+ }
+
+#if DEBUG_LEVEL >= 7
+ // Print send/receive buffer lengths
+ {
+ uint32_t send_buffer_length;
+ uint32_t receive_buffer_length;
+ error = rawrtc_sctp_transport_get_buffer_length(
+ &send_buffer_length, &receive_buffer_length, transport);
+ if (error) {
+ goto out;
+ }
+ DEBUG_PRINTF(
+ "Send/receive buffer length: %" PRIu32 "/%" PRIu32 "\n", send_buffer_length,
+ receive_buffer_length);
+ }
+
+ // Print congestion control algorithm
+ {
+ enum rawrtc_sctp_transport_congestion_ctrl congestion_ctrl_algorithm;
+ error = rawrtc_sctp_transport_get_congestion_ctrl_algorithm(
+ &congestion_ctrl_algorithm, transport);
+ if (error) {
+ goto out;
+ }
+ DEBUG_PRINTF(
+ "Congestion control algorithm: %s\n",
+ rawrtc_sctp_transport_congestion_ctrl_algorithm_to_name(congestion_ctrl_algorithm));
+ }
+#endif
+
+ // Bind local address
+ peer.sconn_family = AF_CONN;
+ // TODO: Check for existance of sconn_len
+ // sconn.sconn_len = sizeof(peer);
+ peer.sconn_port = htons(transport->port);
+ peer.sconn_addr = transport;
+ if (usrsctp_bind(transport->socket, (struct sockaddr*) &peer, sizeof(peer))) {
+ DEBUG_WARNING("Could not bind local address, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+out:
+ if (error) {
+ mem_deref(transport);
+ } else {
+ // Set pointer & mark as initialised
+ *transportp = transport;
+ transport->flags |= RAWRTC_SCTP_TRANSPORT_FLAGS_INITIALIZED;
+ }
+ return error;
+}
+
+/*
+ * Destructor for an existing data channel context.
+ */
+static void channel_context_destroy(void* arg) {
+ struct rawrtc_sctp_data_channel_context* const context = arg;
+
+ // Un-reference
+ mem_deref(context->buffer_inbound);
+}
+
+/*
+ * Allocate data channel context.
+ */
+static enum rawrtc_code channel_context_create(
+ struct rawrtc_sctp_data_channel_context** const contextp, // de-referenced, not checked
+ uint16_t const sid,
+ bool const can_send_unordered) {
+ // Allocate context
+ struct rawrtc_sctp_data_channel_context* const context =
+ mem_zalloc(sizeof(*context), channel_context_destroy);
+ if (!context) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields
+ context->sid = sid;
+ if (can_send_unordered) {
+ context->flags |= RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED;
+ }
+
+ // Set pointer & done
+ *contextp = context;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Check if a data channel is registered on the transport.
+ */
+static bool channel_registered(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel // not checked
+) {
+ // Get context
+ struct rawrtc_sctp_data_channel_context* const context = channel->transport_arg;
+
+ // Check status
+ if (transport->channels[context->sid] != channel) {
+ DEBUG_WARNING("Invalid channel instance in slot. Please report this.\n");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+/*
+ * Register data channel on transport.
+ */
+static void channel_register(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel* const channel, // referenced, not checked
+ struct rawrtc_sctp_data_channel_context* const context, // referenced, not checked
+ bool const raise_event) {
+ // Update channel with referenced context
+ channel->transport_arg = mem_ref(context);
+ transport->channels[context->sid] = mem_ref(channel);
+
+ // Raise data channel event?
+ if (raise_event) {
+ // Call data channel handler (if any)
+ rawrtc_data_channel_call_channel_handler(
+ channel, transport->data_channel_handler, transport->arg);
+ }
+
+ // Update data channel state
+ if (transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_OPEN);
+ }
+}
+
+/*
+ * Create a negotiated SCTP data channel.
+ */
+static enum rawrtc_code channel_create_negotiated(
+ struct rawrtc_sctp_data_channel_context** const contextp, // de-referenced, not checked
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel_parameters const* const parameters // read-only
+) {
+ // Check SID (> max, >= n_channels, or channel already occupied)
+ if (parameters->id > RAWRTC_SCTP_TRANSPORT_SID_MAX || parameters->id >= transport->n_channels ||
+ transport->channels[parameters->id]) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate context to be used as an argument for the data channel handlers
+ // TODO: Is it okay to already allow sending unordered messages here? Assuming: Yes.
+ return channel_context_create(contextp, parameters->id, true);
+}
+
+/*
+ * Create an SCTP data channel that needs negotiation.
+ */
+static enum rawrtc_code channel_create_inband(
+ struct rawrtc_sctp_data_channel_context** const contextp, // de-referenced, not checked
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct rawrtc_data_channel_parameters const* const parameters // read-only
+) {
+ enum rawrtc_code error;
+ enum rawrtc_external_dtls_transport_state dtls_transport_state;
+ enum rawrtc_external_dtls_role dtls_role;
+ uint_fast16_t i;
+ struct rawrtc_sctp_data_channel_context* context;
+ struct mbuf* buffer;
+
+ // Get DTLS transport state
+ error = transport->context.state_getter(&dtls_transport_state, transport->context.arg);
+ if (error) {
+ DEBUG_WARNING(
+ "Getting external DTLS transport state failed: %s\n", rawrtc_code_to_str(error));
+ return RAWRTC_CODE_EXTERNAL_ERROR;
+ }
+
+ // Check DTLS state
+ // Note: We need to have an open DTLS connection to determine whether we use odd or even
+ // SIDs.
+ // TODO: Can we fix this somehow to make it possible to create data channels earlier?
+ if (dtls_transport_state != RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CONNECTED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Get DTLS role
+ error = transport->context.role_getter(&dtls_role, transport->context.arg);
+ if (error) {
+ DEBUG_WARNING("Getting external DTLS role failed: %s\n", rawrtc_code_to_str(error));
+ return RAWRTC_CODE_EXTERNAL_ERROR;
+ }
+
+ // Use odd or even SIDs
+ switch (dtls_role) {
+ case RAWRTC_EXTERNAL_DTLS_ROLE_CLIENT:
+ i = 0;
+ break;
+ case RAWRTC_EXTERNAL_DTLS_ROLE_SERVER:
+ i = 1;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Find free SID
+ context = NULL;
+ for (; i < transport->n_channels; i += 2) {
+ if (!transport->channels[i]) {
+ // Allocate context to be used as an argument for the data channel handlers
+ error = channel_context_create(&context, (uint16_t) i, false);
+ if (error) {
+ return error;
+ }
+ break;
+ }
+ }
+ if (!context) {
+ return RAWRTC_CODE_INSUFFICIENT_SPACE;
+ }
+
+ // Create open message
+ buffer = NULL;
+ error = data_channel_open_message_create(&buffer, parameters);
+ if (error) {
+ goto out;
+ }
+
+ // Send message
+ DEBUG_PRINTF(
+ "Sending data channel open message for channel with SID %" PRIu16 "\n", context->sid);
+ error = send_message(transport, NULL, context, buffer, RAWRTC_SCTP_TRANSPORT_PPID_DCEP);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(buffer);
+
+ if (error) {
+ mem_deref(context);
+ } else {
+ // Set pointer
+ *contextp = context;
+ }
+ return error;
+}
+
+/*
+ * Create the SCTP data channel.
+ */
+static enum rawrtc_code channel_create_handler(
+ struct rawrtc_data_transport* const transport,
+ struct rawrtc_data_channel* const channel, // referenced
+ struct rawrtc_data_channel_parameters const* const parameters // read-only
+) {
+ struct rawrtc_sctp_transport* sctp_transport;
+ enum rawrtc_code error;
+ struct rawrtc_sctp_data_channel_context* context;
+
+ // Check arguments
+ if (!transport || !channel || !parameters) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Check if closed?
+
+ // Get SCTP transport
+ sctp_transport = transport->transport;
+
+ // Create negotiated or in-band data channel
+ if (parameters->negotiated) {
+ error = channel_create_negotiated(&context, sctp_transport, parameters);
+ } else {
+ error = channel_create_inband(&context, sctp_transport, parameters);
+ }
+ if (error) {
+ return error;
+ }
+
+ // Register data channel
+ channel_register(sctp_transport, channel, context, false);
+
+ // Un-reference & done
+ mem_deref(context);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Close the data channel (transport handler).
+ */
+static enum rawrtc_code channel_close_handler(struct rawrtc_data_channel* const channel) {
+ struct rawrtc_sctp_transport* transport;
+ struct rawrtc_sctp_data_channel_context* context;
+
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Check if closed?
+
+ // Get SCTP transport & context
+ transport = channel->transport->transport;
+ context = channel->transport_arg;
+
+ // Un-reference channel and clear pointer (if channel was registered before)
+ // Note: The context will be NULL if the channel was not registered before
+ if (context) {
+ DEBUG_PRINTF("Closing (graceful) channel with SID %" PRIu16 "\n", context->sid);
+
+ // Sanity check
+ if (!channel_registered(transport, channel)) {
+ return RAWRTC_CODE_UNKNOWN_ERROR;
+ }
+
+ // Reset outgoing streams
+ // Important: This function will change the state of the channel to CLOSED
+ // and remove the channel from the transport on error.
+ if (!reset_outgoing_stream(transport, channel)) {
+ rawrtc_data_channel_set_state(channel, RAWRTC_DATA_CHANNEL_STATE_CLOSING);
+ }
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Send data via the data channel (transport handler).
+ */
+static enum rawrtc_code channel_send_handler(
+ struct rawrtc_data_channel* const channel,
+ struct mbuf* buffer, // nullable (if size 0), referenced
+ bool const is_binary) {
+ struct rawrtc_sctp_transport* transport;
+ size_t length;
+ uint_fast32_t ppid;
+ struct mbuf* empty = NULL;
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // TODO: Check if closed?
+
+ // Get SCTP transport
+ transport = channel->transport->transport;
+
+ // We accept both a NULL buffer and a buffer of length 0
+ if (!buffer) {
+ length = 0;
+ } else {
+ length = mbuf_get_left(buffer);
+ }
+
+ // Empty message?
+ if (length == 0) {
+ // Set PPID
+ if (is_binary) {
+ ppid = RAWRTC_SCTP_TRANSPORT_PPID_BINARY_EMPTY;
+ } else {
+ ppid = RAWRTC_SCTP_TRANSPORT_PPID_UTF16_EMPTY;
+ }
+
+ // Create helper message as SCTP is unable to send messages of size 0
+ empty = mbuf_alloc(RAWRTC_SCTP_TRANSPORT_EMPTY_MESSAGE_SIZE);
+ if (!empty) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Note: The content is being ignored
+ error = rawrtc_error_to_code(mbuf_write_u8(empty, 0));
+ if (error) {
+ goto out;
+ }
+
+ // Set position & pointer
+ mbuf_set_pos(empty, 0);
+ buffer = empty;
+ } else {
+ // Check size
+ if (transport->remote_maximum_message_size != 0 &&
+ length > transport->remote_maximum_message_size) {
+ return RAWRTC_CODE_MESSAGE_TOO_LONG;
+ }
+
+ // Set PPID
+ // Note: We will not use the deprecated fragmentation & reassembly
+ if (is_binary) {
+ ppid = RAWRTC_SCTP_TRANSPORT_PPID_BINARY;
+ } else {
+ ppid = RAWRTC_SCTP_TRANSPORT_PPID_UTF16;
+ }
+ }
+
+ // Send
+ error = send_message(transport, channel, channel->transport_arg, buffer, ppid);
+ if (error) {
+ goto out;
+ }
+
+out:
+ // Un-reference
+ mem_deref(empty);
+
+ // Done
+ return error;
+}
+
+/*
+ * Check if we can enable or disable streaming.
+ */
+static enum rawrtc_code channel_set_streaming_handler(
+ struct rawrtc_data_channel* const channel, bool const on) {
+ struct rawrtc_sctp_data_channel_context* context;
+ (void) on;
+
+ // Check arguments
+ if (!channel) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get context
+ context = channel->transport_arg;
+
+ // Check if there's a pending incoming message
+ if (context->flags & RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE) {
+ return RAWRTC_CODE_STILL_IN_USE;
+ }
+
+ // Ok, streaming mode can be activated
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the SCTP data transport instance.
+ * `*transportp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_data_transport(
+ struct rawrtc_data_transport** const transportp, // de-referenced
+ struct rawrtc_sctp_transport* const sctp_transport // referenced
+) {
+ enum rawrtc_code error;
+ struct rawrtc_data_transport* transport;
+
+ // Check arguments
+ if (!sctp_transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check SCTP transport state
+ if (sctp_transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Create data transport
+ error = rawrtc_data_transport_create(
+ &transport, RAWRTC_DATA_TRANSPORT_TYPE_SCTP, sctp_transport, channel_create_handler,
+ channel_close_handler, channel_send_handler, channel_set_streaming_handler);
+ if (error) {
+ return error;
+ }
+
+ // Set pointer & done
+ *transportp = transport;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Start the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_start(
+ struct rawrtc_sctp_transport* const transport,
+ struct rawrtc_sctp_capabilities const* const remote_capabilities, // copied
+ uint16_t remote_port // zeroable
+) {
+ struct sockaddr_conn peer = {0};
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !remote_capabilities) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_NEW) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set default port (if 0)
+ if (remote_port == 0) {
+ remote_port = transport->port;
+ }
+
+ // Store maximum message size
+ transport->remote_maximum_message_size = remote_capabilities->max_message_size;
+
+ // Set remote address
+ peer.sconn_family = AF_CONN;
+ // TODO: Check for existance of sconn_len
+ // sconn.sconn_len = sizeof(peer);
+ peer.sconn_port = htons(remote_port);
+ peer.sconn_addr = transport;
+
+ // Connect
+ DEBUG_PRINTF("Connecting to peer\n");
+ if (usrsctp_connect(transport->socket, (struct sockaddr*) &peer, sizeof(peer)) &&
+ errno != EINPROGRESS) {
+ DEBUG_WARNING("Could not connect, reason: %m\n", errno);
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // TODO: Initiate Path MTU discovery (https://tools.ietf.org/html/rfc4821)
+ // by using probing messages (https://tools.ietf.org/html/rfc4820)
+ // see https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-5
+
+ // Transition to connecting state
+ set_state(transport, RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING);
+
+ // Set remote address
+ transport->remote_address = peer;
+
+ // Set default MTU
+ error = rawrtc_sctp_transport_set_mtu(transport, (uint32_t) RAWRTC_SCTP_TRANSPORT_DEFAULT_MTU);
+ if (error) {
+ DEBUG_WARNING("Could not set MTU, reason: %s\n", rawrtc_code_to_str(error));
+ // Note: Continuing here since it may still work.
+ }
+
+out:
+ if (error) {
+ set_state(transport, RAWRTC_SCTP_TRANSPORT_STATE_CLOSED);
+ }
+ return error;
+}
+
+/*
+ * Stop and close the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_stop(struct rawrtc_sctp_transport* const transport) {
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED ||
+ transport->state == RAWRTC_SCTP_TRANSPORT_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Update state
+ set_state(transport, RAWRTC_SCTP_TRANSPORT_STATE_CLOSED);
+ return RAWRTC_CODE_SUCCESS;
+
+ // TODO: Anything missing?
+}
+
+/*
+ * Send a message (non-deferred) via the SCTP transport.
+ */
+static enum rawrtc_code sctp_transport_send(
+ struct rawrtc_sctp_transport* const transport, // not checked
+ struct mbuf* const buffer, // not checked
+ void* const info, // not checked
+ socklen_t const info_size,
+ unsigned int const info_type,
+ int const flags) {
+ struct sctp_sndinfo* send_info;
+ bool eor_set;
+ size_t length;
+ ssize_t written;
+ enum rawrtc_code error;
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Get reference to send flags
+ switch (info_type) {
+ case SCTP_SENDV_SNDINFO:
+ send_info = (struct sctp_sndinfo* const) info;
+ break;
+ case SCTP_SENDV_SPA:
+ send_info = &((struct sctp_sendv_spa* const) info)->sendv_sndinfo;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // EOR set?
+ eor_set = send_info->snd_flags & SCTP_EOR ? true : false;
+
+ // Send until buffer is empty
+ do {
+ size_t const left = mbuf_get_left(buffer);
+
+ // Carefully chunk the buffer
+ if (left > rawrtcdc_global.usrsctp_chunk_size) {
+ length = rawrtcdc_global.usrsctp_chunk_size;
+
+ // Unset EOR flag
+ send_info->snd_flags &= ~SCTP_EOR;
+ } else {
+ length = left;
+
+ // Reset EOR flag
+ if (eor_set) {
+ send_info->snd_flags |= SCTP_EOR;
+ }
+ }
+
+ // Send
+ DEBUG_PRINTF("Try sending %zu/%zu bytes\n", length, left);
+ written = usrsctp_sendv(
+ transport->socket, mbuf_buf(buffer), length, NULL, 0, info, info_size, info_type,
+ flags);
+#if DEBUG_LEVEL >= 7
+ DEBUG_PRINTF(
+ "usrsctp_sendv(socket=%p, buffer=%p, length=%zu/%zu, info={sid: %" PRIu16 ", "
+ "ppid: %" PRIu32 ", eor: %s (was %s}) -> %zd (errno: %m)\n",
+ transport->socket, mbuf_buf(buffer), length, left, send_info->snd_sid,
+ ntohl(send_info->snd_ppid), send_info->snd_flags & SCTP_EOR ? "true" : "false",
+ eor_set ? "true" : "false", written, errno);
+#endif
+ if (written < 0) {
+ error = rawrtc_error_to_code(errno);
+ goto out;
+ }
+
+ // TODO: Remove
+ if (written == 0) {
+#if DEBUG_LEVEL >= 7
+ DEBUG_NOTICE("@tuexen: usrsctp_sendv returned 0\n");
+#endif
+ error = RAWRTC_CODE_TRY_AGAIN_LATER;
+ goto out;
+ }
+
+ // If not all bytes have been written, this obviously means that usrsctp's buffer is full
+ // and we need to try again later.
+ if ((size_t) written < length) {
+ // TODO: Comment in and remove section above
+ // error = RAWRTC_CODE_TRY_AGAIN_LATER;
+ // goto out;
+ }
+
+ // Update buffer position
+ mbuf_advance(buffer, written);
+ } while (mbuf_get_left(buffer) > 0);
+
+ // Done
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ // Reset EOR flag
+ if (eor_set) {
+ send_info->snd_flags |= SCTP_EOR;
+ }
+
+ return error;
+}
+
+/*
+ * Feed inbound data to the SCTP transport.
+ *
+ * `buffer` contains the data to be fed to the SCTP transport. Since
+ * the data is not going to be referenced, you can pass a *fake* `mbuf`
+ * structure that hasn't been allocated with `mbuf_alloc` to avoid
+ * copying.
+ * `ecn_bits` are the explicit congestion notification bits to be
+ * passed to usrsctp.
+ *
+ * Return `RAWRTC_CODE_INVALID_STATE` in case the transport is closed.
+ * Otherwise, `RAWRTC_CODE_SUCCESS` is being returned.
+ */
+enum rawrtc_code rawrtc_sctp_transport_feed_inbound(
+ struct rawrtc_sctp_transport* const transport,
+ struct mbuf* const buffer,
+ uint8_t const ecn_bits) {
+ void* raw_buffer;
+ size_t length;
+
+ // Check arguments
+ if (!transport || !buffer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Detached?
+ if (transport->flags & RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Get buffer & length
+ raw_buffer = mbuf_buf(buffer);
+ length = mbuf_get_left(buffer);
+
+ // Trace (if trace handle)
+ // Note: No need to check if NULL as the function does it for us
+ trace_packet(transport, raw_buffer, length, SCTP_DUMP_INBOUND);
+
+ // Verify CRC32-C checksum
+ if (!(checksum_flags & RAWRTC_SCTP_TRANSPORT_CHECKSUM_DISABLE_INBOUND)) {
+ if (length >= sizeof(struct sctp_common_header)) {
+ struct sctp_common_header* const header = raw_buffer;
+ uint32_t const actual_checksum = header->crc32c;
+ uint32_t expected_checksum;
+
+ // Calculate checksum
+ // Note: The field is still in network byte order (even though we don't convert the
+ // zeroes). Furthermore, the result from `rawrtc_crc32c` is also in network byte order.
+ header->crc32c = 0x00000000;
+ expected_checksum = rawrtc_crc32c(raw_buffer, length);
+ header->crc32c = actual_checksum;
+
+ // Verify checksum is correct
+ if (actual_checksum != expected_checksum) {
+ DEBUG_NOTICE(
+ "Inbound packet has invalid CRC32-C checksum, expected=%08x, actual=%08x\n",
+ ntohl(expected_checksum), ntohl(actual_checksum));
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+ } else {
+ DEBUG_WARNING("Inbound packet too short (%zu bytes)!\n", length);
+ return RAWRTC_CODE_INVALID_MESSAGE;
+ }
+ }
+
+ // Feed into SCTP socket
+ DEBUG_PRINTF("Feeding SCTP packet of %zu bytes\n", length);
+ usrsctp_conninput(transport, raw_buffer, length, ecn_bits);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's send and receive buffer length in bytes.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_buffer_length(
+ struct rawrtc_sctp_transport* const transport,
+ uint32_t const send_buffer_length,
+ uint32_t const receive_buffer_length) {
+ int option_value;
+
+ // Check arguments
+ if (!transport || send_buffer_length == 0 || receive_buffer_length == 0 ||
+ send_buffer_length > INT_MAX || receive_buffer_length > INT_MAX) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set length for send/receive buffer
+ option_value = (int) send_buffer_length;
+ if (usrsctp_setsockopt(
+ transport->socket, SOL_SOCKET, SO_SNDBUF, &option_value, sizeof(option_value))) {
+ return rawrtc_error_to_code(errno);
+ }
+ option_value = (int) receive_buffer_length;
+ if (usrsctp_setsockopt(
+ transport->socket, SOL_SOCKET, SO_RCVBUF, &option_value, sizeof(option_value))) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Done
+ DEBUG_PRINTF(
+ "Set send/receive buffer length to %" PRIu32 "/%" PRIu32 "\n", send_buffer_length,
+ receive_buffer_length);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the SCTP transport's send and receive buffer length in bytes.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_buffer_length(
+ uint32_t* const send_buffer_lengthp,
+ uint32_t* const receive_buffer_lengthp,
+ struct rawrtc_sctp_transport* const transport) {
+ int option_value;
+ socklen_t option_size;
+
+ // Check arguments
+ if (!send_buffer_lengthp || !receive_buffer_lengthp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get length of send/receive buffer
+ option_size = sizeof(option_value);
+ if (usrsctp_getsockopt(transport->socket, SOL_SOCKET, SO_SNDBUF, &option_value, &option_size)) {
+ return rawrtc_error_to_code(errno);
+ }
+ *send_buffer_lengthp = (uint32_t) option_value;
+ option_size = sizeof(option_value);
+ if (usrsctp_getsockopt(transport->socket, SOL_SOCKET, SO_RCVBUF, &option_value, &option_size)) {
+ return rawrtc_error_to_code(errno);
+ }
+ *receive_buffer_lengthp = (uint32_t) option_value;
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's congestion control algorithm.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_congestion_ctrl_algorithm(
+ struct rawrtc_sctp_transport* const transport,
+ enum rawrtc_sctp_transport_congestion_ctrl const algorithm) {
+ struct sctp_assoc_value av;
+
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Translate algorithm
+ switch (algorithm) {
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581:
+ av.assoc_value = SCTP_CC_RFC2581;
+ break;
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HSTCP:
+ av.assoc_value = SCTP_CC_HSTCP;
+ break;
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HTCP:
+ av.assoc_value = SCTP_CC_HTCP;
+ break;
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RTCC:
+ av.assoc_value = SCTP_CC_RTCC;
+ break;
+ default:
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set congestion control algorithm
+ av.assoc_id = SCTP_ALL_ASSOC;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_PLUGGABLE_CC, &av,
+ sizeof(struct sctp_assoc_value))) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Done
+ DEBUG_PRINTF(
+ "Set congestion control algorithm to %s\n",
+ rawrtc_sctp_transport_congestion_ctrl_algorithm_to_name(algorithm));
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the current SCTP transport's congestion control algorithm.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_congestion_ctrl_algorithm(
+ enum rawrtc_sctp_transport_congestion_ctrl* const algorithmp,
+ struct rawrtc_sctp_transport* const transport) {
+ struct sctp_assoc_value av;
+ socklen_t option_size;
+
+ // Check arguments
+ if (!algorithmp || !transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get congestion control algorithm
+ option_size = sizeof(av);
+ if (usrsctp_getsockopt(transport->socket, IPPROTO_SCTP, SCTP_PLUGGABLE_CC, &av, &option_size)) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Translate algorithm
+ switch (av.assoc_value) {
+ case SCTP_CC_RFC2581:
+ *algorithmp = RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581;
+ break;
+ case SCTP_CC_HSTCP:
+ *algorithmp = RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HSTCP;
+ break;
+ case SCTP_CC_HTCP:
+ *algorithmp = RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HTCP;
+ break;
+ case SCTP_CC_RTCC:
+ *algorithmp = RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RTCC;
+ break;
+ default:
+ return RAWRTC_CODE_UNSUPPORTED_ALGORITHM;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's maximum transmission unit (MTU).
+ * This will disable MTU discovery.
+ *
+ * Note: The MTU cannot be set before the SCTP transport has been
+ * started.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_mtu(
+ struct rawrtc_sctp_transport* const transport, uint32_t mtu) {
+ struct sctp_paddrparams peer_address_parameters = {0};
+
+ // Check arguments
+ if (!transport || !transport->socket) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING &&
+ transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Set fixed MTU
+ // See: https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-5
+ // .spp_assoc_id is being ignored for 1:1 associations
+ memcpy(
+ &peer_address_parameters.spp_address, &transport->remote_address,
+ sizeof(transport->remote_address));
+ peer_address_parameters.spp_flags = SPP_PMTUD_DISABLE;
+ peer_address_parameters.spp_pathmtu = mtu;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_address_parameters,
+ sizeof(peer_address_parameters))) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Done
+ DEBUG_PRINTF("Set MTU to %" PRIu32 " (and disabled MTU discovery)\n", mtu);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get the current SCTP transport's maximum transmission unit (MTU)
+ * and an indication whether MTU discovery is enabled.
+ *
+ * Note: The MTU cannot be retrieved before the SCTP transport has been
+ * started.
+ */
+enum rawrtc_code rawrtc_sctp_transport_get_mtu(
+ uint32_t* const mtup, // de-referenced
+ bool* const mtu_discovery_enabledp, // de-referenced
+ struct rawrtc_sctp_transport* const transport) {
+ struct sctp_paddrparams peer_address_parameters = {0};
+ socklen_t option_size;
+
+ // Check arguments
+ if (!mtup || !mtu_discovery_enabledp || !transport || !transport->socket) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING &&
+ transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Get MTU info
+ memcpy(
+ &peer_address_parameters.spp_address, &transport->remote_address,
+ sizeof(transport->remote_address));
+ option_size = sizeof(peer_address_parameters);
+ if (usrsctp_getsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_address_parameters,
+ &option_size)) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Check value
+ if (option_size != sizeof(peer_address_parameters)) {
+ return RAWRTC_CODE_EXTERNAL_ERROR;
+ }
+
+ // Set value
+ *mtu_discovery_enabledp = (bool) (peer_address_parameters.spp_flags & SPP_PMTUD_ENABLE);
+ if (*mtu_discovery_enabledp) {
+ *mtup = 0;
+ } else {
+ *mtup = peer_address_parameters.spp_pathmtu;
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Enable MTU discovery for the SCTP transport.
+ *
+ * Note: MTU discovery cannot be enabled before the SCTP transport has
+ * been started.
+ */
+enum rawrtc_code rawrtc_sctp_transport_enable_mtu_discovery(
+ struct rawrtc_sctp_transport* const transport) {
+ struct sctp_paddrparams peer_address_parameters;
+
+ // Check arguments
+ if (!transport) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING &&
+ transport->state != RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // TODO: Re-enable support for path MTU.
+ // See: https://github.com/sctplab/usrsctp/issues/205
+ return RAWRTC_CODE_NOT_IMPLEMENTED;
+
+ // Set MTU discovery
+ // See: https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-5
+ // .spp_assoc_id is being ignored for 1:1 associations
+ memcpy(
+ &peer_address_parameters.spp_address, &transport->remote_address,
+ sizeof(transport->remote_address));
+ peer_address_parameters.spp_flags = SPP_PMTUD_ENABLE;
+ if (usrsctp_setsockopt(
+ transport->socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_address_parameters,
+ sizeof(peer_address_parameters))) {
+ return rawrtc_error_to_code(errno);
+ }
+
+ // Done
+ DEBUG_PRINTF("Enabled MTU discovery\n");
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Set the SCTP transport's context.
+ */
+enum rawrtc_code rawrtc_sctp_transport_set_context(
+ struct rawrtc_sctp_transport* const transport,
+ struct rawrtc_sctp_transport_context* const context // copied
+) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!transport || !context) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Ensure the context contains all callbacks
+ error = validate_context(context);
+ if (error) {
+ return error;
+ }
+
+ // Set context & done
+ transport->context = *context;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/sctp_transport/transport.h b/src/sctp_transport/transport.h
new file mode 100644
index 0000000..5d845e9
--- /dev/null
+++ b/src/sctp_transport/transport.h
@@ -0,0 +1,120 @@
+#pragma once
+#include <rawrtcdc/data_channel.h>
+#include <rawrtcdc/external.h>
+#include <rawrtcdc/sctp_transport.h>
+#include <re.h>
+#include <usrsctp.h> // SCTP_EVENT_*, ...
+#include <stdio.h> // FILE
+
+/*
+ * usrsctp event flag extensions for handlers.
+ */
+#define RAWRTC_SCTP_EVENT_NONE (0)
+#define RAWRTC_SCTP_EVENT_ALL (SCTP_EVENT_READ | SCTP_EVENT_WRITE | SCTP_EVENT_ERROR)
+
+enum {
+ RAWRTC_SCTP_TRANSPORT_MAX_MESSAGE_SIZE = 0,
+ RAWRTC_SCTP_TRANSPORT_TIMER_TIMEOUT = 10,
+ RAWRTC_SCTP_TRANSPORT_DEFAULT_PORT = 5000,
+ // As specified by https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-5
+ RAWRTC_SCTP_TRANSPORT_DEFAULT_NUMBER_OF_STREAMS = 65535,
+ RAWRTC_SCTP_TRANSPORT_SID_MAX = 65534,
+ RAWRTC_SCTP_TRANSPORT_EMPTY_MESSAGE_SIZE = 1,
+};
+
+/*
+ * SCTP transport flags.
+ */
+enum {
+ RAWRTC_SCTP_TRANSPORT_FLAGS_INITIALIZED = 1 << 0,
+ // The detached flag is virtually identical to the 'closed' state but is applied before the
+ // detach handler is being called. Thus, any other functions should check for the detached flag
+ // instead of checking for the 'closed' state since that is being set at a later stage.
+ RAWRTC_SCTP_TRANSPORT_FLAGS_DETACHED = 1 << 1,
+ RAWRTC_SCTP_TRANSPORT_FLAGS_SENDING_IN_PROGRESS = 1 << 2,
+ RAWRTC_SCTP_TRANSPORT_FLAGS_BUFFERED_AMOUNT_LOW = 1 << 3,
+};
+
+/*
+ * SCTP data channel flags.
+ */
+enum {
+ RAWRTC_SCTP_DATA_CHANNEL_FLAGS_CAN_SEND_UNORDERED = 1 << 0,
+ RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_INBOUND_MESSAGE = 1 << 1,
+ RAWRTC_SCTP_DATA_CHANNEL_FLAGS_PENDING_STREAM_RESET = 1 << 2,
+ RAWRTC_SCTP_DATA_CHANNEL_FLAGS_INCOMING_STREAM_RESET = 1 << 3,
+ RAWRTC_SCTP_DATA_CHANNEL_FLAGS_OUTGOING_STREAM_RESET = 1 << 4,
+};
+
+/*
+ * DCEP message types.
+ */
+enum {
+ RAWRTC_DCEP_MESSAGE_TYPE_ACK = 0x02,
+ RAWRTC_DCEP_MESSAGE_TYPE_OPEN = 0x03,
+};
+
+/*
+ * DCEP message sizes
+ */
+enum {
+ RAWRTC_DCEP_MESSAGE_ACK_BASE_SIZE = 1,
+ RAWRTC_DCEP_MESSAGE_OPEN_BASE_SIZE = 12,
+};
+
+/*
+ * DCEP message priorities.
+ */
+enum {
+ RAWRTC_DCEP_CHANNEL_PRIORITY_LOW = 128,
+ RAWRTC_DCEP_CHANNEL_PRIORITY_NORMAL = 256,
+ RAWRTC_DCEP_CHANNEL_PRIORITY_HIGH = 512,
+ RAWRTC_DCEP_CHANNEL_PRIORITY_EXTRA_HIGH = 1024,
+};
+
+/*
+ * DCEP payload protocol identifiers.
+ */
+enum {
+ RAWRTC_SCTP_TRANSPORT_PPID_DCEP = 50,
+ RAWRTC_SCTP_TRANSPORT_PPID_UTF16 = 51,
+ RAWRTC_SCTP_TRANSPORT_PPID_UTF16_EMPTY = 56,
+ RAWRTC_SCTP_TRANSPORT_PPID_UTF16_PARTIAL = 54, // deprecated
+ RAWRTC_SCTP_TRANSPORT_PPID_BINARY = 53,
+ RAWRTC_SCTP_TRANSPORT_PPID_BINARY_EMPTY = 57,
+ RAWRTC_SCTP_TRANSPORT_PPID_BINARY_PARTIAL = 52, // deprecated
+};
+
+/*
+ * SCTP transport.
+ */
+struct rawrtc_sctp_transport {
+ struct rawrtc_sctp_transport_context context;
+ uint_fast8_t flags;
+ enum rawrtc_sctp_transport_state state;
+ uint16_t port;
+ struct sockaddr_conn remote_address;
+ uint64_t remote_maximum_message_size;
+ rawrtc_data_channel_handler data_channel_handler; // nullable
+ rawrtc_sctp_transport_state_change_handler state_change_handler; // nullable
+ void* arg; // nullable
+ struct list buffered_messages_outgoing;
+ struct mbuf* buffer_dcep_inbound;
+ struct sctp_rcvinfo info_dcep_inbound;
+ struct rawrtc_data_channel** channels;
+ uint_fast16_t n_channels;
+ uint_fast16_t current_channel_sid;
+ FILE* trace_handle;
+ struct socket* socket;
+};
+
+/*
+ * Contextual data required by the SCTP transport used in conjunction with a
+ * data channel.
+ */
+struct rawrtc_sctp_data_channel_context {
+ uint16_t sid;
+ uint_fast8_t flags;
+ struct mbuf* buffer_inbound;
+ struct sctp_rcvinfo info_inbound;
+};
diff --git a/src/sctp_transport/utils.c b/src/sctp_transport/utils.c
new file mode 100644
index 0000000..7c69e5b
--- /dev/null
+++ b/src/sctp_transport/utils.c
@@ -0,0 +1,38 @@
+#include <rawrtcdc/sctp_transport.h>
+
+/*
+ * Get the corresponding name for an SCTP transport state.
+ */
+char const* rawrtc_sctp_transport_state_to_name(enum rawrtc_sctp_transport_state const state) {
+ switch (state) {
+ case RAWRTC_SCTP_TRANSPORT_STATE_NEW:
+ return "new";
+ case RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING:
+ return "connecting";
+ case RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED:
+ return "connected";
+ case RAWRTC_SCTP_TRANSPORT_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Get the corresponding name for a congestion control algorithm.
+ */
+char const* rawrtc_sctp_transport_congestion_ctrl_algorithm_to_name(
+ enum rawrtc_sctp_transport_congestion_ctrl const algorithm) {
+ switch (algorithm) {
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581:
+ return "RFC2581";
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HSTCP:
+ return "HSTCP";
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HTCP:
+ return "HTCP";
+ case RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RTCC:
+ return "RTCC";
+ default:
+ return "???";
+ }
+}