Browse Source

Merge pull request #213 from paullouisageneau/sdp-default-addr-port

Enhancements for candidates
Paul-Louis Ageneau 4 years ago
parent
commit
129c8b187a

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 92fc9e7a9d8cd19a5c5d59cbc0a11cc9f684483b
+Subproject commit 13f1cbe7ef346cce7f847229f2e1827f4b21061b

+ 3 - 5
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<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())

+ 3 - 5
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<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())

+ 23 - 14
include/rtc/candidate.hpp

@@ -25,38 +25,47 @@
 
 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;
+	TransportType transportType() 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
 

+ 3 - 1
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<Candidate> defaultCandidate() const;
 	std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
 	void removeApplication();
 
@@ -201,6 +202,7 @@ private:
 
 	// Session-level attributes
 	Role mRole;
+	string mUsername;
 	string mSessionId;
 	string mIceUfrag, mIcePwd;
 	std::optional<string> mFingerprint;

+ 1 - 1
include/rtc/peerconnection.hpp

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

+ 2 - 0
include/rtc/rtc.h

@@ -127,6 +127,8 @@ RTC_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
+RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize);
+
 // DataChannel
 RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id

+ 102 - 33
src/candidate.cpp

@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <array>
 #include <sstream>
+#include <unordered_map>
 
 #ifdef _WIN32
 #include <winsock2.h>
@@ -47,20 +48,40 @@ inline bool hasprefix(const string &str, const string &prefix) {
 
 namespace rtc {
 
-Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
-	const std::array prefixes{"a=", "candidate:"};
-	for (const string &prefix : prefixes)
-		if (hasprefix(candidate, prefix))
-			candidate.erase(0, prefix.size());
+Candidate::Candidate(string candidate, string mid)
+    : mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
+      mPort(0), mPriority(0) {
+
+	if (!candidate.empty()) {
+		const std::array prefixes{"a=", "candidate:"};
+		for (const string &prefix : prefixes)
+			if (hasprefix(candidate, prefix))
+				candidate.erase(0, prefix.size());
+	}
 
 	mCandidate = std::move(candidate);
 	mMid = std::move(mid);
 }
 
 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 +96,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 +146,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 +168,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 +181,60 @@ 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; }
+
+Candidate::TransportType Candidate::transportType() const { return mTransportType; }
+
+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";
 	}
 }

+ 31 - 0
src/capi.cpp

@@ -619,6 +619,37 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 	});
 }
 
+int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+
+		if (!local)
+			localSize = 0;
+		if (!remote)
+			remoteSize = 0;
+
+		Candidate localCand;
+		Candidate remoteCand;
+		if (peerConnection->getSelectedCandidatePair(&localCand, &remoteCand)) {
+			if (localSize > 0) {
+				string localSdp = string(localCand);
+				localSize = std::min(localSize - 1, int(localSdp.size()));
+				std::copy(localSdp.begin(), localSdp.begin() + localSize, local);
+				local[localSize] = '\0';
+			}
+			if (remoteSize > 0) {
+				string remoteSdp = string(remoteCand);
+				remoteSize = std::min(remoteSize - 1, int(remoteSdp.size()));
+				std::copy(remoteSdp.begin(), remoteSdp.begin() + remoteSize, remote);
+				remote[remoteSize] = '\0';
+			}
+			return localSize + remoteSize;
+		}
+
+		return RTC_ERR_FAILURE;
+	});
+}
+
 int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
 	return WRAP({
 		auto dataChannel = getDataChannel(dc);

+ 57 - 23
src/description.cpp

@@ -74,11 +74,6 @@ Description::Description(const string &sdp, Type type, Role role)
     : mType(Type::Unspec), mRole(role) {
 	hintType(type);
 
-	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
-	std::default_random_engine generator(seed);
-	std::uniform_int_distribution<uint32_t> uniform;
-	mSessionId = std::to_string(uniform(generator));
-
 	int index = -1;
 	std::shared_ptr<Entry> current;
 	std::istringstream ss(sdp);
@@ -89,14 +84,14 @@ Description::Description(const string &sdp, Type type, Role role)
 		if (line.empty())
 			continue;
 
-		// Media description line (aka m-line)
-		if (match_prefix(line, "m=")) {
-			++index;
-			string mline = line.substr(2);
-			current = createEntry(std::move(mline), std::to_string(index), Direction::Unknown);
+		if (match_prefix(line, "m=")) { // Media description line (aka m-line)
+			current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
+
+		} else if (match_prefix(line, "o=")) { // Origin line
+			std::istringstream origin(line.substr(2));
+			origin >> mUsername >> mSessionId;
 
-			// Attribute line
-		} else if (match_prefix(line, "a=")) {
+		} else if (match_prefix(line, "a=")) { // Attribute line
 			string attr = line.substr(2);
 			auto [key, value] = parse_pair(attr);
 
@@ -139,6 +134,16 @@ Description::Description(const string &sdp, Type type, Role role)
 
 	if (mIcePwd.empty())
 		throw std::invalid_argument("Missing ice-pwd parameter in SDP description");
+
+	if (mUsername.empty())
+		mUsername = "rtc";
+
+	if (mSessionId.empty()) {
+		auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
+		std::default_random_engine generator(seed);
+		std::uniform_int_distribution<uint32_t> uniform;
+		mSessionId = std::to_string(uniform(generator));
+	}
 }
 
 Description::Type Description::type() const { return mType; }
@@ -194,7 +199,7 @@ string Description::generateSdp(string_view eol) const {
 
 	// Header
 	sdp << "v=0" << eol;
-	sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
+	sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
 	sdp << "s=-" << eol;
 	sdp << "t=0 0" << eol;
 
@@ -227,10 +232,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
@@ -250,13 +263,21 @@ string Description::generateApplicationSdp(string_view eol) const {
 
 	// Header
 	sdp << "v=0" << eol;
-	sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
+	sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
 	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 +301,21 @@ 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 +426,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();
@@ -515,8 +550,7 @@ Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown
 }
 
 Description::Media::Media(const string &mline, string mid, Direction dir)
-    : Entry(mline, std::move(mid), dir) {
-}
+    : Entry(mline, std::move(mid), dir) {}
 
 string Description::Media::description() const {
 	std::ostringstream desc;

+ 32 - 46
src/icetransport.cpp

@@ -187,6 +187,24 @@ std::optional<string> IceTransport::getRemoteAddress() const {
 	return nullopt;
 }
 
+bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
+	char sdpLocal[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
+	char sdpRemote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
+	if (juice_get_selected_candidates(mAgent.get(), sdpLocal, JUICE_MAX_CANDIDATE_SDP_STRING_LEN,
+	                                 sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
+		if (local) {
+			*local = Candidate(sdpLocal, mMid);
+			local->resolve(Candidate::ResolveMode::Simple);
+		}
+		if (remote) {
+			*remote = Candidate(sdpRemote, mMid);
+			remote->resolve(Candidate::ResolveMode::Simple);
+		}
+		return true;
+	}
+	return false;
+}
+
 bool IceTransport::send(message_ptr message) {
 	auto s = state();
 	if (!message || (s != State::Connected && s != State::Completed))
@@ -716,56 +734,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);
-
-	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 *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
+	if(local) *local = Candidate(sdpLocal, mMid);
+	g_free(sdpLocal);
 
-	return true;
-}
+	gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
+	if(remote) *remote = Candidate(sdpRemote, mMid);
+	g_free(sdpRemote);
 
-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;
-	}
+	if (local)
+		local->resolve(Candidate::ResolveMode::Simple);
+	if (remote)
+		remote->resolve(Candidate::ResolveMode::Simple);
+	return true;
 }
 
 } // namespace rtc

+ 1 - 6
src/icetransport.hpp

@@ -63,9 +63,7 @@ public:
 	bool stop() override;
 	bool send(message_ptr message) override; // false if dropped
 
-#if USE_NICE
-	bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
-#endif
+	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
 private:
 	bool outgoing(message_ptr message) override;
@@ -113,9 +111,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
 };
 

+ 4 - 8
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,15 +900,10 @@ void PeerConnection::resetCallbacks() {
 	mGatheringStateChangeCallback = nullptr;
 }
 
-bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
-                                              [[maybe_unused]] CandidateInfo *remote) {
-#if USE_NICE
+bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] Candidate *local,
+                                              [[maybe_unused]] Candidate *remote) {
 	auto iceTransport = std::atomic_load(&mIceTransport);
-	return iceTransport->getSelectedCandidatePair(local, remote);
-#else
-	PLOG_WARNING << "getSelectedCandidatePair() is only implemented with libnice as ICE backend";
-	return false;
-#endif
+	return iceTransport ? iceTransport->getSelectedCandidatePair(local, remote) : false;
 }
 
 void PeerConnection::clearStats() {

+ 17 - 0
test/capi_connectivity.cpp

@@ -186,6 +186,7 @@ int test_capi_connectivity_main() {
 	}
 
 	char buffer[BUFFER_SIZE];
+	char buffer2[BUFFER_SIZE];
 
 	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
 		fprintf(stderr, "rtcGetLocalDescription failed\n");
@@ -235,6 +236,22 @@ int test_capi_connectivity_main() {
 	}
 	printf("Remote address 2: %s\n", buffer);
 
+
+	if (rtcGetSelectedCandidatePair(peer1->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
+		goto error;
+	}
+	printf("Local candidate 1:  %s\n", buffer);
+	printf("Remote candidate 1: %s\n", buffer2);
+
+	if (rtcGetSelectedCandidatePair(peer2->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
+		goto error;
+	}
+	printf("Local candidate 2:  %s\n", buffer);
+	printf("Remote candidate 2: %s\n", buffer2);
+
+
 	deletePeer(peer1);
 	sleep(1);
 	deletePeer(peer2);

+ 10 - 0
test/connectivity.cpp

@@ -147,6 +147,16 @@ void test_connectivity() {
 	if (auto addr = pc2->remoteAddress())
 		cout << "Remote address 2: " << *addr << endl;
 
+	Candidate local, remote;
+	if(pc1->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 1:  " << local << endl;
+		cout << "Remote candidate 1: " << remote << endl;
+	}
+	if(pc2->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 2:  " << local << endl;
+		cout << "Remote candidate 2: " << remote << endl;
+	}
+
 	// Try to open a second data channel with another label
 	shared_ptr<DataChannel> second2;
 	pc2->onDataChannel([&second2](shared_ptr<DataChannel> dc) {