Use c=IN IP4 <hostname> to support the presence of hostname candidates.

Bug: chromium:927309
Change-Id: I1b014ee3d194bf2b8fcc47b37e31c7af866a8322
Reviewed-on: https://webrtc-review.googlesource.com/c/121960
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Qingsi Wang <qingsi@google.com>
Cr-Commit-Position: refs/heads/master@{#26619}
diff --git a/pc/jsep_session_description.cc b/pc/jsep_session_description.cc
index 9499e9a..88db174 100644
--- a/pc/jsep_session_description.cc
+++ b/pc/jsep_session_description.cc
@@ -56,6 +56,7 @@
     cricket::MediaContentDescription* media_desc) {
   int port = kDummyPort;
   std::string ip = kDummyAddress;
+  std::string hostname;
   int current_preference = kPreferenceUnknown;
   int current_family = AF_UNSPEC;
   for (size_t i = 0; i < candidate_collection.count(); ++i) {
@@ -81,12 +82,16 @@
     }
     current_preference = preference;
     current_family = family;
-    port = jsep_candidate->candidate().address().port();
-    ip = jsep_candidate->candidate().address().ipaddr().ToString();
+    const rtc::SocketAddress& candidate_addr =
+        jsep_candidate->candidate().address();
+    port = candidate_addr.port();
+    ip = candidate_addr.ipaddr().ToString();
+    hostname = candidate_addr.hostname();
   }
-  rtc::SocketAddress connection_addr;
-  connection_addr.SetIP(ip);
-  connection_addr.SetPort(port);
+  rtc::SocketAddress connection_addr(ip, port);
+  if (rtc::IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) {
+    connection_addr = rtc::SocketAddress(hostname, port);
+  }
   media_desc->set_connection_address(connection_addr);
 }
 
diff --git a/pc/jsep_session_description_unittest.cc b/pc/jsep_session_description_unittest.cc
index 4dc0788..6ac1a00 100644
--- a/pc/jsep_session_description_unittest.cc
+++ b/pc/jsep_session_description_unittest.cc
@@ -212,6 +212,22 @@
   EXPECT_EQ(1u, jsep_desc_->candidates(0)->count());
 }
 
+// Test that the connection address is set to a hostname address after adding a
+// hostname candidate.
+TEST_F(JsepSessionDescriptionTest, AddHostnameCandidate) {
+  cricket::Candidate c;
+  c.set_component(cricket::ICE_CANDIDATE_COMPONENT_RTP);
+  c.set_protocol(cricket::UDP_PROTOCOL_NAME);
+  c.set_address(rtc::SocketAddress("example.local", 1234));
+  c.set_type(cricket::LOCAL_PORT_TYPE);
+  JsepIceCandidate hostname_candidate("audio", 0, c);
+  EXPECT_TRUE(jsep_desc_->AddCandidate(&hostname_candidate));
+  ASSERT_NE(nullptr, jsep_desc_->description());
+  const auto& content = jsep_desc_->description()->contents()[0];
+  EXPECT_EQ("example.local:1234",
+            content.media_description()->connection_address().ToString());
+}
+
 // Test that we can serialize a JsepSessionDescription and deserialize it again.
 TEST_F(JsepSessionDescriptionTest, SerializeDeserialize) {
   std::string sdp = Serialize(jsep_desc_.get());
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index 736122b..f7a26af 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -1385,9 +1385,15 @@
   } else if (media_desc->connection_address().family() == AF_INET) {
     os << " " << kConnectionIpv4Addrtype << " "
        << media_desc->connection_address().ipaddr().ToString();
-  } else {
+  } else if (media_desc->connection_address().family() == AF_INET6) {
     os << " " << kConnectionIpv6Addrtype << " "
        << media_desc->connection_address().ipaddr().ToString();
+  } else if (!media_desc->connection_address().hostname().empty()) {
+    // For hostname candidates, we use c=IN IP4 <hostname>.
+    os << " " << kConnectionIpv4Addrtype << " "
+       << media_desc->connection_address().hostname();
+  } else {
+    os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress;
   }
   AddLine(os.str(), message);
 
diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc
index c04a1d9..b232caf 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/pc/webrtc_sdp_unittest.cc
@@ -3991,7 +3991,28 @@
             content2.media_description()->connection_address().ToString());
 }
 
-// Test that the invalid or unsupprted connection data cannot be parsed.
+// Test that a c= line that contains a hostname connection address can be
+// parsed.
+TEST_F(WebRtcSdpTest, ParseConnectionDataWithHostnameConnectionAddress) {
+  JsepSessionDescription jsep_desc(kDummyType);
+  std::string sdp = kSdpString;
+  EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc));
+
+  sdp = kSdpString;
+  Replace("c=IN IP4 0.0.0.0\r\n", "c=IN IP4 example.local\r\n", &sdp);
+  Replace("c=IN IP4 0.0.0.0\r\n", "c=IN IP4 example.local\r\n", &sdp);
+  ASSERT_TRUE(SdpDeserialize(sdp, &jsep_desc));
+
+  ASSERT_NE(nullptr, jsep_desc.description());
+  const auto& content1 = jsep_desc.description()->contents()[0];
+  EXPECT_EQ("example.local:9",
+            content1.media_description()->connection_address().ToString());
+  const auto& content2 = jsep_desc.description()->contents()[1];
+  EXPECT_EQ("example.local:9",
+            content2.media_description()->connection_address().ToString());
+}
+
+// Test that the invalid or unsupported connection data cannot be parsed.
 TEST_F(WebRtcSdpTest, ParseConnectionDataFailure) {
   JsepSessionDescription jsep_desc(kDummyType);
   std::string sdp = kSdpString;
@@ -4038,6 +4059,31 @@
             video_desc->connection_address().ToString());
 }
 
+// Test that a media description that contains a hostname connection address can
+// be correctly serialized.
+TEST_F(WebRtcSdpTest, SerializeAndDeserializeWithHostnameConnectionAddress) {
+  JsepSessionDescription expected_jsep(kDummyType);
+  cricket::Candidate c;
+  const rtc::SocketAddress hostname_addr("example.local", 1234);
+  audio_desc_->set_connection_address(hostname_addr);
+  video_desc_->set_connection_address(hostname_addr);
+  ASSERT_TRUE(
+      expected_jsep.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+  // Serialization.
+  std::string message = webrtc::SdpSerialize(expected_jsep);
+  // Deserialization.
+  JsepSessionDescription jdesc(kDummyType);
+  ASSERT_TRUE(SdpDeserialize(message, &jdesc));
+  auto audio_desc = jdesc.description()
+                        ->GetContentByName(kAudioContentName)
+                        ->media_description();
+  auto video_desc = jdesc.description()
+                        ->GetContentByName(kVideoContentName)
+                        ->media_description();
+  EXPECT_EQ(hostname_addr, audio_desc->connection_address());
+  EXPECT_EQ(hostname_addr, video_desc->connection_address());
+}
+
 // RFC4566 says "If a session has no meaningful name, the value "s= " SHOULD be
 // used (i.e., a single space as the session name)." So we should accept that.
 TEST_F(WebRtcSdpTest, DeserializeEmptySessionName) {