Enhance candidate parsing and expose the info directly

This commit is contained in:
Paul-Louis Ageneau
2020-10-16 00:05:05 +02:00
parent 65631f06a6
commit 137d4e3e8e
10 changed files with 180 additions and 113 deletions

View File

@ -127,13 +127,11 @@ int main(int argc, char **argv) {
cout << "** Channel is not Open ** ";
break;
}
CandidateInfo local, remote;
Candidate local, remote;
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
if (pc->getSelectedCandidatePair(&local, &remote)) {
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
<< local.transportType << endl;
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
<< " " << remote.transportType << endl;
cout << "Local: " << local << endl;
cout << "Remote: " << remote << endl;
cout << "Bytes Sent:" << pc->bytesSent()
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
if (rtt.has_value())

View File

@ -127,13 +127,11 @@ int main(int argc, char **argv) {
cout << "** Channel is not Open ** ";
break;
}
CandidateInfo local, remote;
Candidate local, remote;
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
if (pc->getSelectedCandidatePair(&local, &remote)) {
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
<< local.transportType << endl;
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
<< " " << remote.transportType << endl;
cout << "Local: " << local << endl;
cout << "Remote: " << remote << endl;
cout << "Bytes Sent:" << pc->bytesSent()
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
if (rtt.has_value())

View File

@ -25,38 +25,46 @@
namespace rtc {
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
struct CandidateInfo {
string address;
int port;
CandidateType type;
CandidateTransportType transportType;
};
class Candidate {
public:
Candidate(string candidate, string mid = "");
enum class Family { Unresolved, Ipv4, Ipv6 };
enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown };
Candidate(string candidate = "", string mid = "");
enum class ResolveMode { Simple, Lookup };
bool resolve(ResolveMode mode = ResolveMode::Simple);
bool isResolved() const;
string candidate() const;
string mid() const;
operator string() const;
bool isResolved() const;
Family family() const;
Type type() const;
std::optional<string> address() const;
std::optional<uint16_t> port() const;
std::optional<uint32_t> priority() const;
private:
string mCandidate;
string mMid;
bool mIsResolved;
// Extracted on resolution
Family mFamily;
Type mType;
TransportType mTransportType;
string mAddress;
uint16_t mPort;
uint32_t mPriority;
};
} // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
#endif

View File

@ -74,7 +74,7 @@ public:
void setDirection(Direction dir);
operator string() const;
string generateSdp(string_view eol) const;
string generateSdp(string_view eol, string_view addr, string_view port) const;
virtual void parseSdpLine(string_view line);
@ -194,6 +194,7 @@ public:
Application *application();
private:
std::optional<Candidate> defaultCandidate() const;
std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
void removeApplication();

View File

@ -112,7 +112,7 @@ public:
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
// libnice only
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
private:
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);

View File

@ -21,6 +21,7 @@
#include <algorithm>
#include <array>
#include <sstream>
#include <unordered_map>
#ifdef _WIN32
#include <winsock2.h>
@ -47,7 +48,10 @@ inline bool hasprefix(const string &str, const string &prefix) {
namespace rtc {
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
Candidate::Candidate(string candidate, string mid)
: mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
mPort(0), mPriority(0) {
const std::array prefixes{"a=", "candidate:"};
for (const string &prefix : prefixes)
if (hasprefix(candidate, prefix))
@ -58,9 +62,24 @@ Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
}
bool Candidate::resolve(ResolveMode mode) {
if (mIsResolved)
using TypeMap_t = std::unordered_map<string, Type>;
using TcpTypeMap_t = std::unordered_map<string, TransportType>;
static const TypeMap_t TypeMap = {{"host", Type::Host},
{"srflx", Type::ServerReflexive},
{"prflx", Type::PeerReflexive},
{"relay", Type::Relayed}};
static const TcpTypeMap_t TcpTypeMap = {{"active", TransportType::TcpActive},
{"passive", TransportType::TcpPassive},
{"so", TransportType::TcpSo}};
if (mFamily != Family::Unresolved)
return true;
if(mCandidate.empty())
throw std::logic_error("Candidate is empty");
PLOG_VERBOSE << "Resolving candidate (mode="
<< (mode == ResolveMode::Simple ? "simple" : "lookup")
<< "): " << mCandidate;
@ -75,16 +94,39 @@ bool Candidate::resolve(ResolveMode mode) {
string left;
std::getline(iss, left);
if (auto it = TypeMap.find(type); it != TypeMap.end())
mType = it->second;
else
mType = Type::Unknown;
if (transport == "UDP" || transport == "udp") {
mTransportType = TransportType::Udp;
}
else if (transport == "TCP" || transport == "tcp") {
std::istringstream iss(left);
string tcptype_, tcptype;
if(iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
mTransportType = it->second;
else
mTransportType = TransportType::TcpUnknown;
} else {
mTransportType = TransportType::TcpUnknown;
}
} else {
mTransportType = TransportType::Unknown;
}
// Try to resolve the node
struct addrinfo hints = {};
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG;
if (transport == "UDP" || transport == "udp") {
if (mTransportType == TransportType::Udp) {
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
}
if (transport == "TCP" || transport == "tcp") {
else if (mTransportType != TransportType::Unknown) {
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
}
@ -102,13 +144,18 @@ bool Candidate::resolve(ResolveMode mode) {
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
mAddress = nodebuffer;
mPort = uint16_t(std::stoul(servbuffer));
mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
const char sp{' '};
std::ostringstream oss;
oss << foundation << sp << component << sp << transport << sp << priority;
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
oss << left;
mCandidate = oss.str();
mIsResolved = true;
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
break;
}
@ -119,11 +166,9 @@ bool Candidate::resolve(ResolveMode mode) {
}
}
return mIsResolved;
return mFamily != Family::Unresolved;
}
bool Candidate::isResolved() const { return mIsResolved; }
string Candidate::candidate() const { return "candidate:" + mCandidate; }
string Candidate::mid() const { return mMid; }
@ -134,38 +179,62 @@ Candidate::operator string() const {
return line.str();
}
bool Candidate::isResolved() const { return mFamily != Family::Unresolved; }
Candidate::Family Candidate::family() const {
return mFamily;
}
Candidate::Type Candidate::type() const {
return mType;
}
std::optional<string> Candidate::address() const {
return isResolved() ? std::make_optional(mAddress) : nullopt;
}
std::optional<uint16_t> Candidate::port() const {
return isResolved() ? std::make_optional(mPort) : nullopt;
}
std::optional<uint32_t> Candidate::priority() const {
return isResolved() ? std::make_optional(mPriority) : nullopt;
}
} // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
return out << std::string(candidate);
}
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type) {
switch (type) {
case rtc::CandidateType::Host:
return out << "Host";
case rtc::CandidateType::PeerReflexive:
return out << "PeerReflexive";
case rtc::CandidateType::Relayed:
return out << "Relayed";
case rtc::CandidateType::ServerReflexive:
return out << "ServerReflexive";
case rtc::Candidate::Type::Host:
return out << "host";
case rtc::Candidate::Type::PeerReflexive:
return out << "peer_reflexive";
case rtc::Candidate::Type::ServerReflexive:
return out << "server_reflexive";
case rtc::Candidate::Type::Relayed:
return out << "relayed";
default:
return out << "Unknown";
return out << "unknown";
}
}
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType) {
switch (transportType) {
case rtc::CandidateTransportType::TcpActive:
return out << "TcpActive";
case rtc::CandidateTransportType::TcpPassive:
return out << "TcpPassive";
case rtc::CandidateTransportType::TcpSo:
return out << "TcpSo";
case rtc::CandidateTransportType::Udp:
return out << "Udp";
case rtc::Candidate::TransportType::Udp:
return out << "UDP";
case rtc::Candidate::TransportType::TcpActive:
return out << "TCP_active";
case rtc::Candidate::TransportType::TcpPassive:
return out << "TCP_passive";
case rtc::Candidate::TransportType::TcpSo:
return out << "TCP_so";
case rtc::Candidate::TransportType::TcpUnknown:
return out << "TCP_unknown";
default:
return out << "Unknown";
return out << "unknown";
}
}

View File

@ -227,10 +227,18 @@ string Description::generateSdp(string_view eol) const {
if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
auto cand = defaultCandidate();
const string addr = cand && cand->isResolved()
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
" " + *cand->address())
: "IP4 0.0.0.0";
const string port = std::to_string(
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
// Entries
bool first = true;
for (const auto &entry : mEntries) {
sdp << entry->generateSdp(eol);
sdp << entry->generateSdp(eol, addr, port);
if (std::exchange(first, false)) {
// Candidates
@ -254,9 +262,17 @@ string Description::generateApplicationSdp(string_view eol) const {
sdp << "s=-" << eol;
sdp << "t=0 0" << eol;
auto cand = defaultCandidate();
const string addr = cand && cand->isResolved()
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
" " + *cand->address())
: "IP4 0.0.0.0";
const string port = std::to_string(
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
// Application
auto app = mApplication ? mApplication : std::make_shared<Application>();
sdp << app->generateSdp(eol);
sdp << app->generateSdp(eol, addr, port);
// Session-level attributes
sdp << "a=msid-semantic:WMS *" << eol;
@ -280,6 +296,20 @@ string Description::generateApplicationSdp(string_view eol) const {
return sdp.str();
}
std::optional<Candidate> Description::defaultCandidate() const {
// Return the first host candidate with highest priority, favoring IPv4
std::optional<Candidate> result;
for(const auto &c : mCandidates) {
if(c.type() == Candidate::Type::Host) {
if(!result
|| (result->family() == Candidate::Family::Ipv6 && c.family() == Candidate::Family::Ipv4)
|| (result->family() == c.family() && result->priority() < c.priority()))
result.emplace(c);
}
}
return result;
}
shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
string type = mline.substr(0, mline.find(' '));
if (type == "application") {
@ -390,13 +420,12 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
Description::Entry::operator string() const { return generateSdp("\r\n"); }
Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
string Description::Entry::generateSdp(string_view eol) const {
string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
std::ostringstream sdp;
// Port 9 is the discard protocol
sdp << "m=" << type() << ' ' << 9 << ' ' << description() << eol;
sdp << "c=IN IP4 0.0.0.0" << eol;
sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
sdp << "c=IN " << addr << eol;
sdp << generateSdpLines(eol);
return sdp.str();

View File

@ -716,58 +716,24 @@ void IceTransport::LogCallback(const gchar * /*logDomain*/, GLogLevelFlags logLe
PLOG(severity) << "nice: " << message;
}
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
NiceCandidate *local, *remote;
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
if (!result)
bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
NiceCandidate *niceLocal, *niceRemote;
if(!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
return false;
char ipaddr[INET6_ADDRSTRLEN];
nice_address_to_string(&local->addr, ipaddr);
localInfo->address = std::string(ipaddr);
localInfo->port = nice_address_get_port(&local->addr);
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
localInfo->transportType =
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
gchar *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
if(local) *local = Candidate(sdpLocal);
g_free(sdpLocal);
nice_address_to_string(&remote->addr, ipaddr);
remoteInfo->address = std::string(ipaddr);
remoteInfo->port = nice_address_get_port(&remote->addr);
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
remoteInfo->transportType =
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
if(remote) *remote = Candidate(sdpRemote);
g_free(sdpRemote);
local->resolve(Candidate::ResolveMode::Simple);
remote->resolve(Candidate::ResolveMode::Simple);
return true;
}
CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
switch (type) {
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
return CandidateType::PeerReflexive;
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
return CandidateType::Relayed;
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
return CandidateType::ServerReflexive;
default:
return CandidateType::Host;
}
}
CandidateTransportType
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
switch (type) {
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
return CandidateTransportType::TcpActive;
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
return CandidateTransportType::TcpPassive;
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
return CandidateTransportType::TcpSo;
default:
return CandidateTransportType::Udp;
}
}
} // namespace rtc
#endif

View File

@ -64,7 +64,7 @@ public:
bool send(message_ptr message) override; // false if dropped
#if USE_NICE
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
#endif
private:
@ -113,9 +113,6 @@ private:
static gboolean TimeoutCallback(gpointer userData);
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
gpointer user_data);
static CandidateType NiceTypeToCandidateType(NiceCandidateType type);
static CandidateTransportType
NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
#endif
};

View File

@ -835,6 +835,7 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
if (!mLocalDescription)
throw std::logic_error("Got a local candidate without local description");
candidate.resolve(Candidate::ResolveMode::Simple); // for proper SDP generation later
mLocalDescription->addCandidate(candidate);
mProcessor->enqueue([this, candidate = std::move(candidate)]() {
@ -899,8 +900,8 @@ void PeerConnection::resetCallbacks() {
mGatheringStateChangeCallback = nullptr;
}
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
[[maybe_unused]] CandidateInfo *remote) {
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] Candidate *local,
[[maybe_unused]] Candidate *remote) {
#if USE_NICE
auto iceTransport = std::atomic_load(&mIceTransport);
return iceTransport->getSelectedCandidatePair(local, remote);