Encoding and Decoding of TCP candidates as defined in RFC 6544.

R=juberti@chromium.org, jiayl@webrtc.org, juberti@webrtc.org
BUG=2204

Review URL: https://webrtc-codereview.appspot.com/21479004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6857 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
index 4f774a7..64f8e5e 100644
--- a/talk/app/webrtc/webrtcsdp.cc
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -168,6 +168,7 @@
 // TODO: How to map the prflx with circket candidate type
 // static const char kCandidatePrflx[] = "prflx";
 static const char kCandidateRelay[] = "relay";
+static const char kTcpCandidateType[] = "tcptype";
 
 static const char kSdpDelimiterEqual = '=';
 static const char kSdpDelimiterSpace = ' ';
@@ -1042,6 +1043,25 @@
     ++current_position;
   }
 
+  // If this is a TCP candidate, it has additional extension as defined in
+  // RFC 6544.
+  std::string tcptype;
+  if (fields.size() >= (current_position + 2) &&
+      fields[current_position] == kTcpCandidateType) {
+    tcptype = fields[++current_position];
+    ++current_position;
+
+    if (tcptype != cricket::TCPTYPE_ACTIVE_STR &&
+        tcptype != cricket::TCPTYPE_PASSIVE_STR &&
+        tcptype != cricket::TCPTYPE_SIMOPEN_STR) {
+      return ParseFailed(first_line, "Invalid TCP candidate type.", error);
+    }
+
+    if (protocol != cricket::PROTO_TCP) {
+      return ParseFailed(first_line, "Invalid non-TCP candidate", error);
+    }
+  }
+
   // Extension
   // Empty string as the candidate username and password.
   // Will be updated later with the ice-ufrag and ice-pwd.
@@ -1074,6 +1094,7 @@
       address, priority, username, password, candidate_type, network_name,
       generation, foundation);
   candidate->set_related_address(related_address);
+  candidate->set_tcptype(tcptype);
   return true;
 }
 
@@ -1694,11 +1715,14 @@
 
     InitAttrLine(kAttributeCandidate, &os);
     os << kSdpDelimiterColon
-       << it->foundation() << " " << it->component() << " "
-       << it->protocol() << " " << it->priority() << " "
+       << it->foundation() << " "
+       << it->component() << " "
+       << it->protocol() << " "
+       << it->priority() << " "
        << it->address().ipaddr().ToString() << " "
        << it->address().PortAsString() << " "
-       << kAttributeCandidateTyp << " " << type << " ";
+       << kAttributeCandidateTyp << " "
+       << type << " ";
 
     // Related address
     if (!it->related_address().IsNil()) {
@@ -1708,6 +1732,17 @@
          << it->related_address().PortAsString() << " ";
     }
 
+    if (it->protocol() == cricket::TCP_PROTOCOL_NAME) {
+      // In case of WebRTC, candidate must be always "active" only. That means
+      // it should have port number either 0 or 9.
+      ASSERT(it->address().port() == 0 ||
+             it->address().port() == cricket::DISCARD_PORT);
+      ASSERT(it->tcptype() == cricket::TCPTYPE_ACTIVE_STR);
+      // TODO(mallinath) : Uncomment below line once WebRTCSdp capable of
+      // parsing RFC 6544.
+      // os << kTcpCandidateType << " " << it->tcptype() << " ";
+    }
+
     // Extensions
     os << kAttributeCandidateGeneration << " " << it->generation();
 
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
index 71ebe0d..f0546f8 100644
--- a/talk/app/webrtc/webrtcsdp_unittest.cc
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -354,6 +354,19 @@
 static const char kSdpOneCandidate[] =
     "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host "
     "generation 2\r\n";
+static const char kSdpTcpActiveCandidate[] =
+    "candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
+    "tcptype active generation 2";
+static const char kSdpTcpPassiveCandidate[] =
+    "candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
+    "tcptype passive generation 2";
+static const char kSdpTcpSOCandidate[] =
+    "candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
+    "tcptype so generation 2";
+static const char kSdpTcpInvalidCandidate[] =
+    "candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
+    "tcptype invalid generation 2";
+
 
 // One candidate reference string.
 static const char kSdpOneCandidateOldFormat[] =
@@ -1611,6 +1624,22 @@
   EXPECT_EQ(std::string(kRawCandidate), message);
 }
 
+// TODO(mallinath) : Enable this test once WebRTCSdp capable of parsing
+// RFC 6544.
+TEST_F(WebRtcSdpTest, DISABLED_SerializeTcpCandidates) {
+  Candidate candidate(
+      "", ICE_CANDIDATE_COMPONENT_RTP, "tcp",
+      rtc::SocketAddress("192.168.1.5", 9), kCandidatePriority,
+      "", "", LOCAL_PORT_TYPE,
+      "", kCandidateGeneration, kCandidateFoundation1);
+  candidate.set_tcptype(cricket::TCPTYPE_ACTIVE_STR);
+  rtc::scoped_ptr<IceCandidateInterface> jcandidate(
+    new JsepIceCandidate(std::string("audio_content_name"), 0, candidate));
+
+  std::string message = webrtc::SdpSerializeCandidate(*jcandidate);
+  EXPECT_EQ(std::string(kSdpTcpActiveCandidate), message);
+}
+
 TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
   JsepSessionDescription jdesc(kDummyString);
   // Deserialize
@@ -1897,6 +1926,25 @@
   EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
   EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
   EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
+
+  sdp = kSdpTcpActiveCandidate;
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  // Make a cricket::Candidate equivalent to kSdpTcpCandidate string.
+  Candidate candidate(
+      "", ICE_CANDIDATE_COMPONENT_RTP, "tcp",
+      rtc::SocketAddress("192.168.1.5", 9), kCandidatePriority,
+      "", "", LOCAL_PORT_TYPE,
+      "", kCandidateGeneration, kCandidateFoundation1);
+  rtc::scoped_ptr<IceCandidateInterface> jcandidate_template(
+    new JsepIceCandidate(std::string("audio_content_name"), 0, candidate));
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(
+                    jcandidate_template->candidate()));
+  sdp = kSdpTcpPassiveCandidate;
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  sdp = kSdpTcpSOCandidate;
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  sdp = kSdpTcpInvalidCandidate;
+  EXPECT_FALSE(SdpDeserializeCandidate(sdp, &jcandidate));
 }
 
 // This test verifies the deserialization of candidate-attribute
diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h
index 56174bd..9c1198d 100644
--- a/talk/p2p/base/candidate.h
+++ b/talk/p2p/base/candidate.h
@@ -139,6 +139,10 @@
       const rtc::SocketAddress & related_address) {
     related_address_ = related_address;
   }
+  const std::string& tcptype() const { return tcptype_; }
+  void set_tcptype(const std::string& tcptype){
+    tcptype_ = tcptype;
+  }
 
   // Determines whether this candidate is equivalent to the given one.
   bool IsEquivalent(const Candidate& c) const {
@@ -217,6 +221,7 @@
   uint32 generation_;
   std::string foundation_;
   rtc::SocketAddress related_address_;
+  std::string tcptype_;
 };
 
 }  // namespace cricket
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
index 0d3a5cd..42f7a95 100644
--- a/talk/p2p/base/port.cc
+++ b/talk/p2p/base/port.cc
@@ -147,6 +147,12 @@
   return false;
 }
 
+// RFC 6544, TCP candidate encoding rules.
+const int DISCARD_PORT = 9;
+const char TCPTYPE_ACTIVE_STR[] = "active";
+const char TCPTYPE_PASSIVE_STR[] = "passive";
+const char TCPTYPE_SIMOPEN_STR[] = "so";
+
 // Foundation:  An arbitrary string that is the same for two candidates
 //   that have the same type, base IP address, protocol (UDP, TCP,
 //   etc.), and STUN or TURN server.  If any of these are different,
@@ -250,26 +256,21 @@
                       const rtc::SocketAddress& base_address,
                       const rtc::SocketAddress& related_address,
                       const std::string& protocol,
-                      const std::string& type,
-                      uint32 type_preference,
-                      bool final) {
-  AddAddress(address, base_address, related_address, protocol,
-             type, type_preference, 0, final);
-}
-
-void Port::AddAddress(const rtc::SocketAddress& address,
-                      const rtc::SocketAddress& base_address,
-                      const rtc::SocketAddress& related_address,
-                      const std::string& protocol,
+                      const std::string& tcptype,
                       const std::string& type,
                       uint32 type_preference,
                       uint32 relay_preference,
                       bool final) {
+  if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
+    ASSERT(!tcptype.empty());
+  }
+
   Candidate c;
   c.set_id(rtc::CreateRandomString(8));
   c.set_component(component_);
   c.set_type(type);
   c.set_protocol(protocol);
+  c.set_tcptype(tcptype);
   c.set_address(address);
   c.set_priority(c.GetPriority(type_preference, network_->preference(),
                                relay_preference));
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
index 0071a03..a417c0a 100644
--- a/talk/p2p/base/port.h
+++ b/talk/p2p/base/port.h
@@ -61,6 +61,12 @@
 extern const char TCP_PROTOCOL_NAME[];
 extern const char SSLTCP_PROTOCOL_NAME[];
 
+// RFC 6544, TCP candidate encoding rules.
+extern const int DISCARD_PORT;
+extern const char TCPTYPE_ACTIVE_STR[];
+extern const char TCPTYPE_PASSIVE_STR[];
+extern const char TCPTYPE_SIMOPEN_STR[];
+
 // The length of time we wait before timing out readability on a connection.
 const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000;   // 30 seconds
 
@@ -308,18 +314,13 @@
   };
 
   void set_type(const std::string& type) { type_ = type; }
-  // Fills in the local address of the port.
-  void AddAddress(const rtc::SocketAddress& address,
-                  const rtc::SocketAddress& base_address,
-                  const rtc::SocketAddress& related_address,
-                  const std::string& protocol, const std::string& type,
-                  uint32 type_preference, bool final);
 
   void AddAddress(const rtc::SocketAddress& address,
                   const rtc::SocketAddress& base_address,
                   const rtc::SocketAddress& related_address,
-                  const std::string& protocol, const std::string& type,
-                  uint32 type_preference, uint32 relay_preference, bool final);
+                  const std::string& protocol, const std::string& tcptype,
+                  const std::string& type, uint32 type_preference,
+                  uint32 relay_preference, bool final);
 
   // Adds the given connection to the list.  (Deleting removes them.)
   void AddConnection(Connection* conn);
diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc
index e4f37a9..9b9568c 100644
--- a/talk/p2p/base/port_unittest.cc
+++ b/talk/p2p/base/port_unittest.cc
@@ -146,22 +146,22 @@
 
   virtual void PrepareAddress() {
     rtc::SocketAddress addr(ip(), min_port());
-    AddAddress(addr, addr, rtc::SocketAddress(), "udp", Type(),
-               ICE_TYPE_PREFERENCE_HOST, true);
+    AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(),
+               ICE_TYPE_PREFERENCE_HOST, 0, true);
   }
 
   // Exposed for testing candidate building.
   void AddCandidateAddress(const rtc::SocketAddress& addr) {
-    AddAddress(addr, addr, rtc::SocketAddress(), "udp", Type(),
-               type_preference_, false);
+    AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(),
+               type_preference_, 0, false);
   }
   void AddCandidateAddress(const rtc::SocketAddress& addr,
                            const rtc::SocketAddress& base_address,
                            const std::string& type,
                            int type_preference,
                            bool final) {
-    AddAddress(addr, base_address, rtc::SocketAddress(), "udp", type,
-               type_preference, final);
+    AddAddress(addr, base_address, rtc::SocketAddress(), "udp", "", type,
+               type_preference, 0, final);
   }
 
   virtual Connection* CreateConnection(const Candidate& remote_candidate,
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
index 78bf65a..9de0897 100644
--- a/talk/p2p/base/relayport.cc
+++ b/talk/p2p/base/relayport.cc
@@ -244,7 +244,8 @@
       // This is due to as mapped address stun attribute is used for allocated
       // address.
       AddAddress(iter->address, iter->address, rtc::SocketAddress(),
-                 proto_name, RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY, false);
+                 proto_name, "", RELAY_PORT_TYPE,
+                 ICE_TYPE_PREFERENCE_RELAY, 0, false);
     }
     ready_ = true;
     SignalPortComplete(this);
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
index 57c7850..2ed0fa7 100644
--- a/talk/p2p/base/stunport.cc
+++ b/talk/p2p/base/stunport.cc
@@ -289,8 +289,8 @@
 void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
                                   const rtc::SocketAddress& address) {
   AddAddress(address, address, rtc::SocketAddress(),
-             UDP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
-             ICE_TYPE_PREFERENCE_HOST, false);
+             UDP_PROTOCOL_NAME, "", LOCAL_PORT_TYPE,
+             ICE_TYPE_PREFERENCE_HOST, 0, false);
   MaybePrepareStunCandidate();
 }
 
@@ -394,8 +394,8 @@
     // address then discarding the stun address.
     // For STUN related address is local socket address.
     AddAddress(stun_reflected_addr, socket_->GetLocalAddress(),
-               socket_->GetLocalAddress(), UDP_PROTOCOL_NAME,
-               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, false);
+               socket_->GetLocalAddress(), UDP_PROTOCOL_NAME, "",
+               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, false);
   }
   MaybeSetPortCompleteOrError();
 }
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
index f6d9ae6..74d566d 100644
--- a/talk/p2p/base/tcpport.cc
+++ b/talk/p2p/base/tcpport.cc
@@ -81,6 +81,13 @@
     return NULL;
   }
 
+  if (address.tcptype() == TCPTYPE_ACTIVE_STR ||
+      (address.tcptype().empty() && address.address().port() == 0)) {
+    // It's active only candidate, we should not try to create connections
+    // for these candidates.
+    return NULL;
+  }
+
   // We can't accept TCP connections incoming on other ports
   if (origin == ORIGIN_OTHER_PORT)
     return NULL;
@@ -115,23 +122,23 @@
   if (socket_) {
     // If socket isn't bound yet the address will be added in
     // OnAddressReady(). Socket may be in the CLOSED state if Listen()
-    // failed, we still want ot add the socket address.
+    // failed, we still want to add the socket address.
     LOG(LS_VERBOSE) << "Preparing TCP address, current state: "
                     << socket_->GetState();
     if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND ||
         socket_->GetState() == rtc::AsyncPacketSocket::STATE_CLOSED)
       AddAddress(socket_->GetLocalAddress(), socket_->GetLocalAddress(),
                  rtc::SocketAddress(),
-                 TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
-                 ICE_TYPE_PREFERENCE_HOST_TCP, true);
+                 TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE,
+                 ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
   } else {
     LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions.";
     // Note: We still add the address, since otherwise the remote side won't
     // recognize our incoming TCP connections.
     AddAddress(rtc::SocketAddress(ip(), 0),
                rtc::SocketAddress(ip(), 0), rtc::SocketAddress(),
-               TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP,
-               true);
+               TCP_PROTOCOL_NAME, TCPTYPE_ACTIVE_STR, LOCAL_PORT_TYPE,
+               ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
   }
 }
 
@@ -223,9 +230,9 @@
 
 void TCPPort::OnAddressReady(rtc::AsyncPacketSocket* socket,
                              const rtc::SocketAddress& address) {
-  AddAddress(address, address, rtc::SocketAddress(), "tcp",
-             LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP,
-             true);
+  AddAddress(address, address, rtc::SocketAddress(),
+             TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE,
+             ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
 }
 
 TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate,
diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc
index 825142a..6ca3628 100644
--- a/talk/p2p/base/transport.cc
+++ b/talk/p2p/base/transport.cc
@@ -34,6 +34,7 @@
 #include "talk/p2p/base/constants.h"
 #include "talk/p2p/base/sessionmanager.h"
 #include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/port.h"
 #include "talk/p2p/base/transportchannelimpl.h"
 #include "talk/xmllite/xmlelement.h"
 #include "talk/xmpp/constants.h"
@@ -438,11 +439,12 @@
 
   // Disallow all ports below 1024, except for 80 and 443 on public addresses.
   int port = cand.address().port();
-  if (port == 0) {
+  if (cand.protocol() == TCP_PROTOCOL_NAME &&
+      (cand.tcptype() == TCPTYPE_ACTIVE_STR || port == 0)) {
     // Expected for active-only candidates per
     // http://tools.ietf.org/html/rfc6544#section-4.5 so no error.
-    *error = "";
-    return false;
+    // Libjingle clients emit port 0, in "active" mode.
+    return true;
   }
   if (port < 1024) {
     if ((port != 80) && (port != 443)) {
diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc
index 7255a2b..3fa6829 100644
--- a/talk/p2p/base/turnport.cc
+++ b/talk/p2p/base/turnport.cc
@@ -526,6 +526,7 @@
              address,  // Base address.
              stun_address,  // Related address.
              UDP_PROTOCOL_NAME,
+             "",  // TCP canddiate type, empty for turn candidates.
              RELAY_PORT_TYPE,
              GetRelayPreference(server_address_.proto, server_address_.secure),
              server_priority_,
diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc
index d1a6525..51bdfef 100644
--- a/talk/p2p/client/connectivitychecker_unittest.cc
+++ b/talk/p2p/client/connectivitychecker_unittest.cc
@@ -73,8 +73,8 @@
 
   // Just set external address and signal that we are done.
   virtual void PrepareAddress() {
-    AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp",
-               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, true);
+    AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp", "",
+               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, true);
     SignalPortComplete(this);
   }
 };