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