Compare commits

...

21 Commits

Author SHA1 Message Date
dc065add0b Bumped version to 0.4.5 2020-02-27 14:06:02 +01:00
e64d4049a6 Updated libjuice to v0.2.5 2020-02-27 14:05:33 +01:00
cb3bc85474 Fixed && instead of || when EWOULDBLOCK != EAGAIN #38 2020-02-26 14:45:39 +01:00
7af3da7872 Revised handling of path MTU discovery to exclude Mac OS 2020-02-26 09:15:42 +01:00
3c77d717d2 Explicitely added COMP-NULL to GnuTLS priorities 2020-02-25 00:17:06 +01:00
6f399945fe Updated libjuice 2020-02-25 00:02:05 +01:00
c8b14b1262 Change state to failed if a transport initialization fails 2020-02-24 23:53:10 +01:00
35d4455c4f Cleaned up tests and fixed SDP reading from console 2020-02-24 11:45:36 +01:00
7d21b4b42b Guess the description type from the context (useful for tests) 2020-02-24 11:39:11 +01:00
24e9e06c5a Bumped version to 0.4.4 2020-02-23 17:30:27 +01:00
443a19d8e7 Updated libjuice to v0.2.4 with better host candidates gathering 2020-02-23 17:24:47 +01:00
83de743924 Bumped version to 0.4.3 2020-02-21 12:39:27 +01:00
1dc1de4b86 Added platforms to Readme 2020-02-21 12:39:27 +01:00
8ca7722d48 Updated libjuice to v0.2.3 with Windows compilation support 2020-02-21 12:39:25 +01:00
3079072e63 For Win32, define _WIN32_WINNT to 0x0601 (Windows 7) if undefined 2020-02-21 00:04:57 +01:00
982d1c10e1 Merge pull request #35 from murat-dogan/master
Compile support on Windows with mingw-w64
2020-02-20 22:53:15 +00:00
50b22bbf3c delete win32 directive 2020-02-20 21:06:54 +03:00
93e153398f Compile support on Windows with mingw-w64 2020-02-20 14:55:21 +03:00
be6470d8bc Version 0.4.2 2020-02-06 11:42:03 +01:00
8a92c97058 Updated libjuice to v0.2.2 2020-02-06 11:36:01 +01:00
93da605230 Changed usrsctp submodule origin to sctplab/usrsctp 2020-01-29 11:24:11 +01:00
18 changed files with 344 additions and 235 deletions

2
.gitmodules vendored
View File

@ -3,7 +3,7 @@
url = https://github.com/SergiusTheBest/plog
[submodule "usrsctp"]
path = deps/usrsctp
url = https://github.com/paullouisageneau/usrsctp.git
url = https://github.com/sctplab/usrsctp.git
[submodule "deps/libjuice"]
path = deps/libjuice
url = https://github.com/paullouisageneau/libjuice

View File

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 3.7)
project (libdatachannel
DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.4.1
VERSION 0.4.5
LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
@ -16,6 +16,12 @@ endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
if(WIN32)
if (MSYS OR MINGW)
add_definitions(-DSCTP_STDINT_INCLUDE=<stdint.h>)
endif()
endif()
set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
@ -62,6 +68,12 @@ target_link_libraries(datachannel
Threads::Threads
Usrsctp::UsrsctpStatic
)
if(WIN32)
target_link_libraries(datachannel
"wsock32" #winsock2
"ws2_32" #winsock2
)
endif()
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel-static PROPERTIES
@ -76,6 +88,12 @@ target_link_libraries(datachannel-static
Threads::Threads
Usrsctp::UsrsctpStatic
)
if(WIN32)
target_link_libraries(datachannel-static
"wsock32" #winsock2
"ws2_32" #winsock2
)
endif()
if (USE_GNUTLS)
find_package(GnuTLS REQUIRED)
@ -138,6 +156,6 @@ add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
set_target_properties(datachannel-answerer PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME datachannel)
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
target_link_libraries(datachannel-answerer datachannel)

View File

@ -4,8 +4,8 @@ NAME=libdatachannel
CXX=$(CROSS)g++
AR=$(CROSS)ar
RM=rm -f
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
CXXFLAGS=-std=c++17
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
LDFLAGS=-pthread
LIBS=
LOCALLIBS=libusrsctp.a

View File

@ -1,6 +1,6 @@
# libdatachannel - C/C++ WebRTC DataChannels
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings for POSIX platforms and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.

2
deps/libjuice vendored

2
deps/usrsctp vendored

View File

@ -35,6 +35,7 @@ public:
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
Description(const string &sdp, const string &typeString = "");
Description(const string &sdp, Type type);
Description(const string &sdp, Type type, Role role);
Type type() const;
@ -47,6 +48,7 @@ public:
std::optional<size_t> maxMessageSize() const;
bool trickleEnabled() const;
void hintType(Type type);
void setFingerprint(string fingerprint);
void setSctpPort(uint16_t port);
void setMaxMessageSize(size_t size);

View File

@ -19,6 +19,12 @@
#ifndef RTC_INCLUDE_H
#define RTC_INCLUDE_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0602
#endif
#endif
#include <cstddef>
#include <functional>
#include <memory>

View File

@ -22,8 +22,14 @@
#include <array>
#include <sstream>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#elif __linux__
#include <netdb.h>
#include <sys/socket.h>
#endif
#include <sys/types.h>
using std::array;

View File

@ -45,12 +45,13 @@ inline void trim_end(string &str) {
namespace rtc {
Description::Description(const string &sdp, const string &typeString)
: Description(sdp, stringToType(typeString), Description::Role::ActPass) {}
: Description(sdp, stringToType(typeString)) {}
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
Description::Description(const string &sdp, Type type, Role role)
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) {
if (mType == Type::Answer && mRole == Role::ActPass)
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
: mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
hintType(type);
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed);
@ -109,6 +110,14 @@ std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSi
bool Description::trickleEnabled() const { return mTrickle; }
void Description::hintType(Type type) {
if (mType == Type::Unspec) {
mType = type;
if (mType == Type::Answer && mRole == Role::ActPass)
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
}
}
void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint));
}

View File

@ -73,7 +73,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL";
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
const char *err_pos = NULL;
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
"Unable to set TLS priorities");

View File

@ -19,9 +19,14 @@
#include "icetransport.hpp"
#include "configuration.hpp"
#ifdef _WIN32
#include <winsock2.h>
#elif __linux__
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
#include <sys/types.h>
#include <iostream>
@ -109,9 +114,6 @@ void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active;
mMid = description.mid();
// TODO
// mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP");
}

View File

@ -79,9 +79,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
}
void PeerConnection::setRemoteDescription(Description description) {
std::lock_guard lock(mRemoteDescriptionMutex);
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
auto remoteCandidates = description.extractCandidates();
std::lock_guard lock(mRemoteDescriptionMutex);
mRemoteDescription.emplace(std::move(description));
auto iceTransport = std::atomic_load(&mIceTransport);
@ -209,6 +210,7 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
}
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mIceTransport))
return transport;
@ -250,9 +252,15 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
});
std::atomic_store(&mIceTransport, transport);
return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("ICE transport initialization failed");
}
}
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mDtlsTransport))
return transport;
@ -278,9 +286,15 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
});
std::atomic_store(&mDtlsTransport, transport);
return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("DTLS transport initialization failed");
}
}
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mSctpTransport))
return transport;
@ -311,6 +325,11 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
});
std::atomic_store(&mSctpTransport, transport);
return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("SCTP transport initialization failed");
}
}
void PeerConnection::endLocalCandidates() {

View File

@ -23,7 +23,28 @@
#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
#define USE_PMTUD 1
#else
// Setting the DF flag is not available on Mac OS
#define USE_PMTUD 0
#endif
#else
#ifdef __linux__
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
// It should be safe to enable discovery for SCTP.
#define USE_PMTUD 1
#else
// Otherwise assume fragmentation
#define USE_PMTUD 0
#endif
#endif
using namespace std::chrono_literals;
using namespace std::chrono;
@ -117,12 +138,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
std::to_string(errno));
struct sctp_paddrparams spp = {};
#ifdef __linux__
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE).
// It should be safe to enable discovery for SCTP.
#if USE_PMTUD
// Enabled SCTP path MTU discovery
spp.spp_flags = SPP_PMTUD_ENABLE;
#else
// Otherwise, fall back to a safe MTU value.
// Fall back to a safe MTU value.
spp.spp_flags = SPP_PMTUD_DISABLE;
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
// See https://tools.ietf.org/html/rfc8261#section-5
@ -353,7 +373,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
if (ret >= 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size();
return true;
} else if (errno == EWOULDBLOCK && errno == EAGAIN) {
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
} else {

View File

@ -29,7 +29,12 @@
#include <map>
#include <mutex>
#ifdef _WIN32
#include <winsock2.h>
#elif __linux__
#include <sys/socket.h>
#endif
#include <sys/types.h>
#include "usrsctp.h"

View File

@ -23,6 +23,10 @@
#include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc;
using namespace std;
@ -31,14 +35,17 @@ 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:stun.l.google.com:19302");
// config.enableIceTcp = true;
// TURN server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
// 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);
@ -122,14 +129,18 @@ int main(int argc, char **argv) {
if (auto addr = pc2->remoteAddress())
cout << "Remote address 2: " << *addr << endl;
if (dc1->isOpen() && dc2->isOpen()) {
bool success;
if ((success = dc1->isOpen() && dc2->isOpen())) {
pc1->close();
pc2->close();
cout << "Success" << endl;
return 0;
} else {
cout << "Failure" << endl;
return 1;
}
#ifdef _WIN32
WSACleanup();
#endif
return success ? 0 : 1;
}

View File

@ -21,6 +21,11 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc;
using namespace std;
@ -28,27 +33,28 @@ 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::Warning);
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
Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302");
// config.enableIceTcp = true;
// TURN Server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) {
std::string s(sdp);
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
pc->onLocalDescription([](const Description &description) {
cout << "Local Description (Paste this to the other peer):" << endl;
cout << string(description) << endl;
});
pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
});
pc->onStateChange(
@ -66,7 +72,7 @@ int main(int argc, char **argv) {
dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "[ Received: " << get<string>(message) << " ]" << endl;
cout << "[Received message: " << get<string>(message) << "]" << endl;
}
});
});
@ -74,71 +80,69 @@ int main(int argc, char **argv) {
bool exit = false;
while (!exit) {
cout << endl
<< "**********************************************************************************"
"*****"
<< endl
<< "*************************************************************************" << endl
<< "* 0: Exit /"
<< " 1: Enter Description /"
<< " 2: Enter Candidate /"
<< " 3: Send Message *" << endl
<< " 1: Enter remote description /"
<< " 2: Enter remote candidate /"
<< " 3: Send message *" << endl
<< "[Command]: ";
int command;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
int command = -1;
cin >> command;
cin.ignore();
switch (command) {
case 0:
case 0: {
exit = true;
break;
case 1:
}
case 1: {
// Parse Description
cout << "[SDP]: ";
sdp = "";
while (sdp.length() == 0)
getline(cin, sdp);
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer,
Description::Role::Passive);
pc->setRemoteDescription(*descPtr);
cout << "[Description]: ";
string sdp, line;
while (getline(cin, line) && !line.empty()) {
sdp += line;
sdp += "\r\n";
}
std::cout << sdp;
pc->setRemoteDescription(sdp);
break;
case 2:
}
case 2: {
// Parse Candidate
cout << "[Candidate]: ";
candidate = "";
while (candidate.length() == 0)
string candidate;
getline(cin, candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
pc->addRemoteCandidate(candidate);
break;
case 3:
}
case 3: {
// Send Message
if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
cout << "[Message]: ";
message = "";
while (message.length() == 0)
string message;
getline(cin, message);
dc->send(message);
break;
default:
}
default: {
cout << "** Invalid Command ** ";
break;
}
}
}
if (dc)
dc->close();
if (pc)
pc->close();
#ifdef _WIN32
WSACleanup();
#endif
}

View File

@ -21,6 +21,11 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc;
using namespace std;
@ -30,25 +35,26 @@ 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");
// config.enableIceTcp = true;
// TURN server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) {
std::string s(sdp);
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
pc->onLocalDescription([](const Description &description) {
cout << "Local Description (Paste this to the other peer):" << endl;
cout << string(description) << endl;
});
pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
});
pc->onStateChange(
@ -58,7 +64,8 @@ int main(int argc, char **argv) {
cout << "[Gathering State: " << state << "]" << endl;
});
auto dc = pc->createDataChannel("test");
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
@ -69,73 +76,73 @@ int main(int argc, char **argv) {
}
});
this_thread::sleep_for(1s);
bool exit = false;
while (!exit) {
cout << endl
<< "**********************************************************************************"
"*****"
<< endl
<< "*************************************************************************" << endl
<< "* 0: Exit /"
<< " 1: Enter Description /"
<< " 2: Enter Candidate /"
<< " 3: Send Message *" << endl
<< " 1: Enter remote description /"
<< " 2: Enter remote candidate /"
<< " 3: Send message *" << endl
<< "[Command]: ";
int command;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
int command = -1;
cin >> command;
cin.ignore();
switch (command) {
case 0:
case 0: {
exit = true;
break;
case 1:
}
case 1: {
// Parse Description
cout << "[SDP]: ";
sdp = "";
while (sdp.length() == 0)
getline(cin, sdp);
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
descPtr = std::make_unique<Description>(sdp);
pc->setRemoteDescription(*descPtr);
cout << "[Description]: ";
string sdp, line;
while (getline(cin, line) && !line.empty()) {
sdp += line;
sdp += "\r\n";
}
pc->setRemoteDescription(sdp);
break;
case 2:
}
case 2: {
// Parse Candidate
cout << "[Candidate]: ";
candidate = "";
while (candidate.length() == 0)
string candidate;
getline(cin, candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
pc->addRemoteCandidate(candidate);
break;
case 3:
}
case 3: {
// Send Message
if (!dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
cout << "[Message]: ";
message = "";
while (message.length() == 0)
string message;
getline(cin, message);
dc->send(message);
break;
default:
}
default: {
cout << "** Invalid Command ** ";
break;
}
}
}
if (dc)
dc->close();
if (pc)
pc->close();
#ifdef _WIN32
WSACleanup();
#endif
}