blob: 7ead1bf591e650721a574f68543cce48abeb29bd [file] [log] [blame]
/*
* libjingle
* Copyright 2010, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <string>
#include "talk/base/gunit.h"
#include "talk/base/messagehandler.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/stream.h"
#include "talk/base/thread.h"
#include "talk/base/timeutils.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/base/transport.h"
#include "talk/p2p/client/fakeportallocator.h"
#include "talk/session/tunnel/tunnelsessionclient.h"
static const int kTimeoutMs = 10000;
static const int kBlockSize = 4096;
static const buzz::Jid kLocalJid("local@localhost");
static const buzz::Jid kRemoteJid("remote@localhost");
// This test fixture creates the necessary plumbing to create and run
// two TunnelSessionClients that talk to each other.
class TunnelSessionClientTest : public testing::Test,
public talk_base::MessageHandler,
public sigslot::has_slots<> {
public:
TunnelSessionClientTest()
: local_pa_(talk_base::Thread::Current(), NULL),
remote_pa_(talk_base::Thread::Current(), NULL),
local_sm_(&local_pa_, talk_base::Thread::Current()),
remote_sm_(&remote_pa_, talk_base::Thread::Current()),
local_client_(kLocalJid, &local_sm_),
remote_client_(kRemoteJid, &remote_sm_),
done_(false) {
local_sm_.SignalSessionCreate.connect(this,
&TunnelSessionClientTest::OnSessionCreate);
local_sm_.SignalRequestSignaling.connect(this,
&TunnelSessionClientTest::OnLocalRequestSignaling);
local_sm_.SignalOutgoingMessage.connect(this,
&TunnelSessionClientTest::OnOutgoingMessage);
remote_sm_.SignalSessionCreate.connect(this,
&TunnelSessionClientTest::OnSessionCreate);
remote_sm_.SignalRequestSignaling.connect(this,
&TunnelSessionClientTest::OnRemoteRequestSignaling);
remote_sm_.SignalOutgoingMessage.connect(this,
&TunnelSessionClientTest::OnOutgoingMessage);
remote_client_.SignalIncomingTunnel.connect(this,
&TunnelSessionClientTest::OnIncomingTunnel);
}
// Transfer the desired amount of data from the local to the remote client.
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);
// Create the tunnel and set things in motion.
local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test"));
local_tunnel_->SignalEvent.connect(this,
&TunnelSessionClientTest::OnStreamEvent);
EXPECT_TRUE_WAIT(done_, kTimeoutMs);
// Make sure we received the right data.
EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
recv_stream_.GetBuffer(), size));
}
private:
enum { MSG_LSIGNAL, MSG_RSIGNAL };
// Use this callback to allow local ips for this test.
void OnSessionCreate(cricket::Session* session, bool incoming) {
session->set_allow_local_ips(true);
}
// There's no SessionManager* argument in this callback, so we need 2 of them.
void OnLocalRequestSignaling() {
local_sm_.OnSignalingReady();
}
void OnRemoteRequestSignaling() {
remote_sm_.OnSignalingReady();
}
// Post a message, to avoid problems with directly connecting the callbacks.
void OnOutgoingMessage(cricket::SessionManager* manager,
const buzz::XmlElement* stanza) {
if (manager == &local_sm_) {
talk_base::Thread::Current()->Post(this, MSG_LSIGNAL,
talk_base::WrapMessageData(*stanza));
} else if (manager == &remote_sm_) {
talk_base::Thread::Current()->Post(this, MSG_RSIGNAL,
talk_base::WrapMessageData(*stanza));
}
}
// Need to add a "from=" attribute (normally added by the server)
// Then route the incoming signaling message to the "other" session manager.
virtual void OnMessage(talk_base::Message* message) {
talk_base::TypedMessageData<buzz::XmlElement>* data =
static_cast<talk_base::TypedMessageData<buzz::XmlElement>*>(
message->pdata);
bool response = data->data().Attr(buzz::QN_TYPE) == buzz::STR_RESULT;
if (message->message_id == MSG_RSIGNAL) {
data->data().AddAttr(buzz::QN_FROM, remote_client_.jid().Str());
if (!response) {
local_sm_.OnIncomingMessage(&data->data());
} else {
local_sm_.OnIncomingResponse(NULL, &data->data());
}
} else if (message->message_id == MSG_LSIGNAL) {
data->data().AddAttr(buzz::QN_FROM, local_client_.jid().Str());
if (!response) {
remote_sm_.OnIncomingMessage(&data->data());
} else {
remote_sm_.OnIncomingResponse(NULL, &data->data());
}
}
delete data;
}
// Accept the tunnel when it arrives and wire up the stream.
void OnIncomingTunnel(cricket::TunnelSessionClient* client,
buzz::Jid jid, std::string description,
cricket::Session* session) {
remote_tunnel_.reset(remote_client_.AcceptTunnel(session));
remote_tunnel_->SignalEvent.connect(this,
&TunnelSessionClientTest::OnStreamEvent);
}
// Send from send_stream_ as long as we're not flow-controlled.
// Read bytes out into recv_stream_ as they arrive.
// End the test when we are notified that the local side has closed the
// tunnel. All data has been read out at this point.
void OnStreamEvent(talk_base::StreamInterface* stream, int events,
int error) {
if (events & talk_base::SE_READ) {
if (stream == remote_tunnel_.get()) {
ReadData();
}
}
if (events & talk_base::SE_WRITE) {
if (stream == local_tunnel_.get()) {
bool done = false;
WriteData(&done);
if (done) {
local_tunnel_->Close();
}
}
}
if (events & talk_base::SE_CLOSE) {
if (stream == remote_tunnel_.get()) {
remote_tunnel_->Close();
done_ = true;
}
}
}
// Spool from the tunnel into recv_stream.
// Flow() doesn't work here because it won't write if the read blocks.
void ReadData() {
char block[kBlockSize];
size_t read, position;
talk_base::StreamResult res;
while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) ==
talk_base::SR_SUCCESS) {
recv_stream_.Write(block, read, NULL, NULL);
}
ASSERT(res != talk_base::SR_EOS);
recv_stream_.GetPosition(&position);
LOG(LS_VERBOSE) << "Recv position: " << position;
}
// Spool from send_stream into the tunnel. Back up if we get flow controlled.
void WriteData(bool* done) {
char block[kBlockSize];
size_t leftover = 0, position;
talk_base::StreamResult res = talk_base::Flow(&send_stream_,
block, sizeof(block), local_tunnel_.get(), &leftover);
if (res == talk_base::SR_BLOCK) {
send_stream_.GetPosition(&position);
send_stream_.SetPosition(position - leftover);
LOG(LS_VERBOSE) << "Send position: " << position - leftover;
*done = false;
} else if (res == talk_base::SR_SUCCESS) {
*done = true;
} else {
ASSERT(false); // shouldn't happen
}
}
private:
cricket::FakePortAllocator local_pa_;
cricket::FakePortAllocator remote_pa_;
cricket::SessionManager local_sm_;
cricket::SessionManager remote_sm_;
cricket::TunnelSessionClient local_client_;
cricket::TunnelSessionClient remote_client_;
talk_base::scoped_ptr<talk_base::StreamInterface> local_tunnel_;
talk_base::scoped_ptr<talk_base::StreamInterface> remote_tunnel_;
talk_base::MemoryStream send_stream_;
talk_base::MemoryStream recv_stream_;
bool done_;
};
// Test the normal case of sending data from one side to the other.
TEST_F(TunnelSessionClientTest, TestTransfer) {
TestTransfer(1000000);
}