diff --git a/examples/copy-paste/answerer.cpp b/examples/copy-paste/answerer.cpp index 3892ffa..d5fc630 100644 --- a/examples/copy-paste/answerer.cpp +++ b/examples/copy-paste/answerer.cpp @@ -127,13 +127,11 @@ int main(int argc, char **argv) { cout << "** Channel is not Open ** "; break; } - CandidateInfo local, remote; + Candidate local, remote; std::optional 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()) diff --git a/examples/copy-paste/offerer.cpp b/examples/copy-paste/offerer.cpp index 66bef82..0a5bb32 100644 --- a/examples/copy-paste/offerer.cpp +++ b/examples/copy-paste/offerer.cpp @@ -127,13 +127,11 @@ int main(int argc, char **argv) { cout << "** Channel is not Open ** "; break; } - CandidateInfo local, remote; + Candidate local, remote; std::optional 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()) diff --git a/include/rtc/candidate.hpp b/include/rtc/candidate.hpp index 8e3519f..816be92 100644 --- a/include/rtc/candidate.hpp +++ b/include/rtc/candidate.hpp @@ -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 address() const; + std::optional port() const; + std::optional 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 diff --git a/include/rtc/description.hpp b/include/rtc/description.hpp index 9e42d31..3b7a703 100644 --- a/include/rtc/description.hpp +++ b/include/rtc/description.hpp @@ -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 defaultCandidate() const; std::shared_ptr createEntry(string mline, string mid, Direction dir); void removeApplication(); diff --git a/include/rtc/peerconnection.hpp b/include/rtc/peerconnection.hpp index 0ae54f5..b0af009 100644 --- a/include/rtc/peerconnection.hpp +++ b/include/rtc/peerconnection.hpp @@ -112,7 +112,7 @@ public: void onTrack(std::function track)> callback); // libnice only - bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote); + bool getSelectedCandidatePair(Candidate *local, Candidate *remote); private: std::shared_ptr initIceTransport(Description::Role role); diff --git a/src/candidate.cpp b/src/candidate.cpp index ddb002d..111057b 100644 --- a/src/candidate.cpp +++ b/src/candidate.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -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; + using TcpTypeMap_t = std::unordered_map; + + 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 Candidate::address() const { + return isResolved() ? std::make_optional(mAddress) : nullopt; +} + +std::optional Candidate::port() const { + return isResolved() ? std::make_optional(mPort) : nullopt; +} + +std::optional 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"; } } diff --git a/src/description.cpp b/src/description.cpp index dd288d0..ec14e8d 100644 --- a/src/description.cpp +++ b/src/description.cpp @@ -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(); - 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 Description::defaultCandidate() const { + // Return the first host candidate with highest priority, favoring IPv4 + std::optional 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::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(); diff --git a/src/icetransport.cpp b/src/icetransport.cpp index d1561d2..b84f85a 100644 --- a/src/icetransport.cpp +++ b/src/icetransport.cpp @@ -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 diff --git a/src/icetransport.hpp b/src/icetransport.hpp index 61ca7d2..ea44f4f 100644 --- a/src/icetransport.hpp +++ b/src/icetransport.hpp @@ -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 }; diff --git a/src/peerconnection.cpp b/src/peerconnection.cpp index 79a77f5..50c9a15 100644 --- a/src/peerconnection.cpp +++ b/src/peerconnection.cpp @@ -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);