Bläddra i källkod

Added source code

Paul-Louis Ageneau 6 år sedan
förälder
incheckning
47e84a0290

+ 95 - 0
src/candidate.cpp

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "candidate.hpp"
+
+#include <algorithm>
+#include <sstream>
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+using std::string;
+
+namespace {
+
+inline bool hasprefix(const string &str, const string &prefix) {
+	return str.size() >= prefix.size() &&
+	       std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
+}
+
+} // namespace
+
+namespace rtc {
+
+Candidate::Candidate(string candidate, string mid) {
+	const string prefix{"candidate:"};
+	mCandidate =
+	    hasprefix(candidate, prefix) ? candidate.substr(prefix.size()) : std::move(candidate);
+	mMid = std::move(mid);
+
+	// See RFC 5245
+	std::stringstream ss(mCandidate);
+	int component{0}, priority{0};
+	string foundation, transport, node, service, typ_, type;
+	if (ss >> foundation >> component >> transport >> priority &&
+	    ss >> node >> service >> typ_ >> type && typ_ == "typ") {
+
+		// Try to resolv the node
+		struct addrinfo hints = {};
+		hints.ai_family = AF_UNSPEC;
+		hints.ai_flags = AI_ADDRCONFIG;
+		if (transport == "UDP" || transport == "udp") {
+			hints.ai_socktype = SOCK_DGRAM;
+			hints.ai_protocol = IPPROTO_UDP;
+		}
+		struct addrinfo *result = nullptr;
+		if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
+			for (auto p = result; p; p = p->ai_next)
+				if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
+					// Rewrite the candidate
+					char nodebuffer[MAX_NUMERICNODE_LEN];
+					char servbuffer[MAX_NUMERICSERV_LEN];
+					if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
+					                servbuffer, MAX_NUMERICSERV_LEN,
+					                NI_NUMERICHOST | NI_NUMERICHOST) == 0) {
+						const string left{ss.str()};
+						const char sp{' '};
+						ss.clear();
+						ss << foundation << sp << component << sp << transport << sp << priority;
+						ss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
+						ss << sp << left;
+						mCandidate = ss.str();
+						break;
+					}
+				}
+		}
+
+		freeaddrinfo(result);
+	}
+}
+
+string Candidate::candidate() const { return mCandidate; }
+
+string Candidate::mid() const { return mMid; }
+
+Candidate::operator string() const { return mCandidate; }
+
+} // namespace rtc
+

+ 45 - 0
src/candidate.hpp

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_CANDIDATE_H
+#define RTC_CANDIDATE_H
+
+#include "include.hpp"
+
+#include <string>
+
+namespace rtc {
+
+class Candidate {
+public:
+	Candidate(string candidate, string mid);
+
+	string candidate() const;
+	string mid() const;
+
+	operator string() const;
+
+private:
+	string mCandidate;
+	string mMid;
+};
+
+} // namespace rtc
+
+#endif
+

+ 170 - 0
src/certificate.cpp

@@ -0,0 +1,170 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "certificate.hpp"
+
+#include <cassert>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+
+#include <gnutls/crypto.h>
+
+using std::string;
+
+namespace {
+
+void check_gnutls(int ret, const string &message = "GnuTLS error") {
+	if (ret != GNUTLS_E_SUCCESS)
+		throw std::runtime_error(message + ": " + gnutls_strerror(ret));
+}
+
+gnutls_certificate_credentials_t *create_credentials() {
+	auto creds = new gnutls_certificate_credentials_t;
+	check_gnutls(gnutls_certificate_allocate_credentials(creds));
+	return creds;
+}
+
+void delete_credentials(gnutls_certificate_credentials_t *creds) {
+	gnutls_certificate_free_credentials(*creds);
+	delete creds;
+}
+
+gnutls_x509_crt_t *create_crt() {
+	auto crt = new gnutls_x509_crt_t;
+	check_gnutls(gnutls_x509_crt_init(crt));
+	return crt;
+}
+
+void delete_crt(gnutls_x509_crt_t *crt) {
+	gnutls_x509_crt_deinit(*crt);
+	delete crt;
+}
+
+gnutls_x509_privkey_t *create_privkey() {
+	auto privkey = new gnutls_x509_privkey_t;
+	check_gnutls(gnutls_x509_privkey_init(privkey));
+	return privkey;
+}
+
+void delete_privkey(gnutls_x509_privkey_t *privkey) {
+	gnutls_x509_privkey_deinit(*privkey);
+	delete privkey;
+}
+
+gnutls_datum_t make_datum(char *data, size_t size) {
+	gnutls_datum_t datum;
+	datum.data = reinterpret_cast<unsigned char *>(data);
+	datum.size = size;
+	return datum;
+}
+
+gnutls_datum_t make_datum(const char *data, size_t size) {
+	return make_datum(const_cast<char *>(data), size);
+}
+
+} // namespace
+
+namespace rtc {
+
+Certificate::Certificate(string crt_pem, string key_pem)
+    : mCredentials(create_credentials(), delete_credentials) {
+
+	gnutls_datum_t crt_datum = make_datum(crt_pem.data(), crt_pem.size());
+	gnutls_datum_t key_datum = make_datum(key_pem.data(), key_pem.size());
+
+	check_gnutls(gnutls_certificate_set_x509_key_mem(*mCredentials, &crt_datum, &key_datum,
+	                                                 GNUTLS_X509_FMT_PEM),
+	             "Unable to import PEM");
+
+	auto create_crt_list = [this]() -> gnutls_x509_crt_t * {
+		gnutls_x509_crt_t *crt_list = nullptr;
+		unsigned int crt_list_size = 0;
+		check_gnutls(gnutls_certificate_get_x509_crt(*mCredentials, 0, &crt_list, &crt_list_size));
+		assert(crt_list_size == 1);
+		return crt_list;
+	};
+
+	auto delete_crt_list = [](gnutls_x509_crt_t *crt_list) {
+		gnutls_x509_crt_deinit(crt_list[0]);
+		gnutls_free(crt_list);
+	};
+
+	std::unique_ptr<gnutls_x509_crt_t, decltype(delete_crt_list)> crt_list(create_crt_list(),
+	                                                                       delete_crt_list);
+
+	mFingerprint = make_fingerprint(*crt_list);
+}
+
+Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
+    : mCredentials(create_credentials(), delete_credentials), mFingerprint(make_fingerprint(crt)) {
+
+	check_gnutls(gnutls_certificate_set_x509_key(*mCredentials, &crt, 1, privkey),
+	             "Unable to set certificate and key pair in credentials");
+}
+
+string Certificate::fingerprint() const {
+	return mFingerprint;
+}
+
+string make_fingerprint(gnutls_x509_crt_t crt) {
+	const size_t size = 32;
+	unsigned char buffer[size];
+	size_t len = size;
+	check_gnutls(gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_SHA256, buffer, &len),
+	             "X509 fingerprint error");
+
+	std::ostringstream oss;
+	oss << std::hex << std::uppercase << std::setfill('0');
+	for (size_t i = 0; i < len; ++i) {
+		if (i)
+			oss << std::setw(1) << ':';
+		oss << std::setw(2) << unsigned(buffer[i]);
+	}
+	return oss.str();
+}
+
+Certificate make_certificate(const string &common_name) {
+	std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt);
+	std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(),
+	                                                                          delete_privkey);
+
+	const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
+	check_gnutls(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
+	             "Unable to generate key pair");
+
+	gnutls_x509_crt_set_activation_time(*crt, std::time(NULL) - 3600);
+	gnutls_x509_crt_set_expiration_time(*crt, std::time(NULL) + 365 * 24 * 3600);
+	gnutls_x509_crt_set_version(*crt, 1);
+	gnutls_x509_crt_set_key(*crt, *privkey);
+	gnutls_x509_crt_set_dn_by_oid(*crt, GNUTLS_OID_X520_COMMON_NAME, 0, common_name.data(),
+	                              common_name.size());
+
+	const size_t serialSize = 16;
+	char serial[serialSize];
+	gnutls_rnd(GNUTLS_RND_NONCE, serial, serialSize);
+	gnutls_x509_crt_set_serial(*crt, serial, serialSize);
+
+	check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
+	             "Unable to auto-sign certificate");
+
+	return Certificate(*crt, *privkey);
+}
+
+} // namespace rtc
+

+ 50 - 0
src/certificate.hpp

@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_CERTIFICATE_H
+#define RTC_CERTIFICATE_H
+
+#include "include.hpp"
+
+#include <memory>
+#include <string>
+
+#include <gnutls/x509.h>
+
+namespace rtc {
+
+class Certificate {
+public:
+	Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
+	Certificate(string crt_pem, string key_pem);
+
+	string fingerprint() const;
+	gnutls_certificate_credentials_t credentials() const { return *mCredentials; }
+
+private:
+	std::shared_ptr<gnutls_certificate_credentials_t> mCredentials;
+	string mFingerprint;
+};
+
+string make_fingerprint(gnutls_x509_crt_t crt);
+Certificate make_certificate(const string &common_name);
+
+} // namespace rtc
+
+#endif
+

+ 56 - 0
src/channel.cpp

@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "channel.hpp"
+
+namespace rtc {
+
+void Channel::onOpen(std::function<void()> callback) { mOpenCallback = callback; }
+
+void Channel::onClosed(std::function<void()> callback) { mClosedCallback = callback; }
+
+void Channel::onError(std::function<void(const string &error)> callback) {
+	mErrorCallback = callback;
+}
+
+void Channel::onMessage(std::function<void(const std::variant<binary, string> &data)> callback) {
+	mMessageCallback = callback;
+}
+
+void Channel::triggerOpen(void) {
+	if (mOpenCallback)
+		mOpenCallback();
+}
+
+void Channel::triggerClosed(void) {
+	if (mClosedCallback)
+		mClosedCallback();
+}
+
+void Channel::triggerError(const string &error) {
+	if (mErrorCallback)
+		mErrorCallback(error);
+}
+
+void Channel::triggerMessage(const std::variant<binary, string> &data) {
+	if (mMessageCallback)
+		mMessageCallback(data);
+}
+
+} // namespace rtc
+

+ 57 - 0
src/channel.hpp

@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_CHANNEL_H
+#define RTC_CHANNEL_H
+
+#include "include.hpp"
+
+#include <functional>
+#include <variant>
+
+namespace rtc {
+
+class Channel {
+public:
+	virtual void close(void) = 0;
+	virtual void send(const std::variant<binary, string> &data) = 0;
+
+	virtual bool isOpen(void) const = 0;
+	virtual bool isClosed(void) const = 0;
+
+	void onOpen(std::function<void()> callback);
+	void onClosed(std::function<void()> callback);
+	void onError(std::function<void(const string &error)> callback);
+	void onMessage(std::function<void(const std::variant<binary, string> &data)> callback);
+
+protected:
+	virtual void triggerOpen(void);
+	virtual void triggerClosed(void);
+	virtual void triggerError(const string &error);
+	virtual void triggerMessage(const std::variant<binary, string> &data);
+
+private:
+	std::function<void()> mOpenCallback;
+	std::function<void()> mClosedCallback;
+	std::function<void(const string &)> mErrorCallback;
+	std::function<void(const std::variant<binary, string> &)> mMessageCallback;
+};
+
+} // namespace rtc
+
+#endif // RTC_CHANNEL_H

+ 211 - 0
src/datachannel.cpp

@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "datachannel.hpp"
+#include "peerconnection.hpp"
+#include "sctptransport.hpp"
+
+namespace rtc {
+
+using std::shared_ptr;
+
+enum MessageType : uint8_t {
+	MESSAGE_OPEN_REQUEST = 0x00,
+	MESSAGE_OPEN_RESPONSE = 0x01,
+	MESSAGE_ACK = 0x02,
+	MESSAGE_OPEN = 0x03,
+	MESSAGE_CLOSE = 0x04
+};
+
+struct OpenMessage {
+	uint8_t type = MESSAGE_OPEN;
+	uint8_t channelType;
+	uint16_t priority;
+	uint32_t reliabilityParameter;
+	uint16_t labelLength;
+	uint16_t protocolLength;
+	// label
+	// protocol
+};
+
+struct AckMessage {
+	uint8_t type = MESSAGE_ACK;
+};
+
+struct CloseMessage {
+	uint8_t type = MESSAGE_CLOSE;
+};
+
+DataChannel::DataChannel(shared_ptr<SctpTransport> sctpTransport, unsigned int streamId)
+    : mSctpTransport(sctpTransport), mStreamId(streamId) {}
+
+DataChannel::DataChannel(shared_ptr<SctpTransport> sctpTransport, unsigned int streamId,
+                         string label, string protocol, Reliability reliability)
+    : DataChannel(sctpTransport, streamId) {
+	mLabel = std::move(label);
+	mProtocol = std::move(protocol);
+	mReliability = std::make_shared<Reliability>(std::move(reliability));
+}
+
+DataChannel::~DataChannel() { close(); }
+
+void DataChannel::close() {
+	mIsOpen = false;
+	if (!mIsClosed) {
+		mIsClosed = true;
+		mSctpTransport->reset(mStreamId);
+	}
+}
+
+void DataChannel::send(const std::variant<binary, string> &data) {
+	std::visit(
+	    [this](const auto &d) {
+		    using T = std::decay_t<decltype(d)>;
+		    constexpr auto type = std::is_same_v<T, string> ? Message::String : Message::Binary;
+		    auto *b = reinterpret_cast<const byte *>(d.data());
+		    mSctpTransport->send(make_message(b, b + d.size(), type, mStreamId, mReliability));
+	    },
+	    data);
+}
+
+void DataChannel::send(const byte *data, size_t size) {
+	mSctpTransport->send(make_message(data, data + size, Message::Binary, mStreamId));
+}
+
+string DataChannel::label() const { return mLabel; }
+
+string DataChannel::protocol() const { return mProtocol; }
+
+Reliability DataChannel::reliability() const { return *mReliability; }
+
+bool DataChannel::isOpen(void) const { return mIsOpen; }
+
+bool DataChannel::isClosed(void) const { return mIsClosed; }
+
+void DataChannel::open() {
+	uint8_t channelType = static_cast<uint8_t>(mReliability->type);
+	if (mReliability->unordered)
+		channelType &= 0x80;
+
+	using std::chrono::milliseconds;
+	uint32_t reliabilityParameter = 0;
+	if (mReliability->type == Reliability::TYPE_PARTIAL_RELIABLE_REXMIT)
+		reliabilityParameter = uint32_t(std::get<int>(mReliability->rexmit));
+	else if (mReliability->type == Reliability::TYPE_PARTIAL_RELIABLE_TIMED)
+		reliabilityParameter = uint32_t(std::get<milliseconds>(mReliability->rexmit).count());
+
+	const size_t len = sizeof(OpenMessage) + mLabel.size() + mProtocol.size();
+	binary buffer(len, byte(0));
+	auto &open = *reinterpret_cast<OpenMessage *>(buffer.data());
+	open.type = MESSAGE_OPEN;
+	open.channelType = mReliability->type;
+	open.priority = htons(0);
+	open.reliabilityParameter = htonl(reliabilityParameter);
+	open.labelLength = htons(mLabel.size());
+	open.protocolLength = htons(mProtocol.size());
+
+	auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
+	std::copy(mLabel.begin(), mLabel.end(), end);
+	std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
+
+	mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStreamId));
+}
+
+void DataChannel::incoming(message_ptr message) {
+	switch (message->type) {
+	case Message::Control: {
+		auto raw = reinterpret_cast<const uint8_t *>(message->data());
+		switch (raw[0]) {
+		case MESSAGE_OPEN:
+			processOpenMessage(message);
+			break;
+		case MESSAGE_ACK:
+			if (!mIsOpen) {
+				mIsOpen = true;
+				triggerOpen();
+			}
+			break;
+		case MESSAGE_CLOSE:
+			if (mIsOpen) {
+				close();
+				triggerClosed();
+			}
+			break;
+		default:
+			// Ignore
+			break;
+		}
+		break;
+	}
+	case Message::String: {
+		triggerMessage(string(reinterpret_cast<const char *>(message->data()), message->size()));
+		break;
+	}
+	case Message::Binary: {
+		triggerMessage(*message);
+		break;
+	}
+	}
+}
+
+void DataChannel::processOpenMessage(message_ptr message) {
+	auto *raw = reinterpret_cast<const uint8_t *>(message->data());
+
+	if (message->size() < 12)
+		throw std::invalid_argument("DataChannel open message too small");
+
+	OpenMessage open;
+	open.channelType = raw[1];
+	open.priority = (raw[2] << 8) + raw[3];
+	open.reliabilityParameter = (raw[4] << 24) + (raw[5] << 16) + (raw[6] << 8) + raw[7];
+	open.labelLength = (raw[8] << 8) + raw[9];
+	open.protocolLength = (raw[10] << 8) + raw[11];
+
+	if (message->size() < 12 + open.labelLength + open.protocolLength)
+		throw std::invalid_argument("DataChannel open message truncated");
+
+	mLabel.assign(reinterpret_cast<const char *>(raw + 12), open.labelLength);
+	mProtocol.assign(reinterpret_cast<const char *>(raw + 12 + open.labelLength),
+	                 open.protocolLength);
+
+	using std::chrono::milliseconds;
+	mReliability->unordered = (open.reliabilityParameter & 0x80) != 0;
+	switch (open.channelType & 0x7F) {
+	case Reliability::TYPE_PARTIAL_RELIABLE_REXMIT:
+		mReliability->type = Reliability::TYPE_PARTIAL_RELIABLE_REXMIT;
+		mReliability->rexmit = int(open.reliabilityParameter);
+		break;
+	case Reliability::TYPE_PARTIAL_RELIABLE_TIMED:
+		mReliability->type = Reliability::TYPE_PARTIAL_RELIABLE_TIMED;
+		mReliability->rexmit = milliseconds(open.reliabilityParameter);
+		break;
+	default:
+		mReliability->type = Reliability::TYPE_RELIABLE;
+		mReliability->rexmit = int(0);
+	}
+
+	binary buffer(sizeof(AckMessage), byte(0));
+	auto &ack = *reinterpret_cast<AckMessage *>(buffer.data());
+	ack.type = MESSAGE_ACK;
+
+	mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStreamId));
+
+	triggerOpen();
+}
+
+} // namespace rtc

+ 75 - 0
src/datachannel.hpp

@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_DATA_CHANNEL_H
+#define RTC_DATA_CHANNEL_H
+
+#include "channel.hpp"
+#include "include.hpp"
+#include "message.hpp"
+#include "reliability.hpp"
+
+#include <chrono>
+#include <functional>
+#include <variant>
+
+namespace rtc {
+
+class SctpTransport;
+class PeerConnection;
+
+class DataChannel : public Channel {
+public:
+	DataChannel(std::shared_ptr<SctpTransport> sctpTransport, unsigned int streamId);
+	DataChannel(std::shared_ptr<SctpTransport> sctpTransport, unsigned int streamId, string label,
+	            string protocol, Reliability reliability);
+	~DataChannel();
+
+	void close(void);
+	void send(const std::variant<binary, string> &data);
+	void send(const byte *data, size_t size);
+
+	string label() const;
+	string protocol() const;
+	Reliability reliability() const;
+
+	bool isOpen(void) const;
+	bool isClosed(void) const;
+
+private:
+	void open();
+	void incoming(message_ptr message);
+	void processOpenMessage(message_ptr message);
+
+	const std::shared_ptr<SctpTransport> mSctpTransport;
+	const unsigned int mStreamId;
+
+	string mLabel;
+	string mProtocol;
+	std::shared_ptr<Reliability> mReliability;
+
+	bool mIsOpen = false;
+	bool mIsClosed = false;
+
+	friend class PeerConnection;
+};
+
+} // namespace rtc
+
+#endif
+

+ 114 - 0
src/description.cpp

@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "description.hpp"
+
+#include <algorithm>
+#include <cctype>
+#include <chrono>
+#include <random>
+#include <sstream>
+
+using std::size_t;
+using std::string;
+
+namespace {
+
+inline bool hasprefix(const string &str, const string &prefix) {
+	return str.size() >= prefix.size() &&
+	       std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
+}
+
+inline void trim_end(string &str) {
+	str.erase(
+	    std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
+	    str.end());
+}
+
+} // namespace
+
+namespace rtc {
+
+Description::Description(Role role, const string &mid) : mRole(role), mMid(mid) {
+	auto seed = std::chrono::system_clock::now().time_since_epoch().count();
+	std::default_random_engine generator(seed);
+	std::uniform_int_distribution<uint32_t> uniform;
+	mSessionId = std::to_string(uniform(generator));
+}
+
+Description::Description(const string &sdp) {
+	std::istringstream ss(sdp);
+	string line;
+	while (std::getline(ss, line)) {
+		trim_end(line);
+		if (hasprefix(line, "a=setup:")) {
+			const string setup = line.substr(line.find(':') + 1);
+			if (setup == "active" && mRole == Role::Active) {
+				mRole = Role::Passive;
+			} else if (setup == "passive" && mRole == Role::Passive) {
+				mRole = Role::Active;
+			} else { // actpass, nothing to do
+			}
+		} else if (hasprefix(line, "a=mid:")) {
+			mMid = line.substr(line.find(':') + 1);
+		} else if (hasprefix(line, "a=fingerprint:sha-256")) {
+			mFingerprint = line.substr(line.find(':') + 1);
+		}
+	}
+}
+
+Description::Role Description::role() const { return mRole; }
+
+void Description::setFingerprint(const string &fingerprint) { mFingerprint = fingerprint; }
+
+void Description::addCandidate(Candidate candidate) {
+	mCandidates.emplace_back(std::move(candidate));
+}
+
+void Description::addCandidate(Candidate &&candidate) {
+	mCandidates.emplace_back(std::forward<Candidate>(candidate));
+}
+
+Description::operator string() const {
+	if (!mFingerprint)
+		throw std::runtime_error("Fingerprint must be set to generate a SDP");
+
+    std::ostringstream sdp;
+	sdp << "v=0\r\n";
+	sdp << "o=- " << mSessionId << " 0 IN IP4 0.0.0.0\r\n";
+	sdp << "s=-\r\n";
+	sdp << "t=0 0\r\n";
+    sdp << "m=application 0 UDP/DTLS/SCTP webrtc-datachannel\r\n";
+    sdp << "c=IN IP4 0.0.0.0\r\n";
+	sdp << "a=ice-options:trickle\r\n";
+	sdp << "a=mid:" << mMid << "\r\n";
+	sdp << "a=setup:" << (mRole == Role::Active ? "active" : "passive") << "\r\n";
+	sdp << "a=dtls-id:1\r\n";
+	sdp << "a=fingerprint:sha-256 " << *mFingerprint << "\r\n";
+    sdp << "a=sctp-port:5000\r\n";
+    // sdp << "a=max-message-size:100000\r\n";
+
+	for (const auto &candidate : mCandidates) {
+		sdp << "a=candidate:" << string(candidate);
+	}
+
+	return sdp.str();
+}
+
+} // namespace rtc
+

+ 58 - 0
src/description.hpp

@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_DESCRIPTION_H
+#define RTC_DESCRIPTION_H
+
+#include "candidate.hpp"
+#include "include.hpp"
+
+#include <map>
+#include <optional>
+#include <vector>
+
+namespace rtc {
+
+class Description {
+public:
+	enum class Role { Passive, Active, ActPass };
+
+	Description(Role role, const string &mid = "0");
+	Description(const string &sdp);
+
+	Role role() const;
+
+	void setFingerprint(const string &fingerprint);
+	void addCandidate(Candidate candidate);
+	void addCandidate(Candidate &&candidate);
+
+	operator string() const;
+
+private:
+	Role mRole = Role::Passive;
+	string mSessionId;
+	string mMid;
+	std::optional<string> mFingerprint;
+
+	std::vector<Candidate> mCandidates;
+};
+
+} // namespace rtc
+
+#endif
+

+ 175 - 0
src/dtlstransport.cpp

@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "dtlstransport.hpp"
+#include "icetransport.hpp"
+
+#include <cassert>
+#include <cstring>
+#include <exception>
+
+#include <gnutls/dtls.h>
+
+using std::string;
+
+namespace {
+
+static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
+	if (ret < 0) {
+		if (!gnutls_error_is_fatal(ret))
+			return false;
+		throw std::runtime_error(message + ": " + gnutls_strerror(ret));
+	}
+	return true;
+}
+
+} // namespace
+
+namespace rtc {
+
+using std::shared_ptr;
+
+DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, Certificate certificate,
+                             verifier_callback verifier, ready_callback ready)
+    : Transport(lower), mCertificate(std::move(certificate)),
+      mVerifierCallback(std::move(verifier)), mReadyCallback(std::move(ready)) {
+	gnutls_certificate_set_verify_function(mCertificate.credentials(), CertificateCallback);
+
+	bool active = lower->role() == Description::Role::Active;
+	unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER);
+	check_gnutls(gnutls_init(&mSession, flags));
+
+	const char *priorities = "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128";
+	const char *err_pos = NULL;
+	check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
+	             "Unable to set TLS priorities");
+
+	gnutls_session_set_ptr(mSession, this);
+	gnutls_transport_set_ptr(mSession, this);
+	gnutls_transport_set_push_function(mSession, WriteCallback);
+	gnutls_transport_set_pull_function(mSession, ReadCallback);
+	gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
+
+	check_gnutls(
+	    gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate.credentials()));
+
+	mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
+}
+
+DtlsTransport::~DtlsTransport() {
+	mIncomingQueue.stop();
+	if (mRecvThread.joinable())
+		mRecvThread.join();
+
+	gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
+	gnutls_deinit(mSession);
+}
+
+bool DtlsTransport::send(message_ptr message) {
+	while (true) {
+		ssize_t ret = gnutls_record_send(mSession, message->data(), message->size());
+		if (check_gnutls(ret)) {
+			return ret > 0;
+		}
+	}
+}
+
+void DtlsTransport::incoming(message_ptr message) { mIncomingQueue.push(message); }
+
+void DtlsTransport::runRecvLoop() {
+	while (!check_gnutls(gnutls_handshake(mSession), "TLS handshake failed")) {
+	}
+
+	mReadyCallback();
+
+	const size_t bufferSize = 2048;
+	char buffer[bufferSize];
+
+	while (true) {
+		ssize_t ret = gnutls_record_recv(mSession, buffer, bufferSize);
+		if (check_gnutls(ret)) {
+			if (ret == 0) {
+				// Closed
+				break;
+			}
+			auto *b = reinterpret_cast<byte *>(buffer);
+			recv(make_message(b, b + ret));
+		}
+	}
+}
+
+int DtlsTransport::CertificateCallback(gnutls_session_t session) {
+	DtlsTransport *t = static_cast<DtlsTransport *>(gnutls_session_get_ptr(session));
+
+	if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
+		return GNUTLS_E_CERTIFICATE_ERROR;
+	}
+
+	// Get peer's certificate
+	unsigned int count = 0;
+	const gnutls_datum_t *array = gnutls_certificate_get_peers(session, &count);
+	if (!array || count == 0) {
+		return GNUTLS_E_CERTIFICATE_ERROR;
+	}
+
+	gnutls_x509_crt_t crt;
+	check_gnutls(gnutls_x509_crt_init(&crt));
+	int ret = gnutls_x509_crt_import(crt, &array[0], GNUTLS_X509_FMT_DER);
+	if (ret != GNUTLS_E_SUCCESS) {
+		gnutls_x509_crt_deinit(crt);
+		return GNUTLS_E_CERTIFICATE_ERROR;
+	}
+
+	string fingerprint = make_fingerprint(crt);
+	gnutls_x509_crt_deinit(crt);
+
+	bool success = t->mVerifierCallback(fingerprint);
+	return success ? GNUTLS_E_SUCCESS : GNUTLS_E_CERTIFICATE_ERROR;
+}
+
+ssize_t DtlsTransport::WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len) {
+	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
+	if (len > 0) {
+		auto b = reinterpret_cast<const byte *>(data);
+		t->outgoing(make_message(b, b + len));
+	}
+	gnutls_transport_set_errno(t->mSession, 0);
+	return ssize_t(len);
+}
+
+ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
+	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
+	auto next = t->mIncomingQueue.pop();
+	if (!next) {
+		// Closed
+		gnutls_transport_set_errno(t->mSession, 0);
+		return 0;
+	}
+
+	auto message = *next;
+	ssize_t len = std::min(maxlen, message->size());
+	std::memcpy(data, message->data(), len);
+	gnutls_transport_set_errno(t->mSession, 0);
+	return len;
+}
+
+int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
+	return 1; // So ReadCallback is called
+}
+
+} // namespace rtc

+ 71 - 0
src/dtlstransport.hpp

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_DTLS_TRANSPORT_H
+#define RTC_DTLS_TRANSPORT_H
+
+#include "include.hpp"
+#include "peerconnection.hpp"
+#include "queue.hpp"
+#include "transport.hpp"
+
+#include <functional>
+#include <memory>
+#include <thread>
+
+#include <gnutls/gnutls.h>
+
+namespace rtc {
+
+class IceTransport;
+
+class DtlsTransport : public Transport {
+public:
+	using verifier_callback = std::function<bool(const std::string &fingerprint)>;
+	using ready_callback = std::function<void(void)>;
+
+	DtlsTransport(std::shared_ptr<IceTransport> lower, Certificate certificate,
+	              verifier_callback verifier, ready_callback ready);
+	~DtlsTransport();
+
+	const Certificate *certificate();
+
+	bool send(message_ptr message);
+
+private:
+	void incoming(message_ptr message);
+	void runRecvLoop();
+
+	const Certificate mCertificate;
+
+	gnutls_session_t mSession;
+	Queue<message_ptr> mIncomingQueue;
+	std::thread mRecvThread;
+	verifier_callback mVerifierCallback;
+	ready_callback mReadyCallback;
+
+	static int CertificateCallback(gnutls_session_t session);
+	static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len);
+	static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
+	static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
+};
+
+} // namespace rtc
+
+#endif
+

+ 44 - 0
src/iceconfiguration.hpp

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_ICE_CONFIGURATION_H
+#define RTC_ICE_CONFIGURATION_H
+
+#include "include.hpp"
+#include "message.hpp"
+#include "queue.hpp"
+
+#include <vector>
+
+namespace rtc {
+
+struct IceServer {
+	string hostname;
+	string service;
+};
+
+struct IceConfiguration {
+	std::vector<IceServer> servers;
+	uint16_t portRangeBegin = 0;
+	uint16_t portRangeEnd = 0xFFFF;
+};
+
+} // namespace rtc
+
+#endif
+

+ 221 - 0
src/icetransport.cpp

@@ -0,0 +1,221 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "icetransport.hpp"
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+#include <random>
+#include <sstream>
+
+namespace rtc {
+
+using std::shared_ptr;
+using std::weak_ptr;
+
+IceTransport::IceTransport(const IceConfiguration &config, Description::Role role,
+                           candidate_callback candidateCallback, ready_callback ready)
+    : mRole(role), mCandidateCallback(std::move(candidateCallback)), mReadyCallback(ready),
+      mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
+
+	auto logLevelFlags = GLogLevelFlags(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION);
+	g_log_set_handler(nullptr, logLevelFlags, LogCallback, this);
+	nice_debug_disable(true);
+
+	mMainLoop = decltype(mMainLoop)(g_main_loop_new(nullptr, FALSE), g_main_loop_unref);
+	if (!mMainLoop)
+		std::runtime_error("Failed to create the main loop");
+
+	mNiceAgent = decltype(mNiceAgent)(
+	    nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245),
+	    g_object_unref);
+
+	if (!mNiceAgent)
+		throw std::runtime_error("Failed to create the nice agent");
+
+	mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get());
+	g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", false, nullptr);
+	g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", 0, nullptr);
+
+	std::vector<IceServer> servers = config.servers;
+	unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
+	std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
+
+	bool success = false;
+	for (const auto &server : servers) {
+		struct addrinfo hints = {};
+		hints.ai_family = AF_INET; // IPv4
+		hints.ai_socktype = SOCK_DGRAM;
+		hints.ai_protocol = IPPROTO_UDP;
+		hints.ai_flags = AI_ADDRCONFIG;
+		struct addrinfo *result = nullptr;
+		if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
+			continue;
+
+		for (auto p = result; p; p = p->ai_next) {
+			if(p->ai_family == AF_INET) {
+				char nodebuffer[MAX_NUMERICNODE_LEN];
+				char servbuffer[MAX_NUMERICSERV_LEN];
+				if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
+				                servbuffer, MAX_NUMERICNODE_LEN,
+				                NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
+					g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr);
+					g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port",
+					             std::atoi(servbuffer), nullptr);
+					success = true;
+					break;
+				}
+			}
+		}
+
+		freeaddrinfo(result);
+	    if (success)
+		    break;
+	}
+
+	g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
+	                 G_CALLBACK(StateChangedCallback), this);
+	g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
+	                 G_CALLBACK(CandidateCallback), this);
+	g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
+	                 G_CALLBACK(GatheringDoneCallback), this);
+
+	mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
+	if (mStreamId == 0)
+	    throw std::runtime_error("Failed to add a stream");
+
+    nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
+    nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1,
+                              config.portRangeBegin, config.portRangeEnd);
+
+	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
+	                       RecvCallback, this);
+}
+
+IceTransport::~IceTransport() {
+	g_main_loop_quit(mMainLoop.get());
+	if (mMainLoopThread.joinable())
+		mMainLoopThread.join();
+}
+
+Description::Role IceTransport::role() const { return mRole; }
+
+IceTransport::State IceTransport::state() const { return mState; }
+
+Description IceTransport::getLocalDescription() const {
+	std::unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()),
+	                                               g_free);
+	return Description(string(sdp.get()));
+}
+
+void IceTransport::setRemoteDescription(const Description &description) {
+	if (nice_agent_parse_remote_sdp(mNiceAgent.get(), string(description).c_str()))
+		throw std::runtime_error("Unable to parse remote SDP");
+}
+
+void IceTransport::gatherLocalCandidates() {
+	if (!nice_agent_gather_candidates(mNiceAgent.get(), mStreamId))
+		throw std::runtime_error("Unable to gather local ICE candidates");
+}
+
+bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
+	NiceCandidate *cand = nice_agent_parse_remote_candidate_sdp(mNiceAgent.get(), mStreamId,
+	                                                            string(candidate).c_str());
+	if (!cand)
+		return false;
+
+	GSList *list = g_slist_append(nullptr, cand);
+	int ret = nice_agent_set_remote_candidates(mNiceAgent.get(), mStreamId, 1, list);
+
+	g_slist_free_full(list, reinterpret_cast<GDestroyNotify>(nice_candidate_free));
+	return ret > 0;
+}
+
+bool IceTransport::send(message_ptr message) {
+	if (!mStreamId)
+		return false;
+
+	outgoing(message);
+	return true;
+}
+
+void IceTransport::incoming(message_ptr message) { recv(message); }
+
+void IceTransport::incoming(const byte *data, int size) {
+	incoming(make_message(data, data + size));
+}
+
+void IceTransport::outgoing(message_ptr message) {
+	nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
+	                reinterpret_cast<const char *>(message->data()));
+}
+
+void IceTransport::processCandidate(const string &candidate) {
+	mCandidateCallback(Candidate(candidate, getStreamName()));
+}
+
+void IceTransport::processGatheringDone() { mCandidateCallback(nullopt); }
+
+void IceTransport::changeState(uint32_t state) {
+	mState = static_cast<State>(state);
+	if (mState == State::CONNECTED)
+		mReadyCallback();
+}
+
+string IceTransport::getStreamName() const {
+	const gchar *stream = nice_agent_get_stream_name(mNiceAgent.get(), mStreamId);
+	return string(stream);
+}
+
+void IceTransport::CandidateCallback(NiceAgent *agent, NiceCandidate *candidate,
+                                     gpointer userData) {
+	auto iceTransport = static_cast<rtc::IceTransport *>(userData);
+	gchar *cand = nice_agent_generate_local_candidate_sdp(agent, candidate);
+	iceTransport->processCandidate(cand);
+	g_free(cand);
+}
+
+void IceTransport::GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData) {
+	auto iceTransport = static_cast<rtc::IceTransport *>(userData);
+	iceTransport->processGatheringDone();
+}
+
+void IceTransport::StateChangedCallback(NiceAgent *agent, guint streamId, guint componentId,
+                                        guint state, gpointer userData) {
+	auto iceTransport = static_cast<rtc::IceTransport *>(userData);
+	iceTransport->changeState(state);
+}
+
+void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componentId, guint len,
+                                gchar *buf, gpointer userData) {
+	auto iceTransport = static_cast<rtc::IceTransport *>(userData);
+	iceTransport->incoming(reinterpret_cast<std::byte *>(buf), len);
+}
+
+void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel, const gchar *message,
+                 gpointer userData) {
+	std::cout << message << std::endl;
+}
+
+} // namespace rtc
+

+ 100 - 0
src/icetransport.hpp

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_ICE_TRANSPORT_H
+#define RTC_ICE_TRANSPORT_H
+
+#include "candidate.hpp"
+#include "description.hpp"
+#include "iceconfiguration.hpp"
+#include "include.hpp"
+#include "peerconnection.hpp"
+#include "transport.hpp"
+
+extern "C" {
+#include <nice/agent.h>
+}
+
+#include <atomic>
+#include <optional>
+#include <thread>
+
+namespace rtc {
+
+class IceTransport : public Transport {
+public:
+	enum class State : uint32_t {
+		DISCONNECTED = NICE_COMPONENT_STATE_DISCONNECTED,
+		GATHERING = NICE_COMPONENT_STATE_GATHERING,
+		CONNECTING = NICE_COMPONENT_STATE_CONNECTING,
+		CONNECTED = NICE_COMPONENT_STATE_CONNECTED,
+		COMPLETED = NICE_COMPONENT_STATE_READY,
+		FAILED = NICE_COMPONENT_STATE_FAILED
+	};
+
+	using candidate_callback = std::function<void(const std::optional<Candidate> &candidate)>;
+	using ready_callback = std::function<void(void)>;
+
+	IceTransport(const IceConfiguration &config, Description::Role role,
+	             candidate_callback candidateCallback, ready_callback ready);
+	~IceTransport();
+
+	Description::Role role() const;
+	State state() const;
+	Description getLocalDescription() const;
+	void setRemoteDescription(const Description &description);
+	void gatherLocalCandidates();
+	bool addRemoteCandidate(const Candidate &candidate);
+
+	bool send(message_ptr message);
+
+private:
+	void incoming(message_ptr message);
+	void incoming(const byte *data, int size);
+	void outgoing(message_ptr message);
+
+	string getStreamName() const;
+
+	void changeState(uint32_t state);
+	void processCandidate(const string &candidate);
+	void processGatheringDone();
+
+	const Description::Role mRole;
+	State mState = State::DISCONNECTED;
+
+	uint32_t mStreamId = 0;
+	std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
+	std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
+	std::thread mMainLoopThread;
+	std::mutex mSendMutex;
+	candidate_callback mCandidateCallback;
+	ready_callback mReadyCallback;
+
+	static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData);
+	static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData);
+	static void StateChangedCallback(NiceAgent *agent, guint streamId, guint componentId,
+	                                 guint state, gpointer userData);
+	static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len,
+	                         gchar *buf, gpointer userData);
+	static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data);
+};
+
+} // namespace rtc
+
+#endif
+

+ 44 - 0
src/include.hpp

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_INCLUDE_H
+#define RTC_INCLUDE_H
+
+#include <cstddef>
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace rtc {
+
+using std::byte;
+using std::string;
+using binary = std::vector<byte>;
+
+using std::nullopt;
+
+using std::uint16_t;
+using std::uint32_t;
+using std::uint64_t;
+using std::uint8_t;
+
+const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
+const size_t MAX_NUMERICSERV_LEN = 6;  // Max port string representation length
+}
+
+#endif

+ 59 - 0
src/message.hpp

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_MESSAGE_H
+#define RTC_MESSAGE_H
+
+#include "include.hpp"
+#include "reliability.hpp"
+
+#include <functional>
+#include <memory>
+
+namespace rtc {
+
+struct Message : binary {
+	enum Type { Binary, String, Control };
+
+        template <typename Iterator>
+        Message(Iterator begin_, Iterator end_, Type type_ = Binary,
+                unsigned int stream_ = 0,
+                std::shared_ptr<Reliability> reliability_ = nullptr)
+            : binary(begin_, end_), type(type_), stream(stream_),
+              reliability(reliability_) {}
+
+        Type type;
+	unsigned int stream;
+        std::shared_ptr<Reliability> reliability;
+};
+
+using message_ptr = std::shared_ptr<const Message>;
+using message_callback = std::function<void(message_ptr message)>;
+
+template <typename Iterator>
+message_ptr make_message(Iterator begin, Iterator end,
+                         Message::Type type = Message::Binary,
+                         unsigned int stream = 0,
+                         std::shared_ptr<Reliability> reliability = nullptr) {
+  return std::make_shared<Message>(begin, end, type, stream, reliability);
+}
+
+} // namespace rtc
+
+#endif
+

+ 161 - 0
src/peerconnection.cpp

@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "peerconnection.hpp"
+#include "dtlstransport.hpp"
+#include "icetransport.hpp"
+#include "sctptransport.hpp"
+
+#include <chrono>
+#include <random>
+#include <sstream>
+
+namespace rtc {
+
+using namespace std::placeholders;
+
+using std::function;
+using std::shared_ptr;
+
+PeerConnection::PeerConnection(const IceConfiguration &config)
+    : mConfig(config), mCertificate(make_certificate("libdatachannel")), mMid("0") {}
+
+PeerConnection::~PeerConnection() {
+}
+
+const IceConfiguration *PeerConnection::config() const { return &mConfig; }
+
+const Certificate *PeerConnection::certificate() const { return &mCertificate; }
+
+void PeerConnection::setRemoteDescription(const string &description) {
+	Description desc(description);
+	if (!mIceTransport) {
+		initIceTransport(Description::Role::ActPass);
+		mIceTransport->setRemoteDescription(desc);
+		triggerLocalDescription();
+		mIceTransport->gatherLocalCandidates();
+	} else {
+		mIceTransport->setRemoteDescription(desc);
+	}
+}
+
+void PeerConnection::setRemoteCandidate(const string &candidate) {
+	Candidate cand(candidate, mMid);
+	if (mIceTransport) {
+		mIceTransport->addRemoteCandidate(cand);
+	}
+}
+
+shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
+                                                          const string &protocol,
+                                                          const Reliability &reliability) {
+	auto seed = std::chrono::system_clock::now().time_since_epoch().count();
+	std::default_random_engine generator(seed);
+	std::uniform_int_distribution<uint16_t> uniform;
+	uint16_t streamId = uniform(generator);
+
+	auto channel =
+	    std::make_shared<DataChannel>(mSctpTransport, streamId, label, protocol, reliability);
+	mDataChannels[streamId] = channel;
+
+	if (!mIceTransport) {
+		initIceTransport(Description::Role::Active);
+		triggerLocalDescription();
+		mIceTransport->gatherLocalCandidates();
+	} else if (mSctpTransport && mSctpTransport->isReady()) {
+		channel->open();
+	}
+	return channel;
+}
+
+void PeerConnection::onDataChannel(
+    std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback) {
+	mDataChannelCallback = callback;
+}
+
+void PeerConnection::onLocalDescription(std::function<void(const string &description)> callback) {
+	mLocalDescriptionCallback = callback;
+}
+
+void PeerConnection::onLocalCandidate(
+    std::function<void(const std::optional<string> &candidate)> callback) {
+	mLocalCandidateCallback = callback;
+}
+
+void PeerConnection::initIceTransport(Description::Role role) {
+	mIceTransport = std::make_shared<IceTransport>(
+	    mConfig, role, std::bind(&PeerConnection::triggerLocalCandidate, this, _1),
+	    std::bind(&PeerConnection::initDtlsTransport, this));
+}
+
+void PeerConnection::initDtlsTransport() {
+	mDtlsTransport = std::make_shared<DtlsTransport>(
+	    mIceTransport, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
+	    std::bind(&PeerConnection::initSctpTransport, this));
+}
+
+void PeerConnection::initSctpTransport() {
+  mSctpTransport = std::make_shared<SctpTransport>(
+      mDtlsTransport, std::bind(&PeerConnection::openDataChannels, this),
+      std::bind(&PeerConnection::forwardMessage, this, _1));
+}
+
+bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
+	return mRemoteFingerprint && *mRemoteFingerprint == fingerprint;
+}
+
+void PeerConnection::forwardMessage(message_ptr message) {
+	shared_ptr<DataChannel> channel;
+	if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
+		channel = it->second;
+	} else {
+		channel = std::make_shared<DataChannel>(mSctpTransport, message->stream);
+		channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this, channel));
+		mDataChannels.insert(std::make_pair(message->stream, channel));
+	}
+
+	channel->incoming(message);
+}
+
+void PeerConnection::openDataChannels(void) {
+  for (auto it = mDataChannels.begin(); it != mDataChannels.end(); ++it) {
+    it->second->open();
+  }
+}
+
+void PeerConnection::triggerLocalDescription() {
+	if (mLocalDescriptionCallback && mIceTransport) {
+		Description desc{mIceTransport->getLocalDescription()};
+		desc.setFingerprint(mCertificate.fingerprint());
+		mLocalDescriptionCallback(string(desc));
+	}
+}
+
+void PeerConnection::triggerLocalCandidate(const std::optional<Candidate> &candidate) {
+	if (mLocalCandidateCallback) {
+		mLocalCandidateCallback(candidate ? std::make_optional(string(*candidate)) : nullopt);
+	}
+}
+
+void PeerConnection::triggerDataChannel(std::shared_ptr<DataChannel> dataChannel) {
+	if (mDataChannelCallback)
+		mDataChannelCallback(dataChannel);
+}
+
+} // namespace rtc
+

+ 93 - 0
src/peerconnection.hpp

@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_PEER_CONNECTION_H
+#define RTC_PEER_CONNECTION_H
+
+#include "candidate.hpp"
+#include "certificate.hpp"
+#include "datachannel.hpp"
+#include "description.hpp"
+#include "iceconfiguration.hpp"
+#include "include.hpp"
+#include "message.hpp"
+#include "reliability.hpp"
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <unordered_map>
+
+namespace rtc {
+
+class IceTransport;
+class DtlsTransport;
+class SctpTransport;
+
+class PeerConnection {
+public:
+	PeerConnection(const IceConfiguration &config);
+	~PeerConnection();
+
+	const IceConfiguration *config() const;
+	const Certificate *certificate() const;
+
+	void setRemoteDescription(const string &description);
+	void setRemoteCandidate(const string &candidate);
+
+	std::shared_ptr<DataChannel> createDataChannel(const string &label, const string &protocol = "",
+	                                               const Reliability &reliability = {});
+
+	void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback);
+	void onLocalDescription(std::function<void(const string &description)> callback);
+	void onLocalCandidate(std::function<void(const std::optional<string> &candidate)> callback);
+
+private:
+	void initIceTransport(Description::Role role);
+	void initDtlsTransport();
+	void initSctpTransport();
+
+	bool checkFingerprint(const std::string &fingerprint) const;
+	void forwardMessage(message_ptr message);
+	void openDataChannels(void);
+
+	void triggerLocalDescription();
+	void triggerLocalCandidate(const std::optional<Candidate> &candidate);
+	void triggerDataChannel(std::shared_ptr<DataChannel> dataChannel);
+
+	const IceConfiguration mConfig;
+	Certificate mCertificate;
+	string mMid;
+	std::optional<string> mRemoteFingerprint;
+
+	std::shared_ptr<IceTransport> mIceTransport;
+	std::shared_ptr<DtlsTransport> mDtlsTransport;
+	std::shared_ptr<SctpTransport> mSctpTransport;
+
+	std::unordered_map<unsigned int, std::shared_ptr<DataChannel>> mDataChannels;
+
+	std::function<void(std::shared_ptr<DataChannel> dataChannel)> mDataChannelCallback;
+	std::function<void(const string &description)> mLocalDescriptionCallback;
+	std::function<void(const std::optional<string> &candidate)> mLocalCandidateCallback;
+};
+
+} // namespace rtc
+
+#endif
+

+ 90 - 0
src/queue.hpp

@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_QUEUE_H
+#define RTC_QUEUE_H
+
+#include "include.hpp"
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <queue>
+
+namespace rtc {
+
+template <typename T> class Queue {
+public:
+	Queue();
+	~Queue();
+
+	void stop();
+	void push(const T &element);
+	std::optional<T> pop();
+
+	bool empty() const;
+
+private:
+	std::queue<T> mQueue;
+	std::condition_variable mCondition;
+	std::atomic<bool> mStopping;
+
+	mutable std::mutex mMutex;
+};
+
+template <typename T> Queue<T>::Queue() : mStopping(false) {}
+
+template <typename T> Queue<T>::~Queue() { stop(); }
+
+template <typename T> void Queue<T>::stop() {
+	std::lock_guard<std::mutex> lock(mMutex);
+	mStopping = true;
+	mCondition.notify_all();
+}
+
+template <typename T> void Queue<T>::push(const T &element) {
+	std::lock_guard<std::mutex> lock(mMutex);
+	if (mStopping)
+		return;
+	mQueue.push(element);
+	mCondition.notify_one();
+}
+
+template <typename T> std::optional<T> Queue<T>::pop() {
+	std::unique_lock<std::mutex> lock(mMutex);
+	while (mQueue.empty()) {
+		if (mStopping)
+			return nullopt;
+		mCondition.wait(lock);
+	}
+
+	std::optional<T> element = mQueue.front();
+	mQueue.pop();
+	return element;
+}
+
+template <typename T> bool Queue<T>::empty() const {
+	std::lock_guard<std::mutex> lock(mMutex);
+	return mQueue.empty();
+}
+
+} // namespace rtc
+
+#endif
+

+ 44 - 0
src/reliability.hpp

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_RELIABILITY_H
+#define RTC_RELIABILITY_H
+
+#include "include.hpp"
+
+#include <chrono>
+#include <variant>
+
+namespace rtc {
+
+struct Reliability {
+	enum Type : uint8_t {
+		TYPE_RELIABLE = 0x00,
+		TYPE_PARTIAL_RELIABLE_REXMIT = 0x01,
+		TYPE_PARTIAL_RELIABLE_TIMED = 0x02,
+	};
+
+	Type type = TYPE_RELIABLE;
+	bool unordered = false;
+	std::variant<int, std::chrono::milliseconds> rexmit = 0;
+};
+
+} // namespace rtc
+
+#endif
+

+ 303 - 0
src/sctptransport.cpp

@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sctptransport.hpp"
+
+#include <exception>
+#include <iostream>
+#include <vector>
+
+#include <arpa/inet.h>
+
+namespace rtc {
+
+using std::shared_ptr;
+
+SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, ready_callback ready,
+                             message_callback recv)
+    : Transport(lower), mReadyCallback(std::move(ready)), mLocalPort(5000), mRemotePort(5000) {
+
+	onRecv(recv);
+
+	usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	usrsctp_register_address(this);
+
+	mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::ReadCallback,
+	                       nullptr, 0, this);
+	if (!mSock)
+		throw std::runtime_error("Could not create usrsctp socket, errno=" + std::to_string(errno));
+
+	struct linger linger_opt = {};
+	linger_opt.l_onoff = 1;
+	linger_opt.l_linger = 0;
+	if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt)))
+		throw std::runtime_error("Could not set socket option SO_LINGER, errno=" +
+		                         std::to_string(errno));
+
+	struct sctp_paddrparams peer_param = {};
+	peer_param.spp_flags = SPP_PMTUD_DISABLE;
+	peer_param.spp_pathmtu = 1200; // TODO: MTU
+	if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_param,
+	                       sizeof(peer_param)))
+		throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" +
+		                         std::to_string(errno));
+
+	struct sctp_assoc_value av = {};
+	av.assoc_id = SCTP_ALL_ASSOC;
+	av.assoc_value = 1;
+	if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)))
+		throw std::runtime_error("Could not set socket option SCTP_ENABLE_STREAM_RESET, errno=" +
+		                         std::to_string(errno));
+
+	uint32_t nodelay = 1;
+	if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)))
+		throw std::runtime_error("Could not set socket option SCTP_NODELAY, errno=" +
+		                         std::to_string(errno));
+
+	const static uint16_t interested_events[] = {
+	    SCTP_ASSOC_CHANGE,          SCTP_PEER_ADDR_CHANGE,       SCTP_REMOTE_ERROR,
+	    SCTP_SEND_FAILED,           SCTP_SENDER_DRY_EVENT,       SCTP_SHUTDOWN_EVENT,
+	    SCTP_ADAPTATION_INDICATION, SCTP_PARTIAL_DELIVERY_EVENT, SCTP_AUTHENTICATION_EVENT,
+	    SCTP_STREAM_RESET_EVENT,    SCTP_ASSOC_RESET_EVENT,      SCTP_STREAM_CHANGE_EVENT,
+	    SCTP_SEND_FAILED_EVENT};
+
+	struct sctp_event event = {};
+	event.se_assoc_id = SCTP_ALL_ASSOC;
+	event.se_on = 1;
+	int num_events = sizeof(interested_events) / sizeof(uint16_t);
+	for (int i = 0; i < num_events; ++i) {
+		event.se_type = interested_events[i];
+		if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)))
+			throw std::runtime_error("Could not set socket option SCTP_EVENT, errno=" +
+			                         std::to_string(errno));
+	}
+
+	struct sctp_initmsg init_msg = {};
+	init_msg.sinit_num_ostreams = 0xFF;
+	init_msg.sinit_max_instreams = 0xFF;
+	if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_INITMSG, &init_msg, sizeof(init_msg)))
+		throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
+		                         std::to_string(errno));
+
+	struct sockaddr_conn sconn = {};
+	sconn.sconn_family = AF_CONN;
+	sconn.sconn_port = htons(mRemotePort);
+	sconn.sconn_addr = (void *)this;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(sconn);
+#endif
+
+	if (usrsctp_bind(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)))
+		throw std::runtime_error("Could not bind usrsctp socket, errno=" + std::to_string(errno));
+
+	mConnectThread = std::thread(&SctpTransport::runConnect, this);
+}
+
+SctpTransport::~SctpTransport() {
+	mStopping = true;
+	if (mConnectThread.joinable())
+		mConnectThread.join();
+
+	if (mSock) {
+		usrsctp_shutdown(mSock, SHUT_RDWR);
+		usrsctp_close(mSock);
+	}
+
+	usrsctp_deregister_address(this);
+	usrsctp_finish();
+}
+
+bool SctpTransport::isReady() const { return mIsReady; }
+
+void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
+	if (len != size_t(notify->sn_header.sn_length))
+		return;
+
+	switch (notify->sn_header.sn_type) {
+	case SCTP_STREAM_RESET_EVENT: {
+		const struct sctp_stream_reset_event *reset_event = &notify->sn_strreset_event;
+		const int count = (reset_event->strreset_length - sizeof(*reset_event)) / sizeof(uint16_t);
+
+		if (reset_event->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+			for (int i = 0; i < count; ++i) {
+				uint16_t streamId = reset_event->strreset_stream_list[i];
+				reset(streamId);
+			}
+		}
+
+		if (reset_event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+			const byte dataChannelCloseMessage = byte(0x04);
+			for (int i = 0; i < count; ++i) {
+				uint16_t streamId = reset_event->strreset_stream_list[i];
+				recv(make_message(&dataChannelCloseMessage, &dataChannelCloseMessage + 1,
+				                  Message::Control, streamId));
+			}
+		}
+		break;
+	}
+
+	default:
+		// Ignore
+		break;
+	}
+}
+
+int SctpTransport::WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos,
+                                 uint8_t set_df) {
+	return static_cast<SctpTransport *>(sctp_ptr)->handleWrite(data, len, tos, set_df);
+}
+
+int SctpTransport::handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df) {
+	const byte *b = reinterpret_cast<const byte *>(data);
+	outgoing(make_message(b, b + len));
+	return 0; // success
+}
+
+int SctpTransport::ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data,
+                                size_t len, struct sctp_rcvinfo recv_info, int flags,
+                                void *user_data) {
+	return static_cast<SctpTransport *>(user_data)->process(sock, addr, data, len, recv_info,
+	                                                        flags);
+}
+
+int SctpTransport::process(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
+                           struct sctp_rcvinfo recv_info, int flags) {
+	if (flags & MSG_NOTIFICATION) {
+		processNotification((union sctp_notification *)data, len);
+	} else {
+		processData((const byte *)data, len, recv_info.rcv_sid,
+		            PayloadId(ntohl(recv_info.rcv_ppid)));
+	}
+	free(data);
+	return 0;
+}
+
+void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
+	Message::Type type;
+	switch (ppid) {
+	case PPID_STRING:
+		type = Message::String;
+		break;
+	case PPID_STRING_EMPTY:
+		type = Message::String;
+		len = 0;
+		break;
+	case PPID_BINARY:
+		type = Message::Binary;
+		break;
+	case PPID_BINARY_EMPTY:
+		type = Message::Binary;
+		len = 0;
+	default:
+		type = Message::Control;
+		break;
+	}
+	recv(make_message(data, data + len, type, sid));
+}
+
+bool SctpTransport::send(message_ptr message) {
+  const Reliability reliability =
+      message->reliability ? *message->reliability : Reliability();
+
+  struct sctp_sendv_spa spa = {};
+
+  uint32_t ppid;
+  switch (message->type) {
+  case Message::String:
+    ppid = message->empty() ? PPID_STRING : PPID_STRING_EMPTY;
+    break;
+  case Message::Binary:
+    ppid = message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY;
+    break;
+  default:
+    ppid = PPID_CONTROL;
+    break;
+	}
+
+	// set sndinfo
+	spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID;
+	spa.sendv_sndinfo.snd_sid = uint16_t(message->stream);
+	spa.sendv_sndinfo.snd_ppid = ppid;
+	spa.sendv_sndinfo.snd_flags |= SCTP_EOR;
+
+	// set prinfo
+	spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+	if (reliability.unordered)
+		spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
+
+	using std::chrono::milliseconds;
+	switch (reliability.type) {
+	case Reliability::TYPE_PARTIAL_RELIABLE_REXMIT:
+		spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+		spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX;
+		spa.sendv_prinfo.pr_value = uint32_t(std::get<int>(reliability.rexmit));
+		break;
+	case Reliability::TYPE_PARTIAL_RELIABLE_TIMED:
+		spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+		spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL;
+		spa.sendv_prinfo.pr_value = uint32_t(std::get<milliseconds>(reliability.rexmit).count());
+		break;
+	default:
+		spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_NONE;
+		break;
+	}
+
+	if (!message->empty()) {
+		return usrsctp_sendv(mSock, message->data(), message->size(), nullptr, 0, &spa, sizeof(spa),
+		                     SCTP_SENDV_SPA, 0) > 0;
+	} else {
+		const char zero = 0;
+		return usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0) > 0;
+	}
+}
+
+void SctpTransport::reset(unsigned int stream) {
+	using reset_streams_t = struct sctp_reset_streams;
+	const size_t len = sizeof(reset_streams_t) + sizeof(uint16_t);
+	std::byte buffer[len] = {};
+	reset_streams_t &reset_streams = *reinterpret_cast<reset_streams_t *>(buffer);
+	reset_streams.srs_flags = SCTP_STREAM_RESET_OUTGOING;
+	reset_streams.srs_number_streams = 1;
+	reset_streams.srs_stream_list[0] = uint16_t(stream);
+	usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &reset_streams, len);
+}
+
+void SctpTransport::incoming(message_ptr message) {
+	usrsctp_conninput(this, message->data(), message->size(), 0);
+}
+
+void SctpTransport::runConnect() {
+	struct sockaddr_conn sconn = {};
+	sconn.sconn_family = AF_CONN;
+	sconn.sconn_port = htons(mRemotePort);
+	sconn.sconn_addr = this;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(sconn);
+#endif
+
+	// Blocks until connection succeeds/fails
+	if (usrsctp_connect(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)) != 0) {
+		mStopping = true;
+		return;
+	}
+
+        mIsReady = true;
+        mReadyCallback();
+}
+} // namespace rtc

+ 88 - 0
src/sctptransport.hpp

@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_SCTP_TRANSPORT_H
+#define RTC_SCTP_TRANSPORT_H
+
+#include "include.hpp"
+#include "peerconnection.hpp"
+#include "transport.hpp"
+
+#include <functional>
+#include <thread>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <usrsctp.h>
+
+namespace rtc {
+
+class SctpTransport : public Transport {
+public:
+	using ready_callback = std::function<void(void)>;
+
+	SctpTransport(std::shared_ptr<Transport> lower, ready_callback ready, message_callback recv);
+	~SctpTransport();
+
+        bool isReady() const;
+
+        bool send(message_ptr message);
+	void reset(unsigned int stream);
+
+private:
+	enum PayloadId : uint32_t {
+		PPID_CONTROL = 50,
+		PPID_STRING = 51,
+		PPID_BINARY = 53,
+		PPID_STRING_EMPTY = 56,
+		PPID_BINARY_EMPTY = 57
+	};
+
+	void incoming(message_ptr message);
+	void runConnect();
+
+	int handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df);
+
+	int process(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
+	            struct sctp_rcvinfo recv_info, int flags);
+
+	void processData(const byte *data, size_t len, uint16_t streamId, PayloadId ppid);
+	void processNotification(const union sctp_notification *notify, size_t len);
+
+	ready_callback mReadyCallback;
+
+	struct socket *mSock;
+	uint16_t mLocalPort;
+	uint16_t mRemotePort;
+
+        std::thread mConnectThread;
+	std::atomic<bool> mStopping = false;
+        std::atomic<bool> mIsReady = false;
+
+        std::mutex mConnectMutex;
+	std::condition_variable mConnectCondition;
+
+	static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
+	static int ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
+	                        struct sctp_rcvinfo recv_info, int flags, void *user_data);
+};
+
+} // namespace rtc
+
+#endif
+

+ 66 - 0
src/transport.hpp

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef RTC_TRANSPORT_H
+#define RTC_TRANSPORT_H
+
+#include "include.hpp"
+#include "message.hpp"
+
+#include <functional>
+#include <memory>
+
+namespace rtc {
+
+using namespace std::placeholders;
+
+class Transport {
+public:
+	Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(lower) { init(); }
+	virtual ~Transport() {}
+
+	virtual bool send(message_ptr message) = 0;
+	void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
+
+protected:
+	void recv(message_ptr message) { if(mRecvCallback) mRecvCallback(message); }
+
+	virtual void incoming(message_ptr message) = 0;
+	virtual void outgoing(message_ptr message) { getLower()->send(message); }
+
+private:
+	void init() {
+		if (mLower)
+			mLower->onRecv(std::bind(&Transport::incoming, this, _1));
+	}
+
+	std::shared_ptr<Transport> getLower() {
+		if (mLower)
+			return mLower;
+		else
+			throw std::logic_error("No lower transport to call");
+	}
+
+	std::shared_ptr<Transport> mLower;
+	message_callback mRecvCallback;
+};
+
+} // namespace rtc
+
+#endif
+