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/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..be44098
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,37 @@
+os: Visual Studio 2017
+
+platform:
+ - x64
+
+environment:
+ # Build matrix
+ matrix:
+ - arch: x86
+ compiler: msvc2015
+ - arch: x64
+ compiler: msvc2015
+ - arch: x86
+ compiler: msvc2017
+ - arch: x64
+ compiler: msvc2017
+
+install:
+ # Download ninja
+ - cmd: mkdir C:\ninja-build
+ - ps: (new-object net.webclient).DownloadFile('https://github.com/mesonbuild/cidata/raw/master/ninja.exe', 'C:\ninja-build\ninja.exe')
+ # Set python root based on architecture
+ - cmd: if %arch%==x86 (set PYTHON_ROOT=C:\python37) else (set PYTHON_ROOT=C:\python37-x64)
+ # Add necessary paths to PATH variable
+ - cmd: set PATH=%cd%;C:\ninja-build;%PYTHON_ROOT%;%PYTHON_ROOT%\Scripts;%PATH%
+ # Install meson
+ - cmd: pip install meson
+ # Set up the build environment
+ - cmd: if %compiler%==msvc2015 ( call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %arch% )
+ - cmd: if %compiler%==msvc2017 ( call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" %arch% )
+
+build_script:
+ - cmd: echo Building on %arch% with %compiler%
+ # Configure
+ - cmd: meson --backend=ninja build
+ # Build
+ - cmd: ninja -C build
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..bffc990
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,184 @@
+version: 2
+
+shared:
+ host: &shared-host
+ working_directory: ~/rawrtc-data-channel
+ steps:
+ - checkout
+
+ # Configure library
+ - run:
+ name: Configure
+ command: |
+ mkdir build
+ meson build --prefix /tmp/prefix
+
+ # Build library
+ - run:
+ name: Build
+ command: |
+ cd build
+ ninja install
+
+ cross: &shared-cross
+ working_directory: ~/rawrtc-data-channel
+ steps:
+ - checkout
+
+ # Configure library
+ - run:
+ name: Configure
+ command: |
+ mkdir build
+ meson build --prefix /tmp/prefix --cross-file ${CROSS_FILE_NAME}
+
+ # Build library
+ - run:
+ name: Build
+ command: |
+ cd build
+ ninja install
+
+
+jobs:
+ # Host: Ubuntu 14.04 LTS
+ trusty-gcc:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:trusty
+ environment:
+ CC: gcc
+ trusty-clang:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:trusty
+ environment:
+ CC: clang
+
+ # Host: Ubuntu 16.04 LTS
+ xenial-gcc:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:xenial
+ environment:
+ CC: gcc
+ xenial-clang:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:xenial
+ environment:
+ CC: clang
+
+ # Host: Ubuntu 18.04 LTS
+ bionic-gcc:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:bionic
+ environment:
+ CC: gcc
+ bionic-clang:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:bionic
+ environment:
+ CC: clang
+
+ # Host: Arch Linux
+ archlinux-gcc:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:archlinux
+ environment:
+ CC: gcc
+ archlinux-clang:
+ <<: *shared-host
+ docker:
+ - image: rawrtc/ci-image:archlinux
+ environment:
+ CC: clang
+
+ # Cross: Linux ARMv6
+ linux-armv6:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:linux-armv6
+
+ # Cross: Linux ARMv7
+ linux-armv7:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:linux-armv7
+
+ # Cross: Android API 16 ARM
+ android-16-arm:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-16-arm
+
+ # Cross: Android API 16 x86
+ android-16-x86:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-16-x86
+
+ # Cross: Android API 28 ARM
+ android-28-arm:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-28-arm
+
+ # Cross: Android API 28 ARM64
+ android-28-arm64:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-28-arm64
+
+ # Cross: Android API 28 x86
+ android-28-x86:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-28-x86
+
+ # Cross: Android API 28 x86_64
+ android-28-x86_64:
+ <<: *shared-cross
+ docker:
+ - image: rawrtc/cross-build:android-28-x86_64
+
+ # Cross: Windows x86
+ #windows-x86:
+ # <<: *shared-cross
+ # docker:
+ # - image: rawrtc/cross-build:windows-x86
+
+ # Cross: Windows x64
+ #windows-x64:
+ # <<: *shared-cross
+ # docker:
+ # - image: rawrtc/cross-build:windows-x64
+
+
+workflows:
+ version: 2
+
+ # Build all
+ build:
+ jobs:
+ - trusty-gcc
+ - trusty-clang
+ - xenial-gcc
+ - xenial-clang
+ - bionic-gcc
+ - bionic-clang
+ - archlinux-gcc
+ - archlinux-clang
+ - linux-armv6
+ - linux-armv7
+ - android-16-arm
+ - android-16-x86
+ - android-28-arm
+ - android-28-arm64
+ - android-28-x86
+ - android-28-x86_64
+ #- windows-x86
+ #- windows-x64
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..666fc71
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,74 @@
+---
+BasedOnStyle: LLVM
+Language: Cpp
+AlignAfterOpenBracket: AlwaysBreak
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: DontAlign
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: true
+BinPackParameters: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeTernaryOperators: true
+BreakInheritanceList: BeforeColon
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+ContinuationIndentWidth: 4
+DerivePointerAlignment: false
+DisableFormat: false
+ForEachMacros:
+ - LIST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"[^\.]+'
+ Priority: 1
+ - Regex: '^"\.\./'
+ Priority: 2
+ - Regex: '^<rawrtcdc'
+ Priority: 3
+ - Regex: '^<(re\.h|rawrtcc|usrsctp)'
+ Priority: 4
+ - Regex: '.*'
+ Priority: 5
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: true
+IndentPPDirectives: AfterHash
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 6000
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SpaceAfterCStyleCast: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: Never
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b9db27c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,47 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+
+# Var
+/var
+
+# Build folders
+/build*
+/docs/_build
+/docs/_xml
+
+# Generated files
+/docs/Doxyfile
+/docs/conf.py
+
+# Subprojects
+/subprojects/*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..dd200dc
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,29 @@
+# Build matrix
+language: c
+matrix:
+ include:
+ # Mac OS 10.13 (default)
+ - os: osx
+ osx_image: xcode9.4
+ compiler: clang
+
+ # Mac OS 10.13 (latest)
+ - os: osx
+ osx_image: xcode10.1
+ compiler: clang
+
+# Dependencies
+addons:
+ homebrew:
+ packages:
+ - meson
+ - ninja
+ update: true
+
+# Build library
+script:
+ - |
+ mkdir build
+ meson build --prefix /tmp/prefix
+ cd build
+ ninja install
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8aaa342
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,25 @@
+# Changelog
+
+## [0.1.3] (2019-08-15)
+
+* Add getter/setter for send/receive buffer length
+* Add getter/setter for congestion control algorithm
+
+## [0.1.2] (2019-03-18)
+
+* Fix SSE 4.2 detection
+
+## [0.1.1] (2019-03-10)
+
+* Declare dependency in Meson
+
+## [0.1.0] (2019-03-02)
+
+* Initial release of RAWRTCDC
+
+
+
+[0.1.3]: https://github.com/rawrtc/rawrtc-data-channel/compare/v0.1.2...v0.1.3
+[0.1.2]: https://github.com/rawrtc/rawrtc-data-channel/compare/v0.1.1...v0.1.2
+[0.1.1]: https://github.com/rawrtc/rawrtc-data-channel/compare/v0.1.0...v0.1.1
+[0.1.0]: https://github.com/rawrtc/rawrtc-data-channel/compare/59e65d96a9feb4dd6b4a3b2f3a10ab7c067e2a60...v0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..34a8d75
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2018, Lennart Grahl
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fb532b6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,336 @@
+# RAWRTCDC
+
+[![CircleCI build status][circleci-badge]][circleci-url]
+[![Travis CI build status][travis-ci-badge]][travis-ci-url]
+[![Join our chat on Gitter][gitter-icon]][gitter]
+
+A standalone [WebRTC][w3c-webrtc] and [ORTC][w3c-ortc] data channel
+implementation.
+
+## Features
+
+The following list represents all features that are planned for RAWRTCDC.
+Features with a check mark are already implemented.
+
+* [x] C-API based on the [W3C CG ORTC API][w3c-ortc]
+* [x] SCTP-based data channels [[draft-ietf-rtcweb-data-channel-13]][sctp-dc]
+ - [x] DCEP implementation [[draft-ietf-rtcweb-data-protocol-09]][dcep]
+ - [x] Support for arbitrarily sized messages
+ - [ ] Support for SCTP ndata [RFC 8260][sctp-ndata] (partially implemented,
+ see [#14][#14])
+ - [ ] Streaming mode (partially implemented, see [#16][#16])
+ - [x] Hardware CRC32-C checksum offloading (requires SSE4.2)
+
+## FAQ
+
+1. *Who should use this?*
+
+ If you have built a WebRTC stack and...
+
+ * you want data channel support but you don't have it so far, or
+ * you don't want to maintain your own data channel implementation, or
+ * your data channel implementation is lacking some features,
+
+ then you should consider integrating this library. :tada:
+
+2. *But I also need ICE/DTLS!*
+
+ Check out [RAWRTC][rawrtc].
+
+3. *How does it work?*
+
+ Basically, you pass in SCTP packets and you get out SCTP packets.
+ Put that on top of your DTLS transport and you're ready to go.
+
+ See the [*Getting Started*](#getting-started) section on how to set it up.
+
+4. *Can I use it in an event loop?*
+
+ Yes.
+
+5. *Can I use it in a threaded environment?*
+
+ Yes. Just make sure you're always calling it from the same thread the event
+ loop is running on, or either [lock/unlock the event loop thread][re-lock]
+ or [use the message queues provided by re][re-mqueue] to exchange data with
+ the event loop thread. However, it is important that you only run one *re*
+ event loop in one thread.
+
+6. *Does it create threads?*
+
+ No.
+
+7. *Is this a custom SCTP implementation?*
+
+ No, it uses [usrsctp](https://github.com/sctplab/usrsctp) underneath but
+ handles all the nitty-gritty details for you.
+
+## Prerequisites
+
+The following tools are required:
+
+* [git][git]
+* [ninja][ninja] >= 1.5
+* [meson][meson] >= 0.46.0
+* Optional: pkg-config (`pkgconf` for newer FreeBSD versions)
+
+## Build
+
+```bash
+cd <path-to-rawrtcdc>
+mkdir build
+meson build
+cd build
+ninja
+```
+
+## Getting Started
+
+Now that you've built the library, let's get started integrating this library
+into your stack.
+
+### Initialise
+
+Before doing anything, initialise the library:
+
+```c
+#include <rawrtcc.h>
+#include <rawrtcdc.h>
+
+[...]
+
+enum rawrtc_code error = rawrtcdc_init(init_re, timer_handler);
+if (error) {
+ your_log_function("Initialisation failed: %s", rawrtc_code_to_str(error));
+}
+```
+
+In the following code examples, the error handling will be omitted. But you of
+course still need to handle it in your code.
+
+Unless you're initialising [re][re] yourselves, the `init_re` parameter to
+`rawrtcdc_init` should be `true`. The second is a pointer to a timer handler.
+
+The timer handler function works in the following way (see comments inline):
+
+```c
+enum rawrtc_code timer_handler(bool const on, uint_fast16_t const interval) {
+ if (on) {
+ // Start a timer that calls `rawrtcdc_timer_tick` every `interval`
+ // milliseconds.
+ } else {
+ // Stop the timer.
+ }
+
+ // Indicate success. In case something fails for you, you can also
+ // backpropagate an appropriate error code here.
+ return RAWRTC_CODE_SUCCESS;
+}
+```
+
+### Create an SCTP transport
+
+Before you can create data channels, you will need to create an SCTP transport:
+
+```c
+// Create SCTP transport context
+struct rawrtc_sctp_transport_context context = {
+ .role_getter = dtls_role_getter,
+ .state_getter = dtls_transport_state_getter,
+ .outbound_handler = sctp_transport_outbound_handler,
+ .detach_handler = sctp_transport_detach_handler,
+ .destroyed_handler = sctp_transport_destroy,
+ .trace_packets = false,
+ .arg = your_reference,
+};
+
+// Create SCTP transport
+struct rawrtc_sctp_transport transport;
+error = rawrtc_sctp_transport_create_from_external(
+ &transport, &context, local_sctp_port,
+ data_channel_handler, state_change_handler, arg);
+if (error) {
+ your_log_function("Creating SCTP transport failed: %s",
+ rawrtc_code_to_str(error));
+ goto out;
+}
+
+// Attach your DTLS transport here
+```
+
+After the transport has been created successfully, `transport` will point to
+some dynamically allocated memory which is reference counted (and the reference
+counter value will be `1` after the function returned). If you want to increase
+the reference, call `mem_ref(transport)`. If you need to decrease the
+reference, call `mem_deref(transport)`. Once the counter value reaches `0`, it
+will run a destructor function and free the memory. However, you should
+normally stop the transport with `rawrtc_sctp_transport_stop` in a more
+graceful manner before doing so. We're pointing this out here since basically
+everything in this library that allocates dynamic memory works that way.
+
+Furthermore, from this moment on your DTLS transport should feed SCTP packets
+into the SCTP transport by calling
+`rawrtc_sctp_transport_feed_inbound(transport, buffer, ecn_bits)`. Check the
+[header file][sctp_transport.h] for details on the parameters.
+
+You're probably already wondering what the SCTP transport context is all about.
+Basically, it contains pointers to some handler functions you will need to
+define. The only exception is the `arg` field which let's you pass an arbitrary
+pointer to the various handler functions. So, let's go through them:
+
+```c
+enum rawrtc_code dtls_role_getter(
+ enum rawrtc_external_dtls_role* const rolep, void* const arg) {
+ // Set the local role of your DTLS transport
+ *rolep = your_dtls_transport.local_role;
+ return RAWRTC_CODE_SUCCESS;
+}
+
+enum rawrtc_code dtls_transport_state_getter(
+ enum rawrtc_external_dtls_transport_state* const statep, void* const arg) {
+ // Set the state of your DTLS transport
+ *statep = your_dtls_transport.state;
+ return RAWRTC_CODE_SUCCESS;
+}
+```
+
+These DTLS handler functions were fairly straightforward. Now to the SCTP
+handler that hands back outbound SCTP packets. These packets will need to be
+fed into the DTLS transport as application data and sent to the other peer:
+
+```c
+enum rawrtc_code sctp_transport_outbound_handler(
+ struct mbuf* const buffer, uint8_t const tos, uint8_t const set_df,
+ void* const arg) {
+ // Feed the data to the DTLS transport
+ your_dtls_transport_send(buffer, tos, set_df);
+ return RAWRTC_CODE_SUCCESS;
+}
+```
+
+The `struct buffer` and its functions are documented [here][re-mbuf]. As a rule
+of thumb, you should call `mbuf_buf(buffer)` to get a pointer to the current
+position and `mbuf_get_left(buffer)` to get the amount of bytes left in the
+buffer.
+Be aware `buffer` in this case is not dynamically allocated and shall not be
+referenced. This has been done for optimisation purposes.
+Check the [header file][external.h] for further details on the various
+parameters passed to this handler function.
+
+The following handler is merely a way to let you know that you should not feed
+any data to the SCTP transport anymore:
+
+```c
+void sctp_transport_detach_handler(void* const arg) {
+ // Detach from DTLS transport
+ your_dtls_transport.stop_feeding_data = true;
+}
+```
+
+The last handler function we need to talk about is a way to tell you that the
+SCTP transport's reference count has been decreased to `0` and its about to be
+free'd. Be aware that you may not call any SCTP transport or data channel
+functions once this handler is being called.
+
+```c
+void sctp_transport_destroy(void* const arg) {
+ // Your cleanup code here
+}
+```
+
+The `trace_packets` attribute allows you to enable writing SCTP packets to a
+trace file. The name of that file is randomly generated and will be placed in
+the current working directory.
+
+That's all for the SCTP transport.
+
+### Create a Data Channel
+
+The data channel API is very similar to the one used in WebRTC and ORTC, so we
+will not go into detail for them. Here's a quick example on how to create a
+data channel with the following properties:
+
+* label: `meow`
+* protocol: `v1.cat-noises`
+* reliable
+* unordered
+* pre-negotiated
+* stream id is fixed to `42`
+
+```c
+// Create data channel parameters
+struct rawrtc_data_channel_parameters parameters;
+error = rawrtc_data_channel_parameters_create(
+ ¶meters, "meow", RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_UNORDERED, 0,
+ "v1.cat-noises", true, 42);
+
+// Create the data channel, using the transport and the parameters
+struct rawrtc_data_channel channel;
+error = rawrtc_data_channel_create(
+ &channel, transport, parameters,
+ open_handler, buffered_amount_low_handler, error_handler,
+ close_handler, message_handler, pointer_passed_to_handlers);
+
+mem_deref(parameters);
+```
+
+Instead of adding handler functions on creation, you can also pass `NULL` and
+register handler functions later.
+
+For further explanation on the various parameters, check the
+[header files][headers].
+
+Once the SCTP transport goes into the *connected* state, the created channels
+will open. If you see this happening, this is a good indication that you've set
+everything up correctly. :clap:
+
+### Exit
+
+Once your code exits, you should call `rawrtcdc_close(close_re)`. If the
+`close_re` parameter is `true`, [re][re] will be closed as well.
+
+### Any Questions?
+
+![Draw The Rest Of The Owl Meme][owl-meme]
+
+Do you feel like this now? If yes, please join our [gitter chat][gitter], so we
+can help you and work out what's missing in this little tutorial.
+
+## Contributing
+
+When creating a pull request, it is recommended to run `format-all.sh` to
+apply a consistent code style.
+
+
+
+[circleci-badge]: https://circleci.com/gh/rawrtc/rawrtc-data-channel.svg?style=shield
+[circleci-url]: https://circleci.com/gh/rawrtc/rawrtc-data-channel
+[travis-ci-badge]: https://travis-ci.org/rawrtc/rawrtc-data-channel.svg?branch=master
+[travis-ci-url]: https://travis-ci.org/rawrtc/rawrtc-data-channel
+
+[gitter]: https://gitter.im/rawrtc/Lobby
+[gitter-icon]: https://badges.gitter.im/rawrtc/Lobby.svg
+
+[w3c-webrtc]: https://www.w3.org/TR/webrtc/
+[w3c-ortc]: https://draft.ortc.org
+[dcep]: https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09
+[sctp-dc]: https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13
+[#14]: https://github.com/rawrtc/rawrtc-data-channel/issues/14
+[#16]: https://github.com/rawrtc/rawrtc-data-channel/issues/16
+[sctp-ndata]: https://tools.ietf.org/html/rfc8260
+
+[rawrtc]: https://github.com/rawrtc/rawrtc
+
+[git]: https://git-scm.com
+[meson]: https://mesonbuild.com
+[ninja]: https://ninja-build.org
+
+[re-lock]: http://www.creytiv.com/doxygen/re-dox/html/re__main_8h.html#ad335fcaa56e36b39cb1192af1a6b9904
+[re-mqueue]: http://www.creytiv.com/doxygen/re-dox/html/re__mqueue_8h.html
+[usrsctp-neat-issue-12]: https://github.com/NEAT-project/usrsctp-neat/issues/12
+[sctp_transport.h]: include/rawrtcdc/sctp_transport.h
+[external.h]: include/rawrtcdc/external.h
+[headers]: include/rawrtcdc
+[re]: https://github.com/creytiv/re
+[re-mbuf]: http://www.creytiv.com/doxygen/re-dox/html/re__mbuf_8h.html
+[owl-meme]: ../assets/draw-the-rest-of-the-owl.jpg?raw=true
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..30100ec
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,39 @@
+# Release Process
+
+Signing key: https://lgrahl.de/pub/pgp-key.txt
+
+1. Set variables:
+
+ ```bash
+ export VERSION=<version>
+ export GPG_KEY=3FDB14868A2B36D638F3C495F98FBED10482ABA6
+ ```
+
+2. Update version number in `meson.build` and `CHANGELOG.md`. Also, update the
+ URL with the corresponding tags.
+
+3. Do a signed commit and signed tag of the release:
+
+ ```bash
+ git add meson.build CHANGELOG.md
+ git commit -S${GPG_KEY} -m "Release v${VERSION}"
+ git tag -u ${GPG_KEY} -m "Release v${VERSION}" v${VERSION}
+ ```
+
+4. Push.
+
+ ```bash
+ git push && git push --tags
+ ```
+
+5. Create a new release on GitHub.
+
+6. Prepare CHANGELOG.md for upcoming changes:
+
+ ```md
+ ## [Unreleased] (YYYY-MM-DD)
+
+ * ...
+ ```
+
+7. Pat yourself on the back and celebrate!
diff --git a/format-all.sh b/format-all.sh
new file mode 100755
index 0000000..ba846ae
--- /dev/null
+++ b/format-all.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+for dir in "include" "src"; do
+ pushd "${dir}" &>/dev/null
+ find . \
+ -type f \
+ \( -name "*.c" -o -name "*.h" \) \
+ -exec clang-format -i '{}' \;
+ popd &>/dev/null
+done
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 0000000..15f8f29
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1,3 @@
+# Install headers
+install_headers(files('rawrtcdc.h'))
+subdir('rawrtcdc')
diff --git a/include/rawrtcdc.h b/include/rawrtcdc.h
new file mode 100644
index 0000000..68f62a3
--- /dev/null
+++ b/include/rawrtcdc.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "rawrtcdc/config.h"
+
+#include "rawrtcdc/data_channel.h"
+#include "rawrtcdc/data_channel_parameters.h"
+#include "rawrtcdc/data_transport.h"
+#include "rawrtcdc/external.h"
+#include "rawrtcdc/main.h"
+#include "rawrtcdc/sctp_capabilities.h"
+#if RAWRTCDC_HAVE_SCTP_REDIRECT_TRANSPORT
+# include "rawrtcdc/sctp_redirect_transport.h"
+#endif
+#include "rawrtcdc/sctp_transport.h"
diff --git a/include/rawrtcdc/config.h.in b/include/rawrtcdc/config.h.in
new file mode 100644
index 0000000..3ca6f05
--- /dev/null
+++ b/include/rawrtcdc/config.h.in
@@ -0,0 +1,21 @@
+#pragma once
+
+/// Current version of the library.
+///
+/// Follows [Semantic Versioning 2.0.0](https://semver.org)
+#mesondefine RAWRTCDC_VERSION
+#mesondefine RAWRTCDC_VERSION_MAJOR
+#mesondefine RAWRTCDC_VERSION_MINOR
+#mesondefine RAWRTCDC_VERSION_PATCH
+
+/// Debug level
+#mesondefine RAWRTC_DEBUG_LEVEL
+
+/// Whether hardware CRC32-C has been enabled
+///
+/// Note: This does not necessarily mean that hardware CRC32-C will be used
+/// which depends on whether your CPU supports SSE 4.2 at runtime.
+#mesondefine RAWRTCDC_ENABLE_SSE42_CRC32C
+
+/// Whether the SCTP redirect transport has been compiled
+#mesondefine RAWRTCDC_HAVE_SCTP_REDIRECT_TRANSPORT
diff --git a/include/rawrtcdc/data_channel.h b/include/rawrtcdc/data_channel.h
new file mode 100644
index 0000000..423aafd
--- /dev/null
+++ b/include/rawrtcdc/data_channel.h
@@ -0,0 +1,272 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+// Dependencies
+struct rawrtc_data_channel_parameters;
+struct rawrtc_data_transport;
+
+/**
+ * Data channel types.
+ */
+enum rawrtc_data_channel_type {
+ RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_ORDERED = 0x00,
+ RAWRTC_DATA_CHANNEL_TYPE_RELIABLE_UNORDERED = 0x80,
+ RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_RETRANSMIT = 0x01,
+ RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_RETRANSMIT = 0x81,
+ RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_ORDERED_TIMED = 0x02,
+ RAWRTC_DATA_CHANNEL_TYPE_UNRELIABLE_UNORDERED_TIMED = 0x82,
+}; // IMPORTANT: If you add a new type, ensure that every data channel transport handles it
+ // correctly! Also, ensure this still works with the unordered bit flag above or
+ // update the implementations.
+
+/**
+ * Data channel message flags.
+ */
+enum rawrtc_data_channel_message_flag {
+ RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_ABORTED = 1 << 0,
+ RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_COMPLETE = 1 << 1,
+ RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_STRING = 1 << 2,
+ RAWRTC_DATA_CHANNEL_MESSAGE_FLAG_IS_BINARY = 1 << 3,
+};
+
+/**
+ * Data channel state.
+ */
+enum rawrtc_data_channel_state {
+ RAWRTC_DATA_CHANNEL_STATE_CONNECTING,
+ RAWRTC_DATA_CHANNEL_STATE_OPEN,
+ RAWRTC_DATA_CHANNEL_STATE_CLOSING,
+ RAWRTC_DATA_CHANNEL_STATE_CLOSED,
+};
+
+/**
+ * Data channel.
+ */
+struct rawrtc_data_channel;
+
+/**
+ * Data channel open handler.
+ */
+typedef void (*rawrtc_data_channel_open_handler)(void* const arg);
+
+/**
+ * Data channel buffered amount low handler.
+ */
+typedef void (*rawrtc_data_channel_buffered_amount_low_handler)(void* const arg);
+
+/**
+ * Data channel error handler.
+ */
+typedef void (*rawrtc_data_channel_error_handler)(void* const arg); // TODO: error parameter
+
+/**
+ * Data channel close handler.
+ */
+typedef void (*rawrtc_data_channel_close_handler)(void* const arg);
+
+/**
+ * Data channel message handler.
+ *
+ * Note: `buffer` may be NULL in case partial delivery has been
+ * requested and a message has been aborted (this can only happen
+ * on partially reliable channels).
+ */
+typedef void (*rawrtc_data_channel_message_handler)(
+ struct mbuf* const buffer, // nullable (in case partial delivery has been requested)
+ enum rawrtc_data_channel_message_flag const flags,
+ void* const arg);
+
+/**
+ * Data channel handler.
+ *
+ * Note: You should call `rawrtc_data_channel_set_streaming`
+ * in this handler before doing anything else if you want to
+ * enable streamed delivery of data for this channel from the
+ * beginning of the first incoming message.
+ */
+typedef void (*rawrtc_data_channel_handler)(
+ struct rawrtc_data_channel* const data_channel, // read-only, MUST be referenced when used
+ void* const 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
+);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * Close the data channel.
+ */
+enum rawrtc_code rawrtc_data_channel_close(struct rawrtc_data_channel* const channel);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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 an *invalid state* error.
+ */
+enum rawrtc_code rawrtc_data_channel_set_streaming(
+ struct rawrtc_data_channel* const channel, bool const on);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * Get the corresponding name for a data channel state.
+ */
+char const* rawrtc_data_channel_state_to_name(enum rawrtc_data_channel_state const state);
diff --git a/include/rawrtcdc/data_channel_parameters.h b/include/rawrtcdc/data_channel_parameters.h
new file mode 100644
index 0000000..9c91f37
--- /dev/null
+++ b/include/rawrtcdc/data_channel_parameters.h
@@ -0,0 +1,97 @@
+#pragma once
+#include "data_channel.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Data channel parameters.
+ */
+struct rawrtc_data_channel_parameters;
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
diff --git a/include/rawrtcdc/data_transport.h b/include/rawrtcdc/data_transport.h
new file mode 100644
index 0000000..e201aaa
--- /dev/null
+++ b/include/rawrtcdc/data_transport.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Data transport type.
+ */
+enum rawrtc_data_transport_type {
+ RAWRTC_DATA_TRANSPORT_TYPE_SCTP,
+};
+
+/**
+ * Generic data transport.
+ */
+struct rawrtc_data_transport;
+
+/**
+ * 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);
+
+/**
+ * Translate a data transport type to str.
+ */
+char const* rawrtc_data_transport_type_to_str(enum rawrtc_data_transport_type const type);
diff --git a/include/rawrtcdc/external.h b/include/rawrtcdc/external.h
new file mode 100644
index 0000000..7e79063
--- /dev/null
+++ b/include/rawrtcdc/external.h
@@ -0,0 +1,99 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * External DTLS role.
+ */
+enum rawrtc_external_dtls_role {
+ RAWRTC_EXTERNAL_DTLS_ROLE_CLIENT,
+ RAWRTC_EXTERNAL_DTLS_ROLE_SERVER,
+};
+
+/**
+ * External DTLS transport state.
+ */
+enum rawrtc_external_dtls_transport_state {
+ RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_NEW_OR_CONNECTING,
+ RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CONNECTED,
+ RAWRTC_EXTERNAL_DTLS_TRANSPORT_STATE_CLOSED_OR_FAILED,
+};
+
+/**
+ * DTLS role getter.
+ *
+ * `*rolep` will contain the current external DTLS role.
+ * `arg` is the argument passed to the SCTP transport context.
+ *
+ * Return `RAWRTC_CODE_SUCCESS` in case the role has been set or any
+ * other code in case of an error.
+ */
+typedef enum rawrtc_code (*rawrtc_dtls_role_getter)(
+ enum rawrtc_external_dtls_role* const rolep, // de-referenced
+ void* const arg);
+
+/**
+ * DTLS transport state getter.
+ *
+ * `*statep` will contain the current external DTLS transport state.
+ * `arg` is the argument passed to the SCTP transport context.
+ *
+ * Return `RAWRTC_CODE_SUCCESS` in case the state has been set or any
+ * other code in case of an error.
+ */
+typedef enum rawrtc_code (*rawrtc_dtls_transport_state_getter)(
+ enum rawrtc_external_dtls_transport_state* const statep, // de-referenced
+ void* const arg);
+
+/**
+ * SCTP transport outbound data handler.
+ *
+ * `buffer` contains the data to be fed to the DTLS transport.
+ * Note that the `mbuf` structure shall not be `mem_ref`ed or
+ * `mem_deref`ed since it hasn't been allocated properly for
+ * optimisation purposes. This has been done since we expect you to
+ * either send this data directly or drop it. There's no need to hold
+ * data back. If you for any reason need the data after the callback
+ * returned, you are required to copy it.
+ * `tos` contains the type of service field as reported by usrsctp.
+ * `set_df` TODO: Probably don't fragment bit? Dunno...
+ *
+ * Return `RAWRTC_CODE_SUCCESS` in case the packet has been sent (or
+ * dropped) or any other code in case of an error.
+ */
+typedef enum rawrtc_code (*rawrtc_sctp_transport_outbound_handler)(
+ struct mbuf* const buffer, uint8_t const tos, uint8_t const set_df, void* const arg);
+
+/**
+ * SCTP transport detach handler.
+ * Will be called when the SCTP transport is about to be closed and
+ * should be detached from the underlying DTLS transport. At this
+ * point, no further data should be fed to the SCTP transport.
+ *
+ * `arg` is the argument passed to the SCTP transport context.
+ */
+typedef void (*rawrtc_sctp_transport_detach_handler)(void* const arg);
+
+/**
+ * SCTP transport destroyed handler.
+ * Will be called when the SCTP transport is about to be free'd.
+ *
+ * Note: This handler only exists for cleanup purposes. You may not use
+ * any of the transport's functions at this point.
+ *
+ * `arg` is the argument passed to the SCTP transport context.
+ */
+typedef void (*rawrtc_sctp_transport_destroyed_handler)(void* const arg);
+
+/**
+ * SCTP transport context.
+ */
+struct rawrtc_sctp_transport_context {
+ rawrtc_dtls_role_getter role_getter;
+ rawrtc_dtls_transport_state_getter state_getter;
+ rawrtc_sctp_transport_outbound_handler outbound_handler;
+ rawrtc_sctp_transport_detach_handler detach_handler; // nullable
+ rawrtc_sctp_transport_destroyed_handler destroyed_handler; // nullable
+ bool trace_packets;
+ void* arg; // nullable
+};
diff --git a/include/rawrtcdc/main.h b/include/rawrtcdc/main.h
new file mode 100644
index 0000000..41a7f80
--- /dev/null
+++ b/include/rawrtcdc/main.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * Timer handler.
+ *
+ * @param on If set to `true`, a timer needs to be scheduled to call
+ * rawrtcdc_timer_tick() every `interval` milliseconds.
+ * If set to `false`, the timer needs to be cancelled.
+ * @param interval the interval for the timer. Should be ignored in
+ * case `on` is `false`.
+ */
+typedef enum rawrtc_code (*rawrtcdc_timer_handler)(bool const on, uint_fast16_t const interval);
+
+/**
+ * Initialise RAWRTCDC. Must be called before making a call to any
+ * other function.
+ *
+ * Note: In case you override the default mutex used by re it's vital
+ * that you create a recursive mutex or you will get deadlocks!
+ */
+enum rawrtc_code rawrtcdc_init(bool const init_re, rawrtcdc_timer_handler const timer_handler);
+
+/**
+ * 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);
+
+/**
+ * Handle timer tick.
+ * `delta` contains the delta milliseconds passed between calls.
+ */
+void rawrtcdc_timer_tick(uint_fast16_t const delta);
diff --git a/include/rawrtcdc/meson.build b/include/rawrtcdc/meson.build
new file mode 100644
index 0000000..d7dc6f7
--- /dev/null
+++ b/include/rawrtcdc/meson.build
@@ -0,0 +1,21 @@
+# Generate configuration header
+configure_file(
+ input: 'config.h.in',
+ output: 'config.h',
+ configuration: configuration,
+ install_dir: '/'.join([get_option('includedir'), 'rawrtcdc']))
+
+# Install headers
+includes = files([
+ 'data_channel.h',
+ 'data_channel_parameters.h',
+ 'data_transport.h',
+ 'external.h',
+ 'main.h',
+ 'sctp_capabilities.h',
+ 'sctp_transport.h',
+])
+if get_option('sctp_redirect_transport')
+ includes += files('sctp_redirect_transport.h')
+endif
+install_headers(includes, subdir: 'rawrtcdc')
diff --git a/include/rawrtcdc/sctp_capabilities.h b/include/rawrtcdc/sctp_capabilities.h
new file mode 100644
index 0000000..995382f
--- /dev/null
+++ b/include/rawrtcdc/sctp_capabilities.h
@@ -0,0 +1,32 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+/**
+ * SCTP capabilities.
+ */
+struct rawrtc_sctp_capabilities;
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * Print debug information for SCTP capabilities.
+ */
+int rawrtc_sctp_capabilities_debug(
+ struct re_printf* const pf, struct rawrtc_sctp_capabilities const* const capabilities);
diff --git a/include/rawrtcdc/sctp_redirect_transport.h b/include/rawrtcdc/sctp_redirect_transport.h
new file mode 100644
index 0000000..7d07f89
--- /dev/null
+++ b/include/rawrtcdc/sctp_redirect_transport.h
@@ -0,0 +1,106 @@
+#pragma once
+#include <rawrtcc/code.h>
+#include <re.h>
+
+// Dependencies
+struct rawrtc_sctp_capabilities;
+struct rawrtc_sctp_transport_context;
+
+/**
+ * SCTP redirect transport states.
+ */
+enum rawrtc_sctp_redirect_transport_state {
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_NEW,
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_OPEN,
+ RAWRTC_SCTP_REDIRECT_TRANSPORT_STATE_CLOSED,
+};
+
+/**
+ * Redirect transport.
+ */
+struct rawrtc_sctp_redirect_transport;
+
+/**
+ * SCTP redirect transport state change handler.
+ */
+typedef void (*rawrtc_sctp_redirect_transport_state_change_handler)(
+ enum rawrtc_sctp_redirect_transport_state const state, void* const arg);
+
+/**
+ * Create an SCTP redirect transport from an external DTLS transport.
+ * `*transportp` must be unreferenced.
+ *
+ * `port` defaults to `5000` if set to `0`.
+ * `redirect_ip` is the target IP SCTP packets will be redirected to
+ * and must be a IPv4 address.
+ * `redirect_port` is the target SCTP port packets will be redirected
+ * to.
+ *
+ * Note: The underlying DTLS transport is supposed to be immediately
+ * attached after creation of this transport.
+ * 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
+);
+
+/**
+ * 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
+);
+
+/**
+ * Stop and close the SCTP redirect transport.
+ */
+enum rawrtc_code rawrtc_sctp_redirect_transport_stop(
+ struct rawrtc_sctp_redirect_transport* const transport);
+
+/**
+ * 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);
+
+/**
+ * Get the current 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
diff --git a/include/rawrtcdc/sctp_transport.h b/include/rawrtcdc/sctp_transport.h
new file mode 100644
index 0000000..44460f0
--- /dev/null
+++ b/include/rawrtcdc/sctp_transport.h
@@ -0,0 +1,263 @@
+#pragma once
+#include "data_channel.h"
+#include <rawrtcc/code.h>
+#include <re.h>
+
+// Dependencies
+struct rawrtc_data_transport;
+struct rawrtc_sctp_capabilities;
+struct rawrtc_sctp_transport_context;
+
+/**
+ * Missing in usrsctp.h
+ */
+#define SCTP_PLUGGABLE_CC 0x00001202
+
+/**
+ * SCTP transport checksum configuration flags.
+ * TODO: Add configuration to make these applyable.
+ */
+enum rawrtc_sctp_transport_checksum_flags {
+ RAWRTC_SCTP_TRANSPORT_CHECKSUM_ENABLE_ALL = 0,
+ RAWRTC_SCTP_TRANSPORT_CHECKSUM_DISABLE_INBOUND = 1 << 0,
+ RAWRTC_SCTP_TRANSPORT_CHECKSUM_DISABLE_OUTBOUND = 1 << 1,
+ RAWRTC_SCTP_TRANSPORT_CHECKSUM_DISABLE_ALL = (1 << 0 | 1 << 1),
+};
+
+/**
+ * SCTP transport state.
+ */
+enum rawrtc_sctp_transport_state {
+ RAWRTC_SCTP_TRANSPORT_STATE_NEW,
+ RAWRTC_SCTP_TRANSPORT_STATE_CONNECTING,
+ RAWRTC_SCTP_TRANSPORT_STATE_CONNECTED,
+ RAWRTC_SCTP_TRANSPORT_STATE_CLOSED,
+};
+
+/**
+ * SCTP transport congestion control algorithm.
+ *
+ * Note: RFC 2581 is the original default algorithm for TCP.
+ */
+enum rawrtc_sctp_transport_congestion_ctrl {
+ RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RFC2581,
+ RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HSTCP,
+ RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_HTCP,
+ RAWRTC_SCTP_TRANSPORT_CONGESTION_CTRL_RTCC,
+};
+
+/**
+ * SCTP transport default MTU.
+ */
+enum {
+ RAWRTC_SCTP_TRANSPORT_DEFAULT_MTU = 1200,
+};
+
+/**
+ * SCTP transport.
+ */
+struct rawrtc_sctp_transport;
+
+/**
+ * SCTP transport state change handler.
+ */
+typedef void (*rawrtc_sctp_transport_state_change_handler)(
+ enum rawrtc_sctp_transport_state const state, void* const arg);
+
+/**
+ * Create an SCTP transport from an external DTLS transport.
+ * `*transportp` must be unreferenced.
+ *
+ * Note: The underlying DTLS transport is supposed to be immediately
+ * attached after creation of this transport.
+ */
+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
+);
+
+/**
+ * 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
+);
+
+/**
+ * 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
+);
+
+/**
+ * Stop and close the SCTP transport.
+ */
+enum rawrtc_code rawrtc_sctp_transport_stop(struct rawrtc_sctp_transport* const transport);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * 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
+);
+
+/**
+ * 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);
+
+/**
+ * Get the corresponding name for an SCTP transport state.
+ */
+char const* rawrtc_sctp_transport_state_to_name(enum rawrtc_sctp_transport_state const state);
+
+/*
+ * 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);
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..23a2630
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,117 @@
+# Project definition
+project('rawrtcdc', 'c',
+ version: '0.1.3',
+ default_options: ['c_std=c99'],
+ meson_version: '>=0.46.0')
+
+# Set compiler warning flags
+compiler = meson.get_compiler('c')
+compiler_args = compiler.get_supported_arguments([
+ '-Wall',
+ '-Wmissing-declarations',
+ '-Wmissing-prototypes',
+ '-Wstrict-prototypes',
+ '-Wbad-function-cast',
+ '-Wsign-compare',
+ '-Wnested-externs',
+ '-Wshadow',
+ '-Waggregate-return',
+ '-Wcast-align',
+ '-Wextra',
+ '-Wold-style-definition',
+ '-Wdeclaration-after-statement',
+ '-Wuninitialized',
+ '-Wshorten-64-to-32',
+ '-pedantic',
+])
+add_project_arguments(compiler_args, language: 'c')
+
+# Configuration
+configuration = configuration_data()
+
+# Dependency: re
+# Note: We need to force using our own fork until re has accepted all our patches
+re_dep = dependency('librawrre',
+ version: '>=0.6.0',
+ fallback: ['re', 're_dep'],
+ required: true)
+
+# Dependency: usrsctp
+sctp_debug = get_option('debug_level') >= 7
+usrsctp_dep = dependency('usrsctp',
+ version: ['>=1.0.0', '<2'],
+ fallback: ['usrsctp', 'usrsctp_dep'],
+ default_options: [
+ 'sctp_build_programs=false',
+ 'sctp_debug=@0@'.format(sctp_debug),
+ 'sctp_inet=false',
+ 'sctp_inet6=false',
+ ],
+ required: true)
+
+# Dependency: rawrtcc
+rawrtcc_dep = dependency('rawrtcc',
+ version: '>=0.1.3',
+ fallback: ['rawrtcc', 'rawrtcc_dep'],
+ required: true)
+
+# Dependencies list
+dependencies = [
+ re_dep,
+ usrsctp_dep,
+ rawrtcc_dep,
+]
+
+# Feature: Hardware CRC32-C (requires SSE 4.2)
+# Note: If it compiles, it only enables the check for SSE 4.2 at runtime.
+code = '''
+#include <stdint.h>
+int main(int argc, char* argv[]) {
+ uint32_t eax, ecx;
+ eax = 1;
+ __asm__("cpuid" : "=c"(ecx) : "a"(eax) : "%ebx", "%edx");
+ return 0;
+}
+'''
+have_cpuid = compiler.compiles(code) and host_machine.cpu() == 'x86_64'
+configuration.set10('RAWRTCDC_ENABLE_SSE42_CRC32C', have_cpuid)
+
+# Options
+configuration.set10('RAWRTCDC_HAVE_SCTP_REDIRECT_TRANSPORT', get_option('sctp_redirect_transport'))
+
+# Version
+version = meson.project_version()
+version_array = version.split('.')
+configuration.set_quoted('RAWRTCDC_VERSION', version)
+configuration.set('RAWRTCDC_VERSION_MAJOR', version_array[0])
+configuration.set('RAWRTCDC_VERSION_MINOR', version_array[1])
+configuration.set('RAWRTCDC_VERSION_PATCH', version_array[2])
+
+# Set debug level
+configuration.set('RAWRTC_DEBUG_LEVEL', get_option('debug_level'))
+
+# Includes
+include_dir = include_directories('include')
+subdir('include')
+
+# Sources
+subdir('src')
+
+# Build library
+rawrtcdc = library(meson.project_name(), sources,
+ dependencies: dependencies,
+ include_directories: include_dir,
+ install: true,
+ version: version)
+
+# Declare dependency
+rawrtcdc_dep = declare_dependency(
+ include_directories: include_dir,
+ link_with: rawrtcdc)
+
+# Generate pkg-config file
+pkg = import('pkgconfig')
+pkg.generate(rawrtcdc,
+ name: meson.project_name(),
+ description: 'A standalone WebRTC and ORTC data channel implementation.',
+ url: 'https://github.com/rawrtc/rawrtc-data-channel')
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..934989c
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,4 @@
+option('debug_level', type: 'integer', min: 0, max: 7, value: 5,
+ description: 'Global debug level', yield: true)
+option('sctp_redirect_transport', type: 'boolean', value: false,
+ description: 'Build SCTP redirect transport', yield: true)
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 "???";
+ }
+}
diff --git a/subprojects/rawrtcc.wrap b/subprojects/rawrtcc.wrap
new file mode 100644
index 0000000..6669766
--- /dev/null
+++ b/subprojects/rawrtcc.wrap
@@ -0,0 +1,4 @@
+[wrap-git]
+directory = rawrtcc
+url = https://github.com/rawrtc/rawrtc-common.git
+revision = v0.1.3
diff --git a/subprojects/re.wrap b/subprojects/re.wrap
new file mode 100644
index 0000000..329ac5a
--- /dev/null
+++ b/subprojects/re.wrap
@@ -0,0 +1,4 @@
+[wrap-git]
+directory = re
+url = https://github.com/rawrtc/re.git
+revision = 9384f3a5f38a03c871270fda566045b3bf57bbee
diff --git a/subprojects/usrsctp.wrap b/subprojects/usrsctp.wrap
new file mode 100644
index 0000000..340062f
--- /dev/null
+++ b/subprojects/usrsctp.wrap
@@ -0,0 +1,4 @@
+[wrap-git]
+directory = usrsctp
+url = https://github.com/sctplab/usrsctp.git
+revision = master