blob: 9bd41a6f1c0d2066dc2df4804256afa874b1891b [file] [log] [blame]
/*
* libjingle
* Copyright 2004 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.h>
#include <deque>
#include <map>
#include <sstream>
#include "talk/p2p/base/basicpacketsocketfactory.h"
#include "talk/p2p/base/constants.h"
#include "talk/p2p/base/p2ptransport.h"
#include "talk/p2p/base/parsing.h"
#include "talk/p2p/base/portallocator.h"
#include "talk/p2p/base/relayport.h"
#include "talk/p2p/base/relayserver.h"
#include "talk/p2p/base/session.h"
#include "talk/p2p/base/sessionclient.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/base/stunport.h"
#include "talk/p2p/base/stunserver.h"
#include "talk/p2p/base/transportchannel.h"
#include "talk/p2p/base/transportchannelproxy.h"
#include "talk/p2p/base/udpport.h"
#include "talk/xmpp/constants.h"
#include "webrtc/base/base64.h"
#include "webrtc/base/common.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/helpers.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/natserver.h"
#include "webrtc/base/natsocketfactory.h"
#include "webrtc/base/physicalsocketserver.h"
#include "webrtc/base/stringencode.h"
#include "webrtc/base/virtualsocketserver.h"
using cricket::SignalingProtocol;
using cricket::PROTOCOL_HYBRID;
using cricket::PROTOCOL_JINGLE;
using cricket::PROTOCOL_GINGLE;
static const std::string kInitiator = "init@init.com";
static const std::string kResponder = "resp@resp.com";
// Expected from test random number generator.
static const std::string kSessionId = "9254631414740579489";
// TODO: When we need to test more than one transport type,
// allow this to be injected like the content types are.
static const std::string kTransportType = "http://www.google.com/transport/p2p";
// Controls how long we wait for a session to send messages that we
// expect, in milliseconds. We put it high to avoid flaky tests.
static const int kEventTimeout = 5000;
static const int kNumPorts = 2;
static const int kPort0 = 28653;
static const int kPortStep = 5;
int GetPort(int port_index) {
return kPort0 + (port_index * kPortStep);
}
std::string GetPortString(int port_index) {
return rtc::ToString(GetPort(port_index));
}
// Only works for port_index < 10, which is fine for our purposes.
std::string GetUsername(int port_index) {
return "username" + std::string(8, rtc::ToString(port_index)[0]);
}
// Only works for port_index < 10, which is fine for our purposes.
std::string GetPassword(int port_index) {
return "password" + std::string(8, rtc::ToString(port_index)[0]);
}
std::string IqAck(const std::string& id,
const std::string& from,
const std::string& to) {
return "<cli:iq"
" to=\"" + to + "\""
" id=\"" + id + "\""
" type=\"result\""
" from=\"" + from + "\""
" xmlns:cli=\"jabber:client\""
"/>";
}
std::string IqSet(const std::string& id,
const std::string& from,
const std::string& to,
const std::string& content) {
return "<cli:iq"
" to=\"" + to + "\""
" type=\"set\""
" from=\"" + from + "\""
" id=\"" + id + "\""
" xmlns:cli=\"jabber:client\""
">"
+ content +
"</cli:iq>";
}
std::string IqError(const std::string& id,
const std::string& from,
const std::string& to,
const std::string& content) {
return "<cli:error"
" to=\"" + to + "\""
" type=\"error\""
" from=\"" + from + "\""
" id=\"" + id + "\""
" xmlns:cli=\"jabber:client\""
">"
+ content +
"</cli:error>";
}
std::string GingleSessionXml(const std::string& type,
const std::string& content) {
return "<session"
" xmlns=\"http://www.google.com/session\""
" type=\"" + type + "\""
" id=\"" + kSessionId + "\""
" initiator=\"" + kInitiator + "\""
">"
+ content +
"</session>";
}
std::string GingleDescriptionXml(const std::string& content_type) {
return "<description"
" xmlns=\"" + content_type + "\""
"/>";
}
std::string P2pCandidateXml(const std::string& name, int port_index) {
// Port will update the rtcp username by +1 on the last character. So we need
// to compensate here. See Port::username_fragment() for detail.
std::string username = GetUsername(port_index);
// TODO: Use the component id instead of the channel name to
// determinte if we need to covert the username here.
if (name == "rtcp" || name == "video_rtcp" || name == "chanb") {
char next_ch = username[username.size() - 1];
ASSERT(username.size() > 0);
rtc::Base64::GetNextBase64Char(next_ch, &next_ch);
username[username.size() - 1] = next_ch;
}
return "<candidate"
" name=\"" + name + "\""
" address=\"127.0.0.1\""
" port=\"" + GetPortString(port_index) + "\""
" preference=\"0.99\""
" username=\"" + username + "\""
" protocol=\"udp\""
" generation=\"0\""
" password=\"" + GetPassword(port_index) + "\""
" type=\"local\""
" network=\"network\""
"/>";
}
std::string JingleActionXml(const std::string& action,
const std::string& content) {
return "<jingle"
" xmlns=\"urn:xmpp:jingle:1\""
" action=\"" + action + "\""
" sid=\"" + kSessionId + "\""
">"
+ content +
"</jingle>";
}
std::string JingleInitiateActionXml(const std::string& content) {
return "<jingle"
" xmlns=\"urn:xmpp:jingle:1\""
" action=\"session-initiate\""
" sid=\"" + kSessionId + "\""
" initiator=\"" + kInitiator + "\""
">"
+ content +
"</jingle>";
}
std::string JingleGroupInfoXml(const std::string& content_name_a,
const std::string& content_name_b) {
std::string group_info = "<jin:group"
" type=\"BUNDLE\""
" xmlns:jin=\"google:jingle\""
">";
if (!content_name_a.empty())
group_info += "<content name=\"" + content_name_a + "\""
"/>";
if (!content_name_b.empty())
group_info += "<content name=\"" + content_name_b + "\""
"/>";
group_info += "</jin:group>";
return group_info;
}
std::string JingleEmptyContentXml(const std::string& content_name,
const std::string& content_type,
const std::string& transport_type) {
return "<content"
" name=\"" + content_name + "\""
" creator=\"initiator\""
">"
"<description"
" xmlns=\"" + content_type + "\""
"/>"
"<transport"
" xmlns=\"" + transport_type + "\""
"/>"
"</content>";
}
std::string JingleContentXml(const std::string& content_name,
const std::string& content_type,
const std::string& transport_type,
const std::string& transport_main) {
std::string transport = transport_type.empty() ? "" :
"<transport"
" xmlns=\"" + transport_type + "\""
">"
+ transport_main +
"</transport>";
return"<content"
" name=\"" + content_name + "\""
" creator=\"initiator\""
">"
"<description"
" xmlns=\"" + content_type + "\""
"/>"
+ transport +
"</content>";
}
std::string JingleTransportContentXml(const std::string& content_name,
const std::string& transport_type,
const std::string& content) {
return "<content"
" name=\"" + content_name + "\""
" creator=\"initiator\""
">"
"<transport"
" xmlns=\"" + transport_type + "\""
">"
+ content +
"</transport>"
"</content>";
}
std::string GingleInitiateXml(const std::string& content_type) {
return GingleSessionXml(
"initiate",
GingleDescriptionXml(content_type));
}
std::string JingleInitiateXml(const std::string& content_name_a,
const std::string& content_type_a,
const std::string& content_name_b,
const std::string& content_type_b,
bool bundle = false) {
std::string content_xml;
if (content_name_b.empty()) {
content_xml = JingleEmptyContentXml(
content_name_a, content_type_a, kTransportType);
} else {
content_xml = JingleEmptyContentXml(
content_name_a, content_type_a, kTransportType) +
JingleEmptyContentXml(
content_name_b, content_type_b, kTransportType);
if (bundle) {
content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
}
}
return JingleInitiateActionXml(content_xml);
}
std::string GingleAcceptXml(const std::string& content_type) {
return GingleSessionXml(
"accept",
GingleDescriptionXml(content_type));
}
std::string JingleAcceptXml(const std::string& content_name_a,
const std::string& content_type_a,
const std::string& content_name_b,
const std::string& content_type_b,
bool bundle = false) {
std::string content_xml;
if (content_name_b.empty()) {
content_xml = JingleEmptyContentXml(
content_name_a, content_type_a, kTransportType);
} else {
content_xml = JingleEmptyContentXml(
content_name_a, content_type_a, kTransportType) +
JingleEmptyContentXml(
content_name_b, content_type_b, kTransportType);
}
if (bundle) {
content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
}
return JingleActionXml("session-accept", content_xml);
}
std::string Gingle2CandidatesXml(const std::string& channel_name,
int port_index0,
int port_index1) {
return GingleSessionXml(
"candidates",
P2pCandidateXml(channel_name, port_index0) +
P2pCandidateXml(channel_name, port_index1));
}
std::string Gingle4CandidatesXml(const std::string& channel_name_a,
int port_index0,
int port_index1,
const std::string& channel_name_b,
int port_index2,
int port_index3) {
return GingleSessionXml(
"candidates",
P2pCandidateXml(channel_name_a, port_index0) +
P2pCandidateXml(channel_name_a, port_index1) +
P2pCandidateXml(channel_name_b, port_index2) +
P2pCandidateXml(channel_name_b, port_index3));
}
std::string Jingle2TransportInfoXml(const std::string& content_name,
const std::string& channel_name,
int port_index0,
int port_index1) {
return JingleActionXml(
"transport-info",
JingleTransportContentXml(
content_name, kTransportType,
P2pCandidateXml(channel_name, port_index0) +
P2pCandidateXml(channel_name, port_index1)));
}
std::string Jingle4TransportInfoXml(const std::string& content_name,
const std::string& channel_name_a,
int port_index0,
int port_index1,
const std::string& channel_name_b,
int port_index2,
int port_index3) {
return JingleActionXml(
"transport-info",
JingleTransportContentXml(
content_name, kTransportType,
P2pCandidateXml(channel_name_a, port_index0) +
P2pCandidateXml(channel_name_a, port_index1) +
P2pCandidateXml(channel_name_b, port_index2) +
P2pCandidateXml(channel_name_b, port_index3)));
}
std::string JingleDescriptionInfoXml(const std::string& content_name,
const std::string& content_type) {
return JingleActionXml(
"description-info",
JingleContentXml(content_name, content_type, "", ""));
}
std::string GingleRejectXml(const std::string& reason) {
return GingleSessionXml(
"reject",
"<" + reason + "/>");
}
std::string JingleTerminateXml(const std::string& reason) {
return JingleActionXml(
"session-terminate",
"<reason><" + reason + "/></reason>");
}
std::string GingleTerminateXml(const std::string& reason) {
return GingleSessionXml(
"terminate",
"<" + reason + "/>");
}
std::string GingleRedirectXml(const std::string& intitiate,
const std::string& target) {
return intitiate +
"<error code=\"302\" type=\"modify\">"
"<redirect xmlns=\"http://www.google.com/session\">"
"xmpp:" + target +
"</redirect>"
"</error>";
}
std::string JingleRedirectXml(const std::string& intitiate,
const std::string& target) {
return intitiate +
"<error code=\"302\" type=\"modify\">"
"<redirect xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"
"xmpp:" + target +
"</redirect>"
"</error>";
}
std::string InitiateXml(SignalingProtocol protocol,
const std::string& gingle_content_type,
const std::string& content_name_a,
const std::string& content_type_a,
const std::string& content_name_b,
const std::string& content_type_b,
bool bundle = false) {
switch (protocol) {
case PROTOCOL_JINGLE:
return JingleInitiateXml(content_name_a, content_type_a,
content_name_b, content_type_b,
bundle);
case PROTOCOL_GINGLE:
return GingleInitiateXml(gingle_content_type);
case PROTOCOL_HYBRID:
return JingleInitiateXml(content_name_a, content_type_a,
content_name_b, content_type_b) +
GingleInitiateXml(gingle_content_type);
}
return "";
}
std::string InitiateXml(SignalingProtocol protocol,
const std::string& content_name,
const std::string& content_type) {
return InitiateXml(protocol,
content_type,
content_name, content_type,
"", "");
}
std::string AcceptXml(SignalingProtocol protocol,
const std::string& gingle_content_type,
const std::string& content_name_a,
const std::string& content_type_a,
const std::string& content_name_b,
const std::string& content_type_b,
bool bundle = false) {
switch (protocol) {
case PROTOCOL_JINGLE:
return JingleAcceptXml(content_name_a, content_type_a,
content_name_b, content_type_b, bundle);
case PROTOCOL_GINGLE:
return GingleAcceptXml(gingle_content_type);
case PROTOCOL_HYBRID:
return
JingleAcceptXml(content_name_a, content_type_a,
content_name_b, content_type_b) +
GingleAcceptXml(gingle_content_type);
}
return "";
}
std::string AcceptXml(SignalingProtocol protocol,
const std::string& content_name,
const std::string& content_type,
bool bundle = false) {
return AcceptXml(protocol,
content_type,
content_name, content_type,
"", "");
}
std::string TransportInfo2Xml(SignalingProtocol protocol,
const std::string& content_name,
const std::string& channel_name,
int port_index0,
int port_index1) {
switch (protocol) {
case PROTOCOL_JINGLE:
return Jingle2TransportInfoXml(
content_name,
channel_name, port_index0, port_index1);
case PROTOCOL_GINGLE:
return Gingle2CandidatesXml(
channel_name, port_index0, port_index1);
case PROTOCOL_HYBRID:
return
Jingle2TransportInfoXml(
content_name,
channel_name, port_index0, port_index1) +
Gingle2CandidatesXml(
channel_name, port_index0, port_index1);
}
return "";
}
std::string TransportInfo4Xml(SignalingProtocol protocol,
const std::string& content_name,
const std::string& channel_name_a,
int port_index0,
int port_index1,
const std::string& channel_name_b,
int port_index2,
int port_index3) {
switch (protocol) {
case PROTOCOL_JINGLE:
return Jingle4TransportInfoXml(
content_name,
channel_name_a, port_index0, port_index1,
channel_name_b, port_index2, port_index3);
case PROTOCOL_GINGLE:
return Gingle4CandidatesXml(
channel_name_a, port_index0, port_index1,
channel_name_b, port_index2, port_index3);
case PROTOCOL_HYBRID:
return
Jingle4TransportInfoXml(
content_name,
channel_name_a, port_index0, port_index1,
channel_name_b, port_index2, port_index3) +
Gingle4CandidatesXml(
channel_name_a, port_index0, port_index1,
channel_name_b, port_index2, port_index3);
}
return "";
}
std::string RejectXml(SignalingProtocol protocol,
const std::string& reason) {
switch (protocol) {
case PROTOCOL_JINGLE:
return JingleTerminateXml(reason);
case PROTOCOL_GINGLE:
return GingleRejectXml(reason);
case PROTOCOL_HYBRID:
return JingleTerminateXml(reason) +
GingleRejectXml(reason);
}
return "";
}
std::string TerminateXml(SignalingProtocol protocol,
const std::string& reason) {
switch (protocol) {
case PROTOCOL_JINGLE:
return JingleTerminateXml(reason);
case PROTOCOL_GINGLE:
return GingleTerminateXml(reason);
case PROTOCOL_HYBRID:
return JingleTerminateXml(reason) +
GingleTerminateXml(reason);
}
return "";
}
std::string RedirectXml(SignalingProtocol protocol,
const std::string& initiate,
const std::string& target) {
switch (protocol) {
case PROTOCOL_JINGLE:
return JingleRedirectXml(initiate, target);
case PROTOCOL_GINGLE:
return GingleRedirectXml(initiate, target);
default:
break;
}
return "";
}
// TODO: Break out and join with fakeportallocator.h
class TestPortAllocatorSession : public cricket::PortAllocatorSession {
public:
TestPortAllocatorSession(const std::string& content_name,
int component,
const std::string& ice_ufrag,
const std::string& ice_pwd,
const int port_offset)
: PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, 0),
port_offset_(port_offset),
ports_(kNumPorts),
address_("127.0.0.1", 0),
network_("network", "unittest",
rtc::IPAddress(INADDR_LOOPBACK), 8),
socket_factory_(rtc::Thread::Current()),
running_(false) {
network_.AddIP(address_.ipaddr());
}
~TestPortAllocatorSession() {
for (size_t i = 0; i < ports_.size(); i++)
delete ports_[i];
}
virtual void StartGettingPorts() {
for (int i = 0; i < kNumPorts; i++) {
int index = port_offset_ + i;
ports_[i] = cricket::UDPPort::Create(
rtc::Thread::Current(), &socket_factory_,
&network_, address_.ipaddr(), GetPort(index), GetPort(index),
GetUsername(index), GetPassword(index));
AddPort(ports_[i]);
}
running_ = true;
}
virtual void StopGettingPorts() { running_ = false; }
virtual bool IsGettingPorts() { return running_; }
void AddPort(cricket::Port* port) {
port->set_component(component_);
port->set_generation(0);
port->SignalDestroyed.connect(
this, &TestPortAllocatorSession::OnPortDestroyed);
port->SignalPortComplete.connect(
this, &TestPortAllocatorSession::OnPortComplete);
port->PrepareAddress();
SignalPortReady(this, port);
}
void OnPortDestroyed(cricket::PortInterface* port) {
for (size_t i = 0; i < ports_.size(); i++) {
if (ports_[i] == port)
ports_[i] = NULL;
}
}
void OnPortComplete(cricket::Port* port) {
SignalCandidatesReady(this, port->Candidates());
}
private:
int port_offset_;
std::vector<cricket::Port*> ports_;
rtc::SocketAddress address_;
rtc::Network network_;
rtc::BasicPacketSocketFactory socket_factory_;
bool running_;
};
class TestPortAllocator : public cricket::PortAllocator {
public:
TestPortAllocator() : port_offset_(0) {}
virtual cricket::PortAllocatorSession*
CreateSessionInternal(
const std::string& content_name,
int component,
const std::string& ice_ufrag,
const std::string& ice_pwd) {
port_offset_ += 2;
return new TestPortAllocatorSession(content_name, component,
ice_ufrag, ice_pwd, port_offset_ - 2);
}
int port_offset_;
};
class TestContentDescription : public cricket::ContentDescription {
public:
explicit TestContentDescription(const std::string& gingle_content_type,
const std::string& content_type)
: gingle_content_type(gingle_content_type),
content_type(content_type) {
}
virtual ContentDescription* Copy() const {
return new TestContentDescription(*this);
}
std::string gingle_content_type;
std::string content_type;
};
cricket::SessionDescription* NewTestSessionDescription(
const std::string gingle_content_type,
const std::string& content_name_a, const std::string& content_type_a,
const std::string& content_name_b, const std::string& content_type_b) {
cricket::SessionDescription* offer = new cricket::SessionDescription();
offer->AddContent(content_name_a, content_type_a,
new TestContentDescription(gingle_content_type,
content_type_a));
cricket::TransportDescription desc(cricket::NS_GINGLE_P2P,
std::string(), std::string());
offer->AddTransportInfo(cricket::TransportInfo(content_name_a, desc));
if (content_name_a != content_name_b) {
offer->AddContent(content_name_b, content_type_b,
new TestContentDescription(gingle_content_type,
content_type_b));
offer->AddTransportInfo(cricket::TransportInfo(content_name_b, desc));
}
return offer;
}
cricket::SessionDescription* NewTestSessionDescription(
const std::string& content_name, const std::string& content_type) {
cricket::SessionDescription* offer = new cricket::SessionDescription();
offer->AddContent(content_name, content_type,
new TestContentDescription(content_type,
content_type));
offer->AddTransportInfo(cricket::TransportInfo
(content_name, cricket::TransportDescription(
cricket::NS_GINGLE_P2P,
std::string(), std::string())));
return offer;
}
struct TestSessionClient: public cricket::SessionClient,
public sigslot::has_slots<> {
public:
TestSessionClient() {
}
~TestSessionClient() {
}
virtual bool ParseContent(SignalingProtocol protocol,
const buzz::XmlElement* elem,
cricket::ContentDescription** content,
cricket::ParseError* error) {
std::string content_type;
std::string gingle_content_type;
if (protocol == PROTOCOL_GINGLE) {
gingle_content_type = elem->Name().Namespace();
} else {
content_type = elem->Name().Namespace();
}
*content = new TestContentDescription(gingle_content_type, content_type);
return true;
}
virtual bool WriteContent(SignalingProtocol protocol,
const cricket::ContentDescription* untyped_content,
buzz::XmlElement** elem,
cricket::WriteError* error) {
const TestContentDescription* content =
static_cast<const TestContentDescription*>(untyped_content);
std::string content_type = (protocol == PROTOCOL_GINGLE ?
content->gingle_content_type :
content->content_type);
*elem = new buzz::XmlElement(
buzz::QName(content_type, "description"), true);
return true;
}
void OnSessionCreate(cricket::Session* session, bool initiate) {
}
void OnSessionDestroy(cricket::Session* session) {
}
};
struct ChannelHandler : sigslot::has_slots<> {
explicit ChannelHandler(cricket::TransportChannel* p, const std::string& name)
: channel(p), last_readable(false), last_writable(false), data_count(0),
last_size(0), name(name) {
p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState);
p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState);
p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket);
}
bool writable() const {
return last_writable && channel->writable();
}
bool readable() const {
return last_readable && channel->readable();
}
void OnReadableState(cricket::TransportChannel* p) {
EXPECT_EQ(channel, p);
last_readable = channel->readable();
}
void OnWritableState(cricket::TransportChannel* p) {
EXPECT_EQ(channel, p);
last_writable = channel->writable();
}
void OnReadPacket(cricket::TransportChannel* p, const char* buf,
size_t size, const rtc::PacketTime& time, int flags) {
if (memcmp(buf, name.c_str(), name.size()) != 0)
return; // drop packet if packet doesn't belong to this channel. This
// can happen when transport channels are muxed together.
buf += name.size(); // Remove channel name from the message.
size -= name.size(); // Decrement size by channel name string size.
EXPECT_EQ(channel, p);
EXPECT_LE(size, sizeof(last_data));
data_count += 1;
last_size = size;
memcpy(last_data, buf, size);
}
void Send(const char* data, size_t size) {
rtc::PacketOptions options;
std::string data_with_id(name);
data_with_id += data;
int result = channel->SendPacket(data_with_id.c_str(), data_with_id.size(),
options, 0);
EXPECT_EQ(static_cast<int>(data_with_id.size()), result);
}
cricket::TransportChannel* channel;
bool last_readable, last_writable;
int data_count;
char last_data[4096];
size_t last_size;
std::string name;
};
void PrintStanza(const std::string& message,
const buzz::XmlElement* stanza) {
printf("%s: %s\n", message.c_str(), stanza->Str().c_str());
}
class TestClient : public sigslot::has_slots<> {
public:
// TODO: Add channel_component_a/b as inputs to the ctor.
TestClient(cricket::PortAllocator* port_allocator,
int* next_message_id,
const std::string& local_name,
SignalingProtocol start_protocol,
const std::string& content_type,
const std::string& content_name_a,
const std::string& channel_name_a,
const std::string& content_name_b,
const std::string& channel_name_b) {
Construct(port_allocator, next_message_id, local_name, start_protocol,
content_type, content_name_a, channel_name_a,
content_name_b, channel_name_b);
}
~TestClient() {
if (session) {
session_manager->DestroySession(session);
EXPECT_EQ(1U, session_destroyed_count);
}
delete session_manager;
delete client;
for (std::deque<buzz::XmlElement*>::iterator it = sent_stanzas.begin();
it != sent_stanzas.end(); ++it) {
delete *it;
}
}
void Construct(cricket::PortAllocator* pa,
int* message_id,
const std::string& lname,
SignalingProtocol protocol,
const std::string& cont_type,
const std::string& cont_name_a,
const std::string& chan_name_a,
const std::string& cont_name_b,
const std::string& chan_name_b) {
port_allocator_ = pa;
next_message_id = message_id;
local_name = lname;
start_protocol = protocol;
content_type = cont_type;
content_name_a = cont_name_a;
channel_name_a = chan_name_a;
content_name_b = cont_name_b;
channel_name_b = chan_name_b;
session_created_count = 0;
session_destroyed_count = 0;
session_remote_description_update_count = 0;
new_local_description = false;
new_remote_description = false;
last_content_action = cricket::CA_OFFER;
last_content_source = cricket::CS_LOCAL;
session = NULL;
last_session_state = cricket::BaseSession::STATE_INIT;
blow_up_on_error = true;
error_count = 0;
session_manager = new cricket::SessionManager(port_allocator_);
session_manager->SignalSessionCreate.connect(
this, &TestClient::OnSessionCreate);
session_manager->SignalSessionDestroy.connect(
this, &TestClient::OnSessionDestroy);
session_manager->SignalOutgoingMessage.connect(
this, &TestClient::OnOutgoingMessage);
client = new TestSessionClient();
session_manager->AddClient(content_type, client);
EXPECT_EQ(client, session_manager->GetClient(content_type));
}
uint32 sent_stanza_count() const {
return static_cast<uint32>(sent_stanzas.size());
}
const buzz::XmlElement* stanza() const {
return last_expected_sent_stanza.get();
}
cricket::BaseSession::State session_state() const {
EXPECT_EQ(last_session_state, session->state());
return session->state();
}
void SetSessionState(cricket::BaseSession::State state) {
session->SetState(state);
EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout);
}
void CreateSession() {
session_manager->CreateSession(local_name, content_type);
}
void DeliverStanza(const buzz::XmlElement* stanza) {
session_manager->OnIncomingMessage(stanza);
}
void DeliverStanza(const std::string& str) {
buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str);
session_manager->OnIncomingMessage(stanza);
delete stanza;
}
void DeliverAckToLastStanza() {
const buzz::XmlElement* orig_stanza = stanza();
const buzz::XmlElement* response_stanza =
buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", ""));
session_manager->OnIncomingResponse(orig_stanza, response_stanza);
delete response_stanza;
}
void ExpectSentStanza(const std::string& expected) {
EXPECT_TRUE(!sent_stanzas.empty()) <<
"Found no stanza when expected " << expected;
last_expected_sent_stanza.reset(sent_stanzas.front());
sent_stanzas.pop_front();
std::string actual = last_expected_sent_stanza->Str();
EXPECT_EQ(expected, actual);
}
void SkipUnsentStanza() {
GetNextOutgoingMessageID();
}
bool HasTransport(const std::string& content_name) const {
ASSERT(session != NULL);
const cricket::Transport* transport = session->GetTransport(content_name);
return transport != NULL && (kTransportType == transport->type());
}
bool HasChannel(const std::string& content_name,
int component) const {
ASSERT(session != NULL);
const cricket::TransportChannel* channel =
session->GetChannel(content_name, component);
return channel != NULL && (component == channel->component());
}
cricket::TransportChannel* GetChannel(const std::string& content_name,
int component) const {
ASSERT(session != NULL);
return session->GetChannel(content_name, component);
}
void OnSessionCreate(cricket::Session* created_session, bool initiate) {
session_created_count += 1;
session = created_session;
session->set_current_protocol(start_protocol);
session->SignalState.connect(this, &TestClient::OnSessionState);
session->SignalError.connect(this, &TestClient::OnSessionError);
session->SignalRemoteDescriptionUpdate.connect(
this, &TestClient::OnSessionRemoteDescriptionUpdate);
session->SignalNewLocalDescription.connect(
this, &TestClient::OnNewLocalDescription);
session->SignalNewRemoteDescription.connect(
this, &TestClient::OnNewRemoteDescription);
CreateChannels();
}
void OnSessionDestroy(cricket::Session *session) {
session_destroyed_count += 1;
}
void OnSessionState(cricket::BaseSession* session,
cricket::BaseSession::State state) {
// EXPECT_EQ does not allow use of this, hence the tmp variable.
cricket::BaseSession* tmp = this->session;
EXPECT_EQ(tmp, session);
last_session_state = state;
}
void OnSessionError(cricket::BaseSession* session,
cricket::BaseSession::Error error) {
// EXPECT_EQ does not allow use of this, hence the tmp variable.
cricket::BaseSession* tmp = this->session;
EXPECT_EQ(tmp, session);
if (blow_up_on_error) {
EXPECT_TRUE(false);
} else {
error_count++;
}
}
void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
const cricket::ContentInfos& contents) {
session_remote_description_update_count++;
}
void OnNewLocalDescription(cricket::BaseSession* session,
cricket::ContentAction action) {
new_local_description = true;
last_content_action = action;
last_content_source = cricket::CS_LOCAL;
}
void OnNewRemoteDescription(cricket::BaseSession* session,
cricket::ContentAction action) {
new_remote_description = true;
last_content_action = action;
last_content_source = cricket::CS_REMOTE;
}
void PrepareCandidates() {
session_manager->OnSignalingReady();
}
void OnOutgoingMessage(cricket::SessionManager* manager,
const buzz::XmlElement* stanza) {
buzz::XmlElement* elem = new buzz::XmlElement(*stanza);
EXPECT_TRUE(elem->Name() == buzz::QN_IQ);
EXPECT_TRUE(elem->HasAttr(buzz::QN_TO));
EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM));
EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE));
EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") ||
(elem->Attr(buzz::QN_TYPE) == "result") ||
(elem->Attr(buzz::QN_TYPE) == "error"));
elem->SetAttr(buzz::QN_FROM, local_name);
if (elem->Attr(buzz::QN_TYPE) == "set") {
EXPECT_FALSE(elem->HasAttr(buzz::QN_ID));
elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID());
}
// Uncommenting this is useful for debugging.
// PrintStanza("OutgoingMessage", elem);
sent_stanzas.push_back(elem);
}
std::string GetNextOutgoingMessageID() {
int message_id = (*next_message_id)++;
std::ostringstream ost;
ost << message_id;
return ost.str();
}
void CreateChannels() {
ASSERT(session != NULL);
// We either have a single content with multiple components (RTP/RTCP), or
// multiple contents with single components, but not both.
int component_a = 1;
int component_b = (content_name_a == content_name_b) ? 2 : 1;
chan_a.reset(new ChannelHandler(
session->CreateChannel(content_name_a, channel_name_a, component_a),
channel_name_a));
chan_b.reset(new ChannelHandler(
session->CreateChannel(content_name_b, channel_name_b, component_b),
channel_name_b));
}
int* next_message_id;
std::string local_name;
SignalingProtocol start_protocol;
std::string content_type;
std::string content_name_a;
std::string channel_name_a;
std::string content_name_b;
std::string channel_name_b;
uint32 session_created_count;
uint32 session_destroyed_count;
uint32 session_remote_description_update_count;
bool new_local_description;
bool new_remote_description;
cricket::ContentAction last_content_action;
cricket::ContentSource last_content_source;
std::deque<buzz::XmlElement*> sent_stanzas;
rtc::scoped_ptr<buzz::XmlElement> last_expected_sent_stanza;
cricket::SessionManager* session_manager;
TestSessionClient* client;
cricket::PortAllocator* port_allocator_;
cricket::Session* session;
cricket::BaseSession::State last_session_state;
rtc::scoped_ptr<ChannelHandler> chan_a;
rtc::scoped_ptr<ChannelHandler> chan_b;
bool blow_up_on_error;
int error_count;
};
class SessionTest : public testing::Test {
protected:
SessionTest()
: pss_(new rtc::PhysicalSocketServer),
ss_(new rtc::VirtualSocketServer(pss_.get())),
ss_scope_(ss_.get()) {}
virtual void SetUp() {
// Seed needed for each test to satisfy expectations.
rtc::SetRandomTestMode(true);
}
virtual void TearDown() {
rtc::SetRandomTestMode(false);
}
// Tests sending data between two clients, over two channels.
void TestSendRecv(ChannelHandler* chan1a,
ChannelHandler* chan1b,
ChannelHandler* chan2a,
ChannelHandler* chan2b) {
const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam";
const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps";
const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce...";
const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL";
for (int i = 0; i < 20; i++) {
chan1a->Send(dat1a, strlen(dat1a));
chan1b->Send(dat1b, strlen(dat1b));
chan2a->Send(dat2a, strlen(dat2a));
chan2b->Send(dat2b, strlen(dat2b));
EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout);
EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout);
EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout);
EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout);
EXPECT_EQ(strlen(dat2a), chan1a->last_size);
EXPECT_EQ(strlen(dat2b), chan1b->last_size);
EXPECT_EQ(strlen(dat1a), chan2a->last_size);
EXPECT_EQ(strlen(dat1b), chan2b->last_size);
EXPECT_EQ(0, memcmp(chan1a->last_data, dat2a, strlen(dat2a)));
EXPECT_EQ(0, memcmp(chan1b->last_data, dat2b, strlen(dat2b)));
EXPECT_EQ(0, memcmp(chan2a->last_data, dat1a, strlen(dat1a)));
EXPECT_EQ(0, memcmp(chan2b->last_data, dat1b, strlen(dat1b)));
}
}
// Test an initiate from one client to another, each with
// independent initial protocols. Checks for the correct initiates,
// candidates, and accept messages, and tests that working network
// channels are established.
void TestSession(SignalingProtocol initiator_protocol,
SignalingProtocol responder_protocol,
SignalingProtocol resulting_protocol,
const std::string& gingle_content_type,
const std::string& content_type,
const std::string& content_name_a,
const std::string& channel_name_a,
const std::string& content_name_b,
const std::string& channel_name_b,
const std::string& initiate_xml,
const std::string& transport_info_a_xml,
const std::string& transport_info_b_xml,
const std::string& transport_info_reply_a_xml,
const std::string& transport_info_reply_b_xml,
const std::string& accept_xml,
bool bundle = false) {
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, initiator_protocol,
content_type,
content_name_a, channel_name_a,
content_name_b, channel_name_b));
rtc::scoped_ptr<TestClient> responder(
new TestClient(allocator.get(), &next_message_id,
kResponder, responder_protocol,
content_type,
content_name_a, channel_name_a,
content_name_b, channel_name_b));
// Create Session and check channels and state.
initiator->CreateSession();
EXPECT_EQ(1U, initiator->session_created_count);
EXPECT_EQ(kSessionId, initiator->session->id());
EXPECT_EQ(initiator->session->local_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_INIT,
initiator->session_state());
// See comment in CreateChannels about how we choose component IDs.
int component_a = 1;
int component_b = (content_name_a == content_name_b) ? 2 : 1;
EXPECT_TRUE(initiator->HasTransport(content_name_a));
EXPECT_TRUE(initiator->HasChannel(content_name_a, component_a));
EXPECT_TRUE(initiator->HasTransport(content_name_b));
EXPECT_TRUE(initiator->HasChannel(content_name_b, component_b));
// Initiate and expect initiate message sent.
cricket::SessionDescription* offer = NewTestSessionDescription(
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type);
if (bundle) {
cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
group.AddContentName(content_name_a);
group.AddContentName(content_name_b);
EXPECT_TRUE(group.HasContentName(content_name_a));
EXPECT_TRUE(group.HasContentName(content_name_b));
offer->AddGroup(group);
}
EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
EXPECT_EQ(initiator->session->remote_name(), kResponder);
EXPECT_EQ(initiator->session->local_description(), offer);
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder, initiate_xml));
// Deliver the initiate. Expect ack and session created with
// transports.
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("0", kResponder, kInitiator));
EXPECT_EQ(0U, responder->sent_stanza_count());
EXPECT_EQ(1U, responder->session_created_count);
EXPECT_EQ(kSessionId, responder->session->id());
EXPECT_EQ(responder->session->local_name(), kResponder);
EXPECT_EQ(responder->session->remote_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
responder->session_state());
EXPECT_TRUE(responder->HasTransport(content_name_a));
EXPECT_TRUE(responder->HasChannel(content_name_a, component_a));
EXPECT_TRUE(responder->HasTransport(content_name_b));
EXPECT_TRUE(responder->HasChannel(content_name_b, component_b));
// Expect transport-info message from initiator.
// But don't send candidates until initiate ack is received.
initiator->PrepareCandidates();
WAIT(initiator->sent_stanza_count() > 0, 100);
EXPECT_EQ(0U, initiator->sent_stanza_count());
initiator->DeliverAckToLastStanza();
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder, transport_info_a_xml));
// Deliver transport-info and expect ack.
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("1", kResponder, kInitiator));
if (!transport_info_b_xml.empty()) {
// Expect second transport-info message from initiator.
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("2", kInitiator, kResponder, transport_info_b_xml));
EXPECT_EQ(0U, initiator->sent_stanza_count());
// Deliver second transport-info message and expect ack.
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("2", kResponder, kInitiator));
} else {
EXPECT_EQ(0U, initiator->sent_stanza_count());
EXPECT_EQ(0U, responder->sent_stanza_count());
initiator->SkipUnsentStanza();
}
// Expect reply transport-info message from responder.
responder->PrepareCandidates();
EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
responder->ExpectSentStanza(
IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml));
// Deliver reply transport-info and expect ack.
initiator->DeliverStanza(responder->stanza());
initiator->ExpectSentStanza(
IqAck("3", kInitiator, kResponder));
if (!transport_info_reply_b_xml.empty()) {
// Expect second reply transport-info message from responder.
EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
responder->ExpectSentStanza(
IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml));
EXPECT_EQ(0U, responder->sent_stanza_count());
// Deliver second reply transport-info message and expect ack.
initiator->DeliverStanza(responder->stanza());
initiator->ExpectSentStanza(
IqAck("4", kInitiator, kResponder));
EXPECT_EQ(0U, initiator->sent_stanza_count());
} else {
EXPECT_EQ(0U, initiator->sent_stanza_count());
EXPECT_EQ(0U, responder->sent_stanza_count());
responder->SkipUnsentStanza();
}
// The channels should be able to become writable at this point. This
// requires pinging, so it may take a little while.
EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
initiator->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
initiator->chan_b->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
responder->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
responder->chan_b->readable(), kEventTimeout);
// Accept the session and expect accept stanza.
cricket::SessionDescription* answer = NewTestSessionDescription(
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type);
if (bundle) {
cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
group.AddContentName(content_name_a);
group.AddContentName(content_name_b);
EXPECT_TRUE(group.HasContentName(content_name_a));
EXPECT_TRUE(group.HasContentName(content_name_b));
answer->AddGroup(group);
}
EXPECT_TRUE(responder->session->Accept(answer));
EXPECT_EQ(responder->session->local_description(), answer);
responder->ExpectSentStanza(
IqSet("5", kResponder, kInitiator, accept_xml));
EXPECT_EQ(0U, responder->sent_stanza_count());
// Deliver the accept message and expect an ack.
initiator->DeliverStanza(responder->stanza());
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqAck("5", kInitiator, kResponder));
EXPECT_EQ(0U, initiator->sent_stanza_count());
// Both sessions should be in progress and have functioning
// channels.
EXPECT_EQ(resulting_protocol, initiator->session->current_protocol());
EXPECT_EQ(resulting_protocol, responder->session->current_protocol());
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
initiator->session_state(), kEventTimeout);
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
responder->session_state(), kEventTimeout);
if (bundle) {
cricket::TransportChannel* initiator_chan_a = initiator->chan_a->channel;
cricket::TransportChannel* initiator_chan_b = initiator->chan_b->channel;
// Since we know these are TransportChannelProxy, type cast it.
cricket::TransportChannelProxy* initiator_proxy_chan_a =
static_cast<cricket::TransportChannelProxy*>(initiator_chan_a);
cricket::TransportChannelProxy* initiator_proxy_chan_b =
static_cast<cricket::TransportChannelProxy*>(initiator_chan_b);
EXPECT_TRUE(initiator_proxy_chan_a->impl() != NULL);
EXPECT_TRUE(initiator_proxy_chan_b->impl() != NULL);
EXPECT_EQ(initiator_proxy_chan_a->impl(), initiator_proxy_chan_b->impl());
cricket::TransportChannel* responder_chan_a = responder->chan_a->channel;
cricket::TransportChannel* responder_chan_b = responder->chan_b->channel;
// Since we know these are TransportChannelProxy, type cast it.
cricket::TransportChannelProxy* responder_proxy_chan_a =
static_cast<cricket::TransportChannelProxy*>(responder_chan_a);
cricket::TransportChannelProxy* responder_proxy_chan_b =
static_cast<cricket::TransportChannelProxy*>(responder_chan_b);
EXPECT_TRUE(responder_proxy_chan_a->impl() != NULL);
EXPECT_TRUE(responder_proxy_chan_b->impl() != NULL);
EXPECT_EQ(responder_proxy_chan_a->impl(), responder_proxy_chan_b->impl());
}
TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
responder->chan_a.get(), responder->chan_b.get());
if (resulting_protocol == PROTOCOL_JINGLE) {
// Deliver a description-info message to the initiator and check if the
// content description changes.
EXPECT_EQ(0U, initiator->session_remote_description_update_count);
const cricket::SessionDescription* old_session_desc =
initiator->session->remote_description();
const cricket::ContentInfo* old_content_a =
old_session_desc->GetContentByName(content_name_a);
const cricket::ContentDescription* old_content_desc_a =
old_content_a->description;
const cricket::ContentInfo* old_content_b =
old_session_desc->GetContentByName(content_name_b);
const cricket::ContentDescription* old_content_desc_b =
old_content_b->description;
EXPECT_TRUE(old_content_desc_a != NULL);
EXPECT_TRUE(old_content_desc_b != NULL);
LOG(LS_INFO) << "A " << old_content_a->name;
LOG(LS_INFO) << "B " << old_content_b->name;
std::string description_info_xml =
JingleDescriptionInfoXml(content_name_a, content_type);
initiator->DeliverStanza(
IqSet("6", kResponder, kInitiator, description_info_xml));
responder->SkipUnsentStanza();
EXPECT_EQ(1U, initiator->session_remote_description_update_count);
const cricket::SessionDescription* new_session_desc =
initiator->session->remote_description();
const cricket::ContentInfo* new_content_a =
new_session_desc->GetContentByName(content_name_a);
const cricket::ContentDescription* new_content_desc_a =
new_content_a->description;
const cricket::ContentInfo* new_content_b =
new_session_desc->GetContentByName(content_name_b);
const cricket::ContentDescription* new_content_desc_b =
new_content_b->description;
EXPECT_TRUE(new_content_desc_a != NULL);
EXPECT_TRUE(new_content_desc_b != NULL);
// TODO: We used to replace contents from an update, but
// that no longer works with partial updates. We need to figure out
// a way to merge patial updates into contents. For now, users of
// Session should listen to SignalRemoteDescriptionUpdate and handle
// updates. They should not expect remote_description to be the
// latest value.
// See session.cc OnDescriptionInfoMessage.
// EXPECT_NE(old_content_desc_a, new_content_desc_a);
// if (content_name_a != content_name_b) {
// // If content_name_a != content_name_b, then b's content description
// // should not have changed since the description-info message only
// // contained an update for content_name_a.
// EXPECT_EQ(old_content_desc_b, new_content_desc_b);
// }
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqAck("6", kInitiator, kResponder));
EXPECT_EQ(0U, initiator->sent_stanza_count());
} else {
responder->SkipUnsentStanza();
}
initiator->session->Terminate();
initiator->ExpectSentStanza(
IqSet("7", kInitiator, kResponder,
TerminateXml(resulting_protocol,
cricket::STR_TERMINATE_SUCCESS)));
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("7", kResponder, kInitiator));
EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
initiator->session_state());
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
responder->session_state());
}
// Test an initiate with other content, called "main".
void TestOtherContent(SignalingProtocol initiator_protocol,
SignalingProtocol responder_protocol,
SignalingProtocol resulting_protocol) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
std::string content_name_a = content_name;
std::string channel_name_a = "rtp";
std::string content_name_b = content_name;
std::string channel_name_b = "rtcp";
std::string initiate_xml = InitiateXml(
initiator_protocol,
content_name_a, content_type);
std::string transport_info_a_xml = TransportInfo4Xml(
initiator_protocol, content_name,
channel_name_a, 0, 1,
channel_name_b, 2, 3);
std::string transport_info_b_xml = "";
std::string transport_info_reply_a_xml = TransportInfo4Xml(
resulting_protocol, content_name,
channel_name_a, 4, 5,
channel_name_b, 6, 7);
std::string transport_info_reply_b_xml = "";
std::string accept_xml = AcceptXml(
resulting_protocol,
content_name_a, content_type);
TestSession(initiator_protocol, responder_protocol, resulting_protocol,
content_type,
content_type,
content_name_a, channel_name_a,
content_name_b, channel_name_b,
initiate_xml,
transport_info_a_xml, transport_info_b_xml,
transport_info_reply_a_xml, transport_info_reply_b_xml,
accept_xml);
}
// Test an initiate with audio content.
void TestAudioContent(SignalingProtocol initiator_protocol,
SignalingProtocol responder_protocol,
SignalingProtocol resulting_protocol) {
std::string gingle_content_type = cricket::NS_GINGLE_AUDIO;
std::string content_name = cricket::CN_AUDIO;
std::string content_type = cricket::NS_JINGLE_RTP;
std::string channel_name_a = "rtp";
std::string channel_name_b = "rtcp";
std::string initiate_xml = InitiateXml(
initiator_protocol,
gingle_content_type,
content_name, content_type,
"", "");
std::string transport_info_a_xml = TransportInfo4Xml(
initiator_protocol, content_name,
channel_name_a, 0, 1,
channel_name_b, 2, 3);
std::string transport_info_b_xml = "";
std::string transport_info_reply_a_xml = TransportInfo4Xml(
resulting_protocol, content_name,
channel_name_a, 4, 5,
channel_name_b, 6, 7);
std::string transport_info_reply_b_xml = "";
std::string accept_xml = AcceptXml(
resulting_protocol,
gingle_content_type,
content_name, content_type,
"", "");
TestSession(initiator_protocol, responder_protocol, resulting_protocol,
gingle_content_type,
content_type,
content_name, channel_name_a,
content_name, channel_name_b,
initiate_xml,
transport_info_a_xml, transport_info_b_xml,
transport_info_reply_a_xml, transport_info_reply_b_xml,
accept_xml);
}
// Since media content is "split" into two contents (audio and
// video), we need to treat it special.
void TestVideoContents(SignalingProtocol initiator_protocol,
SignalingProtocol responder_protocol,
SignalingProtocol resulting_protocol) {
std::string content_type = cricket::NS_JINGLE_RTP;
std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
std::string content_name_a = cricket::CN_AUDIO;
std::string channel_name_a = "rtp";
std::string content_name_b = cricket::CN_VIDEO;
std::string channel_name_b = "video_rtp";
std::string initiate_xml = InitiateXml(
initiator_protocol,
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type);
std::string transport_info_a_xml = TransportInfo2Xml(
initiator_protocol, content_name_a,
channel_name_a, 0, 1);
std::string transport_info_b_xml = TransportInfo2Xml(
initiator_protocol, content_name_b,
channel_name_b, 2, 3);
std::string transport_info_reply_a_xml = TransportInfo2Xml(
resulting_protocol, content_name_a,
channel_name_a, 4, 5);
std::string transport_info_reply_b_xml = TransportInfo2Xml(
resulting_protocol, content_name_b,
channel_name_b, 6, 7);
std::string accept_xml = AcceptXml(
resulting_protocol,
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type);
TestSession(initiator_protocol, responder_protocol, resulting_protocol,
gingle_content_type,
content_type,
content_name_a, channel_name_a,
content_name_b, channel_name_b,
initiate_xml,
transport_info_a_xml, transport_info_b_xml,
transport_info_reply_a_xml, transport_info_reply_b_xml,
accept_xml);
}
void TestBadRedirect(SignalingProtocol protocol) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
std::string channel_name_a = "chana";
std::string channel_name_b = "chanb";
std::string initiate_xml = InitiateXml(
protocol, content_name, content_type);
std::string transport_info_xml = TransportInfo4Xml(
protocol, content_name,
channel_name_a, 0, 1,
channel_name_b, 2, 3);
std::string transport_info_reply_xml = TransportInfo4Xml(
protocol, content_name,
channel_name_a, 4, 5,
channel_name_b, 6, 7);
std::string accept_xml = AcceptXml(
protocol, content_name, content_type);
std::string responder_full = kResponder + "/full";
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
rtc::scoped_ptr<TestClient> responder(
new TestClient(allocator.get(), &next_message_id,
responder_full, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
// Create Session and check channels and state.
initiator->CreateSession();
EXPECT_EQ(1U, initiator->session_created_count);
EXPECT_EQ(kSessionId, initiator->session->id());
EXPECT_EQ(initiator->session->local_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_INIT,
initiator->session_state());
EXPECT_TRUE(initiator->HasChannel(content_name, 1));
EXPECT_TRUE(initiator->HasChannel(content_name, 2));
// Initiate and expect initiate message sent.
cricket::SessionDescription* offer = NewTestSessionDescription(
content_name, content_type);
EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
EXPECT_EQ(initiator->session->remote_name(), kResponder);
EXPECT_EQ(initiator->session->local_description(), offer);
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder, initiate_xml));
// Expect transport-info message from initiator.
initiator->DeliverAckToLastStanza();
initiator->PrepareCandidates();
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder, transport_info_xml));
// Send an unauthorized redirect to the initiator and expect it be ignored.
initiator->blow_up_on_error = false;
const buzz::XmlElement* initiate_stanza = initiator->stanza();
rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
buzz::XmlElement::ForStr(
IqError("ER", kResponder, kInitiator,
RedirectXml(protocol, initiate_xml, "not@allowed.com"))));
initiator->session_manager->OnFailedSend(
initiate_stanza, redirect_stanza.get());
EXPECT_EQ(initiator->session->remote_name(), kResponder);
initiator->blow_up_on_error = true;
EXPECT_EQ(initiator->error_count, 1);
}
void TestGoodRedirect(SignalingProtocol protocol) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
std::string channel_name_a = "chana";
std::string channel_name_b = "chanb";
std::string initiate_xml = InitiateXml(
protocol, content_name, content_type);
std::string transport_info_xml = TransportInfo4Xml(
protocol, content_name,
channel_name_a, 0, 1,
channel_name_b, 2, 3);
std::string transport_info_reply_xml = TransportInfo4Xml(
protocol, content_name,
channel_name_a, 4, 5,
channel_name_b, 6, 7);
std::string accept_xml = AcceptXml(
protocol, content_name, content_type);
std::string responder_full = kResponder + "/full";
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
rtc::scoped_ptr<TestClient> responder(
new TestClient(allocator.get(), &next_message_id,
responder_full, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
// Create Session and check channels and state.
initiator->CreateSession();
EXPECT_EQ(1U, initiator->session_created_count);
EXPECT_EQ(kSessionId, initiator->session->id());
EXPECT_EQ(initiator->session->local_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_INIT,
initiator->session_state());
EXPECT_TRUE(initiator->HasChannel(content_name, 1));
EXPECT_TRUE(initiator->HasChannel(content_name, 2));
// Initiate and expect initiate message sent.
cricket::SessionDescription* offer = NewTestSessionDescription(
content_name, content_type);
EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
EXPECT_EQ(initiator->session->remote_name(), kResponder);
EXPECT_EQ(initiator->session->local_description(), offer);
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder, initiate_xml));
// Expect transport-info message from initiator.
initiator->DeliverAckToLastStanza();
initiator->PrepareCandidates();
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder, transport_info_xml));
// Send a redirect to the initiator and expect all of the message
// to be resent.
const buzz::XmlElement* initiate_stanza = initiator->stanza();
rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
buzz::XmlElement::ForStr(
IqError("ER2", kResponder, kInitiator,
RedirectXml(protocol, initiate_xml, responder_full))));
initiator->session_manager->OnFailedSend(
initiate_stanza, redirect_stanza.get());
EXPECT_EQ(initiator->session->remote_name(), responder_full);
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("2", kInitiator, responder_full, initiate_xml));
initiator->ExpectSentStanza(
IqSet("3", kInitiator, responder_full, transport_info_xml));
// Deliver the initiate. Expect ack and session created with
// transports.
responder->DeliverStanza(
IqSet("2", kInitiator, responder_full, initiate_xml));
responder->ExpectSentStanza(
IqAck("2", responder_full, kInitiator));
EXPECT_EQ(0U, responder->sent_stanza_count());
EXPECT_EQ(1U, responder->session_created_count);
EXPECT_EQ(kSessionId, responder->session->id());
EXPECT_EQ(responder->session->local_name(), responder_full);
EXPECT_EQ(responder->session->remote_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
responder->session_state());
EXPECT_TRUE(responder->HasChannel(content_name, 1));
EXPECT_TRUE(responder->HasChannel(content_name, 2));
// Deliver transport-info and expect ack.
responder->DeliverStanza(
IqSet("3", kInitiator, responder_full, transport_info_xml));
responder->ExpectSentStanza(
IqAck("3", responder_full, kInitiator));
// Expect reply transport-infos sent to new remote JID
responder->PrepareCandidates();
EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
responder->ExpectSentStanza(
IqSet("4", responder_full, kInitiator, transport_info_reply_xml));
initiator->DeliverStanza(responder->stanza());
initiator->ExpectSentStanza(
IqAck("4", kInitiator, responder_full));
// The channels should be able to become writable at this point. This
// requires pinging, so it may take a little while.
EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
initiator->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
initiator->chan_b->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
responder->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
responder->chan_b->readable(), kEventTimeout);
// Accept the session and expect accept stanza.
cricket::SessionDescription* answer = NewTestSessionDescription(
content_name, content_type);
EXPECT_TRUE(responder->session->Accept(answer));
EXPECT_EQ(responder->session->local_description(), answer);
responder->ExpectSentStanza(
IqSet("5", responder_full, kInitiator, accept_xml));
EXPECT_EQ(0U, responder->sent_stanza_count());
// Deliver the accept message and expect an ack.
initiator->DeliverStanza(responder->stanza());
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqAck("5", kInitiator, responder_full));
EXPECT_EQ(0U, initiator->sent_stanza_count());
// Both sessions should be in progress and have functioning
// channels.
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
initiator->session_state(), kEventTimeout);
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
responder->session_state(), kEventTimeout);
TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
responder->chan_a.get(), responder->chan_b.get());
}
void TestCandidatesInInitiateAndAccept(const std::string& test_name) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
std::string channel_name_a = "rtp";
std::string channel_name_b = "rtcp";
cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
rtc::scoped_ptr<TestClient> responder(
new TestClient(allocator.get(), &next_message_id,
kResponder, protocol,
content_type,
content_name, channel_name_a,
content_name, channel_name_b));
// Create Session and check channels and state.
initiator->CreateSession();
EXPECT_TRUE(initiator->HasTransport(content_name));
EXPECT_TRUE(initiator->HasChannel(content_name, 1));
EXPECT_TRUE(initiator->HasTransport(content_name));
EXPECT_TRUE(initiator->HasChannel(content_name, 2));
// Initiate and expect initiate message sent.
cricket::SessionDescription* offer = NewTestSessionDescription(
content_name, content_type);
EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder,
InitiateXml(protocol, content_name, content_type)));
// Fake the delivery the initiate and candidates together.
responder->DeliverStanza(
IqSet("A", kInitiator, kResponder,
JingleInitiateActionXml(
JingleContentXml(
content_name, content_type, kTransportType,
P2pCandidateXml(channel_name_a, 0) +
P2pCandidateXml(channel_name_a, 1) +
P2pCandidateXml(channel_name_b, 2) +
P2pCandidateXml(channel_name_b, 3)))));
responder->ExpectSentStanza(
IqAck("A", kResponder, kInitiator));
EXPECT_EQ(0U, responder->sent_stanza_count());
EXPECT_EQ(1U, responder->session_created_count);
EXPECT_EQ(kSessionId, responder->session->id());
EXPECT_EQ(responder->session->local_name(), kResponder);
EXPECT_EQ(responder->session->remote_name(), kInitiator);
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
responder->session_state());
EXPECT_TRUE(responder->HasTransport(content_name));
EXPECT_TRUE(responder->HasChannel(content_name, 1));
EXPECT_TRUE(responder->HasTransport(content_name));
EXPECT_TRUE(responder->HasChannel(content_name, 2));
// Expect transport-info message from initiator.
// But don't send candidates until initiate ack is received.
initiator->DeliverAckToLastStanza();
initiator->PrepareCandidates();
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder,
TransportInfo4Xml(protocol, content_name,
channel_name_a, 0, 1,
channel_name_b, 2, 3)));
responder->PrepareCandidates();
EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
responder->ExpectSentStanza(
IqSet("2", kResponder, kInitiator,
TransportInfo4Xml(protocol, content_name,
channel_name_a, 4, 5,
channel_name_b, 6, 7)));
// Accept the session and expect accept stanza.
cricket::SessionDescription* answer = NewTestSessionDescription(
content_name, content_type);
EXPECT_TRUE(responder->session->Accept(answer));
responder->ExpectSentStanza(
IqSet("3", kResponder, kInitiator,
AcceptXml(protocol, content_name, content_type)));
EXPECT_EQ(0U, responder->sent_stanza_count());
// Fake the delivery the accept and candidates together.
initiator->DeliverStanza(
IqSet("B", kResponder, kInitiator,
JingleActionXml("session-accept",
JingleContentXml(
content_name, content_type, kTransportType,
P2pCandidateXml(channel_name_a, 4) +
P2pCandidateXml(channel_name_a, 5) +
P2pCandidateXml(channel_name_b, 6) +
P2pCandidateXml(channel_name_b, 7)))));
EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
initiator->ExpectSentStanza(
IqAck("B", kInitiator, kResponder));
EXPECT_EQ(0U, initiator->sent_stanza_count());
// The channels should be able to become writable at this point. This
// requires pinging, so it may take a little while.
EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
initiator->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
initiator->chan_b->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
responder->chan_a->readable(), kEventTimeout);
EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
responder->chan_b->readable(), kEventTimeout);
// Both sessions should be in progress and have functioning
// channels.
EXPECT_EQ(protocol, initiator->session->current_protocol());
EXPECT_EQ(protocol, responder->session->current_protocol());
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
initiator->session_state(), kEventTimeout);
EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
responder->session_state(), kEventTimeout);
TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
responder->chan_a.get(), responder->chan_b.get());
}
// Tests that when an initiator terminates right after initiate,
// everything behaves correctly.
void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, protocol,
content_type,
content_name, "a",
content_name, "b"));
rtc::scoped_ptr<TestClient> responder(
new TestClient(allocator.get(), &next_message_id,
kResponder, protocol,
content_type,
content_name, "a",
content_name, "b"));
// Send initiate
initiator->CreateSession();
EXPECT_TRUE(initiator->session->Initiate(
kResponder, NewTestSessionDescription(content_name, content_type)));
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder,
InitiateXml(protocol, content_name, content_type)));
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("0", kResponder, kInitiator));
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
responder->session_state());
initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR);
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder,
TerminateXml(protocol, cricket::STR_TERMINATE_ERROR)));
EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
initiator->session_state());
responder->DeliverStanza(initiator->stanza());
responder->ExpectSentStanza(
IqAck("1", kResponder, kInitiator));
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
responder->session_state());
}
// Tests that when the responder rejects, everything behaves
// correctly.
void TestRejection(SignalingProtocol protocol) {
std::string content_name = "main";
std::string content_type = "http://oink.splat/session";
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, protocol,
content_type,
content_name, "a",
content_name, "b"));
// Send initiate
initiator->CreateSession();
EXPECT_TRUE(initiator->session->Initiate(
kResponder, NewTestSessionDescription(content_name, content_type)));
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder,
InitiateXml(protocol, content_name, content_type)));
EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
initiator->session_state());
initiator->DeliverStanza(
IqSet("1", kResponder, kInitiator,
RejectXml(protocol, cricket::STR_TERMINATE_ERROR)));
initiator->ExpectSentStanza(
IqAck("1", kInitiator, kResponder));
if (protocol == PROTOCOL_JINGLE) {
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
initiator->session_state());
} else {
EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT,
initiator->session_state());
}
}
void TestTransportMux() {
SignalingProtocol initiator_protocol = PROTOCOL_JINGLE;
SignalingProtocol responder_protocol = PROTOCOL_JINGLE;
SignalingProtocol resulting_protocol = PROTOCOL_JINGLE;
std::string content_type = cricket::NS_JINGLE_RTP;
std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
std::string content_name_a = cricket::CN_AUDIO;
std::string channel_name_a = "rtp";
std::string content_name_b = cricket::CN_VIDEO;
std::string channel_name_b = "video_rtp";
std::string initiate_xml = InitiateXml(
initiator_protocol,
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type, true);
std::string transport_info_a_xml = TransportInfo2Xml(
initiator_protocol, content_name_a,
channel_name_a, 0, 1);
std::string transport_info_b_xml = TransportInfo2Xml(
initiator_protocol, content_name_b,
channel_name_b, 2, 3);
std::string transport_info_reply_a_xml = TransportInfo2Xml(
resulting_protocol, content_name_a,
channel_name_a, 4, 5);
std::string transport_info_reply_b_xml = TransportInfo2Xml(
resulting_protocol, content_name_b,
channel_name_b, 6, 7);
std::string accept_xml = AcceptXml(
resulting_protocol,
gingle_content_type,
content_name_a, content_type,
content_name_b, content_type, true);
TestSession(initiator_protocol, responder_protocol, resulting_protocol,
gingle_content_type,
content_type,
content_name_a, channel_name_a,
content_name_b, channel_name_b,
initiate_xml,
transport_info_a_xml, transport_info_b_xml,
transport_info_reply_a_xml, transport_info_reply_b_xml,
accept_xml,
true);
}
void TestSendDescriptionInfo() {
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
std::string content_name = "content-name";
std::string content_type = "content-type";
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, PROTOCOL_JINGLE,
content_type,
content_name, "",
"", ""));
initiator->CreateSession();
cricket::SessionDescription* offer = NewTestSessionDescription(
content_name, content_type);
std::string initiate_xml = InitiateXml(
PROTOCOL_JINGLE, content_name, content_type);
cricket::ContentInfos contents;
TestContentDescription content(content_type, content_type);
contents.push_back(
cricket::ContentInfo(content_name, content_type, &content));
std::string description_info_xml = JingleDescriptionInfoXml(
content_name, content_type);
EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
initiator->ExpectSentStanza(
IqSet("0", kInitiator, kResponder, initiate_xml));
EXPECT_TRUE(initiator->session->SendDescriptionInfoMessage(contents));
initiator->ExpectSentStanza(
IqSet("1", kInitiator, kResponder, description_info_xml));
}
void DoTestSignalNewDescription(
TestClient* client,
cricket::BaseSession::State state,
cricket::ContentAction expected_content_action,
cricket::ContentSource expected_content_source) {
// Clean up before the new test.
client->new_local_description = false;
client->new_remote_description = false;
client->SetSessionState(state);
EXPECT_EQ((expected_content_source == cricket::CS_LOCAL),
client->new_local_description);
EXPECT_EQ((expected_content_source == cricket::CS_REMOTE),
client->new_remote_description);
EXPECT_EQ(expected_content_action, client->last_content_action);
EXPECT_EQ(expected_content_source, client->last_content_source);
}
void TestCallerSignalNewDescription() {
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
std::string content_name = "content-name";
std::string content_type = "content-type";
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, PROTOCOL_JINGLE,
content_type,
content_name, "",
"", ""));
initiator->CreateSession();
// send offer -> send update offer ->
// receive pr answer -> receive update pr answer ->
// receive answer
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
cricket::CA_OFFER, cricket::CS_LOCAL);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
cricket::CA_OFFER, cricket::CS_LOCAL);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
cricket::CA_PRANSWER, cricket::CS_REMOTE);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
cricket::CA_PRANSWER, cricket::CS_REMOTE);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_RECEIVEDACCEPT,
cricket::CA_ANSWER, cricket::CS_REMOTE);
}
void TestCalleeSignalNewDescription() {
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
std::string content_name = "content-name";
std::string content_type = "content-type";
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, PROTOCOL_JINGLE,
content_type,
content_name, "",
"", ""));
initiator->CreateSession();
// receive offer -> receive update offer ->
// send pr answer -> send update pr answer ->
// send answer
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
cricket::CA_OFFER, cricket::CS_REMOTE);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
cricket::CA_OFFER, cricket::CS_REMOTE);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
cricket::CA_PRANSWER, cricket::CS_LOCAL);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
cricket::CA_PRANSWER, cricket::CS_LOCAL);
DoTestSignalNewDescription(
initiator.get(), cricket::BaseSession::STATE_SENTACCEPT,
cricket::CA_ANSWER, cricket::CS_LOCAL);
}
void TestGetTransportStats() {
rtc::scoped_ptr<cricket::PortAllocator> allocator(
new TestPortAllocator());
int next_message_id = 0;
std::string content_name = "content-name";
std::string content_type = "content-type";
rtc::scoped_ptr<TestClient> initiator(
new TestClient(allocator.get(), &next_message_id,
kInitiator, PROTOCOL_JINGLE,
content_type,
content_name, "",
"", ""));
initiator->CreateSession();
cricket::SessionStats stats;
EXPECT_TRUE(initiator->session->GetStats(&stats));
// At initiation, there are 2 transports.
EXPECT_EQ(2ul, stats.proxy_to_transport.size());
EXPECT_EQ(2ul, stats.transport_stats.size());
}
rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_;
rtc::scoped_ptr<rtc::VirtualSocketServer> ss_;
rtc::SocketServerScope ss_scope_;
};
// For each of these, "X => Y = Z" means "if a client with protocol X
// initiates to a client with protocol Y, they end up speaking protocol Z.
// Gingle => Gingle = Gingle (with other content)
TEST_F(SessionTest, GingleToGingleOtherContent) {
TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Gingle => Gingle = Gingle (with audio content)
TEST_F(SessionTest, GingleToGingleAudioContent) {
TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Gingle => Gingle = Gingle (with video contents)
TEST_F(SessionTest, GingleToGingleVideoContents) {
TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Jingle => Jingle = Jingle (with other content)
TEST_F(SessionTest, JingleToJingleOtherContent) {
TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
// Jingle => Jingle = Jingle (with audio content)
TEST_F(SessionTest, JingleToJingleAudioContent) {
TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
// Jingle => Jingle = Jingle (with video contents)
TEST_F(SessionTest, JingleToJingleVideoContents) {
TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
// Hybrid => Hybrid = Jingle (with other content)
TEST_F(SessionTest, HybridToHybridOtherContent) {
TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Hybrid => Hybrid = Jingle (with audio content)
TEST_F(SessionTest, HybridToHybridAudioContent) {
TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Hybrid => Hybrid = Jingle (with video contents)
TEST_F(SessionTest, HybridToHybridVideoContents) {
TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Gingle => Hybrid = Gingle (with other content)
TEST_F(SessionTest, GingleToHybridOtherContent) {
TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}
// Gingle => Hybrid = Gingle (with audio content)
TEST_F(SessionTest, GingleToHybridAudioContent) {
TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}
// Gingle => Hybrid = Gingle (with video contents)
TEST_F(SessionTest, GingleToHybridVideoContents) {
TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}
// Jingle => Hybrid = Jingle (with other content)
TEST_F(SessionTest, JingleToHybridOtherContent) {
TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Jingle => Hybrid = Jingle (with audio content)
TEST_F(SessionTest, JingleToHybridAudioContent) {
TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Jingle => Hybrid = Jingle (with video contents)
TEST_F(SessionTest, JingleToHybridVideoContents) {
TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}
// Hybrid => Gingle = Gingle (with other content)
TEST_F(SessionTest, HybridToGingleOtherContent) {
TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Hybrid => Gingle = Gingle (with audio content)
TEST_F(SessionTest, HybridToGingleAudioContent) {
TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Hybrid => Gingle = Gingle (with video contents)
TEST_F(SessionTest, HybridToGingleVideoContents) {
TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}
// Hybrid => Jingle = Jingle (with other content)
TEST_F(SessionTest, HybridToJingleOtherContent) {
TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
// Hybrid => Jingle = Jingle (with audio content)
TEST_F(SessionTest, HybridToJingleAudioContent) {
TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
// Hybrid => Jingle = Jingle (with video contents)
TEST_F(SessionTest, HybridToJingleVideoContents) {
TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}
TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) {
TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE);
}
TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) {
TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE);
}
TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) {
TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID);
}
TEST_F(SessionTest, GingleRejection) {
TestRejection(PROTOCOL_GINGLE);
}
TEST_F(SessionTest, JingleRejection) {
TestRejection(PROTOCOL_JINGLE);
}
TEST_F(SessionTest, GingleGoodRedirect) {
TestGoodRedirect(PROTOCOL_GINGLE);
}
TEST_F(SessionTest, JingleGoodRedirect) {
TestGoodRedirect(PROTOCOL_JINGLE);
}
TEST_F(SessionTest, GingleBadRedirect) {
TestBadRedirect(PROTOCOL_GINGLE);
}
TEST_F(SessionTest, JingleBadRedirect) {
TestBadRedirect(PROTOCOL_JINGLE);
}
TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) {
TestCandidatesInInitiateAndAccept("Candidates in initiate/accept");
}
TEST_F(SessionTest, TestTransportMux) {
TestTransportMux();
}
TEST_F(SessionTest, TestSendDescriptionInfo) {
TestSendDescriptionInfo();
}
TEST_F(SessionTest, TestCallerSignalNewDescription) {
TestCallerSignalNewDescription();
}
TEST_F(SessionTest, TestCalleeSignalNewDescription) {
TestCalleeSignalNewDescription();
}
TEST_F(SessionTest, TestGetTransportStats) {
TestGetTransportStats();
}