diff options
Diffstat (limited to 'Plugins/jingle/libjingle/talk/p2p/base/session_unittest.cc')
-rw-r--r-- | Plugins/jingle/libjingle/talk/p2p/base/session_unittest.cc | 1039 |
1 files changed, 1039 insertions, 0 deletions
diff --git a/Plugins/jingle/libjingle/talk/p2p/base/session_unittest.cc b/Plugins/jingle/libjingle/talk/p2p/base/session_unittest.cc new file mode 100644 index 0000000..972ed9d --- /dev/null +++ b/Plugins/jingle/libjingle/talk/p2p/base/session_unittest.cc @@ -0,0 +1,1039 @@ +#include <iostream> +#include <sstream> +#include <deque> +#include <map> + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/host.h" +#include "talk/base/natserver.h" +#include "talk/base/natsocketfactory.h" +#include "talk/base/helpers.h" +#include "talk/xmpp/constants.h" +#include "talk/p2p/base/constants.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/sessionclient.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/portallocator.h" +#include "talk/p2p/base/transportchannel.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/p2ptransport.h" +#include "talk/p2p/base/rawtransport.h" +#include "talk/p2p/base/stunserver.h" +#include "talk/p2p/base/relayserver.h" + +using namespace cricket; +using namespace buzz; + +const std::string kSessionType = "http://oink.splat/session"; + +const talk_base::SocketAddress kStunServerAddress("127.0.0.1", 7000); +const talk_base::SocketAddress kStunServerAddress2("127.0.0.1", 7001); + +const talk_base::SocketAddress kRelayServerIntAddress("127.0.0.1", 7002); +const talk_base::SocketAddress kRelayServerExtAddress("127.0.0.1", 7003); + +const int kNumPorts = 2; + +int gPort = 28653; +int GetNextPort() { + int p = gPort; + gPort += 5; + return p; +} + +int gID = 0; +std::string GetNextID() { + std::ostringstream ost; + ost << gID++; + return ost.str(); +} + +class TestPortAllocatorSession : public PortAllocatorSession { +public: + TestPortAllocatorSession(talk_base::Thread* worker_thread, talk_base::SocketFactory* factory, + const std::string& name, const std::string& session_type) + : PortAllocatorSession(0), worker_thread_(worker_thread), + factory_(factory), name_(name), ports_(kNumPorts), + address_("127.0.0.1", 0), network_("network", address_.ip()), + running_(false) { + } + + ~TestPortAllocatorSession() { + for (int i = 0; i < ports_.size(); i++) + delete ports_[i]; + } + + virtual void GetInitialPorts() { + // These are the flags set by the raw transport. + uint32 raw_flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP; + + // If the client doesn't care, just give them two UDP ports. + if (flags() == 0) { + for (int i = 0; i < kNumPorts; i++) { + ports_[i] = new UDPPort(worker_thread_, factory_, &network_, + GetAddress()); + AddPort(ports_[i]); + } + + // If the client requested just stun and relay, we have to oblidge. + } else if (flags() == raw_flags) { + StunPort* sport = new StunPort(worker_thread_, factory_, &network_, + GetAddress(), kStunServerAddress); + sport->set_server_addr2(kStunServerAddress2); + ports_[0] = sport; + AddPort(sport); + + std::string username = CreateRandomString(16); + std::string password = CreateRandomString(16); + RelayPort* rport = new RelayPort(worker_thread_, factory_, &network_, + GetAddress(), username, password, ""); + rport->AddServerAddress( + ProtocolAddress(kRelayServerIntAddress, PROTO_UDP)); + ports_[1] = rport; + AddPort(rport); + } else { + ASSERT(false); + } + } + + virtual void StartGetAllPorts() { running_ = true; } + virtual void StopGetAllPorts() { running_ = false; } + virtual bool IsGettingAllPorts() { return running_; } + + talk_base::SocketAddress GetAddress() const { + talk_base::SocketAddress addr(address_); + addr.SetPort(GetNextPort()); + return addr; + } + + void AddPort(Port* port) { + port->set_name(name_); + port->set_preference(1.0); + port->set_generation(0); + port->SignalDestroyed.connect( + this, &TestPortAllocatorSession::OnPortDestroyed); + port->SignalAddressReady.connect( + this, &TestPortAllocatorSession::OnAddressReady); + port->PrepareAddress(); + SignalPortReady(this, port); + } + + void OnPortDestroyed(Port* port) { + for (int i = 0; i < ports_.size(); i++) { + if (ports_[i] == port) + ports_[i] = NULL; + } + } + + void OnAddressReady(Port* port) { + SignalCandidatesReady(this, port->candidates()); + } + +private: + talk_base::Thread* worker_thread_; + talk_base::SocketFactory* factory_; + std::string name_; + std::vector<Port*> ports_; + talk_base::SocketAddress address_; + talk_base::Network network_; + bool running_; +}; + +class TestPortAllocator : public PortAllocator { +public: + TestPortAllocator(talk_base::Thread* worker_thread, talk_base::SocketFactory* factory) + : worker_thread_(worker_thread), factory_(factory) { + if (factory_ == NULL) + factory_ = worker_thread_->socketserver(); + } + + virtual PortAllocatorSession *CreateSession(const std::string &name, const std::string &session_type) { + return new TestPortAllocatorSession(worker_thread_, factory_, name, session_type); + } + +private: + talk_base::Thread* worker_thread_; + talk_base::SocketFactory* factory_; +}; + +struct SessionManagerHandler : sigslot::has_slots<> { + SessionManagerHandler(SessionManager* m, const std::string& u) + : manager(m), username(u), create_count(0), destroy_count(0) { + manager->SignalSessionCreate.connect( + this, &SessionManagerHandler::OnSessionCreate); + manager->SignalSessionDestroy.connect( + this, &SessionManagerHandler::OnSessionDestroy); + manager->SignalOutgoingMessage.connect( + this, &SessionManagerHandler::OnOutgoingMessage); + manager->SignalRequestSignaling.connect( + this, &SessionManagerHandler::OnRequestSignaling); + } + + void OnSessionCreate(Session *session, bool initiate) { + create_count += 1; + last_id = session->id(); + } + + void OnSessionDestroy(Session *session) { + destroy_count += 1; + last_id = session->id(); + } + + void OnOutgoingMessage(const XmlElement* stanza) { + XmlElement* elem = new XmlElement(*stanza); + ASSERT(elem->Name() == QN_IQ); + ASSERT(elem->HasAttr(QN_TO)); + ASSERT(!elem->HasAttr(QN_FROM)); + ASSERT(elem->HasAttr(QN_TYPE)); + ASSERT((elem->Attr(QN_TYPE) == "set") || + (elem->Attr(QN_TYPE) == "result") || + (elem->Attr(QN_TYPE) == "error")); + + // Add in the appropriate "from". + elem->SetAttr(QN_FROM, username); + + // Add in the appropriate IQ ID. + if (elem->Attr(QN_TYPE) == "set") { + ASSERT(!elem->HasAttr(QN_ID)); + elem->SetAttr(QN_ID, GetNextID()); + } + + stanzas_.push_back(elem); + } + + void OnRequestSignaling() { + manager->OnSignalingReady(); + } + + + XmlElement* CheckNextStanza(const std::string& expected) { + // Get the next stanza, which should exist. + ASSERT(stanzas_.size() > 0); + XmlElement* stanza = stanzas_.front(); + stanzas_.pop_front(); + + // Make sure the stanza is correct. + std::string actual = stanza->Str(); + if (actual != expected) { + LOG(LERROR) << "Incorrect stanza: expected=\"" << expected + << "\" actual=\"" << actual << "\""; + ASSERT(actual == expected); + } + + return stanza; + } + + void CheckNoStanza() { + ASSERT(stanzas_.size() == 0); + } + + void PrintNextStanza() { + ASSERT(stanzas_.size() > 0); + printf("Stanza: %s\n", stanzas_.front()->Str().c_str()); + } + + SessionManager* manager; + std::string username; + SessionID last_id; + uint32 create_count; + uint32 destroy_count; + std::deque<XmlElement*> stanzas_; +}; + +struct SessionHandler : sigslot::has_slots<> { + SessionHandler(Session* s) : session(s) { + session->SignalState.connect(this, &SessionHandler::OnState); + session->SignalError.connect(this, &SessionHandler::OnError); + } + + void PrepareTransport() { + Transport* transport = session->GetTransport(kNsP2pTransport); + if (transport != NULL) + transport->set_allow_local_ips(true); + } + + void OnState(Session* session, Session::State state) { + ASSERT(session == this->session); + last_state = state; + } + + void OnError(Session* session, Session::Error error) { + ASSERT(session == this->session); + ASSERT(false); // errors are bad! + } + + Session* session; + Session::State last_state; +}; + +struct MySessionClient: public SessionClient, public sigslot::has_slots<> { + MySessionClient() : create_count(0), a(NULL), b(NULL) { } + + void AddManager(SessionManager* manager) { + manager->AddClient(kSessionType, this); + ASSERT(manager->GetClient(kSessionType) == this); + manager->SignalSessionCreate.connect( + this, &MySessionClient::OnSessionCreate); + } + + const SessionDescription* CreateSessionDescription( + const XmlElement* element) { + return new SessionDescription(); + } + + XmlElement* TranslateSessionDescription( + const SessionDescription* description) { + return new XmlElement(QName(kSessionType, "description")); + } + + void OnSessionCreate(Session *session, bool initiate) { + create_count += 1; + a = session->CreateChannel("a"); + b = session->CreateChannel("b"); + + if (transport_name.size() > 0) + session->SetPotentialTransports(&transport_name, 1); + } + + void OnSessionDestroy(Session *session) + { + } + + void SetTransports(bool p2p, bool raw) { + if (p2p && raw) + return; // this is the default + + if (p2p) { + transport_name = kNsP2pTransport; + } + } + + int create_count; + TransportChannel* a; + TransportChannel* b; + std::string transport_name; +}; + +struct ChannelHandler : sigslot::has_slots<> { + ChannelHandler(TransportChannel* p) + : channel(p), last_readable(false), last_writable(false), data_count(0), + last_size(0) { + p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState); + p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState); + p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket); + } + + void OnReadableState(TransportChannel* p) { + ASSERT(p == channel); + last_readable = channel->readable(); + } + + void OnWritableState(TransportChannel* p) { + ASSERT(p == channel); + last_writable = channel->writable(); + } + + void OnReadPacket(TransportChannel* p, const char* buf, size_t size) { + ASSERT(p == channel); + ASSERT(size <= sizeof(last_data)); + data_count += 1; + last_size = size; + std::memcpy(last_data, buf, size); + } + + void Send(const char* data, size_t size) { + int result = channel->SendPacket(data, size); + ASSERT(result == static_cast<int>(size)); + } + + TransportChannel* channel; + bool last_readable, last_writable; + int data_count; + char last_data[4096]; + size_t last_size; +}; + +char* Reverse(const char* str) { + int len = strlen(str); + char* rev = new char[len+1]; + for (int i = 0; i < len; i++) + rev[i] = str[len-i-1]; + rev[len] = '\0'; + return rev; +} + +// Sets up values that should be the same for every test. +void InitTest() { + SetRandomSeed(7); + gPort = 28653; + gID = 0; +} + +// Tests having client2 accept the session. +void TestAccept(talk_base::Thread* signaling_thread, + Session* session1, Session* session2, + SessionHandler* handler1, SessionHandler* handler2, + SessionManager* manager1, SessionManager* manager2, + SessionManagerHandler* manhandler1, + SessionManagerHandler* manhandler2) { + // Make sure the IQ ID is 5. + ASSERT(gID <= 5); + while (gID < 5) GetNextID(); + + // Accept the session. + SessionDescription* desc2 = new SessionDescription(); + bool valid = session2->Accept(desc2); + ASSERT(valid); + + scoped_ptr<buzz::XmlElement> stanza; + stanza.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"5\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"accept\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + + // Simulate a tiny delay in sending. + signaling_thread->ProcessMessages(10); + + // Delivery the accept. + manager1->OnIncomingMessage(stanza.get()); + stanza.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"5\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // Both sessions should be in progress after a short wait. + signaling_thread->ProcessMessages(10); + ASSERT(handler1->last_state == Session::STATE_INPROGRESS); + ASSERT(handler2->last_state == Session::STATE_INPROGRESS); +} + +// Tests sending data between two clients, over two channels. +void TestSendRecv(ChannelHandler* chanhandler1a, ChannelHandler* chanhandler1b, + ChannelHandler* chanhandler2a, ChannelHandler* chanhandler2b, + talk_base::Thread* signaling_thread, bool first_dropped) { + const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam"; + const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce..."; + const char* dat2a = Reverse(dat1a); + const char* dat2b = Reverse(dat1b); + + // Sending from 2 -> 1 will enable 1 to send to 2 below. That will then + // enable 2 to send back to 1. So the code below will just work. + if (first_dropped) { + chanhandler2a->Send(dat2a, strlen(dat2a)); + chanhandler2b->Send(dat2b, strlen(dat2b)); + } + + for (int i = 0; i < 20; i++) { + chanhandler1a->Send(dat1a, strlen(dat1a)); + chanhandler1b->Send(dat1b, strlen(dat1b)); + chanhandler2a->Send(dat2a, strlen(dat2a)); + chanhandler2b->Send(dat2b, strlen(dat2b)); + + signaling_thread->ProcessMessages(10); + + ASSERT(chanhandler1a->data_count == i + 1); + ASSERT(chanhandler1b->data_count == i + 1); + ASSERT(chanhandler2a->data_count == i + 1); + ASSERT(chanhandler2b->data_count == i + 1); + + ASSERT(chanhandler1a->last_size == strlen(dat2a)); + ASSERT(chanhandler1b->last_size == strlen(dat2b)); + ASSERT(chanhandler2a->last_size == strlen(dat1a)); + ASSERT(chanhandler2b->last_size == strlen(dat1b)); + + ASSERT(std::memcmp(chanhandler1a->last_data, dat2a, strlen(dat2a)) == 0); + ASSERT(std::memcmp(chanhandler1b->last_data, dat2b, strlen(dat2b)) == 0); + ASSERT(std::memcmp(chanhandler2a->last_data, dat1a, strlen(dat1a)) == 0); + ASSERT(std::memcmp(chanhandler2b->last_data, dat1b, strlen(dat1b)) == 0); + } +} + +// Tests a session between two clients. The inputs indicate whether we should +// replace each client's output with what we would see from an old client. +void TestP2PCompatibility(const std::string& test_name, bool old1, bool old2) { + InitTest(); + + talk_base::Thread* signaling_thread = talk_base::Thread::Current(); + scoped_ptr<talk_base::Thread> worker_thread(new talk_base::Thread()); + worker_thread->Start(); + + scoped_ptr<PortAllocator> allocator( + new TestPortAllocator(worker_thread.get(), NULL)); + scoped_ptr<MySessionClient> client(new MySessionClient()); + client->SetTransports(true, false); + + scoped_ptr<SessionManager> manager1( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler1( + new SessionManagerHandler(manager1.get(), "foo@baz.com")); + client->AddManager(manager1.get()); + + Session* session1 = manager1->CreateSession("foo@baz.com", kSessionType); + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler1->last_id == session1->id()); + scoped_ptr<SessionHandler> handler1(new SessionHandler(session1)); + + ASSERT(client->create_count == 1); + TransportChannel* chan1a = client->a; + ASSERT(chan1a->name() == "a"); + ASSERT(session1->GetChannel("a") == chan1a); + scoped_ptr<ChannelHandler> chanhandler1a(new ChannelHandler(chan1a)); + TransportChannel* chan1b = client->b; + ASSERT(chan1b->name() == "b"); + ASSERT(session1->GetChannel("b") == chan1b); + scoped_ptr<ChannelHandler> chanhandler1b(new ChannelHandler(chan1b)); + + SessionDescription* desc1 = new SessionDescription(); + ASSERT(session1->state() == Session::STATE_INIT); + bool valid = session1->Initiate("bar@baz.com", NULL, desc1); + ASSERT(valid); + handler1->PrepareTransport(); + + signaling_thread->ProcessMessages(100); + + ASSERT(handler1->last_state == Session::STATE_SENTINITIATE); + scoped_ptr<XmlElement> stanza1, stanza2; + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler1->CheckNoStanza(); + + // If the first client were old, the initiate would have no transports and + // the candidates would be sent in a candidates message. + if (old1) { + stanza1.reset(XmlElement::ForStr( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "</session>" + "</cli:iq>")); + stanza2.reset(XmlElement::ForStr( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>")); + } + + scoped_ptr<SessionManager> manager2( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler2( + new SessionManagerHandler(manager2.get(), "bar@baz.com")); + client->AddManager(manager2.get()); + + // Deliver the initiate. + manager2->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"0\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + // If client1 is old, we will not see a transport-accept. If client2 is old, + // then we should act as if it did not send one. + if (!old1) { + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"2\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\"" + " type=\"transport-accept\" id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + } else { + GetNextID(); // Advance the ID count to be the same in all cases. + stanza1.reset(NULL); + } + if (old2) { + stanza1.reset(NULL); + } + manhandler2->CheckNoStanza(); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler2->last_id == session1->id()); + + Session* session2 = manager2->GetSession(session1->id()); + ASSERT(session2); + ASSERT(session1->id() == session2->id()); + ASSERT(manhandler2->last_id == session2->id()); + ASSERT(session2->state() == Session::STATE_RECEIVEDINITIATE); + scoped_ptr<SessionHandler> handler2(new SessionHandler(session2)); + handler2->PrepareTransport(); + + ASSERT(session2->name() == session1->remote_name()); + ASSERT(session1->name() == session2->remote_name()); + + ASSERT(session2->transport() != NULL); + ASSERT(session2->transport()->name() == kNsP2pTransport); + + ASSERT(client->create_count == 2); + TransportChannel* chan2a = client->a; + scoped_ptr<ChannelHandler> chanhandler2a(new ChannelHandler(chan2a)); + TransportChannel* chan2b = client->b; + scoped_ptr<ChannelHandler> chanhandler2b(new ChannelHandler(chan2b)); + + // Deliver the candidates. + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"1\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + signaling_thread->ProcessMessages(10); + + // If client1 is old, we should see a candidates message instead of a + // transport-info. If client2 is old, we should act as if we did. + const char* kCandidates2 = + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>"; + if (old1) { + stanza2.reset(manhandler2->CheckNextStanza(kCandidates2)); + } else { + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + } + if (old2) { + stanza2.reset(XmlElement::ForStr(kCandidates2)); + } + manhandler2->CheckNoStanza(); + + // Deliver the transport-accept if one exists. + if (stanza1.get() != NULL) { + manager1->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"2\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session should now have a transport. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + } + + // Deliver the candidates. If client2 is old (or is acting old because + // client1 is), then client1 will correct its earlier mistake of sending + // transport-info by sending a candidates message. If client1 is supposed to + // be old, then it sent candidates earlier, so we drop this. + manager1->OnIncomingMessage(stanza2.get()); + if (old1 || old2) { + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"4\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>")); + } else { + GetNextID(); // Advance the ID count to be the same in all cases. + stanza2.reset(NULL); + } + if (old1) { + stanza2.reset(NULL); + } + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"3\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session must have a transport in either case now. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + + // If client1 just generated a candidates message, then we must deliver it. + if (stanza2.get() != NULL) { + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"4\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler2->CheckNoStanza(); + } + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + signaling_thread->ProcessMessages(500); + ASSERT(chan1a->writable() && chan1a->readable()); + ASSERT(chan1b->writable() && chan1b->readable()); + ASSERT(chan2a->writable() && chan2a->readable()); + ASSERT(chan2b->writable() && chan2b->readable()); + ASSERT(chanhandler1a->last_writable); + ASSERT(chanhandler1b->last_writable); + ASSERT(chanhandler2a->last_writable); + ASSERT(chanhandler2b->last_writable); + + // Accept the session. + TestAccept(signaling_thread, session1, session2, + handler1.get(), handler2.get(), + manager1.get(), manager2.get(), + manhandler1.get(), manhandler2.get()); + + // Send a bunch of data between them. + TestSendRecv(chanhandler1a.get(), chanhandler1b.get(), chanhandler2a.get(), + chanhandler2b.get(), signaling_thread, false); + + manager1->DestroySession(session1); + manager2->DestroySession(session2); + + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler1->destroy_count == 1); + ASSERT(manhandler2->destroy_count == 1); + + worker_thread->Stop(); + + std::cout << "P2P Compatibility: " << test_name << ": PASS" << std::endl; +} + +// Tests the P2P transport. The flags indicate whether they clients will +// advertise support for raw as well. +void TestP2P(const std::string& test_name, bool raw1, bool raw2) { + InitTest(); + + talk_base::Thread* signaling_thread = talk_base::Thread::Current(); + scoped_ptr<talk_base::Thread> worker_thread(new talk_base::Thread()); + worker_thread->Start(); + + scoped_ptr<PortAllocator> allocator( + new TestPortAllocator(worker_thread.get(), NULL)); + scoped_ptr<MySessionClient> client1(new MySessionClient()); + client1->SetTransports(true, raw1); + scoped_ptr<MySessionClient> client2(new MySessionClient()); + client2->SetTransports(true, raw2); + + scoped_ptr<SessionManager> manager1( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler1( + new SessionManagerHandler(manager1.get(), "foo@baz.com")); + client1->AddManager(manager1.get()); + + Session* session1 = manager1->CreateSession("foo@baz.com", kSessionType); + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler1->last_id == session1->id()); + scoped_ptr<SessionHandler> handler1(new SessionHandler(session1)); + + ASSERT(client1->create_count == 1); + TransportChannel* chan1a = client1->a; + ASSERT(chan1a->name() == "a"); + ASSERT(session1->GetChannel("a") == chan1a); + scoped_ptr<ChannelHandler> chanhandler1a(new ChannelHandler(chan1a)); + TransportChannel* chan1b = client1->b; + ASSERT(chan1b->name() == "b"); + ASSERT(session1->GetChannel("b") == chan1b); + scoped_ptr<ChannelHandler> chanhandler1b(new ChannelHandler(chan1b)); + + SessionDescription* desc1 = new SessionDescription(); + ASSERT(session1->state() == Session::STATE_INIT); + bool valid = session1->Initiate("bar@baz.com", NULL, desc1); + ASSERT(valid); + handler1->PrepareTransport(); + + signaling_thread->ProcessMessages(100); + + ASSERT(handler1->last_state == Session::STATE_SENTINITIATE); + scoped_ptr<XmlElement> stanza1, stanza2; + if (raw1) { + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "<raw:transport xmlns:raw=\"http://www.google.com/transport/raw\"/>" + "</session>" + "</cli:iq>")); + } else { + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + } + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler1->CheckNoStanza(); + + scoped_ptr<SessionManager> manager2( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler2( + new SessionManagerHandler(manager2.get(), "bar@baz.com")); + client2->AddManager(manager2.get()); + + // Deliver the initiate. + manager2->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"0\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"2\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\"" + " type=\"transport-accept\" id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler2->last_id == session1->id()); + + Session* session2 = manager2->GetSession(session1->id()); + ASSERT(session2); + ASSERT(session1->id() == session2->id()); + ASSERT(manhandler2->last_id == session2->id()); + ASSERT(session2->state() == Session::STATE_RECEIVEDINITIATE); + scoped_ptr<SessionHandler> handler2(new SessionHandler(session2)); + handler2->PrepareTransport(); + + ASSERT(session2->name() == session1->remote_name()); + ASSERT(session1->name() == session2->remote_name()); + + ASSERT(session2->transport() != NULL); + ASSERT(session2->transport()->name() == kNsP2pTransport); + + ASSERT(client2->create_count == 1); + TransportChannel* chan2a = client2->a; + scoped_ptr<ChannelHandler> chanhandler2a(new ChannelHandler(chan2a)); + TransportChannel* chan2b = client2->b; + scoped_ptr<ChannelHandler> chanhandler2b(new ChannelHandler(chan2b)); + + // Deliver the candidates. + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"1\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + signaling_thread->ProcessMessages(10); + + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + + // Deliver the transport-accept. + manager1->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"2\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session should now have a transport. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + + // Deliver the candidates. + manager1->OnIncomingMessage(stanza2.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"3\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + signaling_thread->ProcessMessages(500); + ASSERT(chan1a->writable() && chan1a->readable()); + ASSERT(chan1b->writable() && chan1b->readable()); + ASSERT(chan2a->writable() && chan2a->readable()); + ASSERT(chan2b->writable() && chan2b->readable()); + ASSERT(chanhandler1a->last_writable); + ASSERT(chanhandler1b->last_writable); + ASSERT(chanhandler2a->last_writable); + ASSERT(chanhandler2b->last_writable); + + // Accept the session. + TestAccept(signaling_thread, session1, session2, + handler1.get(), handler2.get(), + manager1.get(), manager2.get(), + manhandler1.get(), manhandler2.get()); + + // Send a bunch of data between them. + TestSendRecv(chanhandler1a.get(), chanhandler1b.get(), chanhandler2a.get(), + chanhandler2b.get(), signaling_thread, false); + + manager1->DestroySession(session1); + manager2->DestroySession(session2); + + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler1->destroy_count == 1); + ASSERT(manhandler2->destroy_count == 1); + + worker_thread->Stop(); + + std::cout << "P2P: " << test_name << ": PASS" << std::endl; +} +// +int main(int argc, char* argv[]) { + talk_base::LogMessage::LogToDebug(talk_base::LS_WARNING); + + TestP2P("{p2p} => {p2p}", false, false); + TestP2P("{p2p} => {p2p,raw}", false, true); + TestP2P("{p2p,raw} => {p2p}", true, false); + TestP2P("{p2p,raw} => {p2p,raw}", true, true); + TestP2PCompatibility("New => New", false, false); + TestP2PCompatibility("Old => New", true, false); + TestP2PCompatibility("New => Old", false, true); + TestP2PCompatibility("Old => Old", true, true); + + return 0; +} |