1/* 2 * libjingle 3 * Copyright 2010, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include <string> 29#include "talk/p2p/base/sessionmanager.h" 30#include "talk/p2p/base/transport.h" 31#include "talk/p2p/client/fakeportallocator.h" 32#include "talk/session/tunnel/tunnelsessionclient.h" 33#include "webrtc/base/gunit.h" 34#include "webrtc/base/messagehandler.h" 35#include "webrtc/base/scoped_ptr.h" 36#include "webrtc/base/stream.h" 37#include "webrtc/base/thread.h" 38#include "webrtc/base/timeutils.h" 39 40static const int kTimeoutMs = 10000; 41static const int kBlockSize = 4096; 42static const buzz::Jid kLocalJid("local@localhost"); 43static const buzz::Jid kRemoteJid("remote@localhost"); 44 45// This test fixture creates the necessary plumbing to create and run 46// two TunnelSessionClients that talk to each other. 47class TunnelSessionClientTest : public testing::Test, 48 public rtc::MessageHandler, 49 public sigslot::has_slots<> { 50 public: 51 TunnelSessionClientTest() 52 : local_pa_(rtc::Thread::Current(), NULL), 53 remote_pa_(rtc::Thread::Current(), NULL), 54 local_sm_(&local_pa_, rtc::Thread::Current()), 55 remote_sm_(&remote_pa_, rtc::Thread::Current()), 56 local_client_(kLocalJid, &local_sm_), 57 remote_client_(kRemoteJid, &remote_sm_), 58 done_(false) { 59 local_sm_.SignalRequestSignaling.connect(this, 60 &TunnelSessionClientTest::OnLocalRequestSignaling); 61 local_sm_.SignalOutgoingMessage.connect(this, 62 &TunnelSessionClientTest::OnOutgoingMessage); 63 remote_sm_.SignalRequestSignaling.connect(this, 64 &TunnelSessionClientTest::OnRemoteRequestSignaling); 65 remote_sm_.SignalOutgoingMessage.connect(this, 66 &TunnelSessionClientTest::OnOutgoingMessage); 67 remote_client_.SignalIncomingTunnel.connect(this, 68 &TunnelSessionClientTest::OnIncomingTunnel); 69 } 70 71 // Transfer the desired amount of data from the local to the remote client. 72 void TestTransfer(int size) { 73 // Create some dummy data to send. 74 send_stream_.ReserveSize(size); 75 for (int i = 0; i < size; ++i) { 76 char ch = static_cast<char>(i); 77 send_stream_.Write(&ch, 1, NULL, NULL); 78 } 79 send_stream_.Rewind(); 80 // Prepare the receive stream. 81 recv_stream_.ReserveSize(size); 82 // Create the tunnel and set things in motion. 83 local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test")); 84 local_tunnel_->SignalEvent.connect(this, 85 &TunnelSessionClientTest::OnStreamEvent); 86 EXPECT_TRUE_WAIT(done_, kTimeoutMs); 87 // Make sure we received the right data. 88 EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(), 89 recv_stream_.GetBuffer(), size)); 90 } 91 92 private: 93 enum { MSG_LSIGNAL, MSG_RSIGNAL }; 94 95 // There's no SessionManager* argument in this callback, so we need 2 of them. 96 void OnLocalRequestSignaling() { 97 local_sm_.OnSignalingReady(); 98 } 99 void OnRemoteRequestSignaling() { 100 remote_sm_.OnSignalingReady(); 101 } 102 103 // Post a message, to avoid problems with directly connecting the callbacks. 104 void OnOutgoingMessage(cricket::SessionManager* manager, 105 const buzz::XmlElement* stanza) { 106 if (manager == &local_sm_) { 107 rtc::Thread::Current()->Post(this, MSG_LSIGNAL, 108 rtc::WrapMessageData(*stanza)); 109 } else if (manager == &remote_sm_) { 110 rtc::Thread::Current()->Post(this, MSG_RSIGNAL, 111 rtc::WrapMessageData(*stanza)); 112 } 113 } 114 115 // Need to add a "from=" attribute (normally added by the server) 116 // Then route the incoming signaling message to the "other" session manager. 117 virtual void OnMessage(rtc::Message* message) { 118 rtc::TypedMessageData<buzz::XmlElement>* data = 119 static_cast<rtc::TypedMessageData<buzz::XmlElement>*>( 120 message->pdata); 121 bool response = data->data().Attr(buzz::QN_TYPE) == buzz::STR_RESULT; 122 if (message->message_id == MSG_RSIGNAL) { 123 data->data().AddAttr(buzz::QN_FROM, remote_client_.jid().Str()); 124 if (!response) { 125 local_sm_.OnIncomingMessage(&data->data()); 126 } else { 127 local_sm_.OnIncomingResponse(NULL, &data->data()); 128 } 129 } else if (message->message_id == MSG_LSIGNAL) { 130 data->data().AddAttr(buzz::QN_FROM, local_client_.jid().Str()); 131 if (!response) { 132 remote_sm_.OnIncomingMessage(&data->data()); 133 } else { 134 remote_sm_.OnIncomingResponse(NULL, &data->data()); 135 } 136 } 137 delete data; 138 } 139 140 // Accept the tunnel when it arrives and wire up the stream. 141 void OnIncomingTunnel(cricket::TunnelSessionClient* client, 142 buzz::Jid jid, std::string description, 143 cricket::Session* session) { 144 remote_tunnel_.reset(remote_client_.AcceptTunnel(session)); 145 remote_tunnel_->SignalEvent.connect(this, 146 &TunnelSessionClientTest::OnStreamEvent); 147 } 148 149 // Send from send_stream_ as long as we're not flow-controlled. 150 // Read bytes out into recv_stream_ as they arrive. 151 // End the test when we are notified that the local side has closed the 152 // tunnel. All data has been read out at this point. 153 void OnStreamEvent(rtc::StreamInterface* stream, int events, 154 int error) { 155 if (events & rtc::SE_READ) { 156 if (stream == remote_tunnel_.get()) { 157 ReadData(); 158 } 159 } 160 if (events & rtc::SE_WRITE) { 161 if (stream == local_tunnel_.get()) { 162 bool done = false; 163 WriteData(&done); 164 if (done) { 165 local_tunnel_->Close(); 166 } 167 } 168 } 169 if (events & rtc::SE_CLOSE) { 170 if (stream == remote_tunnel_.get()) { 171 remote_tunnel_->Close(); 172 done_ = true; 173 } 174 } 175 } 176 177 // Spool from the tunnel into recv_stream. 178 // Flow() doesn't work here because it won't write if the read blocks. 179 void ReadData() { 180 char block[kBlockSize]; 181 size_t read, position; 182 rtc::StreamResult res; 183 while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) == 184 rtc::SR_SUCCESS) { 185 recv_stream_.Write(block, read, NULL, NULL); 186 } 187 ASSERT(res != rtc::SR_EOS); 188 recv_stream_.GetPosition(&position); 189 LOG(LS_VERBOSE) << "Recv position: " << position; 190 } 191 // Spool from send_stream into the tunnel. Back up if we get flow controlled. 192 void WriteData(bool* done) { 193 char block[kBlockSize]; 194 size_t leftover = 0, position; 195 rtc::StreamResult res = rtc::Flow(&send_stream_, 196 block, sizeof(block), local_tunnel_.get(), &leftover); 197 if (res == rtc::SR_BLOCK) { 198 send_stream_.GetPosition(&position); 199 send_stream_.SetPosition(position - leftover); 200 LOG(LS_VERBOSE) << "Send position: " << position - leftover; 201 *done = false; 202 } else if (res == rtc::SR_SUCCESS) { 203 *done = true; 204 } else { 205 ASSERT(false); // shouldn't happen 206 } 207 } 208 209 private: 210 cricket::FakePortAllocator local_pa_; 211 cricket::FakePortAllocator remote_pa_; 212 cricket::SessionManager local_sm_; 213 cricket::SessionManager remote_sm_; 214 cricket::TunnelSessionClient local_client_; 215 cricket::TunnelSessionClient remote_client_; 216 rtc::scoped_ptr<rtc::StreamInterface> local_tunnel_; 217 rtc::scoped_ptr<rtc::StreamInterface> remote_tunnel_; 218 rtc::MemoryStream send_stream_; 219 rtc::MemoryStream recv_stream_; 220 bool done_; 221}; 222 223// Test the normal case of sending data from one side to the other. 224TEST_F(TunnelSessionClientTest, TestTransfer) { 225 TestTransfer(1000000); 226} 227