henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 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/base/gunit.h" |
| 30 | #include "talk/base/messagehandler.h" |
| 31 | #include "talk/base/scoped_ptr.h" |
| 32 | #include "talk/base/stream.h" |
| 33 | #include "talk/base/thread.h" |
| 34 | #include "talk/base/timeutils.h" |
| 35 | #include "talk/p2p/base/sessionmanager.h" |
| 36 | #include "talk/p2p/base/transport.h" |
| 37 | #include "talk/p2p/client/fakeportallocator.h" |
| 38 | #include "talk/session/tunnel/tunnelsessionclient.h" |
| 39 | |
| 40 | static const int kTimeoutMs = 10000; |
| 41 | static const int kBlockSize = 4096; |
| 42 | static const buzz::Jid kLocalJid("local@localhost"); |
| 43 | static 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. |
| 47 | class TunnelSessionClientTest : public testing::Test, |
| 48 | public talk_base::MessageHandler, |
| 49 | public sigslot::has_slots<> { |
| 50 | public: |
| 51 | TunnelSessionClientTest() |
| 52 | : local_pa_(talk_base::Thread::Current(), NULL), |
| 53 | remote_pa_(talk_base::Thread::Current(), NULL), |
| 54 | local_sm_(&local_pa_, talk_base::Thread::Current()), |
| 55 | remote_sm_(&remote_pa_, talk_base::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 | talk_base::Thread::Current()->Post(this, MSG_LSIGNAL, |
| 108 | talk_base::WrapMessageData(*stanza)); |
| 109 | } else if (manager == &remote_sm_) { |
| 110 | talk_base::Thread::Current()->Post(this, MSG_RSIGNAL, |
| 111 | talk_base::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(talk_base::Message* message) { |
| 118 | talk_base::TypedMessageData<buzz::XmlElement>* data = |
| 119 | static_cast<talk_base::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(talk_base::StreamInterface* stream, int events, |
| 154 | int error) { |
| 155 | if (events & talk_base::SE_READ) { |
| 156 | if (stream == remote_tunnel_.get()) { |
| 157 | ReadData(); |
| 158 | } |
| 159 | } |
| 160 | if (events & talk_base::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 & talk_base::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 | talk_base::StreamResult res; |
| 183 | while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) == |
| 184 | talk_base::SR_SUCCESS) { |
| 185 | recv_stream_.Write(block, read, NULL, NULL); |
| 186 | } |
| 187 | ASSERT(res != talk_base::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 | talk_base::StreamResult res = talk_base::Flow(&send_stream_, |
| 196 | block, sizeof(block), local_tunnel_.get(), &leftover); |
| 197 | if (res == talk_base::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 == talk_base::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 | talk_base::scoped_ptr<talk_base::StreamInterface> local_tunnel_; |
| 217 | talk_base::scoped_ptr<talk_base::StreamInterface> remote_tunnel_; |
| 218 | talk_base::MemoryStream send_stream_; |
| 219 | talk_base::MemoryStream recv_stream_; |
| 220 | bool done_; |
| 221 | }; |
| 222 | |
| 223 | // Test the normal case of sending data from one side to the other. |
| 224 | TEST_F(TunnelSessionClientTest, TestTransfer) { |
| 225 | TestTransfer(1000000); |
| 226 | } |