From be04d8037eca55bd9a45edbdec8d62d9809cbc9e Mon Sep 17 00:00:00 2001 From: Paul-Louis Ageneau Date: Sun, 8 Mar 2020 20:06:56 +0100 Subject: [PATCH] Added tests for C API --- CMakeLists.txt | 1 + Makefile | 9 +- include/rtc/include.hpp | 1 + include/rtc/rtc.h | 9 +- src/channel.cpp | 2 - src/rtc.cpp | 141 +++++++++++++++++++++++-------- test/capi.cpp | 178 ++++++++++++++++++++++++++++++++++++++++ test/connectivity.cpp | 11 ++- test/main.cpp | 14 +++- 9 files changed, 317 insertions(+), 49 deletions(-) create mode 100644 test/capi.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 233cbe9..6f93d22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ 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 diff --git a/Makefile b/Makefile index bb04c68..8f0c443 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ AR=$(CROSS)ar RM=rm -f CXXFLAGS=-std=c++17 CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member -LDFLAGS=-pthread +LDFLAGS=-pthread -g LIBS= LOCALLIBS=libusrsctp.a USRSCTP_DIR=deps/usrsctp @@ -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 diff --git a/include/rtc/include.hpp b/include/rtc/include.hpp index 3fd07c4..a650d96 100644 --- a/include/rtc/include.hpp +++ b/include/rtc/include.hpp @@ -64,6 +64,7 @@ template overloaded(Ts...)->overloaded; template class synchronized_callback { public: synchronized_callback() = default; + synchronized_callback(std::function func) { *this = std::move(func); }; ~synchronized_callback() { *this = nullptr; } synchronized_callback &operator=(std::function func) { diff --git a/include/rtc/rtc.h b/include/rtc/rtc.h index a8645cd..4bb04a1 100644 --- a/include/rtc/rtc.h +++ b/include/rtc/rtc.h @@ -52,12 +52,18 @@ typedef enum { RTC_LOG_VERBOSE = 6 } rtcLogLevel; +typedef struct { + const char **iceServers; + int iceServersCount; +} rtcConfiguration; + 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); @@ -70,7 +76,7 @@ void rtcInitLogger(rtcLogLevel level); void rtcSetUserPointer(int i, void *ptr); // PeerConnection -int rtcCreatePeerConnection(const char **iceServers, int iceServersCount); +int rtcCreatePeerConnection(const rtcConfiguration *config); int rtcDeletePeerConnection(int pc); int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb); @@ -88,6 +94,7 @@ 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); diff --git a/src/channel.cpp b/src/channel.cpp index 0c2796f..e7a402b 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -18,8 +18,6 @@ #include "channel.hpp" -namespace {} - namespace rtc { size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; } diff --git a/src/rtc.cpp b/src/rtc.cpp index 4d0597a..7f20ea4 100644 --- a/src/rtc.cpp +++ b/src/rtc.cpp @@ -106,15 +106,28 @@ void rtcSetUserPointer(int i, void *ptr) { userPointerMap.erase(i); } -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 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(config)); + return emplacePeerConnection(std::make_shared(c)); } -int rtcDeletePeerConnection(int pc) { return erasePeerConnection(pc) ? 0 : -1; } +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 peerConnection = getPeerConnection(pc); @@ -127,19 +140,36 @@ int rtcCreateDataChannel(int pc, const char *label) { return dc; } -int rtcDeleteDataChannel(int dc) { return eraseDataChannel(dc) ? 0 : -1; } +int rtcDeleteDataChannel(int dc) { + auto dataChannel = getDataChannel(dc); + if (!dataChannel) + return -1; + + dataChannel->onOpen(nullptr); + dataChannel->onClosed(nullptr); + dataChannel->onError(nullptr); + dataChannel->onMessage(nullptr); + dataChannel->onBufferedAmountLow(nullptr); + dataChannel->onAvailable(nullptr); + + eraseDataChannel(dc); + return 0; +} int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) { auto peerConnection = getPeerConnection(pc); if (!peerConnection) return -1; - peerConnection->onDataChannel([pc, cb](std::shared_ptr dataChannel) { - int dc = emplaceDataChannel(dataChannel); - void *ptr = getUserPointer(pc); - rtcSetUserPointer(dc, ptr); - cb(dc, ptr); - }); + if (cb) + peerConnection->onDataChannel([pc, cb](std::shared_ptr dataChannel) { + int dc = emplaceDataChannel(dataChannel); + void *ptr = getUserPointer(pc); + rtcSetUserPointer(dc, ptr); + cb(dc, ptr); + }); + else + peerConnection->onDataChannel(nullptr); return 0; } @@ -148,9 +178,12 @@ int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) { if (!peerConnection) return -1; - peerConnection->onLocalDescription([pc, cb](const Description &desc) { - cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc)); - }); + 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; } @@ -159,9 +192,12 @@ int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) { if (!peerConnection) return -1; - peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) { - cb(cand.candidate().c_str(), cand.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; } @@ -170,9 +206,12 @@ int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) { if (!peerConnection) return -1; - peerConnection->onStateChange([pc, cb](PeerConnection::State state) { - cb(static_cast(state), getUserPointer(pc)); - }); + if (cb) + peerConnection->onStateChange([pc, cb](PeerConnection::State state) { + cb(static_cast(state), getUserPointer(pc)); + }); + else + peerConnection->onStateChange(nullptr); return 0; } @@ -181,9 +220,12 @@ int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) { if (!peerConnection) return -1; - peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) { - cb(static_cast(state), getUserPointer(pc)); - }); + if (cb) + peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) { + cb(static_cast(state), getUserPointer(pc)); + }); + else + peerConnection->onGatheringStateChange(nullptr); return 0; } @@ -225,7 +267,22 @@ int rtcSetOpenCallback(int dc, openCallbackFunc cb) { if (!dataChannel) return -1; - dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); }); + if (cb) + dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); }); + else + dataChannel->onOpen(nullptr); + return 0; +} + +int rtcSetClosedCallback(int dc, closedCallbackFunc cb) { + auto dataChannel = getDataChannel(dc); + if (!dataChannel) + return -1; + + if (cb) + dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); }); + else + dataChannel->onClosed(nullptr); return 0; } @@ -234,7 +291,11 @@ int rtcSetErrorCallback(int dc, errorCallbackFunc cb) { if (!dataChannel) return -1; - dataChannel->onError([dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); }); + if (cb) + dataChannel->onError( + [dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); }); + else + dataChannel->onError(nullptr); return 0; } @@ -243,11 +304,15 @@ int rtcSetMessageCallback(int dc, messageCallbackFunc cb) { if (!dataChannel) return -1; - dataChannel->onMessage( - [dc, cb](const binary &b) { - cb(reinterpret_cast(b.data()), b.size(), getUserPointer(dc)); - }, - [dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); }); + if (cb) + dataChannel->onMessage( + [dc, cb](const binary &b) { + cb(reinterpret_cast(b.data()), b.size(), getUserPointer(dc)); + }, + [dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); }); + else + dataChannel->onMessage(nullptr); + return 0; } @@ -289,7 +354,10 @@ int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) { if (!dataChannel) return -1; - dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); }); + if (cb) + dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); }); + else + dataChannel->onBufferedAmountLow(nullptr); return 0; } @@ -306,7 +374,10 @@ int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) { if (!dataChannel) return -1; - dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); }); + if (cb) + dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); }); + else + dataChannel->onOpen(nullptr); return 0; } @@ -333,11 +404,11 @@ int rtcReceiveMessage(int dc, char *buffer, int *size) { }, [&](const string &s) { int len = std::min(*size - 1, int(s.size())); - *size = -1; if (len >= 0) { std::copy(s.data(), s.data() + len, buffer); buffer[len] = '\0'; } + *size = -(len + 1); return len + 1; }}, *message); diff --git a/test/capi.cpp b/test/capi.cpp new file mode 100644 index 0000000..1dd9c70 --- /dev/null +++ b/test/capi.cpp @@ -0,0 +1,178 @@ +/** + * 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 + +#include +#include +#include +#include + +#include // 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); + + if (peer1->connected && peer2->connected) { + deletePeer(peer1); + deletePeer(peer2); + sleep(1); + printf("Success\n"); + return 0; + } + +error: + deletePeer(peer1); + deletePeer(peer2); + return -1; +} + +#include + +void test_capi() { + if (test_capi_main()) + throw std::runtime_error("C API test failed"); +} diff --git a/test/connectivity.cpp b/test/connectivity.cpp index 4cfbc3c..3c9d559 100644 --- a/test/connectivity.cpp +++ b/test/connectivity.cpp @@ -33,10 +33,9 @@ void test_connectivity() { Configuration config; // config.iceServers.emplace_back("stun:stun.l.google.com:19302"); - // config.iceServers.emplace_back("turn:USER@PASSWORD:turn.example.net:3478?transport=udp"); // - // libnice only config.enableIceTcp = true; // libnice only auto pc1 = std::make_shared(config); + auto pc2 = std::make_shared(config); pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) { @@ -83,11 +82,11 @@ void test_connectivity() { shared_ptr dc2; pc2->onDataChannel([&dc2](shared_ptr dc) { - cout << "Got a DataChannel with label: " << dc->label() << endl; + cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl; dc2 = dc; dc2->onMessage([](const variant &message) { if (holds_alternative(message)) { - cout << "Received 2: " << get(message) << endl; + cout << "Message 2: " << get(message) << endl; } }); dc2->send("Hello from 2"); @@ -98,12 +97,12 @@ void test_connectivity() { auto dc1 = wdc1.lock(); if (!dc1) return; - cout << "DataChannel open: " << dc1->label() << endl; + cout << "DataChannel 1: Open" << endl; dc1->send("Hello from 1"); }); dc1->onMessage([](const variant &message) { if (holds_alternative(message)) { - cout << "Received 1: " << get(message) << endl; + cout << "Message 1: " << get(message) << endl; } }); diff --git a/test/main.cpp b/test/main.cpp index 44020e5..738bb2d 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -21,14 +21,24 @@ using namespace std; void test_connectivity(); +void test_capi(); int main(int argc, char **argv) { try { + std::cout << "*** Running connectivity test..." << std::endl; test_connectivity(); + std::cout << "*** Finished connectivity test" << std::endl; } catch (const exception &e) { - std::cerr << "Connectivity check failed: " << e.what() << endl; + std::cerr << "Connectivity test failed: " << e.what() << endl; + return -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; }