summaryrefslogtreecommitdiff
path: root/p2p/base
diff options
context:
space:
mode:
Diffstat (limited to 'p2p/base')
-rw-r--r--p2p/base/asyncstuntcpsocket.cc153
-rw-r--r--p2p/base/asyncstuntcpsocket.h50
-rw-r--r--p2p/base/asyncstuntcpsocket_unittest.cc263
-rw-r--r--p2p/base/basicpacketsocketfactory.cc204
-rw-r--r--p2p/base/basicpacketsocketfactory.h51
-rw-r--r--p2p/base/candidate.h212
-rw-r--r--p2p/base/common.h20
-rw-r--r--p2p/base/constants.cc257
-rw-r--r--p2p/base/constants.h259
-rw-r--r--p2p/base/dtlstransport.h240
-rw-r--r--p2p/base/dtlstransportchannel.cc623
-rw-r--r--p2p/base/dtlstransportchannel.h246
-rw-r--r--p2p/base/dtlstransportchannel_unittest.cc823
-rw-r--r--p2p/base/fakesession.h492
-rw-r--r--p2p/base/p2ptransport.cc246
-rw-r--r--p2p/base/p2ptransport.h86
-rw-r--r--p2p/base/p2ptransportchannel.cc1274
-rw-r--r--p2p/base/p2ptransportchannel.h242
-rw-r--r--p2p/base/p2ptransportchannel_unittest.cc1702
-rw-r--r--p2p/base/packetsocketfactory.h52
-rw-r--r--p2p/base/parsing.cc141
-rw-r--r--p2p/base/parsing.h140
-rw-r--r--p2p/base/port.cc1430
-rw-r--r--p2p/base/port.h602
-rw-r--r--p2p/base/port_unittest.cc2494
-rw-r--r--p2p/base/portallocator.cc92
-rw-r--r--p2p/base/portallocator.h192
-rw-r--r--p2p/base/portallocatorsessionproxy.cc222
-rw-r--r--p2p/base/portallocatorsessionproxy.h106
-rw-r--r--p2p/base/portallocatorsessionproxy_unittest.cc146
-rw-r--r--p2p/base/portinterface.h126
-rw-r--r--p2p/base/portproxy.cc163
-rw-r--r--p2p/base/portproxy.h87
-rw-r--r--p2p/base/pseudotcp.cc1274
-rw-r--r--p2p/base/pseudotcp.h241
-rw-r--r--p2p/base/pseudotcp_unittest.cc841
-rw-r--r--p2p/base/rawtransport.cc115
-rw-r--r--p2p/base/rawtransport.h64
-rw-r--r--p2p/base/rawtransportchannel.cc260
-rw-r--r--p2p/base/rawtransportchannel.h189
-rw-r--r--p2p/base/relayport.cc818
-rw-r--r--p2p/base/relayport.h101
-rw-r--r--p2p/base/relayport_unittest.cc272
-rw-r--r--p2p/base/relayserver.cc746
-rw-r--r--p2p/base/relayserver.h235
-rw-r--r--p2p/base/relayserver_unittest.cc519
-rw-r--r--p2p/base/session.cc1760
-rw-r--r--p2p/base/session.h730
-rw-r--r--p2p/base/session_unittest.cc2430
-rw-r--r--p2p/base/sessionclient.h78
-rw-r--r--p2p/base/sessiondescription.cc222
-rw-r--r--p2p/base/sessiondescription.h185
-rw-r--r--p2p/base/sessionid.h20
-rw-r--r--p2p/base/sessionmanager.cc309
-rw-r--r--p2p/base/sessionmanager.h194
-rw-r--r--p2p/base/sessionmessages.cc1132
-rw-r--r--p2p/base/sessionmessages.h226
-rw-r--r--p2p/base/stun.cc915
-rw-r--r--p2p/base/stun.h632
-rw-r--r--p2p/base/stun_unittest.cc1402
-rw-r--r--p2p/base/stunport.cc451
-rw-r--r--p2p/base/stunport.h238
-rw-r--r--p2p/base/stunport_unittest.cc283
-rw-r--r--p2p/base/stunrequest.cc193
-rw-r--r--p2p/base/stunrequest.h116
-rw-r--r--p2p/base/stunrequest_unittest.cc203
-rw-r--r--p2p/base/stunserver.cc99
-rw-r--r--p2p/base/stunserver.h66
-rw-r--r--p2p/base/stunserver_unittest.cc109
-rw-r--r--p2p/base/tcpport.cc321
-rw-r--r--p2p/base/tcpport.h136
-rw-r--r--p2p/base/testrelayserver.h101
-rw-r--r--p2p/base/teststunserver.h58
-rw-r--r--p2p/base/testturnserver.h103
-rw-r--r--p2p/base/transport.cc960
-rw-r--r--p2p/base/transport.h513
-rw-r--r--p2p/base/transport_unittest.cc438
-rw-r--r--p2p/base/transportchannel.cc43
-rw-r--r--p2p/base/transportchannel.h143
-rw-r--r--p2p/base/transportchannelimpl.h111
-rw-r--r--p2p/base/transportchannelproxy.cc249
-rw-r--r--p2p/base/transportchannelproxy.h95
-rw-r--r--p2p/base/transportdescription.cc55
-rw-r--r--p2p/base/transportdescription.h171
-rw-r--r--p2p/base/transportdescriptionfactory.cc160
-rw-r--r--p2p/base/transportdescriptionfactory.h66
-rw-r--r--p2p/base/transportdescriptionfactory_unittest.cc365
-rw-r--r--p2p/base/transportinfo.h43
-rw-r--r--p2p/base/turnport.cc1196
-rw-r--r--p2p/base/turnport.h237
-rw-r--r--p2p/base/turnport_unittest.cc668
-rw-r--r--p2p/base/turnserver.cc1011
-rw-r--r--p2p/base/turnserver.h190
-rw-r--r--p2p/base/udpport.h17
94 files changed, 38554 insertions, 0 deletions
diff --git a/p2p/base/asyncstuntcpsocket.cc b/p2p/base/asyncstuntcpsocket.cc
new file mode 100644
index 00000000..2b1b6935
--- /dev/null
+++ b/p2p/base/asyncstuntcpsocket.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2013 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/asyncstuntcpsocket.h"
+
+#include <string.h>
+
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+static const size_t kMaxPacketSize = 64 * 1024;
+
+typedef uint16 PacketLength;
+static const size_t kPacketLenSize = sizeof(PacketLength);
+static const size_t kPacketLenOffset = 2;
+static const size_t kBufSize = kMaxPacketSize + kStunHeaderSize;
+static const size_t kTurnChannelDataHdrSize = 4;
+
+inline bool IsStunMessage(uint16 msg_type) {
+ // The first two bits of a channel data message are 0b01.
+ return (msg_type & 0xC000) ? false : true;
+}
+
+// AsyncStunTCPSocket
+// Binds and connects |socket| and creates AsyncTCPSocket for
+// it. Takes ownership of |socket|. Returns NULL if bind() or
+// connect() fail (|socket| is destroyed in that case).
+AsyncStunTCPSocket* AsyncStunTCPSocket::Create(
+ rtc::AsyncSocket* socket,
+ const rtc::SocketAddress& bind_address,
+ const rtc::SocketAddress& remote_address) {
+ return new AsyncStunTCPSocket(AsyncTCPSocketBase::ConnectSocket(
+ socket, bind_address, remote_address), false);
+}
+
+AsyncStunTCPSocket::AsyncStunTCPSocket(
+ rtc::AsyncSocket* socket, bool listen)
+ : rtc::AsyncTCPSocketBase(socket, listen, kBufSize) {
+}
+
+int AsyncStunTCPSocket::Send(const void *pv, size_t cb,
+ const rtc::PacketOptions& options) {
+ if (cb > kBufSize || cb < kPacketLenSize + kPacketLenOffset) {
+ SetError(EMSGSIZE);
+ return -1;
+ }
+
+ // If we are blocking on send, then silently drop this packet
+ if (!IsOutBufferEmpty())
+ return static_cast<int>(cb);
+
+ int pad_bytes;
+ size_t expected_pkt_len = GetExpectedLength(pv, cb, &pad_bytes);
+
+ // Accepts only complete STUN/ChannelData packets.
+ if (cb != expected_pkt_len)
+ return -1;
+
+ AppendToOutBuffer(pv, cb);
+
+ ASSERT(pad_bytes < 4);
+ char padding[4] = {0};
+ AppendToOutBuffer(padding, pad_bytes);
+
+ int res = FlushOutBuffer();
+ if (res <= 0) {
+ // drop packet if we made no progress
+ ClearOutBuffer();
+ return res;
+ }
+
+ // We claim to have sent the whole thing, even if we only sent partial
+ return static_cast<int>(cb);
+}
+
+void AsyncStunTCPSocket::ProcessInput(char* data, size_t* len) {
+ rtc::SocketAddress remote_addr(GetRemoteAddress());
+ // STUN packet - First 4 bytes. Total header size is 20 bytes.
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // |0 0| STUN Message Type | Message Length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // TURN ChannelData
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Channel Number | Length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ while (true) {
+ // We need at least 4 bytes to read the STUN or ChannelData packet length.
+ if (*len < kPacketLenOffset + kPacketLenSize)
+ return;
+
+ int pad_bytes;
+ size_t expected_pkt_len = GetExpectedLength(data, *len, &pad_bytes);
+ size_t actual_length = expected_pkt_len + pad_bytes;
+
+ if (*len < actual_length) {
+ return;
+ }
+
+ SignalReadPacket(this, data, expected_pkt_len, remote_addr,
+ rtc::CreatePacketTime(0));
+
+ *len -= actual_length;
+ if (*len > 0) {
+ memmove(data, data + actual_length, *len);
+ }
+ }
+}
+
+void AsyncStunTCPSocket::HandleIncomingConnection(
+ rtc::AsyncSocket* socket) {
+ SignalNewConnection(this, new AsyncStunTCPSocket(socket, false));
+}
+
+size_t AsyncStunTCPSocket::GetExpectedLength(const void* data, size_t len,
+ int* pad_bytes) {
+ *pad_bytes = 0;
+ PacketLength pkt_len =
+ rtc::GetBE16(static_cast<const char*>(data) + kPacketLenOffset);
+ size_t expected_pkt_len;
+ uint16 msg_type = rtc::GetBE16(data);
+ if (IsStunMessage(msg_type)) {
+ // STUN message.
+ expected_pkt_len = kStunHeaderSize + pkt_len;
+ } else {
+ // TURN ChannelData message.
+ expected_pkt_len = kTurnChannelDataHdrSize + pkt_len;
+ // From RFC 5766 section 11.5
+ // Over TCP and TLS-over-TCP, the ChannelData message MUST be padded to
+ // a multiple of four bytes in order to ensure the alignment of
+ // subsequent messages. The padding is not reflected in the length
+ // field of the ChannelData message, so the actual size of a ChannelData
+ // message (including padding) is (4 + Length) rounded up to the nearest
+ // multiple of 4. Over UDP, the padding is not required but MAY be
+ // included.
+ if (expected_pkt_len % 4)
+ *pad_bytes = 4 - (expected_pkt_len % 4);
+ }
+ return expected_pkt_len;
+}
+
+} // namespace cricket
diff --git a/p2p/base/asyncstuntcpsocket.h b/p2p/base/asyncstuntcpsocket.h
new file mode 100644
index 00000000..4f53b031
--- /dev/null
+++ b/p2p/base/asyncstuntcpsocket.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_
+#define WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_
+
+#include "webrtc/base/asynctcpsocket.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketfactory.h"
+
+namespace cricket {
+
+class AsyncStunTCPSocket : public rtc::AsyncTCPSocketBase {
+ public:
+ // Binds and connects |socket| and creates AsyncTCPSocket for
+ // it. Takes ownership of |socket|. Returns NULL if bind() or
+ // connect() fail (|socket| is destroyed in that case).
+ static AsyncStunTCPSocket* Create(
+ rtc::AsyncSocket* socket,
+ const rtc::SocketAddress& bind_address,
+ const rtc::SocketAddress& remote_address);
+
+ AsyncStunTCPSocket(rtc::AsyncSocket* socket, bool listen);
+ virtual ~AsyncStunTCPSocket() {}
+
+ virtual int Send(const void* pv, size_t cb,
+ const rtc::PacketOptions& options);
+ virtual void ProcessInput(char* data, size_t* len);
+ virtual void HandleIncomingConnection(rtc::AsyncSocket* socket);
+
+ private:
+ // This method returns the message hdr + length written in the header.
+ // This method also returns the number of padding bytes needed/added to the
+ // turn message. |pad_bytes| should be used only when |is_turn| is true.
+ size_t GetExpectedLength(const void* data, size_t len,
+ int* pad_bytes);
+
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncStunTCPSocket);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_
diff --git a/p2p/base/asyncstuntcpsocket_unittest.cc b/p2p/base/asyncstuntcpsocket_unittest.cc
new file mode 100644
index 00000000..22c1b269
--- /dev/null
+++ b/p2p/base/asyncstuntcpsocket_unittest.cc
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2013 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/asyncstuntcpsocket.h"
+#include "webrtc/base/asyncsocket.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+namespace cricket {
+
+static unsigned char kStunMessageWithZeroLength[] = {
+ 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes)
+ 0x21, 0x12, 0xA4, 0x42,
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+};
+
+
+static unsigned char kTurnChannelDataMessageWithZeroLength[] = {
+ 0x40, 0x00, 0x00, 0x00, // length of 0 (last 2 bytes)
+};
+
+static unsigned char kTurnChannelDataMessage[] = {
+ 0x40, 0x00, 0x00, 0x10,
+ 0x21, 0x12, 0xA4, 0x42,
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+};
+
+static unsigned char kStunMessageWithInvalidLength[] = {
+ 0x00, 0x01, 0x00, 0x10,
+ 0x21, 0x12, 0xA4, 0x42,
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithInvalidLength[] = {
+ 0x80, 0x00, 0x00, 0x20,
+ 0x21, 0x12, 0xA4, 0x42,
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithOddLength[] = {
+ 0x40, 0x00, 0x00, 0x05,
+ 0x21, 0x12, 0xA4, 0x42,
+ '0',
+};
+
+
+static const rtc::SocketAddress kClientAddr("11.11.11.11", 0);
+static const rtc::SocketAddress kServerAddr("22.22.22.22", 0);
+
+class AsyncStunTCPSocketTest : public testing::Test,
+ public sigslot::has_slots<> {
+ protected:
+ AsyncStunTCPSocketTest()
+ : vss_(new rtc::VirtualSocketServer(NULL)),
+ ss_scope_(vss_.get()) {
+ }
+
+ virtual void SetUp() {
+ CreateSockets();
+ }
+
+ void CreateSockets() {
+ rtc::AsyncSocket* server = vss_->CreateAsyncSocket(
+ kServerAddr.family(), SOCK_STREAM);
+ server->Bind(kServerAddr);
+ recv_socket_.reset(new AsyncStunTCPSocket(server, true));
+ recv_socket_->SignalNewConnection.connect(
+ this, &AsyncStunTCPSocketTest::OnNewConnection);
+
+ rtc::AsyncSocket* client = vss_->CreateAsyncSocket(
+ kClientAddr.family(), SOCK_STREAM);
+ send_socket_.reset(AsyncStunTCPSocket::Create(
+ client, kClientAddr, recv_socket_->GetLocalAddress()));
+ ASSERT_TRUE(send_socket_.get() != NULL);
+ vss_->ProcessMessagesUntilIdle();
+ }
+
+ void OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data,
+ size_t len, const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ recv_packets_.push_back(std::string(data, len));
+ }
+
+ void OnNewConnection(rtc::AsyncPacketSocket* server,
+ rtc::AsyncPacketSocket* new_socket) {
+ listen_socket_.reset(new_socket);
+ new_socket->SignalReadPacket.connect(
+ this, &AsyncStunTCPSocketTest::OnReadPacket);
+ }
+
+ bool Send(const void* data, size_t len) {
+ rtc::PacketOptions options;
+ size_t ret = send_socket_->Send(
+ reinterpret_cast<const char*>(data), len, options);
+ vss_->ProcessMessagesUntilIdle();
+ return (ret == len);
+ }
+
+ bool CheckData(const void* data, int len) {
+ bool ret = false;
+ if (recv_packets_.size()) {
+ std::string packet = recv_packets_.front();
+ recv_packets_.pop_front();
+ ret = (memcmp(data, packet.c_str(), len) == 0);
+ }
+ return ret;
+ }
+
+ rtc::scoped_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::scoped_ptr<AsyncStunTCPSocket> send_socket_;
+ rtc::scoped_ptr<AsyncStunTCPSocket> recv_socket_;
+ rtc::scoped_ptr<rtc::AsyncPacketSocket> listen_socket_;
+ std::list<std::string> recv_packets_;
+};
+
+// Testing a stun packet sent/recv properly.
+TEST_F(AsyncStunTCPSocketTest, TestSingleStunPacket) {
+ EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+}
+
+// Verify sending multiple packets.
+TEST_F(AsyncStunTCPSocketTest, TestMultipleStunPackets) {
+ EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(4u, recv_packets_.size());
+}
+
+// Verifying TURN channel data message with zero length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithZeroLength) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessageWithZeroLength,
+ sizeof(kTurnChannelDataMessageWithZeroLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithZeroLength,
+ sizeof(kTurnChannelDataMessageWithZeroLength)));
+}
+
+// Verifying TURN channel data message.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelData) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessage,
+ sizeof(kTurnChannelDataMessage)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessage,
+ sizeof(kTurnChannelDataMessage)));
+}
+
+// Verifying TURN channel messages which needs padding handled properly.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataPadding) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+// Verifying stun message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestStunInvalidLength) {
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+ EXPECT_EQ(0u, recv_packets_.size());
+
+ // Modify the message length to larger value.
+ kStunMessageWithInvalidLength[2] = 0xFF;
+ kStunMessageWithInvalidLength[3] = 0xFF;
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+
+ // Modify the message length to smaller value.
+ kStunMessageWithInvalidLength[2] = 0x00;
+ kStunMessageWithInvalidLength[3] = 0x01;
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+}
+
+// Verifying TURN channel data message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithInvalidLength) {
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+ // Modify the length to larger value.
+ kTurnChannelDataMessageWithInvalidLength[2] = 0xFF;
+ kTurnChannelDataMessageWithInvalidLength[3] = 0xF0;
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+
+ // Modify the length to smaller value.
+ kTurnChannelDataMessageWithInvalidLength[2] = 0x00;
+ kTurnChannelDataMessageWithInvalidLength[3] = 0x00;
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+}
+
+// Verifying a small buffer handled (dropped) properly. This will be
+// a common one for both stun and turn.
+TEST_F(AsyncStunTCPSocketTest, TestTooSmallMessageBuffer) {
+ char data[1];
+ EXPECT_FALSE(Send(data, sizeof(data)));
+}
+
+// Verifying a legal large turn message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeTurnPacket) {
+ // We have problem in getting the SignalWriteEvent from the virtual socket
+ // server. So increasing the send buffer to 64k.
+ // TODO(mallinath) - Remove this setting after we fix vss issue.
+ vss_->set_send_buffer_capacity(64 * 1024);
+ unsigned char packet[65539];
+ packet[0] = 0x40;
+ packet[1] = 0x00;
+ packet[2] = 0xFF;
+ packet[3] = 0xFF;
+ EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Verifying a legal large stun message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeStunPacket) {
+ // We have problem in getting the SignalWriteEvent from the virtual socket
+ // server. So increasing the send buffer to 64k.
+ // TODO(mallinath) - Remove this setting after we fix vss issue.
+ vss_->set_send_buffer_capacity(64 * 1024);
+ unsigned char packet[65552];
+ packet[0] = 0x00;
+ packet[1] = 0x01;
+ packet[2] = 0xFF;
+ packet[3] = 0xFC;
+ EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Investigate why WriteEvent is not signaled from VSS.
+TEST_F(AsyncStunTCPSocketTest, DISABLED_TestWithSmallSendBuffer) {
+ vss_->set_send_buffer_capacity(1);
+ Send(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+} // namespace cricket
diff --git a/p2p/base/basicpacketsocketfactory.cc b/p2p/base/basicpacketsocketfactory.cc
new file mode 100644
index 00000000..06dfe76e
--- /dev/null
+++ b/p2p/base/basicpacketsocketfactory.cc
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+
+#include "webrtc/p2p/base/asyncstuntcpsocket.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/asynctcpsocket.h"
+#include "webrtc/base/asyncudpsocket.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/nethelpers.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketadapters.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/thread.h"
+
+namespace rtc {
+
+BasicPacketSocketFactory::BasicPacketSocketFactory()
+ : thread_(Thread::Current()),
+ socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(Thread* thread)
+ : thread_(thread),
+ socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+ SocketFactory* socket_factory)
+ : thread_(NULL),
+ socket_factory_(socket_factory) {
+}
+
+BasicPacketSocketFactory::~BasicPacketSocketFactory() {
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) {
+ // UDP sockets are simple.
+ rtc::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(
+ address.family(), SOCK_DGRAM);
+ if (!socket) {
+ return NULL;
+ }
+ if (BindSocket(socket, address, min_port, max_port) < 0) {
+ LOG(LS_ERROR) << "UDP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+ return new rtc::AsyncUDPSocket(socket);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port, int opts) {
+
+ // Fail if TLS is required.
+ if (opts & PacketSocketFactory::OPT_TLS) {
+ LOG(LS_ERROR) << "TLS support currently is not available.";
+ return NULL;
+ }
+
+ rtc::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(local_address.family(),
+ SOCK_STREAM);
+ if (!socket) {
+ return NULL;
+ }
+
+ if (BindSocket(socket, local_address, min_port, max_port) < 0) {
+ LOG(LS_ERROR) << "TCP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+ if (opts & PacketSocketFactory::OPT_SSLTCP) {
+ ASSERT(!(opts & PacketSocketFactory::OPT_TLS));
+ socket = new rtc::AsyncSSLSocket(socket);
+ }
+
+ // Set TCP_NODELAY (via OPT_NODELAY) for improved performance.
+ // See http://go/gtalktcpnodelayexperiment
+ socket->SetOption(rtc::Socket::OPT_NODELAY, 1);
+
+ if (opts & PacketSocketFactory::OPT_STUN)
+ return new cricket::AsyncStunTCPSocket(socket, true);
+
+ return new rtc::AsyncTCPSocket(socket, true);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, int opts) {
+
+ rtc::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(local_address.family(), SOCK_STREAM);
+ if (!socket) {
+ return NULL;
+ }
+
+ if (BindSocket(socket, local_address, 0, 0) < 0) {
+ LOG(LS_ERROR) << "TCP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // If using a proxy, wrap the socket in a proxy socket.
+ if (proxy_info.type == rtc::PROXY_SOCKS5) {
+ socket = new rtc::AsyncSocksProxySocket(
+ socket, proxy_info.address, proxy_info.username, proxy_info.password);
+ } else if (proxy_info.type == rtc::PROXY_HTTPS) {
+ socket = new rtc::AsyncHttpsProxySocket(
+ socket, user_agent, proxy_info.address,
+ proxy_info.username, proxy_info.password);
+ }
+
+ // If using TLS, wrap the socket in an SSL adapter.
+ if (opts & PacketSocketFactory::OPT_TLS) {
+ ASSERT(!(opts & PacketSocketFactory::OPT_SSLTCP));
+
+ rtc::SSLAdapter* ssl_adapter = rtc::SSLAdapter::Create(socket);
+ if (!ssl_adapter) {
+ return NULL;
+ }
+
+ socket = ssl_adapter;
+
+ if (ssl_adapter->StartSSL(remote_address.hostname().c_str(), false) != 0) {
+ delete ssl_adapter;
+ return NULL;
+ }
+
+ // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+ } else if (opts & PacketSocketFactory::OPT_SSLTCP) {
+ ASSERT(!(opts & PacketSocketFactory::OPT_TLS));
+ socket = new rtc::AsyncSSLSocket(socket);
+ }
+
+ if (socket->Connect(remote_address) < 0) {
+ LOG(LS_ERROR) << "TCP connect failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // Finally, wrap that socket in a TCP or STUN TCP packet socket.
+ AsyncPacketSocket* tcp_socket;
+ if (opts & PacketSocketFactory::OPT_STUN) {
+ tcp_socket = new cricket::AsyncStunTCPSocket(socket, false);
+ } else {
+ tcp_socket = new rtc::AsyncTCPSocket(socket, false);
+ }
+
+ // Set TCP_NODELAY (via OPT_NODELAY) for improved performance.
+ // See http://go/gtalktcpnodelayexperiment
+ tcp_socket->SetOption(rtc::Socket::OPT_NODELAY, 1);
+
+ return tcp_socket;
+}
+
+AsyncResolverInterface* BasicPacketSocketFactory::CreateAsyncResolver() {
+ return new rtc::AsyncResolver();
+}
+
+int BasicPacketSocketFactory::BindSocket(
+ AsyncSocket* socket, const SocketAddress& local_address,
+ int min_port, int max_port) {
+ int ret = -1;
+ if (min_port == 0 && max_port == 0) {
+ // If there's no port range, let the OS pick a port for us.
+ ret = socket->Bind(local_address);
+ } else {
+ // Otherwise, try to find a port in the provided range.
+ for (int port = min_port; ret < 0 && port <= max_port; ++port) {
+ ret = socket->Bind(rtc::SocketAddress(local_address.ipaddr(),
+ port));
+ }
+ }
+ return ret;
+}
+
+SocketFactory* BasicPacketSocketFactory::socket_factory() {
+ if (thread_) {
+ ASSERT(thread_ == Thread::Current());
+ return thread_->socketserver();
+ } else {
+ return socket_factory_;
+ }
+}
+
+} // namespace rtc
diff --git a/p2p/base/basicpacketsocketfactory.h b/p2p/base/basicpacketsocketfactory.h
new file mode 100644
index 00000000..fb3a5269
--- /dev/null
+++ b/p2p/base/basicpacketsocketfactory.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_
+#define WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_
+
+#include "webrtc/p2p/base/packetsocketfactory.h"
+
+namespace rtc {
+
+class AsyncSocket;
+class SocketFactory;
+class Thread;
+
+class BasicPacketSocketFactory : public PacketSocketFactory {
+ public:
+ BasicPacketSocketFactory();
+ explicit BasicPacketSocketFactory(Thread* thread);
+ explicit BasicPacketSocketFactory(SocketFactory* socket_factory);
+ virtual ~BasicPacketSocketFactory();
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& local_address, int min_port, int max_port);
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port, int opts);
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, int opts);
+
+ virtual AsyncResolverInterface* CreateAsyncResolver();
+
+ private:
+ int BindSocket(AsyncSocket* socket, const SocketAddress& local_address,
+ int min_port, int max_port);
+
+ SocketFactory* socket_factory();
+
+ Thread* thread_;
+ SocketFactory* socket_factory_;
+};
+
+} // namespace rtc
+
+#endif // WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_
diff --git a/p2p/base/candidate.h b/p2p/base/candidate.h
new file mode 100644
index 00000000..72bc69ef
--- /dev/null
+++ b/p2p/base/candidate.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_CANDIDATE_H_
+#define WEBRTC_P2P_BASE_CANDIDATE_H_
+
+#include <limits.h>
+#include <math.h>
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace cricket {
+
+// Candidate for ICE based connection discovery.
+
+class Candidate {
+ public:
+ // TODO: Match the ordering and param list as per RFC 5245
+ // candidate-attribute syntax. http://tools.ietf.org/html/rfc5245#section-15.1
+ Candidate() : component_(0), priority_(0), generation_(0) {}
+ Candidate(const std::string& id, int component, const std::string& protocol,
+ const rtc::SocketAddress& address, uint32 priority,
+ const std::string& username, const std::string& password,
+ const std::string& type, const std::string& network_name,
+ uint32 generation, const std::string& foundation)
+ : id_(id), component_(component), protocol_(protocol), address_(address),
+ priority_(priority), username_(username), password_(password),
+ type_(type), network_name_(network_name), generation_(generation),
+ foundation_(foundation) {
+ }
+
+ const std::string & id() const { return id_; }
+ void set_id(const std::string & id) { id_ = id; }
+
+ int component() const { return component_; }
+ void set_component(int component) { component_ = component; }
+
+ const std::string & protocol() const { return protocol_; }
+ void set_protocol(const std::string & protocol) { protocol_ = protocol; }
+
+ const rtc::SocketAddress & address() const { return address_; }
+ void set_address(const rtc::SocketAddress & address) {
+ address_ = address;
+ }
+
+ uint32 priority() const { return priority_; }
+ void set_priority(const uint32 priority) { priority_ = priority; }
+
+// void set_type_preference(uint32 type_preference) {
+// priority_ = GetPriority(type_preference);
+// }
+
+ // Maps old preference (which was 0.0-1.0) to match priority (which
+ // is 0-2^32-1) to to match RFC 5245, section 4.1.2.1. Also see
+ // https://docs.google.com/a/google.com/document/d/
+ // 1iNQDiwDKMh0NQOrCqbj3DKKRT0Dn5_5UJYhmZO-t7Uc/edit
+ float preference() const {
+ // The preference value is clamped to two decimal precision.
+ return static_cast<float>(((priority_ >> 24) * 100 / 127) / 100.0);
+ }
+
+ void set_preference(float preference) {
+ // Limiting priority to UINT_MAX when value exceeds uint32 max.
+ // This can happen for e.g. when preference = 3.
+ uint64 prio_val = static_cast<uint64>(preference * 127) << 24;
+ priority_ = static_cast<uint32>(
+ rtc::_min(prio_val, static_cast<uint64>(UINT_MAX)));
+ }
+
+ const std::string & username() const { return username_; }
+ void set_username(const std::string & username) { username_ = username; }
+
+ const std::string & password() const { return password_; }
+ void set_password(const std::string & password) { password_ = password; }
+
+ const std::string & type() const { return type_; }
+ void set_type(const std::string & type) { type_ = type; }
+
+ const std::string & network_name() const { return network_name_; }
+ void set_network_name(const std::string & network_name) {
+ network_name_ = network_name;
+ }
+
+ // Candidates in a new generation replace those in the old generation.
+ uint32 generation() const { return generation_; }
+ void set_generation(uint32 generation) { generation_ = generation; }
+ const std::string generation_str() const {
+ std::ostringstream ost;
+ ost << generation_;
+ return ost.str();
+ }
+ void set_generation_str(const std::string& str) {
+ std::istringstream ist(str);
+ ist >> generation_;
+ }
+
+ const std::string& foundation() const {
+ return foundation_;
+ }
+
+ void set_foundation(const std::string& foundation) {
+ foundation_ = foundation;
+ }
+
+ const rtc::SocketAddress & related_address() const {
+ return related_address_;
+ }
+ void set_related_address(
+ 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 {
+ // We ignore the network name, since that is just debug information, and
+ // the priority, since that should be the same if the rest is (and it's
+ // a float so equality checking is always worrisome).
+ return (id_ == c.id_) &&
+ (component_ == c.component_) &&
+ (protocol_ == c.protocol_) &&
+ (address_ == c.address_) &&
+ (username_ == c.username_) &&
+ (password_ == c.password_) &&
+ (type_ == c.type_) &&
+ (generation_ == c.generation_) &&
+ (foundation_ == c.foundation_) &&
+ (related_address_ == c.related_address_);
+ }
+
+ std::string ToString() const {
+ return ToStringInternal(false);
+ }
+
+ std::string ToSensitiveString() const {
+ return ToStringInternal(true);
+ }
+
+ uint32 GetPriority(uint32 type_preference,
+ int network_adapter_preference,
+ int relay_preference) const {
+ // RFC 5245 - 4.1.2.1.
+ // priority = (2^24)*(type preference) +
+ // (2^8)*(local preference) +
+ // (2^0)*(256 - component ID)
+
+ // |local_preference| length is 2 bytes, 0-65535 inclusive.
+ // In our implemenation we will partion local_preference into
+ // 0 1
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | NIC Pref | Addr Pref |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // NIC Type - Type of the network adapter e.g. 3G/Wifi/Wired.
+ // Addr Pref - Address preference value as per RFC 3484.
+ // local preference = (NIC Type << 8 | Addr_Pref) - relay preference.
+
+ int addr_pref = IPAddressPrecedence(address_.ipaddr());
+ int local_preference = ((network_adapter_preference << 8) | addr_pref) +
+ relay_preference;
+
+ return (type_preference << 24) |
+ (local_preference << 8) |
+ (256 - component_);
+ }
+
+ private:
+ std::string ToStringInternal(bool sensitive) const {
+ std::ostringstream ost;
+ std::string address = sensitive ? address_.ToSensitiveString() :
+ address_.ToString();
+ ost << "Cand[" << foundation_ << ":" << component_ << ":"
+ << protocol_ << ":" << priority_ << ":"
+ << address << ":" << type_ << ":" << related_address_ << ":"
+ << username_ << ":" << password_ << "]";
+ return ost.str();
+ }
+
+ std::string id_;
+ int component_;
+ std::string protocol_;
+ rtc::SocketAddress address_;
+ uint32 priority_;
+ std::string username_;
+ std::string password_;
+ std::string type_;
+ std::string network_name_;
+ uint32 generation_;
+ std::string foundation_;
+ rtc::SocketAddress related_address_;
+ std::string tcptype_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_CANDIDATE_H_
diff --git a/p2p/base/common.h b/p2p/base/common.h
new file mode 100644
index 00000000..8a3178c8
--- /dev/null
+++ b/p2p/base/common.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_COMMON_H_
+#define WEBRTC_P2P_BASE_COMMON_H_
+
+#include "webrtc/base/logging.h"
+
+// Common log description format for jingle messages
+#define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": "
+#define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": "
+
+#endif // WEBRTC_P2P_BASE_COMMON_H_
diff --git a/p2p/base/constants.cc b/p2p/base/constants.cc
new file mode 100644
index 00000000..84c9ea26
--- /dev/null
+++ b/p2p/base/constants.cc
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/constants.h"
+
+#include <string>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+
+namespace cricket {
+
+const char NS_EMPTY[] = "";
+const char NS_JINGLE[] = "urn:xmpp:jingle:1";
+const char NS_JINGLE_DRAFT[] = "google:jingle";
+const char NS_GINGLE[] = "http://www.google.com/session";
+
+// actions (aka <session> or <jingle>)
+const buzz::StaticQName QN_ACTION = { NS_EMPTY, "action" };
+const char LN_INITIATOR[] = "initiator";
+const buzz::StaticQName QN_INITIATOR = { NS_EMPTY, LN_INITIATOR };
+const buzz::StaticQName QN_CREATOR = { NS_EMPTY, "creator" };
+
+const buzz::StaticQName QN_JINGLE = { NS_JINGLE, "jingle" };
+const buzz::StaticQName QN_JINGLE_CONTENT = { NS_JINGLE, "content" };
+const buzz::StaticQName QN_JINGLE_CONTENT_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA = { NS_EMPTY, "media" };
+const buzz::StaticQName QN_JINGLE_REASON = { NS_JINGLE, "reason" };
+const buzz::StaticQName QN_JINGLE_DRAFT_GROUP = { NS_JINGLE_DRAFT, "group" };
+const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE = { NS_EMPTY, "type" };
+const char JINGLE_CONTENT_MEDIA_AUDIO[] = "audio";
+const char JINGLE_CONTENT_MEDIA_VIDEO[] = "video";
+const char JINGLE_CONTENT_MEDIA_DATA[] = "data";
+const char JINGLE_ACTION_SESSION_INITIATE[] = "session-initiate";
+const char JINGLE_ACTION_SESSION_INFO[] = "session-info";
+const char JINGLE_ACTION_SESSION_ACCEPT[] = "session-accept";
+const char JINGLE_ACTION_SESSION_TERMINATE[] = "session-terminate";
+const char JINGLE_ACTION_TRANSPORT_INFO[] = "transport-info";
+const char JINGLE_ACTION_TRANSPORT_ACCEPT[] = "transport-accept";
+const char JINGLE_ACTION_DESCRIPTION_INFO[] = "description-info";
+
+const buzz::StaticQName QN_GINGLE_SESSION = { NS_GINGLE, "session" };
+const char GINGLE_ACTION_INITIATE[] = "initiate";
+const char GINGLE_ACTION_INFO[] = "info";
+const char GINGLE_ACTION_ACCEPT[] = "accept";
+const char GINGLE_ACTION_REJECT[] = "reject";
+const char GINGLE_ACTION_TERMINATE[] = "terminate";
+const char GINGLE_ACTION_CANDIDATES[] = "candidates";
+const char GINGLE_ACTION_UPDATE[] = "update";
+
+const char LN_ERROR[] = "error";
+const buzz::StaticQName QN_GINGLE_REDIRECT = { NS_GINGLE, "redirect" };
+const char STR_REDIRECT_PREFIX[] = "xmpp:";
+
+// Session Contents (aka Gingle <session><description>
+// or Jingle <content><description>)
+const char LN_DESCRIPTION[] = "description";
+const char LN_PAYLOADTYPE[] = "payload-type";
+const buzz::StaticQName QN_ID = { NS_EMPTY, "id" };
+const buzz::StaticQName QN_SID = { NS_EMPTY, "sid" };
+const buzz::StaticQName QN_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_CLOCKRATE = { NS_EMPTY, "clockrate" };
+const buzz::StaticQName QN_BITRATE = { NS_EMPTY, "bitrate" };
+const buzz::StaticQName QN_CHANNELS = { NS_EMPTY, "channels" };
+const buzz::StaticQName QN_WIDTH = { NS_EMPTY, "width" };
+const buzz::StaticQName QN_HEIGHT = { NS_EMPTY, "height" };
+const buzz::StaticQName QN_FRAMERATE = { NS_EMPTY, "framerate" };
+const char LN_NAME[] = "name";
+const char LN_VALUE[] = "value";
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME = { NS_EMPTY, LN_NAME };
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE = { NS_EMPTY, LN_VALUE };
+const char PAYLOADTYPE_PARAMETER_BITRATE[] = "bitrate";
+const char PAYLOADTYPE_PARAMETER_HEIGHT[] = "height";
+const char PAYLOADTYPE_PARAMETER_WIDTH[] = "width";
+const char PAYLOADTYPE_PARAMETER_FRAMERATE[] = "framerate";
+const char LN_BANDWIDTH[] = "bandwidth";
+
+const char CN_AUDIO[] = "audio";
+const char CN_VIDEO[] = "video";
+const char CN_DATA[] = "data";
+const char CN_OTHER[] = "main";
+// other SDP related strings
+const char GROUP_TYPE_BUNDLE[] = "BUNDLE";
+
+const char NS_JINGLE_RTP[] = "urn:xmpp:jingle:apps:rtp:1";
+const buzz::StaticQName QN_JINGLE_RTP_CONTENT =
+ { NS_JINGLE_RTP, LN_DESCRIPTION };
+const buzz::StaticQName QN_SSRC = { NS_EMPTY, "ssrc" };
+const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE =
+ { NS_JINGLE_RTP, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH =
+ { NS_JINGLE_RTP, LN_BANDWIDTH };
+const buzz::StaticQName QN_JINGLE_RTCP_MUX = { NS_JINGLE_RTP, "rtcp-mux" };
+const buzz::StaticQName QN_JINGLE_RTCP_FB = { NS_JINGLE_RTP, "rtcp-fb" };
+const buzz::StaticQName QN_SUBTYPE = { NS_EMPTY, "subtype" };
+const buzz::StaticQName QN_PARAMETER = { NS_JINGLE_RTP, "parameter" };
+const buzz::StaticQName QN_JINGLE_RTP_HDREXT =
+ { NS_JINGLE_RTP, "rtp-hdrext" };
+const buzz::StaticQName QN_URI = { NS_EMPTY, "uri" };
+
+const char NS_JINGLE_DRAFT_SCTP[] = "google:jingle:sctp";
+const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT =
+ { NS_JINGLE_DRAFT_SCTP, LN_DESCRIPTION };
+const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM =
+ { NS_JINGLE_DRAFT_SCTP, "stream" };
+
+const char NS_GINGLE_AUDIO[] = "http://www.google.com/session/phone";
+const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT =
+ { NS_GINGLE_AUDIO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE =
+ { NS_GINGLE_AUDIO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_AUDIO_SRCID = { NS_GINGLE_AUDIO, "src-id" };
+const char NS_GINGLE_VIDEO[] = "http://www.google.com/session/video";
+const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT =
+ { NS_GINGLE_VIDEO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE =
+ { NS_GINGLE_VIDEO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_VIDEO_SRCID = { NS_GINGLE_VIDEO, "src-id" };
+const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH =
+ { NS_GINGLE_VIDEO, LN_BANDWIDTH };
+
+// Crypto support.
+const buzz::StaticQName QN_ENCRYPTION = { NS_JINGLE_RTP, "encryption" };
+const buzz::StaticQName QN_ENCRYPTION_REQUIRED = { NS_EMPTY, "required" };
+const buzz::StaticQName QN_CRYPTO = { NS_JINGLE_RTP, "crypto" };
+const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE =
+ { NS_GINGLE_AUDIO, "usage" };
+const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE =
+ { NS_GINGLE_VIDEO, "usage" };
+const buzz::StaticQName QN_CRYPTO_SUITE = { NS_EMPTY, "crypto-suite" };
+const buzz::StaticQName QN_CRYPTO_KEY_PARAMS = { NS_EMPTY, "key-params" };
+const buzz::StaticQName QN_CRYPTO_TAG = { NS_EMPTY, "tag" };
+const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS =
+ { NS_EMPTY, "session-params" };
+
+// Transports and candidates.
+const char LN_TRANSPORT[] = "transport";
+const char LN_CANDIDATE[] = "candidate";
+const buzz::StaticQName QN_UFRAG = { cricket::NS_EMPTY, "ufrag" };
+const buzz::StaticQName QN_PWD = { cricket::NS_EMPTY, "pwd" };
+const buzz::StaticQName QN_COMPONENT = { cricket::NS_EMPTY, "component" };
+const buzz::StaticQName QN_IP = { cricket::NS_EMPTY, "ip" };
+const buzz::StaticQName QN_PORT = { cricket::NS_EMPTY, "port" };
+const buzz::StaticQName QN_NETWORK = { cricket::NS_EMPTY, "network" };
+const buzz::StaticQName QN_GENERATION = { cricket::NS_EMPTY, "generation" };
+const buzz::StaticQName QN_PRIORITY = { cricket::NS_EMPTY, "priority" };
+const buzz::StaticQName QN_PROTOCOL = { cricket::NS_EMPTY, "protocol" };
+const char ICE_CANDIDATE_TYPE_PEER_STUN[] = "prflx";
+const char ICE_CANDIDATE_TYPE_SERVER_STUN[] = "srflx";
+// Minimum ufrag length is 4 characters as per RFC5245. We chose 16 because
+// some internal systems expect username to be 16 bytes.
+const int ICE_UFRAG_LENGTH = 16;
+// Minimum password length of 22 characters as per RFC5245. We chose 24 because
+// some internal systems expect password to be multiple of 4.
+const int ICE_PWD_LENGTH = 24;
+const size_t ICE_UFRAG_MIN_LENGTH = 4;
+const size_t ICE_PWD_MIN_LENGTH = 22;
+const size_t ICE_UFRAG_MAX_LENGTH = 255;
+const size_t ICE_PWD_MAX_LENGTH = 256;
+// TODO: This is media-specific, so might belong
+// somewhere like media/base/constants.h
+const int ICE_CANDIDATE_COMPONENT_RTP = 1;
+const int ICE_CANDIDATE_COMPONENT_RTCP = 2;
+const int ICE_CANDIDATE_COMPONENT_DEFAULT = 1;
+
+const buzz::StaticQName QN_FINGERPRINT = { cricket::NS_EMPTY, "fingerprint" };
+const buzz::StaticQName QN_FINGERPRINT_ALGORITHM =
+ { cricket::NS_EMPTY, "algorithm" };
+const buzz::StaticQName QN_FINGERPRINT_DIGEST = { cricket::NS_EMPTY, "digest" };
+
+const char NS_JINGLE_ICE_UDP[] = "urn:xmpp:jingle:transports:ice-udp:1";
+
+const char ICE_OPTION_GICE[] = "google-ice";
+const char NS_GINGLE_P2P[] = "http://www.google.com/transport/p2p";
+const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT =
+ { NS_GINGLE_P2P, LN_TRANSPORT };
+const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE =
+ { NS_GINGLE_P2P, LN_CANDIDATE };
+const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME =
+ { NS_GINGLE_P2P, "unknown-channel-name" };
+const buzz::StaticQName QN_GINGLE_CANDIDATE = { NS_GINGLE, LN_CANDIDATE };
+const buzz::StaticQName QN_ADDRESS = { cricket::NS_EMPTY, "address" };
+const buzz::StaticQName QN_USERNAME = { cricket::NS_EMPTY, "username" };
+const buzz::StaticQName QN_PASSWORD = { cricket::NS_EMPTY, "password" };
+const buzz::StaticQName QN_PREFERENCE = { cricket::NS_EMPTY, "preference" };
+const char GICE_CHANNEL_NAME_RTP[] = "rtp";
+const char GICE_CHANNEL_NAME_RTCP[] = "rtcp";
+const char GICE_CHANNEL_NAME_VIDEO_RTP[] = "video_rtp";
+const char GICE_CHANNEL_NAME_VIDEO_RTCP[] = "video_rtcp";
+const char GICE_CHANNEL_NAME_DATA_RTP[] = "data_rtp";
+const char GICE_CHANNEL_NAME_DATA_RTCP[] = "data_rtcp";
+
+// terminate reasons and errors
+const char JINGLE_ERROR_BAD_REQUEST[] = "bad-request";
+const char JINGLE_ERROR_OUT_OF_ORDER[] = "out-of-order";
+const char JINGLE_ERROR_UNKNOWN_SESSION[] = "unknown-session";
+
+// Call terminate reasons from XEP-166
+const char STR_TERMINATE_DECLINE[] = "decline";
+const char STR_TERMINATE_SUCCESS[] = "success";
+const char STR_TERMINATE_ERROR[] = "general-error";
+const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[] = "incompatible-parameters";
+
+// Old terminate reasons used by cricket
+const char STR_TERMINATE_CALL_ENDED[] = "call-ended";
+const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[] = "recipient-unavailable";
+const char STR_TERMINATE_RECIPIENT_BUSY[] = "recipient-busy";
+const char STR_TERMINATE_INSUFFICIENT_FUNDS[] = "insufficient-funds";
+const char STR_TERMINATE_NUMBER_MALFORMED[] = "number-malformed";
+const char STR_TERMINATE_NUMBER_DISALLOWED[] = "number-disallowed";
+const char STR_TERMINATE_PROTOCOL_ERROR[] = "protocol-error";
+const char STR_TERMINATE_INTERNAL_SERVER_ERROR[] = "internal-server-error";
+const char STR_TERMINATE_UNKNOWN_ERROR[] = "unknown-error";
+
+// Draft view and notify messages.
+const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[] = "video";
+const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[] = "audio";
+const buzz::StaticQName QN_NICK = { cricket::NS_EMPTY, "nick" };
+const buzz::StaticQName QN_TYPE = { cricket::NS_EMPTY, "type" };
+const buzz::StaticQName QN_JINGLE_DRAFT_VIEW = { NS_JINGLE_DRAFT, "view" };
+const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[] = "none";
+const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[] = "static";
+const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS = { NS_JINGLE_DRAFT, "params" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS = { NS_JINGLE_DRAFT, "streams" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAM = { NS_JINGLE_DRAFT, "stream" };
+const buzz::StaticQName QN_DISPLAY = { cricket::NS_EMPTY, "display" };
+const buzz::StaticQName QN_CNAME = { cricket::NS_EMPTY, "cname" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC = { NS_JINGLE_DRAFT, "ssrc" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP =
+ { NS_JINGLE_DRAFT, "ssrc-group" };
+const buzz::StaticQName QN_SEMANTICS = { cricket::NS_EMPTY, "semantics" };
+const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY = { NS_JINGLE_DRAFT, "notify" };
+const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE = { NS_JINGLE_DRAFT, "source" };
+
+const char NS_GINGLE_RAW[] = "http://www.google.com/transport/raw-udp";
+const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT = { NS_GINGLE_RAW, "transport" };
+const buzz::StaticQName QN_GINGLE_RAW_CHANNEL = { NS_GINGLE_RAW, "channel" };
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const char NS_VOICEMAIL[] = "http://www.google.com/session/voicemail";
+const buzz::StaticQName QN_VOICEMAIL_REGARDING = { NS_VOICEMAIL, "regarding" };
+#endif
+
+// From RFC 4145, SDP setup attribute values.
+const char CONNECTIONROLE_ACTIVE_STR[] = "active";
+const char CONNECTIONROLE_PASSIVE_STR[] = "passive";
+const char CONNECTIONROLE_ACTPASS_STR[] = "actpass";
+const char CONNECTIONROLE_HOLDCONN_STR[] = "holdconn";
+
+} // namespace cricket
diff --git a/p2p/base/constants.h b/p2p/base/constants.h
new file mode 100644
index 00000000..57423f5b
--- /dev/null
+++ b/p2p/base/constants.h
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_CONSTANTS_H_
+#define WEBRTC_P2P_BASE_CONSTANTS_H_
+
+#include <string>
+#include "webrtc/libjingle/xmllite/qname.h"
+
+// This file contains constants related to signaling that are used in various
+// classes in this directory.
+
+namespace cricket {
+
+// NS_ == namespace
+// QN_ == buzz::QName (namespace + name)
+// LN_ == "local name" == QName::LocalPart()
+// these are useful when you need to find a tag
+// that has different namespaces (like <description> or <transport>)
+
+extern const char NS_EMPTY[];
+extern const char NS_JINGLE[];
+extern const char NS_JINGLE_DRAFT[];
+extern const char NS_GINGLE[];
+
+enum SignalingProtocol {
+ PROTOCOL_JINGLE,
+ PROTOCOL_GINGLE,
+ PROTOCOL_HYBRID,
+};
+
+// actions (aka Gingle <session> or Jingle <jingle>)
+extern const buzz::StaticQName QN_ACTION;
+extern const char LN_INITIATOR[];
+extern const buzz::StaticQName QN_INITIATOR;
+extern const buzz::StaticQName QN_CREATOR;
+
+extern const buzz::StaticQName QN_JINGLE;
+extern const buzz::StaticQName QN_JINGLE_CONTENT;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_NAME;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA;
+extern const buzz::StaticQName QN_JINGLE_REASON;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_GROUP;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE;
+extern const char JINGLE_CONTENT_MEDIA_AUDIO[];
+extern const char JINGLE_CONTENT_MEDIA_VIDEO[];
+extern const char JINGLE_CONTENT_MEDIA_DATA[];
+extern const char JINGLE_ACTION_SESSION_INITIATE[];
+extern const char JINGLE_ACTION_SESSION_INFO[];
+extern const char JINGLE_ACTION_SESSION_ACCEPT[];
+extern const char JINGLE_ACTION_SESSION_TERMINATE[];
+extern const char JINGLE_ACTION_TRANSPORT_INFO[];
+extern const char JINGLE_ACTION_TRANSPORT_ACCEPT[];
+extern const char JINGLE_ACTION_DESCRIPTION_INFO[];
+
+extern const buzz::StaticQName QN_GINGLE_SESSION;
+extern const char GINGLE_ACTION_INITIATE[];
+extern const char GINGLE_ACTION_INFO[];
+extern const char GINGLE_ACTION_ACCEPT[];
+extern const char GINGLE_ACTION_REJECT[];
+extern const char GINGLE_ACTION_TERMINATE[];
+extern const char GINGLE_ACTION_CANDIDATES[];
+extern const char GINGLE_ACTION_UPDATE[];
+
+extern const char LN_ERROR[];
+extern const buzz::StaticQName QN_GINGLE_REDIRECT;
+extern const char STR_REDIRECT_PREFIX[];
+
+// Session Contents (aka Gingle <session><description>
+// or Jingle <content><description>)
+extern const char LN_DESCRIPTION[];
+extern const char LN_PAYLOADTYPE[];
+extern const buzz::StaticQName QN_ID;
+extern const buzz::StaticQName QN_SID;
+extern const buzz::StaticQName QN_NAME;
+extern const buzz::StaticQName QN_CLOCKRATE;
+extern const buzz::StaticQName QN_BITRATE;
+extern const buzz::StaticQName QN_CHANNELS;
+extern const buzz::StaticQName QN_PARAMETER;
+extern const char LN_NAME[];
+extern const char LN_VALUE[];
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME;
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE;
+extern const char PAYLOADTYPE_PARAMETER_BITRATE[];
+extern const char PAYLOADTYPE_PARAMETER_HEIGHT[];
+extern const char PAYLOADTYPE_PARAMETER_WIDTH[];
+extern const char PAYLOADTYPE_PARAMETER_FRAMERATE[];
+extern const char LN_BANDWIDTH[];
+
+// CN_ == "content name". When we initiate a session, we choose the
+// name, and when we receive a Gingle session, we provide default
+// names (since Gingle has no content names). But when we receive a
+// Jingle call, the content name can be anything, so don't rely on
+// these values being the same as the ones received.
+extern const char CN_AUDIO[];
+extern const char CN_VIDEO[];
+extern const char CN_DATA[];
+extern const char CN_OTHER[];
+// other SDP related strings
+// GN stands for group name
+extern const char GROUP_TYPE_BUNDLE[];
+
+extern const char NS_JINGLE_RTP[];
+extern const buzz::StaticQName QN_JINGLE_RTP_CONTENT;
+extern const buzz::StaticQName QN_SSRC;
+extern const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE;
+extern const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH;
+extern const buzz::StaticQName QN_JINGLE_RTCP_MUX;
+extern const buzz::StaticQName QN_JINGLE_RTCP_FB;
+extern const buzz::StaticQName QN_SUBTYPE;
+extern const buzz::StaticQName QN_JINGLE_RTP_HDREXT;
+extern const buzz::StaticQName QN_URI;
+
+extern const char NS_JINGLE_DRAFT_SCTP[];
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM;
+
+extern const char NS_GINGLE_AUDIO[];
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_SRCID;
+extern const char NS_GINGLE_VIDEO[];
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_SRCID;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH;
+
+// Crypto support.
+extern const buzz::StaticQName QN_ENCRYPTION;
+extern const buzz::StaticQName QN_ENCRYPTION_REQUIRED;
+extern const buzz::StaticQName QN_CRYPTO;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_CRYPTO_SUITE;
+extern const buzz::StaticQName QN_CRYPTO_KEY_PARAMS;
+extern const buzz::StaticQName QN_CRYPTO_TAG;
+extern const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS;
+
+// Transports and candidates.
+extern const char LN_TRANSPORT[];
+extern const char LN_CANDIDATE[];
+extern const buzz::StaticQName QN_JINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_JINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_UFRAG;
+extern const buzz::StaticQName QN_COMPONENT;
+extern const buzz::StaticQName QN_PWD;
+extern const buzz::StaticQName QN_IP;
+extern const buzz::StaticQName QN_PORT;
+extern const buzz::StaticQName QN_NETWORK;
+extern const buzz::StaticQName QN_GENERATION;
+extern const buzz::StaticQName QN_PRIORITY;
+extern const buzz::StaticQName QN_PROTOCOL;
+extern const char ICE_CANDIDATE_TYPE_PEER_STUN[];
+extern const char ICE_CANDIDATE_TYPE_SERVER_STUN[];
+extern const int ICE_UFRAG_LENGTH;
+extern const int ICE_PWD_LENGTH;
+extern const size_t ICE_UFRAG_MIN_LENGTH;
+extern const size_t ICE_PWD_MIN_LENGTH;
+extern const size_t ICE_UFRAG_MAX_LENGTH;
+extern const size_t ICE_PWD_MAX_LENGTH;
+extern const int ICE_CANDIDATE_COMPONENT_RTP;
+extern const int ICE_CANDIDATE_COMPONENT_RTCP;
+extern const int ICE_CANDIDATE_COMPONENT_DEFAULT;
+
+extern const buzz::StaticQName QN_FINGERPRINT;
+extern const buzz::StaticQName QN_FINGERPRINT_ALGORITHM;
+extern const buzz::StaticQName QN_FINGERPRINT_DIGEST;
+
+extern const char NS_JINGLE_ICE_UDP[];
+
+extern const char ICE_OPTION_GICE[];
+extern const char NS_GINGLE_P2P[];
+extern const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
+extern const buzz::StaticQName QN_GINGLE_CANDIDATE;
+extern const buzz::StaticQName QN_ADDRESS;
+extern const buzz::StaticQName QN_USERNAME;
+extern const buzz::StaticQName QN_PASSWORD;
+extern const buzz::StaticQName QN_PREFERENCE;
+extern const char GINGLE_CANDIDATE_TYPE_STUN[];
+extern const char GICE_CHANNEL_NAME_RTP[];
+extern const char GICE_CHANNEL_NAME_RTCP[];
+extern const char GICE_CHANNEL_NAME_VIDEO_RTP[];
+extern const char GICE_CHANNEL_NAME_VIDEO_RTCP[];
+extern const char GICE_CHANNEL_NAME_DATA_RTP[];
+extern const char GICE_CHANNEL_NAME_DATA_RTCP[];
+
+extern const char NS_GINGLE_RAW[];
+extern const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_RAW_CHANNEL;
+
+// terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html
+extern const char JINGLE_ERROR_BAD_REQUEST[]; // like parse error
+// got transport-info before session-initiate, for example
+extern const char JINGLE_ERROR_OUT_OF_ORDER[];
+extern const char JINGLE_ERROR_UNKNOWN_SESSION[];
+
+// Call terminate reasons from XEP-166
+extern const char STR_TERMINATE_DECLINE[]; // polite reject
+extern const char STR_TERMINATE_SUCCESS[]; // polite hangup
+extern const char STR_TERMINATE_ERROR[]; // something bad happened
+extern const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[]; // no codecs?
+
+// Old terminate reasons used by cricket
+extern const char STR_TERMINATE_CALL_ENDED[];
+extern const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[];
+extern const char STR_TERMINATE_RECIPIENT_BUSY[];
+extern const char STR_TERMINATE_INSUFFICIENT_FUNDS[];
+extern const char STR_TERMINATE_NUMBER_MALFORMED[];
+extern const char STR_TERMINATE_NUMBER_DISALLOWED[];
+extern const char STR_TERMINATE_PROTOCOL_ERROR[];
+extern const char STR_TERMINATE_INTERNAL_SERVER_ERROR[];
+extern const char STR_TERMINATE_UNKNOWN_ERROR[];
+
+// Draft view and notify messages.
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[];
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[];
+extern const buzz::StaticQName QN_NICK;
+extern const buzz::StaticQName QN_TYPE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_VIEW;
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[];
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[];
+extern const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS;
+extern const buzz::StaticQName QN_WIDTH;
+extern const buzz::StaticQName QN_HEIGHT;
+extern const buzz::StaticQName QN_FRAMERATE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAM;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS;
+extern const buzz::StaticQName QN_DISPLAY;
+extern const buzz::StaticQName QN_CNAME;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP;
+extern const buzz::StaticQName QN_SEMANTICS;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE;
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const char NS_VOICEMAIL[];
+extern const buzz::StaticQName QN_VOICEMAIL_REGARDING;
+#endif
+
+// RFC 4145, SDP setup attribute values.
+extern const char CONNECTIONROLE_ACTIVE_STR[];
+extern const char CONNECTIONROLE_PASSIVE_STR[];
+extern const char CONNECTIONROLE_ACTPASS_STR[];
+extern const char CONNECTIONROLE_HOLDCONN_STR[];
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_CONSTANTS_H_
diff --git a/p2p/base/dtlstransport.h b/p2p/base/dtlstransport.h
new file mode 100644
index 00000000..bb80dc81
--- /dev/null
+++ b/p2p/base/dtlstransport.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORT_H_
+#define WEBRTC_P2P_BASE_DTLSTRANSPORT_H_
+
+#include "webrtc/p2p/base/dtlstransportchannel.h"
+#include "webrtc/p2p/base/transport.h"
+
+namespace rtc {
+class SSLIdentity;
+}
+
+namespace cricket {
+
+class PortAllocator;
+
+// Base should be a descendant of cricket::Transport
+template<class Base>
+class DtlsTransport : public Base {
+ public:
+ DtlsTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* allocator,
+ rtc::SSLIdentity* identity)
+ : Base(signaling_thread, worker_thread, content_name, allocator),
+ identity_(identity),
+ secure_role_(rtc::SSL_CLIENT) {
+ }
+
+ ~DtlsTransport() {
+ Base::DestroyAllChannels();
+ }
+ virtual void SetIdentity_w(rtc::SSLIdentity* identity) {
+ identity_ = identity;
+ }
+ virtual bool GetIdentity_w(rtc::SSLIdentity** identity) {
+ if (!identity_)
+ return false;
+
+ *identity = identity_->GetReference();
+ return true;
+ }
+
+ virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl* channel,
+ std::string* error_desc) {
+ rtc::SSLFingerprint* local_fp =
+ Base::local_description()->identity_fingerprint.get();
+
+ if (local_fp) {
+ // Sanity check local fingerprint.
+ if (identity_) {
+ rtc::scoped_ptr<rtc::SSLFingerprint> local_fp_tmp(
+ rtc::SSLFingerprint::Create(local_fp->algorithm,
+ identity_));
+ ASSERT(local_fp_tmp.get() != NULL);
+ if (!(*local_fp_tmp == *local_fp)) {
+ std::ostringstream desc;
+ desc << "Local fingerprint does not match identity. Expected: ";
+ desc << local_fp_tmp->ToString();
+ desc << " Got: " << local_fp->ToString();
+ return BadTransportDescription(desc.str(), error_desc);
+ }
+ } else {
+ return BadTransportDescription(
+ "Local fingerprint provided but no identity available.",
+ error_desc);
+ }
+ } else {
+ identity_ = NULL;
+ }
+
+ if (!channel->SetLocalIdentity(identity_)) {
+ return BadTransportDescription("Failed to set local identity.",
+ error_desc);
+ }
+
+ // Apply the description in the base class.
+ return Base::ApplyLocalTransportDescription_w(channel, error_desc);
+ }
+
+ virtual bool NegotiateTransportDescription_w(ContentAction local_role,
+ std::string* error_desc) {
+ if (!Base::local_description() || !Base::remote_description()) {
+ const std::string msg = "Local and Remote description must be set before "
+ "transport descriptions are negotiated";
+ return BadTransportDescription(msg, error_desc);
+ }
+
+ rtc::SSLFingerprint* local_fp =
+ Base::local_description()->identity_fingerprint.get();
+ rtc::SSLFingerprint* remote_fp =
+ Base::remote_description()->identity_fingerprint.get();
+
+ if (remote_fp && local_fp) {
+ remote_fingerprint_.reset(new rtc::SSLFingerprint(*remote_fp));
+
+ // From RFC 4145, section-4.1, The following are the values that the
+ // 'setup' attribute can take in an offer/answer exchange:
+ // Offer Answer
+ // ________________
+ // active passive / holdconn
+ // passive active / holdconn
+ // actpass active / passive / holdconn
+ // holdconn holdconn
+ //
+ // Set the role that is most conformant with RFC 5763, Section 5, bullet 1
+ // The endpoint MUST use the setup attribute defined in [RFC4145].
+ // The endpoint that is the offerer MUST use the setup attribute
+ // value of setup:actpass and be prepared to receive a client_hello
+ // before it receives the answer. The answerer MUST use either a
+ // setup attribute value of setup:active or setup:passive. Note that
+ // if the answerer uses setup:passive, then the DTLS handshake will
+ // not begin until the answerer is received, which adds additional
+ // latency. setup:active allows the answer and the DTLS handshake to
+ // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
+ // party is active MUST initiate a DTLS handshake by sending a
+ // ClientHello over each flow (host/port quartet).
+ // IOW - actpass and passive modes should be treated as server and
+ // active as client.
+ ConnectionRole local_connection_role =
+ Base::local_description()->connection_role;
+ ConnectionRole remote_connection_role =
+ Base::remote_description()->connection_role;
+
+ bool is_remote_server = false;
+ if (local_role == CA_OFFER) {
+ if (local_connection_role != CONNECTIONROLE_ACTPASS) {
+ return BadTransportDescription(
+ "Offerer must use actpass value for setup attribute.",
+ error_desc);
+ }
+
+ if (remote_connection_role == CONNECTIONROLE_ACTIVE ||
+ remote_connection_role == CONNECTIONROLE_PASSIVE ||
+ remote_connection_role == CONNECTIONROLE_NONE) {
+ is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE);
+ } else {
+ const std::string msg =
+ "Answerer must use either active or passive value "
+ "for setup attribute.";
+ return BadTransportDescription(msg, error_desc);
+ }
+ // If remote is NONE or ACTIVE it will act as client.
+ } else {
+ if (remote_connection_role != CONNECTIONROLE_ACTPASS &&
+ remote_connection_role != CONNECTIONROLE_NONE) {
+ return BadTransportDescription(
+ "Offerer must use actpass value for setup attribute.",
+ error_desc);
+ }
+
+ if (local_connection_role == CONNECTIONROLE_ACTIVE ||
+ local_connection_role == CONNECTIONROLE_PASSIVE) {
+ is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE);
+ } else {
+ const std::string msg =
+ "Answerer must use either active or passive value "
+ "for setup attribute.";
+ return BadTransportDescription(msg, error_desc);
+ }
+
+ // If local is passive, local will act as server.
+ }
+
+ secure_role_ = is_remote_server ? rtc::SSL_CLIENT :
+ rtc::SSL_SERVER;
+
+ } else if (local_fp && (local_role == CA_ANSWER)) {
+ return BadTransportDescription(
+ "Local fingerprint supplied when caller didn't offer DTLS.",
+ error_desc);
+ } else {
+ // We are not doing DTLS
+ remote_fingerprint_.reset(new rtc::SSLFingerprint(
+ "", NULL, 0));
+ }
+
+ // Now run the negotiation for the base class.
+ return Base::NegotiateTransportDescription_w(local_role, error_desc);
+ }
+
+ virtual DtlsTransportChannelWrapper* CreateTransportChannel(int component) {
+ return new DtlsTransportChannelWrapper(
+ this, Base::CreateTransportChannel(component));
+ }
+
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+ // Kind of ugly, but this lets us do the exact inverse of the create.
+ DtlsTransportChannelWrapper* dtls_channel =
+ static_cast<DtlsTransportChannelWrapper*>(channel);
+ TransportChannelImpl* base_channel = dtls_channel->channel();
+ delete dtls_channel;
+ Base::DestroyTransportChannel(base_channel);
+ }
+
+ virtual bool GetSslRole_w(rtc::SSLRole* ssl_role) const {
+ ASSERT(ssl_role != NULL);
+ *ssl_role = secure_role_;
+ return true;
+ }
+
+ private:
+ virtual bool ApplyNegotiatedTransportDescription_w(
+ TransportChannelImpl* channel,
+ std::string* error_desc) {
+ // Set ssl role. Role must be set before fingerprint is applied, which
+ // initiates DTLS setup.
+ if (!channel->SetSslRole(secure_role_)) {
+ return BadTransportDescription("Failed to set ssl role for the channel.",
+ error_desc);
+ }
+ // Apply remote fingerprint.
+ if (!channel->SetRemoteFingerprint(
+ remote_fingerprint_->algorithm,
+ reinterpret_cast<const uint8 *>(remote_fingerprint_->
+ digest.data()),
+ remote_fingerprint_->digest.length())) {
+ return BadTransportDescription("Failed to apply remote fingerprint.",
+ error_desc);
+ }
+ return Base::ApplyNegotiatedTransportDescription_w(channel, error_desc);
+ }
+
+ rtc::SSLIdentity* identity_;
+ rtc::SSLRole secure_role_;
+ rtc::scoped_ptr<rtc::SSLFingerprint> remote_fingerprint_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_DTLSTRANSPORT_H_
diff --git a/p2p/base/dtlstransportchannel.cc b/p2p/base/dtlstransportchannel.cc
new file mode 100644
index 00000000..9cceca35
--- /dev/null
+++ b/p2p/base/dtlstransportchannel.cc
@@ -0,0 +1,623 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/dtlstransportchannel.h"
+
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/base/buffer.h"
+#include "webrtc/base/dscp.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/sslstreamadapter.h"
+#include "webrtc/base/stream.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+// We don't pull the RTP constants from rtputils.h, to avoid a layer violation.
+static const size_t kDtlsRecordHeaderLen = 13;
+static const size_t kMaxDtlsPacketLen = 2048;
+static const size_t kMinRtpPacketLen = 12;
+
+static bool IsDtlsPacket(const char* data, size_t len) {
+ const uint8* u = reinterpret_cast<const uint8*>(data);
+ return (len >= kDtlsRecordHeaderLen && (u[0] > 19 && u[0] < 64));
+}
+static bool IsRtpPacket(const char* data, size_t len) {
+ const uint8* u = reinterpret_cast<const uint8*>(data);
+ return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80);
+}
+
+rtc::StreamResult StreamInterfaceChannel::Read(void* buffer,
+ size_t buffer_len,
+ size_t* read,
+ int* error) {
+ if (state_ == rtc::SS_CLOSED)
+ return rtc::SR_EOS;
+ if (state_ == rtc::SS_OPENING)
+ return rtc::SR_BLOCK;
+
+ return fifo_.Read(buffer, buffer_len, read, error);
+}
+
+rtc::StreamResult StreamInterfaceChannel::Write(const void* data,
+ size_t data_len,
+ size_t* written,
+ int* error) {
+ // Always succeeds, since this is an unreliable transport anyway.
+ // TODO: Should this block if channel_'s temporarily unwritable?
+ rtc::PacketOptions packet_options;
+ channel_->SendPacket(static_cast<const char*>(data), data_len,
+ packet_options);
+ if (written) {
+ *written = data_len;
+ }
+ return rtc::SR_SUCCESS;
+}
+
+bool StreamInterfaceChannel::OnPacketReceived(const char* data, size_t size) {
+ // We force a read event here to ensure that we don't overflow our FIFO.
+ // Under high packet rate this can occur if we wait for the FIFO to post its
+ // own SE_READ.
+ bool ret = (fifo_.WriteAll(data, size, NULL, NULL) == rtc::SR_SUCCESS);
+ if (ret) {
+ SignalEvent(this, rtc::SE_READ, 0);
+ }
+ return ret;
+}
+
+void StreamInterfaceChannel::OnEvent(rtc::StreamInterface* stream,
+ int sig, int err) {
+ SignalEvent(this, sig, err);
+}
+
+DtlsTransportChannelWrapper::DtlsTransportChannelWrapper(
+ Transport* transport,
+ TransportChannelImpl* channel)
+ : TransportChannelImpl(channel->content_name(), channel->component()),
+ transport_(transport),
+ worker_thread_(rtc::Thread::Current()),
+ channel_(channel),
+ downward_(NULL),
+ dtls_state_(STATE_NONE),
+ local_identity_(NULL),
+ ssl_role_(rtc::SSL_CLIENT) {
+ channel_->SignalReadableState.connect(this,
+ &DtlsTransportChannelWrapper::OnReadableState);
+ channel_->SignalWritableState.connect(this,
+ &DtlsTransportChannelWrapper::OnWritableState);
+ channel_->SignalReadPacket.connect(this,
+ &DtlsTransportChannelWrapper::OnReadPacket);
+ channel_->SignalReadyToSend.connect(this,
+ &DtlsTransportChannelWrapper::OnReadyToSend);
+ channel_->SignalRequestSignaling.connect(this,
+ &DtlsTransportChannelWrapper::OnRequestSignaling);
+ channel_->SignalCandidateReady.connect(this,
+ &DtlsTransportChannelWrapper::OnCandidateReady);
+ channel_->SignalCandidatesAllocationDone.connect(this,
+ &DtlsTransportChannelWrapper::OnCandidatesAllocationDone);
+ channel_->SignalRoleConflict.connect(this,
+ &DtlsTransportChannelWrapper::OnRoleConflict);
+ channel_->SignalRouteChange.connect(this,
+ &DtlsTransportChannelWrapper::OnRouteChange);
+ channel_->SignalConnectionRemoved.connect(this,
+ &DtlsTransportChannelWrapper::OnConnectionRemoved);
+}
+
+DtlsTransportChannelWrapper::~DtlsTransportChannelWrapper() {
+}
+
+void DtlsTransportChannelWrapper::Connect() {
+ // We should only get a single call to Connect.
+ ASSERT(dtls_state_ == STATE_NONE ||
+ dtls_state_ == STATE_OFFERED ||
+ dtls_state_ == STATE_ACCEPTED);
+ channel_->Connect();
+}
+
+void DtlsTransportChannelWrapper::Reset() {
+ channel_->Reset();
+ set_writable(false);
+ set_readable(false);
+
+ // Re-call SetupDtls()
+ if (!SetupDtls()) {
+ LOG_J(LS_ERROR, this) << "Error re-initializing DTLS";
+ dtls_state_ = STATE_CLOSED;
+ return;
+ }
+
+ dtls_state_ = STATE_ACCEPTED;
+}
+
+bool DtlsTransportChannelWrapper::SetLocalIdentity(
+ rtc::SSLIdentity* identity) {
+ if (dtls_state_ != STATE_NONE) {
+ if (identity == local_identity_) {
+ // This may happen during renegotiation.
+ LOG_J(LS_INFO, this) << "Ignoring identical DTLS identity";
+ return true;
+ } else {
+ LOG_J(LS_ERROR, this) << "Can't change DTLS local identity in this state";
+ return false;
+ }
+ }
+
+ if (identity) {
+ local_identity_ = identity;
+ dtls_state_ = STATE_OFFERED;
+ } else {
+ LOG_J(LS_INFO, this) << "NULL DTLS identity supplied. Not doing DTLS";
+ }
+
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::GetLocalIdentity(
+ rtc::SSLIdentity** identity) const {
+ if (!local_identity_)
+ return false;
+
+ *identity = local_identity_->GetReference();
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::SetSslRole(rtc::SSLRole role) {
+ if (dtls_state_ == STATE_OPEN) {
+ if (ssl_role_ != role) {
+ LOG(LS_ERROR) << "SSL Role can't be reversed after the session is setup.";
+ return false;
+ }
+ return true;
+ }
+
+ ssl_role_ = role;
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::GetSslRole(rtc::SSLRole* role) const {
+ *role = ssl_role_;
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::SetRemoteFingerprint(
+ const std::string& digest_alg,
+ const uint8* digest,
+ size_t digest_len) {
+
+ rtc::Buffer remote_fingerprint_value(digest, digest_len);
+
+ if (dtls_state_ != STATE_NONE &&
+ remote_fingerprint_value_ == remote_fingerprint_value &&
+ !digest_alg.empty()) {
+ // This may happen during renegotiation.
+ LOG_J(LS_INFO, this) << "Ignoring identical remote DTLS fingerprint";
+ return true;
+ }
+
+ // Allow SetRemoteFingerprint with a NULL digest even if SetLocalIdentity
+ // hasn't been called.
+ if (dtls_state_ > STATE_OFFERED ||
+ (dtls_state_ == STATE_NONE && !digest_alg.empty())) {
+ LOG_J(LS_ERROR, this) << "Can't set DTLS remote settings in this state.";
+ return false;
+ }
+
+ if (digest_alg.empty()) {
+ LOG_J(LS_INFO, this) << "Other side didn't support DTLS.";
+ dtls_state_ = STATE_NONE;
+ return true;
+ }
+
+ // At this point we know we are doing DTLS
+ remote_fingerprint_value.TransferTo(&remote_fingerprint_value_);
+ remote_fingerprint_algorithm_ = digest_alg;
+
+ if (!SetupDtls()) {
+ dtls_state_ = STATE_CLOSED;
+ return false;
+ }
+
+ dtls_state_ = STATE_ACCEPTED;
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::GetRemoteCertificate(
+ rtc::SSLCertificate** cert) const {
+ if (!dtls_)
+ return false;
+
+ return dtls_->GetPeerCertificate(cert);
+}
+
+bool DtlsTransportChannelWrapper::SetupDtls() {
+ StreamInterfaceChannel* downward =
+ new StreamInterfaceChannel(worker_thread_, channel_);
+
+ dtls_.reset(rtc::SSLStreamAdapter::Create(downward));
+ if (!dtls_) {
+ LOG_J(LS_ERROR, this) << "Failed to create DTLS adapter.";
+ delete downward;
+ return false;
+ }
+
+ downward_ = downward;
+
+ dtls_->SetIdentity(local_identity_->GetReference());
+ dtls_->SetMode(rtc::SSL_MODE_DTLS);
+ dtls_->SetServerRole(ssl_role_);
+ dtls_->SignalEvent.connect(this, &DtlsTransportChannelWrapper::OnDtlsEvent);
+ if (!dtls_->SetPeerCertificateDigest(
+ remote_fingerprint_algorithm_,
+ reinterpret_cast<unsigned char *>(remote_fingerprint_value_.data()),
+ remote_fingerprint_value_.length())) {
+ LOG_J(LS_ERROR, this) << "Couldn't set DTLS certificate digest.";
+ return false;
+ }
+
+ // Set up DTLS-SRTP, if it's been enabled.
+ if (!srtp_ciphers_.empty()) {
+ if (!dtls_->SetDtlsSrtpCiphers(srtp_ciphers_)) {
+ LOG_J(LS_ERROR, this) << "Couldn't set DTLS-SRTP ciphers.";
+ return false;
+ }
+ } else {
+ LOG_J(LS_INFO, this) << "Not using DTLS.";
+ }
+
+ LOG_J(LS_INFO, this) << "DTLS setup complete.";
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::SetSrtpCiphers(
+ const std::vector<std::string>& ciphers) {
+ if (srtp_ciphers_ == ciphers)
+ return true;
+
+ if (dtls_state_ == STATE_STARTED) {
+ LOG(LS_WARNING) << "Ignoring new SRTP ciphers while DTLS is negotiating";
+ return true;
+ }
+
+ if (dtls_state_ == STATE_OPEN) {
+ // We don't support DTLS renegotiation currently. If new set of srtp ciphers
+ // are different than what's being used currently, we will not use it.
+ // So for now, let's be happy (or sad) with a warning message.
+ std::string current_srtp_cipher;
+ if (!dtls_->GetDtlsSrtpCipher(&current_srtp_cipher)) {
+ LOG(LS_ERROR) << "Failed to get the current SRTP cipher for DTLS channel";
+ return false;
+ }
+ const std::vector<std::string>::const_iterator iter =
+ std::find(ciphers.begin(), ciphers.end(), current_srtp_cipher);
+ if (iter == ciphers.end()) {
+ std::string requested_str;
+ for (size_t i = 0; i < ciphers.size(); ++i) {
+ requested_str.append(" ");
+ requested_str.append(ciphers[i]);
+ requested_str.append(" ");
+ }
+ LOG(LS_WARNING) << "Ignoring new set of SRTP ciphers, as DTLS "
+ << "renegotiation is not supported currently "
+ << "current cipher = " << current_srtp_cipher << " and "
+ << "requested = " << "[" << requested_str << "]";
+ }
+ return true;
+ }
+
+ if (dtls_state_ != STATE_NONE &&
+ dtls_state_ != STATE_OFFERED &&
+ dtls_state_ != STATE_ACCEPTED) {
+ ASSERT(false);
+ return false;
+ }
+
+ srtp_ciphers_ = ciphers;
+ return true;
+}
+
+bool DtlsTransportChannelWrapper::GetSrtpCipher(std::string* cipher) {
+ if (dtls_state_ != STATE_OPEN) {
+ return false;
+ }
+
+ return dtls_->GetDtlsSrtpCipher(cipher);
+}
+
+
+// Called from upper layers to send a media packet.
+int DtlsTransportChannelWrapper::SendPacket(
+ const char* data, size_t size,
+ const rtc::PacketOptions& options, int flags) {
+ int result = -1;
+
+ switch (dtls_state_) {
+ case STATE_OFFERED:
+ // We don't know if we are doing DTLS yet, so we can't send a packet.
+ // TODO(ekr@rtfm.com): assert here?
+ result = -1;
+ break;
+
+ case STATE_STARTED:
+ case STATE_ACCEPTED:
+ // Can't send data until the connection is active
+ result = -1;
+ break;
+
+ case STATE_OPEN:
+ if (flags & PF_SRTP_BYPASS) {
+ ASSERT(!srtp_ciphers_.empty());
+ if (!IsRtpPacket(data, size)) {
+ result = -1;
+ break;
+ }
+
+ result = channel_->SendPacket(data, size, options);
+ } else {
+ result = (dtls_->WriteAll(data, size, NULL, NULL) ==
+ rtc::SR_SUCCESS) ? static_cast<int>(size) : -1;
+ }
+ break;
+ // Not doing DTLS.
+ case STATE_NONE:
+ result = channel_->SendPacket(data, size, options);
+ break;
+
+ case STATE_CLOSED: // Can't send anything when we're closed.
+ return -1;
+ }
+
+ return result;
+}
+
+// The state transition logic here is as follows:
+// (1) If we're not doing DTLS-SRTP, then the state is just the
+// state of the underlying impl()
+// (2) If we're doing DTLS-SRTP:
+// - Prior to the DTLS handshake, the state is neither readable or
+// writable
+// - When the impl goes writable for the first time we
+// start the DTLS handshake
+// - Once the DTLS handshake completes, the state is that of the
+// impl again
+void DtlsTransportChannelWrapper::OnReadableState(TransportChannel* channel) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == channel_);
+ LOG_J(LS_VERBOSE, this)
+ << "DTLSTransportChannelWrapper: channel readable state changed.";
+
+ if (dtls_state_ == STATE_NONE || dtls_state_ == STATE_OPEN) {
+ set_readable(channel_->readable());
+ // Note: SignalReadableState fired by set_readable.
+ }
+}
+
+void DtlsTransportChannelWrapper::OnWritableState(TransportChannel* channel) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == channel_);
+ LOG_J(LS_VERBOSE, this)
+ << "DTLSTransportChannelWrapper: channel writable state changed.";
+
+ switch (dtls_state_) {
+ case STATE_NONE:
+ case STATE_OPEN:
+ set_writable(channel_->writable());
+ // Note: SignalWritableState fired by set_writable.
+ break;
+
+ case STATE_OFFERED:
+ // Do nothing
+ break;
+
+ case STATE_ACCEPTED:
+ if (!MaybeStartDtls()) {
+ // This should never happen:
+ // Because we are operating in a nonblocking mode and all
+ // incoming packets come in via OnReadPacket(), which rejects
+ // packets in this state, the incoming queue must be empty. We
+ // ignore write errors, thus any errors must be because of
+ // configuration and therefore are our fault.
+ // Note that in non-debug configurations, failure in
+ // MaybeStartDtls() changes the state to STATE_CLOSED.
+ ASSERT(false);
+ }
+ break;
+
+ case STATE_STARTED:
+ // Do nothing
+ break;
+
+ case STATE_CLOSED:
+ // Should not happen. Do nothing
+ break;
+ }
+}
+
+void DtlsTransportChannelWrapper::OnReadPacket(
+ TransportChannel* channel, const char* data, size_t size,
+ const rtc::PacketTime& packet_time, int flags) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == channel_);
+ ASSERT(flags == 0);
+
+ switch (dtls_state_) {
+ case STATE_NONE:
+ // We are not doing DTLS
+ SignalReadPacket(this, data, size, packet_time, 0);
+ break;
+
+ case STATE_OFFERED:
+ // Currently drop the packet, but we might in future
+ // decide to take this as evidence that the other
+ // side is ready to do DTLS and start the handshake
+ // on our end
+ LOG_J(LS_WARNING, this) << "Received packet before we know if we are "
+ << "doing DTLS or not; dropping.";
+ break;
+
+ case STATE_ACCEPTED:
+ // Drop packets received before DTLS has actually started
+ LOG_J(LS_INFO, this) << "Dropping packet received before DTLS started.";
+ break;
+
+ case STATE_STARTED:
+ case STATE_OPEN:
+ // We should only get DTLS or SRTP packets; STUN's already been demuxed.
+ // Is this potentially a DTLS packet?
+ if (IsDtlsPacket(data, size)) {
+ if (!HandleDtlsPacket(data, size)) {
+ LOG_J(LS_ERROR, this) << "Failed to handle DTLS packet.";
+ return;
+ }
+ } else {
+ // Not a DTLS packet; our handshake should be complete by now.
+ if (dtls_state_ != STATE_OPEN) {
+ LOG_J(LS_ERROR, this) << "Received non-DTLS packet before DTLS "
+ << "complete.";
+ return;
+ }
+
+ // And it had better be a SRTP packet.
+ if (!IsRtpPacket(data, size)) {
+ LOG_J(LS_ERROR, this) << "Received unexpected non-DTLS packet.";
+ return;
+ }
+
+ // Sanity check.
+ ASSERT(!srtp_ciphers_.empty());
+
+ // Signal this upwards as a bypass packet.
+ SignalReadPacket(this, data, size, packet_time, PF_SRTP_BYPASS);
+ }
+ break;
+ case STATE_CLOSED:
+ // This shouldn't be happening. Drop the packet
+ break;
+ }
+}
+
+void DtlsTransportChannelWrapper::OnReadyToSend(TransportChannel* channel) {
+ if (writable()) {
+ SignalReadyToSend(this);
+ }
+}
+
+void DtlsTransportChannelWrapper::OnDtlsEvent(rtc::StreamInterface* dtls,
+ int sig, int err) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(dtls == dtls_.get());
+ if (sig & rtc::SE_OPEN) {
+ // This is the first time.
+ LOG_J(LS_INFO, this) << "DTLS handshake complete.";
+ if (dtls_->GetState() == rtc::SS_OPEN) {
+ // The check for OPEN shouldn't be necessary but let's make
+ // sure we don't accidentally frob the state if it's closed.
+ dtls_state_ = STATE_OPEN;
+
+ set_readable(true);
+ set_writable(true);
+ }
+ }
+ if (sig & rtc::SE_READ) {
+ char buf[kMaxDtlsPacketLen];
+ size_t read;
+ if (dtls_->Read(buf, sizeof(buf), &read, NULL) == rtc::SR_SUCCESS) {
+ SignalReadPacket(this, buf, read, rtc::CreatePacketTime(0), 0);
+ }
+ }
+ if (sig & rtc::SE_CLOSE) {
+ ASSERT(sig == rtc::SE_CLOSE); // SE_CLOSE should be by itself.
+ if (!err) {
+ LOG_J(LS_INFO, this) << "DTLS channel closed";
+ } else {
+ LOG_J(LS_INFO, this) << "DTLS channel error, code=" << err;
+ }
+
+ set_readable(false);
+ set_writable(false);
+ dtls_state_ = STATE_CLOSED;
+ }
+}
+
+bool DtlsTransportChannelWrapper::MaybeStartDtls() {
+ if (channel_->writable()) {
+ if (dtls_->StartSSLWithPeer()) {
+ LOG_J(LS_ERROR, this) << "Couldn't start DTLS handshake";
+ dtls_state_ = STATE_CLOSED;
+ return false;
+ }
+ LOG_J(LS_INFO, this)
+ << "DtlsTransportChannelWrapper: Started DTLS handshake";
+
+ dtls_state_ = STATE_STARTED;
+ }
+ return true;
+}
+
+// Called from OnReadPacket when a DTLS packet is received.
+bool DtlsTransportChannelWrapper::HandleDtlsPacket(const char* data,
+ size_t size) {
+ // Sanity check we're not passing junk that
+ // just looks like DTLS.
+ const uint8* tmp_data = reinterpret_cast<const uint8* >(data);
+ size_t tmp_size = size;
+ while (tmp_size > 0) {
+ if (tmp_size < kDtlsRecordHeaderLen)
+ return false; // Too short for the header
+
+ size_t record_len = (tmp_data[11] << 8) | (tmp_data[12]);
+ if ((record_len + kDtlsRecordHeaderLen) > tmp_size)
+ return false; // Body too short
+
+ tmp_data += record_len + kDtlsRecordHeaderLen;
+ tmp_size -= record_len + kDtlsRecordHeaderLen;
+ }
+
+ // Looks good. Pass to the SIC which ends up being passed to
+ // the DTLS stack.
+ return downward_->OnPacketReceived(data, size);
+}
+
+void DtlsTransportChannelWrapper::OnRequestSignaling(
+ TransportChannelImpl* channel) {
+ ASSERT(channel == channel_);
+ SignalRequestSignaling(this);
+}
+
+void DtlsTransportChannelWrapper::OnCandidateReady(
+ TransportChannelImpl* channel, const Candidate& c) {
+ ASSERT(channel == channel_);
+ SignalCandidateReady(this, c);
+}
+
+void DtlsTransportChannelWrapper::OnCandidatesAllocationDone(
+ TransportChannelImpl* channel) {
+ ASSERT(channel == channel_);
+ SignalCandidatesAllocationDone(this);
+}
+
+void DtlsTransportChannelWrapper::OnRoleConflict(
+ TransportChannelImpl* channel) {
+ ASSERT(channel == channel_);
+ SignalRoleConflict(this);
+}
+
+void DtlsTransportChannelWrapper::OnRouteChange(
+ TransportChannel* channel, const Candidate& candidate) {
+ ASSERT(channel == channel_);
+ SignalRouteChange(this, candidate);
+}
+
+void DtlsTransportChannelWrapper::OnConnectionRemoved(
+ TransportChannelImpl* channel) {
+ ASSERT(channel == channel_);
+ SignalConnectionRemoved(this);
+}
+
+} // namespace cricket
diff --git a/p2p/base/dtlstransportchannel.h b/p2p/base/dtlstransportchannel.h
new file mode 100644
index 00000000..d12f724d
--- /dev/null
+++ b/p2p/base/dtlstransportchannel.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
+#define WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/base/buffer.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sslstreamadapter.h"
+#include "webrtc/base/stream.h"
+
+namespace cricket {
+
+// A bridge between a packet-oriented/channel-type interface on
+// the bottom and a StreamInterface on the top.
+class StreamInterfaceChannel : public rtc::StreamInterface,
+ public sigslot::has_slots<> {
+ public:
+ StreamInterfaceChannel(rtc::Thread* owner, TransportChannel* channel)
+ : channel_(channel),
+ state_(rtc::SS_OPEN),
+ fifo_(kFifoSize, owner) {
+ fifo_.SignalEvent.connect(this, &StreamInterfaceChannel::OnEvent);
+ }
+
+ // Push in a packet; this gets pulled out from Read().
+ bool OnPacketReceived(const char* data, size_t size);
+
+ // Implementations of StreamInterface
+ virtual rtc::StreamState GetState() const { return state_; }
+ virtual void Close() { state_ = rtc::SS_CLOSED; }
+ virtual rtc::StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual rtc::StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+
+ private:
+ static const size_t kFifoSize = 8192;
+
+ // Forward events
+ virtual void OnEvent(rtc::StreamInterface* stream, int sig, int err);
+
+ TransportChannel* channel_; // owned by DtlsTransportChannelWrapper
+ rtc::StreamState state_;
+ rtc::FifoBuffer fifo_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamInterfaceChannel);
+};
+
+
+// This class provides a DTLS SSLStreamAdapter inside a TransportChannel-style
+// packet-based interface, wrapping an existing TransportChannel instance
+// (e.g a P2PTransportChannel)
+// Here's the way this works:
+//
+// DtlsTransportChannelWrapper {
+// SSLStreamAdapter* dtls_ {
+// StreamInterfaceChannel downward_ {
+// TransportChannelImpl* channel_;
+// }
+// }
+// }
+//
+// - Data which comes into DtlsTransportChannelWrapper from the underlying
+// channel_ via OnReadPacket() is checked for whether it is DTLS
+// or not, and if it is, is passed to DtlsTransportChannelWrapper::
+// HandleDtlsPacket, which pushes it into to downward_.
+// dtls_ is listening for events on downward_, so it immediately calls
+// downward_->Read().
+//
+// - Data written to DtlsTransportChannelWrapper is passed either to
+// downward_ or directly to channel_, depending on whether DTLS is
+// negotiated and whether the flags include PF_SRTP_BYPASS
+//
+// - The SSLStreamAdapter writes to downward_->Write()
+// which translates it into packet writes on channel_.
+class DtlsTransportChannelWrapper : public TransportChannelImpl {
+ public:
+ enum State {
+ STATE_NONE, // No state or rejected.
+ STATE_OFFERED, // Our identity has been set.
+ STATE_ACCEPTED, // The other side sent a fingerprint.
+ STATE_STARTED, // We are negotiating.
+ STATE_OPEN, // Negotiation complete.
+ STATE_CLOSED // Connection closed.
+ };
+
+ // The parameters here are:
+ // transport -- the DtlsTransport that created us
+ // channel -- the TransportChannel we are wrapping
+ DtlsTransportChannelWrapper(Transport* transport,
+ TransportChannelImpl* channel);
+ virtual ~DtlsTransportChannelWrapper();
+
+ virtual void SetIceRole(IceRole role) {
+ channel_->SetIceRole(role);
+ }
+ virtual IceRole GetIceRole() const {
+ return channel_->GetIceRole();
+ }
+ virtual size_t GetConnectionCount() const {
+ return channel_->GetConnectionCount();
+ }
+ virtual bool SetLocalIdentity(rtc::SSLIdentity *identity);
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const;
+
+ virtual bool SetRemoteFingerprint(const std::string& digest_alg,
+ const uint8* digest,
+ size_t digest_len);
+ virtual bool IsDtlsActive() const { return dtls_state_ != STATE_NONE; }
+
+ // Called to send a packet (via DTLS, if turned on).
+ virtual int SendPacket(const char* data, size_t size,
+ const rtc::PacketOptions& options,
+ int flags);
+
+ // TransportChannel calls that we forward to the wrapped transport.
+ virtual int SetOption(rtc::Socket::Option opt, int value) {
+ return channel_->SetOption(opt, value);
+ }
+ virtual int GetError() {
+ return channel_->GetError();
+ }
+ virtual bool GetStats(ConnectionInfos* infos) {
+ return channel_->GetStats(infos);
+ }
+ virtual const std::string SessionId() const {
+ return channel_->SessionId();
+ }
+
+ // Set up the ciphers to use for DTLS-SRTP. If this method is not called
+ // before DTLS starts, or |ciphers| is empty, SRTP keys won't be negotiated.
+ // This method should be called before SetupDtls.
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
+
+ // Find out which DTLS-SRTP cipher was negotiated
+ virtual bool GetSrtpCipher(std::string* cipher);
+
+ virtual bool GetSslRole(rtc::SSLRole* role) const;
+ virtual bool SetSslRole(rtc::SSLRole role);
+
+ // Once DTLS has been established, this method retrieves the certificate in
+ // use by the remote peer, for use in external identity verification.
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const;
+
+ // Once DTLS has established (i.e., this channel is writable), this method
+ // extracts the keys negotiated during the DTLS handshake, for use in external
+ // encryption. DTLS-SRTP uses this to extract the needed SRTP keys.
+ // See the SSLStreamAdapter documentation for info on the specific parameters.
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ return (dtls_.get()) ? dtls_->ExportKeyingMaterial(label, context,
+ context_len,
+ use_context,
+ result, result_len)
+ : false;
+ }
+
+ // TransportChannelImpl calls.
+ virtual Transport* GetTransport() {
+ return transport_;
+ }
+ virtual void SetIceTiebreaker(uint64 tiebreaker) {
+ channel_->SetIceTiebreaker(tiebreaker);
+ }
+ virtual bool GetIceProtocolType(IceProtocolType* type) const {
+ return channel_->GetIceProtocolType(type);
+ }
+ virtual void SetIceProtocolType(IceProtocolType type) {
+ channel_->SetIceProtocolType(type);
+ }
+ virtual void SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ channel_->SetIceCredentials(ice_ufrag, ice_pwd);
+ }
+ virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ channel_->SetRemoteIceCredentials(ice_ufrag, ice_pwd);
+ }
+ virtual void SetRemoteIceMode(IceMode mode) {
+ channel_->SetRemoteIceMode(mode);
+ }
+
+ virtual void Connect();
+ virtual void Reset();
+
+ virtual void OnSignalingReady() {
+ channel_->OnSignalingReady();
+ }
+ virtual void OnCandidate(const Candidate& candidate) {
+ channel_->OnCandidate(candidate);
+ }
+
+ // Needed by DtlsTransport.
+ TransportChannelImpl* channel() { return channel_; }
+
+ private:
+ void OnReadableState(TransportChannel* channel);
+ void OnWritableState(TransportChannel* channel);
+ void OnReadPacket(TransportChannel* channel, const char* data, size_t size,
+ const rtc::PacketTime& packet_time, int flags);
+ void OnReadyToSend(TransportChannel* channel);
+ void OnDtlsEvent(rtc::StreamInterface* stream_, int sig, int err);
+ bool SetupDtls();
+ bool MaybeStartDtls();
+ bool HandleDtlsPacket(const char* data, size_t size);
+ void OnRequestSignaling(TransportChannelImpl* channel);
+ void OnCandidateReady(TransportChannelImpl* channel, const Candidate& c);
+ void OnCandidatesAllocationDone(TransportChannelImpl* channel);
+ void OnRoleConflict(TransportChannelImpl* channel);
+ void OnRouteChange(TransportChannel* channel, const Candidate& candidate);
+ void OnConnectionRemoved(TransportChannelImpl* channel);
+
+ Transport* transport_; // The transport_ that created us.
+ rtc::Thread* worker_thread_; // Everything should occur on this thread.
+ TransportChannelImpl* channel_; // Underlying channel, owned by transport_.
+ rtc::scoped_ptr<rtc::SSLStreamAdapter> dtls_; // The DTLS stream
+ StreamInterfaceChannel* downward_; // Wrapper for channel_, owned by dtls_.
+ std::vector<std::string> srtp_ciphers_; // SRTP ciphers to use with DTLS.
+ State dtls_state_;
+ rtc::SSLIdentity* local_identity_;
+ rtc::SSLRole ssl_role_;
+ rtc::Buffer remote_fingerprint_value_;
+ std::string remote_fingerprint_algorithm_;
+
+ DISALLOW_COPY_AND_ASSIGN(DtlsTransportChannelWrapper);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
diff --git a/p2p/base/dtlstransportchannel_unittest.cc b/p2p/base/dtlstransportchannel_unittest.cc
new file mode 100644
index 00000000..52f8c1e7
--- /dev/null
+++ b/p2p/base/dtlstransportchannel_unittest.cc
@@ -0,0 +1,823 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <set>
+
+#include "webrtc/p2p/base/dtlstransport.h"
+#include "webrtc/p2p/base/fakesession.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/dscp.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/sslidentity.h"
+#include "webrtc/base/sslstreamadapter.h"
+#include "webrtc/base/stringutils.h"
+#include "webrtc/base/thread.h"
+
+#define MAYBE_SKIP_TEST(feature) \
+ if (!(rtc::SSLStreamAdapter::feature())) { \
+ LOG(LS_INFO) << "Feature disabled... skipping"; \
+ return; \
+ }
+
+static const char AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+static const size_t kPacketNumOffset = 8;
+static const size_t kPacketHeaderLen = 12;
+
+static bool IsRtpLeadByte(uint8 b) {
+ return ((b & 0xC0) == 0x80);
+}
+
+using cricket::ConnectionRole;
+
+enum Flags { NF_REOFFER = 0x1, NF_EXPECT_FAILURE = 0x2 };
+
+class DtlsTestClient : public sigslot::has_slots<> {
+ public:
+ DtlsTestClient(const std::string& name,
+ rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread) :
+ name_(name),
+ signaling_thread_(signaling_thread),
+ worker_thread_(worker_thread),
+ protocol_(cricket::ICEPROTO_GOOGLE),
+ packet_size_(0),
+ use_dtls_srtp_(false),
+ negotiated_dtls_(false),
+ received_dtls_client_hello_(false),
+ received_dtls_server_hello_(false) {
+ }
+ void SetIceProtocol(cricket::TransportProtocol proto) {
+ protocol_ = proto;
+ }
+ void CreateIdentity() {
+ identity_.reset(rtc::SSLIdentity::Generate(name_));
+ }
+ rtc::SSLIdentity* identity() { return identity_.get(); }
+ void SetupSrtp() {
+ ASSERT(identity_.get() != NULL);
+ use_dtls_srtp_ = true;
+ }
+ void SetupChannels(int count, cricket::IceRole role) {
+ transport_.reset(new cricket::DtlsTransport<cricket::FakeTransport>(
+ signaling_thread_, worker_thread_, "dtls content name", NULL,
+ identity_.get()));
+ transport_->SetAsync(true);
+ transport_->SetIceRole(role);
+ transport_->SetIceTiebreaker(
+ (role == cricket::ICEROLE_CONTROLLING) ? 1 : 2);
+ transport_->SignalWritableState.connect(this,
+ &DtlsTestClient::OnTransportWritableState);
+
+ for (int i = 0; i < count; ++i) {
+ cricket::DtlsTransportChannelWrapper* channel =
+ static_cast<cricket::DtlsTransportChannelWrapper*>(
+ transport_->CreateChannel(i));
+ ASSERT_TRUE(channel != NULL);
+ channel->SignalWritableState.connect(this,
+ &DtlsTestClient::OnTransportChannelWritableState);
+ channel->SignalReadPacket.connect(this,
+ &DtlsTestClient::OnTransportChannelReadPacket);
+ channels_.push_back(channel);
+
+ // Hook the raw packets so that we can verify they are encrypted.
+ channel->channel()->SignalReadPacket.connect(
+ this, &DtlsTestClient::OnFakeTransportChannelReadPacket);
+ }
+ }
+
+ cricket::Transport* transport() { return transport_.get(); }
+
+ cricket::FakeTransportChannel* GetFakeChannel(int component) {
+ cricket::TransportChannelImpl* ch = transport_->GetChannel(component);
+ cricket::DtlsTransportChannelWrapper* wrapper =
+ static_cast<cricket::DtlsTransportChannelWrapper*>(ch);
+ return (wrapper) ?
+ static_cast<cricket::FakeTransportChannel*>(wrapper->channel()) : NULL;
+ }
+
+ // Offer DTLS if we have an identity; pass in a remote fingerprint only if
+ // both sides support DTLS.
+ void Negotiate(DtlsTestClient* peer, cricket::ContentAction action,
+ ConnectionRole local_role, ConnectionRole remote_role,
+ int flags) {
+ Negotiate(identity_.get(), (identity_) ? peer->identity_.get() : NULL,
+ action, local_role, remote_role, flags);
+ }
+
+ // Allow any DTLS configuration to be specified (including invalid ones).
+ void Negotiate(rtc::SSLIdentity* local_identity,
+ rtc::SSLIdentity* remote_identity,
+ cricket::ContentAction action,
+ ConnectionRole local_role,
+ ConnectionRole remote_role,
+ int flags) {
+ rtc::scoped_ptr<rtc::SSLFingerprint> local_fingerprint;
+ rtc::scoped_ptr<rtc::SSLFingerprint> remote_fingerprint;
+ if (local_identity) {
+ local_fingerprint.reset(rtc::SSLFingerprint::Create(
+ rtc::DIGEST_SHA_1, local_identity));
+ ASSERT_TRUE(local_fingerprint.get() != NULL);
+ }
+ if (remote_identity) {
+ remote_fingerprint.reset(rtc::SSLFingerprint::Create(
+ rtc::DIGEST_SHA_1, remote_identity));
+ ASSERT_TRUE(remote_fingerprint.get() != NULL);
+ }
+
+ if (use_dtls_srtp_ && !(flags & NF_REOFFER)) {
+ // SRTP ciphers will be set only in the beginning.
+ for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it =
+ channels_.begin(); it != channels_.end(); ++it) {
+ std::vector<std::string> ciphers;
+ ciphers.push_back(AES_CM_128_HMAC_SHA1_80);
+ ASSERT_TRUE((*it)->SetSrtpCiphers(ciphers));
+ }
+ }
+
+ std::string transport_type = (protocol_ == cricket::ICEPROTO_GOOGLE) ?
+ cricket::NS_GINGLE_P2P : cricket::NS_JINGLE_ICE_UDP;
+ cricket::TransportDescription local_desc(
+ transport_type, std::vector<std::string>(), kIceUfrag1, kIcePwd1,
+ cricket::ICEMODE_FULL, local_role,
+ // If remote if the offerer and has no DTLS support, answer will be
+ // without any fingerprint.
+ (action == cricket::CA_ANSWER && !remote_identity) ?
+ NULL : local_fingerprint.get(),
+ cricket::Candidates());
+
+ cricket::TransportDescription remote_desc(
+ transport_type, std::vector<std::string>(), kIceUfrag1, kIcePwd1,
+ cricket::ICEMODE_FULL, remote_role, remote_fingerprint.get(),
+ cricket::Candidates());
+
+ bool expect_success = (flags & NF_EXPECT_FAILURE) ? false : true;
+ // If |expect_success| is false, expect SRTD or SLTD to fail when
+ // content action is CA_ANSWER.
+ if (action == cricket::CA_OFFER) {
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(
+ local_desc, cricket::CA_OFFER, NULL));
+ ASSERT_EQ(expect_success, transport_->SetRemoteTransportDescription(
+ remote_desc, cricket::CA_ANSWER, NULL));
+ } else {
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(
+ remote_desc, cricket::CA_OFFER, NULL));
+ ASSERT_EQ(expect_success, transport_->SetLocalTransportDescription(
+ local_desc, cricket::CA_ANSWER, NULL));
+ }
+ negotiated_dtls_ = (local_identity && remote_identity);
+ }
+
+ bool Connect(DtlsTestClient* peer) {
+ transport_->ConnectChannels();
+ transport_->SetDestination(peer->transport_.get());
+ return true;
+ }
+
+ bool writable() const { return transport_->writable(); }
+
+ void CheckRole(rtc::SSLRole role) {
+ if (role == rtc::SSL_CLIENT) {
+ ASSERT_FALSE(received_dtls_client_hello_);
+ ASSERT_TRUE(received_dtls_server_hello_);
+ } else {
+ ASSERT_TRUE(received_dtls_client_hello_);
+ ASSERT_FALSE(received_dtls_server_hello_);
+ }
+ }
+
+ void CheckSrtp(const std::string& expected_cipher) {
+ for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it =
+ channels_.begin(); it != channels_.end(); ++it) {
+ std::string cipher;
+
+ bool rv = (*it)->GetSrtpCipher(&cipher);
+ if (negotiated_dtls_ && !expected_cipher.empty()) {
+ ASSERT_TRUE(rv);
+
+ ASSERT_EQ(cipher, expected_cipher);
+ } else {
+ ASSERT_FALSE(rv);
+ }
+ }
+ }
+
+ void SendPackets(size_t channel, size_t size, size_t count, bool srtp) {
+ ASSERT(channel < channels_.size());
+ rtc::scoped_ptr<char[]> packet(new char[size]);
+ size_t sent = 0;
+ do {
+ // Fill the packet with a known value and a sequence number to check
+ // against, and make sure that it doesn't look like DTLS.
+ memset(packet.get(), sent & 0xff, size);
+ packet[0] = (srtp) ? 0x80 : 0x00;
+ rtc::SetBE32(packet.get() + kPacketNumOffset,
+ static_cast<uint32>(sent));
+
+ // Only set the bypass flag if we've activated DTLS.
+ int flags = (identity_.get() && srtp) ? cricket::PF_SRTP_BYPASS : 0;
+ rtc::PacketOptions packet_options;
+ int rv = channels_[channel]->SendPacket(
+ packet.get(), size, packet_options, flags);
+ ASSERT_GT(rv, 0);
+ ASSERT_EQ(size, static_cast<size_t>(rv));
+ ++sent;
+ } while (sent < count);
+ }
+
+ int SendInvalidSrtpPacket(size_t channel, size_t size) {
+ ASSERT(channel < channels_.size());
+ rtc::scoped_ptr<char[]> packet(new char[size]);
+ // Fill the packet with 0 to form an invalid SRTP packet.
+ memset(packet.get(), 0, size);
+
+ rtc::PacketOptions packet_options;
+ return channels_[channel]->SendPacket(
+ packet.get(), size, packet_options, cricket::PF_SRTP_BYPASS);
+ }
+
+ void ExpectPackets(size_t channel, size_t size) {
+ packet_size_ = size;
+ received_.clear();
+ }
+
+ size_t NumPacketsReceived() {
+ return received_.size();
+ }
+
+ bool VerifyPacket(const char* data, size_t size, uint32* out_num) {
+ if (size != packet_size_ ||
+ (data[0] != 0 && static_cast<uint8>(data[0]) != 0x80)) {
+ return false;
+ }
+ uint32 packet_num = rtc::GetBE32(data + kPacketNumOffset);
+ for (size_t i = kPacketHeaderLen; i < size; ++i) {
+ if (static_cast<uint8>(data[i]) != (packet_num & 0xff)) {
+ return false;
+ }
+ }
+ if (out_num) {
+ *out_num = packet_num;
+ }
+ return true;
+ }
+ bool VerifyEncryptedPacket(const char* data, size_t size) {
+ // This is an encrypted data packet; let's make sure it's mostly random;
+ // less than 10% of the bytes should be equal to the cleartext packet.
+ if (size <= packet_size_) {
+ return false;
+ }
+ uint32 packet_num = rtc::GetBE32(data + kPacketNumOffset);
+ int num_matches = 0;
+ for (size_t i = kPacketNumOffset; i < size; ++i) {
+ if (static_cast<uint8>(data[i]) == (packet_num & 0xff)) {
+ ++num_matches;
+ }
+ }
+ return (num_matches < ((static_cast<int>(size) - 5) / 10));
+ }
+
+ // Transport callbacks
+ void OnTransportWritableState(cricket::Transport* transport) {
+ LOG(LS_INFO) << name_ << ": is writable";
+ }
+
+ // Transport channel callbacks
+ void OnTransportChannelWritableState(cricket::TransportChannel* channel) {
+ LOG(LS_INFO) << name_ << ": Channel '" << channel->component()
+ << "' is writable";
+ }
+
+ void OnTransportChannelReadPacket(cricket::TransportChannel* channel,
+ const char* data, size_t size,
+ const rtc::PacketTime& packet_time,
+ int flags) {
+ uint32 packet_num = 0;
+ ASSERT_TRUE(VerifyPacket(data, size, &packet_num));
+ received_.insert(packet_num);
+ // Only DTLS-SRTP packets should have the bypass flag set.
+ int expected_flags = (identity_.get() && IsRtpLeadByte(data[0])) ?
+ cricket::PF_SRTP_BYPASS : 0;
+ ASSERT_EQ(expected_flags, flags);
+ }
+
+ // Hook into the raw packet stream to make sure DTLS packets are encrypted.
+ void OnFakeTransportChannelReadPacket(cricket::TransportChannel* channel,
+ const char* data, size_t size,
+ const rtc::PacketTime& time,
+ int flags) {
+ // Flags shouldn't be set on the underlying TransportChannel packets.
+ ASSERT_EQ(0, flags);
+
+ // Look at the handshake packets to see what role we played.
+ // Check that non-handshake packets are DTLS data or SRTP bypass.
+ if (negotiated_dtls_) {
+ if (data[0] == 22 && size > 17) {
+ if (data[13] == 1) {
+ received_dtls_client_hello_ = true;
+ } else if (data[13] == 2) {
+ received_dtls_server_hello_ = true;
+ }
+ } else if (!(data[0] >= 20 && data[0] <= 22)) {
+ ASSERT_TRUE(data[0] == 23 || IsRtpLeadByte(data[0]));
+ if (data[0] == 23) {
+ ASSERT_TRUE(VerifyEncryptedPacket(data, size));
+ } else if (IsRtpLeadByte(data[0])) {
+ ASSERT_TRUE(VerifyPacket(data, size, NULL));
+ }
+ }
+ }
+ }
+
+ private:
+ std::string name_;
+ rtc::Thread* signaling_thread_;
+ rtc::Thread* worker_thread_;
+ cricket::TransportProtocol protocol_;
+ rtc::scoped_ptr<rtc::SSLIdentity> identity_;
+ rtc::scoped_ptr<cricket::FakeTransport> transport_;
+ std::vector<cricket::DtlsTransportChannelWrapper*> channels_;
+ size_t packet_size_;
+ std::set<int> received_;
+ bool use_dtls_srtp_;
+ bool negotiated_dtls_;
+ bool received_dtls_client_hello_;
+ bool received_dtls_server_hello_;
+};
+
+
+class DtlsTransportChannelTest : public testing::Test {
+ public:
+ DtlsTransportChannelTest() :
+ client1_("P1", rtc::Thread::Current(),
+ rtc::Thread::Current()),
+ client2_("P2", rtc::Thread::Current(),
+ rtc::Thread::Current()),
+ channel_ct_(1),
+ use_dtls_(false),
+ use_dtls_srtp_(false) {
+ }
+
+ void SetChannelCount(size_t channel_ct) {
+ channel_ct_ = static_cast<int>(channel_ct);
+ }
+ void PrepareDtls(bool c1, bool c2) {
+ if (c1) {
+ client1_.CreateIdentity();
+ }
+ if (c2) {
+ client2_.CreateIdentity();
+ }
+ if (c1 && c2)
+ use_dtls_ = true;
+ }
+ void PrepareDtlsSrtp(bool c1, bool c2) {
+ if (!use_dtls_)
+ return;
+
+ if (c1)
+ client1_.SetupSrtp();
+ if (c2)
+ client2_.SetupSrtp();
+
+ if (c1 && c2)
+ use_dtls_srtp_ = true;
+ }
+
+ bool Connect(ConnectionRole client1_role, ConnectionRole client2_role) {
+ Negotiate(client1_role, client2_role);
+
+ bool rv = client1_.Connect(&client2_);
+ EXPECT_TRUE(rv);
+ if (!rv)
+ return false;
+
+ EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000);
+ if (!client1_.writable() || !client2_.writable())
+ return false;
+
+ // Check that we used the right roles.
+ if (use_dtls_) {
+ rtc::SSLRole client1_ssl_role =
+ (client1_role == cricket::CONNECTIONROLE_ACTIVE ||
+ (client2_role == cricket::CONNECTIONROLE_PASSIVE &&
+ client1_role == cricket::CONNECTIONROLE_ACTPASS)) ?
+ rtc::SSL_CLIENT : rtc::SSL_SERVER;
+
+ rtc::SSLRole client2_ssl_role =
+ (client2_role == cricket::CONNECTIONROLE_ACTIVE ||
+ (client1_role == cricket::CONNECTIONROLE_PASSIVE &&
+ client2_role == cricket::CONNECTIONROLE_ACTPASS)) ?
+ rtc::SSL_CLIENT : rtc::SSL_SERVER;
+
+ client1_.CheckRole(client1_ssl_role);
+ client2_.CheckRole(client2_ssl_role);
+ }
+
+ // Check that we negotiated the right ciphers.
+ if (use_dtls_srtp_) {
+ client1_.CheckSrtp(AES_CM_128_HMAC_SHA1_80);
+ client2_.CheckSrtp(AES_CM_128_HMAC_SHA1_80);
+ } else {
+ client1_.CheckSrtp("");
+ client2_.CheckSrtp("");
+ }
+
+ return true;
+ }
+
+ bool Connect() {
+ // By default, Client1 will be Server and Client2 will be Client.
+ return Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE);
+ }
+
+ void Negotiate() {
+ Negotiate(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE);
+ }
+
+ void Negotiate(ConnectionRole client1_role, ConnectionRole client2_role) {
+ client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING);
+ client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED);
+ // Expect success from SLTD and SRTD.
+ client1_.Negotiate(&client2_, cricket::CA_OFFER,
+ client1_role, client2_role, 0);
+ client2_.Negotiate(&client1_, cricket::CA_ANSWER,
+ client2_role, client1_role, 0);
+ }
+
+ // Negotiate with legacy client |client2|. Legacy client doesn't use setup
+ // attributes, except NONE.
+ void NegotiateWithLegacy() {
+ client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING);
+ client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED);
+ // Expect success from SLTD and SRTD.
+ client1_.Negotiate(&client2_, cricket::CA_OFFER,
+ cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_NONE, 0);
+ client2_.Negotiate(&client1_, cricket::CA_ANSWER,
+ cricket::CONNECTIONROLE_ACTIVE,
+ cricket::CONNECTIONROLE_NONE, 0);
+ }
+
+ void Renegotiate(DtlsTestClient* reoffer_initiator,
+ ConnectionRole client1_role, ConnectionRole client2_role,
+ int flags) {
+ if (reoffer_initiator == &client1_) {
+ client1_.Negotiate(&client2_, cricket::CA_OFFER,
+ client1_role, client2_role, flags);
+ client2_.Negotiate(&client1_, cricket::CA_ANSWER,
+ client2_role, client1_role, flags);
+ } else {
+ client2_.Negotiate(&client1_, cricket::CA_OFFER,
+ client2_role, client1_role, flags);
+ client1_.Negotiate(&client2_, cricket::CA_ANSWER,
+ client1_role, client2_role, flags);
+ }
+ }
+
+ void TestTransfer(size_t channel, size_t size, size_t count, bool srtp) {
+ LOG(LS_INFO) << "Expect packets, size=" << size;
+ client2_.ExpectPackets(channel, size);
+ client1_.SendPackets(channel, size, count, srtp);
+ EXPECT_EQ_WAIT(count, client2_.NumPacketsReceived(), 10000);
+ }
+
+ protected:
+ DtlsTestClient client1_;
+ DtlsTestClient client2_;
+ int channel_ct_;
+ bool use_dtls_;
+ bool use_dtls_srtp_;
+};
+
+// Test that transport negotiation of ICE, no DTLS works properly.
+TEST_F(DtlsTransportChannelTest, TestChannelSetupIce) {
+ client1_.SetIceProtocol(cricket::ICEPROTO_RFC5245);
+ client2_.SetIceProtocol(cricket::ICEPROTO_RFC5245);
+ Negotiate();
+ cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0);
+ cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0);
+ ASSERT_TRUE(channel1 != NULL);
+ ASSERT_TRUE(channel2 != NULL);
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole());
+ EXPECT_EQ(1U, channel1->IceTiebreaker());
+ EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel1->protocol());
+ EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag());
+ EXPECT_EQ(kIcePwd1, channel1->ice_pwd());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole());
+ EXPECT_EQ(2U, channel2->IceTiebreaker());
+ EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel2->protocol());
+}
+
+// Test that transport negotiation of GICE, no DTLS works properly.
+TEST_F(DtlsTransportChannelTest, TestChannelSetupGice) {
+ client1_.SetIceProtocol(cricket::ICEPROTO_GOOGLE);
+ client2_.SetIceProtocol(cricket::ICEPROTO_GOOGLE);
+ Negotiate();
+ cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0);
+ cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0);
+ ASSERT_TRUE(channel1 != NULL);
+ ASSERT_TRUE(channel2 != NULL);
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole());
+ EXPECT_EQ(1U, channel1->IceTiebreaker());
+ EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel1->protocol());
+ EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag());
+ EXPECT_EQ(kIcePwd1, channel1->ice_pwd());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole());
+ EXPECT_EQ(2U, channel2->IceTiebreaker());
+ EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel2->protocol());
+}
+
+// Connect without DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransfer) {
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+}
+
+// Create two channels without DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferTwoChannels) {
+ SetChannelCount(2);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+ TestTransfer(1, 1000, 100, false);
+}
+
+// Connect without DTLS, and transfer SRTP data.
+TEST_F(DtlsTransportChannelTest, TestTransferSrtp) {
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, true);
+}
+
+// Create two channels without DTLS, and transfer SRTP data.
+TEST_F(DtlsTransportChannelTest, TestTransferSrtpTwoChannels) {
+ SetChannelCount(2);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Connect with DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtls) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ PrepareDtls(true, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+}
+
+// Create two channels with DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsTwoChannels) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+ TestTransfer(1, 1000, 100, false);
+}
+
+// Connect with A doing DTLS and B not, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsRejected) {
+ PrepareDtls(true, false);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+}
+
+// Connect with B doing DTLS and A not, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsNotOffered) {
+ PrepareDtls(false, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+}
+
+// Connect with DTLS, negotiate DTLS-SRTP, and transfer SRTP using bypass.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, true);
+}
+
+// Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1
+// returned.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect());
+ int result = client1_.SendInvalidSrtpPacket(0, 100);
+ ASSERT_EQ(-1, result);
+}
+
+// Connect with DTLS. A does DTLS-SRTP but B does not.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, false);
+ ASSERT_TRUE(Connect());
+}
+
+// Connect with DTLS. B does DTLS-SRTP but A does not.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpNotOffered) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(false, true);
+ ASSERT_TRUE(Connect());
+}
+
+// Create two channels with DTLS, negotiate DTLS-SRTP, and transfer bypass SRTP.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Create a single channel with DTLS, and send normal data and SRTP data on it.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect());
+ TestTransfer(0, 1000, 100, false);
+ TestTransfer(0, 1000, 100, true);
+}
+
+// Testing when the remote is passive.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsAnswererIsPassive) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_PASSIVE));
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Testing with the legacy DTLS client which doesn't use setup attribute.
+// In this case legacy is the answerer.
+TEST_F(DtlsTransportChannelTest, TestDtlsSetupWithLegacyAsAnswerer) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ PrepareDtls(true, true);
+ NegotiateWithLegacy();
+ rtc::SSLRole channel1_role;
+ rtc::SSLRole channel2_role;
+ EXPECT_TRUE(client1_.transport()->GetSslRole(&channel1_role));
+ EXPECT_TRUE(client2_.transport()->GetSslRole(&channel2_role));
+ EXPECT_EQ(rtc::SSL_SERVER, channel1_role);
+ EXPECT_EQ(rtc::SSL_CLIENT, channel2_role);
+}
+
+// Testing re offer/answer after the session is estbalished. Roles will be
+// kept same as of the previous negotiation.
+TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromOfferer) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ // Initial role for client1 is ACTPASS and client2 is ACTIVE.
+ ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE));
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+ // Using input roles for the re-offer.
+ Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER);
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromAnswerer) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ // Initial role for client1 is ACTPASS and client2 is ACTIVE.
+ ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE));
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+ // Using input roles for the re-offer.
+ Renegotiate(&client2_, cricket::CONNECTIONROLE_PASSIVE,
+ cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER);
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Test that any change in role after the intial setup will result in failure.
+TEST_F(DtlsTransportChannelTest, TestDtlsRoleReversal) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_PASSIVE));
+
+ // Renegotiate from client2 with actpass and client1 as active.
+ Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE,
+ NF_REOFFER | NF_EXPECT_FAILURE);
+}
+
+// Test that using different setup attributes which results in similar ssl
+// role as the initial negotiation will result in success.
+TEST_F(DtlsTransportChannelTest, TestDtlsReOfferWithDifferentSetupAttr) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_PASSIVE));
+ // Renegotiate from client2 with actpass and client1 as active.
+ Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTIVE,
+ cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER);
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Test that re-negotiation can be started before the clients become connected
+// in the first negotiation.
+TEST_F(DtlsTransportChannelTest, TestRenegotiateBeforeConnect) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ SetChannelCount(2);
+ PrepareDtls(true, true);
+ PrepareDtlsSrtp(true, true);
+ Negotiate();
+
+ Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS,
+ cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER);
+ bool rv = client1_.Connect(&client2_);
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000);
+
+ TestTransfer(0, 1000, 100, true);
+ TestTransfer(1, 1000, 100, true);
+}
+
+// Test Certificates state after negotiation but before connection.
+TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ PrepareDtls(true, true);
+ Negotiate();
+
+ rtc::scoped_ptr<rtc::SSLIdentity> identity1;
+ rtc::scoped_ptr<rtc::SSLIdentity> identity2;
+ rtc::scoped_ptr<rtc::SSLCertificate> remote_cert1;
+ rtc::scoped_ptr<rtc::SSLCertificate> remote_cert2;
+
+ // After negotiation, each side has a distinct local certificate, but still no
+ // remote certificate, because connection has not yet occurred.
+ ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept()));
+ ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept()));
+ ASSERT_NE(identity1->certificate().ToPEMString(),
+ identity2->certificate().ToPEMString());
+ ASSERT_FALSE(
+ client1_.transport()->GetRemoteCertificate(remote_cert1.accept()));
+ ASSERT_FALSE(remote_cert1 != NULL);
+ ASSERT_FALSE(
+ client2_.transport()->GetRemoteCertificate(remote_cert2.accept()));
+ ASSERT_FALSE(remote_cert2 != NULL);
+}
+
+// Test Certificates state after connection.
+TEST_F(DtlsTransportChannelTest, TestCertificatesAfterConnect) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ PrepareDtls(true, true);
+ ASSERT_TRUE(Connect());
+
+ rtc::scoped_ptr<rtc::SSLIdentity> identity1;
+ rtc::scoped_ptr<rtc::SSLIdentity> identity2;
+ rtc::scoped_ptr<rtc::SSLCertificate> remote_cert1;
+ rtc::scoped_ptr<rtc::SSLCertificate> remote_cert2;
+
+ // After connection, each side has a distinct local certificate.
+ ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept()));
+ ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept()));
+ ASSERT_NE(identity1->certificate().ToPEMString(),
+ identity2->certificate().ToPEMString());
+
+ // Each side's remote certificate is the other side's local certificate.
+ ASSERT_TRUE(
+ client1_.transport()->GetRemoteCertificate(remote_cert1.accept()));
+ ASSERT_EQ(remote_cert1->ToPEMString(),
+ identity2->certificate().ToPEMString());
+ ASSERT_TRUE(
+ client2_.transport()->GetRemoteCertificate(remote_cert2.accept()));
+ ASSERT_EQ(remote_cert2->ToPEMString(),
+ identity1->certificate().ToPEMString());
+}
diff --git a/p2p/base/fakesession.h b/p2p/base/fakesession.h
new file mode 100644
index 00000000..30bc9816
--- /dev/null
+++ b/p2p/base/fakesession.h
@@ -0,0 +1,492 @@
+/*
+ * Copyright 2009 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_FAKESESSION_H_
+#define WEBRTC_P2P_BASE_FAKESESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/base/buffer.h"
+#include "webrtc/base/fakesslidentity.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sslfingerprint.h"
+
+namespace cricket {
+
+class FakeTransport;
+
+struct PacketMessageData : public rtc::MessageData {
+ PacketMessageData(const char* data, size_t len) : packet(data, len) {
+ }
+ rtc::Buffer packet;
+};
+
+// Fake transport channel class, which can be passed to anything that needs a
+// transport channel. Can be informed of another FakeTransportChannel via
+// SetDestination.
+class FakeTransportChannel : public TransportChannelImpl,
+ public rtc::MessageHandler {
+ public:
+ explicit FakeTransportChannel(Transport* transport,
+ const std::string& content_name,
+ int component)
+ : TransportChannelImpl(content_name, component),
+ transport_(transport),
+ dest_(NULL),
+ state_(STATE_INIT),
+ async_(false),
+ identity_(NULL),
+ do_dtls_(false),
+ role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ ice_proto_(ICEPROTO_HYBRID),
+ remote_ice_mode_(ICEMODE_FULL),
+ dtls_fingerprint_("", NULL, 0),
+ ssl_role_(rtc::SSL_CLIENT),
+ connection_count_(0) {
+ }
+ ~FakeTransportChannel() {
+ Reset();
+ }
+
+ uint64 IceTiebreaker() const { return tiebreaker_; }
+ TransportProtocol protocol() const { return ice_proto_; }
+ IceMode remote_ice_mode() const { return remote_ice_mode_; }
+ const std::string& ice_ufrag() const { return ice_ufrag_; }
+ const std::string& ice_pwd() const { return ice_pwd_; }
+ const std::string& remote_ice_ufrag() const { return remote_ice_ufrag_; }
+ const std::string& remote_ice_pwd() const { return remote_ice_pwd_; }
+ const rtc::SSLFingerprint& dtls_fingerprint() const {
+ return dtls_fingerprint_;
+ }
+
+ void SetAsync(bool async) {
+ async_ = async;
+ }
+
+ virtual Transport* GetTransport() {
+ return transport_;
+ }
+
+ virtual void SetIceRole(IceRole role) { role_ = role; }
+ virtual IceRole GetIceRole() const { return role_; }
+ virtual size_t GetConnectionCount() const { return connection_count_; }
+ virtual void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+ virtual bool GetIceProtocolType(IceProtocolType* type) const {
+ *type = ice_proto_;
+ return true;
+ }
+ virtual void SetIceProtocolType(IceProtocolType type) { ice_proto_ = type; }
+ virtual void SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ ice_ufrag_ = ice_ufrag;
+ ice_pwd_ = ice_pwd;
+ }
+ virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ remote_ice_ufrag_ = ice_ufrag;
+ remote_ice_pwd_ = ice_pwd;
+ }
+
+ virtual void SetRemoteIceMode(IceMode mode) { remote_ice_mode_ = mode; }
+ virtual bool SetRemoteFingerprint(const std::string& alg, const uint8* digest,
+ size_t digest_len) {
+ dtls_fingerprint_ = rtc::SSLFingerprint(alg, digest, digest_len);
+ return true;
+ }
+ virtual bool SetSslRole(rtc::SSLRole role) {
+ ssl_role_ = role;
+ return true;
+ }
+ virtual bool GetSslRole(rtc::SSLRole* role) const {
+ *role = ssl_role_;
+ return true;
+ }
+
+ virtual void Connect() {
+ if (state_ == STATE_INIT) {
+ state_ = STATE_CONNECTING;
+ }
+ }
+ virtual void Reset() {
+ if (state_ != STATE_INIT) {
+ state_ = STATE_INIT;
+ if (dest_) {
+ dest_->state_ = STATE_INIT;
+ dest_->dest_ = NULL;
+ dest_ = NULL;
+ }
+ }
+ }
+
+ void SetWritable(bool writable) {
+ set_writable(writable);
+ }
+
+ void SetDestination(FakeTransportChannel* dest) {
+ if (state_ == STATE_CONNECTING && dest) {
+ // This simulates the delivery of candidates.
+ dest_ = dest;
+ dest_->dest_ = this;
+ if (identity_ && dest_->identity_) {
+ do_dtls_ = true;
+ dest_->do_dtls_ = true;
+ NegotiateSrtpCiphers();
+ }
+ state_ = STATE_CONNECTED;
+ dest_->state_ = STATE_CONNECTED;
+ set_writable(true);
+ dest_->set_writable(true);
+ } else if (state_ == STATE_CONNECTED && !dest) {
+ // Simulates loss of connectivity, by asymmetrically forgetting dest_.
+ dest_ = NULL;
+ state_ = STATE_CONNECTING;
+ set_writable(false);
+ }
+ }
+
+ void SetConnectionCount(size_t connection_count) {
+ size_t old_connection_count = connection_count_;
+ connection_count_ = connection_count;
+ if (connection_count_ < old_connection_count)
+ SignalConnectionRemoved(this);
+ }
+
+ virtual int SendPacket(const char* data, size_t len,
+ const rtc::PacketOptions& options, int flags) {
+ if (state_ != STATE_CONNECTED) {
+ return -1;
+ }
+
+ if (flags != PF_SRTP_BYPASS && flags != 0) {
+ return -1;
+ }
+
+ PacketMessageData* packet = new PacketMessageData(data, len);
+ if (async_) {
+ rtc::Thread::Current()->Post(this, 0, packet);
+ } else {
+ rtc::Thread::Current()->Send(this, 0, packet);
+ }
+ return static_cast<int>(len);
+ }
+ virtual int SetOption(rtc::Socket::Option opt, int value) {
+ return true;
+ }
+ virtual int GetError() {
+ return 0;
+ }
+
+ virtual void OnSignalingReady() {
+ }
+ virtual void OnCandidate(const Candidate& candidate) {
+ }
+
+ virtual void OnMessage(rtc::Message* msg) {
+ PacketMessageData* data = static_cast<PacketMessageData*>(
+ msg->pdata);
+ dest_->SignalReadPacket(dest_, data->packet.data(),
+ data->packet.length(),
+ rtc::CreatePacketTime(0), 0);
+ delete data;
+ }
+
+ bool SetLocalIdentity(rtc::SSLIdentity* identity) {
+ identity_ = identity;
+ return true;
+ }
+
+
+ void SetRemoteCertificate(rtc::FakeSSLCertificate* cert) {
+ remote_cert_ = cert;
+ }
+
+ virtual bool IsDtlsActive() const {
+ return do_dtls_;
+ }
+
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+ srtp_ciphers_ = ciphers;
+ return true;
+ }
+
+ virtual bool GetSrtpCipher(std::string* cipher) {
+ if (!chosen_srtp_cipher_.empty()) {
+ *cipher = chosen_srtp_cipher_;
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
+ if (!identity_)
+ return false;
+
+ *identity = identity_->GetReference();
+ return true;
+ }
+
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const {
+ if (!remote_cert_)
+ return false;
+
+ *cert = remote_cert_->GetReference();
+ return true;
+ }
+
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ if (!chosen_srtp_cipher_.empty()) {
+ memset(result, 0xff, result_len);
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual void NegotiateSrtpCiphers() {
+ for (std::vector<std::string>::const_iterator it1 = srtp_ciphers_.begin();
+ it1 != srtp_ciphers_.end(); ++it1) {
+ for (std::vector<std::string>::const_iterator it2 =
+ dest_->srtp_ciphers_.begin();
+ it2 != dest_->srtp_ciphers_.end(); ++it2) {
+ if (*it1 == *it2) {
+ chosen_srtp_cipher_ = *it1;
+ dest_->chosen_srtp_cipher_ = *it2;
+ return;
+ }
+ }
+ }
+ }
+
+ virtual bool GetStats(ConnectionInfos* infos) OVERRIDE {
+ ConnectionInfo info;
+ infos->clear();
+ infos->push_back(info);
+ return true;
+ }
+
+ private:
+ enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED };
+ Transport* transport_;
+ FakeTransportChannel* dest_;
+ State state_;
+ bool async_;
+ rtc::SSLIdentity* identity_;
+ rtc::FakeSSLCertificate* remote_cert_;
+ bool do_dtls_;
+ std::vector<std::string> srtp_ciphers_;
+ std::string chosen_srtp_cipher_;
+ IceRole role_;
+ uint64 tiebreaker_;
+ IceProtocolType ice_proto_;
+ std::string ice_ufrag_;
+ std::string ice_pwd_;
+ std::string remote_ice_ufrag_;
+ std::string remote_ice_pwd_;
+ IceMode remote_ice_mode_;
+ rtc::SSLFingerprint dtls_fingerprint_;
+ rtc::SSLRole ssl_role_;
+ size_t connection_count_;
+};
+
+// Fake transport class, which can be passed to anything that needs a Transport.
+// Can be informed of another FakeTransport via SetDestination (low-tech way
+// of doing candidates)
+class FakeTransport : public Transport {
+ public:
+ typedef std::map<int, FakeTransportChannel*> ChannelMap;
+ FakeTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* alllocator = NULL)
+ : Transport(signaling_thread, worker_thread,
+ content_name, "test_type", NULL),
+ dest_(NULL),
+ async_(false),
+ identity_(NULL) {
+ }
+ ~FakeTransport() {
+ DestroyAllChannels();
+ }
+
+ const ChannelMap& channels() const { return channels_; }
+
+ void SetAsync(bool async) { async_ = async; }
+ void SetDestination(FakeTransport* dest) {
+ dest_ = dest;
+ for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+ ++it) {
+ it->second->SetLocalIdentity(identity_);
+ SetChannelDestination(it->first, it->second);
+ }
+ }
+
+ void SetWritable(bool writable) {
+ for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+ ++it) {
+ it->second->SetWritable(writable);
+ }
+ }
+
+ void set_identity(rtc::SSLIdentity* identity) {
+ identity_ = identity;
+ }
+
+ using Transport::local_description;
+ using Transport::remote_description;
+
+ protected:
+ virtual TransportChannelImpl* CreateTransportChannel(int component) {
+ if (channels_.find(component) != channels_.end()) {
+ return NULL;
+ }
+ FakeTransportChannel* channel =
+ new FakeTransportChannel(this, content_name(), component);
+ channel->SetAsync(async_);
+ SetChannelDestination(component, channel);
+ channels_[component] = channel;
+ return channel;
+ }
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+ channels_.erase(channel->component());
+ delete channel;
+ }
+ virtual void SetIdentity_w(rtc::SSLIdentity* identity) {
+ identity_ = identity;
+ }
+ virtual bool GetIdentity_w(rtc::SSLIdentity** identity) {
+ if (!identity_)
+ return false;
+
+ *identity = identity_->GetReference();
+ return true;
+ }
+
+ private:
+ FakeTransportChannel* GetFakeChannel(int component) {
+ ChannelMap::iterator it = channels_.find(component);
+ return (it != channels_.end()) ? it->second : NULL;
+ }
+ void SetChannelDestination(int component,
+ FakeTransportChannel* channel) {
+ FakeTransportChannel* dest_channel = NULL;
+ if (dest_) {
+ dest_channel = dest_->GetFakeChannel(component);
+ if (dest_channel) {
+ dest_channel->SetLocalIdentity(dest_->identity_);
+ }
+ }
+ channel->SetDestination(dest_channel);
+ }
+
+ // Note, this is distinct from the Channel map owned by Transport.
+ // This map just tracks the FakeTransportChannels created by this class.
+ ChannelMap channels_;
+ FakeTransport* dest_;
+ bool async_;
+ rtc::SSLIdentity* identity_;
+};
+
+// Fake session class, which can be passed into a BaseChannel object for
+// test purposes. Can be connected to other FakeSessions via Connect().
+class FakeSession : public BaseSession {
+ public:
+ explicit FakeSession()
+ : BaseSession(rtc::Thread::Current(),
+ rtc::Thread::Current(),
+ NULL, "", "", true),
+ fail_create_channel_(false) {
+ }
+ explicit FakeSession(bool initiator)
+ : BaseSession(rtc::Thread::Current(),
+ rtc::Thread::Current(),
+ NULL, "", "", initiator),
+ fail_create_channel_(false) {
+ }
+ FakeSession(rtc::Thread* worker_thread, bool initiator)
+ : BaseSession(rtc::Thread::Current(),
+ worker_thread,
+ NULL, "", "", initiator),
+ fail_create_channel_(false) {
+ }
+
+ FakeTransport* GetTransport(const std::string& content_name) {
+ return static_cast<FakeTransport*>(
+ BaseSession::GetTransport(content_name));
+ }
+
+ void Connect(FakeSession* dest) {
+ // Simulate the exchange of candidates.
+ CompleteNegotiation();
+ dest->CompleteNegotiation();
+ for (TransportMap::const_iterator it = transport_proxies().begin();
+ it != transport_proxies().end(); ++it) {
+ static_cast<FakeTransport*>(it->second->impl())->SetDestination(
+ dest->GetTransport(it->first));
+ }
+ }
+
+ virtual TransportChannel* CreateChannel(
+ const std::string& content_name,
+ const std::string& channel_name,
+ int component) {
+ if (fail_create_channel_) {
+ return NULL;
+ }
+ return BaseSession::CreateChannel(content_name, channel_name, component);
+ }
+
+ void set_fail_channel_creation(bool fail_channel_creation) {
+ fail_create_channel_ = fail_channel_creation;
+ }
+
+ // TODO: Hoist this into Session when we re-work the Session code.
+ void set_ssl_identity(rtc::SSLIdentity* identity) {
+ for (TransportMap::const_iterator it = transport_proxies().begin();
+ it != transport_proxies().end(); ++it) {
+ // We know that we have a FakeTransport*
+
+ static_cast<FakeTransport*>(it->second->impl())->set_identity
+ (identity);
+ }
+ }
+
+ protected:
+ virtual Transport* CreateTransport(const std::string& content_name) {
+ return new FakeTransport(signaling_thread(), worker_thread(), content_name);
+ }
+
+ void CompleteNegotiation() {
+ for (TransportMap::const_iterator it = transport_proxies().begin();
+ it != transport_proxies().end(); ++it) {
+ it->second->CompleteNegotiation();
+ it->second->ConnectChannels();
+ }
+ }
+
+ private:
+ bool fail_create_channel_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_FAKESESSION_H_
diff --git a/p2p/base/p2ptransport.cc b/p2p/base/p2ptransport.cc
new file mode 100644
index 00000000..e873756f
--- /dev/null
+++ b/p2p/base/p2ptransport.cc
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/p2ptransport.h"
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/p2ptransportchannel.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/base64.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/stringencode.h"
+#include "webrtc/base/stringutils.h"
+
+namespace {
+
+// Limits for GICE and ICE username sizes.
+const size_t kMaxGiceUsernameSize = 16;
+const size_t kMaxIceUsernameSize = 512;
+
+} // namespace
+
+namespace cricket {
+
+static buzz::XmlElement* NewTransportElement(const std::string& name) {
+ return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
+}
+
+P2PTransport::P2PTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* allocator)
+ : Transport(signaling_thread, worker_thread,
+ content_name, NS_GINGLE_P2P, allocator) {
+}
+
+P2PTransport::~P2PTransport() {
+ DestroyAllChannels();
+}
+
+TransportChannelImpl* P2PTransport::CreateTransportChannel(int component) {
+ return new P2PTransportChannel(content_name(), component, this,
+ port_allocator());
+}
+
+void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+ delete channel;
+}
+
+bool P2PTransportParser::ParseTransportDescription(
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ TransportDescription* desc,
+ ParseError* error) {
+ ASSERT(elem->Name().LocalPart() == LN_TRANSPORT);
+ desc->transport_type = elem->Name().Namespace();
+ if (desc->transport_type != NS_GINGLE_P2P)
+ return BadParse("Unsupported transport type", error);
+
+ for (const buzz::XmlElement* candidate_elem = elem->FirstElement();
+ candidate_elem != NULL;
+ candidate_elem = candidate_elem->NextElement()) {
+ // Only look at local part because the namespace might (eventually)
+ // be NS_GINGLE_P2P or NS_JINGLE_ICE_UDP.
+ if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+ Candidate candidate;
+ if (!ParseCandidate(ICEPROTO_GOOGLE, candidate_elem, translator,
+ &candidate, error)) {
+ return false;
+ }
+
+ desc->candidates.push_back(candidate);
+ }
+ }
+ return true;
+}
+
+bool P2PTransportParser::WriteTransportDescription(
+ const TransportDescription& desc,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** out_elem,
+ WriteError* error) {
+ TransportProtocol proto = TransportProtocolFromDescription(&desc);
+ rtc::scoped_ptr<buzz::XmlElement> trans_elem(
+ NewTransportElement(desc.transport_type));
+
+ // Fail if we get HYBRID or ICE right now.
+ // TODO(juberti): Add ICE and HYBRID serialization.
+ if (proto != ICEPROTO_GOOGLE) {
+ LOG(LS_ERROR) << "Failed to serialize non-GICE TransportDescription";
+ return false;
+ }
+
+ for (std::vector<Candidate>::const_iterator iter = desc.candidates.begin();
+ iter != desc.candidates.end(); ++iter) {
+ rtc::scoped_ptr<buzz::XmlElement> cand_elem(
+ new buzz::XmlElement(QN_GINGLE_P2P_CANDIDATE));
+ if (!WriteCandidate(proto, *iter, translator, cand_elem.get(), error)) {
+ return false;
+ }
+ trans_elem->AddElement(cand_elem.release());
+ }
+
+ *out_elem = trans_elem.release();
+ return true;
+}
+
+bool P2PTransportParser::ParseGingleCandidate(
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidate* candidate,
+ ParseError* error) {
+ return ParseCandidate(ICEPROTO_GOOGLE, elem, translator, candidate, error);
+}
+
+bool P2PTransportParser::WriteGingleCandidate(
+ const Candidate& candidate,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** out_elem,
+ WriteError* error) {
+ rtc::scoped_ptr<buzz::XmlElement> elem(
+ new buzz::XmlElement(QN_GINGLE_CANDIDATE));
+ bool ret = WriteCandidate(ICEPROTO_GOOGLE, candidate, translator, elem.get(),
+ error);
+ if (ret) {
+ *out_elem = elem.release();
+ }
+ return ret;
+}
+
+bool P2PTransportParser::VerifyUsernameFormat(TransportProtocol proto,
+ const std::string& username,
+ ParseError* error) {
+ if (proto == ICEPROTO_GOOGLE || proto == ICEPROTO_HYBRID) {
+ if (username.size() > kMaxGiceUsernameSize)
+ return BadParse("candidate username is too long", error);
+ if (!rtc::Base64::IsBase64Encoded(username))
+ return BadParse("candidate username has non-base64 encoded characters",
+ error);
+ } else if (proto == ICEPROTO_RFC5245) {
+ if (username.size() > kMaxIceUsernameSize)
+ return BadParse("candidate username is too long", error);
+ }
+ return true;
+}
+
+bool P2PTransportParser::ParseCandidate(TransportProtocol proto,
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidate* candidate,
+ ParseError* error) {
+ ASSERT(proto == ICEPROTO_GOOGLE);
+ ASSERT(translator != NULL);
+
+ if (!elem->HasAttr(buzz::QN_NAME) ||
+ !elem->HasAttr(QN_ADDRESS) ||
+ !elem->HasAttr(QN_PORT) ||
+ !elem->HasAttr(QN_USERNAME) ||
+ !elem->HasAttr(QN_PROTOCOL) ||
+ !elem->HasAttr(QN_GENERATION)) {
+ return BadParse("candidate missing required attribute", error);
+ }
+
+ rtc::SocketAddress address;
+ if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, &address, error))
+ return false;
+
+ std::string channel_name = elem->Attr(buzz::QN_NAME);
+ int component = 0;
+ if (!translator ||
+ !translator->GetComponentFromChannelName(channel_name, &component)) {
+ return BadParse("candidate has unknown channel name " + channel_name,
+ error);
+ }
+
+ float preference = 0.0;
+ if (!GetXmlAttr(elem, QN_PREFERENCE, 0.0f, &preference)) {
+ return BadParse("candidate has unknown preference", error);
+ }
+
+ candidate->set_component(component);
+ candidate->set_address(address);
+ candidate->set_username(elem->Attr(QN_USERNAME));
+ candidate->set_preference(preference);
+ candidate->set_protocol(elem->Attr(QN_PROTOCOL));
+ candidate->set_generation_str(elem->Attr(QN_GENERATION));
+ if (elem->HasAttr(QN_PASSWORD))
+ candidate->set_password(elem->Attr(QN_PASSWORD));
+ if (elem->HasAttr(buzz::QN_TYPE))
+ candidate->set_type(elem->Attr(buzz::QN_TYPE));
+ if (elem->HasAttr(QN_NETWORK))
+ candidate->set_network_name(elem->Attr(QN_NETWORK));
+
+ if (!VerifyUsernameFormat(proto, candidate->username(), error))
+ return false;
+
+ return true;
+}
+
+bool P2PTransportParser::WriteCandidate(TransportProtocol proto,
+ const Candidate& candidate,
+ const CandidateTranslator* translator,
+ buzz::XmlElement* elem,
+ WriteError* error) {
+ ASSERT(proto == ICEPROTO_GOOGLE);
+ ASSERT(translator != NULL);
+
+ std::string channel_name;
+ if (!translator ||
+ !translator->GetChannelNameFromComponent(
+ candidate.component(), &channel_name)) {
+ return BadWrite("Cannot write candidate because of unknown component.",
+ error);
+ }
+
+ elem->SetAttr(buzz::QN_NAME, channel_name);
+ elem->SetAttr(QN_ADDRESS, candidate.address().ipaddr().ToString());
+ elem->SetAttr(QN_PORT, candidate.address().PortAsString());
+ AddXmlAttr(elem, QN_PREFERENCE, candidate.preference());
+ elem->SetAttr(QN_USERNAME, candidate.username());
+ elem->SetAttr(QN_PROTOCOL, candidate.protocol());
+ elem->SetAttr(QN_GENERATION, candidate.generation_str());
+ if (!candidate.password().empty())
+ elem->SetAttr(QN_PASSWORD, candidate.password());
+ elem->SetAttr(buzz::QN_TYPE, candidate.type());
+ if (!candidate.network_name().empty())
+ elem->SetAttr(QN_NETWORK, candidate.network_name());
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/p2p/base/p2ptransport.h b/p2p/base/p2ptransport.h
new file mode 100644
index 00000000..efc65991
--- /dev/null
+++ b/p2p/base/p2ptransport.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_P2PTRANSPORT_H_
+#define WEBRTC_P2P_BASE_P2PTRANSPORT_H_
+
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/transport.h"
+
+namespace cricket {
+
+class P2PTransport : public Transport {
+ public:
+ P2PTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* allocator);
+ virtual ~P2PTransport();
+
+ protected:
+ // Creates and destroys P2PTransportChannel.
+ virtual TransportChannelImpl* CreateTransportChannel(int component);
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ friend class P2PTransportChannel;
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransport);
+};
+
+class P2PTransportParser : public TransportParser {
+ public:
+ P2PTransportParser() {}
+ // Translator may be null, in which case ParseCandidates should
+ // return false if there are candidates to parse. We can't not call
+ // ParseCandidates because there's no way to know ahead of time if
+ // there are candidates or not.
+
+ // Jingle-specific functions; can be used with either ICE, GICE, or HYBRID.
+ virtual bool ParseTransportDescription(const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ TransportDescription* desc,
+ ParseError* error);
+ virtual bool WriteTransportDescription(const TransportDescription& desc,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** elem,
+ WriteError* error);
+
+ // Legacy Gingle functions; only can be used with GICE.
+ virtual bool ParseGingleCandidate(const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidate* candidate,
+ ParseError* error);
+ virtual bool WriteGingleCandidate(const Candidate& candidate,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** elem,
+ WriteError* error);
+
+ private:
+ bool ParseCandidate(TransportProtocol proto,
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidate* candidate,
+ ParseError* error);
+ bool WriteCandidate(TransportProtocol proto,
+ const Candidate& candidate,
+ const CandidateTranslator* translator,
+ buzz::XmlElement* elem,
+ WriteError* error);
+ bool VerifyUsernameFormat(TransportProtocol proto,
+ const std::string& username,
+ ParseError* error);
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransportParser);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_P2PTRANSPORT_H_
diff --git a/p2p/base/p2ptransportchannel.cc b/p2p/base/p2ptransportchannel.cc
new file mode 100644
index 00000000..84a2420b
--- /dev/null
+++ b/p2p/base/p2ptransportchannel.cc
@@ -0,0 +1,1274 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/p2ptransportchannel.h"
+
+#include <set>
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/p2p/base/relayport.h" // For RELAY_PORT_TYPE.
+#include "webrtc/p2p/base/stunport.h" // For STUN_PORT_TYPE.
+#include "webrtc/base/common.h"
+#include "webrtc/base/crc32.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/stringencode.h"
+
+namespace {
+
+// messages for queuing up work for ourselves
+enum {
+ MSG_SORT = 1,
+ MSG_PING,
+};
+
+// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers)
+// for pinging. When the socket is writable, we will use only 1 Kbps because
+// we don't want to degrade the quality on a modem. These numbers should work
+// well on a 28.8K modem, which is the slowest connection on which the voice
+// quality is reasonable at all.
+static const uint32 PING_PACKET_SIZE = 60 * 8;
+static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000; // 480ms
+static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000; // 50ms
+
+// If there is a current writable connection, then we will also try hard to
+// make sure it is pinged at this rate.
+static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; // 2*WRITABLE_DELAY - bit
+
+// The minimum improvement in RTT that justifies a switch.
+static const double kMinImprovement = 10;
+
+cricket::PortInterface::CandidateOrigin GetOrigin(cricket::PortInterface* port,
+ cricket::PortInterface* origin_port) {
+ if (!origin_port)
+ return cricket::PortInterface::ORIGIN_MESSAGE;
+ else if (port == origin_port)
+ return cricket::PortInterface::ORIGIN_THIS_PORT;
+ else
+ return cricket::PortInterface::ORIGIN_OTHER_PORT;
+}
+
+// Compares two connections based only on static information about them.
+int CompareConnectionCandidates(cricket::Connection* a,
+ cricket::Connection* b) {
+ // Compare connection priority. Lower values get sorted last.
+ if (a->priority() > b->priority())
+ return 1;
+ if (a->priority() < b->priority())
+ return -1;
+
+ // If we're still tied at this point, prefer a younger generation.
+ return (a->remote_candidate().generation() + a->port()->generation()) -
+ (b->remote_candidate().generation() + b->port()->generation());
+}
+
+// Compare two connections based on their writability and static preferences.
+int CompareConnections(cricket::Connection *a, cricket::Connection *b) {
+ // Sort based on write-state. Better states have lower values.
+ if (a->write_state() < b->write_state())
+ return 1;
+ if (a->write_state() > b->write_state())
+ return -1;
+
+ // Compare the candidate information.
+ return CompareConnectionCandidates(a, b);
+}
+
+// Wraps the comparison connection into a less than operator that puts higher
+// priority writable connections first.
+class ConnectionCompare {
+ public:
+ bool operator()(const cricket::Connection *ca,
+ const cricket::Connection *cb) {
+ cricket::Connection* a = const_cast<cricket::Connection*>(ca);
+ cricket::Connection* b = const_cast<cricket::Connection*>(cb);
+
+ ASSERT(a->port()->IceProtocol() == b->port()->IceProtocol());
+
+ // Compare first on writability and static preferences.
+ int cmp = CompareConnections(a, b);
+ if (cmp > 0)
+ return true;
+ if (cmp < 0)
+ return false;
+
+ // Otherwise, sort based on latency estimate.
+ return a->rtt() < b->rtt();
+
+ // Should we bother checking for the last connection that last received
+ // data? It would help rendezvous on the connection that is also receiving
+ // packets.
+ //
+ // TODO: Yes we should definitely do this. The TCP protocol gains
+ // efficiency by being used bidirectionally, as opposed to two separate
+ // unidirectional streams. This test should probably occur before
+ // comparison of local prefs (assuming combined prefs are the same). We
+ // need to be careful though, not to bounce back and forth with both sides
+ // trying to rendevous with the other.
+ }
+};
+
+// Determines whether we should switch between two connections, based first on
+// static preferences and then (if those are equal) on latency estimates.
+bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) {
+ if (a_conn == b_conn)
+ return false;
+
+ if (!a_conn || !b_conn) // don't think the latter should happen
+ return true;
+
+ int prefs_cmp = CompareConnections(a_conn, b_conn);
+ if (prefs_cmp < 0)
+ return true;
+ if (prefs_cmp > 0)
+ return false;
+
+ return b_conn->rtt() <= a_conn->rtt() + kMinImprovement;
+}
+
+} // unnamed namespace
+
+namespace cricket {
+
+P2PTransportChannel::P2PTransportChannel(const std::string& content_name,
+ int component,
+ P2PTransport* transport,
+ PortAllocator *allocator) :
+ TransportChannelImpl(content_name, component),
+ transport_(transport),
+ allocator_(allocator),
+ worker_thread_(rtc::Thread::Current()),
+ incoming_only_(false),
+ waiting_for_signaling_(false),
+ error_(0),
+ best_connection_(NULL),
+ pending_best_connection_(NULL),
+ sort_dirty_(false),
+ was_writable_(false),
+ protocol_type_(ICEPROTO_HYBRID),
+ remote_ice_mode_(ICEMODE_FULL),
+ ice_role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ remote_candidate_generation_(0) {
+}
+
+P2PTransportChannel::~P2PTransportChannel() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+ delete allocator_sessions_[i];
+}
+
+// Add the allocator session to our list so that we know which sessions
+// are still active.
+void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) {
+ session->set_generation(static_cast<uint32>(allocator_sessions_.size()));
+ allocator_sessions_.push_back(session);
+
+ // We now only want to apply new candidates that we receive to the ports
+ // created by this new session because these are replacing those of the
+ // previous sessions.
+ ports_.clear();
+
+ session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);
+ session->SignalCandidatesReady.connect(
+ this, &P2PTransportChannel::OnCandidatesReady);
+ session->SignalCandidatesAllocationDone.connect(
+ this, &P2PTransportChannel::OnCandidatesAllocationDone);
+ session->StartGettingPorts();
+}
+
+void P2PTransportChannel::AddConnection(Connection* connection) {
+ connections_.push_back(connection);
+ connection->set_remote_ice_mode(remote_ice_mode_);
+ connection->SignalReadPacket.connect(
+ this, &P2PTransportChannel::OnReadPacket);
+ connection->SignalReadyToSend.connect(
+ this, &P2PTransportChannel::OnReadyToSend);
+ connection->SignalStateChange.connect(
+ this, &P2PTransportChannel::OnConnectionStateChange);
+ connection->SignalDestroyed.connect(
+ this, &P2PTransportChannel::OnConnectionDestroyed);
+ connection->SignalUseCandidate.connect(
+ this, &P2PTransportChannel::OnUseCandidate);
+}
+
+void P2PTransportChannel::SetIceRole(IceRole ice_role) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (ice_role_ != ice_role) {
+ ice_role_ = ice_role;
+ for (std::vector<PortInterface *>::iterator it = ports_.begin();
+ it != ports_.end(); ++it) {
+ (*it)->SetIceRole(ice_role);
+ }
+ }
+}
+
+void P2PTransportChannel::SetIceTiebreaker(uint64 tiebreaker) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (!ports_.empty()) {
+ LOG(LS_ERROR)
+ << "Attempt to change tiebreaker after Port has been allocated.";
+ return;
+ }
+
+ tiebreaker_ = tiebreaker;
+}
+
+bool P2PTransportChannel::GetIceProtocolType(IceProtocolType* type) const {
+ *type = protocol_type_;
+ return true;
+}
+
+void P2PTransportChannel::SetIceProtocolType(IceProtocolType type) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ protocol_type_ = type;
+ for (std::vector<PortInterface *>::iterator it = ports_.begin();
+ it != ports_.end(); ++it) {
+ (*it)->SetIceProtocolType(protocol_type_);
+ }
+}
+
+void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ bool ice_restart = false;
+ if (!ice_ufrag_.empty() && !ice_pwd_.empty()) {
+ // Restart candidate allocation if there is any change in either
+ // ice ufrag or password.
+ ice_restart =
+ IceCredentialsChanged(ice_ufrag_, ice_pwd_, ice_ufrag, ice_pwd);
+ }
+
+ ice_ufrag_ = ice_ufrag;
+ ice_pwd_ = ice_pwd;
+
+ if (ice_restart) {
+ // Restart candidate gathering.
+ Allocate();
+ }
+}
+
+void P2PTransportChannel::SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ bool ice_restart = false;
+ if (!remote_ice_ufrag_.empty() && !remote_ice_pwd_.empty()) {
+ ice_restart = (remote_ice_ufrag_ != ice_ufrag) ||
+ (remote_ice_pwd_!= ice_pwd);
+ }
+
+ remote_ice_ufrag_ = ice_ufrag;
+ remote_ice_pwd_ = ice_pwd;
+
+ if (ice_restart) {
+ // |candidate.generation()| is not signaled in ICEPROTO_RFC5245.
+ // Therefore we need to keep track of the remote ice restart so
+ // newer connections are prioritized over the older.
+ ++remote_candidate_generation_;
+ }
+}
+
+void P2PTransportChannel::SetRemoteIceMode(IceMode mode) {
+ remote_ice_mode_ = mode;
+}
+
+// Go into the state of processing candidates, and running in general
+void P2PTransportChannel::Connect() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (ice_ufrag_.empty() || ice_pwd_.empty()) {
+ ASSERT(false);
+ LOG(LS_ERROR) << "P2PTransportChannel::Connect: The ice_ufrag_ and the "
+ << "ice_pwd_ are not set.";
+ return;
+ }
+
+ // Kick off an allocator session
+ Allocate();
+
+ // Start pinging as the ports come in.
+ thread()->Post(this, MSG_PING);
+}
+
+// Reset the socket, clear up any previous allocations and start over
+void P2PTransportChannel::Reset() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Get rid of all the old allocators. This should clean up everything.
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+ delete allocator_sessions_[i];
+
+ allocator_sessions_.clear();
+ ports_.clear();
+ connections_.clear();
+ best_connection_ = NULL;
+
+ // Forget about all of the candidates we got before.
+ remote_candidates_.clear();
+
+ // Revert to the initial state.
+ set_readable(false);
+ set_writable(false);
+
+ // Reinitialize the rest of our state.
+ waiting_for_signaling_ = false;
+ sort_dirty_ = false;
+
+ // If we allocated before, start a new one now.
+ if (transport_->connect_requested())
+ Allocate();
+
+ // Start pinging as the ports come in.
+ thread()->Clear(this);
+ thread()->Post(this, MSG_PING);
+}
+
+// A new port is available, attempt to make connections for it
+void P2PTransportChannel::OnPortReady(PortAllocatorSession *session,
+ PortInterface* port) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Set in-effect options on the new port
+ for (OptionMap::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ int val = port->SetOption(it->first, it->second);
+ if (val < 0) {
+ LOG_J(LS_WARNING, port) << "SetOption(" << it->first
+ << ", " << it->second
+ << ") failed: " << port->GetError();
+ }
+ }
+
+ // Remember the ports and candidates, and signal that candidates are ready.
+ // The session will handle this, and send an initiate/accept/modify message
+ // if one is pending.
+
+ port->SetIceProtocolType(protocol_type_);
+ port->SetIceRole(ice_role_);
+ port->SetIceTiebreaker(tiebreaker_);
+ ports_.push_back(port);
+ port->SignalUnknownAddress.connect(
+ this, &P2PTransportChannel::OnUnknownAddress);
+ port->SignalDestroyed.connect(this, &P2PTransportChannel::OnPortDestroyed);
+ port->SignalRoleConflict.connect(
+ this, &P2PTransportChannel::OnRoleConflict);
+
+ // Attempt to create a connection from this new port to all of the remote
+ // candidates that we were given so far.
+
+ std::vector<RemoteCandidate>::iterator iter;
+ for (iter = remote_candidates_.begin(); iter != remote_candidates_.end();
+ ++iter) {
+ CreateConnection(port, *iter, iter->origin_port(), false);
+ }
+
+ SortConnections();
+}
+
+// A new candidate is available, let listeners know
+void P2PTransportChannel::OnCandidatesReady(
+ PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ SignalCandidateReady(this, candidates[i]);
+ }
+}
+
+void P2PTransportChannel::OnCandidatesAllocationDone(
+ PortAllocatorSession* session) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ SignalCandidatesAllocationDone(this);
+}
+
+// Handle stun packets
+void P2PTransportChannel::OnUnknownAddress(
+ PortInterface* port,
+ const rtc::SocketAddress& address, ProtocolType proto,
+ IceMessage* stun_msg, const std::string &remote_username,
+ bool port_muxed) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Port has received a valid stun packet from an address that no Connection
+ // is currently available for. See if we already have a candidate with the
+ // address. If it isn't we need to create new candidate for it.
+
+ // Determine if the remote candidates use shared ufrag.
+ bool ufrag_per_port = false;
+ std::vector<RemoteCandidate>::iterator it;
+ if (remote_candidates_.size() > 0) {
+ it = remote_candidates_.begin();
+ std::string username = it->username();
+ for (; it != remote_candidates_.end(); ++it) {
+ if (it->username() != username) {
+ ufrag_per_port = true;
+ break;
+ }
+ }
+ }
+
+ const Candidate* candidate = NULL;
+ bool known_username = false;
+ std::string remote_password;
+ for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) {
+ if (it->username() == remote_username) {
+ remote_password = it->password();
+ known_username = true;
+ if (ufrag_per_port ||
+ (it->address() == address &&
+ it->protocol() == ProtoToString(proto))) {
+ candidate = &(*it);
+ break;
+ }
+ // We don't want to break here because we may find a match of the address
+ // later.
+ }
+ }
+
+ if (!known_username) {
+ if (port_muxed) {
+ // When Ports are muxed, SignalUnknownAddress is delivered to all
+ // P2PTransportChannel belong to a session. Return from here will
+ // save us from sending stun binding error message from incorrect channel.
+ return;
+ }
+ // Don't know about this username, the request is bogus
+ // This sometimes happens if a binding response comes in before the ACCEPT
+ // message. It is totally valid; the retry state machine will try again.
+ port->SendBindingErrorResponse(stun_msg, address,
+ STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS);
+ return;
+ }
+
+ Candidate new_remote_candidate;
+ if (candidate != NULL) {
+ new_remote_candidate = *candidate;
+ if (ufrag_per_port) {
+ new_remote_candidate.set_address(address);
+ }
+ } else {
+ // Create a new candidate with this address.
+ std::string type;
+ if (port->IceProtocol() == ICEPROTO_RFC5245) {
+ type = PRFLX_PORT_TYPE;
+ } else {
+ // G-ICE doesn't support prflx candidate.
+ // We set candidate type to STUN_PORT_TYPE if the binding request comes
+ // from a relay port or the shared socket is used. Otherwise we use the
+ // port's type as the candidate type.
+ if (port->Type() == RELAY_PORT_TYPE || port->SharedSocket()) {
+ type = STUN_PORT_TYPE;
+ } else {
+ type = port->Type();
+ }
+ }
+
+ std::string id = rtc::CreateRandomString(8);
+ new_remote_candidate = Candidate(
+ id, component(), ProtoToString(proto), address,
+ 0, remote_username, remote_password, type,
+ port->Network()->name(), 0U,
+ rtc::ToString<uint32>(rtc::ComputeCrc32(id)));
+ new_remote_candidate.set_priority(
+ new_remote_candidate.GetPriority(ICE_TYPE_PREFERENCE_SRFLX,
+ port->Network()->preference(), 0));
+ }
+
+ if (port->IceProtocol() == ICEPROTO_RFC5245) {
+ // RFC 5245
+ // If the source transport address of the request does not match any
+ // existing remote candidates, it represents a new peer reflexive remote
+ // candidate.
+
+ // The priority of the candidate is set to the PRIORITY attribute
+ // from the request.
+ const StunUInt32Attribute* priority_attr =
+ stun_msg->GetUInt32(STUN_ATTR_PRIORITY);
+ if (!priority_attr) {
+ LOG(LS_WARNING) << "P2PTransportChannel::OnUnknownAddress - "
+ << "No STUN_ATTR_PRIORITY found in the "
+ << "stun request message";
+ port->SendBindingErrorResponse(stun_msg, address,
+ STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return;
+ }
+ new_remote_candidate.set_priority(priority_attr->value());
+
+ // RFC5245, the agent constructs a pair whose local candidate is equal to
+ // the transport address on which the STUN request was received, and a
+ // remote candidate equal to the source transport address where the
+ // request came from.
+
+ // There shouldn't be an existing connection with this remote address.
+ // When ports are muxed, this channel might get multiple unknown address
+ // signals. In that case if the connection is already exists, we should
+ // simply ignore the signal othewise send server error.
+ if (port->GetConnection(new_remote_candidate.address())) {
+ if (port_muxed) {
+ LOG(LS_INFO) << "Connection already exists for peer reflexive "
+ << "candidate: " << new_remote_candidate.ToString();
+ return;
+ } else {
+ ASSERT(false);
+ port->SendBindingErrorResponse(stun_msg, address,
+ STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ return;
+ }
+ }
+
+ Connection* connection = port->CreateConnection(
+ new_remote_candidate, cricket::PortInterface::ORIGIN_THIS_PORT);
+ if (!connection) {
+ ASSERT(false);
+ port->SendBindingErrorResponse(stun_msg, address,
+ STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ return;
+ }
+
+ AddConnection(connection);
+ connection->ReceivedPing();
+
+ // Send the pinger a successful stun response.
+ port->SendBindingResponse(stun_msg, address);
+
+ // Update the list of connections since we just added another. We do this
+ // after sending the response since it could (in principle) delete the
+ // connection in question.
+ SortConnections();
+ } else {
+ // Check for connectivity to this address. Create connections
+ // to this address across all local ports. First, add this as a new remote
+ // address
+ if (!CreateConnections(new_remote_candidate, port, true)) {
+ // Hopefully this won't occur, because changing a destination address
+ // shouldn't cause a new connection to fail
+ ASSERT(false);
+ port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ return;
+ }
+
+ // Send the pinger a successful stun response.
+ port->SendBindingResponse(stun_msg, address);
+
+ // Update the list of connections since we just added another. We do this
+ // after sending the response since it could (in principle) delete the
+ // connection in question.
+ SortConnections();
+ }
+}
+
+void P2PTransportChannel::OnRoleConflict(PortInterface* port) {
+ SignalRoleConflict(this); // STUN ping will be sent when SetRole is called
+ // from Transport.
+}
+
+// When the signalling channel is ready, we can really kick off the allocator
+void P2PTransportChannel::OnSignalingReady() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (waiting_for_signaling_) {
+ waiting_for_signaling_ = false;
+ AddAllocatorSession(allocator_->CreateSession(
+ SessionId(), content_name(), component(), ice_ufrag_, ice_pwd_));
+ }
+}
+
+void P2PTransportChannel::OnUseCandidate(Connection* conn) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ ASSERT(ice_role_ == ICEROLE_CONTROLLED);
+ ASSERT(protocol_type_ == ICEPROTO_RFC5245);
+ if (conn->write_state() == Connection::STATE_WRITABLE) {
+ if (best_connection_ != conn) {
+ pending_best_connection_ = NULL;
+ SwitchBestConnectionTo(conn);
+ // Now we have selected the best connection, time to prune other existing
+ // connections and update the read/write state of the channel.
+ RequestSort();
+ }
+ } else {
+ pending_best_connection_ = conn;
+ }
+}
+
+void P2PTransportChannel::OnCandidate(const Candidate& candidate) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Create connections to this remote candidate.
+ CreateConnections(candidate, NULL, false);
+
+ // Resort the connections list, which may have new elements.
+ SortConnections();
+}
+
+// Creates connections from all of the ports that we care about to the given
+// remote candidate. The return value is true if we created a connection from
+// the origin port.
+bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
+ PortInterface* origin_port,
+ bool readable) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ Candidate new_remote_candidate(remote_candidate);
+ new_remote_candidate.set_generation(
+ GetRemoteCandidateGeneration(remote_candidate));
+ // ICE candidates don't need to have username and password set, but
+ // the code below this (specifically, ConnectionRequest::Prepare in
+ // port.cc) uses the remote candidates's username. So, we set it
+ // here.
+ if (remote_candidate.username().empty()) {
+ new_remote_candidate.set_username(remote_ice_ufrag_);
+ }
+ if (remote_candidate.password().empty()) {
+ new_remote_candidate.set_password(remote_ice_pwd_);
+ }
+
+ // If we've already seen the new remote candidate (in the current candidate
+ // generation), then we shouldn't try creating connections for it.
+ // We either already have a connection for it, or we previously created one
+ // and then later pruned it. If we don't return, the channel will again
+ // re-create any connections that were previously pruned, which will then
+ // immediately be re-pruned, churning the network for no purpose.
+ // This only applies to candidates received over signaling (i.e. origin_port
+ // is NULL).
+ if (!origin_port && IsDuplicateRemoteCandidate(new_remote_candidate)) {
+ // return true to indicate success, without creating any new connections.
+ return true;
+ }
+
+ // Add a new connection for this candidate to every port that allows such a
+ // connection (i.e., if they have compatible protocols) and that does not
+ // already have a connection to an equivalent candidate. We must be careful
+ // to make sure that the origin port is included, even if it was pruned,
+ // since that may be the only port that can create this connection.
+ bool created = false;
+ std::vector<PortInterface *>::reverse_iterator it;
+ for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
+ if (CreateConnection(*it, new_remote_candidate, origin_port, readable)) {
+ if (*it == origin_port)
+ created = true;
+ }
+ }
+
+ if ((origin_port != NULL) &&
+ std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) {
+ if (CreateConnection(
+ origin_port, new_remote_candidate, origin_port, readable))
+ created = true;
+ }
+
+ // Remember this remote candidate so that we can add it to future ports.
+ RememberRemoteCandidate(new_remote_candidate, origin_port);
+
+ return created;
+}
+
+// Setup a connection object for the local and remote candidate combination.
+// And then listen to connection object for changes.
+bool P2PTransportChannel::CreateConnection(PortInterface* port,
+ const Candidate& remote_candidate,
+ PortInterface* origin_port,
+ bool readable) {
+ // Look for an existing connection with this remote address. If one is not
+ // found, then we can create a new connection for this address.
+ Connection* connection = port->GetConnection(remote_candidate.address());
+ if (connection != NULL) {
+ // It is not legal to try to change any of the parameters of an existing
+ // connection; however, the other side can send a duplicate candidate.
+ if (!remote_candidate.IsEquivalent(connection->remote_candidate())) {
+ LOG(INFO) << "Attempt to change a remote candidate."
+ << " Existing remote candidate: "
+ << connection->remote_candidate().ToString()
+ << "New remote candidate: "
+ << remote_candidate.ToString();
+ return false;
+ }
+ } else {
+ PortInterface::CandidateOrigin origin = GetOrigin(port, origin_port);
+
+ // Don't create connection if this is a candidate we received in a
+ // message and we are not allowed to make outgoing connections.
+ if (origin == cricket::PortInterface::ORIGIN_MESSAGE && incoming_only_)
+ return false;
+
+ connection = port->CreateConnection(remote_candidate, origin);
+ if (!connection)
+ return false;
+
+ AddConnection(connection);
+
+ LOG_J(LS_INFO, this) << "Created connection with origin=" << origin << ", ("
+ << connections_.size() << " total)";
+ }
+
+ // If we are readable, it is because we are creating this in response to a
+ // ping from the other side. This will cause the state to become readable.
+ if (readable)
+ connection->ReceivedPing();
+
+ return true;
+}
+
+bool P2PTransportChannel::FindConnection(
+ cricket::Connection* connection) const {
+ std::vector<Connection*>::const_iterator citer =
+ std::find(connections_.begin(), connections_.end(), connection);
+ return citer != connections_.end();
+}
+
+uint32 P2PTransportChannel::GetRemoteCandidateGeneration(
+ const Candidate& candidate) {
+ if (protocol_type_ == ICEPROTO_GOOGLE) {
+ // The Candidate.generation() can be trusted. Nothing needs to be done.
+ return candidate.generation();
+ }
+ // |candidate.generation()| is not signaled in ICEPROTO_RFC5245.
+ // Therefore we need to keep track of the remote ice restart so
+ // newer connections are prioritized over the older.
+ ASSERT(candidate.generation() == 0 ||
+ candidate.generation() == remote_candidate_generation_);
+ return remote_candidate_generation_;
+}
+
+// Check if remote candidate is already cached.
+bool P2PTransportChannel::IsDuplicateRemoteCandidate(
+ const Candidate& candidate) {
+ for (uint32 i = 0; i < remote_candidates_.size(); ++i) {
+ if (remote_candidates_[i].IsEquivalent(candidate)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Maintain our remote candidate list, adding this new remote one.
+void P2PTransportChannel::RememberRemoteCandidate(
+ const Candidate& remote_candidate, PortInterface* origin_port) {
+ // Remove any candidates whose generation is older than this one. The
+ // presence of a new generation indicates that the old ones are not useful.
+ uint32 i = 0;
+ while (i < remote_candidates_.size()) {
+ if (remote_candidates_[i].generation() < remote_candidate.generation()) {
+ LOG(INFO) << "Pruning candidate from old generation: "
+ << remote_candidates_[i].address().ToSensitiveString();
+ remote_candidates_.erase(remote_candidates_.begin() + i);
+ } else {
+ i += 1;
+ }
+ }
+
+ // Make sure this candidate is not a duplicate.
+ if (IsDuplicateRemoteCandidate(remote_candidate)) {
+ LOG(INFO) << "Duplicate candidate: " << remote_candidate.ToString();
+ return;
+ }
+
+ // Try this candidate for all future ports.
+ remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port));
+}
+
+// Set options on ourselves is simply setting options on all of our available
+// port objects.
+int P2PTransportChannel::SetOption(rtc::Socket::Option opt, int value) {
+ OptionMap::iterator it = options_.find(opt);
+ if (it == options_.end()) {
+ options_.insert(std::make_pair(opt, value));
+ } else if (it->second == value) {
+ return 0;
+ } else {
+ it->second = value;
+ }
+
+ for (uint32 i = 0; i < ports_.size(); ++i) {
+ int val = ports_[i]->SetOption(opt, value);
+ if (val < 0) {
+ // Because this also occurs deferred, probably no point in reporting an
+ // error
+ LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: "
+ << ports_[i]->GetError();
+ }
+ }
+ return 0;
+}
+
+// Send data to the other side, using our best connection.
+int P2PTransportChannel::SendPacket(const char *data, size_t len,
+ const rtc::PacketOptions& options,
+ int flags) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (flags != 0) {
+ error_ = EINVAL;
+ return -1;
+ }
+ if (best_connection_ == NULL) {
+ error_ = EWOULDBLOCK;
+ return -1;
+ }
+
+ int sent = best_connection_->Send(data, len, options);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = best_connection_->GetError();
+ }
+ return sent;
+}
+
+bool P2PTransportChannel::GetStats(ConnectionInfos *infos) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ // Gather connection infos.
+ infos->clear();
+
+ std::vector<Connection *>::const_iterator it;
+ for (it = connections_.begin(); it != connections_.end(); ++it) {
+ Connection *connection = *it;
+ ConnectionInfo info;
+ info.best_connection = (best_connection_ == connection);
+ info.readable =
+ (connection->read_state() == Connection::STATE_READABLE);
+ info.writable =
+ (connection->write_state() == Connection::STATE_WRITABLE);
+ info.timeout =
+ (connection->write_state() == Connection::STATE_WRITE_TIMEOUT);
+ info.new_connection = !connection->reported();
+ connection->set_reported(true);
+ info.rtt = connection->rtt();
+ info.sent_total_bytes = connection->sent_total_bytes();
+ info.sent_bytes_second = connection->sent_bytes_second();
+ info.recv_total_bytes = connection->recv_total_bytes();
+ info.recv_bytes_second = connection->recv_bytes_second();
+ info.local_candidate = connection->local_candidate();
+ info.remote_candidate = connection->remote_candidate();
+ info.key = connection;
+ infos->push_back(info);
+ }
+
+ return true;
+}
+
+rtc::DiffServCodePoint P2PTransportChannel::DefaultDscpValue() const {
+ OptionMap::const_iterator it = options_.find(rtc::Socket::OPT_DSCP);
+ if (it == options_.end()) {
+ return rtc::DSCP_NO_CHANGE;
+ }
+ return static_cast<rtc::DiffServCodePoint> (it->second);
+}
+
+// Begin allocate (or immediately re-allocate, if MSG_ALLOCATE pending)
+void P2PTransportChannel::Allocate() {
+ // Time for a new allocator, lets make sure we have a signalling channel
+ // to communicate candidates through first.
+ waiting_for_signaling_ = true;
+ SignalRequestSignaling(this);
+}
+
+// Monitor connection states.
+void P2PTransportChannel::UpdateConnectionStates() {
+ uint32 now = rtc::Time();
+
+ // We need to copy the list of connections since some may delete themselves
+ // when we call UpdateState.
+ for (uint32 i = 0; i < connections_.size(); ++i)
+ connections_[i]->UpdateState(now);
+}
+
+// Prepare for best candidate sorting.
+void P2PTransportChannel::RequestSort() {
+ if (!sort_dirty_) {
+ worker_thread_->Post(this, MSG_SORT);
+ sort_dirty_ = true;
+ }
+}
+
+// Sort the available connections to find the best one. We also monitor
+// the number of available connections and the current state.
+void P2PTransportChannel::SortConnections() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Make sure the connection states are up-to-date since this affects how they
+ // will be sorted.
+ UpdateConnectionStates();
+
+ if (protocol_type_ == ICEPROTO_HYBRID) {
+ // If we are in hybrid mode, we are not sending any ping requests, so there
+ // is no point in sorting the connections. In hybrid state, ports can have
+ // different protocol than hybrid and protocol may differ from one another.
+ // Instead just update the state of this channel
+ UpdateChannelState();
+ return;
+ }
+
+ // Any changes after this point will require a re-sort.
+ sort_dirty_ = false;
+
+ // Get a list of the networks that we are using.
+ std::set<rtc::Network*> networks;
+ for (uint32 i = 0; i < connections_.size(); ++i)
+ networks.insert(connections_[i]->port()->Network());
+
+ // Find the best alternative connection by sorting. It is important to note
+ // that amongst equal preference, writable connections, this will choose the
+ // one whose estimated latency is lowest. So it is the only one that we
+ // need to consider switching to.
+
+ ConnectionCompare cmp;
+ std::stable_sort(connections_.begin(), connections_.end(), cmp);
+ LOG(LS_VERBOSE) << "Sorting available connections:";
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ LOG(LS_VERBOSE) << connections_[i]->ToString();
+ }
+
+ Connection* top_connection = NULL;
+ if (connections_.size() > 0)
+ top_connection = connections_[0];
+
+ // We don't want to pick the best connections if channel is using RFC5245
+ // and it's mode is CONTROLLED, as connections will be selected by the
+ // CONTROLLING agent.
+
+ // If necessary, switch to the new choice.
+ if (protocol_type_ != ICEPROTO_RFC5245 || ice_role_ == ICEROLE_CONTROLLING) {
+ if (ShouldSwitch(best_connection_, top_connection))
+ SwitchBestConnectionTo(top_connection);
+ }
+
+ // We can prune any connection for which there is a writable connection on
+ // the same network with better or equal priority. We leave those with
+ // better priority just in case they become writable later (at which point,
+ // we would prune out the current best connection). We leave connections on
+ // other networks because they may not be using the same resources and they
+ // may represent very distinct paths over which we can switch.
+ std::set<rtc::Network*>::iterator network;
+ for (network = networks.begin(); network != networks.end(); ++network) {
+ Connection* primier = GetBestConnectionOnNetwork(*network);
+ if (!primier || (primier->write_state() != Connection::STATE_WRITABLE))
+ continue;
+
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if ((connections_[i] != primier) &&
+ (connections_[i]->port()->Network() == *network) &&
+ (CompareConnectionCandidates(primier, connections_[i]) >= 0)) {
+ connections_[i]->Prune();
+ }
+ }
+ }
+
+ // Check if all connections are timedout.
+ bool all_connections_timedout = true;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (connections_[i]->write_state() != Connection::STATE_WRITE_TIMEOUT) {
+ all_connections_timedout = false;
+ break;
+ }
+ }
+
+ // Now update the writable state of the channel with the information we have
+ // so far.
+ if (best_connection_ && best_connection_->writable()) {
+ HandleWritable();
+ } else if (all_connections_timedout) {
+ HandleAllTimedOut();
+ } else {
+ HandleNotWritable();
+ }
+
+ // Update the state of this channel. This method is called whenever the
+ // state of any connection changes, so this is a good place to do this.
+ UpdateChannelState();
+}
+
+
+// Track the best connection, and let listeners know
+void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) {
+ // Note: if conn is NULL, the previous best_connection_ has been destroyed,
+ // so don't use it.
+ Connection* old_best_connection = best_connection_;
+ best_connection_ = conn;
+ if (best_connection_) {
+ if (old_best_connection) {
+ LOG_J(LS_INFO, this) << "Previous best connection: "
+ << old_best_connection->ToString();
+ }
+ LOG_J(LS_INFO, this) << "New best connection: "
+ << best_connection_->ToString();
+ SignalRouteChange(this, best_connection_->remote_candidate());
+ } else {
+ LOG_J(LS_INFO, this) << "No best connection";
+ }
+}
+
+void P2PTransportChannel::UpdateChannelState() {
+ // The Handle* functions already set the writable state. We'll just double-
+ // check it here.
+ bool writable = ((best_connection_ != NULL) &&
+ (best_connection_->write_state() ==
+ Connection::STATE_WRITABLE));
+ ASSERT(writable == this->writable());
+ if (writable != this->writable())
+ LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch";
+
+ bool readable = false;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (connections_[i]->read_state() == Connection::STATE_READABLE) {
+ readable = true;
+ break;
+ }
+ }
+ set_readable(readable);
+}
+
+// We checked the status of our connections and we had at least one that
+// was writable, go into the writable state.
+void P2PTransportChannel::HandleWritable() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (!writable()) {
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i) {
+ if (allocator_sessions_[i]->IsGettingPorts()) {
+ allocator_sessions_[i]->StopGettingPorts();
+ }
+ }
+ }
+
+ was_writable_ = true;
+ set_writable(true);
+}
+
+// Notify upper layer about channel not writable state, if it was before.
+void P2PTransportChannel::HandleNotWritable() {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+ if (was_writable_) {
+ was_writable_ = false;
+ set_writable(false);
+ }
+}
+
+void P2PTransportChannel::HandleAllTimedOut() {
+ // Currently we are treating this as channel not writable.
+ HandleNotWritable();
+}
+
+// If we have a best connection, return it, otherwise return top one in the
+// list (later we will mark it best).
+Connection* P2PTransportChannel::GetBestConnectionOnNetwork(
+ rtc::Network* network) {
+ // If the best connection is on this network, then it wins.
+ if (best_connection_ && (best_connection_->port()->Network() == network))
+ return best_connection_;
+
+ // Otherwise, we return the top-most in sorted order.
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (connections_[i]->port()->Network() == network)
+ return connections_[i];
+ }
+
+ return NULL;
+}
+
+// Handle any queued up requests
+void P2PTransportChannel::OnMessage(rtc::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_SORT:
+ OnSort();
+ break;
+ case MSG_PING:
+ OnPing();
+ break;
+ default:
+ ASSERT(false);
+ break;
+ }
+}
+
+// Handle queued up sort request
+void P2PTransportChannel::OnSort() {
+ // Resort the connections based on the new statistics.
+ SortConnections();
+}
+
+// Handle queued up ping request
+void P2PTransportChannel::OnPing() {
+ // Make sure the states of the connections are up-to-date (since this affects
+ // which ones are pingable).
+ UpdateConnectionStates();
+
+ // Find the oldest pingable connection and have it do a ping.
+ Connection* conn = FindNextPingableConnection();
+ if (conn)
+ PingConnection(conn);
+
+ // Post ourselves a message to perform the next ping.
+ uint32 delay = writable() ? WRITABLE_DELAY : UNWRITABLE_DELAY;
+ thread()->PostDelayed(delay, this, MSG_PING);
+}
+
+// Is the connection in a state for us to even consider pinging the other side?
+bool P2PTransportChannel::IsPingable(Connection* conn) {
+ // An unconnected connection cannot be written to at all, so pinging is out
+ // of the question.
+ if (!conn->connected())
+ return false;
+
+ if (writable()) {
+ // If we are writable, then we only want to ping connections that could be
+ // better than this one, i.e., the ones that were not pruned.
+ return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT);
+ } else {
+ // If we are not writable, then we need to try everything that might work.
+ // This includes both connections that do not have write timeout as well as
+ // ones that do not have read timeout. A connection could be readable but
+ // be in write-timeout if we pruned it before. Since the other side is
+ // still pinging it, it very well might still work.
+ return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) ||
+ (conn->read_state() != Connection::STATE_READ_TIMEOUT);
+ }
+}
+
+// Returns the next pingable connection to ping. This will be the oldest
+// pingable connection unless we have a writable connection that is past the
+// maximum acceptable ping delay.
+Connection* P2PTransportChannel::FindNextPingableConnection() {
+ uint32 now = rtc::Time();
+ if (best_connection_ &&
+ (best_connection_->write_state() == Connection::STATE_WRITABLE) &&
+ (best_connection_->last_ping_sent()
+ + MAX_CURRENT_WRITABLE_DELAY <= now)) {
+ return best_connection_;
+ }
+
+ Connection* oldest_conn = NULL;
+ uint32 oldest_time = 0xFFFFFFFF;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (IsPingable(connections_[i])) {
+ if (connections_[i]->last_ping_sent() < oldest_time) {
+ oldest_time = connections_[i]->last_ping_sent();
+ oldest_conn = connections_[i];
+ }
+ }
+ }
+ return oldest_conn;
+}
+
+// Apart from sending ping from |conn| this method also updates
+// |use_candidate_attr| flag. The criteria to update this flag is
+// explained below.
+// Set USE-CANDIDATE if doing ICE AND this channel is in CONTROLLING AND
+// a) Channel is in FULL ICE AND
+// a.1) |conn| is the best connection OR
+// a.2) there is no best connection OR
+// a.3) the best connection is unwritable OR
+// a.4) |conn| has higher priority than best_connection.
+// b) we're doing LITE ICE AND
+// b.1) |conn| is the best_connection AND
+// b.2) |conn| is writable.
+void P2PTransportChannel::PingConnection(Connection* conn) {
+ bool use_candidate = false;
+ if (protocol_type_ == ICEPROTO_RFC5245) {
+ if (remote_ice_mode_ == ICEMODE_FULL && ice_role_ == ICEROLE_CONTROLLING) {
+ use_candidate = (conn == best_connection_) ||
+ (best_connection_ == NULL) ||
+ (!best_connection_->writable()) ||
+ (conn->priority() > best_connection_->priority());
+ } else if (remote_ice_mode_ == ICEMODE_LITE && conn == best_connection_) {
+ use_candidate = best_connection_->writable();
+ }
+ }
+ conn->set_use_candidate_attr(use_candidate);
+ conn->Ping(rtc::Time());
+}
+
+// When a connection's state changes, we need to figure out who to use as
+// the best connection again. It could have become usable, or become unusable.
+void P2PTransportChannel::OnConnectionStateChange(Connection* connection) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Update the best connection if the state change is from pending best
+ // connection and role is controlled.
+ if (protocol_type_ == ICEPROTO_RFC5245 && ice_role_ == ICEROLE_CONTROLLED) {
+ if (connection == pending_best_connection_ && connection->writable()) {
+ pending_best_connection_ = NULL;
+ SwitchBestConnectionTo(connection);
+ }
+ }
+
+ // We have to unroll the stack before doing this because we may be changing
+ // the state of connections while sorting.
+ RequestSort();
+}
+
+// When a connection is removed, edit it out, and then update our best
+// connection.
+void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Note: the previous best_connection_ may be destroyed by now, so don't
+ // use it.
+
+ // Remove this connection from the list.
+ std::vector<Connection*>::iterator iter =
+ std::find(connections_.begin(), connections_.end(), connection);
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+
+ LOG_J(LS_INFO, this) << "Removed connection ("
+ << static_cast<int>(connections_.size()) << " remaining)";
+
+ if (pending_best_connection_ == connection) {
+ pending_best_connection_ = NULL;
+ }
+
+ // If this is currently the best connection, then we need to pick a new one.
+ // The call to SortConnections will pick a new one. It looks at the current
+ // best connection in order to avoid switching between fairly similar ones.
+ // Since this connection is no longer an option, we can just set best to NULL
+ // and re-choose a best assuming that there was no best connection.
+ if (best_connection_ == connection) {
+ SwitchBestConnectionTo(NULL);
+ RequestSort();
+ }
+
+ SignalConnectionRemoved(this);
+}
+
+// When a port is destroyed remove it from our list of ports to use for
+// connection attempts.
+void P2PTransportChannel::OnPortDestroyed(PortInterface* port) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Remove this port from the list (if we didn't drop it already).
+ std::vector<PortInterface*>::iterator iter =
+ std::find(ports_.begin(), ports_.end(), port);
+ if (iter != ports_.end())
+ ports_.erase(iter);
+
+ LOG(INFO) << "Removed port from p2p socket: "
+ << static_cast<int>(ports_.size()) << " remaining";
+}
+
+// We data is available, let listeners know
+void P2PTransportChannel::OnReadPacket(
+ Connection *connection, const char *data, size_t len,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(worker_thread_ == rtc::Thread::Current());
+
+ // Do not deliver, if packet doesn't belong to the correct transport channel.
+ if (!FindConnection(connection))
+ return;
+
+ // Let the client know of an incoming packet
+ SignalReadPacket(this, data, len, packet_time, 0);
+}
+
+void P2PTransportChannel::OnReadyToSend(Connection* connection) {
+ if (connection == best_connection_ && writable()) {
+ SignalReadyToSend(this);
+ }
+}
+
+} // namespace cricket
diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h
new file mode 100644
index 00000000..8e3d50de
--- /dev/null
+++ b/p2p/base/p2ptransportchannel.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// P2PTransportChannel wraps up the state management of the connection between
+// two P2P clients. Clients have candidate ports for connecting, and
+// connections which are combinations of candidates from each end (Alice and
+// Bob each have candidates, one candidate from Alice and one candidate from
+// Bob are used to make a connection, repeat to make many connections).
+//
+// When all of the available connections become invalid (non-writable), we
+// kick off a process of determining more candidates and more connections.
+//
+#ifndef WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+#define WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/p2ptransport.h"
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/sigslot.h"
+
+namespace cricket {
+
+// Adds the port on which the candidate originated.
+class RemoteCandidate : public Candidate {
+ public:
+ RemoteCandidate(const Candidate& c, PortInterface* origin_port)
+ : Candidate(c), origin_port_(origin_port) {}
+
+ PortInterface* origin_port() { return origin_port_; }
+
+ private:
+ PortInterface* origin_port_;
+};
+
+// P2PTransportChannel manages the candidates and connection process to keep
+// two P2P clients connected to each other.
+class P2PTransportChannel : public TransportChannelImpl,
+ public rtc::MessageHandler {
+ public:
+ P2PTransportChannel(const std::string& content_name,
+ int component,
+ P2PTransport* transport,
+ PortAllocator *allocator);
+ virtual ~P2PTransportChannel();
+
+ // From TransportChannelImpl:
+ virtual Transport* GetTransport() { return transport_; }
+ virtual void SetIceRole(IceRole role);
+ virtual IceRole GetIceRole() const { return ice_role_; }
+ virtual void SetIceTiebreaker(uint64 tiebreaker);
+ virtual size_t GetConnectionCount() const { return connections_.size(); }
+ virtual bool GetIceProtocolType(IceProtocolType* type) const;
+ virtual void SetIceProtocolType(IceProtocolType type);
+ virtual void SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd);
+ virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd);
+ virtual void SetRemoteIceMode(IceMode mode);
+ virtual void Connect();
+ virtual void Reset();
+ virtual void OnSignalingReady();
+ virtual void OnCandidate(const Candidate& candidate);
+
+ // From TransportChannel:
+ virtual int SendPacket(const char *data, size_t len,
+ const rtc::PacketOptions& options, int flags);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetError() { return error_; }
+ virtual bool GetStats(std::vector<ConnectionInfo>* stats);
+
+ const Connection* best_connection() const { return best_connection_; }
+ void set_incoming_only(bool value) { incoming_only_ = value; }
+
+ // Note: This is only for testing purpose.
+ // |ports_| should not be changed from outside.
+ const std::vector<PortInterface *>& ports() { return ports_; }
+
+ IceMode remote_ice_mode() const { return remote_ice_mode_; }
+
+ // DTLS methods.
+ virtual bool IsDtlsActive() const { return false; }
+
+ // Default implementation.
+ virtual bool GetSslRole(rtc::SSLRole* role) const {
+ return false;
+ }
+
+ virtual bool SetSslRole(rtc::SSLRole role) {
+ return false;
+ }
+
+ // Set up the ciphers to use for DTLS-SRTP.
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+ return false;
+ }
+
+ // Find out which DTLS-SRTP cipher was negotiated
+ virtual bool GetSrtpCipher(std::string* cipher) {
+ return false;
+ }
+
+ // Returns false because the channel is not encrypted by default.
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
+ return false;
+ }
+
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const {
+ return false;
+ }
+
+ // Allows key material to be extracted for external encryption.
+ virtual bool ExportKeyingMaterial(
+ const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ return false;
+ }
+
+ virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) {
+ return false;
+ }
+
+ // Set DTLS Remote fingerprint. Must be after local identity set.
+ virtual bool SetRemoteFingerprint(
+ const std::string& digest_alg,
+ const uint8* digest,
+ size_t digest_len) {
+ return false;
+ }
+
+ // Helper method used only in unittest.
+ rtc::DiffServCodePoint DefaultDscpValue() const;
+
+ private:
+ rtc::Thread* thread() { return worker_thread_; }
+ PortAllocatorSession* allocator_session() {
+ return allocator_sessions_.back();
+ }
+
+ void Allocate();
+ void UpdateConnectionStates();
+ void RequestSort();
+ void SortConnections();
+ void SwitchBestConnectionTo(Connection* conn);
+ void UpdateChannelState();
+ void HandleWritable();
+ void HandleNotWritable();
+ void HandleAllTimedOut();
+
+ Connection* GetBestConnectionOnNetwork(rtc::Network* network);
+ bool CreateConnections(const Candidate &remote_candidate,
+ PortInterface* origin_port, bool readable);
+ bool CreateConnection(PortInterface* port, const Candidate& remote_candidate,
+ PortInterface* origin_port, bool readable);
+ bool FindConnection(cricket::Connection* connection) const;
+
+ uint32 GetRemoteCandidateGeneration(const Candidate& candidate);
+ bool IsDuplicateRemoteCandidate(const Candidate& candidate);
+ void RememberRemoteCandidate(const Candidate& remote_candidate,
+ PortInterface* origin_port);
+ bool IsPingable(Connection* conn);
+ Connection* FindNextPingableConnection();
+ void PingConnection(Connection* conn);
+ void AddAllocatorSession(PortAllocatorSession* session);
+ void AddConnection(Connection* connection);
+
+ void OnPortReady(PortAllocatorSession *session, PortInterface* port);
+ void OnCandidatesReady(PortAllocatorSession *session,
+ const std::vector<Candidate>& candidates);
+ void OnCandidatesAllocationDone(PortAllocatorSession* session);
+ void OnUnknownAddress(PortInterface* port,
+ const rtc::SocketAddress& addr,
+ ProtocolType proto,
+ IceMessage* stun_msg,
+ const std::string& remote_username,
+ bool port_muxed);
+ void OnPortDestroyed(PortInterface* port);
+ void OnRoleConflict(PortInterface* port);
+
+ void OnConnectionStateChange(Connection* connection);
+ void OnReadPacket(Connection *connection, const char *data, size_t len,
+ const rtc::PacketTime& packet_time);
+ void OnReadyToSend(Connection* connection);
+ void OnConnectionDestroyed(Connection *connection);
+
+ void OnUseCandidate(Connection* conn);
+
+ virtual void OnMessage(rtc::Message *pmsg);
+ void OnSort();
+ void OnPing();
+
+ P2PTransport* transport_;
+ PortAllocator *allocator_;
+ rtc::Thread *worker_thread_;
+ bool incoming_only_;
+ bool waiting_for_signaling_;
+ int error_;
+ std::vector<PortAllocatorSession*> allocator_sessions_;
+ std::vector<PortInterface *> ports_;
+ std::vector<Connection *> connections_;
+ Connection* best_connection_;
+ // Connection selected by the controlling agent. This should be used only
+ // at controlled side when protocol type is RFC5245.
+ Connection* pending_best_connection_;
+ std::vector<RemoteCandidate> remote_candidates_;
+ bool sort_dirty_; // indicates whether another sort is needed right now
+ bool was_writable_;
+ typedef std::map<rtc::Socket::Option, int> OptionMap;
+ OptionMap options_;
+ std::string ice_ufrag_;
+ std::string ice_pwd_;
+ std::string remote_ice_ufrag_;
+ std::string remote_ice_pwd_;
+ IceProtocolType protocol_type_;
+ IceMode remote_ice_mode_;
+ IceRole ice_role_;
+ uint64 tiebreaker_;
+ uint32 remote_candidate_generation_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransportChannel);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_
diff --git a/p2p/base/p2ptransportchannel_unittest.cc b/p2p/base/p2ptransportchannel_unittest.cc
new file mode 100644
index 00000000..4f32719e
--- /dev/null
+++ b/p2p/base/p2ptransportchannel_unittest.cc
@@ -0,0 +1,1702 @@
+/*
+ * Copyright 2009 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/p2ptransportchannel.h"
+#include "webrtc/p2p/base/testrelayserver.h"
+#include "webrtc/p2p/base/teststunserver.h"
+#include "webrtc/p2p/base/testturnserver.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/base/dscp.h"
+#include "webrtc/base/fakenetwork.h"
+#include "webrtc/base/firewallsocketserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/natserver.h"
+#include "webrtc/base/natsocketfactory.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/proxyserver.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using cricket::kDefaultPortAllocatorFlags;
+using cricket::kMinimumStepDelay;
+using cricket::kDefaultStepDelay;
+using cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
+using cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET;
+using cricket::ServerAddresses;
+using rtc::SocketAddress;
+
+static const int kDefaultTimeout = 1000;
+static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_DISABLE_TCP;
+// Addresses on the public internet.
+static const SocketAddress kPublicAddrs[2] =
+ { SocketAddress("11.11.11.11", 0), SocketAddress("22.22.22.22", 0) };
+// IPv6 Addresses on the public internet.
+static const SocketAddress kIPv6PublicAddrs[2] = {
+ SocketAddress("2400:4030:1:2c00:be30:abcd:efab:cdef", 0),
+ SocketAddress("2620:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)
+};
+// For configuring multihomed clients.
+static const SocketAddress kAlternateAddrs[2] =
+ { SocketAddress("11.11.11.101", 0), SocketAddress("22.22.22.202", 0) };
+// Addresses for HTTP proxy servers.
+static const SocketAddress kHttpsProxyAddrs[2] =
+ { SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
+// Addresses for SOCKS proxy servers.
+static const SocketAddress kSocksProxyAddrs[2] =
+ { SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
+// Internal addresses for NAT boxes.
+static const SocketAddress kNatAddrs[2] =
+ { SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
+// Private addresses inside the NAT private networks.
+static const SocketAddress kPrivateAddrs[2] =
+ { SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0) };
+// For cascaded NATs, the internal addresses of the inner NAT boxes.
+static const SocketAddress kCascadedNatAddrs[2] =
+ { SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0) };
+// For cascaded NATs, private addresses inside the inner private networks.
+static const SocketAddress kCascadedPrivateAddrs[2] =
+ { SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0) };
+// The address of the public STUN server.
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+// The addresses for the public relay server.
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+// The addresses for the public turn server.
+static const SocketAddress kTurnUdpIntAddr("99.99.99.4",
+ cricket::STUN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const cricket::RelayCredentials kRelayCredentials("test", "test");
+
+// Based on ICE_UFRAG_LENGTH
+static const char* kIceUfrag[4] = {"TESTICEUFRAG0000", "TESTICEUFRAG0001",
+ "TESTICEUFRAG0002", "TESTICEUFRAG0003"};
+// Based on ICE_PWD_LENGTH
+static const char* kIcePwd[4] = {"TESTICEPWD00000000000000",
+ "TESTICEPWD00000000000001",
+ "TESTICEPWD00000000000002",
+ "TESTICEPWD00000000000003"};
+
+static const uint64 kTiebreaker1 = 11111;
+static const uint64 kTiebreaker2 = 22222;
+
+// This test simulates 2 P2P endpoints that want to establish connectivity
+// with each other over various network topologies and conditions, which can be
+// specified in each individial test.
+// A virtual network (via VirtualSocketServer) along with virtual firewalls and
+// NATs (via Firewall/NATSocketServer) are used to simulate the various network
+// conditions. We can configure the IP addresses of the endpoints,
+// block various types of connectivity, or add arbitrary levels of NAT.
+// We also run a STUN server and a relay server on the virtual network to allow
+// our typical P2P mechanisms to do their thing.
+// For each case, we expect the P2P stack to eventually settle on a specific
+// form of connectivity to the other side. The test checks that the P2P
+// negotiation successfully establishes connectivity within a certain time,
+// and that the result is what we expect.
+// Note that this class is a base class for use by other tests, who will provide
+// specialized test behavior.
+class P2PTransportChannelTestBase : public testing::Test,
+ public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ P2PTransportChannelTestBase()
+ : main_(rtc::Thread::Current()),
+ pss_(new rtc::PhysicalSocketServer),
+ vss_(new rtc::VirtualSocketServer(pss_.get())),
+ nss_(new rtc::NATSocketServer(vss_.get())),
+ ss_(new rtc::FirewallSocketServer(nss_.get())),
+ ss_scope_(ss_.get()),
+ stun_server_(cricket::TestStunServer::Create(main_, kStunAddr)),
+ turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
+ relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+ kRelayTcpIntAddr, kRelayTcpExtAddr,
+ kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+ socks_server1_(ss_.get(), kSocksProxyAddrs[0],
+ ss_.get(), kSocksProxyAddrs[0]),
+ socks_server2_(ss_.get(), kSocksProxyAddrs[1],
+ ss_.get(), kSocksProxyAddrs[1]),
+ clear_remote_candidates_ufrag_pwd_(false),
+ force_relay_(false) {
+ ep1_.role_ = cricket::ICEROLE_CONTROLLING;
+ ep2_.role_ = cricket::ICEROLE_CONTROLLED;
+
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ ep1_.allocator_.reset(new cricket::BasicPortAllocator(
+ &ep1_.network_manager_,
+ stun_servers, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr));
+ ep2_.allocator_.reset(new cricket::BasicPortAllocator(
+ &ep2_.network_manager_,
+ stun_servers, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr));
+ }
+
+ protected:
+ enum Config {
+ OPEN, // Open to the Internet
+ NAT_FULL_CONE, // NAT, no filtering
+ NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv
+ NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv
+ NAT_SYMMETRIC, // NAT, endpoint-dependent bindings
+ NAT_DOUBLE_CONE, // Double NAT, both cone
+ NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
+ BLOCK_UDP, // Firewall, UDP in/out blocked
+ BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
+ BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
+ PROXY_HTTPS, // All traffic through HTTPS proxy
+ PROXY_SOCKS, // All traffic through SOCKS proxy
+ NUM_CONFIGS
+ };
+
+ struct Result {
+ Result(const std::string& lt, const std::string& lp,
+ const std::string& rt, const std::string& rp,
+ const std::string& lt2, const std::string& lp2,
+ const std::string& rt2, const std::string& rp2, int wait)
+ : local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp),
+ local_type2(lt2), local_proto2(lp2), remote_type2(rt2),
+ remote_proto2(rp2), connect_wait(wait) {
+ }
+ std::string local_type;
+ std::string local_proto;
+ std::string remote_type;
+ std::string remote_proto;
+ std::string local_type2;
+ std::string local_proto2;
+ std::string remote_type2;
+ std::string remote_proto2;
+ int connect_wait;
+ };
+
+ struct ChannelData {
+ bool CheckData(const char* data, int len) {
+ bool ret = false;
+ if (!ch_packets_.empty()) {
+ std::string packet = ch_packets_.front();
+ ret = (packet == std::string(data, len));
+ ch_packets_.pop_front();
+ }
+ return ret;
+ }
+
+ std::string name_; // TODO - Currently not used.
+ std::list<std::string> ch_packets_;
+ rtc::scoped_ptr<cricket::P2PTransportChannel> ch_;
+ };
+
+ struct Endpoint {
+ Endpoint() : signaling_delay_(0), role_(cricket::ICEROLE_UNKNOWN),
+ tiebreaker_(0), role_conflict_(false),
+ protocol_type_(cricket::ICEPROTO_GOOGLE) {}
+ bool HasChannel(cricket::TransportChannel* ch) {
+ return (ch == cd1_.ch_.get() || ch == cd2_.ch_.get());
+ }
+ ChannelData* GetChannelData(cricket::TransportChannel* ch) {
+ if (!HasChannel(ch)) return NULL;
+ if (cd1_.ch_.get() == ch)
+ return &cd1_;
+ else
+ return &cd2_;
+ }
+ void SetSignalingDelay(int delay) { signaling_delay_ = delay; }
+
+ void SetIceRole(cricket::IceRole role) { role_ = role; }
+ cricket::IceRole ice_role() { return role_; }
+ void SetIceProtocolType(cricket::IceProtocolType type) {
+ protocol_type_ = type;
+ }
+ cricket::IceProtocolType protocol_type() { return protocol_type_; }
+ void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+ uint64 GetIceTiebreaker() { return tiebreaker_; }
+ void OnRoleConflict(bool role_conflict) { role_conflict_ = role_conflict; }
+ bool role_conflict() { return role_conflict_; }
+ void SetAllocationStepDelay(uint32 delay) {
+ allocator_->set_step_delay(delay);
+ }
+ void SetAllowTcpListen(bool allow_tcp_listen) {
+ allocator_->set_allow_tcp_listen(allow_tcp_listen);
+ }
+
+ rtc::FakeNetworkManager network_manager_;
+ rtc::scoped_ptr<cricket::BasicPortAllocator> allocator_;
+ ChannelData cd1_;
+ ChannelData cd2_;
+ int signaling_delay_;
+ cricket::IceRole role_;
+ uint64 tiebreaker_;
+ bool role_conflict_;
+ cricket::IceProtocolType protocol_type_;
+ };
+
+ struct CandidateData : public rtc::MessageData {
+ CandidateData(cricket::TransportChannel* ch, const cricket::Candidate& c)
+ : channel(ch), candidate(c) {
+ }
+ cricket::TransportChannel* channel;
+ cricket::Candidate candidate;
+ };
+
+ ChannelData* GetChannelData(cricket::TransportChannel* channel) {
+ if (ep1_.HasChannel(channel))
+ return ep1_.GetChannelData(channel);
+ else
+ return ep2_.GetChannelData(channel);
+ }
+
+ void CreateChannels(int num) {
+ std::string ice_ufrag_ep1_cd1_ch = kIceUfrag[0];
+ std::string ice_pwd_ep1_cd1_ch = kIcePwd[0];
+ std::string ice_ufrag_ep2_cd1_ch = kIceUfrag[1];
+ std::string ice_pwd_ep2_cd1_ch = kIcePwd[1];
+ ep1_.cd1_.ch_.reset(CreateChannel(
+ 0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch,
+ ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch));
+ ep2_.cd1_.ch_.reset(CreateChannel(
+ 1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch,
+ ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch));
+ if (num == 2) {
+ std::string ice_ufrag_ep1_cd2_ch = kIceUfrag[2];
+ std::string ice_pwd_ep1_cd2_ch = kIcePwd[2];
+ std::string ice_ufrag_ep2_cd2_ch = kIceUfrag[3];
+ std::string ice_pwd_ep2_cd2_ch = kIcePwd[3];
+ // In BUNDLE each endpoint must share common ICE credentials.
+ if (ep1_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) {
+ ice_ufrag_ep1_cd2_ch = ice_ufrag_ep1_cd1_ch;
+ ice_pwd_ep1_cd2_ch = ice_pwd_ep1_cd1_ch;
+ }
+ if (ep2_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) {
+ ice_ufrag_ep2_cd2_ch = ice_ufrag_ep2_cd1_ch;
+ ice_pwd_ep2_cd2_ch = ice_pwd_ep2_cd1_ch;
+ }
+ ep1_.cd2_.ch_.reset(CreateChannel(
+ 0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch,
+ ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch));
+ ep2_.cd2_.ch_.reset(CreateChannel(
+ 1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch,
+ ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch));
+ }
+ }
+ cricket::P2PTransportChannel* CreateChannel(
+ int endpoint,
+ int component,
+ const std::string& local_ice_ufrag,
+ const std::string& local_ice_pwd,
+ const std::string& remote_ice_ufrag,
+ const std::string& remote_ice_pwd) {
+ cricket::P2PTransportChannel* channel = new cricket::P2PTransportChannel(
+ "test content name", component, NULL, GetAllocator(endpoint));
+ channel->SignalRequestSignaling.connect(
+ this, &P2PTransportChannelTestBase::OnChannelRequestSignaling);
+ channel->SignalCandidateReady.connect(this,
+ &P2PTransportChannelTestBase::OnCandidate);
+ channel->SignalReadPacket.connect(
+ this, &P2PTransportChannelTestBase::OnReadPacket);
+ channel->SignalRoleConflict.connect(
+ this, &P2PTransportChannelTestBase::OnRoleConflict);
+ channel->SetIceProtocolType(GetEndpoint(endpoint)->protocol_type());
+ channel->SetIceCredentials(local_ice_ufrag, local_ice_pwd);
+ if (clear_remote_candidates_ufrag_pwd_) {
+ // This only needs to be set if we're clearing them from the
+ // candidates. Some unit tests rely on this not being set.
+ channel->SetRemoteIceCredentials(remote_ice_ufrag, remote_ice_pwd);
+ }
+ channel->SetIceRole(GetEndpoint(endpoint)->ice_role());
+ channel->SetIceTiebreaker(GetEndpoint(endpoint)->GetIceTiebreaker());
+ channel->Connect();
+ return channel;
+ }
+ void DestroyChannels() {
+ ep1_.cd1_.ch_.reset();
+ ep2_.cd1_.ch_.reset();
+ ep1_.cd2_.ch_.reset();
+ ep2_.cd2_.ch_.reset();
+ }
+ cricket::P2PTransportChannel* ep1_ch1() { return ep1_.cd1_.ch_.get(); }
+ cricket::P2PTransportChannel* ep1_ch2() { return ep1_.cd2_.ch_.get(); }
+ cricket::P2PTransportChannel* ep2_ch1() { return ep2_.cd1_.ch_.get(); }
+ cricket::P2PTransportChannel* ep2_ch2() { return ep2_.cd2_.ch_.get(); }
+
+ // Common results.
+ static const Result kLocalUdpToLocalUdp;
+ static const Result kLocalUdpToStunUdp;
+ static const Result kLocalUdpToPrflxUdp;
+ static const Result kPrflxUdpToLocalUdp;
+ static const Result kStunUdpToLocalUdp;
+ static const Result kStunUdpToStunUdp;
+ static const Result kPrflxUdpToStunUdp;
+ static const Result kLocalUdpToRelayUdp;
+ static const Result kPrflxUdpToRelayUdp;
+ static const Result kLocalTcpToLocalTcp;
+ static const Result kLocalTcpToPrflxTcp;
+ static const Result kPrflxTcpToLocalTcp;
+
+ rtc::NATSocketServer* nat() { return nss_.get(); }
+ rtc::FirewallSocketServer* fw() { return ss_.get(); }
+
+ Endpoint* GetEndpoint(int endpoint) {
+ if (endpoint == 0) {
+ return &ep1_;
+ } else if (endpoint == 1) {
+ return &ep2_;
+ } else {
+ return NULL;
+ }
+ }
+ cricket::PortAllocator* GetAllocator(int endpoint) {
+ return GetEndpoint(endpoint)->allocator_.get();
+ }
+ void AddAddress(int endpoint, const SocketAddress& addr) {
+ GetEndpoint(endpoint)->network_manager_.AddInterface(addr);
+ }
+ void RemoveAddress(int endpoint, const SocketAddress& addr) {
+ GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
+ }
+ void SetProxy(int endpoint, rtc::ProxyType type) {
+ rtc::ProxyInfo info;
+ info.type = type;
+ info.address = (type == rtc::PROXY_HTTPS) ?
+ kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
+ GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
+ }
+ void SetAllocatorFlags(int endpoint, int flags) {
+ GetAllocator(endpoint)->set_flags(flags);
+ }
+ void SetSignalingDelay(int endpoint, int delay) {
+ GetEndpoint(endpoint)->SetSignalingDelay(delay);
+ }
+ void SetIceProtocol(int endpoint, cricket::IceProtocolType type) {
+ GetEndpoint(endpoint)->SetIceProtocolType(type);
+ }
+ void SetIceRole(int endpoint, cricket::IceRole role) {
+ GetEndpoint(endpoint)->SetIceRole(role);
+ }
+ void SetIceTiebreaker(int endpoint, uint64 tiebreaker) {
+ GetEndpoint(endpoint)->SetIceTiebreaker(tiebreaker);
+ }
+ bool GetRoleConflict(int endpoint) {
+ return GetEndpoint(endpoint)->role_conflict();
+ }
+ void SetAllocationStepDelay(int endpoint, uint32 delay) {
+ return GetEndpoint(endpoint)->SetAllocationStepDelay(delay);
+ }
+ void SetAllowTcpListen(int endpoint, bool allow_tcp_listen) {
+ return GetEndpoint(endpoint)->SetAllowTcpListen(allow_tcp_listen);
+ }
+
+ void Test(const Result& expected) {
+ int32 connect_start = rtc::Time(), connect_time;
+
+ // Create the channels and wait for them to connect.
+ CreateChannels(1);
+ EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL &&
+ ep2_ch1() != NULL &&
+ ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ expected.connect_wait,
+ 1000);
+ connect_time = rtc::TimeSince(connect_start);
+ if (connect_time < expected.connect_wait) {
+ LOG(LS_INFO) << "Connect time: " << connect_time << " ms";
+ } else {
+ LOG(LS_INFO) << "Connect time: " << "TIMEOUT ("
+ << expected.connect_wait << " ms)";
+ }
+
+ // Allow a few turns of the crank for the best connections to emerge.
+ // This may take up to 2 seconds.
+ if (ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection()) {
+ int32 converge_start = rtc::Time(), converge_time;
+ int converge_wait = 2000;
+ EXPECT_TRUE_WAIT_MARGIN(
+ LocalCandidate(ep1_ch1())->type() == expected.local_type &&
+ LocalCandidate(ep1_ch1())->protocol() == expected.local_proto &&
+ RemoteCandidate(ep1_ch1())->type() == expected.remote_type &&
+ RemoteCandidate(ep1_ch1())->protocol() == expected.remote_proto,
+ converge_wait,
+ converge_wait);
+
+ // Also do EXPECT_EQ on each part so that failures are more verbose.
+ EXPECT_EQ(expected.local_type, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(expected.local_proto, LocalCandidate(ep1_ch1())->protocol());
+ EXPECT_EQ(expected.remote_type, RemoteCandidate(ep1_ch1())->type());
+ EXPECT_EQ(expected.remote_proto, RemoteCandidate(ep1_ch1())->protocol());
+
+ // Verifying remote channel best connection information. This is done
+ // only for the RFC 5245 as controlled agent will use USE-CANDIDATE
+ // from controlling (ep1) agent. We can easily predict from EP1 result
+ // matrix.
+ if (ep2_.protocol_type_ == cricket::ICEPROTO_RFC5245) {
+ // Checking for best connection candidates information at remote.
+ EXPECT_TRUE_WAIT(
+ LocalCandidate(ep2_ch1())->type() == expected.local_type2 &&
+ LocalCandidate(ep2_ch1())->protocol() == expected.local_proto2 &&
+ RemoteCandidate(ep2_ch1())->protocol() == expected.remote_proto2,
+ kDefaultTimeout);
+
+ // For verbose
+ EXPECT_EQ(expected.local_type2, LocalCandidate(ep2_ch1())->type());
+ EXPECT_EQ(expected.local_proto2, LocalCandidate(ep2_ch1())->protocol());
+ EXPECT_EQ(expected.remote_proto2,
+ RemoteCandidate(ep2_ch1())->protocol());
+ // Removed remote_type comparision aginst best connection remote
+ // candidate. This is done to handle remote type discrepancy from
+ // local to stun based on the test type.
+ // For example in case of Open -> NAT, ep2 channels will have LULU
+ // and in other cases like NAT -> NAT it will be LUSU. To avoid these
+ // mismatches and we are doing comparision in different way.
+ // i.e. when don't match its remote type is either local or stun.
+ // TODO(ronghuawu): Refine the test criteria.
+ // https://code.google.com/p/webrtc/issues/detail?id=1953
+ if (expected.remote_type2 != RemoteCandidate(ep2_ch1())->type()) {
+ EXPECT_TRUE(expected.remote_type2 == cricket::LOCAL_PORT_TYPE ||
+ expected.remote_type2 == cricket::STUN_PORT_TYPE);
+ EXPECT_TRUE(
+ RemoteCandidate(ep2_ch1())->type() == cricket::LOCAL_PORT_TYPE ||
+ RemoteCandidate(ep2_ch1())->type() == cricket::STUN_PORT_TYPE ||
+ RemoteCandidate(ep2_ch1())->type() == cricket::PRFLX_PORT_TYPE);
+ }
+ }
+
+ converge_time = rtc::TimeSince(converge_start);
+ if (converge_time < converge_wait) {
+ LOG(LS_INFO) << "Converge time: " << converge_time << " ms";
+ } else {
+ LOG(LS_INFO) << "Converge time: " << "TIMEOUT ("
+ << converge_wait << " ms)";
+ }
+ }
+ // Try sending some data to other end.
+ TestSendRecv(1);
+
+ // Destroy the channels, and wait for them to be fully cleaned up.
+ DestroyChannels();
+ }
+
+ void TestSendRecv(int channels) {
+ for (int i = 0; i < 10; ++i) {
+ const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ int len = static_cast<int>(strlen(data));
+ // local_channel1 <==> remote_channel1
+ EXPECT_EQ_WAIT(len, SendData(ep1_ch1(), data, len), 1000);
+ EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch1(), data, len), 1000);
+ EXPECT_EQ_WAIT(len, SendData(ep2_ch1(), data, len), 1000);
+ EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch1(), data, len), 1000);
+ if (channels == 2 && ep1_ch2() && ep2_ch2()) {
+ // local_channel2 <==> remote_channel2
+ EXPECT_EQ_WAIT(len, SendData(ep1_ch2(), data, len), 1000);
+ EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch2(), data, len), 1000);
+ EXPECT_EQ_WAIT(len, SendData(ep2_ch2(), data, len), 1000);
+ EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch2(), data, len), 1000);
+ }
+ }
+ }
+
+ // This test waits for the transport to become readable and writable on both
+ // end points. Once they are, the end points set new local ice credentials to
+ // restart the ice gathering. Finally it waits for the transport to select a
+ // new connection using the newly generated ice candidates.
+ // Before calling this function the end points must be configured.
+ void TestHandleIceUfragPasswordChanged() {
+ ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[1], kIcePwd[1]);
+ ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[0], kIcePwd[0]);
+ EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000, 1000);
+
+ const cricket::Candidate* old_local_candidate1 = LocalCandidate(ep1_ch1());
+ const cricket::Candidate* old_local_candidate2 = LocalCandidate(ep2_ch1());
+ const cricket::Candidate* old_remote_candidate1 =
+ RemoteCandidate(ep1_ch1());
+ const cricket::Candidate* old_remote_candidate2 =
+ RemoteCandidate(ep2_ch1());
+
+ ep1_ch1()->SetIceCredentials(kIceUfrag[2], kIcePwd[2]);
+ ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[3], kIcePwd[3]);
+ ep2_ch1()->SetIceCredentials(kIceUfrag[3], kIcePwd[3]);
+ ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]);
+
+ EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep1_ch1())->generation() !=
+ old_local_candidate1->generation(),
+ 1000, 1000);
+ EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep2_ch1())->generation() !=
+ old_local_candidate2->generation(),
+ 1000, 1000);
+ EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep1_ch1())->generation() !=
+ old_remote_candidate1->generation(),
+ 1000, 1000);
+ EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep2_ch1())->generation() !=
+ old_remote_candidate2->generation(),
+ 1000, 1000);
+ EXPECT_EQ(1u, RemoteCandidate(ep2_ch1())->generation());
+ EXPECT_EQ(1u, RemoteCandidate(ep1_ch1())->generation());
+ }
+
+ void TestSignalRoleConflict() {
+ SetIceProtocol(0, cricket::ICEPROTO_RFC5245);
+ SetIceTiebreaker(0, kTiebreaker1); // Default EP1 is in controlling state.
+
+ SetIceProtocol(1, cricket::ICEPROTO_RFC5245);
+ SetIceRole(1, cricket::ICEROLE_CONTROLLING);
+ SetIceTiebreaker(1, kTiebreaker2);
+
+ // Creating channels with both channels role set to CONTROLLING.
+ CreateChannels(1);
+ // Since both the channels initiated with controlling state and channel2
+ // has higher tiebreaker value, channel1 should receive SignalRoleConflict.
+ EXPECT_TRUE_WAIT(GetRoleConflict(0), 1000);
+ EXPECT_FALSE(GetRoleConflict(1));
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ 1000);
+
+ EXPECT_TRUE(ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection());
+
+ TestSendRecv(1);
+ }
+
+ void TestHybridConnectivity(cricket::IceProtocolType proto) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ SetIceRole(0, cricket::ICEROLE_CONTROLLING);
+ SetIceProtocol(0, cricket::ICEPROTO_HYBRID);
+ SetIceTiebreaker(0, kTiebreaker1);
+ SetIceRole(1, cricket::ICEROLE_CONTROLLED);
+ SetIceProtocol(1, proto);
+ SetIceTiebreaker(1, kTiebreaker2);
+
+ CreateChannels(1);
+ // When channel is in hybrid and it's controlling agent, channel will
+ // receive ping request from the remote. Hence connection is readable.
+ // Since channel is in hybrid, it will not send any pings, so no writable
+ // connection. Since channel2 is in controlled state, it will not have
+ // any connections which are readable or writable, as it didn't received
+ // pings (or none) with USE-CANDIDATE attribute.
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable(), 1000);
+
+ // Set real protocol type.
+ ep1_ch1()->SetIceProtocolType(proto);
+
+ // Channel should able to send ping requests and connections become writable
+ // in both directions.
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ TestSendRecv(1);
+ DestroyChannels();
+ }
+
+ void OnChannelRequestSignaling(cricket::TransportChannelImpl* channel) {
+ channel->OnSignalingReady();
+ }
+ // We pass the candidates directly to the other side.
+ void OnCandidate(cricket::TransportChannelImpl* ch,
+ const cricket::Candidate& c) {
+ if (force_relay_ && c.type() != cricket::RELAY_PORT_TYPE)
+ return;
+
+ main_->PostDelayed(GetEndpoint(ch)->signaling_delay_, this, 0,
+ new CandidateData(ch, c));
+ }
+ void OnMessage(rtc::Message* msg) {
+ rtc::scoped_ptr<CandidateData> data(
+ static_cast<CandidateData*>(msg->pdata));
+ cricket::P2PTransportChannel* rch = GetRemoteChannel(data->channel);
+ cricket::Candidate c = data->candidate;
+ if (clear_remote_candidates_ufrag_pwd_) {
+ c.set_username("");
+ c.set_password("");
+ }
+ LOG(LS_INFO) << "Candidate(" << data->channel->component() << "->"
+ << rch->component() << "): " << c.type() << ", " << c.protocol()
+ << ", " << c.address().ToString() << ", " << c.username()
+ << ", " << c.generation();
+ rch->OnCandidate(c);
+ }
+ void OnReadPacket(cricket::TransportChannel* channel, const char* data,
+ size_t len, const rtc::PacketTime& packet_time,
+ int flags) {
+ std::list<std::string>& packets = GetPacketList(channel);
+ packets.push_front(std::string(data, len));
+ }
+ void OnRoleConflict(cricket::TransportChannelImpl* channel) {
+ GetEndpoint(channel)->OnRoleConflict(true);
+ cricket::IceRole new_role =
+ GetEndpoint(channel)->ice_role() == cricket::ICEROLE_CONTROLLING ?
+ cricket::ICEROLE_CONTROLLED : cricket::ICEROLE_CONTROLLING;
+ channel->SetIceRole(new_role);
+ }
+ int SendData(cricket::TransportChannel* channel,
+ const char* data, size_t len) {
+ rtc::PacketOptions options;
+ return channel->SendPacket(data, len, options, 0);
+ }
+ bool CheckDataOnChannel(cricket::TransportChannel* channel,
+ const char* data, int len) {
+ return GetChannelData(channel)->CheckData(data, len);
+ }
+ static const cricket::Candidate* LocalCandidate(
+ cricket::P2PTransportChannel* ch) {
+ return (ch && ch->best_connection()) ?
+ &ch->best_connection()->local_candidate() : NULL;
+ }
+ static const cricket::Candidate* RemoteCandidate(
+ cricket::P2PTransportChannel* ch) {
+ return (ch && ch->best_connection()) ?
+ &ch->best_connection()->remote_candidate() : NULL;
+ }
+ Endpoint* GetEndpoint(cricket::TransportChannel* ch) {
+ if (ep1_.HasChannel(ch)) {
+ return &ep1_;
+ } else if (ep2_.HasChannel(ch)) {
+ return &ep2_;
+ } else {
+ return NULL;
+ }
+ }
+ cricket::P2PTransportChannel* GetRemoteChannel(
+ cricket::TransportChannel* ch) {
+ if (ch == ep1_ch1())
+ return ep2_ch1();
+ else if (ch == ep1_ch2())
+ return ep2_ch2();
+ else if (ch == ep2_ch1())
+ return ep1_ch1();
+ else if (ch == ep2_ch2())
+ return ep1_ch2();
+ else
+ return NULL;
+ }
+ std::list<std::string>& GetPacketList(cricket::TransportChannel* ch) {
+ return GetChannelData(ch)->ch_packets_;
+ }
+
+ void set_clear_remote_candidates_ufrag_pwd(bool clear) {
+ clear_remote_candidates_ufrag_pwd_ = clear;
+ }
+
+ void set_force_relay(bool relay) {
+ force_relay_ = relay;
+ }
+
+ private:
+ rtc::Thread* main_;
+ rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::scoped_ptr<rtc::NATSocketServer> nss_;
+ rtc::scoped_ptr<rtc::FirewallSocketServer> ss_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::scoped_ptr<cricket::TestStunServer> stun_server_;
+ cricket::TestTurnServer turn_server_;
+ cricket::TestRelayServer relay_server_;
+ rtc::SocksProxyServer socks_server1_;
+ rtc::SocksProxyServer socks_server2_;
+ Endpoint ep1_;
+ Endpoint ep2_;
+ bool clear_remote_candidates_ufrag_pwd_;
+ bool force_relay_;
+};
+
+// The tests have only a few outcomes, which we predefine.
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToLocalUdp("local", "udp", "local", "udp",
+ "local", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToStunUdp("local", "udp", "stun", "udp",
+ "local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToPrflxUdp("local", "udp", "prflx", "udp",
+ "prflx", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kPrflxUdpToLocalUdp("prflx", "udp", "local", "udp",
+ "local", "udp", "prflx", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kStunUdpToLocalUdp("stun", "udp", "local", "udp",
+ "local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kStunUdpToStunUdp("stun", "udp", "stun", "udp",
+ "stun", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kPrflxUdpToStunUdp("prflx", "udp", "stun", "udp",
+ "local", "udp", "prflx", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToRelayUdp("local", "udp", "relay", "udp",
+ "relay", "udp", "local", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kPrflxUdpToRelayUdp("prflx", "udp", "relay", "udp",
+ "relay", "udp", "prflx", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalTcpToLocalTcp("local", "tcp", "local", "tcp",
+ "local", "tcp", "local", "tcp", 3000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalTcpToPrflxTcp("local", "tcp", "prflx", "tcp",
+ "prflx", "tcp", "local", "tcp", 3000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kPrflxTcpToLocalTcp("prflx", "tcp", "local", "tcp",
+ "local", "tcp", "prflx", "tcp", 3000);
+
+// Test the matrix of all the connectivity types we expect to see in the wild.
+// Just test every combination of the configs in the Config enum.
+class P2PTransportChannelTest : public P2PTransportChannelTestBase {
+ protected:
+ static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
+ static const Result* kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS];
+ static const Result* kMatrixSharedSocketAsGice[NUM_CONFIGS][NUM_CONFIGS];
+ static const Result* kMatrixSharedSocketAsIce[NUM_CONFIGS][NUM_CONFIGS];
+ void ConfigureEndpoints(Config config1, Config config2,
+ int allocator_flags1, int allocator_flags2,
+ int delay1, int delay2,
+ cricket::IceProtocolType type) {
+ // Ideally we want to use TURN server for both GICE and ICE, but in case
+ // of GICE, TURN server usage is not producing results reliabally.
+ // TODO(mallinath): Remove Relay and use TURN server for all tests.
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ GetEndpoint(0)->allocator_.reset(
+ new cricket::BasicPortAllocator(&(GetEndpoint(0)->network_manager_),
+ stun_servers,
+ rtc::SocketAddress(), rtc::SocketAddress(),
+ rtc::SocketAddress()));
+ GetEndpoint(1)->allocator_.reset(
+ new cricket::BasicPortAllocator(&(GetEndpoint(1)->network_manager_),
+ stun_servers,
+ rtc::SocketAddress(), rtc::SocketAddress(),
+ rtc::SocketAddress()));
+
+ cricket::RelayServerConfig relay_server(cricket::RELAY_GTURN);
+ if (type == cricket::ICEPROTO_RFC5245) {
+ relay_server.type = cricket::RELAY_TURN;
+ relay_server.credentials = kRelayCredentials;
+ relay_server.ports.push_back(cricket::ProtocolAddress(
+ kTurnUdpIntAddr, cricket::PROTO_UDP, false));
+ } else {
+ relay_server.ports.push_back(cricket::ProtocolAddress(
+ kRelayUdpIntAddr, cricket::PROTO_UDP, false));
+ relay_server.ports.push_back(cricket::ProtocolAddress(
+ kRelayTcpIntAddr, cricket::PROTO_TCP, false));
+ relay_server.ports.push_back(cricket::ProtocolAddress(
+ kRelaySslTcpIntAddr, cricket::PROTO_SSLTCP, false));
+ }
+ GetEndpoint(0)->allocator_->AddRelay(relay_server);
+ GetEndpoint(1)->allocator_->AddRelay(relay_server);
+
+ ConfigureEndpoint(0, config1);
+ SetIceProtocol(0, type);
+ SetAllocatorFlags(0, allocator_flags1);
+ SetAllocationStepDelay(0, delay1);
+ ConfigureEndpoint(1, config2);
+ SetIceProtocol(1, type);
+ SetAllocatorFlags(1, allocator_flags2);
+ SetAllocationStepDelay(1, delay2);
+ }
+ void ConfigureEndpoint(int endpoint, Config config) {
+ switch (config) {
+ case OPEN:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ break;
+ case NAT_FULL_CONE:
+ case NAT_ADDR_RESTRICTED:
+ case NAT_PORT_RESTRICTED:
+ case NAT_SYMMETRIC:
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ // Add a single NAT of the desired type
+ nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ static_cast<rtc::NATType>(config - NAT_FULL_CONE))->
+ AddClient(kPrivateAddrs[endpoint]);
+ break;
+ case NAT_DOUBLE_CONE:
+ case NAT_SYMMETRIC_THEN_CONE:
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ // Add a two cascaded NATs of the desired types
+ nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ (config == NAT_DOUBLE_CONE) ?
+ rtc::NAT_OPEN_CONE : rtc::NAT_SYMMETRIC)->
+ AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+ rtc::NAT_OPEN_CONE)->
+ AddClient(kCascadedPrivateAddrs[endpoint]);
+ break;
+ case BLOCK_UDP:
+ case BLOCK_UDP_AND_INCOMING_TCP:
+ case BLOCK_ALL_BUT_OUTGOING_HTTP:
+ case PROXY_HTTPS:
+ case PROXY_SOCKS:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ // Block all UDP
+ fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ if (config == BLOCK_UDP_AND_INCOMING_TCP) {
+ // Block TCP inbound to the endpoint
+ fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
+ kPublicAddrs[endpoint]);
+ } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
+ // Block all TCP to/from the endpoint except 80/443 out
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(rtc::IPAddress(INADDR_ANY), 80));
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(rtc::IPAddress(INADDR_ANY), 443));
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ } else if (config == PROXY_HTTPS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ kHttpsProxyAddrs[endpoint]);
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, rtc::PROXY_HTTPS);
+ } else if (config == PROXY_SOCKS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ kSocksProxyAddrs[endpoint]);
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, rtc::PROXY_SOCKS5);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+// Shorthands for use in the test matrix.
+#define LULU &kLocalUdpToLocalUdp
+#define LUSU &kLocalUdpToStunUdp
+#define LUPU &kLocalUdpToPrflxUdp
+#define PULU &kPrflxUdpToLocalUdp
+#define SULU &kStunUdpToLocalUdp
+#define SUSU &kStunUdpToStunUdp
+#define PUSU &kPrflxUdpToStunUdp
+#define LURU &kLocalUdpToRelayUdp
+#define PURU &kPrflxUdpToRelayUdp
+#define LTLT &kLocalTcpToLocalTcp
+#define LTPT &kLocalTcpToPrflxTcp
+#define PTLT &kPrflxTcpToLocalTcp
+// TODO: Enable these once TestRelayServer can accept external TCP.
+#define LTRT NULL
+#define LSRS NULL
+
+// Test matrix. Originator behavior defined by rows, receiever by columns.
+
+// Currently the p2ptransportchannel.cc (specifically the
+// P2PTransportChannel::OnUnknownAddress) operates in 2 modes depend on the
+// remote candidates - ufrag per port or shared ufrag.
+// For example, if the remote candidates have the shared ufrag, for the unknown
+// address reaches the OnUnknownAddress, we will try to find the matched
+// remote candidate based on the address and protocol, if not found, a new
+// remote candidate will be created for this address. But if the remote
+// candidates have different ufrags, we will try to find the matched remote
+// candidate by comparing the ufrag. If not found, an error will be returned.
+// Because currently the shared ufrag feature is under the experiment and will
+// be rolled out gradually. We want to test the different combinations of peers
+// with/without the shared ufrag enabled. And those different combinations have
+// different expectation of the best connection. For example in the OpenToCONE
+// case, an unknown address will be updated to a "host" remote candidate if the
+// remote peer uses different ufrag per port. But in the shared ufrag case,
+// a "stun" (should be peer-reflexive eventually) candidate will be created for
+// that. So the expected best candidate will be LUSU instead of LULU.
+// With all these, we have to keep 2 test matrixes for the tests:
+// kMatrix - for the tests that the remote peer uses different ufrag per port.
+// kMatrixSharedUfrag - for the tests that remote peer uses shared ufrag.
+// The different between the two matrixes are on:
+// OPToCONE, OPTo2CON,
+// COToCONE, COToADDR, COToPORT, COToSYMM, COTo2CON, COToSCON,
+// ADToCONE, ADToADDR, ADTo2CON,
+// POToADDR,
+// SYToADDR,
+// 2CToCONE, 2CToADDR, 2CToPORT, 2CToSYMM, 2CTo2CON, 2CToSCON,
+// SCToADDR,
+
+// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
+// TODO: Fix NULLs caused by no HTTP proxy support.
+// TODO: Rearrange rows/columns from best to worst.
+// TODO(ronghuawu): Keep only one test matrix once the shared ufrag is enabled.
+const P2PTransportChannelTest::Result*
+ P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
+// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
+/*OP*/ {LULU, LULU, LULU, LULU, LULU, LULU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LULU, LULU, SUSU, SUSU, LULU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+const P2PTransportChannelTest::Result*
+ P2PTransportChannelTest::kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS] = {
+// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
+/*OP*/ {LULU, LUSU, LULU, LULU, LULU, LUSU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+const P2PTransportChannelTest::Result*
+ P2PTransportChannelTest::kMatrixSharedSocketAsGice
+ [NUM_CONFIGS][NUM_CONFIGS] = {
+// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
+/*OP*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+const P2PTransportChannelTest::Result*
+ P2PTransportChannelTest::kMatrixSharedSocketAsIce
+ [NUM_CONFIGS][NUM_CONFIGS] = {
+// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
+/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, PTLT, LTPT, LSRS, NULL, PTLT},
+/*CO*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTPT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+
+// The actual tests that exercise all the various configurations.
+// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
+// Same test case is run in both GICE and ICE mode.
+// kDefaultStepDelay - is used for all Gice cases.
+// kMinimumStepDelay - is used when both end points have
+// PORTALLOCATOR_ENABLE_SHARED_UFRAG flag enabled.
+// Technically we should be able to use kMinimumStepDelay irrespective of
+// protocol type. But which might need modifications to current result matrices
+// for tests in this file.
+#define P2P_TEST_DECLARATION(x, y, z) \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceNoneSharedUfrag) { \
+ ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \
+ kDefaultPortAllocatorFlags, \
+ kDefaultStepDelay, kDefaultStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrix[x][y] != NULL) \
+ Test(*kMatrix[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP0SharedUfrag) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ kDefaultPortAllocatorFlags, \
+ kDefaultStepDelay, kDefaultStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrix[x][y] != NULL) \
+ Test(*kMatrix[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP1SharedUfrag) { \
+ ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ kDefaultStepDelay, kDefaultStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrixSharedUfrag[x][y] != NULL) \
+ Test(*kMatrixSharedUfrag[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceBothSharedUfrag) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ kDefaultStepDelay, kDefaultStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrixSharedUfrag[x][y] != NULL) \
+ Test(*kMatrixSharedUfrag[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, \
+ z##Test##x##To##y##AsGiceBothSharedUfragWithMinimumStepDelay) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+ kMinimumStepDelay, kMinimumStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrixSharedUfrag[x][y] != NULL) \
+ Test(*kMatrixSharedUfrag[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, \
+ z##Test##x##To##y##AsGiceBothSharedUfragSocket) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ kMinimumStepDelay, kMinimumStepDelay, \
+ cricket::ICEPROTO_GOOGLE); \
+ if (kMatrixSharedSocketAsGice[x][y] != NULL) \
+ Test(*kMatrixSharedSocketAsGice[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ } \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsIce) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ kMinimumStepDelay, kMinimumStepDelay, \
+ cricket::ICEPROTO_RFC5245); \
+ if (kMatrixSharedSocketAsIce[x][y] != NULL) \
+ Test(*kMatrixSharedSocketAsIce[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ }
+
+#define P2P_TEST(x, y) \
+ P2P_TEST_DECLARATION(x, y,)
+
+#define FLAKY_P2P_TEST(x, y) \
+ P2P_TEST_DECLARATION(x, y, DISABLED_)
+
+// TODO(holmer): Disabled due to randomly failing on webrtc buildbots.
+// Issue: webrtc/2383
+#define P2P_TEST_SET(x) \
+ P2P_TEST(x, OPEN) \
+ FLAKY_P2P_TEST(x, NAT_FULL_CONE) \
+ FLAKY_P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+ FLAKY_P2P_TEST(x, NAT_PORT_RESTRICTED) \
+ P2P_TEST(x, NAT_SYMMETRIC) \
+ FLAKY_P2P_TEST(x, NAT_DOUBLE_CONE) \
+ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+ P2P_TEST(x, BLOCK_UDP) \
+ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+ P2P_TEST(x, PROXY_HTTPS) \
+ P2P_TEST(x, PROXY_SOCKS)
+
+#define FLAKY_P2P_TEST_SET(x) \
+ P2P_TEST(x, OPEN) \
+ P2P_TEST(x, NAT_FULL_CONE) \
+ P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+ P2P_TEST(x, NAT_PORT_RESTRICTED) \
+ P2P_TEST(x, NAT_SYMMETRIC) \
+ P2P_TEST(x, NAT_DOUBLE_CONE) \
+ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+ P2P_TEST(x, BLOCK_UDP) \
+ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+ P2P_TEST(x, PROXY_HTTPS) \
+ P2P_TEST(x, PROXY_SOCKS)
+
+P2P_TEST_SET(OPEN)
+P2P_TEST_SET(NAT_FULL_CONE)
+P2P_TEST_SET(NAT_ADDR_RESTRICTED)
+P2P_TEST_SET(NAT_PORT_RESTRICTED)
+P2P_TEST_SET(NAT_SYMMETRIC)
+P2P_TEST_SET(NAT_DOUBLE_CONE)
+P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
+P2P_TEST_SET(BLOCK_UDP)
+P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
+P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
+P2P_TEST_SET(PROXY_HTTPS)
+P2P_TEST_SET(PROXY_SOCKS)
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Standard Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsIce) {
+ ConfigureEndpoints(OPEN, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kMinimumStepDelay, kMinimumStepDelay,
+ cricket::ICEPROTO_RFC5245);
+ CreateChannels(1);
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Standard Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsIce) {
+ ConfigureEndpoints(OPEN, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kMinimumStepDelay, kMinimumStepDelay,
+ cricket::ICEPROTO_RFC5245);
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+ CreateChannels(2);
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Google Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsGice) {
+ ConfigureEndpoints(OPEN, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+ CreateChannels(1);
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Test that ICE restart works when bundle is enabled.
+// Google Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsGice) {
+ ConfigureEndpoints(OPEN, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+ CreateChannels(2);
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Test the operation of GetStats.
+TEST_F(P2PTransportChannelTest, GetStats) {
+ ConfigureEndpoints(OPEN, OPEN,
+ kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+ CreateChannels(1);
+ EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000, 1000);
+ TestSendRecv(1);
+ cricket::ConnectionInfos infos;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&infos));
+ ASSERT_EQ(1U, infos.size());
+ EXPECT_TRUE(infos[0].new_connection);
+ EXPECT_TRUE(infos[0].best_connection);
+ EXPECT_TRUE(infos[0].readable);
+ EXPECT_TRUE(infos[0].writable);
+ EXPECT_FALSE(infos[0].timeout);
+ EXPECT_EQ(10 * 36U, infos[0].sent_total_bytes);
+ EXPECT_EQ(10 * 36U, infos[0].recv_total_bytes);
+ EXPECT_GT(infos[0].rtt, 0U);
+ DestroyChannels();
+}
+
+// Test that we properly handle getting a STUN error due to slow signaling.
+TEST_F(P2PTransportChannelTest, DISABLED_SlowSignaling) {
+ ConfigureEndpoints(OPEN, NAT_SYMMETRIC,
+ kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+ // Make signaling from the callee take 500ms, so that the initial STUN pings
+ // from the callee beat the signaling, and so the caller responds with a
+ // unknown username error. We should just eat that and carry on; mishandling
+ // this will instead cause all the callee's connections to be discarded.
+ SetSignalingDelay(1, 1000);
+ CreateChannels(1);
+ const cricket::Connection* best_connection = NULL;
+ // Wait until the callee's connections are created.
+ WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000);
+ // Wait to see if they get culled; they shouldn't.
+ WAIT(ep2_ch1()->best_connection() != best_connection, 1000);
+ EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection);
+ DestroyChannels();
+}
+
+// Test that if remote candidates don't have ufrag and pwd, we still work.
+TEST_F(P2PTransportChannelTest, RemoteCandidatesWithoutUfragPwd) {
+ set_clear_remote_candidates_ufrag_pwd(true);
+ ConfigureEndpoints(OPEN, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kMinimumStepDelay, kMinimumStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+ CreateChannels(1);
+ const cricket::Connection* best_connection = NULL;
+ // Wait until the callee's connections are created.
+ WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000);
+ // Wait to see if they get culled; they shouldn't.
+ WAIT(ep2_ch1()->best_connection() != best_connection, 1000);
+ EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection);
+ DestroyChannels();
+}
+
+// Test that a host behind NAT cannot be reached when incoming_only
+// is set to true.
+TEST_F(P2PTransportChannelTest, IncomingOnlyBlocked) {
+ ConfigureEndpoints(NAT_FULL_CONE, OPEN,
+ kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ CreateChannels(1);
+ ep1_ch1()->set_incoming_only(true);
+
+ // Pump for 1 second and verify that the channels are not connected.
+ rtc::Thread::Current()->ProcessMessages(1000);
+
+ EXPECT_FALSE(ep1_ch1()->readable());
+ EXPECT_FALSE(ep1_ch1()->writable());
+ EXPECT_FALSE(ep2_ch1()->readable());
+ EXPECT_FALSE(ep2_ch1()->writable());
+
+ DestroyChannels();
+}
+
+// Test that a peer behind NAT can connect to a peer that has
+// incoming_only flag set.
+TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
+ ConfigureEndpoints(OPEN, NAT_FULL_CONE,
+ kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_GOOGLE);
+
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ CreateChannels(1);
+ ep1_ch1()->set_incoming_only(true);
+
+ EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL && ep2_ch1() != NULL &&
+ ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000, 1000);
+
+ DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ int kOnlyLocalTcpPorts = cricket::PORTALLOCATOR_DISABLE_UDP |
+ cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
+ // Disable all protocols except TCP.
+ SetAllocatorFlags(0, kOnlyLocalTcpPorts);
+ SetAllocatorFlags(1, kOnlyLocalTcpPorts);
+
+ SetAllowTcpListen(0, true); // actpass.
+ SetAllowTcpListen(1, false); // active.
+
+ CreateChannels(1);
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ std::string kTcpProtocol = "tcp";
+ EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep1_ch1())->protocol());
+ EXPECT_EQ(kTcpProtocol, LocalCandidate(ep1_ch1())->protocol());
+ EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep2_ch1())->protocol());
+ EXPECT_EQ(kTcpProtocol, LocalCandidate(ep2_ch1())->protocol());
+
+ TestSendRecv(1);
+ DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestBundleAllocatorToBundleAllocator) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+ CreateChannels(2);
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection());
+
+ EXPECT_FALSE(ep1_ch2()->readable());
+ EXPECT_FALSE(ep1_ch2()->writable());
+ EXPECT_FALSE(ep2_ch2()->readable());
+ EXPECT_FALSE(ep2_ch2()->writable());
+
+ TestSendRecv(1); // Only 1 channel is writable per Endpoint.
+ DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestBundleAllocatorToNonBundleAllocator) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Enable BUNDLE flag at one side.
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+ CreateChannels(2);
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE_WAIT(ep1_ch2()->readable() &&
+ ep1_ch2()->writable() &&
+ ep2_ch2()->readable() &&
+ ep2_ch2()->writable(),
+ 1000);
+
+ EXPECT_TRUE(ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection());
+ EXPECT_TRUE(ep1_ch2()->best_connection() &&
+ ep2_ch2()->best_connection());
+
+ TestSendRecv(2);
+ DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithoutBundle) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ TestSignalRoleConflict();
+}
+
+TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithBundle) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ TestSignalRoleConflict();
+}
+
+// Tests that the ice configs (protocol, tiebreaker and role) can be passed
+// down to ports.
+TEST_F(P2PTransportChannelTest, TestIceConfigWillPassDownToPort) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetIceRole(0, cricket::ICEROLE_CONTROLLING);
+ SetIceProtocol(0, cricket::ICEPROTO_GOOGLE);
+ SetIceTiebreaker(0, kTiebreaker1);
+ SetIceRole(1, cricket::ICEROLE_CONTROLLING);
+ SetIceProtocol(1, cricket::ICEPROTO_RFC5245);
+ SetIceTiebreaker(1, kTiebreaker2);
+
+ CreateChannels(1);
+
+ EXPECT_EQ_WAIT(2u, ep1_ch1()->ports().size(), 1000);
+
+ const std::vector<cricket::PortInterface *> ports_before = ep1_ch1()->ports();
+ for (size_t i = 0; i < ports_before.size(); ++i) {
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, ports_before[i]->GetIceRole());
+ EXPECT_EQ(cricket::ICEPROTO_GOOGLE, ports_before[i]->IceProtocol());
+ EXPECT_EQ(kTiebreaker1, ports_before[i]->IceTiebreaker());
+ }
+
+ ep1_ch1()->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ ep1_ch1()->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ ep1_ch1()->SetIceTiebreaker(kTiebreaker2);
+
+ const std::vector<cricket::PortInterface *> ports_after = ep1_ch1()->ports();
+ for (size_t i = 0; i < ports_after.size(); ++i) {
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, ports_before[i]->GetIceRole());
+ EXPECT_EQ(cricket::ICEPROTO_RFC5245, ports_before[i]->IceProtocol());
+ // SetIceTiebreaker after Connect() has been called will fail. So expect the
+ // original value.
+ EXPECT_EQ(kTiebreaker1, ports_before[i]->IceTiebreaker());
+ }
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ 1000);
+
+ EXPECT_TRUE(ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection());
+
+ TestSendRecv(1);
+ DestroyChannels();
+}
+
+// This test verifies channel can handle ice messages when channel is in
+// hybrid mode.
+TEST_F(P2PTransportChannelTest, TestConnectivityBetweenHybridandIce) {
+ TestHybridConnectivity(cricket::ICEPROTO_RFC5245);
+}
+
+// This test verifies channel can handle Gice messages when channel is in
+// hybrid mode.
+TEST_F(P2PTransportChannelTest, TestConnectivityBetweenHybridandGice) {
+ TestHybridConnectivity(cricket::ICEPROTO_GOOGLE);
+}
+
+// Verify that we can set DSCP value and retrieve properly from P2PTC.
+TEST_F(P2PTransportChannelTest, TestDefaultDscpValue) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ CreateChannels(1);
+ EXPECT_EQ(rtc::DSCP_NO_CHANGE,
+ GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_NO_CHANGE,
+ GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+ GetEndpoint(0)->cd1_.ch_->SetOption(
+ rtc::Socket::OPT_DSCP, rtc::DSCP_CS6);
+ GetEndpoint(1)->cd1_.ch_->SetOption(
+ rtc::Socket::OPT_DSCP, rtc::DSCP_CS6);
+ EXPECT_EQ(rtc::DSCP_CS6,
+ GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_CS6,
+ GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+ GetEndpoint(0)->cd1_.ch_->SetOption(
+ rtc::Socket::OPT_DSCP, rtc::DSCP_AF41);
+ GetEndpoint(1)->cd1_.ch_->SetOption(
+ rtc::Socket::OPT_DSCP, rtc::DSCP_AF41);
+ EXPECT_EQ(rtc::DSCP_AF41,
+ GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_AF41,
+ GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+}
+
+// Verify IPv6 connection is preferred over IPv4.
+// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3317
+TEST_F(P2PTransportChannelTest, DISABLED_TestIPv6Connections) {
+ AddAddress(0, kIPv6PublicAddrs[0]);
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kIPv6PublicAddrs[1]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ // Enable IPv6
+ SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_IPV6);
+ SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_IPV6);
+
+ CreateChannels(1);
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kIPv6PublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kIPv6PublicAddrs[1]));
+
+ TestSendRecv(1);
+ DestroyChannels();
+}
+
+// Testing forceful TURN connections.
+TEST_F(P2PTransportChannelTest, TestForceTurn) {
+ ConfigureEndpoints(NAT_PORT_RESTRICTED, NAT_SYMMETRIC,
+ kDefaultPortAllocatorFlags |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kDefaultPortAllocatorFlags |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+ kDefaultStepDelay, kDefaultStepDelay,
+ cricket::ICEPROTO_RFC5245);
+ set_force_relay(true);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ CreateChannels(1);
+
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+ ep1_ch1()->writable() &&
+ ep2_ch1()->readable() &&
+ ep2_ch1()->writable(),
+ 1000);
+
+ EXPECT_TRUE(ep1_ch1()->best_connection() &&
+ ep2_ch1()->best_connection());
+
+ EXPECT_EQ("relay", RemoteCandidate(ep1_ch1())->type());
+ EXPECT_EQ("relay", LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ("relay", RemoteCandidate(ep2_ch1())->type());
+ EXPECT_EQ("relay", LocalCandidate(ep2_ch1())->type());
+
+ TestSendRecv(1);
+ DestroyChannels();
+}
+
+// Test what happens when we have 2 users behind the same NAT. This can lead
+// to interesting behavior because the STUN server will only give out the
+// address of the outermost NAT.
+class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase {
+ protected:
+ void ConfigureEndpoints(Config nat_type, Config config1, Config config2) {
+ ASSERT(nat_type >= NAT_FULL_CONE && nat_type <= NAT_SYMMETRIC);
+ rtc::NATSocketServer::Translator* outer_nat =
+ nat()->AddTranslator(kPublicAddrs[0], kNatAddrs[0],
+ static_cast<rtc::NATType>(nat_type - NAT_FULL_CONE));
+ ConfigureEndpoint(outer_nat, 0, config1);
+ ConfigureEndpoint(outer_nat, 1, config2);
+ }
+ void ConfigureEndpoint(rtc::NATSocketServer::Translator* nat,
+ int endpoint, Config config) {
+ ASSERT(config <= NAT_SYMMETRIC);
+ if (config == OPEN) {
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ nat->AddClient(kPrivateAddrs[endpoint]);
+ } else {
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+ static_cast<rtc::NATType>(config - NAT_FULL_CONE))->AddClient(
+ kCascadedPrivateAddrs[endpoint]);
+ }
+ }
+};
+
+TEST_F(P2PTransportChannelSameNatTest, TestConesBehindSameCone) {
+ ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE);
+ Test(kLocalUdpToStunUdp);
+}
+
+// Test what happens when we have multiple available pathways.
+// In the future we will try different RTTs and configs for the different
+// interfaces, so that we can simulate a user with Ethernet and VPN networks.
+class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase {
+};
+
+// Test that we can establish connectivity when both peers are multihomed.
+TEST_F(P2PTransportChannelMultihomedTest, DISABLED_TestBasic) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(0, kAlternateAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ AddAddress(1, kAlternateAddrs[1]);
+ Test(kLocalUdpToLocalUdp);
+}
+
+// Test that we can quickly switch links if an interface goes down.
+TEST_F(P2PTransportChannelMultihomedTest, TestFailover) {
+ AddAddress(0, kPublicAddrs[0]);
+ // Adding alternate address will make sure |kPublicAddrs| has the higher
+ // priority than others. This is due to FakeNetwork::AddInterface method.
+ AddAddress(1, kAlternateAddrs[1]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels(1);
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ // Blackhole any traffic to or from the public addrs.
+ LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY,
+ kPublicAddrs[1]);
+
+ // We should detect loss of connectivity within 5 seconds or so.
+ EXPECT_TRUE_WAIT(!ep1_ch1()->writable(), 7000);
+
+ // We should switch over to use the alternate addr immediately
+ // when we lose writability.
+ EXPECT_TRUE_WAIT(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]),
+ 3000);
+
+ DestroyChannels();
+}
+
+// Test that we can switch links in a coordinated fashion.
+TEST_F(P2PTransportChannelMultihomedTest, TestDrain) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels(1);
+ EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+ ep2_ch1()->readable() && ep2_ch1()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ // Remove the public interface, add the alternate interface, and allocate
+ // a new generation of candidates for the new interface (via Connect()).
+ LOG(LS_INFO) << "Draining...";
+ AddAddress(1, kAlternateAddrs[1]);
+ RemoveAddress(1, kPublicAddrs[1]);
+ ep2_ch1()->Connect();
+
+ // We should switch over to use the alternate address after
+ // an exchange of pings.
+ EXPECT_TRUE_WAIT(
+ ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+ LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]),
+ 3000);
+
+ DestroyChannels();
+}
diff --git a/p2p/base/packetsocketfactory.h b/p2p/base/packetsocketfactory.h
new file mode 100644
index 00000000..1f45feca
--- /dev/null
+++ b/p2p/base/packetsocketfactory.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_
+#define WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_
+
+#include "webrtc/base/proxyinfo.h"
+
+namespace rtc {
+
+class AsyncPacketSocket;
+class AsyncResolverInterface;
+
+class PacketSocketFactory {
+ public:
+ enum Options {
+ OPT_SSLTCP = 0x01, // Pseudo-TLS.
+ OPT_TLS = 0x02,
+ OPT_STUN = 0x04,
+ };
+
+ PacketSocketFactory() { }
+ virtual ~PacketSocketFactory() { }
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) = 0;
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ int opts) = 0;
+
+ // TODO: |proxy_info| and |user_agent| should be set
+ // per-factory and not when socket is created.
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, int opts) = 0;
+
+ virtual AsyncResolverInterface* CreateAsyncResolver() = 0;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(PacketSocketFactory);
+};
+
+} // namespace rtc
+
+#endif // WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_
diff --git a/p2p/base/parsing.cc b/p2p/base/parsing.cc
new file mode 100644
index 00000000..04d7e79e
--- /dev/null
+++ b/p2p/base/parsing.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/parsing.h"
+
+#include <stdlib.h>
+#include <algorithm>
+#include "webrtc/base/stringutils.h"
+
+namespace {
+static const char kTrue[] = "true";
+static const char kOne[] = "1";
+}
+
+namespace cricket {
+
+bool BadParse(const std::string& text, ParseError* err) {
+ if (err != NULL) {
+ err->text = text;
+ }
+ return false;
+}
+
+bool BadWrite(const std::string& text, WriteError* err) {
+ if (err != NULL) {
+ err->text = text;
+ }
+ return false;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const std::string& def) {
+ std::string val = elem->Attr(name);
+ return val.empty() ? def : val;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const char* def) {
+ return GetXmlAttr(elem, name, std::string(def));
+}
+
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, bool def) {
+ std::string val = elem->Attr(name);
+ std::transform(val.begin(), val.end(), val.begin(), tolower);
+
+ return val.empty() ? def : (val == kTrue || val == kOne);
+}
+
+int GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, int def) {
+ std::string val = elem->Attr(name);
+ return val.empty() ? def : atoi(val.c_str());
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+ const std::string& name) {
+ for (const buzz::XmlElement* child = parent->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ if (child->Name().LocalPart() == name) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+ const std::string& name,
+ const buzz::XmlElement** child,
+ ParseError* error) {
+ *child = GetXmlChild(parent, name);
+ if (*child == NULL) {
+ return BadParse("element '" + parent->Name().Merged() +
+ "' missing required child '" + name,
+ error);
+ } else {
+ return true;
+ }
+}
+
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ std::string* value,
+ ParseError* error) {
+ if (!elem->HasAttr(name)) {
+ return BadParse("element '" + elem->Name().Merged() +
+ "' missing required attribute '"
+ + name.Merged() + "'",
+ error);
+ } else {
+ *value = elem->Attr(name);
+ return true;
+ }
+}
+
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+ const buzz::QName name,
+ const std::string& value) {
+ if (!value.empty()) {
+ elem->AddAttr(name, value);
+ }
+}
+
+void AddXmlChildren(buzz::XmlElement* parent,
+ const std::vector<buzz::XmlElement*>& children) {
+ for (std::vector<buzz::XmlElement*>::const_iterator iter = children.begin();
+ iter != children.end();
+ iter++) {
+ parent->AddElement(*iter);
+ }
+}
+
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest) {
+ for (const buzz::XmlElement* child = source->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ dest->AddElement(new buzz::XmlElement(*child));
+ }
+}
+
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem) {
+ std::vector<buzz::XmlElement*> children;
+ for (const buzz::XmlElement* child = elem->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ children.push_back(new buzz::XmlElement(*child));
+ }
+ return children;
+}
+
+} // namespace cricket
diff --git a/p2p/base/parsing.h b/p2p/base/parsing.h
new file mode 100644
index 00000000..6b32b5db
--- /dev/null
+++ b/p2p/base/parsing.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PARSING_H_
+#define WEBRTC_P2P_BASE_PARSING_H_
+
+#include <string>
+#include <vector>
+#include "webrtc/libjingle/xmllite/xmlelement.h" // Needed to delete ParseError.extra.
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/stringencode.h"
+
+namespace cricket {
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+
+// We decided "bool Parse(in, out*, error*)" is generally the best
+// parse signature. "out Parse(in)" doesn't allow for errors.
+// "error* Parse(in, out*)" doesn't allow flexible memory management.
+
+// The error type for parsing.
+struct ParseError {
+ public:
+ // explains the error
+ std::string text;
+ // provide details about what wasn't parsable
+ const buzz::XmlElement* extra;
+
+ ParseError() : extra(NULL) {}
+
+ ~ParseError() {
+ delete extra;
+ }
+
+ void SetText(const std::string& text) {
+ this->text = text;
+ }
+};
+
+// The error type for writing.
+struct WriteError {
+ std::string text;
+
+ void SetText(const std::string& text) {
+ this->text = text;
+ }
+};
+
+// Convenience method for returning a message when parsing fails.
+bool BadParse(const std::string& text, ParseError* err);
+
+// Convenience method for returning a message when writing fails.
+bool BadWrite(const std::string& text, WriteError* error);
+
+// helper XML functions
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const std::string& def);
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const char* def);
+// Return true if the value is "true" or "1".
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, bool def);
+int GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, int def);
+
+template <class T>
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ T* val_out) {
+ if (!elem->HasAttr(name)) {
+ return false;
+ }
+ std::string unparsed = elem->Attr(name);
+ return rtc::FromString(unparsed, val_out);
+}
+
+template <class T>
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const T& def,
+ T* val_out) {
+ if (!elem->HasAttr(name)) {
+ *val_out = def;
+ return true;
+ }
+ return GetXmlAttr(elem, name, val_out);
+}
+
+template <class T>
+bool AddXmlAttr(buzz::XmlElement* elem,
+ const buzz::QName& name, const T& val) {
+ std::string buf;
+ if (!rtc::ToString(val, &buf)) {
+ return false;
+ }
+ elem->AddAttr(name, buf);
+ return true;
+}
+
+template <class T>
+bool SetXmlBody(buzz::XmlElement* elem, const T& val) {
+ std::string buf;
+ if (!rtc::ToString(val, &buf)) {
+ return false;
+ }
+ elem->SetBodyText(buf);
+ return true;
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+ const std::string& name);
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+ const std::string& name,
+ const buzz::XmlElement** child,
+ ParseError* error);
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ std::string* value,
+ ParseError* error);
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+ const buzz::QName name,
+ const std::string& value);
+void AddXmlChildren(buzz::XmlElement* parent,
+ const std::vector<buzz::XmlElement*>& children);
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest);
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem);
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PARSING_H_
diff --git a/p2p/base/port.cc b/p2p/base/port.cc
new file mode 100644
index 00000000..f569d9f5
--- /dev/null
+++ b/p2p/base/port.cc
@@ -0,0 +1,1430 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/port.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/base/base64.h"
+#include "webrtc/base/crc32.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagedigest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stringencode.h"
+#include "webrtc/base/stringutils.h"
+
+namespace {
+
+// Determines whether we have seen at least the given maximum number of
+// pings fail to have a response.
+inline bool TooManyFailures(
+ const std::vector<uint32>& pings_since_last_response,
+ uint32 maximum_failures,
+ uint32 rtt_estimate,
+ uint32 now) {
+
+ // If we haven't sent that many pings, then we can't have failed that many.
+ if (pings_since_last_response.size() < maximum_failures)
+ return false;
+
+ // Check if the window in which we would expect a response to the ping has
+ // already elapsed.
+ return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now;
+}
+
+// Determines whether we have gone too long without seeing any response.
+inline bool TooLongWithoutResponse(
+ const std::vector<uint32>& pings_since_last_response,
+ uint32 maximum_time,
+ uint32 now) {
+
+ if (pings_since_last_response.size() == 0)
+ return false;
+
+ return pings_since_last_response[0] + maximum_time < now;
+}
+
+// GICE(ICEPROTO_GOOGLE) requires different username for RTP and RTCP.
+// This function generates a different username by +1 on the last character of
+// the given username (|rtp_ufrag|).
+std::string GetRtcpUfragFromRtpUfrag(const std::string& rtp_ufrag) {
+ ASSERT(!rtp_ufrag.empty());
+ if (rtp_ufrag.empty()) {
+ return rtp_ufrag;
+ }
+ // Change the last character to the one next to it in the base64 table.
+ char new_last_char;
+ if (!rtc::Base64::GetNextBase64Char(rtp_ufrag[rtp_ufrag.size() - 1],
+ &new_last_char)) {
+ // Should not be here.
+ ASSERT(false);
+ }
+ std::string rtcp_ufrag = rtp_ufrag;
+ rtcp_ufrag[rtcp_ufrag.size() - 1] = new_last_char;
+ ASSERT(rtcp_ufrag != rtp_ufrag);
+ return rtcp_ufrag;
+}
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const uint32 MINIMUM_RTT = 100; // 0.1 seconds
+const uint32 MAXIMUM_RTT = 3000; // 3 seconds
+
+// When we don't have any RTT data, we have to pick something reasonable. We
+// use a large value just in case the connection is really slow.
+const uint32 DEFAULT_RTT = MAXIMUM_RTT;
+
+// Computes our estimate of the RTT given the current estimate.
+inline uint32 ConservativeRTTEstimate(uint32 rtt) {
+ return rtc::_max(MINIMUM_RTT, rtc::_min(MAXIMUM_RTT, 2 * rtt));
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3; // 3 : 1
+
+// The delay before we begin checking if this port is useless.
+const int kPortTimeoutDelay = 30 * 1000; // 30 seconds
+
+// Used by the Connection.
+const uint32 MSG_DELETE = 1;
+}
+
+namespace cricket {
+
+// TODO(ronghuawu): Use "host", "srflx", "prflx" and "relay". But this requires
+// the signaling part be updated correspondingly as well.
+const char LOCAL_PORT_TYPE[] = "local";
+const char STUN_PORT_TYPE[] = "stun";
+const char PRFLX_PORT_TYPE[] = "prflx";
+const char RELAY_PORT_TYPE[] = "relay";
+
+const char UDP_PROTOCOL_NAME[] = "udp";
+const char TCP_PROTOCOL_NAME[] = "tcp";
+const char SSLTCP_PROTOCOL_NAME[] = "ssltcp";
+
+static const char* const PROTO_NAMES[] = { UDP_PROTOCOL_NAME,
+ TCP_PROTOCOL_NAME,
+ SSLTCP_PROTOCOL_NAME };
+
+const char* ProtoToString(ProtocolType proto) {
+ return PROTO_NAMES[proto];
+}
+
+bool StringToProto(const char* value, ProtocolType* proto) {
+ for (size_t i = 0; i <= PROTO_LAST; ++i) {
+ if (_stricmp(PROTO_NAMES[i], value) == 0) {
+ *proto = static_cast<ProtocolType>(i);
+ return true;
+ }
+ }
+ 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,
+// then the foundation will be different. Two candidate pairs with
+// the same foundation pairs are likely to have similar network
+// characteristics. Foundations are used in the frozen algorithm.
+static std::string ComputeFoundation(
+ const std::string& type,
+ const std::string& protocol,
+ const rtc::SocketAddress& base_address) {
+ std::ostringstream ost;
+ ost << type << base_address.ipaddr().ToString() << protocol;
+ return rtc::ToString<uint32>(rtc::ComputeCrc32(ost.str()));
+}
+
+Port::Port(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ const std::string& username_fragment, const std::string& password)
+ : thread_(thread),
+ factory_(factory),
+ send_retransmit_count_attribute_(false),
+ network_(network),
+ ip_(ip),
+ min_port_(0),
+ max_port_(0),
+ component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
+ generation_(0),
+ ice_username_fragment_(username_fragment),
+ password_(password),
+ timeout_delay_(kPortTimeoutDelay),
+ enable_port_packets_(false),
+ ice_protocol_(ICEPROTO_HYBRID),
+ ice_role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ shared_socket_(true),
+ candidate_filter_(CF_ALL) {
+ Construct();
+}
+
+Port::Port(rtc::Thread* thread, const std::string& type,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username_fragment,
+ const std::string& password)
+ : thread_(thread),
+ factory_(factory),
+ type_(type),
+ send_retransmit_count_attribute_(false),
+ network_(network),
+ ip_(ip),
+ min_port_(min_port),
+ max_port_(max_port),
+ component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
+ generation_(0),
+ ice_username_fragment_(username_fragment),
+ password_(password),
+ timeout_delay_(kPortTimeoutDelay),
+ enable_port_packets_(false),
+ ice_protocol_(ICEPROTO_HYBRID),
+ ice_role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ shared_socket_(false),
+ candidate_filter_(CF_ALL) {
+ ASSERT(factory_ != NULL);
+ Construct();
+}
+
+void Port::Construct() {
+ // If the username_fragment and password are empty, we should just create one.
+ if (ice_username_fragment_.empty()) {
+ ASSERT(password_.empty());
+ ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
+ password_ = rtc::CreateRandomString(ICE_PWD_LENGTH);
+ }
+ LOG_J(LS_INFO, this) << "Port created";
+}
+
+Port::~Port() {
+ // Delete all of the remaining connections. We copy the list up front
+ // because each deletion will cause it to be modified.
+
+ std::vector<Connection*> list;
+
+ AddressMap::iterator iter = connections_.begin();
+ while (iter != connections_.end()) {
+ list.push_back(iter->second);
+ ++iter;
+ }
+
+ for (uint32 i = 0; i < list.size(); i++)
+ delete list[i];
+}
+
+Connection* Port::GetConnection(const rtc::SocketAddress& remote_addr) {
+ AddressMap::const_iterator iter = connections_.find(remote_addr);
+ if (iter != connections_.end())
+ return iter->second;
+ else
+ return NULL;
+}
+
+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));
+ c.set_username(username_fragment());
+ c.set_password(password_);
+ c.set_network_name(network_->name());
+ c.set_generation(generation_);
+ c.set_related_address(related_address);
+ c.set_foundation(ComputeFoundation(type, protocol, base_address));
+ candidates_.push_back(c);
+ SignalCandidateReady(this, c);
+
+ if (final) {
+ SignalPortComplete(this);
+ }
+}
+
+void Port::AddConnection(Connection* conn) {
+ connections_[conn->remote_candidate().address()] = conn;
+ conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
+ SignalConnectionCreated(this, conn);
+}
+
+void Port::OnReadPacket(
+ const char* data, size_t size, const rtc::SocketAddress& addr,
+ ProtocolType proto) {
+ // If the user has enabled port packets, just hand this over.
+ if (enable_port_packets_) {
+ SignalReadPacket(this, data, size, addr);
+ return;
+ }
+
+ // If this is an authenticated STUN request, then signal unknown address and
+ // send back a proper binding response.
+ rtc::scoped_ptr<IceMessage> msg;
+ std::string remote_username;
+ if (!GetStunMessage(data, size, addr, msg.accept(), &remote_username)) {
+ LOG_J(LS_ERROR, this) << "Received non-STUN packet from unknown address ("
+ << addr.ToSensitiveString() << ")";
+ } else if (!msg) {
+ // STUN message handled already
+ } else if (msg->type() == STUN_BINDING_REQUEST) {
+ // Check for role conflicts.
+ if (IsStandardIce() &&
+ !MaybeIceRoleConflict(addr, msg.get(), remote_username)) {
+ LOG(LS_INFO) << "Received conflicting role from the peer.";
+ return;
+ }
+
+ SignalUnknownAddress(this, addr, proto, msg.get(), remote_username, false);
+ } else {
+ // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
+ // pruned a connection for this port while it had STUN requests in flight,
+ // because we then get back responses for them, which this code correctly
+ // does not handle.
+ if (msg->type() != STUN_BINDING_RESPONSE) {
+ LOG_J(LS_ERROR, this) << "Received unexpected STUN message type ("
+ << msg->type() << ") from unknown address ("
+ << addr.ToSensitiveString() << ")";
+ }
+ }
+}
+
+void Port::OnReadyToSend() {
+ AddressMap::iterator iter = connections_.begin();
+ for (; iter != connections_.end(); ++iter) {
+ iter->second->OnReadyToSend();
+ }
+}
+
+size_t Port::AddPrflxCandidate(const Candidate& local) {
+ candidates_.push_back(local);
+ return (candidates_.size() - 1);
+}
+
+bool Port::IsStandardIce() const {
+ return (ice_protocol_ == ICEPROTO_RFC5245);
+}
+
+bool Port::IsGoogleIce() const {
+ return (ice_protocol_ == ICEPROTO_GOOGLE);
+}
+
+bool Port::IsHybridIce() const {
+ return (ice_protocol_ == ICEPROTO_HYBRID);
+}
+
+bool Port::GetStunMessage(const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ IceMessage** out_msg, std::string* out_username) {
+ // NOTE: This could clearly be optimized to avoid allocating any memory.
+ // However, at the data rates we'll be looking at on the client side,
+ // this probably isn't worth worrying about.
+ ASSERT(out_msg != NULL);
+ ASSERT(out_username != NULL);
+ *out_msg = NULL;
+ out_username->clear();
+
+ // Don't bother parsing the packet if we can tell it's not STUN.
+ // In ICE mode, all STUN packets will have a valid fingerprint.
+ if (IsStandardIce() && !StunMessage::ValidateFingerprint(data, size)) {
+ return false;
+ }
+
+ // Parse the request message. If the packet is not a complete and correct
+ // STUN message, then ignore it.
+ rtc::scoped_ptr<IceMessage> stun_msg(new IceMessage());
+ rtc::ByteBuffer buf(data, size);
+ if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
+ return false;
+ }
+
+ if (stun_msg->type() == STUN_BINDING_REQUEST) {
+ // Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first.
+ // If not present, fail with a 400 Bad Request.
+ if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
+ (IsStandardIce() &&
+ !stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY))) {
+ LOG_J(LS_ERROR, this) << "Received STUN request without username/M-I "
+ << "from " << addr.ToSensitiveString();
+ SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return true;
+ }
+
+ // If the username is bad or unknown, fail with a 401 Unauthorized.
+ std::string local_ufrag;
+ std::string remote_ufrag;
+ IceProtocolType remote_protocol_type;
+ if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag,
+ &remote_protocol_type) ||
+ local_ufrag != username_fragment()) {
+ LOG_J(LS_ERROR, this) << "Received STUN request with bad local username "
+ << local_ufrag << " from "
+ << addr.ToSensitiveString();
+ SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ return true;
+ }
+
+ // Port is initialized to GOOGLE-ICE protocol type. If pings from remote
+ // are received before the signal message, protocol type may be different.
+ // Based on the STUN username, we can determine what's the remote protocol.
+ // This also enables us to send the response back using the same protocol
+ // as the request.
+ if (IsHybridIce()) {
+ SetIceProtocolType(remote_protocol_type);
+ }
+
+ // If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
+ if (IsStandardIce() &&
+ !stun_msg->ValidateMessageIntegrity(data, size, password_)) {
+ LOG_J(LS_ERROR, this) << "Received STUN request with bad M-I "
+ << "from " << addr.ToSensitiveString();
+ SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ return true;
+ }
+ out_username->assign(remote_ufrag);
+ } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) ||
+ (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
+ if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+ if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
+ LOG_J(LS_ERROR, this) << "Received STUN binding error:"
+ << " class=" << error_code->eclass()
+ << " number=" << error_code->number()
+ << " reason='" << error_code->reason() << "'"
+ << " from " << addr.ToSensitiveString();
+ // Return message to allow error-specific processing
+ } else {
+ LOG_J(LS_ERROR, this) << "Received STUN binding error without a error "
+ << "code from " << addr.ToSensitiveString();
+ return true;
+ }
+ }
+ // NOTE: Username should not be used in verifying response messages.
+ out_username->clear();
+ } else if (stun_msg->type() == STUN_BINDING_INDICATION) {
+ LOG_J(LS_VERBOSE, this) << "Received STUN binding indication:"
+ << " from " << addr.ToSensitiveString();
+ out_username->clear();
+ // No stun attributes will be verified, if it's stun indication message.
+ // Returning from end of the this method.
+ } else {
+ LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type ("
+ << stun_msg->type() << ") from "
+ << addr.ToSensitiveString();
+ return true;
+ }
+
+ // Return the STUN message found.
+ *out_msg = stun_msg.release();
+ return true;
+}
+
+bool Port::IsCompatibleAddress(const rtc::SocketAddress& addr) {
+ int family = ip().family();
+ // We use single-stack sockets, so families must match.
+ if (addr.family() != family) {
+ return false;
+ }
+ // Link-local IPv6 ports can only connect to other link-local IPv6 ports.
+ if (family == AF_INET6 && (IPIsPrivate(ip()) != IPIsPrivate(addr.ipaddr()))) {
+ return false;
+ }
+ return true;
+}
+
+bool Port::ParseStunUsername(const StunMessage* stun_msg,
+ std::string* local_ufrag,
+ std::string* remote_ufrag,
+ IceProtocolType* remote_protocol_type) const {
+ // The packet must include a username that either begins or ends with our
+ // fragment. It should begin with our fragment if it is a request and it
+ // should end with our fragment if it is a response.
+ local_ufrag->clear();
+ remote_ufrag->clear();
+ const StunByteStringAttribute* username_attr =
+ stun_msg->GetByteString(STUN_ATTR_USERNAME);
+ if (username_attr == NULL)
+ return false;
+
+ const std::string username_attr_str = username_attr->GetString();
+ size_t colon_pos = username_attr_str.find(":");
+ // If we are in hybrid mode set the appropriate ice protocol type based on
+ // the username argument style.
+ if (IsHybridIce()) {
+ *remote_protocol_type = (colon_pos != std::string::npos) ?
+ ICEPROTO_RFC5245 : ICEPROTO_GOOGLE;
+ } else {
+ *remote_protocol_type = ice_protocol_;
+ }
+ if (*remote_protocol_type == ICEPROTO_RFC5245) {
+ if (colon_pos != std::string::npos) { // RFRAG:LFRAG
+ *local_ufrag = username_attr_str.substr(0, colon_pos);
+ *remote_ufrag = username_attr_str.substr(
+ colon_pos + 1, username_attr_str.size());
+ } else {
+ return false;
+ }
+ } else if (*remote_protocol_type == ICEPROTO_GOOGLE) {
+ int remote_frag_len = static_cast<int>(username_attr_str.size());
+ remote_frag_len -= static_cast<int>(username_fragment().size());
+ if (remote_frag_len < 0)
+ return false;
+
+ *local_ufrag = username_attr_str.substr(0, username_fragment().size());
+ *remote_ufrag = username_attr_str.substr(
+ username_fragment().size(), username_attr_str.size());
+ }
+ return true;
+}
+
+bool Port::MaybeIceRoleConflict(
+ const rtc::SocketAddress& addr, IceMessage* stun_msg,
+ const std::string& remote_ufrag) {
+ // Validate ICE_CONTROLLING or ICE_CONTROLLED attributes.
+ bool ret = true;
+ IceRole remote_ice_role = ICEROLE_UNKNOWN;
+ uint64 remote_tiebreaker = 0;
+ const StunUInt64Attribute* stun_attr =
+ stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+ if (stun_attr) {
+ remote_ice_role = ICEROLE_CONTROLLING;
+ remote_tiebreaker = stun_attr->value();
+ }
+
+ // If |remote_ufrag| is same as port local username fragment and
+ // tie breaker value received in the ping message matches port
+ // tiebreaker value this must be a loopback call.
+ // We will treat this as valid scenario.
+ if (remote_ice_role == ICEROLE_CONTROLLING &&
+ username_fragment() == remote_ufrag &&
+ remote_tiebreaker == IceTiebreaker()) {
+ return true;
+ }
+
+ stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
+ if (stun_attr) {
+ remote_ice_role = ICEROLE_CONTROLLED;
+ remote_tiebreaker = stun_attr->value();
+ }
+
+ switch (ice_role_) {
+ case ICEROLE_CONTROLLING:
+ if (ICEROLE_CONTROLLING == remote_ice_role) {
+ if (remote_tiebreaker >= tiebreaker_) {
+ SignalRoleConflict(this);
+ } else {
+ // Send Role Conflict (487) error response.
+ SendBindingErrorResponse(stun_msg, addr,
+ STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
+ ret = false;
+ }
+ }
+ break;
+ case ICEROLE_CONTROLLED:
+ if (ICEROLE_CONTROLLED == remote_ice_role) {
+ if (remote_tiebreaker < tiebreaker_) {
+ SignalRoleConflict(this);
+ } else {
+ // Send Role Conflict (487) error response.
+ SendBindingErrorResponse(stun_msg, addr,
+ STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
+ ret = false;
+ }
+ }
+ break;
+ default:
+ ASSERT(false);
+ }
+ return ret;
+}
+
+void Port::CreateStunUsername(const std::string& remote_username,
+ std::string* stun_username_attr_str) const {
+ stun_username_attr_str->clear();
+ *stun_username_attr_str = remote_username;
+ if (IsStandardIce()) {
+ // Connectivity checks from L->R will have username RFRAG:LFRAG.
+ stun_username_attr_str->append(":");
+ }
+ stun_username_attr_str->append(username_fragment());
+}
+
+void Port::SendBindingResponse(StunMessage* request,
+ const rtc::SocketAddress& addr) {
+ ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+ // Retrieve the username from the request.
+ const StunByteStringAttribute* username_attr =
+ request->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT(username_attr != NULL);
+ if (username_attr == NULL) {
+ // No valid username, skip the response.
+ return;
+ }
+
+ // Fill in the response message.
+ StunMessage response;
+ response.SetType(STUN_BINDING_RESPONSE);
+ response.SetTransactionID(request->transaction_id());
+ const StunUInt32Attribute* retransmit_attr =
+ request->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ if (retransmit_attr) {
+ // Inherit the incoming retransmit value in the response so the other side
+ // can see our view of lost pings.
+ response.AddAttribute(new StunUInt32Attribute(
+ STUN_ATTR_RETRANSMIT_COUNT, retransmit_attr->value()));
+
+ if (retransmit_attr->value() > CONNECTION_WRITE_CONNECT_FAILURES) {
+ LOG_J(LS_INFO, this)
+ << "Received a remote ping with high retransmit count: "
+ << retransmit_attr->value();
+ }
+ }
+
+ // Only GICE messages have USERNAME and MAPPED-ADDRESS in the response.
+ // ICE messages use XOR-MAPPED-ADDRESS, and add MESSAGE-INTEGRITY.
+ if (IsStandardIce()) {
+ response.AddAttribute(
+ new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, addr));
+ response.AddMessageIntegrity(password_);
+ response.AddFingerprint();
+ } else if (IsGoogleIce()) {
+ response.AddAttribute(
+ new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, addr));
+ response.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_USERNAME, username_attr->GetString()));
+ }
+
+ // Send the response message.
+ rtc::ByteBuffer buf;
+ response.Write(&buf);
+ rtc::PacketOptions options(DefaultDscpValue());
+ if (SendTo(buf.Data(), buf.Length(), addr, options, false) < 0) {
+ LOG_J(LS_ERROR, this) << "Failed to send STUN ping response to "
+ << addr.ToSensitiveString();
+ }
+
+ // The fact that we received a successful request means that this connection
+ // (if one exists) should now be readable.
+ Connection* conn = GetConnection(addr);
+ ASSERT(conn != NULL);
+ if (conn)
+ conn->ReceivedPing();
+}
+
+void Port::SendBindingErrorResponse(StunMessage* request,
+ const rtc::SocketAddress& addr,
+ int error_code, const std::string& reason) {
+ ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+ // Fill in the response message.
+ StunMessage response;
+ response.SetType(STUN_BINDING_ERROR_RESPONSE);
+ response.SetTransactionID(request->transaction_id());
+
+ // When doing GICE, we need to write out the error code incorrectly to
+ // maintain backwards compatiblility.
+ StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode();
+ if (IsStandardIce()) {
+ error_attr->SetCode(error_code);
+ } else if (IsGoogleIce()) {
+ error_attr->SetClass(error_code / 256);
+ error_attr->SetNumber(error_code % 256);
+ }
+ error_attr->SetReason(reason);
+ response.AddAttribute(error_attr);
+
+ if (IsStandardIce()) {
+ // Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
+ // because we don't have enough information to determine the shared secret.
+ if (error_code != STUN_ERROR_BAD_REQUEST &&
+ error_code != STUN_ERROR_UNAUTHORIZED)
+ response.AddMessageIntegrity(password_);
+ response.AddFingerprint();
+ } else if (IsGoogleIce()) {
+ // GICE responses include a username, if one exists.
+ const StunByteStringAttribute* username_attr =
+ request->GetByteString(STUN_ATTR_USERNAME);
+ if (username_attr)
+ response.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_USERNAME, username_attr->GetString()));
+ }
+
+ // Send the response message.
+ rtc::ByteBuffer buf;
+ response.Write(&buf);
+ rtc::PacketOptions options(DefaultDscpValue());
+ SendTo(buf.Data(), buf.Length(), addr, options, false);
+ LOG_J(LS_INFO, this) << "Sending STUN binding error: reason=" << reason
+ << " to " << addr.ToSensitiveString();
+}
+
+void Port::OnMessage(rtc::Message *pmsg) {
+ ASSERT(pmsg->message_id == MSG_CHECKTIMEOUT);
+ CheckTimeout();
+}
+
+std::string Port::ToString() const {
+ std::stringstream ss;
+ ss << "Port[" << content_name_ << ":" << component_
+ << ":" << generation_ << ":" << type_
+ << ":" << network_->ToString() << "]";
+ return ss.str();
+}
+
+void Port::EnablePortPackets() {
+ enable_port_packets_ = true;
+}
+
+void Port::OnConnectionDestroyed(Connection* conn) {
+ AddressMap::iterator iter =
+ connections_.find(conn->remote_candidate().address());
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+
+ // On the controlled side, ports time out, but only after all connections
+ // fail. Note: If a new connection is added after this message is posted,
+ // but it fails and is removed before kPortTimeoutDelay, then this message
+ // will still cause the Port to be destroyed.
+ if (ice_role_ == ICEROLE_CONTROLLED)
+ thread_->PostDelayed(timeout_delay_, this, MSG_CHECKTIMEOUT);
+}
+
+void Port::Destroy() {
+ ASSERT(connections_.empty());
+ LOG_J(LS_INFO, this) << "Port deleted";
+ SignalDestroyed(this);
+ delete this;
+}
+
+void Port::CheckTimeout() {
+ ASSERT(ice_role_ == ICEROLE_CONTROLLED);
+ // If this port has no connections, then there's no reason to keep it around.
+ // When the connections time out (both read and write), they will delete
+ // themselves, so if we have any connections, they are either readable or
+ // writable (or still connecting).
+ if (connections_.empty())
+ Destroy();
+}
+
+const std::string Port::username_fragment() const {
+ if (!IsStandardIce() &&
+ component_ == ICE_CANDIDATE_COMPONENT_RTCP) {
+ // In GICE mode, we should adjust username fragment for rtcp component.
+ return GetRtcpUfragFromRtpUfrag(ice_username_fragment_);
+ } else {
+ return ice_username_fragment_;
+ }
+}
+
+// A ConnectionRequest is a simple STUN ping used to determine writability.
+class ConnectionRequest : public StunRequest {
+ public:
+ explicit ConnectionRequest(Connection* connection)
+ : StunRequest(new IceMessage()),
+ connection_(connection) {
+ }
+
+ virtual ~ConnectionRequest() {
+ }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ std::string username;
+ connection_->port()->CreateStunUsername(
+ connection_->remote_candidate().username(), &username);
+ request->AddAttribute(
+ new StunByteStringAttribute(STUN_ATTR_USERNAME, username));
+
+ // connection_ already holds this ping, so subtract one from count.
+ if (connection_->port()->send_retransmit_count_attribute()) {
+ request->AddAttribute(new StunUInt32Attribute(
+ STUN_ATTR_RETRANSMIT_COUNT,
+ static_cast<uint32>(
+ connection_->pings_since_last_response_.size() - 1)));
+ }
+
+ // Adding ICE-specific attributes to the STUN request message.
+ if (connection_->port()->IsStandardIce()) {
+ // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role.
+ if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLING) {
+ request->AddAttribute(new StunUInt64Attribute(
+ STUN_ATTR_ICE_CONTROLLING, connection_->port()->IceTiebreaker()));
+ // Since we are trying aggressive nomination, sending USE-CANDIDATE
+ // attribute in every ping.
+ // If we are dealing with a ice-lite end point, nomination flag
+ // in Connection will be set to false by default. Once the connection
+ // becomes "best connection", nomination flag will be turned on.
+ if (connection_->use_candidate_attr()) {
+ request->AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_USE_CANDIDATE));
+ }
+ } else if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLED) {
+ request->AddAttribute(new StunUInt64Attribute(
+ STUN_ATTR_ICE_CONTROLLED, connection_->port()->IceTiebreaker()));
+ } else {
+ ASSERT(false);
+ }
+
+ // Adding PRIORITY Attribute.
+ // Changing the type preference to Peer Reflexive and local preference
+ // and component id information is unchanged from the original priority.
+ // priority = (2^24)*(type preference) +
+ // (2^8)*(local preference) +
+ // (2^0)*(256 - component ID)
+ uint32 prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
+ (connection_->local_candidate().priority() & 0x00FFFFFF);
+ request->AddAttribute(
+ new StunUInt32Attribute(STUN_ATTR_PRIORITY, prflx_priority));
+
+ // Adding Message Integrity attribute.
+ request->AddMessageIntegrity(connection_->remote_candidate().password());
+ // Adding Fingerprint.
+ request->AddFingerprint();
+ }
+ }
+
+ virtual void OnResponse(StunMessage* response) {
+ connection_->OnConnectionRequestResponse(this, response);
+ }
+
+ virtual void OnErrorResponse(StunMessage* response) {
+ connection_->OnConnectionRequestErrorResponse(this, response);
+ }
+
+ virtual void OnTimeout() {
+ connection_->OnConnectionRequestTimeout(this);
+ }
+
+ virtual int GetNextDelay() {
+ // Each request is sent only once. After a single delay , the request will
+ // time out.
+ timeout_ = true;
+ return CONNECTION_RESPONSE_TIMEOUT;
+ }
+
+ private:
+ Connection* connection_;
+};
+
+//
+// Connection
+//
+
+Connection::Connection(Port* port, size_t index,
+ const Candidate& remote_candidate)
+ : port_(port), local_candidate_index_(index),
+ remote_candidate_(remote_candidate), read_state_(STATE_READ_INIT),
+ write_state_(STATE_WRITE_INIT), connected_(true), pruned_(false),
+ use_candidate_attr_(false), remote_ice_mode_(ICEMODE_FULL),
+ requests_(port->thread()), rtt_(DEFAULT_RTT), last_ping_sent_(0),
+ last_ping_received_(0), last_data_received_(0),
+ last_ping_response_received_(0), reported_(false), state_(STATE_WAITING) {
+ // All of our connections start in WAITING state.
+ // TODO(mallinath) - Start connections from STATE_FROZEN.
+ // Wire up to send stun packets
+ requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket);
+ LOG_J(LS_INFO, this) << "Connection created";
+}
+
+Connection::~Connection() {
+}
+
+const Candidate& Connection::local_candidate() const {
+ ASSERT(local_candidate_index_ < port_->Candidates().size());
+ return port_->Candidates()[local_candidate_index_];
+}
+
+uint64 Connection::priority() const {
+ uint64 priority = 0;
+ // RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs
+ // Let G be the priority for the candidate provided by the controlling
+ // agent. Let D be the priority for the candidate provided by the
+ // controlled agent.
+ // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+ IceRole role = port_->GetIceRole();
+ if (role != ICEROLE_UNKNOWN) {
+ uint32 g = 0;
+ uint32 d = 0;
+ if (role == ICEROLE_CONTROLLING) {
+ g = local_candidate().priority();
+ d = remote_candidate_.priority();
+ } else {
+ g = remote_candidate_.priority();
+ d = local_candidate().priority();
+ }
+ priority = rtc::_min(g, d);
+ priority = priority << 32;
+ priority += 2 * rtc::_max(g, d) + (g > d ? 1 : 0);
+ }
+ return priority;
+}
+
+void Connection::set_read_state(ReadState value) {
+ ReadState old_value = read_state_;
+ read_state_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_read_state";
+ SignalStateChange(this);
+ CheckTimeout();
+ }
+}
+
+void Connection::set_write_state(WriteState value) {
+ WriteState old_value = write_state_;
+ write_state_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_write_state";
+ SignalStateChange(this);
+ CheckTimeout();
+ }
+}
+
+void Connection::set_state(State state) {
+ State old_state = state_;
+ state_ = state;
+ if (state != old_state) {
+ LOG_J(LS_VERBOSE, this) << "set_state";
+ }
+}
+
+void Connection::set_connected(bool value) {
+ bool old_value = connected_;
+ connected_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_connected";
+ }
+}
+
+void Connection::set_use_candidate_attr(bool enable) {
+ use_candidate_attr_ = enable;
+}
+
+void Connection::OnSendStunPacket(const void* data, size_t size,
+ StunRequest* req) {
+ rtc::PacketOptions options(port_->DefaultDscpValue());
+ if (port_->SendTo(data, size, remote_candidate_.address(),
+ options, false) < 0) {
+ LOG_J(LS_WARNING, this) << "Failed to send STUN ping " << req->id();
+ }
+}
+
+void Connection::OnReadPacket(
+ const char* data, size_t size, const rtc::PacketTime& packet_time) {
+ rtc::scoped_ptr<IceMessage> msg;
+ std::string remote_ufrag;
+ const rtc::SocketAddress& addr(remote_candidate_.address());
+ if (!port_->GetStunMessage(data, size, addr, msg.accept(), &remote_ufrag)) {
+ // The packet did not parse as a valid STUN message
+
+ // If this connection is readable, then pass along the packet.
+ if (read_state_ == STATE_READABLE) {
+ // readable means data from this address is acceptable
+ // Send it on!
+
+ last_data_received_ = rtc::Time();
+ recv_rate_tracker_.Update(size);
+ SignalReadPacket(this, data, size, packet_time);
+
+ // If timed out sending writability checks, start up again
+ if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) {
+ LOG(LS_WARNING) << "Received a data packet on a timed-out Connection. "
+ << "Resetting state to STATE_WRITE_INIT.";
+ set_write_state(STATE_WRITE_INIT);
+ }
+ } else {
+ // Not readable means the remote address hasn't sent a valid
+ // binding request yet.
+
+ LOG_J(LS_WARNING, this)
+ << "Received non-STUN packet from an unreadable connection.";
+ }
+ } else if (!msg) {
+ // The packet was STUN, but failed a check and was handled internally.
+ } else {
+ // The packet is STUN and passed the Port checks.
+ // Perform our own checks to ensure this packet is valid.
+ // If this is a STUN request, then update the readable bit and respond.
+ // If this is a STUN response, then update the writable bit.
+ switch (msg->type()) {
+ case STUN_BINDING_REQUEST:
+ if (remote_ufrag == remote_candidate_.username()) {
+ // Check for role conflicts.
+ if (port_->IsStandardIce() &&
+ !port_->MaybeIceRoleConflict(addr, msg.get(), remote_ufrag)) {
+ // Received conflicting role from the peer.
+ LOG(LS_INFO) << "Received conflicting role from the peer.";
+ return;
+ }
+
+ // Incoming, validated stun request from remote peer.
+ // This call will also set the connection readable.
+ port_->SendBindingResponse(msg.get(), addr);
+
+ // If timed out sending writability checks, start up again
+ if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+ set_write_state(STATE_WRITE_INIT);
+
+ if ((port_->IsStandardIce()) &&
+ (port_->GetIceRole() == ICEROLE_CONTROLLED)) {
+ const StunByteStringAttribute* use_candidate_attr =
+ msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
+ if (use_candidate_attr)
+ SignalUseCandidate(this);
+ }
+ } else {
+ // The packet had the right local username, but the remote username
+ // was not the right one for the remote address.
+ LOG_J(LS_ERROR, this)
+ << "Received STUN request with bad remote username "
+ << remote_ufrag;
+ port_->SendBindingErrorResponse(msg.get(), addr,
+ STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+
+ }
+ break;
+
+ // Response from remote peer. Does it match request sent?
+ // This doesn't just check, it makes callbacks if transaction
+ // id's match.
+ case STUN_BINDING_RESPONSE:
+ case STUN_BINDING_ERROR_RESPONSE:
+ if (port_->IsGoogleIce() ||
+ msg->ValidateMessageIntegrity(
+ data, size, remote_candidate().password())) {
+ requests_.CheckResponse(msg.get());
+ }
+ // Otherwise silently discard the response message.
+ break;
+
+ // Remote end point sent an STUN indication instead of regular
+ // binding request. In this case |last_ping_received_| will be updated.
+ // Otherwise we can mark connection to read timeout. No response will be
+ // sent in this scenario.
+ case STUN_BINDING_INDICATION:
+ if (port_->IsStandardIce() && read_state_ == STATE_READABLE) {
+ ReceivedPing();
+ } else {
+ LOG_J(LS_WARNING, this) << "Received STUN binding indication "
+ << "from an unreadable connection.";
+ }
+ break;
+
+ default:
+ ASSERT(false);
+ break;
+ }
+ }
+}
+
+void Connection::OnReadyToSend() {
+ if (write_state_ == STATE_WRITABLE) {
+ SignalReadyToSend(this);
+ }
+}
+
+void Connection::Prune() {
+ if (!pruned_) {
+ LOG_J(LS_VERBOSE, this) << "Connection pruned";
+ pruned_ = true;
+ requests_.Clear();
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::Destroy() {
+ LOG_J(LS_VERBOSE, this) << "Connection destroyed";
+ set_read_state(STATE_READ_TIMEOUT);
+ set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void Connection::UpdateState(uint32 now) {
+ uint32 rtt = ConservativeRTTEstimate(rtt_);
+
+ std::string pings;
+ for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+ char buf[32];
+ rtc::sprintfn(buf, sizeof(buf), "%u",
+ pings_since_last_response_[i]);
+ pings.append(buf).append(" ");
+ }
+ LOG_J(LS_VERBOSE, this) << "UpdateState(): pings_since_last_response_=" <<
+ pings << ", rtt=" << rtt << ", now=" << now;
+
+ // Check the readable state.
+ //
+ // Since we don't know how many pings the other side has attempted, the best
+ // test we can do is a simple window.
+ // If other side has not sent ping after connection has become readable, use
+ // |last_data_received_| as the indication.
+ // If remote endpoint is doing RFC 5245, it's not required to send ping
+ // after connection is established. If this connection is serving a data
+ // channel, it may not be in a position to send media continuously. Do not
+ // mark connection timeout if it's in RFC5245 mode.
+ // Below check will be performed with end point if it's doing google-ice.
+ if (port_->IsGoogleIce() && (read_state_ == STATE_READABLE) &&
+ (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now) &&
+ (last_data_received_ + CONNECTION_READ_TIMEOUT <= now)) {
+ LOG_J(LS_INFO, this) << "Unreadable after "
+ << now - last_ping_received_
+ << " ms without a ping,"
+ << " ms since last received response="
+ << now - last_ping_response_received_
+ << " ms since last received data="
+ << now - last_data_received_
+ << " rtt=" << rtt;
+ set_read_state(STATE_READ_TIMEOUT);
+ }
+
+ // Check the writable state. (The order of these checks is important.)
+ //
+ // Before becoming unwritable, we allow for a fixed number of pings to fail
+ // (i.e., receive no response). We also have to give the response time to
+ // get back, so we include a conservative estimate of this.
+ //
+ // Before timing out writability, we give a fixed amount of time. This is to
+ // allow for changes in network conditions.
+
+ if ((write_state_ == STATE_WRITABLE) &&
+ TooManyFailures(pings_since_last_response_,
+ CONNECTION_WRITE_CONNECT_FAILURES,
+ rtt,
+ now) &&
+ TooLongWithoutResponse(pings_since_last_response_,
+ CONNECTION_WRITE_CONNECT_TIMEOUT,
+ now)) {
+ uint32 max_pings = CONNECTION_WRITE_CONNECT_FAILURES;
+ LOG_J(LS_INFO, this) << "Unwritable after " << max_pings
+ << " ping failures and "
+ << now - pings_since_last_response_[0]
+ << " ms without a response,"
+ << " ms since last received ping="
+ << now - last_ping_received_
+ << " ms since last received data="
+ << now - last_data_received_
+ << " rtt=" << rtt;
+ set_write_state(STATE_WRITE_UNRELIABLE);
+ }
+
+ if ((write_state_ == STATE_WRITE_UNRELIABLE ||
+ write_state_ == STATE_WRITE_INIT) &&
+ TooLongWithoutResponse(pings_since_last_response_,
+ CONNECTION_WRITE_TIMEOUT,
+ now)) {
+ LOG_J(LS_INFO, this) << "Timed out after "
+ << now - pings_since_last_response_[0]
+ << " ms without a response, rtt=" << rtt;
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::Ping(uint32 now) {
+ ASSERT(connected_);
+ last_ping_sent_ = now;
+ pings_since_last_response_.push_back(now);
+ ConnectionRequest *req = new ConnectionRequest(this);
+ LOG_J(LS_VERBOSE, this) << "Sending STUN ping " << req->id() << " at " << now;
+ requests_.Send(req);
+ state_ = STATE_INPROGRESS;
+}
+
+void Connection::ReceivedPing() {
+ last_ping_received_ = rtc::Time();
+ set_read_state(STATE_READABLE);
+}
+
+std::string Connection::ToString() const {
+ const char CONNECT_STATE_ABBREV[2] = {
+ '-', // not connected (false)
+ 'C', // connected (true)
+ };
+ const char READ_STATE_ABBREV[3] = {
+ '-', // STATE_READ_INIT
+ 'R', // STATE_READABLE
+ 'x', // STATE_READ_TIMEOUT
+ };
+ const char WRITE_STATE_ABBREV[4] = {
+ 'W', // STATE_WRITABLE
+ 'w', // STATE_WRITE_UNRELIABLE
+ '-', // STATE_WRITE_INIT
+ 'x', // STATE_WRITE_TIMEOUT
+ };
+ const std::string ICESTATE[4] = {
+ "W", // STATE_WAITING
+ "I", // STATE_INPROGRESS
+ "S", // STATE_SUCCEEDED
+ "F" // STATE_FAILED
+ };
+ const Candidate& local = local_candidate();
+ const Candidate& remote = remote_candidate();
+ std::stringstream ss;
+ ss << "Conn[" << port_->content_name()
+ << ":" << local.id() << ":" << local.component()
+ << ":" << local.generation()
+ << ":" << local.type() << ":" << local.protocol()
+ << ":" << local.address().ToSensitiveString()
+ << "->" << remote.id() << ":" << remote.component()
+ << ":" << remote.priority()
+ << ":" << remote.type() << ":"
+ << remote.protocol() << ":" << remote.address().ToSensitiveString() << "|"
+ << CONNECT_STATE_ABBREV[connected()]
+ << READ_STATE_ABBREV[read_state()]
+ << WRITE_STATE_ABBREV[write_state()]
+ << ICESTATE[state()] << "|"
+ << priority() << "|";
+ if (rtt_ < DEFAULT_RTT) {
+ ss << rtt_ << "]";
+ } else {
+ ss << "-]";
+ }
+ return ss.str();
+}
+
+std::string Connection::ToSensitiveString() const {
+ return ToString();
+}
+
+void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ // We've already validated that this is a STUN binding response with
+ // the correct local and remote username for this connection.
+ // So if we're not already, become writable. We may be bringing a pruned
+ // connection back to life, but if we don't really want it, we can always
+ // prune it again.
+ uint32 rtt = request->Elapsed();
+ set_write_state(STATE_WRITABLE);
+ set_state(STATE_SUCCEEDED);
+
+ if (remote_ice_mode_ == ICEMODE_LITE) {
+ // A ice-lite end point never initiates ping requests. This will allow
+ // us to move to STATE_READABLE.
+ ReceivedPing();
+ }
+
+ std::string pings;
+ for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+ char buf[32];
+ rtc::sprintfn(buf, sizeof(buf), "%u",
+ pings_since_last_response_[i]);
+ pings.append(buf).append(" ");
+ }
+
+ rtc::LoggingSeverity level =
+ (pings_since_last_response_.size() > CONNECTION_WRITE_CONNECT_FAILURES) ?
+ rtc::LS_INFO : rtc::LS_VERBOSE;
+
+ LOG_JV(level, this) << "Received STUN ping response " << request->id()
+ << ", pings_since_last_response_=" << pings
+ << ", rtt=" << rtt;
+
+ pings_since_last_response_.clear();
+ last_ping_response_received_ = rtc::Time();
+ rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
+
+ // Peer reflexive candidate is only for RFC 5245 ICE.
+ if (port_->IsStandardIce()) {
+ MaybeAddPrflxCandidate(request, response);
+ }
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ const StunErrorCodeAttribute* error_attr = response->GetErrorCode();
+ int error_code = STUN_ERROR_GLOBAL_FAILURE;
+ if (error_attr) {
+ if (port_->IsGoogleIce()) {
+ // When doing GICE, the error code is written out incorrectly, so we need
+ // to unmunge it here.
+ error_code = error_attr->eclass() * 256 + error_attr->number();
+ } else {
+ error_code = error_attr->code();
+ }
+ }
+
+ if (error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE ||
+ error_code == STUN_ERROR_SERVER_ERROR ||
+ error_code == STUN_ERROR_UNAUTHORIZED) {
+ // Recoverable error, retry
+ } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) {
+ // Race failure, retry
+ } else if (error_code == STUN_ERROR_ROLE_CONFLICT) {
+ HandleRoleConflictFromPeer();
+ } else {
+ // This is not a valid connection.
+ LOG_J(LS_ERROR, this) << "Received STUN error response, code="
+ << error_code << "; killing connection";
+ set_state(STATE_FAILED);
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
+ // Log at LS_INFO if we miss a ping on a writable connection.
+ rtc::LoggingSeverity sev = (write_state_ == STATE_WRITABLE) ?
+ rtc::LS_INFO : rtc::LS_VERBOSE;
+ LOG_JV(sev, this) << "Timing-out STUN ping " << request->id()
+ << " after " << request->Elapsed() << " ms";
+}
+
+void Connection::CheckTimeout() {
+ // If both read and write have timed out or read has never initialized, then
+ // this connection can contribute no more to p2p socket unless at some later
+ // date readability were to come back. However, we gave readability a long
+ // time to timeout, so at this point, it seems fair to get rid of this
+ // connection.
+ if ((read_state_ == STATE_READ_TIMEOUT ||
+ read_state_ == STATE_READ_INIT) &&
+ write_state_ == STATE_WRITE_TIMEOUT) {
+ port_->thread()->Post(this, MSG_DELETE);
+ }
+}
+
+void Connection::HandleRoleConflictFromPeer() {
+ port_->SignalRoleConflict(port_);
+}
+
+void Connection::OnMessage(rtc::Message *pmsg) {
+ ASSERT(pmsg->message_id == MSG_DELETE);
+
+ LOG_J(LS_INFO, this) << "Connection deleted";
+ SignalDestroyed(this);
+ delete this;
+}
+
+size_t Connection::recv_bytes_second() {
+ return recv_rate_tracker_.units_second();
+}
+
+size_t Connection::recv_total_bytes() {
+ return recv_rate_tracker_.total_units();
+}
+
+size_t Connection::sent_bytes_second() {
+ return send_rate_tracker_.units_second();
+}
+
+size_t Connection::sent_total_bytes() {
+ return send_rate_tracker_.total_units();
+}
+
+void Connection::MaybeAddPrflxCandidate(ConnectionRequest* request,
+ StunMessage* response) {
+ // RFC 5245
+ // The agent checks the mapped address from the STUN response. If the
+ // transport address does not match any of the local candidates that the
+ // agent knows about, the mapped address represents a new candidate -- a
+ // peer reflexive candidate.
+ const StunAddressAttribute* addr =
+ response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ if (!addr) {
+ LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - "
+ << "No MAPPED-ADDRESS or XOR-MAPPED-ADDRESS found in the "
+ << "stun response message";
+ return;
+ }
+
+ bool known_addr = false;
+ for (size_t i = 0; i < port_->Candidates().size(); ++i) {
+ if (port_->Candidates()[i].address() == addr->GetAddress()) {
+ known_addr = true;
+ break;
+ }
+ }
+ if (known_addr) {
+ return;
+ }
+
+ // RFC 5245
+ // Its priority is set equal to the value of the PRIORITY attribute
+ // in the Binding request.
+ const StunUInt32Attribute* priority_attr =
+ request->msg()->GetUInt32(STUN_ATTR_PRIORITY);
+ if (!priority_attr) {
+ LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - "
+ << "No STUN_ATTR_PRIORITY found in the "
+ << "stun response message";
+ return;
+ }
+ const uint32 priority = priority_attr->value();
+ std::string id = rtc::CreateRandomString(8);
+
+ Candidate new_local_candidate;
+ new_local_candidate.set_id(id);
+ new_local_candidate.set_component(local_candidate().component());
+ new_local_candidate.set_type(PRFLX_PORT_TYPE);
+ new_local_candidate.set_protocol(local_candidate().protocol());
+ new_local_candidate.set_address(addr->GetAddress());
+ new_local_candidate.set_priority(priority);
+ new_local_candidate.set_username(local_candidate().username());
+ new_local_candidate.set_password(local_candidate().password());
+ new_local_candidate.set_network_name(local_candidate().network_name());
+ new_local_candidate.set_related_address(local_candidate().address());
+ new_local_candidate.set_foundation(
+ ComputeFoundation(PRFLX_PORT_TYPE, local_candidate().protocol(),
+ local_candidate().address()));
+
+ // Change the local candidate of this Connection to the new prflx candidate.
+ local_candidate_index_ = port_->AddPrflxCandidate(new_local_candidate);
+
+ // SignalStateChange to force a re-sort in P2PTransportChannel as this
+ // Connection's local candidate has changed.
+ SignalStateChange(this);
+}
+
+ProxyConnection::ProxyConnection(Port* port, size_t index,
+ const Candidate& candidate)
+ : Connection(port, index, candidate), error_(0) {
+}
+
+int ProxyConnection::Send(const void* data, size_t size,
+ const rtc::PacketOptions& options) {
+ if (write_state_ == STATE_WRITE_INIT || write_state_ == STATE_WRITE_TIMEOUT) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ int sent = port_->SendTo(data, size, remote_candidate_.address(),
+ options, true);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = port_->GetError();
+ } else {
+ send_rate_tracker_.Update(sent);
+ }
+ return sent;
+}
+
+} // namespace cricket
diff --git a/p2p/base/port.h b/p2p/base/port.h
new file mode 100644
index 00000000..48b85302
--- /dev/null
+++ b/p2p/base/port.h
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PORT_H_
+#define WEBRTC_P2P_BASE_PORT_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/packetsocketfactory.h"
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/p2p/base/stunrequest.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/network.h"
+#include "webrtc/base/proxyinfo.h"
+#include "webrtc/base/ratetracker.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+class Connection;
+class ConnectionRequest;
+
+extern const char LOCAL_PORT_TYPE[];
+extern const char STUN_PORT_TYPE[];
+extern const char PRFLX_PORT_TYPE[];
+extern const char RELAY_PORT_TYPE[];
+
+extern const char UDP_PROTOCOL_NAME[];
+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
+
+// The length of time we wait before timing out writability on a connection.
+const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000; // 15 seconds
+
+// The length of time we wait before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000; // 5 seconds
+
+// The number of pings that must fail to respond before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5;
+
+// This is the length of time that we wait for a ping response to come back.
+const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000; // 5 seconds
+
+enum RelayType {
+ RELAY_GTURN, // Legacy google relay service.
+ RELAY_TURN // Standard (TURN) relay service.
+};
+
+enum IcePriorityValue {
+ // The reason we are choosing Relay preference 2 is because, we can run
+ // Relay from client to server on UDP/TCP/TLS. To distinguish the transport
+ // protocol, we prefer UDP over TCP over TLS.
+ // For UDP ICE_TYPE_PREFERENCE_RELAY will be 2.
+ // For TCP ICE_TYPE_PREFERENCE_RELAY will be 1.
+ // For TLS ICE_TYPE_PREFERENCE_RELAY will be 0.
+ // Check turnport.cc for setting these values.
+ ICE_TYPE_PREFERENCE_RELAY = 2,
+ ICE_TYPE_PREFERENCE_HOST_TCP = 90,
+ ICE_TYPE_PREFERENCE_SRFLX = 100,
+ ICE_TYPE_PREFERENCE_PRFLX = 110,
+ ICE_TYPE_PREFERENCE_HOST = 126
+};
+
+const char* ProtoToString(ProtocolType proto);
+bool StringToProto(const char* value, ProtocolType* proto);
+
+struct ProtocolAddress {
+ rtc::SocketAddress address;
+ ProtocolType proto;
+ bool secure;
+
+ ProtocolAddress(const rtc::SocketAddress& a, ProtocolType p)
+ : address(a), proto(p), secure(false) { }
+ ProtocolAddress(const rtc::SocketAddress& a, ProtocolType p, bool sec)
+ : address(a), proto(p), secure(sec) { }
+};
+
+typedef std::set<rtc::SocketAddress> ServerAddresses;
+
+// Represents a local communication mechanism that can be used to create
+// connections to similar mechanisms of the other client. Subclasses of this
+// one add support for specific mechanisms like local UDP ports.
+class Port : public PortInterface, public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ Port(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ const std::string& username_fragment, const std::string& password);
+ Port(rtc::Thread* thread, const std::string& type,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username_fragment,
+ const std::string& password);
+ virtual ~Port();
+
+ virtual const std::string& Type() const { return type_; }
+ virtual rtc::Network* Network() const { return network_; }
+
+ // This method will set the flag which enables standard ICE/STUN procedures
+ // in STUN connectivity checks. Currently this method does
+ // 1. Add / Verify MI attribute in STUN binding requests.
+ // 2. Username attribute in STUN binding request will be RFRAF:LFRAG,
+ // as opposed to RFRAGLFRAG.
+ virtual void SetIceProtocolType(IceProtocolType protocol) {
+ ice_protocol_ = protocol;
+ }
+ virtual IceProtocolType IceProtocol() const { return ice_protocol_; }
+
+ // Methods to set/get ICE role and tiebreaker values.
+ IceRole GetIceRole() const { return ice_role_; }
+ void SetIceRole(IceRole role) { ice_role_ = role; }
+
+ void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+ uint64 IceTiebreaker() const { return tiebreaker_; }
+
+ virtual bool SharedSocket() const { return shared_socket_; }
+ void ResetSharedSocket() { shared_socket_ = false; }
+
+ // The thread on which this port performs its I/O.
+ rtc::Thread* thread() { return thread_; }
+
+ // The factory used to create the sockets of this port.
+ rtc::PacketSocketFactory* socket_factory() const { return factory_; }
+ void set_socket_factory(rtc::PacketSocketFactory* factory) {
+ factory_ = factory;
+ }
+
+ // For debugging purposes.
+ const std::string& content_name() const { return content_name_; }
+ void set_content_name(const std::string& content_name) {
+ content_name_ = content_name;
+ }
+
+ int component() const { return component_; }
+ void set_component(int component) { component_ = component; }
+
+ bool send_retransmit_count_attribute() const {
+ return send_retransmit_count_attribute_;
+ }
+ void set_send_retransmit_count_attribute(bool enable) {
+ send_retransmit_count_attribute_ = enable;
+ }
+
+ // Identifies the generation that this port was created in.
+ uint32 generation() { return generation_; }
+ void set_generation(uint32 generation) { generation_ = generation; }
+
+ // ICE requires a single username/password per content/media line. So the
+ // |ice_username_fragment_| of the ports that belongs to the same content will
+ // be the same. However this causes a small complication with our relay
+ // server, which expects different username for RTP and RTCP.
+ //
+ // To resolve this problem, we implemented the username_fragment(),
+ // which returns a different username (calculated from
+ // |ice_username_fragment_|) for RTCP in the case of ICEPROTO_GOOGLE. And the
+ // username_fragment() simply returns |ice_username_fragment_| when running
+ // in ICEPROTO_RFC5245.
+ //
+ // As a result the ICEPROTO_GOOGLE will use different usernames for RTP and
+ // RTCP. And the ICEPROTO_RFC5245 will use same username for both RTP and
+ // RTCP.
+ const std::string username_fragment() const;
+ const std::string& password() const { return password_; }
+
+ // Fired when candidates are discovered by the port. When all candidates
+ // are discovered that belong to port SignalAddressReady is fired.
+ sigslot::signal2<Port*, const Candidate&> SignalCandidateReady;
+
+ // Provides all of the above information in one handy object.
+ virtual const std::vector<Candidate>& Candidates() const {
+ return candidates_;
+ }
+
+ // SignalPortComplete is sent when port completes the task of candidates
+ // allocation.
+ sigslot::signal1<Port*> SignalPortComplete;
+ // This signal sent when port fails to allocate candidates and this port
+ // can't be used in establishing the connections. When port is in shared mode
+ // and port fails to allocate one of the candidates, port shouldn't send
+ // this signal as other candidates might be usefull in establishing the
+ // connection.
+ sigslot::signal1<Port*> SignalPortError;
+
+ // Returns a map containing all of the connections of this port, keyed by the
+ // remote address.
+ typedef std::map<rtc::SocketAddress, Connection*> AddressMap;
+ const AddressMap& connections() { return connections_; }
+
+ // Returns the connection to the given address or NULL if none exists.
+ virtual Connection* GetConnection(
+ const rtc::SocketAddress& remote_addr);
+
+ // Called each time a connection is created.
+ sigslot::signal2<Port*, Connection*> SignalConnectionCreated;
+
+ // In a shared socket mode each port which shares the socket will decide
+ // to accept the packet based on the |remote_addr|. Currently only UDP
+ // port implemented this method.
+ // TODO(mallinath) - Make it pure virtual.
+ virtual bool HandleIncomingPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(false);
+ return false;
+ }
+
+ // Sends a response message (normal or error) to the given request. One of
+ // these methods should be called as a response to SignalUnknownAddress.
+ // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
+ virtual void SendBindingResponse(StunMessage* request,
+ const rtc::SocketAddress& addr);
+ virtual void SendBindingErrorResponse(
+ StunMessage* request, const rtc::SocketAddress& addr,
+ int error_code, const std::string& reason);
+
+ void set_proxy(const std::string& user_agent,
+ const rtc::ProxyInfo& proxy) {
+ user_agent_ = user_agent;
+ proxy_ = proxy;
+ }
+ const std::string& user_agent() { return user_agent_; }
+ const rtc::ProxyInfo& proxy() { return proxy_; }
+
+ virtual void EnablePortPackets();
+
+ // Called if the port has no connections and is no longer useful.
+ void Destroy();
+
+ virtual void OnMessage(rtc::Message *pmsg);
+
+ // Debugging description of this port
+ virtual std::string ToString() const;
+ rtc::IPAddress& ip() { return ip_; }
+ int min_port() { return min_port_; }
+ int max_port() { return max_port_; }
+
+ // Timeout shortening function to speed up unit tests.
+ void set_timeout_delay(int delay) { timeout_delay_ = delay; }
+
+ // This method will return local and remote username fragements from the
+ // stun username attribute if present.
+ bool ParseStunUsername(const StunMessage* stun_msg,
+ std::string* local_username,
+ std::string* remote_username,
+ IceProtocolType* remote_protocol_type) const;
+ void CreateStunUsername(const std::string& remote_username,
+ std::string* stun_username_attr_str) const;
+
+ bool MaybeIceRoleConflict(const rtc::SocketAddress& addr,
+ IceMessage* stun_msg,
+ const std::string& remote_ufrag);
+
+ // Called when the socket is currently able to send.
+ void OnReadyToSend();
+
+ // Called when the Connection discovers a local peer reflexive candidate.
+ // Returns the index of the new local candidate.
+ size_t AddPrflxCandidate(const Candidate& local);
+
+ // Returns if RFC 5245 ICE protocol is used.
+ bool IsStandardIce() const;
+
+ // Returns if Google ICE protocol is used.
+ bool IsGoogleIce() const;
+
+ // Returns if Hybrid ICE protocol is used.
+ bool IsHybridIce() const;
+
+ void set_candidate_filter(uint32 candidate_filter) {
+ candidate_filter_ = candidate_filter;
+ }
+
+ protected:
+ enum {
+ MSG_CHECKTIMEOUT = 0,
+ MSG_FIRST_AVAILABLE
+ };
+
+ void set_type(const std::string& type) { type_ = type; }
+
+ void 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);
+
+ // Adds the given connection to the list. (Deleting removes them.)
+ void AddConnection(Connection* conn);
+
+ // Called when a packet is received from an unknown address that is not
+ // currently a connection. If this is an authenticated STUN binding request,
+ // then we will signal the client.
+ void OnReadPacket(const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ ProtocolType proto);
+
+ // If the given data comprises a complete and correct STUN message then the
+ // return value is true, otherwise false. If the message username corresponds
+ // with this port's username fragment, msg will contain the parsed STUN
+ // message. Otherwise, the function may send a STUN response internally.
+ // remote_username contains the remote fragment of the STUN username.
+ bool GetStunMessage(const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ IceMessage** out_msg, std::string* out_username);
+
+ // Checks if the address in addr is compatible with the port's ip.
+ bool IsCompatibleAddress(const rtc::SocketAddress& addr);
+
+ // Returns default DSCP value.
+ rtc::DiffServCodePoint DefaultDscpValue() const {
+ // No change from what MediaChannel set.
+ return rtc::DSCP_NO_CHANGE;
+ }
+
+ uint32 candidate_filter() { return candidate_filter_; }
+
+ private:
+ void Construct();
+ // Called when one of our connections deletes itself.
+ void OnConnectionDestroyed(Connection* conn);
+
+ // Checks if this port is useless, and hence, should be destroyed.
+ void CheckTimeout();
+
+ rtc::Thread* thread_;
+ rtc::PacketSocketFactory* factory_;
+ std::string type_;
+ bool send_retransmit_count_attribute_;
+ rtc::Network* network_;
+ rtc::IPAddress ip_;
+ int min_port_;
+ int max_port_;
+ std::string content_name_;
+ int component_;
+ uint32 generation_;
+ // In order to establish a connection to this Port (so that real data can be
+ // sent through), the other side must send us a STUN binding request that is
+ // authenticated with this username_fragment and password.
+ // PortAllocatorSession will provide these username_fragment and password.
+ //
+ // Note: we should always use username_fragment() instead of using
+ // |ice_username_fragment_| directly. For the details see the comment on
+ // username_fragment().
+ std::string ice_username_fragment_;
+ std::string password_;
+ std::vector<Candidate> candidates_;
+ AddressMap connections_;
+ int timeout_delay_;
+ bool enable_port_packets_;
+ IceProtocolType ice_protocol_;
+ IceRole ice_role_;
+ uint64 tiebreaker_;
+ bool shared_socket_;
+ // Information to use when going through a proxy.
+ std::string user_agent_;
+ rtc::ProxyInfo proxy_;
+
+ // Candidate filter is pushed down to Port such that each Port could
+ // make its own decision on how to create candidates. For example,
+ // when IceTransportsType is set to relay, both RelayPort and
+ // TurnPort will hide raddr to avoid local address leakage.
+ uint32 candidate_filter_;
+
+ friend class Connection;
+};
+
+// Represents a communication link between a port on the local client and a
+// port on the remote client.
+class Connection : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ // States are from RFC 5245. http://tools.ietf.org/html/rfc5245#section-5.7.4
+ enum State {
+ STATE_WAITING = 0, // Check has not been performed, Waiting pair on CL.
+ STATE_INPROGRESS, // Check has been sent, transaction is in progress.
+ STATE_SUCCEEDED, // Check already done, produced a successful result.
+ STATE_FAILED // Check for this connection failed.
+ };
+
+ virtual ~Connection();
+
+ // The local port where this connection sends and receives packets.
+ Port* port() { return port_; }
+ const Port* port() const { return port_; }
+
+ // Returns the description of the local port
+ virtual const Candidate& local_candidate() const;
+
+ // Returns the description of the remote port to which we communicate.
+ const Candidate& remote_candidate() const { return remote_candidate_; }
+
+ // Returns the pair priority.
+ uint64 priority() const;
+
+ enum ReadState {
+ STATE_READ_INIT = 0, // we have yet to receive a ping
+ STATE_READABLE = 1, // we have received pings recently
+ STATE_READ_TIMEOUT = 2, // we haven't received pings in a while
+ };
+
+ ReadState read_state() const { return read_state_; }
+ bool readable() const { return read_state_ == STATE_READABLE; }
+
+ enum WriteState {
+ STATE_WRITABLE = 0, // we have received ping responses recently
+ STATE_WRITE_UNRELIABLE = 1, // we have had a few ping failures
+ STATE_WRITE_INIT = 2, // we have yet to receive a ping response
+ STATE_WRITE_TIMEOUT = 3, // we have had a large number of ping failures
+ };
+
+ WriteState write_state() const { return write_state_; }
+ bool writable() const { return write_state_ == STATE_WRITABLE; }
+
+ // Determines whether the connection has finished connecting. This can only
+ // be false for TCP connections.
+ bool connected() const { return connected_; }
+
+ // Estimate of the round-trip time over this connection.
+ uint32 rtt() const { return rtt_; }
+
+ size_t sent_total_bytes();
+ size_t sent_bytes_second();
+ size_t recv_total_bytes();
+ size_t recv_bytes_second();
+ sigslot::signal1<Connection*> SignalStateChange;
+
+ // Sent when the connection has decided that it is no longer of value. It
+ // will delete itself immediately after this call.
+ sigslot::signal1<Connection*> SignalDestroyed;
+
+ // The connection can send and receive packets asynchronously. This matches
+ // the interface of AsyncPacketSocket, which may use UDP or TCP under the
+ // covers.
+ virtual int Send(const void* data, size_t size,
+ const rtc::PacketOptions& options) = 0;
+
+ // Error if Send() returns < 0
+ virtual int GetError() = 0;
+
+ sigslot::signal4<Connection*, const char*, size_t,
+ const rtc::PacketTime&> SignalReadPacket;
+
+ sigslot::signal1<Connection*> SignalReadyToSend;
+
+ // Called when a packet is received on this connection.
+ void OnReadPacket(const char* data, size_t size,
+ const rtc::PacketTime& packet_time);
+
+ // Called when the socket is currently able to send.
+ void OnReadyToSend();
+
+ // Called when a connection is determined to be no longer useful to us. We
+ // still keep it around in case the other side wants to use it. But we can
+ // safely stop pinging on it and we can allow it to time out if the other
+ // side stops using it as well.
+ bool pruned() const { return pruned_; }
+ void Prune();
+
+ bool use_candidate_attr() const { return use_candidate_attr_; }
+ void set_use_candidate_attr(bool enable);
+
+ void set_remote_ice_mode(IceMode mode) {
+ remote_ice_mode_ = mode;
+ }
+
+ // Makes the connection go away.
+ void Destroy();
+
+ // Checks that the state of this connection is up-to-date. The argument is
+ // the current time, which is compared against various timeouts.
+ void UpdateState(uint32 now);
+
+ // Called when this connection should try checking writability again.
+ uint32 last_ping_sent() const { return last_ping_sent_; }
+ void Ping(uint32 now);
+
+ // Called whenever a valid ping is received on this connection. This is
+ // public because the connection intercepts the first ping for us.
+ uint32 last_ping_received() const { return last_ping_received_; }
+ void ReceivedPing();
+
+ // Debugging description of this connection
+ std::string ToString() const;
+ std::string ToSensitiveString() const;
+
+ bool reported() const { return reported_; }
+ void set_reported(bool reported) { reported_ = reported;}
+
+ // This flag will be set if this connection is the chosen one for media
+ // transmission. This connection will send STUN ping with USE-CANDIDATE
+ // attribute.
+ sigslot::signal1<Connection*> SignalUseCandidate;
+ // Invoked when Connection receives STUN error response with 487 code.
+ void HandleRoleConflictFromPeer();
+
+ State state() const { return state_; }
+
+ IceMode remote_ice_mode() const { return remote_ice_mode_; }
+
+ protected:
+ // Constructs a new connection to the given remote port.
+ Connection(Port* port, size_t index, const Candidate& candidate);
+
+ // Called back when StunRequestManager has a stun packet to send
+ void OnSendStunPacket(const void* data, size_t size, StunRequest* req);
+
+ // Callbacks from ConnectionRequest
+ void OnConnectionRequestResponse(ConnectionRequest* req,
+ StunMessage* response);
+ void OnConnectionRequestErrorResponse(ConnectionRequest* req,
+ StunMessage* response);
+ void OnConnectionRequestTimeout(ConnectionRequest* req);
+
+ // Changes the state and signals if necessary.
+ void set_read_state(ReadState value);
+ void set_write_state(WriteState value);
+ void set_state(State state);
+ void set_connected(bool value);
+
+ // Checks if this connection is useless, and hence, should be destroyed.
+ void CheckTimeout();
+
+ void OnMessage(rtc::Message *pmsg);
+
+ Port* port_;
+ size_t local_candidate_index_;
+ Candidate remote_candidate_;
+ ReadState read_state_;
+ WriteState write_state_;
+ bool connected_;
+ bool pruned_;
+ // By default |use_candidate_attr_| flag will be true,
+ // as we will be using agrressive nomination.
+ // But when peer is ice-lite, this flag "must" be initialized to false and
+ // turn on when connection becomes "best connection".
+ bool use_candidate_attr_;
+ IceMode remote_ice_mode_;
+ StunRequestManager requests_;
+ uint32 rtt_;
+ uint32 last_ping_sent_; // last time we sent a ping to the other side
+ uint32 last_ping_received_; // last time we received a ping from the other
+ // side
+ uint32 last_data_received_;
+ uint32 last_ping_response_received_;
+ std::vector<uint32> pings_since_last_response_;
+
+ rtc::RateTracker recv_rate_tracker_;
+ rtc::RateTracker send_rate_tracker_;
+
+ private:
+ void MaybeAddPrflxCandidate(ConnectionRequest* request,
+ StunMessage* response);
+
+ bool reported_;
+ State state_;
+
+ friend class Port;
+ friend class ConnectionRequest;
+};
+
+// ProxyConnection defers all the interesting work to the port
+class ProxyConnection : public Connection {
+ public:
+ ProxyConnection(Port* port, size_t index, const Candidate& candidate);
+
+ virtual int Send(const void* data, size_t size,
+ const rtc::PacketOptions& options);
+ virtual int GetError() { return error_; }
+
+ private:
+ int error_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PORT_H_
diff --git a/p2p/base/port_unittest.cc b/p2p/base/port_unittest.cc
new file mode 100644
index 00000000..8805709a
--- /dev/null
+++ b/p2p/base/port_unittest.cc
@@ -0,0 +1,2494 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/portproxy.h"
+#include "webrtc/p2p/base/relayport.h"
+#include "webrtc/p2p/base/stunport.h"
+#include "webrtc/p2p/base/tcpport.h"
+#include "webrtc/p2p/base/testrelayserver.h"
+#include "webrtc/p2p/base/teststunserver.h"
+#include "webrtc/p2p/base/testturnserver.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/turnport.h"
+#include "webrtc/base/crc32.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/natserver.h"
+#include "webrtc/base/natsocketfactory.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/stringutils.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using rtc::AsyncPacketSocket;
+using rtc::ByteBuffer;
+using rtc::NATType;
+using rtc::NAT_OPEN_CONE;
+using rtc::NAT_ADDR_RESTRICTED;
+using rtc::NAT_PORT_RESTRICTED;
+using rtc::NAT_SYMMETRIC;
+using rtc::PacketSocketFactory;
+using rtc::scoped_ptr;
+using rtc::Socket;
+using rtc::SocketAddress;
+using namespace cricket;
+
+static const int kTimeout = 1000;
+static const SocketAddress kLocalAddr1("192.168.1.2", 0);
+static const SocketAddress kLocalAddr2("192.168.1.3", 0);
+static const SocketAddress kNatAddr1("77.77.77.77", rtc::NAT_SERVER_PORT);
+static const SocketAddress kNatAddr2("88.88.88.88", rtc::NAT_SERVER_PORT);
+static const SocketAddress kStunAddr("99.99.99.1", STUN_SERVER_PORT);
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+static const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const RelayCredentials kRelayCredentials("test", "test");
+
+// TODO: Update these when RFC5245 is completely supported.
+// Magic value of 30 is from RFC3484, for IPv4 addresses.
+static const uint32 kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
+ 30 << 8 | (256 - ICE_CANDIDATE_COMPONENT_DEFAULT);
+static const int STUN_ERROR_BAD_REQUEST_AS_GICE =
+ STUN_ERROR_BAD_REQUEST / 256 * 100 + STUN_ERROR_BAD_REQUEST % 256;
+static const int STUN_ERROR_UNAUTHORIZED_AS_GICE =
+ STUN_ERROR_UNAUTHORIZED / 256 * 100 + STUN_ERROR_UNAUTHORIZED % 256;
+static const int STUN_ERROR_SERVER_ERROR_AS_GICE =
+ STUN_ERROR_SERVER_ERROR / 256 * 100 + STUN_ERROR_SERVER_ERROR % 256;
+
+static const int kTiebreaker1 = 11111;
+static const int kTiebreaker2 = 22222;
+
+static Candidate GetCandidate(Port* port) {
+ assert(port->Candidates().size() == 1);
+ return port->Candidates()[0];
+}
+
+static SocketAddress GetAddress(Port* port) {
+ return GetCandidate(port).address();
+}
+
+static IceMessage* CopyStunMessage(const IceMessage* src) {
+ IceMessage* dst = new IceMessage();
+ ByteBuffer buf;
+ src->Write(&buf);
+ dst->Read(&buf);
+ return dst;
+}
+
+static bool WriteStunMessage(const StunMessage* msg, ByteBuffer* buf) {
+ buf->Resize(0); // clear out any existing buffer contents
+ return msg->Write(buf);
+}
+
+// Stub port class for testing STUN generation and processing.
+class TestPort : public Port {
+ public:
+ TestPort(rtc::Thread* thread, const std::string& type,
+ rtc::PacketSocketFactory* factory, rtc::Network* network,
+ const rtc::IPAddress& ip, int min_port, int max_port,
+ const std::string& username_fragment, const std::string& password)
+ : Port(thread, type, factory, network, ip,
+ min_port, max_port, username_fragment, password) {
+ }
+ ~TestPort() {}
+
+ // Expose GetStunMessage so that we can test it.
+ using cricket::Port::GetStunMessage;
+
+ // The last StunMessage that was sent on this Port.
+ // TODO: Make these const; requires changes to SendXXXXResponse.
+ ByteBuffer* last_stun_buf() { return last_stun_buf_.get(); }
+ IceMessage* last_stun_msg() { return last_stun_msg_.get(); }
+ int last_stun_error_code() {
+ int code = 0;
+ if (last_stun_msg_) {
+ const StunErrorCodeAttribute* error_attr = last_stun_msg_->GetErrorCode();
+ if (error_attr) {
+ code = error_attr->code();
+ }
+ }
+ return code;
+ }
+
+ virtual void PrepareAddress() {
+ rtc::SocketAddress addr(ip(), min_port());
+ 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_, 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, 0, final);
+ }
+
+ virtual Connection* CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ Connection* conn = new ProxyConnection(this, 0, remote_candidate);
+ AddConnection(conn);
+ // Set use-candidate attribute flag as this will add USE-CANDIDATE attribute
+ // in STUN binding requests.
+ conn->set_use_candidate_attr(true);
+ return conn;
+ }
+ virtual int SendTo(
+ const void* data, size_t size, const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options, bool payload) {
+ if (!payload) {
+ IceMessage* msg = new IceMessage;
+ ByteBuffer* buf = new ByteBuffer(static_cast<const char*>(data), size);
+ ByteBuffer::ReadPosition pos(buf->GetReadPosition());
+ if (!msg->Read(buf)) {
+ delete msg;
+ delete buf;
+ return -1;
+ }
+ buf->SetReadPosition(pos);
+ last_stun_buf_.reset(buf);
+ last_stun_msg_.reset(msg);
+ }
+ return static_cast<int>(size);
+ }
+ virtual int SetOption(rtc::Socket::Option opt, int value) {
+ return 0;
+ }
+ virtual int GetOption(rtc::Socket::Option opt, int* value) {
+ return -1;
+ }
+ virtual int GetError() {
+ return 0;
+ }
+ void Reset() {
+ last_stun_buf_.reset();
+ last_stun_msg_.reset();
+ }
+ void set_type_preference(int type_preference) {
+ type_preference_ = type_preference;
+ }
+
+ private:
+ rtc::scoped_ptr<ByteBuffer> last_stun_buf_;
+ rtc::scoped_ptr<IceMessage> last_stun_msg_;
+ int type_preference_;
+};
+
+class TestChannel : public sigslot::has_slots<> {
+ public:
+ // Takes ownership of |p1| (but not |p2|).
+ TestChannel(Port* p1, Port* p2)
+ : ice_mode_(ICEMODE_FULL), src_(p1), dst_(p2), complete_count_(0),
+ conn_(NULL), remote_request_(), nominated_(false) {
+ src_->SignalPortComplete.connect(
+ this, &TestChannel::OnPortComplete);
+ src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
+ src_->SignalDestroyed.connect(this, &TestChannel::OnSrcPortDestroyed);
+ }
+
+ int complete_count() { return complete_count_; }
+ Connection* conn() { return conn_; }
+ const SocketAddress& remote_address() { return remote_address_; }
+ const std::string remote_fragment() { return remote_frag_; }
+
+ void Start() {
+ src_->PrepareAddress();
+ }
+ void CreateConnection() {
+ conn_ = src_->CreateConnection(GetCandidate(dst_), Port::ORIGIN_MESSAGE);
+ IceMode remote_ice_mode =
+ (ice_mode_ == ICEMODE_FULL) ? ICEMODE_LITE : ICEMODE_FULL;
+ conn_->set_remote_ice_mode(remote_ice_mode);
+ conn_->set_use_candidate_attr(remote_ice_mode == ICEMODE_FULL);
+ conn_->SignalStateChange.connect(
+ this, &TestChannel::OnConnectionStateChange);
+ }
+ void OnConnectionStateChange(Connection* conn) {
+ if (conn->write_state() == Connection::STATE_WRITABLE) {
+ conn->set_use_candidate_attr(true);
+ nominated_ = true;
+ }
+ }
+ void AcceptConnection() {
+ ASSERT_TRUE(remote_request_.get() != NULL);
+ Candidate c = GetCandidate(dst_);
+ c.set_address(remote_address_);
+ conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE);
+ src_->SendBindingResponse(remote_request_.get(), remote_address_);
+ remote_request_.reset();
+ }
+ void Ping() {
+ Ping(0);
+ }
+ void Ping(uint32 now) {
+ conn_->Ping(now);
+ }
+ void Stop() {
+ conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+ conn_->Destroy();
+ }
+
+ void OnPortComplete(Port* port) {
+ complete_count_++;
+ }
+ void SetIceMode(IceMode ice_mode) {
+ ice_mode_ = ice_mode;
+ }
+
+ void OnUnknownAddress(PortInterface* port, const SocketAddress& addr,
+ ProtocolType proto,
+ IceMessage* msg, const std::string& rf,
+ bool /*port_muxed*/) {
+ ASSERT_EQ(src_.get(), port);
+ if (!remote_address_.IsNil()) {
+ ASSERT_EQ(remote_address_, addr);
+ }
+ // MI and PRIORITY attribute should be present in ping requests when port
+ // is in ICEPROTO_RFC5245 mode.
+ const cricket::StunUInt32Attribute* priority_attr =
+ msg->GetUInt32(STUN_ATTR_PRIORITY);
+ const cricket::StunByteStringAttribute* mi_attr =
+ msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ const cricket::StunUInt32Attribute* fingerprint_attr =
+ msg->GetUInt32(STUN_ATTR_FINGERPRINT);
+ if (src_->IceProtocol() == cricket::ICEPROTO_RFC5245) {
+ EXPECT_TRUE(priority_attr != NULL);
+ EXPECT_TRUE(mi_attr != NULL);
+ EXPECT_TRUE(fingerprint_attr != NULL);
+ } else {
+ EXPECT_TRUE(priority_attr == NULL);
+ EXPECT_TRUE(mi_attr == NULL);
+ EXPECT_TRUE(fingerprint_attr == NULL);
+ }
+ remote_address_ = addr;
+ remote_request_.reset(CopyStunMessage(msg));
+ remote_frag_ = rf;
+ }
+
+ void OnDestroyed(Connection* conn) {
+ ASSERT_EQ(conn_, conn);
+ conn_ = NULL;
+ }
+
+ void OnSrcPortDestroyed(PortInterface* port) {
+ Port* destroyed_src = src_.release();
+ ASSERT_EQ(destroyed_src, port);
+ }
+
+ bool nominated() const { return nominated_; }
+
+ private:
+ IceMode ice_mode_;
+ rtc::scoped_ptr<Port> src_;
+ Port* dst_;
+
+ int complete_count_;
+ Connection* conn_;
+ SocketAddress remote_address_;
+ rtc::scoped_ptr<StunMessage> remote_request_;
+ std::string remote_frag_;
+ bool nominated_;
+};
+
+class PortTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ PortTest()
+ : main_(rtc::Thread::Current()),
+ pss_(new rtc::PhysicalSocketServer),
+ ss_(new rtc::VirtualSocketServer(pss_.get())),
+ ss_scope_(ss_.get()),
+ network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32),
+ socket_factory_(rtc::Thread::Current()),
+ nat_factory1_(ss_.get(), kNatAddr1),
+ nat_factory2_(ss_.get(), kNatAddr2),
+ nat_socket_factory1_(&nat_factory1_),
+ nat_socket_factory2_(&nat_factory2_),
+ stun_server_(TestStunServer::Create(main_, kStunAddr)),
+ turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
+ relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+ kRelayTcpIntAddr, kRelayTcpExtAddr,
+ kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+ username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)),
+ password_(rtc::CreateRandomString(ICE_PWD_LENGTH)),
+ ice_protocol_(cricket::ICEPROTO_GOOGLE),
+ role_conflict_(false),
+ destroyed_(false) {
+ network_.AddIP(rtc::IPAddress(INADDR_ANY));
+ }
+
+ protected:
+ void TestLocalToLocal() {
+ Port* port1 = CreateUdpPort(kLocalAddr1);
+ Port* port2 = CreateUdpPort(kLocalAddr2);
+ TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+ }
+ void TestLocalToStun(NATType ntype) {
+ Port* port1 = CreateUdpPort(kLocalAddr1);
+ nat_server2_.reset(CreateNatServer(kNatAddr2, ntype));
+ Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ TestConnectivity("udp", port1, StunName(ntype), port2,
+ ntype == NAT_OPEN_CONE, true,
+ ntype != NAT_SYMMETRIC, true);
+ }
+ void TestLocalToRelay(RelayType rtype, ProtocolType proto) {
+ Port* port1 = CreateUdpPort(kLocalAddr1);
+ Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP);
+ TestConnectivity("udp", port1, RelayName(rtype, proto), port2,
+ rtype == RELAY_GTURN, true, true, true);
+ }
+ void TestStunToLocal(NATType ntype) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, ntype));
+ Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ Port* port2 = CreateUdpPort(kLocalAddr2);
+ TestConnectivity(StunName(ntype), port1, "udp", port2,
+ true, ntype != NAT_SYMMETRIC, true, true);
+ }
+ void TestStunToStun(NATType ntype1, NATType ntype2) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, ntype1));
+ Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ nat_server2_.reset(CreateNatServer(kNatAddr2, ntype2));
+ Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ TestConnectivity(StunName(ntype1), port1, StunName(ntype2), port2,
+ ntype2 == NAT_OPEN_CONE,
+ ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC,
+ ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
+ }
+ void TestStunToRelay(NATType ntype, RelayType rtype, ProtocolType proto) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, ntype));
+ Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP);
+ TestConnectivity(StunName(ntype), port1, RelayName(rtype, proto), port2,
+ rtype == RELAY_GTURN, ntype != NAT_SYMMETRIC, true, true);
+ }
+ void TestTcpToTcp() {
+ Port* port1 = CreateTcpPort(kLocalAddr1);
+ Port* port2 = CreateTcpPort(kLocalAddr2);
+ TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true);
+ }
+ void TestTcpToRelay(RelayType rtype, ProtocolType proto) {
+ Port* port1 = CreateTcpPort(kLocalAddr1);
+ Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_TCP);
+ TestConnectivity("tcp", port1, RelayName(rtype, proto), port2,
+ rtype == RELAY_GTURN, false, true, true);
+ }
+ void TestSslTcpToRelay(RelayType rtype, ProtocolType proto) {
+ Port* port1 = CreateTcpPort(kLocalAddr1);
+ Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_SSLTCP);
+ TestConnectivity("ssltcp", port1, RelayName(rtype, proto), port2,
+ rtype == RELAY_GTURN, false, true, true);
+ }
+
+ // helpers for above functions
+ UDPPort* CreateUdpPort(const SocketAddress& addr) {
+ return CreateUdpPort(addr, &socket_factory_);
+ }
+ UDPPort* CreateUdpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ UDPPort* port = UDPPort::Create(main_, socket_factory, &network_,
+ addr.ipaddr(), 0, 0, username_, password_);
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ TCPPort* CreateTcpPort(const SocketAddress& addr) {
+ TCPPort* port = CreateTcpPort(addr, &socket_factory_);
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ TCPPort* CreateTcpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ TCPPort* port = TCPPort::Create(main_, socket_factory, &network_,
+ addr.ipaddr(), 0, 0, username_, password_,
+ true);
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ StunPort* CreateStunPort(const SocketAddress& addr,
+ rtc::PacketSocketFactory* factory) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ StunPort* port = StunPort::Create(main_, factory, &network_,
+ addr.ipaddr(), 0, 0,
+ username_, password_, stun_servers);
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ Port* CreateRelayPort(const SocketAddress& addr, RelayType rtype,
+ ProtocolType int_proto, ProtocolType ext_proto) {
+ if (rtype == RELAY_TURN) {
+ return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
+ } else {
+ return CreateGturnPort(addr, int_proto, ext_proto);
+ }
+ }
+ TurnPort* CreateTurnPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory,
+ ProtocolType int_proto, ProtocolType ext_proto) {
+ return CreateTurnPort(addr, socket_factory,
+ int_proto, ext_proto, kTurnUdpIntAddr);
+ }
+ TurnPort* CreateTurnPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory,
+ ProtocolType int_proto, ProtocolType ext_proto,
+ const rtc::SocketAddress& server_addr) {
+ TurnPort* port = TurnPort::Create(main_, socket_factory, &network_,
+ addr.ipaddr(), 0, 0,
+ username_, password_, ProtocolAddress(
+ server_addr, PROTO_UDP),
+ kRelayCredentials, 0);
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ RelayPort* CreateGturnPort(const SocketAddress& addr,
+ ProtocolType int_proto, ProtocolType ext_proto) {
+ RelayPort* port = CreateGturnPort(addr);
+ SocketAddress addrs[] =
+ { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr };
+ port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto));
+ return port;
+ }
+ RelayPort* CreateGturnPort(const SocketAddress& addr) {
+ RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_,
+ addr.ipaddr(), 0, 0,
+ username_, password_);
+ // TODO: Add an external address for ext_proto, so that the
+ // other side can connect to this port using a non-UDP protocol.
+ port->SetIceProtocolType(ice_protocol_);
+ return port;
+ }
+ rtc::NATServer* CreateNatServer(const SocketAddress& addr,
+ rtc::NATType type) {
+ return new rtc::NATServer(type, ss_.get(), addr, ss_.get(), addr);
+ }
+ static const char* StunName(NATType type) {
+ switch (type) {
+ case NAT_OPEN_CONE: return "stun(open cone)";
+ case NAT_ADDR_RESTRICTED: return "stun(addr restricted)";
+ case NAT_PORT_RESTRICTED: return "stun(port restricted)";
+ case NAT_SYMMETRIC: return "stun(symmetric)";
+ default: return "stun(?)";
+ }
+ }
+ static const char* RelayName(RelayType type, ProtocolType proto) {
+ if (type == RELAY_TURN) {
+ switch (proto) {
+ case PROTO_UDP: return "turn(udp)";
+ case PROTO_TCP: return "turn(tcp)";
+ case PROTO_SSLTCP: return "turn(ssltcp)";
+ default: return "turn(?)";
+ }
+ } else {
+ switch (proto) {
+ case PROTO_UDP: return "gturn(udp)";
+ case PROTO_TCP: return "gturn(tcp)";
+ case PROTO_SSLTCP: return "gturn(ssltcp)";
+ default: return "gturn(?)";
+ }
+ }
+ }
+
+ void TestCrossFamilyPorts(int type);
+
+ // This does all the work and then deletes |port1| and |port2|.
+ void TestConnectivity(const char* name1, Port* port1,
+ const char* name2, Port* port2,
+ bool accept, bool same_addr1,
+ bool same_addr2, bool possible);
+
+ // This connects and disconnects the provided channels in the same sequence as
+ // TestConnectivity with all options set to |true|. It does not delete either
+ // channel.
+ void ConnectAndDisconnectChannels(TestChannel* ch1, TestChannel* ch2);
+
+ void SetIceProtocolType(cricket::IceProtocolType protocol) {
+ ice_protocol_ = protocol;
+ }
+
+ IceMessage* CreateStunMessage(int type) {
+ IceMessage* msg = new IceMessage();
+ msg->SetType(type);
+ msg->SetTransactionID("TESTTESTTEST");
+ return msg;
+ }
+ IceMessage* CreateStunMessageWithUsername(int type,
+ const std::string& username) {
+ IceMessage* msg = CreateStunMessage(type);
+ msg->AddAttribute(
+ new StunByteStringAttribute(STUN_ATTR_USERNAME, username));
+ return msg;
+ }
+ TestPort* CreateTestPort(const rtc::SocketAddress& addr,
+ const std::string& username,
+ const std::string& password) {
+ TestPort* port = new TestPort(main_, "test", &socket_factory_, &network_,
+ addr.ipaddr(), 0, 0, username, password);
+ port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
+ return port;
+ }
+ TestPort* CreateTestPort(const rtc::SocketAddress& addr,
+ const std::string& username,
+ const std::string& password,
+ cricket::IceProtocolType type,
+ cricket::IceRole role,
+ int tiebreaker) {
+ TestPort* port = CreateTestPort(addr, username, password);
+ port->SetIceProtocolType(type);
+ port->SetIceRole(role);
+ port->SetIceTiebreaker(tiebreaker);
+ return port;
+ }
+
+ void OnRoleConflict(PortInterface* port) {
+ role_conflict_ = true;
+ }
+ bool role_conflict() const { return role_conflict_; }
+
+ void ConnectToSignalDestroyed(PortInterface* port) {
+ port->SignalDestroyed.connect(this, &PortTest::OnDestroyed);
+ }
+
+ void OnDestroyed(PortInterface* port) {
+ destroyed_ = true;
+ }
+ bool destroyed() const { return destroyed_; }
+
+ rtc::BasicPacketSocketFactory* nat_socket_factory1() {
+ return &nat_socket_factory1_;
+ }
+
+ private:
+ rtc::Thread* main_;
+ rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ rtc::scoped_ptr<rtc::NATServer> nat_server1_;
+ rtc::scoped_ptr<rtc::NATServer> nat_server2_;
+ rtc::NATSocketFactory nat_factory1_;
+ rtc::NATSocketFactory nat_factory2_;
+ rtc::BasicPacketSocketFactory nat_socket_factory1_;
+ rtc::BasicPacketSocketFactory nat_socket_factory2_;
+ scoped_ptr<TestStunServer> stun_server_;
+ TestTurnServer turn_server_;
+ TestRelayServer relay_server_;
+ std::string username_;
+ std::string password_;
+ cricket::IceProtocolType ice_protocol_;
+ bool role_conflict_;
+ bool destroyed_;
+};
+
+void PortTest::TestConnectivity(const char* name1, Port* port1,
+ const char* name2, Port* port2,
+ bool accept, bool same_addr1,
+ bool same_addr2, bool possible) {
+ LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": ";
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+ EXPECT_EQ(0, ch1.complete_count());
+ EXPECT_EQ(0, ch2.complete_count());
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout);
+
+ // Send a ping from src to dst. This may or may not make it.
+ ch1.CreateConnection();
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect
+ ch1.Ping();
+ WAIT(!ch2.remote_address().IsNil(), kTimeout);
+
+ if (accept) {
+ // We are able to send a ping from src to dst. This is the case when
+ // sending to UDP ports and cone NATs.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_EQ(ch2.remote_fragment(), port1->username_fragment());
+
+ // Ensure the ping came from the same address used for src.
+ // This is the case unless the source NAT was symmetric.
+ if (same_addr1) EXPECT_EQ(ch2.remote_address(), GetAddress(port1));
+ EXPECT_TRUE(same_addr2);
+
+ // Send a ping from dst to src.
+ ch2.AcceptConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+ kTimeout);
+ } else {
+ // We can't send a ping from src to dst, so flip it around. This will happen
+ // when the destination NAT is addr/port restricted or symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+
+ // Send a ping from dst to src. Again, this may or may not make it.
+ ch2.CreateConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE, kTimeout);
+
+ if (same_addr1 && same_addr2) {
+ // The new ping got back to the source.
+ EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+
+ // First connection may not be writable if the first ping did not get
+ // through. So we will have to do another.
+ if (ch1.conn()->write_state() == Connection::STATE_WRITE_INIT) {
+ ch1.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ }
+ } else if (!same_addr1 && possible) {
+ // The new ping went to the candidate address, but that address was bad.
+ // This will happen when the source NAT is symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+
+ // However, since we have now sent a ping to the source IP, we should be
+ // able to get a ping from it. This gives us the real source address.
+ ch1.Ping();
+ EXPECT_TRUE_WAIT(!ch2.remote_address().IsNil(), kTimeout);
+ EXPECT_EQ(Connection::STATE_READ_INIT, ch2.conn()->read_state());
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+
+ // Pick up the actual address and establish the connection.
+ ch2.AcceptConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+ kTimeout);
+ } else if (!same_addr2 && possible) {
+ // The new ping came in, but from an unexpected address. This will happen
+ // when the destination NAT is symmetric.
+ EXPECT_FALSE(ch1.remote_address().IsNil());
+ EXPECT_EQ(Connection::STATE_READ_INIT, ch1.conn()->read_state());
+
+ // Update our address and complete the connection.
+ ch1.AcceptConnection();
+ ch1.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ } else { // (!possible)
+ // There should be s no way for the pings to reach each other. Check it.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+ ch1.Ping();
+ WAIT(!ch2.remote_address().IsNil(), kTimeout);
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+ }
+ }
+
+ // Everything should be good, unless we know the situation is impossible.
+ ASSERT_TRUE(ch1.conn() != NULL);
+ ASSERT_TRUE(ch2.conn() != NULL);
+ if (possible) {
+ EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_EQ(Connection::STATE_READABLE, ch2.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ } else {
+ EXPECT_NE(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_NE(Connection::STATE_READABLE, ch2.conn()->read_state());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ }
+
+ // Tear down and ensure that goes smoothly.
+ ch1.Stop();
+ ch2.Stop();
+ EXPECT_TRUE_WAIT(ch1.conn() == NULL, kTimeout);
+ EXPECT_TRUE_WAIT(ch2.conn() == NULL, kTimeout);
+}
+
+void PortTest::ConnectAndDisconnectChannels(TestChannel* ch1,
+ TestChannel* ch2) {
+ // Acquire addresses.
+ ch1->Start();
+ ch2->Start();
+
+ // Send a ping from src to dst.
+ ch1->CreateConnection();
+ EXPECT_TRUE_WAIT(ch1->conn()->connected(), kTimeout); // for TCP connect
+ ch1->Ping();
+ WAIT(!ch2->remote_address().IsNil(), kTimeout);
+
+ // Send a ping from dst to src.
+ ch2->AcceptConnection();
+ ch2->Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2->conn()->write_state(),
+ kTimeout);
+
+ // Destroy the connections.
+ ch1->Stop();
+ ch2->Stop();
+}
+
+class FakePacketSocketFactory : public rtc::PacketSocketFactory {
+ public:
+ FakePacketSocketFactory()
+ : next_udp_socket_(NULL),
+ next_server_tcp_socket_(NULL),
+ next_client_tcp_socket_(NULL) {
+ }
+ virtual ~FakePacketSocketFactory() { }
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) {
+ EXPECT_TRUE(next_udp_socket_ != NULL);
+ AsyncPacketSocket* result = next_udp_socket_;
+ next_udp_socket_ = NULL;
+ return result;
+ }
+
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ int opts) {
+ EXPECT_TRUE(next_server_tcp_socket_ != NULL);
+ AsyncPacketSocket* result = next_server_tcp_socket_;
+ next_server_tcp_socket_ = NULL;
+ return result;
+ }
+
+ // TODO: |proxy_info| and |user_agent| should be set
+ // per-factory and not when socket is created.
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const rtc::ProxyInfo& proxy_info,
+ const std::string& user_agent, int opts) {
+ EXPECT_TRUE(next_client_tcp_socket_ != NULL);
+ AsyncPacketSocket* result = next_client_tcp_socket_;
+ next_client_tcp_socket_ = NULL;
+ return result;
+ }
+
+ void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) {
+ next_udp_socket_ = next_udp_socket;
+ }
+ void set_next_server_tcp_socket(AsyncPacketSocket* next_server_tcp_socket) {
+ next_server_tcp_socket_ = next_server_tcp_socket;
+ }
+ void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) {
+ next_client_tcp_socket_ = next_client_tcp_socket;
+ }
+ rtc::AsyncResolverInterface* CreateAsyncResolver() {
+ return NULL;
+ }
+
+ private:
+ AsyncPacketSocket* next_udp_socket_;
+ AsyncPacketSocket* next_server_tcp_socket_;
+ AsyncPacketSocket* next_client_tcp_socket_;
+};
+
+class FakeAsyncPacketSocket : public AsyncPacketSocket {
+ public:
+ // Returns current local address. Address may be set to NULL if the
+ // socket is not bound yet (GetState() returns STATE_BINDING).
+ virtual SocketAddress GetLocalAddress() const {
+ return SocketAddress();
+ }
+
+ // Returns remote address. Returns zeroes if this is not a client TCP socket.
+ virtual SocketAddress GetRemoteAddress() const {
+ return SocketAddress();
+ }
+
+ // Send a packet.
+ virtual int Send(const void *pv, size_t cb,
+ const rtc::PacketOptions& options) {
+ return static_cast<int>(cb);
+ }
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr,
+ const rtc::PacketOptions& options) {
+ return static_cast<int>(cb);
+ }
+ virtual int Close() {
+ return 0;
+ }
+
+ virtual State GetState() const { return state_; }
+ virtual int GetOption(Socket::Option opt, int* value) { return 0; }
+ virtual int SetOption(Socket::Option opt, int value) { return 0; }
+ virtual int GetError() const { return 0; }
+ virtual void SetError(int error) { }
+
+ void set_state(State state) { state_ = state; }
+
+ private:
+ State state_;
+};
+
+// Local -> XXXX
+TEST_F(PortTest, TestLocalToLocal) {
+ TestLocalToLocal();
+}
+
+TEST_F(PortTest, TestLocalToConeNat) {
+ TestLocalToStun(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestLocalToARNat) {
+ TestLocalToStun(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToPRNat) {
+ TestLocalToStun(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToSymNat) {
+ TestLocalToStun(NAT_SYMMETRIC);
+}
+
+// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316.
+TEST_F(PortTest, DISABLED_TestLocalToTurn) {
+ TestLocalToRelay(RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToGturn) {
+ TestLocalToRelay(RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToTcpGturn) {
+ TestLocalToRelay(RELAY_GTURN, PROTO_TCP);
+}
+
+TEST_F(PortTest, TestLocalToSslTcpGturn) {
+ TestLocalToRelay(RELAY_GTURN, PROTO_SSLTCP);
+}
+
+// Cone NAT -> XXXX
+TEST_F(PortTest, TestConeNatToLocal) {
+ TestStunToLocal(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToConeNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToARNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToPRNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToSymNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestConeNatToTurn) {
+ TestStunToRelay(NAT_OPEN_CONE, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToGturn) {
+ TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToTcpGturn) {
+ TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_TCP);
+}
+
+// Address-restricted NAT -> XXXX
+TEST_F(PortTest, TestARNatToLocal) {
+ TestStunToLocal(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToConeNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestARNatToARNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToPRNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToSymNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestARNatToTurn) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNatToGturn) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNATNatToTcpGturn) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_TCP);
+}
+
+// Port-restricted NAT -> XXXX
+TEST_F(PortTest, TestPRNatToLocal) {
+ TestStunToLocal(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToConeNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestPRNatToARNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToPRNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestPRNatToTurn) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToGturn) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToTcpGturn) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_TCP);
+}
+
+// Symmetric NAT -> XXXX
+TEST_F(PortTest, TestSymNatToLocal) {
+ TestStunToLocal(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToConeNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestSymNatToARNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToPRNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToTurn) {
+ TestStunToRelay(NAT_SYMMETRIC, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToGturn) {
+ TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToTcpGturn) {
+ TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_TCP);
+}
+
+// Outbound TCP -> XXXX
+TEST_F(PortTest, TestTcpToTcp) {
+ TestTcpToTcp();
+}
+
+/* TODO: Enable these once testrelayserver can accept external TCP.
+TEST_F(PortTest, TestTcpToTcpRelay) {
+ TestTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestTcpToSslTcpRelay) {
+ TestTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// Outbound SSLTCP -> XXXX
+/* TODO: Enable these once testrelayserver can accept external SSL.
+TEST_F(PortTest, TestSslTcpToTcpRelay) {
+ TestSslTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestSslTcpToSslTcpRelay) {
+ TestSslTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// This test case verifies standard ICE features in STUN messages. Currently it
+// verifies Message Integrity attribute in STUN messages and username in STUN
+// binding request will have colon (":") between remote and local username.
+TEST_F(PortTest, TestLocalToLocalAsIce) {
+ SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+ ASSERT_EQ(cricket::ICEPROTO_RFC5245, port1->IceProtocol());
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+ ASSERT_EQ(cricket::ICEPROTO_RFC5245, port2->IceProtocol());
+ // Same parameters as TestLocalToLocal above.
+ TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+}
+
+// This test is trying to validate a successful and failure scenario in a
+// loopback test when protocol is RFC5245. For success IceTiebreaker, username
+// should remain equal to the request generated by the port and role of port
+// must be in controlling.
+TEST_F(PortTest, TestLoopbackCallAsIce) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ lport->SetIceProtocolType(ICEPROTO_RFC5245);
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ lport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ Connection* conn = lport->CreateConnection(lport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ conn->Ping(0);
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ conn->OnReadPacket(lport->last_stun_buf()->Data(),
+ lport->last_stun_buf()->Length(),
+ rtc::PacketTime());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+
+ // If the tiebreaker value is different from port, we expect a error
+ // response.
+ lport->Reset();
+ lport->AddCandidateAddress(kLocalAddr2);
+ // Creating a different connection as |conn| is in STATE_READABLE.
+ Connection* conn1 = lport->CreateConnection(lport->Candidates()[1],
+ Port::ORIGIN_MESSAGE);
+ conn1->Ping(0);
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ rtc::scoped_ptr<IceMessage> modified_req(
+ CreateStunMessage(STUN_BINDING_REQUEST));
+ const StunByteStringAttribute* username_attr = msg->GetByteString(
+ STUN_ATTR_USERNAME);
+ modified_req->AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_USERNAME, username_attr->GetString()));
+ // To make sure we receive error response, adding tiebreaker less than
+ // what's present in request.
+ modified_req->AddAttribute(new StunUInt64Attribute(
+ STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1));
+ modified_req->AddMessageIntegrity("lpass");
+ modified_req->AddFingerprint();
+
+ lport->Reset();
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ WriteStunMessage(modified_req.get(), buf.get());
+ conn1->OnReadPacket(buf->Data(), buf->Length(), rtc::PacketTime());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+}
+
+// This test verifies role conflict signal is received when there is
+// conflict in the role. In this case both ports are in controlling and
+// |rport| has higher tiebreaker value than |lport|. Since |lport| has lower
+// value of tiebreaker, when it receives ping request from |rport| it will
+// send role conflict signal.
+TEST_F(PortTest, TestIceRoleConflict) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ lport->SetIceProtocolType(ICEPROTO_RFC5245);
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ rport->SetIceProtocolType(ICEPROTO_RFC5245);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn = lport->CreateConnection(rport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* rconn = rport->CreateConnection(lport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ rconn->Ping(0);
+
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ // Send rport binding request to lport.
+ lconn->OnReadPacket(rport->last_stun_buf()->Data(),
+ rport->last_stun_buf()->Length(),
+ rtc::PacketTime());
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+ EXPECT_TRUE(role_conflict());
+}
+
+TEST_F(PortTest, TestTcpNoDelay) {
+ TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+ int option_value = -1;
+ int success = port1->GetOption(rtc::Socket::OPT_NODELAY,
+ &option_value);
+ ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0
+ ASSERT_EQ(1, option_value);
+ delete port1;
+}
+
+TEST_F(PortTest, TestDelayedBindingUdp) {
+ FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_udp_socket(socket);
+ scoped_ptr<UDPPort> port(
+ CreateUdpPort(kLocalAddr1, &socket_factory));
+
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ port->PrepareAddress();
+
+ EXPECT_EQ(0U, port->Candidates().size());
+ socket->SignalAddressReady(socket, kLocalAddr2);
+
+ EXPECT_EQ(1U, port->Candidates().size());
+}
+
+TEST_F(PortTest, TestDelayedBindingTcp) {
+ FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_server_tcp_socket(socket);
+ scoped_ptr<TCPPort> port(
+ CreateTcpPort(kLocalAddr1, &socket_factory));
+
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ port->PrepareAddress();
+
+ EXPECT_EQ(0U, port->Candidates().size());
+ socket->SignalAddressReady(socket, kLocalAddr2);
+
+ EXPECT_EQ(1U, port->Candidates().size());
+}
+
+void PortTest::TestCrossFamilyPorts(int type) {
+ FakePacketSocketFactory factory;
+ scoped_ptr<Port> ports[4];
+ SocketAddress addresses[4] = {SocketAddress("192.168.1.3", 0),
+ SocketAddress("192.168.1.4", 0),
+ SocketAddress("2001:db8::1", 0),
+ SocketAddress("2001:db8::2", 0)};
+ for (int i = 0; i < 4; i++) {
+ FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+ if (type == SOCK_DGRAM) {
+ factory.set_next_udp_socket(socket);
+ ports[i].reset(CreateUdpPort(addresses[i], &factory));
+ } else if (type == SOCK_STREAM) {
+ factory.set_next_server_tcp_socket(socket);
+ ports[i].reset(CreateTcpPort(addresses[i], &factory));
+ }
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ socket->SignalAddressReady(socket, addresses[i]);
+ ports[i]->PrepareAddress();
+ }
+
+ // IPv4 Port, connects to IPv6 candidate and then to IPv4 candidate.
+ if (type == SOCK_STREAM) {
+ FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+ factory.set_next_client_tcp_socket(clientsocket);
+ }
+ Connection* c = ports[0]->CreateConnection(GetCandidate(ports[2].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE(NULL == c);
+ EXPECT_EQ(0U, ports[0]->connections().size());
+ c = ports[0]->CreateConnection(GetCandidate(ports[1].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_FALSE(NULL == c);
+ EXPECT_EQ(1U, ports[0]->connections().size());
+
+ // IPv6 Port, connects to IPv4 candidate and to IPv6 candidate.
+ if (type == SOCK_STREAM) {
+ FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+ factory.set_next_client_tcp_socket(clientsocket);
+ }
+ c = ports[2]->CreateConnection(GetCandidate(ports[0].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE(NULL == c);
+ EXPECT_EQ(0U, ports[2]->connections().size());
+ c = ports[2]->CreateConnection(GetCandidate(ports[3].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_FALSE(NULL == c);
+ EXPECT_EQ(1U, ports[2]->connections().size());
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyTcp) {
+ TestCrossFamilyPorts(SOCK_STREAM);
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyUdp) {
+ TestCrossFamilyPorts(SOCK_DGRAM);
+}
+
+// This test verifies DSCP value set through SetOption interface can be
+// get through DefaultDscpValue.
+TEST_F(PortTest, TestDefaultDscpValue) {
+ int dscp;
+ rtc::scoped_ptr<UDPPort> udpport(CreateUdpPort(kLocalAddr1));
+ EXPECT_EQ(0, udpport->SetOption(rtc::Socket::OPT_DSCP,
+ rtc::DSCP_CS6));
+ EXPECT_EQ(0, udpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ rtc::scoped_ptr<TCPPort> tcpport(CreateTcpPort(kLocalAddr1));
+ EXPECT_EQ(0, tcpport->SetOption(rtc::Socket::OPT_DSCP,
+ rtc::DSCP_AF31));
+ EXPECT_EQ(0, tcpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_AF31, dscp);
+ rtc::scoped_ptr<StunPort> stunport(
+ CreateStunPort(kLocalAddr1, nat_socket_factory1()));
+ EXPECT_EQ(0, stunport->SetOption(rtc::Socket::OPT_DSCP,
+ rtc::DSCP_AF41));
+ EXPECT_EQ(0, stunport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_AF41, dscp);
+ rtc::scoped_ptr<TurnPort> turnport1(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+ // Socket is created in PrepareAddress.
+ turnport1->PrepareAddress();
+ EXPECT_EQ(0, turnport1->SetOption(rtc::Socket::OPT_DSCP,
+ rtc::DSCP_CS7));
+ EXPECT_EQ(0, turnport1->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_CS7, dscp);
+ // This will verify correct value returned without the socket.
+ rtc::scoped_ptr<TurnPort> turnport2(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+ EXPECT_EQ(0, turnport2->SetOption(rtc::Socket::OPT_DSCP,
+ rtc::DSCP_CS6));
+ EXPECT_EQ(0, turnport2->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_CS6, dscp);
+}
+
+// Test sending STUN messages in GICE format.
+TEST_F(PortTest, TestSendStunMessageAsGice) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ lport->SetIceProtocolType(ICEPROTO_GOOGLE);
+ rport->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* conn = lport->CreateConnection(rport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ conn->Ping(0);
+
+ // Check that it's a proper BINDING-REQUEST.
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunByteStringAttribute* username_attr = msg->GetByteString(
+ STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username_attr != NULL);
+ EXPECT_EQ("rfraglfrag", username_attr->GetString());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+
+ // Save a copy of the BINDING-REQUEST for use below.
+ rtc::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+
+ // Respond with a BINDING-RESPONSE.
+ rport->SendBindingResponse(request.get(), lport->Candidates()[0].address());
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ username_attr = msg->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response.
+ EXPECT_EQ("rfraglfrag", username_attr->GetString());
+ const StunAddressAttribute* addr_attr = msg->GetAddress(
+ STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(addr_attr != NULL);
+ EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_XOR_MAPPED_ADDRESS) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+
+ // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life,
+ // but we can do it here.
+ rport->SendBindingErrorResponse(request.get(),
+ rport->Candidates()[0].address(),
+ STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ username_attr = msg->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response.
+ EXPECT_EQ("rfraglfrag", username_attr->GetString());
+ const StunErrorCodeAttribute* error_attr = msg->GetErrorCode();
+ ASSERT_TRUE(error_attr != NULL);
+ // The GICE wire format for error codes is incorrect.
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, error_attr->code());
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR / 256, error_attr->eclass());
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR % 256, error_attr->number());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+}
+
+// Test sending STUN messages in ICE format.
+TEST_F(PortTest, TestSendStunMessageAsIce) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ lport->SetIceProtocolType(ICEPROTO_RFC5245);
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceProtocolType(ICEPROTO_RFC5245);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn = lport->CreateConnection(
+ rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn = rport->CreateConnection(
+ lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ lconn->Ping(0);
+
+ // Check that it's a proper BINDING-REQUEST.
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username_attr != NULL);
+ const StunUInt32Attribute* priority_attr = msg->GetUInt32(STUN_ATTR_PRIORITY);
+ ASSERT_TRUE(priority_attr != NULL);
+ EXPECT_EQ(kDefaultPrflxPriority, priority_attr->value());
+ EXPECT_EQ("rfrag:lfrag", username_attr->GetString());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length(),
+ "rpass"));
+ const StunUInt64Attribute* ice_controlling_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+ ASSERT_TRUE(ice_controlling_attr != NULL);
+ EXPECT_EQ(lport->IceTiebreaker(), ice_controlling_attr->value());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+
+ // Request should not include ping count.
+ ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+ // Save a copy of the BINDING-REQUEST for use below.
+ rtc::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+
+ // Respond with a BINDING-RESPONSE.
+ rport->SendBindingResponse(request.get(), lport->Candidates()[0].address());
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+
+
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunAddressAttribute* addr_attr = msg->GetAddress(
+ STUN_ATTR_XOR_MAPPED_ADDRESS);
+ ASSERT_TRUE(addr_attr != NULL);
+ EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(),
+ "rpass"));
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+ // No USERNAME or PRIORITY in ICE responses.
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MAPPED_ADDRESS) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLING) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Response should not include ping count.
+ ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+ // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life,
+ // but we can do it here.
+ rport->SendBindingErrorResponse(request.get(),
+ lport->Candidates()[0].address(),
+ STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunErrorCodeAttribute* error_attr = msg->GetErrorCode();
+ ASSERT_TRUE(error_attr != NULL);
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR, error_attr->code());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(),
+ "rpass"));
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+ // No USERNAME with ICE.
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+
+ // Testing STUN binding requests from rport --> lport, having ICE_CONTROLLED
+ // and (incremented) RETRANSMIT_COUNT attributes.
+ rport->Reset();
+ rport->set_send_retransmit_count_attribute(true);
+ rconn->Ping(0);
+ rconn->Ping(0);
+ rconn->Ping(0);
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+ msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ const StunUInt64Attribute* ice_controlled_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
+ ASSERT_TRUE(ice_controlled_attr != NULL);
+ EXPECT_EQ(rport->IceTiebreaker(), ice_controlled_attr->value());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Request should include ping count.
+ const StunUInt32Attribute* retransmit_attr =
+ msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ ASSERT_TRUE(retransmit_attr != NULL);
+ EXPECT_EQ(2U, retransmit_attr->value());
+
+ // Respond with a BINDING-RESPONSE.
+ request.reset(CopyStunMessage(msg));
+ lport->SendBindingResponse(request.get(), rport->Candidates()[0].address());
+ msg = lport->last_stun_msg();
+
+ // Response should include same ping count.
+ retransmit_attr = msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ ASSERT_TRUE(retransmit_attr != NULL);
+ EXPECT_EQ(2U, retransmit_attr->value());
+}
+
+TEST_F(PortTest, TestUseCandidateAttribute) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ lport->SetIceProtocolType(ICEPROTO_RFC5245);
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceProtocolType(ICEPROTO_RFC5245);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn = lport->CreateConnection(
+ rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ lconn->Ping(0);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = lport->last_stun_msg();
+ const StunUInt64Attribute* ice_controlling_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+ ASSERT_TRUE(ice_controlling_attr != NULL);
+ const StunByteStringAttribute* use_candidate_attr = msg->GetByteString(
+ STUN_ATTR_USE_CANDIDATE);
+ ASSERT_TRUE(use_candidate_attr != NULL);
+}
+
+// Test handling STUN messages in GICE format.
+TEST_F(PortTest, TestHandleStunMessageAsGice) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid GICE username and no M-I.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfraglfrag"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL); // Succeeds, since this is GICE.
+ EXPECT_EQ("lfrag", username);
+
+ // Add M-I; should be ignored and rest of message parsed normally.
+ in_msg->AddMessageIntegrity("password");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("lfrag", username);
+
+ // BINDING-RESPONSE with username, as done in GICE. Should succeed.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_RESPONSE,
+ "rfraglfrag"));
+ in_msg->AddAttribute(
+ new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+
+ // BINDING-RESPONSE without username. Should be tolerated as well.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+ in_msg->AddAttribute(
+ new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+
+ // BINDING-ERROR-RESPONSE with username and error code.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_ERROR_RESPONSE,
+ "rfraglfrag"));
+ in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+ STUN_ERROR_SERVER_ERROR_AS_GICE, STUN_ERROR_REASON_SERVER_ERROR));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ ASSERT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+ ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
+ // GetStunMessage doesn't unmunge the GICE error code (happens downstream).
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, out_msg->GetErrorCode()->code());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR),
+ out_msg->GetErrorCode()->reason());
+}
+
+// Test handling STUN messages in ICE format.
+TEST_F(PortTest, TestHandleStunMessageAsIce) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username,
+ // MESSAGE-INTEGRITY, and FINGERPRINT.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfrag:lfrag"));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("lfrag", username);
+
+ // BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+ in_msg->AddAttribute(
+ new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+
+ // BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE));
+ in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+ STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR));
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+ ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR, out_msg->GetErrorCode()->code());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR),
+ out_msg->GetErrorCode()->reason());
+}
+
+// This test verifies port can handle ICE messages in Hybrid mode and switches
+// ICEPROTO_RFC5245 mode after successfully handling the message.
+TEST_F(PortTest, TestHandleStunMessageAsIceInHybridMode) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_HYBRID);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username,
+ // MESSAGE-INTEGRITY, and FINGERPRINT.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfrag:lfrag"));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("lfrag", username);
+ EXPECT_EQ(ICEPROTO_RFC5245, port->IceProtocol());
+}
+
+// This test verifies port can handle GICE messages in Hybrid mode and switches
+// ICEPROTO_GOOGLE mode after successfully handling the message.
+TEST_F(PortTest, TestHandleStunMessageAsGiceInHybridMode) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_HYBRID);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid GICE username and no M-I.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfraglfrag"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL); // Succeeds, since this is GICE.
+ EXPECT_EQ("lfrag", username);
+ EXPECT_EQ(ICEPROTO_GOOGLE, port->IceProtocol());
+}
+
+// Verify port is not switched out of RFC5245 mode if GICE message is received
+// in that mode.
+TEST_F(PortTest, TestHandleStunMessageAsGiceInIceMode) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid GICE username and no M-I.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfraglfrag"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ // Should fail as there is no MI and fingerprint.
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(ICEPROTO_RFC5245, port->IceProtocol());
+}
+
+
+// Tests handling of GICE binding requests with missing or incorrect usernames.
+TEST_F(PortTest, TestHandleStunMessageAsGiceBadUsername) {
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST with no username.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_BAD_REQUEST_AS_GICE, port->last_stun_error_code());
+
+ // BINDING-REQUEST with empty username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, ""));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+ // BINDING-REQUEST with too-short username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfra"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+ // BINDING-REQUEST with reversed username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "lfragrfrag"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+ // BINDING-REQUEST with garbage username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "abcdefgh"));
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+}
+
+// Tests handling of ICE binding requests with missing or incorrect usernames.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadUsername) {
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST with no username.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+ // BINDING-REQUEST with empty username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, ""));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with too-short username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra"));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with reversed username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "lfrag:rfrag"));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with garbage username.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "abcd:efgh"));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+}
+
+// Test handling STUN messages (as ICE) with missing or malformed M-I.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadMessageIntegrity) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // FINGERPRINT, but no MESSAGE-INTEGRITY.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfrag:lfrag"));
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // FINGERPRINT, but invalid MESSAGE-INTEGRITY.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfrag:lfrag"));
+ in_msg->AddMessageIntegrity("invalid");
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // TODO: BINDING-RESPONSES and BINDING-ERROR-RESPONSES are checked
+ // by the Connection, not the Port, since they require the remote username.
+ // Change this test to pass in data via Connection::OnReadPacket instead.
+}
+
+// Test handling STUN messages (as ICE) with missing or malformed FINGERPRINT.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadFingerprint) {
+ // Our port will act as the "remote" port.
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // MESSAGE-INTEGRITY, but no FINGERPRINT; GetStunMessage should fail.
+ in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+ "rfrag:lfrag"));
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionID("TESTTESTBADD");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Valid BINDING-RESPONSE, except no FINGERPRINT.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+ in_msg->AddAttribute(
+ new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionID("TESTTESTBADD");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT.
+ in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE));
+ in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+ STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR));
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionID("TESTTESTBADD");
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+}
+
+// Test handling of STUN binding indication messages (as ICE). STUN binding
+// indications are allowed only to the connection which is in read mode.
+TEST_F(PortTest, TestHandleStunBindingIndication) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr2, "lfrag", "lpass"));
+ lport->SetIceProtocolType(ICEPROTO_RFC5245);
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+
+ // Verifying encoding and decoding STUN indication message.
+ rtc::scoped_ptr<IceMessage> in_msg, out_msg;
+ rtc::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ in_msg.reset(CreateStunMessage(STUN_BINDING_INDICATION));
+ in_msg->AddFingerprint();
+ WriteStunMessage(in_msg.get(), buf.get());
+ EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr,
+ out_msg.accept(), &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION);
+ EXPECT_EQ("", username);
+
+ // Verify connection can handle STUN indication and updates
+ // last_ping_received.
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ rport->SetIceProtocolType(ICEPROTO_RFC5245);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+
+ Connection* lconn = lport->CreateConnection(rport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* rconn = rport->CreateConnection(lport->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ rconn->Ping(0);
+
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ // Send rport binding request to lport.
+ lconn->OnReadPacket(rport->last_stun_buf()->Data(),
+ rport->last_stun_buf()->Length(),
+ rtc::PacketTime());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+ uint32 last_ping_received1 = lconn->last_ping_received();
+
+ // Adding a delay of 100ms.
+ rtc::Thread::Current()->ProcessMessages(100);
+ // Pinging lconn using stun indication message.
+ lconn->OnReadPacket(buf->Data(), buf->Length(), rtc::PacketTime());
+ uint32 last_ping_received2 = lconn->last_ping_received();
+ EXPECT_GT(last_ping_received2, last_ping_received1);
+}
+
+TEST_F(PortTest, TestComputeCandidatePriority) {
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr1, "name", "pass"));
+ port->set_type_preference(90);
+ port->set_component(177);
+ port->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("2001:db8::1234", 1234));
+ port->AddCandidateAddress(SocketAddress("fc12:3456::1234", 1234));
+ port->AddCandidateAddress(SocketAddress("::ffff:192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("::192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("2002::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("2001::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("fecf::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("3ffe::1234:5678", 1234));
+ // These should all be:
+ // (90 << 24) | ([rfc3484 pref value] << 8) | (256 - 177)
+ uint32 expected_priority_v4 = 1509957199U;
+ uint32 expected_priority_v6 = 1509959759U;
+ uint32 expected_priority_ula = 1509962319U;
+ uint32 expected_priority_v4mapped = expected_priority_v4;
+ uint32 expected_priority_v4compat = 1509949775U;
+ uint32 expected_priority_6to4 = 1509954639U;
+ uint32 expected_priority_teredo = 1509952079U;
+ uint32 expected_priority_sitelocal = 1509949775U;
+ uint32 expected_priority_6bone = 1509949775U;
+ ASSERT_EQ(expected_priority_v4, port->Candidates()[0].priority());
+ ASSERT_EQ(expected_priority_v6, port->Candidates()[1].priority());
+ ASSERT_EQ(expected_priority_ula, port->Candidates()[2].priority());
+ ASSERT_EQ(expected_priority_v4mapped, port->Candidates()[3].priority());
+ ASSERT_EQ(expected_priority_v4compat, port->Candidates()[4].priority());
+ ASSERT_EQ(expected_priority_6to4, port->Candidates()[5].priority());
+ ASSERT_EQ(expected_priority_teredo, port->Candidates()[6].priority());
+ ASSERT_EQ(expected_priority_sitelocal, port->Candidates()[7].priority());
+ ASSERT_EQ(expected_priority_6bone, port->Candidates()[8].priority());
+}
+
+TEST_F(PortTest, TestPortProxyProperties) {
+ rtc::scoped_ptr<TestPort> port(
+ CreateTestPort(kLocalAddr1, "name", "pass"));
+ port->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port->SetIceTiebreaker(kTiebreaker1);
+
+ // Create a proxy port.
+ rtc::scoped_ptr<PortProxy> proxy(new PortProxy());
+ proxy->set_impl(port.get());
+ EXPECT_EQ(port->Type(), proxy->Type());
+ EXPECT_EQ(port->Network(), proxy->Network());
+ EXPECT_EQ(port->GetIceRole(), proxy->GetIceRole());
+ EXPECT_EQ(port->IceTiebreaker(), proxy->IceTiebreaker());
+}
+
+// In the case of shared socket, one port may be shared by local and stun.
+// Test that candidates with different types will have different foundation.
+TEST_F(PortTest, TestFoundation) {
+ rtc::scoped_ptr<TestPort> testport(
+ CreateTestPort(kLocalAddr1, "name", "pass"));
+ testport->AddCandidateAddress(kLocalAddr1, kLocalAddr1,
+ LOCAL_PORT_TYPE,
+ cricket::ICE_TYPE_PREFERENCE_HOST, false);
+ testport->AddCandidateAddress(kLocalAddr2, kLocalAddr1,
+ STUN_PORT_TYPE,
+ cricket::ICE_TYPE_PREFERENCE_SRFLX, true);
+ EXPECT_NE(testport->Candidates()[0].foundation(),
+ testport->Candidates()[1].foundation());
+}
+
+// This test verifies the foundation of different types of ICE candidates.
+TEST_F(PortTest, TestCandidateFoundation) {
+ rtc::scoped_ptr<rtc::NATServer> nat_server(
+ CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
+ rtc::scoped_ptr<UDPPort> udpport1(CreateUdpPort(kLocalAddr1));
+ udpport1->PrepareAddress();
+ rtc::scoped_ptr<UDPPort> udpport2(CreateUdpPort(kLocalAddr1));
+ udpport2->PrepareAddress();
+ EXPECT_EQ(udpport1->Candidates()[0].foundation(),
+ udpport2->Candidates()[0].foundation());
+ rtc::scoped_ptr<TCPPort> tcpport1(CreateTcpPort(kLocalAddr1));
+ tcpport1->PrepareAddress();
+ rtc::scoped_ptr<TCPPort> tcpport2(CreateTcpPort(kLocalAddr1));
+ tcpport2->PrepareAddress();
+ EXPECT_EQ(tcpport1->Candidates()[0].foundation(),
+ tcpport2->Candidates()[0].foundation());
+ rtc::scoped_ptr<Port> stunport(
+ CreateStunPort(kLocalAddr1, nat_socket_factory1()));
+ stunport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout);
+ EXPECT_NE(tcpport1->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(tcpport2->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(udpport1->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(udpport2->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ // Verify GTURN candidate foundation.
+ rtc::scoped_ptr<RelayPort> relayport(
+ CreateGturnPort(kLocalAddr1));
+ relayport->AddServerAddress(
+ cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
+ relayport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout);
+ EXPECT_NE(udpport1->Candidates()[0].foundation(),
+ relayport->Candidates()[0].foundation());
+ EXPECT_NE(udpport2->Candidates()[0].foundation(),
+ relayport->Candidates()[0].foundation());
+ // Verifying TURN candidate foundation.
+ rtc::scoped_ptr<Port> turnport1(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+ turnport1->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport1->Candidates().size(), kTimeout);
+ EXPECT_NE(udpport1->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ EXPECT_NE(udpport2->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ EXPECT_NE(stunport->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ rtc::scoped_ptr<Port> turnport2(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+ turnport2->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport2->Candidates().size(), kTimeout);
+ EXPECT_EQ(turnport1->Candidates()[0].foundation(),
+ turnport2->Candidates()[0].foundation());
+
+ // Running a second turn server, to get different base IP address.
+ SocketAddress kTurnUdpIntAddr2("99.99.98.4", STUN_SERVER_PORT);
+ SocketAddress kTurnUdpExtAddr2("99.99.98.5", 0);
+ TestTurnServer turn_server2(
+ rtc::Thread::Current(), kTurnUdpIntAddr2, kTurnUdpExtAddr2);
+ rtc::scoped_ptr<Port> turnport3(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP,
+ kTurnUdpIntAddr2));
+ turnport3->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport3->Candidates().size(), kTimeout);
+ EXPECT_NE(turnport3->Candidates()[0].foundation(),
+ turnport2->Candidates()[0].foundation());
+}
+
+// This test verifies the related addresses of different types of
+// ICE candiates.
+TEST_F(PortTest, TestCandidateRelatedAddress) {
+ rtc::scoped_ptr<rtc::NATServer> nat_server(
+ CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
+ rtc::scoped_ptr<UDPPort> udpport(CreateUdpPort(kLocalAddr1));
+ udpport->PrepareAddress();
+ // For UDPPort, related address will be empty.
+ EXPECT_TRUE(udpport->Candidates()[0].related_address().IsNil());
+ // Testing related address for stun candidates.
+ // For stun candidate related address must be equal to the base
+ // socket address.
+ rtc::scoped_ptr<StunPort> stunport(
+ CreateStunPort(kLocalAddr1, nat_socket_factory1()));
+ stunport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout);
+ // Check STUN candidate address.
+ EXPECT_EQ(stunport->Candidates()[0].address().ipaddr(),
+ kNatAddr1.ipaddr());
+ // Check STUN candidate related address.
+ EXPECT_EQ(stunport->Candidates()[0].related_address(),
+ stunport->GetLocalAddress());
+ // Verifying the related address for the GTURN candidates.
+ // NOTE: In case of GTURN related address will be equal to the mapped
+ // address, but address(mapped) will not be XOR.
+ rtc::scoped_ptr<RelayPort> relayport(
+ CreateGturnPort(kLocalAddr1));
+ relayport->AddServerAddress(
+ cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
+ relayport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout);
+ // For Gturn related address is set to "0.0.0.0:0"
+ EXPECT_EQ(rtc::SocketAddress(),
+ relayport->Candidates()[0].related_address());
+ // Verifying the related address for TURN candidate.
+ // For TURN related address must be equal to the mapped address.
+ rtc::scoped_ptr<Port> turnport(CreateTurnPort(
+ kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+ turnport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout);
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turnport->Candidates()[0].address().ipaddr());
+ EXPECT_EQ(kNatAddr1.ipaddr(),
+ turnport->Candidates()[0].related_address().ipaddr());
+}
+
+// Test priority value overflow handling when preference is set to 3.
+TEST_F(PortTest, TestCandidatePreference) {
+ cricket::Candidate cand1;
+ cand1.set_preference(3);
+ cricket::Candidate cand2;
+ cand2.set_preference(1);
+ EXPECT_TRUE(cand1.preference() > cand2.preference());
+}
+
+// Test the Connection priority is calculated correctly.
+TEST_F(PortTest, TestConnectionPriority) {
+ rtc::scoped_ptr<TestPort> lport(
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+ lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST);
+ rtc::scoped_ptr<TestPort> rport(
+ CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+ rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY);
+ lport->set_component(123);
+ lport->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+ rport->set_component(23);
+ rport->AddCandidateAddress(SocketAddress("10.1.1.100", 1234));
+
+ EXPECT_EQ(0x7E001E85U, lport->Candidates()[0].priority());
+ EXPECT_EQ(0x2001EE9U, rport->Candidates()[0].priority());
+
+ // RFC 5245
+ // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ Connection* lconn = lport->CreateConnection(
+ rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WEBRTC_WIN)
+ EXPECT_EQ(0x2001EE9FC003D0BU, lconn->priority());
+#else
+ EXPECT_EQ(0x2001EE9FC003D0BLLU, lconn->priority());
+#endif
+
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ Connection* rconn = rport->CreateConnection(
+ lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WEBRTC_WIN)
+ EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority());
+#else
+ EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority());
+#endif
+}
+
+TEST_F(PortTest, TestWritableState) {
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+
+ // Set up channels.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout);
+
+ // Send a ping from src to dst.
+ ch1.CreateConnection();
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect
+ ch1.Ping();
+ WAIT(!ch2.remote_address().IsNil(), kTimeout);
+
+ // Data should be unsendable until the connection is accepted.
+ char data[] = "abcd";
+ int data_size = ARRAY_SIZE(data);
+ rtc::PacketOptions options;
+ EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size, options));
+
+ // Accept the connection to return the binding response, transition to
+ // writable, and allow data to be sent.
+ ch2.AcceptConnection();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ // Ask the connection to update state as if enough time has passed to lose
+ // full writability and 5 pings went unresponded to. We'll accomplish the
+ // latter by sending pings but not pumping messages.
+ for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(i);
+ }
+ uint32 unreliable_timeout_delay = CONNECTION_WRITE_CONNECT_TIMEOUT + 500u;
+ ch1.conn()->UpdateState(unreliable_timeout_delay);
+ EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state());
+
+ // Data should be able to be sent in this state.
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ // And now allow the other side to process the pings and send binding
+ // responses.
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+
+ // Wait long enough for a full timeout (past however long we've already
+ // waited).
+ for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(unreliable_timeout_delay + i);
+ }
+ ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT +
+ 500u);
+ EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+
+ // Now that the connection has completely timed out, data send should fail.
+ EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size, options));
+
+ ch1.Stop();
+ ch2.Stop();
+}
+
+TEST_F(PortTest, TestTimeoutForNeverWritable) {
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+
+ // Set up channels.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+
+ ch1.CreateConnection();
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+ // Attempt to go directly to write timeout.
+ for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(i);
+ }
+ ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + 500u);
+ EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+}
+
+// This test verifies the connection setup between ICEMODE_FULL
+// and ICEMODE_LITE.
+// In this test |ch1| behaves like FULL mode client and we have created
+// port which responds to the ping message just like LITE client.
+TEST_F(PortTest, TestIceLiteConnectivity) {
+ TestPort* ice_full_port = CreateTestPort(
+ kLocalAddr1, "lfrag", "lpass", cricket::ICEPROTO_RFC5245,
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+
+ rtc::scoped_ptr<TestPort> ice_lite_port(CreateTestPort(
+ kLocalAddr2, "rfrag", "rpass", cricket::ICEPROTO_RFC5245,
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2));
+ // Setup TestChannel. This behaves like FULL mode client.
+ TestChannel ch1(ice_full_port, ice_lite_port.get());
+ ch1.SetIceMode(ICEMODE_FULL);
+
+ // Start gathering candidates.
+ ch1.Start();
+ ice_lite_port->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+ ASSERT_FALSE(ice_lite_port->Candidates().empty());
+
+ ch1.CreateConnection();
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+ // Send ping from full mode client.
+ // This ping must not have USE_CANDIDATE_ATTR.
+ ch1.Ping();
+
+ // Verify stun ping is without USE_CANDIDATE_ATTR. Getting message directly
+ // from port.
+ ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000);
+ IceMessage* msg = ice_full_port->last_stun_msg();
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Respond with a BINDING-RESPONSE from litemode client.
+ // NOTE: Ideally we should't create connection at this stage from lite
+ // port, as it should be done only after receiving ping with USE_CANDIDATE.
+ // But we need a connection to send a response message.
+ ice_lite_port->CreateConnection(
+ ice_full_port->Candidates()[0], cricket::Port::ORIGIN_MESSAGE);
+ rtc::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+ ice_lite_port->SendBindingResponse(
+ request.get(), ice_full_port->Candidates()[0].address());
+
+ // Feeding the respone message from litemode to the full mode connection.
+ ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->Data(),
+ ice_lite_port->last_stun_buf()->Length(),
+ rtc::PacketTime());
+ // Verifying full mode connection becomes writable from the response.
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ EXPECT_TRUE_WAIT(ch1.nominated(), kTimeout);
+
+ // Clear existing stun messsages. Otherwise we will process old stun
+ // message right after we send ping.
+ ice_full_port->Reset();
+ // Send ping. This must have USE_CANDIDATE_ATTR.
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000);
+ msg = ice_full_port->last_stun_msg();
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+ ch1.Stop();
+}
+
+// This test case verifies that the CONTROLLING port does not time out.
+TEST_F(PortTest, TestControllingNoTimeout) {
+ SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ ConnectToSignalDestroyed(port1);
+ port1->set_timeout_delay(10); // milliseconds
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+
+ // Simulate a connection that succeeds, and then is destroyed.
+ ConnectAndDisconnectChannels(&ch1, &ch2);
+
+ // After the connection is destroyed, the port should not be destroyed.
+ rtc::Thread::Current()->ProcessMessages(kTimeout);
+ EXPECT_FALSE(destroyed());
+}
+
+// This test case verifies that the CONTROLLED port does time out, but only
+// after connectivity is lost.
+TEST_F(PortTest, TestControlledTimeout) {
+ SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+ ConnectToSignalDestroyed(port2);
+ port2->set_timeout_delay(10); // milliseconds
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+
+ // The connection must not be destroyed before a connection is attempted.
+ EXPECT_FALSE(destroyed());
+
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+
+ // Simulate a connection that succeeds, and then is destroyed.
+ ConnectAndDisconnectChannels(&ch1, &ch2);
+
+ // The controlled port should be destroyed after 10 milliseconds.
+ EXPECT_TRUE_WAIT(destroyed(), kTimeout);
+}
diff --git a/p2p/base/portallocator.cc b/p2p/base/portallocator.cc
new file mode 100644
index 00000000..86133474
--- /dev/null
+++ b/p2p/base/portallocator.cc
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/portallocator.h"
+
+#include "webrtc/p2p/base/portallocatorsessionproxy.h"
+
+namespace cricket {
+
+PortAllocatorSession::PortAllocatorSession(const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd,
+ uint32 flags)
+ : content_name_(content_name),
+ component_(component),
+ flags_(flags),
+ generation_(0),
+ // If PORTALLOCATOR_ENABLE_SHARED_UFRAG flag is not enabled, ignore the
+ // incoming ufrag and pwd, which will cause each Port to generate one
+ // by itself.
+ username_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_ufrag : ""),
+ password_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_pwd : "") {
+}
+
+PortAllocator::~PortAllocator() {
+ for (SessionMuxerMap::iterator iter = muxers_.begin();
+ iter != muxers_.end(); ++iter) {
+ delete iter->second;
+ }
+}
+
+PortAllocatorSession* PortAllocator::CreateSession(
+ const std::string& sid,
+ const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) {
+ // If we just use |sid| as key in identifying PortAllocatorSessionMuxer,
+ // ICE restart will not result in different candidates, as |sid| will
+ // be same. To yield different candiates we are using combination of
+ // |ice_ufrag| and |ice_pwd|.
+ // Ideally |ice_ufrag| and |ice_pwd| should change together, but
+ // there can be instances where only ice_pwd will be changed.
+ std::string key_str = ice_ufrag + ":" + ice_pwd;
+ PortAllocatorSessionMuxer* muxer = GetSessionMuxer(key_str);
+ if (!muxer) {
+ PortAllocatorSession* session_impl = CreateSessionInternal(
+ content_name, component, ice_ufrag, ice_pwd);
+ // Create PortAllocatorSessionMuxer object for |session_impl|.
+ muxer = new PortAllocatorSessionMuxer(session_impl);
+ muxer->SignalDestroyed.connect(
+ this, &PortAllocator::OnSessionMuxerDestroyed);
+ // Add PortAllocatorSession to the map.
+ muxers_[key_str] = muxer;
+ }
+ PortAllocatorSessionProxy* proxy =
+ new PortAllocatorSessionProxy(content_name, component, flags_);
+ muxer->RegisterSessionProxy(proxy);
+ return proxy;
+ }
+ return CreateSessionInternal(content_name, component, ice_ufrag, ice_pwd);
+}
+
+PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer(
+ const std::string& key) const {
+ SessionMuxerMap::const_iterator iter = muxers_.find(key);
+ if (iter != muxers_.end())
+ return iter->second;
+ return NULL;
+}
+
+void PortAllocator::OnSessionMuxerDestroyed(
+ PortAllocatorSessionMuxer* session) {
+ SessionMuxerMap::iterator iter;
+ for (iter = muxers_.begin(); iter != muxers_.end(); ++iter) {
+ if (iter->second == session)
+ break;
+ }
+ if (iter != muxers_.end())
+ muxers_.erase(iter);
+}
+
+} // namespace cricket
diff --git a/p2p/base/portallocator.h b/p2p/base/portallocator.h
new file mode 100644
index 00000000..65aab44c
--- /dev/null
+++ b/p2p/base/portallocator.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PORTALLOCATOR_H_
+#define WEBRTC_P2P_BASE_PORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/proxyinfo.h"
+#include "webrtc/base/sigslot.h"
+
+namespace cricket {
+
+// PortAllocator is responsible for allocating Port types for a given
+// P2PSocket. It also handles port freeing.
+//
+// Clients can override this class to control port allocation, including
+// what kinds of ports are allocated.
+
+enum {
+ PORTALLOCATOR_DISABLE_UDP = 0x01,
+ PORTALLOCATOR_DISABLE_STUN = 0x02,
+ PORTALLOCATOR_DISABLE_RELAY = 0x04,
+ PORTALLOCATOR_DISABLE_TCP = 0x08,
+ PORTALLOCATOR_ENABLE_SHAKER = 0x10,
+ PORTALLOCATOR_ENABLE_BUNDLE = 0x20,
+ PORTALLOCATOR_ENABLE_IPV6 = 0x40,
+ PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80,
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100,
+ PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200,
+};
+
+const uint32 kDefaultPortAllocatorFlags = 0;
+
+const uint32 kDefaultStepDelay = 1000; // 1 sec step delay.
+// As per RFC 5245 Appendix B.1, STUN transactions need to be paced at certain
+// internal. Less than 20ms is not acceptable. We choose 50ms as our default.
+const uint32 kMinimumStepDelay = 50;
+
+// CF = CANDIDATE FILTER
+enum {
+ CF_NONE = 0x0,
+ CF_HOST = 0x1,
+ CF_REFLEXIVE = 0x2,
+ CF_RELAY = 0x4,
+ CF_ALL = 0x7,
+};
+
+class PortAllocatorSessionMuxer;
+
+class PortAllocatorSession : public sigslot::has_slots<> {
+ public:
+ // Content name passed in mostly for logging and debugging.
+ // TODO(mallinath) - Change username and password to ice_ufrag and ice_pwd.
+ PortAllocatorSession(const std::string& content_name,
+ int component,
+ const std::string& username,
+ const std::string& password,
+ uint32 flags);
+
+ // Subclasses should clean up any ports created.
+ virtual ~PortAllocatorSession() {}
+
+ uint32 flags() const { return flags_; }
+ void set_flags(uint32 flags) { flags_ = flags; }
+ std::string content_name() const { return content_name_; }
+ int component() const { return component_; }
+
+ // Starts gathering STUN and Relay configurations.
+ virtual void StartGettingPorts() = 0;
+ virtual void StopGettingPorts() = 0;
+ virtual bool IsGettingPorts() = 0;
+
+ sigslot::signal2<PortAllocatorSession*, PortInterface*> SignalPortReady;
+ sigslot::signal2<PortAllocatorSession*,
+ const std::vector<Candidate>&> SignalCandidatesReady;
+ sigslot::signal1<PortAllocatorSession*> SignalCandidatesAllocationDone;
+
+ virtual uint32 generation() { return generation_; }
+ virtual void set_generation(uint32 generation) { generation_ = generation; }
+ sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
+
+ protected:
+ const std::string& username() const { return username_; }
+ const std::string& password() const { return password_; }
+
+ std::string content_name_;
+ int component_;
+
+ private:
+ uint32 flags_;
+ uint32 generation_;
+ std::string username_;
+ std::string password_;
+};
+
+class PortAllocator : public sigslot::has_slots<> {
+ public:
+ PortAllocator() :
+ flags_(kDefaultPortAllocatorFlags),
+ min_port_(0),
+ max_port_(0),
+ step_delay_(kDefaultStepDelay),
+ allow_tcp_listen_(true),
+ candidate_filter_(CF_ALL) {
+ // This will allow us to have old behavior on non webrtc clients.
+ }
+ virtual ~PortAllocator();
+
+ PortAllocatorSession* CreateSession(
+ const std::string& sid,
+ const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd);
+
+ PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& key) const;
+ void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session);
+
+ uint32 flags() const { return flags_; }
+ void set_flags(uint32 flags) { flags_ = flags; }
+
+ const std::string& user_agent() const { return agent_; }
+ const rtc::ProxyInfo& proxy() const { return proxy_; }
+ void set_proxy(const std::string& agent, const rtc::ProxyInfo& proxy) {
+ agent_ = agent;
+ proxy_ = proxy;
+ }
+
+ // Gets/Sets the port range to use when choosing client ports.
+ int min_port() const { return min_port_; }
+ int max_port() const { return max_port_; }
+ bool SetPortRange(int min_port, int max_port) {
+ if (min_port > max_port) {
+ return false;
+ }
+
+ min_port_ = min_port;
+ max_port_ = max_port;
+ return true;
+ }
+
+ uint32 step_delay() const { return step_delay_; }
+ void set_step_delay(uint32 delay) {
+ step_delay_ = delay;
+ }
+
+ bool allow_tcp_listen() const { return allow_tcp_listen_; }
+ void set_allow_tcp_listen(bool allow_tcp_listen) {
+ allow_tcp_listen_ = allow_tcp_listen;
+ }
+
+ uint32 candidate_filter() { return candidate_filter_; }
+ bool set_candidate_filter(uint32 filter) {
+ // TODO(mallinath) - Do transition check?
+ candidate_filter_ = filter;
+ return true;
+ }
+
+ protected:
+ virtual PortAllocatorSession* CreateSessionInternal(
+ const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd) = 0;
+
+ typedef std::map<std::string, PortAllocatorSessionMuxer*> SessionMuxerMap;
+
+ uint32 flags_;
+ std::string agent_;
+ rtc::ProxyInfo proxy_;
+ int min_port_;
+ int max_port_;
+ uint32 step_delay_;
+ SessionMuxerMap muxers_;
+ bool allow_tcp_listen_;
+ uint32 candidate_filter_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PORTALLOCATOR_H_
diff --git a/p2p/base/portallocatorsessionproxy.cc b/p2p/base/portallocatorsessionproxy.cc
new file mode 100644
index 00000000..f5ce9a4a
--- /dev/null
+++ b/p2p/base/portallocatorsessionproxy.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/portallocatorsessionproxy.h"
+
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/portproxy.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+enum {
+ MSG_SEND_ALLOCATION_DONE = 1,
+ MSG_SEND_ALLOCATED_PORTS,
+};
+
+typedef rtc::TypedMessageData<PortAllocatorSessionProxy*> ProxyObjData;
+
+PortAllocatorSessionMuxer::PortAllocatorSessionMuxer(
+ PortAllocatorSession* session)
+ : worker_thread_(rtc::Thread::Current()),
+ session_(session),
+ candidate_done_signal_received_(false) {
+ session_->SignalPortReady.connect(
+ this, &PortAllocatorSessionMuxer::OnPortReady);
+ session_->SignalCandidatesAllocationDone.connect(
+ this, &PortAllocatorSessionMuxer::OnCandidatesAllocationDone);
+}
+
+PortAllocatorSessionMuxer::~PortAllocatorSessionMuxer() {
+ for (size_t i = 0; i < session_proxies_.size(); ++i)
+ delete session_proxies_[i];
+
+ SignalDestroyed(this);
+}
+
+void PortAllocatorSessionMuxer::RegisterSessionProxy(
+ PortAllocatorSessionProxy* session_proxy) {
+ session_proxies_.push_back(session_proxy);
+ session_proxy->SignalDestroyed.connect(
+ this, &PortAllocatorSessionMuxer::OnSessionProxyDestroyed);
+ session_proxy->set_impl(session_.get());
+
+ // Populate new proxy session with the information available in the actual
+ // implementation.
+ if (!ports_.empty()) {
+ worker_thread_->Post(
+ this, MSG_SEND_ALLOCATED_PORTS, new ProxyObjData(session_proxy));
+ }
+
+ if (candidate_done_signal_received_) {
+ worker_thread_->Post(
+ this, MSG_SEND_ALLOCATION_DONE, new ProxyObjData(session_proxy));
+ }
+}
+
+void PortAllocatorSessionMuxer::OnCandidatesAllocationDone(
+ PortAllocatorSession* session) {
+ candidate_done_signal_received_ = true;
+}
+
+void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session,
+ PortInterface* port) {
+ ASSERT(session == session_.get());
+ ports_.push_back(port);
+ port->SignalDestroyed.connect(
+ this, &PortAllocatorSessionMuxer::OnPortDestroyed);
+}
+
+void PortAllocatorSessionMuxer::OnPortDestroyed(PortInterface* port) {
+ std::vector<PortInterface*>::iterator it =
+ std::find(ports_.begin(), ports_.end(), port);
+ if (it != ports_.end())
+ ports_.erase(it);
+}
+
+void PortAllocatorSessionMuxer::OnSessionProxyDestroyed(
+ PortAllocatorSession* proxy) {
+
+ std::vector<PortAllocatorSessionProxy*>::iterator it =
+ std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+ if (it != session_proxies_.end()) {
+ session_proxies_.erase(it);
+ }
+
+ if (session_proxies_.empty()) {
+ // Destroy PortAllocatorSession and its associated muxer object if all
+ // proxies belonging to this session are already destroyed.
+ delete this;
+ }
+}
+
+void PortAllocatorSessionMuxer::OnMessage(rtc::Message *pmsg) {
+ ProxyObjData* proxy = static_cast<ProxyObjData*>(pmsg->pdata);
+ switch (pmsg->message_id) {
+ case MSG_SEND_ALLOCATION_DONE:
+ SendAllocationDone_w(proxy->data());
+ delete proxy;
+ break;
+ case MSG_SEND_ALLOCATED_PORTS:
+ SendAllocatedPorts_w(proxy->data());
+ delete proxy;
+ break;
+ default:
+ ASSERT(false);
+ break;
+ }
+}
+
+void PortAllocatorSessionMuxer::SendAllocationDone_w(
+ PortAllocatorSessionProxy* proxy) {
+ std::vector<PortAllocatorSessionProxy*>::iterator iter =
+ std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+ if (iter != session_proxies_.end()) {
+ proxy->OnCandidatesAllocationDone(session_.get());
+ }
+}
+
+void PortAllocatorSessionMuxer::SendAllocatedPorts_w(
+ PortAllocatorSessionProxy* proxy) {
+ std::vector<PortAllocatorSessionProxy*>::iterator iter =
+ std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+ if (iter != session_proxies_.end()) {
+ for (size_t i = 0; i < ports_.size(); ++i) {
+ PortInterface* port = ports_[i];
+ proxy->OnPortReady(session_.get(), port);
+ // If port already has candidates, send this to the clients of proxy
+ // session. This can happen if proxy is created later than the actual
+ // implementation.
+ if (!port->Candidates().empty()) {
+ proxy->OnCandidatesReady(session_.get(), port->Candidates());
+ }
+ }
+ }
+}
+
+PortAllocatorSessionProxy::~PortAllocatorSessionProxy() {
+ std::map<PortInterface*, PortProxy*>::iterator it;
+ for (it = proxy_ports_.begin(); it != proxy_ports_.end(); it++)
+ delete it->second;
+
+ SignalDestroyed(this);
+}
+
+void PortAllocatorSessionProxy::set_impl(
+ PortAllocatorSession* session) {
+ impl_ = session;
+
+ impl_->SignalCandidatesReady.connect(
+ this, &PortAllocatorSessionProxy::OnCandidatesReady);
+ impl_->SignalPortReady.connect(
+ this, &PortAllocatorSessionProxy::OnPortReady);
+ impl_->SignalCandidatesAllocationDone.connect(
+ this, &PortAllocatorSessionProxy::OnCandidatesAllocationDone);
+}
+
+void PortAllocatorSessionProxy::StartGettingPorts() {
+ ASSERT(impl_ != NULL);
+ // Since all proxies share a common PortAllocatorSession, this check will
+ // prohibit sending multiple STUN ping messages to the stun server, which
+ // is a problem on Chrome. GetInitialPorts() and StartGetAllPorts() called
+ // from the worker thread and are called together from TransportChannel,
+ // checking for IsGettingAllPorts() for GetInitialPorts() will not be a
+ // problem.
+ if (!impl_->IsGettingPorts()) {
+ impl_->StartGettingPorts();
+ }
+}
+
+void PortAllocatorSessionProxy::StopGettingPorts() {
+ ASSERT(impl_ != NULL);
+ if (impl_->IsGettingPorts()) {
+ impl_->StopGettingPorts();
+ }
+}
+
+bool PortAllocatorSessionProxy::IsGettingPorts() {
+ ASSERT(impl_ != NULL);
+ return impl_->IsGettingPorts();
+}
+
+void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session,
+ PortInterface* port) {
+ ASSERT(session == impl_);
+
+ PortProxy* proxy_port = new PortProxy();
+ proxy_port->set_impl(port);
+ proxy_ports_[port] = proxy_port;
+ SignalPortReady(this, proxy_port);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesReady(
+ PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates) {
+ ASSERT(session == impl_);
+
+ // Since all proxy sessions share a common PortAllocatorSession,
+ // all Candidates will have name associated with the common PAS.
+ // Change Candidate name with the PortAllocatorSessionProxy name.
+ std::vector<Candidate> our_candidates;
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ Candidate new_local_candidate = candidates[i];
+ new_local_candidate.set_component(component_);
+ our_candidates.push_back(new_local_candidate);
+ }
+ SignalCandidatesReady(this, our_candidates);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesAllocationDone(
+ PortAllocatorSession* session) {
+ ASSERT(session == impl_);
+ SignalCandidatesAllocationDone(this);
+}
+
+} // namespace cricket
diff --git a/p2p/base/portallocatorsessionproxy.h b/p2p/base/portallocatorsessionproxy.h
new file mode 100644
index 00000000..94ae19d9
--- /dev/null
+++ b/p2p/base/portallocatorsessionproxy.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+#define WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+
+#include <string>
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/portallocator.h"
+
+namespace cricket {
+class PortAllocator;
+class PortAllocatorSessionProxy;
+class PortProxy;
+
+// This class maintains the list of cricket::Port* objects. Ports will be
+// deleted upon receiving SignalDestroyed signal. This class is used when
+// PORTALLOCATOR_ENABLE_BUNDLE flag is set.
+
+class PortAllocatorSessionMuxer : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ explicit PortAllocatorSessionMuxer(PortAllocatorSession* session);
+ virtual ~PortAllocatorSessionMuxer();
+
+ void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy);
+
+ void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+ void OnPortDestroyed(PortInterface* port);
+ void OnCandidatesAllocationDone(PortAllocatorSession* session);
+
+ const std::vector<PortInterface*>& ports() { return ports_; }
+
+ sigslot::signal1<PortAllocatorSessionMuxer*> SignalDestroyed;
+
+ private:
+ virtual void OnMessage(rtc::Message *pmsg);
+ void OnSessionProxyDestroyed(PortAllocatorSession* proxy);
+ void SendAllocationDone_w(PortAllocatorSessionProxy* proxy);
+ void SendAllocatedPorts_w(PortAllocatorSessionProxy* proxy);
+
+ // Port will be deleted when SignalDestroyed received, otherwise delete
+ // happens when PortAllocatorSession dtor is called.
+ rtc::Thread* worker_thread_;
+ std::vector<PortInterface*> ports_;
+ rtc::scoped_ptr<PortAllocatorSession> session_;
+ std::vector<PortAllocatorSessionProxy*> session_proxies_;
+ bool candidate_done_signal_received_;
+};
+
+class PortAllocatorSessionProxy : public PortAllocatorSession {
+ public:
+ PortAllocatorSessionProxy(const std::string& content_name,
+ int component,
+ uint32 flags)
+ // Use empty string as the ufrag and pwd because the proxy always uses
+ // the ufrag and pwd from the underlying implementation.
+ : PortAllocatorSession(content_name, component, "", "", flags),
+ impl_(NULL) {
+ }
+
+ virtual ~PortAllocatorSessionProxy();
+
+ PortAllocatorSession* impl() { return impl_; }
+ void set_impl(PortAllocatorSession* session);
+
+ // Forwards call to the actual PortAllocatorSession.
+ virtual void StartGettingPorts();
+ virtual void StopGettingPorts();
+ virtual bool IsGettingPorts();
+
+ virtual void set_generation(uint32 generation) {
+ ASSERT(impl_ != NULL);
+ impl_->set_generation(generation);
+ }
+
+ virtual uint32 generation() {
+ ASSERT(impl_ != NULL);
+ return impl_->generation();
+ }
+
+ private:
+ void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+ void OnCandidatesReady(PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates);
+ void OnPortDestroyed(PortInterface* port);
+ void OnCandidatesAllocationDone(PortAllocatorSession* session);
+
+ // This is the actual PortAllocatorSession, owned by PortAllocator.
+ PortAllocatorSession* impl_;
+ std::map<PortInterface*, PortProxy*> proxy_ports_;
+
+ friend class PortAllocatorSessionMuxer;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
diff --git a/p2p/base/portallocatorsessionproxy_unittest.cc b/p2p/base/portallocatorsessionproxy_unittest.cc
new file mode 100644
index 00000000..61a9e989
--- /dev/null
+++ b/p2p/base/portallocatorsessionproxy_unittest.cc
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <vector>
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/portallocatorsessionproxy.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/p2p/client/fakeportallocator.h"
+#include "webrtc/base/fakenetwork.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/thread.h"
+
+using cricket::Candidate;
+using cricket::PortAllocatorSession;
+using cricket::PortAllocatorSessionMuxer;
+using cricket::PortAllocatorSessionProxy;
+
+// Based on ICE_UFRAG_LENGTH
+static const char kIceUfrag0[] = "TESTICEUFRAG0000";
+// Based on ICE_PWD_LENGTH
+static const char kIcePwd0[] = "TESTICEPWD00000000000000";
+
+class TestSessionChannel : public sigslot::has_slots<> {
+ public:
+ explicit TestSessionChannel(PortAllocatorSessionProxy* proxy)
+ : proxy_session_(proxy),
+ candidates_count_(0),
+ allocation_complete_(false),
+ ports_count_(0) {
+ proxy_session_->SignalCandidatesAllocationDone.connect(
+ this, &TestSessionChannel::OnCandidatesAllocationDone);
+ proxy_session_->SignalCandidatesReady.connect(
+ this, &TestSessionChannel::OnCandidatesReady);
+ proxy_session_->SignalPortReady.connect(
+ this, &TestSessionChannel::OnPortReady);
+ }
+ virtual ~TestSessionChannel() {
+ delete proxy_session_;
+ }
+ void OnCandidatesReady(PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates) {
+ EXPECT_EQ(proxy_session_, session);
+ candidates_count_ += static_cast<int>(candidates.size());
+ }
+ void OnCandidatesAllocationDone(PortAllocatorSession* session) {
+ EXPECT_EQ(proxy_session_, session);
+ allocation_complete_ = true;
+ }
+ void OnPortReady(PortAllocatorSession* session,
+ cricket::PortInterface* port) {
+ EXPECT_EQ(proxy_session_, session);
+ ++ports_count_;
+ }
+ int candidates_count() { return candidates_count_; }
+ bool allocation_complete() { return allocation_complete_; }
+ int ports_count() { return ports_count_; }
+
+ void StartGettingPorts() {
+ proxy_session_->StartGettingPorts();
+ }
+
+ void StopGettingPorts() {
+ proxy_session_->StopGettingPorts();
+ }
+
+ bool IsGettingPorts() {
+ return proxy_session_->IsGettingPorts();
+ }
+
+ private:
+ PortAllocatorSessionProxy* proxy_session_;
+ int candidates_count_;
+ bool allocation_complete_;
+ int ports_count_;
+};
+
+class PortAllocatorSessionProxyTest : public testing::Test {
+ public:
+ PortAllocatorSessionProxyTest()
+ : socket_factory_(rtc::Thread::Current()),
+ allocator_(rtc::Thread::Current(), NULL),
+ session_(new cricket::FakePortAllocatorSession(
+ rtc::Thread::Current(), &socket_factory_,
+ "test content", 1,
+ kIceUfrag0, kIcePwd0)),
+ session_muxer_(new PortAllocatorSessionMuxer(session_)) {
+ }
+ virtual ~PortAllocatorSessionProxyTest() {}
+ void RegisterSessionProxy(PortAllocatorSessionProxy* proxy) {
+ session_muxer_->RegisterSessionProxy(proxy);
+ }
+
+ TestSessionChannel* CreateChannel() {
+ PortAllocatorSessionProxy* proxy =
+ new PortAllocatorSessionProxy("test content", 1, 0);
+ TestSessionChannel* channel = new TestSessionChannel(proxy);
+ session_muxer_->RegisterSessionProxy(proxy);
+ channel->StartGettingPorts();
+ return channel;
+ }
+
+ protected:
+ rtc::BasicPacketSocketFactory socket_factory_;
+ cricket::FakePortAllocator allocator_;
+ cricket::FakePortAllocatorSession* session_;
+ // Muxer object will be delete itself after all registered session proxies
+ // are deleted.
+ PortAllocatorSessionMuxer* session_muxer_;
+};
+
+TEST_F(PortAllocatorSessionProxyTest, TestBasic) {
+ TestSessionChannel* channel = CreateChannel();
+ EXPECT_EQ_WAIT(1, channel->candidates_count(), 1000);
+ EXPECT_EQ(1, channel->ports_count());
+ EXPECT_TRUE(channel->allocation_complete());
+ delete channel;
+}
+
+TEST_F(PortAllocatorSessionProxyTest, TestLateBinding) {
+ TestSessionChannel* channel1 = CreateChannel();
+ EXPECT_EQ_WAIT(1, channel1->candidates_count(), 1000);
+ EXPECT_EQ(1, channel1->ports_count());
+ EXPECT_TRUE(channel1->allocation_complete());
+ EXPECT_EQ(1, session_->port_config_count());
+ // Creating another PortAllocatorSessionProxy and it also should receive
+ // already happened events.
+ PortAllocatorSessionProxy* proxy =
+ new PortAllocatorSessionProxy("test content", 2, 0);
+ TestSessionChannel* channel2 = new TestSessionChannel(proxy);
+ session_muxer_->RegisterSessionProxy(proxy);
+ EXPECT_TRUE(channel2->IsGettingPorts());
+ EXPECT_EQ_WAIT(1, channel2->candidates_count(), 1000);
+ EXPECT_EQ(1, channel2->ports_count());
+ EXPECT_TRUE_WAIT(channel2->allocation_complete(), 1000);
+ EXPECT_EQ(1, session_->port_config_count());
+ delete channel1;
+ delete channel2;
+}
diff --git a/p2p/base/portinterface.h b/p2p/base/portinterface.h
new file mode 100644
index 00000000..ee6835eb
--- /dev/null
+++ b/p2p/base/portinterface.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PORTINTERFACE_H_
+#define WEBRTC_P2P_BASE_PORTINTERFACE_H_
+
+#include <string>
+
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace rtc {
+class Network;
+struct PacketOptions;
+}
+
+namespace cricket {
+class Connection;
+class IceMessage;
+class StunMessage;
+
+enum ProtocolType {
+ PROTO_UDP,
+ PROTO_TCP,
+ PROTO_SSLTCP,
+ PROTO_LAST = PROTO_SSLTCP
+};
+
+// Defines the interface for a port, which represents a local communication
+// mechanism that can be used to create connections to similar mechanisms of
+// the other client. Various types of ports will implement this interface.
+class PortInterface {
+ public:
+ virtual ~PortInterface() {}
+
+ virtual const std::string& Type() const = 0;
+ virtual rtc::Network* Network() const = 0;
+
+ virtual void SetIceProtocolType(IceProtocolType protocol) = 0;
+ virtual IceProtocolType IceProtocol() const = 0;
+
+ // Methods to set/get ICE role and tiebreaker values.
+ virtual void SetIceRole(IceRole role) = 0;
+ virtual IceRole GetIceRole() const = 0;
+
+ virtual void SetIceTiebreaker(uint64 tiebreaker) = 0;
+ virtual uint64 IceTiebreaker() const = 0;
+
+ virtual bool SharedSocket() const = 0;
+
+ // PrepareAddress will attempt to get an address for this port that other
+ // clients can send to. It may take some time before the address is ready.
+ // Once it is ready, we will send SignalAddressReady. If errors are
+ // preventing the port from getting an address, it may send
+ // SignalAddressError.
+ virtual void PrepareAddress() = 0;
+
+ // Returns the connection to the given address or NULL if none exists.
+ virtual Connection* GetConnection(
+ const rtc::SocketAddress& remote_addr) = 0;
+
+ // Creates a new connection to the given address.
+ enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
+ virtual Connection* CreateConnection(
+ const Candidate& remote_candidate, CandidateOrigin origin) = 0;
+
+ // Functions on the underlying socket(s).
+ virtual int SetOption(rtc::Socket::Option opt, int value) = 0;
+ virtual int GetOption(rtc::Socket::Option opt, int* value) = 0;
+ virtual int GetError() = 0;
+
+ virtual const std::vector<Candidate>& Candidates() const = 0;
+
+ // Sends the given packet to the given address, provided that the address is
+ // that of a connection or an address that has sent to us already.
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options, bool payload) = 0;
+
+ // Indicates that we received a successful STUN binding request from an
+ // address that doesn't correspond to any current connection. To turn this
+ // into a real connection, call CreateConnection.
+ sigslot::signal6<PortInterface*, const rtc::SocketAddress&,
+ ProtocolType, IceMessage*, const std::string&,
+ bool> SignalUnknownAddress;
+
+ // Sends a response message (normal or error) to the given request. One of
+ // these methods should be called as a response to SignalUnknownAddress.
+ // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
+ virtual void SendBindingResponse(StunMessage* request,
+ const rtc::SocketAddress& addr) = 0;
+ virtual void SendBindingErrorResponse(
+ StunMessage* request, const rtc::SocketAddress& addr,
+ int error_code, const std::string& reason) = 0;
+
+ // Signaled when this port decides to delete itself because it no longer has
+ // any usefulness.
+ sigslot::signal1<PortInterface*> SignalDestroyed;
+
+ // Signaled when Port discovers ice role conflict with the peer.
+ sigslot::signal1<PortInterface*> SignalRoleConflict;
+
+ // Normally, packets arrive through a connection (or they result signaling of
+ // unknown address). Calling this method turns off delivery of packets
+ // through their respective connection and instead delivers every packet
+ // through this port.
+ virtual void EnablePortPackets() = 0;
+ sigslot::signal4<PortInterface*, const char*, size_t,
+ const rtc::SocketAddress&> SignalReadPacket;
+
+ virtual std::string ToString() const = 0;
+
+ protected:
+ PortInterface() {}
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PORTINTERFACE_H_
diff --git a/p2p/base/portproxy.cc b/p2p/base/portproxy.cc
new file mode 100644
index 00000000..e28af279
--- /dev/null
+++ b/p2p/base/portproxy.cc
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/portproxy.h"
+
+namespace cricket {
+
+void PortProxy::set_impl(PortInterface* port) {
+ impl_ = port;
+ impl_->SignalUnknownAddress.connect(
+ this, &PortProxy::OnUnknownAddress);
+ impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed);
+ impl_->SignalRoleConflict.connect(this, &PortProxy::OnRoleConflict);
+}
+
+const std::string& PortProxy::Type() const {
+ ASSERT(impl_ != NULL);
+ return impl_->Type();
+}
+
+rtc::Network* PortProxy::Network() const {
+ ASSERT(impl_ != NULL);
+ return impl_->Network();
+}
+
+void PortProxy::SetIceProtocolType(IceProtocolType protocol) {
+ ASSERT(impl_ != NULL);
+ impl_->SetIceProtocolType(protocol);
+}
+
+IceProtocolType PortProxy::IceProtocol() const {
+ ASSERT(impl_ != NULL);
+ return impl_->IceProtocol();
+}
+
+// Methods to set/get ICE role and tiebreaker values.
+void PortProxy::SetIceRole(IceRole role) {
+ ASSERT(impl_ != NULL);
+ impl_->SetIceRole(role);
+}
+
+IceRole PortProxy::GetIceRole() const {
+ ASSERT(impl_ != NULL);
+ return impl_->GetIceRole();
+}
+
+void PortProxy::SetIceTiebreaker(uint64 tiebreaker) {
+ ASSERT(impl_ != NULL);
+ impl_->SetIceTiebreaker(tiebreaker);
+}
+
+uint64 PortProxy::IceTiebreaker() const {
+ ASSERT(impl_ != NULL);
+ return impl_->IceTiebreaker();
+}
+
+bool PortProxy::SharedSocket() const {
+ ASSERT(impl_ != NULL);
+ return impl_->SharedSocket();
+}
+
+void PortProxy::PrepareAddress() {
+ ASSERT(impl_ != NULL);
+ impl_->PrepareAddress();
+}
+
+Connection* PortProxy::CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ ASSERT(impl_ != NULL);
+ return impl_->CreateConnection(remote_candidate, origin);
+}
+
+int PortProxy::SendTo(const void* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ ASSERT(impl_ != NULL);
+ return impl_->SendTo(data, size, addr, options, payload);
+}
+
+int PortProxy::SetOption(rtc::Socket::Option opt,
+ int value) {
+ ASSERT(impl_ != NULL);
+ return impl_->SetOption(opt, value);
+}
+
+int PortProxy::GetOption(rtc::Socket::Option opt,
+ int* value) {
+ ASSERT(impl_ != NULL);
+ return impl_->GetOption(opt, value);
+}
+
+int PortProxy::GetError() {
+ ASSERT(impl_ != NULL);
+ return impl_->GetError();
+}
+
+const std::vector<Candidate>& PortProxy::Candidates() const {
+ ASSERT(impl_ != NULL);
+ return impl_->Candidates();
+}
+
+void PortProxy::SendBindingResponse(
+ StunMessage* request, const rtc::SocketAddress& addr) {
+ ASSERT(impl_ != NULL);
+ impl_->SendBindingResponse(request, addr);
+}
+
+Connection* PortProxy::GetConnection(
+ const rtc::SocketAddress& remote_addr) {
+ ASSERT(impl_ != NULL);
+ return impl_->GetConnection(remote_addr);
+}
+
+void PortProxy::SendBindingErrorResponse(
+ StunMessage* request, const rtc::SocketAddress& addr,
+ int error_code, const std::string& reason) {
+ ASSERT(impl_ != NULL);
+ impl_->SendBindingErrorResponse(request, addr, error_code, reason);
+}
+
+void PortProxy::EnablePortPackets() {
+ ASSERT(impl_ != NULL);
+ impl_->EnablePortPackets();
+}
+
+std::string PortProxy::ToString() const {
+ ASSERT(impl_ != NULL);
+ return impl_->ToString();
+}
+
+void PortProxy::OnUnknownAddress(
+ PortInterface *port,
+ const rtc::SocketAddress &addr,
+ ProtocolType proto,
+ IceMessage *stun_msg,
+ const std::string &remote_username,
+ bool port_muxed) {
+ ASSERT(port == impl_);
+ ASSERT(!port_muxed);
+ SignalUnknownAddress(this, addr, proto, stun_msg, remote_username, true);
+}
+
+void PortProxy::OnRoleConflict(PortInterface* port) {
+ ASSERT(port == impl_);
+ SignalRoleConflict(this);
+}
+
+void PortProxy::OnPortDestroyed(PortInterface* port) {
+ ASSERT(port == impl_);
+ // |port| will be destroyed in PortAllocatorSessionMuxer.
+ SignalDestroyed(this);
+}
+
+} // namespace cricket
diff --git a/p2p/base/portproxy.h b/p2p/base/portproxy.h
new file mode 100644
index 00000000..79507fea
--- /dev/null
+++ b/p2p/base/portproxy.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PORTPROXY_H_
+#define WEBRTC_P2P_BASE_PORTPROXY_H_
+
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/base/sigslot.h"
+
+namespace rtc {
+class Network;
+}
+
+namespace cricket {
+
+class PortProxy : public PortInterface, public sigslot::has_slots<> {
+ public:
+ PortProxy() {}
+ virtual ~PortProxy() {}
+
+ PortInterface* impl() { return impl_; }
+ void set_impl(PortInterface* port);
+
+ virtual const std::string& Type() const;
+ virtual rtc::Network* Network() const;
+
+ virtual void SetIceProtocolType(IceProtocolType protocol);
+ virtual IceProtocolType IceProtocol() const;
+
+ // Methods to set/get ICE role and tiebreaker values.
+ virtual void SetIceRole(IceRole role);
+ virtual IceRole GetIceRole() const;
+
+ virtual void SetIceTiebreaker(uint64 tiebreaker);
+ virtual uint64 IceTiebreaker() const;
+
+ virtual bool SharedSocket() const;
+
+ // Forwards call to the actual Port.
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin);
+ virtual Connection* GetConnection(
+ const rtc::SocketAddress& remote_addr);
+
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetOption(rtc::Socket::Option opt, int* value);
+ virtual int GetError();
+
+ virtual const std::vector<Candidate>& Candidates() const;
+
+ virtual void SendBindingResponse(StunMessage* request,
+ const rtc::SocketAddress& addr);
+ virtual void SendBindingErrorResponse(
+ StunMessage* request, const rtc::SocketAddress& addr,
+ int error_code, const std::string& reason);
+
+ virtual void EnablePortPackets();
+ virtual std::string ToString() const;
+
+ private:
+ void OnUnknownAddress(PortInterface *port,
+ const rtc::SocketAddress &addr,
+ ProtocolType proto,
+ IceMessage *stun_msg,
+ const std::string &remote_username,
+ bool port_muxed);
+ void OnRoleConflict(PortInterface* port);
+ void OnPortDestroyed(PortInterface* port);
+
+ PortInterface* impl_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PORTPROXY_H_
diff --git a/p2p/base/pseudotcp.cc b/p2p/base/pseudotcp.cc
new file mode 100644
index 00000000..0dfe7d82
--- /dev/null
+++ b/p2p/base/pseudotcp.cc
@@ -0,0 +1,1274 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/pseudotcp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <set>
+
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/byteorder.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socket.h"
+#include "webrtc/base/stringutils.h"
+#include "webrtc/base/timeutils.h"
+
+// The following logging is for detailed (packet-level) analysis only.
+#define _DBG_NONE 0
+#define _DBG_NORMAL 1
+#define _DBG_VERBOSE 2
+#define _DEBUGMSG _DBG_NONE
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// Network Constants
+//////////////////////////////////////////////////////////////////////
+
+// Standard MTUs
+const uint16 PACKET_MAXIMUMS[] = {
+ 65535, // Theoretical maximum, Hyperchannel
+ 32000, // Nothing
+ 17914, // 16Mb IBM Token Ring
+ 8166, // IEEE 802.4
+ //4464, // IEEE 802.5 (4Mb max)
+ 4352, // FDDI
+ //2048, // Wideband Network
+ 2002, // IEEE 802.5 (4Mb recommended)
+ //1536, // Expermental Ethernet Networks
+ //1500, // Ethernet, Point-to-Point (default)
+ 1492, // IEEE 802.3
+ 1006, // SLIP, ARPANET
+ //576, // X.25 Networks
+ //544, // DEC IP Portal
+ //512, // NETBIOS
+ 508, // IEEE 802/Source-Rt Bridge, ARCNET
+ 296, // Point-to-Point (low delay)
+ //68, // Official minimum
+ 0, // End of list marker
+};
+
+const uint32 MAX_PACKET = 65535;
+// Note: we removed lowest level because packet overhead was larger!
+const uint32 MIN_PACKET = 296;
+
+const uint32 IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?)
+const uint32 UDP_HEADER_SIZE = 8;
+// TODO: Make JINGLE_HEADER_SIZE transparent to this code?
+const uint32 JINGLE_HEADER_SIZE = 64; // when relay framing is in use
+
+// Default size for receive and send buffer.
+const uint32 DEFAULT_RCV_BUF_SIZE = 60 * 1024;
+const uint32 DEFAULT_SND_BUF_SIZE = 90 * 1024;
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Functions
+//////////////////////////////////////////////////////////////////////
+//
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 0 | Conversation Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 4 | Sequence Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 8 | Acknowledgment Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | | |U|A|P|R|S|F| |
+// 12 | Control | |R|C|S|S|Y|I| Window |
+// | | |G|K|H|T|N|N| |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 16 | Timestamp sending |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 20 | Timestamp receiving |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 24 | data |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//////////////////////////////////////////////////////////////////////
+
+#define PSEUDO_KEEPALIVE 0
+
+const uint32 HEADER_SIZE = 24;
+const uint32 PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE;
+
+const uint32 MIN_RTO = 250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
+const uint32 DEF_RTO = 3000; // 3 seconds (RFC1122, Sec 4.2.3.1)
+const uint32 MAX_RTO = 60000; // 60 seconds
+const uint32 DEF_ACK_DELAY = 100; // 100 milliseconds
+
+const uint8 FLAG_CTL = 0x02;
+const uint8 FLAG_RST = 0x04;
+
+const uint8 CTL_CONNECT = 0;
+
+// TCP options.
+const uint8 TCP_OPT_EOL = 0; // End of list.
+const uint8 TCP_OPT_NOOP = 1; // No-op.
+const uint8 TCP_OPT_MSS = 2; // Maximum segment size.
+const uint8 TCP_OPT_WND_SCALE = 3; // Window scale factor.
+
+const long DEFAULT_TIMEOUT = 4000; // If there are no pending clocks, wake up every 4 seconds
+const long CLOSED_TIMEOUT = 60 * 1000; // If the connection is closed, once per minute
+
+#if PSEUDO_KEEPALIVE
+// !?! Rethink these times
+const uint32 IDLE_PING = 20 * 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds)
+const uint32 IDLE_TIMEOUT = 90 * 1000; // 90 seconds;
+#endif // PSEUDO_KEEPALIVE
+
+//////////////////////////////////////////////////////////////////////
+// Helper Functions
+//////////////////////////////////////////////////////////////////////
+
+inline void long_to_bytes(uint32 val, void* buf) {
+ *static_cast<uint32*>(buf) = rtc::HostToNetwork32(val);
+}
+
+inline void short_to_bytes(uint16 val, void* buf) {
+ *static_cast<uint16*>(buf) = rtc::HostToNetwork16(val);
+}
+
+inline uint32 bytes_to_long(const void* buf) {
+ return rtc::NetworkToHost32(*static_cast<const uint32*>(buf));
+}
+
+inline uint16 bytes_to_short(const void* buf) {
+ return rtc::NetworkToHost16(*static_cast<const uint16*>(buf));
+}
+
+uint32 bound(uint32 lower, uint32 middle, uint32 upper) {
+ return rtc::_min(rtc::_max(lower, middle), upper);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Debugging Statistics
+//////////////////////////////////////////////////////////////////////
+
+#if 0 // Not used yet
+
+enum Stat {
+ S_SENT_PACKET, // All packet sends
+ S_RESENT_PACKET, // All packet sends that are retransmits
+ S_RECV_PACKET, // All packet receives
+ S_RECV_NEW, // All packet receives that are too new
+ S_RECV_OLD, // All packet receives that are too old
+ S_NUM_STATS
+};
+
+const char* const STAT_NAMES[S_NUM_STATS] = {
+ "snt",
+ "snt-r",
+ "rcv"
+ "rcv-n",
+ "rcv-o"
+};
+
+int g_stats[S_NUM_STATS];
+inline void Incr(Stat s) { ++g_stats[s]; }
+void ReportStats() {
+ char buffer[256];
+ size_t len = 0;
+ for (int i = 0; i < S_NUM_STATS; ++i) {
+ len += rtc::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d",
+ (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]);
+ g_stats[i] = 0;
+ }
+ LOG(LS_INFO) << "Stats[" << buffer << "]";
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+uint32 PseudoTcp::Now() {
+#if 0 // Use this to synchronize timers with logging timestamps (easier debug)
+ return rtc::TimeSince(StartTime());
+#else
+ return rtc::Time();
+#endif
+}
+
+PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32 conv)
+ : m_notify(notify),
+ m_shutdown(SD_NONE),
+ m_error(0),
+ m_rbuf_len(DEFAULT_RCV_BUF_SIZE),
+ m_rbuf(m_rbuf_len),
+ m_sbuf_len(DEFAULT_SND_BUF_SIZE),
+ m_sbuf(m_sbuf_len) {
+
+ // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic)
+ ASSERT(m_rbuf_len + MIN_PACKET < m_sbuf_len);
+
+ uint32 now = Now();
+
+ m_state = TCP_LISTEN;
+ m_conv = conv;
+ m_rcv_wnd = m_rbuf_len;
+ m_rwnd_scale = m_swnd_scale = 0;
+ m_snd_nxt = 0;
+ m_snd_wnd = 1;
+ m_snd_una = m_rcv_nxt = 0;
+ m_bReadEnable = true;
+ m_bWriteEnable = false;
+ m_t_ack = 0;
+
+ m_msslevel = 0;
+ m_largest = 0;
+ ASSERT(MIN_PACKET > PACKET_OVERHEAD);
+ m_mss = MIN_PACKET - PACKET_OVERHEAD;
+ m_mtu_advise = MAX_PACKET;
+
+ m_rto_base = 0;
+
+ m_cwnd = 2 * m_mss;
+ m_ssthresh = m_rbuf_len;
+ m_lastrecv = m_lastsend = m_lasttraffic = now;
+ m_bOutgoing = false;
+
+ m_dup_acks = 0;
+ m_recover = 0;
+
+ m_ts_recent = m_ts_lastack = 0;
+
+ m_rx_rto = DEF_RTO;
+ m_rx_srtt = m_rx_rttvar = 0;
+
+ m_use_nagling = true;
+ m_ack_delay = DEF_ACK_DELAY;
+ m_support_wnd_scale = true;
+}
+
+PseudoTcp::~PseudoTcp() {
+}
+
+int PseudoTcp::Connect() {
+ if (m_state != TCP_LISTEN) {
+ m_error = EINVAL;
+ return -1;
+ }
+
+ m_state = TCP_SYN_SENT;
+ LOG(LS_INFO) << "State: TCP_SYN_SENT";
+
+ queueConnectMessage();
+ attemptSend();
+
+ return 0;
+}
+
+void PseudoTcp::NotifyMTU(uint16 mtu) {
+ m_mtu_advise = mtu;
+ if (m_state == TCP_ESTABLISHED) {
+ adjustMTU();
+ }
+}
+
+void PseudoTcp::NotifyClock(uint32 now) {
+ if (m_state == TCP_CLOSED)
+ return;
+
+ // Check if it's time to retransmit a segment
+ if (m_rto_base && (rtc::TimeDiff(m_rto_base + m_rx_rto, now) <= 0)) {
+ if (m_slist.empty()) {
+ ASSERT(false);
+ } else {
+ // Note: (m_slist.front().xmit == 0)) {
+ // retransmit segments
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto
+ << ") (rto_base: " << m_rto_base
+ << ") (now: " << now
+ << ") (dup_acks: " << static_cast<unsigned>(m_dup_acks)
+ << ")";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = rtc::_max(nInFlight / 2, 2 * m_mss);
+ //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_mss;
+
+ // Back off retransmit timer. Note: the limit is lower when connecting.
+ uint32 rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
+ m_rx_rto = rtc::_min(rto_limit, m_rx_rto * 2);
+ m_rto_base = now;
+ }
+ }
+
+ // Check if it's time to probe closed windows
+ if ((m_snd_wnd == 0)
+ && (rtc::TimeDiff(m_lastsend + m_rx_rto, now) <= 0)) {
+ if (rtc::TimeDiff(now, m_lastrecv) >= 15000) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // probe the window
+ packet(m_snd_nxt - 1, 0, 0, 0);
+ m_lastsend = now;
+
+ // back off retransmit timer
+ m_rx_rto = rtc::_min(MAX_RTO, m_rx_rto * 2);
+ }
+
+ // Check if it's time to send delayed acks
+ if (m_t_ack && (rtc::TimeDiff(m_t_ack + m_ack_delay, now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+
+#if PSEUDO_KEEPALIVE
+ // Check for idle timeout
+ if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // Check for ping timeout (to keep udp mapping open)
+ if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+#endif // PSEUDO_KEEPALIVE
+}
+
+bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) {
+ if (len > MAX_PACKET) {
+ LOG_F(WARNING) << "packet too large";
+ return false;
+ }
+ return parse(reinterpret_cast<const uint8 *>(buffer), uint32(len));
+}
+
+bool PseudoTcp::GetNextClock(uint32 now, long& timeout) {
+ return clock_check(now, timeout);
+}
+
+void PseudoTcp::GetOption(Option opt, int* value) {
+ if (opt == OPT_NODELAY) {
+ *value = m_use_nagling ? 0 : 1;
+ } else if (opt == OPT_ACKDELAY) {
+ *value = m_ack_delay;
+ } else if (opt == OPT_SNDBUF) {
+ *value = m_sbuf_len;
+ } else if (opt == OPT_RCVBUF) {
+ *value = m_rbuf_len;
+ } else {
+ ASSERT(false);
+ }
+}
+void PseudoTcp::SetOption(Option opt, int value) {
+ if (opt == OPT_NODELAY) {
+ m_use_nagling = value == 0;
+ } else if (opt == OPT_ACKDELAY) {
+ m_ack_delay = value;
+ } else if (opt == OPT_SNDBUF) {
+ ASSERT(m_state == TCP_LISTEN);
+ resizeSendBuffer(value);
+ } else if (opt == OPT_RCVBUF) {
+ ASSERT(m_state == TCP_LISTEN);
+ resizeReceiveBuffer(value);
+ } else {
+ ASSERT(false);
+ }
+}
+
+uint32 PseudoTcp::GetCongestionWindow() const {
+ return m_cwnd;
+}
+
+uint32 PseudoTcp::GetBytesInFlight() const {
+ return m_snd_nxt - m_snd_una;
+}
+
+uint32 PseudoTcp::GetBytesBufferedNotSent() const {
+ size_t buffered_bytes = 0;
+ m_sbuf.GetBuffered(&buffered_bytes);
+ return static_cast<uint32>(m_snd_una + buffered_bytes - m_snd_nxt);
+}
+
+uint32 PseudoTcp::GetRoundTripTimeEstimateMs() const {
+ return m_rx_srtt;
+}
+
+//
+// IPStream Implementation
+//
+
+int PseudoTcp::Recv(char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ size_t read = 0;
+ rtc::StreamResult result = m_rbuf.Read(buffer, len, &read, NULL);
+
+ // If there's no data in |m_rbuf|.
+ if (result == rtc::SR_BLOCK) {
+ m_bReadEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ ASSERT(result == rtc::SR_SUCCESS);
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+
+ if (uint32(available_space) - m_rcv_wnd >=
+ rtc::_min<uint32>(m_rbuf_len / 2, m_mss)) {
+ // TODO(jbeda): !?! Not sure about this was closed business
+ bool bWasClosed = (m_rcv_wnd == 0);
+ m_rcv_wnd = static_cast<uint32>(available_space);
+
+ if (bWasClosed) {
+ attemptSend(sfImmediateAck);
+ }
+ }
+
+ return static_cast<int>(read);
+}
+
+int PseudoTcp::Send(const char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ if (!available_space) {
+ m_bWriteEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ int written = queue(buffer, uint32(len), false);
+ attemptSend();
+ return written;
+}
+
+void PseudoTcp::Close(bool force) {
+ LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")";
+ m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL;
+}
+
+int PseudoTcp::GetError() {
+ return m_error;
+}
+
+//
+// Internal Implementation
+//
+
+uint32 PseudoTcp::queue(const char* data, uint32 len, bool bCtrl) {
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ if (len > static_cast<uint32>(available_space)) {
+ ASSERT(!bCtrl);
+ len = static_cast<uint32>(available_space);
+ }
+
+ // We can concatenate data if the last segment is the same type
+ // (control v. regular data), and has not been transmitted yet
+ if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) &&
+ (m_slist.back().xmit == 0)) {
+ m_slist.back().len += len;
+ } else {
+ size_t snd_buffered = 0;
+ m_sbuf.GetBuffered(&snd_buffered);
+ SSegment sseg(static_cast<uint32>(m_snd_una + snd_buffered), len, bCtrl);
+ m_slist.push_back(sseg);
+ }
+
+ size_t written = 0;
+ m_sbuf.Write(data, len, &written, NULL);
+ return static_cast<uint32>(written);
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags,
+ uint32 offset, uint32 len) {
+ ASSERT(HEADER_SIZE + len <= MAX_PACKET);
+
+ uint32 now = Now();
+
+ rtc::scoped_ptr<uint8[]> buffer(new uint8[MAX_PACKET]);
+ long_to_bytes(m_conv, buffer.get());
+ long_to_bytes(seq, buffer.get() + 4);
+ long_to_bytes(m_rcv_nxt, buffer.get() + 8);
+ buffer[12] = 0;
+ buffer[13] = flags;
+ short_to_bytes(
+ static_cast<uint16>(m_rcv_wnd >> m_rwnd_scale), buffer.get() + 14);
+
+ // Timestamp computations
+ long_to_bytes(now, buffer.get() + 16);
+ long_to_bytes(m_ts_recent, buffer.get() + 20);
+ m_ts_lastack = m_rcv_nxt;
+
+ if (len) {
+ size_t bytes_read = 0;
+ rtc::StreamResult result = m_sbuf.ReadOffset(
+ buffer.get() + HEADER_SIZE, len, offset, &bytes_read);
+ RTC_UNUSED(result);
+ ASSERT(result == rtc::SR_SUCCESS);
+ ASSERT(static_cast<uint32>(bytes_read) == len);
+ }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "<-- <CONV=" << m_conv
+ << "><FLG=" << static_cast<unsigned>(flags)
+ << "><SEQ=" << seq << ":" << seq + len
+ << "><ACK=" << m_rcv_nxt
+ << "><WND=" << m_rcv_wnd
+ << "><TS=" << (now % 10000)
+ << "><TSR=" << (m_ts_recent % 10000)
+ << "><LEN=" << len << ">";
+#endif // _DEBUGMSG
+
+ IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket(
+ this, reinterpret_cast<char *>(buffer.get()), len + HEADER_SIZE);
+ // Note: When len is 0, this is an ACK packet. We don't read the return value for those,
+ // and thus we won't retry. So go ahead and treat the packet as a success (basically simulate
+ // as if it were dropped), which will prevent our timers from being messed up.
+ if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (0 != len))
+ return wres;
+
+ m_t_ack = 0;
+ if (len > 0) {
+ m_lastsend = now;
+ }
+ m_lasttraffic = now;
+ m_bOutgoing = true;
+
+ return IPseudoTcpNotify::WR_SUCCESS;
+}
+
+bool PseudoTcp::parse(const uint8* buffer, uint32 size) {
+ if (size < 12)
+ return false;
+
+ Segment seg;
+ seg.conv = bytes_to_long(buffer);
+ seg.seq = bytes_to_long(buffer + 4);
+ seg.ack = bytes_to_long(buffer + 8);
+ seg.flags = buffer[13];
+ seg.wnd = bytes_to_short(buffer + 14);
+
+ seg.tsval = bytes_to_long(buffer + 16);
+ seg.tsecr = bytes_to_long(buffer + 20);
+
+ seg.data = reinterpret_cast<const char *>(buffer) + HEADER_SIZE;
+ seg.len = size - HEADER_SIZE;
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "--> <CONV=" << seg.conv
+ << "><FLG=" << static_cast<unsigned>(seg.flags)
+ << "><SEQ=" << seg.seq << ":" << seg.seq + seg.len
+ << "><ACK=" << seg.ack
+ << "><WND=" << seg.wnd
+ << "><TS=" << (seg.tsval % 10000)
+ << "><TSR=" << (seg.tsecr % 10000)
+ << "><LEN=" << seg.len << ">";
+#endif // _DEBUGMSG
+
+ return process(seg);
+}
+
+bool PseudoTcp::clock_check(uint32 now, long& nTimeout) {
+ if (m_shutdown == SD_FORCEFUL)
+ return false;
+
+ size_t snd_buffered = 0;
+ m_sbuf.GetBuffered(&snd_buffered);
+ if ((m_shutdown == SD_GRACEFUL)
+ && ((m_state != TCP_ESTABLISHED)
+ || ((snd_buffered == 0) && (m_t_ack == 0)))) {
+ return false;
+ }
+
+ if (m_state == TCP_CLOSED) {
+ nTimeout = CLOSED_TIMEOUT;
+ return true;
+ }
+
+ nTimeout = DEFAULT_TIMEOUT;
+
+ if (m_t_ack) {
+ nTimeout = rtc::_min<int32>(nTimeout,
+ rtc::TimeDiff(m_t_ack + m_ack_delay, now));
+ }
+ if (m_rto_base) {
+ nTimeout = rtc::_min<int32>(nTimeout,
+ rtc::TimeDiff(m_rto_base + m_rx_rto, now));
+ }
+ if (m_snd_wnd == 0) {
+ nTimeout = rtc::_min<int32>(nTimeout, rtc::TimeDiff(m_lastsend + m_rx_rto, now));
+ }
+#if PSEUDO_KEEPALIVE
+ if (m_state == TCP_ESTABLISHED) {
+ nTimeout = rtc::_min<int32>(nTimeout,
+ rtc::TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now));
+ }
+#endif // PSEUDO_KEEPALIVE
+ return true;
+}
+
+bool PseudoTcp::process(Segment& seg) {
+ // If this is the wrong conversation, send a reset!?! (with the correct conversation?)
+ if (seg.conv != m_conv) {
+ //if ((seg.flags & FLAG_RST) == 0) {
+ // packet(tcb, seg.ack, 0, FLAG_RST, 0, 0);
+ //}
+ LOG_F(LS_ERROR) << "wrong conversation";
+ return false;
+ }
+
+ uint32 now = Now();
+ m_lasttraffic = m_lastrecv = now;
+ m_bOutgoing = false;
+
+ if (m_state == TCP_CLOSED) {
+ // !?! send reset?
+ LOG_F(LS_ERROR) << "closed";
+ return false;
+ }
+
+ // Check if this is a reset segment
+ if (seg.flags & FLAG_RST) {
+ closedown(ECONNRESET);
+ return false;
+ }
+
+ // Check for control data
+ bool bConnect = false;
+ if (seg.flags & FLAG_CTL) {
+ if (seg.len == 0) {
+ LOG_F(LS_ERROR) << "Missing control code";
+ return false;
+ } else if (seg.data[0] == CTL_CONNECT) {
+ bConnect = true;
+
+ // TCP options are in the remainder of the payload after CTL_CONNECT.
+ parseOptions(&seg.data[1], seg.len - 1);
+
+ if (m_state == TCP_LISTEN) {
+ m_state = TCP_SYN_RECEIVED;
+ LOG(LS_INFO) << "State: TCP_SYN_RECEIVED";
+ //m_notify->associate(addr);
+ queueConnectMessage();
+ } else if (m_state == TCP_SYN_SENT) {
+ m_state = TCP_ESTABLISHED;
+ LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ //notify(evOpen);
+ }
+ } else {
+ LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0];
+ return false;
+ }
+ }
+
+ // Update timestamp
+ if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) {
+ m_ts_recent = seg.tsval;
+ }
+
+ // Check if this is a valuable ack
+ if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {
+ // Calculate round-trip time
+ if (seg.tsecr) {
+ int32 rtt = rtc::TimeDiff(now, seg.tsecr);
+ if (rtt >= 0) {
+ if (m_rx_srtt == 0) {
+ m_rx_srtt = rtt;
+ m_rx_rttvar = rtt / 2;
+ } else {
+ uint32 unsigned_rtt = static_cast<uint32>(rtt);
+ uint32 abs_err = unsigned_rtt > m_rx_srtt ? unsigned_rtt - m_rx_srtt
+ : m_rx_srtt - unsigned_rtt;
+ m_rx_rttvar = (3 * m_rx_rttvar + abs_err) / 4;
+ m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;
+ }
+ m_rx_rto = bound(MIN_RTO, m_rx_srtt +
+ rtc::_max<uint32>(1, 4 * m_rx_rttvar), MAX_RTO);
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "rtt: " << rtt
+ << " srtt: " << m_rx_srtt
+ << " rto: " << m_rx_rto;
+#endif // _DEBUGMSG
+ } else {
+ ASSERT(false);
+ }
+ }
+
+ m_snd_wnd = static_cast<uint32>(seg.wnd) << m_swnd_scale;
+
+ uint32 nAcked = seg.ack - m_snd_una;
+ m_snd_una = seg.ack;
+
+ m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now;
+
+ m_sbuf.ConsumeReadData(nAcked);
+
+ for (uint32 nFree = nAcked; nFree > 0; ) {
+ ASSERT(!m_slist.empty());
+ if (nFree < m_slist.front().len) {
+ m_slist.front().len -= nFree;
+ nFree = 0;
+ } else {
+ if (m_slist.front().len > m_largest) {
+ m_largest = m_slist.front().len;
+ }
+ nFree -= m_slist.front().len;
+ m_slist.pop_front();
+ }
+ }
+
+ if (m_dup_acks >= 3) {
+ if (m_snd_una >= m_recover) { // NewReno
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_cwnd = rtc::_min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "exit recovery";
+#endif // _DEBUGMSG
+ m_dup_acks = 0;
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_cwnd += m_mss - rtc::_min(nAcked, m_cwnd);
+ }
+ } else {
+ m_dup_acks = 0;
+ // Slow start, congestion avoidance
+ if (m_cwnd < m_ssthresh) {
+ m_cwnd += m_mss;
+ } else {
+ m_cwnd += rtc::_max<uint32>(1, m_mss * m_mss / m_cwnd);
+ }
+ }
+ } else if (seg.ack == m_snd_una) {
+ // !?! Note, tcp says don't do this... but otherwise how does a closed window become open?
+ m_snd_wnd = static_cast<uint32>(seg.wnd) << m_swnd_scale;
+
+ // Check duplicate acks
+ if (seg.len > 0) {
+ // it's a dup ack, but with a data payload, so don't modify m_dup_acks
+ } else if (m_snd_una != m_snd_nxt) {
+ m_dup_acks += 1;
+ if (m_dup_acks == 3) { // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "enter recovery";
+ LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_recover = m_snd_nxt;
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = rtc::_max(nInFlight / 2, 2 * m_mss);
+ //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_ssthresh + 3 * m_mss;
+ } else if (m_dup_acks > 3) {
+ m_cwnd += m_mss;
+ }
+ } else {
+ m_dup_acks = 0;
+ }
+ }
+
+ // !?! A bit hacky
+ if ((m_state == TCP_SYN_RECEIVED) && !bConnect) {
+ m_state = TCP_ESTABLISHED;
+ LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ //notify(evOpen);
+ }
+
+ // If we make room in the send queue, notify the user
+ // The goal it to make sure we always have at least enough data to fill the
+ // window. We'd like to notify the app when we are halfway to that point.
+ const uint32 kIdealRefillSize = (m_sbuf_len + m_rbuf_len) / 2;
+ size_t snd_buffered = 0;
+ m_sbuf.GetBuffered(&snd_buffered);
+ if (m_bWriteEnable && static_cast<uint32>(snd_buffered) < kIdealRefillSize) {
+ m_bWriteEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpWriteable(this);
+ }
+ //notify(evWrite);
+ }
+
+ // Conditions were acks must be sent:
+ // 1) Segment is too old (they missed an ACK) (immediately)
+ // 2) Segment is too new (we missed a segment) (immediately)
+ // 3) Segment has data (so we need to ACK!) (delayed)
+ // ... so the only time we don't need to ACK, is an empty segment that points to rcv_nxt!
+
+ SendFlags sflags = sfNone;
+ if (seg.seq != m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ } else if (seg.len != 0) {
+ if (m_ack_delay == 0) {
+ sflags = sfImmediateAck;
+ } else {
+ sflags = sfDelayedAck;
+ }
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ if (sflags == sfImmediateAck) {
+ if (seg.seq > m_rcv_nxt) {
+ LOG_F(LS_INFO) << "too new";
+ } else if (seg.seq + seg.len <= m_rcv_nxt) {
+ LOG_F(LS_INFO) << "too old";
+ }
+ }
+#endif // _DEBUGMSG
+
+ // Adjust the incoming segment to fit our receive buffer
+ if (seg.seq < m_rcv_nxt) {
+ uint32 nAdjust = m_rcv_nxt - seg.seq;
+ if (nAdjust < seg.len) {
+ seg.seq += nAdjust;
+ seg.data += nAdjust;
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+
+ if ((seg.seq + seg.len - m_rcv_nxt) > static_cast<uint32>(available_space)) {
+ uint32 nAdjust = seg.seq + seg.len - m_rcv_nxt - static_cast<uint32>(available_space);
+ if (nAdjust < seg.len) {
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+
+ bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE);
+ bool bNewData = false;
+
+ if (seg.len > 0) {
+ if (bIgnoreData) {
+ if (seg.seq == m_rcv_nxt) {
+ m_rcv_nxt += seg.len;
+ }
+ } else {
+ uint32 nOffset = seg.seq - m_rcv_nxt;
+
+ rtc::StreamResult result = m_rbuf.WriteOffset(seg.data, seg.len,
+ nOffset, NULL);
+ ASSERT(result == rtc::SR_SUCCESS);
+ RTC_UNUSED(result);
+
+ if (seg.seq == m_rcv_nxt) {
+ m_rbuf.ConsumeWriteBuffer(seg.len);
+ m_rcv_nxt += seg.len;
+ m_rcv_wnd -= seg.len;
+ bNewData = true;
+
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {
+ if (it->seq + it->len > m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt;
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt << " -> " << m_rcv_nxt + nAdjust << ")";
+#endif // _DEBUGMSG
+ m_rbuf.ConsumeWriteBuffer(nAdjust);
+ m_rcv_nxt += nAdjust;
+ m_rcv_wnd -= nAdjust;
+ }
+ it = m_rlist.erase(it);
+ }
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq << " -> " << seg.seq + seg.len << ")";
+#endif // _DEBUGMSG
+ RSegment rseg;
+ rseg.seq = seg.seq;
+ rseg.len = seg.len;
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {
+ ++it;
+ }
+ m_rlist.insert(it, rseg);
+ }
+ }
+ }
+
+ attemptSend(sflags);
+
+ // If we have new data, notify the user
+ if (bNewData && m_bReadEnable) {
+ m_bReadEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpReadable(this);
+ }
+ //notify(evRead);
+ }
+
+ return true;
+}
+
+bool PseudoTcp::transmit(const SList::iterator& seg, uint32 now) {
+ if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) {
+ LOG_F(LS_VERBOSE) << "too many retransmits";
+ return false;
+ }
+
+ uint32 nTransmit = rtc::_min(seg->len, m_mss);
+
+ while (true) {
+ uint32 seq = seg->seq;
+ uint8 flags = (seg->bCtrl ? FLAG_CTL : 0);
+ IPseudoTcpNotify::WriteResult wres = packet(seq,
+ flags,
+ seg->seq - m_snd_una,
+ nTransmit);
+
+ if (wres == IPseudoTcpNotify::WR_SUCCESS)
+ break;
+
+ if (wres == IPseudoTcpNotify::WR_FAIL) {
+ LOG_F(LS_VERBOSE) << "packet failed";
+ return false;
+ }
+
+ ASSERT(wres == IPseudoTcpNotify::WR_TOO_LARGE);
+
+ while (true) {
+ if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) {
+ LOG_F(LS_VERBOSE) << "MTU too small";
+ return false;
+ }
+ // !?! We need to break up all outstanding and pending packets and then retransmit!?!
+
+ m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD;
+ m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula
+ if (m_mss < nTransmit) {
+ nTransmit = m_mss;
+ break;
+ }
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ }
+
+ if (nTransmit < seg->len) {
+ LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss;
+
+ SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl);
+ //subseg.tstamp = seg->tstamp;
+ subseg.xmit = seg->xmit;
+ seg->len = nTransmit;
+
+ SList::iterator next = seg;
+ m_slist.insert(++next, subseg);
+ }
+
+ if (seg->xmit == 0) {
+ m_snd_nxt += seg->len;
+ }
+ seg->xmit += 1;
+ //seg->tstamp = now;
+ if (m_rto_base == 0) {
+ m_rto_base = now;
+ }
+
+ return true;
+}
+
+void PseudoTcp::attemptSend(SendFlags sflags) {
+ uint32 now = Now();
+
+ if (rtc::TimeDiff(now, m_lastsend) > static_cast<long>(m_rx_rto)) {
+ m_cwnd = m_mss;
+ }
+
+#if _DEBUGMSG
+ bool bFirst = true;
+ RTC_UNUSED(bFirst);
+#endif // _DEBUGMSG
+
+ while (true) {
+ uint32 cwnd = m_cwnd;
+ if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit
+ cwnd += m_dup_acks * m_mss;
+ }
+ uint32 nWindow = rtc::_min(m_snd_wnd, cwnd);
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;
+
+ size_t snd_buffered = 0;
+ m_sbuf.GetBuffered(&snd_buffered);
+ uint32 nAvailable =
+ rtc::_min(static_cast<uint32>(snd_buffered) - nInFlight, m_mss);
+
+ if (nAvailable > nUseable) {
+ if (nUseable * 4 < nWindow) {
+ // RFC 813 - avoid SWS
+ nAvailable = 0;
+ } else {
+ nAvailable = nUseable;
+ }
+ }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ if (bFirst) {
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ bFirst = false;
+ LOG(LS_INFO) << "[cwnd: " << m_cwnd
+ << " nWindow: " << nWindow
+ << " nInFlight: " << nInFlight
+ << " nAvailable: " << nAvailable
+ << " nQueued: " << snd_buffered
+ << " nEmpty: " << available_space
+ << " ssthresh: " << m_ssthresh << "]";
+ }
+#endif // _DEBUGMSG
+
+ if (nAvailable == 0) {
+ if (sflags == sfNone)
+ return;
+
+ // If this is an immediate ack, or the second delayed ack
+ if ((sflags == sfImmediateAck) || m_t_ack) {
+ packet(m_snd_nxt, 0, 0, 0);
+ } else {
+ m_t_ack = Now();
+ }
+ return;
+ }
+
+ // Nagle's algorithm.
+ // If there is data already in-flight, and we haven't a full segment of
+ // data ready to send then hold off until we get more to send, or the
+ // in-flight data is acknowledged.
+ if (m_use_nagling && (m_snd_nxt > m_snd_una) && (nAvailable < m_mss)) {
+ return;
+ }
+
+ // Find the next segment to transmit
+ SList::iterator it = m_slist.begin();
+ while (it->xmit > 0) {
+ ++it;
+ ASSERT(it != m_slist.end());
+ }
+ SList::iterator seg = it;
+
+ // If the segment is too large, break it into two
+ if (seg->len > nAvailable) {
+ SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl);
+ seg->len = nAvailable;
+ m_slist.insert(++it, subseg);
+ }
+
+ if (!transmit(seg, now)) {
+ LOG_F(LS_VERBOSE) << "transmit failed";
+ // TODO: consider closing socket
+ return;
+ }
+
+ sflags = sfNone;
+ }
+}
+
+void
+PseudoTcp::closedown(uint32 err) {
+ LOG(LS_INFO) << "State: TCP_CLOSED";
+ m_state = TCP_CLOSED;
+ if (m_notify) {
+ m_notify->OnTcpClosed(this, err);
+ }
+ //notify(evClose, err);
+}
+
+void
+PseudoTcp::adjustMTU() {
+ // Determine our current mss level, so that we can adjust appropriately later
+ for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) {
+ if (static_cast<uint16>(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) {
+ break;
+ }
+ }
+ m_mss = m_mtu_advise - PACKET_OVERHEAD;
+ // !?! Should we reset m_largest here?
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ // Enforce minimums on ssthresh and cwnd
+ m_ssthresh = rtc::_max(m_ssthresh, 2 * m_mss);
+ m_cwnd = rtc::_max(m_cwnd, m_mss);
+}
+
+bool
+PseudoTcp::isReceiveBufferFull() const {
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+ return !available_space;
+}
+
+void
+PseudoTcp::disableWindowScale() {
+ m_support_wnd_scale = false;
+}
+
+void
+PseudoTcp::queueConnectMessage() {
+ rtc::ByteBuffer buf(rtc::ByteBuffer::ORDER_NETWORK);
+
+ buf.WriteUInt8(CTL_CONNECT);
+ if (m_support_wnd_scale) {
+ buf.WriteUInt8(TCP_OPT_WND_SCALE);
+ buf.WriteUInt8(1);
+ buf.WriteUInt8(m_rwnd_scale);
+ }
+ m_snd_wnd = static_cast<uint32>(buf.Length());
+ queue(buf.Data(), static_cast<uint32>(buf.Length()), true);
+}
+
+void
+PseudoTcp::parseOptions(const char* data, uint32 len) {
+ std::set<uint8> options_specified;
+
+ // See http://www.freesoft.org/CIE/Course/Section4/8.htm for
+ // parsing the options list.
+ rtc::ByteBuffer buf(data, len);
+ while (buf.Length()) {
+ uint8 kind = TCP_OPT_EOL;
+ buf.ReadUInt8(&kind);
+
+ if (kind == TCP_OPT_EOL) {
+ // End of option list.
+ break;
+ } else if (kind == TCP_OPT_NOOP) {
+ // No op.
+ continue;
+ }
+
+ // Length of this option.
+ ASSERT(len != 0);
+ RTC_UNUSED(len);
+ uint8 opt_len = 0;
+ buf.ReadUInt8(&opt_len);
+
+ // Content of this option.
+ if (opt_len <= buf.Length()) {
+ applyOption(kind, buf.Data(), opt_len);
+ buf.Consume(opt_len);
+ } else {
+ LOG(LS_ERROR) << "Invalid option length received.";
+ return;
+ }
+ options_specified.insert(kind);
+ }
+
+ if (options_specified.find(TCP_OPT_WND_SCALE) == options_specified.end()) {
+ LOG(LS_WARNING) << "Peer doesn't support window scaling";
+
+ if (m_rwnd_scale > 0) {
+ // Peer doesn't support TCP options and window scaling.
+ // Revert receive buffer size to default value.
+ resizeReceiveBuffer(DEFAULT_RCV_BUF_SIZE);
+ m_swnd_scale = 0;
+ }
+ }
+}
+
+void
+PseudoTcp::applyOption(char kind, const char* data, uint32 len) {
+ if (kind == TCP_OPT_MSS) {
+ LOG(LS_WARNING) << "Peer specified MSS option which is not supported.";
+ // TODO: Implement.
+ } else if (kind == TCP_OPT_WND_SCALE) {
+ // Window scale factor.
+ // http://www.ietf.org/rfc/rfc1323.txt
+ if (len != 1) {
+ LOG_F(WARNING) << "Invalid window scale option received.";
+ return;
+ }
+ applyWindowScaleOption(data[0]);
+ }
+}
+
+void
+PseudoTcp::applyWindowScaleOption(uint8 scale_factor) {
+ m_swnd_scale = scale_factor;
+}
+
+void
+PseudoTcp::resizeSendBuffer(uint32 new_size) {
+ m_sbuf_len = new_size;
+ m_sbuf.SetCapacity(new_size);
+}
+
+void
+PseudoTcp::resizeReceiveBuffer(uint32 new_size) {
+ uint8 scale_factor = 0;
+
+ // Determine the scale factor such that the scaled window size can fit
+ // in a 16-bit unsigned integer.
+ while (new_size > 0xFFFF) {
+ ++scale_factor;
+ new_size >>= 1;
+ }
+
+ // Determine the proper size of the buffer.
+ new_size <<= scale_factor;
+ bool result = m_rbuf.SetCapacity(new_size);
+
+ // Make sure the new buffer is large enough to contain data in the old
+ // buffer. This should always be true because this method is called either
+ // before connection is established or when peers are exchanging connect
+ // messages.
+ ASSERT(result);
+ RTC_UNUSED(result);
+ m_rbuf_len = new_size;
+ m_rwnd_scale = scale_factor;
+ m_ssthresh = new_size;
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+ m_rcv_wnd = static_cast<uint32>(available_space);
+}
+
+} // namespace cricket
diff --git a/p2p/base/pseudotcp.h b/p2p/base/pseudotcp.h
new file mode 100644
index 00000000..b2cfcb79
--- /dev/null
+++ b/p2p/base/pseudotcp.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_PSEUDOTCP_H_
+#define WEBRTC_P2P_BASE_PSEUDOTCP_H_
+
+#include <list>
+
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/stream.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// IPseudoTcpNotify
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp;
+
+class IPseudoTcpNotify {
+ public:
+ // Notification of tcp events
+ virtual void OnTcpOpen(PseudoTcp* tcp) = 0;
+ virtual void OnTcpReadable(PseudoTcp* tcp) = 0;
+ virtual void OnTcpWriteable(PseudoTcp* tcp) = 0;
+ virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) = 0;
+
+ // Write the packet onto the network
+ enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL };
+ virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer, size_t len) = 0;
+
+ protected:
+ virtual ~IPseudoTcpNotify() {}
+};
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp {
+ public:
+ static uint32 Now();
+
+ PseudoTcp(IPseudoTcpNotify* notify, uint32 conv);
+ virtual ~PseudoTcp();
+
+ int Connect();
+ int Recv(char* buffer, size_t len);
+ int Send(const char* buffer, size_t len);
+ void Close(bool force);
+ int GetError();
+
+ enum TcpState {
+ TCP_LISTEN, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_CLOSED
+ };
+ TcpState State() const { return m_state; }
+
+ // Call this when the PMTU changes.
+ void NotifyMTU(uint16 mtu);
+
+ // Call this based on timeout value returned from GetNextClock.
+ // It's ok to call this too frequently.
+ void NotifyClock(uint32 now);
+
+ // Call this whenever a packet arrives.
+ // Returns true if the packet was processed successfully.
+ bool NotifyPacket(const char * buffer, size_t len);
+
+ // Call this to determine the next time NotifyClock should be called.
+ // Returns false if the socket is ready to be destroyed.
+ bool GetNextClock(uint32 now, long& timeout);
+
+ // Call these to get/set option values to tailor this PseudoTcp
+ // instance's behaviour for the kind of data it will carry.
+ // If an unrecognized option is set or got, an assertion will fire.
+ //
+ // Setting options for OPT_RCVBUF or OPT_SNDBUF after Connect() is called
+ // will result in an assertion.
+ enum Option {
+ OPT_NODELAY, // Whether to enable Nagle's algorithm (0 == off)
+ OPT_ACKDELAY, // The Delayed ACK timeout (0 == off).
+ OPT_RCVBUF, // Set the receive buffer size, in bytes.
+ OPT_SNDBUF, // Set the send buffer size, in bytes.
+ };
+ void GetOption(Option opt, int* value);
+ void SetOption(Option opt, int value);
+
+ // Returns current congestion window in bytes.
+ uint32 GetCongestionWindow() const;
+
+ // Returns amount of data in bytes that has been sent, but haven't
+ // been acknowledged.
+ uint32 GetBytesInFlight() const;
+
+ // Returns number of bytes that were written in buffer and haven't
+ // been sent.
+ uint32 GetBytesBufferedNotSent() const;
+
+ // Returns current round-trip time estimate in milliseconds.
+ uint32 GetRoundTripTimeEstimateMs() const;
+
+ protected:
+ enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck };
+
+ struct Segment {
+ uint32 conv, seq, ack;
+ uint8 flags;
+ uint16 wnd;
+ const char * data;
+ uint32 len;
+ uint32 tsval, tsecr;
+ };
+
+ struct SSegment {
+ SSegment(uint32 s, uint32 l, bool c)
+ : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) {
+ }
+ uint32 seq, len;
+ //uint32 tstamp;
+ uint8 xmit;
+ bool bCtrl;
+ };
+ typedef std::list<SSegment> SList;
+
+ struct RSegment {
+ uint32 seq, len;
+ };
+
+ uint32 queue(const char* data, uint32 len, bool bCtrl);
+
+ // Creates a packet and submits it to the network. This method can either
+ // send payload or just an ACK packet.
+ //
+ // |seq| is the sequence number of this packet.
+ // |flags| is the flags for sending this packet.
+ // |offset| is the offset to read from |m_sbuf|.
+ // |len| is the number of bytes to read from |m_sbuf| as payload. If this
+ // value is 0 then this is an ACK packet, otherwise this packet has payload.
+ IPseudoTcpNotify::WriteResult packet(uint32 seq, uint8 flags,
+ uint32 offset, uint32 len);
+ bool parse(const uint8* buffer, uint32 size);
+
+ void attemptSend(SendFlags sflags = sfNone);
+
+ void closedown(uint32 err = 0);
+
+ bool clock_check(uint32 now, long& nTimeout);
+
+ bool process(Segment& seg);
+ bool transmit(const SList::iterator& seg, uint32 now);
+
+ void adjustMTU();
+
+ protected:
+ // This method is used in test only to query receive buffer state.
+ bool isReceiveBufferFull() const;
+
+ // This method is only used in tests, to disable window scaling
+ // support for testing backward compatibility.
+ void disableWindowScale();
+
+ private:
+ // Queue the connect message with TCP options.
+ void queueConnectMessage();
+
+ // Parse TCP options in the header.
+ void parseOptions(const char* data, uint32 len);
+
+ // Apply a TCP option that has been read from the header.
+ void applyOption(char kind, const char* data, uint32 len);
+
+ // Apply window scale option.
+ void applyWindowScaleOption(uint8 scale_factor);
+
+ // Resize the send buffer with |new_size| in bytes.
+ void resizeSendBuffer(uint32 new_size);
+
+ // Resize the receive buffer with |new_size| in bytes. This call adjusts
+ // window scale factor |m_swnd_scale| accordingly.
+ void resizeReceiveBuffer(uint32 new_size);
+
+ IPseudoTcpNotify* m_notify;
+ enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown;
+ int m_error;
+
+ // TCB data
+ TcpState m_state;
+ uint32 m_conv;
+ bool m_bReadEnable, m_bWriteEnable, m_bOutgoing;
+ uint32 m_lasttraffic;
+
+ // Incoming data
+ typedef std::list<RSegment> RList;
+ RList m_rlist;
+ uint32 m_rbuf_len, m_rcv_nxt, m_rcv_wnd, m_lastrecv;
+ uint8 m_rwnd_scale; // Window scale factor.
+ rtc::FifoBuffer m_rbuf;
+
+ // Outgoing data
+ SList m_slist;
+ uint32 m_sbuf_len, m_snd_nxt, m_snd_wnd, m_lastsend, m_snd_una;
+ uint8 m_swnd_scale; // Window scale factor.
+ rtc::FifoBuffer m_sbuf;
+
+ // Maximum segment size, estimated protocol level, largest segment sent
+ uint32 m_mss, m_msslevel, m_largest, m_mtu_advise;
+ // Retransmit timer
+ uint32 m_rto_base;
+
+ // Timestamp tracking
+ uint32 m_ts_recent, m_ts_lastack;
+
+ // Round-trip calculation
+ uint32 m_rx_rttvar, m_rx_srtt, m_rx_rto;
+
+ // Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
+ uint32 m_ssthresh, m_cwnd;
+ uint8 m_dup_acks;
+ uint32 m_recover;
+ uint32 m_t_ack;
+
+ // Configuration options
+ bool m_use_nagling;
+ uint32 m_ack_delay;
+
+ // This is used by unit tests to test backward compatibility of
+ // PseudoTcp implementations that don't support window scaling.
+ bool m_support_wnd_scale;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_PSEUDOTCP_H_
diff --git a/p2p/base/pseudotcp_unittest.cc b/p2p/base/pseudotcp_unittest.cc
new file mode 100644
index 00000000..f5ea7ace
--- /dev/null
+++ b/p2p/base/pseudotcp_unittest.cc
@@ -0,0 +1,841 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <vector>
+
+#include "webrtc/p2p/base/pseudotcp.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/messagehandler.h"
+#include "webrtc/base/stream.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/timeutils.h"
+
+using cricket::PseudoTcp;
+
+static const int kConnectTimeoutMs = 10000; // ~3 * default RTO of 3000ms
+static const int kTransferTimeoutMs = 15000;
+static const int kBlockSize = 4096;
+
+class PseudoTcpForTest : public cricket::PseudoTcp {
+ public:
+ PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32 conv)
+ : PseudoTcp(notify, conv) {
+ }
+
+ bool isReceiveBufferFull() const {
+ return PseudoTcp::isReceiveBufferFull();
+ }
+
+ void disableWindowScale() {
+ PseudoTcp::disableWindowScale();
+ }
+};
+
+class PseudoTcpTestBase : public testing::Test,
+ public rtc::MessageHandler,
+ public cricket::IPseudoTcpNotify {
+ public:
+ PseudoTcpTestBase()
+ : local_(this, 1),
+ remote_(this, 1),
+ have_connected_(false),
+ have_disconnected_(false),
+ local_mtu_(65535),
+ remote_mtu_(65535),
+ delay_(0),
+ loss_(0) {
+ // Set use of the test RNG to get predictable loss patterns.
+ rtc::SetRandomTestMode(true);
+ }
+ ~PseudoTcpTestBase() {
+ // Put it back for the next test.
+ rtc::SetRandomTestMode(false);
+ }
+ void SetLocalMtu(int mtu) {
+ local_.NotifyMTU(mtu);
+ local_mtu_ = mtu;
+ }
+ void SetRemoteMtu(int mtu) {
+ remote_.NotifyMTU(mtu);
+ remote_mtu_ = mtu;
+ }
+ void SetDelay(int delay) {
+ delay_ = delay;
+ }
+ void SetLoss(int percent) {
+ loss_ = percent;
+ }
+ void SetOptNagling(bool enable_nagles) {
+ local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ }
+ void SetOptAckDelay(int ack_delay) {
+ local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ }
+ void SetOptSndBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ remote_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ }
+ void SetRemoteOptRcvBuf(int size) {
+ remote_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void SetLocalOptRcvBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void DisableRemoteWindowScale() {
+ remote_.disableWindowScale();
+ }
+ void DisableLocalWindowScale() {
+ local_.disableWindowScale();
+ }
+
+ protected:
+ int Connect() {
+ int ret = local_.Connect();
+ if (ret == 0) {
+ UpdateLocalClock();
+ }
+ return ret;
+ }
+ void Close() {
+ local_.Close(false);
+ UpdateLocalClock();
+ }
+
+ enum { MSG_LPACKET, MSG_RPACKET, MSG_LCLOCK, MSG_RCLOCK, MSG_IOCOMPLETE,
+ MSG_WRITE};
+ virtual void OnTcpOpen(PseudoTcp* tcp) {
+ // Consider ourselves connected when the local side gets OnTcpOpen.
+ // OnTcpWriteable isn't fired at open, so we trigger it now.
+ LOG(LS_VERBOSE) << "Opened";
+ if (tcp == &local_) {
+ have_connected_ = true;
+ OnTcpWriteable(tcp);
+ }
+ }
+ // Test derived from the base should override
+ // virtual void OnTcpReadable(PseudoTcp* tcp)
+ // and
+ // virtual void OnTcpWritable(PseudoTcp* tcp)
+ virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) {
+ // Consider ourselves closed when the remote side gets OnTcpClosed.
+ // TODO: OnTcpClosed is only ever notified in case of error in
+ // the current implementation. Solicited close is not (yet) supported.
+ LOG(LS_VERBOSE) << "Closed";
+ EXPECT_EQ(0U, error);
+ if (tcp == &remote_) {
+ have_disconnected_ = true;
+ }
+ }
+ virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer, size_t len) {
+ // Randomly drop the desired percentage of packets.
+ // Also drop packets that are larger than the configured MTU.
+ if (rtc::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
+ LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
+ } else if (len > static_cast<size_t>(
+ rtc::_min(local_mtu_, remote_mtu_))) {
+ LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size=" << len;
+ } else {
+ int id = (tcp == &local_) ? MSG_RPACKET : MSG_LPACKET;
+ std::string packet(buffer, len);
+ rtc::Thread::Current()->PostDelayed(delay_, this, id,
+ rtc::WrapMessageData(packet));
+ }
+ return WR_SUCCESS;
+ }
+
+ void UpdateLocalClock() { UpdateClock(&local_, MSG_LCLOCK); }
+ void UpdateRemoteClock() { UpdateClock(&remote_, MSG_RCLOCK); }
+ void UpdateClock(PseudoTcp* tcp, uint32 message) {
+ long interval = 0; // NOLINT
+ tcp->GetNextClock(PseudoTcp::Now(), interval);
+ interval = rtc::_max<int>(interval, 0L); // sometimes interval is < 0
+ rtc::Thread::Current()->Clear(this, message);
+ rtc::Thread::Current()->PostDelayed(interval, this, message);
+ }
+
+ virtual void OnMessage(rtc::Message* message) {
+ switch (message->message_id) {
+ case MSG_LPACKET: {
+ const std::string& s(
+ rtc::UseMessageData<std::string>(message->pdata));
+ local_.NotifyPacket(s.c_str(), s.size());
+ UpdateLocalClock();
+ break;
+ }
+ case MSG_RPACKET: {
+ const std::string& s(
+ rtc::UseMessageData<std::string>(message->pdata));
+ remote_.NotifyPacket(s.c_str(), s.size());
+ UpdateRemoteClock();
+ break;
+ }
+ case MSG_LCLOCK:
+ local_.NotifyClock(PseudoTcp::Now());
+ UpdateLocalClock();
+ break;
+ case MSG_RCLOCK:
+ remote_.NotifyClock(PseudoTcp::Now());
+ UpdateRemoteClock();
+ break;
+ default:
+ break;
+ }
+ delete message->pdata;
+ }
+
+ PseudoTcpForTest local_;
+ PseudoTcpForTest remote_;
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+ bool have_connected_;
+ bool have_disconnected_;
+ int local_mtu_;
+ int remote_mtu_;
+ int delay_;
+ int loss_;
+};
+
+class PseudoTcpTest : public PseudoTcpTestBase {
+ public:
+ void TestTransfer(int size) {
+ uint32 start, elapsed;
+ size_t received;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = rtc::Time();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and complete when all data has
+ // been received.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = rtc::TimeSince(start);
+ recv_stream_.GetSize(&received);
+ // Ensure we closed down OK and we got the right data.
+ // TODO: Ensure the errors are cleared properly.
+ //EXPECT_EQ(0, local_.GetError());
+ //EXPECT_EQ(0, remote_.GetError());
+ EXPECT_EQ(static_cast<size_t>(size), received);
+ EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+ recv_stream_.GetBuffer(), size));
+ LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed
+ << " ms (" << size * 8 / elapsed << " Kbps)";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ // Stream bytes to the recv stream as they arrive.
+ if (tcp == &remote_) {
+ ReadData();
+
+ // TODO: OnTcpClosed() is currently only notified on error -
+ // there is no on-the-wire equivalent of TCP FIN.
+ // So we fake the notification when all the data has been read.
+ size_t received, required;
+ recv_stream_.GetPosition(&received);
+ send_stream_.GetSize(&required);
+ if (received == required)
+ OnTcpClosed(&remote_, 0);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ if (tcp == &local_) {
+ LOG(LS_VERBOSE) << "Flow Control Lifted";
+ bool done;
+ WriteData(&done);
+ if (done) {
+ Close();
+ }
+ }
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData(bool* done) {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+ rtc::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ *done = (tosend == 0);
+ }
+
+ private:
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+};
+
+
+class PseudoTcpTestPingPong : public PseudoTcpTestBase {
+ public:
+ PseudoTcpTestPingPong()
+ : iterations_remaining_(0),
+ sender_(NULL),
+ receiver_(NULL),
+ bytes_per_send_(0) {
+ }
+ void SetBytesPerSend(int bytes) {
+ bytes_per_send_ = bytes;
+ }
+ void TestPingPong(int size, int iterations) {
+ uint32 start, elapsed;
+ iterations_remaining_ = iterations;
+ receiver_ = &remote_;
+ sender_ = &local_;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = rtc::Time();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and stop when the required
+ // number of iterations have completed.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = rtc::TimeSince(start);
+ LOG(LS_INFO) << "Performed " << iterations << " pings in "
+ << elapsed << " ms";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ if (tcp != receiver_) {
+ LOG_F(LS_ERROR) << "unexpected OnTcpReadable";
+ return;
+ }
+ // Stream bytes to the recv stream as they arrive.
+ ReadData();
+ // If we've received the desired amount of data, rewind things
+ // and send it back the other way!
+ size_t position, desired;
+ recv_stream_.GetPosition(&position);
+ send_stream_.GetSize(&desired);
+ if (position == desired) {
+ if (receiver_ == &local_ && --iterations_remaining_ == 0) {
+ Close();
+ // TODO: Fake OnTcpClosed() on the receiver for now.
+ OnTcpClosed(&remote_, 0);
+ return;
+ }
+ PseudoTcp* tmp = receiver_;
+ receiver_ = sender_;
+ sender_ = tmp;
+ recv_stream_.Rewind();
+ send_stream_.Rewind();
+ OnTcpWriteable(sender_);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ if (tcp != sender_)
+ return;
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ LOG(LS_VERBOSE) << "Flow Control Lifted";
+ WriteData();
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = receiver_->Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block);
+ if (send_stream_.Read(block, tosend, &tosend, NULL) !=
+ rtc::SR_EOS) {
+ sent = sender_->Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ }
+
+ private:
+ int iterations_remaining_;
+ PseudoTcp* sender_;
+ PseudoTcp* receiver_;
+ int bytes_per_send_;
+};
+
+// Fill the receiver window until it is full, drain it and then
+// fill it with the same amount. This is to test that receiver window
+// contracts and enlarges correctly.
+class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase {
+ public:
+ // Not all the data are transfered, |size| just need to be big enough
+ // to fill up the receiver window twice.
+ void TestTransfer(int size) {
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+
+ // Connect and wait until connected.
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+
+ rtc::Thread::Current()->Post(this, MSG_WRITE);
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+
+ ASSERT_EQ(2u, send_position_.size());
+ ASSERT_EQ(2u, recv_position_.size());
+
+ const size_t estimated_recv_window = EstimateReceiveWindowSize();
+
+ // The difference in consecutive send positions should equal the
+ // receive window size or match very closely. This verifies that receive
+ // window is open after receiver drained all the data.
+ const size_t send_position_diff = send_position_[1] - send_position_[0];
+ EXPECT_GE(1024u, estimated_recv_window - send_position_diff);
+
+ // Receiver drained the receive window twice.
+ EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]);
+ }
+
+ virtual void OnMessage(rtc::Message* message) {
+ int message_id = message->message_id;
+ PseudoTcpTestBase::OnMessage(message);
+
+ switch (message_id) {
+ case MSG_WRITE: {
+ WriteData();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ uint32 EstimateReceiveWindowSize() const {
+ return static_cast<uint32>(recv_position_[0]);
+ }
+
+ uint32 EstimateSendWindowSize() const {
+ return static_cast<uint32>(send_position_[0] - recv_position_[0]);
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ }
+
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ }
+
+ void ReadUntilIOPending() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+
+ recv_stream_.GetPosition(&position);
+ recv_position_.push_back(position);
+
+ // Disconnect if we have done two transfers.
+ if (recv_position_.size() == 2u) {
+ Close();
+ OnTcpClosed(&remote_, 0);
+ } else {
+ WriteData();
+ }
+ }
+
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+ rtc::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ // At this point, we've filled up the available space in the send queue.
+
+ int message_queue_size =
+ static_cast<int>(rtc::Thread::Current()->size());
+ // The message queue will always have at least 2 messages, an RCLOCK and
+ // an LCLOCK, since they are added back on the delay queue at the same time
+ // they are pulled off and therefore are never really removed.
+ if (message_queue_size > 2) {
+ // If there are non-clock messages remaining, attempt to continue sending
+ // after giving those messages time to process, which should free up the
+ // send buffer.
+ rtc::Thread::Current()->PostDelayed(10, this, MSG_WRITE);
+ } else {
+ if (!remote_.isReceiveBufferFull()) {
+ LOG(LS_ERROR) << "This shouldn't happen - the send buffer is full, "
+ << "the receive buffer is not, and there are no "
+ << "remaining messages to process.";
+ }
+ send_stream_.GetPosition(&position);
+ send_position_.push_back(position);
+
+ // Drain the receiver buffer.
+ ReadUntilIOPending();
+ }
+ }
+
+ private:
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+
+ std::vector<size_t> send_position_;
+ std::vector<size_t> recv_position_;
+};
+
+// Basic end-to-end data transfer tests
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(PseudoTcpTest, TestSend) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestTransfer(1000000);
+}
+
+// Test sending data with a 50 ms RTT. Transmission should take longer due
+// to a slower ramp-up in send rate.
+TEST_F(PseudoTcpTest, TestSendWithDelay) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ TestTransfer(1000000);
+}
+
+// Test sending data with packet loss. Transmission should take much longer due
+// to send back-off when loss occurs.
+TEST_F(PseudoTcpTest, TestSendWithLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should
+// take much longer due to send back-off and slower detection of loss.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Nagling disabled. Transmission
+// should take about the same time as with Nagling enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Delayed ACK disabled.
+// Transmission should be slightly faster than with it enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptAckDelay(0);
+ TestTransfer(100000);
+}
+
+// Test sending data with 50ms delay and Nagling disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 50ms delay and Delayed ACK disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptAckDelay(0);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test a large receive buffer with a sender that doesn't support scaling.
+TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLocalOptRcvBuf(100000);
+ DisableRemoteWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test a large sender-side receive buffer with a receiver that doesn't support
+// scaling.
+TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ DisableLocalWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test when both sides use window scaling.
+TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1000000);
+}
+
+// Test using a large window scale value.
+TEST_F(PseudoTcpTest, TestSendLargeInFlight) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ SetOptSndBuf(150000);
+ TestTransfer(1000000);
+}
+
+TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(1000000);
+ SetLocalOptRcvBuf(1000000);
+ TestTransfer(10000000);
+}
+
+// Test using a small receive buffer.
+TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(10000);
+ SetLocalOptRcvBuf(10000);
+ TestTransfer(1000000);
+}
+
+// Test using a very small receive buffer.
+TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100);
+ SetLocalOptRcvBuf(100);
+ TestTransfer(100000);
+}
+
+// Ping-pong (request/response) tests
+
+// Test sending <= 1x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(100, 100);
+}
+
+// Test sending 2x-3x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(400, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong.
+// Should take ~1s, due to interaction between Nagling and Delayed ACK.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(2000, 5);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(0);
+ TestPingPong(2000, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ TestPingPong(2000, 5);
+}
+
+// Test sending a ping as pair of short (non-full) segments.
+// Should take ~1s, due to Delayed ACK interaction with Nagling.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(5000);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending ping as a pair of short (non-full) segments, with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK.
+// Should take ~1s.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ SetOptAckDelay(0);
+ TestPingPong(100, 5);
+}
+
+// Test that receive window expands and contract correctly.
+TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ TestTransfer(1024 * 1000);
+}
+
+// Test setting send window size to a very small value.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetOptSndBuf(900);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(900u, EstimateSendWindowSize());
+}
+
+// Test setting receive window size to a value other than default.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(100000u, EstimateReceiveWindowSize());
+}
+
+/* Test sending data with mismatched MTUs. We should detect this and reduce
+// our packet size accordingly.
+// TODO: This doesn't actually work right now. The current code
+// doesn't detect if the MTU is set too high on either side.
+TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1280);
+ TestTransfer(1000000);
+}
+*/
diff --git a/p2p/base/rawtransport.cc b/p2p/base/rawtransport.cc
new file mode 100644
index 00000000..374ed984
--- /dev/null
+++ b/p2p/base/rawtransport.cc
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/rawtransport.h"
+#include "webrtc/p2p/base/rawtransportchannel.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/common.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+RawTransport::RawTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* allocator)
+ : Transport(signaling_thread, worker_thread,
+ content_name, NS_GINGLE_RAW, allocator) {
+}
+
+RawTransport::~RawTransport() {
+ DestroyAllChannels();
+}
+
+bool RawTransport::ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidates* candidates,
+ ParseError* error) {
+ for (const buzz::XmlElement* cand_elem = elem->FirstElement();
+ cand_elem != NULL;
+ cand_elem = cand_elem->NextElement()) {
+ if (cand_elem->Name() == QN_GINGLE_RAW_CHANNEL) {
+ if (!cand_elem->HasAttr(buzz::QN_NAME)) {
+ return BadParse("no channel name given", error);
+ }
+ if (type() != cand_elem->Attr(buzz::QN_NAME)) {
+ return BadParse("channel named does not exist", error);
+ }
+ rtc::SocketAddress addr;
+ if (!ParseRawAddress(cand_elem, &addr, error))
+ return false;
+
+ Candidate candidate;
+ candidate.set_component(1);
+ candidate.set_address(addr);
+ candidates->push_back(candidate);
+ }
+ }
+ return true;
+}
+
+bool RawTransport::WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ const CandidateTranslator* translator,
+ XmlElements* candidate_elems,
+ WriteError* error) {
+ for (std::vector<Candidate>::const_iterator
+ cand = candidates.begin();
+ cand != candidates.end();
+ ++cand) {
+ ASSERT(cand->component() == 1);
+ ASSERT(cand->protocol() == "udp");
+ rtc::SocketAddress addr = cand->address();
+
+ buzz::XmlElement* elem = new buzz::XmlElement(QN_GINGLE_RAW_CHANNEL);
+ elem->SetAttr(buzz::QN_NAME, type());
+ elem->SetAttr(QN_ADDRESS, addr.ipaddr().ToString());
+ elem->SetAttr(QN_PORT, addr.PortAsString());
+ candidate_elems->push_back(elem);
+ }
+ return true;
+}
+
+bool RawTransport::ParseRawAddress(const buzz::XmlElement* elem,
+ rtc::SocketAddress* addr,
+ ParseError* error) {
+ // Make sure the required attributes exist
+ if (!elem->HasAttr(QN_ADDRESS) ||
+ !elem->HasAttr(QN_PORT)) {
+ return BadParse("channel missing required attribute", error);
+ }
+
+ // Parse the address.
+ if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, addr, error))
+ return false;
+
+ return true;
+}
+
+TransportChannelImpl* RawTransport::CreateTransportChannel(int component) {
+ return new RawTransportChannel(content_name(), component, this,
+ worker_thread(),
+ port_allocator());
+}
+
+void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+ delete channel;
+}
+
+} // namespace cricket
+#endif // defined(FEATURE_ENABLE_PSTN)
diff --git a/p2p/base/rawtransport.h b/p2p/base/rawtransport.h
new file mode 100644
index 00000000..dbe8f986
--- /dev/null
+++ b/p2p/base/rawtransport.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_RAWTRANSPORT_H_
+#define WEBRTC_P2P_BASE_RAWTRANSPORT_H_
+
+#include <string>
+#include "webrtc/p2p/base/transport.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+// Implements a transport that only sends raw packets, no STUN. As a result,
+// it cannot do pings to determine connectivity, so it only uses a single port
+// that it thinks will work.
+class RawTransport : public Transport, public TransportParser {
+ public:
+ RawTransport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ PortAllocator* allocator);
+ virtual ~RawTransport();
+
+ virtual bool ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidates* candidates,
+ ParseError* error);
+ virtual bool WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ const CandidateTranslator* translator,
+ XmlElements* candidate_elems,
+ WriteError* error);
+
+ protected:
+ // Creates and destroys raw channels.
+ virtual TransportChannelImpl* CreateTransportChannel(int component);
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ private:
+ // Parses the given element, which should describe the address to use for a
+ // given channel. This will return false and signal an error if the address
+ // or channel name is bad.
+ bool ParseRawAddress(const buzz::XmlElement* elem,
+ rtc::SocketAddress* addr,
+ ParseError* error);
+
+ friend class RawTransportChannel; // For ParseAddress.
+
+ DISALLOW_EVIL_CONSTRUCTORS(RawTransport);
+};
+
+} // namespace cricket
+
+#endif // defined(FEATURE_ENABLE_PSTN)
+
+#endif // WEBRTC_P2P_BASE_RAWTRANSPORT_H_
diff --git a/p2p/base/rawtransportchannel.cc b/p2p/base/rawtransportchannel.cc
new file mode 100644
index 00000000..5779c6e5
--- /dev/null
+++ b/p2p/base/rawtransportchannel.cc
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/rawtransportchannel.h"
+
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/p2p/base/rawtransport.h"
+#include "webrtc/p2p/base/relayport.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/p2p/base/stunport.h"
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/common.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace {
+
+const uint32 MSG_DESTROY_RTC_UNUSED_PORTS = 1;
+
+} // namespace
+
+namespace cricket {
+
+RawTransportChannel::RawTransportChannel(const std::string& content_name,
+ int component,
+ RawTransport* transport,
+ rtc::Thread *worker_thread,
+ PortAllocator *allocator)
+ : TransportChannelImpl(content_name, component),
+ raw_transport_(transport),
+ allocator_(allocator),
+ allocator_session_(NULL),
+ stun_port_(NULL),
+ relay_port_(NULL),
+ port_(NULL),
+ use_relay_(false) {
+ if (worker_thread == NULL)
+ worker_thread_ = raw_transport_->worker_thread();
+ else
+ worker_thread_ = worker_thread;
+}
+
+RawTransportChannel::~RawTransportChannel() {
+ delete allocator_session_;
+}
+
+int RawTransportChannel::SendPacket(const char *data, size_t size,
+ const rtc::PacketOptions& options,
+ int flags) {
+ if (port_ == NULL)
+ return -1;
+ if (remote_address_.IsNil())
+ return -1;
+ if (flags != 0)
+ return -1;
+ return port_->SendTo(data, size, remote_address_, options, true);
+}
+
+int RawTransportChannel::SetOption(rtc::Socket::Option opt, int value) {
+ // TODO: allow these to be set before we have a port
+ if (port_ == NULL)
+ return -1;
+ return port_->SetOption(opt, value);
+}
+
+int RawTransportChannel::GetError() {
+ return (port_ != NULL) ? port_->GetError() : 0;
+}
+
+void RawTransportChannel::Connect() {
+ // Create an allocator that only returns stun and relay ports.
+ // Use empty string for ufrag and pwd here. There won't be any STUN or relay
+ // interactions when using RawTC.
+ // TODO: Change raw to only use local udp ports.
+ allocator_session_ = allocator_->CreateSession(
+ SessionId(), content_name(), component(), "", "");
+
+ uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
+
+#if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ flags |= PORTALLOCATOR_DISABLE_RELAY;
+#endif
+ allocator_session_->set_flags(flags);
+ allocator_session_->SignalPortReady.connect(
+ this, &RawTransportChannel::OnPortReady);
+ allocator_session_->SignalCandidatesReady.connect(
+ this, &RawTransportChannel::OnCandidatesReady);
+
+ // The initial ports will include stun.
+ allocator_session_->StartGettingPorts();
+}
+
+void RawTransportChannel::Reset() {
+ set_readable(false);
+ set_writable(false);
+
+ delete allocator_session_;
+
+ allocator_session_ = NULL;
+ stun_port_ = NULL;
+ relay_port_ = NULL;
+ port_ = NULL;
+ remote_address_ = rtc::SocketAddress();
+}
+
+void RawTransportChannel::OnCandidate(const Candidate& candidate) {
+ remote_address_ = candidate.address();
+ ASSERT(!remote_address_.IsNil());
+ set_readable(true);
+
+ // We can write once we have a port and a remote address.
+ if (port_ != NULL)
+ SetWritable();
+}
+
+void RawTransportChannel::OnRemoteAddress(
+ const rtc::SocketAddress& remote_address) {
+ remote_address_ = remote_address;
+ set_readable(true);
+
+ if (port_ != NULL)
+ SetWritable();
+}
+
+// Note about stun classification
+// Code to classify our NAT type and use the relay port if we are behind an
+// asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define.
+// To turn this one we will have to enable a second stun address and make sure
+// that the relay server works for raw UDP.
+//
+// Another option is to classify the NAT type early and not offer the raw
+// transport type at all if we can't support it.
+
+void RawTransportChannel::OnPortReady(
+ PortAllocatorSession* session, PortInterface* port) {
+ ASSERT(session == allocator_session_);
+
+ if (port->Type() == STUN_PORT_TYPE) {
+ stun_port_ = static_cast<StunPort*>(port);
+ } else if (port->Type() == RELAY_PORT_TYPE) {
+ relay_port_ = static_cast<RelayPort*>(port);
+ } else {
+ ASSERT(false);
+ }
+}
+
+void RawTransportChannel::OnCandidatesReady(
+ PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+ ASSERT(session == allocator_session_);
+ ASSERT(candidates.size() >= 1);
+
+ // The most recent candidate is the one we haven't seen yet.
+ Candidate c = candidates[candidates.size() - 1];
+
+ if (c.type() == STUN_PORT_TYPE) {
+ ASSERT(stun_port_ != NULL);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ // We need to wait until we have two addresses.
+ if (stun_port_->candidates().size() < 2)
+ return;
+
+ // This is the second address. If these addresses are the same, then we
+ // are not behind a symmetric NAT. Hence, a stun port should be sufficient.
+ if (stun_port_->candidates()[0].address() ==
+ stun_port_->candidates()[1].address()) {
+ SetPort(stun_port_);
+ return;
+ }
+
+ // We will need to use relay.
+ use_relay_ = true;
+
+ // If we already have a relay address, we're good. Otherwise, we will need
+ // to wait until one arrives.
+ if (relay_port_->candidates().size() > 0)
+ SetPort(relay_port_);
+#else // defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ // Always use the stun port. We don't classify right now so just assume it
+ // will work fine.
+ SetPort(stun_port_);
+#endif
+ } else if (c.type() == RELAY_PORT_TYPE) {
+ if (use_relay_)
+ SetPort(relay_port_);
+ } else {
+ ASSERT(false);
+ }
+}
+
+void RawTransportChannel::SetPort(PortInterface* port) {
+ ASSERT(port_ == NULL);
+ port_ = port;
+
+ // We don't need any ports other than the one we picked.
+ allocator_session_->StopGettingPorts();
+ worker_thread_->Post(
+ this, MSG_DESTROY_RTC_UNUSED_PORTS, NULL);
+
+ // Send a message to the other client containing our address.
+
+ ASSERT(port_->Candidates().size() >= 1);
+ ASSERT(port_->Candidates()[0].protocol() == "udp");
+ SignalCandidateReady(this, port_->Candidates()[0]);
+
+ // Read all packets from this port.
+ port_->EnablePortPackets();
+ port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket);
+
+ // We can write once we have a port and a remote address.
+ if (!remote_address_.IsAny())
+ SetWritable();
+}
+
+void RawTransportChannel::SetWritable() {
+ ASSERT(port_ != NULL);
+ ASSERT(!remote_address_.IsAny());
+
+ set_writable(true);
+
+ Candidate remote_candidate;
+ remote_candidate.set_address(remote_address_);
+ SignalRouteChange(this, remote_candidate);
+}
+
+void RawTransportChannel::OnReadPacket(
+ PortInterface* port, const char* data, size_t size,
+ const rtc::SocketAddress& addr) {
+ ASSERT(port_ == port);
+ SignalReadPacket(this, data, size, rtc::CreatePacketTime(0), 0);
+}
+
+void RawTransportChannel::OnMessage(rtc::Message* msg) {
+ ASSERT(msg->message_id == MSG_DESTROY_RTC_UNUSED_PORTS);
+ ASSERT(port_ != NULL);
+ if (port_ != stun_port_) {
+ stun_port_->Destroy();
+ stun_port_ = NULL;
+ }
+ if (port_ != relay_port_ && relay_port_ != NULL) {
+ relay_port_->Destroy();
+ relay_port_ = NULL;
+ }
+}
+
+} // namespace cricket
+#endif // defined(FEATURE_ENABLE_PSTN)
diff --git a/p2p/base/rawtransportchannel.h b/p2p/base/rawtransportchannel.h
new file mode 100644
index 00000000..3041cad2
--- /dev/null
+++ b/p2p/base/rawtransportchannel.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+#define WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/rawtransport.h"
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/base/messagequeue.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace rtc {
+class Thread;
+}
+
+namespace cricket {
+
+class Connection;
+class PortAllocator;
+class PortAllocatorSession;
+class PortInterface;
+class RelayPort;
+class StunPort;
+
+// Implements a channel that just sends bare packets once we have received the
+// address of the other side. We pick a single address to send them based on
+// a simple investigation of NAT type.
+class RawTransportChannel : public TransportChannelImpl,
+ public rtc::MessageHandler {
+ public:
+ RawTransportChannel(const std::string& content_name,
+ int component,
+ RawTransport* transport,
+ rtc::Thread *worker_thread,
+ PortAllocator *allocator);
+ virtual ~RawTransportChannel();
+
+ // Implementation of normal channel packet sending.
+ virtual int SendPacket(const char *data, size_t len,
+ const rtc::PacketOptions& options, int flags);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetError();
+
+ // Implements TransportChannelImpl.
+ virtual Transport* GetTransport() { return raw_transport_; }
+ virtual void SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {}
+ virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) {}
+
+ // Creates an allocator session to start figuring out which type of
+ // port we should send to the other client. This will send
+ // SignalAvailableCandidate once we have decided.
+ virtual void Connect();
+
+ // Resets state back to unconnected.
+ virtual void Reset();
+
+ // We don't actually worry about signaling since we can't send new candidates.
+ virtual void OnSignalingReady() {}
+
+ // Handles a message setting the remote address. We are writable once we
+ // have this since we now know where to send.
+ virtual void OnCandidate(const Candidate& candidate);
+
+ void OnRemoteAddress(const rtc::SocketAddress& remote_address);
+
+ // Below ICE specific virtual methods not implemented.
+ virtual IceRole GetIceRole() const { return ICEROLE_UNKNOWN; }
+ virtual void SetIceRole(IceRole role) {}
+ virtual void SetIceTiebreaker(uint64 tiebreaker) {}
+
+ virtual bool GetIceProtocolType(IceProtocolType* type) const { return false; }
+ virtual void SetIceProtocolType(IceProtocolType type) {}
+
+ virtual void SetIceUfrag(const std::string& ice_ufrag) {}
+ virtual void SetIcePwd(const std::string& ice_pwd) {}
+ virtual void SetRemoteIceMode(IceMode mode) {}
+ virtual size_t GetConnectionCount() const { return 1; }
+
+ virtual bool GetStats(ConnectionInfos* infos) {
+ return false;
+ }
+
+ // DTLS methods.
+ virtual bool IsDtlsActive() const { return false; }
+
+ // Default implementation.
+ virtual bool GetSslRole(rtc::SSLRole* role) const {
+ return false;
+ }
+
+ virtual bool SetSslRole(rtc::SSLRole role) {
+ return false;
+ }
+
+ // Set up the ciphers to use for DTLS-SRTP.
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+ return false;
+ }
+
+ // Find out which DTLS-SRTP cipher was negotiated
+ virtual bool GetSrtpCipher(std::string* cipher) {
+ return false;
+ }
+
+ // Returns false because the channel is not DTLS.
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
+ return false;
+ }
+
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const {
+ return false;
+ }
+
+ // Allows key material to be extracted for external encryption.
+ virtual bool ExportKeyingMaterial(
+ const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ return false;
+ }
+
+ virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) {
+ return false;
+ }
+
+ // Set DTLS Remote fingerprint. Must be after local identity set.
+ virtual bool SetRemoteFingerprint(
+ const std::string& digest_alg,
+ const uint8* digest,
+ size_t digest_len) {
+ return false;
+ }
+
+ private:
+ RawTransport* raw_transport_;
+ rtc::Thread *worker_thread_;
+ PortAllocator* allocator_;
+ PortAllocatorSession* allocator_session_;
+ StunPort* stun_port_;
+ RelayPort* relay_port_;
+ PortInterface* port_;
+ bool use_relay_;
+ rtc::SocketAddress remote_address_;
+
+ // Called when the allocator creates another port.
+ void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+
+ // Called when one of the ports we are using has determined its address.
+ void OnCandidatesReady(PortAllocatorSession *session,
+ const std::vector<Candidate>& candidates);
+
+ // Called once we have chosen the port to use for communication with the
+ // other client. This will send its address and prepare the port for use.
+ void SetPort(PortInterface* port);
+
+ // Called once we have a port and a remote address. This will set mark the
+ // channel as writable and signal the route to the client.
+ void SetWritable();
+
+ // Called when we receive a packet from the other client.
+ void OnReadPacket(PortInterface* port, const char* data, size_t size,
+ const rtc::SocketAddress& addr);
+
+ // Handles a message to destroy unused ports.
+ virtual void OnMessage(rtc::Message *msg);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RawTransportChannel);
+};
+
+} // namespace cricket
+
+#endif // defined(FEATURE_ENABLE_PSTN)
+#endif // WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_
diff --git a/p2p/base/relayport.cc b/p2p/base/relayport.cc
new file mode 100644
index 00000000..4c40b3da
--- /dev/null
+++ b/p2p/base/relayport.cc
@@ -0,0 +1,818 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/relayport.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+static const uint32 kMessageConnectTimeout = 1;
+static const int kKeepAliveDelay = 10 * 60 * 1000;
+static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs
+// How long to wait for a socket to connect to remote host in milliseconds
+// before trying another connection.
+static const int kSoftConnectTimeoutMs = 3 * 1000;
+
+// Handles a connection to one address/port/protocol combination for a
+// particular RelayEntry.
+class RelayConnection : public sigslot::has_slots<> {
+ public:
+ RelayConnection(const ProtocolAddress* protocol_address,
+ rtc::AsyncPacketSocket* socket,
+ rtc::Thread* thread);
+ ~RelayConnection();
+ rtc::AsyncPacketSocket* socket() const { return socket_; }
+
+ const ProtocolAddress* protocol_address() {
+ return protocol_address_;
+ }
+
+ rtc::SocketAddress GetAddress() const {
+ return protocol_address_->address;
+ }
+
+ ProtocolType GetProtocol() const {
+ return protocol_address_->proto;
+ }
+
+ int SetSocketOption(rtc::Socket::Option opt, int value);
+
+ // Validates a response to a STUN allocate request.
+ bool CheckResponse(StunMessage* msg);
+
+ // Sends data to the relay server.
+ int Send(const void* pv, size_t cb, const rtc::PacketOptions& options);
+
+ // Sends a STUN allocate request message to the relay server.
+ void SendAllocateRequest(RelayEntry* entry, int delay);
+
+ // Return the latest error generated by the socket.
+ int GetError() { return socket_->GetError(); }
+
+ // Called on behalf of a StunRequest to write data to the socket. This is
+ // already STUN intended for the server, so no wrapping is necessary.
+ void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ private:
+ rtc::AsyncPacketSocket* socket_;
+ const ProtocolAddress* protocol_address_;
+ StunRequestManager *request_manager_;
+};
+
+// Manages a number of connections to the relayserver, one for each
+// available protocol. We aim to use each connection for only a
+// specific destination address so that we can avoid wrapping every
+// packet in a STUN send / data indication.
+class RelayEntry : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr);
+ ~RelayEntry();
+
+ RelayPort* port() { return port_; }
+
+ const rtc::SocketAddress& address() const { return ext_addr_; }
+ void set_address(const rtc::SocketAddress& addr) { ext_addr_ = addr; }
+
+ bool connected() const { return connected_; }
+ bool locked() const { return locked_; }
+
+ // Returns the last error on the socket of this entry.
+ int GetError();
+
+ // Returns the most preferred connection of the given
+ // ones. Connections are rated based on protocol in the order of:
+ // UDP, TCP and SSLTCP, where UDP is the most preferred protocol
+ static RelayConnection* GetBestConnection(RelayConnection* conn1,
+ RelayConnection* conn2);
+
+ // Sends the STUN requests to the server to initiate this connection.
+ void Connect();
+
+ // Called when this entry becomes connected. The address given is the one
+ // exposed to the outside world on the relay server.
+ void OnConnect(const rtc::SocketAddress& mapped_addr,
+ RelayConnection* socket);
+
+ // Sends a packet to the given destination address using the socket of this
+ // entry. This will wrap the packet in STUN if necessary.
+ int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options);
+
+ // Schedules a keep-alive allocate request.
+ void ScheduleKeepAlive();
+
+ void SetServerIndex(size_t sindex) { server_index_ = sindex; }
+
+ // Sets this option on the socket of each connection.
+ int SetSocketOption(rtc::Socket::Option opt, int value);
+
+ size_t ServerIndex() const { return server_index_; }
+
+ // Try a different server address
+ void HandleConnectFailure(rtc::AsyncPacketSocket* socket);
+
+ // Implementation of the MessageHandler Interface.
+ virtual void OnMessage(rtc::Message *pmsg);
+
+ private:
+ RelayPort* port_;
+ rtc::SocketAddress ext_addr_;
+ size_t server_index_;
+ bool connected_;
+ bool locked_;
+ RelayConnection* current_connection_;
+
+ // Called when a TCP connection is established or fails
+ void OnSocketConnect(rtc::AsyncPacketSocket* socket);
+ void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
+
+ // Called when a packet is received on this socket.
+ void OnReadPacket(
+ rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+ // Called when the socket is currently able to send.
+ void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ // Sends the given data on the socket to the server with no wrapping. This
+ // returns the number of bytes written or -1 if an error occurred.
+ int SendPacket(const void* data, size_t size,
+ const rtc::PacketOptions& options);
+};
+
+// Handles an allocate request for a particular RelayEntry.
+class AllocateRequest : public StunRequest {
+ public:
+ AllocateRequest(RelayEntry* entry, RelayConnection* connection);
+ virtual ~AllocateRequest() {}
+
+ virtual void Prepare(StunMessage* request);
+
+ virtual int GetNextDelay();
+
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ RelayEntry* entry_;
+ RelayConnection* connection_;
+ uint32 start_time_;
+};
+
+RelayPort::RelayPort(
+ rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password)
+ : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port,
+ username, password),
+ ready_(false),
+ error_(0) {
+ entries_.push_back(
+ new RelayEntry(this, rtc::SocketAddress()));
+ // TODO: set local preference value for TCP based candidates.
+}
+
+RelayPort::~RelayPort() {
+ for (size_t i = 0; i < entries_.size(); ++i)
+ delete entries_[i];
+ thread()->Clear(this);
+}
+
+void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
+ // Since HTTP proxies usually only allow 443,
+ // let's up the priority on PROTO_SSLTCP
+ if (addr.proto == PROTO_SSLTCP &&
+ (proxy().type == rtc::PROXY_HTTPS ||
+ proxy().type == rtc::PROXY_UNKNOWN)) {
+ server_addr_.push_front(addr);
+ } else {
+ server_addr_.push_back(addr);
+ }
+}
+
+void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
+ std::string proto_name = ProtoToString(addr.proto);
+ for (std::vector<ProtocolAddress>::iterator it = external_addr_.begin();
+ it != external_addr_.end(); ++it) {
+ if ((it->address == addr.address) && (it->proto == addr.proto)) {
+ LOG(INFO) << "Redundant relay address: " << proto_name
+ << " @ " << addr.address.ToSensitiveString();
+ return;
+ }
+ }
+ external_addr_.push_back(addr);
+}
+
+void RelayPort::SetReady() {
+ if (!ready_) {
+ std::vector<ProtocolAddress>::iterator iter;
+ for (iter = external_addr_.begin();
+ iter != external_addr_.end(); ++iter) {
+ std::string proto_name = ProtoToString(iter->proto);
+ // In case of Gturn, related address is set to null socket address.
+ // 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, 0, false);
+ }
+ ready_ = true;
+ SignalPortComplete(this);
+ }
+}
+
+const ProtocolAddress * RelayPort::ServerAddress(size_t index) const {
+ if (index < server_addr_.size())
+ return &server_addr_[index];
+ return NULL;
+}
+
+bool RelayPort::HasMagicCookie(const char* data, size_t size) {
+ if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) {
+ return false;
+ } else {
+ return memcmp(data + 24,
+ TURN_MAGIC_COOKIE_VALUE,
+ sizeof(TURN_MAGIC_COOKIE_VALUE)) == 0;
+ }
+}
+
+void RelayPort::PrepareAddress() {
+ // We initiate a connect on the first entry. If this completes, it will fill
+ // in the server address as the address of this port.
+ ASSERT(entries_.size() == 1);
+ entries_[0]->Connect();
+ ready_ = false;
+}
+
+Connection* RelayPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // We only create conns to non-udp sockets if they are incoming on this port
+ if ((address.protocol() != UDP_PROTOCOL_NAME) &&
+ (origin != ORIGIN_THIS_PORT)) {
+ return 0;
+ }
+
+ // We don't support loopback on relays
+ if (address.type() == Type()) {
+ return 0;
+ }
+
+ if (!IsCompatibleAddress(address.address())) {
+ return 0;
+ }
+
+ size_t index = 0;
+ for (size_t i = 0; i < Candidates().size(); ++i) {
+ const Candidate& local = Candidates()[i];
+ if (local.protocol() == address.protocol()) {
+ index = i;
+ break;
+ }
+ }
+
+ Connection * conn = new ProxyConnection(this, index, address);
+ AddConnection(conn);
+ return conn;
+}
+
+int RelayPort::SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ // Try to find an entry for this specific address. Note that the first entry
+ // created was not given an address initially, so it can be set to the first
+ // address that comes along.
+ RelayEntry* entry = 0;
+
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ if (entries_[i]->address().IsNil() && payload) {
+ entry = entries_[i];
+ entry->set_address(addr);
+ break;
+ } else if (entries_[i]->address() == addr) {
+ entry = entries_[i];
+ break;
+ }
+ }
+
+ // If we did not find one, then we make a new one. This will not be useable
+ // until it becomes connected, however.
+ if (!entry && payload) {
+ entry = new RelayEntry(this, addr);
+ if (!entries_.empty()) {
+ entry->SetServerIndex(entries_[0]->ServerIndex());
+ }
+ entry->Connect();
+ entries_.push_back(entry);
+ }
+
+ // If the entry is connected, then we can send on it (though wrapping may
+ // still be necessary). Otherwise, we can't yet use this connection, so we
+ // default to the first one.
+ if (!entry || !entry->connected()) {
+ ASSERT(!entries_.empty());
+ entry = entries_[0];
+ if (!entry->connected()) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ }
+
+ // Send the actual contents to the server using the usual mechanism.
+ int sent = entry->SendTo(data, size, addr, options);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = entry->GetError();
+ return SOCKET_ERROR;
+ }
+ // The caller of the function is expecting the number of user data bytes,
+ // rather than the size of the packet.
+ return static_cast<int>(size);
+}
+
+int RelayPort::SetOption(rtc::Socket::Option opt, int value) {
+ int result = 0;
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ if (entries_[i]->SetSocketOption(opt, value) < 0) {
+ result = -1;
+ error_ = entries_[i]->GetError();
+ }
+ }
+ options_.push_back(OptionValue(opt, value));
+ return result;
+}
+
+int RelayPort::GetOption(rtc::Socket::Option opt, int* value) {
+ std::vector<OptionValue>::iterator it;
+ for (it = options_.begin(); it < options_.end(); ++it) {
+ if (it->first == opt) {
+ *value = it->second;
+ return 0;
+ }
+ }
+ return SOCKET_ERROR;
+}
+
+int RelayPort::GetError() {
+ return error_;
+}
+
+void RelayPort::OnReadPacket(
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ ProtocolType proto,
+ const rtc::PacketTime& packet_time) {
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size, packet_time);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr, proto);
+ }
+}
+
+RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
+ rtc::AsyncPacketSocket* socket,
+ rtc::Thread* thread)
+ : socket_(socket),
+ protocol_address_(protocol_address) {
+ request_manager_ = new StunRequestManager(thread);
+ request_manager_->SignalSendPacket.connect(this,
+ &RelayConnection::OnSendPacket);
+}
+
+RelayConnection::~RelayConnection() {
+ delete request_manager_;
+ delete socket_;
+}
+
+int RelayConnection::SetSocketOption(rtc::Socket::Option opt,
+ int value) {
+ if (socket_) {
+ return socket_->SetOption(opt, value);
+ }
+ return 0;
+}
+
+bool RelayConnection::CheckResponse(StunMessage* msg) {
+ return request_manager_->CheckResponse(msg);
+}
+
+void RelayConnection::OnSendPacket(const void* data, size_t size,
+ StunRequest* req) {
+ // TODO(mallinath) Find a way to get DSCP value from Port.
+ rtc::PacketOptions options; // Default dscp set to NO_CHANGE.
+ int sent = socket_->SendTo(data, size, GetAddress(), options);
+ if (sent <= 0) {
+ LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " << GetAddress() <<
+ strerror(socket_->GetError());
+ ASSERT(sent < 0);
+ }
+}
+
+int RelayConnection::Send(const void* pv, size_t cb,
+ const rtc::PacketOptions& options) {
+ return socket_->SendTo(pv, cb, GetAddress(), options);
+}
+
+void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
+ request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
+}
+
+RelayEntry::RelayEntry(RelayPort* port,
+ const rtc::SocketAddress& ext_addr)
+ : port_(port), ext_addr_(ext_addr),
+ server_index_(0), connected_(false), locked_(false),
+ current_connection_(NULL) {
+}
+
+RelayEntry::~RelayEntry() {
+ // Remove all RelayConnections and dispose sockets.
+ delete current_connection_;
+ current_connection_ = NULL;
+}
+
+void RelayEntry::Connect() {
+ // If we're already connected, return.
+ if (connected_)
+ return;
+
+ // If we've exhausted all options, bail out.
+ const ProtocolAddress* ra = port()->ServerAddress(server_index_);
+ if (!ra) {
+ LOG(LS_WARNING) << "No more relay addresses left to try";
+ return;
+ }
+
+ // Remove any previous connection.
+ if (current_connection_) {
+ port()->thread()->Dispose(current_connection_);
+ current_connection_ = NULL;
+ }
+
+ // Try to set up our new socket.
+ LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) <<
+ " @ " << ra->address.ToSensitiveString();
+
+ rtc::AsyncPacketSocket* socket = NULL;
+
+ if (ra->proto == PROTO_UDP) {
+ // UDP sockets are simple.
+ socket = port_->socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(port_->ip(), 0),
+ port_->min_port(), port_->max_port());
+ } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
+ int opts = (ra->proto == PROTO_SSLTCP) ?
+ rtc::PacketSocketFactory::OPT_SSLTCP : 0;
+ socket = port_->socket_factory()->CreateClientTcpSocket(
+ rtc::SocketAddress(port_->ip(), 0), ra->address,
+ port_->proxy(), port_->user_agent(), opts);
+ } else {
+ LOG(LS_WARNING) << "Unknown protocol (" << ra->proto << ")";
+ }
+
+ if (!socket) {
+ LOG(LS_WARNING) << "Socket creation failed";
+ }
+
+ // If we failed to get a socket, move on to the next protocol.
+ if (!socket) {
+ port()->thread()->Post(this, kMessageConnectTimeout);
+ return;
+ }
+
+ // Otherwise, create the new connection and configure any socket options.
+ socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
+ socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend);
+ current_connection_ = new RelayConnection(ra, socket, port()->thread());
+ for (size_t i = 0; i < port_->options().size(); ++i) {
+ current_connection_->SetSocketOption(port_->options()[i].first,
+ port_->options()[i].second);
+ }
+
+ // If we're trying UDP, start binding requests.
+ // If we're trying TCP, wait for connection with a fixed timeout.
+ if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
+ socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
+ socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
+ port()->thread()->PostDelayed(kSoftConnectTimeoutMs, this,
+ kMessageConnectTimeout);
+ } else {
+ current_connection_->SendAllocateRequest(this, 0);
+ }
+}
+
+int RelayEntry::GetError() {
+ if (current_connection_ != NULL) {
+ return current_connection_->GetError();
+ }
+ return 0;
+}
+
+RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
+ RelayConnection* conn2) {
+ return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
+}
+
+void RelayEntry::OnConnect(const rtc::SocketAddress& mapped_addr,
+ RelayConnection* connection) {
+ // We are connected, notify our parent.
+ ProtocolType proto = PROTO_UDP;
+ LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto)
+ << " @ " << mapped_addr.ToSensitiveString();
+ connected_ = true;
+
+ port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
+ port_->SetReady();
+}
+
+int RelayEntry::SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options) {
+ // If this connection is locked to the address given, then we can send the
+ // packet with no wrapper.
+ if (locked_ && (ext_addr_ == addr))
+ return SendPacket(data, size, options);
+
+ // Otherwise, we must wrap the given data in a STUN SEND request so that we
+ // can communicate the destination address to the server.
+ //
+ // Note that we do not use a StunRequest here. This is because there is
+ // likely no reason to resend this packet. If it is late, we just drop it.
+ // The next send to this address will try again.
+
+ RelayMessage request;
+ request.SetType(STUN_SEND_REQUEST);
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE,
+ sizeof(TURN_MAGIC_COOKIE_VALUE));
+ VERIFY(request.AddAttribute(magic_cookie_attr));
+
+ StunByteStringAttribute* username_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username_attr->CopyBytes(port_->username_fragment().c_str(),
+ port_->username_fragment().size());
+ VERIFY(request.AddAttribute(username_attr));
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ addr_attr->SetIP(addr.ipaddr());
+ addr_attr->SetPort(addr.port());
+ VERIFY(request.AddAttribute(addr_attr));
+
+ // Attempt to lock
+ if (ext_addr_ == addr) {
+ StunUInt32Attribute* options_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
+ options_attr->SetValue(0x1);
+ VERIFY(request.AddAttribute(options_attr));
+ }
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ data_attr->CopyBytes(data, size);
+ VERIFY(request.AddAttribute(data_attr));
+
+ // TODO: compute the HMAC.
+
+ rtc::ByteBuffer buf;
+ request.Write(&buf);
+
+ return SendPacket(buf.Data(), buf.Length(), options);
+}
+
+void RelayEntry::ScheduleKeepAlive() {
+ if (current_connection_) {
+ current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
+ }
+}
+
+int RelayEntry::SetSocketOption(rtc::Socket::Option opt, int value) {
+ // Set the option on all available sockets.
+ int socket_error = 0;
+ if (current_connection_) {
+ socket_error = current_connection_->SetSocketOption(opt, value);
+ }
+ return socket_error;
+}
+
+void RelayEntry::HandleConnectFailure(
+ rtc::AsyncPacketSocket* socket) {
+ // Make sure it's the current connection that has failed, it might
+ // be an old socked that has not yet been disposed.
+ if (!socket ||
+ (current_connection_ && socket == current_connection_->socket())) {
+ if (current_connection_)
+ port()->SignalConnectFailure(current_connection_->protocol_address());
+
+ // Try to connect to the next server address.
+ server_index_ += 1;
+ Connect();
+ }
+}
+
+void RelayEntry::OnMessage(rtc::Message *pmsg) {
+ ASSERT(pmsg->message_id == kMessageConnectTimeout);
+ if (current_connection_) {
+ const ProtocolAddress* ra = current_connection_->protocol_address();
+ LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " <<
+ ra->address << " timed out";
+
+ // Currently we connect to each server address in sequence. If we
+ // have more addresses to try, treat this is an error and move on to
+ // the next address, otherwise give this connection more time and
+ // await the real timeout.
+ //
+ // TODO: Connect to servers in parallel to speed up connect time
+ // and to avoid giving up too early.
+ port_->SignalSoftTimeout(ra);
+ HandleConnectFailure(current_connection_->socket());
+ } else {
+ HandleConnectFailure(NULL);
+ }
+}
+
+void RelayEntry::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
+ LOG(INFO) << "relay tcp connected to " <<
+ socket->GetRemoteAddress().ToSensitiveString();
+ if (current_connection_ != NULL) {
+ current_connection_->SendAllocateRequest(this, 0);
+ }
+}
+
+void RelayEntry::OnSocketClose(rtc::AsyncPacketSocket* socket,
+ int error) {
+ PLOG(LERROR, error) << "Relay connection failed: socket closed";
+ HandleConnectFailure(socket);
+}
+
+void RelayEntry::OnReadPacket(
+ rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ // ASSERT(remote_addr == port_->server_addr());
+ // TODO: are we worried about this?
+
+ if (current_connection_ == NULL || socket != current_connection_->socket()) {
+ // This packet comes from an unknown address.
+ LOG(WARNING) << "Dropping packet: unknown address";
+ return;
+ }
+
+ // If the magic cookie is not present, then this is an unwrapped packet sent
+ // by the server, The actual remote address is the one we recorded.
+ if (!port_->HasMagicCookie(data, size)) {
+ if (locked_) {
+ port_->OnReadPacket(data, size, ext_addr_, PROTO_UDP, packet_time);
+ } else {
+ LOG(WARNING) << "Dropping packet: entry not locked";
+ }
+ return;
+ }
+
+ rtc::ByteBuffer buf(data, size);
+ RelayMessage msg;
+ if (!msg.Read(&buf)) {
+ LOG(INFO) << "Incoming packet was not STUN";
+ return;
+ }
+
+ // The incoming packet should be a STUN ALLOCATE response, SEND response, or
+ // DATA indication.
+ if (current_connection_->CheckResponse(&msg)) {
+ return;
+ } else if (msg.type() == STUN_SEND_RESPONSE) {
+ if (const StunUInt32Attribute* options_attr =
+ msg.GetUInt32(STUN_ATTR_OPTIONS)) {
+ if (options_attr->value() & 0x1) {
+ locked_ = true;
+ }
+ }
+ return;
+ } else if (msg.type() != STUN_DATA_INDICATION) {
+ LOG(INFO) << "Received BAD stun type from server: " << msg.type();
+ return;
+ }
+
+ // This must be a data indication.
+
+ const StunAddressAttribute* addr_attr =
+ msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ if (!addr_attr) {
+ LOG(INFO) << "Data indication has no source address";
+ return;
+ } else if (addr_attr->family() != 1) {
+ LOG(INFO) << "Source address has bad family";
+ return;
+ }
+
+ rtc::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
+
+ const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ LOG(INFO) << "Data indication has no data";
+ return;
+ }
+
+ // Process the actual data and remote address in the normal manner.
+ port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2,
+ PROTO_UDP, packet_time);
+}
+
+void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ if (connected()) {
+ port_->OnReadyToSend();
+ }
+}
+
+int RelayEntry::SendPacket(const void* data, size_t size,
+ const rtc::PacketOptions& options) {
+ int sent = 0;
+ if (current_connection_) {
+ // We are connected, no need to send packets anywere else than to
+ // the current connection.
+ sent = current_connection_->Send(data, size, options);
+ }
+ return sent;
+}
+
+AllocateRequest::AllocateRequest(RelayEntry* entry,
+ RelayConnection* connection)
+ : StunRequest(new RelayMessage()),
+ entry_(entry),
+ connection_(connection) {
+ start_time_ = rtc::Time();
+}
+
+void AllocateRequest::Prepare(StunMessage* request) {
+ request->SetType(STUN_ALLOCATE_REQUEST);
+
+ StunByteStringAttribute* username_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username_attr->CopyBytes(
+ entry_->port()->username_fragment().c_str(),
+ entry_->port()->username_fragment().size());
+ VERIFY(request->AddAttribute(username_attr));
+}
+
+int AllocateRequest::GetNextDelay() {
+ int delay = 100 * rtc::_max(1 << count_, 2);
+ count_ += 1;
+ if (count_ == 5)
+ timeout_ = true;
+ return delay;
+}
+
+void AllocateRequest::OnResponse(StunMessage* response) {
+ const StunAddressAttribute* addr_attr =
+ response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ if (!addr_attr) {
+ LOG(INFO) << "Allocate response missing mapped address.";
+ } else if (addr_attr->family() != 1) {
+ LOG(INFO) << "Mapped address has bad family";
+ } else {
+ rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
+ entry_->OnConnect(addr, connection_);
+ }
+
+ // We will do a keep-alive regardless of whether this request suceeds.
+ // This should have almost no impact on network usage.
+ entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ if (!attr) {
+ LOG(INFO) << "Bad allocate response error code";
+ } else {
+ LOG(INFO) << "Allocate error response:"
+ << " code=" << attr->code()
+ << " reason='" << attr->reason() << "'";
+ }
+
+ if (rtc::TimeSince(start_time_) <= kRetryTimeout)
+ entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnTimeout() {
+ LOG(INFO) << "Allocate request timed out";
+ entry_->HandleConnectFailure(connection_->socket());
+}
+
+} // namespace cricket
diff --git a/p2p/base/relayport.h b/p2p/base/relayport.h
new file mode 100644
index 00000000..3d9538da
--- /dev/null
+++ b/p2p/base/relayport.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_RELAYPORT_H_
+#define WEBRTC_P2P_BASE_RELAYPORT_H_
+
+#include <deque>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/base/stunrequest.h"
+
+namespace cricket {
+
+class RelayEntry;
+class RelayConnection;
+
+// Communicates using an allocated port on the relay server. For each
+// remote candidate that we try to send data to a RelayEntry instance
+// is created. The RelayEntry will try to reach the remote destination
+// by connecting to all available server addresses in a pre defined
+// order with a small delay in between. When a connection is
+// successful all other connection attemts are aborted.
+class RelayPort : public Port {
+ public:
+ typedef std::pair<rtc::Socket::Option, int> OptionValue;
+
+ // RelayPort doesn't yet do anything fancy in the ctor.
+ static RelayPort* Create(
+ rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password) {
+ return new RelayPort(thread, factory, network, ip, min_port, max_port,
+ username, password);
+ }
+ virtual ~RelayPort();
+
+ void AddServerAddress(const ProtocolAddress& addr);
+ void AddExternalAddress(const ProtocolAddress& addr);
+
+ const std::vector<OptionValue>& options() const { return options_; }
+ bool HasMagicCookie(const char* data, size_t size);
+
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetOption(rtc::Socket::Option opt, int* value);
+ virtual int GetError();
+
+ const ProtocolAddress * ServerAddress(size_t index) const;
+ bool IsReady() { return ready_; }
+
+ // Used for testing.
+ sigslot::signal1<const ProtocolAddress*> SignalConnectFailure;
+ sigslot::signal1<const ProtocolAddress*> SignalSoftTimeout;
+
+ protected:
+ RelayPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network*, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password);
+ bool Init();
+
+ void SetReady();
+
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload);
+
+ // Dispatches the given packet to the port or connection as appropriate.
+ void OnReadPacket(const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ ProtocolType proto,
+ const rtc::PacketTime& packet_time);
+
+ private:
+ friend class RelayEntry;
+
+ std::deque<ProtocolAddress> server_addr_;
+ std::vector<ProtocolAddress> external_addr_;
+ bool ready_;
+ std::vector<RelayEntry*> entries_;
+ std::vector<OptionValue> options_;
+ int error_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_RELAYPORT_H_
diff --git a/p2p/base/relayport_unittest.cc b/p2p/base/relayport_unittest.cc
new file mode 100644
index 00000000..d644d67c
--- /dev/null
+++ b/p2p/base/relayport_unittest.cc
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2009 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/relayport.h"
+#include "webrtc/p2p/base/relayserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketadapters.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using rtc::SocketAddress;
+
+static const SocketAddress kLocalAddress = SocketAddress("192.168.1.2", 0);
+static const SocketAddress kRelayUdpAddr = SocketAddress("99.99.99.1", 5000);
+static const SocketAddress kRelayTcpAddr = SocketAddress("99.99.99.2", 5001);
+static const SocketAddress kRelaySslAddr = SocketAddress("99.99.99.3", 443);
+static const SocketAddress kRelayExtAddr = SocketAddress("99.99.99.3", 5002);
+
+static const int kTimeoutMs = 1000;
+static const int kMaxTimeoutMs = 5000;
+
+// Tests connecting a RelayPort to a fake relay server
+// (cricket::RelayServer) using all currently available protocols. The
+// network layer is faked out by using a VirtualSocketServer for
+// creating sockets. The test will monitor the current state of the
+// RelayPort and created sockets by listening for signals such as,
+// SignalConnectFailure, SignalConnectTimeout, SignalSocketClosed and
+// SignalReadPacket.
+class RelayPortTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ RelayPortTest()
+ : main_(rtc::Thread::Current()),
+ physical_socket_server_(new rtc::PhysicalSocketServer),
+ virtual_socket_server_(new rtc::VirtualSocketServer(
+ physical_socket_server_.get())),
+ ss_scope_(virtual_socket_server_.get()),
+ network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32),
+ socket_factory_(rtc::Thread::Current()),
+ username_(rtc::CreateRandomString(16)),
+ password_(rtc::CreateRandomString(16)),
+ relay_port_(cricket::RelayPort::Create(main_, &socket_factory_,
+ &network_,
+ kLocalAddress.ipaddr(),
+ 0, 0, username_, password_)),
+ relay_server_(new cricket::RelayServer(main_)) {
+ }
+
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ received_packet_count_[socket]++;
+ }
+
+ void OnConnectFailure(const cricket::ProtocolAddress* addr) {
+ failed_connections_.push_back(*addr);
+ }
+
+ void OnSoftTimeout(const cricket::ProtocolAddress* addr) {
+ soft_timedout_connections_.push_back(*addr);
+ }
+
+ protected:
+ virtual void SetUp() {
+ // The relay server needs an external socket to work properly.
+ rtc::AsyncUDPSocket* ext_socket =
+ CreateAsyncUdpSocket(kRelayExtAddr);
+ relay_server_->AddExternalSocket(ext_socket);
+
+ // Listen for failures.
+ relay_port_->SignalConnectFailure.
+ connect(this, &RelayPortTest::OnConnectFailure);
+
+ // Listen for soft timeouts.
+ relay_port_->SignalSoftTimeout.
+ connect(this, &RelayPortTest::OnSoftTimeout);
+ }
+
+ // Udp has the highest 'goodness' value of the three different
+ // protocols used for connecting to the relay server. As soon as
+ // PrepareAddress is called, the RelayPort will start trying to
+ // connect to the given UDP address. As soon as a response to the
+ // sent STUN allocate request message has been received, the
+ // RelayPort will consider the connection to be complete and will
+ // abort any other connection attempts.
+ void TestConnectUdp() {
+ // Add a UDP socket to the relay server.
+ rtc::AsyncUDPSocket* internal_udp_socket =
+ CreateAsyncUdpSocket(kRelayUdpAddr);
+ rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+
+ relay_server_->AddInternalSocket(internal_udp_socket);
+ relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+ // Now add our relay addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP));
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+ relay_port_->PrepareAddress();
+
+ // Should be connected.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kTimeoutMs);
+
+ // Make sure that we are happy with UDP, ie. not continuing with
+ // TCP, SSLTCP, etc.
+ WAIT(relay_server_->HasConnection(kRelayTcpAddr), kTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the UDP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelayUdpAddr));
+ }
+
+ // TCP has the second best 'goodness' value, and as soon as UDP
+ // connection has failed, the RelayPort will attempt to connect via
+ // TCP. Here we add a fake UDP address together with a real TCP
+ // address to simulate an UDP failure. As soon as UDP has failed the
+ // RelayPort will try the TCP adress and succed.
+ void TestConnectTcp() {
+ // Create a fake UDP address for relay port to simulate a failure.
+ cricket::ProtocolAddress fake_protocol_address =
+ cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP);
+
+ // Create a server socket for the RelayServer.
+ rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+ relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+ // Add server addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(fake_protocol_address));
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+ relay_port_->PrepareAddress();
+
+ EXPECT_FALSE(relay_port_->IsReady());
+
+ // Should have timed out in 200 + 200 + 400 + 800 + 1600 ms.
+ EXPECT_TRUE_WAIT(HasFailed(&fake_protocol_address), 3600);
+
+ // Wait until relayport is ready.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the TCP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelayTcpAddr));
+ }
+
+ void TestConnectSslTcp() {
+ // Create a fake TCP address for relay port to simulate a failure.
+ // We skip UDP here since transition from UDP to TCP has been
+ // tested above.
+ cricket::ProtocolAddress fake_protocol_address =
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP);
+
+ // Create a ssl server socket for the RelayServer.
+ rtc::AsyncSocket* ssl_server_socket =
+ CreateServerSocket(kRelaySslAddr);
+ relay_server_->AddInternalServerSocket(ssl_server_socket,
+ cricket::PROTO_SSLTCP);
+
+ // Create a tcp server socket that listens on the fake address so
+ // the relay port can attempt to connect to it.
+ rtc::scoped_ptr<rtc::AsyncSocket> tcp_server_socket(
+ CreateServerSocket(kRelayTcpAddr));
+
+ // Add server addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(fake_protocol_address);
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelaySslAddr, cricket::PROTO_SSLTCP));
+ relay_port_->PrepareAddress();
+ EXPECT_FALSE(relay_port_->IsReady());
+
+ // Should have timed out in 3000 ms(relayport.cc, kSoftConnectTimeoutMs).
+ EXPECT_TRUE_WAIT_MARGIN(HasTimedOut(&fake_protocol_address), 3000, 100);
+
+ // Wait until relayport is ready.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the SSLTCP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelaySslAddr));
+ }
+
+ private:
+ rtc::AsyncUDPSocket* CreateAsyncUdpSocket(const SocketAddress addr) {
+ rtc::AsyncSocket* socket =
+ virtual_socket_server_->CreateAsyncSocket(SOCK_DGRAM);
+ rtc::AsyncUDPSocket* packet_socket =
+ rtc::AsyncUDPSocket::Create(socket, addr);
+ EXPECT_TRUE(packet_socket != NULL);
+ packet_socket->SignalReadPacket.connect(this, &RelayPortTest::OnReadPacket);
+ return packet_socket;
+ }
+
+ rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
+ rtc::AsyncSocket* socket =
+ virtual_socket_server_->CreateAsyncSocket(SOCK_STREAM);
+ EXPECT_GE(socket->Bind(addr), 0);
+ EXPECT_GE(socket->Listen(5), 0);
+ return socket;
+ }
+
+ bool HasFailed(cricket::ProtocolAddress* addr) {
+ for (size_t i = 0; i < failed_connections_.size(); i++) {
+ if (failed_connections_[i].address == addr->address &&
+ failed_connections_[i].proto == addr->proto) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool HasTimedOut(cricket::ProtocolAddress* addr) {
+ for (size_t i = 0; i < soft_timedout_connections_.size(); i++) {
+ if (soft_timedout_connections_[i].address == addr->address &&
+ soft_timedout_connections_[i].proto == addr->proto) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ typedef std::map<rtc::AsyncPacketSocket*, int> PacketMap;
+
+ rtc::Thread* main_;
+ rtc::scoped_ptr<rtc::PhysicalSocketServer>
+ physical_socket_server_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> virtual_socket_server_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ std::string username_;
+ std::string password_;
+ rtc::scoped_ptr<cricket::RelayPort> relay_port_;
+ rtc::scoped_ptr<cricket::RelayServer> relay_server_;
+ std::vector<cricket::ProtocolAddress> failed_connections_;
+ std::vector<cricket::ProtocolAddress> soft_timedout_connections_;
+ PacketMap received_packet_count_;
+};
+
+TEST_F(RelayPortTest, ConnectUdp) {
+ TestConnectUdp();
+}
+
+TEST_F(RelayPortTest, ConnectTcp) {
+ TestConnectTcp();
+}
+
+TEST_F(RelayPortTest, ConnectSslTcp) {
+ TestConnectSslTcp();
+}
diff --git a/p2p/base/relayserver.cc b/p2p/base/relayserver.cc
new file mode 100644
index 00000000..e37a1680
--- /dev/null
+++ b/p2p/base/relayserver.cc
@@ -0,0 +1,746 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/relayserver.h"
+
+#ifdef WEBRTC_POSIX
+#include <errno.h>
+#endif // WEBRTC_POSIX
+
+#include <algorithm>
+
+#include "webrtc/base/asynctcpsocket.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/socketadapters.h"
+
+namespace cricket {
+
+// By default, we require a ping every 90 seconds.
+const int MAX_LIFETIME = 15 * 60 * 1000;
+
+// The number of bytes in each of the usernames we use.
+const uint32 USERNAME_LENGTH = 16;
+
+// Calls SendTo on the given socket and logs any bad results.
+void Send(rtc::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const rtc::SocketAddress& addr) {
+ rtc::PacketOptions options;
+ int result = socket->SendTo(bytes, size, addr, options);
+ if (result < static_cast<int>(size)) {
+ LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size
+ << " bytes";
+ } else if (result < 0) {
+ LOG_ERR(LS_ERROR) << "SendTo";
+ }
+}
+
+// Sends the given STUN message on the given socket.
+void SendStun(const StunMessage& msg,
+ rtc::AsyncPacketSocket* socket,
+ const rtc::SocketAddress& addr) {
+ rtc::ByteBuffer buf;
+ msg.Write(&buf);
+ Send(socket, buf.Data(), buf.Length(), addr);
+}
+
+// Constructs a STUN error response and sends it on the given socket.
+void SendStunError(const StunMessage& msg, rtc::AsyncPacketSocket* socket,
+ const rtc::SocketAddress& remote_addr, int error_code,
+ const char* error_desc, const std::string& magic_cookie) {
+ RelayMessage err_msg;
+ err_msg.SetType(GetStunErrorResponseType(msg.type()));
+ err_msg.SetTransactionID(msg.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ if (magic_cookie.size() == 0) {
+ magic_cookie_attr->CopyBytes(cricket::TURN_MAGIC_COOKIE_VALUE,
+ sizeof(cricket::TURN_MAGIC_COOKIE_VALUE));
+ } else {
+ magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size());
+ }
+ err_msg.AddAttribute(magic_cookie_attr);
+
+ StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+ err_code->SetClass(error_code / 100);
+ err_code->SetNumber(error_code % 100);
+ err_code->SetReason(error_desc);
+ err_msg.AddAttribute(err_code);
+
+ SendStun(err_msg, socket, remote_addr);
+}
+
+RelayServer::RelayServer(rtc::Thread* thread)
+ : thread_(thread), log_bindings_(true) {
+}
+
+RelayServer::~RelayServer() {
+ // Deleting the binding will cause it to be removed from the map.
+ while (!bindings_.empty())
+ delete bindings_.begin()->second;
+ for (size_t i = 0; i < internal_sockets_.size(); ++i)
+ delete internal_sockets_[i];
+ for (size_t i = 0; i < external_sockets_.size(); ++i)
+ delete external_sockets_[i];
+ for (size_t i = 0; i < removed_sockets_.size(); ++i)
+ delete removed_sockets_[i];
+ while (!server_sockets_.empty()) {
+ rtc::AsyncSocket* socket = server_sockets_.begin()->first;
+ server_sockets_.erase(server_sockets_.begin()->first);
+ delete socket;
+ }
+}
+
+void RelayServer::AddInternalSocket(rtc::AsyncPacketSocket* socket) {
+ ASSERT(internal_sockets_.end() ==
+ std::find(internal_sockets_.begin(), internal_sockets_.end(), socket));
+ internal_sockets_.push_back(socket);
+ socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket);
+}
+
+void RelayServer::RemoveInternalSocket(rtc::AsyncPacketSocket* socket) {
+ SocketList::iterator iter =
+ std::find(internal_sockets_.begin(), internal_sockets_.end(), socket);
+ ASSERT(iter != internal_sockets_.end());
+ internal_sockets_.erase(iter);
+ removed_sockets_.push_back(socket);
+ socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddExternalSocket(rtc::AsyncPacketSocket* socket) {
+ ASSERT(external_sockets_.end() ==
+ std::find(external_sockets_.begin(), external_sockets_.end(), socket));
+ external_sockets_.push_back(socket);
+ socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket);
+}
+
+void RelayServer::RemoveExternalSocket(rtc::AsyncPacketSocket* socket) {
+ SocketList::iterator iter =
+ std::find(external_sockets_.begin(), external_sockets_.end(), socket);
+ ASSERT(iter != external_sockets_.end());
+ external_sockets_.erase(iter);
+ removed_sockets_.push_back(socket);
+ socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddInternalServerSocket(rtc::AsyncSocket* socket,
+ cricket::ProtocolType proto) {
+ ASSERT(server_sockets_.end() ==
+ server_sockets_.find(socket));
+ server_sockets_[socket] = proto;
+ socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent);
+}
+
+void RelayServer::RemoveInternalServerSocket(
+ rtc::AsyncSocket* socket) {
+ ServerSocketMap::iterator iter = server_sockets_.find(socket);
+ ASSERT(iter != server_sockets_.end());
+ server_sockets_.erase(iter);
+ socket->SignalReadEvent.disconnect(this);
+}
+
+int RelayServer::GetConnectionCount() const {
+ return static_cast<int>(connections_.size());
+}
+
+rtc::SocketAddressPair RelayServer::GetConnection(int connection) const {
+ int i = 0;
+ for (ConnectionMap::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ if (i == connection) {
+ return it->second->addr_pair();
+ }
+ ++i;
+ }
+ return rtc::SocketAddressPair();
+}
+
+bool RelayServer::HasConnection(const rtc::SocketAddress& address) const {
+ for (ConnectionMap::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ if (it->second->addr_pair().destination() == address) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void RelayServer::OnReadEvent(rtc::AsyncSocket* socket) {
+ ASSERT(server_sockets_.find(socket) != server_sockets_.end());
+ AcceptConnection(socket);
+}
+
+void RelayServer::OnInternalPacket(
+ rtc::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+
+ // Get the address of the connection we just received on.
+ rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
+ ASSERT(!ap.destination().IsNil());
+
+ // If this did not come from an existing connection, it should be a STUN
+ // allocate request.
+ ConnectionMap::iterator piter = connections_.find(ap);
+ if (piter == connections_.end()) {
+ HandleStunAllocate(bytes, size, ap, socket);
+ return;
+ }
+
+ RelayServerConnection* int_conn = piter->second;
+
+ // Handle STUN requests to the server itself.
+ if (int_conn->binding()->HasMagicCookie(bytes, size)) {
+ HandleStun(int_conn, bytes, size);
+ return;
+ }
+
+ // Otherwise, this is a non-wrapped packet that we are to forward. Make sure
+ // that this connection has been locked. (Otherwise, we would not know what
+ // address to forward to.)
+ if (!int_conn->locked()) {
+ LOG(LS_WARNING) << "Dropping packet: connection not locked";
+ return;
+ }
+
+ // Forward this to the destination address into the connection.
+ RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection(
+ int_conn->default_destination());
+ if (ext_conn && ext_conn->locked()) {
+ // TODO: Check the HMAC.
+ ext_conn->Send(bytes, size);
+ } else {
+ // This happens very often and is not an error.
+ LOG(LS_INFO) << "Dropping packet: no external connection";
+ }
+}
+
+void RelayServer::OnExternalPacket(
+ rtc::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+
+ // Get the address of the connection we just received on.
+ rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
+ ASSERT(!ap.destination().IsNil());
+
+ // If this connection already exists, then forward the traffic.
+ ConnectionMap::iterator piter = connections_.find(ap);
+ if (piter != connections_.end()) {
+ // TODO: Check the HMAC.
+ RelayServerConnection* ext_conn = piter->second;
+ RelayServerConnection* int_conn =
+ ext_conn->binding()->GetInternalConnection(
+ ext_conn->addr_pair().source());
+ ASSERT(int_conn != NULL);
+ int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+ ext_conn->Lock(); // allow outgoing packets
+ return;
+ }
+
+ // The first packet should always be a STUN / TURN packet. If it isn't, then
+ // we should just ignore this packet.
+ RelayMessage msg;
+ rtc::ByteBuffer buf(bytes, size);
+ if (!msg.Read(&buf)) {
+ LOG(LS_WARNING) << "Dropping packet: first packet not STUN";
+ return;
+ }
+
+ // The initial packet should have a username (which identifies the binding).
+ const StunByteStringAttribute* username_attr =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ if (!username_attr) {
+ LOG(LS_WARNING) << "Dropping packet: no username";
+ return;
+ }
+
+ uint32 length = rtc::_min(static_cast<uint32>(username_attr->length()),
+ USERNAME_LENGTH);
+ std::string username(username_attr->bytes(), length);
+ // TODO: Check the HMAC.
+
+ // The binding should already be present.
+ BindingMap::iterator biter = bindings_.find(username);
+ if (biter == bindings_.end()) {
+ LOG(LS_WARNING) << "Dropping packet: no binding with username";
+ return;
+ }
+
+ // Add this authenticted connection to the binding.
+ RelayServerConnection* ext_conn =
+ new RelayServerConnection(biter->second, ap, socket);
+ ext_conn->binding()->AddExternalConnection(ext_conn);
+ AddConnection(ext_conn);
+
+ // We always know where external packets should be forwarded, so we can lock
+ // them from the beginning.
+ ext_conn->Lock();
+
+ // Send this message on the appropriate internal connection.
+ RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection(
+ ext_conn->addr_pair().source());
+ ASSERT(int_conn != NULL);
+ int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+}
+
+bool RelayServer::HandleStun(
+ const char* bytes, size_t size, const rtc::SocketAddress& remote_addr,
+ rtc::AsyncPacketSocket* socket, std::string* username,
+ StunMessage* msg) {
+
+ // Parse this into a stun message. Eat the message if this fails.
+ rtc::ByteBuffer buf(bytes, size);
+ if (!msg->Read(&buf)) {
+ return false;
+ }
+
+ // The initial packet should have a username (which identifies the binding).
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ if (!username_attr) {
+ SendStunError(*msg, socket, remote_addr, 432, "Missing Username", "");
+ return false;
+ }
+
+ // Record the username if requested.
+ if (username)
+ username->append(username_attr->bytes(), username_attr->length());
+
+ // TODO: Check for unknown attributes (<= 0x7fff)
+
+ return true;
+}
+
+void RelayServer::HandleStunAllocate(
+ const char* bytes, size_t size, const rtc::SocketAddressPair& ap,
+ rtc::AsyncPacketSocket* socket) {
+
+ // Make sure this is a valid STUN request.
+ RelayMessage request;
+ std::string username;
+ if (!HandleStun(bytes, size, ap.source(), socket, &username, &request))
+ return;
+
+ // Make sure this is a an allocate request.
+ if (request.type() != STUN_ALLOCATE_REQUEST) {
+ SendStunError(request,
+ socket,
+ ap.source(),
+ 600,
+ "Operation Not Supported",
+ "");
+ return;
+ }
+
+ // TODO: Check the HMAC.
+
+ // Find or create the binding for this username.
+
+ RelayServerBinding* binding;
+
+ BindingMap::iterator biter = bindings_.find(username);
+ if (biter != bindings_.end()) {
+ binding = biter->second;
+ } else {
+ // NOTE: In the future, bindings will be created by the bot only. This
+ // else-branch will then disappear.
+
+ // Compute the appropriate lifetime for this binding.
+ uint32 lifetime = MAX_LIFETIME;
+ const StunUInt32Attribute* lifetime_attr =
+ request.GetUInt32(STUN_ATTR_LIFETIME);
+ if (lifetime_attr)
+ lifetime = rtc::_min(lifetime, lifetime_attr->value() * 1000);
+
+ binding = new RelayServerBinding(this, username, "0", lifetime);
+ binding->SignalTimeout.connect(this, &RelayServer::OnTimeout);
+ bindings_[username] = binding;
+
+ if (log_bindings_) {
+ LOG(LS_INFO) << "Added new binding " << username << ", "
+ << bindings_.size() << " total";
+ }
+ }
+
+ // Add this connection to the binding. It starts out unlocked.
+ RelayServerConnection* int_conn =
+ new RelayServerConnection(binding, ap, socket);
+ binding->AddInternalConnection(int_conn);
+ AddConnection(int_conn);
+
+ // Now that we have a connection, this other method takes over.
+ HandleStunAllocate(int_conn, request);
+}
+
+void RelayServer::HandleStun(
+ RelayServerConnection* int_conn, const char* bytes, size_t size) {
+
+ // Make sure this is a valid STUN request.
+ RelayMessage request;
+ std::string username;
+ if (!HandleStun(bytes, size, int_conn->addr_pair().source(),
+ int_conn->socket(), &username, &request))
+ return;
+
+ // Make sure the username is the one were were expecting.
+ if (username != int_conn->binding()->username()) {
+ int_conn->SendStunError(request, 430, "Stale Credentials");
+ return;
+ }
+
+ // TODO: Check the HMAC.
+
+ // Send this request to the appropriate handler.
+ if (request.type() == STUN_SEND_REQUEST)
+ HandleStunSend(int_conn, request);
+ else if (request.type() == STUN_ALLOCATE_REQUEST)
+ HandleStunAllocate(int_conn, request);
+ else
+ int_conn->SendStunError(request, 600, "Operation Not Supported");
+}
+
+void RelayServer::HandleStunAllocate(
+ RelayServerConnection* int_conn, const StunMessage& request) {
+
+ // Create a response message that includes an address with which external
+ // clients can communicate.
+
+ RelayMessage response;
+ response.SetType(STUN_ALLOCATE_RESPONSE);
+ response.SetTransactionID(request.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+ int_conn->binding()->magic_cookie().size());
+ response.AddAttribute(magic_cookie_attr);
+
+ size_t index = rand() % external_sockets_.size();
+ rtc::SocketAddress ext_addr =
+ external_sockets_[index]->GetLocalAddress();
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ addr_attr->SetIP(ext_addr.ipaddr());
+ addr_attr->SetPort(ext_addr.port());
+ response.AddAttribute(addr_attr);
+
+ StunUInt32Attribute* res_lifetime_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000);
+ response.AddAttribute(res_lifetime_attr);
+
+ // TODO: Support transport-prefs (preallocate RTCP port).
+ // TODO: Support bandwidth restrictions.
+ // TODO: Add message integrity check.
+
+ // Send a response to the caller.
+ int_conn->SendStun(response);
+}
+
+void RelayServer::HandleStunSend(
+ RelayServerConnection* int_conn, const StunMessage& request) {
+
+ const StunAddressAttribute* addr_attr =
+ request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ if (!addr_attr) {
+ int_conn->SendStunError(request, 400, "Bad Request");
+ return;
+ }
+
+ const StunByteStringAttribute* data_attr =
+ request.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ int_conn->SendStunError(request, 400, "Bad Request");
+ return;
+ }
+
+ rtc::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port());
+ RelayServerConnection* ext_conn =
+ int_conn->binding()->GetExternalConnection(ext_addr);
+ if (!ext_conn) {
+ // Create a new connection to establish the relationship with this binding.
+ ASSERT(external_sockets_.size() == 1);
+ rtc::AsyncPacketSocket* socket = external_sockets_[0];
+ rtc::SocketAddressPair ap(ext_addr, socket->GetLocalAddress());
+ ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket);
+ ext_conn->binding()->AddExternalConnection(ext_conn);
+ AddConnection(ext_conn);
+ }
+
+ // If this connection has pinged us, then allow outgoing traffic.
+ if (ext_conn->locked())
+ ext_conn->Send(data_attr->bytes(), data_attr->length());
+
+ const StunUInt32Attribute* options_attr =
+ request.GetUInt32(STUN_ATTR_OPTIONS);
+ if (options_attr && (options_attr->value() & 0x01)) {
+ int_conn->set_default_destination(ext_addr);
+ int_conn->Lock();
+
+ RelayMessage response;
+ response.SetType(STUN_SEND_RESPONSE);
+ response.SetTransactionID(request.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+ int_conn->binding()->magic_cookie().size());
+ response.AddAttribute(magic_cookie_attr);
+
+ StunUInt32Attribute* options2_attr =
+ StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS);
+ options2_attr->SetValue(0x01);
+ response.AddAttribute(options2_attr);
+
+ int_conn->SendStun(response);
+ }
+}
+
+void RelayServer::AddConnection(RelayServerConnection* conn) {
+ ASSERT(connections_.find(conn->addr_pair()) == connections_.end());
+ connections_[conn->addr_pair()] = conn;
+}
+
+void RelayServer::RemoveConnection(RelayServerConnection* conn) {
+ ConnectionMap::iterator iter = connections_.find(conn->addr_pair());
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+}
+
+void RelayServer::RemoveBinding(RelayServerBinding* binding) {
+ BindingMap::iterator iter = bindings_.find(binding->username());
+ ASSERT(iter != bindings_.end());
+ bindings_.erase(iter);
+
+ if (log_bindings_) {
+ LOG(LS_INFO) << "Removed binding " << binding->username() << ", "
+ << bindings_.size() << " remaining";
+ }
+}
+
+void RelayServer::OnMessage(rtc::Message *pmsg) {
+#if ENABLE_DEBUG
+ static const uint32 kMessageAcceptConnection = 1;
+ ASSERT(pmsg->message_id == kMessageAcceptConnection);
+#endif
+ rtc::MessageData* data = pmsg->pdata;
+ rtc::AsyncSocket* socket =
+ static_cast <rtc::TypedMessageData<rtc::AsyncSocket*>*>
+ (data)->data();
+ AcceptConnection(socket);
+ delete data;
+}
+
+void RelayServer::OnTimeout(RelayServerBinding* binding) {
+ // This call will result in all of the necessary clean-up. We can't call
+ // delete here, because you can't delete an object that is signaling you.
+ thread_->Dispose(binding);
+}
+
+void RelayServer::AcceptConnection(rtc::AsyncSocket* server_socket) {
+ // Check if someone is trying to connect to us.
+ rtc::SocketAddress accept_addr;
+ rtc::AsyncSocket* accepted_socket =
+ server_socket->Accept(&accept_addr);
+ if (accepted_socket != NULL) {
+ // We had someone trying to connect, now check which protocol to
+ // use and create a packet socket.
+ ASSERT(server_sockets_[server_socket] == cricket::PROTO_TCP ||
+ server_sockets_[server_socket] == cricket::PROTO_SSLTCP);
+ if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) {
+ accepted_socket = new rtc::AsyncSSLServerSocket(accepted_socket);
+ }
+ rtc::AsyncTCPSocket* tcp_socket =
+ new rtc::AsyncTCPSocket(accepted_socket, false);
+
+ // Finally add the socket so it can start communicating with the client.
+ AddInternalSocket(tcp_socket);
+ }
+}
+
+RelayServerConnection::RelayServerConnection(
+ RelayServerBinding* binding, const rtc::SocketAddressPair& addrs,
+ rtc::AsyncPacketSocket* socket)
+ : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) {
+ // The creation of a new connection constitutes a use of the binding.
+ binding_->NoteUsed();
+}
+
+RelayServerConnection::~RelayServerConnection() {
+ // Remove this connection from the server's map (if it exists there).
+ binding_->server()->RemoveConnection(this);
+}
+
+void RelayServerConnection::Send(const char* data, size_t size) {
+ // Note that the binding has been used again.
+ binding_->NoteUsed();
+
+ cricket::Send(socket_, data, size, addr_pair_.source());
+}
+
+void RelayServerConnection::Send(
+ const char* data, size_t size, const rtc::SocketAddress& from_addr) {
+ // If the from address is known to the client, we don't need to send it.
+ if (locked() && (from_addr == default_dest_)) {
+ Send(data, size);
+ return;
+ }
+
+ // Wrap the given data in a data-indication packet.
+
+ RelayMessage msg;
+ msg.SetType(STUN_DATA_INDICATION);
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(),
+ binding_->magic_cookie().size());
+ msg.AddAttribute(magic_cookie_attr);
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ addr_attr->SetIP(from_addr.ipaddr());
+ addr_attr->SetPort(from_addr.port());
+ msg.AddAttribute(addr_attr);
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ ASSERT(size <= 65536);
+ data_attr->CopyBytes(data, uint16(size));
+ msg.AddAttribute(data_attr);
+
+ SendStun(msg);
+}
+
+void RelayServerConnection::SendStun(const StunMessage& msg) {
+ // Note that the binding has been used again.
+ binding_->NoteUsed();
+
+ cricket::SendStun(msg, socket_, addr_pair_.source());
+}
+
+void RelayServerConnection::SendStunError(
+ const StunMessage& request, int error_code, const char* error_desc) {
+ // An error does not indicate use. If no legitimate use off the binding
+ // occurs, we want it to be cleaned up even if errors are still occuring.
+
+ cricket::SendStunError(
+ request, socket_, addr_pair_.source(), error_code, error_desc,
+ binding_->magic_cookie());
+}
+
+void RelayServerConnection::Lock() {
+ locked_ = true;
+}
+
+void RelayServerConnection::Unlock() {
+ locked_ = false;
+}
+
+// IDs used for posted messages:
+const uint32 MSG_LIFETIME_TIMER = 1;
+
+RelayServerBinding::RelayServerBinding(
+ RelayServer* server, const std::string& username,
+ const std::string& password, uint32 lifetime)
+ : server_(server), username_(username), password_(password),
+ lifetime_(lifetime) {
+ // For now, every connection uses the standard magic cookie value.
+ magic_cookie_.append(
+ reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
+ sizeof(TURN_MAGIC_COOKIE_VALUE));
+
+ // Initialize the last-used time to now.
+ NoteUsed();
+
+ // Set the first timeout check.
+ server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+}
+
+RelayServerBinding::~RelayServerBinding() {
+ // Clear the outstanding timeout check.
+ server_->thread()->Clear(this);
+
+ // Clean up all of the connections.
+ for (size_t i = 0; i < internal_connections_.size(); ++i)
+ delete internal_connections_[i];
+ for (size_t i = 0; i < external_connections_.size(); ++i)
+ delete external_connections_[i];
+
+ // Remove this binding from the server's map.
+ server_->RemoveBinding(this);
+}
+
+void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) {
+ internal_connections_.push_back(conn);
+}
+
+void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) {
+ external_connections_.push_back(conn);
+}
+
+void RelayServerBinding::NoteUsed() {
+ last_used_ = rtc::Time();
+}
+
+bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const {
+ if (size < 24 + magic_cookie_.size()) {
+ return false;
+ } else {
+ return memcmp(bytes + 24, magic_cookie_.c_str(), magic_cookie_.size()) == 0;
+ }
+}
+
+RelayServerConnection* RelayServerBinding::GetInternalConnection(
+ const rtc::SocketAddress& ext_addr) {
+
+ // Look for an internal connection that is locked to this address.
+ for (size_t i = 0; i < internal_connections_.size(); ++i) {
+ if (internal_connections_[i]->locked() &&
+ (ext_addr == internal_connections_[i]->default_destination()))
+ return internal_connections_[i];
+ }
+
+ // If one was not found, we send to the first connection.
+ ASSERT(internal_connections_.size() > 0);
+ return internal_connections_[0];
+}
+
+RelayServerConnection* RelayServerBinding::GetExternalConnection(
+ const rtc::SocketAddress& ext_addr) {
+ for (size_t i = 0; i < external_connections_.size(); ++i) {
+ if (ext_addr == external_connections_[i]->addr_pair().source())
+ return external_connections_[i];
+ }
+ return 0;
+}
+
+void RelayServerBinding::OnMessage(rtc::Message *pmsg) {
+ if (pmsg->message_id == MSG_LIFETIME_TIMER) {
+ ASSERT(!pmsg->pdata);
+
+ // If the lifetime timeout has been exceeded, then send a signal.
+ // Otherwise, just keep waiting.
+ if (rtc::Time() >= last_used_ + lifetime_) {
+ LOG(LS_INFO) << "Expiring binding " << username_;
+ SignalTimeout(this);
+ } else {
+ server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+ }
+
+ } else {
+ ASSERT(false);
+ }
+}
+
+} // namespace cricket
diff --git a/p2p/base/relayserver.h b/p2p/base/relayserver.h
new file mode 100644
index 00000000..e0e45d52
--- /dev/null
+++ b/p2p/base/relayserver.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_RELAYSERVER_H_
+#define WEBRTC_P2P_BASE_RELAYSERVER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/asyncudpsocket.h"
+#include "webrtc/base/socketaddresspair.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/timeutils.h"
+
+namespace cricket {
+
+class RelayServerBinding;
+class RelayServerConnection;
+
+// Relays traffic between connections to the server that are "bound" together.
+// All connections created with the same username/password are bound together.
+class RelayServer : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ // Creates a server, which will use this thread to post messages to itself.
+ explicit RelayServer(rtc::Thread* thread);
+ ~RelayServer();
+
+ rtc::Thread* thread() { return thread_; }
+
+ // Indicates whether we will print updates of the number of bindings.
+ bool log_bindings() const { return log_bindings_; }
+ void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; }
+
+ // Updates the set of sockets that the server uses to talk to "internal"
+ // clients. These are clients that do the "port allocations".
+ void AddInternalSocket(rtc::AsyncPacketSocket* socket);
+ void RemoveInternalSocket(rtc::AsyncPacketSocket* socket);
+
+ // Updates the set of sockets that the server uses to talk to "external"
+ // clients. These are the clients that do not do allocations. They do not
+ // know that these addresses represent a relay server.
+ void AddExternalSocket(rtc::AsyncPacketSocket* socket);
+ void RemoveExternalSocket(rtc::AsyncPacketSocket* socket);
+
+ // Starts listening for connections on this sockets. When someone
+ // tries to connect, the connection will be accepted and a new
+ // internal socket will be added.
+ void AddInternalServerSocket(rtc::AsyncSocket* socket,
+ cricket::ProtocolType proto);
+
+ // Removes this server socket from the list.
+ void RemoveInternalServerSocket(rtc::AsyncSocket* socket);
+
+ // Methods for testing and debuging.
+ int GetConnectionCount() const;
+ rtc::SocketAddressPair GetConnection(int connection) const;
+ bool HasConnection(const rtc::SocketAddress& address) const;
+
+ private:
+ typedef std::vector<rtc::AsyncPacketSocket*> SocketList;
+ typedef std::map<rtc::AsyncSocket*,
+ cricket::ProtocolType> ServerSocketMap;
+ typedef std::map<std::string, RelayServerBinding*> BindingMap;
+ typedef std::map<rtc::SocketAddressPair,
+ RelayServerConnection*> ConnectionMap;
+
+ rtc::Thread* thread_;
+ bool log_bindings_;
+ SocketList internal_sockets_;
+ SocketList external_sockets_;
+ SocketList removed_sockets_;
+ ServerSocketMap server_sockets_;
+ BindingMap bindings_;
+ ConnectionMap connections_;
+
+ // Called when a packet is received by the server on one of its sockets.
+ void OnInternalPacket(rtc::AsyncPacketSocket* socket,
+ const char* bytes, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+ void OnExternalPacket(rtc::AsyncPacketSocket* socket,
+ const char* bytes, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+
+ void OnReadEvent(rtc::AsyncSocket* socket);
+
+ // Processes the relevant STUN request types from the client.
+ bool HandleStun(const char* bytes, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ rtc::AsyncPacketSocket* socket,
+ std::string* username, StunMessage* msg);
+ void HandleStunAllocate(const char* bytes, size_t size,
+ const rtc::SocketAddressPair& ap,
+ rtc::AsyncPacketSocket* socket);
+ void HandleStun(RelayServerConnection* int_conn, const char* bytes,
+ size_t size);
+ void HandleStunAllocate(RelayServerConnection* int_conn,
+ const StunMessage& msg);
+ void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg);
+
+ // Adds/Removes the a connection or binding.
+ void AddConnection(RelayServerConnection* conn);
+ void RemoveConnection(RelayServerConnection* conn);
+ void RemoveBinding(RelayServerBinding* binding);
+
+ // Handle messages in our worker thread.
+ void OnMessage(rtc::Message *pmsg);
+
+ // Called when the timer for checking lifetime times out.
+ void OnTimeout(RelayServerBinding* binding);
+
+ // Accept connections on this server socket.
+ void AcceptConnection(rtc::AsyncSocket* server_socket);
+
+ friend class RelayServerConnection;
+ friend class RelayServerBinding;
+};
+
+// Maintains information about a connection to the server. Each connection is
+// part of one and only one binding.
+class RelayServerConnection {
+ public:
+ RelayServerConnection(RelayServerBinding* binding,
+ const rtc::SocketAddressPair& addrs,
+ rtc::AsyncPacketSocket* socket);
+ ~RelayServerConnection();
+
+ RelayServerBinding* binding() { return binding_; }
+ rtc::AsyncPacketSocket* socket() { return socket_; }
+
+ // Returns a pair where the source is the remote address and the destination
+ // is the local address.
+ const rtc::SocketAddressPair& addr_pair() { return addr_pair_; }
+
+ // Sends a packet to the connected client. If an address is provided, then
+ // we make sure the internal client receives it, wrapping if necessary.
+ void Send(const char* data, size_t size);
+ void Send(const char* data, size_t size,
+ const rtc::SocketAddress& ext_addr);
+
+ // Sends a STUN message to the connected client with no wrapping.
+ void SendStun(const StunMessage& msg);
+ void SendStunError(const StunMessage& request, int code, const char* desc);
+
+ // A locked connection is one for which we know the intended destination of
+ // any raw packet received.
+ bool locked() const { return locked_; }
+ void Lock();
+ void Unlock();
+
+ // Records the address that raw packets should be forwarded to (for internal
+ // packets only; for external, we already know where they go).
+ const rtc::SocketAddress& default_destination() const {
+ return default_dest_;
+ }
+ void set_default_destination(const rtc::SocketAddress& addr) {
+ default_dest_ = addr;
+ }
+
+ private:
+ RelayServerBinding* binding_;
+ rtc::SocketAddressPair addr_pair_;
+ rtc::AsyncPacketSocket* socket_;
+ bool locked_;
+ rtc::SocketAddress default_dest_;
+};
+
+// Records a set of internal and external connections that we relay between,
+// or in other words, that are "bound" together.
+class RelayServerBinding : public rtc::MessageHandler {
+ public:
+ RelayServerBinding(
+ RelayServer* server, const std::string& username,
+ const std::string& password, uint32 lifetime);
+ virtual ~RelayServerBinding();
+
+ RelayServer* server() { return server_; }
+ uint32 lifetime() { return lifetime_; }
+ const std::string& username() { return username_; }
+ const std::string& password() { return password_; }
+ const std::string& magic_cookie() { return magic_cookie_; }
+
+ // Adds/Removes a connection into the binding.
+ void AddInternalConnection(RelayServerConnection* conn);
+ void AddExternalConnection(RelayServerConnection* conn);
+
+ // We keep track of the use of each binding. If we detect that it was not
+ // used for longer than the lifetime, then we send a signal.
+ void NoteUsed();
+ sigslot::signal1<RelayServerBinding*> SignalTimeout;
+
+ // Determines whether the given packet has the magic cookie present (in the
+ // right place).
+ bool HasMagicCookie(const char* bytes, size_t size) const;
+
+ // Determines the connection to use to send packets to or from the given
+ // external address.
+ RelayServerConnection* GetInternalConnection(
+ const rtc::SocketAddress& ext_addr);
+ RelayServerConnection* GetExternalConnection(
+ const rtc::SocketAddress& ext_addr);
+
+ // MessageHandler:
+ void OnMessage(rtc::Message *pmsg);
+
+ private:
+ RelayServer* server_;
+
+ std::string username_;
+ std::string password_;
+ std::string magic_cookie_;
+
+ std::vector<RelayServerConnection*> internal_connections_;
+ std::vector<RelayServerConnection*> external_connections_;
+
+ uint32 lifetime_;
+ uint32 last_used_;
+ // TODO: bandwidth
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_RELAYSERVER_H_
diff --git a/p2p/base/relayserver_unittest.cc b/p2p/base/relayserver_unittest.cc
new file mode 100644
index 00000000..3349a173
--- /dev/null
+++ b/p2p/base/relayserver_unittest.cc
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/p2p/base/relayserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/testclient.h"
+#include "webrtc/base/thread.h"
+
+using rtc::SocketAddress;
+using namespace cricket;
+
+static const uint32 LIFETIME = 4; // seconds
+static const SocketAddress server_int_addr("127.0.0.1", 5000);
+static const SocketAddress server_ext_addr("127.0.0.1", 5001);
+static const SocketAddress client1_addr("127.0.0.1", 6000 + (rand() % 1000));
+static const SocketAddress client2_addr("127.0.0.1", 7000 + (rand() % 1000));
+static const char* bad = "this is a completely nonsensical message whose only "
+ "purpose is to make the parser go 'ack'. it doesn't "
+ "look anything like a normal stun message";
+static const char* msg1 = "spamspamspamspamspamspamspambakedbeansspam";
+static const char* msg2 = "Lobster Thermidor a Crevette with a mornay sauce...";
+
+class RelayServerTest : public testing::Test {
+ public:
+ RelayServerTest()
+ : main_(rtc::Thread::Current()), ss_(main_->socketserver()),
+ username_(rtc::CreateRandomString(12)),
+ password_(rtc::CreateRandomString(12)) {
+ }
+ protected:
+ virtual void SetUp() {
+ server_.reset(new RelayServer(main_));
+
+ server_->AddInternalSocket(
+ rtc::AsyncUDPSocket::Create(ss_, server_int_addr));
+ server_->AddExternalSocket(
+ rtc::AsyncUDPSocket::Create(ss_, server_ext_addr));
+
+ client1_.reset(new rtc::TestClient(
+ rtc::AsyncUDPSocket::Create(ss_, client1_addr)));
+ client2_.reset(new rtc::TestClient(
+ rtc::AsyncUDPSocket::Create(ss_, client2_addr)));
+ }
+
+ void Allocate() {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST));
+ AddUsernameAttr(req.get(), username_);
+ AddLifetimeAttr(req.get(), LIFETIME);
+ Send1(req.get());
+ delete Receive1();
+ }
+ void Bind() {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST));
+ AddUsernameAttr(req.get(), username_);
+ Send2(req.get());
+ delete Receive1();
+ }
+
+ void Send1(const StunMessage* msg) {
+ rtc::ByteBuffer buf;
+ msg->Write(&buf);
+ SendRaw1(buf.Data(), static_cast<int>(buf.Length()));
+ }
+ void Send2(const StunMessage* msg) {
+ rtc::ByteBuffer buf;
+ msg->Write(&buf);
+ SendRaw2(buf.Data(), static_cast<int>(buf.Length()));
+ }
+ void SendRaw1(const char* data, int len) {
+ return Send(client1_.get(), data, len, server_int_addr);
+ }
+ void SendRaw2(const char* data, int len) {
+ return Send(client2_.get(), data, len, server_ext_addr);
+ }
+ void Send(rtc::TestClient* client, const char* data,
+ int len, const SocketAddress& addr) {
+ client->SendTo(data, len, addr);
+ }
+
+ StunMessage* Receive1() {
+ return Receive(client1_.get());
+ }
+ StunMessage* Receive2() {
+ return Receive(client2_.get());
+ }
+ std::string ReceiveRaw1() {
+ return ReceiveRaw(client1_.get());
+ }
+ std::string ReceiveRaw2() {
+ return ReceiveRaw(client2_.get());
+ }
+ StunMessage* Receive(rtc::TestClient* client) {
+ StunMessage* msg = NULL;
+ rtc::TestClient::Packet* packet = client->NextPacket();
+ if (packet) {
+ rtc::ByteBuffer buf(packet->buf, packet->size);
+ msg = new RelayMessage();
+ msg->Read(&buf);
+ delete packet;
+ }
+ return msg;
+ }
+ std::string ReceiveRaw(rtc::TestClient* client) {
+ std::string raw;
+ rtc::TestClient::Packet* packet = client->NextPacket();
+ if (packet) {
+ raw = std::string(packet->buf, packet->size);
+ delete packet;
+ }
+ return raw;
+ }
+
+ static StunMessage* CreateStunMessage(int type) {
+ StunMessage* msg = new RelayMessage();
+ msg->SetType(type);
+ msg->SetTransactionID(
+ rtc::CreateRandomString(kStunTransactionIdLength));
+ return msg;
+ }
+ static void AddMagicCookieAttr(StunMessage* msg) {
+ StunByteStringAttribute* attr =
+ StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, sizeof(TURN_MAGIC_COOKIE_VALUE));
+ msg->AddAttribute(attr);
+ }
+ static void AddUsernameAttr(StunMessage* msg, const std::string& val) {
+ StunByteStringAttribute* attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ attr->CopyBytes(val.c_str(), val.size());
+ msg->AddAttribute(attr);
+ }
+ static void AddLifetimeAttr(StunMessage* msg, int val) {
+ StunUInt32Attribute* attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ attr->SetValue(val);
+ msg->AddAttribute(attr);
+ }
+ static void AddDestinationAttr(StunMessage* msg, const SocketAddress& addr) {
+ StunAddressAttribute* attr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ attr->SetIP(addr.ipaddr());
+ attr->SetPort(addr.port());
+ msg->AddAttribute(attr);
+ }
+
+ rtc::Thread* main_;
+ rtc::SocketServer* ss_;
+ rtc::scoped_ptr<RelayServer> server_;
+ rtc::scoped_ptr<rtc::TestClient> client1_;
+ rtc::scoped_ptr<rtc::TestClient> client2_;
+ std::string username_;
+ std::string password_;
+};
+
+// Send a complete nonsense message and verify that it is eaten.
+TEST_F(RelayServerTest, TestBadRequest) {
+ rtc::scoped_ptr<StunMessage> res;
+
+ SendRaw1(bad, static_cast<int>(strlen(bad)));
+ res.reset(Receive1());
+
+ ASSERT_TRUE(!res);
+}
+
+// Send an allocate request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestAllocateNoUsername) {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_ALLOCATE_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->eclass());
+ EXPECT_EQ(32, err->number());
+ EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a binding request and verify that it is rejected.
+TEST_F(RelayServerTest, TestBindingRequest) {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->eclass());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Send an allocate request and verify that it is accepted.
+TEST_F(RelayServerTest, TestAllocate) {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+ AddLifetimeAttr(req.get(), LIFETIME);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+ EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+ const StunUInt32Attribute* res_lifetime_attr =
+ res->GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(res_lifetime_attr != NULL);
+ EXPECT_EQ(LIFETIME, res_lifetime_attr->value());
+}
+
+// Send a second allocate request and verify that it is also accepted, though
+// the lifetime should be ignored.
+TEST_F(RelayServerTest, TestReallocate) {
+ Allocate();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+ EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+ const StunUInt32Attribute* lifetime_attr =
+ res->GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(lifetime_attr != NULL);
+ EXPECT_EQ(LIFETIME, lifetime_attr->value());
+}
+
+// Send a request from another client and see that it arrives at the first
+// client in the binding.
+TEST_F(RelayServerTest, TestRemoteBind) {
+ Allocate();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+
+ Send2(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+ const StunByteStringAttribute* recv_data =
+ res->GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(recv_data != NULL);
+
+ rtc::ByteBuffer buf(recv_data->bytes(), recv_data->length());
+ rtc::scoped_ptr<StunMessage> res2(new StunMessage());
+ EXPECT_TRUE(res2->Read(&buf));
+ EXPECT_EQ(STUN_BINDING_REQUEST, res2->type());
+ EXPECT_EQ(req->transaction_id(), res2->transaction_id());
+
+ const StunAddressAttribute* src_addr =
+ res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(src_addr != NULL);
+ EXPECT_EQ(1, src_addr->family());
+ EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+ EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+ EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a complete nonsense message to the established connection and verify
+// that it is dropped by the server.
+TEST_F(RelayServerTest, TestRemoteBadRequest) {
+ Allocate();
+ Bind();
+
+ SendRaw1(bad, static_cast<int>(strlen(bad)));
+ EXPECT_TRUE(Receive1() == NULL);
+ EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a send request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestMissingUsername) {
+ Allocate();
+ Bind();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->eclass());
+ EXPECT_EQ(32, err->number());
+ EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a send request with the wrong username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestBadUsername) {
+ Allocate();
+ Bind();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), "foobarbizbaz");
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->eclass());
+ EXPECT_EQ(30, err->number());
+ EXPECT_EQ("Stale Credentials", err->reason());
+}
+
+// Send a send request without a destination address and verify that it is
+// rejected.
+TEST_F(RelayServerTest, TestSendRequestNoDestinationAddress) {
+ Allocate();
+ Bind();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->eclass());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a send request without data and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestNoData) {
+ Allocate();
+ Bind();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->eclass());
+ EXPECT_EQ(00, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a binding request after an allocate and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestWrongType) {
+ Allocate();
+ Bind();
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->eclass());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Verify that we can send traffic back and forth between the clients after a
+// successful allocate and bind.
+TEST_F(RelayServerTest, TestSendRaw) {
+ Allocate();
+ Bind();
+
+ for (int i = 0; i < 10; i++) {
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ StunByteStringAttribute* send_data =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ send_data->CopyBytes(msg1);
+ req->AddAttribute(send_data);
+
+ Send1(req.get());
+ EXPECT_EQ(msg1, ReceiveRaw2());
+ SendRaw2(msg2, static_cast<int>(strlen(msg2)));
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res);
+ EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+ const StunAddressAttribute* src_addr =
+ res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(src_addr != NULL);
+ EXPECT_EQ(1, src_addr->family());
+ EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+ EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+ const StunByteStringAttribute* recv_data =
+ res->GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(recv_data != NULL);
+ EXPECT_EQ(strlen(msg2), recv_data->length());
+ EXPECT_EQ(0, memcmp(msg2, recv_data->bytes(), recv_data->length()));
+ }
+}
+
+// Verify that a binding expires properly, and rejects send requests.
+TEST_F(RelayServerTest, TestExpiration) {
+ Allocate();
+ Bind();
+
+ // Wait twice the lifetime to make sure the server has expired the binding.
+ rtc::Thread::Current()->ProcessMessages((LIFETIME * 2) * 1000);
+
+ rtc::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ data_attr->CopyBytes(msg1);
+ req->AddAttribute(data_attr);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->eclass());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+
+ // Also verify that traffic from the external client is ignored.
+ SendRaw2(msg2, static_cast<int>(strlen(msg2)));
+ EXPECT_TRUE(ReceiveRaw1().empty());
+}
diff --git a/p2p/base/session.cc b/p2p/base/session.cc
new file mode 100644
index 00000000..9749b14e
--- /dev/null
+++ b/p2p/base/session.cc
@@ -0,0 +1,1760 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/session.h"
+
+#include "webrtc/p2p/base/dtlstransport.h"
+#include "webrtc/p2p/base/p2ptransport.h"
+#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannelproxy.h"
+#include "webrtc/p2p/base/transportinfo.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/bind.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sslstreamadapter.h"
+
+#include "webrtc/p2p/base/constants.h"
+
+namespace cricket {
+
+using rtc::Bind;
+
+bool BadMessage(const buzz::QName type,
+ const std::string& text,
+ MessageError* err) {
+ err->SetType(type);
+ err->SetText(text);
+ return false;
+}
+
+TransportProxy::~TransportProxy() {
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ iter->second->SignalDestroyed(iter->second);
+ delete iter->second;
+ }
+}
+
+const std::string& TransportProxy::type() const {
+ return transport_->get()->type();
+}
+
+TransportChannel* TransportProxy::GetChannel(int component) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ return GetChannelProxy(component);
+}
+
+TransportChannel* TransportProxy::CreateChannel(
+ const std::string& name, int component) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(GetChannel(component) == NULL);
+ ASSERT(!transport_->get()->HasChannel(component));
+
+ // We always create a proxy in case we need to change out the transport later.
+ TransportChannelProxy* channel =
+ new TransportChannelProxy(content_name(), name, component);
+ channels_[component] = channel;
+
+ // If we're already negotiated, create an impl and hook it up to the proxy
+ // channel. If we're connecting, create an impl but don't hook it up yet.
+ if (negotiated_) {
+ SetupChannelProxy_w(component, channel);
+ } else if (connecting_) {
+ GetOrCreateChannelProxyImpl_w(component);
+ }
+ return channel;
+}
+
+bool TransportProxy::HasChannel(int component) {
+ return transport_->get()->HasChannel(component);
+}
+
+void TransportProxy::DestroyChannel(int component) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ TransportChannel* channel = GetChannel(component);
+ if (channel) {
+ // If the state of TransportProxy is not NEGOTIATED
+ // then TransportChannelProxy and its impl are not
+ // connected. Both must be connected before
+ // deletion.
+ if (!negotiated_) {
+ SetupChannelProxy_w(component, GetChannelProxy(component));
+ }
+
+ channels_.erase(component);
+ channel->SignalDestroyed(channel);
+ delete channel;
+ }
+}
+
+void TransportProxy::ConnectChannels() {
+ if (!connecting_) {
+ if (!negotiated_) {
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ GetOrCreateChannelProxyImpl(iter->first);
+ }
+ }
+ connecting_ = true;
+ }
+ // TODO(juberti): Right now Transport::ConnectChannels doesn't work if we
+ // don't have any channels yet, so we need to allow this method to be called
+ // multiple times. Once we fix Transport, we can move this call inside the
+ // if (!connecting_) block.
+ transport_->get()->ConnectChannels();
+}
+
+void TransportProxy::CompleteNegotiation() {
+ if (!negotiated_) {
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ SetupChannelProxy(iter->first, iter->second);
+ }
+ negotiated_ = true;
+ }
+}
+
+void TransportProxy::AddSentCandidates(const Candidates& candidates) {
+ for (Candidates::const_iterator cand = candidates.begin();
+ cand != candidates.end(); ++cand) {
+ sent_candidates_.push_back(*cand);
+ }
+}
+
+void TransportProxy::AddUnsentCandidates(const Candidates& candidates) {
+ for (Candidates::const_iterator cand = candidates.begin();
+ cand != candidates.end(); ++cand) {
+ unsent_candidates_.push_back(*cand);
+ }
+}
+
+bool TransportProxy::GetChannelNameFromComponent(
+ int component, std::string* channel_name) const {
+ const TransportChannelProxy* channel = GetChannelProxy(component);
+ if (channel == NULL) {
+ return false;
+ }
+
+ *channel_name = channel->name();
+ return true;
+}
+
+bool TransportProxy::GetComponentFromChannelName(
+ const std::string& channel_name, int* component) const {
+ const TransportChannelProxy* channel = GetChannelProxyByName(channel_name);
+ if (channel == NULL) {
+ return false;
+ }
+
+ *component = channel->component();
+ return true;
+}
+
+TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const {
+ ChannelMap::const_iterator iter = channels_.find(component);
+ return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+TransportChannelProxy* TransportProxy::GetChannelProxyByName(
+ const std::string& name) const {
+ for (ChannelMap::const_iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ if (iter->second->name() == name) {
+ return iter->second;
+ }
+ }
+ return NULL;
+}
+
+TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl(
+ int component) {
+ return worker_thread_->Invoke<TransportChannelImpl*>(Bind(
+ &TransportProxy::GetOrCreateChannelProxyImpl_w, this, component));
+}
+
+TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl_w(
+ int component) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ TransportChannelImpl* impl = transport_->get()->GetChannel(component);
+ if (impl == NULL) {
+ impl = transport_->get()->CreateChannel(component);
+ }
+ return impl;
+}
+
+void TransportProxy::SetupChannelProxy(
+ int component, TransportChannelProxy* transproxy) {
+ worker_thread_->Invoke<void>(Bind(
+ &TransportProxy::SetupChannelProxy_w, this, component, transproxy));
+}
+
+void TransportProxy::SetupChannelProxy_w(
+ int component, TransportChannelProxy* transproxy) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ TransportChannelImpl* impl = GetOrCreateChannelProxyImpl(component);
+ ASSERT(impl != NULL);
+ transproxy->SetImplementation(impl);
+}
+
+void TransportProxy::ReplaceChannelProxyImpl(TransportChannelProxy* proxy,
+ TransportChannelImpl* impl) {
+ worker_thread_->Invoke<void>(Bind(
+ &TransportProxy::ReplaceChannelProxyImpl_w, this, proxy, impl));
+}
+
+void TransportProxy::ReplaceChannelProxyImpl_w(TransportChannelProxy* proxy,
+ TransportChannelImpl* impl) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(proxy != NULL);
+ proxy->SetImplementation(impl);
+}
+
+// This function muxes |this| onto |target| by repointing |this| at
+// |target|'s transport and setting our TransportChannelProxies
+// to point to |target|'s underlying implementations.
+bool TransportProxy::SetupMux(TransportProxy* target) {
+ // Bail out if there's nothing to do.
+ if (transport_ == target->transport_) {
+ return true;
+ }
+
+ // Run through all channels and remove any non-rtp transport channels before
+ // setting target transport channels.
+ for (ChannelMap::const_iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ if (!target->transport_->get()->HasChannel(iter->first)) {
+ // Remove if channel doesn't exist in |transport_|.
+ ReplaceChannelProxyImpl(iter->second, NULL);
+ } else {
+ // Replace the impl for all the TransportProxyChannels with the channels
+ // from |target|'s transport. Fail if there's not an exact match.
+ ReplaceChannelProxyImpl(
+ iter->second, target->transport_->get()->CreateChannel(iter->first));
+ }
+ }
+
+ // Now replace our transport. Must happen afterwards because
+ // it deletes all impls as a side effect.
+ transport_ = target->transport_;
+ transport_->get()->SignalCandidatesReady.connect(
+ this, &TransportProxy::OnTransportCandidatesReady);
+ set_candidates_allocated(target->candidates_allocated());
+ return true;
+}
+
+void TransportProxy::SetIceRole(IceRole role) {
+ transport_->get()->SetIceRole(role);
+}
+
+bool TransportProxy::SetLocalTransportDescription(
+ const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc) {
+ // If this is an answer, finalize the negotiation.
+ if (action == CA_ANSWER) {
+ CompleteNegotiation();
+ }
+ bool result = transport_->get()->SetLocalTransportDescription(description,
+ action,
+ error_desc);
+ if (result)
+ local_description_set_ = true;
+ return result;
+}
+
+bool TransportProxy::SetRemoteTransportDescription(
+ const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc) {
+ // If this is an answer, finalize the negotiation.
+ if (action == CA_ANSWER) {
+ CompleteNegotiation();
+ }
+ bool result = transport_->get()->SetRemoteTransportDescription(description,
+ action,
+ error_desc);
+ if (result)
+ remote_description_set_ = true;
+ return result;
+}
+
+void TransportProxy::OnSignalingReady() {
+ // If we're starting a new allocation sequence, reset our state.
+ set_candidates_allocated(false);
+ transport_->get()->OnSignalingReady();
+}
+
+bool TransportProxy::OnRemoteCandidates(const Candidates& candidates,
+ std::string* error) {
+ // Ensure the transport is negotiated before handling candidates.
+ // TODO(juberti): Remove this once everybody calls SetLocalTD.
+ CompleteNegotiation();
+
+ // Verify each candidate before passing down to transport layer.
+ for (Candidates::const_iterator cand = candidates.begin();
+ cand != candidates.end(); ++cand) {
+ if (!transport_->get()->VerifyCandidate(*cand, error))
+ return false;
+ if (!HasChannel(cand->component())) {
+ *error = "Candidate has unknown component: " + cand->ToString() +
+ " for content: " + content_name_;
+ return false;
+ }
+ }
+ transport_->get()->OnRemoteCandidates(candidates);
+ return true;
+}
+
+void TransportProxy::SetIdentity(
+ rtc::SSLIdentity* identity) {
+ transport_->get()->SetIdentity(identity);
+}
+
+std::string BaseSession::StateToString(State state) {
+ switch (state) {
+ case Session::STATE_INIT:
+ return "STATE_INIT";
+ case Session::STATE_SENTINITIATE:
+ return "STATE_SENTINITIATE";
+ case Session::STATE_RECEIVEDINITIATE:
+ return "STATE_RECEIVEDINITIATE";
+ case Session::STATE_SENTPRACCEPT:
+ return "STATE_SENTPRACCEPT";
+ case Session::STATE_SENTACCEPT:
+ return "STATE_SENTACCEPT";
+ case Session::STATE_RECEIVEDPRACCEPT:
+ return "STATE_RECEIVEDPRACCEPT";
+ case Session::STATE_RECEIVEDACCEPT:
+ return "STATE_RECEIVEDACCEPT";
+ case Session::STATE_SENTMODIFY:
+ return "STATE_SENTMODIFY";
+ case Session::STATE_RECEIVEDMODIFY:
+ return "STATE_RECEIVEDMODIFY";
+ case Session::STATE_SENTREJECT:
+ return "STATE_SENTREJECT";
+ case Session::STATE_RECEIVEDREJECT:
+ return "STATE_RECEIVEDREJECT";
+ case Session::STATE_SENTREDIRECT:
+ return "STATE_SENTREDIRECT";
+ case Session::STATE_SENTTERMINATE:
+ return "STATE_SENTTERMINATE";
+ case Session::STATE_RECEIVEDTERMINATE:
+ return "STATE_RECEIVEDTERMINATE";
+ case Session::STATE_INPROGRESS:
+ return "STATE_INPROGRESS";
+ case Session::STATE_DEINIT:
+ return "STATE_DEINIT";
+ default:
+ break;
+ }
+ return "STATE_" + rtc::ToString(state);
+}
+
+BaseSession::BaseSession(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ PortAllocator* port_allocator,
+ const std::string& sid,
+ const std::string& content_type,
+ bool initiator)
+ : state_(STATE_INIT),
+ error_(ERROR_NONE),
+ signaling_thread_(signaling_thread),
+ worker_thread_(worker_thread),
+ port_allocator_(port_allocator),
+ sid_(sid),
+ content_type_(content_type),
+ transport_type_(NS_GINGLE_P2P),
+ initiator_(initiator),
+ identity_(NULL),
+ ice_tiebreaker_(rtc::CreateRandomId64()),
+ role_switch_(false) {
+ ASSERT(signaling_thread->IsCurrent());
+}
+
+BaseSession::~BaseSession() {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ ASSERT(state_ != STATE_DEINIT);
+ LogState(state_, STATE_DEINIT);
+ state_ = STATE_DEINIT;
+ SignalState(this, state_);
+
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ delete iter->second;
+ }
+}
+
+const SessionDescription* BaseSession::local_description() const {
+ // TODO(tommi): Assert on thread correctness.
+ return local_description_.get();
+}
+
+const SessionDescription* BaseSession::remote_description() const {
+ // TODO(tommi): Assert on thread correctness.
+ return remote_description_.get();
+}
+
+SessionDescription* BaseSession::remote_description() {
+ // TODO(tommi): Assert on thread correctness.
+ return remote_description_.get();
+}
+
+void BaseSession::set_local_description(const SessionDescription* sdesc) {
+ // TODO(tommi): Assert on thread correctness.
+ if (sdesc != local_description_.get())
+ local_description_.reset(sdesc);
+}
+
+void BaseSession::set_remote_description(SessionDescription* sdesc) {
+ // TODO(tommi): Assert on thread correctness.
+ if (sdesc != remote_description_)
+ remote_description_.reset(sdesc);
+}
+
+const SessionDescription* BaseSession::initiator_description() const {
+ // TODO(tommi): Assert on thread correctness.
+ return initiator_ ? local_description_.get() : remote_description_.get();
+}
+
+bool BaseSession::SetIdentity(rtc::SSLIdentity* identity) {
+ if (identity_)
+ return false;
+ identity_ = identity;
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->SetIdentity(identity_);
+ }
+ return true;
+}
+
+bool BaseSession::PushdownTransportDescription(ContentSource source,
+ ContentAction action,
+ std::string* error_desc) {
+ if (source == CS_LOCAL) {
+ return PushdownLocalTransportDescription(local_description(),
+ action,
+ error_desc);
+ }
+ return PushdownRemoteTransportDescription(remote_description(),
+ action,
+ error_desc);
+}
+
+bool BaseSession::PushdownLocalTransportDescription(
+ const SessionDescription* sdesc,
+ ContentAction action,
+ std::string* error_desc) {
+ // Update the Transports with the right information, and trigger them to
+ // start connecting.
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ // If no transport info was in this session description, ret == false
+ // and we just skip this one.
+ TransportDescription tdesc;
+ bool ret = GetTransportDescription(
+ sdesc, iter->second->content_name(), &tdesc);
+ if (ret) {
+ if (!iter->second->SetLocalTransportDescription(tdesc, action,
+ error_desc)) {
+ return false;
+ }
+
+ iter->second->ConnectChannels();
+ }
+ }
+
+ return true;
+}
+
+bool BaseSession::PushdownRemoteTransportDescription(
+ const SessionDescription* sdesc,
+ ContentAction action,
+ std::string* error_desc) {
+ // Update the Transports with the right information.
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ TransportDescription tdesc;
+
+ // If no transport info was in this session description, ret == false
+ // and we just skip this one.
+ bool ret = GetTransportDescription(
+ sdesc, iter->second->content_name(), &tdesc);
+ if (ret) {
+ if (!iter->second->SetRemoteTransportDescription(tdesc, action,
+ error_desc)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+TransportChannel* BaseSession::CreateChannel(const std::string& content_name,
+ const std::string& channel_name,
+ int component) {
+ // We create the proxy "on demand" here because we need to support
+ // creating channels at any time, even before we send or receive
+ // initiate messages, which is before we create the transports.
+ TransportProxy* transproxy = GetOrCreateTransportProxy(content_name);
+ return transproxy->CreateChannel(channel_name, component);
+}
+
+TransportChannel* BaseSession::GetChannel(const std::string& content_name,
+ int component) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy == NULL)
+ return NULL;
+
+ return transproxy->GetChannel(component);
+}
+
+void BaseSession::DestroyChannel(const std::string& content_name,
+ int component) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ ASSERT(transproxy != NULL);
+ transproxy->DestroyChannel(component);
+}
+
+TransportProxy* BaseSession::GetOrCreateTransportProxy(
+ const std::string& content_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy)
+ return transproxy;
+
+ Transport* transport = CreateTransport(content_name);
+ transport->SetIceRole(initiator_ ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED);
+ transport->SetIceTiebreaker(ice_tiebreaker_);
+ // TODO: Connect all the Transport signals to TransportProxy
+ // then to the BaseSession.
+ transport->SignalConnecting.connect(
+ this, &BaseSession::OnTransportConnecting);
+ transport->SignalWritableState.connect(
+ this, &BaseSession::OnTransportWritable);
+ transport->SignalRequestSignaling.connect(
+ this, &BaseSession::OnTransportRequestSignaling);
+ transport->SignalTransportError.connect(
+ this, &BaseSession::OnTransportSendError);
+ transport->SignalRouteChange.connect(
+ this, &BaseSession::OnTransportRouteChange);
+ transport->SignalCandidatesAllocationDone.connect(
+ this, &BaseSession::OnTransportCandidatesAllocationDone);
+ transport->SignalRoleConflict.connect(
+ this, &BaseSession::OnRoleConflict);
+ transport->SignalCompleted.connect(
+ this, &BaseSession::OnTransportCompleted);
+ transport->SignalFailed.connect(
+ this, &BaseSession::OnTransportFailed);
+
+ transproxy = new TransportProxy(worker_thread_, sid_, content_name,
+ new TransportWrapper(transport));
+ transproxy->SignalCandidatesReady.connect(
+ this, &BaseSession::OnTransportProxyCandidatesReady);
+ if (identity_)
+ transproxy->SetIdentity(identity_);
+ transports_[content_name] = transproxy;
+
+ return transproxy;
+}
+
+Transport* BaseSession::GetTransport(const std::string& content_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy == NULL)
+ return NULL;
+ return transproxy->impl();
+}
+
+TransportProxy* BaseSession::GetTransportProxy(
+ const std::string& content_name) {
+ TransportMap::iterator iter = transports_.find(content_name);
+ return (iter != transports_.end()) ? iter->second : NULL;
+}
+
+TransportProxy* BaseSession::GetTransportProxy(const Transport* transport) {
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ TransportProxy* transproxy = iter->second;
+ if (transproxy->impl() == transport) {
+ return transproxy;
+ }
+ }
+ return NULL;
+}
+
+TransportProxy* BaseSession::GetFirstTransportProxy() {
+ if (transports_.empty())
+ return NULL;
+ return transports_.begin()->second;
+}
+
+void BaseSession::DestroyTransportProxy(
+ const std::string& content_name) {
+ TransportMap::iterator iter = transports_.find(content_name);
+ if (iter != transports_.end()) {
+ delete iter->second;
+ transports_.erase(content_name);
+ }
+}
+
+cricket::Transport* BaseSession::CreateTransport(
+ const std::string& content_name) {
+ ASSERT(transport_type_ == NS_GINGLE_P2P);
+ return new cricket::DtlsTransport<P2PTransport>(
+ signaling_thread(), worker_thread(), content_name,
+ port_allocator(), identity_);
+}
+
+bool BaseSession::GetStats(SessionStats* stats) {
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ std::string proxy_id = iter->second->content_name();
+ // We are ignoring not-yet-instantiated transports.
+ if (iter->second->impl()) {
+ std::string transport_id = iter->second->impl()->content_name();
+ stats->proxy_to_transport[proxy_id] = transport_id;
+ if (stats->transport_stats.find(transport_id)
+ == stats->transport_stats.end()) {
+ TransportStats subinfos;
+ if (!iter->second->impl()->GetStats(&subinfos)) {
+ return false;
+ }
+ stats->transport_stats[transport_id] = subinfos;
+ }
+ }
+ }
+ return true;
+}
+
+void BaseSession::SetState(State state) {
+ ASSERT(signaling_thread_->IsCurrent());
+ if (state != state_) {
+ LogState(state_, state);
+ state_ = state;
+ SignalState(this, state_);
+ signaling_thread_->Post(this, MSG_STATE);
+ }
+ SignalNewDescription();
+}
+
+void BaseSession::SetError(Error error, const std::string& error_desc) {
+ ASSERT(signaling_thread_->IsCurrent());
+ if (error != error_) {
+ error_ = error;
+ error_desc_ = error_desc;
+ SignalError(this, error);
+ }
+}
+
+void BaseSession::OnSignalingReady() {
+ ASSERT(signaling_thread()->IsCurrent());
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->OnSignalingReady();
+ }
+}
+
+// TODO(juberti): Since PushdownLocalTD now triggers the connection process to
+// start, remove this method once everyone calls PushdownLocalTD.
+void BaseSession::SpeculativelyConnectAllTransportChannels() {
+ // Put all transports into the connecting state.
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->ConnectChannels();
+ }
+}
+
+bool BaseSession::OnRemoteCandidates(const std::string& content_name,
+ const Candidates& candidates,
+ std::string* error) {
+ // Give candidates to the appropriate transport, and tell that transport
+ // to start connecting, if it's not already doing so.
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (!transproxy) {
+ *error = "Unknown content name " + content_name;
+ return false;
+ }
+ if (!transproxy->OnRemoteCandidates(candidates, error)) {
+ return false;
+ }
+ // TODO(juberti): Remove this call once we can be sure that we always have
+ // a local transport description (which will trigger the connection).
+ transproxy->ConnectChannels();
+ return true;
+}
+
+bool BaseSession::MaybeEnableMuxingSupport() {
+ // We need both a local and remote description to decide if we should mux.
+ if ((state_ == STATE_SENTINITIATE ||
+ state_ == STATE_RECEIVEDINITIATE) &&
+ ((local_description_ == NULL) ||
+ (remote_description_ == NULL))) {
+ return false;
+ }
+
+ // In order to perform the multiplexing, we need all proxies to be in the
+ // negotiated state, i.e. to have implementations underneath.
+ // Ensure that this is the case, regardless of whether we are going to mux.
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ ASSERT(iter->second->negotiated());
+ if (!iter->second->negotiated())
+ return false;
+ }
+
+ // If both sides agree to BUNDLE, mux all the specified contents onto the
+ // transport belonging to the first content name in the BUNDLE group.
+ // If the contents are already muxed, this will be a no-op.
+ // TODO(juberti): Should this check that local and remote have configured
+ // BUNDLE the same way?
+ bool candidates_allocated = IsCandidateAllocationDone();
+ const ContentGroup* local_bundle_group =
+ local_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
+ const ContentGroup* remote_bundle_group =
+ remote_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
+ if (local_bundle_group && remote_bundle_group &&
+ local_bundle_group->FirstContentName()) {
+ const std::string* content_name = local_bundle_group->FirstContentName();
+ const ContentInfo* content =
+ local_description_->GetContentByName(*content_name);
+ ASSERT(content != NULL);
+ if (!SetSelectedProxy(content->name, local_bundle_group)) {
+ LOG(LS_WARNING) << "Failed to set up BUNDLE";
+ return false;
+ }
+
+ // If we weren't done gathering before, we might be done now, as a result
+ // of enabling mux.
+ LOG(LS_INFO) << "Enabling BUNDLE, bundling onto transport: "
+ << *content_name;
+ if (!candidates_allocated) {
+ MaybeCandidateAllocationDone();
+ }
+ } else {
+ LOG(LS_INFO) << "No BUNDLE information, not bundling.";
+ }
+ return true;
+}
+
+bool BaseSession::SetSelectedProxy(const std::string& content_name,
+ const ContentGroup* muxed_group) {
+ TransportProxy* selected_proxy = GetTransportProxy(content_name);
+ if (!selected_proxy) {
+ return false;
+ }
+
+ ASSERT(selected_proxy->negotiated());
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ // If content is part of the mux group, then repoint its proxy at the
+ // transport object that we have chosen to mux onto. If the proxy
+ // is already pointing at the right object, it will be a no-op.
+ if (muxed_group->HasContentName(iter->first) &&
+ !iter->second->SetupMux(selected_proxy)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void BaseSession::OnTransportCandidatesAllocationDone(Transport* transport) {
+ // TODO(juberti): This is a clunky way of processing the done signal. Instead,
+ // TransportProxy should receive the done signal directly, set its allocated
+ // flag internally, and then reissue the done signal to Session.
+ // Overall we should make TransportProxy receive *all* the signals from
+ // Transport, since this removes the need to manually iterate over all
+ // the transports, as is needed to make sure signals are handled properly
+ // when BUNDLEing.
+ // TODO(juberti): Per b/7998978, devs and QA are hitting this assert in ways
+ // that make it prohibitively difficult to run dbg builds. Disabled for now.
+ //ASSERT(!IsCandidateAllocationDone());
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ if (iter->second->impl() == transport) {
+ iter->second->set_candidates_allocated(true);
+ }
+ }
+ MaybeCandidateAllocationDone();
+}
+
+bool BaseSession::IsCandidateAllocationDone() const {
+ for (TransportMap::const_iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ if (!iter->second->candidates_allocated())
+ return false;
+ }
+ return true;
+}
+
+void BaseSession::MaybeCandidateAllocationDone() {
+ if (IsCandidateAllocationDone()) {
+ LOG(LS_INFO) << "Candidate gathering is complete.";
+ OnCandidatesAllocationDone();
+ }
+}
+
+void BaseSession::OnRoleConflict() {
+ if (role_switch_) {
+ LOG(LS_WARNING) << "Repeat of role conflict signal from Transport.";
+ return;
+ }
+
+ role_switch_ = true;
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ // Role will be reverse of initial role setting.
+ IceRole role = initiator_ ? ICEROLE_CONTROLLED : ICEROLE_CONTROLLING;
+ iter->second->SetIceRole(role);
+ }
+}
+
+void BaseSession::LogState(State old_state, State new_state) {
+ LOG(LS_INFO) << "Session:" << id()
+ << " Old state:" << StateToString(old_state)
+ << " New state:" << StateToString(new_state)
+ << " Type:" << content_type()
+ << " Transport:" << transport_type();
+}
+
+// static
+bool BaseSession::GetTransportDescription(const SessionDescription* description,
+ const std::string& content_name,
+ TransportDescription* tdesc) {
+ if (!description || !tdesc) {
+ return false;
+ }
+ const TransportInfo* transport_info =
+ description->GetTransportInfoByName(content_name);
+ if (!transport_info) {
+ return false;
+ }
+ *tdesc = transport_info->description;
+ return true;
+}
+
+void BaseSession::SignalNewDescription() {
+ ContentAction action;
+ ContentSource source;
+ if (!GetContentAction(&action, &source)) {
+ return;
+ }
+ if (source == CS_LOCAL) {
+ SignalNewLocalDescription(this, action);
+ } else {
+ SignalNewRemoteDescription(this, action);
+ }
+}
+
+bool BaseSession::GetContentAction(ContentAction* action,
+ ContentSource* source) {
+ switch (state_) {
+ // new local description
+ case STATE_SENTINITIATE:
+ *action = CA_OFFER;
+ *source = CS_LOCAL;
+ break;
+ case STATE_SENTPRACCEPT:
+ *action = CA_PRANSWER;
+ *source = CS_LOCAL;
+ break;
+ case STATE_SENTACCEPT:
+ *action = CA_ANSWER;
+ *source = CS_LOCAL;
+ break;
+ // new remote description
+ case STATE_RECEIVEDINITIATE:
+ *action = CA_OFFER;
+ *source = CS_REMOTE;
+ break;
+ case STATE_RECEIVEDPRACCEPT:
+ *action = CA_PRANSWER;
+ *source = CS_REMOTE;
+ break;
+ case STATE_RECEIVEDACCEPT:
+ *action = CA_ANSWER;
+ *source = CS_REMOTE;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void BaseSession::OnMessage(rtc::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_TIMEOUT:
+ // Session timeout has occured.
+ SetError(ERROR_TIME, "Session timeout has occured.");
+ break;
+
+ case MSG_STATE:
+ switch (state_) {
+ case STATE_SENTACCEPT:
+ case STATE_RECEIVEDACCEPT:
+ SetState(STATE_INPROGRESS);
+ break;
+
+ default:
+ // Explicitly ignoring some states here.
+ break;
+ }
+ break;
+ }
+}
+
+Session::Session(SessionManager* session_manager,
+ const std::string& local_name,
+ const std::string& initiator_name,
+ const std::string& sid,
+ const std::string& content_type,
+ SessionClient* client)
+ : BaseSession(session_manager->signaling_thread(),
+ session_manager->worker_thread(),
+ session_manager->port_allocator(),
+ sid, content_type, initiator_name == local_name) {
+ ASSERT(client != NULL);
+ session_manager_ = session_manager;
+ local_name_ = local_name;
+ initiator_name_ = initiator_name;
+ transport_parser_ = new P2PTransportParser();
+ client_ = client;
+ initiate_acked_ = false;
+ current_protocol_ = PROTOCOL_HYBRID;
+}
+
+Session::~Session() {
+ delete transport_parser_;
+}
+
+bool Session::Initiate(const std::string& to,
+ const SessionDescription* sdesc) {
+ ASSERT(signaling_thread()->IsCurrent());
+ SessionError error;
+
+ // Only from STATE_INIT
+ if (state() != STATE_INIT)
+ return false;
+
+ // Setup for signaling.
+ set_remote_name(to);
+ set_local_description(sdesc);
+ if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()),
+ &error)) {
+ LOG(LS_ERROR) << "Could not create transports: " << error.text;
+ return false;
+ }
+
+ if (!SendInitiateMessage(sdesc, &error)) {
+ LOG(LS_ERROR) << "Could not send initiate message: " << error.text;
+ return false;
+ }
+
+ // We need to connect transport proxy and impl here so that we can process
+ // the TransportDescriptions.
+ SpeculativelyConnectAllTransportChannels();
+
+ PushdownTransportDescription(CS_LOCAL, CA_OFFER, NULL);
+ SetState(Session::STATE_SENTINITIATE);
+ return true;
+}
+
+bool Session::Accept(const SessionDescription* sdesc) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ // Only if just received initiate
+ if (state() != STATE_RECEIVEDINITIATE)
+ return false;
+
+ // Setup for signaling.
+ set_local_description(sdesc);
+
+ SessionError error;
+ if (!SendAcceptMessage(sdesc, &error)) {
+ LOG(LS_ERROR) << "Could not send accept message: " << error.text;
+ return false;
+ }
+ // TODO(juberti): Add BUNDLE support to transport-info messages.
+ PushdownTransportDescription(CS_LOCAL, CA_ANSWER, NULL);
+ MaybeEnableMuxingSupport(); // Enable transport channel mux if supported.
+ SetState(Session::STATE_SENTACCEPT);
+ return true;
+}
+
+bool Session::Reject(const std::string& reason) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ // Reject is sent in response to an initiate or modify, to reject the
+ // request
+ if (state() != STATE_RECEIVEDINITIATE && state() != STATE_RECEIVEDMODIFY)
+ return false;
+
+ SessionError error;
+ if (!SendRejectMessage(reason, &error)) {
+ LOG(LS_ERROR) << "Could not send reject message: " << error.text;
+ return false;
+ }
+
+ SetState(STATE_SENTREJECT);
+ return true;
+}
+
+bool Session::TerminateWithReason(const std::string& reason) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ // Either side can terminate, at any time.
+ switch (state()) {
+ case STATE_SENTTERMINATE:
+ case STATE_RECEIVEDTERMINATE:
+ return false;
+
+ case STATE_SENTREJECT:
+ case STATE_RECEIVEDREJECT:
+ // We don't need to send terminate if we sent or received a reject...
+ // it's implicit.
+ break;
+
+ default:
+ SessionError error;
+ if (!SendTerminateMessage(reason, &error)) {
+ LOG(LS_ERROR) << "Could not send terminate message: " << error.text;
+ return false;
+ }
+ break;
+ }
+
+ SetState(STATE_SENTTERMINATE);
+ return true;
+}
+
+bool Session::SendInfoMessage(const XmlElements& elems,
+ const std::string& remote_name) {
+ ASSERT(signaling_thread()->IsCurrent());
+ SessionError error;
+ if (!SendMessage(ACTION_SESSION_INFO, elems, remote_name, &error)) {
+ LOG(LS_ERROR) << "Could not send info message " << error.text;
+ return false;
+ }
+ return true;
+}
+
+bool Session::SendDescriptionInfoMessage(const ContentInfos& contents) {
+ XmlElements elems;
+ WriteError write_error;
+ if (!WriteDescriptionInfo(current_protocol_,
+ contents,
+ GetContentParsers(),
+ &elems, &write_error)) {
+ LOG(LS_ERROR) << "Could not write description info message: "
+ << write_error.text;
+ return false;
+ }
+ SessionError error;
+ if (!SendMessage(ACTION_DESCRIPTION_INFO, elems, &error)) {
+ LOG(LS_ERROR) << "Could not send description info message: "
+ << error.text;
+ return false;
+ }
+ return true;
+}
+
+TransportInfos Session::GetEmptyTransportInfos(
+ const ContentInfos& contents) const {
+ TransportInfos tinfos;
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ tinfos.push_back(TransportInfo(content->name,
+ TransportDescription(transport_type(),
+ std::string(),
+ std::string())));
+ }
+ return tinfos;
+}
+
+bool Session::OnRemoteCandidates(
+ const TransportInfos& tinfos, ParseError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ std::string str_error;
+ if (!BaseSession::OnRemoteCandidates(
+ tinfo->content_name, tinfo->description.candidates, &str_error)) {
+ return BadParse(str_error, error);
+ }
+ }
+ return true;
+}
+
+bool Session::CreateTransportProxies(const TransportInfos& tinfos,
+ SessionError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (tinfo->description.transport_type != transport_type()) {
+ error->SetText("No supported transport in offer.");
+ return false;
+ }
+
+ GetOrCreateTransportProxy(tinfo->content_name);
+ }
+ return true;
+}
+
+TransportParserMap Session::GetTransportParsers() {
+ TransportParserMap parsers;
+ parsers[transport_type()] = transport_parser_;
+ return parsers;
+}
+
+CandidateTranslatorMap Session::GetCandidateTranslators() {
+ CandidateTranslatorMap translators;
+ // NOTE: This technique makes it impossible to parse G-ICE
+ // candidates in session-initiate messages because the channels
+ // aren't yet created at that point. Since we don't use candidates
+ // in session-initiate messages, we should be OK. Once we switch to
+ // ICE, this translation shouldn't be necessary.
+ for (TransportMap::const_iterator iter = transport_proxies().begin();
+ iter != transport_proxies().end(); ++iter) {
+ translators[iter->first] = iter->second;
+ }
+ return translators;
+}
+
+ContentParserMap Session::GetContentParsers() {
+ ContentParserMap parsers;
+ parsers[content_type()] = client_;
+ // We need to be able parse both RTP-based and SCTP-based Jingle
+ // with the same client.
+ if (content_type() == NS_JINGLE_RTP) {
+ parsers[NS_JINGLE_DRAFT_SCTP] = client_;
+ }
+ return parsers;
+}
+
+void Session::OnTransportRequestSignaling(Transport* transport) {
+ ASSERT(signaling_thread()->IsCurrent());
+ TransportProxy* transproxy = GetTransportProxy(transport);
+ ASSERT(transproxy != NULL);
+ if (transproxy) {
+ // Reset candidate allocation status for the transport proxy.
+ transproxy->set_candidates_allocated(false);
+ }
+ SignalRequestSignaling(this);
+}
+
+void Session::OnTransportConnecting(Transport* transport) {
+ // This is an indication that we should begin watching the writability
+ // state of the transport.
+ OnTransportWritable(transport);
+}
+
+void Session::OnTransportWritable(Transport* transport) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ // If the transport is not writable, start a timer to make sure that it
+ // becomes writable within a reasonable amount of time. If it does not, we
+ // terminate since we can't actually send data. If the transport is writable,
+ // cancel the timer. Note that writability transitions may occur repeatedly
+ // during the lifetime of the session.
+ signaling_thread()->Clear(this, MSG_TIMEOUT);
+ if (transport->HasChannels() && !transport->writable()) {
+ signaling_thread()->PostDelayed(
+ session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT);
+ }
+}
+
+void Session::OnTransportProxyCandidatesReady(TransportProxy* transproxy,
+ const Candidates& candidates) {
+ ASSERT(signaling_thread()->IsCurrent());
+ if (transproxy != NULL) {
+ if (initiator() && !initiate_acked_) {
+ // TODO: This is to work around server re-ordering
+ // messages. We send the candidates once the session-initiate
+ // is acked. Once we have fixed the server to guarantee message
+ // order, we can remove this case.
+ transproxy->AddUnsentCandidates(candidates);
+ } else {
+ if (!transproxy->negotiated()) {
+ transproxy->AddSentCandidates(candidates);
+ }
+ SessionError error;
+ if (!SendTransportInfoMessage(transproxy, candidates, &error)) {
+ LOG(LS_ERROR) << "Could not send transport info message: "
+ << error.text;
+ return;
+ }
+ }
+ }
+}
+
+void Session::OnTransportSendError(Transport* transport,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ ASSERT(signaling_thread()->IsCurrent());
+ SignalErrorMessage(this, stanza, name, type, text, extra_info);
+}
+
+void Session::OnIncomingMessage(const SessionMessage& msg) {
+ ASSERT(signaling_thread()->IsCurrent());
+ ASSERT(state() == STATE_INIT || msg.from == remote_name());
+
+ if (current_protocol_== PROTOCOL_HYBRID) {
+ if (msg.protocol == PROTOCOL_GINGLE) {
+ current_protocol_ = PROTOCOL_GINGLE;
+ } else {
+ current_protocol_ = PROTOCOL_JINGLE;
+ }
+ }
+
+ bool valid = false;
+ MessageError error;
+ switch (msg.type) {
+ case ACTION_SESSION_INITIATE:
+ valid = OnInitiateMessage(msg, &error);
+ break;
+ case ACTION_SESSION_INFO:
+ valid = OnInfoMessage(msg);
+ break;
+ case ACTION_SESSION_ACCEPT:
+ valid = OnAcceptMessage(msg, &error);
+ break;
+ case ACTION_SESSION_REJECT:
+ valid = OnRejectMessage(msg, &error);
+ break;
+ case ACTION_SESSION_TERMINATE:
+ valid = OnTerminateMessage(msg, &error);
+ break;
+ case ACTION_TRANSPORT_INFO:
+ valid = OnTransportInfoMessage(msg, &error);
+ break;
+ case ACTION_TRANSPORT_ACCEPT:
+ valid = OnTransportAcceptMessage(msg, &error);
+ break;
+ case ACTION_DESCRIPTION_INFO:
+ valid = OnDescriptionInfoMessage(msg, &error);
+ break;
+ default:
+ valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
+ "unknown session message type",
+ &error);
+ }
+
+ if (valid) {
+ SendAcknowledgementMessage(msg.stanza);
+ } else {
+ SignalErrorMessage(this, msg.stanza, error.type,
+ "modify", error.text, NULL);
+ }
+}
+
+void Session::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza,
+ const SessionMessage& msg) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ if (msg.type == ACTION_SESSION_INITIATE) {
+ OnInitiateAcked();
+ }
+}
+
+void Session::OnInitiateAcked() {
+ // TODO: This is to work around server re-ordering
+ // messages. We send the candidates once the session-initiate
+ // is acked. Once we have fixed the server to guarantee message
+ // order, we can remove this case.
+ if (!initiate_acked_) {
+ initiate_acked_ = true;
+ SessionError error;
+ SendAllUnsentTransportInfoMessages(&error);
+ }
+}
+
+void Session::OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ SessionMessage msg;
+ ParseError parse_error;
+ if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) {
+ LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text
+ << ":" << orig_stanza;
+ return;
+ }
+
+ // If the error is a session redirect, call OnRedirectError, which will
+ // continue the session with a new remote JID.
+ SessionRedirect redirect;
+ if (FindSessionRedirect(error_stanza, &redirect)) {
+ SessionError error;
+ if (!OnRedirectError(redirect, &error)) {
+ // TODO: Should we send a message back? The standard
+ // says nothing about it.
+ std::ostringstream desc;
+ desc << "Failed to redirect: " << error.text;
+ LOG(LS_ERROR) << desc.str();
+ SetError(ERROR_RESPONSE, desc.str());
+ }
+ return;
+ }
+
+ std::string error_type = "cancel";
+
+ const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
+ if (error) {
+ error_type = error->Attr(buzz::QN_TYPE);
+
+ LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n"
+ << "in response to:\n" << orig_stanza->Str();
+ } else {
+ // don't crash if <error> is missing
+ LOG(LS_ERROR) << "Session error without <error/> element, ignoring";
+ return;
+ }
+
+ if (msg.type == ACTION_TRANSPORT_INFO) {
+ // Transport messages frequently generate errors because they are sent right
+ // when we detect a network failure. For that reason, we ignore such
+ // errors, because if we do not establish writability again, we will
+ // terminate anyway. The exceptions are transport-specific error tags,
+ // which we pass on to the respective transport.
+ } else if ((error_type != "continue") && (error_type != "wait")) {
+ // We do not set an error if the other side said it is okay to continue
+ // (possibly after waiting). These errors can be ignored.
+ SetError(ERROR_RESPONSE, "");
+ }
+}
+
+bool Session::OnInitiateMessage(const SessionMessage& msg,
+ MessageError* error) {
+ if (!CheckState(STATE_INIT, error))
+ return false;
+
+ SessionInitiate init;
+ if (!ParseSessionInitiate(msg.protocol, msg.action_elem,
+ GetContentParsers(), GetTransportParsers(),
+ GetCandidateTranslators(),
+ &init, error))
+ return false;
+
+ SessionError session_error;
+ if (!CreateTransportProxies(init.transports, &session_error)) {
+ return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE,
+ session_error.text, error);
+ }
+
+ set_remote_name(msg.from);
+ set_initiator_name(msg.initiator);
+ set_remote_description(new SessionDescription(init.ClearContents(),
+ init.transports,
+ init.groups));
+ // Updating transport with TransportDescription.
+ PushdownTransportDescription(CS_REMOTE, CA_OFFER, NULL);
+ SetState(STATE_RECEIVEDINITIATE);
+
+ // Users of Session may listen to state change and call Reject().
+ if (state() != STATE_SENTREJECT) {
+ if (!OnRemoteCandidates(init.transports, error))
+ return false;
+
+ // TODO(juberti): Auto-generate and push down the local transport answer.
+ // This is necessary for trickling to work with RFC 5245 ICE.
+ }
+ return true;
+}
+
+bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) {
+ if (!CheckState(STATE_SENTINITIATE, error))
+ return false;
+
+ SessionAccept accept;
+ if (!ParseSessionAccept(msg.protocol, msg.action_elem,
+ GetContentParsers(), GetTransportParsers(),
+ GetCandidateTranslators(),
+ &accept, error)) {
+ return false;
+ }
+
+ // If we get an accept, we can assume the initiate has been
+ // received, even if we haven't gotten an IQ response.
+ OnInitiateAcked();
+
+ set_remote_description(new SessionDescription(accept.ClearContents(),
+ accept.transports,
+ accept.groups));
+ // Updating transport with TransportDescription.
+ PushdownTransportDescription(CS_REMOTE, CA_ANSWER, NULL);
+ MaybeEnableMuxingSupport(); // Enable transport channel mux if supported.
+ SetState(STATE_RECEIVEDACCEPT);
+
+ if (!OnRemoteCandidates(accept.transports, error))
+ return false;
+
+ return true;
+}
+
+bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) {
+ if (!CheckState(STATE_SENTINITIATE, error))
+ return false;
+
+ SetState(STATE_RECEIVEDREJECT);
+ return true;
+}
+
+bool Session::OnInfoMessage(const SessionMessage& msg) {
+ SignalInfoMessage(this, msg.action_elem);
+ return true;
+}
+
+bool Session::OnTerminateMessage(const SessionMessage& msg,
+ MessageError* error) {
+ SessionTerminate term;
+ if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error))
+ return false;
+
+ SignalReceivedTerminateReason(this, term.reason);
+ if (term.debug_reason != buzz::STR_EMPTY) {
+ LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason;
+ }
+
+ SetState(STATE_RECEIVEDTERMINATE);
+ return true;
+}
+
+bool Session::OnTransportInfoMessage(const SessionMessage& msg,
+ MessageError* error) {
+ TransportInfos tinfos;
+ if (!ParseTransportInfos(msg.protocol, msg.action_elem,
+ initiator_description()->contents(),
+ GetTransportParsers(), GetCandidateTranslators(),
+ &tinfos, error))
+ return false;
+
+ if (!OnRemoteCandidates(tinfos, error))
+ return false;
+
+ return true;
+}
+
+bool Session::OnTransportAcceptMessage(const SessionMessage& msg,
+ MessageError* error) {
+ // TODO: Currently here only for compatibility with
+ // Gingle 1.1 clients (notably, Google Voice).
+ return true;
+}
+
+bool Session::OnDescriptionInfoMessage(const SessionMessage& msg,
+ MessageError* error) {
+ if (!CheckState(STATE_INPROGRESS, error))
+ return false;
+
+ DescriptionInfo description_info;
+ if (!ParseDescriptionInfo(msg.protocol, msg.action_elem,
+ GetContentParsers(), GetTransportParsers(),
+ GetCandidateTranslators(),
+ &description_info, error)) {
+ return false;
+ }
+
+ ContentInfos& updated_contents = description_info.contents;
+
+ // TODO: Currently, reflector sends back
+ // video stream updates even for an audio-only call, which causes
+ // this to fail. Put this back once reflector is fixed.
+ //
+ // ContentInfos::iterator it;
+ // First, ensure all updates are valid before modifying remote_description_.
+ // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+ // if (remote_description()->GetContentByName(it->name) == NULL) {
+ // return false;
+ // }
+ // }
+
+ // TODO: We used to replace contents from an update, but
+ // that no longer works with partial updates. We need to figure out
+ // a way to merge patial updates into contents. For now, users of
+ // Session should listen to SignalRemoteDescriptionUpdate and handle
+ // updates. They should not expect remote_description to be the
+ // latest value.
+ //
+ // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+ // remote_description()->RemoveContentByName(it->name);
+ // remote_description()->AddContent(it->name, it->type, it->description);
+ // }
+ // }
+
+ SignalRemoteDescriptionUpdate(this, updated_contents);
+ return true;
+}
+
+bool BareJidsEqual(const std::string& name1,
+ const std::string& name2) {
+ buzz::Jid jid1(name1);
+ buzz::Jid jid2(name2);
+
+ return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
+}
+
+bool Session::OnRedirectError(const SessionRedirect& redirect,
+ SessionError* error) {
+ MessageError message_error;
+ if (!CheckState(STATE_SENTINITIATE, &message_error)) {
+ return BadWrite(message_error.text, error);
+ }
+
+ if (!BareJidsEqual(remote_name(), redirect.target))
+ return BadWrite("Redirection not allowed: must be the same bare jid.",
+ error);
+
+ // When we receive a redirect, we point the session at the new JID
+ // and resend the candidates.
+ set_remote_name(redirect.target);
+ return (SendInitiateMessage(local_description(), error) &&
+ ResendAllTransportInfoMessages(error));
+}
+
+bool Session::CheckState(State expected, MessageError* error) {
+ if (state() != expected) {
+ // The server can deliver messages out of order/repeated for various
+ // reasons. For example, if the server does not recive our iq response,
+ // it could assume that the iq it sent was lost, and will then send
+ // it again. Ideally, we should implement reliable messaging with
+ // duplicate elimination.
+ return BadMessage(buzz::QN_STANZA_NOT_ALLOWED,
+ "message not allowed in current state",
+ error);
+ }
+ return true;
+}
+
+void Session::SetError(Error error, const std::string& error_desc) {
+ BaseSession::SetError(error, error_desc);
+ if (error != ERROR_NONE)
+ signaling_thread()->Post(this, MSG_ERROR);
+}
+
+void Session::OnMessage(rtc::Message* pmsg) {
+ // preserve this because BaseSession::OnMessage may modify it
+ State orig_state = state();
+
+ BaseSession::OnMessage(pmsg);
+
+ switch (pmsg->message_id) {
+ case MSG_ERROR:
+ TerminateWithReason(STR_TERMINATE_ERROR);
+ break;
+
+ case MSG_STATE:
+ switch (orig_state) {
+ case STATE_SENTREJECT:
+ case STATE_RECEIVEDREJECT:
+ // Assume clean termination.
+ Terminate();
+ break;
+
+ case STATE_SENTTERMINATE:
+ case STATE_RECEIVEDTERMINATE:
+ session_manager_->DestroySession(this);
+ break;
+
+ default:
+ // Explicitly ignoring some states here.
+ break;
+ }
+ break;
+ }
+}
+
+bool Session::SendInitiateMessage(const SessionDescription* sdesc,
+ SessionError* error) {
+ SessionInitiate init;
+ init.contents = sdesc->contents();
+ init.transports = GetEmptyTransportInfos(init.contents);
+ init.groups = sdesc->groups();
+ return SendMessage(ACTION_SESSION_INITIATE, init, error);
+}
+
+bool Session::WriteSessionAction(
+ SignalingProtocol protocol, const SessionInitiate& init,
+ XmlElements* elems, WriteError* error) {
+ return WriteSessionInitiate(protocol, init.contents, init.transports,
+ GetContentParsers(), GetTransportParsers(),
+ GetCandidateTranslators(), init.groups,
+ elems, error);
+}
+
+bool Session::SendAcceptMessage(const SessionDescription* sdesc,
+ SessionError* error) {
+ XmlElements elems;
+ if (!WriteSessionAccept(current_protocol_,
+ sdesc->contents(),
+ GetEmptyTransportInfos(sdesc->contents()),
+ GetContentParsers(), GetTransportParsers(),
+ GetCandidateTranslators(), sdesc->groups(),
+ &elems, error)) {
+ return false;
+ }
+ return SendMessage(ACTION_SESSION_ACCEPT, elems, error);
+}
+
+bool Session::SendRejectMessage(const std::string& reason,
+ SessionError* error) {
+ SessionTerminate term(reason);
+ return SendMessage(ACTION_SESSION_REJECT, term, error);
+}
+
+bool Session::SendTerminateMessage(const std::string& reason,
+ SessionError* error) {
+ SessionTerminate term(reason);
+ return SendMessage(ACTION_SESSION_TERMINATE, term, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems, WriteError* error) {
+ WriteSessionTerminate(protocol, term, elems);
+ return true;
+}
+
+bool Session::SendTransportInfoMessage(const TransportInfo& tinfo,
+ SessionError* error) {
+ return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error);
+}
+
+bool Session::SendTransportInfoMessage(const TransportProxy* transproxy,
+ const Candidates& candidates,
+ SessionError* error) {
+ return SendTransportInfoMessage(TransportInfo(transproxy->content_name(),
+ TransportDescription(transproxy->type(), std::vector<std::string>(),
+ std::string(), std::string(), ICEMODE_FULL,
+ CONNECTIONROLE_NONE, NULL, candidates)), error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+ const TransportInfo& tinfo,
+ XmlElements* elems, WriteError* error) {
+ TransportInfos tinfos;
+ tinfos.push_back(tinfo);
+ return WriteTransportInfos(protocol, tinfos,
+ GetTransportParsers(), GetCandidateTranslators(),
+ elems, error);
+}
+
+bool Session::ResendAllTransportInfoMessages(SessionError* error) {
+ for (TransportMap::const_iterator iter = transport_proxies().begin();
+ iter != transport_proxies().end(); ++iter) {
+ TransportProxy* transproxy = iter->second;
+ if (transproxy->sent_candidates().size() > 0) {
+ if (!SendTransportInfoMessage(
+ transproxy, transproxy->sent_candidates(), error)) {
+ LOG(LS_ERROR) << "Could not resend transport info messages: "
+ << error->text;
+ return false;
+ }
+ transproxy->ClearSentCandidates();
+ }
+ }
+ return true;
+}
+
+bool Session::SendAllUnsentTransportInfoMessages(SessionError* error) {
+ for (TransportMap::const_iterator iter = transport_proxies().begin();
+ iter != transport_proxies().end(); ++iter) {
+ TransportProxy* transproxy = iter->second;
+ if (transproxy->unsent_candidates().size() > 0) {
+ if (!SendTransportInfoMessage(
+ transproxy, transproxy->unsent_candidates(), error)) {
+ LOG(LS_ERROR) << "Could not send unsent transport info messages: "
+ << error->text;
+ return false;
+ }
+ transproxy->ClearUnsentCandidates();
+ }
+ }
+ return true;
+}
+
+bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
+ SessionError* error) {
+ return SendMessage(type, action_elems, remote_name(), error);
+}
+
+bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
+ const std::string& remote_name, SessionError* error) {
+ rtc::scoped_ptr<buzz::XmlElement> stanza(
+ new buzz::XmlElement(buzz::QN_IQ));
+
+ SessionMessage msg(current_protocol_, type, id(), initiator_name());
+ msg.to = remote_name;
+ WriteSessionMessage(msg, action_elems, stanza.get());
+
+ SignalOutgoingMessage(this, stanza.get());
+ return true;
+}
+
+template <typename Action>
+bool Session::SendMessage(ActionType type, const Action& action,
+ SessionError* error) {
+ rtc::scoped_ptr<buzz::XmlElement> stanza(
+ new buzz::XmlElement(buzz::QN_IQ));
+ if (!WriteActionMessage(type, action, stanza.get(), error))
+ return false;
+
+ SignalOutgoingMessage(this, stanza.get());
+ return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(ActionType type, const Action& action,
+ buzz::XmlElement* stanza,
+ WriteError* error) {
+ if (current_protocol_ == PROTOCOL_HYBRID) {
+ if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error))
+ return false;
+ if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error))
+ return false;
+ } else {
+ if (!WriteActionMessage(current_protocol_, type, action, stanza, error))
+ return false;
+ }
+ return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(SignalingProtocol protocol,
+ ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error) {
+ XmlElements action_elems;
+ if (!WriteSessionAction(protocol, action, &action_elems, error))
+ return false;
+
+ SessionMessage msg(protocol, type, id(), initiator_name());
+ msg.to = remote_name();
+
+ WriteSessionMessage(msg, action_elems, stanza);
+ return true;
+}
+
+void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) {
+ rtc::scoped_ptr<buzz::XmlElement> ack(
+ new buzz::XmlElement(buzz::QN_IQ));
+ ack->SetAttr(buzz::QN_TO, remote_name());
+ ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+ ack->SetAttr(buzz::QN_TYPE, "result");
+
+ SignalOutgoingMessage(this, ack.get());
+}
+
+} // namespace cricket
diff --git a/p2p/base/session.h b/p2p/base/session.h
new file mode 100644
index 00000000..f5eaf413
--- /dev/null
+++ b/p2p/base/session.h
@@ -0,0 +1,730 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSION_H_
+#define WEBRTC_P2P_BASE_SESSION_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/refcount.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace cricket {
+
+class BaseSession;
+class P2PTransportChannel;
+class Transport;
+class TransportChannel;
+class TransportChannelProxy;
+class TransportChannelImpl;
+
+typedef rtc::RefCountedObject<rtc::scoped_ptr<Transport> >
+TransportWrapper;
+
+// Used for errors that will send back a specific error message to the
+// remote peer. We add "type" to the errors because it's needed for
+// SignalErrorMessage.
+struct MessageError : ParseError {
+ buzz::QName type;
+
+ // if unset, assume type is a parse error
+ MessageError() : ParseError(), type(buzz::QN_STANZA_BAD_REQUEST) {}
+
+ void SetType(const buzz::QName type) {
+ this->type = type;
+ }
+};
+
+// Used for errors that may be returned by public session methods that
+// can fail.
+// TODO: Use this error in Session::Initiate and
+// Session::Accept.
+struct SessionError : WriteError {
+};
+
+// Bundles a Transport and ChannelMap together. ChannelMap is used to
+// create transport channels before receiving or sending a session
+// initiate, and for speculatively connecting channels. Previously, a
+// session had one ChannelMap and transport. Now, with multiple
+// transports per session, we need multiple ChannelMaps as well.
+
+typedef std::map<int, TransportChannelProxy*> ChannelMap;
+
+class TransportProxy : public sigslot::has_slots<>,
+ public CandidateTranslator {
+ public:
+ TransportProxy(
+ rtc::Thread* worker_thread,
+ const std::string& sid,
+ const std::string& content_name,
+ TransportWrapper* transport)
+ : worker_thread_(worker_thread),
+ sid_(sid),
+ content_name_(content_name),
+ transport_(transport),
+ connecting_(false),
+ negotiated_(false),
+ sent_candidates_(false),
+ candidates_allocated_(false),
+ local_description_set_(false),
+ remote_description_set_(false) {
+ transport_->get()->SignalCandidatesReady.connect(
+ this, &TransportProxy::OnTransportCandidatesReady);
+ }
+ ~TransportProxy();
+
+ const std::string& content_name() const { return content_name_; }
+ // TODO(juberti): It's not good form to expose the object you're wrapping,
+ // since callers can mutate it. Can we make this return a const Transport*?
+ Transport* impl() const { return transport_->get(); }
+
+ const std::string& type() const;
+ bool negotiated() const { return negotiated_; }
+ const Candidates& sent_candidates() const { return sent_candidates_; }
+ const Candidates& unsent_candidates() const { return unsent_candidates_; }
+ bool candidates_allocated() const { return candidates_allocated_; }
+ void set_candidates_allocated(bool allocated) {
+ candidates_allocated_ = allocated;
+ }
+
+ TransportChannel* GetChannel(int component);
+ TransportChannel* CreateChannel(const std::string& channel_name,
+ int component);
+ bool HasChannel(int component);
+ void DestroyChannel(int component);
+
+ void AddSentCandidates(const Candidates& candidates);
+ void AddUnsentCandidates(const Candidates& candidates);
+ void ClearSentCandidates() { sent_candidates_.clear(); }
+ void ClearUnsentCandidates() { unsent_candidates_.clear(); }
+
+ // Start the connection process for any channels, creating impls if needed.
+ void ConnectChannels();
+ // Hook up impls to the proxy channels. Doesn't change connect state.
+ void CompleteNegotiation();
+
+ // Mux this proxy onto the specified proxy's transport.
+ bool SetupMux(TransportProxy* proxy);
+
+ // Simple functions that thunk down to the same functions on Transport.
+ void SetIceRole(IceRole role);
+ void SetIdentity(rtc::SSLIdentity* identity);
+ bool SetLocalTransportDescription(const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc);
+ bool SetRemoteTransportDescription(const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc);
+ void OnSignalingReady();
+ bool OnRemoteCandidates(const Candidates& candidates, std::string* error);
+
+ // CandidateTranslator methods.
+ virtual bool GetChannelNameFromComponent(
+ int component, std::string* channel_name) const;
+ virtual bool GetComponentFromChannelName(
+ const std::string& channel_name, int* component) const;
+
+ // Called when a transport signals that it has new candidates.
+ void OnTransportCandidatesReady(cricket::Transport* transport,
+ const Candidates& candidates) {
+ SignalCandidatesReady(this, candidates);
+ }
+
+ bool local_description_set() const {
+ return local_description_set_;
+ }
+ bool remote_description_set() const {
+ return remote_description_set_;
+ }
+
+ // Handles sending of ready candidates and receiving of remote candidates.
+ sigslot::signal2<TransportProxy*,
+ const std::vector<Candidate>&> SignalCandidatesReady;
+
+ private:
+ TransportChannelProxy* GetChannelProxy(int component) const;
+ TransportChannelProxy* GetChannelProxyByName(const std::string& name) const;
+
+ TransportChannelImpl* GetOrCreateChannelProxyImpl(int component);
+ TransportChannelImpl* GetOrCreateChannelProxyImpl_w(int component);
+
+ // Manipulators of transportchannelimpl in channel proxy.
+ void SetupChannelProxy(int component,
+ TransportChannelProxy* proxy);
+ void SetupChannelProxy_w(int component,
+ TransportChannelProxy* proxy);
+ void ReplaceChannelProxyImpl(TransportChannelProxy* proxy,
+ TransportChannelImpl* impl);
+ void ReplaceChannelProxyImpl_w(TransportChannelProxy* proxy,
+ TransportChannelImpl* impl);
+
+ rtc::Thread* const worker_thread_;
+ const std::string sid_;
+ const std::string content_name_;
+ rtc::scoped_refptr<TransportWrapper> transport_;
+ bool connecting_;
+ bool negotiated_;
+ ChannelMap channels_;
+ Candidates sent_candidates_;
+ Candidates unsent_candidates_;
+ bool candidates_allocated_;
+ bool local_description_set_;
+ bool remote_description_set_;
+};
+
+typedef std::map<std::string, TransportProxy*> TransportMap;
+
+// Statistics for all the transports of this session.
+typedef std::map<std::string, TransportStats> TransportStatsMap;
+typedef std::map<std::string, std::string> ProxyTransportMap;
+
+struct SessionStats {
+ ProxyTransportMap proxy_to_transport;
+ TransportStatsMap transport_stats;
+};
+
+// A BaseSession manages general session state. This includes negotiation
+// of both the application-level and network-level protocols: the former
+// defines what will be sent and the latter defines how it will be sent. Each
+// network-level protocol is represented by a Transport object. Each Transport
+// participates in the network-level negotiation. The individual streams of
+// packets are represented by TransportChannels. The application-level protocol
+// is represented by SessionDecription objects.
+class BaseSession : public sigslot::has_slots<>,
+ public rtc::MessageHandler {
+ public:
+ enum {
+ MSG_TIMEOUT = 0,
+ MSG_ERROR,
+ MSG_STATE,
+ };
+
+ enum State {
+ STATE_INIT = 0,
+ STATE_SENTINITIATE, // sent initiate, waiting for Accept or Reject
+ STATE_RECEIVEDINITIATE, // received an initiate. Call Accept or Reject
+ STATE_SENTPRACCEPT, // sent provisional Accept
+ STATE_SENTACCEPT, // sent accept. begin connecting transport
+ STATE_RECEIVEDPRACCEPT, // received provisional Accept, waiting for Accept
+ STATE_RECEIVEDACCEPT, // received accept. begin connecting transport
+ STATE_SENTMODIFY, // sent modify, waiting for Accept or Reject
+ STATE_RECEIVEDMODIFY, // received modify, call Accept or Reject
+ STATE_SENTREJECT, // sent reject after receiving initiate
+ STATE_RECEIVEDREJECT, // received reject after sending initiate
+ STATE_SENTREDIRECT, // sent direct after receiving initiate
+ STATE_SENTTERMINATE, // sent terminate (any time / either side)
+ STATE_RECEIVEDTERMINATE, // received terminate (any time / either side)
+ STATE_INPROGRESS, // session accepted and in progress
+ STATE_DEINIT, // session is being destroyed
+ };
+
+ enum Error {
+ ERROR_NONE = 0, // no error
+ ERROR_TIME = 1, // no response to signaling
+ ERROR_RESPONSE = 2, // error during signaling
+ ERROR_NETWORK = 3, // network error, could not allocate network resources
+ ERROR_CONTENT = 4, // channel errors in SetLocalContent/SetRemoteContent
+ ERROR_TRANSPORT = 5, // transport error of some kind
+ };
+
+ // Convert State to a readable string.
+ static std::string StateToString(State state);
+
+ BaseSession(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ PortAllocator* port_allocator,
+ const std::string& sid,
+ const std::string& content_type,
+ bool initiator);
+ virtual ~BaseSession();
+
+ // These are const to allow them to be called from const methods.
+ rtc::Thread* signaling_thread() const { return signaling_thread_; }
+ rtc::Thread* worker_thread() const { return worker_thread_; }
+ PortAllocator* port_allocator() const { return port_allocator_; }
+
+ // The ID of this session.
+ const std::string& id() const { return sid_; }
+
+ // TODO(juberti): This data is largely redundant, as it can now be obtained
+ // from local/remote_description(). Remove these functions and members.
+ // Returns the XML namespace identifying the type of this session.
+ const std::string& content_type() const { return content_type_; }
+ // Returns the XML namespace identifying the transport used for this session.
+ const std::string& transport_type() const { return transport_type_; }
+
+ // Indicates whether we initiated this session.
+ bool initiator() const { return initiator_; }
+
+ // Returns the application-level description given by our client.
+ // If we are the recipient, this will be NULL until we send an accept.
+ const SessionDescription* local_description() const;
+
+ // Returns the application-level description given by the other client.
+ // If we are the initiator, this will be NULL until we receive an accept.
+ const SessionDescription* remote_description() const;
+
+ SessionDescription* remote_description();
+
+ // Takes ownership of SessionDescription*
+ void set_local_description(const SessionDescription* sdesc);
+
+ // Takes ownership of SessionDescription*
+ void set_remote_description(SessionDescription* sdesc);
+
+ const SessionDescription* initiator_description() const;
+
+ // Returns the current state of the session. See the enum above for details.
+ // Each time the state changes, we will fire this signal.
+ State state() const { return state_; }
+ sigslot::signal2<BaseSession* , State> SignalState;
+
+ // Returns the last error in the session. See the enum above for details.
+ // Each time the an error occurs, we will fire this signal.
+ Error error() const { return error_; }
+ const std::string& error_desc() const { return error_desc_; }
+ sigslot::signal2<BaseSession* , Error> SignalError;
+
+ // Updates the state, signaling if necessary.
+ virtual void SetState(State state);
+
+ // Updates the error state, signaling if necessary.
+ // TODO(ronghuawu): remove the SetError method that doesn't take |error_desc|.
+ virtual void SetError(Error error, const std::string& error_desc);
+
+ // Fired when the remote description is updated, with the updated
+ // contents.
+ sigslot::signal2<BaseSession* , const ContentInfos&>
+ SignalRemoteDescriptionUpdate;
+
+ // Fired when SetState is called (regardless if there's a state change), which
+ // indicates the session description might have be updated.
+ sigslot::signal2<BaseSession*, ContentAction> SignalNewLocalDescription;
+
+ // Fired when SetState is called (regardless if there's a state change), which
+ // indicates the session description might have be updated.
+ sigslot::signal2<BaseSession*, ContentAction> SignalNewRemoteDescription;
+
+ // Returns the transport that has been negotiated or NULL if
+ // negotiation is still in progress.
+ virtual Transport* GetTransport(const std::string& content_name);
+
+ // Creates a new channel with the given names. This method may be called
+ // immediately after creating the session. However, the actual
+ // implementation may not be fixed until transport negotiation completes.
+ // This will usually be called from the worker thread, but that
+ // shouldn't be an issue since the main thread will be blocked in
+ // Send when doing so.
+ virtual TransportChannel* CreateChannel(const std::string& content_name,
+ const std::string& channel_name,
+ int component);
+
+ // Returns the channel with the given names.
+ virtual TransportChannel* GetChannel(const std::string& content_name,
+ int component);
+
+ // Destroys the channel with the given names.
+ // This will usually be called from the worker thread, but that
+ // shouldn't be an issue since the main thread will be blocked in
+ // Send when doing so.
+ virtual void DestroyChannel(const std::string& content_name,
+ int component);
+
+ // Returns stats for all channels of all transports.
+ // This avoids exposing the internal structures used to track them.
+ virtual bool GetStats(SessionStats* stats);
+
+ rtc::SSLIdentity* identity() { return identity_; }
+
+ protected:
+ // Specifies the identity to use in this session.
+ bool SetIdentity(rtc::SSLIdentity* identity);
+
+ bool PushdownTransportDescription(ContentSource source,
+ ContentAction action,
+ std::string* error_desc);
+ void set_initiator(bool initiator) { initiator_ = initiator; }
+
+ const TransportMap& transport_proxies() const { return transports_; }
+ // Get a TransportProxy by content_name or transport. NULL if not found.
+ TransportProxy* GetTransportProxy(const std::string& content_name);
+ TransportProxy* GetTransportProxy(const Transport* transport);
+ TransportProxy* GetFirstTransportProxy();
+ void DestroyTransportProxy(const std::string& content_name);
+ // TransportProxy is owned by session. Return proxy just for convenience.
+ TransportProxy* GetOrCreateTransportProxy(const std::string& content_name);
+ // Creates the actual transport object. Overridable for testing.
+ virtual Transport* CreateTransport(const std::string& content_name);
+
+ void OnSignalingReady();
+ void SpeculativelyConnectAllTransportChannels();
+ // Helper method to provide remote candidates to the transport.
+ bool OnRemoteCandidates(const std::string& content_name,
+ const Candidates& candidates,
+ std::string* error);
+
+ // This method will mux transport channels by content_name.
+ // First content is used for muxing.
+ bool MaybeEnableMuxingSupport();
+
+ // Called when a transport requests signaling.
+ virtual void OnTransportRequestSignaling(Transport* transport) {
+ }
+
+ // Called when the first channel of a transport begins connecting. We use
+ // this to start a timer, to make sure that the connection completes in a
+ // reasonable amount of time.
+ virtual void OnTransportConnecting(Transport* transport) {
+ }
+
+ // Called when a transport changes its writable state. We track this to make
+ // sure that the transport becomes writable within a reasonable amount of
+ // time. If this does not occur, we signal an error.
+ virtual void OnTransportWritable(Transport* transport) {
+ }
+ virtual void OnTransportReadable(Transport* transport) {
+ }
+
+ // Called when a transport has found its steady-state connections.
+ virtual void OnTransportCompleted(Transport* transport) {
+ }
+
+ // Called when a transport has failed permanently.
+ virtual void OnTransportFailed(Transport* transport) {
+ }
+
+ // Called when a transport signals that it has new candidates.
+ virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy,
+ const Candidates& candidates) {
+ }
+
+ // Called when a transport signals that it found an error in an incoming
+ // message.
+ virtual void OnTransportSendError(Transport* transport,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ }
+
+ virtual void OnTransportRouteChange(
+ Transport* transport,
+ int component,
+ const cricket::Candidate& remote_candidate) {
+ }
+
+ virtual void OnTransportCandidatesAllocationDone(Transport* transport);
+
+ // Called when all transport channels allocated required candidates.
+ // This method should be used as an indication of candidates gathering process
+ // is completed and application can now send local candidates list to remote.
+ virtual void OnCandidatesAllocationDone() {
+ }
+
+ // Handles the ice role change callback from Transport. This must be
+ // propagated to all the transports.
+ virtual void OnRoleConflict();
+
+ // Handles messages posted to us.
+ virtual void OnMessage(rtc::Message *pmsg);
+
+ protected:
+ State state_;
+ Error error_;
+ std::string error_desc_;
+
+ private:
+ // Helper methods to push local and remote transport descriptions.
+ bool PushdownLocalTransportDescription(
+ const SessionDescription* sdesc, ContentAction action,
+ std::string* error_desc);
+ bool PushdownRemoteTransportDescription(
+ const SessionDescription* sdesc, ContentAction action,
+ std::string* error_desc);
+
+ bool IsCandidateAllocationDone() const;
+ void MaybeCandidateAllocationDone();
+
+ // This method will delete the Transport and TransportChannelImpls and
+ // replace those with the selected Transport objects. Selection is done
+ // based on the content_name and in this case first MediaContent information
+ // is used for mux.
+ bool SetSelectedProxy(const std::string& content_name,
+ const ContentGroup* muxed_group);
+ // Log session state.
+ void LogState(State old_state, State new_state);
+
+ // Returns true and the TransportInfo of the given |content_name|
+ // from |description|. Returns false if it's not available.
+ static bool GetTransportDescription(const SessionDescription* description,
+ const std::string& content_name,
+ TransportDescription* info);
+
+ // Fires the new description signal according to the current state.
+ void SignalNewDescription();
+
+ // Gets the ContentAction and ContentSource according to the session state.
+ bool GetContentAction(ContentAction* action, ContentSource* source);
+
+ rtc::Thread* const signaling_thread_;
+ rtc::Thread* const worker_thread_;
+ PortAllocator* const port_allocator_;
+ const std::string sid_;
+ const std::string content_type_;
+ const std::string transport_type_;
+ bool initiator_;
+ rtc::SSLIdentity* identity_;
+ rtc::scoped_ptr<const SessionDescription> local_description_;
+ rtc::scoped_ptr<SessionDescription> remote_description_;
+ uint64 ice_tiebreaker_;
+ // This flag will be set to true after the first role switch. This flag
+ // will enable us to stop any role switch during the call.
+ bool role_switch_;
+ TransportMap transports_;
+};
+
+// A specific Session created by the SessionManager, using XMPP for protocol.
+class Session : public BaseSession {
+ public:
+ // Returns the manager that created and owns this session.
+ SessionManager* session_manager() const { return session_manager_; }
+
+ // Returns the client that is handling the application data of this session.
+ SessionClient* client() const { return client_; }
+
+ // Returns the JID of this client.
+ const std::string& local_name() const { return local_name_; }
+
+ // Returns the JID of the other peer in this session.
+ const std::string& remote_name() const { return remote_name_; }
+
+ // Set the JID of the other peer in this session.
+ // Typically the remote_name_ is set when the session is initiated.
+ // However, sometimes (e.g when a proxy is used) the peer name is
+ // known after the BaseSession has been initiated and it must be updated
+ // explicitly.
+ void set_remote_name(const std::string& name) { remote_name_ = name; }
+
+ // Set the JID of the initiator of this session. Allows for the overriding
+ // of the initiator to be a third-party, eg. the MUC JID when creating p2p
+ // sessions.
+ void set_initiator_name(const std::string& name) { initiator_name_ = name; }
+
+ // Indicates the JID of the entity who initiated this session.
+ // In special cases, may be different than both local_name and remote_name.
+ const std::string& initiator_name() const { return initiator_name_; }
+
+ SignalingProtocol current_protocol() const { return current_protocol_; }
+
+ void set_current_protocol(SignalingProtocol protocol) {
+ current_protocol_ = protocol;
+ }
+
+ // Updates the error state, signaling if necessary.
+ virtual void SetError(Error error, const std::string& error_desc);
+
+ // When the session needs to send signaling messages, it beings by requesting
+ // signaling. The client should handle this by calling OnSignalingReady once
+ // it is ready to send the messages.
+ // (These are called only by SessionManager.)
+ sigslot::signal1<Session*> SignalRequestSignaling;
+ void OnSignalingReady() { BaseSession::OnSignalingReady(); }
+
+ // Takes ownership of session description.
+ // TODO: Add an error argument to pass back to the caller.
+ bool Initiate(const std::string& to,
+ const SessionDescription* sdesc);
+
+ // When we receive an initiate, we create a session in the
+ // RECEIVEDINITIATE state and respond by accepting or rejecting.
+ // Takes ownership of session description.
+ // TODO: Add an error argument to pass back to the caller.
+ bool Accept(const SessionDescription* sdesc);
+ bool Reject(const std::string& reason);
+ bool Terminate() {
+ return TerminateWithReason(STR_TERMINATE_SUCCESS);
+ }
+ bool TerminateWithReason(const std::string& reason);
+ // Fired whenever we receive a terminate message along with a reason
+ sigslot::signal2<Session*, const std::string&> SignalReceivedTerminateReason;
+
+ // The two clients in the session may also send one another
+ // arbitrary XML messages, which are called "info" messages. Sending
+ // takes ownership of the given elements. The signal does not; the
+ // parent element will be deleted after the signal.
+ bool SendInfoMessage(const XmlElements& elems,
+ const std::string& remote_name);
+ bool SendDescriptionInfoMessage(const ContentInfos& contents);
+ sigslot::signal2<Session*, const buzz::XmlElement*> SignalInfoMessage;
+
+ private:
+ // Creates or destroys a session. (These are called only SessionManager.)
+ Session(SessionManager *session_manager,
+ const std::string& local_name, const std::string& initiator_name,
+ const std::string& sid, const std::string& content_type,
+ SessionClient* client);
+ ~Session();
+ // For each transport info, create a transport proxy. Can fail for
+ // incompatible transport types.
+ bool CreateTransportProxies(const TransportInfos& tinfos,
+ SessionError* error);
+ bool OnRemoteCandidates(const TransportInfos& tinfos,
+ ParseError* error);
+ // Returns a TransportInfo without candidates for each content name.
+ // Uses the transport_type_ of the session.
+ TransportInfos GetEmptyTransportInfos(const ContentInfos& contents) const;
+
+ // Maps passed to serialization functions.
+ TransportParserMap GetTransportParsers();
+ ContentParserMap GetContentParsers();
+ CandidateTranslatorMap GetCandidateTranslators();
+
+ virtual void OnTransportRequestSignaling(Transport* transport);
+ virtual void OnTransportConnecting(Transport* transport);
+ virtual void OnTransportWritable(Transport* transport);
+ virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy,
+ const Candidates& candidates);
+ virtual void OnTransportSendError(Transport* transport,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+ virtual void OnMessage(rtc::Message *pmsg);
+
+ // Send various kinds of session messages.
+ bool SendInitiateMessage(const SessionDescription* sdesc,
+ SessionError* error);
+ bool SendAcceptMessage(const SessionDescription* sdesc, SessionError* error);
+ bool SendRejectMessage(const std::string& reason, SessionError* error);
+ bool SendTerminateMessage(const std::string& reason, SessionError* error);
+ bool SendTransportInfoMessage(const TransportInfo& tinfo,
+ SessionError* error);
+ bool SendTransportInfoMessage(const TransportProxy* transproxy,
+ const Candidates& candidates,
+ SessionError* error);
+
+ bool ResendAllTransportInfoMessages(SessionError* error);
+ bool SendAllUnsentTransportInfoMessages(SessionError* error);
+
+ // All versions of SendMessage send a message of the given type to
+ // the other client. Can pass either a set of elements or an
+ // "action", which must have a WriteSessionAction method to go along
+ // with it. Sending with an action supports sending a "hybrid"
+ // message. Sending with elements must be sent as Jingle or Gingle.
+
+ // When passing elems, must be either Jingle or Gingle protocol.
+ // Takes ownership of action_elems.
+ bool SendMessage(ActionType type, const XmlElements& action_elems,
+ SessionError* error);
+ // Sends a messge, but overrides the remote name.
+ bool SendMessage(ActionType type, const XmlElements& action_elems,
+ const std::string& remote_name,
+ SessionError* error);
+ // When passing an action, may be Hybrid protocol.
+ template <typename Action>
+ bool SendMessage(ActionType type, const Action& action,
+ SessionError* error);
+
+ // Helper methods to write the session message stanza.
+ template <typename Action>
+ bool WriteActionMessage(ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error);
+ template <typename Action>
+ bool WriteActionMessage(SignalingProtocol protocol,
+ ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error);
+
+ // Sending messages in hybrid form requires being able to write them
+ // on a per-protocol basis with a common method signature, which all
+ // of these have.
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const SessionInitiate& init,
+ XmlElements* elems, WriteError* error);
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const TransportInfo& tinfo,
+ XmlElements* elems, WriteError* error);
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems, WriteError* error);
+
+ // Sends a message back to the other client indicating that we have received
+ // and accepted their message.
+ void SendAcknowledgementMessage(const buzz::XmlElement* stanza);
+
+ // Once signaling is ready, the session will use this signal to request the
+ // sending of each message. When messages are received by the other client,
+ // they should be handed to OnIncomingMessage.
+ // (These are called only by SessionManager.)
+ sigslot::signal2<Session* , const buzz::XmlElement*> SignalOutgoingMessage;
+ void OnIncomingMessage(const SessionMessage& msg);
+
+ void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza,
+ const SessionMessage& msg);
+ void OnInitiateAcked();
+ void OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza);
+
+ // Invoked when an error is found in an incoming message. This is translated
+ // into the appropriate XMPP response by SessionManager.
+ sigslot::signal6<BaseSession*,
+ const buzz::XmlElement*,
+ const buzz::QName&,
+ const std::string&,
+ const std::string&,
+ const buzz::XmlElement*> SignalErrorMessage;
+
+ // Handlers for the various types of messages. These functions may take
+ // pointers to the whole stanza or to just the session element.
+ bool OnInitiateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnAcceptMessage(const SessionMessage& msg, MessageError* error);
+ bool OnRejectMessage(const SessionMessage& msg, MessageError* error);
+ bool OnInfoMessage(const SessionMessage& msg);
+ bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
+ bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
+ bool OnDescriptionInfoMessage(const SessionMessage& msg, MessageError* error);
+ bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
+
+ // Verifies that we are in the appropriate state to receive this message.
+ bool CheckState(State state, MessageError* error);
+
+ SessionManager* session_manager_;
+ bool initiate_acked_;
+ std::string local_name_;
+ std::string initiator_name_;
+ std::string remote_name_;
+ SessionClient* client_;
+ TransportParser* transport_parser_;
+ // Keeps track of what protocol we are speaking.
+ SignalingProtocol current_protocol_;
+
+ friend class SessionManager; // For access to constructor, destructor,
+ // and signaling related methods.
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSION_H_
diff --git a/p2p/base/session_unittest.cc b/p2p/base/session_unittest.cc
new file mode 100644
index 00000000..d6f94b29
--- /dev/null
+++ b/p2p/base/session_unittest.cc
@@ -0,0 +1,2430 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string.h>
+
+#include <deque>
+#include <map>
+#include <sstream>
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/p2ptransport.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/relayport.h"
+#include "webrtc/p2p/base/relayserver.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/p2p/base/stunport.h"
+#include "webrtc/p2p/base/stunserver.h"
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/p2p/base/transportchannelproxy.h"
+#include "webrtc/p2p/base/udpport.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/base64.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/natserver.h"
+#include "webrtc/base/natsocketfactory.h"
+#include "webrtc/base/stringencode.h"
+
+using cricket::SignalingProtocol;
+using cricket::PROTOCOL_HYBRID;
+using cricket::PROTOCOL_JINGLE;
+using cricket::PROTOCOL_GINGLE;
+
+static const std::string kInitiator = "init@init.com";
+static const std::string kResponder = "resp@resp.com";
+// Expected from test random number generator.
+static const std::string kSessionId = "9254631414740579489";
+// TODO: When we need to test more than one transport type,
+// allow this to be injected like the content types are.
+static const std::string kTransportType = "http://www.google.com/transport/p2p";
+
+// Controls how long we wait for a session to send messages that we
+// expect, in milliseconds. We put it high to avoid flaky tests.
+static const int kEventTimeout = 5000;
+
+static const int kNumPorts = 2;
+static const int kPort0 = 28653;
+static const int kPortStep = 5;
+
+int GetPort(int port_index) {
+ return kPort0 + (port_index * kPortStep);
+}
+
+std::string GetPortString(int port_index) {
+ return rtc::ToString(GetPort(port_index));
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetUsername(int port_index) {
+ return "username" + std::string(8, rtc::ToString(port_index)[0]);
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetPassword(int port_index) {
+ return "password" + std::string(8, rtc::ToString(port_index)[0]);
+}
+
+std::string IqAck(const std::string& id,
+ const std::string& from,
+ const std::string& to) {
+ return "<cli:iq"
+ " to=\"" + to + "\""
+ " id=\"" + id + "\""
+ " type=\"result\""
+ " from=\"" + from + "\""
+ " xmlns:cli=\"jabber:client\""
+ "/>";
+}
+
+std::string IqSet(const std::string& id,
+ const std::string& from,
+ const std::string& to,
+ const std::string& content) {
+ return "<cli:iq"
+ " to=\"" + to + "\""
+ " type=\"set\""
+ " from=\"" + from + "\""
+ " id=\"" + id + "\""
+ " xmlns:cli=\"jabber:client\""
+ ">"
+ + content +
+ "</cli:iq>";
+}
+
+std::string IqError(const std::string& id,
+ const std::string& from,
+ const std::string& to,
+ const std::string& content) {
+ return "<cli:error"
+ " to=\"" + to + "\""
+ " type=\"error\""
+ " from=\"" + from + "\""
+ " id=\"" + id + "\""
+ " xmlns:cli=\"jabber:client\""
+ ">"
+ + content +
+ "</cli:error>";
+}
+
+std::string GingleSessionXml(const std::string& type,
+ const std::string& content) {
+ return "<session"
+ " xmlns=\"http://www.google.com/session\""
+ " type=\"" + type + "\""
+ " id=\"" + kSessionId + "\""
+ " initiator=\"" + kInitiator + "\""
+ ">"
+ + content +
+ "</session>";
+}
+
+std::string GingleDescriptionXml(const std::string& content_type) {
+ return "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>";
+}
+
+std::string P2pCandidateXml(const std::string& name, int port_index) {
+ // Port will update the rtcp username by +1 on the last character. So we need
+ // to compensate here. See Port::username_fragment() for detail.
+ std::string username = GetUsername(port_index);
+ // TODO: Use the component id instead of the channel name to
+ // determinte if we need to covert the username here.
+ if (name == "rtcp" || name == "video_rtcp" || name == "chanb") {
+ char next_ch = username[username.size() - 1];
+ ASSERT(username.size() > 0);
+ rtc::Base64::GetNextBase64Char(next_ch, &next_ch);
+ username[username.size() - 1] = next_ch;
+ }
+ return "<candidate"
+ " name=\"" + name + "\""
+ " address=\"127.0.0.1\""
+ " port=\"" + GetPortString(port_index) + "\""
+ " preference=\"0.99\""
+ " username=\"" + username + "\""
+ " protocol=\"udp\""
+ " generation=\"0\""
+ " password=\"" + GetPassword(port_index) + "\""
+ " type=\"local\""
+ " network=\"network\""
+ "/>";
+}
+
+std::string JingleActionXml(const std::string& action,
+ const std::string& content) {
+ return "<jingle"
+ " xmlns=\"urn:xmpp:jingle:1\""
+ " action=\"" + action + "\""
+ " sid=\"" + kSessionId + "\""
+ ">"
+ + content +
+ "</jingle>";
+}
+
+std::string JingleInitiateActionXml(const std::string& content) {
+ return "<jingle"
+ " xmlns=\"urn:xmpp:jingle:1\""
+ " action=\"session-initiate\""
+ " sid=\"" + kSessionId + "\""
+ " initiator=\"" + kInitiator + "\""
+ ">"
+ + content +
+ "</jingle>";
+}
+
+std::string JingleGroupInfoXml(const std::string& content_name_a,
+ const std::string& content_name_b) {
+ std::string group_info = "<jin:group"
+ " type=\"BUNDLE\""
+ " xmlns:jin=\"google:jingle\""
+ ">";
+ if (!content_name_a.empty())
+ group_info += "<content name=\"" + content_name_a + "\""
+ "/>";
+ if (!content_name_b.empty())
+ group_info += "<content name=\"" + content_name_b + "\""
+ "/>";
+ group_info += "</jin:group>";
+ return group_info;
+}
+
+
+std::string JingleEmptyContentXml(const std::string& content_name,
+ const std::string& content_type,
+ const std::string& transport_type) {
+ return "<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>"
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ "/>"
+ "</content>";
+}
+
+std::string JingleContentXml(const std::string& content_name,
+ const std::string& content_type,
+ const std::string& transport_type,
+ const std::string& transport_main) {
+ std::string transport = transport_type.empty() ? "" :
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ ">"
+ + transport_main +
+ "</transport>";
+
+ return"<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>"
+ + transport +
+ "</content>";
+}
+
+std::string JingleTransportContentXml(const std::string& content_name,
+ const std::string& transport_type,
+ const std::string& content) {
+ return "<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ ">"
+ + content +
+ "</transport>"
+ "</content>";
+}
+
+std::string GingleInitiateXml(const std::string& content_type) {
+ return GingleSessionXml(
+ "initiate",
+ GingleDescriptionXml(content_type));
+}
+
+std::string JingleInitiateXml(const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b,
+ bool bundle = false) {
+ std::string content_xml;
+ if (content_name_b.empty()) {
+ content_xml = JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType);
+ } else {
+ content_xml = JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType) +
+ JingleEmptyContentXml(
+ content_name_b, content_type_b, kTransportType);
+ if (bundle) {
+ content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
+ }
+ }
+ return JingleInitiateActionXml(content_xml);
+}
+
+std::string GingleAcceptXml(const std::string& content_type) {
+ return GingleSessionXml(
+ "accept",
+ GingleDescriptionXml(content_type));
+}
+
+std::string JingleAcceptXml(const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b,
+ bool bundle = false) {
+ std::string content_xml;
+ if (content_name_b.empty()) {
+ content_xml = JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType);
+ } else {
+ content_xml = JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType) +
+ JingleEmptyContentXml(
+ content_name_b, content_type_b, kTransportType);
+ }
+ if (bundle) {
+ content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
+ }
+
+ return JingleActionXml("session-accept", content_xml);
+}
+
+std::string Gingle2CandidatesXml(const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ return GingleSessionXml(
+ "candidates",
+ P2pCandidateXml(channel_name, port_index0) +
+ P2pCandidateXml(channel_name, port_index1));
+}
+
+std::string Gingle4CandidatesXml(const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ return GingleSessionXml(
+ "candidates",
+ P2pCandidateXml(channel_name_a, port_index0) +
+ P2pCandidateXml(channel_name_a, port_index1) +
+ P2pCandidateXml(channel_name_b, port_index2) +
+ P2pCandidateXml(channel_name_b, port_index3));
+}
+
+std::string Jingle2TransportInfoXml(const std::string& content_name,
+ const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ return JingleActionXml(
+ "transport-info",
+ JingleTransportContentXml(
+ content_name, kTransportType,
+ P2pCandidateXml(channel_name, port_index0) +
+ P2pCandidateXml(channel_name, port_index1)));
+}
+
+std::string Jingle4TransportInfoXml(const std::string& content_name,
+ const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ return JingleActionXml(
+ "transport-info",
+ JingleTransportContentXml(
+ content_name, kTransportType,
+ P2pCandidateXml(channel_name_a, port_index0) +
+ P2pCandidateXml(channel_name_a, port_index1) +
+ P2pCandidateXml(channel_name_b, port_index2) +
+ P2pCandidateXml(channel_name_b, port_index3)));
+}
+
+std::string JingleDescriptionInfoXml(const std::string& content_name,
+ const std::string& content_type) {
+ return JingleActionXml(
+ "description-info",
+ JingleContentXml(content_name, content_type, "", ""));
+}
+
+std::string GingleRejectXml(const std::string& reason) {
+ return GingleSessionXml(
+ "reject",
+ "<" + reason + "/>");
+}
+
+std::string JingleTerminateXml(const std::string& reason) {
+ return JingleActionXml(
+ "session-terminate",
+ "<reason><" + reason + "/></reason>");
+}
+
+std::string GingleTerminateXml(const std::string& reason) {
+ return GingleSessionXml(
+ "terminate",
+ "<" + reason + "/>");
+}
+
+std::string GingleRedirectXml(const std::string& intitiate,
+ const std::string& target) {
+ return intitiate +
+ "<error code=\"302\" type=\"modify\">"
+ "<redirect xmlns=\"http://www.google.com/session\">"
+ "xmpp:" + target +
+ "</redirect>"
+ "</error>";
+}
+
+std::string JingleRedirectXml(const std::string& intitiate,
+ const std::string& target) {
+ return intitiate +
+ "<error code=\"302\" type=\"modify\">"
+ "<redirect xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"
+ "xmpp:" + target +
+ "</redirect>"
+ "</error>";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b,
+ bool bundle = false) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleInitiateXml(content_name_a, content_type_a,
+ content_name_b, content_type_b,
+ bundle);
+ case PROTOCOL_GINGLE:
+ return GingleInitiateXml(gingle_content_type);
+ case PROTOCOL_HYBRID:
+ return JingleInitiateXml(content_name_a, content_type_a,
+ content_name_b, content_type_b) +
+ GingleInitiateXml(gingle_content_type);
+ }
+ return "";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& content_type) {
+ return InitiateXml(protocol,
+ content_type,
+ content_name, content_type,
+ "", "");
+}
+
+std::string AcceptXml(SignalingProtocol protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b,
+ bool bundle = false) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleAcceptXml(content_name_a, content_type_a,
+ content_name_b, content_type_b, bundle);
+ case PROTOCOL_GINGLE:
+ return GingleAcceptXml(gingle_content_type);
+ case PROTOCOL_HYBRID:
+ return
+ JingleAcceptXml(content_name_a, content_type_a,
+ content_name_b, content_type_b) +
+ GingleAcceptXml(gingle_content_type);
+ }
+ return "";
+}
+
+
+std::string AcceptXml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& content_type,
+ bool bundle = false) {
+ return AcceptXml(protocol,
+ content_type,
+ content_name, content_type,
+ "", "");
+}
+
+std::string TransportInfo2Xml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return Jingle2TransportInfoXml(
+ content_name,
+ channel_name, port_index0, port_index1);
+ case PROTOCOL_GINGLE:
+ return Gingle2CandidatesXml(
+ channel_name, port_index0, port_index1);
+ case PROTOCOL_HYBRID:
+ return
+ Jingle2TransportInfoXml(
+ content_name,
+ channel_name, port_index0, port_index1) +
+ Gingle2CandidatesXml(
+ channel_name, port_index0, port_index1);
+ }
+ return "";
+}
+
+std::string TransportInfo4Xml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return Jingle4TransportInfoXml(
+ content_name,
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ case PROTOCOL_GINGLE:
+ return Gingle4CandidatesXml(
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ case PROTOCOL_HYBRID:
+ return
+ Jingle4TransportInfoXml(
+ content_name,
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3) +
+ Gingle4CandidatesXml(
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ }
+ return "";
+}
+
+std::string RejectXml(SignalingProtocol protocol,
+ const std::string& reason) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleTerminateXml(reason);
+ case PROTOCOL_GINGLE:
+ return GingleRejectXml(reason);
+ case PROTOCOL_HYBRID:
+ return JingleTerminateXml(reason) +
+ GingleRejectXml(reason);
+ }
+ return "";
+}
+
+std::string TerminateXml(SignalingProtocol protocol,
+ const std::string& reason) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleTerminateXml(reason);
+ case PROTOCOL_GINGLE:
+ return GingleTerminateXml(reason);
+ case PROTOCOL_HYBRID:
+ return JingleTerminateXml(reason) +
+ GingleTerminateXml(reason);
+ }
+ return "";
+}
+
+std::string RedirectXml(SignalingProtocol protocol,
+ const std::string& initiate,
+ const std::string& target) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleRedirectXml(initiate, target);
+ case PROTOCOL_GINGLE:
+ return GingleRedirectXml(initiate, target);
+ default:
+ break;
+ }
+ return "";
+}
+
+// TODO: Break out and join with fakeportallocator.h
+class TestPortAllocatorSession : public cricket::PortAllocatorSession {
+ public:
+ TestPortAllocatorSession(const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd,
+ const int port_offset)
+ : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, 0),
+ port_offset_(port_offset),
+ ports_(kNumPorts),
+ address_("127.0.0.1", 0),
+ network_("network", "unittest",
+ rtc::IPAddress(INADDR_LOOPBACK), 8),
+ socket_factory_(rtc::Thread::Current()),
+ running_(false) {
+ network_.AddIP(address_.ipaddr());
+ }
+
+ ~TestPortAllocatorSession() {
+ for (size_t i = 0; i < ports_.size(); i++)
+ delete ports_[i];
+ }
+
+ virtual void StartGettingPorts() {
+ for (int i = 0; i < kNumPorts; i++) {
+ int index = port_offset_ + i;
+ ports_[i] = cricket::UDPPort::Create(
+ rtc::Thread::Current(), &socket_factory_,
+ &network_, address_.ipaddr(), GetPort(index), GetPort(index),
+ GetUsername(index), GetPassword(index));
+ AddPort(ports_[i]);
+ }
+ running_ = true;
+ }
+
+ virtual void StopGettingPorts() { running_ = false; }
+ virtual bool IsGettingPorts() { return running_; }
+
+ void AddPort(cricket::Port* port) {
+ port->set_component(component_);
+ port->set_generation(0);
+ port->SignalDestroyed.connect(
+ this, &TestPortAllocatorSession::OnPortDestroyed);
+ port->SignalPortComplete.connect(
+ this, &TestPortAllocatorSession::OnPortComplete);
+ port->PrepareAddress();
+ SignalPortReady(this, port);
+ }
+
+ void OnPortDestroyed(cricket::PortInterface* port) {
+ for (size_t i = 0; i < ports_.size(); i++) {
+ if (ports_[i] == port)
+ ports_[i] = NULL;
+ }
+ }
+
+ void OnPortComplete(cricket::Port* port) {
+ SignalCandidatesReady(this, port->Candidates());
+ }
+
+ private:
+ int port_offset_;
+ std::vector<cricket::Port*> ports_;
+ rtc::SocketAddress address_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ bool running_;
+};
+
+class TestPortAllocator : public cricket::PortAllocator {
+ public:
+ TestPortAllocator() : port_offset_(0) {}
+
+ virtual cricket::PortAllocatorSession*
+ CreateSessionInternal(
+ const std::string& content_name,
+ int component,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd) {
+ port_offset_ += 2;
+ return new TestPortAllocatorSession(content_name, component,
+ ice_ufrag, ice_pwd, port_offset_ - 2);
+ }
+
+ int port_offset_;
+};
+
+class TestContentDescription : public cricket::ContentDescription {
+ public:
+ explicit TestContentDescription(const std::string& gingle_content_type,
+ const std::string& content_type)
+ : gingle_content_type(gingle_content_type),
+ content_type(content_type) {
+ }
+ virtual ContentDescription* Copy() const {
+ return new TestContentDescription(*this);
+ }
+
+ std::string gingle_content_type;
+ std::string content_type;
+};
+
+cricket::SessionDescription* NewTestSessionDescription(
+ const std::string gingle_content_type,
+ const std::string& content_name_a, const std::string& content_type_a,
+ const std::string& content_name_b, const std::string& content_type_b) {
+
+ cricket::SessionDescription* offer = new cricket::SessionDescription();
+ offer->AddContent(content_name_a, content_type_a,
+ new TestContentDescription(gingle_content_type,
+ content_type_a));
+ cricket::TransportDescription desc(cricket::NS_GINGLE_P2P,
+ std::string(), std::string());
+ offer->AddTransportInfo(cricket::TransportInfo(content_name_a, desc));
+
+ if (content_name_a != content_name_b) {
+ offer->AddContent(content_name_b, content_type_b,
+ new TestContentDescription(gingle_content_type,
+ content_type_b));
+ offer->AddTransportInfo(cricket::TransportInfo(content_name_b, desc));
+ }
+ return offer;
+}
+
+cricket::SessionDescription* NewTestSessionDescription(
+ const std::string& content_name, const std::string& content_type) {
+
+ cricket::SessionDescription* offer = new cricket::SessionDescription();
+ offer->AddContent(content_name, content_type,
+ new TestContentDescription(content_type,
+ content_type));
+ offer->AddTransportInfo(cricket::TransportInfo
+ (content_name, cricket::TransportDescription(
+ cricket::NS_GINGLE_P2P,
+ std::string(), std::string())));
+ return offer;
+}
+
+struct TestSessionClient: public cricket::SessionClient,
+ public sigslot::has_slots<> {
+ public:
+ TestSessionClient() {
+ }
+
+ ~TestSessionClient() {
+ }
+
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ cricket::ContentDescription** content,
+ cricket::ParseError* error) {
+ std::string content_type;
+ std::string gingle_content_type;
+ if (protocol == PROTOCOL_GINGLE) {
+ gingle_content_type = elem->Name().Namespace();
+ } else {
+ content_type = elem->Name().Namespace();
+ }
+
+ *content = new TestContentDescription(gingle_content_type, content_type);
+ return true;
+ }
+
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const cricket::ContentDescription* untyped_content,
+ buzz::XmlElement** elem,
+ cricket::WriteError* error) {
+ const TestContentDescription* content =
+ static_cast<const TestContentDescription*>(untyped_content);
+ std::string content_type = (protocol == PROTOCOL_GINGLE ?
+ content->gingle_content_type :
+ content->content_type);
+ *elem = new buzz::XmlElement(
+ buzz::QName(content_type, "description"), true);
+ return true;
+ }
+
+ void OnSessionCreate(cricket::Session* session, bool initiate) {
+ }
+
+ void OnSessionDestroy(cricket::Session* session) {
+ }
+};
+
+struct ChannelHandler : sigslot::has_slots<> {
+ explicit ChannelHandler(cricket::TransportChannel* p, const std::string& name)
+ : channel(p), last_readable(false), last_writable(false), data_count(0),
+ last_size(0), name(name) {
+ p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState);
+ p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState);
+ p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket);
+ }
+
+ bool writable() const {
+ return last_writable && channel->writable();
+ }
+
+ bool readable() const {
+ return last_readable && channel->readable();
+ }
+
+ void OnReadableState(cricket::TransportChannel* p) {
+ EXPECT_EQ(channel, p);
+ last_readable = channel->readable();
+ }
+
+ void OnWritableState(cricket::TransportChannel* p) {
+ EXPECT_EQ(channel, p);
+ last_writable = channel->writable();
+ }
+
+ void OnReadPacket(cricket::TransportChannel* p, const char* buf,
+ size_t size, const rtc::PacketTime& time, int flags) {
+ if (memcmp(buf, name.c_str(), name.size()) != 0)
+ return; // drop packet if packet doesn't belong to this channel. This
+ // can happen when transport channels are muxed together.
+ buf += name.size(); // Remove channel name from the message.
+ size -= name.size(); // Decrement size by channel name string size.
+ EXPECT_EQ(channel, p);
+ EXPECT_LE(size, sizeof(last_data));
+ data_count += 1;
+ last_size = size;
+ memcpy(last_data, buf, size);
+ }
+
+ void Send(const char* data, size_t size) {
+ rtc::PacketOptions options;
+ std::string data_with_id(name);
+ data_with_id += data;
+ int result = channel->SendPacket(data_with_id.c_str(), data_with_id.size(),
+ options, 0);
+ EXPECT_EQ(static_cast<int>(data_with_id.size()), result);
+ }
+
+ cricket::TransportChannel* channel;
+ bool last_readable, last_writable;
+ int data_count;
+ char last_data[4096];
+ size_t last_size;
+ std::string name;
+};
+
+void PrintStanza(const std::string& message,
+ const buzz::XmlElement* stanza) {
+ printf("%s: %s\n", message.c_str(), stanza->Str().c_str());
+}
+
+class TestClient : public sigslot::has_slots<> {
+ public:
+ // TODO: Add channel_component_a/b as inputs to the ctor.
+ TestClient(cricket::PortAllocator* port_allocator,
+ int* next_message_id,
+ const std::string& local_name,
+ SignalingProtocol start_protocol,
+ const std::string& content_type,
+ const std::string& content_name_a,
+ const std::string& channel_name_a,
+ const std::string& content_name_b,
+ const std::string& channel_name_b) {
+ Construct(port_allocator, next_message_id, local_name, start_protocol,
+ content_type, content_name_a, channel_name_a,
+ content_name_b, channel_name_b);
+ }
+
+ ~TestClient() {
+ if (session) {
+ session_manager->DestroySession(session);
+ EXPECT_EQ(1U, session_destroyed_count);
+ }
+ delete session_manager;
+ delete client;
+ for (std::deque<buzz::XmlElement*>::iterator it = sent_stanzas.begin();
+ it != sent_stanzas.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ void Construct(cricket::PortAllocator* pa,
+ int* message_id,
+ const std::string& lname,
+ SignalingProtocol protocol,
+ const std::string& cont_type,
+ const std::string& cont_name_a,
+ const std::string& chan_name_a,
+ const std::string& cont_name_b,
+ const std::string& chan_name_b) {
+ port_allocator_ = pa;
+ next_message_id = message_id;
+ local_name = lname;
+ start_protocol = protocol;
+ content_type = cont_type;
+ content_name_a = cont_name_a;
+ channel_name_a = chan_name_a;
+ content_name_b = cont_name_b;
+ channel_name_b = chan_name_b;
+ session_created_count = 0;
+ session_destroyed_count = 0;
+ session_remote_description_update_count = 0;
+ new_local_description = false;
+ new_remote_description = false;
+ last_content_action = cricket::CA_OFFER;
+ last_content_source = cricket::CS_LOCAL;
+ session = NULL;
+ last_session_state = cricket::BaseSession::STATE_INIT;
+ blow_up_on_error = true;
+ error_count = 0;
+
+ session_manager = new cricket::SessionManager(port_allocator_);
+ session_manager->SignalSessionCreate.connect(
+ this, &TestClient::OnSessionCreate);
+ session_manager->SignalSessionDestroy.connect(
+ this, &TestClient::OnSessionDestroy);
+ session_manager->SignalOutgoingMessage.connect(
+ this, &TestClient::OnOutgoingMessage);
+
+ client = new TestSessionClient();
+ session_manager->AddClient(content_type, client);
+ EXPECT_EQ(client, session_manager->GetClient(content_type));
+ }
+
+ uint32 sent_stanza_count() const {
+ return static_cast<uint32>(sent_stanzas.size());
+ }
+
+ const buzz::XmlElement* stanza() const {
+ return last_expected_sent_stanza.get();
+ }
+
+ cricket::BaseSession::State session_state() const {
+ EXPECT_EQ(last_session_state, session->state());
+ return session->state();
+ }
+
+ void SetSessionState(cricket::BaseSession::State state) {
+ session->SetState(state);
+ EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout);
+ }
+
+ void CreateSession() {
+ session_manager->CreateSession(local_name, content_type);
+ }
+
+ void DeliverStanza(const buzz::XmlElement* stanza) {
+ session_manager->OnIncomingMessage(stanza);
+ }
+
+ void DeliverStanza(const std::string& str) {
+ buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str);
+ session_manager->OnIncomingMessage(stanza);
+ delete stanza;
+ }
+
+ void DeliverAckToLastStanza() {
+ const buzz::XmlElement* orig_stanza = stanza();
+ const buzz::XmlElement* response_stanza =
+ buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", ""));
+ session_manager->OnIncomingResponse(orig_stanza, response_stanza);
+ delete response_stanza;
+ }
+
+ void ExpectSentStanza(const std::string& expected) {
+ EXPECT_TRUE(!sent_stanzas.empty()) <<
+ "Found no stanza when expected " << expected;
+
+ last_expected_sent_stanza.reset(sent_stanzas.front());
+ sent_stanzas.pop_front();
+
+ std::string actual = last_expected_sent_stanza->Str();
+ EXPECT_EQ(expected, actual);
+ }
+
+ void SkipUnsentStanza() {
+ GetNextOutgoingMessageID();
+ }
+
+ bool HasTransport(const std::string& content_name) const {
+ ASSERT(session != NULL);
+ const cricket::Transport* transport = session->GetTransport(content_name);
+ return transport != NULL && (kTransportType == transport->type());
+ }
+
+ bool HasChannel(const std::string& content_name,
+ int component) const {
+ ASSERT(session != NULL);
+ const cricket::TransportChannel* channel =
+ session->GetChannel(content_name, component);
+ return channel != NULL && (component == channel->component());
+ }
+
+ cricket::TransportChannel* GetChannel(const std::string& content_name,
+ int component) const {
+ ASSERT(session != NULL);
+ return session->GetChannel(content_name, component);
+ }
+
+ void OnSessionCreate(cricket::Session* created_session, bool initiate) {
+ session_created_count += 1;
+
+ session = created_session;
+ session->set_current_protocol(start_protocol);
+ session->SignalState.connect(this, &TestClient::OnSessionState);
+ session->SignalError.connect(this, &TestClient::OnSessionError);
+ session->SignalRemoteDescriptionUpdate.connect(
+ this, &TestClient::OnSessionRemoteDescriptionUpdate);
+ session->SignalNewLocalDescription.connect(
+ this, &TestClient::OnNewLocalDescription);
+ session->SignalNewRemoteDescription.connect(
+ this, &TestClient::OnNewRemoteDescription);
+
+ CreateChannels();
+ }
+
+ void OnSessionDestroy(cricket::Session *session) {
+ session_destroyed_count += 1;
+ }
+
+ void OnSessionState(cricket::BaseSession* session,
+ cricket::BaseSession::State state) {
+ // EXPECT_EQ does not allow use of this, hence the tmp variable.
+ cricket::BaseSession* tmp = this->session;
+ EXPECT_EQ(tmp, session);
+ last_session_state = state;
+ }
+
+ void OnSessionError(cricket::BaseSession* session,
+ cricket::BaseSession::Error error) {
+ // EXPECT_EQ does not allow use of this, hence the tmp variable.
+ cricket::BaseSession* tmp = this->session;
+ EXPECT_EQ(tmp, session);
+ if (blow_up_on_error) {
+ EXPECT_TRUE(false);
+ } else {
+ error_count++;
+ }
+ }
+
+ void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
+ const cricket::ContentInfos& contents) {
+ session_remote_description_update_count++;
+ }
+
+ void OnNewLocalDescription(cricket::BaseSession* session,
+ cricket::ContentAction action) {
+ new_local_description = true;
+ last_content_action = action;
+ last_content_source = cricket::CS_LOCAL;
+ }
+
+ void OnNewRemoteDescription(cricket::BaseSession* session,
+ cricket::ContentAction action) {
+ new_remote_description = true;
+ last_content_action = action;
+ last_content_source = cricket::CS_REMOTE;
+ }
+
+ void PrepareCandidates() {
+ session_manager->OnSignalingReady();
+ }
+
+ void OnOutgoingMessage(cricket::SessionManager* manager,
+ const buzz::XmlElement* stanza) {
+ buzz::XmlElement* elem = new buzz::XmlElement(*stanza);
+ EXPECT_TRUE(elem->Name() == buzz::QN_IQ);
+ EXPECT_TRUE(elem->HasAttr(buzz::QN_TO));
+ EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM));
+ EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE));
+ EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") ||
+ (elem->Attr(buzz::QN_TYPE) == "result") ||
+ (elem->Attr(buzz::QN_TYPE) == "error"));
+
+ elem->SetAttr(buzz::QN_FROM, local_name);
+ if (elem->Attr(buzz::QN_TYPE) == "set") {
+ EXPECT_FALSE(elem->HasAttr(buzz::QN_ID));
+ elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID());
+ }
+
+ // Uncommenting this is useful for debugging.
+ // PrintStanza("OutgoingMessage", elem);
+ sent_stanzas.push_back(elem);
+ }
+
+ std::string GetNextOutgoingMessageID() {
+ int message_id = (*next_message_id)++;
+ std::ostringstream ost;
+ ost << message_id;
+ return ost.str();
+ }
+
+ void CreateChannels() {
+ ASSERT(session != NULL);
+ // We either have a single content with multiple components (RTP/RTCP), or
+ // multiple contents with single components, but not both.
+ int component_a = 1;
+ int component_b = (content_name_a == content_name_b) ? 2 : 1;
+ chan_a.reset(new ChannelHandler(
+ session->CreateChannel(content_name_a, channel_name_a, component_a),
+ channel_name_a));
+ chan_b.reset(new ChannelHandler(
+ session->CreateChannel(content_name_b, channel_name_b, component_b),
+ channel_name_b));
+ }
+
+ int* next_message_id;
+ std::string local_name;
+ SignalingProtocol start_protocol;
+ std::string content_type;
+ std::string content_name_a;
+ std::string channel_name_a;
+ std::string content_name_b;
+ std::string channel_name_b;
+
+ uint32 session_created_count;
+ uint32 session_destroyed_count;
+ uint32 session_remote_description_update_count;
+ bool new_local_description;
+ bool new_remote_description;
+ cricket::ContentAction last_content_action;
+ cricket::ContentSource last_content_source;
+ std::deque<buzz::XmlElement*> sent_stanzas;
+ rtc::scoped_ptr<buzz::XmlElement> last_expected_sent_stanza;
+
+ cricket::SessionManager* session_manager;
+ TestSessionClient* client;
+ cricket::PortAllocator* port_allocator_;
+ cricket::Session* session;
+ cricket::BaseSession::State last_session_state;
+ rtc::scoped_ptr<ChannelHandler> chan_a;
+ rtc::scoped_ptr<ChannelHandler> chan_b;
+ bool blow_up_on_error;
+ int error_count;
+};
+
+class SessionTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Seed needed for each test to satisfy expectations.
+ rtc::SetRandomTestMode(true);
+ }
+
+ virtual void TearDown() {
+ rtc::SetRandomTestMode(false);
+ }
+
+ // Tests sending data between two clients, over two channels.
+ void TestSendRecv(ChannelHandler* chan1a,
+ ChannelHandler* chan1b,
+ ChannelHandler* chan2a,
+ ChannelHandler* chan2b) {
+ const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam";
+ const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps";
+ const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce...";
+ const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL";
+
+ for (int i = 0; i < 20; i++) {
+ chan1a->Send(dat1a, strlen(dat1a));
+ chan1b->Send(dat1b, strlen(dat1b));
+ chan2a->Send(dat2a, strlen(dat2a));
+ chan2b->Send(dat2b, strlen(dat2b));
+
+ EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout);
+
+ EXPECT_EQ(strlen(dat2a), chan1a->last_size);
+ EXPECT_EQ(strlen(dat2b), chan1b->last_size);
+ EXPECT_EQ(strlen(dat1a), chan2a->last_size);
+ EXPECT_EQ(strlen(dat1b), chan2b->last_size);
+
+ EXPECT_EQ(0, memcmp(chan1a->last_data, dat2a, strlen(dat2a)));
+ EXPECT_EQ(0, memcmp(chan1b->last_data, dat2b, strlen(dat2b)));
+ EXPECT_EQ(0, memcmp(chan2a->last_data, dat1a, strlen(dat1a)));
+ EXPECT_EQ(0, memcmp(chan2b->last_data, dat1b, strlen(dat1b)));
+ }
+ }
+
+ // Test an initiate from one client to another, each with
+ // independent initial protocols. Checks for the correct initiates,
+ // candidates, and accept messages, and tests that working network
+ // channels are established.
+ void TestSession(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_type,
+ const std::string& content_name_a,
+ const std::string& channel_name_a,
+ const std::string& content_name_b,
+ const std::string& channel_name_b,
+ const std::string& initiate_xml,
+ const std::string& transport_info_a_xml,
+ const std::string& transport_info_b_xml,
+ const std::string& transport_info_reply_a_xml,
+ const std::string& transport_info_reply_b_xml,
+ const std::string& accept_xml,
+ bool bundle = false) {
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, initiator_protocol,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b));
+ rtc::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, responder_protocol,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ // See comment in CreateChannels about how we choose component IDs.
+ int component_a = 1;
+ int component_b = (content_name_a == content_name_b) ? 2 : 1;
+ EXPECT_TRUE(initiator->HasTransport(content_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name_a, component_a));
+ EXPECT_TRUE(initiator->HasTransport(content_name_b));
+ EXPECT_TRUE(initiator->HasChannel(content_name_b, component_b));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ if (bundle) {
+ cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
+ group.AddContentName(content_name_a);
+ group.AddContentName(content_name_b);
+ EXPECT_TRUE(group.HasContentName(content_name_a));
+ EXPECT_TRUE(group.HasContentName(content_name_b));
+ offer->AddGroup(group);
+ }
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Deliver the initiate. Expect ack and session created with
+ // transports.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("0", kResponder, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), kResponder);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasTransport(content_name_a));
+ EXPECT_TRUE(responder->HasChannel(content_name_a, component_a));
+ EXPECT_TRUE(responder->HasTransport(content_name_b));
+ EXPECT_TRUE(responder->HasChannel(content_name_b, component_b));
+
+ // Expect transport-info message from initiator.
+ // But don't send candidates until initiate ack is received.
+ initiator->PrepareCandidates();
+ WAIT(initiator->sent_stanza_count() > 0, 100);
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ initiator->DeliverAckToLastStanza();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_a_xml));
+
+ // Deliver transport-info and expect ack.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("1", kResponder, kInitiator));
+
+ if (!transport_info_b_xml.empty()) {
+ // Expect second transport-info message from initiator.
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("2", kInitiator, kResponder, transport_info_b_xml));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Deliver second transport-info message and expect ack.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("2", kResponder, kInitiator));
+ } else {
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+ initiator->SkipUnsentStanza();
+ }
+
+ // Expect reply transport-info message from responder.
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml));
+
+ // Deliver reply transport-info and expect ack.
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("3", kInitiator, kResponder));
+
+ if (!transport_info_reply_b_xml.empty()) {
+ // Expect second reply transport-info message from responder.
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver second reply transport-info message and expect ack.
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("4", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ } else {
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+ responder->SkipUnsentStanza();
+ }
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ if (bundle) {
+ cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
+ group.AddContentName(content_name_a);
+ group.AddContentName(content_name_b);
+ EXPECT_TRUE(group.HasContentName(content_name_a));
+ EXPECT_TRUE(group.HasContentName(content_name_b));
+ answer->AddGroup(group);
+ }
+ EXPECT_TRUE(responder->session->Accept(answer));
+ EXPECT_EQ(responder->session->local_description(), answer);
+
+ responder->ExpectSentStanza(
+ IqSet("5", kResponder, kInitiator, accept_xml));
+
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver the accept message and expect an ack.
+ initiator->DeliverStanza(responder->stanza());
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("5", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ(resulting_protocol, initiator->session->current_protocol());
+ EXPECT_EQ(resulting_protocol, responder->session->current_protocol());
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ if (bundle) {
+ cricket::TransportChannel* initiator_chan_a = initiator->chan_a->channel;
+ cricket::TransportChannel* initiator_chan_b = initiator->chan_b->channel;
+
+ // Since we know these are TransportChannelProxy, type cast it.
+ cricket::TransportChannelProxy* initiator_proxy_chan_a =
+ static_cast<cricket::TransportChannelProxy*>(initiator_chan_a);
+ cricket::TransportChannelProxy* initiator_proxy_chan_b =
+ static_cast<cricket::TransportChannelProxy*>(initiator_chan_b);
+ EXPECT_TRUE(initiator_proxy_chan_a->impl() != NULL);
+ EXPECT_TRUE(initiator_proxy_chan_b->impl() != NULL);
+ EXPECT_EQ(initiator_proxy_chan_a->impl(), initiator_proxy_chan_b->impl());
+
+ cricket::TransportChannel* responder_chan_a = responder->chan_a->channel;
+ cricket::TransportChannel* responder_chan_b = responder->chan_b->channel;
+
+ // Since we know these are TransportChannelProxy, type cast it.
+ cricket::TransportChannelProxy* responder_proxy_chan_a =
+ static_cast<cricket::TransportChannelProxy*>(responder_chan_a);
+ cricket::TransportChannelProxy* responder_proxy_chan_b =
+ static_cast<cricket::TransportChannelProxy*>(responder_chan_b);
+ EXPECT_TRUE(responder_proxy_chan_a->impl() != NULL);
+ EXPECT_TRUE(responder_proxy_chan_b->impl() != NULL);
+ EXPECT_EQ(responder_proxy_chan_a->impl(), responder_proxy_chan_b->impl());
+ }
+ TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
+ responder->chan_a.get(), responder->chan_b.get());
+
+ if (resulting_protocol == PROTOCOL_JINGLE) {
+ // Deliver a description-info message to the initiator and check if the
+ // content description changes.
+ EXPECT_EQ(0U, initiator->session_remote_description_update_count);
+
+ const cricket::SessionDescription* old_session_desc =
+ initiator->session->remote_description();
+ const cricket::ContentInfo* old_content_a =
+ old_session_desc->GetContentByName(content_name_a);
+ const cricket::ContentDescription* old_content_desc_a =
+ old_content_a->description;
+ const cricket::ContentInfo* old_content_b =
+ old_session_desc->GetContentByName(content_name_b);
+ const cricket::ContentDescription* old_content_desc_b =
+ old_content_b->description;
+ EXPECT_TRUE(old_content_desc_a != NULL);
+ EXPECT_TRUE(old_content_desc_b != NULL);
+
+ LOG(LS_INFO) << "A " << old_content_a->name;
+ LOG(LS_INFO) << "B " << old_content_b->name;
+
+ std::string description_info_xml =
+ JingleDescriptionInfoXml(content_name_a, content_type);
+ initiator->DeliverStanza(
+ IqSet("6", kResponder, kInitiator, description_info_xml));
+ responder->SkipUnsentStanza();
+ EXPECT_EQ(1U, initiator->session_remote_description_update_count);
+
+ const cricket::SessionDescription* new_session_desc =
+ initiator->session->remote_description();
+ const cricket::ContentInfo* new_content_a =
+ new_session_desc->GetContentByName(content_name_a);
+ const cricket::ContentDescription* new_content_desc_a =
+ new_content_a->description;
+ const cricket::ContentInfo* new_content_b =
+ new_session_desc->GetContentByName(content_name_b);
+ const cricket::ContentDescription* new_content_desc_b =
+ new_content_b->description;
+ EXPECT_TRUE(new_content_desc_a != NULL);
+ EXPECT_TRUE(new_content_desc_b != NULL);
+
+ // TODO: We used to replace contents from an update, but
+ // that no longer works with partial updates. We need to figure out
+ // a way to merge patial updates into contents. For now, users of
+ // Session should listen to SignalRemoteDescriptionUpdate and handle
+ // updates. They should not expect remote_description to be the
+ // latest value.
+ // See session.cc OnDescriptionInfoMessage.
+
+ // EXPECT_NE(old_content_desc_a, new_content_desc_a);
+
+ // if (content_name_a != content_name_b) {
+ // // If content_name_a != content_name_b, then b's content description
+ // // should not have changed since the description-info message only
+ // // contained an update for content_name_a.
+ // EXPECT_EQ(old_content_desc_b, new_content_desc_b);
+ // }
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("6", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ } else {
+ responder->SkipUnsentStanza();
+ }
+
+ initiator->session->Terminate();
+ initiator->ExpectSentStanza(
+ IqSet("7", kInitiator, kResponder,
+ TerminateXml(resulting_protocol,
+ cricket::STR_TERMINATE_SUCCESS)));
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("7", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+ initiator->session_state());
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ responder->session_state());
+ }
+
+ // Test an initiate with other content, called "main".
+ void TestOtherContent(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string content_name_a = content_name;
+ std::string channel_name_a = "rtp";
+ std::string content_name_b = content_name;
+ std::string channel_name_b = "rtcp";
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ content_name_a, content_type);
+ std::string transport_info_a_xml = TransportInfo4Xml(
+ initiator_protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_b_xml = "";
+ std::string transport_info_reply_a_xml = TransportInfo4Xml(
+ resulting_protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string transport_info_reply_b_xml = "";
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ content_name_a, content_type);
+
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ content_type,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ // Test an initiate with audio content.
+ void TestAudioContent(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string gingle_content_type = cricket::NS_GINGLE_AUDIO;
+ std::string content_name = cricket::CN_AUDIO;
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string channel_name_a = "rtp";
+ std::string channel_name_b = "rtcp";
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ gingle_content_type,
+ content_name, content_type,
+ "", "");
+ std::string transport_info_a_xml = TransportInfo4Xml(
+ initiator_protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_b_xml = "";
+ std::string transport_info_reply_a_xml = TransportInfo4Xml(
+ resulting_protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string transport_info_reply_b_xml = "";
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ gingle_content_type,
+ content_name, content_type,
+ "", "");
+
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ gingle_content_type,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ // Since media content is "split" into two contents (audio and
+ // video), we need to treat it special.
+ void TestVideoContents(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+ std::string content_name_a = cricket::CN_AUDIO;
+ std::string channel_name_a = "rtp";
+ std::string content_name_b = cricket::CN_VIDEO;
+ std::string channel_name_b = "video_rtp";
+
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ std::string transport_info_a_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_a,
+ channel_name_a, 0, 1);
+ std::string transport_info_b_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_b,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_a_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_a,
+ channel_name_a, 4, 5);
+ std::string transport_info_reply_b_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_b,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ gingle_content_type,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ void TestBadRedirect(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "chana";
+ std::string channel_name_b = "chanb";
+ std::string initiate_xml = InitiateXml(
+ protocol, content_name, content_type);
+ std::string transport_info_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ protocol, content_name, content_type);
+ std::string responder_full = kResponder + "/full";
+
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ rtc::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ responder_full, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ EXPECT_TRUE(initiator->HasChannel(content_name, 1));
+ EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Expect transport-info message from initiator.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+ // Send an unauthorized redirect to the initiator and expect it be ignored.
+ initiator->blow_up_on_error = false;
+ const buzz::XmlElement* initiate_stanza = initiator->stanza();
+ rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
+ buzz::XmlElement::ForStr(
+ IqError("ER", kResponder, kInitiator,
+ RedirectXml(protocol, initiate_xml, "not@allowed.com"))));
+ initiator->session_manager->OnFailedSend(
+ initiate_stanza, redirect_stanza.get());
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ initiator->blow_up_on_error = true;
+ EXPECT_EQ(initiator->error_count, 1);
+ }
+
+ void TestGoodRedirect(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "chana";
+ std::string channel_name_b = "chanb";
+ std::string initiate_xml = InitiateXml(
+ protocol, content_name, content_type);
+ std::string transport_info_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ protocol, content_name, content_type);
+ std::string responder_full = kResponder + "/full";
+
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ rtc::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ responder_full, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ EXPECT_TRUE(initiator->HasChannel(content_name, 1));
+ EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Expect transport-info message from initiator.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+ // Send a redirect to the initiator and expect all of the message
+ // to be resent.
+ const buzz::XmlElement* initiate_stanza = initiator->stanza();
+ rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
+ buzz::XmlElement::ForStr(
+ IqError("ER2", kResponder, kInitiator,
+ RedirectXml(protocol, initiate_xml, responder_full))));
+ initiator->session_manager->OnFailedSend(
+ initiate_stanza, redirect_stanza.get());
+ EXPECT_EQ(initiator->session->remote_name(), responder_full);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("2", kInitiator, responder_full, initiate_xml));
+ initiator->ExpectSentStanza(
+ IqSet("3", kInitiator, responder_full, transport_info_xml));
+
+ // Deliver the initiate. Expect ack and session created with
+ // transports.
+ responder->DeliverStanza(
+ IqSet("2", kInitiator, responder_full, initiate_xml));
+ responder->ExpectSentStanza(
+ IqAck("2", responder_full, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), responder_full);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasChannel(content_name, 1));
+ EXPECT_TRUE(responder->HasChannel(content_name, 2));
+
+ // Deliver transport-info and expect ack.
+ responder->DeliverStanza(
+ IqSet("3", kInitiator, responder_full, transport_info_xml));
+ responder->ExpectSentStanza(
+ IqAck("3", responder_full, kInitiator));
+
+ // Expect reply transport-infos sent to new remote JID
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("4", responder_full, kInitiator, transport_info_reply_xml));
+
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("4", kInitiator, responder_full));
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(responder->session->Accept(answer));
+ EXPECT_EQ(responder->session->local_description(), answer);
+
+ responder->ExpectSentStanza(
+ IqSet("5", responder_full, kInitiator, accept_xml));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver the accept message and expect an ack.
+ initiator->DeliverStanza(responder->stanza());
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("5", kInitiator, responder_full));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
+ responder->chan_a.get(), responder->chan_b.get());
+ }
+
+ void TestCandidatesInInitiateAndAccept(const std::string& test_name) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "rtp";
+ std::string channel_name_b = "rtcp";
+ cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
+
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ rtc::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->HasTransport(content_name));
+ EXPECT_TRUE(initiator->HasChannel(content_name, 1));
+ EXPECT_TRUE(initiator->HasTransport(content_name));
+ EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+
+ // Fake the delivery the initiate and candidates together.
+ responder->DeliverStanza(
+ IqSet("A", kInitiator, kResponder,
+ JingleInitiateActionXml(
+ JingleContentXml(
+ content_name, content_type, kTransportType,
+ P2pCandidateXml(channel_name_a, 0) +
+ P2pCandidateXml(channel_name_a, 1) +
+ P2pCandidateXml(channel_name_b, 2) +
+ P2pCandidateXml(channel_name_b, 3)))));
+ responder->ExpectSentStanza(
+ IqAck("A", kResponder, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), kResponder);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasTransport(content_name));
+ EXPECT_TRUE(responder->HasChannel(content_name, 1));
+ EXPECT_TRUE(responder->HasTransport(content_name));
+ EXPECT_TRUE(responder->HasChannel(content_name, 2));
+
+ // Expect transport-info message from initiator.
+ // But don't send candidates until initiate ack is received.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder,
+ TransportInfo4Xml(protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3)));
+
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("2", kResponder, kInitiator,
+ TransportInfo4Xml(protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7)));
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(responder->session->Accept(answer));
+
+ responder->ExpectSentStanza(
+ IqSet("3", kResponder, kInitiator,
+ AcceptXml(protocol, content_name, content_type)));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Fake the delivery the accept and candidates together.
+ initiator->DeliverStanza(
+ IqSet("B", kResponder, kInitiator,
+ JingleActionXml("session-accept",
+ JingleContentXml(
+ content_name, content_type, kTransportType,
+ P2pCandidateXml(channel_name_a, 4) +
+ P2pCandidateXml(channel_name_a, 5) +
+ P2pCandidateXml(channel_name_b, 6) +
+ P2pCandidateXml(channel_name_b, 7)))));
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("B", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ(protocol, initiator->session->current_protocol());
+ EXPECT_EQ(protocol, responder->session->current_protocol());
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
+ responder->chan_a.get(), responder->chan_b.get());
+ }
+
+ // Tests that when an initiator terminates right after initiate,
+ // everything behaves correctly.
+ void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ rtc::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ // Send initiate
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->session->Initiate(
+ kResponder, NewTestSessionDescription(content_name, content_type)));
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("0", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder,
+ TerminateXml(protocol, cricket::STR_TERMINATE_ERROR)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+ initiator->session_state());
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("1", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ responder->session_state());
+ }
+
+ // Tests that when the responder rejects, everything behaves
+ // correctly.
+ void TestRejection(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ // Send initiate
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->session->Initiate(
+ kResponder, NewTestSessionDescription(content_name, content_type)));
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+
+ initiator->DeliverStanza(
+ IqSet("1", kResponder, kInitiator,
+ RejectXml(protocol, cricket::STR_TERMINATE_ERROR)));
+ initiator->ExpectSentStanza(
+ IqAck("1", kInitiator, kResponder));
+ if (protocol == PROTOCOL_JINGLE) {
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ initiator->session_state());
+ } else {
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT,
+ initiator->session_state());
+ }
+ }
+
+ void TestTransportMux() {
+ SignalingProtocol initiator_protocol = PROTOCOL_JINGLE;
+ SignalingProtocol responder_protocol = PROTOCOL_JINGLE;
+ SignalingProtocol resulting_protocol = PROTOCOL_JINGLE;
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+ std::string content_name_a = cricket::CN_AUDIO;
+ std::string channel_name_a = "rtp";
+ std::string content_name_b = cricket::CN_VIDEO;
+ std::string channel_name_b = "video_rtp";
+
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type, true);
+ std::string transport_info_a_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_a,
+ channel_name_a, 0, 1);
+ std::string transport_info_b_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_b,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_a_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_a,
+ channel_name_a, 4, 5);
+ std::string transport_info_reply_b_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_b,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type, true);
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ gingle_content_type,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml,
+ true);
+ }
+
+ void TestSendDescriptionInfo() {
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ std::string content_name = "content-name";
+ std::string content_type = "content-type";
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, PROTOCOL_JINGLE,
+ content_type,
+ content_name, "",
+ "", ""));
+
+ initiator->CreateSession();
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ std::string initiate_xml = InitiateXml(
+ PROTOCOL_JINGLE, content_name, content_type);
+
+ cricket::ContentInfos contents;
+ TestContentDescription content(content_type, content_type);
+ contents.push_back(
+ cricket::ContentInfo(content_name, content_type, &content));
+ std::string description_info_xml = JingleDescriptionInfoXml(
+ content_name, content_type);
+
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ EXPECT_TRUE(initiator->session->SendDescriptionInfoMessage(contents));
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, description_info_xml));
+ }
+
+ void DoTestSignalNewDescription(
+ TestClient* client,
+ cricket::BaseSession::State state,
+ cricket::ContentAction expected_content_action,
+ cricket::ContentSource expected_content_source) {
+ // Clean up before the new test.
+ client->new_local_description = false;
+ client->new_remote_description = false;
+
+ client->SetSessionState(state);
+ EXPECT_EQ((expected_content_source == cricket::CS_LOCAL),
+ client->new_local_description);
+ EXPECT_EQ((expected_content_source == cricket::CS_REMOTE),
+ client->new_remote_description);
+ EXPECT_EQ(expected_content_action, client->last_content_action);
+ EXPECT_EQ(expected_content_source, client->last_content_source);
+ }
+
+ void TestCallerSignalNewDescription() {
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ std::string content_name = "content-name";
+ std::string content_type = "content-type";
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, PROTOCOL_JINGLE,
+ content_type,
+ content_name, "",
+ "", ""));
+
+ initiator->CreateSession();
+
+ // send offer -> send update offer ->
+ // receive pr answer -> receive update pr answer ->
+ // receive answer
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
+ cricket::CA_OFFER, cricket::CS_LOCAL);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
+ cricket::CA_OFFER, cricket::CS_LOCAL);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
+ cricket::CA_PRANSWER, cricket::CS_REMOTE);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
+ cricket::CA_PRANSWER, cricket::CS_REMOTE);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_RECEIVEDACCEPT,
+ cricket::CA_ANSWER, cricket::CS_REMOTE);
+ }
+
+ void TestCalleeSignalNewDescription() {
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ std::string content_name = "content-name";
+ std::string content_type = "content-type";
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, PROTOCOL_JINGLE,
+ content_type,
+ content_name, "",
+ "", ""));
+
+ initiator->CreateSession();
+
+ // receive offer -> receive update offer ->
+ // send pr answer -> send update pr answer ->
+ // send answer
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ cricket::CA_OFFER, cricket::CS_REMOTE);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ cricket::CA_OFFER, cricket::CS_REMOTE);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
+ cricket::CA_PRANSWER, cricket::CS_LOCAL);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
+ cricket::CA_PRANSWER, cricket::CS_LOCAL);
+
+ DoTestSignalNewDescription(
+ initiator.get(), cricket::BaseSession::STATE_SENTACCEPT,
+ cricket::CA_ANSWER, cricket::CS_LOCAL);
+ }
+
+ void TestGetTransportStats() {
+ rtc::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ std::string content_name = "content-name";
+ std::string content_type = "content-type";
+ rtc::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, PROTOCOL_JINGLE,
+ content_type,
+ content_name, "",
+ "", ""));
+ initiator->CreateSession();
+
+ cricket::SessionStats stats;
+ EXPECT_TRUE(initiator->session->GetStats(&stats));
+ // At initiation, there are 2 transports.
+ EXPECT_EQ(2ul, stats.proxy_to_transport.size());
+ EXPECT_EQ(2ul, stats.transport_stats.size());
+ }
+};
+
+// For each of these, "X => Y = Z" means "if a client with protocol X
+// initiates to a client with protocol Y, they end up speaking protocol Z.
+
+// Gingle => Gingle = Gingle (with other content)
+TEST_F(SessionTest, GingleToGingleOtherContent) {
+ TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, GingleToGingleAudioContent) {
+ TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, GingleToGingleVideoContents) {
+ TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Jingle => Jingle = Jingle (with other content)
+TEST_F(SessionTest, JingleToJingleOtherContent) {
+ TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, JingleToJingleAudioContent) {
+ TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, JingleToJingleVideoContents) {
+ TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, HybridToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, HybridToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, HybridToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with other content)
+TEST_F(SessionTest, GingleToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with audio content)
+TEST_F(SessionTest, GingleToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with video contents)
+TEST_F(SessionTest, GingleToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, JingleToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, JingleToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, JingleToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with other content)
+TEST_F(SessionTest, HybridToGingleOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, HybridToGingleAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, HybridToGingleVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with other content)
+TEST_F(SessionTest, HybridToJingleOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, HybridToJingleAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, HybridToJingleVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID);
+}
+
+TEST_F(SessionTest, GingleRejection) {
+ TestRejection(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleRejection) {
+ TestRejection(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleGoodRedirect) {
+ TestGoodRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleGoodRedirect) {
+ TestGoodRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleBadRedirect) {
+ TestBadRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleBadRedirect) {
+ TestBadRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) {
+ TestCandidatesInInitiateAndAccept("Candidates in initiate/accept");
+}
+
+TEST_F(SessionTest, TestTransportMux) {
+ TestTransportMux();
+}
+
+TEST_F(SessionTest, TestSendDescriptionInfo) {
+ TestSendDescriptionInfo();
+}
+
+TEST_F(SessionTest, TestCallerSignalNewDescription) {
+ TestCallerSignalNewDescription();
+}
+
+TEST_F(SessionTest, TestCalleeSignalNewDescription) {
+ TestCalleeSignalNewDescription();
+}
+
+TEST_F(SessionTest, TestGetTransportStats) {
+ TestGetTransportStats();
+}
diff --git a/p2p/base/sessionclient.h b/p2p/base/sessionclient.h
new file mode 100644
index 00000000..89687885
--- /dev/null
+++ b/p2p/base/sessionclient.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSIONCLIENT_H_
+#define WEBRTC_P2P_BASE_SESSIONCLIENT_H_
+
+#include "webrtc/p2p/base/constants.h"
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+class Session;
+class ContentDescription;
+
+class ContentParser {
+ public:
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ ContentDescription** content,
+ ParseError* error) = 0;
+ // If not IsWriteable, then a given content should be "skipped" when
+ // writing in the given protocol, as if it didn't exist. We assume
+ // most things are writeable. We do this to avoid strange cases
+ // like data contents in Gingle, which aren't writable.
+ virtual bool IsWritable(SignalingProtocol protocol,
+ const ContentDescription* content) {
+ return true;
+ }
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error) = 0;
+ virtual ~ContentParser() {}
+};
+
+// A SessionClient exists in 1-1 relation with each session. The implementor
+// of this interface is the one that understands *what* the two sides are
+// trying to send to one another. The lower-level layers only know how to send
+// data; they do not know what is being sent.
+class SessionClient : public ContentParser {
+ public:
+ // Notifies the client of the creation / destruction of sessions of this type.
+ //
+ // IMPORTANT: The SessionClient, in its handling of OnSessionCreate, must
+ // create whatever channels are indicate in the description. This is because
+ // the remote client may already be attempting to connect those channels. If
+ // we do not create our channel right away, then connection may fail or be
+ // delayed.
+ virtual void OnSessionCreate(Session* session, bool received_initiate) = 0;
+ virtual void OnSessionDestroy(Session* session) = 0;
+
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ ContentDescription** content,
+ ParseError* error) = 0;
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error) = 0;
+ protected:
+ // The SessionClient interface explicitly does not include destructor
+ virtual ~SessionClient() { }
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSIONCLIENT_H_
diff --git a/p2p/base/sessiondescription.cc b/p2p/base/sessiondescription.cc
new file mode 100644
index 00000000..b05dc510
--- /dev/null
+++ b/p2p/base/sessiondescription.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/sessiondescription.h"
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+
+namespace cricket {
+
+ContentInfo* FindContentInfoByName(
+ ContentInfos& contents, const std::string& name) {
+ for (ContentInfos::iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ if (content->name == name) {
+ return &(*content);
+ }
+ }
+ return NULL;
+}
+
+const ContentInfo* FindContentInfoByName(
+ const ContentInfos& contents, const std::string& name) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ if (content->name == name) {
+ return &(*content);
+ }
+ }
+ return NULL;
+}
+
+const ContentInfo* FindContentInfoByType(
+ const ContentInfos& contents, const std::string& type) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ if (content->type == type) {
+ return &(*content);
+ }
+ }
+ return NULL;
+}
+
+const std::string* ContentGroup::FirstContentName() const {
+ return (!content_names_.empty()) ? &(*content_names_.begin()) : NULL;
+}
+
+bool ContentGroup::HasContentName(const std::string& content_name) const {
+ return (std::find(content_names_.begin(), content_names_.end(),
+ content_name) != content_names_.end());
+}
+
+void ContentGroup::AddContentName(const std::string& content_name) {
+ if (!HasContentName(content_name)) {
+ content_names_.push_back(content_name);
+ }
+}
+
+bool ContentGroup::RemoveContentName(const std::string& content_name) {
+ ContentNames::iterator iter = std::find(
+ content_names_.begin(), content_names_.end(), content_name);
+ if (iter == content_names_.end()) {
+ return false;
+ }
+ content_names_.erase(iter);
+ return true;
+}
+
+SessionDescription* SessionDescription::Copy() const {
+ SessionDescription* copy = new SessionDescription(*this);
+ // Copy all ContentDescriptions.
+ for (ContentInfos::iterator content = copy->contents_.begin();
+ content != copy->contents().end(); ++content) {
+ content->description = content->description->Copy();
+ }
+ return copy;
+}
+
+const ContentInfo* SessionDescription::GetContentByName(
+ const std::string& name) const {
+ return FindContentInfoByName(contents_, name);
+}
+
+ContentInfo* SessionDescription::GetContentByName(
+ const std::string& name) {
+ return FindContentInfoByName(contents_, name);
+}
+
+const ContentDescription* SessionDescription::GetContentDescriptionByName(
+ const std::string& name) const {
+ const ContentInfo* cinfo = FindContentInfoByName(contents_, name);
+ if (cinfo == NULL) {
+ return NULL;
+ }
+
+ return cinfo->description;
+}
+
+ContentDescription* SessionDescription::GetContentDescriptionByName(
+ const std::string& name) {
+ ContentInfo* cinfo = FindContentInfoByName(contents_, name);
+ if (cinfo == NULL) {
+ return NULL;
+ }
+
+ return cinfo->description;
+}
+
+const ContentInfo* SessionDescription::FirstContentByType(
+ const std::string& type) const {
+ return FindContentInfoByType(contents_, type);
+}
+
+const ContentInfo* SessionDescription::FirstContent() const {
+ return (contents_.empty()) ? NULL : &(*contents_.begin());
+}
+
+void SessionDescription::AddContent(const std::string& name,
+ const std::string& type,
+ ContentDescription* description) {
+ contents_.push_back(ContentInfo(name, type, description));
+}
+
+void SessionDescription::AddContent(const std::string& name,
+ const std::string& type,
+ bool rejected,
+ ContentDescription* description) {
+ contents_.push_back(ContentInfo(name, type, rejected, description));
+}
+
+bool SessionDescription::RemoveContentByName(const std::string& name) {
+ for (ContentInfos::iterator content = contents_.begin();
+ content != contents_.end(); ++content) {
+ if (content->name == name) {
+ delete content->description;
+ contents_.erase(content);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool SessionDescription::AddTransportInfo(const TransportInfo& transport_info) {
+ if (GetTransportInfoByName(transport_info.content_name) != NULL) {
+ return false;
+ }
+ transport_infos_.push_back(transport_info);
+ return true;
+}
+
+bool SessionDescription::RemoveTransportInfoByName(const std::string& name) {
+ for (TransportInfos::iterator transport_info = transport_infos_.begin();
+ transport_info != transport_infos_.end(); ++transport_info) {
+ if (transport_info->content_name == name) {
+ transport_infos_.erase(transport_info);
+ return true;
+ }
+ }
+ return false;
+}
+
+const TransportInfo* SessionDescription::GetTransportInfoByName(
+ const std::string& name) const {
+ for (TransportInfos::const_iterator iter = transport_infos_.begin();
+ iter != transport_infos_.end(); ++iter) {
+ if (iter->content_name == name) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
+TransportInfo* SessionDescription::GetTransportInfoByName(
+ const std::string& name) {
+ for (TransportInfos::iterator iter = transport_infos_.begin();
+ iter != transport_infos_.end(); ++iter) {
+ if (iter->content_name == name) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
+void SessionDescription::RemoveGroupByName(const std::string& name) {
+ for (ContentGroups::iterator iter = content_groups_.begin();
+ iter != content_groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ content_groups_.erase(iter);
+ break;
+ }
+ }
+}
+
+bool SessionDescription::HasGroup(const std::string& name) const {
+ for (ContentGroups::const_iterator iter = content_groups_.begin();
+ iter != content_groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const ContentGroup* SessionDescription::GetGroupByName(
+ const std::string& name) const {
+ for (ContentGroups::const_iterator iter = content_groups_.begin();
+ iter != content_groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
+} // namespace cricket
diff --git a/p2p/base/sessiondescription.h b/p2p/base/sessiondescription.h
new file mode 100644
index 00000000..1182a677
--- /dev/null
+++ b/p2p/base/sessiondescription.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_
+#define WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/transportinfo.h"
+#include "webrtc/base/constructormagic.h"
+
+namespace cricket {
+
+// Describes a session content. Individual content types inherit from
+// this class. Analagous to a <jingle><content><description> or
+// <session><description>.
+class ContentDescription {
+ public:
+ virtual ~ContentDescription() {}
+ virtual ContentDescription* Copy() const = 0;
+};
+
+// Analagous to a <jingle><content> or <session><description>.
+// name = name of <content name="...">
+// type = xmlns of <content>
+struct ContentInfo {
+ ContentInfo() : description(NULL) {}
+ ContentInfo(const std::string& name,
+ const std::string& type,
+ ContentDescription* description) :
+ name(name), type(type), rejected(false), description(description) {}
+ ContentInfo(const std::string& name,
+ const std::string& type,
+ bool rejected,
+ ContentDescription* description) :
+ name(name), type(type), rejected(rejected), description(description) {}
+ std::string name;
+ std::string type;
+ bool rejected;
+ ContentDescription* description;
+};
+
+typedef std::vector<std::string> ContentNames;
+
+// This class provides a mechanism to aggregate different media contents into a
+// group. This group can also be shared with the peers in a pre-defined format.
+// GroupInfo should be populated only with the |content_name| of the
+// MediaDescription.
+class ContentGroup {
+ public:
+ explicit ContentGroup(const std::string& semantics) :
+ semantics_(semantics) {}
+
+ const std::string& semantics() const { return semantics_; }
+ const ContentNames& content_names() const { return content_names_; }
+
+ const std::string* FirstContentName() const;
+ bool HasContentName(const std::string& content_name) const;
+ void AddContentName(const std::string& content_name);
+ bool RemoveContentName(const std::string& content_name);
+
+ private:
+ std::string semantics_;
+ ContentNames content_names_;
+};
+
+typedef std::vector<ContentInfo> ContentInfos;
+typedef std::vector<ContentGroup> ContentGroups;
+
+const ContentInfo* FindContentInfoByName(
+ const ContentInfos& contents, const std::string& name);
+const ContentInfo* FindContentInfoByType(
+ const ContentInfos& contents, const std::string& type);
+
+// Describes a collection of contents, each with its own name and
+// type. Analogous to a <jingle> or <session> stanza. Assumes that
+// contents are unique be name, but doesn't enforce that.
+class SessionDescription {
+ public:
+ SessionDescription() {}
+ explicit SessionDescription(const ContentInfos& contents) :
+ contents_(contents) {}
+ SessionDescription(const ContentInfos& contents,
+ const ContentGroups& groups) :
+ contents_(contents),
+ content_groups_(groups) {}
+ SessionDescription(const ContentInfos& contents,
+ const TransportInfos& transports,
+ const ContentGroups& groups) :
+ contents_(contents),
+ transport_infos_(transports),
+ content_groups_(groups) {}
+ ~SessionDescription() {
+ for (ContentInfos::iterator content = contents_.begin();
+ content != contents_.end(); ++content) {
+ delete content->description;
+ }
+ }
+
+ SessionDescription* Copy() const;
+
+ // Content accessors.
+ const ContentInfos& contents() const { return contents_; }
+ ContentInfos& contents() { return contents_; }
+ const ContentInfo* GetContentByName(const std::string& name) const;
+ ContentInfo* GetContentByName(const std::string& name);
+ const ContentDescription* GetContentDescriptionByName(
+ const std::string& name) const;
+ ContentDescription* GetContentDescriptionByName(const std::string& name);
+ const ContentInfo* FirstContentByType(const std::string& type) const;
+ const ContentInfo* FirstContent() const;
+
+ // Content mutators.
+ // Adds a content to this description. Takes ownership of ContentDescription*.
+ void AddContent(const std::string& name,
+ const std::string& type,
+ ContentDescription* description);
+ void AddContent(const std::string& name,
+ const std::string& type,
+ bool rejected,
+ ContentDescription* description);
+ bool RemoveContentByName(const std::string& name);
+
+ // Transport accessors.
+ const TransportInfos& transport_infos() const { return transport_infos_; }
+ TransportInfos& transport_infos() { return transport_infos_; }
+ const TransportInfo* GetTransportInfoByName(
+ const std::string& name) const;
+ TransportInfo* GetTransportInfoByName(const std::string& name);
+ const TransportDescription* GetTransportDescriptionByName(
+ const std::string& name) const {
+ const TransportInfo* tinfo = GetTransportInfoByName(name);
+ return tinfo ? &tinfo->description : NULL;
+ }
+
+ // Transport mutators.
+ void set_transport_infos(const TransportInfos& transport_infos) {
+ transport_infos_ = transport_infos;
+ }
+ // Adds a TransportInfo to this description.
+ // Returns false if a TransportInfo with the same name already exists.
+ bool AddTransportInfo(const TransportInfo& transport_info);
+ bool RemoveTransportInfoByName(const std::string& name);
+
+ // Group accessors.
+ const ContentGroups& groups() const { return content_groups_; }
+ const ContentGroup* GetGroupByName(const std::string& name) const;
+ bool HasGroup(const std::string& name) const;
+
+ // Group mutators.
+ void AddGroup(const ContentGroup& group) { content_groups_.push_back(group); }
+ // Remove the first group with the same semantics specified by |name|.
+ void RemoveGroupByName(const std::string& name);
+
+ private:
+ ContentInfos contents_;
+ TransportInfos transport_infos_;
+ ContentGroups content_groups_;
+};
+
+// Indicates whether a ContentDescription was an offer or an answer, as
+// described in http://www.ietf.org/rfc/rfc3264.txt. CA_UPDATE
+// indicates a jingle update message which contains a subset of a full
+// session description
+enum ContentAction {
+ CA_OFFER, CA_PRANSWER, CA_ANSWER, CA_UPDATE
+};
+
+// Indicates whether a ContentDescription was sent by the local client
+// or received from the remote client.
+enum ContentSource {
+ CS_LOCAL, CS_REMOTE
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_
diff --git a/p2p/base/sessionid.h b/p2p/base/sessionid.h
new file mode 100644
index 00000000..f6957003
--- /dev/null
+++ b/p2p/base/sessionid.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSIONID_H_
+#define WEBRTC_P2P_BASE_SESSIONID_H_
+
+// TODO: Remove this file.
+
+namespace cricket {
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSIONID_H_
diff --git a/p2p/base/sessionmanager.cc b/p2p/base/sessionmanager.cc
new file mode 100644
index 00000000..f375dea5
--- /dev/null
+++ b/p2p/base/sessionmanager.cc
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/sessionmanager.h"
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stringencode.h"
+
+namespace cricket {
+
+SessionManager::SessionManager(PortAllocator *allocator,
+ rtc::Thread *worker) {
+ allocator_ = allocator;
+ signaling_thread_ = rtc::Thread::Current();
+ if (worker == NULL) {
+ worker_thread_ = rtc::Thread::Current();
+ } else {
+ worker_thread_ = worker;
+ }
+ timeout_ = 50;
+}
+
+SessionManager::~SessionManager() {
+ // Note: Session::Terminate occurs asynchronously, so it's too late to
+ // delete them now. They better be all gone.
+ ASSERT(session_map_.empty());
+ // TerminateAll();
+ SignalDestroyed();
+}
+
+void SessionManager::AddClient(const std::string& content_type,
+ SessionClient* client) {
+ ASSERT(client_map_.find(content_type) == client_map_.end());
+ client_map_[content_type] = client;
+}
+
+void SessionManager::RemoveClient(const std::string& content_type) {
+ ClientMap::iterator iter = client_map_.find(content_type);
+ ASSERT(iter != client_map_.end());
+ client_map_.erase(iter);
+}
+
+SessionClient* SessionManager::GetClient(const std::string& content_type) {
+ ClientMap::iterator iter = client_map_.find(content_type);
+ return (iter != client_map_.end()) ? iter->second : NULL;
+}
+
+Session* SessionManager::CreateSession(const std::string& local_name,
+ const std::string& content_type) {
+ std::string id;
+ return CreateSession(id, local_name, content_type);
+}
+
+Session* SessionManager::CreateSession(const std::string& id,
+ const std::string& local_name,
+ const std::string& content_type) {
+ std::string sid =
+ id.empty() ? rtc::ToString(rtc::CreateRandomId64()) : id;
+ return CreateSession(local_name, local_name, sid, content_type, false);
+}
+
+Session* SessionManager::CreateSession(
+ const std::string& local_name, const std::string& initiator_name,
+ const std::string& sid, const std::string& content_type,
+ bool received_initiate) {
+ SessionClient* client = GetClient(content_type);
+ ASSERT(client != NULL);
+
+ Session* session = new Session(this, local_name, initiator_name,
+ sid, content_type, client);
+ session->SetIdentity(transport_desc_factory_.identity());
+ session_map_[session->id()] = session;
+ session->SignalRequestSignaling.connect(
+ this, &SessionManager::OnRequestSignaling);
+ session->SignalOutgoingMessage.connect(
+ this, &SessionManager::OnOutgoingMessage);
+ session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage);
+ SignalSessionCreate(session, received_initiate);
+ session->client()->OnSessionCreate(session, received_initiate);
+ return session;
+}
+
+void SessionManager::DestroySession(Session* session) {
+ if (session != NULL) {
+ SessionMap::iterator it = session_map_.find(session->id());
+ if (it != session_map_.end()) {
+ SignalSessionDestroy(session);
+ session->client()->OnSessionDestroy(session);
+ session_map_.erase(it);
+ delete session;
+ }
+ }
+}
+
+Session* SessionManager::GetSession(const std::string& sid) {
+ SessionMap::iterator it = session_map_.find(sid);
+ if (it != session_map_.end())
+ return it->second;
+ return NULL;
+}
+
+void SessionManager::TerminateAll() {
+ while (session_map_.begin() != session_map_.end()) {
+ Session* session = session_map_.begin()->second;
+ session->Terminate();
+ }
+}
+
+bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) {
+ return cricket::IsSessionMessage(stanza);
+}
+
+Session* SessionManager::FindSession(const std::string& sid,
+ const std::string& remote_name) {
+ SessionMap::iterator iter = session_map_.find(sid);
+ if (iter == session_map_.end())
+ return NULL;
+
+ Session* session = iter->second;
+ if (buzz::Jid(remote_name) != buzz::Jid(session->remote_name()))
+ return NULL;
+
+ return session;
+}
+
+void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) {
+ SessionMessage msg;
+ ParseError error;
+
+ if (!ParseSessionMessage(stanza, &msg, &error)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ error.text, NULL);
+ return;
+ }
+
+ Session* session = FindSession(msg.sid, msg.from);
+ if (session) {
+ session->OnIncomingMessage(msg);
+ return;
+ }
+ if (msg.type != ACTION_SESSION_INITIATE) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ "unknown session", NULL);
+ return;
+ }
+
+ std::string content_type;
+ if (!ParseContentType(msg.protocol, msg.action_elem,
+ &content_type, &error)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ error.text, NULL);
+ return;
+ }
+
+ if (!GetClient(content_type)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ "unknown content type: " + content_type, NULL);
+ return;
+ }
+
+ session = CreateSession(msg.to, msg.initiator, msg.sid,
+ content_type, true);
+ session->OnIncomingMessage(msg);
+}
+
+void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza) {
+ if (orig_stanza == NULL || response_stanza == NULL) {
+ return;
+ }
+
+ SessionMessage msg;
+ ParseError error;
+ if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+ LOG(LS_WARNING) << "Error parsing incoming response: " << error.text
+ << ":" << orig_stanza;
+ return;
+ }
+
+ Session* session = FindSession(msg.sid, msg.to);
+ if (!session) {
+ // Also try the QN_FROM in the response stanza, in case we sent the request
+ // to a bare JID but got the response from a full JID.
+ std::string ack_from = response_stanza->Attr(buzz::QN_FROM);
+ session = FindSession(msg.sid, ack_from);
+ }
+ if (session) {
+ session->OnIncomingResponse(orig_stanza, response_stanza, msg);
+ }
+}
+
+void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza) {
+ SessionMessage msg;
+ ParseError error;
+ if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+ return; // TODO: log somewhere?
+ }
+
+ Session* session = FindSession(msg.sid, msg.to);
+ if (session) {
+ rtc::scoped_ptr<buzz::XmlElement> synthetic_error;
+ if (!error_stanza) {
+ // A failed send is semantically equivalent to an error response, so we
+ // can just turn the former into the latter.
+ synthetic_error.reset(
+ CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND,
+ "cancel", "Recipient did not respond", NULL));
+ error_stanza = synthetic_error.get();
+ }
+
+ session->OnFailedSend(orig_stanza, error_stanza);
+ }
+}
+
+void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ rtc::scoped_ptr<buzz::XmlElement> msg(
+ CreateErrorMessage(stanza, name, type, text, extra_info));
+ SignalOutgoingMessage(this, msg.get());
+}
+
+buzz::XmlElement* SessionManager::CreateErrorMessage(
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ);
+ iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM));
+ iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+ iq->SetAttr(buzz::QN_TYPE, "error");
+
+ CopyXmlChildren(stanza, iq);
+
+ buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR);
+ error->SetAttr(buzz::QN_TYPE, type);
+ iq->AddElement(error);
+
+ // If the error name is not in the standard namespace, we have to first add
+ // some error from that namespace.
+ if (name.Namespace() != buzz::NS_STANZA) {
+ error->AddElement(
+ new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION));
+ }
+ error->AddElement(new buzz::XmlElement(name));
+
+ if (extra_info)
+ error->AddElement(new buzz::XmlElement(*extra_info));
+
+ if (text.size() > 0) {
+ // It's okay to always use English here. This text is for debugging
+ // purposes only.
+ buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT);
+ text_elem->SetAttr(buzz::QN_XML_LANG, "en");
+ text_elem->SetBodyText(text);
+ error->AddElement(text_elem);
+ }
+
+ // TODO: Should we include error codes as well for SIP compatibility?
+
+ return iq;
+}
+
+void SessionManager::OnOutgoingMessage(Session* session,
+ const buzz::XmlElement* stanza) {
+ SignalOutgoingMessage(this, stanza);
+}
+
+void SessionManager::OnErrorMessage(BaseSession* session,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ SendErrorMessage(stanza, name, type, text, extra_info);
+}
+
+void SessionManager::OnSignalingReady() {
+ for (SessionMap::iterator it = session_map_.begin();
+ it != session_map_.end();
+ ++it) {
+ it->second->OnSignalingReady();
+ }
+}
+
+void SessionManager::OnRequestSignaling(Session* session) {
+ SignalRequestSignaling();
+}
+
+} // namespace cricket
diff --git a/p2p/base/sessionmanager.h b/p2p/base/sessionmanager.h
new file mode 100644
index 00000000..74ee5c07
--- /dev/null
+++ b/p2p/base/sessionmanager.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSIONMANAGER_H_
+#define WEBRTC_P2P_BASE_SESSIONMANAGER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/transportdescriptionfactory.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/thread.h"
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+class Session;
+class BaseSession;
+class SessionClient;
+
+// SessionManager manages session instances.
+class SessionManager : public sigslot::has_slots<> {
+ public:
+ SessionManager(PortAllocator *allocator,
+ rtc::Thread *worker_thread = NULL);
+ virtual ~SessionManager();
+
+ PortAllocator *port_allocator() const { return allocator_; }
+ rtc::Thread *worker_thread() const { return worker_thread_; }
+ rtc::Thread *signaling_thread() const { return signaling_thread_; }
+
+ int session_timeout() const { return timeout_; }
+ void set_session_timeout(int timeout) { timeout_ = timeout; }
+
+ // Set what transport protocol we want to default to.
+ void set_transport_protocol(TransportProtocol proto) {
+ transport_desc_factory_.set_protocol(proto);
+ }
+
+ // Control use of DTLS. An identity must be supplied if DTLS is enabled.
+ void set_secure(SecurePolicy policy) {
+ transport_desc_factory_.set_secure(policy);
+ }
+ void set_identity(rtc::SSLIdentity* identity) {
+ transport_desc_factory_.set_identity(identity);
+ }
+ const TransportDescriptionFactory* transport_desc_factory() const {
+ return &transport_desc_factory_;
+ }
+
+ // Registers support for the given client. If we receive an initiate
+ // describing a session of the given type, we will automatically create a
+ // Session object and notify this client. The client may then accept or
+ // reject the session.
+ void AddClient(const std::string& content_type, SessionClient* client);
+ void RemoveClient(const std::string& content_type);
+ SessionClient* GetClient(const std::string& content_type);
+
+ // Creates a new session. The given name is the JID of the client on whose
+ // behalf we initiate the session.
+ Session *CreateSession(const std::string& local_name,
+ const std::string& content_type);
+
+ Session *CreateSession(const std::string& id,
+ const std::string& local_name,
+ const std::string& content_type);
+
+ // Destroys the given session.
+ void DestroySession(Session *session);
+
+ // Returns the session with the given ID or NULL if none exists.
+ Session *GetSession(const std::string& sid);
+
+ // Terminates all of the sessions created by this manager.
+ void TerminateAll();
+
+ // These are signaled whenever the set of existing sessions changes.
+ sigslot::signal2<Session *, bool> SignalSessionCreate;
+ sigslot::signal1<Session *> SignalSessionDestroy;
+
+ // Determines whether the given stanza is intended for some session.
+ bool IsSessionMessage(const buzz::XmlElement* stanza);
+
+ // Given a sid, initiator, and remote_name, this finds the matching Session
+ Session* FindSession(const std::string& sid,
+ const std::string& remote_name);
+
+ // Called when we receive a stanza for which IsSessionMessage is true.
+ void OnIncomingMessage(const buzz::XmlElement* stanza);
+
+ // Called when we get a response to a message that we sent.
+ void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza);
+
+ // Called if an attempted to send times out or an error is returned. In the
+ // timeout case error_stanza will be NULL
+ void OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza);
+
+ // Signalled each time a session generates a signaling message to send.
+ // Also signalled on errors, but with a NULL session.
+ sigslot::signal2<SessionManager*,
+ const buzz::XmlElement*> SignalOutgoingMessage;
+
+ // Signaled before sessions try to send certain signaling messages. The
+ // client should call OnSignalingReady once it is safe to send them. These
+ // steps are taken so that we don't send signaling messages trying to
+ // re-establish the connectivity of a session when the client cannot send
+ // the messages (and would probably just drop them on the floor).
+ //
+ // Note: you can connect this directly to OnSignalingReady(), if a signalling
+ // check is not supported.
+ sigslot::signal0<> SignalRequestSignaling;
+ void OnSignalingReady();
+
+ // Signaled when this SessionManager is deleted.
+ sigslot::signal0<> SignalDestroyed;
+
+ private:
+ typedef std::map<std::string, Session*> SessionMap;
+ typedef std::map<std::string, SessionClient*> ClientMap;
+
+ // Helper function for CreateSession. This is also invoked when we receive
+ // a message attempting to initiate a session with this client.
+ Session *CreateSession(const std::string& local_name,
+ const std::string& initiator,
+ const std::string& sid,
+ const std::string& content_type,
+ bool received_initiate);
+
+ // Attempts to find a registered session type whose description appears as
+ // a child of the session element. Such a child should be present indicating
+ // the application they hope to initiate.
+ std::string FindClient(const buzz::XmlElement* session);
+
+ // Sends a message back to the other client indicating that we found an error
+ // in the stanza they sent. name identifies the error, type is one of the
+ // standard XMPP types (cancel, continue, modify, auth, wait), and text is a
+ // description for debugging purposes.
+ void SendErrorMessage(const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ // Creates and returns an error message from the given components. The
+ // caller is responsible for deleting this.
+ buzz::XmlElement* CreateErrorMessage(
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ // Called each time a session requests signaling.
+ void OnRequestSignaling(Session* session);
+
+ // Called each time a session has an outgoing message.
+ void OnOutgoingMessage(Session* session, const buzz::XmlElement* stanza);
+
+ // Called each time a session has an error to send.
+ void OnErrorMessage(BaseSession* session,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ PortAllocator *allocator_;
+ rtc::Thread *signaling_thread_;
+ rtc::Thread *worker_thread_;
+ int timeout_;
+ TransportDescriptionFactory transport_desc_factory_;
+ SessionMap session_map_;
+ ClientMap client_map_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSIONMANAGER_H_
diff --git a/p2p/base/sessionmessages.cc b/p2p/base/sessionmessages.cc
new file mode 100644
index 00000000..cc63673f
--- /dev/null
+++ b/p2p/base/sessionmessages.cc
@@ -0,0 +1,1132 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/sessionmessages.h"
+
+#include <stdio.h>
+#include <string>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/p2ptransport.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/libjingle/xmllite/xmlconstants.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stringutils.h"
+
+namespace cricket {
+
+ActionType ToActionType(const std::string& type) {
+ if (type == GINGLE_ACTION_INITIATE)
+ return ACTION_SESSION_INITIATE;
+ if (type == GINGLE_ACTION_INFO)
+ return ACTION_SESSION_INFO;
+ if (type == GINGLE_ACTION_ACCEPT)
+ return ACTION_SESSION_ACCEPT;
+ if (type == GINGLE_ACTION_REJECT)
+ return ACTION_SESSION_REJECT;
+ if (type == GINGLE_ACTION_TERMINATE)
+ return ACTION_SESSION_TERMINATE;
+ if (type == GINGLE_ACTION_CANDIDATES)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_SESSION_INITIATE)
+ return ACTION_SESSION_INITIATE;
+ if (type == JINGLE_ACTION_TRANSPORT_INFO)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+ return ACTION_TRANSPORT_ACCEPT;
+ if (type == JINGLE_ACTION_SESSION_INFO)
+ return ACTION_SESSION_INFO;
+ if (type == JINGLE_ACTION_SESSION_ACCEPT)
+ return ACTION_SESSION_ACCEPT;
+ if (type == JINGLE_ACTION_SESSION_TERMINATE)
+ return ACTION_SESSION_TERMINATE;
+ if (type == JINGLE_ACTION_TRANSPORT_INFO)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+ return ACTION_TRANSPORT_ACCEPT;
+ if (type == JINGLE_ACTION_DESCRIPTION_INFO)
+ return ACTION_DESCRIPTION_INFO;
+ if (type == GINGLE_ACTION_UPDATE)
+ return ACTION_DESCRIPTION_INFO;
+
+ return ACTION_UNKNOWN;
+}
+
+std::string ToJingleString(ActionType type) {
+ switch (type) {
+ case ACTION_SESSION_INITIATE:
+ return JINGLE_ACTION_SESSION_INITIATE;
+ case ACTION_SESSION_INFO:
+ return JINGLE_ACTION_SESSION_INFO;
+ case ACTION_DESCRIPTION_INFO:
+ return JINGLE_ACTION_DESCRIPTION_INFO;
+ case ACTION_SESSION_ACCEPT:
+ return JINGLE_ACTION_SESSION_ACCEPT;
+ // Notice that reject and terminate both go to
+ // "session-terminate", but there is no "session-reject".
+ case ACTION_SESSION_REJECT:
+ case ACTION_SESSION_TERMINATE:
+ return JINGLE_ACTION_SESSION_TERMINATE;
+ case ACTION_TRANSPORT_INFO:
+ return JINGLE_ACTION_TRANSPORT_INFO;
+ case ACTION_TRANSPORT_ACCEPT:
+ return JINGLE_ACTION_TRANSPORT_ACCEPT;
+ default:
+ return "";
+ }
+}
+
+std::string ToGingleString(ActionType type) {
+ switch (type) {
+ case ACTION_SESSION_INITIATE:
+ return GINGLE_ACTION_INITIATE;
+ case ACTION_SESSION_INFO:
+ return GINGLE_ACTION_INFO;
+ case ACTION_SESSION_ACCEPT:
+ return GINGLE_ACTION_ACCEPT;
+ case ACTION_SESSION_REJECT:
+ return GINGLE_ACTION_REJECT;
+ case ACTION_SESSION_TERMINATE:
+ return GINGLE_ACTION_TERMINATE;
+ case ACTION_TRANSPORT_INFO:
+ return GINGLE_ACTION_CANDIDATES;
+ default:
+ return "";
+ }
+}
+
+
+bool IsJingleMessage(const buzz::XmlElement* stanza) {
+ const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+ if (jingle == NULL)
+ return false;
+
+ return (jingle->HasAttr(buzz::QN_ACTION) && jingle->HasAttr(QN_SID));
+}
+
+bool IsGingleMessage(const buzz::XmlElement* stanza) {
+ const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+ if (session == NULL)
+ return false;
+
+ return (session->HasAttr(buzz::QN_TYPE) &&
+ session->HasAttr(buzz::QN_ID) &&
+ session->HasAttr(QN_INITIATOR));
+}
+
+bool IsSessionMessage(const buzz::XmlElement* stanza) {
+ return (stanza->Name() == buzz::QN_IQ &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET &&
+ (IsJingleMessage(stanza) ||
+ IsGingleMessage(stanza)));
+}
+
+bool ParseGingleSessionMessage(const buzz::XmlElement* session,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->protocol = PROTOCOL_GINGLE;
+ std::string type_string = session->Attr(buzz::QN_TYPE);
+ msg->type = ToActionType(type_string);
+ msg->sid = session->Attr(buzz::QN_ID);
+ msg->initiator = session->Attr(QN_INITIATOR);
+ msg->action_elem = session;
+
+ if (msg->type == ACTION_UNKNOWN)
+ return BadParse("unknown action: " + type_string, error);
+
+ return true;
+}
+
+bool ParseJingleSessionMessage(const buzz::XmlElement* jingle,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->protocol = PROTOCOL_JINGLE;
+ std::string type_string = jingle->Attr(buzz::QN_ACTION);
+ msg->type = ToActionType(type_string);
+ msg->sid = jingle->Attr(QN_SID);
+ msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY);
+ msg->action_elem = jingle;
+
+ if (msg->type == ACTION_UNKNOWN)
+ return BadParse("unknown action: " + type_string, error);
+
+ return true;
+}
+
+bool ParseHybridSessionMessage(const buzz::XmlElement* jingle,
+ SessionMessage* msg,
+ ParseError* error) {
+ if (!ParseJingleSessionMessage(jingle, msg, error))
+ return false;
+ msg->protocol = PROTOCOL_HYBRID;
+
+ return true;
+}
+
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->id = stanza->Attr(buzz::QN_ID);
+ msg->from = stanza->Attr(buzz::QN_FROM);
+ msg->to = stanza->Attr(buzz::QN_TO);
+ msg->stanza = stanza;
+
+ const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+ const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+ if (jingle && session)
+ return ParseHybridSessionMessage(jingle, msg, error);
+ if (jingle != NULL)
+ return ParseJingleSessionMessage(jingle, msg, error);
+ if (session != NULL)
+ return ParseGingleSessionMessage(session, msg, error);
+ return false;
+}
+
+buzz::XmlElement* WriteGingleAction(const SessionMessage& msg,
+ const XmlElements& action_elems) {
+ buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true);
+ session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type));
+ session->AddAttr(buzz::QN_ID, msg.sid);
+ session->AddAttr(QN_INITIATOR, msg.initiator);
+ AddXmlChildren(session, action_elems);
+ return session;
+}
+
+buzz::XmlElement* WriteJingleAction(const SessionMessage& msg,
+ const XmlElements& action_elems) {
+ buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true);
+ jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type));
+ jingle->AddAttr(QN_SID, msg.sid);
+ if (msg.type == ACTION_SESSION_INITIATE) {
+ jingle->AddAttr(QN_INITIATOR, msg.initiator);
+ }
+ AddXmlChildren(jingle, action_elems);
+ return jingle;
+}
+
+void WriteSessionMessage(const SessionMessage& msg,
+ const XmlElements& action_elems,
+ buzz::XmlElement* stanza) {
+ stanza->SetAttr(buzz::QN_TO, msg.to);
+ stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET);
+
+ if (msg.protocol == PROTOCOL_GINGLE) {
+ stanza->AddElement(WriteGingleAction(msg, action_elems));
+ } else {
+ stanza->AddElement(WriteJingleAction(msg, action_elems));
+ }
+}
+
+
+TransportParser* GetTransportParser(const TransportParserMap& trans_parsers,
+ const std::string& transport_type) {
+ TransportParserMap::const_iterator map = trans_parsers.find(transport_type);
+ if (map == trans_parsers.end()) {
+ return NULL;
+ } else {
+ return map->second;
+ }
+}
+
+CandidateTranslator* GetCandidateTranslator(
+ const CandidateTranslatorMap& translators,
+ const std::string& content_name) {
+ CandidateTranslatorMap::const_iterator map = translators.find(content_name);
+ if (map == translators.end()) {
+ return NULL;
+ } else {
+ return map->second;
+ }
+}
+
+bool GetParserAndTranslator(const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ const std::string& transport_type,
+ const std::string& content_name,
+ TransportParser** parser,
+ CandidateTranslator** translator,
+ ParseError* error) {
+ *parser = GetTransportParser(trans_parsers, transport_type);
+ if (*parser == NULL) {
+ return BadParse("unknown transport type: " + transport_type, error);
+ }
+ // Not having a translator isn't fatal when parsing. If this is called for an
+ // initiate message, we won't have our proxies set up to do the translation.
+ // Fortunately, for the cases where translation is needed, candidates are
+ // never sent in initiates.
+ *translator = GetCandidateTranslator(translators, content_name);
+ return true;
+}
+
+bool GetParserAndTranslator(const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ const std::string& transport_type,
+ const std::string& content_name,
+ TransportParser** parser,
+ CandidateTranslator** translator,
+ WriteError* error) {
+ *parser = GetTransportParser(trans_parsers, transport_type);
+ if (*parser == NULL) {
+ return BadWrite("unknown transport type: " + transport_type, error);
+ }
+ *translator = GetCandidateTranslator(translators, content_name);
+ if (*translator == NULL) {
+ return BadWrite("unknown content name: " + content_name, error);
+ }
+ return true;
+}
+
+bool ParseGingleCandidate(const buzz::XmlElement* candidate_elem,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ const std::string& content_name,
+ Candidates* candidates,
+ ParseError* error) {
+ TransportParser* trans_parser;
+ CandidateTranslator* translator;
+ if (!GetParserAndTranslator(trans_parsers, translators,
+ NS_GINGLE_P2P, content_name,
+ &trans_parser, &translator, error))
+ return false;
+
+ Candidate candidate;
+ if (!trans_parser->ParseGingleCandidate(
+ candidate_elem, translator, &candidate, error)) {
+ return false;
+ }
+
+ candidates->push_back(candidate);
+ return true;
+}
+
+bool ParseGingleCandidates(const buzz::XmlElement* parent,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ const std::string& content_name,
+ Candidates* candidates,
+ ParseError* error) {
+ for (const buzz::XmlElement* candidate_elem = parent->FirstElement();
+ candidate_elem != NULL;
+ candidate_elem = candidate_elem->NextElement()) {
+ if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+ if (!ParseGingleCandidate(candidate_elem, trans_parsers, translators,
+ content_name, candidates, error)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL;
+ bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL;
+
+ // If we don't have media, no need to separate the candidates.
+ if (!has_audio && !has_video) {
+ TransportInfo tinfo(CN_OTHER,
+ TransportDescription(NS_GINGLE_P2P, std::string(), std::string()));
+ if (!ParseGingleCandidates(action_elem, trans_parsers, translators,
+ CN_OTHER, &tinfo.description.candidates,
+ error)) {
+ return false;
+ }
+
+ tinfos->push_back(tinfo);
+ return true;
+ }
+
+ // If we have media, separate the candidates.
+ TransportInfo audio_tinfo(
+ CN_AUDIO,
+ TransportDescription(NS_GINGLE_P2P, std::string(), std::string()));
+ TransportInfo video_tinfo(
+ CN_VIDEO,
+ TransportDescription(NS_GINGLE_P2P, std::string(), std::string()));
+ for (const buzz::XmlElement* candidate_elem = action_elem->FirstElement();
+ candidate_elem != NULL;
+ candidate_elem = candidate_elem->NextElement()) {
+ if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+ const std::string& channel_name = candidate_elem->Attr(buzz::QN_NAME);
+ if (has_audio &&
+ (channel_name == GICE_CHANNEL_NAME_RTP ||
+ channel_name == GICE_CHANNEL_NAME_RTCP)) {
+ if (!ParseGingleCandidate(
+ candidate_elem, trans_parsers,
+ translators, CN_AUDIO,
+ &audio_tinfo.description.candidates, error)) {
+ return false;
+ }
+ } else if (has_video &&
+ (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
+ channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP)) {
+ if (!ParseGingleCandidate(
+ candidate_elem, trans_parsers,
+ translators, CN_VIDEO,
+ &video_tinfo.description.candidates, error)) {
+ return false;
+ }
+ } else {
+ return BadParse("Unknown channel name: " + channel_name, error);
+ }
+ }
+ }
+
+ if (has_audio) {
+ tinfos->push_back(audio_tinfo);
+ }
+ if (has_video) {
+ tinfos->push_back(video_tinfo);
+ }
+ return true;
+}
+
+bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem,
+ const std::string& content_name,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ TransportInfo* tinfo,
+ ParseError* error) {
+ TransportParser* trans_parser;
+ CandidateTranslator* translator;
+ if (!GetParserAndTranslator(trans_parsers, translators,
+ trans_elem->Name().Namespace(), content_name,
+ &trans_parser, &translator, error))
+ return false;
+
+ TransportDescription tdesc;
+ if (!trans_parser->ParseTransportDescription(trans_elem, translator,
+ &tdesc, error))
+ return false;
+
+ *tinfo = TransportInfo(content_name, tdesc);
+ return true;
+}
+
+bool ParseJingleTransportInfos(const buzz::XmlElement* jingle,
+ const ContentInfos& contents,
+ const TransportParserMap trans_parsers,
+ const CandidateTranslatorMap& translators,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ for (const buzz::XmlElement* pair_elem
+ = jingle->FirstNamed(QN_JINGLE_CONTENT);
+ pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_name;
+ if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+ &content_name, error))
+ return false;
+
+ const ContentInfo* content = FindContentInfoByName(contents, content_name);
+ if (!content)
+ return BadParse("Unknown content name: " + content_name, error);
+
+ const buzz::XmlElement* trans_elem;
+ if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error))
+ return false;
+
+ TransportInfo tinfo;
+ if (!ParseJingleTransportInfo(trans_elem, content->name,
+ trans_parsers, translators,
+ &tinfo, error))
+ return false;
+
+ tinfos->push_back(tinfo);
+ }
+
+ return true;
+}
+
+buzz::XmlElement* NewTransportElement(const std::string& name) {
+ return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
+}
+
+bool WriteGingleCandidates(const Candidates& candidates,
+ const TransportParserMap& trans_parsers,
+ const std::string& transport_type,
+ const CandidateTranslatorMap& translators,
+ const std::string& content_name,
+ XmlElements* elems,
+ WriteError* error) {
+ TransportParser* trans_parser;
+ CandidateTranslator* translator;
+ if (!GetParserAndTranslator(trans_parsers, translators,
+ transport_type, content_name,
+ &trans_parser, &translator, error))
+ return false;
+
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ rtc::scoped_ptr<buzz::XmlElement> element;
+ if (!trans_parser->WriteGingleCandidate(candidates[i], translator,
+ element.accept(), error)) {
+ return false;
+ }
+
+ elems->push_back(element.release());
+ }
+
+ return true;
+}
+
+bool WriteGingleTransportInfos(const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (!WriteGingleCandidates(tinfo->description.candidates,
+ trans_parsers, tinfo->description.transport_type,
+ translators, tinfo->content_name,
+ elems, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool WriteJingleTransportInfo(const TransportInfo& tinfo,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error) {
+ std::string transport_type = tinfo.description.transport_type;
+ TransportParser* trans_parser;
+ CandidateTranslator* translator;
+ if (!GetParserAndTranslator(trans_parsers, translators,
+ transport_type, tinfo.content_name,
+ &trans_parser, &translator, error))
+ return false;
+
+ buzz::XmlElement* trans_elem;
+ if (!trans_parser->WriteTransportDescription(tinfo.description, translator,
+ &trans_elem, error)) {
+ return false;
+ }
+
+ elems->push_back(trans_elem);
+ return true;
+}
+
+void WriteJingleContent(const std::string name,
+ const XmlElements& child_elems,
+ XmlElements* elems) {
+ buzz::XmlElement* content_elem = new buzz::XmlElement(QN_JINGLE_CONTENT);
+ content_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name);
+ content_elem->SetAttr(QN_CREATOR, LN_INITIATOR);
+ AddXmlChildren(content_elem, child_elems);
+
+ elems->push_back(content_elem);
+}
+
+bool WriteJingleTransportInfos(const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ XmlElements content_child_elems;
+ if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators,
+ &content_child_elems, error))
+
+ return false;
+
+ WriteJingleContent(tinfo->content_name, content_child_elems, elems);
+ }
+
+ return true;
+}
+
+ContentParser* GetContentParser(const ContentParserMap& content_parsers,
+ const std::string& type) {
+ ContentParserMap::const_iterator map = content_parsers.find(type);
+ if (map == content_parsers.end()) {
+ return NULL;
+ } else {
+ return map->second;
+ }
+}
+
+bool ParseContentInfo(SignalingProtocol protocol,
+ const std::string& name,
+ const std::string& type,
+ const buzz::XmlElement* elem,
+ const ContentParserMap& parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ ContentParser* parser = GetContentParser(parsers, type);
+ if (parser == NULL)
+ return BadParse("unknown application content: " + type, error);
+
+ ContentDescription* desc;
+ if (!parser->ParseContent(protocol, elem, &desc, error))
+ return false;
+
+ contents->push_back(ContentInfo(name, type, desc));
+ return true;
+}
+
+bool ParseContentType(const buzz::XmlElement* parent_elem,
+ std::string* content_type,
+ const buzz::XmlElement** content_elem,
+ ParseError* error) {
+ if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error))
+ return false;
+
+ *content_type = (*content_elem)->Name().Namespace();
+ return true;
+}
+
+bool ParseGingleContentInfos(const buzz::XmlElement* session,
+ const ContentParserMap& content_parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ std::string content_type;
+ const buzz::XmlElement* content_elem;
+ if (!ParseContentType(session, &content_type, &content_elem, error))
+ return false;
+
+ if (content_type == NS_GINGLE_VIDEO) {
+ // A parser parsing audio or video content should look at the
+ // namespace and only parse the codecs relevant to that namespace.
+ // We use this to control which codecs get parsed: first audio,
+ // then video.
+ rtc::scoped_ptr<buzz::XmlElement> audio_elem(
+ new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT));
+ CopyXmlChildren(content_elem, audio_elem.get());
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+ audio_elem.get(), content_parsers,
+ contents, error))
+ return false;
+
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ } else if (content_type == NS_GINGLE_AUDIO) {
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ } else {
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ }
+ return true;
+}
+
+bool ParseJingleContentInfos(const buzz::XmlElement* jingle,
+ const ContentParserMap& content_parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ for (const buzz::XmlElement* pair_elem
+ = jingle->FirstNamed(QN_JINGLE_CONTENT);
+ pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_name;
+ if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+ &content_name, error))
+ return false;
+
+ std::string content_type;
+ const buzz::XmlElement* content_elem;
+ if (!ParseContentType(pair_elem, &content_type, &content_elem, error))
+ return false;
+
+ if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ }
+ return true;
+}
+
+bool ParseJingleGroupInfos(const buzz::XmlElement* jingle,
+ ContentGroups* groups,
+ ParseError* error) {
+ for (const buzz::XmlElement* pair_elem
+ = jingle->FirstNamed(QN_JINGLE_DRAFT_GROUP);
+ pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_DRAFT_GROUP)) {
+ std::string group_name;
+ if (!RequireXmlAttr(pair_elem, QN_JINGLE_DRAFT_GROUP_TYPE,
+ &group_name, error))
+ return false;
+
+ ContentGroup group(group_name);
+ for (const buzz::XmlElement* child_elem
+ = pair_elem->FirstNamed(QN_JINGLE_CONTENT);
+ child_elem != NULL;
+ child_elem = child_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_name;
+ if (!RequireXmlAttr(child_elem, QN_JINGLE_CONTENT_NAME,
+ &content_name, error))
+ return false;
+ group.AddContentName(content_name);
+ }
+ groups->push_back(group);
+ }
+ return true;
+}
+
+buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol,
+ const ContentInfo& content,
+ const ContentParserMap& parsers,
+ WriteError* error) {
+ ContentParser* parser = GetContentParser(parsers, content.type);
+ if (parser == NULL) {
+ BadWrite("unknown content type: " + content.type, error);
+ return NULL;
+ }
+
+ buzz::XmlElement* elem = NULL;
+ if (!parser->WriteContent(protocol, content.description, &elem, error))
+ return NULL;
+
+ return elem;
+}
+
+bool IsWritable(SignalingProtocol protocol,
+ const ContentInfo& content,
+ const ContentParserMap& parsers) {
+ ContentParser* parser = GetContentParser(parsers, content.type);
+ if (parser == NULL) {
+ return false;
+ }
+
+ return parser->IsWritable(protocol, content.description);
+}
+
+bool WriteGingleContentInfos(const ContentInfos& contents,
+ const ContentParserMap& parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ if (contents.size() == 1 ||
+ (contents.size() == 2 &&
+ !IsWritable(PROTOCOL_GINGLE, contents.at(1), parsers))) {
+ if (contents.front().rejected) {
+ return BadWrite("Gingle protocol may not reject individual contents.",
+ error);
+ }
+ buzz::XmlElement* elem = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.front(), parsers, error);
+ if (!elem)
+ return false;
+
+ elems->push_back(elem);
+ } else if (contents.size() >= 2 &&
+ contents.at(0).type == NS_JINGLE_RTP &&
+ contents.at(1).type == NS_JINGLE_RTP) {
+ // Special-case audio + video contents so that they are "merged"
+ // into one "video" content.
+ if (contents.at(0).rejected || contents.at(1).rejected) {
+ return BadWrite("Gingle protocol may not reject individual contents.",
+ error);
+ }
+ buzz::XmlElement* audio = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.at(0), parsers, error);
+ if (!audio)
+ return false;
+
+ buzz::XmlElement* video = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.at(1), parsers, error);
+ if (!video) {
+ delete audio;
+ return false;
+ }
+
+ CopyXmlChildren(audio, video);
+ elems->push_back(video);
+ delete audio;
+ } else {
+ return BadWrite("Gingle protocol may only have one content.", error);
+ }
+
+ return true;
+}
+
+const TransportInfo* GetTransportInfoByContentName(
+ const TransportInfos& tinfos, const std::string& content_name) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (content_name == tinfo->content_name) {
+ return &*tinfo;
+ }
+ }
+ return NULL;
+}
+
+bool WriteJingleContents(const ContentInfos& contents,
+ const ContentParserMap& content_parsers,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ if (content->rejected) {
+ continue;
+ }
+ const TransportInfo* tinfo =
+ GetTransportInfoByContentName(tinfos, content->name);
+ if (!tinfo)
+ return BadWrite("No transport for content: " + content->name, error);
+
+ XmlElements pair_elems;
+ buzz::XmlElement* elem = WriteContentInfo(
+ PROTOCOL_JINGLE, *content, content_parsers, error);
+ if (!elem)
+ return false;
+ pair_elems.push_back(elem);
+
+ if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators,
+ &pair_elems, error))
+ return false;
+
+ WriteJingleContent(content->name, pair_elems, elems);
+ }
+ return true;
+}
+
+bool WriteJingleContentInfos(const ContentInfos& contents,
+ const ContentParserMap& content_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ if (content->rejected) {
+ continue;
+ }
+ XmlElements content_child_elems;
+ buzz::XmlElement* elem = WriteContentInfo(
+ PROTOCOL_JINGLE, *content, content_parsers, error);
+ if (!elem)
+ return false;
+ content_child_elems.push_back(elem);
+ WriteJingleContent(content->name, content_child_elems, elems);
+ }
+ return true;
+}
+
+bool WriteJingleGroupInfo(const ContentInfos& contents,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error) {
+ if (!groups.empty()) {
+ buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_GROUP);
+ pair_elem->SetAttr(QN_JINGLE_DRAFT_GROUP_TYPE, GROUP_TYPE_BUNDLE);
+
+ XmlElements pair_elems;
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ buzz::XmlElement* child_elem =
+ new buzz::XmlElement(QN_JINGLE_CONTENT, false);
+ child_elem->SetAttr(QN_JINGLE_CONTENT_NAME, content->name);
+ pair_elems.push_back(child_elem);
+ }
+ AddXmlChildren(pair_elem, pair_elems);
+ elems->push_back(pair_elem);
+ }
+ return true;
+}
+
+bool ParseContentType(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ std::string* content_type,
+ ParseError* error) {
+ const buzz::XmlElement* content_elem;
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!ParseContentType(action_elem, content_type, &content_elem, error))
+ return false;
+
+ // Internally, we only use NS_JINGLE_RTP.
+ if (*content_type == NS_GINGLE_AUDIO ||
+ *content_type == NS_GINGLE_VIDEO)
+ *content_type = NS_JINGLE_RTP;
+ } else {
+ const buzz::XmlElement* pair_elem
+ = action_elem->FirstNamed(QN_JINGLE_CONTENT);
+ if (pair_elem == NULL)
+ return BadParse("No contents found", error);
+
+ if (!ParseContentType(pair_elem, content_type, &content_elem, error))
+ return false;
+ }
+
+ return true;
+}
+
+static bool ParseContentMessage(
+ SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ bool expect_transports,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ SessionInitiate* init,
+ ParseError* error) {
+ init->owns_contents = true;
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!ParseGingleContentInfos(action_elem, content_parsers,
+ &init->contents, error))
+ return false;
+
+ if (expect_transports &&
+ !ParseGingleTransportInfos(action_elem, init->contents,
+ trans_parsers, translators,
+ &init->transports, error))
+ return false;
+ } else {
+ if (!ParseJingleContentInfos(action_elem, content_parsers,
+ &init->contents, error))
+ return false;
+ if (!ParseJingleGroupInfos(action_elem, &init->groups, error))
+ return false;
+
+ if (expect_transports &&
+ !ParseJingleTransportInfos(action_elem, init->contents,
+ trans_parsers, translators,
+ &init->transports, error))
+ return false;
+ }
+
+ return true;
+}
+
+static bool WriteContentMessage(
+ SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!WriteGingleContentInfos(contents, content_parsers, elems, error))
+ return false;
+
+ if (!WriteGingleTransportInfos(tinfos, transport_parsers, translators,
+ elems, error))
+ return false;
+ } else {
+ if (!WriteJingleContents(contents, content_parsers,
+ tinfos, transport_parsers, translators,
+ elems, error))
+ return false;
+ if (!WriteJingleGroupInfo(contents, groups, elems, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseSessionInitiate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ SessionInitiate* init,
+ ParseError* error) {
+ bool expect_transports = true;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
+ content_parsers, trans_parsers, translators,
+ init, error);
+}
+
+
+bool WriteSessionInitiate(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error) {
+ return WriteContentMessage(protocol, contents, tinfos,
+ content_parsers, transport_parsers, translators,
+ groups,
+ elems, error);
+}
+
+bool ParseSessionAccept(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ SessionAccept* accept,
+ ParseError* error) {
+ bool expect_transports = true;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
+ content_parsers, transport_parsers, translators,
+ accept, error);
+}
+
+bool WriteSessionAccept(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error) {
+ return WriteContentMessage(protocol, contents, tinfos,
+ content_parsers, transport_parsers, translators,
+ groups,
+ elems, error);
+}
+
+bool ParseSessionTerminate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ SessionTerminate* term,
+ ParseError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ const buzz::XmlElement* reason_elem = action_elem->FirstElement();
+ if (reason_elem != NULL) {
+ term->reason = reason_elem->Name().LocalPart();
+ const buzz::XmlElement *debug_elem = reason_elem->FirstElement();
+ if (debug_elem != NULL) {
+ term->debug_reason = debug_elem->Name().LocalPart();
+ }
+ }
+ return true;
+ } else {
+ const buzz::XmlElement* reason_elem =
+ action_elem->FirstNamed(QN_JINGLE_REASON);
+ if (reason_elem) {
+ reason_elem = reason_elem->FirstElement();
+ if (reason_elem) {
+ term->reason = reason_elem->Name().LocalPart();
+ }
+ }
+ return true;
+ }
+}
+
+void WriteSessionTerminate(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems) {
+ if (protocol == PROTOCOL_GINGLE) {
+ elems->push_back(new buzz::XmlElement(buzz::QName(NS_GINGLE, term.reason)));
+ } else {
+ if (!term.reason.empty()) {
+ buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON);
+ reason_elem->AddElement(new buzz::XmlElement(
+ buzz::QName(NS_JINGLE, term.reason)));
+ elems->push_back(reason_elem);
+ }
+ }
+}
+
+bool ParseDescriptionInfo(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ DescriptionInfo* description_info,
+ ParseError* error) {
+ bool expect_transports = false;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
+ content_parsers, transport_parsers, translators,
+ description_info, error);
+}
+
+bool WriteDescriptionInfo(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const ContentParserMap& content_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return WriteGingleContentInfos(contents, content_parsers, elems, error);
+ } else {
+ return WriteJingleContentInfos(contents, content_parsers, elems, error);
+ }
+}
+
+bool ParseTransportInfos(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return ParseGingleTransportInfos(
+ action_elem, contents, trans_parsers, translators, tinfos, error);
+ } else {
+ return ParseJingleTransportInfos(
+ action_elem, contents, trans_parsers, translators, tinfos, error);
+ }
+}
+
+bool WriteTransportInfos(SignalingProtocol protocol,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return WriteGingleTransportInfos(tinfos, trans_parsers, translators,
+ elems, error);
+ } else {
+ return WriteJingleTransportInfos(tinfos, trans_parsers, translators,
+ elems, error);
+ }
+}
+
+bool GetUriTarget(const std::string& prefix, const std::string& str,
+ std::string* after) {
+ size_t pos = str.find(prefix);
+ if (pos == std::string::npos)
+ return false;
+
+ *after = str.substr(pos + prefix.size(), std::string::npos);
+ return true;
+}
+
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+ SessionRedirect* redirect) {
+ const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR);
+ if (error_elem == NULL)
+ return false;
+
+ const buzz::XmlElement* redirect_elem =
+ error_elem->FirstNamed(QN_GINGLE_REDIRECT);
+ if (redirect_elem == NULL)
+ redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT);
+ if (redirect_elem == NULL)
+ return false;
+
+ if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(),
+ &redirect->target))
+ return false;
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/p2p/base/sessionmessages.h b/p2p/base/sessionmessages.h
new file mode 100644
index 00000000..7b156d49
--- /dev/null
+++ b/p2p/base/sessionmessages.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_SESSIONMESSAGES_H_
+#define WEBRTC_P2P_BASE_SESSIONMESSAGES_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/sessiondescription.h" // Needed to delete contents.
+#include "webrtc/p2p/base/transportinfo.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/base/basictypes.h"
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class Candidate;
+class ContentParser;
+class TransportParser;
+
+typedef std::vector<Candidate> Candidates;
+typedef std::map<std::string, ContentParser*> ContentParserMap;
+typedef std::map<std::string, TransportParser*> TransportParserMap;
+
+enum ActionType {
+ ACTION_UNKNOWN,
+
+ ACTION_SESSION_INITIATE,
+ ACTION_SESSION_INFO,
+ ACTION_SESSION_ACCEPT,
+ ACTION_SESSION_REJECT,
+ ACTION_SESSION_TERMINATE,
+
+ ACTION_TRANSPORT_INFO,
+ ACTION_TRANSPORT_ACCEPT,
+
+ ACTION_DESCRIPTION_INFO,
+};
+
+// Abstraction of a <jingle> element within an <iq> stanza, per XMPP
+// standard XEP-166. Can be serialized into multiple protocols,
+// including the standard (Jingle) and the draft standard (Gingle).
+// In general, used to communicate actions related to a p2p session,
+// such accept, initiate, terminate, etc.
+
+struct SessionMessage {
+ SessionMessage() : action_elem(NULL), stanza(NULL) {}
+
+ SessionMessage(SignalingProtocol protocol, ActionType type,
+ const std::string& sid, const std::string& initiator) :
+ protocol(protocol), type(type), sid(sid), initiator(initiator),
+ action_elem(NULL), stanza(NULL) {}
+
+ std::string id;
+ std::string from;
+ std::string to;
+ SignalingProtocol protocol;
+ ActionType type;
+ std::string sid; // session id
+ std::string initiator;
+
+ // Used for further parsing when necessary.
+ // Represents <session> or <jingle>.
+ const buzz::XmlElement* action_elem;
+ // Mostly used for debugging.
+ const buzz::XmlElement* stanza;
+};
+
+// TODO: Break up this class so we don't have to typedef it into
+// different classes.
+struct ContentMessage {
+ ContentMessage() : owns_contents(false) {}
+
+ ~ContentMessage() {
+ if (owns_contents) {
+ for (ContentInfos::iterator content = contents.begin();
+ content != contents.end(); content++) {
+ delete content->description;
+ }
+ }
+ }
+
+ // Caller takes ownership of contents.
+ ContentInfos ClearContents() {
+ ContentInfos out;
+ contents.swap(out);
+ owns_contents = false;
+ return out;
+ }
+
+ bool owns_contents;
+ ContentInfos contents;
+ TransportInfos transports;
+ ContentGroups groups;
+};
+
+typedef ContentMessage SessionInitiate;
+typedef ContentMessage SessionAccept;
+// Note that a DescriptionInfo does not have TransportInfos.
+typedef ContentMessage DescriptionInfo;
+
+struct SessionTerminate {
+ SessionTerminate() {}
+
+ explicit SessionTerminate(const std::string& reason) :
+ reason(reason) {}
+
+ std::string reason;
+ std::string debug_reason;
+};
+
+struct SessionRedirect {
+ std::string target;
+};
+
+// Used during parsing and writing to map component to channel name
+// and back. This is primarily for converting old G-ICE candidate
+// signalling to new ICE candidate classes.
+class CandidateTranslator {
+ public:
+ virtual bool GetChannelNameFromComponent(
+ int component, std::string* channel_name) const = 0;
+ virtual bool GetComponentFromChannelName(
+ const std::string& channel_name, int* component) const = 0;
+};
+
+// Content name => translator
+typedef std::map<std::string, CandidateTranslator*> CandidateTranslatorMap;
+
+bool IsSessionMessage(const buzz::XmlElement* stanza);
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+ SessionMessage* msg,
+ ParseError* error);
+// Will return an error if there is more than one content type.
+bool ParseContentType(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ std::string* content_type,
+ ParseError* error);
+void WriteSessionMessage(const SessionMessage& msg,
+ const XmlElements& action_elems,
+ buzz::XmlElement* stanza);
+bool ParseSessionInitiate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ SessionInitiate* init,
+ ParseError* error);
+bool WriteSessionInitiate(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error);
+bool ParseSessionAccept(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ SessionAccept* accept,
+ ParseError* error);
+bool WriteSessionAccept(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ const ContentGroups& groups,
+ XmlElements* elems,
+ WriteError* error);
+bool ParseSessionTerminate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ SessionTerminate* term,
+ ParseError* error);
+void WriteSessionTerminate(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems);
+bool ParseDescriptionInfo(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ const CandidateTranslatorMap& translators,
+ DescriptionInfo* description_info,
+ ParseError* error);
+bool WriteDescriptionInfo(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const ContentParserMap& content_parsers,
+ XmlElements* elems,
+ WriteError* error);
+// Since a TransportInfo is not a transport-info message, and a
+// transport-info message is just a collection of TransportInfos, we
+// say Parse/Write TransportInfos for transport-info messages.
+bool ParseTransportInfos(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ TransportInfos* tinfos,
+ ParseError* error);
+bool WriteTransportInfos(SignalingProtocol protocol,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ const CandidateTranslatorMap& translators,
+ XmlElements* elems,
+ WriteError* error);
+// Handles both Gingle and Jingle syntax.
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+ SessionRedirect* redirect);
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_SESSIONMESSAGES_H_
diff --git a/p2p/base/stun.cc b/p2p/base/stun.cc
new file mode 100644
index 00000000..60367fa1
--- /dev/null
+++ b/p2p/base/stun.cc
@@ -0,0 +1,915 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/stun.h"
+
+#include <string.h>
+
+#include "webrtc/base/byteorder.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/crc32.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagedigest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stringencode.h"
+
+using rtc::ByteBuffer;
+
+namespace cricket {
+
+const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[] = "Try Alternate Server";
+const char STUN_ERROR_REASON_BAD_REQUEST[] = "Bad Request";
+const char STUN_ERROR_REASON_UNAUTHORIZED[] = "Unauthorized";
+const char STUN_ERROR_REASON_FORBIDDEN[] = "Forbidden";
+const char STUN_ERROR_REASON_STALE_CREDENTIALS[] = "Stale Credentials";
+const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[] = "Allocation Mismatch";
+const char STUN_ERROR_REASON_STALE_NONCE[] = "Stale Nonce";
+const char STUN_ERROR_REASON_WRONG_CREDENTIALS[] = "Wrong Credentials";
+const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[] = "Unsupported Protocol";
+const char STUN_ERROR_REASON_ROLE_CONFLICT[] = "Role Conflict";
+const char STUN_ERROR_REASON_SERVER_ERROR[] = "Server Error";
+
+const char TURN_MAGIC_COOKIE_VALUE[] = { '\x72', '\xC6', '\x4B', '\xC6' };
+const char EMPTY_TRANSACTION_ID[] = "0000000000000000";
+const uint32 STUN_FINGERPRINT_XOR_VALUE = 0x5354554E;
+
+// StunMessage
+
+StunMessage::StunMessage()
+ : type_(0),
+ length_(0),
+ transaction_id_(EMPTY_TRANSACTION_ID) {
+ ASSERT(IsValidTransactionId(transaction_id_));
+ attrs_ = new std::vector<StunAttribute*>();
+}
+
+StunMessage::~StunMessage() {
+ for (size_t i = 0; i < attrs_->size(); i++)
+ delete (*attrs_)[i];
+ delete attrs_;
+}
+
+bool StunMessage::IsLegacy() const {
+ if (transaction_id_.size() == kStunLegacyTransactionIdLength)
+ return true;
+ ASSERT(transaction_id_.size() == kStunTransactionIdLength);
+ return false;
+}
+
+bool StunMessage::SetTransactionID(const std::string& str) {
+ if (!IsValidTransactionId(str)) {
+ return false;
+ }
+ transaction_id_ = str;
+ return true;
+}
+
+bool StunMessage::AddAttribute(StunAttribute* attr) {
+ // Fail any attributes that aren't valid for this type of message.
+ if (attr->value_type() != GetAttributeValueType(attr->type())) {
+ return false;
+ }
+ attrs_->push_back(attr);
+ attr->SetOwner(this);
+ size_t attr_length = attr->length();
+ if (attr_length % 4 != 0) {
+ attr_length += (4 - (attr_length % 4));
+ }
+ length_ += static_cast<uint16>(attr_length + 4);
+ return true;
+}
+
+const StunAddressAttribute* StunMessage::GetAddress(int type) const {
+ switch (type) {
+ case STUN_ATTR_MAPPED_ADDRESS: {
+ // Return XOR-MAPPED-ADDRESS when MAPPED-ADDRESS attribute is
+ // missing.
+ const StunAttribute* mapped_address =
+ GetAttribute(STUN_ATTR_MAPPED_ADDRESS);
+ if (!mapped_address)
+ mapped_address = GetAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ return reinterpret_cast<const StunAddressAttribute*>(mapped_address);
+ }
+
+ default:
+ return static_cast<const StunAddressAttribute*>(GetAttribute(type));
+ }
+}
+
+const StunUInt32Attribute* StunMessage::GetUInt32(int type) const {
+ return static_cast<const StunUInt32Attribute*>(GetAttribute(type));
+}
+
+const StunUInt64Attribute* StunMessage::GetUInt64(int type) const {
+ return static_cast<const StunUInt64Attribute*>(GetAttribute(type));
+}
+
+const StunByteStringAttribute* StunMessage::GetByteString(int type) const {
+ return static_cast<const StunByteStringAttribute*>(GetAttribute(type));
+}
+
+const StunErrorCodeAttribute* StunMessage::GetErrorCode() const {
+ return static_cast<const StunErrorCodeAttribute*>(
+ GetAttribute(STUN_ATTR_ERROR_CODE));
+}
+
+const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const {
+ return static_cast<const StunUInt16ListAttribute*>(
+ GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES));
+}
+
+// Verifies a STUN message has a valid MESSAGE-INTEGRITY attribute, using the
+// procedure outlined in RFC 5389, section 15.4.
+bool StunMessage::ValidateMessageIntegrity(const char* data, size_t size,
+ const std::string& password) {
+ // Verifying the size of the message.
+ if ((size % 4) != 0) {
+ return false;
+ }
+
+ // Getting the message length from the STUN header.
+ uint16 msg_length = rtc::GetBE16(&data[2]);
+ if (size != (msg_length + kStunHeaderSize)) {
+ return false;
+ }
+
+ // Finding Message Integrity attribute in stun message.
+ size_t current_pos = kStunHeaderSize;
+ bool has_message_integrity_attr = false;
+ while (current_pos < size) {
+ uint16 attr_type, attr_length;
+ // Getting attribute type and length.
+ attr_type = rtc::GetBE16(&data[current_pos]);
+ attr_length = rtc::GetBE16(&data[current_pos + sizeof(attr_type)]);
+
+ // If M-I, sanity check it, and break out.
+ if (attr_type == STUN_ATTR_MESSAGE_INTEGRITY) {
+ if (attr_length != kStunMessageIntegritySize ||
+ current_pos + attr_length > size) {
+ return false;
+ }
+ has_message_integrity_attr = true;
+ break;
+ }
+
+ // Otherwise, skip to the next attribute.
+ current_pos += sizeof(attr_type) + sizeof(attr_length) + attr_length;
+ if ((attr_length % 4) != 0) {
+ current_pos += (4 - (attr_length % 4));
+ }
+ }
+
+ if (!has_message_integrity_attr) {
+ return false;
+ }
+
+ // Getting length of the message to calculate Message Integrity.
+ size_t mi_pos = current_pos;
+ rtc::scoped_ptr<char[]> temp_data(new char[current_pos]);
+ memcpy(temp_data.get(), data, current_pos);
+ if (size > mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize) {
+ // Stun message has other attributes after message integrity.
+ // Adjust the length parameter in stun message to calculate HMAC.
+ size_t extra_offset = size -
+ (mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize);
+ size_t new_adjusted_len = size - extra_offset - kStunHeaderSize;
+
+ // Writing new length of the STUN message @ Message Length in temp buffer.
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // |0 0| STUN Message Type | Message Length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ rtc::SetBE16(temp_data.get() + 2,
+ static_cast<uint16>(new_adjusted_len));
+ }
+
+ char hmac[kStunMessageIntegritySize];
+ size_t ret = rtc::ComputeHmac(rtc::DIGEST_SHA_1,
+ password.c_str(), password.size(),
+ temp_data.get(), mi_pos,
+ hmac, sizeof(hmac));
+ ASSERT(ret == sizeof(hmac));
+ if (ret != sizeof(hmac))
+ return false;
+
+ // Comparing the calculated HMAC with the one present in the message.
+ return memcmp(data + current_pos + kStunAttributeHeaderSize,
+ hmac,
+ sizeof(hmac)) == 0;
+}
+
+bool StunMessage::AddMessageIntegrity(const std::string& password) {
+ return AddMessageIntegrity(password.c_str(), password.size());
+}
+
+bool StunMessage::AddMessageIntegrity(const char* key,
+ size_t keylen) {
+ // Add the attribute with a dummy value. Since this is a known attribute, it
+ // can't fail.
+ StunByteStringAttribute* msg_integrity_attr =
+ new StunByteStringAttribute(STUN_ATTR_MESSAGE_INTEGRITY,
+ std::string(kStunMessageIntegritySize, '0'));
+ VERIFY(AddAttribute(msg_integrity_attr));
+
+ // Calculate the HMAC for the message.
+ rtc::ByteBuffer buf;
+ if (!Write(&buf))
+ return false;
+
+ int msg_len_for_hmac = static_cast<int>(
+ buf.Length() - kStunAttributeHeaderSize - msg_integrity_attr->length());
+ char hmac[kStunMessageIntegritySize];
+ size_t ret = rtc::ComputeHmac(rtc::DIGEST_SHA_1,
+ key, keylen,
+ buf.Data(), msg_len_for_hmac,
+ hmac, sizeof(hmac));
+ ASSERT(ret == sizeof(hmac));
+ if (ret != sizeof(hmac)) {
+ LOG(LS_ERROR) << "HMAC computation failed. Message-Integrity "
+ << "has dummy value.";
+ return false;
+ }
+
+ // Insert correct HMAC into the attribute.
+ msg_integrity_attr->CopyBytes(hmac, sizeof(hmac));
+ return true;
+}
+
+// Verifies a message is in fact a STUN message, by performing the checks
+// outlined in RFC 5389, section 7.3, including the FINGERPRINT check detailed
+// in section 15.5.
+bool StunMessage::ValidateFingerprint(const char* data, size_t size) {
+ // Check the message length.
+ size_t fingerprint_attr_size =
+ kStunAttributeHeaderSize + StunUInt32Attribute::SIZE;
+ if (size % 4 != 0 || size < kStunHeaderSize + fingerprint_attr_size)
+ return false;
+
+ // Skip the rest if the magic cookie isn't present.
+ const char* magic_cookie =
+ data + kStunTransactionIdOffset - kStunMagicCookieLength;
+ if (rtc::GetBE32(magic_cookie) != kStunMagicCookie)
+ return false;
+
+ // Check the fingerprint type and length.
+ const char* fingerprint_attr_data = data + size - fingerprint_attr_size;
+ if (rtc::GetBE16(fingerprint_attr_data) != STUN_ATTR_FINGERPRINT ||
+ rtc::GetBE16(fingerprint_attr_data + sizeof(uint16)) !=
+ StunUInt32Attribute::SIZE)
+ return false;
+
+ // Check the fingerprint value.
+ uint32 fingerprint =
+ rtc::GetBE32(fingerprint_attr_data + kStunAttributeHeaderSize);
+ return ((fingerprint ^ STUN_FINGERPRINT_XOR_VALUE) ==
+ rtc::ComputeCrc32(data, size - fingerprint_attr_size));
+}
+
+bool StunMessage::AddFingerprint() {
+ // Add the attribute with a dummy value. Since this is a known attribute,
+ // it can't fail.
+ StunUInt32Attribute* fingerprint_attr =
+ new StunUInt32Attribute(STUN_ATTR_FINGERPRINT, 0);
+ VERIFY(AddAttribute(fingerprint_attr));
+
+ // Calculate the CRC-32 for the message and insert it.
+ rtc::ByteBuffer buf;
+ if (!Write(&buf))
+ return false;
+
+ int msg_len_for_crc32 = static_cast<int>(
+ buf.Length() - kStunAttributeHeaderSize - fingerprint_attr->length());
+ uint32 c = rtc::ComputeCrc32(buf.Data(), msg_len_for_crc32);
+
+ // Insert the correct CRC-32, XORed with a constant, into the attribute.
+ fingerprint_attr->SetValue(c ^ STUN_FINGERPRINT_XOR_VALUE);
+ return true;
+}
+
+bool StunMessage::Read(ByteBuffer* buf) {
+ if (!buf->ReadUInt16(&type_))
+ return false;
+
+ if (type_ & 0x8000) {
+ // RTP and RTCP set the MSB of first byte, since first two bits are version,
+ // and version is always 2 (10). If set, this is not a STUN packet.
+ return false;
+ }
+
+ if (!buf->ReadUInt16(&length_))
+ return false;
+
+ std::string magic_cookie;
+ if (!buf->ReadString(&magic_cookie, kStunMagicCookieLength))
+ return false;
+
+ std::string transaction_id;
+ if (!buf->ReadString(&transaction_id, kStunTransactionIdLength))
+ return false;
+
+ uint32 magic_cookie_int =
+ *reinterpret_cast<const uint32*>(magic_cookie.data());
+ if (rtc::NetworkToHost32(magic_cookie_int) != kStunMagicCookie) {
+ // If magic cookie is invalid it means that the peer implements
+ // RFC3489 instead of RFC5389.
+ transaction_id.insert(0, magic_cookie);
+ }
+ ASSERT(IsValidTransactionId(transaction_id));
+ transaction_id_ = transaction_id;
+
+ if (length_ != buf->Length())
+ return false;
+
+ attrs_->resize(0);
+
+ size_t rest = buf->Length() - length_;
+ while (buf->Length() > rest) {
+ uint16 attr_type, attr_length;
+ if (!buf->ReadUInt16(&attr_type))
+ return false;
+ if (!buf->ReadUInt16(&attr_length))
+ return false;
+
+ StunAttribute* attr = CreateAttribute(attr_type, attr_length);
+ if (!attr) {
+ // Skip any unknown or malformed attributes.
+ if ((attr_length % 4) != 0) {
+ attr_length += (4 - (attr_length % 4));
+ }
+ if (!buf->Consume(attr_length))
+ return false;
+ } else {
+ if (!attr->Read(buf))
+ return false;
+ attrs_->push_back(attr);
+ }
+ }
+
+ ASSERT(buf->Length() == rest);
+ return true;
+}
+
+bool StunMessage::Write(ByteBuffer* buf) const {
+ buf->WriteUInt16(type_);
+ buf->WriteUInt16(length_);
+ if (!IsLegacy())
+ buf->WriteUInt32(kStunMagicCookie);
+ buf->WriteString(transaction_id_);
+
+ for (size_t i = 0; i < attrs_->size(); ++i) {
+ buf->WriteUInt16((*attrs_)[i]->type());
+ buf->WriteUInt16(static_cast<uint16>((*attrs_)[i]->length()));
+ if (!(*attrs_)[i]->Write(buf))
+ return false;
+ }
+
+ return true;
+}
+
+StunAttributeValueType StunMessage::GetAttributeValueType(int type) const {
+ switch (type) {
+ case STUN_ATTR_MAPPED_ADDRESS: return STUN_VALUE_ADDRESS;
+ case STUN_ATTR_USERNAME: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_MESSAGE_INTEGRITY: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_ERROR_CODE: return STUN_VALUE_ERROR_CODE;
+ case STUN_ATTR_UNKNOWN_ATTRIBUTES: return STUN_VALUE_UINT16_LIST;
+ case STUN_ATTR_REALM: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_NONCE: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_XOR_MAPPED_ADDRESS: return STUN_VALUE_XOR_ADDRESS;
+ case STUN_ATTR_SOFTWARE: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_ADDRESS;
+ case STUN_ATTR_FINGERPRINT: return STUN_VALUE_UINT32;
+ case STUN_ATTR_RETRANSMIT_COUNT: return STUN_VALUE_UINT32;
+ default: return STUN_VALUE_UNKNOWN;
+ }
+}
+
+StunAttribute* StunMessage::CreateAttribute(int type, size_t length) /*const*/ {
+ StunAttributeValueType value_type = GetAttributeValueType(type);
+ return StunAttribute::Create(value_type, type,
+ static_cast<uint16>(length), this);
+}
+
+const StunAttribute* StunMessage::GetAttribute(int type) const {
+ for (size_t i = 0; i < attrs_->size(); ++i) {
+ if ((*attrs_)[i]->type() == type)
+ return (*attrs_)[i];
+ }
+ return NULL;
+}
+
+bool StunMessage::IsValidTransactionId(const std::string& transaction_id) {
+ return transaction_id.size() == kStunTransactionIdLength ||
+ transaction_id.size() == kStunLegacyTransactionIdLength;
+}
+
+// StunAttribute
+
+StunAttribute::StunAttribute(uint16 type, uint16 length)
+ : type_(type), length_(length) {
+}
+
+void StunAttribute::ConsumePadding(rtc::ByteBuffer* buf) const {
+ int remainder = length_ % 4;
+ if (remainder > 0) {
+ buf->Consume(4 - remainder);
+ }
+}
+
+void StunAttribute::WritePadding(rtc::ByteBuffer* buf) const {
+ int remainder = length_ % 4;
+ if (remainder > 0) {
+ char zeroes[4] = {0};
+ buf->WriteBytes(zeroes, 4 - remainder);
+ }
+}
+
+StunAttribute* StunAttribute::Create(StunAttributeValueType value_type,
+ uint16 type, uint16 length,
+ StunMessage* owner) {
+ switch (value_type) {
+ case STUN_VALUE_ADDRESS:
+ return new StunAddressAttribute(type, length);
+ case STUN_VALUE_XOR_ADDRESS:
+ return new StunXorAddressAttribute(type, length, owner);
+ case STUN_VALUE_UINT32:
+ return new StunUInt32Attribute(type);
+ case STUN_VALUE_UINT64:
+ return new StunUInt64Attribute(type);
+ case STUN_VALUE_BYTE_STRING:
+ return new StunByteStringAttribute(type, length);
+ case STUN_VALUE_ERROR_CODE:
+ return new StunErrorCodeAttribute(type, length);
+ case STUN_VALUE_UINT16_LIST:
+ return new StunUInt16ListAttribute(type, length);
+ default:
+ return NULL;
+ }
+}
+
+StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
+ return new StunAddressAttribute(type, 0);
+}
+
+StunXorAddressAttribute* StunAttribute::CreateXorAddress(uint16 type) {
+ return new StunXorAddressAttribute(type, 0, NULL);
+}
+
+StunUInt64Attribute* StunAttribute::CreateUInt64(uint16 type) {
+ return new StunUInt64Attribute(type);
+}
+
+StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) {
+ return new StunUInt32Attribute(type);
+}
+
+StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) {
+ return new StunByteStringAttribute(type, 0);
+}
+
+StunErrorCodeAttribute* StunAttribute::CreateErrorCode() {
+ return new StunErrorCodeAttribute(
+ STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE);
+}
+
+StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() {
+ return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type,
+ const rtc::SocketAddress& addr)
+ : StunAttribute(type, 0) {
+ SetAddress(addr);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length) {
+}
+
+bool StunAddressAttribute::Read(ByteBuffer* buf) {
+ uint8 dummy;
+ if (!buf->ReadUInt8(&dummy))
+ return false;
+
+ uint8 stun_family;
+ if (!buf->ReadUInt8(&stun_family)) {
+ return false;
+ }
+ uint16 port;
+ if (!buf->ReadUInt16(&port))
+ return false;
+ if (stun_family == STUN_ADDRESS_IPV4) {
+ in_addr v4addr;
+ if (length() != SIZE_IP4) {
+ return false;
+ }
+ if (!buf->ReadBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr))) {
+ return false;
+ }
+ rtc::IPAddress ipaddr(v4addr);
+ SetAddress(rtc::SocketAddress(ipaddr, port));
+ } else if (stun_family == STUN_ADDRESS_IPV6) {
+ in6_addr v6addr;
+ if (length() != SIZE_IP6) {
+ return false;
+ }
+ if (!buf->ReadBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr))) {
+ return false;
+ }
+ rtc::IPAddress ipaddr(v6addr);
+ SetAddress(rtc::SocketAddress(ipaddr, port));
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool StunAddressAttribute::Write(ByteBuffer* buf) const {
+ StunAddressFamily address_family = family();
+ if (address_family == STUN_ADDRESS_UNDEF) {
+ LOG(LS_ERROR) << "Error writing address attribute: unknown family.";
+ return false;
+ }
+ buf->WriteUInt8(0);
+ buf->WriteUInt8(address_family);
+ buf->WriteUInt16(address_.port());
+ switch (address_.family()) {
+ case AF_INET: {
+ in_addr v4addr = address_.ipaddr().ipv4_address();
+ buf->WriteBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr));
+ break;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = address_.ipaddr().ipv6_address();
+ buf->WriteBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr));
+ break;
+ }
+ }
+ return true;
+}
+
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type,
+ const rtc::SocketAddress& addr)
+ : StunAddressAttribute(type, addr), owner_(NULL) {
+}
+
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type,
+ uint16 length,
+ StunMessage* owner)
+ : StunAddressAttribute(type, length), owner_(owner) {}
+
+rtc::IPAddress StunXorAddressAttribute::GetXoredIP() const {
+ if (owner_) {
+ rtc::IPAddress ip = ipaddr();
+ switch (ip.family()) {
+ case AF_INET: {
+ in_addr v4addr = ip.ipv4_address();
+ v4addr.s_addr =
+ (v4addr.s_addr ^ rtc::HostToNetwork32(kStunMagicCookie));
+ return rtc::IPAddress(v4addr);
+ }
+ case AF_INET6: {
+ in6_addr v6addr = ip.ipv6_address();
+ const std::string& transaction_id = owner_->transaction_id();
+ if (transaction_id.length() == kStunTransactionIdLength) {
+ uint32 transactionid_as_ints[3];
+ memcpy(&transactionid_as_ints[0], transaction_id.c_str(),
+ transaction_id.length());
+ uint32* ip_as_ints = reinterpret_cast<uint32*>(&v6addr.s6_addr);
+ // Transaction ID is in network byte order, but magic cookie
+ // is stored in host byte order.
+ ip_as_ints[0] =
+ (ip_as_ints[0] ^ rtc::HostToNetwork32(kStunMagicCookie));
+ ip_as_ints[1] = (ip_as_ints[1] ^ transactionid_as_ints[0]);
+ ip_as_ints[2] = (ip_as_ints[2] ^ transactionid_as_ints[1]);
+ ip_as_ints[3] = (ip_as_ints[3] ^ transactionid_as_ints[2]);
+ return rtc::IPAddress(v6addr);
+ }
+ break;
+ }
+ }
+ }
+ // Invalid ip family or transaction ID, or missing owner.
+ // Return an AF_UNSPEC address.
+ return rtc::IPAddress();
+}
+
+bool StunXorAddressAttribute::Read(ByteBuffer* buf) {
+ if (!StunAddressAttribute::Read(buf))
+ return false;
+ uint16 xoredport = port() ^ (kStunMagicCookie >> 16);
+ rtc::IPAddress xored_ip = GetXoredIP();
+ SetAddress(rtc::SocketAddress(xored_ip, xoredport));
+ return true;
+}
+
+bool StunXorAddressAttribute::Write(ByteBuffer* buf) const {
+ StunAddressFamily address_family = family();
+ if (address_family == STUN_ADDRESS_UNDEF) {
+ LOG(LS_ERROR) << "Error writing xor-address attribute: unknown family.";
+ return false;
+ }
+ rtc::IPAddress xored_ip = GetXoredIP();
+ if (xored_ip.family() == AF_UNSPEC) {
+ return false;
+ }
+ buf->WriteUInt8(0);
+ buf->WriteUInt8(family());
+ buf->WriteUInt16(port() ^ (kStunMagicCookie >> 16));
+ switch (xored_ip.family()) {
+ case AF_INET: {
+ in_addr v4addr = xored_ip.ipv4_address();
+ buf->WriteBytes(reinterpret_cast<const char*>(&v4addr), sizeof(v4addr));
+ break;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = xored_ip.ipv6_address();
+ buf->WriteBytes(reinterpret_cast<const char*>(&v6addr), sizeof(v6addr));
+ break;
+ }
+ }
+ return true;
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type, uint32 value)
+ : StunAttribute(type, SIZE), bits_(value) {
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type)
+ : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt32Attribute::GetBit(size_t index) const {
+ ASSERT(index < 32);
+ return static_cast<bool>((bits_ >> index) & 0x1);
+}
+
+void StunUInt32Attribute::SetBit(size_t index, bool value) {
+ ASSERT(index < 32);
+ bits_ &= ~(1 << index);
+ bits_ |= value ? (1 << index) : 0;
+}
+
+bool StunUInt32Attribute::Read(ByteBuffer* buf) {
+ if (length() != SIZE || !buf->ReadUInt32(&bits_))
+ return false;
+ return true;
+}
+
+bool StunUInt32Attribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt32(bits_);
+ return true;
+}
+
+StunUInt64Attribute::StunUInt64Attribute(uint16 type, uint64 value)
+ : StunAttribute(type, SIZE), bits_(value) {
+}
+
+StunUInt64Attribute::StunUInt64Attribute(uint16 type)
+ : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt64Attribute::Read(ByteBuffer* buf) {
+ if (length() != SIZE || !buf->ReadUInt64(&bits_))
+ return false;
+ return true;
+}
+
+bool StunUInt64Attribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt64(bits_);
+ return true;
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type)
+ : StunAttribute(type, 0), bytes_(NULL) {
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type,
+ const std::string& str)
+ : StunAttribute(type, 0), bytes_(NULL) {
+ CopyBytes(str.c_str(), str.size());
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type,
+ const void* bytes,
+ size_t length)
+ : StunAttribute(type, 0), bytes_(NULL) {
+ CopyBytes(bytes, length);
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length), bytes_(NULL) {
+}
+
+StunByteStringAttribute::~StunByteStringAttribute() {
+ delete [] bytes_;
+}
+
+void StunByteStringAttribute::CopyBytes(const char* bytes) {
+ CopyBytes(bytes, strlen(bytes));
+}
+
+void StunByteStringAttribute::CopyBytes(const void* bytes, size_t length) {
+ char* new_bytes = new char[length];
+ memcpy(new_bytes, bytes, length);
+ SetBytes(new_bytes, length);
+}
+
+uint8 StunByteStringAttribute::GetByte(size_t index) const {
+ ASSERT(bytes_ != NULL);
+ ASSERT(index < length());
+ return static_cast<uint8>(bytes_[index]);
+}
+
+void StunByteStringAttribute::SetByte(size_t index, uint8 value) {
+ ASSERT(bytes_ != NULL);
+ ASSERT(index < length());
+ bytes_[index] = value;
+}
+
+bool StunByteStringAttribute::Read(ByteBuffer* buf) {
+ bytes_ = new char[length()];
+ if (!buf->ReadBytes(bytes_, length())) {
+ return false;
+ }
+
+ ConsumePadding(buf);
+ return true;
+}
+
+bool StunByteStringAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteBytes(bytes_, length());
+ WritePadding(buf);
+ return true;
+}
+
+void StunByteStringAttribute::SetBytes(char* bytes, size_t length) {
+ delete [] bytes_;
+ bytes_ = bytes;
+ SetLength(static_cast<uint16>(length));
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, int code,
+ const std::string& reason)
+ : StunAttribute(type, 0) {
+ SetCode(code);
+ SetReason(reason);
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length), class_(0), number_(0) {
+}
+
+StunErrorCodeAttribute::~StunErrorCodeAttribute() {
+}
+
+int StunErrorCodeAttribute::code() const {
+ return class_ * 100 + number_;
+}
+
+void StunErrorCodeAttribute::SetCode(int code) {
+ class_ = static_cast<uint8>(code / 100);
+ number_ = static_cast<uint8>(code % 100);
+}
+
+void StunErrorCodeAttribute::SetReason(const std::string& reason) {
+ SetLength(MIN_SIZE + static_cast<uint16>(reason.size()));
+ reason_ = reason;
+}
+
+bool StunErrorCodeAttribute::Read(ByteBuffer* buf) {
+ uint32 val;
+ if (length() < MIN_SIZE || !buf->ReadUInt32(&val))
+ return false;
+
+ if ((val >> 11) != 0)
+ LOG(LS_ERROR) << "error-code bits not zero";
+
+ class_ = ((val >> 8) & 0x7);
+ number_ = (val & 0xff);
+
+ if (!buf->ReadString(&reason_, length() - 4))
+ return false;
+
+ ConsumePadding(buf);
+ return true;
+}
+
+bool StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt32(class_ << 8 | number_);
+ buf->WriteString(reason_);
+ WritePadding(buf);
+ return true;
+}
+
+StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length) {
+ attr_types_ = new std::vector<uint16>();
+}
+
+StunUInt16ListAttribute::~StunUInt16ListAttribute() {
+ delete attr_types_;
+}
+
+size_t StunUInt16ListAttribute::Size() const {
+ return attr_types_->size();
+}
+
+uint16 StunUInt16ListAttribute::GetType(int index) const {
+ return (*attr_types_)[index];
+}
+
+void StunUInt16ListAttribute::SetType(int index, uint16 value) {
+ (*attr_types_)[index] = value;
+}
+
+void StunUInt16ListAttribute::AddType(uint16 value) {
+ attr_types_->push_back(value);
+ SetLength(static_cast<uint16>(attr_types_->size() * 2));
+}
+
+bool StunUInt16ListAttribute::Read(ByteBuffer* buf) {
+ if (length() % 2)
+ return false;
+
+ for (size_t i = 0; i < length() / 2; i++) {
+ uint16 attr;
+ if (!buf->ReadUInt16(&attr))
+ return false;
+ attr_types_->push_back(attr);
+ }
+ // Padding of these attributes is done in RFC 5389 style. This is
+ // slightly different from RFC3489, but it shouldn't be important.
+ // RFC3489 pads out to a 32 bit boundary by duplicating one of the
+ // entries in the list (not necessarily the last one - it's unspecified).
+ // RFC5389 pads on the end, and the bytes are always ignored.
+ ConsumePadding(buf);
+ return true;
+}
+
+bool StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
+ for (size_t i = 0; i < attr_types_->size(); ++i) {
+ buf->WriteUInt16((*attr_types_)[i]);
+ }
+ WritePadding(buf);
+ return true;
+}
+
+int GetStunSuccessResponseType(int req_type) {
+ return IsStunRequestType(req_type) ? (req_type | 0x100) : -1;
+}
+
+int GetStunErrorResponseType(int req_type) {
+ return IsStunRequestType(req_type) ? (req_type | 0x110) : -1;
+}
+
+bool IsStunRequestType(int msg_type) {
+ return ((msg_type & kStunTypeMask) == 0x000);
+}
+
+bool IsStunIndicationType(int msg_type) {
+ return ((msg_type & kStunTypeMask) == 0x010);
+}
+
+bool IsStunSuccessResponseType(int msg_type) {
+ return ((msg_type & kStunTypeMask) == 0x100);
+}
+
+bool IsStunErrorResponseType(int msg_type) {
+ return ((msg_type & kStunTypeMask) == 0x110);
+}
+
+bool ComputeStunCredentialHash(const std::string& username,
+ const std::string& realm,
+ const std::string& password,
+ std::string* hash) {
+ // http://tools.ietf.org/html/rfc5389#section-15.4
+ // long-term credentials will be calculated using the key and key is
+ // key = MD5(username ":" realm ":" SASLprep(password))
+ std::string input = username;
+ input += ':';
+ input += realm;
+ input += ':';
+ input += password;
+
+ char digest[rtc::MessageDigest::kMaxSize];
+ size_t size = rtc::ComputeDigest(
+ rtc::DIGEST_MD5, input.c_str(), input.size(),
+ digest, sizeof(digest));
+ if (size == 0) {
+ return false;
+ }
+
+ *hash = std::string(digest, size);
+ return true;
+}
+
+} // namespace cricket
diff --git a/p2p/base/stun.h b/p2p/base/stun.h
new file mode 100644
index 00000000..0f600dbf
--- /dev/null
+++ b/p2p/base/stun.h
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_STUN_H_
+#define WEBRTC_P2P_BASE_STUN_H_
+
+// This file contains classes for dealing with the STUN protocol, as specified
+// in RFC 5389, and its descendants.
+
+#include <string>
+#include <vector>
+
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace cricket {
+
+// These are the types of STUN messages defined in RFC 5389.
+enum StunMessageType {
+ STUN_BINDING_REQUEST = 0x0001,
+ STUN_BINDING_INDICATION = 0x0011,
+ STUN_BINDING_RESPONSE = 0x0101,
+ STUN_BINDING_ERROR_RESPONSE = 0x0111,
+};
+
+// These are all known STUN attributes, defined in RFC 5389 and elsewhere.
+// Next to each is the name of the class (T is StunTAttribute) that implements
+// that type.
+// RETRANSMIT_COUNT is the number of outstanding pings without a response at
+// the time the packet is generated.
+enum StunAttributeType {
+ STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address
+ STUN_ATTR_USERNAME = 0x0006, // ByteString
+ STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, // ByteString, 20 bytes
+ STUN_ATTR_ERROR_CODE = 0x0009, // ErrorCode
+ STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000a, // UInt16List
+ STUN_ATTR_REALM = 0x0014, // ByteString
+ STUN_ATTR_NONCE = 0x0015, // ByteString
+ STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress
+ STUN_ATTR_SOFTWARE = 0x8022, // ByteString
+ STUN_ATTR_ALTERNATE_SERVER = 0x8023, // Address
+ STUN_ATTR_FINGERPRINT = 0x8028, // UInt32
+ STUN_ATTR_RETRANSMIT_COUNT = 0xFF00 // UInt32
+};
+
+// These are the types of the values associated with the attributes above.
+// This allows us to perform some basic validation when reading or adding
+// attributes. Note that these values are for our own use, and not defined in
+// RFC 5389.
+enum StunAttributeValueType {
+ STUN_VALUE_UNKNOWN = 0,
+ STUN_VALUE_ADDRESS = 1,
+ STUN_VALUE_XOR_ADDRESS = 2,
+ STUN_VALUE_UINT32 = 3,
+ STUN_VALUE_UINT64 = 4,
+ STUN_VALUE_BYTE_STRING = 5,
+ STUN_VALUE_ERROR_CODE = 6,
+ STUN_VALUE_UINT16_LIST = 7
+};
+
+// These are the types of STUN addresses defined in RFC 5389.
+enum StunAddressFamily {
+ // NB: UNDEF is not part of the STUN spec.
+ STUN_ADDRESS_UNDEF = 0,
+ STUN_ADDRESS_IPV4 = 1,
+ STUN_ADDRESS_IPV6 = 2
+};
+
+// These are the types of STUN error codes defined in RFC 5389.
+enum StunErrorCode {
+ STUN_ERROR_TRY_ALTERNATE = 300,
+ STUN_ERROR_BAD_REQUEST = 400,
+ STUN_ERROR_UNAUTHORIZED = 401,
+ STUN_ERROR_UNKNOWN_ATTRIBUTE = 420,
+ STUN_ERROR_STALE_CREDENTIALS = 430, // GICE only
+ STUN_ERROR_STALE_NONCE = 438,
+ STUN_ERROR_SERVER_ERROR = 500,
+ STUN_ERROR_GLOBAL_FAILURE = 600
+};
+
+// Strings for the error codes above.
+extern const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[];
+extern const char STUN_ERROR_REASON_BAD_REQUEST[];
+extern const char STUN_ERROR_REASON_UNAUTHORIZED[];
+extern const char STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE[];
+extern const char STUN_ERROR_REASON_STALE_CREDENTIALS[];
+extern const char STUN_ERROR_REASON_STALE_NONCE[];
+extern const char STUN_ERROR_REASON_SERVER_ERROR[];
+
+// The mask used to determine whether a STUN message is a request/response etc.
+const uint32 kStunTypeMask = 0x0110;
+
+// STUN Attribute header length.
+const size_t kStunAttributeHeaderSize = 4;
+
+// Following values correspond to RFC5389.
+const size_t kStunHeaderSize = 20;
+const size_t kStunTransactionIdOffset = 8;
+const size_t kStunTransactionIdLength = 12;
+const uint32 kStunMagicCookie = 0x2112A442;
+const size_t kStunMagicCookieLength = sizeof(kStunMagicCookie);
+
+// Following value corresponds to an earlier version of STUN from
+// RFC3489.
+const size_t kStunLegacyTransactionIdLength = 16;
+
+// STUN Message Integrity HMAC length.
+const size_t kStunMessageIntegritySize = 20;
+
+class StunAttribute;
+class StunAddressAttribute;
+class StunXorAddressAttribute;
+class StunUInt32Attribute;
+class StunUInt64Attribute;
+class StunByteStringAttribute;
+class StunErrorCodeAttribute;
+class StunUInt16ListAttribute;
+
+// Records a complete STUN/TURN message. Each message consists of a type and
+// any number of attributes. Each attribute is parsed into an instance of an
+// appropriate class (see above). The Get* methods will return instances of
+// that attribute class.
+class StunMessage {
+ public:
+ StunMessage();
+ virtual ~StunMessage();
+
+ int type() const { return type_; }
+ size_t length() const { return length_; }
+ const std::string& transaction_id() const { return transaction_id_; }
+
+ // Returns true if the message confirms to RFC3489 rather than
+ // RFC5389. The main difference between two version of the STUN
+ // protocol is the presence of the magic cookie and different length
+ // of transaction ID. For outgoing packets version of the protocol
+ // is determined by the lengths of the transaction ID.
+ bool IsLegacy() const;
+
+ void SetType(int type) { type_ = static_cast<uint16>(type); }
+ bool SetTransactionID(const std::string& str);
+
+ // Gets the desired attribute value, or NULL if no such attribute type exists.
+ const StunAddressAttribute* GetAddress(int type) const;
+ const StunUInt32Attribute* GetUInt32(int type) const;
+ const StunUInt64Attribute* GetUInt64(int type) const;
+ const StunByteStringAttribute* GetByteString(int type) const;
+
+ // Gets these specific attribute values.
+ const StunErrorCodeAttribute* GetErrorCode() const;
+ const StunUInt16ListAttribute* GetUnknownAttributes() const;
+
+ // Takes ownership of the specified attribute, verifies it is of the correct
+ // type, and adds it to the message. The return value indicates whether this
+ // was successful.
+ bool AddAttribute(StunAttribute* attr);
+
+ // Validates that a raw STUN message has a correct MESSAGE-INTEGRITY value.
+ // This can't currently be done on a StunMessage, since it is affected by
+ // padding data (which we discard when reading a StunMessage).
+ static bool ValidateMessageIntegrity(const char* data, size_t size,
+ const std::string& password);
+ // Adds a MESSAGE-INTEGRITY attribute that is valid for the current message.
+ bool AddMessageIntegrity(const std::string& password);
+ bool AddMessageIntegrity(const char* key, size_t keylen);
+
+ // Verifies that a given buffer is STUN by checking for a correct FINGERPRINT.
+ static bool ValidateFingerprint(const char* data, size_t size);
+
+ // Adds a FINGERPRINT attribute that is valid for the current message.
+ bool AddFingerprint();
+
+ // Parses the STUN packet in the given buffer and records it here. The
+ // return value indicates whether this was successful.
+ bool Read(rtc::ByteBuffer* buf);
+
+ // Writes this object into a STUN packet. The return value indicates whether
+ // this was successful.
+ bool Write(rtc::ByteBuffer* buf) const;
+
+ // Creates an empty message. Overridable by derived classes.
+ virtual StunMessage* CreateNew() const { return new StunMessage(); }
+
+ protected:
+ // Verifies that the given attribute is allowed for this message.
+ virtual StunAttributeValueType GetAttributeValueType(int type) const;
+
+ private:
+ StunAttribute* CreateAttribute(int type, size_t length) /* const*/;
+ const StunAttribute* GetAttribute(int type) const;
+ static bool IsValidTransactionId(const std::string& transaction_id);
+
+ uint16 type_;
+ uint16 length_;
+ std::string transaction_id_;
+ std::vector<StunAttribute*>* attrs_;
+};
+
+// Base class for all STUN/TURN attributes.
+class StunAttribute {
+ public:
+ virtual ~StunAttribute() {
+ }
+
+ int type() const { return type_; }
+ size_t length() const { return length_; }
+
+ // Return the type of this attribute.
+ virtual StunAttributeValueType value_type() const = 0;
+
+ // Only XorAddressAttribute needs this so far.
+ virtual void SetOwner(StunMessage* owner) {}
+
+ // Reads the body (not the type or length) for this type of attribute from
+ // the given buffer. Return value is true if successful.
+ virtual bool Read(rtc::ByteBuffer* buf) = 0;
+
+ // Writes the body (not the type or length) to the given buffer. Return
+ // value is true if successful.
+ virtual bool Write(rtc::ByteBuffer* buf) const = 0;
+
+ // Creates an attribute object with the given type and smallest length.
+ static StunAttribute* Create(StunAttributeValueType value_type, uint16 type,
+ uint16 length, StunMessage* owner);
+ // TODO: Allow these create functions to take parameters, to reduce
+ // the amount of work callers need to do to initialize attributes.
+ static StunAddressAttribute* CreateAddress(uint16 type);
+ static StunXorAddressAttribute* CreateXorAddress(uint16 type);
+ static StunUInt32Attribute* CreateUInt32(uint16 type);
+ static StunUInt64Attribute* CreateUInt64(uint16 type);
+ static StunByteStringAttribute* CreateByteString(uint16 type);
+ static StunErrorCodeAttribute* CreateErrorCode();
+ static StunUInt16ListAttribute* CreateUnknownAttributes();
+
+ protected:
+ StunAttribute(uint16 type, uint16 length);
+ void SetLength(uint16 length) { length_ = length; }
+ void WritePadding(rtc::ByteBuffer* buf) const;
+ void ConsumePadding(rtc::ByteBuffer* buf) const;
+
+ private:
+ uint16 type_;
+ uint16 length_;
+};
+
+// Implements STUN attributes that record an Internet address.
+class StunAddressAttribute : public StunAttribute {
+ public:
+ static const uint16 SIZE_UNDEF = 0;
+ static const uint16 SIZE_IP4 = 8;
+ static const uint16 SIZE_IP6 = 20;
+ StunAddressAttribute(uint16 type, const rtc::SocketAddress& addr);
+ StunAddressAttribute(uint16 type, uint16 length);
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_ADDRESS;
+ }
+
+ StunAddressFamily family() const {
+ switch (address_.ipaddr().family()) {
+ case AF_INET:
+ return STUN_ADDRESS_IPV4;
+ case AF_INET6:
+ return STUN_ADDRESS_IPV6;
+ }
+ return STUN_ADDRESS_UNDEF;
+ }
+
+ const rtc::SocketAddress& GetAddress() const { return address_; }
+ const rtc::IPAddress& ipaddr() const { return address_.ipaddr(); }
+ uint16 port() const { return address_.port(); }
+
+ void SetAddress(const rtc::SocketAddress& addr) {
+ address_ = addr;
+ EnsureAddressLength();
+ }
+ void SetIP(const rtc::IPAddress& ip) {
+ address_.SetIP(ip);
+ EnsureAddressLength();
+ }
+ void SetPort(uint16 port) { address_.SetPort(port); }
+
+ virtual bool Read(rtc::ByteBuffer* buf);
+ virtual bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ void EnsureAddressLength() {
+ switch (family()) {
+ case STUN_ADDRESS_IPV4: {
+ SetLength(SIZE_IP4);
+ break;
+ }
+ case STUN_ADDRESS_IPV6: {
+ SetLength(SIZE_IP6);
+ break;
+ }
+ default: {
+ SetLength(SIZE_UNDEF);
+ break;
+ }
+ }
+ }
+ rtc::SocketAddress address_;
+};
+
+// Implements STUN attributes that record an Internet address. When encoded
+// in a STUN message, the address contained in this attribute is XORed with the
+// transaction ID of the message.
+class StunXorAddressAttribute : public StunAddressAttribute {
+ public:
+ StunXorAddressAttribute(uint16 type, const rtc::SocketAddress& addr);
+ StunXorAddressAttribute(uint16 type, uint16 length,
+ StunMessage* owner);
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_XOR_ADDRESS;
+ }
+ virtual void SetOwner(StunMessage* owner) {
+ owner_ = owner;
+ }
+ virtual bool Read(rtc::ByteBuffer* buf);
+ virtual bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ rtc::IPAddress GetXoredIP() const;
+ StunMessage* owner_;
+};
+
+// Implements STUN attributes that record a 32-bit integer.
+class StunUInt32Attribute : public StunAttribute {
+ public:
+ static const uint16 SIZE = 4;
+ StunUInt32Attribute(uint16 type, uint32 value);
+ explicit StunUInt32Attribute(uint16 type);
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_UINT32;
+ }
+
+ uint32 value() const { return bits_; }
+ void SetValue(uint32 bits) { bits_ = bits; }
+
+ bool GetBit(size_t index) const;
+ void SetBit(size_t index, bool value);
+
+ virtual bool Read(rtc::ByteBuffer* buf);
+ virtual bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ uint32 bits_;
+};
+
+class StunUInt64Attribute : public StunAttribute {
+ public:
+ static const uint16 SIZE = 8;
+ StunUInt64Attribute(uint16 type, uint64 value);
+ explicit StunUInt64Attribute(uint16 type);
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_UINT64;
+ }
+
+ uint64 value() const { return bits_; }
+ void SetValue(uint64 bits) { bits_ = bits; }
+
+ virtual bool Read(rtc::ByteBuffer* buf);
+ virtual bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ uint64 bits_;
+};
+
+// Implements STUN attributes that record an arbitrary byte string.
+class StunByteStringAttribute : public StunAttribute {
+ public:
+ explicit StunByteStringAttribute(uint16 type);
+ StunByteStringAttribute(uint16 type, const std::string& str);
+ StunByteStringAttribute(uint16 type, const void* bytes, size_t length);
+ StunByteStringAttribute(uint16 type, uint16 length);
+ ~StunByteStringAttribute();
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_BYTE_STRING;
+ }
+
+ const char* bytes() const { return bytes_; }
+ std::string GetString() const { return std::string(bytes_, length()); }
+
+ void CopyBytes(const char* bytes); // uses strlen
+ void CopyBytes(const void* bytes, size_t length);
+
+ uint8 GetByte(size_t index) const;
+ void SetByte(size_t index, uint8 value);
+
+ virtual bool Read(rtc::ByteBuffer* buf);
+ virtual bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ void SetBytes(char* bytes, size_t length);
+
+ char* bytes_;
+};
+
+// Implements STUN attributes that record an error code.
+class StunErrorCodeAttribute : public StunAttribute {
+ public:
+ static const uint16 MIN_SIZE = 4;
+ StunErrorCodeAttribute(uint16 type, int code, const std::string& reason);
+ StunErrorCodeAttribute(uint16 type, uint16 length);
+ ~StunErrorCodeAttribute();
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_ERROR_CODE;
+ }
+
+ // The combined error and class, e.g. 0x400.
+ int code() const;
+ void SetCode(int code);
+
+ // The individual error components.
+ int eclass() const { return class_; }
+ int number() const { return number_; }
+ const std::string& reason() const { return reason_; }
+ void SetClass(uint8 eclass) { class_ = eclass; }
+ void SetNumber(uint8 number) { number_ = number; }
+ void SetReason(const std::string& reason);
+
+ bool Read(rtc::ByteBuffer* buf);
+ bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ uint8 class_;
+ uint8 number_;
+ std::string reason_;
+};
+
+// Implements STUN attributes that record a list of attribute names.
+class StunUInt16ListAttribute : public StunAttribute {
+ public:
+ StunUInt16ListAttribute(uint16 type, uint16 length);
+ ~StunUInt16ListAttribute();
+
+ virtual StunAttributeValueType value_type() const {
+ return STUN_VALUE_UINT16_LIST;
+ }
+
+ size_t Size() const;
+ uint16 GetType(int index) const;
+ void SetType(int index, uint16 value);
+ void AddType(uint16 value);
+
+ bool Read(rtc::ByteBuffer* buf);
+ bool Write(rtc::ByteBuffer* buf) const;
+
+ private:
+ std::vector<uint16>* attr_types_;
+};
+
+// Returns the (successful) response type for the given request type.
+// Returns -1 if |request_type| is not a valid request type.
+int GetStunSuccessResponseType(int request_type);
+
+// Returns the error response type for the given request type.
+// Returns -1 if |request_type| is not a valid request type.
+int GetStunErrorResponseType(int request_type);
+
+// Returns whether a given message is a request type.
+bool IsStunRequestType(int msg_type);
+
+// Returns whether a given message is an indication type.
+bool IsStunIndicationType(int msg_type);
+
+// Returns whether a given response is a success type.
+bool IsStunSuccessResponseType(int msg_type);
+
+// Returns whether a given response is an error type.
+bool IsStunErrorResponseType(int msg_type);
+
+// Computes the STUN long-term credential hash.
+bool ComputeStunCredentialHash(const std::string& username,
+ const std::string& realm, const std::string& password, std::string* hash);
+
+// TODO: Move the TURN/ICE stuff below out to separate files.
+extern const char TURN_MAGIC_COOKIE_VALUE[4];
+
+// "GTURN" STUN methods.
+// TODO: Rename these methods to GTURN_ to make it clear they aren't
+// part of standard STUN/TURN.
+enum RelayMessageType {
+ // For now, using the same defs from TurnMessageType below.
+ // STUN_ALLOCATE_REQUEST = 0x0003,
+ // STUN_ALLOCATE_RESPONSE = 0x0103,
+ // STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+ STUN_SEND_REQUEST = 0x0004,
+ STUN_SEND_RESPONSE = 0x0104,
+ STUN_SEND_ERROR_RESPONSE = 0x0114,
+ STUN_DATA_INDICATION = 0x0115,
+};
+
+// "GTURN"-specific STUN attributes.
+// TODO: Rename these attributes to GTURN_ to avoid conflicts.
+enum RelayAttributeType {
+ STUN_ATTR_LIFETIME = 0x000d, // UInt32
+ STUN_ATTR_MAGIC_COOKIE = 0x000f, // ByteString, 4 bytes
+ STUN_ATTR_BANDWIDTH = 0x0010, // UInt32
+ STUN_ATTR_DESTINATION_ADDRESS = 0x0011, // Address
+ STUN_ATTR_SOURCE_ADDRESS2 = 0x0012, // Address
+ STUN_ATTR_DATA = 0x0013, // ByteString
+ STUN_ATTR_OPTIONS = 0x8001, // UInt32
+};
+
+// A "GTURN" STUN message.
+class RelayMessage : public StunMessage {
+ protected:
+ virtual StunAttributeValueType GetAttributeValueType(int type) const {
+ switch (type) {
+ case STUN_ATTR_LIFETIME: return STUN_VALUE_UINT32;
+ case STUN_ATTR_MAGIC_COOKIE: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_BANDWIDTH: return STUN_VALUE_UINT32;
+ case STUN_ATTR_DESTINATION_ADDRESS: return STUN_VALUE_ADDRESS;
+ case STUN_ATTR_SOURCE_ADDRESS2: return STUN_VALUE_ADDRESS;
+ case STUN_ATTR_DATA: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_OPTIONS: return STUN_VALUE_UINT32;
+ default: return StunMessage::GetAttributeValueType(type);
+ }
+ }
+ virtual StunMessage* CreateNew() const { return new RelayMessage(); }
+};
+
+// Defined in TURN RFC 5766.
+enum TurnMessageType {
+ STUN_ALLOCATE_REQUEST = 0x0003,
+ STUN_ALLOCATE_RESPONSE = 0x0103,
+ STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+ TURN_REFRESH_REQUEST = 0x0004,
+ TURN_REFRESH_RESPONSE = 0x0104,
+ TURN_REFRESH_ERROR_RESPONSE = 0x0114,
+ TURN_SEND_INDICATION = 0x0016,
+ TURN_DATA_INDICATION = 0x0017,
+ TURN_CREATE_PERMISSION_REQUEST = 0x0008,
+ TURN_CREATE_PERMISSION_RESPONSE = 0x0108,
+ TURN_CREATE_PERMISSION_ERROR_RESPONSE = 0x0118,
+ TURN_CHANNEL_BIND_REQUEST = 0x0009,
+ TURN_CHANNEL_BIND_RESPONSE = 0x0109,
+ TURN_CHANNEL_BIND_ERROR_RESPONSE = 0x0119,
+};
+
+enum TurnAttributeType {
+ STUN_ATTR_CHANNEL_NUMBER = 0x000C, // UInt32
+ STUN_ATTR_TURN_LIFETIME = 0x000d, // UInt32
+ STUN_ATTR_XOR_PEER_ADDRESS = 0x0012, // XorAddress
+ // TODO(mallinath) - Uncomment after RelayAttributes are renamed.
+ // STUN_ATTR_DATA = 0x0013, // ByteString
+ STUN_ATTR_XOR_RELAYED_ADDRESS = 0x0016, // XorAddress
+ STUN_ATTR_EVEN_PORT = 0x0018, // ByteString, 1 byte.
+ STUN_ATTR_REQUESTED_TRANSPORT = 0x0019, // UInt32
+ STUN_ATTR_DONT_FRAGMENT = 0x001A, // No content, Length = 0
+ STUN_ATTR_RESERVATION_TOKEN = 0x0022, // ByteString, 8 bytes.
+ // TODO(mallinath) - Rename STUN_ATTR_TURN_LIFETIME to STUN_ATTR_LIFETIME and
+ // STUN_ATTR_TURN_DATA to STUN_ATTR_DATA. Also rename RelayMessage attributes
+ // by appending G to attribute name.
+};
+
+// RFC 5766-defined errors.
+enum TurnErrorType {
+ STUN_ERROR_FORBIDDEN = 403,
+ STUN_ERROR_ALLOCATION_MISMATCH = 437,
+ STUN_ERROR_WRONG_CREDENTIALS = 441,
+ STUN_ERROR_UNSUPPORTED_PROTOCOL = 442
+};
+extern const char STUN_ERROR_REASON_FORBIDDEN[];
+extern const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[];
+extern const char STUN_ERROR_REASON_WRONG_CREDENTIALS[];
+extern const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[];
+class TurnMessage : public StunMessage {
+ protected:
+ virtual StunAttributeValueType GetAttributeValueType(int type) const {
+ switch (type) {
+ case STUN_ATTR_CHANNEL_NUMBER: return STUN_VALUE_UINT32;
+ case STUN_ATTR_TURN_LIFETIME: return STUN_VALUE_UINT32;
+ case STUN_ATTR_XOR_PEER_ADDRESS: return STUN_VALUE_XOR_ADDRESS;
+ case STUN_ATTR_DATA: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_XOR_RELAYED_ADDRESS: return STUN_VALUE_XOR_ADDRESS;
+ case STUN_ATTR_EVEN_PORT: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_REQUESTED_TRANSPORT: return STUN_VALUE_UINT32;
+ case STUN_ATTR_DONT_FRAGMENT: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_RESERVATION_TOKEN: return STUN_VALUE_BYTE_STRING;
+ default: return StunMessage::GetAttributeValueType(type);
+ }
+ }
+ virtual StunMessage* CreateNew() const { return new TurnMessage(); }
+};
+
+// RFC 5245 ICE STUN attributes.
+enum IceAttributeType {
+ STUN_ATTR_PRIORITY = 0x0024, // UInt32
+ STUN_ATTR_USE_CANDIDATE = 0x0025, // No content, Length = 0
+ STUN_ATTR_ICE_CONTROLLED = 0x8029, // UInt64
+ STUN_ATTR_ICE_CONTROLLING = 0x802A // UInt64
+};
+
+// RFC 5245-defined errors.
+enum IceErrorCode {
+ STUN_ERROR_ROLE_CONFLICT = 487,
+};
+extern const char STUN_ERROR_REASON_ROLE_CONFLICT[];
+
+// A RFC 5245 ICE STUN message.
+class IceMessage : public StunMessage {
+ protected:
+ virtual StunAttributeValueType GetAttributeValueType(int type) const {
+ switch (type) {
+ case STUN_ATTR_PRIORITY: return STUN_VALUE_UINT32;
+ case STUN_ATTR_USE_CANDIDATE: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_ICE_CONTROLLED: return STUN_VALUE_UINT64;
+ case STUN_ATTR_ICE_CONTROLLING: return STUN_VALUE_UINT64;
+ default: return StunMessage::GetAttributeValueType(type);
+ }
+ }
+ virtual StunMessage* CreateNew() const { return new IceMessage(); }
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_STUN_H_
diff --git a/p2p/base/stun_unittest.cc b/p2p/base/stun_unittest.cc
new file mode 100644
index 00000000..396beb6a
--- /dev/null
+++ b/p2p/base/stun_unittest.cc
@@ -0,0 +1,1402 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagedigest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace cricket {
+
+class StunTest : public ::testing::Test {
+ protected:
+ void CheckStunHeader(const StunMessage& msg, StunMessageType expected_type,
+ size_t expected_length) {
+ ASSERT_EQ(expected_type, msg.type());
+ ASSERT_EQ(expected_length, msg.length());
+ }
+
+ void CheckStunTransactionID(const StunMessage& msg,
+ const unsigned char* expectedID, size_t length) {
+ ASSERT_EQ(length, msg.transaction_id().size());
+ ASSERT_EQ(length == kStunTransactionIdLength + 4, msg.IsLegacy());
+ ASSERT_EQ(length == kStunTransactionIdLength, !msg.IsLegacy());
+ ASSERT_EQ(0, memcmp(msg.transaction_id().c_str(), expectedID, length));
+ }
+
+ void CheckStunAddressAttribute(const StunAddressAttribute* addr,
+ StunAddressFamily expected_family,
+ int expected_port,
+ rtc::IPAddress expected_address) {
+ ASSERT_EQ(expected_family, addr->family());
+ ASSERT_EQ(expected_port, addr->port());
+
+ if (addr->family() == STUN_ADDRESS_IPV4) {
+ in_addr v4_address = expected_address.ipv4_address();
+ in_addr stun_address = addr->ipaddr().ipv4_address();
+ ASSERT_EQ(0, memcmp(&v4_address, &stun_address, sizeof(stun_address)));
+ } else if (addr->family() == STUN_ADDRESS_IPV6) {
+ in6_addr v6_address = expected_address.ipv6_address();
+ in6_addr stun_address = addr->ipaddr().ipv6_address();
+ ASSERT_EQ(0, memcmp(&v6_address, &stun_address, sizeof(stun_address)));
+ } else {
+ ASSERT_TRUE(addr->family() == STUN_ADDRESS_IPV6 ||
+ addr->family() == STUN_ADDRESS_IPV4);
+ }
+ }
+
+ size_t ReadStunMessageTestCase(StunMessage* msg,
+ const unsigned char* testcase,
+ size_t size) {
+ const char* input = reinterpret_cast<const char*>(testcase);
+ rtc::ByteBuffer buf(input, size);
+ if (msg->Read(&buf)) {
+ // Returns the size the stun message should report itself as being
+ return (size - 20);
+ } else {
+ return 0;
+ }
+ }
+};
+
+
+// Sample STUN packets with various attributes
+// Gathered by wiresharking pjproject's pjnath test programs
+// pjproject available at www.pjsip.org
+
+static const unsigned char kStunMessageWithIPv6MappedAddress[] = {
+ 0x00, 0x01, 0x00, 0x18, // message header
+ 0x21, 0x12, 0xa4, 0x42, // transaction id
+ 0x29, 0x1f, 0xcd, 0x7c,
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x14, // Address type (mapped), length
+ 0x00, 0x02, 0xb8, 0x81, // family (IPv6), port
+ 0x24, 0x01, 0xfa, 0x00, // an IPv6 address
+ 0x00, 0x04, 0x10, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4MappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x0c, // binding response, length 12
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x08, // Mapped, 8 byte length
+ 0x00, 0x01, 0x9d, 0xfc, // AF_INET, unxor-ed port
+ 0xac, 0x17, 0x44, 0xe6 // IPv4 address
+};
+
+// Test XOR-mapped IP addresses:
+static const unsigned char kStunMessageWithIPv6XorMappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x18, // message header (binding response)
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie (rfc5389)
+ 0xe3, 0xa9, 0x46, 0xe1, // transaction ID
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x20, 0x00, 0x14, // Address Type (XOR), length
+ 0x00, 0x02, 0xcb, 0x5b, // family, XOR-ed port
+ 0x05, 0x13, 0x5e, 0x42, // XOR-ed IPv6 address
+ 0xe3, 0xad, 0x56, 0xe1,
+ 0xc2, 0x30, 0x99, 0x9d,
+ 0xaa, 0xed, 0x01, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4XorMappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x0c, // message header (binding response)
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x20, 0x00, 0x08, // address type (xor), length
+ 0x00, 0x01, 0xfc, 0xb5, // family (AF_INET), XOR-ed port
+ 0x8d, 0x05, 0xe0, 0xa4 // IPv4 address
+};
+
+// ByteString Attribute (username)
+static const unsigned char kStunMessageWithByteStringAttribute[] = {
+ 0x00, 0x01, 0x00, 0x0c,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x06, 0x00, 0x08, // username attribute (length 8)
+ 0x61, 0x62, 0x63, 0x64, // abcdefgh
+ 0x65, 0x66, 0x67, 0x68
+};
+
+// Message with an unknown but comprehensible optional attribute.
+// Parsing should succeed despite this unknown attribute.
+static const unsigned char kStunMessageWithUnknownAttribute[] = {
+ 0x00, 0x01, 0x00, 0x14,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0xaa, 0x00, 0x07, // Unknown attribute, length 7 (needs padding!)
+ 0x61, 0x62, 0x63, 0x64, // abcdefg + padding
+ 0x65, 0x66, 0x67, 0x00,
+ 0x00, 0x06, 0x00, 0x03, // Followed by a known attribute we can
+ 0x61, 0x62, 0x63, 0x00 // check for (username of length 3)
+};
+
+// ByteString Attribute (username) with padding byte
+static const unsigned char kStunMessageWithPaddedByteStringAttribute[] = {
+ 0x00, 0x01, 0x00, 0x08,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x06, 0x00, 0x03, // username attribute (length 3)
+ 0x61, 0x62, 0x63, 0xcc // abc
+};
+
+// Message with an Unknown Attributes (uint16 list) attribute.
+static const unsigned char kStunMessageWithUInt16ListAttribute[] = {
+ 0x00, 0x01, 0x00, 0x0c,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x0a, 0x00, 0x06, // username attribute (length 6)
+ 0x00, 0x01, 0x10, 0x00, // three attributes plus padding
+ 0xAB, 0xCU, 0xBE, 0xEF
+};
+
+// Error response message (unauthorized)
+static const unsigned char kStunMessageWithErrorAttribute[] = {
+ 0x01, 0x11, 0x00, 0x14,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0x29, 0x1f, 0xcd, 0x7c,
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x09, 0x00, 0x10,
+ 0x00, 0x00, 0x04, 0x01,
+ 0x55, 0x6e, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x7a, 0x65, 0x64
+};
+
+// Sample messages with an invalid length Field
+
+// The actual length in bytes of the invalid messages (including STUN header)
+static const int kRealLengthOfInvalidLengthTestCases = 32;
+
+static const unsigned char kStunMessageWithZeroLength[] = {
+ 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes)
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithExcessLength[] = {
+ 0x00, 0x01, 0x00, 0x55, // length of 85
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithSmallLength[] = {
+ 0x00, 0x01, 0x00, 0x03, // length of 3
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+// RTCP packet, for testing we correctly ignore non stun packet types.
+// V=2, P=false, RC=0, Type=200, Len=6, Sender-SSRC=85, etc
+static const unsigned char kRtcpPacket[] = {
+ 0x80, 0xc8, 0x00, 0x06, 0x00, 0x00, 0x00, 0x55,
+ 0xce, 0xa5, 0x18, 0x3a, 0x39, 0xcc, 0x7d, 0x09,
+ 0x23, 0xed, 0x19, 0x07, 0x00, 0x00, 0x01, 0x56,
+ 0x00, 0x03, 0x73, 0x50,
+};
+
+// RFC5769 Test Vectors
+// Software name (request): "STUN test client" (without quotes)
+// Software name (response): "test vector" (without quotes)
+// Username: "evtj:h6vY" (without quotes)
+// Password: "VOkJxbRl1RmTxUk/WvJxBt" (without quotes)
+static const unsigned char kRfc5769SampleMsgTransactionId[] = {
+ 0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae
+};
+static const char kRfc5769SampleMsgClientSoftware[] = "STUN test client";
+static const char kRfc5769SampleMsgServerSoftware[] = "test vector";
+static const char kRfc5769SampleMsgUsername[] = "evtj:h6vY";
+static const char kRfc5769SampleMsgPassword[] = "VOkJxbRl1RmTxUk/WvJxBt";
+static const rtc::SocketAddress kRfc5769SampleMsgMappedAddress(
+ "192.0.2.1", 32853);
+static const rtc::SocketAddress kRfc5769SampleMsgIPv6MappedAddress(
+ "2001:db8:1234:5678:11:2233:4455:6677", 32853);
+
+static const unsigned char kRfc5769SampleMsgWithAuthTransactionId[] = {
+ 0x78, 0xad, 0x34, 0x33, 0xc6, 0xad, 0x72, 0xc0, 0x29, 0xda, 0x41, 0x2e
+};
+static const char kRfc5769SampleMsgWithAuthUsername[] =
+ "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xb9";
+static const char kRfc5769SampleMsgWithAuthPassword[] = "TheMatrIX";
+static const char kRfc5769SampleMsgWithAuthNonce[] =
+ "f//499k954d6OL34oL9FSTvy64sA";
+static const char kRfc5769SampleMsgWithAuthRealm[] = "example.org";
+
+// 2.1. Sample Request
+static const unsigned char kRfc5769SampleRequest[] = {
+ 0x00, 0x01, 0x00, 0x58, // Request type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0xb7, 0xe7, 0xa7, 0x01, // }
+ 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
+ 0xfa, 0x87, 0xdf, 0xae, // }
+ 0x80, 0x22, 0x00, 0x10, // SOFTWARE attribute header
+ 0x53, 0x54, 0x55, 0x4e, // }
+ 0x20, 0x74, 0x65, 0x73, // } User-agent...
+ 0x74, 0x20, 0x63, 0x6c, // } ...name
+ 0x69, 0x65, 0x6e, 0x74, // }
+ 0x00, 0x24, 0x00, 0x04, // PRIORITY attribute header
+ 0x6e, 0x00, 0x01, 0xff, // ICE priority value
+ 0x80, 0x29, 0x00, 0x08, // ICE-CONTROLLED attribute header
+ 0x93, 0x2f, 0xf9, 0xb1, // } Pseudo-random tie breaker...
+ 0x51, 0x26, 0x3b, 0x36, // } ...for ICE control
+ 0x00, 0x06, 0x00, 0x09, // USERNAME attribute header
+ 0x65, 0x76, 0x74, 0x6a, // }
+ 0x3a, 0x68, 0x36, 0x76, // } Username (9 bytes) and padding (3 bytes)
+ 0x59, 0x20, 0x20, 0x20, // }
+ 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header
+ 0x9a, 0xea, 0xa7, 0x0c, // }
+ 0xbf, 0xd8, 0xcb, 0x56, // }
+ 0x78, 0x1e, 0xf2, 0xb5, // } HMAC-SHA1 fingerprint
+ 0xb2, 0xd3, 0xf2, 0x49, // }
+ 0xc1, 0xb5, 0x71, 0xa2, // }
+ 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header
+ 0xe5, 0x7a, 0x3b, 0xcf // CRC32 fingerprint
+};
+
+// 2.2. Sample IPv4 Response
+static const unsigned char kRfc5769SampleResponse[] = {
+ 0x01, 0x01, 0x00, 0x3c, // Response type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0xb7, 0xe7, 0xa7, 0x01, // }
+ 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
+ 0xfa, 0x87, 0xdf, 0xae, // }
+ 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header
+ 0x74, 0x65, 0x73, 0x74, // }
+ 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name
+ 0x74, 0x6f, 0x72, 0x20, // }
+ 0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header
+ 0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port
+ 0xe1, 0x12, 0xa6, 0x43, // Xor'd mapped IPv4 address
+ 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header
+ 0x2b, 0x91, 0xf5, 0x99, // }
+ 0xfd, 0x9e, 0x90, 0xc3, // }
+ 0x8c, 0x74, 0x89, 0xf9, // } HMAC-SHA1 fingerprint
+ 0x2a, 0xf9, 0xba, 0x53, // }
+ 0xf0, 0x6b, 0xe7, 0xd7, // }
+ 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header
+ 0xc0, 0x7d, 0x4c, 0x96 // CRC32 fingerprint
+};
+
+// 2.3. Sample IPv6 Response
+static const unsigned char kRfc5769SampleResponseIPv6[] = {
+ 0x01, 0x01, 0x00, 0x48, // Response type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0xb7, 0xe7, 0xa7, 0x01, // }
+ 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
+ 0xfa, 0x87, 0xdf, 0xae, // }
+ 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header
+ 0x74, 0x65, 0x73, 0x74, // }
+ 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name
+ 0x74, 0x6f, 0x72, 0x20, // }
+ 0x00, 0x20, 0x00, 0x14, // XOR-MAPPED-ADDRESS attribute header
+ 0x00, 0x02, 0xa1, 0x47, // Address family (IPv6) and xor'd mapped port.
+ 0x01, 0x13, 0xa9, 0xfa, // }
+ 0xa5, 0xd3, 0xf1, 0x79, // } Xor'd mapped IPv6 address
+ 0xbc, 0x25, 0xf4, 0xb5, // }
+ 0xbe, 0xd2, 0xb9, 0xd9, // }
+ 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header
+ 0xa3, 0x82, 0x95, 0x4e, // }
+ 0x4b, 0xe6, 0x7b, 0xf1, // }
+ 0x17, 0x84, 0xc9, 0x7c, // } HMAC-SHA1 fingerprint
+ 0x82, 0x92, 0xc2, 0x75, // }
+ 0xbf, 0xe3, 0xed, 0x41, // }
+ 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header
+ 0xc8, 0xfb, 0x0b, 0x4c // CRC32 fingerprint
+};
+
+// 2.4. Sample Request with Long-Term Authentication
+static const unsigned char kRfc5769SampleRequestLongTermAuth[] = {
+ 0x00, 0x01, 0x00, 0x60, // Request type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0x78, 0xad, 0x34, 0x33, // }
+ 0xc6, 0xad, 0x72, 0xc0, // } Transaction ID
+ 0x29, 0xda, 0x41, 0x2e, // }
+ 0x00, 0x06, 0x00, 0x12, // USERNAME attribute header
+ 0xe3, 0x83, 0x9e, 0xe3, // }
+ 0x83, 0x88, 0xe3, 0x83, // }
+ 0xaa, 0xe3, 0x83, 0x83, // } Username value (18 bytes) and padding (2 bytes)
+ 0xe3, 0x82, 0xaf, 0xe3, // }
+ 0x82, 0xb9, 0x00, 0x00, // }
+ 0x00, 0x15, 0x00, 0x1c, // NONCE attribute header
+ 0x66, 0x2f, 0x2f, 0x34, // }
+ 0x39, 0x39, 0x6b, 0x39, // }
+ 0x35, 0x34, 0x64, 0x36, // }
+ 0x4f, 0x4c, 0x33, 0x34, // } Nonce value
+ 0x6f, 0x4c, 0x39, 0x46, // }
+ 0x53, 0x54, 0x76, 0x79, // }
+ 0x36, 0x34, 0x73, 0x41, // }
+ 0x00, 0x14, 0x00, 0x0b, // REALM attribute header
+ 0x65, 0x78, 0x61, 0x6d, // }
+ 0x70, 0x6c, 0x65, 0x2e, // } Realm value (11 bytes) and padding (1 byte)
+ 0x6f, 0x72, 0x67, 0x00, // }
+ 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header
+ 0xf6, 0x70, 0x24, 0x65, // }
+ 0x6d, 0xd6, 0x4a, 0x3e, // }
+ 0x02, 0xb8, 0xe0, 0x71, // } HMAC-SHA1 fingerprint
+ 0x2e, 0x85, 0xc9, 0xa2, // }
+ 0x8c, 0xa8, 0x96, 0x66 // }
+};
+
+// Length parameter is changed to 0x38 from 0x58.
+// AddMessageIntegrity will add MI information and update the length param
+// accordingly.
+static const unsigned char kRfc5769SampleRequestWithoutMI[] = {
+ 0x00, 0x01, 0x00, 0x38, // Request type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0xb7, 0xe7, 0xa7, 0x01, // }
+ 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
+ 0xfa, 0x87, 0xdf, 0xae, // }
+ 0x80, 0x22, 0x00, 0x10, // SOFTWARE attribute header
+ 0x53, 0x54, 0x55, 0x4e, // }
+ 0x20, 0x74, 0x65, 0x73, // } User-agent...
+ 0x74, 0x20, 0x63, 0x6c, // } ...name
+ 0x69, 0x65, 0x6e, 0x74, // }
+ 0x00, 0x24, 0x00, 0x04, // PRIORITY attribute header
+ 0x6e, 0x00, 0x01, 0xff, // ICE priority value
+ 0x80, 0x29, 0x00, 0x08, // ICE-CONTROLLED attribute header
+ 0x93, 0x2f, 0xf9, 0xb1, // } Pseudo-random tie breaker...
+ 0x51, 0x26, 0x3b, 0x36, // } ...for ICE control
+ 0x00, 0x06, 0x00, 0x09, // USERNAME attribute header
+ 0x65, 0x76, 0x74, 0x6a, // }
+ 0x3a, 0x68, 0x36, 0x76, // } Username (9 bytes) and padding (3 bytes)
+ 0x59, 0x20, 0x20, 0x20 // }
+};
+
+// This HMAC differs from the RFC 5769 SampleRequest message. This differs
+// because spec uses 0x20 for the padding where as our implementation uses 0.
+static const unsigned char kCalculatedHmac1[] = {
+ 0x79, 0x07, 0xc2, 0xd2, // }
+ 0xed, 0xbf, 0xea, 0x48, // }
+ 0x0e, 0x4c, 0x76, 0xd8, // } HMAC-SHA1 fingerprint
+ 0x29, 0x62, 0xd5, 0xc3, // }
+ 0x74, 0x2a, 0xf9, 0xe3 // }
+};
+
+// Length parameter is changed to 0x1c from 0x3c.
+// AddMessageIntegrity will add MI information and update the length param
+// accordingly.
+static const unsigned char kRfc5769SampleResponseWithoutMI[] = {
+ 0x01, 0x01, 0x00, 0x1c, // Response type and message length
+ 0x21, 0x12, 0xa4, 0x42, // Magic cookie
+ 0xb7, 0xe7, 0xa7, 0x01, // }
+ 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
+ 0xfa, 0x87, 0xdf, 0xae, // }
+ 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header
+ 0x74, 0x65, 0x73, 0x74, // }
+ 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name
+ 0x74, 0x6f, 0x72, 0x20, // }
+ 0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header
+ 0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port
+ 0xe1, 0x12, 0xa6, 0x43 // Xor'd mapped IPv4 address
+};
+
+// This HMAC differs from the RFC 5769 SampleResponse message. This differs
+// because spec uses 0x20 for the padding where as our implementation uses 0.
+static const unsigned char kCalculatedHmac2[] = {
+ 0x5d, 0x6b, 0x58, 0xbe, // }
+ 0xad, 0x94, 0xe0, 0x7e, // }
+ 0xef, 0x0d, 0xfc, 0x12, // } HMAC-SHA1 fingerprint
+ 0x82, 0xa2, 0xbd, 0x08, // }
+ 0x43, 0x14, 0x10, 0x28 // }
+};
+
+// A transaction ID without the 'magic cookie' portion
+// pjnat's test programs use this transaction ID a lot.
+const unsigned char kTestTransactionId1[] = { 0x029, 0x01f, 0x0cd, 0x07c,
+ 0x0ba, 0x058, 0x0ab, 0x0d7,
+ 0x0f2, 0x041, 0x001, 0x000 };
+
+// They use this one sometimes too.
+const unsigned char kTestTransactionId2[] = { 0x0e3, 0x0a9, 0x046, 0x0e1,
+ 0x07c, 0x000, 0x0c2, 0x062,
+ 0x054, 0x008, 0x001, 0x000 };
+
+const in6_addr kIPv6TestAddress1 = { { { 0x24, 0x01, 0xfa, 0x00,
+ 0x00, 0x04, 0x10, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3 } } };
+const in6_addr kIPv6TestAddress2 = { { { 0x24, 0x01, 0xfa, 0x00,
+ 0x00, 0x04, 0x10, 0x12,
+ 0x06, 0x0c, 0xce, 0xff,
+ 0xfe, 0x1f, 0x61, 0xa4 } } };
+
+#ifdef WEBRTC_POSIX
+const in_addr kIPv4TestAddress1 = { 0xe64417ac };
+#elif defined WEBRTC_WIN
+// Windows in_addr has a union with a uchar[] array first.
+const in_addr kIPv4TestAddress1 = { { 0x0ac, 0x017, 0x044, 0x0e6 } };
+#endif
+const char kTestUserName1[] = "abcdefgh";
+const char kTestUserName2[] = "abc";
+const char kTestErrorReason[] = "Unauthorized";
+const int kTestErrorClass = 4;
+const int kTestErrorNumber = 1;
+const int kTestErrorCode = 401;
+
+const int kTestMessagePort1 = 59977;
+const int kTestMessagePort2 = 47233;
+const int kTestMessagePort3 = 56743;
+const int kTestMessagePort4 = 40444;
+
+#define ReadStunMessage(X, Y) ReadStunMessageTestCase(X, Y, sizeof(Y));
+
+// Test that the GetStun*Type and IsStun*Type methods work as expected.
+TEST_F(StunTest, MessageTypes) {
+ EXPECT_EQ(STUN_BINDING_RESPONSE,
+ GetStunSuccessResponseType(STUN_BINDING_REQUEST));
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE,
+ GetStunErrorResponseType(STUN_BINDING_REQUEST));
+ EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_INDICATION));
+ EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_RESPONSE));
+ EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_ERROR_RESPONSE));
+ EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_INDICATION));
+ EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_RESPONSE));
+ EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_ERROR_RESPONSE));
+
+ int types[] = {
+ STUN_BINDING_REQUEST, STUN_BINDING_INDICATION,
+ STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE
+ };
+ for (int i = 0; i < ARRAY_SIZE(types); ++i) {
+ EXPECT_EQ(i == 0, IsStunRequestType(types[i]));
+ EXPECT_EQ(i == 1, IsStunIndicationType(types[i]));
+ EXPECT_EQ(i == 2, IsStunSuccessResponseType(types[i]));
+ EXPECT_EQ(i == 3, IsStunErrorResponseType(types[i]));
+ EXPECT_EQ(1, types[i] & 0xFEEF);
+ }
+}
+
+TEST_F(StunTest, ReadMessageWithIPv4AddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::IPAddress test_address(kIPv4TestAddress1);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort4, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv4XorAddressAttribute) {
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ rtc::IPAddress test_address(kIPv4TestAddress1);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort3, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6AddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ rtc::IPAddress test_address(kIPv6TestAddress1);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithInvalidAddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ rtc::IPAddress test_address(kIPv6TestAddress1);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6XorAddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+ rtc::IPAddress test_address(kIPv6TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort1, test_address);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN request.
+TEST_F(StunTest, ReadRfc5769RequestMessage) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kRfc5769SampleRequest);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+ kStunTransactionIdLength);
+
+ const StunByteStringAttribute* software =
+ msg.GetByteString(STUN_ATTR_SOFTWARE);
+ ASSERT_TRUE(software != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgClientSoftware, software->GetString());
+
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgUsername, username->GetString());
+
+ // Actual M-I value checked in a later test.
+ ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+
+ // Fingerprint checked in a later test, but double-check the value here.
+ const StunUInt32Attribute* fingerprint =
+ msg.GetUInt32(STUN_ATTR_FINGERPRINT);
+ ASSERT_TRUE(fingerprint != NULL);
+ EXPECT_EQ(0xe57a3bcf, fingerprint->value());
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response.
+TEST_F(StunTest, ReadRfc5769ResponseMessage) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kRfc5769SampleResponse);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+ kStunTransactionIdLength);
+
+ const StunByteStringAttribute* software =
+ msg.GetByteString(STUN_ATTR_SOFTWARE);
+ ASSERT_TRUE(software != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString());
+
+ const StunAddressAttribute* mapped_address =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_address != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgMappedAddress, mapped_address->GetAddress());
+
+ // Actual M-I and fingerprint checked in later tests.
+ ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response for IPv6.
+TEST_F(StunTest, ReadRfc5769ResponseMessageIPv6) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kRfc5769SampleResponseIPv6);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+ kStunTransactionIdLength);
+
+ const StunByteStringAttribute* software =
+ msg.GetByteString(STUN_ATTR_SOFTWARE);
+ ASSERT_TRUE(software != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString());
+
+ const StunAddressAttribute* mapped_address =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_address != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgIPv6MappedAddress, mapped_address->GetAddress());
+
+ // Actual M-I and fingerprint checked in later tests.
+ ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response with auth.
+TEST_F(StunTest, ReadRfc5769RequestMessageLongTermAuth) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kRfc5769SampleRequestLongTermAuth);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kRfc5769SampleMsgWithAuthTransactionId,
+ kStunTransactionIdLength);
+
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgWithAuthUsername, username->GetString());
+
+ const StunByteStringAttribute* nonce =
+ msg.GetByteString(STUN_ATTR_NONCE);
+ ASSERT_TRUE(nonce != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgWithAuthNonce, nonce->GetString());
+
+ const StunByteStringAttribute* realm =
+ msg.GetByteString(STUN_ATTR_REALM);
+ ASSERT_TRUE(realm != NULL);
+ EXPECT_EQ(kRfc5769SampleMsgWithAuthRealm, realm->GetString());
+
+ // No fingerprint, actual M-I checked in later tests.
+ ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) == NULL);
+}
+
+// The RFC3489 packet in this test is the same as
+// kStunMessageWithIPv4MappedAddress, but with a different value where the
+// magic cookie was.
+TEST_F(StunTest, ReadLegacyMessage) {
+ unsigned char rfc3489_packet[sizeof(kStunMessageWithIPv4MappedAddress)];
+ memcpy(rfc3489_packet, kStunMessageWithIPv4MappedAddress,
+ sizeof(kStunMessageWithIPv4MappedAddress));
+ // Overwrite the magic cookie here.
+ memcpy(&rfc3489_packet[4], "ABCD", 4);
+
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, rfc3489_packet);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, &rfc3489_packet[4], kStunTransactionIdLength + 4);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::IPAddress test_address(kIPv4TestAddress1);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort4, test_address);
+}
+
+TEST_F(StunTest, SetIPv6XorAddressAttributeOwner) {
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+ rtc::IPAddress test_address(kIPv6TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort1, test_address);
+
+ // Owner with a different transaction ID.
+ msg2.SetTransactionID("ABCDABCDABCD");
+ StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL);
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ addr2.SetOwner(&msg2);
+ // The internal IP address shouldn't change.
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+ rtc::ByteBuffer correct_buf;
+ rtc::ByteBuffer wrong_buf;
+ EXPECT_TRUE(addr->Write(&correct_buf));
+ EXPECT_TRUE(addr2.Write(&wrong_buf));
+ // But when written out, the buffers should look different.
+ ASSERT_NE(0,
+ memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length()));
+ // And when reading a known good value, the address should be wrong.
+ addr2.Read(&correct_buf);
+ ASSERT_NE(addr->ipaddr(), addr2.ipaddr());
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ // Try writing with no owner at all, should fail and write nothing.
+ addr2.SetOwner(NULL);
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+ wrong_buf.Consume(wrong_buf.Length());
+ EXPECT_FALSE(addr2.Write(&wrong_buf));
+ ASSERT_EQ(0U, wrong_buf.Length());
+}
+
+TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) {
+ // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes
+ // should _not_ be affected by a change in owner. IPv4 XOR address uses the
+ // magic cookie value which is fixed.
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+
+ rtc::IPAddress test_address(kIPv4TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort3, test_address);
+
+ // Owner with a different transaction ID.
+ msg2.SetTransactionID("ABCDABCDABCD");
+ StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL);
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ addr2.SetOwner(&msg2);
+ // The internal IP address shouldn't change.
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+ rtc::ByteBuffer correct_buf;
+ rtc::ByteBuffer wrong_buf;
+ EXPECT_TRUE(addr->Write(&correct_buf));
+ EXPECT_TRUE(addr2.Write(&wrong_buf));
+ // The same address data should be written.
+ ASSERT_EQ(0,
+ memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length()));
+ // And an attribute should be able to un-XOR an address belonging to a message
+ // with a different transaction ID.
+ EXPECT_TRUE(addr2.Read(&correct_buf));
+ ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
+
+ // However, no owner is still an error, should fail and write nothing.
+ addr2.SetOwner(NULL);
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+ wrong_buf.Consume(wrong_buf.Length());
+ EXPECT_FALSE(addr2.Write(&wrong_buf));
+}
+
+TEST_F(StunTest, CreateIPv6AddressAttribute) {
+ rtc::IPAddress test_ip(kIPv6TestAddress2);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_ip);
+ delete addr;
+}
+
+TEST_F(StunTest, CreateIPv4AddressAttribute) {
+ struct in_addr test_in_addr;
+ test_in_addr.s_addr = 0xBEB0B0BE;
+ rtc::IPAddress test_ip(test_in_addr);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort2, test_ip);
+ delete addr;
+}
+
+// Test that we don't care what order we set the parts of an address
+TEST_F(StunTest, CreateAddressInArbitraryOrder) {
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ // Port first
+ addr->SetPort(kTestMessagePort1);
+ addr->SetIP(rtc::IPAddress(kIPv4TestAddress1));
+ ASSERT_EQ(kTestMessagePort1, addr->port());
+ ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr->ipaddr());
+
+ StunAddressAttribute* addr2 =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ // IP first
+ addr2->SetIP(rtc::IPAddress(kIPv4TestAddress1));
+ addr2->SetPort(kTestMessagePort2);
+ ASSERT_EQ(kTestMessagePort2, addr2->port());
+ ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr2->ipaddr());
+
+ delete addr;
+ delete addr2;
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6AddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv6MappedAddress);
+
+ rtc::IPAddress test_ip(kIPv6TestAddress1);
+
+ msg.SetType(STUN_BINDING_REQUEST);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+ EXPECT_TRUE(msg.AddAttribute(addr));
+
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6MappedAddress));
+ int len1 = static_cast<int>(out.Length());
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, memcmp(bytes.c_str(), kStunMessageWithIPv6MappedAddress, len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4AddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv4MappedAddress);
+
+ rtc::IPAddress test_ip(kIPv4TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort4);
+ addr->SetAddress(test_addr);
+ EXPECT_TRUE(msg.AddAttribute(addr));
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4MappedAddress));
+ int len1 = static_cast<int>(out.Length());
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, memcmp(bytes.c_str(), kStunMessageWithIPv4MappedAddress, len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6XorAddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv6XorMappedAddress);
+
+ rtc::IPAddress test_ip(kIPv6TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort1);
+ addr->SetAddress(test_addr);
+ EXPECT_TRUE(msg.AddAttribute(addr));
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6XorMappedAddress));
+ int len1 = static_cast<int>(out.Length());
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0,
+ memcmp(bytes.c_str(), kStunMessageWithIPv6XorMappedAddress, len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4XoreAddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv4XorMappedAddress);
+
+ rtc::IPAddress test_ip(kIPv4TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ rtc::SocketAddress test_addr(test_ip, kTestMessagePort3);
+ addr->SetAddress(test_addr);
+ EXPECT_TRUE(msg.AddAttribute(addr));
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4XorMappedAddress));
+ int len1 = static_cast<int>(out.Length());
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0,
+ memcmp(bytes.c_str(), kStunMessageWithIPv4XorMappedAddress, len1));
+}
+
+TEST_F(StunTest, ReadByteStringAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithByteStringAttribute);
+
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username != NULL);
+ EXPECT_EQ(kTestUserName1, username->GetString());
+}
+
+TEST_F(StunTest, ReadPaddedByteStringAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg,
+ kStunMessageWithPaddedByteStringAttribute);
+ ASSERT_NE(0U, size);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username != NULL);
+ EXPECT_EQ(kTestUserName2, username->GetString());
+}
+
+TEST_F(StunTest, ReadErrorCodeAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithErrorAttribute);
+
+ CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+ const StunErrorCodeAttribute* errorcode = msg.GetErrorCode();
+ ASSERT_TRUE(errorcode != NULL);
+ EXPECT_EQ(kTestErrorClass, errorcode->eclass());
+ EXPECT_EQ(kTestErrorNumber, errorcode->number());
+ EXPECT_EQ(kTestErrorReason, errorcode->reason());
+ EXPECT_EQ(kTestErrorCode, errorcode->code());
+}
+
+TEST_F(StunTest, ReadMessageWithAUInt16ListAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithUInt16ListAttribute);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ const StunUInt16ListAttribute* types = msg.GetUnknownAttributes();
+ ASSERT_TRUE(types != NULL);
+ EXPECT_EQ(3U, types->Size());
+ EXPECT_EQ(0x1U, types->GetType(0));
+ EXPECT_EQ(0x1000U, types->GetType(1));
+ EXPECT_EQ(0xAB0CU, types->GetType(2));
+}
+
+TEST_F(StunTest, ReadMessageWithAnUnknownAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithUnknownAttribute);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+
+ // Parsing should have succeeded and there should be a USERNAME attribute
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username != NULL);
+ EXPECT_EQ(kTestUserName2, username->GetString());
+}
+
+TEST_F(StunTest, WriteMessageWithAnErrorCodeAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithErrorAttribute);
+
+ msg.SetType(STUN_BINDING_ERROR_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+ StunErrorCodeAttribute* errorcode = StunAttribute::CreateErrorCode();
+ errorcode->SetCode(kTestErrorCode);
+ errorcode->SetReason(kTestErrorReason);
+ EXPECT_TRUE(msg.AddAttribute(errorcode));
+ CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(size, out.Length());
+ // No padding.
+ ASSERT_EQ(0, memcmp(out.Data(), kStunMessageWithErrorAttribute, size));
+}
+
+TEST_F(StunTest, WriteMessageWithAUInt16ListAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithUInt16ListAttribute);
+
+ msg.SetType(STUN_BINDING_REQUEST);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ StunUInt16ListAttribute* list = StunAttribute::CreateUnknownAttributes();
+ list->AddType(0x1U);
+ list->AddType(0x1000U);
+ list->AddType(0xAB0CU);
+ EXPECT_TRUE(msg.AddAttribute(list));
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ ASSERT_EQ(size, out.Length());
+ // Check everything up to the padding.
+ ASSERT_EQ(0,
+ memcmp(out.Data(), kStunMessageWithUInt16ListAttribute, size - 2));
+}
+
+// Test that we fail to read messages with invalid lengths.
+void CheckFailureToRead(const unsigned char* testcase, size_t length) {
+ StunMessage msg;
+ const char* input = reinterpret_cast<const char*>(testcase);
+ rtc::ByteBuffer buf(input, length);
+ ASSERT_FALSE(msg.Read(&buf));
+}
+
+TEST_F(StunTest, FailToReadInvalidMessages) {
+ CheckFailureToRead(kStunMessageWithZeroLength,
+ kRealLengthOfInvalidLengthTestCases);
+ CheckFailureToRead(kStunMessageWithSmallLength,
+ kRealLengthOfInvalidLengthTestCases);
+ CheckFailureToRead(kStunMessageWithExcessLength,
+ kRealLengthOfInvalidLengthTestCases);
+}
+
+// Test that we properly fail to read a non-STUN message.
+TEST_F(StunTest, FailToReadRtcpPacket) {
+ CheckFailureToRead(kRtcpPacket, sizeof(kRtcpPacket));
+}
+
+// Check our STUN message validation code against the RFC5769 test messages.
+TEST_F(StunTest, ValidateMessageIntegrity) {
+ // Try the messages from RFC 5769.
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleRequest),
+ sizeof(kRfc5769SampleRequest),
+ kRfc5769SampleMsgPassword));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleRequest),
+ sizeof(kRfc5769SampleRequest),
+ "InvalidPassword"));
+
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleResponse),
+ sizeof(kRfc5769SampleResponse),
+ kRfc5769SampleMsgPassword));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleResponse),
+ sizeof(kRfc5769SampleResponse),
+ "InvalidPassword"));
+
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+ sizeof(kRfc5769SampleResponseIPv6),
+ kRfc5769SampleMsgPassword));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+ sizeof(kRfc5769SampleResponseIPv6),
+ "InvalidPassword"));
+
+ // We first need to compute the key for the long-term authentication HMAC.
+ std::string key;
+ ComputeStunCredentialHash(kRfc5769SampleMsgWithAuthUsername,
+ kRfc5769SampleMsgWithAuthRealm, kRfc5769SampleMsgWithAuthPassword, &key);
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleRequestLongTermAuth),
+ sizeof(kRfc5769SampleRequestLongTermAuth), key));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kRfc5769SampleRequestLongTermAuth),
+ sizeof(kRfc5769SampleRequestLongTermAuth),
+ "InvalidPassword"));
+
+ // Try some edge cases.
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kStunMessageWithZeroLength),
+ sizeof(kStunMessageWithZeroLength),
+ kRfc5769SampleMsgPassword));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kStunMessageWithExcessLength),
+ sizeof(kStunMessageWithExcessLength),
+ kRfc5769SampleMsgPassword));
+ EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(kStunMessageWithSmallLength),
+ sizeof(kStunMessageWithSmallLength),
+ kRfc5769SampleMsgPassword));
+
+ // Test that munging a single bit anywhere in the message causes the
+ // message-integrity check to fail, unless it is after the M-I attribute.
+ char buf[sizeof(kRfc5769SampleRequest)];
+ memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest));
+ for (size_t i = 0; i < sizeof(buf); ++i) {
+ buf[i] ^= 0x01;
+ if (i > 0)
+ buf[i - 1] ^= 0x01;
+ EXPECT_EQ(i >= sizeof(buf) - 8, StunMessage::ValidateMessageIntegrity(
+ buf, sizeof(buf), kRfc5769SampleMsgPassword));
+ }
+}
+
+// Validate that we generate correct MESSAGE-INTEGRITY attributes.
+// Note the use of IceMessage instead of StunMessage; this is necessary because
+// the RFC5769 test messages used include attributes not found in basic STUN.
+TEST_F(StunTest, AddMessageIntegrity) {
+ IceMessage msg;
+ rtc::ByteBuffer buf(
+ reinterpret_cast<const char*>(kRfc5769SampleRequestWithoutMI),
+ sizeof(kRfc5769SampleRequestWithoutMI));
+ EXPECT_TRUE(msg.Read(&buf));
+ EXPECT_TRUE(msg.AddMessageIntegrity(kRfc5769SampleMsgPassword));
+ const StunByteStringAttribute* mi_attr =
+ msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ EXPECT_EQ(20U, mi_attr->length());
+ EXPECT_EQ(0, memcmp(
+ mi_attr->bytes(), kCalculatedHmac1, sizeof(kCalculatedHmac1)));
+
+ rtc::ByteBuffer buf1;
+ EXPECT_TRUE(msg.Write(&buf1));
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(buf1.Data()), buf1.Length(),
+ kRfc5769SampleMsgPassword));
+
+ IceMessage msg2;
+ rtc::ByteBuffer buf2(
+ reinterpret_cast<const char*>(kRfc5769SampleResponseWithoutMI),
+ sizeof(kRfc5769SampleResponseWithoutMI));
+ EXPECT_TRUE(msg2.Read(&buf2));
+ EXPECT_TRUE(msg2.AddMessageIntegrity(kRfc5769SampleMsgPassword));
+ const StunByteStringAttribute* mi_attr2 =
+ msg2.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ EXPECT_EQ(20U, mi_attr2->length());
+ EXPECT_EQ(
+ 0, memcmp(mi_attr2->bytes(), kCalculatedHmac2, sizeof(kCalculatedHmac2)));
+
+ rtc::ByteBuffer buf3;
+ EXPECT_TRUE(msg2.Write(&buf3));
+ EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+ reinterpret_cast<const char*>(buf3.Data()), buf3.Length(),
+ kRfc5769SampleMsgPassword));
+}
+
+// Check our STUN message validation code against the RFC5769 test messages.
+TEST_F(StunTest, ValidateFingerprint) {
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kRfc5769SampleRequest),
+ sizeof(kRfc5769SampleRequest)));
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kRfc5769SampleResponse),
+ sizeof(kRfc5769SampleResponse)));
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+ sizeof(kRfc5769SampleResponseIPv6)));
+
+ EXPECT_FALSE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kStunMessageWithZeroLength),
+ sizeof(kStunMessageWithZeroLength)));
+ EXPECT_FALSE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kStunMessageWithExcessLength),
+ sizeof(kStunMessageWithExcessLength)));
+ EXPECT_FALSE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(kStunMessageWithSmallLength),
+ sizeof(kStunMessageWithSmallLength)));
+
+ // Test that munging a single bit anywhere in the message causes the
+ // fingerprint check to fail.
+ char buf[sizeof(kRfc5769SampleRequest)];
+ memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest));
+ for (size_t i = 0; i < sizeof(buf); ++i) {
+ buf[i] ^= 0x01;
+ if (i > 0)
+ buf[i - 1] ^= 0x01;
+ EXPECT_FALSE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
+ }
+ // Put them all back to normal and the check should pass again.
+ buf[sizeof(buf) - 1] ^= 0x01;
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
+}
+
+TEST_F(StunTest, AddFingerprint) {
+ IceMessage msg;
+ rtc::ByteBuffer buf(
+ reinterpret_cast<const char*>(kRfc5769SampleRequestWithoutMI),
+ sizeof(kRfc5769SampleRequestWithoutMI));
+ EXPECT_TRUE(msg.Read(&buf));
+ EXPECT_TRUE(msg.AddFingerprint());
+
+ rtc::ByteBuffer buf1;
+ EXPECT_TRUE(msg.Write(&buf1));
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ reinterpret_cast<const char*>(buf1.Data()), buf1.Length()));
+}
+
+// Sample "GTURN" relay message.
+static const unsigned char kRelayMessage[] = {
+ 0x00, 0x01, 0x00, 88, // message header
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x01, 0x00, 8, // mapped address
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x06, 0x00, 12, // username
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 0x00, 0x0d, 0x00, 4, // lifetime
+ 0x00, 0x00, 0x00, 11,
+ 0x00, 0x0f, 0x00, 4, // magic cookie
+ 0x72, 0xc6, 0x4b, 0xc6,
+ 0x00, 0x10, 0x00, 4, // bandwidth
+ 0x00, 0x00, 0x00, 6,
+ 0x00, 0x11, 0x00, 8, // destination address
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x12, 0x00, 8, // source address 2
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x13, 0x00, 7, // data
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 0 // DATA must be padded per rfc5766.
+};
+
+// Test that we can read the GTURN-specific fields.
+TEST_F(StunTest, ReadRelayMessage) {
+ RelayMessage msg, msg2;
+
+ const char* input = reinterpret_cast<const char*>(kRelayMessage);
+ size_t size = sizeof(kRelayMessage);
+ rtc::ByteBuffer buf(input, size);
+ EXPECT_TRUE(msg.Read(&buf));
+
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg.type());
+ EXPECT_EQ(size - 20, msg.length());
+ EXPECT_EQ("0123456789ab", msg.transaction_id());
+
+ msg2.SetType(STUN_BINDING_REQUEST);
+ msg2.SetTransactionID("0123456789ab");
+
+ in_addr legacy_in_addr;
+ legacy_in_addr.s_addr = htonl(17U);
+ rtc::IPAddress legacy_ip(legacy_in_addr);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ StunAddressAttribute* addr2 =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ EXPECT_TRUE(msg2.AddAttribute(addr2));
+
+ const StunByteStringAttribute* bytes = msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(12U, bytes->length());
+ EXPECT_EQ("abcdefghijkl", bytes->GetString());
+
+ StunByteStringAttribute* bytes2 =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ bytes2->CopyBytes("abcdefghijkl");
+ EXPECT_TRUE(msg2.AddAttribute(bytes2));
+
+ const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(uval != NULL);
+ EXPECT_EQ(11U, uval->value());
+
+ StunUInt32Attribute* uval2 = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ uval2->SetValue(11);
+ EXPECT_TRUE(msg2.AddAttribute(uval2));
+
+ bytes = msg.GetByteString(STUN_ATTR_MAGIC_COOKIE);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(4U, bytes->length());
+ EXPECT_EQ(0,
+ memcmp(bytes->bytes(),
+ TURN_MAGIC_COOKIE_VALUE,
+ sizeof(TURN_MAGIC_COOKIE_VALUE)));
+
+ bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ bytes2->CopyBytes(reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
+ sizeof(TURN_MAGIC_COOKIE_VALUE));
+ EXPECT_TRUE(msg2.AddAttribute(bytes2));
+
+ uval = msg.GetUInt32(STUN_ATTR_BANDWIDTH);
+ ASSERT_TRUE(uval != NULL);
+ EXPECT_EQ(6U, uval->value());
+
+ uval2 = StunAttribute::CreateUInt32(STUN_ATTR_BANDWIDTH);
+ uval2->SetValue(6);
+ EXPECT_TRUE(msg2.AddAttribute(uval2));
+
+ addr = msg.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ addr2 = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ EXPECT_TRUE(msg2.AddAttribute(addr2));
+
+ addr = msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ addr2 = StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ EXPECT_TRUE(msg2.AddAttribute(addr2));
+
+ bytes = msg.GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(7U, bytes->length());
+ EXPECT_EQ("abcdefg", bytes->GetString());
+
+ bytes2 = StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ bytes2->CopyBytes("abcdefg");
+ EXPECT_TRUE(msg2.AddAttribute(bytes2));
+
+ rtc::ByteBuffer out;
+ EXPECT_TRUE(msg.Write(&out));
+ EXPECT_EQ(size, out.Length());
+ size_t len1 = out.Length();
+ std::string outstring;
+ out.ReadString(&outstring, len1);
+ EXPECT_EQ(0, memcmp(outstring.c_str(), input, len1));
+
+ rtc::ByteBuffer out2;
+ EXPECT_TRUE(msg2.Write(&out2));
+ EXPECT_EQ(size, out2.Length());
+ size_t len2 = out2.Length();
+ std::string outstring2;
+ out2.ReadString(&outstring2, len2);
+ EXPECT_EQ(0, memcmp(outstring2.c_str(), input, len2));
+}
+
+} // namespace cricket
diff --git a/p2p/base/stunport.cc b/p2p/base/stunport.cc
new file mode 100644
index 00000000..ec6232a6
--- /dev/null
+++ b/p2p/base/stunport.cc
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/stunport.h"
+
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/p2p/base/portallocator.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/nethelpers.h"
+
+namespace cricket {
+
+// TODO: Move these to a common place (used in relayport too)
+const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts
+const int RETRY_DELAY = 50; // 50ms, from ICE spec
+const int RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs
+
+// Handles a binding request sent to the STUN server.
+class StunBindingRequest : public StunRequest {
+ public:
+ StunBindingRequest(UDPPort* port, bool keep_alive,
+ const rtc::SocketAddress& addr)
+ : port_(port), keep_alive_(keep_alive), server_addr_(addr) {
+ start_time_ = rtc::Time();
+ }
+
+ virtual ~StunBindingRequest() {
+ }
+
+ const rtc::SocketAddress& server_addr() const { return server_addr_; }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ }
+
+ virtual void OnResponse(StunMessage* response) {
+ const StunAddressAttribute* addr_attr =
+ response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ if (!addr_attr) {
+ LOG(LS_ERROR) << "Binding response missing mapped address.";
+ } else if (addr_attr->family() != STUN_ADDRESS_IPV4 &&
+ addr_attr->family() != STUN_ADDRESS_IPV6) {
+ LOG(LS_ERROR) << "Binding address has bad family";
+ } else {
+ rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
+ port_->OnStunBindingRequestSucceeded(server_addr_, addr);
+ }
+
+ // We will do a keep-alive regardless of whether this request succeeds.
+ // This should have almost no impact on network usage.
+ if (keep_alive_) {
+ port_->requests_.SendDelayed(
+ new StunBindingRequest(port_, true, server_addr_),
+ port_->stun_keepalive_delay());
+ }
+ }
+
+ virtual void OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ if (!attr) {
+ LOG(LS_ERROR) << "Bad allocate response error code";
+ } else {
+ LOG(LS_ERROR) << "Binding error response:"
+ << " class=" << attr->eclass()
+ << " number=" << attr->number()
+ << " reason='" << attr->reason() << "'";
+ }
+
+ port_->OnStunBindingOrResolveRequestFailed(server_addr_);
+
+ if (keep_alive_
+ && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+ port_->requests_.SendDelayed(
+ new StunBindingRequest(port_, true, server_addr_),
+ port_->stun_keepalive_delay());
+ }
+ }
+
+ virtual void OnTimeout() {
+ LOG(LS_ERROR) << "Binding request timed out from "
+ << port_->GetLocalAddress().ToSensitiveString()
+ << " (" << port_->Network()->name() << ")";
+
+ port_->OnStunBindingOrResolveRequestFailed(server_addr_);
+
+ if (keep_alive_
+ && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+ port_->requests_.SendDelayed(
+ new StunBindingRequest(port_, true, server_addr_),
+ RETRY_DELAY);
+ }
+ }
+
+ private:
+ UDPPort* port_;
+ bool keep_alive_;
+ const rtc::SocketAddress server_addr_;
+ uint32 start_time_;
+};
+
+UDPPort::AddressResolver::AddressResolver(
+ rtc::PacketSocketFactory* factory)
+ : socket_factory_(factory) {}
+
+UDPPort::AddressResolver::~AddressResolver() {
+ for (ResolverMap::iterator it = resolvers_.begin();
+ it != resolvers_.end(); ++it) {
+ it->second->Destroy(true);
+ }
+}
+
+void UDPPort::AddressResolver::Resolve(
+ const rtc::SocketAddress& address) {
+ if (resolvers_.find(address) != resolvers_.end())
+ return;
+
+ rtc::AsyncResolverInterface* resolver =
+ socket_factory_->CreateAsyncResolver();
+ resolvers_.insert(
+ std::pair<rtc::SocketAddress, rtc::AsyncResolverInterface*>(
+ address, resolver));
+
+ resolver->SignalDone.connect(this,
+ &UDPPort::AddressResolver::OnResolveResult);
+
+ resolver->Start(address);
+}
+
+bool UDPPort::AddressResolver::GetResolvedAddress(
+ const rtc::SocketAddress& input,
+ int family,
+ rtc::SocketAddress* output) const {
+ ResolverMap::const_iterator it = resolvers_.find(input);
+ if (it == resolvers_.end())
+ return false;
+
+ return it->second->GetResolvedAddress(family, output);
+}
+
+void UDPPort::AddressResolver::OnResolveResult(
+ rtc::AsyncResolverInterface* resolver) {
+ for (ResolverMap::iterator it = resolvers_.begin();
+ it != resolvers_.end(); ++it) {
+ if (it->second == resolver) {
+ SignalDone(it->first, resolver->GetError());
+ return;
+ }
+ }
+}
+
+UDPPort::UDPPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& username, const std::string& password)
+ : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(),
+ username, password),
+ requests_(thread),
+ socket_(socket),
+ error_(0),
+ ready_(false),
+ stun_keepalive_delay_(KEEPALIVE_DELAY) {
+}
+
+UDPPort::UDPPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip, int min_port, int max_port,
+ const std::string& username, const std::string& password)
+ : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port,
+ username, password),
+ requests_(thread),
+ socket_(NULL),
+ error_(0),
+ ready_(false),
+ stun_keepalive_delay_(KEEPALIVE_DELAY) {
+}
+
+bool UDPPort::Init() {
+ if (!SharedSocket()) {
+ ASSERT(socket_ == NULL);
+ socket_ = socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(ip(), 0), min_port(), max_port());
+ if (!socket_) {
+ LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+ return false;
+ }
+ socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
+ }
+ socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
+ socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
+ requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket);
+ return true;
+}
+
+UDPPort::~UDPPort() {
+ if (!SharedSocket())
+ delete socket_;
+}
+
+void UDPPort::PrepareAddress() {
+ ASSERT(requests_.empty());
+ if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
+ OnLocalAddressReady(socket_, socket_->GetLocalAddress());
+ }
+}
+
+void UDPPort::MaybePrepareStunCandidate() {
+ // Sending binding request to the STUN server if address is available to
+ // prepare STUN candidate.
+ if (!server_addresses_.empty()) {
+ SendStunBindingRequests();
+ } else {
+ // Port is done allocating candidates.
+ MaybeSetPortCompleteOrError();
+ }
+}
+
+Connection* UDPPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ if (address.protocol() != "udp")
+ return NULL;
+
+ if (!IsCompatibleAddress(address.address())) {
+ return NULL;
+ }
+
+ if (SharedSocket() && Candidates()[0].type() != LOCAL_PORT_TYPE) {
+ ASSERT(false);
+ return NULL;
+ }
+
+ Connection* conn = new ProxyConnection(this, 0, address);
+ AddConnection(conn);
+ return conn;
+}
+
+int UDPPort::SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ int sent = socket_->SendTo(data, size, addr, options);
+ if (sent < 0) {
+ error_ = socket_->GetError();
+ LOG_J(LS_ERROR, this) << "UDP send of " << size
+ << " bytes failed with error " << error_;
+ }
+ return sent;
+}
+
+int UDPPort::SetOption(rtc::Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int UDPPort::GetOption(rtc::Socket::Option opt, int* value) {
+ return socket_->GetOption(opt, value);
+}
+
+int UDPPort::GetError() {
+ return error_;
+}
+
+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, 0, false);
+ MaybePrepareStunCandidate();
+}
+
+void UDPPort::OnReadPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(socket == socket_);
+ ASSERT(!remote_addr.IsUnresolved());
+
+ // Look for a response from the STUN server.
+ // Even if the response doesn't match one of our outstanding requests, we
+ // will eat it because it might be a response to a retransmitted packet, and
+ // we already cleared the request when we got the first response.
+ if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
+ requests_.CheckResponse(data, size);
+ return;
+ }
+
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size, packet_time);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
+ }
+}
+
+void UDPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ Port::OnReadyToSend();
+}
+
+void UDPPort::SendStunBindingRequests() {
+ // We will keep pinging the stun server to make sure our NAT pin-hole stays
+ // open during the call.
+ ASSERT(requests_.empty());
+
+ for (ServerAddresses::const_iterator it = server_addresses_.begin();
+ it != server_addresses_.end(); ++it) {
+ SendStunBindingRequest(*it);
+ }
+}
+
+void UDPPort::ResolveStunAddress(const rtc::SocketAddress& stun_addr) {
+ if (!resolver_) {
+ resolver_.reset(new AddressResolver(socket_factory()));
+ resolver_->SignalDone.connect(this, &UDPPort::OnResolveResult);
+ }
+
+ resolver_->Resolve(stun_addr);
+}
+
+void UDPPort::OnResolveResult(const rtc::SocketAddress& input,
+ int error) {
+ ASSERT(resolver_.get() != NULL);
+
+ rtc::SocketAddress resolved;
+ if (error != 0 ||
+ !resolver_->GetResolvedAddress(input, ip().family(), &resolved)) {
+ LOG_J(LS_WARNING, this) << "StunPort: stun host lookup received error "
+ << error;
+ OnStunBindingOrResolveRequestFailed(input);
+ return;
+ }
+
+ server_addresses_.erase(input);
+
+ if (server_addresses_.find(resolved) == server_addresses_.end()) {
+ server_addresses_.insert(resolved);
+ SendStunBindingRequest(resolved);
+ }
+}
+
+void UDPPort::SendStunBindingRequest(
+ const rtc::SocketAddress& stun_addr) {
+ if (stun_addr.IsUnresolved()) {
+ ResolveStunAddress(stun_addr);
+
+ } else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
+ // Check if |server_addr_| is compatible with the port's ip.
+ if (IsCompatibleAddress(stun_addr)) {
+ requests_.Send(new StunBindingRequest(this, true, stun_addr));
+ } else {
+ // Since we can't send stun messages to the server, we should mark this
+ // port ready.
+ LOG(LS_WARNING) << "STUN server address is incompatible.";
+ OnStunBindingOrResolveRequestFailed(stun_addr);
+ }
+ }
+}
+
+void UDPPort::OnStunBindingRequestSucceeded(
+ const rtc::SocketAddress& stun_server_addr,
+ const rtc::SocketAddress& stun_reflected_addr) {
+ if (bind_request_succeeded_servers_.find(stun_server_addr) !=
+ bind_request_succeeded_servers_.end()) {
+ return;
+ }
+ bind_request_succeeded_servers_.insert(stun_server_addr);
+
+ // If socket is shared and |stun_reflected_addr| is equal to local socket
+ // address, or if the same address has been added by another STUN server,
+ // then discarding the stun address.
+ // For STUN, related address is the local socket address.
+ if ((!SharedSocket() || stun_reflected_addr != socket_->GetLocalAddress()) &&
+ !HasCandidateWithAddress(stun_reflected_addr)) {
+
+ rtc::SocketAddress related_address = socket_->GetLocalAddress();
+ if (!(candidate_filter() & CF_HOST)) {
+ // If candidate filter doesn't have CF_HOST specified, empty raddr to
+ // avoid local address leakage.
+ related_address = rtc::EmptySocketAddressWithFamily(
+ related_address.family());
+ }
+
+ AddAddress(stun_reflected_addr, socket_->GetLocalAddress(),
+ related_address, UDP_PROTOCOL_NAME, "",
+ STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, false);
+ }
+ MaybeSetPortCompleteOrError();
+}
+
+void UDPPort::OnStunBindingOrResolveRequestFailed(
+ const rtc::SocketAddress& stun_server_addr) {
+ if (bind_request_failed_servers_.find(stun_server_addr) !=
+ bind_request_failed_servers_.end()) {
+ return;
+ }
+ bind_request_failed_servers_.insert(stun_server_addr);
+ MaybeSetPortCompleteOrError();
+}
+
+void UDPPort::MaybeSetPortCompleteOrError() {
+ if (ready_)
+ return;
+
+ // Do not set port ready if we are still waiting for bind responses.
+ const size_t servers_done_bind_request = bind_request_failed_servers_.size() +
+ bind_request_succeeded_servers_.size();
+ if (server_addresses_.size() != servers_done_bind_request) {
+ return;
+ }
+
+ // Setting ready status.
+ ready_ = true;
+
+ // The port is "completed" if there is no stun server provided, or the bind
+ // request succeeded for any stun server, or the socket is shared.
+ if (server_addresses_.empty() ||
+ bind_request_succeeded_servers_.size() > 0 ||
+ SharedSocket()) {
+ SignalPortComplete(this);
+ } else {
+ SignalPortError(this);
+ }
+}
+
+// TODO: merge this with SendTo above.
+void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
+ StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
+ rtc::PacketOptions options(DefaultDscpValue());
+ if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0)
+ PLOG(LERROR, socket_->GetError()) << "sendto";
+}
+
+bool UDPPort::HasCandidateWithAddress(const rtc::SocketAddress& addr) const {
+ const std::vector<Candidate>& existing_candidates = Candidates();
+ std::vector<Candidate>::const_iterator it = existing_candidates.begin();
+ for (; it != existing_candidates.end(); ++it) {
+ if (it->address() == addr)
+ return true;
+ }
+ return false;
+}
+
+} // namespace cricket
diff --git a/p2p/base/stunport.h b/p2p/base/stunport.h
new file mode 100644
index 00000000..eda7bb90
--- /dev/null
+++ b/p2p/base/stunport.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_STUNPORT_H_
+#define WEBRTC_P2P_BASE_STUNPORT_H_
+
+#include <string>
+
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/base/stunrequest.h"
+#include "webrtc/base/asyncpacketsocket.h"
+
+// TODO(mallinath) - Rename stunport.cc|h to udpport.cc|h.
+namespace rtc {
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+// Communicates using the address on the outside of a NAT.
+class UDPPort : public Port {
+ public:
+ static UDPPort* Create(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& username,
+ const std::string& password) {
+ UDPPort* port = new UDPPort(thread, factory, network, socket,
+ username, password);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+
+ static UDPPort* Create(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username,
+ const std::string& password) {
+ UDPPort* port = new UDPPort(thread, factory, network,
+ ip, min_port, max_port,
+ username, password);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+ virtual ~UDPPort();
+
+ rtc::SocketAddress GetLocalAddress() const {
+ return socket_->GetLocalAddress();
+ }
+
+ const ServerAddresses& server_addresses() const {
+ return server_addresses_;
+ }
+ void
+ set_server_addresses(const ServerAddresses& addresses) {
+ server_addresses_ = addresses;
+ }
+
+ virtual void PrepareAddress();
+
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetOption(rtc::Socket::Option opt, int* value);
+ virtual int GetError();
+
+ virtual bool HandleIncomingPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ // All packets given to UDP port will be consumed.
+ OnReadPacket(socket, data, size, remote_addr, packet_time);
+ return true;
+ }
+
+ void set_stun_keepalive_delay(int delay) {
+ stun_keepalive_delay_ = delay;
+ }
+ int stun_keepalive_delay() const {
+ return stun_keepalive_delay_;
+ }
+
+ protected:
+ UDPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username, const std::string& password);
+
+ UDPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, rtc::AsyncPacketSocket* socket,
+ const std::string& username, const std::string& password);
+
+ bool Init();
+
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload);
+
+ void OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
+ const rtc::SocketAddress& address);
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+
+ void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ // This method will send STUN binding request if STUN server address is set.
+ void MaybePrepareStunCandidate();
+
+ void SendStunBindingRequests();
+
+ private:
+ // A helper class which can be called repeatedly to resolve multiple
+ // addresses, as opposed to rtc::AsyncResolverInterface, which can only
+ // resolve one address per instance.
+ class AddressResolver : public sigslot::has_slots<> {
+ public:
+ explicit AddressResolver(rtc::PacketSocketFactory* factory);
+ ~AddressResolver();
+
+ void Resolve(const rtc::SocketAddress& address);
+ bool GetResolvedAddress(const rtc::SocketAddress& input,
+ int family,
+ rtc::SocketAddress* output) const;
+
+ // The signal is sent when resolving the specified address is finished. The
+ // first argument is the input address, the second argument is the error
+ // or 0 if it succeeded.
+ sigslot::signal2<const rtc::SocketAddress&, int> SignalDone;
+
+ private:
+ typedef std::map<rtc::SocketAddress,
+ rtc::AsyncResolverInterface*> ResolverMap;
+
+ void OnResolveResult(rtc::AsyncResolverInterface* resolver);
+
+ rtc::PacketSocketFactory* socket_factory_;
+ ResolverMap resolvers_;
+ };
+
+ // DNS resolution of the STUN server.
+ void ResolveStunAddress(const rtc::SocketAddress& stun_addr);
+ void OnResolveResult(const rtc::SocketAddress& input, int error);
+
+ void SendStunBindingRequest(const rtc::SocketAddress& stun_addr);
+
+ // Below methods handles binding request responses.
+ void OnStunBindingRequestSucceeded(
+ const rtc::SocketAddress& stun_server_addr,
+ const rtc::SocketAddress& stun_reflected_addr);
+ void OnStunBindingOrResolveRequestFailed(
+ const rtc::SocketAddress& stun_server_addr);
+
+ // Sends STUN requests to the server.
+ void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ // TODO(mallinaht) - Move this up to cricket::Port when SignalAddressReady is
+ // changed to SignalPortReady.
+ void MaybeSetPortCompleteOrError();
+
+ bool HasCandidateWithAddress(const rtc::SocketAddress& addr) const;
+
+ ServerAddresses server_addresses_;
+ ServerAddresses bind_request_succeeded_servers_;
+ ServerAddresses bind_request_failed_servers_;
+ StunRequestManager requests_;
+ rtc::AsyncPacketSocket* socket_;
+ int error_;
+ rtc::scoped_ptr<AddressResolver> resolver_;
+ bool ready_;
+ int stun_keepalive_delay_;
+
+ friend class StunBindingRequest;
+};
+
+class StunPort : public UDPPort {
+ public:
+ static StunPort* Create(
+ rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username,
+ const std::string& password,
+ const ServerAddresses& servers) {
+ StunPort* port = new StunPort(thread, factory, network,
+ ip, min_port, max_port,
+ username, password, servers);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+
+ virtual ~StunPort() {}
+
+ virtual void PrepareAddress() {
+ SendStunBindingRequests();
+ }
+
+ protected:
+ StunPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username, const std::string& password,
+ const ServerAddresses& servers)
+ : UDPPort(thread, factory, network, ip, min_port, max_port, username,
+ password) {
+ // UDPPort will set these to local udp, updating these to STUN.
+ set_type(STUN_PORT_TYPE);
+ set_server_addresses(servers);
+ }
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_STUNPORT_H_
diff --git a/p2p/base/stunport_unittest.cc b/p2p/base/stunport_unittest.cc
new file mode 100644
index 00000000..81b68086
--- /dev/null
+++ b/p2p/base/stunport_unittest.cc
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2009 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/stunport.h"
+#include "webrtc/p2p/base/teststunserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using cricket::ServerAddresses;
+using rtc::SocketAddress;
+
+static const SocketAddress kLocalAddr("127.0.0.1", 0);
+static const SocketAddress kStunAddr1("127.0.0.1", 5000);
+static const SocketAddress kStunAddr2("127.0.0.1", 4000);
+static const SocketAddress kBadAddr("0.0.0.1", 5000);
+static const SocketAddress kStunHostnameAddr("localhost", 5000);
+static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000);
+static const int kTimeoutMs = 10000;
+// stun prio = 100 << 24 | 30 (IPV4) << 8 | 256 - 0
+static const uint32 kStunCandidatePriority = 1677729535;
+
+// Tests connecting a StunPort to a fake STUN server (cricket::StunServer)
+// TODO: Use a VirtualSocketServer here. We have to use a
+// PhysicalSocketServer right now since DNS is not part of SocketServer yet.
+class StunPortTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ StunPortTest()
+ : pss_(new rtc::PhysicalSocketServer),
+ ss_(new rtc::VirtualSocketServer(pss_.get())),
+ ss_scope_(ss_.get()),
+ network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32),
+ socket_factory_(rtc::Thread::Current()),
+ stun_server_1_(cricket::TestStunServer::Create(
+ rtc::Thread::Current(), kStunAddr1)),
+ stun_server_2_(cricket::TestStunServer::Create(
+ rtc::Thread::Current(), kStunAddr2)),
+ done_(false), error_(false), stun_keepalive_delay_(0) {
+ }
+
+ const cricket::Port* port() const { return stun_port_.get(); }
+ bool done() const { return done_; }
+ bool error() const { return error_; }
+
+ void CreateStunPort(const rtc::SocketAddress& server_addr) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(server_addr);
+ CreateStunPort(stun_servers);
+ }
+
+ void CreateStunPort(const ServerAddresses& stun_servers) {
+ stun_port_.reset(cricket::StunPort::Create(
+ rtc::Thread::Current(), &socket_factory_, &network_,
+ kLocalAddr.ipaddr(), 0, 0, rtc::CreateRandomString(16),
+ rtc::CreateRandomString(22), stun_servers));
+ stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_);
+ stun_port_->SignalPortComplete.connect(this,
+ &StunPortTest::OnPortComplete);
+ stun_port_->SignalPortError.connect(this,
+ &StunPortTest::OnPortError);
+ }
+
+ void CreateSharedStunPort(const rtc::SocketAddress& server_addr) {
+ socket_.reset(socket_factory_.CreateUdpSocket(
+ rtc::SocketAddress(kLocalAddr.ipaddr(), 0), 0, 0));
+ ASSERT_TRUE(socket_ != NULL);
+ socket_->SignalReadPacket.connect(this, &StunPortTest::OnReadPacket);
+ stun_port_.reset(cricket::UDPPort::Create(
+ rtc::Thread::Current(), &socket_factory_,
+ &network_, socket_.get(),
+ rtc::CreateRandomString(16), rtc::CreateRandomString(22)));
+ ASSERT_TRUE(stun_port_ != NULL);
+ ServerAddresses stun_servers;
+ stun_servers.insert(server_addr);
+ stun_port_->set_server_addresses(stun_servers);
+ stun_port_->SignalPortComplete.connect(this,
+ &StunPortTest::OnPortComplete);
+ stun_port_->SignalPortError.connect(this,
+ &StunPortTest::OnPortError);
+ }
+
+ void PrepareAddress() {
+ stun_port_->PrepareAddress();
+ }
+
+ void OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data,
+ size_t size, const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ stun_port_->HandleIncomingPacket(
+ socket, data, size, remote_addr, rtc::PacketTime());
+ }
+
+ void SendData(const char* data, size_t len) {
+ stun_port_->HandleIncomingPacket(
+ socket_.get(), data, len, rtc::SocketAddress("22.22.22.22", 0),
+ rtc::PacketTime());
+ }
+
+ protected:
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ rtc::InitRandom(NULL, 0);
+
+ }
+
+ void OnPortComplete(cricket::Port* port) {
+ ASSERT_FALSE(done_);
+ done_ = true;
+ error_ = false;
+ }
+ void OnPortError(cricket::Port* port) {
+ done_ = true;
+ error_ = true;
+ }
+ void SetKeepaliveDelay(int delay) {
+ stun_keepalive_delay_ = delay;
+ }
+
+ cricket::TestStunServer* stun_server_1() {
+ return stun_server_1_.get();
+ }
+ cricket::TestStunServer* stun_server_2() {
+ return stun_server_2_.get();
+ }
+
+ private:
+ rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ rtc::scoped_ptr<cricket::UDPPort> stun_port_;
+ rtc::scoped_ptr<cricket::TestStunServer> stun_server_1_;
+ rtc::scoped_ptr<cricket::TestStunServer> stun_server_2_;
+ rtc::scoped_ptr<rtc::AsyncPacketSocket> socket_;
+ bool done_;
+ bool error_;
+ int stun_keepalive_delay_;
+};
+
+// Test that we can create a STUN port
+TEST_F(StunPortTest, TestBasic) {
+ CreateStunPort(kStunAddr1);
+ EXPECT_EQ("stun", port()->Type());
+ EXPECT_EQ(0U, port()->Candidates().size());
+}
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunPortTest, TestPrepareAddress) {
+ CreateStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+
+ // TODO: Add IPv6 tests here, once either physicalsocketserver supports
+ // IPv6, or this test is changed to use VirtualSocketServer.
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunPortTest, TestPrepareAddressFail) {
+ CreateStunPort(kBadAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+}
+
+// Test that we can get an address from a STUN server specified by a hostname.
+TEST_F(StunPortTest, TestPrepareAddressHostname) {
+ CreateStunPort(kStunHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kStunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+// Test that we handle hostname lookup failures properly.
+TEST_F(StunPortTest, TestPrepareAddressHostnameFail) {
+ CreateStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+}
+
+// This test verifies keepalive response messages don't result in
+// additional candidate generation.
+TEST_F(StunPortTest, TestKeepAliveResponse) {
+ SetKeepaliveDelay(500); // 500ms of keepalive delay.
+ CreateStunPort(kStunHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ // Waiting for 1 seond, which will allow us to process
+ // response for keepalive binding request. 500 ms is the keepalive delay.
+ rtc::Thread::Current()->ProcessMessages(1000);
+ ASSERT_EQ(1U, port()->Candidates().size());
+}
+
+// Test that a local candidate can be generated using a shared socket.
+TEST_F(StunPortTest, TestSharedSocketPrepareAddress) {
+ CreateSharedStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+}
+
+// Test that we still a get a local candidate with invalid stun server hostname.
+// Also verifing that UDPPort can receive packets when stun address can't be
+// resolved.
+TEST_F(StunPortTest, TestSharedSocketPrepareAddressInvalidHostname) {
+ CreateSharedStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+
+ // Send data to port after it's ready. This is to make sure, UDP port can
+ // handle data with unresolved stun server address.
+ std::string data = "some random data, sending to cricket::Port.";
+ SendData(data.c_str(), data.length());
+ // No crash is success.
+}
+
+// Test that the same address is added only once if two STUN servers are in use.
+TEST_F(StunPortTest, TestNoDuplicatedAddressWithTwoStunServers) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kStunAddr2);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_EQ(1U, port()->Candidates().size());
+}
+
+// Test that candidates can be allocated for multiple STUN servers, one of which
+// is not reachable.
+TEST_F(StunPortTest, TestMultipleStunServersWithBadServer) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kBadAddr);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_EQ(1U, port()->Candidates().size());
+}
+
+// Test that two candidates are allocated if the two STUN servers return
+// different mapped addresses.
+TEST_F(StunPortTest, TestTwoCandidatesWithTwoStunServersAcrossNat) {
+ const SocketAddress kStunMappedAddr1("77.77.77.77", 0);
+ const SocketAddress kStunMappedAddr2("88.77.77.77", 0);
+ stun_server_1()->set_fake_stun_addr(kStunMappedAddr1);
+ stun_server_2()->set_fake_stun_addr(kStunMappedAddr2);
+
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kStunAddr2);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_EQ(2U, port()->Candidates().size());
+}
diff --git a/p2p/base/stunrequest.cc b/p2p/base/stunrequest.cc
new file mode 100644
index 00000000..65eb027f
--- /dev/null
+++ b/p2p/base/stunrequest.cc
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/stunrequest.h"
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+const uint32 MSG_STUN_SEND = 1;
+
+const int MAX_SENDS = 9;
+const int DELAY_UNIT = 100; // 100 milliseconds
+const int DELAY_MAX_FACTOR = 16;
+
+StunRequestManager::StunRequestManager(rtc::Thread* thread)
+ : thread_(thread) {
+}
+
+StunRequestManager::~StunRequestManager() {
+ while (requests_.begin() != requests_.end()) {
+ StunRequest *request = requests_.begin()->second;
+ requests_.erase(requests_.begin());
+ delete request;
+ }
+}
+
+void StunRequestManager::Send(StunRequest* request) {
+ SendDelayed(request, 0);
+}
+
+void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
+ request->set_manager(this);
+ ASSERT(requests_.find(request->id()) == requests_.end());
+ request->Construct();
+ requests_[request->id()] = request;
+ thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL);
+}
+
+void StunRequestManager::Remove(StunRequest* request) {
+ ASSERT(request->manager() == this);
+ RequestMap::iterator iter = requests_.find(request->id());
+ if (iter != requests_.end()) {
+ ASSERT(iter->second == request);
+ requests_.erase(iter);
+ thread_->Clear(request);
+ }
+}
+
+void StunRequestManager::Clear() {
+ std::vector<StunRequest*> requests;
+ for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i)
+ requests.push_back(i->second);
+
+ for (uint32 i = 0; i < requests.size(); ++i) {
+ // StunRequest destructor calls Remove() which deletes requests
+ // from |requests_|.
+ delete requests[i];
+ }
+}
+
+bool StunRequestManager::CheckResponse(StunMessage* msg) {
+ RequestMap::iterator iter = requests_.find(msg->transaction_id());
+ if (iter == requests_.end())
+ return false;
+
+ StunRequest* request = iter->second;
+ if (msg->type() == GetStunSuccessResponseType(request->type())) {
+ request->OnResponse(msg);
+ } else if (msg->type() == GetStunErrorResponseType(request->type())) {
+ request->OnErrorResponse(msg);
+ } else {
+ LOG(LERROR) << "Received response with wrong type: " << msg->type()
+ << " (expecting "
+ << GetStunSuccessResponseType(request->type()) << ")";
+ return false;
+ }
+
+ delete request;
+ return true;
+}
+
+bool StunRequestManager::CheckResponse(const char* data, size_t size) {
+ // Check the appropriate bytes of the stream to see if they match the
+ // transaction ID of a response we are expecting.
+
+ if (size < 20)
+ return false;
+
+ std::string id;
+ id.append(data + kStunTransactionIdOffset, kStunTransactionIdLength);
+
+ RequestMap::iterator iter = requests_.find(id);
+ if (iter == requests_.end())
+ return false;
+
+ // Parse the STUN message and continue processing as usual.
+
+ rtc::ByteBuffer buf(data, size);
+ rtc::scoped_ptr<StunMessage> response(iter->second->msg_->CreateNew());
+ if (!response->Read(&buf))
+ return false;
+
+ return CheckResponse(response.get());
+}
+
+StunRequest::StunRequest()
+ : count_(0), timeout_(false), manager_(0),
+ msg_(new StunMessage()), tstamp_(0) {
+ msg_->SetTransactionID(
+ rtc::CreateRandomString(kStunTransactionIdLength));
+}
+
+StunRequest::StunRequest(StunMessage* request)
+ : count_(0), timeout_(false), manager_(0),
+ msg_(request), tstamp_(0) {
+ msg_->SetTransactionID(
+ rtc::CreateRandomString(kStunTransactionIdLength));
+}
+
+StunRequest::~StunRequest() {
+ ASSERT(manager_ != NULL);
+ if (manager_) {
+ manager_->Remove(this);
+ manager_->thread_->Clear(this);
+ }
+ delete msg_;
+}
+
+void StunRequest::Construct() {
+ if (msg_->type() == 0) {
+ Prepare(msg_);
+ ASSERT(msg_->type() != 0);
+ }
+}
+
+int StunRequest::type() {
+ ASSERT(msg_ != NULL);
+ return msg_->type();
+}
+
+const StunMessage* StunRequest::msg() const {
+ return msg_;
+}
+
+uint32 StunRequest::Elapsed() const {
+ return rtc::TimeSince(tstamp_);
+}
+
+
+void StunRequest::set_manager(StunRequestManager* manager) {
+ ASSERT(!manager_);
+ manager_ = manager;
+}
+
+void StunRequest::OnMessage(rtc::Message* pmsg) {
+ ASSERT(manager_ != NULL);
+ ASSERT(pmsg->message_id == MSG_STUN_SEND);
+
+ if (timeout_) {
+ OnTimeout();
+ delete this;
+ return;
+ }
+
+ tstamp_ = rtc::Time();
+
+ rtc::ByteBuffer buf;
+ msg_->Write(&buf);
+ manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
+
+ int delay = GetNextDelay();
+ manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL);
+}
+
+int StunRequest::GetNextDelay() {
+ int delay = DELAY_UNIT * rtc::_min(1 << count_, DELAY_MAX_FACTOR);
+ count_ += 1;
+ if (count_ == MAX_SENDS)
+ timeout_ = true;
+ return delay;
+}
+
+} // namespace cricket
diff --git a/p2p/base/stunrequest.h b/p2p/base/stunrequest.h
new file mode 100644
index 00000000..5fefc2ff
--- /dev/null
+++ b/p2p/base/stunrequest.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_STUNREQUEST_H_
+#define WEBRTC_P2P_BASE_STUNREQUEST_H_
+
+#include <map>
+#include <string>
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+class StunRequest;
+
+// Manages a set of STUN requests, sending and resending until we receive a
+// response or determine that the request has timed out.
+class StunRequestManager {
+public:
+ StunRequestManager(rtc::Thread* thread);
+ ~StunRequestManager();
+
+ // Starts sending the given request (perhaps after a delay).
+ void Send(StunRequest* request);
+ void SendDelayed(StunRequest* request, int delay);
+
+ // Removes a stun request that was added previously. This will happen
+ // automatically when a request succeeds, fails, or times out.
+ void Remove(StunRequest* request);
+
+ // Removes all stun requests that were added previously.
+ void Clear();
+
+ // Determines whether the given message is a response to one of the
+ // outstanding requests, and if so, processes it appropriately.
+ bool CheckResponse(StunMessage* msg);
+ bool CheckResponse(const char* data, size_t size);
+
+ bool empty() { return requests_.empty(); }
+
+ // Raised when there are bytes to be sent.
+ sigslot::signal3<const void*, size_t, StunRequest*> SignalSendPacket;
+
+private:
+ typedef std::map<std::string, StunRequest*> RequestMap;
+
+ rtc::Thread* thread_;
+ RequestMap requests_;
+
+ friend class StunRequest;
+};
+
+// Represents an individual request to be sent. The STUN message can either be
+// constructed beforehand or built on demand.
+class StunRequest : public rtc::MessageHandler {
+public:
+ StunRequest();
+ StunRequest(StunMessage* request);
+ virtual ~StunRequest();
+
+ // Causes our wrapped StunMessage to be Prepared
+ void Construct();
+
+ // The manager handling this request (if it has been scheduled for sending).
+ StunRequestManager* manager() { return manager_; }
+
+ // Returns the transaction ID of this request.
+ const std::string& id() { return msg_->transaction_id(); }
+
+ // Returns the STUN type of the request message.
+ int type();
+
+ // Returns a const pointer to |msg_|.
+ const StunMessage* msg() const;
+
+ // Time elapsed since last send (in ms)
+ uint32 Elapsed() const;
+
+protected:
+ int count_;
+ bool timeout_;
+
+ // Fills in a request object to be sent. Note that request's transaction ID
+ // will already be set and cannot be changed.
+ virtual void Prepare(StunMessage* request) {}
+
+ // Called when the message receives a response or times out.
+ virtual void OnResponse(StunMessage* response) {}
+ virtual void OnErrorResponse(StunMessage* response) {}
+ virtual void OnTimeout() {}
+ virtual int GetNextDelay();
+
+private:
+ void set_manager(StunRequestManager* manager);
+
+ // Handles messages for sending and timeout.
+ void OnMessage(rtc::Message* pmsg);
+
+ StunRequestManager* manager_;
+ StunMessage* msg_;
+ uint32 tstamp_;
+
+ friend class StunRequestManager;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_STUNREQUEST_H_
diff --git a/p2p/base/stunrequest_unittest.cc b/p2p/base/stunrequest_unittest.cc
new file mode 100644
index 00000000..3ff6cbaf
--- /dev/null
+++ b/p2p/base/stunrequest_unittest.cc
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/stunrequest.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/timeutils.h"
+
+using namespace cricket;
+
+class StunRequestTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ StunRequestTest()
+ : manager_(rtc::Thread::Current()),
+ request_count_(0), response_(NULL),
+ success_(false), failure_(false), timeout_(false) {
+ manager_.SignalSendPacket.connect(this, &StunRequestTest::OnSendPacket);
+ }
+
+ void OnSendPacket(const void* data, size_t size, StunRequest* req) {
+ request_count_++;
+ }
+
+ void OnResponse(StunMessage* res) {
+ response_ = res;
+ success_ = true;
+ }
+ void OnErrorResponse(StunMessage* res) {
+ response_ = res;
+ failure_ = true;
+ }
+ void OnTimeout() {
+ timeout_ = true;
+ }
+
+ protected:
+ static StunMessage* CreateStunMessage(StunMessageType type,
+ StunMessage* req) {
+ StunMessage* msg = new StunMessage();
+ msg->SetType(type);
+ if (req) {
+ msg->SetTransactionID(req->transaction_id());
+ }
+ return msg;
+ }
+ static int TotalDelay(int sends) {
+ int total = 0;
+ for (int i = 0; i < sends; i++) {
+ if (i < 4)
+ total += 100 << i;
+ else
+ total += 1600;
+ }
+ return total;
+ }
+
+ StunRequestManager manager_;
+ int request_count_;
+ StunMessage* response_;
+ bool success_;
+ bool failure_;
+ bool timeout_;
+};
+
+// Forwards results to the test class.
+class StunRequestThunker : public StunRequest {
+ public:
+ StunRequestThunker(StunMessage* msg, StunRequestTest* test)
+ : StunRequest(msg), test_(test) {}
+ explicit StunRequestThunker(StunRequestTest* test) : test_(test) {}
+ private:
+ virtual void OnResponse(StunMessage* res) {
+ test_->OnResponse(res);
+ }
+ virtual void OnErrorResponse(StunMessage* res) {
+ test_->OnErrorResponse(res);
+ }
+ virtual void OnTimeout() {
+ test_->OnTimeout();
+ }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ }
+
+ StunRequestTest* test_;
+};
+
+// Test handling of a normal binding response.
+TEST_F(StunRequestTest, TestSuccess) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test handling of an error binding response.
+TEST_F(StunRequestTest, TestError) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_FALSE(success_);
+ EXPECT_TRUE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test handling of a binding response with the wrong transaction id.
+TEST_F(StunRequestTest, TestUnexpected) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL);
+ EXPECT_FALSE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test that requests are sent at the right times, and that the 9th request
+// (sent at 7900 ms) can be properly replied to.
+TEST_F(StunRequestTest, TestBackoff) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+
+ uint32 start = rtc::Time();
+ manager_.Send(new StunRequestThunker(req, this));
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+ for (int i = 0; i < 9; ++i) {
+ while (request_count_ == i)
+ rtc::Thread::Current()->ProcessMessages(1);
+ int32 elapsed = rtc::TimeSince(start);
+ LOG(LS_INFO) << "STUN request #" << (i + 1)
+ << " sent at " << elapsed << " ms";
+ EXPECT_GE(TotalDelay(i + 1), elapsed);
+ }
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test that we timeout properly if no response is received in 9500 ms.
+TEST_F(StunRequestTest, TestTimeout) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ rtc::Thread::Current()->ProcessMessages(10000); // > STUN timeout
+ EXPECT_FALSE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_TRUE(timeout_);
+ delete res;
+}
+
+// Regression test for specific crash where we receive a response with the
+// same id as a request that doesn't have an underlying StunMessage yet.
+TEST_F(StunRequestTest, TestNoEmptyRequest) {
+ StunRequestThunker* request = new StunRequestThunker(this);
+
+ manager_.SendDelayed(request, 100);
+
+ StunMessage dummy_req;
+ dummy_req.SetTransactionID(request->id());
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req);
+
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
diff --git a/p2p/base/stunserver.cc b/p2p/base/stunserver.cc
new file mode 100644
index 00000000..fbc316bd
--- /dev/null
+++ b/p2p/base/stunserver.cc
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/stunserver.h"
+
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+StunServer::StunServer(rtc::AsyncUDPSocket* socket) : socket_(socket) {
+ socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
+}
+
+StunServer::~StunServer() {
+ socket_->SignalReadPacket.disconnect(this);
+}
+
+void StunServer::OnPacket(
+ rtc::AsyncPacketSocket* socket, const char* buf, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ // Parse the STUN message; eat any messages that fail to parse.
+ rtc::ByteBuffer bbuf(buf, size);
+ StunMessage msg;
+ if (!msg.Read(&bbuf)) {
+ return;
+ }
+
+ // TODO: If unknown non-optional (<= 0x7fff) attributes are found, send a
+ // 420 "Unknown Attribute" response.
+
+ // Send the message to the appropriate handler function.
+ switch (msg.type()) {
+ case STUN_BINDING_REQUEST:
+ OnBindingRequest(&msg, remote_addr);
+ break;
+
+ default:
+ SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
+ }
+}
+
+void StunServer::OnBindingRequest(
+ StunMessage* msg, const rtc::SocketAddress& remote_addr) {
+ StunMessage response;
+ GetStunBindReqponse(msg, remote_addr, &response);
+ SendResponse(response, remote_addr);
+}
+
+void StunServer::SendErrorResponse(
+ const StunMessage& msg, const rtc::SocketAddress& addr,
+ int error_code, const char* error_desc) {
+ StunMessage err_msg;
+ err_msg.SetType(GetStunErrorResponseType(msg.type()));
+ err_msg.SetTransactionID(msg.transaction_id());
+
+ StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+ err_code->SetCode(error_code);
+ err_code->SetReason(error_desc);
+ err_msg.AddAttribute(err_code);
+
+ SendResponse(err_msg, addr);
+}
+
+void StunServer::SendResponse(
+ const StunMessage& msg, const rtc::SocketAddress& addr) {
+ rtc::ByteBuffer buf;
+ msg.Write(&buf);
+ rtc::PacketOptions options;
+ if (socket_->SendTo(buf.Data(), buf.Length(), addr, options) < 0)
+ LOG_ERR(LS_ERROR) << "sendto";
+}
+
+void StunServer::GetStunBindReqponse(StunMessage* request,
+ const rtc::SocketAddress& remote_addr,
+ StunMessage* response) const {
+ response->SetType(STUN_BINDING_RESPONSE);
+ response->SetTransactionID(request->transaction_id());
+
+ // Tell the user the address that we received their request from.
+ StunAddressAttribute* mapped_addr;
+ if (!request->IsLegacy()) {
+ mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ } else {
+ mapped_addr = StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ }
+ mapped_addr->SetAddress(remote_addr);
+ response->AddAttribute(mapped_addr);
+}
+
+} // namespace cricket
diff --git a/p2p/base/stunserver.h b/p2p/base/stunserver.h
new file mode 100644
index 00000000..a7eeab15
--- /dev/null
+++ b/p2p/base/stunserver.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_STUNSERVER_H_
+#define WEBRTC_P2P_BASE_STUNSERVER_H_
+
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/asyncudpsocket.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace cricket {
+
+const int STUN_SERVER_PORT = 3478;
+
+class StunServer : public sigslot::has_slots<> {
+ public:
+ // Creates a STUN server, which will listen on the given socket.
+ explicit StunServer(rtc::AsyncUDPSocket* socket);
+ // Removes the STUN server from the socket and deletes the socket.
+ ~StunServer();
+
+ protected:
+ // Slot for AsyncSocket.PacketRead:
+ void OnPacket(
+ rtc::AsyncPacketSocket* socket, const char* buf, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+
+ // Handlers for the different types of STUN/TURN requests:
+ virtual void OnBindingRequest(StunMessage* msg,
+ const rtc::SocketAddress& addr);
+ void OnAllocateRequest(StunMessage* msg,
+ const rtc::SocketAddress& addr);
+ void OnSharedSecretRequest(StunMessage* msg,
+ const rtc::SocketAddress& addr);
+ void OnSendRequest(StunMessage* msg,
+ const rtc::SocketAddress& addr);
+
+ // Sends an error response to the given message back to the user.
+ void SendErrorResponse(
+ const StunMessage& msg, const rtc::SocketAddress& addr,
+ int error_code, const char* error_desc);
+
+ // Sends the given message to the appropriate destination.
+ void SendResponse(const StunMessage& msg,
+ const rtc::SocketAddress& addr);
+
+ // A helper method to compose a STUN binding response.
+ void GetStunBindReqponse(StunMessage* request,
+ const rtc::SocketAddress& remote_addr,
+ StunMessage* response) const;
+
+ private:
+ rtc::scoped_ptr<rtc::AsyncUDPSocket> socket_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_STUNSERVER_H_
diff --git a/p2p/base/stunserver_unittest.cc b/p2p/base/stunserver_unittest.cc
new file mode 100644
index 00000000..7266eae8
--- /dev/null
+++ b/p2p/base/stunserver_unittest.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/p2p/base/stunserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/testclient.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using namespace cricket;
+
+static const rtc::SocketAddress server_addr("99.99.99.1", 3478);
+static const rtc::SocketAddress client_addr("1.2.3.4", 1234);
+
+class StunServerTest : public testing::Test {
+ public:
+ StunServerTest()
+ : pss_(new rtc::PhysicalSocketServer),
+ ss_(new rtc::VirtualSocketServer(pss_.get())),
+ worker_(ss_.get()) {
+ }
+ virtual void SetUp() {
+ server_.reset(new StunServer(
+ rtc::AsyncUDPSocket::Create(ss_.get(), server_addr)));
+ client_.reset(new rtc::TestClient(
+ rtc::AsyncUDPSocket::Create(ss_.get(), client_addr)));
+
+ worker_.Start();
+ }
+ void Send(const StunMessage& msg) {
+ rtc::ByteBuffer buf;
+ msg.Write(&buf);
+ Send(buf.Data(), static_cast<int>(buf.Length()));
+ }
+ void Send(const char* buf, int len) {
+ client_->SendTo(buf, len, server_addr);
+ }
+ StunMessage* Receive() {
+ StunMessage* msg = NULL;
+ rtc::TestClient::Packet* packet = client_->NextPacket();
+ if (packet) {
+ rtc::ByteBuffer buf(packet->buf, packet->size);
+ msg = new StunMessage();
+ msg->Read(&buf);
+ delete packet;
+ }
+ return msg;
+ }
+ private:
+ rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::Thread worker_;
+ rtc::scoped_ptr<StunServer> server_;
+ rtc::scoped_ptr<rtc::TestClient> client_;
+};
+
+// Disable for TSan v2, see
+// https://code.google.com/p/webrtc/issues/detail?id=2517 for details.
+#if !defined(THREAD_SANITIZER)
+
+TEST_F(StunServerTest, TestGood) {
+ StunMessage req;
+ std::string transaction_id = "0123456789ab";
+ req.SetType(STUN_BINDING_REQUEST);
+ req.SetTransactionID(transaction_id);
+ Send(req);
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ msg->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ EXPECT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(client_addr.port(), mapped_addr->port());
+ if (mapped_addr->ipaddr() != client_addr.ipaddr()) {
+ LOG(LS_WARNING) << "Warning: mapped IP ("
+ << mapped_addr->ipaddr()
+ << ") != local IP (" << client_addr.ipaddr()
+ << ")";
+ }
+
+ delete msg;
+}
+
+#endif // if !defined(THREAD_SANITIZER)
+
+TEST_F(StunServerTest, TestBad) {
+ const char* bad = "this is a completely nonsensical message whose only "
+ "purpose is to make the parser go 'ack'. it doesn't "
+ "look anything like a normal stun message";
+ Send(bad, static_cast<int>(strlen(bad)));
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg == NULL);
+}
diff --git a/p2p/base/tcpport.cc b/p2p/base/tcpport.cc
new file mode 100644
index 00000000..be3068be
--- /dev/null
+++ b/p2p/base/tcpport.cc
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/tcpport.h"
+
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+TCPPort::TCPPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password, bool allow_listen)
+ : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port,
+ username, password),
+ incoming_only_(false),
+ allow_listen_(allow_listen),
+ socket_(NULL),
+ error_(0) {
+ // TODO(mallinath) - Set preference value as per RFC 6544.
+ // http://b/issue?id=7141794
+}
+
+bool TCPPort::Init() {
+ if (allow_listen_) {
+ // Treat failure to create or bind a TCP socket as fatal. This
+ // should never happen.
+ socket_ = socket_factory()->CreateServerTcpSocket(
+ rtc::SocketAddress(ip(), 0), min_port(), max_port(),
+ false /* ssl */);
+ if (!socket_) {
+ LOG_J(LS_ERROR, this) << "TCP socket creation failed.";
+ return false;
+ }
+ socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
+ socket_->SignalAddressReady.connect(this, &TCPPort::OnAddressReady);
+ }
+ return true;
+}
+
+TCPPort::~TCPPort() {
+ delete socket_;
+ std::list<Incoming>::iterator it;
+ for (it = incoming_.begin(); it != incoming_.end(); ++it)
+ delete it->socket;
+ incoming_.clear();
+}
+
+Connection* TCPPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // We only support TCP protocols
+ if ((address.protocol() != TCP_PROTOCOL_NAME) &&
+ (address.protocol() != SSLTCP_PROTOCOL_NAME)) {
+ 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;
+
+ // Check if we are allowed to make outgoing TCP connections
+ if (incoming_only_ && (origin == ORIGIN_MESSAGE))
+ return NULL;
+
+ // We don't know how to act as an ssl server yet
+ if ((address.protocol() == SSLTCP_PROTOCOL_NAME) &&
+ (origin == ORIGIN_THIS_PORT)) {
+ return NULL;
+ }
+
+ if (!IsCompatibleAddress(address.address())) {
+ return NULL;
+ }
+
+ TCPConnection* conn = NULL;
+ if (rtc::AsyncPacketSocket* socket =
+ GetIncoming(address.address(), true)) {
+ socket->SignalReadPacket.disconnect(this);
+ conn = new TCPConnection(this, address, socket);
+ } else {
+ conn = new TCPConnection(this, address);
+ }
+ AddConnection(conn);
+ return conn;
+}
+
+void TCPPort::PrepareAddress() {
+ 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 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, 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, TCPTYPE_ACTIVE_STR, LOCAL_PORT_TYPE,
+ ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
+ }
+}
+
+int TCPPort::SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ rtc::AsyncPacketSocket * socket = NULL;
+ if (TCPConnection * conn = static_cast<TCPConnection*>(GetConnection(addr))) {
+ socket = conn->socket();
+ } else {
+ socket = GetIncoming(addr);
+ }
+ if (!socket) {
+ LOG_J(LS_ERROR, this) << "Attempted to send to an unknown destination, "
+ << addr.ToSensitiveString();
+ return -1; // TODO: Set error_
+ }
+
+ int sent = socket->Send(data, size, options);
+ if (sent < 0) {
+ error_ = socket->GetError();
+ LOG_J(LS_ERROR, this) << "TCP send of " << size
+ << " bytes failed with error " << error_;
+ }
+ return sent;
+}
+
+int TCPPort::GetOption(rtc::Socket::Option opt, int* value) {
+ if (socket_) {
+ return socket_->GetOption(opt, value);
+ } else {
+ return SOCKET_ERROR;
+ }
+}
+
+int TCPPort::SetOption(rtc::Socket::Option opt, int value) {
+ if (socket_) {
+ return socket_->SetOption(opt, value);
+ } else {
+ return SOCKET_ERROR;
+ }
+}
+
+int TCPPort::GetError() {
+ return error_;
+}
+
+void TCPPort::OnNewConnection(rtc::AsyncPacketSocket* socket,
+ rtc::AsyncPacketSocket* new_socket) {
+ ASSERT(socket == socket_);
+
+ Incoming incoming;
+ incoming.addr = new_socket->GetRemoteAddress();
+ incoming.socket = new_socket;
+ incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket);
+ incoming.socket->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend);
+
+ LOG_J(LS_VERBOSE, this) << "Accepted connection from "
+ << incoming.addr.ToSensitiveString();
+ incoming_.push_back(incoming);
+}
+
+rtc::AsyncPacketSocket* TCPPort::GetIncoming(
+ const rtc::SocketAddress& addr, bool remove) {
+ rtc::AsyncPacketSocket* socket = NULL;
+ for (std::list<Incoming>::iterator it = incoming_.begin();
+ it != incoming_.end(); ++it) {
+ if (it->addr == addr) {
+ socket = it->socket;
+ if (remove)
+ incoming_.erase(it);
+ break;
+ }
+ }
+ return socket;
+}
+
+void TCPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ Port::OnReadPacket(data, size, remote_addr, PROTO_TCP);
+}
+
+void TCPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ Port::OnReadyToSend();
+}
+
+void TCPPort::OnAddressReady(rtc::AsyncPacketSocket* socket,
+ const rtc::SocketAddress& address) {
+ 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,
+ rtc::AsyncPacketSocket* socket)
+ : Connection(port, 0, candidate), socket_(socket), error_(0) {
+ bool outgoing = (socket_ == NULL);
+ if (outgoing) {
+ // TODO: Handle failures here (unlikely since TCP).
+ int opts = (candidate.protocol() == SSLTCP_PROTOCOL_NAME) ?
+ rtc::PacketSocketFactory::OPT_SSLTCP : 0;
+ socket_ = port->socket_factory()->CreateClientTcpSocket(
+ rtc::SocketAddress(port->ip(), 0),
+ candidate.address(), port->proxy(), port->user_agent(), opts);
+ if (socket_) {
+ LOG_J(LS_VERBOSE, this) << "Connecting from "
+ << socket_->GetLocalAddress().ToSensitiveString()
+ << " to "
+ << candidate.address().ToSensitiveString();
+ set_connected(false);
+ socket_->SignalConnect.connect(this, &TCPConnection::OnConnect);
+ } else {
+ LOG_J(LS_WARNING, this) << "Failed to create connection to "
+ << candidate.address().ToSensitiveString();
+ }
+ } else {
+ // Incoming connections should match the network address.
+ ASSERT(socket_->GetLocalAddress().ipaddr() == port->ip());
+ }
+
+ if (socket_) {
+ socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket);
+ socket_->SignalReadyToSend.connect(this, &TCPConnection::OnReadyToSend);
+ socket_->SignalClose.connect(this, &TCPConnection::OnClose);
+ }
+}
+
+TCPConnection::~TCPConnection() {
+ delete socket_;
+}
+
+int TCPConnection::Send(const void* data, size_t size,
+ const rtc::PacketOptions& options) {
+ if (!socket_) {
+ error_ = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ if (write_state() != STATE_WRITABLE) {
+ // TODO: Should STATE_WRITE_TIMEOUT return a non-blocking error?
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ int sent = socket_->Send(data, size, options);
+ if (sent < 0) {
+ error_ = socket_->GetError();
+ } else {
+ send_rate_tracker_.Update(sent);
+ }
+ return sent;
+}
+
+int TCPConnection::GetError() {
+ return error_;
+}
+
+void TCPConnection::OnConnect(rtc::AsyncPacketSocket* socket) {
+ ASSERT(socket == socket_);
+ // Do not use this connection if the socket bound to a different address than
+ // the one we asked for. This is seen in Chrome, where TCP sockets cannot be
+ // given a binding address, and the platform is expected to pick the
+ // correct local address.
+ if (socket->GetLocalAddress().ipaddr() == port()->ip()) {
+ LOG_J(LS_VERBOSE, this) << "Connection established to "
+ << socket->GetRemoteAddress().ToSensitiveString();
+ set_connected(true);
+ } else {
+ LOG_J(LS_WARNING, this) << "Dropping connection as TCP socket bound to a "
+ << "different address from the local candidate.";
+ socket_->Close();
+ }
+}
+
+void TCPConnection::OnClose(rtc::AsyncPacketSocket* socket, int error) {
+ ASSERT(socket == socket_);
+ LOG_J(LS_VERBOSE, this) << "Connection closed with error " << error;
+ set_connected(false);
+ set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void TCPConnection::OnReadPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(socket == socket_);
+ Connection::OnReadPacket(data, size, packet_time);
+}
+
+void TCPConnection::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ ASSERT(socket == socket_);
+ Connection::OnReadyToSend();
+}
+
+} // namespace cricket
diff --git a/p2p/base/tcpport.h b/p2p/base/tcpport.h
new file mode 100644
index 00000000..43e49366
--- /dev/null
+++ b/p2p/base/tcpport.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TCPPORT_H_
+#define WEBRTC_P2P_BASE_TCPPORT_H_
+
+#include <list>
+#include <string>
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/base/asyncpacketsocket.h"
+
+namespace cricket {
+
+class TCPConnection;
+
+// Communicates using a local TCP port.
+//
+// This class is designed to allow subclasses to take advantage of the
+// connection management provided by this class. A subclass should take of all
+// packet sending and preparation, but when a packet is received, it should
+// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection.
+class TCPPort : public Port {
+ public:
+ static TCPPort* Create(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username,
+ const std::string& password,
+ bool allow_listen) {
+ TCPPort* port = new TCPPort(thread, factory, network,
+ ip, min_port, max_port,
+ username, password, allow_listen);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+ virtual ~TCPPort();
+
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+
+ virtual void PrepareAddress();
+
+ virtual int GetOption(rtc::Socket::Option opt, int* value);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetError();
+
+ protected:
+ TCPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,
+ rtc::Network* network, const rtc::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password, bool allow_listen);
+ bool Init();
+
+ // Handles sending using the local TCP socket.
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload);
+
+ // Accepts incoming TCP connection.
+ void OnNewConnection(rtc::AsyncPacketSocket* socket,
+ rtc::AsyncPacketSocket* new_socket);
+
+ private:
+ struct Incoming {
+ rtc::SocketAddress addr;
+ rtc::AsyncPacketSocket* socket;
+ };
+
+ rtc::AsyncPacketSocket* GetIncoming(
+ const rtc::SocketAddress& addr, bool remove = false);
+
+ // Receives packet signal from the local TCP Socket.
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+
+ void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ void OnAddressReady(rtc::AsyncPacketSocket* socket,
+ const rtc::SocketAddress& address);
+
+ // TODO: Is this still needed?
+ bool incoming_only_;
+ bool allow_listen_;
+ rtc::AsyncPacketSocket* socket_;
+ int error_;
+ std::list<Incoming> incoming_;
+
+ friend class TCPConnection;
+};
+
+class TCPConnection : public Connection {
+ public:
+ // Connection is outgoing unless socket is specified
+ TCPConnection(TCPPort* port, const Candidate& candidate,
+ rtc::AsyncPacketSocket* socket = 0);
+ virtual ~TCPConnection();
+
+ virtual int Send(const void* data, size_t size,
+ const rtc::PacketOptions& options);
+ virtual int GetError();
+
+ rtc::AsyncPacketSocket* socket() { return socket_; }
+
+ private:
+ void OnConnect(rtc::AsyncPacketSocket* socket);
+ void OnClose(rtc::AsyncPacketSocket* socket, int error);
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+ void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ rtc::AsyncPacketSocket* socket_;
+ int error_;
+
+ friend class TCPPort;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TCPPORT_H_
diff --git a/p2p/base/testrelayserver.h b/p2p/base/testrelayserver.h
new file mode 100644
index 00000000..87cb9e5d
--- /dev/null
+++ b/p2p/base/testrelayserver.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2008 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TESTRELAYSERVER_H_
+#define WEBRTC_P2P_BASE_TESTRELAYSERVER_H_
+
+#include "webrtc/p2p/base/relayserver.h"
+#include "webrtc/base/asynctcpsocket.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/socketadapters.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+// A test relay server. Useful for unit tests.
+class TestRelayServer : public sigslot::has_slots<> {
+ public:
+ TestRelayServer(rtc::Thread* thread,
+ const rtc::SocketAddress& udp_int_addr,
+ const rtc::SocketAddress& udp_ext_addr,
+ const rtc::SocketAddress& tcp_int_addr,
+ const rtc::SocketAddress& tcp_ext_addr,
+ const rtc::SocketAddress& ssl_int_addr,
+ const rtc::SocketAddress& ssl_ext_addr)
+ : server_(thread) {
+ server_.AddInternalSocket(rtc::AsyncUDPSocket::Create(
+ thread->socketserver(), udp_int_addr));
+ server_.AddExternalSocket(rtc::AsyncUDPSocket::Create(
+ thread->socketserver(), udp_ext_addr));
+
+ tcp_int_socket_.reset(CreateListenSocket(thread, tcp_int_addr));
+ tcp_ext_socket_.reset(CreateListenSocket(thread, tcp_ext_addr));
+ ssl_int_socket_.reset(CreateListenSocket(thread, ssl_int_addr));
+ ssl_ext_socket_.reset(CreateListenSocket(thread, ssl_ext_addr));
+ }
+ int GetConnectionCount() const {
+ return server_.GetConnectionCount();
+ }
+ rtc::SocketAddressPair GetConnection(int connection) const {
+ return server_.GetConnection(connection);
+ }
+ bool HasConnection(const rtc::SocketAddress& address) const {
+ return server_.HasConnection(address);
+ }
+
+ private:
+ rtc::AsyncSocket* CreateListenSocket(rtc::Thread* thread,
+ const rtc::SocketAddress& addr) {
+ rtc::AsyncSocket* socket =
+ thread->socketserver()->CreateAsyncSocket(addr.family(), SOCK_STREAM);
+ socket->Bind(addr);
+ socket->Listen(5);
+ socket->SignalReadEvent.connect(this, &TestRelayServer::OnAccept);
+ return socket;
+ }
+ void OnAccept(rtc::AsyncSocket* socket) {
+ bool external = (socket == tcp_ext_socket_.get() ||
+ socket == ssl_ext_socket_.get());
+ bool ssl = (socket == ssl_int_socket_.get() ||
+ socket == ssl_ext_socket_.get());
+ rtc::AsyncSocket* raw_socket = socket->Accept(NULL);
+ if (raw_socket) {
+ rtc::AsyncTCPSocket* packet_socket = new rtc::AsyncTCPSocket(
+ (!ssl) ? raw_socket :
+ new rtc::AsyncSSLServerSocket(raw_socket), false);
+ if (!external) {
+ packet_socket->SignalClose.connect(this,
+ &TestRelayServer::OnInternalClose);
+ server_.AddInternalSocket(packet_socket);
+ } else {
+ packet_socket->SignalClose.connect(this,
+ &TestRelayServer::OnExternalClose);
+ server_.AddExternalSocket(packet_socket);
+ }
+ }
+ }
+ void OnInternalClose(rtc::AsyncPacketSocket* socket, int error) {
+ server_.RemoveInternalSocket(socket);
+ }
+ void OnExternalClose(rtc::AsyncPacketSocket* socket, int error) {
+ server_.RemoveExternalSocket(socket);
+ }
+ private:
+ cricket::RelayServer server_;
+ rtc::scoped_ptr<rtc::AsyncSocket> tcp_int_socket_;
+ rtc::scoped_ptr<rtc::AsyncSocket> tcp_ext_socket_;
+ rtc::scoped_ptr<rtc::AsyncSocket> ssl_int_socket_;
+ rtc::scoped_ptr<rtc::AsyncSocket> ssl_ext_socket_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TESTRELAYSERVER_H_
diff --git a/p2p/base/teststunserver.h b/p2p/base/teststunserver.h
new file mode 100644
index 00000000..a2a8b6fc
--- /dev/null
+++ b/p2p/base/teststunserver.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2008 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TESTSTUNSERVER_H_
+#define WEBRTC_P2P_BASE_TESTSTUNSERVER_H_
+
+#include "webrtc/p2p/base/stunserver.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+// A test STUN server. Useful for unit tests.
+class TestStunServer : StunServer {
+ public:
+ static TestStunServer* Create(rtc::Thread* thread,
+ const rtc::SocketAddress& addr) {
+ rtc::AsyncSocket* socket =
+ thread->socketserver()->CreateAsyncSocket(addr.family(), SOCK_DGRAM);
+ rtc::AsyncUDPSocket* udp_socket =
+ rtc::AsyncUDPSocket::Create(socket, addr);
+
+ return new TestStunServer(udp_socket);
+ }
+
+ // Set a fake STUN address to return to the client.
+ void set_fake_stun_addr(const rtc::SocketAddress& addr) {
+ fake_stun_addr_ = addr;
+ }
+
+ private:
+ explicit TestStunServer(rtc::AsyncUDPSocket* socket) : StunServer(socket) {}
+
+ void OnBindingRequest(StunMessage* msg,
+ const rtc::SocketAddress& remote_addr) OVERRIDE {
+ if (fake_stun_addr_.IsNil()) {
+ StunServer::OnBindingRequest(msg, remote_addr);
+ } else {
+ StunMessage response;
+ GetStunBindReqponse(msg, fake_stun_addr_, &response);
+ SendResponse(response, remote_addr);
+ }
+ }
+
+ private:
+ rtc::SocketAddress fake_stun_addr_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TESTSTUNSERVER_H_
diff --git a/p2p/base/testturnserver.h b/p2p/base/testturnserver.h
new file mode 100644
index 00000000..19f73e7f
--- /dev/null
+++ b/p2p/base/testturnserver.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TESTTURNSERVER_H_
+#define WEBRTC_P2P_BASE_TESTTURNSERVER_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/p2p/base/turnserver.h"
+#include "webrtc/base/asyncudpsocket.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+static const char kTestRealm[] = "example.org";
+static const char kTestSoftware[] = "TestTurnServer";
+
+class TestTurnRedirector : public TurnRedirectInterface {
+ public:
+ explicit TestTurnRedirector(const std::vector<rtc::SocketAddress>& addresses)
+ : alternate_server_addresses_(addresses),
+ iter_(alternate_server_addresses_.begin()) {
+ }
+
+ virtual bool ShouldRedirect(const rtc::SocketAddress&,
+ rtc::SocketAddress* out) {
+ if (!out || iter_ == alternate_server_addresses_.end()) {
+ return false;
+ }
+ *out = *iter_++;
+ return true;
+ }
+
+ private:
+ const std::vector<rtc::SocketAddress>& alternate_server_addresses_;
+ std::vector<rtc::SocketAddress>::const_iterator iter_;
+};
+
+class TestTurnServer : public TurnAuthInterface {
+ public:
+ TestTurnServer(rtc::Thread* thread,
+ const rtc::SocketAddress& udp_int_addr,
+ const rtc::SocketAddress& udp_ext_addr)
+ : server_(thread) {
+ AddInternalSocket(udp_int_addr, cricket::PROTO_UDP);
+ server_.SetExternalSocketFactory(new rtc::BasicPacketSocketFactory(),
+ udp_ext_addr);
+ server_.set_realm(kTestRealm);
+ server_.set_software(kTestSoftware);
+ server_.set_auth_hook(this);
+ }
+
+ void set_enable_otu_nonce(bool enable) {
+ server_.set_enable_otu_nonce(enable);
+ }
+
+ TurnServer* server() { return &server_; }
+
+ void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
+ server_.set_redirect_hook(redirect_hook);
+ }
+
+ void AddInternalSocket(const rtc::SocketAddress& int_addr,
+ ProtocolType proto) {
+ rtc::Thread* thread = rtc::Thread::Current();
+ if (proto == cricket::PROTO_UDP) {
+ server_.AddInternalSocket(rtc::AsyncUDPSocket::Create(
+ thread->socketserver(), int_addr), proto);
+ } else if (proto == cricket::PROTO_TCP) {
+ // For TCP we need to create a server socket which can listen for incoming
+ // new connections.
+ rtc::AsyncSocket* socket =
+ thread->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+ socket->Bind(int_addr);
+ socket->Listen(5);
+ server_.AddInternalServerSocket(socket, proto);
+ }
+ }
+
+ private:
+ // For this test server, succeed if the password is the same as the username.
+ // Obviously, do not use this in a production environment.
+ virtual bool GetKey(const std::string& username, const std::string& realm,
+ std::string* key) {
+ return ComputeStunCredentialHash(username, realm, username, key);
+ }
+
+ TurnServer server_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TESTTURNSERVER_H_
diff --git a/p2p/base/transport.cc b/p2p/base/transport.cc
new file mode 100644
index 00000000..05c455e4
--- /dev/null
+++ b/p2p/base/transport.cc
@@ -0,0 +1,960 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/transport.h"
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/bind.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+
+namespace cricket {
+
+using rtc::Bind;
+
+enum {
+ MSG_ONSIGNALINGREADY = 1,
+ MSG_ONREMOTECANDIDATE,
+ MSG_READSTATE,
+ MSG_WRITESTATE,
+ MSG_REQUESTSIGNALING,
+ MSG_CANDIDATEREADY,
+ MSG_ROUTECHANGE,
+ MSG_CONNECTING,
+ MSG_CANDIDATEALLOCATIONCOMPLETE,
+ MSG_ROLECONFLICT,
+ MSG_COMPLETED,
+ MSG_FAILED,
+};
+
+struct ChannelParams : public rtc::MessageData {
+ ChannelParams() : channel(NULL), candidate(NULL) {}
+ explicit ChannelParams(int component)
+ : component(component), channel(NULL), candidate(NULL) {}
+ explicit ChannelParams(Candidate* candidate)
+ : channel(NULL), candidate(candidate) {
+ }
+
+ ~ChannelParams() {
+ delete candidate;
+ }
+
+ std::string name;
+ int component;
+ TransportChannelImpl* channel;
+ Candidate* candidate;
+};
+
+static std::string IceProtoToString(TransportProtocol proto) {
+ std::string proto_str;
+ switch (proto) {
+ case ICEPROTO_GOOGLE:
+ proto_str = "gice";
+ break;
+ case ICEPROTO_HYBRID:
+ proto_str = "hybrid";
+ break;
+ case ICEPROTO_RFC5245:
+ proto_str = "ice";
+ break;
+ default:
+ ASSERT(false);
+ break;
+ }
+ return proto_str;
+}
+
+static bool VerifyIceParams(const TransportDescription& desc) {
+ // For legacy protocols.
+ if (desc.ice_ufrag.empty() && desc.ice_pwd.empty())
+ return true;
+
+ if (desc.ice_ufrag.length() < ICE_UFRAG_MIN_LENGTH ||
+ desc.ice_ufrag.length() > ICE_UFRAG_MAX_LENGTH) {
+ return false;
+ }
+ if (desc.ice_pwd.length() < ICE_PWD_MIN_LENGTH ||
+ desc.ice_pwd.length() > ICE_PWD_MAX_LENGTH) {
+ return false;
+ }
+ return true;
+}
+
+bool BadTransportDescription(const std::string& desc, std::string* err_desc) {
+ if (err_desc) {
+ *err_desc = desc;
+ }
+ LOG(LS_ERROR) << desc;
+ return false;
+}
+
+bool IceCredentialsChanged(const std::string& old_ufrag,
+ const std::string& old_pwd,
+ const std::string& new_ufrag,
+ const std::string& new_pwd) {
+ // TODO(jiayl): The standard (RFC 5245 Section 9.1.1.1) says that ICE should
+ // restart when both the ufrag and password are changed, but we do restart
+ // when either ufrag or passwrod is changed to keep compatible with GICE. We
+ // should clean this up when GICE is no longer used.
+ return (old_ufrag != new_ufrag) || (old_pwd != new_pwd);
+}
+
+static bool IceCredentialsChanged(const TransportDescription& old_desc,
+ const TransportDescription& new_desc) {
+ return IceCredentialsChanged(old_desc.ice_ufrag, old_desc.ice_pwd,
+ new_desc.ice_ufrag, new_desc.ice_pwd);
+}
+
+Transport::Transport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ const std::string& type,
+ PortAllocator* allocator)
+ : signaling_thread_(signaling_thread),
+ worker_thread_(worker_thread),
+ content_name_(content_name),
+ type_(type),
+ allocator_(allocator),
+ destroyed_(false),
+ readable_(TRANSPORT_STATE_NONE),
+ writable_(TRANSPORT_STATE_NONE),
+ was_writable_(false),
+ connect_requested_(false),
+ ice_role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ protocol_(ICEPROTO_HYBRID),
+ remote_ice_mode_(ICEMODE_FULL) {
+}
+
+Transport::~Transport() {
+ ASSERT(signaling_thread_->IsCurrent());
+ ASSERT(destroyed_);
+}
+
+void Transport::SetIceRole(IceRole role) {
+ worker_thread_->Invoke<void>(Bind(&Transport::SetIceRole_w, this, role));
+}
+
+void Transport::SetIdentity(rtc::SSLIdentity* identity) {
+ worker_thread_->Invoke<void>(Bind(&Transport::SetIdentity_w, this, identity));
+}
+
+bool Transport::GetIdentity(rtc::SSLIdentity** identity) {
+ // The identity is set on the worker thread, so for safety it must also be
+ // acquired on the worker thread.
+ return worker_thread_->Invoke<bool>(
+ Bind(&Transport::GetIdentity_w, this, identity));
+}
+
+bool Transport::GetRemoteCertificate(rtc::SSLCertificate** cert) {
+ // Channels can be deleted on the worker thread, so for safety the remote
+ // certificate is acquired on the worker thread.
+ return worker_thread_->Invoke<bool>(
+ Bind(&Transport::GetRemoteCertificate_w, this, cert));
+}
+
+bool Transport::GetRemoteCertificate_w(rtc::SSLCertificate** cert) {
+ ASSERT(worker_thread()->IsCurrent());
+ if (channels_.empty())
+ return false;
+
+ ChannelMap::iterator iter = channels_.begin();
+ return iter->second->GetRemoteCertificate(cert);
+}
+
+bool Transport::SetLocalTransportDescription(
+ const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc) {
+ return worker_thread_->Invoke<bool>(Bind(
+ &Transport::SetLocalTransportDescription_w, this,
+ description, action, error_desc));
+}
+
+bool Transport::SetRemoteTransportDescription(
+ const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc) {
+ return worker_thread_->Invoke<bool>(Bind(
+ &Transport::SetRemoteTransportDescription_w, this,
+ description, action, error_desc));
+}
+
+TransportChannelImpl* Transport::CreateChannel(int component) {
+ return worker_thread_->Invoke<TransportChannelImpl*>(Bind(
+ &Transport::CreateChannel_w, this, component));
+}
+
+TransportChannelImpl* Transport::CreateChannel_w(int component) {
+ ASSERT(worker_thread()->IsCurrent());
+ TransportChannelImpl *impl;
+ rtc::CritScope cs(&crit_);
+
+ // Create the entry if it does not exist.
+ bool impl_exists = false;
+ if (channels_.find(component) == channels_.end()) {
+ impl = CreateTransportChannel(component);
+ channels_[component] = ChannelMapEntry(impl);
+ } else {
+ impl = channels_[component].get();
+ impl_exists = true;
+ }
+
+ // Increase the ref count.
+ channels_[component].AddRef();
+ destroyed_ = false;
+
+ if (impl_exists) {
+ // If this is an existing channel, we should just return it without
+ // connecting to all the signal again.
+ return impl;
+ }
+
+ // Push down our transport state to the new channel.
+ impl->SetIceRole(ice_role_);
+ impl->SetIceTiebreaker(tiebreaker_);
+ // TODO(ronghuawu): Change CreateChannel_w to be able to return error since
+ // below Apply**Description_w calls can fail.
+ if (local_description_)
+ ApplyLocalTransportDescription_w(impl, NULL);
+ if (remote_description_)
+ ApplyRemoteTransportDescription_w(impl, NULL);
+ if (local_description_ && remote_description_)
+ ApplyNegotiatedTransportDescription_w(impl, NULL);
+
+ impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState);
+ impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState);
+ impl->SignalRequestSignaling.connect(
+ this, &Transport::OnChannelRequestSignaling);
+ impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady);
+ impl->SignalRouteChange.connect(this, &Transport::OnChannelRouteChange);
+ impl->SignalCandidatesAllocationDone.connect(
+ this, &Transport::OnChannelCandidatesAllocationDone);
+ impl->SignalRoleConflict.connect(this, &Transport::OnRoleConflict);
+ impl->SignalConnectionRemoved.connect(
+ this, &Transport::OnChannelConnectionRemoved);
+
+ if (connect_requested_) {
+ impl->Connect();
+ if (channels_.size() == 1) {
+ // If this is the first channel, then indicate that we have started
+ // connecting.
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+ }
+ return impl;
+}
+
+TransportChannelImpl* Transport::GetChannel(int component) {
+ rtc::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(component);
+ return (iter != channels_.end()) ? iter->second.get() : NULL;
+}
+
+bool Transport::HasChannels() {
+ rtc::CritScope cs(&crit_);
+ return !channels_.empty();
+}
+
+void Transport::DestroyChannel(int component) {
+ worker_thread_->Invoke<void>(Bind(
+ &Transport::DestroyChannel_w, this, component));
+}
+
+void Transport::DestroyChannel_w(int component) {
+ ASSERT(worker_thread()->IsCurrent());
+
+ TransportChannelImpl* impl = NULL;
+ {
+ rtc::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(component);
+ if (iter == channels_.end())
+ return;
+
+ iter->second.DecRef();
+ if (!iter->second.ref()) {
+ impl = iter->second.get();
+ channels_.erase(iter);
+ }
+ }
+
+ if (connect_requested_ && channels_.empty()) {
+ // We're no longer attempting to connect.
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+
+ if (impl) {
+ // Check in case the deleted channel was the only non-writable channel.
+ OnChannelWritableState(impl);
+ DestroyTransportChannel(impl);
+ }
+}
+
+void Transport::ConnectChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread_->Invoke<void>(Bind(&Transport::ConnectChannels_w, this));
+}
+
+void Transport::ConnectChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+ if (connect_requested_ || channels_.empty())
+ return;
+ connect_requested_ = true;
+ signaling_thread()->Post(
+ this, MSG_CANDIDATEREADY, NULL);
+
+ if (!local_description_) {
+ // TOOD(mallinath) : TransportDescription(TD) shouldn't be generated here.
+ // As Transport must know TD is offer or answer and cricket::Transport
+ // doesn't have the capability to decide it. This should be set by the
+ // Session.
+ // Session must generate local TD before remote candidates pushed when
+ // initiate request initiated by the remote.
+ LOG(LS_INFO) << "Transport::ConnectChannels_w: No local description has "
+ << "been set. Will generate one.";
+ TransportDescription desc(NS_GINGLE_P2P, std::vector<std::string>(),
+ rtc::CreateRandomString(ICE_UFRAG_LENGTH),
+ rtc::CreateRandomString(ICE_PWD_LENGTH),
+ ICEMODE_FULL, CONNECTIONROLE_NONE, NULL,
+ Candidates());
+ SetLocalTransportDescription_w(desc, CA_OFFER, NULL);
+ }
+
+ CallChannels_w(&TransportChannelImpl::Connect);
+ if (!channels_.empty()) {
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+}
+
+void Transport::OnConnecting_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ SignalConnecting(this);
+}
+
+void Transport::DestroyAllChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread_->Invoke<void>(
+ Bind(&Transport::DestroyAllChannels_w, this));
+ worker_thread()->Clear(this);
+ signaling_thread()->Clear(this);
+ destroyed_ = true;
+}
+
+void Transport::DestroyAllChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+ std::vector<TransportChannelImpl*> impls;
+ {
+ rtc::CritScope cs(&crit_);
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ iter->second.DecRef();
+ if (!iter->second.ref())
+ impls.push_back(iter->second.get());
+ }
+ }
+ channels_.clear();
+
+
+ for (size_t i = 0; i < impls.size(); ++i)
+ DestroyTransportChannel(impls[i]);
+}
+
+void Transport::ResetChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread_->Invoke<void>(Bind(&Transport::ResetChannels_w, this));
+}
+
+void Transport::ResetChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+
+ // We are no longer attempting to connect
+ connect_requested_ = false;
+
+ // Clear out the old messages, they aren't relevant
+ rtc::CritScope cs(&crit_);
+ ready_candidates_.clear();
+
+ // Reset all of the channels
+ CallChannels_w(&TransportChannelImpl::Reset);
+}
+
+void Transport::OnSignalingReady() {
+ ASSERT(signaling_thread()->IsCurrent());
+ if (destroyed_) return;
+
+ worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL);
+
+ // Notify the subclass.
+ OnTransportSignalingReady();
+}
+
+void Transport::CallChannels_w(TransportChannelFunc func) {
+ ASSERT(worker_thread()->IsCurrent());
+ rtc::CritScope cs(&crit_);
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ ((iter->second.get())->*func)();
+ }
+}
+
+bool Transport::VerifyCandidate(const Candidate& cand, std::string* error) {
+ // No address zero.
+ if (cand.address().IsNil() || cand.address().IsAny()) {
+ *error = "candidate has address of zero";
+ return false;
+ }
+
+ // Disallow all ports below 1024, except for 80 and 443 on public addresses.
+ int port = cand.address().port();
+ 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.
+ // Libjingle clients emit port 0, in "active" mode.
+ return true;
+ }
+ if (port < 1024) {
+ if ((port != 80) && (port != 443)) {
+ *error = "candidate has port below 1024, but not 80 or 443";
+ return false;
+ }
+
+ if (cand.address().IsPrivateIP()) {
+ *error = "candidate has port of 80 or 443 with private IP address";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool Transport::GetStats(TransportStats* stats) {
+ ASSERT(signaling_thread()->IsCurrent());
+ return worker_thread_->Invoke<bool>(Bind(
+ &Transport::GetStats_w, this, stats));
+}
+
+bool Transport::GetStats_w(TransportStats* stats) {
+ ASSERT(worker_thread()->IsCurrent());
+ stats->content_name = content_name();
+ stats->channel_stats.clear();
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ TransportChannelStats substats;
+ substats.component = iter->second->component();
+ if (!iter->second->GetStats(&substats.connection_infos)) {
+ return false;
+ }
+ stats->channel_stats.push_back(substats);
+ }
+ return true;
+}
+
+bool Transport::GetSslRole(rtc::SSLRole* ssl_role) const {
+ return worker_thread_->Invoke<bool>(Bind(
+ &Transport::GetSslRole_w, this, ssl_role));
+}
+
+void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
+ for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+ iter != candidates.end();
+ ++iter) {
+ OnRemoteCandidate(*iter);
+ }
+}
+
+void Transport::OnRemoteCandidate(const Candidate& candidate) {
+ ASSERT(signaling_thread()->IsCurrent());
+ if (destroyed_) return;
+
+ if (!HasChannel(candidate.component())) {
+ LOG(LS_WARNING) << "Ignoring candidate for unknown component "
+ << candidate.component();
+ return;
+ }
+
+ ChannelParams* params = new ChannelParams(new Candidate(candidate));
+ worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, params);
+}
+
+void Transport::OnRemoteCandidate_w(const Candidate& candidate) {
+ ASSERT(worker_thread()->IsCurrent());
+ ChannelMap::iterator iter = channels_.find(candidate.component());
+ // It's ok for a channel to go away while this message is in transit.
+ if (iter != channels_.end()) {
+ iter->second->OnCandidate(candidate);
+ }
+}
+
+void Transport::OnChannelReadableState(TransportChannel* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ signaling_thread()->Post(this, MSG_READSTATE, NULL);
+}
+
+void Transport::OnChannelReadableState_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ TransportState readable = GetTransportState_s(true);
+ if (readable_ != readable) {
+ readable_ = readable;
+ SignalReadableState(this);
+ }
+}
+
+void Transport::OnChannelWritableState(TransportChannel* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ signaling_thread()->Post(this, MSG_WRITESTATE, NULL);
+
+ MaybeCompleted_w();
+}
+
+void Transport::OnChannelWritableState_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ TransportState writable = GetTransportState_s(false);
+ if (writable_ != writable) {
+ was_writable_ = (writable_ == TRANSPORT_STATE_ALL);
+ writable_ = writable;
+ SignalWritableState(this);
+ }
+}
+
+TransportState Transport::GetTransportState_s(bool read) {
+ ASSERT(signaling_thread()->IsCurrent());
+ rtc::CritScope cs(&crit_);
+ bool any = false;
+ bool all = !channels_.empty();
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ bool b = (read ? iter->second->readable() :
+ iter->second->writable());
+ any = any || b;
+ all = all && b;
+ }
+ if (all) {
+ return TRANSPORT_STATE_ALL;
+ } else if (any) {
+ return TRANSPORT_STATE_SOME;
+ } else {
+ return TRANSPORT_STATE_NONE;
+ }
+}
+
+void Transport::OnChannelRequestSignaling(TransportChannelImpl* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ ChannelParams* params = new ChannelParams(channel->component());
+ signaling_thread()->Post(this, MSG_REQUESTSIGNALING, params);
+}
+
+void Transport::OnChannelRequestSignaling_s(int component) {
+ ASSERT(signaling_thread()->IsCurrent());
+ LOG(LS_INFO) << "Transport: " << content_name_ << ", allocating candidates";
+ // Resetting ICE state for the channel.
+ {
+ rtc::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(component);
+ if (iter != channels_.end())
+ iter->second.set_candidates_allocated(false);
+ }
+ SignalRequestSignaling(this);
+}
+
+void Transport::OnChannelCandidateReady(TransportChannelImpl* channel,
+ const Candidate& candidate) {
+ ASSERT(worker_thread()->IsCurrent());
+ rtc::CritScope cs(&crit_);
+ ready_candidates_.push_back(candidate);
+
+ // We hold any messages until the client lets us connect.
+ if (connect_requested_) {
+ signaling_thread()->Post(
+ this, MSG_CANDIDATEREADY, NULL);
+ }
+}
+
+void Transport::OnChannelCandidateReady_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ ASSERT(connect_requested_);
+
+ std::vector<Candidate> candidates;
+ {
+ rtc::CritScope cs(&crit_);
+ candidates.swap(ready_candidates_);
+ }
+
+ // we do the deleting of Candidate* here to keep the new above and
+ // delete below close to each other
+ if (!candidates.empty()) {
+ SignalCandidatesReady(this, candidates);
+ }
+}
+
+void Transport::OnChannelRouteChange(TransportChannel* channel,
+ const Candidate& remote_candidate) {
+ ASSERT(worker_thread()->IsCurrent());
+ ChannelParams* params = new ChannelParams(new Candidate(remote_candidate));
+ params->channel = static_cast<cricket::TransportChannelImpl*>(channel);
+ signaling_thread()->Post(this, MSG_ROUTECHANGE, params);
+}
+
+void Transport::OnChannelRouteChange_s(const TransportChannel* channel,
+ const Candidate& remote_candidate) {
+ ASSERT(signaling_thread()->IsCurrent());
+ SignalRouteChange(this, remote_candidate.component(), remote_candidate);
+}
+
+void Transport::OnChannelCandidatesAllocationDone(
+ TransportChannelImpl* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ rtc::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(channel->component());
+ ASSERT(iter != channels_.end());
+ LOG(LS_INFO) << "Transport: " << content_name_ << ", component "
+ << channel->component() << " allocation complete";
+ iter->second.set_candidates_allocated(true);
+
+ // If all channels belonging to this Transport got signal, then
+ // forward this signal to upper layer.
+ // Can this signal arrive before all transport channels are created?
+ for (iter = channels_.begin(); iter != channels_.end(); ++iter) {
+ if (!iter->second.candidates_allocated())
+ return;
+ }
+ signaling_thread_->Post(this, MSG_CANDIDATEALLOCATIONCOMPLETE);
+
+ MaybeCompleted_w();
+}
+
+void Transport::OnChannelCandidatesAllocationDone_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ LOG(LS_INFO) << "Transport: " << content_name_ << " allocation complete";
+ SignalCandidatesAllocationDone(this);
+}
+
+void Transport::OnRoleConflict(TransportChannelImpl* channel) {
+ signaling_thread_->Post(this, MSG_ROLECONFLICT);
+}
+
+void Transport::OnChannelConnectionRemoved(TransportChannelImpl* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ MaybeCompleted_w();
+
+ // Check if the state is now Failed.
+ // Failed is only available in the Controlling ICE role.
+ if (channel->GetIceRole() != ICEROLE_CONTROLLING) {
+ return;
+ }
+
+ ChannelMap::iterator iter = channels_.find(channel->component());
+ ASSERT(iter != channels_.end());
+ // Failed can only occur after candidate allocation has stopped.
+ if (!iter->second.candidates_allocated()) {
+ return;
+ }
+
+ size_t connections = channel->GetConnectionCount();
+ if (connections == 0) {
+ // A Transport has failed if any of its channels have no remaining
+ // connections.
+ signaling_thread_->Post(this, MSG_FAILED);
+ }
+}
+
+void Transport::MaybeCompleted_w() {
+ ASSERT(worker_thread()->IsCurrent());
+
+ // A Transport's ICE process is completed if all of its channels are writable,
+ // have finished allocating candidates, and have pruned all but one of their
+ // connections.
+ ChannelMap::const_iterator iter;
+ for (iter = channels_.begin(); iter != channels_.end(); ++iter) {
+ const TransportChannelImpl* channel = iter->second.get();
+ if (!(channel->writable() &&
+ channel->GetConnectionCount() == 1 &&
+ channel->GetIceRole() == ICEROLE_CONTROLLING &&
+ iter->second.candidates_allocated())) {
+ return;
+ }
+ }
+
+ signaling_thread_->Post(this, MSG_COMPLETED);
+}
+
+void Transport::SetIceRole_w(IceRole role) {
+ rtc::CritScope cs(&crit_);
+ ice_role_ = role;
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ iter->second->SetIceRole(ice_role_);
+ }
+}
+
+void Transport::SetRemoteIceMode_w(IceMode mode) {
+ rtc::CritScope cs(&crit_);
+ remote_ice_mode_ = mode;
+ // Shouldn't channels be created after this method executed?
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ iter->second->SetRemoteIceMode(remote_ice_mode_);
+ }
+}
+
+bool Transport::SetLocalTransportDescription_w(
+ const TransportDescription& desc,
+ ContentAction action,
+ std::string* error_desc) {
+ bool ret = true;
+ rtc::CritScope cs(&crit_);
+
+ if (!VerifyIceParams(desc)) {
+ return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
+ error_desc);
+ }
+
+ if (local_description_ && IceCredentialsChanged(*local_description_, desc)) {
+ IceRole new_ice_role = (action == CA_OFFER) ? ICEROLE_CONTROLLING
+ : ICEROLE_CONTROLLED;
+
+ // It must be called before ApplyLocalTransportDescription_w, which may
+ // trigger an ICE restart and depends on the new ICE role.
+ SetIceRole_w(new_ice_role);
+ }
+
+ local_description_.reset(new TransportDescription(desc));
+
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ ret &= ApplyLocalTransportDescription_w(iter->second.get(), error_desc);
+ }
+ if (!ret)
+ return false;
+
+ // If PRANSWER/ANSWER is set, we should decide transport protocol type.
+ if (action == CA_PRANSWER || action == CA_ANSWER) {
+ ret &= NegotiateTransportDescription_w(action, error_desc);
+ }
+ return ret;
+}
+
+bool Transport::SetRemoteTransportDescription_w(
+ const TransportDescription& desc,
+ ContentAction action,
+ std::string* error_desc) {
+ bool ret = true;
+ rtc::CritScope cs(&crit_);
+
+ if (!VerifyIceParams(desc)) {
+ return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
+ error_desc);
+ }
+
+ remote_description_.reset(new TransportDescription(desc));
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ ret &= ApplyRemoteTransportDescription_w(iter->second.get(), error_desc);
+ }
+
+ // If PRANSWER/ANSWER is set, we should decide transport protocol type.
+ if (action == CA_PRANSWER || action == CA_ANSWER) {
+ ret = NegotiateTransportDescription_w(CA_OFFER, error_desc);
+ }
+ return ret;
+}
+
+bool Transport::ApplyLocalTransportDescription_w(TransportChannelImpl* ch,
+ std::string* error_desc) {
+ // If existing protocol_type is HYBRID, we may have not chosen the final
+ // protocol type, so update the channel protocol type from the
+ // local description. Otherwise, skip updating the protocol type.
+ // We check for HYBRID to avoid accidental changes; in the case of a
+ // session renegotiation, the new offer will have the google-ice ICE option,
+ // so we need to make sure we don't switch back from ICE mode to HYBRID
+ // when this happens.
+ // There are some other ways we could have solved this, but this is the
+ // simplest. The ultimate solution will be to get rid of GICE altogether.
+ IceProtocolType protocol_type;
+ if (ch->GetIceProtocolType(&protocol_type) &&
+ protocol_type == ICEPROTO_HYBRID) {
+ ch->SetIceProtocolType(
+ TransportProtocolFromDescription(local_description()));
+ }
+ ch->SetIceCredentials(local_description_->ice_ufrag,
+ local_description_->ice_pwd);
+ return true;
+}
+
+bool Transport::ApplyRemoteTransportDescription_w(TransportChannelImpl* ch,
+ std::string* error_desc) {
+ ch->SetRemoteIceCredentials(remote_description_->ice_ufrag,
+ remote_description_->ice_pwd);
+ return true;
+}
+
+bool Transport::ApplyNegotiatedTransportDescription_w(
+ TransportChannelImpl* channel, std::string* error_desc) {
+ channel->SetIceProtocolType(protocol_);
+ channel->SetRemoteIceMode(remote_ice_mode_);
+ return true;
+}
+
+bool Transport::NegotiateTransportDescription_w(ContentAction local_role,
+ std::string* error_desc) {
+ // TODO(ekr@rtfm.com): This is ICE-specific stuff. Refactor into
+ // P2PTransport.
+ const TransportDescription* offer;
+ const TransportDescription* answer;
+
+ if (local_role == CA_OFFER) {
+ offer = local_description_.get();
+ answer = remote_description_.get();
+ } else {
+ offer = remote_description_.get();
+ answer = local_description_.get();
+ }
+
+ TransportProtocol offer_proto = TransportProtocolFromDescription(offer);
+ TransportProtocol answer_proto = TransportProtocolFromDescription(answer);
+
+ // If offered protocol is gice/ice, then we expect to receive matching
+ // protocol in answer, anything else is treated as an error.
+ // HYBRID is not an option when offered specific protocol.
+ // If offered protocol is HYBRID and answered protocol is HYBRID then
+ // gice is preferred protocol.
+ // TODO(mallinath) - Answer from local or remote should't have both ice
+ // and gice support. It should always pick which protocol it wants to use.
+ // Once WebRTC stops supporting gice (for backward compatibility), HYBRID in
+ // answer must be treated as error.
+ if ((offer_proto == ICEPROTO_GOOGLE || offer_proto == ICEPROTO_RFC5245) &&
+ (offer_proto != answer_proto)) {
+ std::ostringstream desc;
+ desc << "Offer and answer protocol mismatch: "
+ << IceProtoToString(offer_proto)
+ << " vs "
+ << IceProtoToString(answer_proto);
+ return BadTransportDescription(desc.str(), error_desc);
+ }
+ protocol_ = answer_proto == ICEPROTO_HYBRID ? ICEPROTO_GOOGLE : answer_proto;
+
+ // If transport is in ICEROLE_CONTROLLED and remote end point supports only
+ // ice_lite, this local end point should take CONTROLLING role.
+ if (ice_role_ == ICEROLE_CONTROLLED &&
+ remote_description_->ice_mode == ICEMODE_LITE) {
+ SetIceRole_w(ICEROLE_CONTROLLING);
+ }
+
+ // Update remote ice_mode to all existing channels.
+ remote_ice_mode_ = remote_description_->ice_mode;
+
+ // Now that we have negotiated everything, push it downward.
+ // Note that we cache the result so that if we have race conditions
+ // between future SetRemote/SetLocal invocations and new channel
+ // creation, we have the negotiation state saved until a new
+ // negotiation happens.
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ if (!ApplyNegotiatedTransportDescription_w(iter->second.get(), error_desc))
+ return false;
+ }
+ return true;
+}
+
+void Transport::OnMessage(rtc::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_ONSIGNALINGREADY:
+ CallChannels_w(&TransportChannelImpl::OnSignalingReady);
+ break;
+ case MSG_ONREMOTECANDIDATE: {
+ ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+ OnRemoteCandidate_w(*params->candidate);
+ delete params;
+ }
+ break;
+ case MSG_CONNECTING:
+ OnConnecting_s();
+ break;
+ case MSG_READSTATE:
+ OnChannelReadableState_s();
+ break;
+ case MSG_WRITESTATE:
+ OnChannelWritableState_s();
+ break;
+ case MSG_REQUESTSIGNALING: {
+ ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+ OnChannelRequestSignaling_s(params->component);
+ delete params;
+ }
+ break;
+ case MSG_CANDIDATEREADY:
+ OnChannelCandidateReady_s();
+ break;
+ case MSG_ROUTECHANGE: {
+ ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+ OnChannelRouteChange_s(params->channel, *params->candidate);
+ delete params;
+ }
+ break;
+ case MSG_CANDIDATEALLOCATIONCOMPLETE:
+ OnChannelCandidatesAllocationDone_s();
+ break;
+ case MSG_ROLECONFLICT:
+ SignalRoleConflict();
+ break;
+ case MSG_COMPLETED:
+ SignalCompleted(this);
+ break;
+ case MSG_FAILED:
+ SignalFailed(this);
+ break;
+ }
+}
+
+bool TransportParser::ParseAddress(const buzz::XmlElement* elem,
+ const buzz::QName& address_name,
+ const buzz::QName& port_name,
+ rtc::SocketAddress* address,
+ ParseError* error) {
+ if (!elem->HasAttr(address_name))
+ return BadParse("address does not have " + address_name.LocalPart(), error);
+ if (!elem->HasAttr(port_name))
+ return BadParse("address does not have " + port_name.LocalPart(), error);
+
+ address->SetIP(elem->Attr(address_name));
+ std::istringstream ist(elem->Attr(port_name));
+ int port = 0;
+ ist >> port;
+ address->SetPort(port);
+
+ return true;
+}
+
+// We're GICE if the namespace is NS_GOOGLE_P2P, or if NS_JINGLE_ICE_UDP is
+// used and the GICE ice-option is set.
+TransportProtocol TransportProtocolFromDescription(
+ const TransportDescription* desc) {
+ ASSERT(desc != NULL);
+ if (desc->transport_type == NS_JINGLE_ICE_UDP) {
+ return (desc->HasOption(ICE_OPTION_GICE)) ?
+ ICEPROTO_HYBRID : ICEPROTO_RFC5245;
+ }
+ return ICEPROTO_GOOGLE;
+}
+
+} // namespace cricket
diff --git a/p2p/base/transport.h b/p2p/base/transport.h
new file mode 100644
index 00000000..ab772fe5
--- /dev/null
+++ b/p2p/base/transport.h
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// A Transport manages a set of named channels of the same type.
+//
+// Subclasses choose the appropriate class to instantiate for each channel;
+// however, this base class keeps track of the channels by name, watches their
+// state changes (in order to update the manager's state), and forwards
+// requests to begin connecting or to reset to each of the channels.
+//
+// On Threading: Transport performs work on both the signaling and worker
+// threads. For subclasses, the rule is that all signaling related calls will
+// be made on the signaling thread and all channel related calls (including
+// signaling for a channel) will be made on the worker thread. When
+// information needs to be sent between the two threads, this class should do
+// the work (e.g., OnRemoteCandidate).
+//
+// Note: Subclasses must call DestroyChannels() in their own constructors.
+// It is not possible to do so here because the subclass constructor will
+// already have run.
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORT_H_
+#define WEBRTC_P2P_BASE_TRANSPORT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/p2p/base/transportinfo.h"
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sslstreamadapter.h"
+
+namespace rtc {
+class Thread;
+}
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class CandidateTranslator;
+class PortAllocator;
+class SessionManager;
+class Session;
+class TransportChannel;
+class TransportChannelImpl;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+
+// Used to parse and serialize (write) transport candidates. For
+// convenience of old code, Transports will implement TransportParser.
+// Parse/Write seems better than Serialize/Deserialize or
+// Create/Translate.
+class TransportParser {
+ public:
+ // The incoming Translator value may be null, in which case
+ // ParseCandidates should return false if there are candidates to
+ // parse (indicating a failure to parse). If the Translator is null
+ // and there are no candidates to parse, then return true,
+ // indicating a successful parse of 0 candidates.
+
+ // Parse or write a transport description, including ICE credentials and
+ // any DTLS fingerprint. Since only Jingle has transport descriptions, these
+ // functions are only used when serializing to Jingle.
+ virtual bool ParseTransportDescription(const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ TransportDescription* tdesc,
+ ParseError* error) = 0;
+ virtual bool WriteTransportDescription(const TransportDescription& tdesc,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** tdesc_elem,
+ WriteError* error) = 0;
+
+
+ // Parse a single candidate. This must be used when parsing Gingle
+ // candidates, since there is no enclosing transport description.
+ virtual bool ParseGingleCandidate(const buzz::XmlElement* elem,
+ const CandidateTranslator* translator,
+ Candidate* candidates,
+ ParseError* error) = 0;
+ virtual bool WriteGingleCandidate(const Candidate& candidate,
+ const CandidateTranslator* translator,
+ buzz::XmlElement** candidate_elem,
+ WriteError* error) = 0;
+
+ // Helper function to parse an element describing an address. This
+ // retrieves the IP and port from the given element and verifies
+ // that they look like plausible values.
+ bool ParseAddress(const buzz::XmlElement* elem,
+ const buzz::QName& address_name,
+ const buzz::QName& port_name,
+ rtc::SocketAddress* address,
+ ParseError* error);
+
+ virtual ~TransportParser() {}
+};
+
+// For "writable" and "readable", we need to differentiate between
+// none, all, and some.
+enum TransportState {
+ TRANSPORT_STATE_NONE = 0,
+ TRANSPORT_STATE_SOME,
+ TRANSPORT_STATE_ALL
+};
+
+// Stats that we can return about the connections for a transport channel.
+// TODO(hta): Rename to ConnectionStats
+struct ConnectionInfo {
+ ConnectionInfo()
+ : best_connection(false),
+ writable(false),
+ readable(false),
+ timeout(false),
+ new_connection(false),
+ rtt(0),
+ sent_total_bytes(0),
+ sent_bytes_second(0),
+ recv_total_bytes(0),
+ recv_bytes_second(0),
+ key(NULL) {}
+
+ bool best_connection; // Is this the best connection we have?
+ bool writable; // Has this connection received a STUN response?
+ bool readable; // Has this connection received a STUN request?
+ bool timeout; // Has this connection timed out?
+ bool new_connection; // Is this a newly created connection?
+ size_t rtt; // The STUN RTT for this connection.
+ size_t sent_total_bytes; // Total bytes sent on this connection.
+ size_t sent_bytes_second; // Bps over the last measurement interval.
+ size_t recv_total_bytes; // Total bytes received on this connection.
+ size_t recv_bytes_second; // Bps over the last measurement interval.
+ Candidate local_candidate; // The local candidate for this connection.
+ Candidate remote_candidate; // The remote candidate for this connection.
+ void* key; // A static value that identifies this conn.
+};
+
+// Information about all the connections of a channel.
+typedef std::vector<ConnectionInfo> ConnectionInfos;
+
+// Information about a specific channel
+struct TransportChannelStats {
+ int component;
+ ConnectionInfos connection_infos;
+};
+
+// Information about all the channels of a transport.
+// TODO(hta): Consider if a simple vector is as good as a map.
+typedef std::vector<TransportChannelStats> TransportChannelStatsList;
+
+// Information about the stats of a transport.
+struct TransportStats {
+ std::string content_name;
+ TransportChannelStatsList channel_stats;
+};
+
+bool BadTransportDescription(const std::string& desc, std::string* err_desc);
+
+bool IceCredentialsChanged(const std::string& old_ufrag,
+ const std::string& old_pwd,
+ const std::string& new_ufrag,
+ const std::string& new_pwd);
+
+class Transport : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ Transport(rtc::Thread* signaling_thread,
+ rtc::Thread* worker_thread,
+ const std::string& content_name,
+ const std::string& type,
+ PortAllocator* allocator);
+ virtual ~Transport();
+
+ // Returns the signaling thread. The app talks to Transport on this thread.
+ rtc::Thread* signaling_thread() { return signaling_thread_; }
+ // Returns the worker thread. The actual networking is done on this thread.
+ rtc::Thread* worker_thread() { return worker_thread_; }
+
+ // Returns the content_name of this transport.
+ const std::string& content_name() const { return content_name_; }
+ // Returns the type of this transport.
+ const std::string& type() const { return type_; }
+
+ // Returns the port allocator object for this transport.
+ PortAllocator* port_allocator() { return allocator_; }
+
+ // Returns the readable and states of this manager. These bits are the ORs
+ // of the corresponding bits on the managed channels. Each time one of these
+ // states changes, a signal is raised.
+ // TODO: Replace uses of readable() and writable() with
+ // any_channels_readable() and any_channels_writable().
+ bool readable() const { return any_channels_readable(); }
+ bool writable() const { return any_channels_writable(); }
+ bool was_writable() const { return was_writable_; }
+ bool any_channels_readable() const {
+ return (readable_ == TRANSPORT_STATE_SOME ||
+ readable_ == TRANSPORT_STATE_ALL);
+ }
+ bool any_channels_writable() const {
+ return (writable_ == TRANSPORT_STATE_SOME ||
+ writable_ == TRANSPORT_STATE_ALL);
+ }
+ bool all_channels_readable() const {
+ return (readable_ == TRANSPORT_STATE_ALL);
+ }
+ bool all_channels_writable() const {
+ return (writable_ == TRANSPORT_STATE_ALL);
+ }
+ sigslot::signal1<Transport*> SignalReadableState;
+ sigslot::signal1<Transport*> SignalWritableState;
+ sigslot::signal1<Transport*> SignalCompleted;
+ sigslot::signal1<Transport*> SignalFailed;
+
+ // Returns whether the client has requested the channels to connect.
+ bool connect_requested() const { return connect_requested_; }
+
+ void SetIceRole(IceRole role);
+ IceRole ice_role() const { return ice_role_; }
+
+ void SetIceTiebreaker(uint64 IceTiebreaker) { tiebreaker_ = IceTiebreaker; }
+ uint64 IceTiebreaker() { return tiebreaker_; }
+
+ // Must be called before applying local session description.
+ void SetIdentity(rtc::SSLIdentity* identity);
+
+ // Get a copy of the local identity provided by SetIdentity.
+ bool GetIdentity(rtc::SSLIdentity** identity);
+
+ // Get a copy of the remote certificate in use by the specified channel.
+ bool GetRemoteCertificate(rtc::SSLCertificate** cert);
+
+ TransportProtocol protocol() const { return protocol_; }
+
+ // Create, destroy, and lookup the channels of this type by their components.
+ TransportChannelImpl* CreateChannel(int component);
+ // Note: GetChannel may lead to race conditions, since the mutex is not held
+ // after the pointer is returned.
+ TransportChannelImpl* GetChannel(int component);
+ // Note: HasChannel does not lead to race conditions, unlike GetChannel.
+ bool HasChannel(int component) {
+ return (NULL != GetChannel(component));
+ }
+ bool HasChannels();
+ void DestroyChannel(int component);
+
+ // Set the local TransportDescription to be used by TransportChannels.
+ // This should be called before ConnectChannels().
+ bool SetLocalTransportDescription(const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc);
+
+ // Set the remote TransportDescription to be used by TransportChannels.
+ bool SetRemoteTransportDescription(const TransportDescription& description,
+ ContentAction action,
+ std::string* error_desc);
+
+ // Tells all current and future channels to start connecting. When the first
+ // channel begins connecting, the following signal is raised.
+ void ConnectChannels();
+ sigslot::signal1<Transport*> SignalConnecting;
+
+ // Resets all of the channels back to their initial state. They are no
+ // longer connecting.
+ void ResetChannels();
+
+ // Destroys every channel created so far.
+ void DestroyAllChannels();
+
+ bool GetStats(TransportStats* stats);
+
+ // Before any stanza is sent, the manager will request signaling. Once
+ // signaling is available, the client should call OnSignalingReady. Once
+ // this occurs, the transport (or its channels) can send any waiting stanzas.
+ // OnSignalingReady invokes OnTransportSignalingReady and then forwards this
+ // signal to each channel.
+ sigslot::signal1<Transport*> SignalRequestSignaling;
+ void OnSignalingReady();
+
+ // Handles sending of ready candidates and receiving of remote candidates.
+ sigslot::signal2<Transport*,
+ const std::vector<Candidate>&> SignalCandidatesReady;
+
+ sigslot::signal1<Transport*> SignalCandidatesAllocationDone;
+ void OnRemoteCandidates(const std::vector<Candidate>& candidates);
+
+ // If candidate is not acceptable, returns false and sets error.
+ // Call this before calling OnRemoteCandidates.
+ virtual bool VerifyCandidate(const Candidate& candidate,
+ std::string* error);
+
+ // Signals when the best connection for a channel changes.
+ sigslot::signal3<Transport*,
+ int, // component
+ const Candidate&> SignalRouteChange;
+
+ // A transport message has generated an transport-specific error. The
+ // stanza that caused the error is available in session_msg. If false is
+ // returned, the error is considered unrecoverable, and the session is
+ // terminated.
+ // TODO(juberti): Remove these obsolete functions once Session no longer
+ // references them.
+ virtual void OnTransportError(const buzz::XmlElement* error) {}
+ sigslot::signal6<Transport*, const buzz::XmlElement*, const buzz::QName&,
+ const std::string&, const std::string&,
+ const buzz::XmlElement*>
+ SignalTransportError;
+
+ // Forwards the signal from TransportChannel to BaseSession.
+ sigslot::signal0<> SignalRoleConflict;
+
+ virtual bool GetSslRole(rtc::SSLRole* ssl_role) const;
+
+ protected:
+ // These are called by Create/DestroyChannel above in order to create or
+ // destroy the appropriate type of channel.
+ virtual TransportChannelImpl* CreateTransportChannel(int component) = 0;
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0;
+
+ // Informs the subclass that we received the signaling ready message.
+ virtual void OnTransportSignalingReady() {}
+
+ // The current local transport description, for use by derived classes
+ // when performing transport description negotiation.
+ const TransportDescription* local_description() const {
+ return local_description_.get();
+ }
+
+ // The current remote transport description, for use by derived classes
+ // when performing transport description negotiation.
+ const TransportDescription* remote_description() const {
+ return remote_description_.get();
+ }
+
+ virtual void SetIdentity_w(rtc::SSLIdentity* identity) {}
+
+ virtual bool GetIdentity_w(rtc::SSLIdentity** identity) {
+ return false;
+ }
+
+ // Pushes down the transport parameters from the local description, such
+ // as the ICE ufrag and pwd.
+ // Derived classes can override, but must call the base as well.
+ virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl* channel,
+ std::string* error_desc);
+
+ // Pushes down remote ice credentials from the remote description to the
+ // transport channel.
+ virtual bool ApplyRemoteTransportDescription_w(TransportChannelImpl* ch,
+ std::string* error_desc);
+
+ // Negotiates the transport parameters based on the current local and remote
+ // transport description, such at the version of ICE to use, and whether DTLS
+ // should be activated.
+ // Derived classes can negotiate their specific parameters here, but must call
+ // the base as well.
+ virtual bool NegotiateTransportDescription_w(ContentAction local_role,
+ std::string* error_desc);
+
+ // Pushes down the transport parameters obtained via negotiation.
+ // Derived classes can set their specific parameters here, but must call the
+ // base as well.
+ virtual bool ApplyNegotiatedTransportDescription_w(
+ TransportChannelImpl* channel, std::string* error_desc);
+
+ virtual bool GetSslRole_w(rtc::SSLRole* ssl_role) const {
+ return false;
+ }
+
+ private:
+ struct ChannelMapEntry {
+ ChannelMapEntry() : impl_(NULL), candidates_allocated_(false), ref_(0) {}
+ explicit ChannelMapEntry(TransportChannelImpl *impl)
+ : impl_(impl),
+ candidates_allocated_(false),
+ ref_(0) {
+ }
+
+ void AddRef() { ++ref_; }
+ void DecRef() {
+ ASSERT(ref_ > 0);
+ --ref_;
+ }
+ int ref() const { return ref_; }
+
+ TransportChannelImpl* get() const { return impl_; }
+ TransportChannelImpl* operator->() const { return impl_; }
+ void set_candidates_allocated(bool status) {
+ candidates_allocated_ = status;
+ }
+ bool candidates_allocated() const { return candidates_allocated_; }
+
+ private:
+ TransportChannelImpl *impl_;
+ bool candidates_allocated_;
+ int ref_;
+ };
+
+ // Candidate component => ChannelMapEntry
+ typedef std::map<int, ChannelMapEntry> ChannelMap;
+
+ // Called when the state of a channel changes.
+ void OnChannelReadableState(TransportChannel* channel);
+ void OnChannelWritableState(TransportChannel* channel);
+
+ // Called when a channel requests signaling.
+ void OnChannelRequestSignaling(TransportChannelImpl* channel);
+
+ // Called when a candidate is ready from remote peer.
+ void OnRemoteCandidate(const Candidate& candidate);
+ // Called when a candidate is ready from channel.
+ void OnChannelCandidateReady(TransportChannelImpl* channel,
+ const Candidate& candidate);
+ void OnChannelRouteChange(TransportChannel* channel,
+ const Candidate& remote_candidate);
+ void OnChannelCandidatesAllocationDone(TransportChannelImpl* channel);
+ // Called when there is ICE role change.
+ void OnRoleConflict(TransportChannelImpl* channel);
+ // Called when the channel removes a connection.
+ void OnChannelConnectionRemoved(TransportChannelImpl* channel);
+
+ // Dispatches messages to the appropriate handler (below).
+ void OnMessage(rtc::Message* msg);
+
+ // These are versions of the above methods that are called only on a
+ // particular thread (s = signaling, w = worker). The above methods post or
+ // send a message to invoke this version.
+ TransportChannelImpl* CreateChannel_w(int component);
+ void DestroyChannel_w(int component);
+ void ConnectChannels_w();
+ void ResetChannels_w();
+ void DestroyAllChannels_w();
+ void OnRemoteCandidate_w(const Candidate& candidate);
+ void OnChannelReadableState_s();
+ void OnChannelWritableState_s();
+ void OnChannelRequestSignaling_s(int component);
+ void OnConnecting_s();
+ void OnChannelRouteChange_s(const TransportChannel* channel,
+ const Candidate& remote_candidate);
+ void OnChannelCandidatesAllocationDone_s();
+
+ // Helper function that invokes the given function on every channel.
+ typedef void (TransportChannelImpl::* TransportChannelFunc)();
+ void CallChannels_w(TransportChannelFunc func);
+
+ // Computes the OR of the channel's read or write state (argument picks).
+ TransportState GetTransportState_s(bool read);
+
+ void OnChannelCandidateReady_s();
+
+ void SetIceRole_w(IceRole role);
+ void SetRemoteIceMode_w(IceMode mode);
+ bool SetLocalTransportDescription_w(const TransportDescription& desc,
+ ContentAction action,
+ std::string* error_desc);
+ bool SetRemoteTransportDescription_w(const TransportDescription& desc,
+ ContentAction action,
+ std::string* error_desc);
+ bool GetStats_w(TransportStats* infos);
+ bool GetRemoteCertificate_w(rtc::SSLCertificate** cert);
+
+ // Sends SignalCompleted if we are now in that state.
+ void MaybeCompleted_w();
+
+ rtc::Thread* signaling_thread_;
+ rtc::Thread* worker_thread_;
+ std::string content_name_;
+ std::string type_;
+ PortAllocator* allocator_;
+ bool destroyed_;
+ TransportState readable_;
+ TransportState writable_;
+ bool was_writable_;
+ bool connect_requested_;
+ IceRole ice_role_;
+ uint64 tiebreaker_;
+ TransportProtocol protocol_;
+ IceMode remote_ice_mode_;
+ rtc::scoped_ptr<TransportDescription> local_description_;
+ rtc::scoped_ptr<TransportDescription> remote_description_;
+
+ ChannelMap channels_;
+ // Buffers the ready_candidates so that SignalCanidatesReady can
+ // provide them in multiples.
+ std::vector<Candidate> ready_candidates_;
+ // Protects changes to channels and messages
+ rtc::CriticalSection crit_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Transport);
+};
+
+// Extract a TransportProtocol from a TransportDescription.
+TransportProtocol TransportProtocolFromDescription(
+ const TransportDescription* desc);
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORT_H_
diff --git a/p2p/base/transport_unittest.cc b/p2p/base/transport_unittest.cc
new file mode 100644
index 00000000..abcf32ce
--- /dev/null
+++ b/p2p/base/transport_unittest.cc
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/fakesession.h"
+#include "webrtc/p2p/base/p2ptransport.h"
+#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/p2p/base/rawtransport.h"
+#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/fakesslidentity.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/thread.h"
+
+using cricket::Candidate;
+using cricket::Candidates;
+using cricket::Transport;
+using cricket::FakeTransport;
+using cricket::TransportChannel;
+using cricket::FakeTransportChannel;
+using cricket::IceRole;
+using cricket::TransportDescription;
+using cricket::WriteError;
+using cricket::ParseError;
+using rtc::SocketAddress;
+
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+
+static const char kIceUfrag2[] = "TESTICEUFRAG0002";
+static const char kIcePwd2[] = "TESTICEPWD00000000000002";
+
+class TransportTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ TransportTest()
+ : thread_(rtc::Thread::Current()),
+ transport_(new FakeTransport(
+ thread_, thread_, "test content name", NULL)),
+ channel_(NULL),
+ connecting_signalled_(false),
+ completed_(false),
+ failed_(false) {
+ transport_->SignalConnecting.connect(this, &TransportTest::OnConnecting);
+ transport_->SignalCompleted.connect(this, &TransportTest::OnCompleted);
+ transport_->SignalFailed.connect(this, &TransportTest::OnFailed);
+ }
+ ~TransportTest() {
+ transport_->DestroyAllChannels();
+ }
+ bool SetupChannel() {
+ channel_ = CreateChannel(1);
+ return (channel_ != NULL);
+ }
+ FakeTransportChannel* CreateChannel(int component) {
+ return static_cast<FakeTransportChannel*>(
+ transport_->CreateChannel(component));
+ }
+ void DestroyChannel() {
+ transport_->DestroyChannel(1);
+ channel_ = NULL;
+ }
+
+ protected:
+ void OnConnecting(Transport* transport) {
+ connecting_signalled_ = true;
+ }
+ void OnCompleted(Transport* transport) {
+ completed_ = true;
+ }
+ void OnFailed(Transport* transport) {
+ failed_ = true;
+ }
+
+ rtc::Thread* thread_;
+ rtc::scoped_ptr<FakeTransport> transport_;
+ FakeTransportChannel* channel_;
+ bool connecting_signalled_;
+ bool completed_;
+ bool failed_;
+};
+
+class FakeCandidateTranslator : public cricket::CandidateTranslator {
+ public:
+ void AddMapping(int component, const std::string& channel_name) {
+ name_to_component[channel_name] = component;
+ component_to_name[component] = channel_name;
+ }
+
+ bool GetChannelNameFromComponent(
+ int component, std::string* channel_name) const {
+ if (component_to_name.find(component) == component_to_name.end()) {
+ return false;
+ }
+ *channel_name = component_to_name.find(component)->second;
+ return true;
+ }
+ bool GetComponentFromChannelName(
+ const std::string& channel_name, int* component) const {
+ if (name_to_component.find(channel_name) == name_to_component.end()) {
+ return false;
+ }
+ *component = name_to_component.find(channel_name)->second;
+ return true;
+ }
+
+ std::map<std::string, int> name_to_component;
+ std::map<int, std::string> component_to_name;
+};
+
+// Test that calling ConnectChannels triggers an OnConnecting signal.
+TEST_F(TransportTest, TestConnectChannelsDoesSignal) {
+ EXPECT_TRUE(SetupChannel());
+ transport_->ConnectChannels();
+ EXPECT_FALSE(connecting_signalled_);
+
+ EXPECT_TRUE_WAIT(connecting_signalled_, 100);
+}
+
+// Test that DestroyAllChannels kills any pending OnConnecting signals.
+TEST_F(TransportTest, TestDestroyAllClearsPosts) {
+ EXPECT_TRUE(transport_->CreateChannel(1) != NULL);
+
+ transport_->ConnectChannels();
+ transport_->DestroyAllChannels();
+
+ thread_->ProcessMessages(0);
+ EXPECT_FALSE(connecting_signalled_);
+}
+
+// This test verifies channels are created with proper ICE
+// role, tiebreaker and remote ice mode and credentials after offer and
+// answer negotiations.
+TEST_F(TransportTest, TestChannelIceParameters) {
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ transport_->SetIceTiebreaker(99U);
+ cricket::TransportDescription local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+ cricket::CA_OFFER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+ EXPECT_TRUE(SetupChannel());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+ EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+ EXPECT_EQ(kIceUfrag1, channel_->ice_ufrag());
+ EXPECT_EQ(kIcePwd1, channel_->ice_pwd());
+
+ cricket::TransportDescription remote_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+ EXPECT_EQ(99U, channel_->IceTiebreaker());
+ EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+ // Changing the transport role from CONTROLLING to CONTROLLED.
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel_->GetIceRole());
+ EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+ EXPECT_EQ(kIceUfrag1, channel_->remote_ice_ufrag());
+ EXPECT_EQ(kIcePwd1, channel_->remote_ice_pwd());
+}
+
+// Verifies that IceCredentialsChanged returns true when either ufrag or pwd
+// changed, and false in other cases.
+TEST_F(TransportTest, TestIceCredentialsChanged) {
+ EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p2"));
+ EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p1"));
+ EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p2"));
+ EXPECT_FALSE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p1"));
+}
+
+// This test verifies that the callee's ICE role changes from controlled to
+// controlling when the callee triggers an ICE restart.
+TEST_F(TransportTest, TestIceControlledToControllingOnIceRestart) {
+ EXPECT_TRUE(SetupChannel());
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ cricket::TransportDescription desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc,
+ cricket::CA_OFFER,
+ NULL));
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role());
+
+ cricket::TransportDescription new_local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc,
+ cricket::CA_OFFER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+}
+
+// This test verifies that the caller's ICE role changes from controlling to
+// controlled when the callee triggers an ICE restart.
+TEST_F(TransportTest, TestIceControllingToControlledOnIceRestart) {
+ EXPECT_TRUE(SetupChannel());
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+
+ cricket::TransportDescription desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(desc,
+ cricket::CA_OFFER,
+ NULL));
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+
+ cricket::TransportDescription new_local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel_->GetIceRole());
+}
+
+// This test verifies that the caller's ICE role is still controlling after the
+// callee triggers ICE restart if the callee's ICE mode is LITE.
+TEST_F(TransportTest, TestIceControllingOnIceRestartIfRemoteIsIceLite) {
+ EXPECT_TRUE(SetupChannel());
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+
+ cricket::TransportDescription desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(desc,
+ cricket::CA_OFFER,
+ NULL));
+
+ cricket::TransportDescription remote_desc(
+ cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+ kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE,
+ cricket::CONNECTIONROLE_NONE, NULL, cricket::Candidates());
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+ cricket::CA_ANSWER,
+ NULL));
+
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+
+ cricket::TransportDescription new_local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+}
+
+// This test verifies that the Completed and Failed states can be reached.
+TEST_F(TransportTest, TestChannelCompletedAndFailed) {
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ cricket::TransportDescription local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+ cricket::CA_OFFER,
+ NULL));
+ EXPECT_TRUE(SetupChannel());
+
+ cricket::TransportDescription remote_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+ cricket::CA_ANSWER,
+ NULL));
+
+ channel_->SetConnectionCount(2);
+ channel_->SignalCandidatesAllocationDone(channel_);
+ channel_->SetWritable(true);
+ EXPECT_TRUE_WAIT(transport_->all_channels_writable(), 100);
+ // ICE is not yet completed because there is still more than one connection.
+ EXPECT_FALSE(completed_);
+ EXPECT_FALSE(failed_);
+
+ // When the connection count drops to 1, SignalCompleted should be emitted,
+ // and completed() should be true.
+ channel_->SetConnectionCount(1);
+ EXPECT_TRUE_WAIT(completed_, 100);
+ completed_ = false;
+
+ // When the connection count drops to 0, SignalFailed should be emitted, and
+ // completed() should be false.
+ channel_->SetConnectionCount(0);
+ EXPECT_TRUE_WAIT(failed_, 100);
+ EXPECT_FALSE(completed_);
+}
+
+// Tests channel role is reversed after receiving ice-lite from remote.
+TEST_F(TransportTest, TestSetRemoteIceLiteInOffer) {
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ cricket::TransportDescription remote_desc(
+ cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+ kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE,
+ cricket::CONNECTIONROLE_ACTPASS, NULL, cricket::Candidates());
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+ cricket::CA_OFFER,
+ NULL));
+ cricket::TransportDescription local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+ EXPECT_TRUE(SetupChannel());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+ EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode());
+}
+
+// Tests ice-lite in remote answer.
+TEST_F(TransportTest, TestSetRemoteIceLiteInAnswer) {
+ transport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ cricket::TransportDescription local_desc(
+ cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1);
+ ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+ cricket::CA_OFFER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role());
+ EXPECT_TRUE(SetupChannel());
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+ // Channels will be created in ICEFULL_MODE.
+ EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+ cricket::TransportDescription remote_desc(
+ cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+ kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE,
+ cricket::CONNECTIONROLE_NONE, NULL, cricket::Candidates());
+ ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+ cricket::CA_ANSWER,
+ NULL));
+ EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole());
+ // After receiving remote description with ICEMODE_LITE, channel should
+ // have mode set to ICEMODE_LITE.
+ EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode());
+}
+
+// Tests that we can properly serialize/deserialize candidates.
+TEST_F(TransportTest, TestP2PTransportWriteAndParseCandidate) {
+ Candidate test_candidate(
+ "", 1, "udp",
+ rtc::SocketAddress("2001:db8:fefe::1", 9999),
+ 738197504, "abcdef", "ghijkl", "foo", "testnet", 50, "");
+ Candidate test_candidate2(
+ "", 2, "tcp",
+ rtc::SocketAddress("192.168.7.1", 9999),
+ 1107296256, "mnopqr", "stuvwx", "bar", "testnet2", 100, "");
+ rtc::SocketAddress host_address("www.google.com", 24601);
+ host_address.SetResolvedIP(rtc::IPAddress(0x0A000001));
+ Candidate test_candidate3(
+ "", 3, "spdy", host_address, 1476395008, "yzabcd",
+ "efghij", "baz", "testnet3", 150, "");
+ WriteError write_error;
+ ParseError parse_error;
+ rtc::scoped_ptr<buzz::XmlElement> elem;
+ cricket::Candidate parsed_candidate;
+ cricket::P2PTransportParser parser;
+
+ FakeCandidateTranslator translator;
+ translator.AddMapping(1, "test");
+ translator.AddMapping(2, "test2");
+ translator.AddMapping(3, "test3");
+
+ EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate, &translator,
+ elem.accept(), &write_error));
+ EXPECT_EQ("", write_error.text);
+ EXPECT_EQ("test", elem->Attr(buzz::QN_NAME));
+ EXPECT_EQ("udp", elem->Attr(cricket::QN_PROTOCOL));
+ EXPECT_EQ("2001:db8:fefe::1", elem->Attr(cricket::QN_ADDRESS));
+ EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT));
+ EXPECT_EQ("0.34", elem->Attr(cricket::QN_PREFERENCE));
+ EXPECT_EQ("abcdef", elem->Attr(cricket::QN_USERNAME));
+ EXPECT_EQ("ghijkl", elem->Attr(cricket::QN_PASSWORD));
+ EXPECT_EQ("foo", elem->Attr(cricket::QN_TYPE));
+ EXPECT_EQ("testnet", elem->Attr(cricket::QN_NETWORK));
+ EXPECT_EQ("50", elem->Attr(cricket::QN_GENERATION));
+
+ EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+ &parsed_candidate, &parse_error));
+ EXPECT_TRUE(test_candidate.IsEquivalent(parsed_candidate));
+
+ EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate2, &translator,
+ elem.accept(), &write_error));
+ EXPECT_EQ("test2", elem->Attr(buzz::QN_NAME));
+ EXPECT_EQ("tcp", elem->Attr(cricket::QN_PROTOCOL));
+ EXPECT_EQ("192.168.7.1", elem->Attr(cricket::QN_ADDRESS));
+ EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT));
+ EXPECT_EQ("0.51", elem->Attr(cricket::QN_PREFERENCE));
+ EXPECT_EQ("mnopqr", elem->Attr(cricket::QN_USERNAME));
+ EXPECT_EQ("stuvwx", elem->Attr(cricket::QN_PASSWORD));
+ EXPECT_EQ("bar", elem->Attr(cricket::QN_TYPE));
+ EXPECT_EQ("testnet2", elem->Attr(cricket::QN_NETWORK));
+ EXPECT_EQ("100", elem->Attr(cricket::QN_GENERATION));
+
+ EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+ &parsed_candidate, &parse_error));
+ EXPECT_TRUE(test_candidate2.IsEquivalent(parsed_candidate));
+
+ // Check that an ip is preferred over hostname.
+ EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate3, &translator,
+ elem.accept(), &write_error));
+ EXPECT_EQ("test3", elem->Attr(cricket::QN_NAME));
+ EXPECT_EQ("spdy", elem->Attr(cricket::QN_PROTOCOL));
+ EXPECT_EQ("10.0.0.1", elem->Attr(cricket::QN_ADDRESS));
+ EXPECT_EQ("24601", elem->Attr(cricket::QN_PORT));
+ EXPECT_EQ("0.69", elem->Attr(cricket::QN_PREFERENCE));
+ EXPECT_EQ("yzabcd", elem->Attr(cricket::QN_USERNAME));
+ EXPECT_EQ("efghij", elem->Attr(cricket::QN_PASSWORD));
+ EXPECT_EQ("baz", elem->Attr(cricket::QN_TYPE));
+ EXPECT_EQ("testnet3", elem->Attr(cricket::QN_NETWORK));
+ EXPECT_EQ("150", elem->Attr(cricket::QN_GENERATION));
+
+ EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+ &parsed_candidate, &parse_error));
+ EXPECT_TRUE(test_candidate3.IsEquivalent(parsed_candidate));
+}
+
+TEST_F(TransportTest, TestGetStats) {
+ EXPECT_TRUE(SetupChannel());
+ cricket::TransportStats stats;
+ EXPECT_TRUE(transport_->GetStats(&stats));
+ // Note that this tests the behavior of a FakeTransportChannel.
+ ASSERT_EQ(1U, stats.channel_stats.size());
+ EXPECT_EQ(1, stats.channel_stats[0].component);
+ transport_->ConnectChannels();
+ EXPECT_TRUE(transport_->GetStats(&stats));
+ ASSERT_EQ(1U, stats.channel_stats.size());
+ EXPECT_EQ(1, stats.channel_stats[0].component);
+}
diff --git a/p2p/base/transportchannel.cc b/p2p/base/transportchannel.cc
new file mode 100644
index 00000000..16ae27d1
--- /dev/null
+++ b/p2p/base/transportchannel.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <sstream>
+#include "webrtc/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+std::string TransportChannel::ToString() const {
+ const char READABLE_ABBREV[2] = { '_', 'R' };
+ const char WRITABLE_ABBREV[2] = { '_', 'W' };
+ std::stringstream ss;
+ ss << "Channel[" << content_name_
+ << "|" << component_
+ << "|" << READABLE_ABBREV[readable_] << WRITABLE_ABBREV[writable_] << "]";
+ return ss.str();
+}
+
+void TransportChannel::set_readable(bool readable) {
+ if (readable_ != readable) {
+ readable_ = readable;
+ SignalReadableState(this);
+ }
+}
+
+void TransportChannel::set_writable(bool writable) {
+ if (writable_ != writable) {
+ writable_ = writable;
+ if (writable_) {
+ SignalReadyToSend(this);
+ }
+ SignalWritableState(this);
+ }
+}
+
+} // namespace cricket
diff --git a/p2p/base/transportchannel.h b/p2p/base/transportchannel.h
new file mode 100644
index 00000000..f91c4a8e
--- /dev/null
+++ b/p2p/base/transportchannel.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_
+#define WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportdescription.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/dscp.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/socket.h"
+#include "webrtc/base/sslidentity.h"
+#include "webrtc/base/sslstreamadapter.h"
+
+namespace cricket {
+
+class Candidate;
+
+// Flags for SendPacket/SignalReadPacket.
+enum PacketFlags {
+ PF_NORMAL = 0x00, // A normal packet.
+ PF_SRTP_BYPASS = 0x01, // An encrypted SRTP packet; bypass any additional
+ // crypto provided by the transport (e.g. DTLS)
+};
+
+// A TransportChannel represents one logical stream of packets that are sent
+// between the two sides of a session.
+class TransportChannel : public sigslot::has_slots<> {
+ public:
+ explicit TransportChannel(const std::string& content_name, int component)
+ : content_name_(content_name),
+ component_(component),
+ readable_(false), writable_(false) {}
+ virtual ~TransportChannel() {}
+
+ // TODO(mallinath) - Remove this API, as it's no longer useful.
+ // Returns the session id of this channel.
+ virtual const std::string SessionId() const { return std::string(); }
+
+ const std::string& content_name() const { return content_name_; }
+ int component() const { return component_; }
+
+ // Returns the readable and states of this channel. Each time one of these
+ // states changes, a signal is raised. These states are aggregated by the
+ // TransportManager.
+ bool readable() const { return readable_; }
+ bool writable() const { return writable_; }
+ sigslot::signal1<TransportChannel*> SignalReadableState;
+ sigslot::signal1<TransportChannel*> SignalWritableState;
+ // Emitted when the TransportChannel's ability to send has changed.
+ sigslot::signal1<TransportChannel*> SignalReadyToSend;
+
+ // Attempts to send the given packet. The return value is < 0 on failure.
+ // TODO: Remove the default argument once channel code is updated.
+ virtual int SendPacket(const char* data, size_t len,
+ const rtc::PacketOptions& options,
+ int flags = 0) = 0;
+
+ // Sets a socket option on this channel. Note that not all options are
+ // supported by all transport types.
+ virtual int SetOption(rtc::Socket::Option opt, int value) = 0;
+
+ // Returns the most recent error that occurred on this channel.
+ virtual int GetError() = 0;
+
+ // Returns the current stats for this connection.
+ virtual bool GetStats(ConnectionInfos* infos) = 0;
+
+ // Is DTLS active?
+ virtual bool IsDtlsActive() const = 0;
+
+ // Default implementation.
+ virtual bool GetSslRole(rtc::SSLRole* role) const = 0;
+
+ // Sets up the ciphers to use for DTLS-SRTP.
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) = 0;
+
+ // Finds out which DTLS-SRTP cipher was negotiated
+ virtual bool GetSrtpCipher(std::string* cipher) = 0;
+
+ // Gets a copy of the local SSL identity, owned by the caller.
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const = 0;
+
+ // Gets a copy of the remote side's SSL certificate, owned by the caller.
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const = 0;
+
+ // Allows key material to be extracted for external encryption.
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) = 0;
+
+ // Signalled each time a packet is received on this channel.
+ sigslot::signal5<TransportChannel*, const char*,
+ size_t, const rtc::PacketTime&, int> SignalReadPacket;
+
+ // This signal occurs when there is a change in the way that packets are
+ // being routed, i.e. to a different remote location. The candidate
+ // indicates where and how we are currently sending media.
+ sigslot::signal2<TransportChannel*, const Candidate&> SignalRouteChange;
+
+ // Invoked when the channel is being destroyed.
+ sigslot::signal1<TransportChannel*> SignalDestroyed;
+
+ // Debugging description of this transport channel.
+ std::string ToString() const;
+
+ protected:
+ // Sets the readable state, signaling if necessary.
+ void set_readable(bool readable);
+
+ // Sets the writable state, signaling if necessary.
+ void set_writable(bool writable);
+
+
+ private:
+ // Used mostly for debugging.
+ std::string content_name_;
+ int component_;
+ bool readable_;
+ bool writable_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannel);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_
diff --git a/p2p/base/transportchannelimpl.h b/p2p/base/transportchannelimpl.h
new file mode 100644
index 00000000..060df7fd
--- /dev/null
+++ b/p2p/base/transportchannelimpl.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+#define WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+
+#include <string>
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannel.h"
+
+namespace buzz { class XmlElement; }
+
+namespace cricket {
+
+class Candidate;
+
+// Base class for real implementations of TransportChannel. This includes some
+// methods called only by Transport, which do not need to be exposed to the
+// client.
+class TransportChannelImpl : public TransportChannel {
+ public:
+ explicit TransportChannelImpl(const std::string& content_name, int component)
+ : TransportChannel(content_name, component) {}
+
+ // Returns the transport that created this channel.
+ virtual Transport* GetTransport() = 0;
+
+ // For ICE channels.
+ virtual IceRole GetIceRole() const = 0;
+ virtual void SetIceRole(IceRole role) = 0;
+ virtual void SetIceTiebreaker(uint64 tiebreaker) = 0;
+ virtual size_t GetConnectionCount() const = 0;
+ // To toggle G-ICE/ICE.
+ virtual bool GetIceProtocolType(IceProtocolType* type) const = 0;
+ virtual void SetIceProtocolType(IceProtocolType type) = 0;
+ // SetIceCredentials only need to be implemented by the ICE
+ // transport channels. Non-ICE transport channels can just ignore.
+ // The ufrag and pwd should be set before the Connect() is called.
+ virtual void SetIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) = 0;
+ // SetRemoteIceCredentials only need to be implemented by the ICE
+ // transport channels. Non-ICE transport channels can just ignore.
+ virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+ const std::string& ice_pwd) = 0;
+
+ // SetRemoteIceMode must be implemented only by the ICE transport channels.
+ virtual void SetRemoteIceMode(IceMode mode) = 0;
+
+ // Begins the process of attempting to make a connection to the other client.
+ virtual void Connect() = 0;
+
+ // Resets this channel back to the initial state (i.e., not connecting).
+ virtual void Reset() = 0;
+
+ // Allows an individual channel to request signaling and be notified when it
+ // is ready. This is useful if the individual named channels have need to
+ // send their own transport-info stanzas.
+ sigslot::signal1<TransportChannelImpl*> SignalRequestSignaling;
+ virtual void OnSignalingReady() = 0;
+
+ // Handles sending and receiving of candidates. The Transport
+ // receives the candidates and may forward them to the relevant
+ // channel.
+ //
+ // Note: Since candidates are delivered asynchronously to the
+ // channel, they cannot return an error if the message is invalid.
+ // It is assumed that the Transport will have checked validity
+ // before forwarding.
+ sigslot::signal2<TransportChannelImpl*,
+ const Candidate&> SignalCandidateReady;
+ virtual void OnCandidate(const Candidate& candidate) = 0;
+
+ // DTLS methods
+ // Set DTLS local identity. The identity object is not copied, but the caller
+ // retains ownership and must delete it after this TransportChannelImpl is
+ // destroyed.
+ // TODO(bemasc): Fix the ownership semantics of this method.
+ virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) = 0;
+
+ // Set DTLS Remote fingerprint. Must be after local identity set.
+ virtual bool SetRemoteFingerprint(const std::string& digest_alg,
+ const uint8* digest,
+ size_t digest_len) = 0;
+
+ virtual bool SetSslRole(rtc::SSLRole role) = 0;
+
+ // TransportChannel is forwarding this signal from PortAllocatorSession.
+ sigslot::signal1<TransportChannelImpl*> SignalCandidatesAllocationDone;
+
+ // Invoked when there is conflict in the ICE role between local and remote
+ // agents.
+ sigslot::signal1<TransportChannelImpl*> SignalRoleConflict;
+
+ // Emitted whenever the number of connections available to the transport
+ // channel decreases.
+ sigslot::signal1<TransportChannelImpl*> SignalConnectionRemoved;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_
diff --git a/p2p/base/transportchannelproxy.cc b/p2p/base/transportchannelproxy.cc
new file mode 100644
index 00000000..b5e09571
--- /dev/null
+++ b/p2p/base/transportchannelproxy.cc
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannelimpl.h"
+#include "webrtc/p2p/base/transportchannelproxy.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+enum {
+ MSG_UPDATESTATE,
+};
+
+TransportChannelProxy::TransportChannelProxy(const std::string& content_name,
+ const std::string& name,
+ int component)
+ : TransportChannel(content_name, component),
+ name_(name),
+ impl_(NULL) {
+ worker_thread_ = rtc::Thread::Current();
+}
+
+TransportChannelProxy::~TransportChannelProxy() {
+ // Clearing any pending signal.
+ worker_thread_->Clear(this);
+ if (impl_)
+ impl_->GetTransport()->DestroyChannel(impl_->component());
+}
+
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+
+ if (impl == impl_) {
+ // Ignore if the |impl| has already been set.
+ LOG(LS_WARNING) << "Ignored TransportChannelProxy::SetImplementation call "
+ << "with a same impl as the existing one.";
+ return;
+ }
+
+ // Destroy any existing impl_.
+ if (impl_) {
+ impl_->GetTransport()->DestroyChannel(impl_->component());
+ }
+
+ // Adopt the supplied impl, and connect to its signals.
+ impl_ = impl;
+
+ if (impl_) {
+ impl_->SignalReadableState.connect(
+ this, &TransportChannelProxy::OnReadableState);
+ impl_->SignalWritableState.connect(
+ this, &TransportChannelProxy::OnWritableState);
+ impl_->SignalReadPacket.connect(
+ this, &TransportChannelProxy::OnReadPacket);
+ impl_->SignalReadyToSend.connect(
+ this, &TransportChannelProxy::OnReadyToSend);
+ impl_->SignalRouteChange.connect(
+ this, &TransportChannelProxy::OnRouteChange);
+ for (OptionList::iterator it = pending_options_.begin();
+ it != pending_options_.end();
+ ++it) {
+ impl_->SetOption(it->first, it->second);
+ }
+
+ // Push down the SRTP ciphers, if any were set.
+ if (!pending_srtp_ciphers_.empty()) {
+ impl_->SetSrtpCiphers(pending_srtp_ciphers_);
+ }
+ pending_options_.clear();
+ }
+
+ // Post ourselves a message to see if we need to fire state callbacks.
+ worker_thread_->Post(this, MSG_UPDATESTATE);
+}
+
+int TransportChannelProxy::SendPacket(const char* data, size_t len,
+ const rtc::PacketOptions& options,
+ int flags) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ // Fail if we don't have an impl yet.
+ if (!impl_) {
+ return -1;
+ }
+ return impl_->SendPacket(data, len, options, flags);
+}
+
+int TransportChannelProxy::SetOption(rtc::Socket::Option opt, int value) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ pending_options_.push_back(OptionPair(opt, value));
+ return 0;
+ }
+ return impl_->SetOption(opt, value);
+}
+
+int TransportChannelProxy::GetError() {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return 0;
+ }
+ return impl_->GetError();
+}
+
+bool TransportChannelProxy::GetStats(ConnectionInfos* infos) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetStats(infos);
+}
+
+bool TransportChannelProxy::IsDtlsActive() const {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->IsDtlsActive();
+}
+
+bool TransportChannelProxy::GetSslRole(rtc::SSLRole* role) const {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetSslRole(role);
+}
+
+bool TransportChannelProxy::SetSslRole(rtc::SSLRole role) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->SetSslRole(role);
+}
+
+bool TransportChannelProxy::SetSrtpCiphers(const std::vector<std::string>&
+ ciphers) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ pending_srtp_ciphers_ = ciphers; // Cache so we can send later, but always
+ // set so it stays consistent.
+ if (impl_) {
+ return impl_->SetSrtpCiphers(ciphers);
+ }
+ return true;
+}
+
+bool TransportChannelProxy::GetSrtpCipher(std::string* cipher) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetSrtpCipher(cipher);
+}
+
+bool TransportChannelProxy::GetLocalIdentity(
+ rtc::SSLIdentity** identity) const {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetLocalIdentity(identity);
+}
+
+bool TransportChannelProxy::GetRemoteCertificate(
+ rtc::SSLCertificate** cert) const {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetRemoteCertificate(cert);
+}
+
+bool TransportChannelProxy::ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->ExportKeyingMaterial(label, context, context_len, use_context,
+ result, result_len);
+}
+
+IceRole TransportChannelProxy::GetIceRole() const {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return ICEROLE_UNKNOWN;
+ }
+ return impl_->GetIceRole();
+}
+
+void TransportChannelProxy::OnReadableState(TransportChannel* channel) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == impl_);
+ set_readable(impl_->readable());
+ // Note: SignalReadableState fired by set_readable.
+}
+
+void TransportChannelProxy::OnWritableState(TransportChannel* channel) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == impl_);
+ set_writable(impl_->writable());
+ // Note: SignalWritableState fired by set_readable.
+}
+
+void TransportChannelProxy::OnReadPacket(
+ TransportChannel* channel, const char* data, size_t size,
+ const rtc::PacketTime& packet_time, int flags) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == impl_);
+ SignalReadPacket(this, data, size, packet_time, flags);
+}
+
+void TransportChannelProxy::OnReadyToSend(TransportChannel* channel) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == impl_);
+ SignalReadyToSend(this);
+}
+
+void TransportChannelProxy::OnRouteChange(TransportChannel* channel,
+ const Candidate& candidate) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ ASSERT(channel == impl_);
+ SignalRouteChange(this, candidate);
+}
+
+void TransportChannelProxy::OnMessage(rtc::Message* msg) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (msg->message_id == MSG_UPDATESTATE) {
+ // If impl_ is already readable or writable, push up those signals.
+ set_readable(impl_ ? impl_->readable() : false);
+ set_writable(impl_ ? impl_->writable() : false);
+ }
+}
+
+} // namespace cricket
diff --git a/p2p/base/transportchannelproxy.h b/p2p/base/transportchannelproxy.h
new file mode 100644
index 00000000..cfd07f85
--- /dev/null
+++ b/p2p/base/transportchannelproxy.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+#define WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/base/messagehandler.h"
+
+namespace rtc {
+class Thread;
+}
+
+namespace cricket {
+
+class TransportChannelImpl;
+
+// Proxies calls between the client and the transport channel implementation.
+// This is needed because clients are allowed to create channels before the
+// network negotiation is complete. Hence, we create a proxy up front, and
+// when negotiation completes, connect the proxy to the implementaiton.
+class TransportChannelProxy : public TransportChannel,
+ public rtc::MessageHandler {
+ public:
+ TransportChannelProxy(const std::string& content_name,
+ const std::string& name,
+ int component);
+ virtual ~TransportChannelProxy();
+
+ const std::string& name() const { return name_; }
+ TransportChannelImpl* impl() { return impl_; }
+
+ // Sets the implementation to which we will proxy.
+ void SetImplementation(TransportChannelImpl* impl);
+
+ // Implementation of the TransportChannel interface. These simply forward to
+ // the implementation.
+ virtual int SendPacket(const char* data, size_t len,
+ const rtc::PacketOptions& options,
+ int flags);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetError();
+ virtual IceRole GetIceRole() const;
+ virtual bool GetStats(ConnectionInfos* infos);
+ virtual bool IsDtlsActive() const;
+ virtual bool GetSslRole(rtc::SSLRole* role) const;
+ virtual bool SetSslRole(rtc::SSLRole role);
+ virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
+ virtual bool GetSrtpCipher(std::string* cipher);
+ virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const;
+ virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const;
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len);
+
+ private:
+ // Catch signals from the implementation channel. These just forward to the
+ // client (after updating our state to match).
+ void OnReadableState(TransportChannel* channel);
+ void OnWritableState(TransportChannel* channel);
+ void OnReadPacket(TransportChannel* channel, const char* data, size_t size,
+ const rtc::PacketTime& packet_time, int flags);
+ void OnReadyToSend(TransportChannel* channel);
+ void OnRouteChange(TransportChannel* channel, const Candidate& candidate);
+
+ void OnMessage(rtc::Message* message);
+
+ typedef std::pair<rtc::Socket::Option, int> OptionPair;
+ typedef std::vector<OptionPair> OptionList;
+ std::string name_;
+ rtc::Thread* worker_thread_;
+ TransportChannelImpl* impl_;
+ OptionList pending_options_;
+ std::vector<std::string> pending_srtp_ciphers_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannelProxy);
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_
diff --git a/p2p/base/transportdescription.cc b/p2p/base/transportdescription.cc
new file mode 100644
index 00000000..01c6a8f0
--- /dev/null
+++ b/p2p/base/transportdescription.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/transportdescription.h"
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/base/stringutils.h"
+
+namespace cricket {
+
+bool StringToConnectionRole(const std::string& role_str, ConnectionRole* role) {
+ const char* const roles[] = {
+ CONNECTIONROLE_ACTIVE_STR,
+ CONNECTIONROLE_PASSIVE_STR,
+ CONNECTIONROLE_ACTPASS_STR,
+ CONNECTIONROLE_HOLDCONN_STR
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(roles); ++i) {
+ if (_stricmp(roles[i], role_str.c_str()) == 0) {
+ *role = static_cast<ConnectionRole>(CONNECTIONROLE_ACTIVE + i);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str) {
+ switch (role) {
+ case cricket::CONNECTIONROLE_ACTIVE:
+ *role_str = cricket::CONNECTIONROLE_ACTIVE_STR;
+ break;
+ case cricket::CONNECTIONROLE_ACTPASS:
+ *role_str = cricket::CONNECTIONROLE_ACTPASS_STR;
+ break;
+ case cricket::CONNECTIONROLE_PASSIVE:
+ *role_str = cricket::CONNECTIONROLE_PASSIVE_STR;
+ break;
+ case cricket::CONNECTIONROLE_HOLDCONN:
+ *role_str = cricket::CONNECTIONROLE_HOLDCONN_STR;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+} // namespace cricket
diff --git a/p2p/base/transportdescription.h b/p2p/base/transportdescription.h
new file mode 100644
index 00000000..5ab1cd6a
--- /dev/null
+++ b/p2p/base/transportdescription.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_
+#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sslfingerprint.h"
+
+namespace cricket {
+
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer, ignore any supplied crypto.
+// SEC_ENABLED: Crypto in outgoing offer and answer (if supplied in offer).
+// SEC_REQUIRED: Crypto in outgoing offer and answer. Fail any offer with absent
+// or unsupported crypto.
+enum SecurePolicy {
+ SEC_DISABLED,
+ SEC_ENABLED,
+ SEC_REQUIRED
+};
+
+// The transport protocol we've elected to use.
+enum TransportProtocol {
+ ICEPROTO_GOOGLE, // Google version of ICE protocol.
+ ICEPROTO_HYBRID, // ICE, but can fall back to the Google version.
+ ICEPROTO_RFC5245 // Standard RFC 5245 version of ICE.
+};
+// The old name for TransportProtocol.
+// TODO(juberti): remove this.
+typedef TransportProtocol IceProtocolType;
+
+// Whether our side of the call is driving the negotiation, or the other side.
+enum IceRole {
+ ICEROLE_CONTROLLING = 0,
+ ICEROLE_CONTROLLED,
+ ICEROLE_UNKNOWN
+};
+
+// ICE RFC 5245 implementation type.
+enum IceMode {
+ ICEMODE_FULL, // As defined in http://tools.ietf.org/html/rfc5245#section-4.1
+ ICEMODE_LITE // As defined in http://tools.ietf.org/html/rfc5245#section-4.2
+};
+
+// RFC 4145 - http://tools.ietf.org/html/rfc4145#section-4
+// 'active': The endpoint will initiate an outgoing connection.
+// 'passive': The endpoint will accept an incoming connection.
+// 'actpass': The endpoint is willing to accept an incoming
+// connection or to initiate an outgoing connection.
+enum ConnectionRole {
+ CONNECTIONROLE_NONE = 0,
+ CONNECTIONROLE_ACTIVE,
+ CONNECTIONROLE_PASSIVE,
+ CONNECTIONROLE_ACTPASS,
+ CONNECTIONROLE_HOLDCONN,
+};
+
+extern const char CONNECTIONROLE_ACTIVE_STR[];
+extern const char CONNECTIONROLE_PASSIVE_STR[];
+extern const char CONNECTIONROLE_ACTPASS_STR[];
+extern const char CONNECTIONROLE_HOLDCONN_STR[];
+
+bool StringToConnectionRole(const std::string& role_str, ConnectionRole* role);
+bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str);
+
+typedef std::vector<Candidate> Candidates;
+
+struct TransportDescription {
+ TransportDescription()
+ : ice_mode(ICEMODE_FULL),
+ connection_role(CONNECTIONROLE_NONE) {}
+
+ TransportDescription(const std::string& transport_type,
+ const std::vector<std::string>& transport_options,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd,
+ IceMode ice_mode,
+ ConnectionRole role,
+ const rtc::SSLFingerprint* identity_fingerprint,
+ const Candidates& candidates)
+ : transport_type(transport_type),
+ transport_options(transport_options),
+ ice_ufrag(ice_ufrag),
+ ice_pwd(ice_pwd),
+ ice_mode(ice_mode),
+ connection_role(role),
+ identity_fingerprint(CopyFingerprint(identity_fingerprint)),
+ candidates(candidates) {}
+ TransportDescription(const std::string& transport_type,
+ const std::string& ice_ufrag,
+ const std::string& ice_pwd)
+ : transport_type(transport_type),
+ ice_ufrag(ice_ufrag),
+ ice_pwd(ice_pwd),
+ ice_mode(ICEMODE_FULL),
+ connection_role(CONNECTIONROLE_NONE) {}
+ TransportDescription(const TransportDescription& from)
+ : transport_type(from.transport_type),
+ transport_options(from.transport_options),
+ ice_ufrag(from.ice_ufrag),
+ ice_pwd(from.ice_pwd),
+ ice_mode(from.ice_mode),
+ connection_role(from.connection_role),
+ identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())),
+ candidates(from.candidates) {}
+
+ TransportDescription& operator=(const TransportDescription& from) {
+ // Self-assignment
+ if (this == &from)
+ return *this;
+
+ transport_type = from.transport_type;
+ transport_options = from.transport_options;
+ ice_ufrag = from.ice_ufrag;
+ ice_pwd = from.ice_pwd;
+ ice_mode = from.ice_mode;
+ connection_role = from.connection_role;
+
+ identity_fingerprint.reset(CopyFingerprint(
+ from.identity_fingerprint.get()));
+ candidates = from.candidates;
+ return *this;
+ }
+
+ bool HasOption(const std::string& option) const {
+ return (std::find(transport_options.begin(), transport_options.end(),
+ option) != transport_options.end());
+ }
+ void AddOption(const std::string& option) {
+ transport_options.push_back(option);
+ }
+ bool secure() const { return identity_fingerprint != NULL; }
+
+ static rtc::SSLFingerprint* CopyFingerprint(
+ const rtc::SSLFingerprint* from) {
+ if (!from)
+ return NULL;
+
+ return new rtc::SSLFingerprint(*from);
+ }
+
+ std::string transport_type; // xmlns of <transport>
+ std::vector<std::string> transport_options;
+ std::string ice_ufrag;
+ std::string ice_pwd;
+ IceMode ice_mode;
+ ConnectionRole connection_role;
+
+ rtc::scoped_ptr<rtc::SSLFingerprint> identity_fingerprint;
+ Candidates candidates;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_
diff --git a/p2p/base/transportdescriptionfactory.cc b/p2p/base/transportdescriptionfactory.cc
new file mode 100644
index 00000000..619c9d16
--- /dev/null
+++ b/p2p/base/transportdescriptionfactory.cc
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/transportdescriptionfactory.h"
+
+#include "webrtc/p2p/base/transportdescription.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagedigest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sslfingerprint.h"
+
+namespace cricket {
+
+static TransportProtocol kDefaultProtocol = ICEPROTO_GOOGLE;
+
+TransportDescriptionFactory::TransportDescriptionFactory()
+ : protocol_(kDefaultProtocol),
+ secure_(SEC_DISABLED),
+ identity_(NULL) {
+}
+
+TransportDescription* TransportDescriptionFactory::CreateOffer(
+ const TransportOptions& options,
+ const TransportDescription* current_description) const {
+ rtc::scoped_ptr<TransportDescription> desc(new TransportDescription());
+
+ // Set the transport type depending on the selected protocol.
+ if (protocol_ == ICEPROTO_RFC5245) {
+ desc->transport_type = NS_JINGLE_ICE_UDP;
+ } else if (protocol_ == ICEPROTO_HYBRID) {
+ desc->transport_type = NS_JINGLE_ICE_UDP;
+ desc->AddOption(ICE_OPTION_GICE);
+ } else if (protocol_ == ICEPROTO_GOOGLE) {
+ desc->transport_type = NS_GINGLE_P2P;
+ }
+
+ // Generate the ICE credentials if we don't already have them.
+ if (!current_description || options.ice_restart) {
+ desc->ice_ufrag = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
+ desc->ice_pwd = rtc::CreateRandomString(ICE_PWD_LENGTH);
+ } else {
+ desc->ice_ufrag = current_description->ice_ufrag;
+ desc->ice_pwd = current_description->ice_pwd;
+ }
+
+ // If we are trying to establish a secure transport, add a fingerprint.
+ if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+ // Fail if we can't create the fingerprint.
+ // If we are the initiator set role to "actpass".
+ if (!SetSecurityInfo(desc.get(), CONNECTIONROLE_ACTPASS)) {
+ return NULL;
+ }
+ }
+
+ return desc.release();
+}
+
+TransportDescription* TransportDescriptionFactory::CreateAnswer(
+ const TransportDescription* offer,
+ const TransportOptions& options,
+ const TransportDescription* current_description) const {
+ // A NULL offer is treated as a GICE transport description.
+ // TODO(juberti): Figure out why we get NULL offers, and fix this upstream.
+ rtc::scoped_ptr<TransportDescription> desc(new TransportDescription());
+
+ // Figure out which ICE variant to negotiate; prefer RFC 5245 ICE, but fall
+ // back to G-ICE if needed. Note that we never create a hybrid answer, since
+ // we know what the other side can support already.
+ if (offer && offer->transport_type == NS_JINGLE_ICE_UDP &&
+ (protocol_ == ICEPROTO_RFC5245 || protocol_ == ICEPROTO_HYBRID)) {
+ // Offer is ICE or hybrid, we support ICE or hybrid: use ICE.
+ desc->transport_type = NS_JINGLE_ICE_UDP;
+ } else if (offer && offer->transport_type == NS_JINGLE_ICE_UDP &&
+ offer->HasOption(ICE_OPTION_GICE) &&
+ protocol_ == ICEPROTO_GOOGLE) {
+ desc->transport_type = NS_GINGLE_P2P;
+ // Offer is hybrid, we support GICE: use GICE.
+ } else if ((!offer || offer->transport_type == NS_GINGLE_P2P) &&
+ (protocol_ == ICEPROTO_HYBRID || protocol_ == ICEPROTO_GOOGLE)) {
+ // Offer is GICE, we support hybrid or GICE: use GICE.
+ desc->transport_type = NS_GINGLE_P2P;
+ } else {
+ // Mismatch.
+ LOG(LS_WARNING) << "Failed to create TransportDescription answer "
+ "because of incompatible transport types";
+ return NULL;
+ }
+
+ // Generate the ICE credentials if we don't already have them or ice is
+ // being restarted.
+ if (!current_description || options.ice_restart) {
+ desc->ice_ufrag = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
+ desc->ice_pwd = rtc::CreateRandomString(ICE_PWD_LENGTH);
+ } else {
+ desc->ice_ufrag = current_description->ice_ufrag;
+ desc->ice_pwd = current_description->ice_pwd;
+ }
+
+ // Negotiate security params.
+ if (offer && offer->identity_fingerprint.get()) {
+ // The offer supports DTLS, so answer with DTLS, as long as we support it.
+ if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+ // Fail if we can't create the fingerprint.
+ // Setting DTLS role to active.
+ ConnectionRole role = (options.prefer_passive_role) ?
+ CONNECTIONROLE_PASSIVE : CONNECTIONROLE_ACTIVE;
+
+ if (!SetSecurityInfo(desc.get(), role)) {
+ return NULL;
+ }
+ }
+ } else if (secure_ == SEC_REQUIRED) {
+ // We require DTLS, but the other side didn't offer it. Fail.
+ LOG(LS_WARNING) << "Failed to create TransportDescription answer "
+ "because of incompatible security settings";
+ return NULL;
+ }
+
+ return desc.release();
+}
+
+bool TransportDescriptionFactory::SetSecurityInfo(
+ TransportDescription* desc, ConnectionRole role) const {
+ if (!identity_) {
+ LOG(LS_ERROR) << "Cannot create identity digest with no identity";
+ return false;
+ }
+
+ // This digest algorithm is used to produce the a=fingerprint lines in SDP.
+ // RFC 4572 Section 5 requires that those lines use the same hash function as
+ // the certificate's signature.
+ std::string digest_alg;
+ if (!identity_->certificate().GetSignatureDigestAlgorithm(&digest_alg)) {
+ LOG(LS_ERROR) << "Failed to retrieve the certificate's digest algorithm";
+ return false;
+ }
+
+ desc->identity_fingerprint.reset(
+ rtc::SSLFingerprint::Create(digest_alg, identity_));
+ if (!desc->identity_fingerprint.get()) {
+ LOG(LS_ERROR) << "Failed to create identity fingerprint, alg="
+ << digest_alg;
+ return false;
+ }
+
+ // Assign security role.
+ desc->connection_role = role;
+ return true;
+}
+
+} // namespace cricket
+
diff --git a/p2p/base/transportdescriptionfactory.h b/p2p/base/transportdescriptionfactory.h
new file mode 100644
index 00000000..a137f721
--- /dev/null
+++ b/p2p/base/transportdescriptionfactory.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_
+#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_
+
+#include "webrtc/p2p/base/transportdescription.h"
+
+namespace rtc {
+class SSLIdentity;
+}
+
+namespace cricket {
+
+struct TransportOptions {
+ TransportOptions() : ice_restart(false), prefer_passive_role(false) {}
+ bool ice_restart;
+ bool prefer_passive_role;
+};
+
+// Creates transport descriptions according to the supplied configuration.
+// When creating answers, performs the appropriate negotiation
+// of the various fields to determine the proper result.
+class TransportDescriptionFactory {
+ public:
+ // Default ctor; use methods below to set configuration.
+ TransportDescriptionFactory();
+ SecurePolicy secure() const { return secure_; }
+ // The identity to use when setting up DTLS.
+ rtc::SSLIdentity* identity() const { return identity_; }
+
+ // Specifies the transport protocol to be use.
+ void set_protocol(TransportProtocol protocol) { protocol_ = protocol; }
+ // Specifies the transport security policy to use.
+ void set_secure(SecurePolicy s) { secure_ = s; }
+ // Specifies the identity to use (only used when secure is not SEC_DISABLED).
+ void set_identity(rtc::SSLIdentity* identity) { identity_ = identity; }
+
+ // Creates a transport description suitable for use in an offer.
+ TransportDescription* CreateOffer(const TransportOptions& options,
+ const TransportDescription* current_description) const;
+ // Create a transport description that is a response to an offer.
+ TransportDescription* CreateAnswer(
+ const TransportDescription* offer,
+ const TransportOptions& options,
+ const TransportDescription* current_description) const;
+
+ private:
+ bool SetSecurityInfo(TransportDescription* description,
+ ConnectionRole role) const;
+
+ TransportProtocol protocol_;
+ SecurePolicy secure_;
+ rtc::SSLIdentity* identity_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_
diff --git a/p2p/base/transportdescriptionfactory_unittest.cc b/p2p/base/transportdescriptionfactory_unittest.cc
new file mode 100644
index 00000000..22816a2f
--- /dev/null
+++ b/p2p/base/transportdescriptionfactory_unittest.cc
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/transportdescription.h"
+#include "webrtc/p2p/base/transportdescriptionfactory.h"
+#include "webrtc/base/fakesslidentity.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/ssladapter.h"
+
+using rtc::scoped_ptr;
+using cricket::TransportDescriptionFactory;
+using cricket::TransportDescription;
+using cricket::TransportOptions;
+
+class TransportDescriptionFactoryTest : public testing::Test {
+ public:
+ TransportDescriptionFactoryTest()
+ : id1_(new rtc::FakeSSLIdentity("User1")),
+ id2_(new rtc::FakeSSLIdentity("User2")) {
+ }
+
+ void CheckDesc(const TransportDescription* desc, const std::string& type,
+ const std::string& opt, const std::string& ice_ufrag,
+ const std::string& ice_pwd, const std::string& dtls_alg) {
+ ASSERT_TRUE(desc != NULL);
+ EXPECT_EQ(type, desc->transport_type);
+ EXPECT_EQ(!opt.empty(), desc->HasOption(opt));
+ if (ice_ufrag.empty() && ice_pwd.empty()) {
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+ desc->ice_ufrag.size());
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+ desc->ice_pwd.size());
+ } else {
+ EXPECT_EQ(ice_ufrag, desc->ice_ufrag);
+ EXPECT_EQ(ice_pwd, desc->ice_pwd);
+ }
+ if (dtls_alg.empty()) {
+ EXPECT_TRUE(desc->identity_fingerprint.get() == NULL);
+ } else {
+ ASSERT_TRUE(desc->identity_fingerprint.get() != NULL);
+ EXPECT_EQ(desc->identity_fingerprint->algorithm, dtls_alg);
+ EXPECT_GT(desc->identity_fingerprint->digest.length(), 0U);
+ }
+ }
+
+ // This test ice restart by doing two offer answer exchanges. On the second
+ // exchange ice is restarted. The test verifies that the ufrag and password
+ // in the offer and answer is changed.
+ // If |dtls| is true, the test verifies that the finger print is not changed.
+ void TestIceRestart(bool dtls) {
+ if (dtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_identity(id1_.get());
+ f2_.set_identity(id2_.get());
+ } else {
+ f1_.set_secure(cricket::SEC_DISABLED);
+ f2_.set_secure(cricket::SEC_DISABLED);
+ }
+
+ cricket::TransportOptions options;
+ // The initial offer / answer exchange.
+ rtc::scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+ options, NULL));
+ rtc::scoped_ptr<TransportDescription> answer(
+ f2_.CreateAnswer(offer.get(),
+ options, NULL));
+
+ // Create an updated offer where we restart ice.
+ options.ice_restart = true;
+ rtc::scoped_ptr<TransportDescription> restart_offer(f1_.CreateOffer(
+ options, offer.get()));
+
+ VerifyUfragAndPasswordChanged(dtls, offer.get(), restart_offer.get());
+
+ // Create a new answer. The transport ufrag and password is changed since
+ // |options.ice_restart == true|
+ rtc::scoped_ptr<TransportDescription> restart_answer(
+ f2_.CreateAnswer(restart_offer.get(), options, answer.get()));
+ ASSERT_TRUE(restart_answer.get() != NULL);
+
+ VerifyUfragAndPasswordChanged(dtls, answer.get(), restart_answer.get());
+ }
+
+ void VerifyUfragAndPasswordChanged(bool dtls,
+ const TransportDescription* org_desc,
+ const TransportDescription* restart_desc) {
+ EXPECT_NE(org_desc->ice_pwd, restart_desc->ice_pwd);
+ EXPECT_NE(org_desc->ice_ufrag, restart_desc->ice_ufrag);
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+ restart_desc->ice_ufrag.size());
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+ restart_desc->ice_pwd.size());
+ // If DTLS is enabled, make sure the finger print is unchanged.
+ if (dtls) {
+ EXPECT_FALSE(
+ org_desc->identity_fingerprint->GetRfc4572Fingerprint().empty());
+ EXPECT_EQ(org_desc->identity_fingerprint->GetRfc4572Fingerprint(),
+ restart_desc->identity_fingerprint->GetRfc4572Fingerprint());
+ }
+ }
+
+ protected:
+ TransportDescriptionFactory f1_;
+ TransportDescriptionFactory f2_;
+ scoped_ptr<rtc::SSLIdentity> id1_;
+ scoped_ptr<rtc::SSLIdentity> id2_;
+};
+
+// Test that in the default case, we generate the expected G-ICE offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferGice) {
+ f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test generating a hybrid offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybrid) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "", "");
+}
+
+// Test generating an ICE-only offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferIce) {
+ f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test generating a hybrid offer with DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtls) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_identity(id1_.get());
+ std::string digest_alg;
+ ASSERT_TRUE(id1_->certificate().GetSignatureDigestAlgorithm(&digest_alg));
+ scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "",
+ digest_alg);
+ // Ensure it also works with SEC_REQUIRED.
+ f1_.set_secure(cricket::SEC_REQUIRED);
+ desc.reset(f1_.CreateOffer(TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "",
+ digest_alg);
+}
+
+// Test generating a hybrid offer with DTLS fails with no identity.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsWithNoIdentity) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f1_.set_secure(cricket::SEC_ENABLED);
+ scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test updating a hybrid offer with DTLS to pick ICE.
+// The ICE credentials should stay the same in the new offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsReofferIceDtls) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_identity(id1_.get());
+ std::string digest_alg;
+ ASSERT_TRUE(id1_->certificate().GetSignatureDigestAlgorithm(&digest_alg));
+ scoped_ptr<TransportDescription> old_desc(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(old_desc.get() != NULL);
+ f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> desc(
+ f1_.CreateOffer(TransportOptions(), old_desc.get()));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "",
+ old_desc->ice_ufrag, old_desc->ice_pwd, digest_alg);
+}
+
+// Test that we can answer a GICE offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToGice) {
+ f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+ offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+ // Should get the same result when answering as hybrid.
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+ NULL));
+ CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test that we can answer a hybrid offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToHybrid) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test that we can answer a hybrid offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToHybrid) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+ // Should get the same result when answering as hybrid.
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+ NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we can answer an ICE offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIce) {
+ f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+ f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+ TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+ offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+ // Should get the same result when answering as hybrid.
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+ NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we can't answer a GICE offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToGice) {
+ f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we can't answer an ICE offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToIce) {
+ f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+ f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+ offer.get(), TransportOptions(), NULL));
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we can update an answer properly; ICE credentials shouldn't change.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIceReanswer) {
+ f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+ f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> old_desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ ASSERT_TRUE(old_desc.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(),
+ old_desc.get()));
+ ASSERT_TRUE(desc.get() != NULL);
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "",
+ old_desc->ice_ufrag, old_desc->ice_pwd, "");
+}
+
+// Test that we handle answering an offer with DTLS with no DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridToHybridDtls) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_identity(id1_.get());
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we handle answering an offer without DTLS if we have DTLS enabled,
+// but fail if we require DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybrid) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_identity(id2_.get());
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+ f2_.set_secure(cricket::SEC_REQUIRED);
+ desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+ NULL));
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we handle answering an DTLS offer with DTLS, both if we have
+// DTLS enabled and required.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybridDtls) {
+ f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_identity(id1_.get());
+
+ f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_identity(id2_.get());
+ // f2_ produces the answer that is being checked in this test, so the
+ // answer must contain fingerprint lines with id2_'s digest algorithm.
+ std::string digest_alg2;
+ ASSERT_TRUE(id2_->certificate().GetSignatureDigestAlgorithm(&digest_alg2));
+
+ scoped_ptr<TransportDescription> offer(
+ f1_.CreateOffer(TransportOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ scoped_ptr<TransportDescription> desc(
+ f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", digest_alg2);
+ f2_.set_secure(cricket::SEC_REQUIRED);
+ desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+ NULL));
+ CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", digest_alg2);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if |TransportDescriptionOptions::ice_restart| is true.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestart) {
+ TestIceRestart(false);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if |TransportDescriptionOptions::ice_restart| is true and DTLS is enabled.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestartWithDtls) {
+ TestIceRestart(true);
+}
diff --git a/p2p/base/transportinfo.h b/p2p/base/transportinfo.h
new file mode 100644
index 00000000..3fbf204d
--- /dev/null
+++ b/p2p/base/transportinfo.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TRANSPORTINFO_H_
+#define WEBRTC_P2P_BASE_TRANSPORTINFO_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/transportdescription.h"
+#include "webrtc/base/helpers.h"
+
+namespace cricket {
+
+// A TransportInfo is NOT a transport-info message. It is comparable
+// to a "ContentInfo". A transport-infos message is basically just a
+// collection of TransportInfos.
+struct TransportInfo {
+ TransportInfo() {}
+
+ TransportInfo(const std::string& content_name,
+ const TransportDescription& description)
+ : content_name(content_name),
+ description(description) {}
+
+ std::string content_name;
+ TransportDescription description;
+};
+
+typedef std::vector<TransportInfo> TransportInfos;
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TRANSPORTINFO_H_
diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc
new file mode 100644
index 00000000..e7626fe0
--- /dev/null
+++ b/p2p/base/turnport.cc
@@ -0,0 +1,1196 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/turnport.h"
+
+#include <functional>
+
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/byteorder.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/nethelpers.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/stringencode.h"
+
+namespace cricket {
+
+// TODO(juberti): Move to stun.h when relay messages have been renamed.
+static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
+
+// TODO(juberti): Extract to turnmessage.h
+static const int TURN_DEFAULT_PORT = 3478;
+static const int TURN_CHANNEL_NUMBER_START = 0x4000;
+static const int TURN_PERMISSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
+
+static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+
+// Retry at most twice (i.e. three different ALLOCATE requests) on
+// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766.
+static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2;
+
+inline bool IsTurnChannelData(uint16 msg_type) {
+ return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01
+}
+
+static int GetRelayPreference(cricket::ProtocolType proto, bool secure) {
+ int relay_preference = ICE_TYPE_PREFERENCE_RELAY;
+ if (proto == cricket::PROTO_TCP) {
+ relay_preference -= 1;
+ if (secure)
+ relay_preference -= 1;
+ }
+
+ ASSERT(relay_preference >= 0);
+ return relay_preference;
+}
+
+class TurnAllocateRequest : public StunRequest {
+ public:
+ explicit TurnAllocateRequest(TurnPort* port);
+ virtual void Prepare(StunMessage* request);
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ // Handles authentication challenge from the server.
+ void OnAuthChallenge(StunMessage* response, int code);
+ void OnTryAlternate(StunMessage* response, int code);
+ void OnUnknownAttribute(StunMessage* response);
+
+ TurnPort* port_;
+};
+
+class TurnRefreshRequest : public StunRequest {
+ public:
+ explicit TurnRefreshRequest(TurnPort* port);
+ virtual void Prepare(StunMessage* request);
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ TurnPort* port_;
+};
+
+class TurnCreatePermissionRequest : public StunRequest,
+ public sigslot::has_slots<> {
+ public:
+ TurnCreatePermissionRequest(TurnPort* port, TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr);
+ virtual void Prepare(StunMessage* request);
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ void OnEntryDestroyed(TurnEntry* entry);
+
+ TurnPort* port_;
+ TurnEntry* entry_;
+ rtc::SocketAddress ext_addr_;
+};
+
+class TurnChannelBindRequest : public StunRequest,
+ public sigslot::has_slots<> {
+ public:
+ TurnChannelBindRequest(TurnPort* port, TurnEntry* entry, int channel_id,
+ const rtc::SocketAddress& ext_addr);
+ virtual void Prepare(StunMessage* request);
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ void OnEntryDestroyed(TurnEntry* entry);
+
+ TurnPort* port_;
+ TurnEntry* entry_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+};
+
+// Manages a "connection" to a remote destination. We will attempt to bring up
+// a channel for this remote destination to reduce the overhead of sending data.
+class TurnEntry : public sigslot::has_slots<> {
+ public:
+ enum BindState { STATE_UNBOUND, STATE_BINDING, STATE_BOUND };
+ TurnEntry(TurnPort* port, int channel_id,
+ const rtc::SocketAddress& ext_addr);
+
+ TurnPort* port() { return port_; }
+
+ int channel_id() const { return channel_id_; }
+ const rtc::SocketAddress& address() const { return ext_addr_; }
+ BindState state() const { return state_; }
+
+ // Helper methods to send permission and channel bind requests.
+ void SendCreatePermissionRequest();
+ void SendChannelBindRequest(int delay);
+ // Sends a packet to the given destination address.
+ // This will wrap the packet in STUN if necessary.
+ int Send(const void* data, size_t size, bool payload,
+ const rtc::PacketOptions& options);
+
+ void OnCreatePermissionSuccess();
+ void OnCreatePermissionError(StunMessage* response, int code);
+ void OnChannelBindSuccess();
+ void OnChannelBindError(StunMessage* response, int code);
+ // Signal sent when TurnEntry is destroyed.
+ sigslot::signal1<TurnEntry*> SignalDestroyed;
+
+ private:
+ TurnPort* port_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+ BindState state_;
+};
+
+TurnPort::TurnPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& username,
+ const std::string& password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority)
+ : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(),
+ username, password),
+ server_address_(server_address),
+ credentials_(credentials),
+ socket_(socket),
+ resolver_(NULL),
+ error_(0),
+ request_manager_(thread),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ connected_(false),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0) {
+ request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
+}
+
+TurnPort::TurnPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username,
+ const std::string& password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority)
+ : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port,
+ username, password),
+ server_address_(server_address),
+ credentials_(credentials),
+ socket_(NULL),
+ resolver_(NULL),
+ error_(0),
+ request_manager_(thread),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ connected_(false),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0) {
+ request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
+}
+
+TurnPort::~TurnPort() {
+ // TODO(juberti): Should this even be necessary?
+ while (!entries_.empty()) {
+ DestroyEntry(entries_.front()->address());
+ }
+ if (resolver_) {
+ resolver_->Destroy(false);
+ }
+ if (!SharedSocket()) {
+ delete socket_;
+ }
+}
+
+void TurnPort::PrepareAddress() {
+ if (credentials_.username.empty() ||
+ credentials_.password.empty()) {
+ LOG(LS_ERROR) << "Allocation can't be started without setting the"
+ << " TURN server credentials for the user.";
+ OnAllocateError();
+ return;
+ }
+
+ if (!server_address_.address.port()) {
+ // We will set default TURN port, if no port is set in the address.
+ server_address_.address.SetPort(TURN_DEFAULT_PORT);
+ }
+
+ if (server_address_.address.IsUnresolved()) {
+ ResolveTurnAddress(server_address_.address);
+ } else {
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(server_address_.address)) {
+ LOG(LS_ERROR) << "Server IP address family does not match with "
+ << "local host address family type";
+ OnAllocateError();
+ return;
+ }
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+
+ LOG_J(LS_INFO, this) << "Trying to connect to TURN server via "
+ << ProtoToString(server_address_.proto) << " @ "
+ << server_address_.address.ToSensitiveString();
+ if (!CreateTurnClientSocket()) {
+ OnAllocateError();
+ } else if (server_address_.proto == PROTO_UDP) {
+ // If its UDP, send AllocateRequest now.
+ // For TCP and TLS AllcateRequest will be sent by OnSocketConnect.
+ SendRequest(new TurnAllocateRequest(this), 0);
+ }
+ }
+}
+
+bool TurnPort::CreateTurnClientSocket() {
+ ASSERT(!socket_ || SharedSocket());
+
+ if (server_address_.proto == PROTO_UDP && !SharedSocket()) {
+ socket_ = socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(ip(), 0), min_port(), max_port());
+ } else if (server_address_.proto == PROTO_TCP) {
+ ASSERT(!SharedSocket());
+ int opts = rtc::PacketSocketFactory::OPT_STUN;
+ // If secure bit is enabled in server address, use TLS over TCP.
+ if (server_address_.secure) {
+ opts |= rtc::PacketSocketFactory::OPT_TLS;
+ }
+ socket_ = socket_factory()->CreateClientTcpSocket(
+ rtc::SocketAddress(ip(), 0), server_address_.address,
+ proxy(), user_agent(), opts);
+ }
+
+ if (!socket_) {
+ error_ = SOCKET_ERROR;
+ return false;
+ }
+
+ // Apply options if any.
+ for (SocketOptionsMap::iterator iter = socket_options_.begin();
+ iter != socket_options_.end(); ++iter) {
+ socket_->SetOption(iter->first, iter->second);
+ }
+
+ if (!SharedSocket()) {
+ // If socket is shared, AllocationSequence will receive the packet.
+ socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket);
+ }
+
+ socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend);
+
+ if (server_address_.proto == PROTO_TCP) {
+ socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
+ socket_->SignalClose.connect(this, &TurnPort::OnSocketClose);
+ }
+ return true;
+}
+
+void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
+ ASSERT(server_address_.proto == PROTO_TCP);
+ // Do not use this port if the socket bound to a different address than
+ // the one we asked for. This is seen in Chrome, where TCP sockets cannot be
+ // given a binding address, and the platform is expected to pick the
+ // correct local address.
+ if (socket->GetLocalAddress().ipaddr() != ip()) {
+ LOG(LS_WARNING) << "Socket is bound to a different address then the "
+ << "local port. Discarding TURN port.";
+ OnAllocateError();
+ return;
+ }
+
+ if (server_address_.address.IsUnresolved()) {
+ server_address_.address = socket_->GetRemoteAddress();
+ }
+
+ LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress()
+ << " using tcp.";
+ SendRequest(new TurnAllocateRequest(this), 0);
+}
+
+void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
+ LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error;
+ if (!connected_) {
+ OnAllocateError();
+ }
+}
+
+void TurnPort::OnAllocateMismatch() {
+ if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) {
+ LOG_J(LS_WARNING, this) << "Giving up on the port after "
+ << allocate_mismatch_retries_
+ << " retries for STUN_ERROR_ALLOCATION_MISMATCH";
+ OnAllocateError();
+ return;
+ }
+
+ LOG_J(LS_INFO, this) << "Allocating a new socket after "
+ << "STUN_ERROR_ALLOCATION_MISMATCH, retry = "
+ << allocate_mismatch_retries_ + 1;
+ if (SharedSocket()) {
+ ResetSharedSocket();
+ } else {
+ delete socket_;
+ }
+ socket_ = NULL;
+
+ PrepareAddress();
+ ++allocate_mismatch_retries_;
+}
+
+Connection* TurnPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // TURN-UDP can only connect to UDP candidates.
+ if (address.protocol() != UDP_PROTOCOL_NAME) {
+ return NULL;
+ }
+
+ if (!IsCompatibleAddress(address.address())) {
+ return NULL;
+ }
+
+ // Create an entry, if needed, so we can get our permissions set up correctly.
+ CreateEntry(address.address());
+
+ // A TURN port will have two candiates, STUN and TURN. STUN may not
+ // present in all cases. If present stun candidate will be added first
+ // and TURN candidate later.
+ for (size_t index = 0; index < Candidates().size(); ++index) {
+ if (Candidates()[index].type() == RELAY_PORT_TYPE) {
+ ProxyConnection* conn = new ProxyConnection(this, index, address);
+ conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed);
+ AddConnection(conn);
+ return conn;
+ }
+ }
+ return NULL;
+}
+
+int TurnPort::SetOption(rtc::Socket::Option opt, int value) {
+ if (!socket_) {
+ // If socket is not created yet, these options will be applied during socket
+ // creation.
+ socket_options_[opt] = value;
+ return 0;
+ }
+ return socket_->SetOption(opt, value);
+}
+
+int TurnPort::GetOption(rtc::Socket::Option opt, int* value) {
+ if (!socket_) {
+ SocketOptionsMap::const_iterator it = socket_options_.find(opt);
+ if (it == socket_options_.end()) {
+ return -1;
+ }
+ *value = it->second;
+ return 0;
+ }
+
+ return socket_->GetOption(opt, value);
+}
+
+int TurnPort::GetError() {
+ return error_;
+}
+
+int TurnPort::SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ // Try to find an entry for this specific address; we should have one.
+ TurnEntry* entry = FindEntry(addr);
+ ASSERT(entry != NULL);
+ if (!entry) {
+ return 0;
+ }
+
+ if (!connected()) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ // Send the actual contents to the server using the usual mechanism.
+ int sent = entry->Send(data, size, payload, options);
+ if (sent <= 0) {
+ return SOCKET_ERROR;
+ }
+
+ // The caller of the function is expecting the number of user data bytes,
+ // rather than the size of the packet.
+ return static_cast<int>(size);
+}
+
+void TurnPort::OnReadPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(socket == socket_);
+ ASSERT(remote_addr == server_address_.address);
+
+ // The message must be at least the size of a channel header.
+ if (size < TURN_CHANNEL_HEADER_SIZE) {
+ LOG_J(LS_WARNING, this) << "Received TURN message that was too short";
+ return;
+ }
+
+ // Check the message type, to see if is a Channel Data message.
+ // The message will either be channel data, a TURN data indication, or
+ // a response to a previous request.
+ uint16 msg_type = rtc::GetBE16(data);
+ if (IsTurnChannelData(msg_type)) {
+ HandleChannelData(msg_type, data, size, packet_time);
+ } else if (msg_type == TURN_DATA_INDICATION) {
+ HandleDataIndication(data, size, packet_time);
+ } else {
+ // This must be a response for one of our requests.
+ // Check success responses, but not errors, for MESSAGE-INTEGRITY.
+ if (IsStunSuccessResponseType(msg_type) &&
+ !StunMessage::ValidateMessageIntegrity(data, size, hash())) {
+ LOG_J(LS_WARNING, this) << "Received TURN message with invalid "
+ << "message integrity, msg_type=" << msg_type;
+ return;
+ }
+ request_manager_.CheckResponse(data, size);
+ }
+}
+
+void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ if (connected_) {
+ Port::OnReadyToSend();
+ }
+}
+
+
+// Update current server address port with the alternate server address port.
+bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) {
+ // Check if we have seen this address before and reject if we did.
+ AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address);
+ if (iter != attempted_server_addresses_.end()) {
+ LOG_J(LS_WARNING, this) << "Redirection to ["
+ << address.ToSensitiveString()
+ << "] ignored, allocation failed.";
+ return false;
+ }
+
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(address)) {
+ LOG(LS_WARNING) << "Server IP address family does not match with "
+ << "local host address family type";
+ return false;
+ }
+
+ LOG_J(LS_INFO, this) << "Redirecting from TURN server ["
+ << server_address_.address.ToSensitiveString()
+ << "] to TURN server ["
+ << address.ToSensitiveString()
+ << "]";
+ server_address_ = ProtocolAddress(address, server_address_.proto,
+ server_address_.secure);
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+ return true;
+}
+
+void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) {
+ if (resolver_)
+ return;
+
+ resolver_ = socket_factory()->CreateAsyncResolver();
+ resolver_->SignalDone.connect(this, &TurnPort::OnResolveResult);
+ resolver_->Start(address);
+}
+
+void TurnPort::OnResolveResult(rtc::AsyncResolverInterface* resolver) {
+ ASSERT(resolver == resolver_);
+ // If DNS resolve is failed when trying to connect to the server using TCP,
+ // one of the reason could be due to DNS queries blocked by firewall.
+ // In such cases we will try to connect to the server with hostname, assuming
+ // socket layer will resolve the hostname through a HTTP proxy (if any).
+ if (resolver_->GetError() != 0 && server_address_.proto == PROTO_TCP) {
+ if (!CreateTurnClientSocket()) {
+ OnAllocateError();
+ }
+ return;
+ }
+
+ // Copy the original server address in |resolved_address|. For TLS based
+ // sockets we need hostname along with resolved address.
+ rtc::SocketAddress resolved_address = server_address_.address;
+ if (resolver_->GetError() != 0 ||
+ !resolver_->GetResolvedAddress(ip().family(), &resolved_address)) {
+ LOG_J(LS_WARNING, this) << "TURN host lookup received error "
+ << resolver_->GetError();
+ error_ = resolver_->GetError();
+ OnAllocateError();
+ return;
+ }
+ // Signal needs both resolved and unresolved address. After signal is sent
+ // we can copy resolved address back into |server_address_|.
+ SignalResolvedServerAddress(this, server_address_.address,
+ resolved_address);
+ server_address_.address = resolved_address;
+ PrepareAddress();
+}
+
+void TurnPort::OnSendStunPacket(const void* data, size_t size,
+ StunRequest* request) {
+ rtc::PacketOptions options(DefaultDscpValue());
+ if (Send(data, size, options) < 0) {
+ LOG_J(LS_ERROR, this) << "Failed to send TURN message, err="
+ << socket_->GetError();
+ }
+}
+
+void TurnPort::OnStunAddress(const rtc::SocketAddress& address) {
+ // STUN Port will discover STUN candidate, as it's supplied with first TURN
+ // server address.
+ // Why not using this address? - P2PTransportChannel will start creating
+ // connections after first candidate, which means it could start creating the
+ // connections before TURN candidate added. For that to handle, we need to
+ // supply STUN candidate from this port to UDPPort, and TurnPort should have
+ // handle to UDPPort to pass back the address.
+}
+
+void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
+ const rtc::SocketAddress& stun_address) {
+ connected_ = true;
+
+ rtc::SocketAddress related_address = stun_address;
+ if (!(candidate_filter() & CF_REFLEXIVE)) {
+ // If candidate filter only allows relay type of address, empty raddr to
+ // avoid local address leakage.
+ related_address = rtc::EmptySocketAddressWithFamily(stun_address.family());
+ }
+
+ // For relayed candidate, Base is the candidate itself.
+ AddAddress(address, // Candidate address.
+ address, // Base address.
+ related_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_,
+ true);
+}
+
+void TurnPort::OnAllocateError() {
+ // We will send SignalPortError asynchronously as this can be sent during
+ // port initialization. This way it will not be blocking other port
+ // creation.
+ thread()->Post(this, MSG_ERROR);
+}
+
+void TurnPort::OnMessage(rtc::Message* message) {
+ if (message->message_id == MSG_ERROR) {
+ SignalPortError(this);
+ return;
+ } else if (message->message_id == MSG_ALLOCATE_MISMATCH) {
+ OnAllocateMismatch();
+ return;
+ }
+
+ Port::OnMessage(message);
+}
+
+void TurnPort::OnAllocateRequestTimeout() {
+ OnAllocateError();
+}
+
+void TurnPort::HandleDataIndication(const char* data, size_t size,
+ const rtc::PacketTime& packet_time) {
+ // Read in the message, and process according to RFC5766, Section 10.4.
+ rtc::ByteBuffer buf(data, size);
+ TurnMessage msg;
+ if (!msg.Read(&buf)) {
+ LOG_J(LS_WARNING, this) << "Received invalid TURN data indication";
+ return;
+ }
+
+ // Check mandatory attributes.
+ const StunAddressAttribute* addr_attr =
+ msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!addr_attr) {
+ LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_XOR_PEER_ADDRESS attribute "
+ << "in data indication.";
+ return;
+ }
+
+ const StunByteStringAttribute* data_attr =
+ msg.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_DATA attribute in "
+ << "data indication.";
+ return;
+ }
+
+ // Verify that the data came from somewhere we think we have a permission for.
+ rtc::SocketAddress ext_addr(addr_attr->GetAddress());
+ if (!HasPermission(ext_addr.ipaddr())) {
+ LOG_J(LS_WARNING, this) << "Received TURN data indication with invalid "
+ << "peer address, addr="
+ << ext_addr.ToSensitiveString();
+ return;
+ }
+
+ DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr,
+ PROTO_UDP, packet_time);
+}
+
+void TurnPort::HandleChannelData(int channel_id, const char* data,
+ size_t size,
+ const rtc::PacketTime& packet_time) {
+ // Read the message, and process according to RFC5766, Section 11.6.
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Channel Number | Length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // / Application Data /
+ // / /
+ // | |
+ // | +-------------------------------+
+ // | |
+ // +-------------------------------+
+
+ // Extract header fields from the message.
+ uint16 len = rtc::GetBE16(data + 2);
+ if (len > size - TURN_CHANNEL_HEADER_SIZE) {
+ LOG_J(LS_WARNING, this) << "Received TURN channel data message with "
+ << "incorrect length, len=" << len;
+ return;
+ }
+ // Allowing messages larger than |len|, as ChannelData can be padded.
+
+ TurnEntry* entry = FindEntry(channel_id);
+ if (!entry) {
+ LOG_J(LS_WARNING, this) << "Received TURN channel data message for invalid "
+ << "channel, channel_id=" << channel_id;
+ return;
+ }
+
+ DispatchPacket(data + TURN_CHANNEL_HEADER_SIZE, len, entry->address(),
+ PROTO_UDP, packet_time);
+}
+
+void TurnPort::DispatchPacket(const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ ProtocolType proto, const rtc::PacketTime& packet_time) {
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size, packet_time);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr, proto);
+ }
+}
+
+bool TurnPort::ScheduleRefresh(int lifetime) {
+ // Lifetime is in seconds; we schedule a refresh for one minute less.
+ if (lifetime < 2 * 60) {
+ LOG_J(LS_WARNING, this) << "Received response with lifetime that was "
+ << "too short, lifetime=" << lifetime;
+ return false;
+ }
+
+ SendRequest(new TurnRefreshRequest(this), (lifetime - 60) * 1000);
+ return true;
+}
+
+void TurnPort::SendRequest(StunRequest* req, int delay) {
+ request_manager_.SendDelayed(req, delay);
+}
+
+void TurnPort::AddRequestAuthInfo(StunMessage* msg) {
+ // If we've gotten the necessary data from the server, add it to our request.
+ VERIFY(!hash_.empty());
+ VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_USERNAME, credentials_.username)));
+ VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_REALM, realm_)));
+ VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_NONCE, nonce_)));
+ VERIFY(msg->AddMessageIntegrity(hash()));
+}
+
+int TurnPort::Send(const void* data, size_t len,
+ const rtc::PacketOptions& options) {
+ return socket_->SendTo(data, len, server_address_.address, options);
+}
+
+void TurnPort::UpdateHash() {
+ VERIFY(ComputeStunCredentialHash(credentials_.username, realm_,
+ credentials_.password, &hash_));
+}
+
+bool TurnPort::UpdateNonce(StunMessage* response) {
+ // When stale nonce error received, we should update
+ // hash and store realm and nonce.
+ // Check the mandatory attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (!realm_attr) {
+ LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in "
+ << "stale nonce error response.";
+ return false;
+ }
+ set_realm(realm_attr->GetString());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in "
+ << "stale nonce error response.";
+ return false;
+ }
+ set_nonce(nonce_attr->GetString());
+ return true;
+}
+
+static bool MatchesIP(TurnEntry* e, rtc::IPAddress ipaddr) {
+ return e->address().ipaddr() == ipaddr;
+}
+bool TurnPort::HasPermission(const rtc::IPAddress& ipaddr) const {
+ return (std::find_if(entries_.begin(), entries_.end(),
+ std::bind2nd(std::ptr_fun(MatchesIP), ipaddr)) != entries_.end());
+}
+
+static bool MatchesAddress(TurnEntry* e, rtc::SocketAddress addr) {
+ return e->address() == addr;
+}
+TurnEntry* TurnPort::FindEntry(const rtc::SocketAddress& addr) const {
+ EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
+ std::bind2nd(std::ptr_fun(MatchesAddress), addr));
+ return (it != entries_.end()) ? *it : NULL;
+}
+
+static bool MatchesChannelId(TurnEntry* e, int id) {
+ return e->channel_id() == id;
+}
+TurnEntry* TurnPort::FindEntry(int channel_id) const {
+ EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
+ std::bind2nd(std::ptr_fun(MatchesChannelId), channel_id));
+ return (it != entries_.end()) ? *it : NULL;
+}
+
+TurnEntry* TurnPort::CreateEntry(const rtc::SocketAddress& addr) {
+ ASSERT(FindEntry(addr) == NULL);
+ TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr);
+ entries_.push_back(entry);
+ return entry;
+}
+
+void TurnPort::DestroyEntry(const rtc::SocketAddress& addr) {
+ TurnEntry* entry = FindEntry(addr);
+ ASSERT(entry != NULL);
+ entry->SignalDestroyed(entry);
+ entries_.remove(entry);
+ delete entry;
+}
+
+void TurnPort::OnConnectionDestroyed(Connection* conn) {
+ // Destroying TurnEntry for the connection, which is already destroyed.
+ DestroyEntry(conn->remote_candidate().address());
+}
+
+TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
+ : StunRequest(new TurnMessage()),
+ port_(port) {
+}
+
+void TurnAllocateRequest::Prepare(StunMessage* request) {
+ // Create the request as indicated in RFC 5766, Section 6.1.
+ request->SetType(TURN_ALLOCATE_REQUEST);
+ StunUInt32Attribute* transport_attr = StunAttribute::CreateUInt32(
+ STUN_ATTR_REQUESTED_TRANSPORT);
+ transport_attr->SetValue(IPPROTO_UDP << 24);
+ VERIFY(request->AddAttribute(transport_attr));
+ if (!port_->hash().empty()) {
+ port_->AddRequestAuthInfo(request);
+ }
+}
+
+void TurnAllocateRequest::OnResponse(StunMessage* response) {
+ // Check mandatory attributes as indicated in RFC5766, Section 6.3.
+ const StunAddressAttribute* mapped_attr =
+ response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ if (!mapped_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_MAPPED_ADDRESS "
+ << "attribute in allocate success response";
+ return;
+ }
+ // Using XOR-Mapped-Address for stun.
+ port_->OnStunAddress(mapped_attr->GetAddress());
+
+ const StunAddressAttribute* relayed_attr =
+ response->GetAddress(STUN_ATTR_XOR_RELAYED_ADDRESS);
+ if (!relayed_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_RELAYED_ADDRESS "
+ << "attribute in allocate success response";
+ return;
+ }
+
+ const StunUInt32Attribute* lifetime_attr =
+ response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+ if (!lifetime_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
+ << "allocate success response";
+ return;
+ }
+ // Notify the port the allocate succeeded, and schedule a refresh request.
+ port_->OnAllocateSuccess(relayed_attr->GetAddress(),
+ mapped_attr->GetAddress());
+ port_->ScheduleRefresh(lifetime_attr->value());
+}
+
+void TurnAllocateRequest::OnErrorResponse(StunMessage* response) {
+ // Process error response according to RFC5766, Section 6.4.
+ const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+ switch (error_code->code()) {
+ case STUN_ERROR_UNAUTHORIZED: // Unauthrorized.
+ OnAuthChallenge(response, error_code->code());
+ break;
+ case STUN_ERROR_TRY_ALTERNATE:
+ OnTryAlternate(response, error_code->code());
+ break;
+ case STUN_ERROR_ALLOCATION_MISMATCH:
+ // We must handle this error async because trying to delete the socket in
+ // OnErrorResponse will cause a deadlock on the socket.
+ port_->thread()->Post(port_, TurnPort::MSG_ALLOCATE_MISMATCH);
+ break;
+ default:
+ LOG_J(LS_WARNING, port_) << "Allocate response error, code="
+ << error_code->code();
+ port_->OnAllocateError();
+ }
+}
+
+void TurnAllocateRequest::OnTimeout() {
+ LOG_J(LS_WARNING, port_) << "Allocate request timeout";
+ port_->OnAllocateRequestTimeout();
+}
+
+void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) {
+ // If we failed to authenticate even after we sent our credentials, fail hard.
+ if (code == STUN_ERROR_UNAUTHORIZED && !port_->hash().empty()) {
+ LOG_J(LS_WARNING, port_) << "Failed to authenticate with the server "
+ << "after challenge.";
+ port_->OnAllocateError();
+ return;
+ }
+
+ // Check the mandatory attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (!realm_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_REALM attribute in "
+ << "allocate unauthorized response.";
+ return;
+ }
+ port_->set_realm(realm_attr->GetString());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_NONCE attribute in "
+ << "allocate unauthorized response.";
+ return;
+ }
+ port_->set_nonce(nonce_attr->GetString());
+
+ // Send another allocate request, with the received realm and nonce values.
+ port_->SendRequest(new TurnAllocateRequest(port_), 0);
+}
+
+void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) {
+ // TODO(guoweis): Currently, we only support UDP redirect
+ if (port_->server_address().proto != PROTO_UDP) {
+ LOG_J(LS_WARNING, port_) << "Receiving 300 Alternate Server on non-UDP "
+ << "allocating request from ["
+ << port_->server_address().address.ToSensitiveString()
+ << "], failed as currently not supported";
+ port_->OnAllocateError();
+ return;
+ }
+
+ // According to RFC 5389 section 11, there are use cases where
+ // authentication of response is not possible, we're not validating
+ // message integrity.
+
+ // Get the alternate server address attribute value.
+ const StunAddressAttribute* alternate_server_attr =
+ response->GetAddress(STUN_ATTR_ALTERNATE_SERVER);
+ if (!alternate_server_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_ALTERNATE_SERVER "
+ << "attribute in try alternate error response";
+ port_->OnAllocateError();
+ return;
+ }
+ if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) {
+ port_->OnAllocateError();
+ return;
+ }
+
+ // Check the attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (realm_attr) {
+ LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_REALM attribute in "
+ << "try alternate error response.";
+ port_->set_realm(realm_attr->GetString());
+ }
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (nonce_attr) {
+ LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_NONCE attribute in "
+ << "try alternate error response.";
+ port_->set_nonce(nonce_attr->GetString());
+ }
+
+ // Send another allocate request to alternate server,
+ // with the received realm and nonce values.
+ port_->SendRequest(new TurnAllocateRequest(port_), 0);
+}
+
+TurnRefreshRequest::TurnRefreshRequest(TurnPort* port)
+ : StunRequest(new TurnMessage()),
+ port_(port) {
+}
+
+void TurnRefreshRequest::Prepare(StunMessage* request) {
+ // Create the request as indicated in RFC 5766, Section 7.1.
+ // No attributes need to be included.
+ request->SetType(TURN_REFRESH_REQUEST);
+ port_->AddRequestAuthInfo(request);
+}
+
+void TurnRefreshRequest::OnResponse(StunMessage* response) {
+ // Check mandatory attributes as indicated in RFC5766, Section 7.3.
+ const StunUInt32Attribute* lifetime_attr =
+ response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+ if (!lifetime_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
+ << "refresh success response.";
+ return;
+ }
+
+ // Schedule a refresh based on the returned lifetime value.
+ port_->ScheduleRefresh(lifetime_attr->value());
+}
+
+void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+ LOG_J(LS_WARNING, port_) << "Refresh response error, code="
+ << error_code->code();
+
+ if (error_code->code() == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send RefreshRequest immediately.
+ port_->SendRequest(new TurnRefreshRequest(port_), 0);
+ }
+ }
+}
+
+void TurnRefreshRequest::OnTimeout() {
+}
+
+TurnCreatePermissionRequest::TurnCreatePermissionRequest(
+ TurnPort* port, TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr)
+ : StunRequest(new TurnMessage()),
+ port_(port),
+ entry_(entry),
+ ext_addr_(ext_addr) {
+ entry_->SignalDestroyed.connect(
+ this, &TurnCreatePermissionRequest::OnEntryDestroyed);
+}
+
+void TurnCreatePermissionRequest::Prepare(StunMessage* request) {
+ // Create the request as indicated in RFC5766, Section 9.1.
+ request->SetType(TURN_CREATE_PERMISSION_REQUEST);
+ VERIFY(request->AddAttribute(new StunXorAddressAttribute(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+ port_->AddRequestAuthInfo(request);
+}
+
+void TurnCreatePermissionRequest::OnResponse(StunMessage* response) {
+ if (entry_) {
+ entry_->OnCreatePermissionSuccess();
+ }
+}
+
+void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) {
+ if (entry_) {
+ const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+ entry_->OnCreatePermissionError(response, error_code->code());
+ }
+}
+
+void TurnCreatePermissionRequest::OnTimeout() {
+ LOG_J(LS_WARNING, port_) << "Create permission timeout";
+}
+
+void TurnCreatePermissionRequest::OnEntryDestroyed(TurnEntry* entry) {
+ ASSERT(entry_ == entry);
+ entry_ = NULL;
+}
+
+TurnChannelBindRequest::TurnChannelBindRequest(
+ TurnPort* port, TurnEntry* entry,
+ int channel_id, const rtc::SocketAddress& ext_addr)
+ : StunRequest(new TurnMessage()),
+ port_(port),
+ entry_(entry),
+ channel_id_(channel_id),
+ ext_addr_(ext_addr) {
+ entry_->SignalDestroyed.connect(
+ this, &TurnChannelBindRequest::OnEntryDestroyed);
+}
+
+void TurnChannelBindRequest::Prepare(StunMessage* request) {
+ // Create the request as indicated in RFC5766, Section 11.1.
+ request->SetType(TURN_CHANNEL_BIND_REQUEST);
+ VERIFY(request->AddAttribute(new StunUInt32Attribute(
+ STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16)));
+ VERIFY(request->AddAttribute(new StunXorAddressAttribute(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+ port_->AddRequestAuthInfo(request);
+}
+
+void TurnChannelBindRequest::OnResponse(StunMessage* response) {
+ if (entry_) {
+ entry_->OnChannelBindSuccess();
+ // Refresh the channel binding just under the permission timeout
+ // threshold. The channel binding has a longer lifetime, but
+ // this is the easiest way to keep both the channel and the
+ // permission from expiring.
+ entry_->SendChannelBindRequest(TURN_PERMISSION_TIMEOUT - 60 * 1000);
+ }
+}
+
+void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) {
+ if (entry_) {
+ const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+ entry_->OnChannelBindError(response, error_code->code());
+ }
+}
+
+void TurnChannelBindRequest::OnTimeout() {
+ LOG_J(LS_WARNING, port_) << "Channel bind timeout";
+}
+
+void TurnChannelBindRequest::OnEntryDestroyed(TurnEntry* entry) {
+ ASSERT(entry_ == entry);
+ entry_ = NULL;
+}
+
+TurnEntry::TurnEntry(TurnPort* port, int channel_id,
+ const rtc::SocketAddress& ext_addr)
+ : port_(port),
+ channel_id_(channel_id),
+ ext_addr_(ext_addr),
+ state_(STATE_UNBOUND) {
+ // Creating permission for |ext_addr_|.
+ SendCreatePermissionRequest();
+}
+
+void TurnEntry::SendCreatePermissionRequest() {
+ port_->SendRequest(new TurnCreatePermissionRequest(
+ port_, this, ext_addr_), 0);
+}
+
+void TurnEntry::SendChannelBindRequest(int delay) {
+ port_->SendRequest(new TurnChannelBindRequest(
+ port_, this, channel_id_, ext_addr_), delay);
+}
+
+int TurnEntry::Send(const void* data, size_t size, bool payload,
+ const rtc::PacketOptions& options) {
+ rtc::ByteBuffer buf;
+ if (state_ != STATE_BOUND) {
+ // If we haven't bound the channel yet, we have to use a Send Indication.
+ TurnMessage msg;
+ msg.SetType(TURN_SEND_INDICATION);
+ msg.SetTransactionID(
+ rtc::CreateRandomString(kStunTransactionIdLength));
+ VERIFY(msg.AddAttribute(new StunXorAddressAttribute(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+ VERIFY(msg.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_DATA, data, size)));
+ VERIFY(msg.Write(&buf));
+
+ // If we're sending real data, request a channel bind that we can use later.
+ if (state_ == STATE_UNBOUND && payload) {
+ SendChannelBindRequest(0);
+ state_ = STATE_BINDING;
+ }
+ } else {
+ // If the channel is bound, we can send the data as a Channel Message.
+ buf.WriteUInt16(channel_id_);
+ buf.WriteUInt16(static_cast<uint16>(size));
+ buf.WriteBytes(reinterpret_cast<const char*>(data), size);
+ }
+ return port_->Send(buf.Data(), buf.Length(), options);
+}
+
+void TurnEntry::OnCreatePermissionSuccess() {
+ LOG_J(LS_INFO, port_) << "Create permission for "
+ << ext_addr_.ToSensitiveString()
+ << " succeeded";
+ // For success result code will be 0.
+ port_->SignalCreatePermissionResult(port_, ext_addr_, 0);
+}
+
+void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
+ LOG_J(LS_WARNING, port_) << "Create permission for "
+ << ext_addr_.ToSensitiveString()
+ << " failed, code=" << code;
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ SendCreatePermissionRequest();
+ }
+ } else {
+ // Send signal with error code.
+ port_->SignalCreatePermissionResult(port_, ext_addr_, code);
+ }
+}
+
+void TurnEntry::OnChannelBindSuccess() {
+ LOG_J(LS_INFO, port_) << "Channel bind for " << ext_addr_.ToSensitiveString()
+ << " succeeded";
+ ASSERT(state_ == STATE_BINDING || state_ == STATE_BOUND);
+ state_ = STATE_BOUND;
+}
+
+void TurnEntry::OnChannelBindError(StunMessage* response, int code) {
+ // TODO(mallinath) - Implement handling of error response for channel
+ // bind request as per http://tools.ietf.org/html/rfc5766#section-11.3
+ LOG_J(LS_WARNING, port_) << "Channel bind for "
+ << ext_addr_.ToSensitiveString()
+ << " failed, code=" << code;
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send channel bind request with fresh nonce.
+ SendChannelBindRequest(0);
+ }
+ }
+}
+
+} // namespace cricket
diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h
new file mode 100644
index 00000000..17fad176
--- /dev/null
+++ b/p2p/base/turnport.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TURNPORT_H_
+#define WEBRTC_P2P_BASE_TURNPORT_H_
+
+#include <stdio.h>
+#include <list>
+#include <set>
+#include <string>
+
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/base/asyncpacketsocket.h"
+
+namespace rtc {
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+extern const char TURN_PORT_TYPE[];
+class TurnAllocateRequest;
+class TurnEntry;
+
+class TurnPort : public Port {
+ public:
+ static TurnPort* Create(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& username, // ice username.
+ const std::string& password, // ice password.
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority) {
+ return new TurnPort(thread, factory, network, socket,
+ username, password, server_address,
+ credentials, server_priority);
+ }
+
+ static TurnPort* Create(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username, // ice username.
+ const std::string& password, // ice password.
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority) {
+ return new TurnPort(thread, factory, network, ip, min_port, max_port,
+ username, password, server_address, credentials,
+ server_priority);
+ }
+
+ virtual ~TurnPort();
+
+ const ProtocolAddress& server_address() const { return server_address_; }
+
+ bool connected() const { return connected_; }
+ const RelayCredentials& credentials() const { return credentials_; }
+
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(
+ const Candidate& c, PortInterface::CandidateOrigin origin);
+ virtual int SendTo(const void* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload);
+ virtual int SetOption(rtc::Socket::Option opt, int value);
+ virtual int GetOption(rtc::Socket::Option opt, int* value);
+ virtual int GetError();
+
+ virtual bool HandleIncomingPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ OnReadPacket(socket, data, size, remote_addr, packet_time);
+ return true;
+ }
+ virtual void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time);
+
+ virtual void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ void OnSocketConnect(rtc::AsyncPacketSocket* socket);
+ void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
+
+
+ const std::string& hash() const { return hash_; }
+ const std::string& nonce() const { return nonce_; }
+
+ int error() const { return error_; }
+
+ void OnAllocateMismatch();
+
+ rtc::AsyncPacketSocket* socket() const {
+ return socket_;
+ }
+
+ // Signal with resolved server address.
+ // Parameters are port, server address and resolved server address.
+ // This signal will be sent only if server address is resolved successfully.
+ sigslot::signal3<TurnPort*,
+ const rtc::SocketAddress&,
+ const rtc::SocketAddress&> SignalResolvedServerAddress;
+
+ // This signal is only for testing purpose.
+ sigslot::signal3<TurnPort*, const rtc::SocketAddress&, int>
+ SignalCreatePermissionResult;
+
+ protected:
+ TurnPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& username,
+ const std::string& password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority);
+
+ TurnPort(rtc::Thread* thread,
+ rtc::PacketSocketFactory* factory,
+ rtc::Network* network,
+ const rtc::IPAddress& ip,
+ int min_port, int max_port,
+ const std::string& username,
+ const std::string& password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority);
+
+ private:
+ enum {
+ MSG_ERROR = MSG_FIRST_AVAILABLE,
+ MSG_ALLOCATE_MISMATCH
+ };
+
+ typedef std::list<TurnEntry*> EntryList;
+ typedef std::map<rtc::Socket::Option, int> SocketOptionsMap;
+ typedef std::set<rtc::SocketAddress> AttemptedServerSet;
+
+ virtual void OnMessage(rtc::Message* pmsg);
+
+ bool CreateTurnClientSocket();
+
+ void set_nonce(const std::string& nonce) { nonce_ = nonce; }
+ void set_realm(const std::string& realm) {
+ if (realm != realm_) {
+ realm_ = realm;
+ UpdateHash();
+ }
+ }
+
+ bool SetAlternateServer(const rtc::SocketAddress& address);
+ void ResolveTurnAddress(const rtc::SocketAddress& address);
+ void OnResolveResult(rtc::AsyncResolverInterface* resolver);
+
+ void AddRequestAuthInfo(StunMessage* msg);
+ void OnSendStunPacket(const void* data, size_t size, StunRequest* request);
+ // Stun address from allocate success response.
+ // Currently used only for testing.
+ void OnStunAddress(const rtc::SocketAddress& address);
+ void OnAllocateSuccess(const rtc::SocketAddress& address,
+ const rtc::SocketAddress& stun_address);
+ void OnAllocateError();
+ void OnAllocateRequestTimeout();
+
+ void HandleDataIndication(const char* data, size_t size,
+ const rtc::PacketTime& packet_time);
+ void HandleChannelData(int channel_id, const char* data, size_t size,
+ const rtc::PacketTime& packet_time);
+ void DispatchPacket(const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ ProtocolType proto, const rtc::PacketTime& packet_time);
+
+ bool ScheduleRefresh(int lifetime);
+ void SendRequest(StunRequest* request, int delay);
+ int Send(const void* data, size_t size,
+ const rtc::PacketOptions& options);
+ void UpdateHash();
+ bool UpdateNonce(StunMessage* response);
+
+ bool HasPermission(const rtc::IPAddress& ipaddr) const;
+ TurnEntry* FindEntry(const rtc::SocketAddress& address) const;
+ TurnEntry* FindEntry(int channel_id) const;
+ TurnEntry* CreateEntry(const rtc::SocketAddress& address);
+ void DestroyEntry(const rtc::SocketAddress& address);
+ void OnConnectionDestroyed(Connection* conn);
+
+ ProtocolAddress server_address_;
+ RelayCredentials credentials_;
+ AttemptedServerSet attempted_server_addresses_;
+
+ rtc::AsyncPacketSocket* socket_;
+ SocketOptionsMap socket_options_;
+ rtc::AsyncResolverInterface* resolver_;
+ int error_;
+
+ StunRequestManager request_manager_;
+ std::string realm_; // From 401/438 response message.
+ std::string nonce_; // From 401/438 response message.
+ std::string hash_; // Digest of username:realm:password
+
+ int next_channel_number_;
+ EntryList entries_;
+
+ bool connected_;
+ // By default the value will be set to 0. This value will be used in
+ // calculating the candidate priority.
+ int server_priority_;
+
+ // The number of retries made due to allocate mismatch error.
+ size_t allocate_mismatch_retries_;
+
+ friend class TurnEntry;
+ friend class TurnAllocateRequest;
+ friend class TurnRefreshRequest;
+ friend class TurnCreatePermissionRequest;
+ friend class TurnChannelBindRequest;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TURNPORT_H_
diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc
new file mode 100644
index 00000000..4b524d5b
--- /dev/null
+++ b/p2p/base/turnport_unittest.cc
@@ -0,0 +1,668 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#if defined(WEBRTC_POSIX)
+#include <dirent.h>
+#endif
+
+#include "webrtc/p2p/base/basicpacketsocketfactory.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/tcpport.h"
+#include "webrtc/p2p/base/testturnserver.h"
+#include "webrtc/p2p/base/turnport.h"
+#include "webrtc/p2p/base/udpport.h"
+#include "webrtc/base/asynctcpsocket.h"
+#include "webrtc/base/buffer.h"
+#include "webrtc/base/dscp.h"
+#include "webrtc/base/firewallsocketserver.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+using rtc::SocketAddress;
+using cricket::Connection;
+using cricket::Port;
+using cricket::PortInterface;
+using cricket::TurnPort;
+using cricket::UDPPort;
+
+static const SocketAddress kLocalAddr1("11.11.11.11", 0);
+static const SocketAddress kLocalAddr2("22.22.22.22", 0);
+static const SocketAddress kLocalIPv6Addr(
+ "2401:fa00:4:1000:be30:5bff:fee5:c3", 0);
+static const SocketAddress kTurnUdpIntAddr("99.99.99.3",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const SocketAddress kTurnAlternateUdpIntAddr(
+ "99.99.99.6", cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpIPv6IntAddr(
+ "2400:4030:1:2c00:be30:abcd:efab:cdef", cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpIPv6ExtAddr(
+ "2620:0:1000:1b03:2e41:38ff:fea6:f2a4", 0);
+
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIceUfrag2[] = "TESTICEUFRAG0002";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+static const char kIcePwd2[] = "TESTICEPWD00000000000002";
+static const char kTurnUsername[] = "test";
+static const char kTurnPassword[] = "test";
+static const unsigned int kTimeout = 1000;
+
+static const cricket::ProtocolAddress kTurnUdpProtoAddr(
+ kTurnUdpIntAddr, cricket::PROTO_UDP);
+static const cricket::ProtocolAddress kTurnTcpProtoAddr(
+ kTurnTcpIntAddr, cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnUdpIPv6ProtoAddr(
+ kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
+
+static const unsigned int MSG_TESTFINISH = 0;
+
+#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
+static int GetFDCount() {
+ struct dirent *dp;
+ int fd_count = 0;
+ DIR *dir = opendir("/proc/self/fd/");
+ while ((dp = readdir(dir)) != NULL) {
+ if (dp->d_name[0] == '.')
+ continue;
+ ++fd_count;
+ }
+ closedir(dir);
+ return fd_count;
+}
+#endif
+
+class TurnPortTest : public testing::Test,
+ public sigslot::has_slots<>,
+ public rtc::MessageHandler {
+ public:
+ TurnPortTest()
+ : main_(rtc::Thread::Current()),
+ pss_(new rtc::PhysicalSocketServer),
+ ss_(new rtc::VirtualSocketServer(pss_.get())),
+ ss_scope_(ss_.get()),
+ network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32),
+ socket_factory_(rtc::Thread::Current()),
+ turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
+ turn_ready_(false),
+ turn_error_(false),
+ turn_unknown_address_(false),
+ turn_create_permission_success_(false),
+ udp_ready_(false),
+ test_finish_(false) {
+ network_.AddIP(rtc::IPAddress(INADDR_ANY));
+ }
+
+ virtual void OnMessage(rtc::Message* msg) {
+ ASSERT(msg->message_id == MSG_TESTFINISH);
+ if (msg->message_id == MSG_TESTFINISH)
+ test_finish_ = true;
+ }
+
+ void OnTurnPortComplete(Port* port) {
+ turn_ready_ = true;
+ }
+ void OnTurnPortError(Port* port) {
+ turn_error_ = true;
+ }
+ void OnTurnUnknownAddress(PortInterface* port, const SocketAddress& addr,
+ cricket::ProtocolType proto,
+ cricket::IceMessage* msg, const std::string& rf,
+ bool /*port_muxed*/) {
+ turn_unknown_address_ = true;
+ }
+ void OnTurnCreatePermissionResult(TurnPort* port, const SocketAddress& addr,
+ int code) {
+ // Ignoring the address.
+ if (code == 0) {
+ turn_create_permission_success_ = true;
+ }
+ }
+ void OnTurnReadPacket(Connection* conn, const char* data, size_t size,
+ const rtc::PacketTime& packet_time) {
+ turn_packets_.push_back(rtc::Buffer(data, size));
+ }
+ void OnUdpPortComplete(Port* port) {
+ udp_ready_ = true;
+ }
+ void OnUdpReadPacket(Connection* conn, const char* data, size_t size,
+ const rtc::PacketTime& packet_time) {
+ udp_packets_.push_back(rtc::Buffer(data, size));
+ }
+ void OnSocketReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ turn_port_->HandleIncomingPacket(socket, data, size, remote_addr,
+ packet_time);
+ }
+ rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
+ rtc::AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_STREAM);
+ EXPECT_GE(socket->Bind(addr), 0);
+ EXPECT_GE(socket->Listen(5), 0);
+ return socket;
+ }
+
+ void CreateTurnPort(const std::string& username,
+ const std::string& password,
+ const cricket::ProtocolAddress& server_address) {
+ CreateTurnPort(kLocalAddr1, username, password, server_address);
+ }
+ void CreateTurnPort(const rtc::SocketAddress& local_address,
+ const std::string& username,
+ const std::string& password,
+ const cricket::ProtocolAddress& server_address) {
+ cricket::RelayCredentials credentials(username, password);
+ turn_port_.reset(TurnPort::Create(main_, &socket_factory_, &network_,
+ local_address.ipaddr(), 0, 0,
+ kIceUfrag1, kIcePwd1,
+ server_address, credentials, 0));
+ // Set ICE protocol type to ICEPROTO_RFC5245, as port by default will be
+ // in Hybrid mode. Protocol type is necessary to send correct type STUN ping
+ // messages.
+ // This TURN port will be the controlling.
+ turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ ConnectSignals();
+ }
+
+ void CreateSharedTurnPort(const std::string& username,
+ const std::string& password,
+ const cricket::ProtocolAddress& server_address) {
+ ASSERT(server_address.proto == cricket::PROTO_UDP);
+
+ if (!socket_) {
+ socket_.reset(socket_factory_.CreateUdpSocket(
+ rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0));
+ ASSERT_TRUE(socket_ != NULL);
+ socket_->SignalReadPacket.connect(
+ this, &TurnPortTest::OnSocketReadPacket);
+ }
+
+ cricket::RelayCredentials credentials(username, password);
+ turn_port_.reset(cricket::TurnPort::Create(
+ main_, &socket_factory_, &network_, socket_.get(),
+ kIceUfrag1, kIcePwd1, server_address, credentials, 0));
+ // Set ICE protocol type to ICEPROTO_RFC5245, as port by default will be
+ // in Hybrid mode. Protocol type is necessary to send correct type STUN ping
+ // messages.
+ // This TURN port will be the controlling.
+ turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ ConnectSignals();
+ }
+
+ void ConnectSignals() {
+ turn_port_->SignalPortComplete.connect(this,
+ &TurnPortTest::OnTurnPortComplete);
+ turn_port_->SignalPortError.connect(this,
+ &TurnPortTest::OnTurnPortError);
+ turn_port_->SignalUnknownAddress.connect(this,
+ &TurnPortTest::OnTurnUnknownAddress);
+ turn_port_->SignalCreatePermissionResult.connect(this,
+ &TurnPortTest::OnTurnCreatePermissionResult);
+ }
+ void CreateUdpPort() {
+ udp_port_.reset(UDPPort::Create(main_, &socket_factory_, &network_,
+ kLocalAddr2.ipaddr(), 0, 0,
+ kIceUfrag2, kIcePwd2));
+ // Set protocol type to RFC5245, as turn port is also in same mode.
+ // UDP port will be controlled.
+ udp_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+ udp_port_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ udp_port_->SignalPortComplete.connect(
+ this, &TurnPortTest::OnUdpPortComplete);
+ }
+
+ void TestTurnConnection() {
+ // Create ports and prepare addresses.
+ ASSERT_TRUE(turn_port_ != NULL);
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_WAIT(turn_ready_, kTimeout);
+ CreateUdpPort();
+ udp_port_->PrepareAddress();
+ ASSERT_TRUE_WAIT(udp_ready_, kTimeout);
+
+ // Send ping from UDP to TURN.
+ Connection* conn1 = udp_port_->CreateConnection(
+ turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ conn1->Ping(0);
+ WAIT(!turn_unknown_address_, kTimeout);
+ EXPECT_FALSE(turn_unknown_address_);
+ EXPECT_EQ(Connection::STATE_READ_INIT, conn1->read_state());
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+ // Send ping from TURN to UDP.
+ Connection* conn2 = turn_port_->CreateConnection(
+ udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn2 != NULL);
+ ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout);
+ conn2->Ping(0);
+
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout);
+ EXPECT_EQ(Connection::STATE_READABLE, conn1->read_state());
+ EXPECT_EQ(Connection::STATE_READ_INIT, conn2->read_state());
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+ // Send another ping from UDP to TURN.
+ conn1->Ping(0);
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout);
+ EXPECT_EQ(Connection::STATE_READABLE, conn2->read_state());
+ }
+
+ void TestTurnSendData() {
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ CreateUdpPort();
+ udp_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(udp_ready_, kTimeout);
+ // Create connections and send pings.
+ Connection* conn1 = turn_port_->CreateConnection(
+ udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* conn2 = udp_port_->CreateConnection(
+ turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ ASSERT_TRUE(conn2 != NULL);
+ conn1->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnTurnReadPacket);
+ conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnUdpReadPacket);
+ conn1->Ping(0);
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout);
+ conn2->Ping(0);
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout);
+
+ // Send some data.
+ size_t num_packets = 256;
+ for (size_t i = 0; i < num_packets; ++i) {
+ unsigned char buf[256] = { 0 };
+ for (size_t j = 0; j < i + 1; ++j) {
+ buf[j] = 0xFF - static_cast<unsigned char>(j);
+ }
+ conn1->Send(buf, i + 1, options);
+ conn2->Send(buf, i + 1, options);
+ main_->ProcessMessages(0);
+ }
+
+ // Check the data.
+ ASSERT_EQ_WAIT(num_packets, turn_packets_.size(), kTimeout);
+ ASSERT_EQ_WAIT(num_packets, udp_packets_.size(), kTimeout);
+ for (size_t i = 0; i < num_packets; ++i) {
+ EXPECT_EQ(i + 1, turn_packets_[i].length());
+ EXPECT_EQ(i + 1, udp_packets_[i].length());
+ EXPECT_EQ(turn_packets_[i], udp_packets_[i]);
+ }
+ }
+
+ protected:
+ rtc::Thread* main_;
+ rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
+ rtc::scoped_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::SocketServerScope ss_scope_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ rtc::scoped_ptr<rtc::AsyncPacketSocket> socket_;
+ cricket::TestTurnServer turn_server_;
+ rtc::scoped_ptr<TurnPort> turn_port_;
+ rtc::scoped_ptr<UDPPort> udp_port_;
+ bool turn_ready_;
+ bool turn_error_;
+ bool turn_unknown_address_;
+ bool turn_create_permission_success_;
+ bool udp_ready_;
+ bool test_finish_;
+ std::vector<rtc::Buffer> turn_packets_;
+ std::vector<rtc::Buffer> udp_packets_;
+ rtc::PacketOptions options;
+};
+
+// Do a normal TURN allocation.
+TEST_F(TurnPortTest, TestTurnAllocate) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10*1024));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// Testing a normal UDP allocation using TCP connection.
+TEST_F(TurnPortTest, TestTurnTcpAllocate) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10*1024));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// Testing turn port will attempt to create TCP socket on address resolution
+// failure.
+TEST_F(TurnPortTest, DISABLED_TestTurnTcpOnAddressResolveFailure) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress(
+ rtc::SocketAddress("www.webrtc-blah-blah.com", 3478),
+ cricket::PROTO_TCP));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ // As VSS doesn't provide a DNS resolution, name resolve will fail. TurnPort
+ // will proceed in creating a TCP socket which will fail as there is no
+ // server on the above domain and error will be set to SOCKET_ERROR.
+ EXPECT_EQ(SOCKET_ERROR, turn_port_->error());
+}
+
+// In case of UDP on address resolve failure, TurnPort will not create socket
+// and return allocate failure.
+TEST_F(TurnPortTest, DISABLED_TestTurnUdpOnAdressResolveFailure) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress(
+ rtc::SocketAddress("www.webrtc-blah-blah.com", 3478),
+ cricket::PROTO_UDP));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ // Error from turn port will not be socket error.
+ EXPECT_NE(SOCKET_ERROR, turn_port_->error());
+}
+
+// Try to do a TURN allocation with an invalid password.
+TEST_F(TurnPortTest, TestTurnAllocateBadPassword) {
+ CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+// Tests that a new local address is created after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestTurnAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+}
+
+// Tests that a shared-socket-TurnPort creates its own socket after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestSharedSocketAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ turn_ready_ = false;
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_TRUE(turn_port_->SharedSocket());
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_FALSE(turn_port_->SharedSocket());
+}
+
+TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+
+ // Do a normal allocation first.
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+}
+
+// Do a TURN allocation and try to send a packet to it from the outside.
+// The packet should be dropped. Then, try to send a packet from TURN to the
+// outside. It should reach its destination. Finally, try again from the
+// outside. It should now work as well.
+TEST_F(TurnPortTest, TestTurnConnection) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection();
+}
+
+// Similar to above, except that this test will use the shared socket.
+TEST_F(TurnPortTest, TestTurnConnectionUsingSharedSocket) {
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection();
+}
+
+// Test that we can establish a TCP connection with TURN server.
+TEST_F(TurnPortTest, TestTurnTcpConnection) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnConnection();
+}
+
+// Test that we fail to create a connection when we want to use TLS over TCP.
+// This test should be removed once we have TLS support.
+TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) {
+ cricket::ProtocolAddress secure_addr(kTurnTcpProtoAddr.address,
+ kTurnTcpProtoAddr.proto,
+ true);
+ CreateTurnPort(kTurnUsername, kTurnPassword, secure_addr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+// Test try-alternate-server feature.
+TEST_F(TurnPortTest, TestTurnAlternateServer) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // Retrieve the address before we run the state machine.
+ const SocketAddress old_addr = turn_port_->server_address().address;
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ // Retrieve the address again, the turn port's address should be
+ // changed.
+ const SocketAddress new_addr = turn_port_->server_address().address;
+ EXPECT_NE(old_addr, new_addr);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// Test that we fail when we redirect to an address different from
+// current IP family.
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnUdpIPv6IntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+}
+
+// Test that we fail to handle alternate-server response over TCP protocol.
+TEST_F(TurnPortTest, TestTurnAlternateServerTcp) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.set_redirect_hook(&redirector);
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, cricket::PROTO_TCP);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+}
+
+// Test try-alternate-server catches the case of pingpong.
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPong) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+ redirect_addresses.push_back(kTurnUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+ rtc::SocketAddress address;
+ // Verify that we have exhausted all alternate servers instead of
+ // failure caused by other errors.
+ EXPECT_FALSE(redirector.ShouldRedirect(address, &address));
+}
+
+// Test try-alternate-server catch the case of repeated server.
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetition) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+
+// Run TurnConnectionTest with one-time-use nonce feature.
+// Here server will send a 438 STALE_NONCE error message for
+// every TURN transaction.
+TEST_F(TurnPortTest, TestTurnConnectionUsingOTUNonce) {
+ turn_server_.set_enable_otu_nonce(true);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection();
+}
+
+// Do a TURN allocation, establish a UDP connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnUdpToUdp) {
+ // Create ports and prepare addresses.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnSendData();
+}
+
+// Do a TURN allocation, establish a TCP connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnTcpToUdp) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ // Create ports and prepare addresses.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnSendData();
+}
+
+// Test TURN fails to make a connection from IPv6 address to a server which has
+// IPv4 address.
+TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv4) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_WAIT(turn_error_, kTimeout);
+ EXPECT_TRUE(turn_port_->Candidates().empty());
+}
+
+// Test TURN make a connection from IPv6 address to a server which has
+// IPv6 intenal address. But in this test external address is a IPv4 address,
+// hence allocated address will be a IPv4 address.
+TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv6ExtenalIPv4) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpIPv6ProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// This test verifies any FD's are not leaked after TurnPort is destroyed.
+// https://code.google.com/p/webrtc/issues/detail?id=2651
+#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
+TEST_F(TurnPortTest, TestResolverShutdown) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
+ int last_fd_count = GetFDCount();
+ // Need to supply unresolved address to kick off resolver.
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ cricket::ProtocolAddress(rtc::SocketAddress(
+ "stun.l.google.com", 3478), cricket::PROTO_UDP));
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_WAIT(turn_error_, kTimeout);
+ EXPECT_TRUE(turn_port_->Candidates().empty());
+ turn_port_.reset();
+ rtc::Thread::Current()->Post(this, MSG_TESTFINISH);
+ // Waiting for above message to be processed.
+ ASSERT_TRUE_WAIT(test_finish_, kTimeout);
+ EXPECT_EQ(last_fd_count, GetFDCount());
+}
+#endif
diff --git a/p2p/base/turnserver.cc b/p2p/base/turnserver.cc
new file mode 100644
index 00000000..8605e98d
--- /dev/null
+++ b/p2p/base/turnserver.cc
@@ -0,0 +1,1011 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/turnserver.h"
+
+#include "webrtc/p2p/base/asyncstuntcpsocket.h"
+#include "webrtc/p2p/base/common.h"
+#include "webrtc/p2p/base/packetsocketfactory.h"
+#include "webrtc/p2p/base/stun.h"
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagedigest.h"
+#include "webrtc/base/socketadapters.h"
+#include "webrtc/base/stringencode.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+// TODO(juberti): Move this all to a future turnmessage.h
+//static const int IPPROTO_UDP = 17;
+static const int kNonceTimeout = 60 * 60 * 1000; // 60 minutes
+static const int kDefaultAllocationTimeout = 10 * 60 * 1000; // 10 minutes
+static const int kPermissionTimeout = 5 * 60 * 1000; // 5 minutes
+static const int kChannelTimeout = 10 * 60 * 1000; // 10 minutes
+
+static const int kMinChannelNumber = 0x4000;
+static const int kMaxChannelNumber = 0x7FFF;
+
+static const size_t kNonceKeySize = 16;
+static const size_t kNonceSize = 40;
+
+static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+
+// TODO(mallinath) - Move these to a common place.
+inline bool IsTurnChannelData(uint16 msg_type) {
+ // The first two bits of a channel data message are 0b01.
+ return ((msg_type & 0xC000) == 0x4000);
+}
+
+// IDs used for posted messages for TurnServer::Allocation.
+enum {
+ MSG_ALLOCATION_TIMEOUT,
+};
+
+// Encapsulates a TURN allocation.
+// The object is created when an allocation request is received, and then
+// handles TURN messages (via HandleTurnMessage) and channel data messages
+// (via HandleChannelData) for this allocation when received by the server.
+// The object self-deletes and informs the server if its lifetime timer expires.
+class TurnServer::Allocation : public rtc::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ Allocation(TurnServer* server_,
+ rtc::Thread* thread, const Connection& conn,
+ rtc::AsyncPacketSocket* server_socket,
+ const std::string& key);
+ virtual ~Allocation();
+
+ Connection* conn() { return &conn_; }
+ const std::string& key() const { return key_; }
+ const std::string& transaction_id() const { return transaction_id_; }
+ const std::string& username() const { return username_; }
+ const std::string& last_nonce() const { return last_nonce_; }
+ void set_last_nonce(const std::string& nonce) { last_nonce_ = nonce; }
+
+ std::string ToString() const;
+
+ void HandleTurnMessage(const TurnMessage* msg);
+ void HandleChannelData(const char* data, size_t size);
+
+ sigslot::signal1<Allocation*> SignalDestroyed;
+
+ private:
+ typedef std::list<Permission*> PermissionList;
+ typedef std::list<Channel*> ChannelList;
+
+ void HandleAllocateRequest(const TurnMessage* msg);
+ void HandleRefreshRequest(const TurnMessage* msg);
+ void HandleSendIndication(const TurnMessage* msg);
+ void HandleCreatePermissionRequest(const TurnMessage* msg);
+ void HandleChannelBindRequest(const TurnMessage* msg);
+
+ void OnExternalPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketTime& packet_time);
+
+ static int ComputeLifetime(const TurnMessage* msg);
+ bool HasPermission(const rtc::IPAddress& addr);
+ void AddPermission(const rtc::IPAddress& addr);
+ Permission* FindPermission(const rtc::IPAddress& addr) const;
+ Channel* FindChannel(int channel_id) const;
+ Channel* FindChannel(const rtc::SocketAddress& addr) const;
+
+ void SendResponse(TurnMessage* msg);
+ void SendBadRequestResponse(const TurnMessage* req);
+ void SendErrorResponse(const TurnMessage* req, int code,
+ const std::string& reason);
+ void SendExternal(const void* data, size_t size,
+ const rtc::SocketAddress& peer);
+
+ void OnPermissionDestroyed(Permission* perm);
+ void OnChannelDestroyed(Channel* channel);
+ virtual void OnMessage(rtc::Message* msg);
+
+ TurnServer* server_;
+ rtc::Thread* thread_;
+ Connection conn_;
+ rtc::scoped_ptr<rtc::AsyncPacketSocket> external_socket_;
+ std::string key_;
+ std::string transaction_id_;
+ std::string username_;
+ std::string last_nonce_;
+ PermissionList perms_;
+ ChannelList channels_;
+};
+
+// Encapsulates a TURN permission.
+// The object is created when a create permission request is received by an
+// allocation, and self-deletes when its lifetime timer expires.
+class TurnServer::Permission : public rtc::MessageHandler {
+ public:
+ Permission(rtc::Thread* thread, const rtc::IPAddress& peer);
+ ~Permission();
+
+ const rtc::IPAddress& peer() const { return peer_; }
+ void Refresh();
+
+ sigslot::signal1<Permission*> SignalDestroyed;
+
+ private:
+ virtual void OnMessage(rtc::Message* msg);
+
+ rtc::Thread* thread_;
+ rtc::IPAddress peer_;
+};
+
+// Encapsulates a TURN channel binding.
+// The object is created when a channel bind request is received by an
+// allocation, and self-deletes when its lifetime timer expires.
+class TurnServer::Channel : public rtc::MessageHandler {
+ public:
+ Channel(rtc::Thread* thread, int id,
+ const rtc::SocketAddress& peer);
+ ~Channel();
+
+ int id() const { return id_; }
+ const rtc::SocketAddress& peer() const { return peer_; }
+ void Refresh();
+
+ sigslot::signal1<Channel*> SignalDestroyed;
+
+ private:
+ virtual void OnMessage(rtc::Message* msg);
+
+ rtc::Thread* thread_;
+ int id_;
+ rtc::SocketAddress peer_;
+};
+
+static bool InitResponse(const StunMessage* req, StunMessage* resp) {
+ int resp_type = (req) ? GetStunSuccessResponseType(req->type()) : -1;
+ if (resp_type == -1)
+ return false;
+ resp->SetType(resp_type);
+ resp->SetTransactionID(req->transaction_id());
+ return true;
+}
+
+static bool InitErrorResponse(const StunMessage* req, int code,
+ const std::string& reason, StunMessage* resp) {
+ int resp_type = (req) ? GetStunErrorResponseType(req->type()) : -1;
+ if (resp_type == -1)
+ return false;
+ resp->SetType(resp_type);
+ resp->SetTransactionID(req->transaction_id());
+ VERIFY(resp->AddAttribute(new cricket::StunErrorCodeAttribute(
+ STUN_ATTR_ERROR_CODE, code, reason)));
+ return true;
+}
+
+TurnServer::TurnServer(rtc::Thread* thread)
+ : thread_(thread),
+ nonce_key_(rtc::CreateRandomString(kNonceKeySize)),
+ auth_hook_(NULL),
+ redirect_hook_(NULL),
+ enable_otu_nonce_(false) {
+}
+
+TurnServer::~TurnServer() {
+ for (AllocationMap::iterator it = allocations_.begin();
+ it != allocations_.end(); ++it) {
+ delete it->second;
+ }
+
+ for (InternalSocketMap::iterator it = server_sockets_.begin();
+ it != server_sockets_.end(); ++it) {
+ rtc::AsyncPacketSocket* socket = it->first;
+ delete socket;
+ }
+
+ for (ServerSocketMap::iterator it = server_listen_sockets_.begin();
+ it != server_listen_sockets_.end(); ++it) {
+ rtc::AsyncSocket* socket = it->first;
+ delete socket;
+ }
+}
+
+void TurnServer::AddInternalSocket(rtc::AsyncPacketSocket* socket,
+ ProtocolType proto) {
+ ASSERT(server_sockets_.end() == server_sockets_.find(socket));
+ server_sockets_[socket] = proto;
+ socket->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket);
+}
+
+void TurnServer::AddInternalServerSocket(rtc::AsyncSocket* socket,
+ ProtocolType proto) {
+ ASSERT(server_listen_sockets_.end() ==
+ server_listen_sockets_.find(socket));
+ server_listen_sockets_[socket] = proto;
+ socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection);
+}
+
+void TurnServer::SetExternalSocketFactory(
+ rtc::PacketSocketFactory* factory,
+ const rtc::SocketAddress& external_addr) {
+ external_socket_factory_.reset(factory);
+ external_addr_ = external_addr;
+}
+
+void TurnServer::OnNewInternalConnection(rtc::AsyncSocket* socket) {
+ ASSERT(server_listen_sockets_.find(socket) != server_listen_sockets_.end());
+ AcceptConnection(socket);
+}
+
+void TurnServer::AcceptConnection(rtc::AsyncSocket* server_socket) {
+ // Check if someone is trying to connect to us.
+ rtc::SocketAddress accept_addr;
+ rtc::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr);
+ if (accepted_socket != NULL) {
+ ProtocolType proto = server_listen_sockets_[server_socket];
+ cricket::AsyncStunTCPSocket* tcp_socket =
+ new cricket::AsyncStunTCPSocket(accepted_socket, false);
+
+ tcp_socket->SignalClose.connect(this, &TurnServer::OnInternalSocketClose);
+ // Finally add the socket so it can start communicating with the client.
+ AddInternalSocket(tcp_socket, proto);
+ }
+}
+
+void TurnServer::OnInternalSocketClose(rtc::AsyncPacketSocket* socket,
+ int err) {
+ DestroyInternalSocket(socket);
+}
+
+void TurnServer::OnInternalPacket(rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketTime& packet_time) {
+ // Fail if the packet is too small to even contain a channel header.
+ if (size < TURN_CHANNEL_HEADER_SIZE) {
+ return;
+ }
+ InternalSocketMap::iterator iter = server_sockets_.find(socket);
+ ASSERT(iter != server_sockets_.end());
+ Connection conn(addr, iter->second, socket);
+ uint16 msg_type = rtc::GetBE16(data);
+ if (!IsTurnChannelData(msg_type)) {
+ // This is a STUN message.
+ HandleStunMessage(&conn, data, size);
+ } else {
+ // This is a channel message; let the allocation handle it.
+ Allocation* allocation = FindAllocation(&conn);
+ if (allocation) {
+ allocation->HandleChannelData(data, size);
+ }
+ }
+}
+
+void TurnServer::HandleStunMessage(Connection* conn, const char* data,
+ size_t size) {
+ TurnMessage msg;
+ rtc::ByteBuffer buf(data, size);
+ if (!msg.Read(&buf) || (buf.Length() > 0)) {
+ LOG(LS_WARNING) << "Received invalid STUN message";
+ return;
+ }
+
+ // If it's a STUN binding request, handle that specially.
+ if (msg.type() == STUN_BINDING_REQUEST) {
+ HandleBindingRequest(conn, &msg);
+ return;
+ }
+
+ if (redirect_hook_ != NULL && msg.type() == STUN_ALLOCATE_REQUEST) {
+ rtc::SocketAddress address;
+ if (redirect_hook_->ShouldRedirect(conn->src(), &address)) {
+ SendErrorResponseWithAlternateServer(
+ conn, &msg, address);
+ return;
+ }
+ }
+
+ // Look up the key that we'll use to validate the M-I. If we have an
+ // existing allocation, the key will already be cached.
+ Allocation* allocation = FindAllocation(conn);
+ std::string key;
+ if (!allocation) {
+ GetKey(&msg, &key);
+ } else {
+ key = allocation->key();
+ }
+
+ // Ensure the message is authorized; only needed for requests.
+ if (IsStunRequestType(msg.type())) {
+ if (!CheckAuthorization(conn, &msg, data, size, key)) {
+ return;
+ }
+ }
+
+ if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) {
+ HandleAllocateRequest(conn, &msg, key);
+ } else if (allocation &&
+ (msg.type() != STUN_ALLOCATE_REQUEST ||
+ msg.transaction_id() == allocation->transaction_id())) {
+ // This is a non-allocate request, or a retransmit of an allocate.
+ // Check that the username matches the previous username used.
+ if (IsStunRequestType(msg.type()) &&
+ msg.GetByteString(STUN_ATTR_USERNAME)->GetString() !=
+ allocation->username()) {
+ SendErrorResponse(conn, &msg, STUN_ERROR_WRONG_CREDENTIALS,
+ STUN_ERROR_REASON_WRONG_CREDENTIALS);
+ return;
+ }
+ allocation->HandleTurnMessage(&msg);
+ } else {
+ // Allocation mismatch.
+ SendErrorResponse(conn, &msg, STUN_ERROR_ALLOCATION_MISMATCH,
+ STUN_ERROR_REASON_ALLOCATION_MISMATCH);
+ }
+}
+
+bool TurnServer::GetKey(const StunMessage* msg, std::string* key) {
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ if (!username_attr) {
+ return false;
+ }
+
+ std::string username = username_attr->GetString();
+ return (auth_hook_ != NULL && auth_hook_->GetKey(username, realm_, key));
+}
+
+bool TurnServer::CheckAuthorization(Connection* conn,
+ const StunMessage* msg,
+ const char* data, size_t size,
+ const std::string& key) {
+ // RFC 5389, 10.2.2.
+ ASSERT(IsStunRequestType(msg->type()));
+ const StunByteStringAttribute* mi_attr =
+ msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ const StunByteStringAttribute* realm_attr =
+ msg->GetByteString(STUN_ATTR_REALM);
+ const StunByteStringAttribute* nonce_attr =
+ msg->GetByteString(STUN_ATTR_NONCE);
+
+ // Fail if no M-I.
+ if (!mi_attr) {
+ SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ return false;
+ }
+
+ // Fail if there is M-I but no username, nonce, or realm.
+ if (!username_attr || !realm_attr || !nonce_attr) {
+ SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return false;
+ }
+
+ // Fail if bad nonce.
+ if (!ValidateNonce(nonce_attr->GetString())) {
+ SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
+ STUN_ERROR_REASON_STALE_NONCE);
+ return false;
+ }
+
+ // Fail if bad username or M-I.
+ // We need |data| and |size| for the call to ValidateMessageIntegrity.
+ if (key.empty() || !StunMessage::ValidateMessageIntegrity(data, size, key)) {
+ SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ return false;
+ }
+
+ // Fail if one-time-use nonce feature is enabled.
+ Allocation* allocation = FindAllocation(conn);
+ if (enable_otu_nonce_ && allocation &&
+ allocation->last_nonce() == nonce_attr->GetString()) {
+ SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
+ STUN_ERROR_REASON_STALE_NONCE);
+ return false;
+ }
+
+ if (allocation) {
+ allocation->set_last_nonce(nonce_attr->GetString());
+ }
+ // Success.
+ return true;
+}
+
+void TurnServer::HandleBindingRequest(Connection* conn,
+ const StunMessage* req) {
+ StunMessage response;
+ InitResponse(req, &response);
+
+ // Tell the user the address that we received their request from.
+ StunAddressAttribute* mapped_addr_attr;
+ mapped_addr_attr = new StunXorAddressAttribute(
+ STUN_ATTR_XOR_MAPPED_ADDRESS, conn->src());
+ VERIFY(response.AddAttribute(mapped_addr_attr));
+
+ SendStun(conn, &response);
+}
+
+void TurnServer::HandleAllocateRequest(Connection* conn,
+ const TurnMessage* msg,
+ const std::string& key) {
+ // Check the parameters in the request.
+ const StunUInt32Attribute* transport_attr =
+ msg->GetUInt32(STUN_ATTR_REQUESTED_TRANSPORT);
+ if (!transport_attr) {
+ SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return;
+ }
+
+ // Only UDP is supported right now.
+ int proto = transport_attr->value() >> 24;
+ if (proto != IPPROTO_UDP) {
+ SendErrorResponse(conn, msg, STUN_ERROR_UNSUPPORTED_PROTOCOL,
+ STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL);
+ return;
+ }
+
+ // Create the allocation and let it send the success response.
+ // If the actual socket allocation fails, send an internal error.
+ Allocation* alloc = CreateAllocation(conn, proto, key);
+ if (alloc) {
+ alloc->HandleTurnMessage(msg);
+ } else {
+ SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR,
+ "Failed to allocate socket");
+ }
+}
+
+std::string TurnServer::GenerateNonce() const {
+ // Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now))
+ uint32 now = rtc::Time();
+ std::string input(reinterpret_cast<const char*>(&now), sizeof(now));
+ std::string nonce = rtc::hex_encode(input.c_str(), input.size());
+ nonce += rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_, input);
+ ASSERT(nonce.size() == kNonceSize);
+ return nonce;
+}
+
+bool TurnServer::ValidateNonce(const std::string& nonce) const {
+ // Check the size.
+ if (nonce.size() != kNonceSize) {
+ return false;
+ }
+
+ // Decode the timestamp.
+ uint32 then;
+ char* p = reinterpret_cast<char*>(&then);
+ size_t len = rtc::hex_decode(p, sizeof(then),
+ nonce.substr(0, sizeof(then) * 2));
+ if (len != sizeof(then)) {
+ return false;
+ }
+
+ // Verify the HMAC.
+ if (nonce.substr(sizeof(then) * 2) != rtc::ComputeHmac(
+ rtc::DIGEST_MD5, nonce_key_, std::string(p, sizeof(then)))) {
+ return false;
+ }
+
+ // Validate the timestamp.
+ return rtc::TimeSince(then) < kNonceTimeout;
+}
+
+TurnServer::Allocation* TurnServer::FindAllocation(Connection* conn) {
+ AllocationMap::const_iterator it = allocations_.find(*conn);
+ return (it != allocations_.end()) ? it->second : NULL;
+}
+
+TurnServer::Allocation* TurnServer::CreateAllocation(Connection* conn,
+ int proto,
+ const std::string& key) {
+ rtc::AsyncPacketSocket* external_socket = (external_socket_factory_) ?
+ external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0) : NULL;
+ if (!external_socket) {
+ return NULL;
+ }
+
+ // The Allocation takes ownership of the socket.
+ Allocation* allocation = new Allocation(this,
+ thread_, *conn, external_socket, key);
+ allocation->SignalDestroyed.connect(this, &TurnServer::OnAllocationDestroyed);
+ allocations_[*conn] = allocation;
+ return allocation;
+}
+
+void TurnServer::SendErrorResponse(Connection* conn,
+ const StunMessage* req,
+ int code, const std::string& reason) {
+ TurnMessage resp;
+ InitErrorResponse(req, code, reason, &resp);
+ LOG(LS_INFO) << "Sending error response, type=" << resp.type()
+ << ", code=" << code << ", reason=" << reason;
+ SendStun(conn, &resp);
+}
+
+void TurnServer::SendErrorResponseWithRealmAndNonce(
+ Connection* conn, const StunMessage* msg,
+ int code, const std::string& reason) {
+ TurnMessage resp;
+ InitErrorResponse(msg, code, reason, &resp);
+ VERIFY(resp.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_NONCE, GenerateNonce())));
+ VERIFY(resp.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_REALM, realm_)));
+ SendStun(conn, &resp);
+}
+
+void TurnServer::SendErrorResponseWithAlternateServer(
+ Connection* conn, const StunMessage* msg,
+ const rtc::SocketAddress& addr) {
+ TurnMessage resp;
+ InitErrorResponse(msg, STUN_ERROR_TRY_ALTERNATE,
+ STUN_ERROR_REASON_TRY_ALTERNATE_SERVER, &resp);
+ VERIFY(resp.AddAttribute(new StunAddressAttribute(
+ STUN_ATTR_ALTERNATE_SERVER, addr)));
+ SendStun(conn, &resp);
+}
+
+void TurnServer::SendStun(Connection* conn, StunMessage* msg) {
+ rtc::ByteBuffer buf;
+ // Add a SOFTWARE attribute if one is set.
+ if (!software_.empty()) {
+ VERIFY(msg->AddAttribute(
+ new StunByteStringAttribute(STUN_ATTR_SOFTWARE, software_)));
+ }
+ msg->Write(&buf);
+ Send(conn, buf);
+}
+
+void TurnServer::Send(Connection* conn,
+ const rtc::ByteBuffer& buf) {
+ rtc::PacketOptions options;
+ conn->socket()->SendTo(buf.Data(), buf.Length(), conn->src(), options);
+}
+
+void TurnServer::OnAllocationDestroyed(Allocation* allocation) {
+ // Removing the internal socket if the connection is not udp.
+ rtc::AsyncPacketSocket* socket = allocation->conn()->socket();
+ InternalSocketMap::iterator iter = server_sockets_.find(socket);
+ ASSERT(iter != server_sockets_.end());
+ // Skip if the socket serving this allocation is UDP, as this will be shared
+ // by all allocations.
+ if (iter->second != cricket::PROTO_UDP) {
+ DestroyInternalSocket(socket);
+ }
+
+ AllocationMap::iterator it = allocations_.find(*(allocation->conn()));
+ if (it != allocations_.end())
+ allocations_.erase(it);
+}
+
+void TurnServer::DestroyInternalSocket(rtc::AsyncPacketSocket* socket) {
+ InternalSocketMap::iterator iter = server_sockets_.find(socket);
+ if (iter != server_sockets_.end()) {
+ rtc::AsyncPacketSocket* socket = iter->first;
+ // We must destroy the socket async to avoid invalidating the sigslot
+ // callback list iterator inside a sigslot callback.
+ rtc::Thread::Current()->Dispose(socket);
+ server_sockets_.erase(iter);
+ }
+}
+
+TurnServer::Connection::Connection(const rtc::SocketAddress& src,
+ ProtocolType proto,
+ rtc::AsyncPacketSocket* socket)
+ : src_(src),
+ dst_(socket->GetRemoteAddress()),
+ proto_(proto),
+ socket_(socket) {
+}
+
+bool TurnServer::Connection::operator==(const Connection& c) const {
+ return src_ == c.src_ && dst_ == c.dst_ && proto_ == c.proto_;
+}
+
+bool TurnServer::Connection::operator<(const Connection& c) const {
+ return src_ < c.src_ || dst_ < c.dst_ || proto_ < c.proto_;
+}
+
+std::string TurnServer::Connection::ToString() const {
+ const char* const kProtos[] = {
+ "unknown", "udp", "tcp", "ssltcp"
+ };
+ std::ostringstream ost;
+ ost << src_.ToString() << "-" << dst_.ToString() << ":"<< kProtos[proto_];
+ return ost.str();
+}
+
+TurnServer::Allocation::Allocation(TurnServer* server,
+ rtc::Thread* thread,
+ const Connection& conn,
+ rtc::AsyncPacketSocket* socket,
+ const std::string& key)
+ : server_(server),
+ thread_(thread),
+ conn_(conn),
+ external_socket_(socket),
+ key_(key) {
+ external_socket_->SignalReadPacket.connect(
+ this, &TurnServer::Allocation::OnExternalPacket);
+}
+
+TurnServer::Allocation::~Allocation() {
+ for (ChannelList::iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ delete *it;
+ }
+ for (PermissionList::iterator it = perms_.begin();
+ it != perms_.end(); ++it) {
+ delete *it;
+ }
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ LOG_J(LS_INFO, this) << "Allocation destroyed";
+}
+
+std::string TurnServer::Allocation::ToString() const {
+ std::ostringstream ost;
+ ost << "Alloc[" << conn_.ToString() << "]";
+ return ost.str();
+}
+
+void TurnServer::Allocation::HandleTurnMessage(const TurnMessage* msg) {
+ ASSERT(msg != NULL);
+ switch (msg->type()) {
+ case STUN_ALLOCATE_REQUEST:
+ HandleAllocateRequest(msg);
+ break;
+ case TURN_REFRESH_REQUEST:
+ HandleRefreshRequest(msg);
+ break;
+ case TURN_SEND_INDICATION:
+ HandleSendIndication(msg);
+ break;
+ case TURN_CREATE_PERMISSION_REQUEST:
+ HandleCreatePermissionRequest(msg);
+ break;
+ case TURN_CHANNEL_BIND_REQUEST:
+ HandleChannelBindRequest(msg);
+ break;
+ default:
+ // Not sure what to do with this, just eat it.
+ LOG_J(LS_WARNING, this) << "Invalid TURN message type received: "
+ << msg->type();
+ }
+}
+
+void TurnServer::Allocation::HandleAllocateRequest(const TurnMessage* msg) {
+ // Copy the important info from the allocate request.
+ transaction_id_ = msg->transaction_id();
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT(username_attr != NULL);
+ username_ = username_attr->GetString();
+
+ // Figure out the lifetime and start the allocation timer.
+ int lifetime_secs = ComputeLifetime(msg);
+ thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT);
+
+ LOG_J(LS_INFO, this) << "Created allocation, lifetime=" << lifetime_secs;
+
+ // We've already validated all the important bits; just send a response here.
+ TurnMessage response;
+ InitResponse(msg, &response);
+
+ StunAddressAttribute* mapped_addr_attr =
+ new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, conn_.src());
+ StunAddressAttribute* relayed_addr_attr =
+ new StunXorAddressAttribute(STUN_ATTR_XOR_RELAYED_ADDRESS,
+ external_socket_->GetLocalAddress());
+ StunUInt32Attribute* lifetime_attr =
+ new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs);
+ VERIFY(response.AddAttribute(mapped_addr_attr));
+ VERIFY(response.AddAttribute(relayed_addr_attr));
+ VERIFY(response.AddAttribute(lifetime_attr));
+
+ SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleRefreshRequest(const TurnMessage* msg) {
+ // Figure out the new lifetime.
+ int lifetime_secs = ComputeLifetime(msg);
+
+ // Reset the expiration timer.
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT);
+
+ LOG_J(LS_INFO, this) << "Refreshed allocation, lifetime=" << lifetime_secs;
+
+ // Send a success response with a LIFETIME attribute.
+ TurnMessage response;
+ InitResponse(msg, &response);
+
+ StunUInt32Attribute* lifetime_attr =
+ new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs);
+ VERIFY(response.AddAttribute(lifetime_attr));
+
+ SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleSendIndication(const TurnMessage* msg) {
+ // Check mandatory attributes.
+ const StunByteStringAttribute* data_attr = msg->GetByteString(STUN_ATTR_DATA);
+ const StunAddressAttribute* peer_attr =
+ msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!data_attr || !peer_attr) {
+ LOG_J(LS_WARNING, this) << "Received invalid send indication";
+ return;
+ }
+
+ // If a permission exists, send the data on to the peer.
+ if (HasPermission(peer_attr->GetAddress().ipaddr())) {
+ SendExternal(data_attr->bytes(), data_attr->length(),
+ peer_attr->GetAddress());
+ } else {
+ LOG_J(LS_WARNING, this) << "Received send indication without permission"
+ << "peer=" << peer_attr->GetAddress();
+ }
+}
+
+void TurnServer::Allocation::HandleCreatePermissionRequest(
+ const TurnMessage* msg) {
+ // Check mandatory attributes.
+ const StunAddressAttribute* peer_attr =
+ msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!peer_attr) {
+ SendBadRequestResponse(msg);
+ return;
+ }
+
+ // Add this permission.
+ AddPermission(peer_attr->GetAddress().ipaddr());
+
+ LOG_J(LS_INFO, this) << "Created permission, peer="
+ << peer_attr->GetAddress();
+
+ // Send a success response.
+ TurnMessage response;
+ InitResponse(msg, &response);
+ SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleChannelBindRequest(const TurnMessage* msg) {
+ // Check mandatory attributes.
+ const StunUInt32Attribute* channel_attr =
+ msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER);
+ const StunAddressAttribute* peer_attr =
+ msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!channel_attr || !peer_attr) {
+ SendBadRequestResponse(msg);
+ return;
+ }
+
+ // Check that channel id is valid.
+ int channel_id = channel_attr->value() >> 16;
+ if (channel_id < kMinChannelNumber || channel_id > kMaxChannelNumber) {
+ SendBadRequestResponse(msg);
+ return;
+ }
+
+ // Check that this channel id isn't bound to another transport address, and
+ // that this transport address isn't bound to another channel id.
+ Channel* channel1 = FindChannel(channel_id);
+ Channel* channel2 = FindChannel(peer_attr->GetAddress());
+ if (channel1 != channel2) {
+ SendBadRequestResponse(msg);
+ return;
+ }
+
+ // Add or refresh this channel.
+ if (!channel1) {
+ channel1 = new Channel(thread_, channel_id, peer_attr->GetAddress());
+ channel1->SignalDestroyed.connect(this,
+ &TurnServer::Allocation::OnChannelDestroyed);
+ channels_.push_back(channel1);
+ } else {
+ channel1->Refresh();
+ }
+
+ // Channel binds also refresh permissions.
+ AddPermission(peer_attr->GetAddress().ipaddr());
+
+ LOG_J(LS_INFO, this) << "Bound channel, id=" << channel_id
+ << ", peer=" << peer_attr->GetAddress();
+
+ // Send a success response.
+ TurnMessage response;
+ InitResponse(msg, &response);
+ SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleChannelData(const char* data, size_t size) {
+ // Extract the channel number from the data.
+ uint16 channel_id = rtc::GetBE16(data);
+ Channel* channel = FindChannel(channel_id);
+ if (channel) {
+ // Send the data to the peer address.
+ SendExternal(data + TURN_CHANNEL_HEADER_SIZE,
+ size - TURN_CHANNEL_HEADER_SIZE, channel->peer());
+ } else {
+ LOG_J(LS_WARNING, this) << "Received channel data for invalid channel, id="
+ << channel_id;
+ }
+}
+
+void TurnServer::Allocation::OnExternalPacket(
+ rtc::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(external_socket_.get() == socket);
+ Channel* channel = FindChannel(addr);
+ if (channel) {
+ // There is a channel bound to this address. Send as a channel message.
+ rtc::ByteBuffer buf;
+ buf.WriteUInt16(channel->id());
+ buf.WriteUInt16(static_cast<uint16>(size));
+ buf.WriteBytes(data, size);
+ server_->Send(&conn_, buf);
+ } else if (HasPermission(addr.ipaddr())) {
+ // No channel, but a permission exists. Send as a data indication.
+ TurnMessage msg;
+ msg.SetType(TURN_DATA_INDICATION);
+ msg.SetTransactionID(
+ rtc::CreateRandomString(kStunTransactionIdLength));
+ VERIFY(msg.AddAttribute(new StunXorAddressAttribute(
+ STUN_ATTR_XOR_PEER_ADDRESS, addr)));
+ VERIFY(msg.AddAttribute(new StunByteStringAttribute(
+ STUN_ATTR_DATA, data, size)));
+ server_->SendStun(&conn_, &msg);
+ } else {
+ LOG_J(LS_WARNING, this) << "Received external packet without permission, "
+ << "peer=" << addr;
+ }
+}
+
+int TurnServer::Allocation::ComputeLifetime(const TurnMessage* msg) {
+ // Return the smaller of our default lifetime and the requested lifetime.
+ uint32 lifetime = kDefaultAllocationTimeout / 1000; // convert to seconds
+ const StunUInt32Attribute* lifetime_attr = msg->GetUInt32(STUN_ATTR_LIFETIME);
+ if (lifetime_attr && lifetime_attr->value() < lifetime) {
+ lifetime = lifetime_attr->value();
+ }
+ return lifetime;
+}
+
+bool TurnServer::Allocation::HasPermission(const rtc::IPAddress& addr) {
+ return (FindPermission(addr) != NULL);
+}
+
+void TurnServer::Allocation::AddPermission(const rtc::IPAddress& addr) {
+ Permission* perm = FindPermission(addr);
+ if (!perm) {
+ perm = new Permission(thread_, addr);
+ perm->SignalDestroyed.connect(
+ this, &TurnServer::Allocation::OnPermissionDestroyed);
+ perms_.push_back(perm);
+ } else {
+ perm->Refresh();
+ }
+}
+
+TurnServer::Permission* TurnServer::Allocation::FindPermission(
+ const rtc::IPAddress& addr) const {
+ for (PermissionList::const_iterator it = perms_.begin();
+ it != perms_.end(); ++it) {
+ if ((*it)->peer() == addr)
+ return *it;
+ }
+ return NULL;
+}
+
+TurnServer::Channel* TurnServer::Allocation::FindChannel(int channel_id) const {
+ for (ChannelList::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ if ((*it)->id() == channel_id)
+ return *it;
+ }
+ return NULL;
+}
+
+TurnServer::Channel* TurnServer::Allocation::FindChannel(
+ const rtc::SocketAddress& addr) const {
+ for (ChannelList::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ if ((*it)->peer() == addr)
+ return *it;
+ }
+ return NULL;
+}
+
+void TurnServer::Allocation::SendResponse(TurnMessage* msg) {
+ // Success responses always have M-I.
+ msg->AddMessageIntegrity(key_);
+ server_->SendStun(&conn_, msg);
+}
+
+void TurnServer::Allocation::SendBadRequestResponse(const TurnMessage* req) {
+ SendErrorResponse(req, STUN_ERROR_BAD_REQUEST, STUN_ERROR_REASON_BAD_REQUEST);
+}
+
+void TurnServer::Allocation::SendErrorResponse(const TurnMessage* req, int code,
+ const std::string& reason) {
+ server_->SendErrorResponse(&conn_, req, code, reason);
+}
+
+void TurnServer::Allocation::SendExternal(const void* data, size_t size,
+ const rtc::SocketAddress& peer) {
+ rtc::PacketOptions options;
+ external_socket_->SendTo(data, size, peer, options);
+}
+
+void TurnServer::Allocation::OnMessage(rtc::Message* msg) {
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
+ SignalDestroyed(this);
+ delete this;
+}
+
+void TurnServer::Allocation::OnPermissionDestroyed(Permission* perm) {
+ PermissionList::iterator it = std::find(perms_.begin(), perms_.end(), perm);
+ ASSERT(it != perms_.end());
+ perms_.erase(it);
+}
+
+void TurnServer::Allocation::OnChannelDestroyed(Channel* channel) {
+ ChannelList::iterator it =
+ std::find(channels_.begin(), channels_.end(), channel);
+ ASSERT(it != channels_.end());
+ channels_.erase(it);
+}
+
+TurnServer::Permission::Permission(rtc::Thread* thread,
+ const rtc::IPAddress& peer)
+ : thread_(thread), peer_(peer) {
+ Refresh();
+}
+
+TurnServer::Permission::~Permission() {
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+}
+
+void TurnServer::Permission::Refresh() {
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(kPermissionTimeout, this, MSG_ALLOCATION_TIMEOUT);
+}
+
+void TurnServer::Permission::OnMessage(rtc::Message* msg) {
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
+ SignalDestroyed(this);
+ delete this;
+}
+
+TurnServer::Channel::Channel(rtc::Thread* thread, int id,
+ const rtc::SocketAddress& peer)
+ : thread_(thread), id_(id), peer_(peer) {
+ Refresh();
+}
+
+TurnServer::Channel::~Channel() {
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+}
+
+void TurnServer::Channel::Refresh() {
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(kChannelTimeout, this, MSG_ALLOCATION_TIMEOUT);
+}
+
+void TurnServer::Channel::OnMessage(rtc::Message* msg) {
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
+ SignalDestroyed(this);
+ delete this;
+}
+
+} // namespace cricket
diff --git a/p2p/base/turnserver.h b/p2p/base/turnserver.h
new file mode 100644
index 00000000..670b6180
--- /dev/null
+++ b/p2p/base/turnserver.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_TURNSERVER_H_
+#define WEBRTC_P2P_BASE_TURNSERVER_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/base/asyncpacketsocket.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace rtc {
+class ByteBuffer;
+class PacketSocketFactory;
+class Thread;
+}
+
+namespace cricket {
+
+class StunMessage;
+class TurnMessage;
+
+// The default server port for TURN, as specified in RFC5766.
+const int TURN_SERVER_PORT = 3478;
+
+// An interface through which the MD5 credential hash can be retrieved.
+class TurnAuthInterface {
+ public:
+ // Gets HA1 for the specified user and realm.
+ // HA1 = MD5(A1) = MD5(username:realm:password).
+ // Return true if the given username and realm are valid, or false if not.
+ virtual bool GetKey(const std::string& username, const std::string& realm,
+ std::string* key) = 0;
+};
+
+// An interface enables Turn Server to control redirection behavior.
+class TurnRedirectInterface {
+ public:
+ virtual bool ShouldRedirect(const rtc::SocketAddress& address,
+ rtc::SocketAddress* out) = 0;
+ virtual ~TurnRedirectInterface() {}
+};
+
+// The core TURN server class. Give it a socket to listen on via
+// AddInternalServerSocket, and a factory to create external sockets via
+// SetExternalSocketFactory, and it's ready to go.
+// Not yet wired up: TCP support.
+class TurnServer : public sigslot::has_slots<> {
+ public:
+ explicit TurnServer(rtc::Thread* thread);
+ ~TurnServer();
+
+ // Gets/sets the realm value to use for the server.
+ const std::string& realm() const { return realm_; }
+ void set_realm(const std::string& realm) { realm_ = realm; }
+
+ // Gets/sets the value for the SOFTWARE attribute for TURN messages.
+ const std::string& software() const { return software_; }
+ void set_software(const std::string& software) { software_ = software; }
+
+ // Sets the authentication callback; does not take ownership.
+ void set_auth_hook(TurnAuthInterface* auth_hook) { auth_hook_ = auth_hook; }
+
+ void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
+ redirect_hook_ = redirect_hook;
+ }
+
+ void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; }
+
+ // Starts listening for packets from internal clients.
+ void AddInternalSocket(rtc::AsyncPacketSocket* socket,
+ ProtocolType proto);
+ // Starts listening for the connections on this socket. When someone tries
+ // to connect, the connection will be accepted and a new internal socket
+ // will be added.
+ void AddInternalServerSocket(rtc::AsyncSocket* socket,
+ ProtocolType proto);
+ // Specifies the factory to use for creating external sockets.
+ void SetExternalSocketFactory(rtc::PacketSocketFactory* factory,
+ const rtc::SocketAddress& address);
+
+ private:
+ // Encapsulates the client's connection to the server.
+ class Connection {
+ public:
+ Connection() : proto_(PROTO_UDP), socket_(NULL) {}
+ Connection(const rtc::SocketAddress& src,
+ ProtocolType proto,
+ rtc::AsyncPacketSocket* socket);
+ const rtc::SocketAddress& src() const { return src_; }
+ rtc::AsyncPacketSocket* socket() { return socket_; }
+ bool operator==(const Connection& t) const;
+ bool operator<(const Connection& t) const;
+ std::string ToString() const;
+
+ private:
+ rtc::SocketAddress src_;
+ rtc::SocketAddress dst_;
+ cricket::ProtocolType proto_;
+ rtc::AsyncPacketSocket* socket_;
+ };
+ class Allocation;
+ class Permission;
+ class Channel;
+ typedef std::map<Connection, Allocation*> AllocationMap;
+
+ void OnInternalPacket(rtc::AsyncPacketSocket* socket, const char* data,
+ size_t size, const rtc::SocketAddress& address,
+ const rtc::PacketTime& packet_time);
+
+ void OnNewInternalConnection(rtc::AsyncSocket* socket);
+
+ // Accept connections on this server socket.
+ void AcceptConnection(rtc::AsyncSocket* server_socket);
+ void OnInternalSocketClose(rtc::AsyncPacketSocket* socket, int err);
+
+ void HandleStunMessage(Connection* conn, const char* data, size_t size);
+ void HandleBindingRequest(Connection* conn, const StunMessage* msg);
+ void HandleAllocateRequest(Connection* conn, const TurnMessage* msg,
+ const std::string& key);
+
+ bool GetKey(const StunMessage* msg, std::string* key);
+ bool CheckAuthorization(Connection* conn, const StunMessage* msg,
+ const char* data, size_t size,
+ const std::string& key);
+ std::string GenerateNonce() const;
+ bool ValidateNonce(const std::string& nonce) const;
+
+ Allocation* FindAllocation(Connection* conn);
+ Allocation* CreateAllocation(Connection* conn, int proto,
+ const std::string& key);
+
+ void SendErrorResponse(Connection* conn, const StunMessage* req,
+ int code, const std::string& reason);
+
+ void SendErrorResponseWithRealmAndNonce(Connection* conn,
+ const StunMessage* req,
+ int code,
+ const std::string& reason);
+
+ void SendErrorResponseWithAlternateServer(Connection* conn,
+ const StunMessage* req,
+ const rtc::SocketAddress& addr);
+
+ void SendStun(Connection* conn, StunMessage* msg);
+ void Send(Connection* conn, const rtc::ByteBuffer& buf);
+
+ void OnAllocationDestroyed(Allocation* allocation);
+ void DestroyInternalSocket(rtc::AsyncPacketSocket* socket);
+
+ typedef std::map<rtc::AsyncPacketSocket*,
+ ProtocolType> InternalSocketMap;
+ typedef std::map<rtc::AsyncSocket*,
+ ProtocolType> ServerSocketMap;
+
+ rtc::Thread* thread_;
+ std::string nonce_key_;
+ std::string realm_;
+ std::string software_;
+ TurnAuthInterface* auth_hook_;
+ TurnRedirectInterface* redirect_hook_;
+ // otu - one-time-use. Server will respond with 438 if it's
+ // sees the same nonce in next transaction.
+ bool enable_otu_nonce_;
+
+ InternalSocketMap server_sockets_;
+ ServerSocketMap server_listen_sockets_;
+ rtc::scoped_ptr<rtc::PacketSocketFactory>
+ external_socket_factory_;
+ rtc::SocketAddress external_addr_;
+
+ AllocationMap allocations_;
+};
+
+} // namespace cricket
+
+#endif // WEBRTC_P2P_BASE_TURNSERVER_H_
diff --git a/p2p/base/udpport.h b/p2p/base/udpport.h
new file mode 100644
index 00000000..9f868644
--- /dev/null
+++ b/p2p/base/udpport.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_P2P_BASE_UDPPORT_H_
+#define WEBRTC_P2P_BASE_UDPPORT_H_
+
+// StunPort will be handling UDPPort functionality.
+#include "webrtc/p2p/base/stunport.h"
+
+#endif // WEBRTC_P2P_BASE_UDPPORT_H_