Compare commits

...

9 Commits

11 changed files with 274 additions and 279 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 3.7)
project (libdatachannel
DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.4.4
VERSION 0.4.5
LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
@ -156,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)

2
deps/libjuice 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

@ -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

@ -114,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,108 +210,126 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
}
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mIceTransport))
return transport;
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mIceTransport))
return transport;
auto transport = std::make_shared<IceTransport>(
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
[this](IceTransport::State state) {
switch (state) {
case IceTransport::State::Connecting:
changeState(State::Connecting);
break;
case IceTransport::State::Failed:
changeState(State::Failed);
break;
case IceTransport::State::Connected:
initDtlsTransport();
break;
case IceTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
},
[this](IceTransport::GatheringState state) {
switch (state) {
case IceTransport::GatheringState::InProgress:
changeGatheringState(GatheringState::InProgress);
break;
case IceTransport::GatheringState::Complete:
endLocalCandidates();
changeGatheringState(GatheringState::Complete);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mIceTransport, transport);
return transport;
auto transport = std::make_shared<IceTransport>(
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
[this](IceTransport::State state) {
switch (state) {
case IceTransport::State::Connecting:
changeState(State::Connecting);
break;
case IceTransport::State::Failed:
changeState(State::Failed);
break;
case IceTransport::State::Connected:
initDtlsTransport();
break;
case IceTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
},
[this](IceTransport::GatheringState state) {
switch (state) {
case IceTransport::GatheringState::InProgress:
changeGatheringState(GatheringState::InProgress);
break;
case IceTransport::GatheringState::Complete:
endLocalCandidates();
changeGatheringState(GatheringState::Complete);
break;
default:
// Ignore
break;
}
});
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() {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mDtlsTransport))
return transport;
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mDtlsTransport))
return transport;
auto lower = std::atomic_load(&mIceTransport);
auto transport = std::make_shared<DtlsTransport>(
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
[this](DtlsTransport::State state) {
switch (state) {
case DtlsTransport::State::Connected:
initSctpTransport();
break;
case DtlsTransport::State::Failed:
changeState(State::Failed);
break;
case DtlsTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mDtlsTransport, transport);
return transport;
auto lower = std::atomic_load(&mIceTransport);
auto transport = std::make_shared<DtlsTransport>(
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
[this](DtlsTransport::State state) {
switch (state) {
case DtlsTransport::State::Connected:
initSctpTransport();
break;
case DtlsTransport::State::Failed:
changeState(State::Failed);
break;
case DtlsTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
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() {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mSctpTransport))
return transport;
try {
std::lock_guard lock(mInitMutex);
if (auto transport = std::atomic_load(&mSctpTransport))
return transport;
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
auto lower = std::atomic_load(&mDtlsTransport);
auto transport = std::make_shared<SctpTransport>(
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
[this](SctpTransport::State state) {
switch (state) {
case SctpTransport::State::Connected:
changeState(State::Connected);
openDataChannels();
break;
case SctpTransport::State::Failed:
remoteCloseDataChannels();
changeState(State::Failed);
break;
case SctpTransport::State::Disconnected:
remoteCloseDataChannels();
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mSctpTransport, transport);
return transport;
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
auto lower = std::atomic_load(&mDtlsTransport);
auto transport = std::make_shared<SctpTransport>(
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
[this](SctpTransport::State state) {
switch (state) {
case SctpTransport::State::Connected:
changeState(State::Connected);
openDataChannels();
break;
case SctpTransport::State::Failed:
remoteCloseDataChannels();
changeState(State::Failed);
break;
case SctpTransport::State::Disconnected:
remoteCloseDataChannels();
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
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

@ -27,6 +27,25 @@
#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;
@ -119,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
@ -355,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

@ -35,32 +35,21 @@ 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);
#ifdef _WIN32
WSADATA wsaData;
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::string err("WSAStartup failed. Error:");
err.append(WSAGetLastError() + "");
std::cout << err;
return -1;
}
#endif
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
auto pc2 = wpc2.lock();
if (!pc2)
@ -140,20 +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;
#ifdef _WIN32
WSACleanup();
#endif
return 0;
} else {
cout << "Failure" << endl;
}
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}
return success ? 0 : 1;
}

View File

@ -21,6 +21,7 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
@ -32,60 +33,46 @@ 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);
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);
InitLogger(LogLevel::Debug);
#ifdef _WIN32
WSADATA wsaData;
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::string err("WSAStartup failed. Error:");
err.append(WSAGetLastError() + "");
std::cout << err;
return -1;
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
pc->onLocalDescription([](const Description &sdp) {
std::string s(sdp);
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
std::replace(s.begin(), s.end(), '\r', static_cast<char>(95));
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302");
auto pc = std::make_shared<PeerConnection>(config);
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(
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[ Gathering State: " << state << " ]" << endl;
cout << "[Gathering State: " << state << "]" << endl;
});
shared_ptr<DataChannel> dc = nullptr;
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
cout << "[ Got a DataChannel with label: " << _dc->label() << " ]" << endl;
cout << "[Got a DataChannel with label: " << _dc->label() << "]" << endl;
dc = _dc;
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
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;
}
});
});
@ -93,68 +80,61 @@ 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
<< " [Command]: ";
<< " 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');
std::replace(sdp.begin(), sdp.end(), static_cast<char>(95), '\r');
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)
getline(cin, candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
string candidate;
getline(cin, candidate);
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)
getline(cin, message);
string message;
getline(cin, message);
dc->send(message);
break;
default:
}
default: {
cout << "** Invalid Command ** ";
break;
}
}
}
if (dc)

View File

@ -21,6 +21,7 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
@ -34,124 +35,106 @@ 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);
#ifdef _WIN32
WSADATA wsaData;
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::string err("WSAStartup failed. Error:");
err.append(WSAGetLastError() + "");
std::cout << err;
return -1;
}
#endif
pc->onLocalDescription([](const Description &sdp) {
std::string s(sdp);
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
std::replace(s.begin(), s.end(), '\r', static_cast<char>(95));
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(
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[ Gathering State: " << state << " ]" << endl;
cout << "[Gathering State: " << state << "]" << endl;
});
auto dc = pc->createDataChannel("test");
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "[ Received: " << get<string>(message) << " ]" << endl;
cout << "[Received: " << get<string>(message) << "]" << endl;
}
});
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
<< " [Command]: ";
<< " 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');
std::replace(sdp.begin(), sdp.end(), static_cast<char>(95), '\r');
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)
getline(cin, candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
string candidate;
getline(cin, candidate);
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)
getline(cin, message);
string message;
getline(cin, message);
dc->send(message);
break;
default:
}
default: {
cout << "** Invalid Command ** ";
break;
}
}
}
if (dc)