Move candidate-attribute logic to the Candidate class
Introduce a new method, `Candidate::ToCandidateAttribute`, to handle
the serialization of a Candidate object into the SDP
'candidate-attribute' string format defined in RFC 5245.
This change is the first step in breaking a circular dependency
between the 'api' target (which contains `Candidate`) and the
'webrtc_sdp' target. Previously, 'api' relied on serialization
functions within 'webrtc_sdp', which in turn depended on 'api'.
By moving this logic into the `Candidate` class itself, 'api' no
longer needs 'webrtc_sdp' for this functionality.
This CL copies the existing logic from `pc/webrtc_sdp.cc` into
`api/candidate.cc` and adds `RTC_DCHECK` assertions to verify that
the new method produces output identical to the old logic. The
old code paths will be removed in a subsequent change.
Bug: webrtc:12330
Change-Id: I5a950e2d131c0ee1400091e454823f67070f730e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/411040
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45768}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 207c169..229e279 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -208,6 +208,7 @@
"../rtc_base:crypto_random",
"../rtc_base:ip_address",
"../rtc_base:logging",
+ "../rtc_base:net_helper",
"../rtc_base:network_constants",
"../rtc_base:socket_address",
"../rtc_base:stringutils",
diff --git a/api/candidate.cc b/api/candidate.cc
index 1222b67..87e153c 100644
--- a/api/candidate.cc
+++ b/api/candidate.cc
@@ -21,6 +21,7 @@
#include "rtc_base/crc32.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/ip_address.h"
+#include "rtc_base/net_helper.h"
#include "rtc_base/network_constants.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/strings/string_builder.h"
@@ -28,6 +29,68 @@
using webrtc::IceCandidateType;
namespace webrtc {
+namespace {
+constexpr char kAttributeCandidate[] = "candidate";
+constexpr char kAttributeCandidateTyp[] = "typ";
+constexpr char kAttributeCandidateRaddr[] = "raddr";
+constexpr char kAttributeCandidateRport[] = "rport";
+constexpr char kAttributeCandidateUfrag[] = "ufrag";
+constexpr char kAttributeCandidateGeneration[] = "generation";
+constexpr char kAttributeCandidateNetworkId[] = "network-id";
+constexpr char kAttributeCandidateNetworkCost[] = "network-cost";
+constexpr absl::string_view kSdpDelimiterColon = ":";
+constexpr char kTcpCandidateType[] = "tcptype";
+
+// Returns the `candidate-attribute` as described in:
+// https://www.rfc-editor.org/rfc/rfc5245#section-15.1
+std::string BuildCandidate(const Candidate& candidate, bool include_ufrag) {
+ StringBuilder os;
+ os << kAttributeCandidate;
+
+ absl::string_view type = candidate.type_name();
+ os << kSdpDelimiterColon << candidate.foundation() << " "
+ << candidate.component() << " " << candidate.protocol() << " "
+ << candidate.priority() << " "
+ << (candidate.address().ipaddr().IsNil()
+ ? candidate.address().hostname()
+ : candidate.address().ipaddr().ToString())
+ << " " << candidate.address().PortAsString() << " "
+ << kAttributeCandidateTyp << " " << type << " ";
+
+ // Related address
+ if (!candidate.related_address().IsNil()) {
+ os << kAttributeCandidateRaddr << " "
+ << candidate.related_address().ipaddr().ToString() << " "
+ << kAttributeCandidateRport << " "
+ << candidate.related_address().PortAsString() << " ";
+ }
+
+ // Note that we allow the tcptype to be missing, for backwards
+ // compatibility; the implementation treats this as a passive candidate.
+ // TODO(bugs.webrtc.org/11466): Treat a missing tcptype as an error?
+ if (candidate.protocol() == TCP_PROTOCOL_NAME &&
+ !candidate.tcptype().empty()) {
+ os << kTcpCandidateType << " " << candidate.tcptype() << " ";
+ }
+
+ // Extensions
+ os << kAttributeCandidateGeneration << " " << candidate.generation();
+ if (include_ufrag && !candidate.username().empty()) {
+ os << " " << kAttributeCandidateUfrag << " " << candidate.username();
+ }
+ if (candidate.network_id() > 0) {
+ os << " " << kAttributeCandidateNetworkId << " " << candidate.network_id();
+ }
+ if (candidate.network_cost() > 0) {
+ os << " " << kAttributeCandidateNetworkCost << " "
+ << candidate.network_cost();
+ }
+
+ return os.str();
+}
+
+} // namespace
+
absl::string_view IceCandidateTypeToString(IceCandidateType type) {
switch (type) {
case IceCandidateType::kHost:
@@ -40,9 +103,6 @@
return "relay";
}
}
-} // namespace webrtc
-
-namespace webrtc {
Candidate::Candidate()
: id_(CreateRandomString(8)),
@@ -135,6 +195,10 @@
return ost.Release();
}
+std::string Candidate::ToCandidateAttribute(bool include_ufrag) const {
+ return BuildCandidate(*this, include_ufrag);
+}
+
uint32_t Candidate::GetPriority(uint32_t type_preference,
int network_adapter_preference,
int relay_preference,
diff --git a/api/candidate.h b/api/candidate.h
index 9823107..4b18cac 100644
--- a/api/candidate.h
+++ b/api/candidate.h
@@ -206,6 +206,14 @@
std::string ToSensitiveString() const { return ToStringInternal(true); }
+ // Returns the `candidate-attribute` as described in:
+ // https://www.rfc-editor.org/rfc/rfc5245#section-15.1
+ // The returned string will start with "candidate:", not "a=candidate:" and
+ // will not end with "\r\n".
+ // include_ufrag: Controls whether or not the username is included in the
+ // returned string.
+ std::string ToCandidateAttribute(bool include_ufrag) const;
+
uint32_t GetPriority(uint32_t type_preference,
int network_adapter_preference,
int relay_preference,
diff --git a/api/candidate_unittest.cc b/api/candidate_unittest.cc
index 2eb9707..ae9ff7e 100644
--- a/api/candidate_unittest.cc
+++ b/api/candidate_unittest.cc
@@ -10,15 +10,27 @@
#include "api/candidate.h"
+#include <cstdint>
#include <string>
+#include "absl/strings/string_view.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/socket_address.h"
#include "test/gtest.h"
-using webrtc::IceCandidateType;
-
namespace webrtc {
+namespace {
+constexpr absl::string_view kRawCandidate =
+ "candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2";
+constexpr absl::string_view kRawHostnameCandidate =
+ "candidate:a0+B/1 1 udp 2130706432 a.test 1234 typ host generation 2";
+constexpr char kSdpTcpActiveCandidate[] =
+ "candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
+ "tcptype active generation 2";
+constexpr uint32_t kCandidatePriority = 2130706432U; // pref = 1.0
+constexpr uint32_t kCandidateGeneration = 2;
+constexpr char kCandidateFoundation1[] = "a0+B/1";
+} // namespace
TEST(CandidateTest, Id) {
Candidate c;
@@ -100,4 +112,50 @@
EXPECT_NE(foundation1, c.foundation());
}
+TEST(CandidateTest, ToCandidateAttribute) {
+ SocketAddress address("192.168.1.5", 1234);
+ Candidate candidate(ICE_CANDIDATE_COMPONENT_RTP, "udp", address,
+ kCandidatePriority, "", "", IceCandidateType::kHost,
+ kCandidateGeneration, kCandidateFoundation1);
+
+ EXPECT_EQ(candidate.ToCandidateAttribute(true), kRawCandidate);
+
+ Candidate candidate_with_ufrag(candidate);
+ candidate_with_ufrag.set_username("ABC");
+ EXPECT_EQ(candidate_with_ufrag.ToCandidateAttribute(true),
+ std::string(kRawCandidate) + " ufrag ABC");
+ EXPECT_EQ(candidate_with_ufrag.ToCandidateAttribute(false), kRawCandidate);
+
+ Candidate candidate_with_network_info(candidate);
+ candidate_with_network_info.set_network_id(1);
+ EXPECT_EQ(candidate_with_network_info.ToCandidateAttribute(true),
+ std::string(kRawCandidate) + " network-id 1");
+ candidate_with_network_info.set_network_cost(999);
+ EXPECT_EQ(candidate_with_network_info.ToCandidateAttribute(true),
+ std::string(kRawCandidate) + " network-id 1 network-cost 999");
+}
+
+TEST(CandidateTest, ToCandidateAttributeHostnameCandidate) {
+ SocketAddress address("a.test", 1234);
+ Candidate candidate(ICE_CANDIDATE_COMPONENT_RTP, "udp", address,
+ kCandidatePriority, "", "", IceCandidateType::kHost,
+ kCandidateGeneration, kCandidateFoundation1);
+ EXPECT_EQ(candidate.ToCandidateAttribute(true), kRawHostnameCandidate);
+}
+
+TEST(CandidateTest, ToCandidateAttributeTcpCandidates) {
+ // TODO: webrtc:12330 - This constant is currently defined in port.h
+ // as `TCPTYPE_ACTIVE_STR`. Remove this test only constant and use the
+ // main definition instead when the circular dependency problem has been
+ // fixed.
+ constexpr char TCPTYPE_ACTIVE_STR[] = "active";
+
+ Candidate candidate(ICE_CANDIDATE_COMPONENT_RTP, "tcp",
+ SocketAddress("192.168.1.5", 9), kCandidatePriority, "",
+ "", IceCandidateType::kHost, kCandidateGeneration,
+ kCandidateFoundation1);
+ candidate.set_tcptype(TCPTYPE_ACTIVE_STR);
+ EXPECT_EQ(candidate.ToCandidateAttribute(true), kSdpTcpActiveCandidate);
+}
+
} // namespace webrtc
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index 4346364..51c7a6a 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -25,6 +25,7 @@
#include <vector>
#include "absl/algorithm/container.h"
+#include "absl/base/nullability.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
@@ -997,7 +998,16 @@
<< candidate.network_cost();
}
- AddLine(os.str(), message);
+ const std::string& line = os.str();
+#if RTC_DCHECK_IS_ON
+ // TODO: webrtc:12330 - While moving this functionality into the Candidate
+ // class itself, verify that what this loop produces, is identical to what
+ // the SdpSerialize() method returns.
+ std::string compare = candidate.ToCandidateAttribute(include_ufrag);
+ // Note that SdpSerialize will not return the "a=" part.
+ RTC_DCHECK_EQ(line.substr(2), compare);
+#endif
+ AddLine(line, message);
}
}
@@ -3339,6 +3349,7 @@
message.erase(0, 2);
RTC_DCHECK(message.find(kLineBreak) == message.size() - 2);
message.resize(message.size() - 2);
+ RTC_DCHECK_EQ(candidate.ToCandidateAttribute(true), message);
return message;
}