Browse Source

Merge pull request #41 from paullouisageneau/c-api

C API update and fixes
Paul-Louis Ageneau 5 years ago
parent
commit
b6f2176be8

+ 2 - 0
CMakeLists.txt

@@ -38,6 +38,8 @@ set(LIBDATACHANNEL_SOURCES
 
 set(TESTS_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
 )
 
 set(TESTS_OFFERER_SOURCES

+ 5 - 2
Makefile

@@ -44,6 +44,9 @@ LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
 SRCS=$(shell printf "%s " src/*.cpp)
 OBJS=$(subst .cpp,.o,$(SRCS))
 
+TEST_SRCS=$(shell printf "%s " test/*.cpp)
+TEST_OBJS=$(subst .cpp,.o,$(TEST_SRCS))
+
 all: $(NAME).a $(NAME).so tests
 
 src/%.o: src/%.cpp
@@ -60,8 +63,8 @@ $(NAME).a: $(OBJS)
 $(NAME).so: $(LOCALLIBS) $(OBJS)
 	$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
 
-tests: $(NAME).a test/main.o
-	$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
+tests: $(NAME).a $(TEST_OBJS)
+	$(CXX) $(LDFLAGS) -o $@ $(TEST_OBJS) $(NAME).a $(LDLIBS)
 
 clean:
 	-$(RM) include/rtc/*.d *.d

+ 5 - 3
README.md

@@ -79,11 +79,11 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
 ### Observe the PeerConnection state
 
 ```cpp
-pc->onStateChanged([](PeerConnection::State state) {
+pc->onStateChange([](PeerConnection::State state) {
     cout << "State: " << state << endl;
 });
 
-pc->onGatheringStateChanged([](PeerConnection::GatheringState state) {
+pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
     cout << "Gathering state: " << state << endl;
 });
 
@@ -114,5 +114,7 @@ pc->onDataChannel([&dc](shared_ptr<rtc::DataChannel> incoming) {
 
 ```
 
-See [test/main.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/main.cpp) for a complete local connection example.
+See [test/connectivity.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/connectivity.cpp) for a complete local connection example.
+
+See [test/cpai.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/capi.cpp) for a C API example.
 

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 455ac32ef6b892932fd0127a76899c1c92cba292
+Subproject commit 4154996a01a4007513511dd849745cfb7494c6f7

+ 6 - 5
include/rtc/channel.hpp

@@ -31,12 +31,10 @@ class Channel {
 public:
 	virtual void close() = 0;
 	virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
-	virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
 
 	virtual bool isOpen() const = 0;
 	virtual bool isClosed() const = 0;
-
-	virtual size_t availableAmount() const; // total size available to receive
+	virtual size_t maxMessageSize() const; // max message size in a call to send
 	virtual size_t bufferedAmount() const; // total size buffered to send
 
 	void onOpen(std::function<void()> callback);
@@ -47,11 +45,14 @@ public:
 	void onMessage(std::function<void(const binary &data)> binaryCallback,
 	               std::function<void(const string &data)> stringCallback);
 
-	void onAvailable(std::function<void()> callback);
 	void onBufferedAmountLow(std::function<void()> callback);
-
 	void setBufferedAmountLowThreshold(size_t amount);
 
+	// Extended API
+	virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
+	virtual size_t availableAmount() const; // total size available to receive
+	void onAvailable(std::function<void()> callback);
+
 protected:
 	virtual void triggerOpen();
 	virtual void triggerClosed();

+ 9 - 11
include/rtc/datachannel.hpp

@@ -44,26 +44,24 @@ public:
 	            unsigned int stream);
 	~DataChannel();
 
-	void close(void) override;
+	unsigned int stream() const;
+	string label() const;
+	string protocol() const;
+	Reliability reliability() const;
 
+	void close(void) override;
 	bool send(const std::variant<binary, string> &data) override;
 	bool send(const byte *data, size_t size);
-
 	template <typename Buffer> bool sendBuffer(const Buffer &buf);
 	template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
 
-	std::optional<std::variant<binary, string>> receive() override;
-
 	bool isOpen(void) const override;
 	bool isClosed(void) const override;
-	size_t availableAmount() const override;
-
-	size_t maxMessageSize() const;  // maximum message size in a call to send or sendBuffer
+	size_t maxMessageSize() const override;
 
-	unsigned int stream() const;
-	string label() const;
-	string protocol() const;
-	Reliability reliability() const;
+	// Extended API
+	size_t availableAmount() const override;
+	std::optional<std::variant<binary, string>> receive() override;
 
 private:
 	void remoteClose();

+ 4 - 28
include/rtc/include.hpp

@@ -20,11 +20,14 @@
 #define RTC_INCLUDE_H
 
 #ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
 #ifndef _WIN32_WINNT
 #define _WIN32_WINNT 0x0602
 #endif
 #endif
 
+#include "log.hpp"
+
 #include <cstddef>
 #include <functional>
 #include <memory>
@@ -33,9 +36,6 @@
 #include <string>
 #include <vector>
 
-#include "plog/Appenders/ColorConsoleAppender.h"
-#include "plog/Log.h"
-
 namespace rtc {
 
 using std::byte;
@@ -50,8 +50,6 @@ using std::uint32_t;
 using std::uint64_t;
 using std::uint8_t;
 
-// Constants
-
 const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
 const size_t MAX_NUMERICSERV_LEN = 6;  // Max port string representation length
 
@@ -59,29 +57,6 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
 const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536;    // Remote max message size if not specified in SDP
 const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
 
-// Log
-
-enum class LogLevel { // Don't change, it must match plog severity
-	None = 0,
-	Fatal = 1,
-	Error = 2,
-	Warning = 3,
-	Info = 4,
-	Debug = 5,
-	Verbose = 6
-};
-
-inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
-	static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
-	if (!appender)
-		appender = &consoleAppender;
-	plog::init(severity, appender);
-	PLOG_DEBUG << "Logger initialized";
-}
-
-inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
-
-// Utils
 
 template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
 template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
@@ -89,6 +64,7 @@ template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
 template <typename... P> class synchronized_callback {
 public:
 	synchronized_callback() = default;
+	synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); };
 	~synchronized_callback() { *this = nullptr; }
 
 	synchronized_callback &operator=(std::function<void(P...)> func) {

+ 55 - 0
include/rtc/log.hpp

@@ -0,0 +1,55 @@
+/**
+ * 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_LOG_H
+#define RTC_LOG_H
+
+#include "plog/Appenders/ColorConsoleAppender.h"
+#include "plog/Log.h"
+#include "plog/Logger.h"
+
+namespace rtc {
+
+enum class LogLevel { // Don't change, it must match plog severity
+	None = 0,
+	Fatal = 1,
+	Error = 2,
+	Warning = 3,
+	Info = 4,
+	Debug = 5,
+	Verbose = 6
+};
+
+inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
+	static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
+	static plog::Logger<0> *logger = nullptr;
+	if (!logger) {
+		logger = &plog::init(severity, appender ? appender : &consoleAppender);
+		PLOG_DEBUG << "Logger initialized";
+	} else {
+		logger->setMaxSeverity(severity);
+		if (appender)
+			logger->addAppender(appender);
+	}
+}
+
+inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
+
+}
+
+#endif

+ 7 - 0
include/rtc/peerconnection.hpp

@@ -32,6 +32,7 @@
 #include <functional>
 #include <list>
 #include <mutex>
+#include <shared_mutex>
 #include <thread>
 #include <unordered_map>
 
@@ -95,6 +96,11 @@ private:
 	bool checkFingerprint(const std::string &fingerprint) const;
 	void forwardMessage(message_ptr message);
 	void forwardBufferedAmount(uint16_t stream, size_t amount);
+
+	std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, const string &label,
+	                                                const string &protocol,
+	                                                const Reliability &reliability);
+	std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
 	void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
 	void openDataChannels();
 	void closeDataChannels();
@@ -118,6 +124,7 @@ private:
 	std::recursive_mutex mInitMutex;
 
 	std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
+	std::shared_mutex mDataChannelsMutex;
 
 	std::atomic<State> mState;
 	std::atomic<GatheringState> mGatheringState;

+ 58 - 23
include/rtc/rtc.h

@@ -33,13 +33,13 @@ typedef enum {
 	RTC_FAILED = 4,
 	RTC_CLOSED = 5,
 	RTC_DESTROYING = 6 // internal
-} rtc_state_t;
+} rtcState;
 
 typedef enum {
 	RTC_GATHERING_NEW = 0,
 	RTC_GATHERING_INPROGRESS = 1,
 	RTC_GATHERING_COMPLETE = 2
-} rtc_gathering_state_t;
+} rtcGatheringState;
 
 // Don't change, it must match plog severity
 typedef enum {
@@ -50,31 +50,66 @@ typedef enum {
 	RTC_LOG_INFO = 4,
 	RTC_LOG_DEBUG = 5,
 	RTC_LOG_VERBOSE = 6
-} rtc_log_level_t;
+} rtcLogLevel;
 
-void rtcInitLogger(rtc_log_level_t level);
+typedef struct {
+	const char **iceServers;
+	int iceServersCount;
+} rtcConfiguration;
 
-int rtcCreatePeerConnection(const char **iceServers, int iceServersCount);
-void rtcDeletePeerConnection(int pc);
+typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
+typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
+typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
+typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
+typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
+typedef void (*openCallbackFunc)(void *ptr);
+typedef void (*closedCallbackFunc)(void *ptr);
+typedef void (*errorCallbackFunc)(const char *error, void *ptr);
+typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
+typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
+typedef void (*availableCallbackFunc)(void *ptr);
+
+// Log
+void rtcInitLogger(rtcLogLevel level);
+
+// User pointer
+void rtcSetUserPointer(int i, void *ptr);
+
+// PeerConnection
+int rtcCreatePeerConnection(const rtcConfiguration *config);
+int rtcDeletePeerConnection(int pc);
+
+int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
+int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
+int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
+int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
+int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
+
+int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
+int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
+
+int rtcGetLocalAddress(int pc, char *buffer, int size);
+int rtcGetRemoteAddress(int pc, char *buffer, int size);
+
+// DataChannel
 int rtcCreateDataChannel(int pc, const char *label);
-void rtcDeleteDataChannel(int dc);
-void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *));
-void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
-                                                                        void *));
-void rtcSetLocalCandidateCallback(int pc,
-                                  void (*candidateCallback)(const char *, const char *, void *));
-void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *));
-void rtcSetGatheringStateChangeCallback(int pc,
-                                        void (*gatheringStateCallback)(rtc_gathering_state_t state,
-                                                                       void *));
-void rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
-void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid);
-int rtcGetDataChannelLabel(int dc, char *data, int size);
-void rtcSetOpenCallback(int dc, void (*openCallback)(void *));
-void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *));
-void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *));
+int rtcDeleteDataChannel(int dc);
+
+int rtcGetDataChannelLabel(int dc, char *buffer, int size);
+int rtcSetOpenCallback(int dc, openCallbackFunc cb);
+int rtcSetClosedCallback(int dc, closedCallbackFunc cb);
+int rtcSetErrorCallback(int dc, errorCallbackFunc cb);
+int rtcSetMessageCallback(int dc, messageCallbackFunc cb);
 int rtcSendMessage(int dc, const char *data, int size);
-void rtcSetUserPointer(int i, void *ptr);
+
+int rtcGetBufferedAmount(int dc); // total size buffered to send
+int rtcSetBufferedAmountLowThreshold(int dc, int amount);
+int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb);
+
+// DataChannel extended API
+int rtcGetAvailableAmount(int dc); // total size available to receive
+int rtcSetAvailableCallback(int dc, availableCallbackFunc cb);
+int rtcReceiveMessage(int dc, char *buffer, int *size);
 
 #ifdef __cplusplus
 } // extern "C"

+ 1 - 0
include/rtc/rtc.hpp

@@ -18,6 +18,7 @@
 
 // C++ API
 #include "datachannel.hpp"
+#include "log.hpp"
 #include "peerconnection.hpp"
 
 // C API

+ 10 - 10
src/channel.cpp

@@ -18,10 +18,14 @@
 
 #include "channel.hpp"
 
-namespace {}
-
 namespace rtc {
 
+size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
+
+size_t Channel::bufferedAmount() const { return mBufferedAmount; }
+
+size_t Channel::availableAmount() const { return 0; }
+
 void Channel::onOpen(std::function<void()> callback) {
 	mOpenCallback = callback;
 }
@@ -49,20 +53,16 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
 	});
 }
 
-void Channel::onAvailable(std::function<void()> callback) {
-	mAvailableCallback = callback;
-}
-
 void Channel::onBufferedAmountLow(std::function<void()> callback) {
 	mBufferedAmountLowCallback = callback;
 }
 
-size_t Channel::availableAmount() const { return 0; }
-
-size_t Channel::bufferedAmount() const { return mBufferedAmount; }
-
 void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
 
+void Channel::onAvailable(std::function<void()> callback) {
+	mAvailableCallback = callback;
+}
+
 void Channel::triggerOpen() { mOpenCallback(); }
 
 void Channel::triggerClosed() { mClosedCallback(); }

+ 15 - 9
src/datachannel.cpp

@@ -21,6 +21,12 @@
 #include "peerconnection.hpp"
 #include "sctptransport.hpp"
 
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
 namespace rtc {
 
 using std::shared_ptr;
@@ -77,6 +83,14 @@ DataChannel::~DataChannel() {
 	close();
 }
 
+unsigned int DataChannel::stream() const { return mStream; }
+
+string DataChannel::label() const { return mLabel; }
+
+string DataChannel::protocol() const { return mProtocol; }
+
+Reliability DataChannel::reliability() const { return *mReliability; }
+
 void DataChannel::close() {
 	if (mIsOpen.exchange(false) && mSctpTransport)
 		mSctpTransport->reset(mStream);
@@ -131,8 +145,6 @@ bool DataChannel::isOpen(void) const { return mIsOpen; }
 
 bool DataChannel::isClosed(void) const { return mIsClosed; }
 
-size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
-
 size_t DataChannel::maxMessageSize() const {
 	size_t max = DEFAULT_MAX_MESSAGE_SIZE;
 	if (auto description = mPeerConnection->remoteDescription())
@@ -142,13 +154,7 @@ size_t DataChannel::maxMessageSize() const {
 	return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
 }
 
-unsigned int DataChannel::stream() const { return mStream; }
-
-string DataChannel::label() const { return mLabel; }
-
-string DataChannel::protocol() const { return mProtocol; }
-
-Reliability DataChannel::reliability() const { return *mReliability; }
+size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
 
 void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
 	mSctpTransport = sctpTransport;

+ 8 - 9
src/icetransport.cpp

@@ -19,9 +19,15 @@
 #include "icetransport.hpp"
 #include "configuration.hpp"
 
+#include <iostream>
+#include <random>
+#include <sstream>
+
 #ifdef _WIN32
 #include <winsock2.h>
-#elif __linux__
+#include <ws2tcpip.h>
+#else
+#include <arpa/inet.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
@@ -29,10 +35,6 @@
 
 #include <sys/types.h>
 
-#include <iostream>
-#include <random>
-#include <sstream>
-
 using namespace std::chrono_literals;
 
 using std::shared_ptr;
@@ -243,11 +245,8 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
 	case JUICE_LOG_LEVEL_INFO:
 		severity = plog::info;
 		break;
-	case JUICE_LOG_LEVEL_DEBUG:
-		severity = plog::debug;
-		break;
 	default:
-		severity = plog::verbose;
+		severity = plog::verbose; // libjuice debug as verbose
 		break;
 	}
 	PLOG(severity) << "juice: " << message;

+ 52 - 29
src/peerconnection.cpp

@@ -25,6 +25,10 @@
 
 #include <iostream>
 
+#ifdef _WIN32
+#include <winsock2.h>
+#endif
+
 namespace rtc {
 
 using namespace std::placeholders;
@@ -32,7 +36,13 @@ using namespace std::placeholders;
 using std::shared_ptr;
 using std::weak_ptr;
 
-PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
+PeerConnection::PeerConnection() : PeerConnection(Configuration()) {
+#ifdef _WIN32
+	WSADATA wsaData;
+	if (WSAStartup(MAKEWORD(2, 2), &wsaData))
+		throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
+#endif
+}
 
 PeerConnection::PeerConnection(const Configuration &config)
     : mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New) {}
@@ -43,12 +53,15 @@ PeerConnection::~PeerConnection() {
 	mSctpTransport.reset();
 	mDtlsTransport.reset();
 	mIceTransport.reset();
+
+#ifdef _WIN32
+	WSACleanup();
+#endif
 }
 
 void PeerConnection::close() {
 	// Close DataChannels
 	closeDataChannels();
-	mDataChannels.clear();
 
 	// Close Transports
 	for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
@@ -101,12 +114,16 @@ void PeerConnection::setRemoteDescription(Description description) {
 		if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
 			// Since we assumed passive role during DataChannel creation, we need to shift the
 			// stream numbers by one to shift them from odd to even.
+			std::unique_lock lock(mDataChannelsMutex);
 			decltype(mDataChannels) newDataChannels;
-			iterateDataChannels([&](shared_ptr<DataChannel> channel) {
+			auto it = mDataChannels.begin();
+			while (it != mDataChannels.end()) {
+				auto channel = it->second.lock();
 				if (channel->stream() % 2 == 1)
 					channel->mStream -= 1;
 				newDataChannels.emplace(channel->stream(), channel);
-			});
+				++it;
+			}
 			std::swap(mDataChannels, newDataChannels);
 		}
 	}
@@ -158,19 +175,7 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
 
-	// The active side must use streams with even identifiers, whereas the passive side must use
-	// streams with odd identifiers.
-	// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
-	unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
-	while (mDataChannels.find(stream) != mDataChannels.end()) {
-		stream += 2;
-		if (stream >= 65535)
-			throw std::runtime_error("Too many DataChannels");
-	}
-
-	auto channel =
-	    std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
-	mDataChannels.insert(std::make_pair(stream, channel));
+	auto channel = emplaceDataChannel(role, label, protocol, reliability);
 
 	if (!iceTransport) {
 		// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
@@ -353,14 +358,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
 		return;
 	}
 
-	shared_ptr<DataChannel> channel;
-	if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
-		channel = it->second.lock();
-		if (!channel || channel->isClosed()) {
-			mDataChannels.erase(it);
-			channel = nullptr;
-		}
-	}
+	auto channel = findDataChannel(message->stream);
 
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	auto sctpTransport = std::atomic_load(&mSctpTransport);
@@ -388,21 +386,46 @@ void PeerConnection::forwardMessage(message_ptr message) {
 }
 
 void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
+	if (auto channel = findDataChannel(stream))
+		channel->triggerBufferedAmount(amount);
+}
+
+shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
+                                                           const string &label,
+                                                           const string &protocol,
+                                                           const Reliability &reliability) {
+	// The active side must use streams with even identifiers, whereas the passive side must use
+	// streams with odd identifiers.
+	// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
+	std::unique_lock lock(mDataChannelsMutex);
+	unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
+	while (mDataChannels.find(stream) != mDataChannels.end()) {
+		stream += 2;
+		if (stream >= 65535)
+			throw std::runtime_error("Too many DataChannels");
+	}
+	auto channel =
+	    std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
+	mDataChannels.emplace(std::make_pair(stream, channel));
+	return channel;
+}
+
+shared_ptr<DataChannel> PeerConnection::findDataChannel(uint16_t stream) {
+	std::shared_lock lock(mDataChannelsMutex);
 	shared_ptr<DataChannel> channel;
 	if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) {
 		channel = it->second.lock();
 		if (!channel || channel->isClosed()) {
 			mDataChannels.erase(it);
-			channel = nullptr;
+			channel.reset();
 		}
 	}
-
-	if (channel)
-		channel->triggerBufferedAmount(amount);
+	return channel;
 }
 
 void PeerConnection::iterateDataChannels(
     std::function<void(shared_ptr<DataChannel> channel)> func) {
+	std::shared_lock lock(mDataChannelsMutex);
 	auto it = mDataChannels.begin();
 	while (it != mDataChannels.end()) {
 		auto channel = it->second.lock();

+ 336 - 108
src/rtc.cpp

@@ -22,195 +22,423 @@
 
 #include <rtc.h>
 
+#include <exception>
+#include <mutex>
 #include <unordered_map>
-
-#include <plog/Appenders/ColorConsoleAppender.h>
+#include <utility>
 
 using namespace rtc;
 using std::shared_ptr;
 using std::string;
 
+#define CATCH(statement)                                                                           \
+	try {                                                                                          \
+		statement;                                                                                 \
+	} catch (const std::exception &e) {                                                            \
+		PLOG_ERROR << e.what();                                                                    \
+		return -1;                                                                                 \
+	}
+
 namespace {
 
 std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
 std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
 std::unordered_map<int, void *> userPointerMap;
+std::mutex mutex;
 int lastId = 0;
 
 void *getUserPointer(int id) {
+	std::lock_guard lock(mutex);
 	auto it = userPointerMap.find(id);
 	return it != userPointerMap.end() ? it->second : nullptr;
 }
 
-} // namespace
+shared_ptr<PeerConnection> getPeerConnection(int id) {
+	std::lock_guard lock(mutex);
+	auto it = peerConnectionMap.find(id);
+	return it != peerConnectionMap.end() ? it->second : nullptr;
+}
 
-void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); }
+shared_ptr<DataChannel> getDataChannel(int id) {
+	std::lock_guard lock(mutex);
+	auto it = dataChannelMap.find(id);
+	return it != dataChannelMap.end() ? it->second : nullptr;
+}
 
-int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
-	Configuration config;
-	for (int i = 0; i < iceServersCount; ++i) {
-		config.iceServers.emplace_back(IceServer(string(iceServers[i])));
-	}
+int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
+	std::lock_guard lock(mutex);
 	int pc = ++lastId;
-	peerConnectionMap.emplace(std::make_pair(pc, std::make_shared<PeerConnection>(config)));
+	peerConnectionMap.emplace(std::make_pair(pc, ptr));
 	return pc;
 }
 
-void rtcDeletePeerConnection(int pc) { peerConnectionMap.erase(pc); }
+int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
+	std::lock_guard lock(mutex);
+	int dc = ++lastId;
+	dataChannelMap.emplace(std::make_pair(dc, ptr));
+	return dc;
+}
+
+bool erasePeerConnection(int pc) {
+	std::lock_guard lock(mutex);
+	if (peerConnectionMap.erase(pc) == 0)
+		return false;
+	userPointerMap.erase(pc);
+	return true;
+}
+
+bool eraseDataChannel(int dc) {
+	std::lock_guard lock(mutex);
+	if (dataChannelMap.erase(dc) == 0)
+		return false;
+	userPointerMap.erase(dc);
+	return true;
+}
+
+} // namespace
+
+void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
+
+void rtcSetUserPointer(int i, void *ptr) {
+	if (ptr)
+		userPointerMap.insert(std::make_pair(i, ptr));
+	else
+		userPointerMap.erase(i);
+}
+
+int rtcCreatePeerConnection(const rtcConfiguration *config) {
+	Configuration c;
+	for (int i = 0; i < config->iceServersCount; ++i)
+		c.iceServers.emplace_back(string(config->iceServers[i]));
+
+	return emplacePeerConnection(std::make_shared<PeerConnection>(c));
+}
+
+int rtcDeletePeerConnection(int pc) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	peerConnection->onDataChannel(nullptr);
+	peerConnection->onLocalDescription(nullptr);
+	peerConnection->onLocalCandidate(nullptr);
+	peerConnection->onStateChange(nullptr);
+	peerConnection->onGatheringStateChange(nullptr);
+
+	erasePeerConnection(pc);
+	return 0;
+}
 
 int rtcCreateDataChannel(int pc, const char *label) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return 0;
-	auto dataChannel = it->second->createDataChannel(string(label));
-	int dc = ++lastId;
-	dataChannelMap.emplace(std::make_pair(dc, dataChannel));
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
+	void *ptr = getUserPointer(pc);
+	rtcSetUserPointer(dc, ptr);
 	return dc;
 }
 
-void rtcDeleteDataChannel(int dc) { dataChannelMap.erase(dc); }
+int rtcDeleteDataChannel(int dc) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
-void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+	dataChannel->onOpen(nullptr);
+	dataChannel->onClosed(nullptr);
+	dataChannel->onError(nullptr);
+	dataChannel->onMessage(nullptr);
+	dataChannel->onBufferedAmountLow(nullptr);
+	dataChannel->onAvailable(nullptr);
 
-	it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) {
-		int dc = ++lastId;
-		dataChannelMap.emplace(std::make_pair(dc, dataChannel));
-		dataChannelCallback(dc, getUserPointer(pc));
-	});
+	eraseDataChannel(dc);
+	return 0;
 }
 
-void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
-                                                                        void *)) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	if (cb)
+		peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
+			int dc = emplaceDataChannel(dataChannel);
+			void *ptr = getUserPointer(pc);
+			rtcSetUserPointer(dc, ptr);
+			cb(dc, ptr);
+		});
+	else
+		peerConnection->onDataChannel(nullptr);
+	return 0;
+}
 
-	it->second->onLocalDescription([pc, descriptionCallback](const Description &description) {
-		descriptionCallback(string(description).c_str(), description.typeString().c_str(),
-		                    getUserPointer(pc));
-	});
+int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	if (cb)
+		peerConnection->onLocalDescription([pc, cb](const Description &desc) {
+			cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
+		});
+	else
+		peerConnection->onLocalDescription(nullptr);
+	return 0;
 }
 
-void rtcSetLocalCandidateCallback(int pc,
-                                  void (*candidateCallback)(const char *, const char *, void *)) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
 
-	it->second->onLocalCandidate([pc, candidateCallback](const Candidate &candidate) {
-		candidateCallback(candidate.candidate().c_str(), candidate.mid().c_str(),
-		                  getUserPointer(pc));
-	});
+	if (cb)
+		peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
+			cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
+		});
+	else
+		peerConnection->onLocalCandidate(nullptr);
+	return 0;
 }
 
-void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
 
-	it->second->onStateChange([pc, stateCallback](PeerConnection::State state) {
-		stateCallback(static_cast<rtc_state_t>(state), getUserPointer(pc));
-	});
+	if (cb)
+		peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
+			cb(static_cast<rtcState>(state), getUserPointer(pc));
+		});
+	else
+		peerConnection->onStateChange(nullptr);
+	return 0;
+}
+
+int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	if (cb)
+		peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
+			cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
+		});
+	else
+		peerConnection->onGatheringStateChange(nullptr);
+	return 0;
 }
 
-void rtcSetGatheringStateChangeCallback(int pc,
-                                        void (*gatheringStateCallback)(rtc_gathering_state_t state,
-                                                                       void *)) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
 
-	it->second->onGatheringStateChange(
-	    [pc, gatheringStateCallback](PeerConnection::GatheringState state) {
-		    gatheringStateCallback(static_cast<rtc_gathering_state_t>(state), getUserPointer(pc));
-	    });
+	CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
+	return 0;
 }
 
-void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
 
-	it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : ""));
+	CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
+	return 0;
 }
 
-void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) {
-	auto it = peerConnectionMap.find(pc);
-	if (it == peerConnectionMap.end())
-		return;
+int rtcGetLocalAddress(int pc, char *buffer, int size) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
 
-	it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : ""));
+	if (auto addr = peerConnection->localAddress()) {
+		size = std::min(size_t(size - 1), addr->size());
+		std::copy(addr->data(), addr->data() + size, buffer);
+		buffer[size] = '\0';
+		return size + 1;
+	}
+	return -1;
+}
+
+int rtcGetRemoteAddress(int pc, char *buffer, int size) {
+	auto peerConnection = getPeerConnection(pc);
+	if (!peerConnection)
+		return -1;
+
+	if (auto addr = peerConnection->remoteAddress()) {
+		size = std::min(size_t(size - 1), addr->size());
+		std::copy(addr->data(), addr->data() + size, buffer);
+		buffer[size] = '\0';
+		return size + 1;
+	}
+	return -1;
 }
 
 int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
-	auto it = dataChannelMap.find(dc);
-	if (it == dataChannelMap.end())
-		return 0;
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
 	if (!size)
 		return 0;
 
-	string label = it->second->label();
+	string label = dataChannel->label();
 	size = std::min(size_t(size - 1), label.size());
 	std::copy(label.data(), label.data() + size, buffer);
 	buffer[size] = '\0';
 	return size + 1;
 }
 
-void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) {
-	auto it = dataChannelMap.find(dc);
-	if (it == dataChannelMap.end())
-		return;
+int rtcSetOpenCallback(int dc, openCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
-	it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); });
+	if (cb)
+		dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
+	else
+		dataChannel->onOpen(nullptr);
+	return 0;
 }
 
-void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) {
-	auto it = dataChannelMap.find(dc);
-	if (it == dataChannelMap.end())
-		return;
+int rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
-	it->second->onError([dc, errorCallback](const string &error) {
-		errorCallback(error.c_str(), getUserPointer(dc));
-	});
+	if (cb)
+		dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
+	else
+		dataChannel->onClosed(nullptr);
+	return 0;
 }
 
-void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) {
-	auto it = dataChannelMap.find(dc);
-	if (it == dataChannelMap.end())
-		return;
+int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
-	it->second->onMessage(
-	    [dc, messageCallback](const binary &b) {
-		    messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
-	    },
-	    [dc, messageCallback](const string &s) {
-		    messageCallback(s.c_str(), -1, getUserPointer(dc));
-	    });
+	if (cb)
+		dataChannel->onError(
+		    [dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); });
+	else
+		dataChannel->onError(nullptr);
+	return 0;
+}
+
+int rtcSetMessageCallback(int dc, messageCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	if (cb)
+		dataChannel->onMessage(
+		    [dc, cb](const binary &b) {
+			    cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
+		    },
+		    [dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
+	else
+		dataChannel->onMessage(nullptr);
+
+	return 0;
 }
 
 int rtcSendMessage(int dc, const char *data, int size) {
-	auto it = dataChannelMap.find(dc);
-	if (it == dataChannelMap.end())
-		return 0;
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
 
 	if (size >= 0) {
 		auto b = reinterpret_cast<const byte *>(data);
-		it->second->send(b, size);
+		CATCH(dataChannel->send(b, size));
 		return size;
 	} else {
 		string s(data);
-		it->second->send(s);
+		CATCH(dataChannel->send(s));
 		return s.size();
 	}
 }
 
-void rtcSetUserPointer(int i, void *ptr) {
-	if (ptr)
-		userPointerMap.insert(std::make_pair(i, ptr));
+int rtcGetBufferedAmount(int dc) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	CATCH(return int(dataChannel->bufferedAmount()));
+}
+
+int rtcSetBufferedAmountLowThreshold(int dc, int amount) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	CATCH(dataChannel->setBufferedAmountLowThreshold(size_t(amount)));
+	return 0;
+}
+
+int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	if (cb)
+		dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); });
 	else
-		userPointerMap.erase(i);
+		dataChannel->onBufferedAmountLow(nullptr);
+	return 0;
+}
+
+int rtcGetAvailableAmount(int dc) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	CATCH(return int(dataChannel->availableAmount()));
+}
+
+int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	if (cb)
+		dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
+	else
+		dataChannel->onOpen(nullptr);
+	return 0;
+}
+
+int rtcReceiveMessage(int dc, char *buffer, int *size) {
+	auto dataChannel = getDataChannel(dc);
+	if (!dataChannel)
+		return -1;
+
+	if (!size)
+		return -1;
+
+	CATCH({
+		auto message = dataChannel->receive();
+		if (!message)
+			return 0;
+
+		return std::visit( //
+		    overloaded{    //
+		               [&](const binary &b) {
+			               *size = std::min(*size, int(b.size()));
+			               auto data = reinterpret_cast<const char *>(b.data());
+			               std::copy(data, data + *size, buffer);
+			               return *size;
+		               },
+		               [&](const string &s) {
+			               int len = std::min(*size - 1, int(s.size()));
+			               if (len >= 0) {
+				               std::copy(s.data(), s.data() + len, buffer);
+				               buffer[len] = '\0';
+			               }
+			               *size = -(len + 1);
+			               return len + 1;
+		               }},
+		    *message);
+	});
 }

+ 0 - 4
src/sctptransport.cpp

@@ -23,10 +23,6 @@
 #include <iostream>
 #include <vector>
 
-#ifdef __linux__
-#include <arpa/inet.h>
-#endif
-
 #ifdef USE_JUICE
 #ifndef __APPLE__
 // libjuice enables Linux path MTU discovery or sets the DF flag

+ 0 - 8
src/sctptransport.hpp

@@ -29,14 +29,6 @@
 #include <map>
 #include <mutex>
 
-#ifdef _WIN32
-#include <winsock2.h>
-#elif __linux__
-#include <sys/socket.h>
-#endif
-
-#include <sys/types.h>
-
 #include "usrsctp.h"
 
 namespace rtc {

+ 188 - 0
test/capi.cpp

@@ -0,0 +1,188 @@
+/**
+ * Copyright (c) 2020 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 <rtc/rtc.h>
+
+#include <cstdbool>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include <unistd.h> // for sleep
+
+using namespace std;
+
+typedef struct {
+	rtcState state;
+	rtcGatheringState gatheringState;
+	int pc;
+	int dc;
+	bool connected;
+} Peer;
+
+Peer *peer1 = NULL;
+Peer *peer2 = NULL;
+
+static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
+	Peer *other = peer == peer1 ? peer2 : peer1;
+	rtcSetRemoteDescription(other->pc, sdp, type);
+}
+
+static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
+	Peer *other = peer == peer1 ? peer2 : peer1;
+	rtcAddRemoteCandidate(other->pc, cand, mid);
+}
+
+static void stateChangeCallback(rtcState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->state = state;
+	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
+static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->gatheringState = state;
+	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
+static void openCallback(void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->connected = true;
+	printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
+
+	const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
+	rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
+}
+
+static void closedCallback(void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->connected = false;
+}
+
+static void messageCallback(const char *message, int size, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	if (size < 0) { // negative size indicates a null-terminated string
+		printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
+	} else {
+		printf("Message %d: [binary of size %d]\n", peer == peer1 ? 1 : 2, size);
+	}
+}
+
+static void dataChannelCallback(int dc, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->dc = dc;
+	peer->connected = true;
+	rtcSetClosedCallback(dc, closedCallback);
+	rtcSetMessageCallback(dc, messageCallback);
+
+	char buffer[256];
+	if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
+		printf("DataChannel %d: Received with label \"%s\"\n", peer == peer1 ? 1 : 2, buffer);
+
+	const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
+	rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
+}
+
+static Peer *createPeer(const rtcConfiguration *config) {
+	Peer *peer = (Peer *)malloc(sizeof(Peer));
+	if (!peer)
+		return nullptr;
+	memset(peer, 0, sizeof(Peer));
+
+	// Create peer connection
+	peer->pc = rtcCreatePeerConnection(config);
+	rtcSetUserPointer(peer->pc, peer);
+	rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
+	rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
+	rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
+	rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
+	rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
+
+	return peer;
+}
+
+static void deletePeer(Peer *peer) {
+	if (peer) {
+		if (peer->dc)
+			rtcDeleteDataChannel(peer->dc);
+		if (peer->pc)
+			rtcDeletePeerConnection(peer->pc);
+	}
+}
+
+int test_capi_main() {
+	rtcInitLogger(RTC_LOG_DEBUG);
+
+	rtcConfiguration config;
+	memset(&config, 0, sizeof(config));
+	// const char *iceServers[1] = {"stun:stun.l.google.com:19302"};
+	// config.iceServers = iceServers;
+	// config.iceServersCount = 1;
+
+	// Create peer 1
+	peer1 = createPeer(&config);
+	if (!peer1)
+		goto error;
+
+	// Create peer 2
+	peer2 = createPeer(&config);
+	if (!peer2)
+		goto error;
+
+	// Peer 1: Create data channel
+	peer1->dc = rtcCreateDataChannel(peer1->pc, "test");
+	rtcSetOpenCallback(peer1->dc, openCallback);
+	rtcSetClosedCallback(peer1->dc, closedCallback);
+	rtcSetMessageCallback(peer1->dc, messageCallback);
+
+	sleep(3);
+
+	char buffer[256];
+	if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
+		printf("Local address 1:  %s\n", buffer);
+	if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
+		printf("Remote address 1: %s\n", buffer);
+	if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
+		printf("Local address 2:  %s\n", buffer);
+	if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
+		printf("Remote address 2: %s\n", buffer);
+
+	if (peer1->connected && peer2->connected) {
+		deletePeer(peer1);
+		deletePeer(peer2);
+		sleep(1);
+		printf("Success\n");
+		return 0;
+	}
+
+error:
+	deletePeer(peer1);
+	deletePeer(peer2);
+	return -1;
+}
+
+#include <stdexcept>
+
+void test_capi() {
+	if (test_capi_main())
+		throw std::runtime_error("Connection failed");
+}

+ 129 - 0
test/connectivity.cpp

@@ -0,0 +1,129 @@
+/**
+ * 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 "rtc/rtc.hpp"
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <thread>
+
+using namespace rtc;
+using namespace std;
+
+template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
+
+void test_connectivity() {
+	InitLogger(LogLevel::Debug);
+
+	Configuration config;
+	// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
+
+	auto pc1 = std::make_shared<PeerConnection>(config);
+
+	auto pc2 = std::make_shared<PeerConnection>(config);
+
+	pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
+		auto pc2 = wpc2.lock();
+		if (!pc2)
+			return;
+		cout << "Description 1: " << sdp << endl;
+		pc2->setRemoteDescription(sdp);
+	});
+
+	pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
+		auto pc2 = wpc2.lock();
+		if (!pc2)
+			return;
+		cout << "Candidate 1: " << candidate << endl;
+		pc2->addRemoteCandidate(candidate);
+	});
+
+	pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
+	pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
+		cout << "Gathering state 1: " << state << endl;
+	});
+
+	pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
+		auto pc1 = wpc1.lock();
+		if (!pc1)
+			return;
+		cout << "Description 2: " << sdp << endl;
+		pc1->setRemoteDescription(sdp);
+	});
+
+	pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
+		auto pc1 = wpc1.lock();
+		if (!pc1)
+			return;
+		cout << "Candidate 2: " << candidate << endl;
+		pc1->addRemoteCandidate(candidate);
+	});
+
+	pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
+	pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
+		cout << "Gathering state 2: " << state << endl;
+	});
+
+	shared_ptr<DataChannel> dc2;
+	pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
+		cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
+		dc2 = dc;
+		dc2->onMessage([](const variant<binary, string> &message) {
+			if (holds_alternative<string>(message)) {
+				cout << "Message 2: " << get<string>(message) << endl;
+			}
+		});
+		dc2->send("Hello from 2");
+	});
+
+	auto dc1 = pc1->createDataChannel("test");
+	dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
+		auto dc1 = wdc1.lock();
+		if (!dc1)
+			return;
+		cout << "DataChannel 1: Open" << endl;
+		dc1->send("Hello from 1");
+	});
+	dc1->onMessage([](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Message 1: " << get<string>(message) << endl;
+		}
+	});
+
+	this_thread::sleep_for(3s);
+
+	if (auto addr = pc1->localAddress())
+		cout << "Local address 1:  " << *addr << endl;
+	if (auto addr = pc1->remoteAddress())
+		cout << "Remote address 1: " << *addr << endl;
+	if (auto addr = pc2->localAddress())
+		cout << "Local address 2:  " << *addr << endl;
+	if (auto addr = pc2->remoteAddress())
+		cout << "Remote address 2: " << *addr << endl;
+
+	if (!dc1->isOpen() || !dc2->isOpen())
+		throw runtime_error("DataChannel is not open");
+
+	pc1->close();
+	pc2->close();
+
+	this_thread::sleep_for(1s);
+
+	cout << "Success" << endl;
+}

+ 18 - 120
test/main.cpp

@@ -16,131 +16,29 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#include "rtc/rtc.hpp"
-
-#include <chrono>
 #include <iostream>
-#include <memory>
-#include <thread>
-
-#ifdef _WIN32
-#include <winsock2.h>
-#endif
 
-using namespace rtc;
 using namespace std;
 
-template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
+void test_connectivity();
+void test_capi();
 
 int main(int argc, char **argv) {
-	InitLogger(LogLevel::Warning);
-
-#ifdef _WIN32
-	WSADATA wsaData;
-	if (WSAStartup(MAKEWORD(2, 2), &wsaData))
-		throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
-#endif
-
-	Configuration config;
-	// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
-	// config.iceServers.emplace_back(IceServer("TURN_SERVER_URL", "PORT", "USERNAME", "PASSWORD",
-	//                                         IceServer::RelayType::TurnUdp)); // libnice only
-	// config.enableIceTcp = true; // libnice only
-
-	auto pc1 = std::make_shared<PeerConnection>(config);
-	auto pc2 = std::make_shared<PeerConnection>(config);
-
-	pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
-		auto pc2 = wpc2.lock();
-		if (!pc2)
-			return;
-		cout << "Description 1: " << sdp << endl;
-		pc2->setRemoteDescription(sdp);
-	});
-
-	pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
-		auto pc2 = wpc2.lock();
-		if (!pc2)
-			return;
-		cout << "Candidate 1: " << candidate << endl;
-		pc2->addRemoteCandidate(candidate);
-	});
-
-	pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
-	pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
-		cout << "Gathering state 1: " << state << endl;
-	});
-
-	pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
-		auto pc1 = wpc1.lock();
-		if (!pc1)
-			return;
-		cout << "Description 2: " << sdp << endl;
-		pc1->setRemoteDescription(sdp);
-	});
-
-	pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
-		auto pc1 = wpc1.lock();
-		if (!pc1)
-			return;
-		cout << "Candidate 2: " << candidate << endl;
-		pc1->addRemoteCandidate(candidate);
-	});
-
-	pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
-	pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
-		cout << "Gathering state 2: " << state << endl;
-	});
-
-	shared_ptr<DataChannel> dc2;
-	pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
-		cout << "Got a DataChannel with label: " << dc->label() << endl;
-		dc2 = dc;
-		dc2->onMessage([](const variant<binary, string> &message) {
-			if (holds_alternative<string>(message)) {
-				cout << "Received 2: " << get<string>(message) << endl;
-			}
-		});
-		dc2->send("Hello from 2");
-	});
-
-	auto dc1 = pc1->createDataChannel("test");
-	dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
-		auto dc1 = wdc1.lock();
-		if (!dc1)
-			return;
-		cout << "DataChannel open: " << dc1->label() << endl;
-		dc1->send("Hello from 1");
-	});
-	dc1->onMessage([](const variant<binary, string> &message) {
-		if (holds_alternative<string>(message)) {
-			cout << "Received 1: " << get<string>(message) << endl;
-		}
-	});
-
-	this_thread::sleep_for(3s);
-
-	if (auto addr = pc1->localAddress())
-		cout << "Local address 1:  " << *addr << endl;
-	if (auto addr = pc1->remoteAddress())
-		cout << "Remote address 1: " << *addr << endl;
-	if (auto addr = pc2->localAddress())
-		cout << "Local address 2:  " << *addr << endl;
-	if (auto addr = pc2->remoteAddress())
-		cout << "Remote address 2: " << *addr << endl;
-
-	bool success;
-	if ((success = dc1->isOpen() && dc2->isOpen())) {
-		pc1->close();
-		pc2->close();
-		cout << "Success" << endl;
-	} else {
-		cout << "Failure" << endl;
+	try {
+		std::cout << "*** Running connectivity test..." << std::endl;
+		test_connectivity();
+		std::cout << "*** Finished connectivity test" << std::endl;
+	} catch (const exception &e) {
+		std::cerr << "Connectivity test failed: " << e.what() << endl;
+		return -1;
 	}
-
-#ifdef _WIN32
-		WSACleanup();
-#endif
-
-	    return success ? 0 : 1;
+	try {
+		std::cout << "*** Running C API test..." << std::endl;
+		test_capi();
+		std::cout << "*** Finished C API test" << std::endl;
+	} catch (const exception &e) {
+		std::cerr << "C API test failed: " << e.what() << endl;
+		return -1;
+	}
+	return 0;
 }

+ 1 - 15
test/p2p/answerer.cpp

@@ -23,23 +23,13 @@
 #include <memory>
 #include <thread>
 
-#ifdef _WIN32
-#include <winsock2.h>
-#endif
-
 using namespace rtc;
 using namespace std;
 
 template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
 
 int main(int argc, char **argv) {
-	InitLogger(LogLevel::Debug);
-
-#ifdef _WIN32
-	WSADATA wsaData;
-	if (WSAStartup(MAKEWORD(2, 2), &wsaData))
-		throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
-#endif
+	InitLogger(LogLevel::Warning);
 
 	Configuration config;
 	// config.iceServers.emplace_back("stun.l.google.com:19302");
@@ -141,8 +131,4 @@ int main(int argc, char **argv) {
 		dc->close();
 	if (pc)
 		pc->close();
-
-#ifdef _WIN32
-	WSACleanup();
-#endif
 }

+ 0 - 14
test/p2p/offerer.cpp

@@ -23,10 +23,6 @@
 #include <memory>
 #include <thread>
 
-#ifdef _WIN32
-#include <winsock2.h>
-#endif
-
 using namespace rtc;
 using namespace std;
 
@@ -35,12 +31,6 @@ template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
 int main(int argc, char **argv) {
 	InitLogger(LogLevel::Warning);
 
-#ifdef _WIN32
-	WSADATA wsaData;
-	if (WSAStartup(MAKEWORD(2, 2), &wsaData))
-		throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
-#endif
-
 	Configuration config;
 	// config.iceServers.emplace_back("stun.l.google.com:19302");
 
@@ -141,8 +131,4 @@ int main(int argc, char **argv) {
 		dc->close();
 	if (pc)
 		pc->close();
-
-#ifdef _WIN32
-	WSACleanup();
-#endif
 }