Update libjingle to 50654631.

R=mallinath@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/2000006

git-svn-id: http://webrtc.googlecode.com/svn/trunk@4519 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
wu@webrtc.org 2013-08-10 07:18:04 +00:00
parent bf853f2732
commit 91053e7c5a
50 changed files with 2377 additions and 819 deletions

View File

@ -159,6 +159,37 @@ bool DataChannel::Send(const DataBuffer& buffer) {
return true;
}
void DataChannel::QueueControl(const talk_base::Buffer* buffer) {
queued_control_data_.push(buffer);
}
bool DataChannel::SendControl(const talk_base::Buffer* buffer) {
if (state_ != kOpen) {
QueueControl(buffer);
return true;
}
if (session_->data_channel_type() == cricket::DCT_RTP) {
delete buffer;
return false;
}
cricket::SendDataParams send_params;
send_params.ssrc = config_.id;
send_params.ordered = true;
send_params.type = cricket::DMT_CONTROL;
cricket::SendDataResult send_result;
bool retval = session_->data_channel()->SendData(
send_params, *buffer, &send_result);
if (!retval && send_result == cricket::SDR_BLOCK) {
// Link is congested. Queue for later.
QueueControl(buffer);
} else {
delete buffer;
}
return retval;
}
void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) {
if (receive_ssrc_set_) {
ASSERT(session_->data_channel_type() == cricket::DCT_RTP ||
@ -278,9 +309,9 @@ void DataChannel::SetState(DataState state) {
}
void DataChannel::ConnectToDataSession() {
ASSERT(session_->data_channel() != NULL);
if (!session_->data_channel()) {
LOG(LS_ERROR) << "The DataEngine does not exist.";
ASSERT(session_->data_channel() != NULL);
return;
}
@ -288,9 +319,17 @@ void DataChannel::ConnectToDataSession() {
data_session_->SignalReadyToSendData.connect(this,
&DataChannel::OnChannelReady);
data_session_->SignalDataReceived.connect(this, &DataChannel::OnDataReceived);
cricket::StreamParams params =
cricket::StreamParams::CreateLegacy(id());
data_session_->media_channel()->AddSendStream(params);
data_session_->media_channel()->AddRecvStream(params);
}
void DataChannel::DisconnectFromDataSession() {
if (data_session_->media_channel() != NULL) {
data_session_->media_channel()->RemoveSendStream(id());
data_session_->media_channel()->RemoveRecvStream(id());
}
data_session_->SignalReadyToSendData.disconnect(this);
data_session_->SignalDataReceived.disconnect(this);
data_session_ = NULL;
@ -318,6 +357,7 @@ void DataChannel::ClearQueuedReceivedData() {
}
void DataChannel::SendQueuedSendData() {
DeliverQueuedControlData();
if (!was_ever_writable_) {
return;
}
@ -335,6 +375,16 @@ void DataChannel::SendQueuedSendData() {
}
}
void DataChannel::DeliverQueuedControlData() {
if (was_ever_writable_) {
while (!queued_control_data_.empty()) {
const talk_base::Buffer *buf = queued_control_data_.front();
queued_control_data_.pop();
SendControl(buf);
}
}
}
void DataChannel::ClearQueuedSendData() {
while (!queued_send_data_.empty()) {
DataBuffer* buffer = queued_send_data_.front();

View File

@ -74,6 +74,9 @@ class DataChannel : public DataChannelInterface,
virtual void Close();
virtual DataState state() const { return state_; }
virtual bool Send(const DataBuffer& buffer);
// Send a control message right now, or queue for later.
virtual bool SendControl(const talk_base::Buffer* buffer);
void ConnectToDataSession();
// Set the SSRC this channel should use to receive data from the
// underlying data engine.
@ -89,6 +92,10 @@ class DataChannel : public DataChannelInterface,
// Called if the underlying data engine is closing.
void OnDataEngineClose();
// Called when the channel's ready to use. That can happen when the
// underlying DataMediaChannel becomes ready, or when this channel is a new
// stream on an existing DataMediaChannel, and we've finished negotiation.
void OnChannelReady(bool writable);
protected:
DataChannel(WebRtcSession* session, const std::string& label);
virtual ~DataChannel();
@ -100,15 +107,15 @@ class DataChannel : public DataChannelInterface,
void OnDataReceived(cricket::DataChannel* channel,
const cricket::ReceiveDataParams& params,
const talk_base::Buffer& payload);
void OnChannelReady(bool writable);
private:
void DoClose();
void UpdateState();
void SetState(DataState state);
void ConnectToDataSession();
void DisconnectFromDataSession();
bool IsConnectedToDataSession() { return data_session_ != NULL; }
void DeliverQueuedControlData();
void QueueControl(const talk_base::Buffer* buffer);
void DeliverQueuedReceivedData();
void ClearQueuedReceivedData();
void SendQueuedSendData();
@ -128,6 +135,9 @@ class DataChannel : public DataChannelInterface,
uint32 send_ssrc_;
bool receive_ssrc_set_;
uint32 receive_ssrc_;
// Control messages that always have to get sent out before any queued
// data.
std::queue<const talk_base::Buffer*> queued_control_data_;
std::queue<DataBuffer*> queued_received_data_;
std::deque<DataBuffer*> queued_send_data_;
};

View File

@ -26,6 +26,7 @@
*/
#include "talk/app/webrtc/datachannel.h"
#include "talk/app/webrtc/jsep.h"
#include "talk/app/webrtc/mediastreamsignaling.h"
#include "talk/app/webrtc/test/fakeconstraints.h"
#include "talk/app/webrtc/webrtcsession.h"
@ -34,10 +35,31 @@
#include "talk/media/devices/fakedevicemanager.h"
#include "talk/session/media/channelmanager.h"
using webrtc::CreateSessionDescriptionObserver;
using webrtc::MediaConstraintsInterface;
using webrtc::SessionDescriptionInterface;
const uint32 kFakeSsrc = 1;
class CreateSessionDescriptionObserverForTest
: public talk_base::RefCountedObject<CreateSessionDescriptionObserver> {
public:
CreateSessionDescriptionObserverForTest() : description_(NULL) {}
virtual void OnSuccess(SessionDescriptionInterface* desc) {
description_ = desc;
}
virtual void OnFailure(const std::string& error) {}
SessionDescriptionInterface* description() { return description_; }
protected:
~CreateSessionDescriptionObserverForTest() {}
private:
SessionDescriptionInterface* description_;
};
class SctpDataChannelTest : public testing::Test {
protected:
SctpDataChannelTest()
@ -49,13 +71,14 @@ class SctpDataChannelTest : public testing::Test {
new cricket::FakeDeviceManager(),
new cricket::CaptureManager(),
talk_base::Thread::Current())),
ms_signaling_(new webrtc::MediaStreamSignaling(
talk_base::Thread::Current(), NULL)),
media_stream_signaling_(
new webrtc::MediaStreamSignaling(talk_base::Thread::Current(),
NULL)),
session_(channel_manager_.get(),
talk_base::Thread::Current(),
talk_base::Thread::Current(),
NULL,
ms_signaling_.get()),
media_stream_signaling_.get()),
webrtc_data_channel_(NULL) {}
virtual void SetUp() {
@ -67,10 +90,13 @@ class SctpDataChannelTest : public testing::Test {
constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, true);
constraints.AddMandatory(MediaConstraintsInterface::kEnableSctpDataChannels,
true);
ASSERT_TRUE(session_.Initialize(&constraints));
webrtc::SessionDescriptionInterface* offer = session_.CreateOffer(NULL);
ASSERT_TRUE(offer != NULL);
ASSERT_TRUE(session_.SetLocalDescription(offer, NULL));
ASSERT_TRUE(session_.Initialize(&constraints, NULL));
talk_base::scoped_refptr<CreateSessionDescriptionObserverForTest> observer
= new CreateSessionDescriptionObserverForTest();
session_.CreateOffer(observer.get(), NULL);
EXPECT_TRUE_WAIT(observer->description() != NULL, 1000);
ASSERT_TRUE(observer->description() != NULL);
ASSERT_TRUE(session_.SetLocalDescription(observer->description(), NULL));
webrtc_data_channel_ = webrtc::DataChannel::Create(&session_, "test", NULL);
// Connect to the media channel.
@ -91,7 +117,7 @@ class SctpDataChannelTest : public testing::Test {
cricket::FakeMediaEngine* media_engine_;
cricket::FakeDataEngine* data_engine_;
talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
talk_base::scoped_ptr<webrtc::MediaStreamSignaling> ms_signaling_;
talk_base::scoped_ptr<webrtc::MediaStreamSignaling> media_stream_signaling_;
webrtc::WebRtcSession session_;
talk_base::scoped_refptr<webrtc::DataChannel> webrtc_data_channel_;
};

View File

@ -34,6 +34,7 @@
#include "talk/app/webrtc/mediaconstraintsinterface.h"
#include "talk/app/webrtc/mediastreamtrackproxy.h"
#include "talk/app/webrtc/videotrack.h"
#include "talk/base/bytebuffer.h"
static const char kDefaultStreamLabel[] = "default";
static const char kDefaultAudioTrackLabel[] = "defaulta0";
@ -235,6 +236,29 @@ bool MediaStreamSignaling::AddDataChannel(DataChannel* data_channel) {
return true;
}
bool MediaStreamSignaling::AddDataChannelFromOpenMessage(
const std::string& label,
const DataChannelInit& config) {
if (!data_channel_factory_) {
LOG(LS_WARNING) << "Remote peer requested a DataChannel but DataChannels "
<< "are not supported.";
return false;
}
if (data_channels_.find(label) != data_channels_.end()) {
LOG(LS_ERROR) << "DataChannel with label " << label
<< " already exists.";
return false;
}
scoped_refptr<DataChannel> channel(
data_channel_factory_->CreateDataChannel(label, &config));
data_channels_[label] = channel;
stream_observer_->OnAddDataChannel(channel);
// It's immediately ready to use.
channel->OnChannelReady(true);
return true;
}
bool MediaStreamSignaling::AddLocalStream(MediaStreamInterface* local_stream) {
if (local_streams_->find(local_stream->label()) != NULL) {
LOG(LS_WARNING) << "MediaStream with label " << local_stream->label()
@ -864,6 +888,145 @@ void MediaStreamSignaling::CreateRemoteDataChannel(const std::string& label,
stream_observer_->OnAddDataChannel(channel);
}
// Format defined at
// http://tools.ietf.org/html/draft-jesup-rtcweb-data-protocol-04
const uint8 DATA_CHANNEL_OPEN_MESSAGE_TYPE = 0x03;
enum DataChannelOpenMessageChannelType {
DCOMCT_ORDERED_RELIABLE = 0x00,
DCOMCT_ORDERED_PARTIAL_RTXS = 0x01,
DCOMCT_ORDERED_PARTIAL_TIME = 0x02,
DCOMCT_UNORDERED_RELIABLE = 0x80,
DCOMCT_UNORDERED_PARTIAL_RTXS = 0x81,
DCOMCT_UNORDERED_PARTIAL_TIME = 0x82,
};
bool MediaStreamSignaling::ParseDataChannelOpenMessage(
const talk_base::Buffer& payload,
std::string* label,
DataChannelInit* config) {
// Format defined at
// http://tools.ietf.org/html/draft-jesup-rtcweb-data-protocol-04
talk_base::ByteBuffer buffer(payload.data(), payload.length());
uint8 message_type;
if (!buffer.ReadUInt8(&message_type)) {
LOG(LS_WARNING) << "Could not read OPEN message type.";
return false;
}
if (message_type != DATA_CHANNEL_OPEN_MESSAGE_TYPE) {
LOG(LS_WARNING) << "Data Channel OPEN message of unexpected type: "
<< message_type;
return false;
}
uint8 channel_type;
if (!buffer.ReadUInt8(&channel_type)) {
LOG(LS_WARNING) << "Could not read OPEN message channel type.";
return false;
}
uint16 priority;
if (!buffer.ReadUInt16(&priority)) {
LOG(LS_WARNING) << "Could not read OPEN message reliabilility prioirty.";
return false;
}
uint32 reliability_param;
if (!buffer.ReadUInt32(&reliability_param)) {
LOG(LS_WARNING) << "Could not read OPEN message reliabilility param.";
return false;
}
uint16 label_length;
if (!buffer.ReadUInt16(&label_length)) {
LOG(LS_WARNING) << "Could not read OPEN message label length.";
return false;
}
uint16 protocol_length;
if (!buffer.ReadUInt16(&protocol_length)) {
LOG(LS_WARNING) << "Could not read OPEN message protocol length.";
return false;
}
if (!buffer.ReadString(label, (size_t) label_length)) {
LOG(LS_WARNING) << "Could not read OPEN message label";
return false;
}
if (!buffer.ReadString(&config->protocol, protocol_length)) {
LOG(LS_WARNING) << "Could not read OPEN message protocol.";
return false;
}
config->ordered = true;
switch (channel_type) {
case DCOMCT_UNORDERED_RELIABLE:
case DCOMCT_UNORDERED_PARTIAL_RTXS:
case DCOMCT_UNORDERED_PARTIAL_TIME:
config->ordered = false;
}
config->maxRetransmits = -1;
config->maxRetransmitTime = -1;
switch (channel_type) {
case DCOMCT_ORDERED_PARTIAL_RTXS:
case DCOMCT_UNORDERED_PARTIAL_RTXS:
config->maxRetransmits = reliability_param;
case DCOMCT_ORDERED_PARTIAL_TIME:
case DCOMCT_UNORDERED_PARTIAL_TIME:
config->maxRetransmitTime = reliability_param;
}
return true;
}
bool MediaStreamSignaling::WriteDataChannelOpenMessage(
const std::string& label,
const DataChannelInit& config,
talk_base::Buffer* payload) {
// Format defined at
// http://tools.ietf.org/html/draft-jesup-rtcweb-data-protocol-04
// TODO(pthatcher)
uint8 channel_type = 0;
uint32 reliability_param = 0;
uint16 priority = 0;
if (config.ordered) {
if (config.maxRetransmits > -1) {
channel_type = DCOMCT_ORDERED_PARTIAL_RTXS;
reliability_param = config.maxRetransmits;
} else if (config.maxRetransmitTime > -1) {
channel_type = DCOMCT_ORDERED_PARTIAL_TIME;
reliability_param = config.maxRetransmitTime;
} else {
channel_type = DCOMCT_ORDERED_RELIABLE;
}
} else {
if (config.maxRetransmits > -1) {
channel_type = DCOMCT_UNORDERED_PARTIAL_RTXS;
reliability_param = config.maxRetransmits;
} else if (config.maxRetransmitTime > -1) {
channel_type = DCOMCT_UNORDERED_PARTIAL_TIME;
reliability_param = config.maxRetransmitTime;
} else {
channel_type = DCOMCT_UNORDERED_RELIABLE;
}
}
talk_base::ByteBuffer buffer(
NULL, 20 + label.length() + config.protocol.length(),
talk_base::ByteBuffer::ORDER_NETWORK);
buffer.WriteUInt8(DATA_CHANNEL_OPEN_MESSAGE_TYPE);
buffer.WriteUInt8(channel_type);
buffer.WriteUInt16(priority);
buffer.WriteUInt32(reliability_param);
buffer.WriteUInt16(static_cast<uint16>(label.length()));
buffer.WriteUInt16(static_cast<uint16>(config.protocol.length()));
buffer.WriteString(label);
buffer.WriteString(config.protocol);
payload->SetData(buffer.Data(), buffer.Length());
return true;
}
void MediaStreamSignaling::UpdateLocalSctpDataChannels() {
DataChannels::iterator it = data_channels_.begin();
for (; it != data_channels_.end(); ++it) {

View File

@ -190,6 +190,15 @@ class MediaStreamSignaling {
// Adds |data_channel| to the collection of DataChannels that will be
// be offered in a SessionDescription.
bool AddDataChannel(DataChannel* data_channel);
// After we receive an OPEN message, create a data channel and add it.
bool AddDataChannelFromOpenMessage(
const std::string& label, const DataChannelInit& config);
bool ParseDataChannelOpenMessage(
const talk_base::Buffer& payload, std::string* label,
DataChannelInit* config);
bool WriteDataChannelOpenMessage(
const std::string& label, const DataChannelInit& config,
talk_base::Buffer* payload);
// Returns a MediaSessionOptions struct with options decided by |constraints|,
// the local MediaStreams and DataChannels.
@ -243,6 +252,8 @@ class MediaStreamSignaling {
StreamCollectionInterface* remote_streams() const {
return remote_streams_.get();
}
void UpdateLocalSctpDataChannels();
void UpdateRemoteSctpDataChannels();
private:
struct RemotePeerInfo {
@ -357,8 +368,6 @@ class MediaStreamSignaling {
void UpdateClosingDataChannels(
const std::vector<std::string>& active_channels, bool is_local_update);
void CreateRemoteDataChannel(const std::string& label, uint32 remote_ssrc);
void UpdateLocalSctpDataChannels();
void UpdateRemoteSctpDataChannels();
RemotePeerInfo remote_info_;
talk_base::Thread* signaling_thread_;

View File

@ -50,9 +50,11 @@ static const size_t kTurnHostTokensNum = 2;
// Number of tokens must be preset when TURN uri has transport param.
static const size_t kTurnTransportTokensNum = 2;
// The default stun port.
static const int kDefaultPort = 3478;
static const int kDefaultStunPort = 3478;
static const int kDefaultStunTlsPort = 5349;
static const char kTransport[] = "transport";
static const char kDefaultTransportType[] = "udp";
static const char kUdpTransportType[] = "udp";
static const char kTcpTransportType[] = "tcp";
// NOTE: Must be in the same order as the ServiceType enum.
static const char* kValidIceServiceTypes[] = {
@ -67,9 +69,7 @@ enum ServiceType {
};
enum {
MSG_CREATE_SESSIONDESCRIPTION_SUCCESS = 0,
MSG_CREATE_SESSIONDESCRIPTION_FAILED,
MSG_SET_SESSIONDESCRIPTION_SUCCESS,
MSG_SET_SESSIONDESCRIPTION_SUCCESS = 0,
MSG_SET_SESSIONDESCRIPTION_FAILED,
MSG_GETSTATS,
MSG_ICECONNECTIONCHANGE,
@ -85,17 +85,6 @@ struct CandidateMsg : public talk_base::MessageData {
talk_base::scoped_ptr<const webrtc::JsepIceCandidate> candidate;
};
struct CreateSessionDescriptionMsg : public talk_base::MessageData {
explicit CreateSessionDescriptionMsg(
webrtc::CreateSessionDescriptionObserver* observer)
: observer(observer) {
}
talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
std::string error;
talk_base::scoped_ptr<webrtc::SessionDescriptionInterface> description;
};
struct SetSessionDescriptionMsg : public talk_base::MessageData {
explicit SetSessionDescriptionMsg(
webrtc::SetSessionDescriptionObserver* observer)
@ -145,7 +134,7 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
continue;
}
std::vector<std::string> tokens;
std::string turn_transport_type = kDefaultTransportType;
std::string turn_transport_type = kUdpTransportType;
talk_base::tokenize(server.uri, '?', &tokens);
std::string uri_without_transport = tokens[0];
// Let's look into transport= param, if it exists.
@ -153,6 +142,12 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
std::string uri_transport_param = tokens[1];
talk_base::tokenize(uri_transport_param, '=', &tokens);
if (tokens[0] == kTransport) {
// As per above grammar transport param will be consist of lower case
// letters.
if (tokens[1] != kUdpTransportType && tokens[1] != kTcpTransportType) {
LOG(LS_WARNING) << "Transport param should always be udp or tcp.";
continue;
}
turn_transport_type = tokens[1];
}
}
@ -176,7 +171,10 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
continue;
}
std::string address = tokens[1];
int port = kDefaultPort;
int port = kDefaultStunPort;
if (service_type == TURNS)
port = kDefaultStunTlsPort;
if (tokens.size() > kMinIceUriTokens) {
if (!talk_base::FromString(tokens[2], &port)) {
LOG(LS_WARNING) << "Failed to parse port string: " << tokens[2];
@ -194,7 +192,8 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
case STUNS:
stun_config->push_back(StunConfiguration(address, port));
break;
case TURN: {
case TURN:
case TURNS: {
if (server.username.empty()) {
// Turn url example from the spec |url:"turn:user@turn.example.org"|.
std::vector<std::string> turn_tokens;
@ -204,15 +203,23 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
address = turn_tokens[1];
}
}
bool secure = (service_type == TURNS);
turn_config->push_back(TurnConfiguration(address, port,
server.username,
server.password,
turn_transport_type));
turn_transport_type,
secure));
// STUN functionality is part of TURN.
// Note: If there is only TURNS is supplied as part of configuration,
// we will have problem in fetching server reflexive candidate, as
// currently we don't have support of TCP/TLS in stunport.cc.
// In that case we should fetch server reflexive addess from
// TURN allocate response.
stun_config->push_back(StunConfiguration(address, port));
break;
}
case TURNS:
case INVALID:
default:
LOG(WARNING) << "Configuration not supported: " << server.uri;
@ -261,7 +268,8 @@ PeerConnection::~PeerConnection() {
bool PeerConnection::Initialize(
const PeerConnectionInterface::IceServers& configuration,
const MediaConstraintsInterface* constraints,
webrtc::PortAllocatorFactoryInterface* allocator_factory,
PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer) {
std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_config;
std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_config;
@ -270,7 +278,7 @@ bool PeerConnection::Initialize(
}
return DoInitialize(stun_config, turn_config, constraints,
allocator_factory, observer);
allocator_factory, dtls_identity_service, observer);
}
bool PeerConnection::DoInitialize(
@ -278,6 +286,7 @@ bool PeerConnection::DoInitialize(
const TurnConfigurations& turn_config,
const MediaConstraintsInterface* constraints,
webrtc::PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer) {
ASSERT(observer != NULL);
if (!observer)
@ -306,10 +315,9 @@ bool PeerConnection::DoInitialize(
stats_.set_session(session_.get());
// Initialize the WebRtcSession. It creates transport channels etc.
if (!session_->Initialize(constraints))
if (!session_->Initialize(constraints, dtls_identity_service))
return false;
// Register PeerConnection as receiver of local ice candidates.
// All the callbacks will be posted to the application from PeerConnection.
session_->RegisterIceObserver(this);
@ -416,7 +424,16 @@ PeerConnection::CreateDataChannel(
if (!channel.get())
return NULL;
// If we've already passed the underlying channel's setup phase, have the
// MediaStreamSignaling update data channels manually.
if (session_->data_channel() != NULL &&
session_->data_channel_type() == cricket::DCT_SCTP) {
mediastream_signaling_->UpdateLocalSctpDataChannels();
mediastream_signaling_->UpdateRemoteSctpDataChannels();
}
observer_->OnRenegotiationNeeded();
return DataChannelProxy::Create(signaling_thread(), channel.get());
}
@ -426,17 +443,7 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
return;
}
CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
msg->description.reset(
session_->CreateOffer(constraints));
if (!msg->description) {
msg->error = "CreateOffer failed.";
signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
return;
}
signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
session_->CreateOffer(observer, constraints);
}
void PeerConnection::CreateAnswer(
@ -446,15 +453,7 @@ void PeerConnection::CreateAnswer(
LOG(LS_ERROR) << "CreateAnswer - observer is NULL.";
return;
}
CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
msg->description.reset(session_->CreateAnswer(constraints));
if (!msg->description) {
msg->error = "CreateAnswer failed.";
signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
return;
}
signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
session_->CreateAnswer(observer, constraints);
}
void PeerConnection::SetLocalDescription(
@ -468,7 +467,6 @@ void PeerConnection::SetLocalDescription(
PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
return;
}
// Update stats here so that we have the most recent stats for tracks and
// streams that might be removed by updating the session description.
stats_.UpdateStats();
@ -488,7 +486,6 @@ void PeerConnection::SetRemoteDescription(
LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
return;
}
if (!desc) {
PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
return;
@ -572,20 +569,6 @@ void PeerConnection::OnSessionStateChange(cricket::BaseSession* /*session*/,
void PeerConnection::OnMessage(talk_base::Message* msg) {
switch (msg->message_id) {
case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
CreateSessionDescriptionMsg* param =
static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
param->observer->OnSuccess(param->description.release());
delete param;
break;
}
case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
CreateSessionDescriptionMsg* param =
static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
param->observer->OnFailure(param->error);
delete param;
break;
}
case MSG_SET_SESSIONDESCRIPTION_SUCCESS: {
SetSessionDescriptionMsg* param =
static_cast<SetSessionDescriptionMsg*>(msg->pdata);

View File

@ -59,7 +59,8 @@ class PeerConnection : public PeerConnectionInterface,
bool Initialize(const PeerConnectionInterface::IceServers& configuration,
const MediaConstraintsInterface* constraints,
webrtc::PortAllocatorFactoryInterface* allocator_factory,
PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer);
virtual talk_base::scoped_refptr<StreamCollectionInterface> local_streams();
virtual talk_base::scoped_refptr<StreamCollectionInterface> remote_streams();
@ -152,7 +153,8 @@ class PeerConnection : public PeerConnectionInterface,
bool DoInitialize(const StunConfigurations& stun_config,
const TurnConfigurations& turn_config,
const MediaConstraintsInterface* constraints,
webrtc::PortAllocatorFactoryInterface* allocator_factory,
PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer);
talk_base::Thread* signaling_thread() const {

View File

@ -54,16 +54,19 @@ struct CreatePeerConnectionParams : public talk_base::MessageData {
const webrtc::PeerConnectionInterface::IceServers& configuration,
const webrtc::MediaConstraintsInterface* constraints,
webrtc::PortAllocatorFactoryInterface* allocator_factory,
webrtc::DTLSIdentityServiceInterface* dtls_identity_service,
webrtc::PeerConnectionObserver* observer)
: configuration(configuration),
constraints(constraints),
allocator_factory(allocator_factory),
dtls_identity_service(dtls_identity_service),
observer(observer) {
}
scoped_refptr<webrtc::PeerConnectionInterface> peerconnection;
const webrtc::PeerConnectionInterface::IceServers& configuration;
const webrtc::MediaConstraintsInterface* constraints;
scoped_refptr<webrtc::PortAllocatorFactoryInterface> allocator_factory;
webrtc::DTLSIdentityServiceInterface* dtls_identity_service;
webrtc::PeerConnectionObserver* observer;
};
@ -199,10 +202,12 @@ void PeerConnectionFactory::OnMessage(talk_base::Message* msg) {
}
case MSG_CREATE_PEERCONNECTION: {
CreatePeerConnectionParams* pdata =
static_cast<CreatePeerConnectionParams*>(msg->pdata);
pdata->peerconnection = CreatePeerConnection_s(pdata->configuration,
static_cast<CreatePeerConnectionParams*> (msg->pdata);
pdata->peerconnection = CreatePeerConnection_s(
pdata->configuration,
pdata->constraints,
pdata->allocator_factory,
pdata->dtls_identity_service,
pdata->observer);
break;
}
@ -278,7 +283,8 @@ PeerConnectionFactory::CreatePeerConnection(
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer) {
CreatePeerConnectionParams params(configuration, constraints,
allocator_factory, observer);
allocator_factory, dtls_identity_service,
observer);
signaling_thread_->Send(this, MSG_CREATE_PEERCONNECTION, &params);
return params.peerconnection;
}
@ -298,6 +304,7 @@ PeerConnectionFactory::CreatePeerConnection_s(
const PeerConnectionInterface::IceServers& configuration,
const MediaConstraintsInterface* constraints,
PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer) {
ASSERT(allocator_factory || allocator_factory_);
talk_base::scoped_refptr<PeerConnection> pc(
@ -306,6 +313,7 @@ PeerConnectionFactory::CreatePeerConnection_s(
configuration,
constraints,
allocator_factory ? allocator_factory : allocator_factory_.get(),
dtls_identity_service,
observer)) {
return NULL;
}

View File

@ -101,6 +101,7 @@ class PeerConnectionFactory : public PeerConnectionFactoryInterface,
const PeerConnectionInterface::IceServers& configuration,
const MediaConstraintsInterface* constraints,
PortAllocatorFactoryInterface* allocator_factory,
DTLSIdentityServiceInterface* dtls_identity_service,
PeerConnectionObserver* observer);
// Implements talk_base::MessageHandler.
void OnMessage(talk_base::Message* msg);

View File

@ -64,7 +64,8 @@ static const char kSecureTurnIceServer[] =
static const char kTurnIceServerWithNoUsernameInUri[] =
"turn:test.com:1234";
static const char kTurnPassword[] = "turnpassword";
static const int kDefaultPort = 3478;
static const int kDefaultStunPort = 3478;
static const int kDefaultStunTlsPort = 5349;
static const char kTurnUsername[] = "test";
class NullPeerConnectionObserver : public PeerConnectionObserver {
@ -174,15 +175,15 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) {
"test.com", 1234);
stun_configs.push_back(stun2);
webrtc::PortAllocatorFactoryInterface::StunConfiguration stun3(
"hello.com", kDefaultPort);
"hello.com", kDefaultStunPort);
stun_configs.push_back(stun3);
VerifyStunConfigurations(stun_configs);
TurnConfigurations turn_configs;
webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1(
"test.com", 1234, "test@hello.com", kTurnPassword, "udp");
"test.com", 1234, "test@hello.com", kTurnPassword, "udp", false);
turn_configs.push_back(turn1);
webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn2(
"hello.com", kDefaultPort, "test", kTurnPassword, "tcp");
"hello.com", kDefaultStunPort, "test", kTurnPassword, "tcp", false);
turn_configs.push_back(turn2);
VerifyTurnConfigurations(turn_configs);
}
@ -204,7 +205,7 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingNoUsernameInUri) {
EXPECT_TRUE(pc.get() != NULL);
TurnConfigurations turn_configs;
webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
"test.com", 1234, kTurnUsername, kTurnPassword, "udp");
"test.com", 1234, kTurnUsername, kTurnPassword, "udp", false);
turn_configs.push_back(turn);
VerifyTurnConfigurations(turn_configs);
}
@ -225,19 +226,16 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingTurnUrlWithTransportParam) {
EXPECT_TRUE(pc.get() != NULL);
TurnConfigurations turn_configs;
webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
"hello.com", kDefaultPort, "test", kTurnPassword, "tcp");
"hello.com", kDefaultStunPort, "test", kTurnPassword, "tcp", false);
turn_configs.push_back(turn);
VerifyTurnConfigurations(turn_configs);
StunConfigurations stun_configs;
webrtc::PortAllocatorFactoryInterface::StunConfiguration stun(
"hello.com", kDefaultPort);
"hello.com", kDefaultStunPort);
stun_configs.push_back(stun);
VerifyStunConfigurations(stun_configs);
}
// This test verifies factory failed to create a peerconneciton object when
// a valid secure TURN url passed. Connecting to a secure TURN server is not
// supported currently.
TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) {
webrtc::PeerConnectionInterface::IceServers ice_servers;
webrtc::PeerConnectionInterface::IceServer ice_server;
@ -249,8 +247,11 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) {
allocator_factory_.get(),
NULL,
&observer_));
EXPECT_TRUE(pc.get() == NULL);
EXPECT_TRUE(pc.get() != NULL);
TurnConfigurations turn_configs;
webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
"hello.com", kDefaultStunTlsPort, "test", kTurnPassword, "tcp", true);
turn_configs.push_back(turn);
VerifyTurnConfigurations(turn_configs);
}

View File

@ -315,15 +315,18 @@ class PortAllocatorFactoryInterface : public talk_base::RefCountInterface {
int port,
const std::string& username,
const std::string& password,
const std::string& transport_type)
const std::string& transport_type,
bool secure)
: server(address, port),
username(username),
password(password),
transport_type(transport_type) {}
transport_type(transport_type),
secure(secure) {}
talk_base::SocketAddress server;
std::string username;
std::string password;
std::string transport_type;
bool secure;
};
virtual cricket::PortAllocator* CreatePortAllocator(
@ -339,8 +342,8 @@ class PortAllocatorFactoryInterface : public talk_base::RefCountInterface {
class DTLSIdentityRequestObserver : public talk_base::RefCountInterface {
public:
virtual void OnFailure(int error) = 0;
virtual void OnSuccess(const std::string& certificate,
const std::string& private_key) = 0;
virtual void OnSuccess(const std::string& der_cert,
const std::string& der_private_key) = 0;
protected:
virtual ~DTLSIdentityRequestObserver() {}
};
@ -372,6 +375,8 @@ class DTLSIdentityServiceInterface {
const std::string& identity_name,
const std::string& common_name,
DTLSIdentityRequestObserver* observer) = 0;
virtual ~DTLSIdentityServiceInterface() {}
};
// PeerConnectionFactoryInterface is the factory interface use for creating

View File

@ -77,7 +77,7 @@ cricket::PortAllocator* PortAllocatorFactory::CreatePortAllocator(
cricket::ProtocolType protocol;
if (cricket::StringToProto(turn[i].transport_type.c_str(), &protocol)) {
relay_server.ports.push_back(cricket::ProtocolAddress(
turn[i].server, protocol));
turn[i].server, protocol, turn[i].secure));
relay_server.credentials = credentials;
allocator->AddRelay(relay_server);
} else {

View File

@ -0,0 +1,135 @@
/*
* libjingle
* Copyright 2013, 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.
*
*/
#ifndef TALK_APP_WEBRTC_TEST_FAKEDTLSIDENTITYSERVICE_H_
#define TALK_APP_WEBRTC_TEST_FAKEDTLSIDENTITYSERVICE_H_
#include "talk/app/webrtc/peerconnectioninterface.h"
static const char kRSA_PRIVATE_KEY_PEM[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXQIBAAKBgQDCueE4a9hDMZ3sbVZdlXOz9ZA+cvzie3zJ9gXnT/BCt9P4b9HE\n"
"vD/tr73YBqD3Wr5ZWScmyGYF9EMn0r3rzBxv6oooLU5TdUvOm4rzUjkCLQaQML8o\n"
"NxXq+qW/j3zUKGikLhaaAl/amaX2zSWUsRQ1CpngQ3+tmDNH4/25TncNmQIDAQAB\n"
"AoGAUcuU0Id0k10fMjYHZk4mCPzot2LD2Tr4Aznl5vFMQipHzv7hhZtx2xzMSRcX\n"
"vG+Qr6VkbcUWHgApyWubvZXCh3+N7Vo2aYdMAQ8XqmFpBdIrL5CVdVfqFfEMlgEy\n"
"LSZNG5klnrIfl3c7zQVovLr4eMqyl2oGfAqPQz75+fecv1UCQQD6wNHch9NbAG1q\n"
"yuFEhMARB6gDXb+5SdzFjjtTWW5uJfm4DcZLoYyaIZm0uxOwsUKd0Rsma+oGitS1\n"
"CXmuqfpPAkEAxszyN3vIdpD44SREEtyKZBMNOk5pEIIGdbeMJC5/XHvpxww9xkoC\n"
"+39NbvUZYd54uT+rafbx4QZKc0h9xA/HlwJBAL37lYVWy4XpPv1olWCKi9LbUCqs\n"
"vvQtyD1N1BkEayy9TQRsO09WKOcmigRqsTJwOx7DLaTgokEuspYvhagWVPUCQE/y\n"
"0+YkTbYBD1Xbs9SyBKXCU6uDJRWSdO6aZi2W1XloC9gUwDMiSJjD1Wwt/YsyYPJ+\n"
"/Hyc5yFL2l0KZimW/vkCQQCjuZ/lPcH46EuzhdbRfumDOG5N3ld7UhGI1TIRy17W\n"
"dGF90cG33/L6BfS8Ll+fkkW/2AMRk8FDvF4CZi2nfW4L\n"
"-----END RSA PRIVATE KEY-----\n";
static const char kCERT_PEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIBmTCCAQICCQCPNJORW/M13DANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDDAZ3\n"
"ZWJydGMwHhcNMTMwNjE0MjIzMDAxWhcNMTQwNjE0MjIzMDAxWjARMQ8wDQYDVQQD\n"
"DAZ3ZWJydGMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMK54Thr2EMxnext\n"
"Vl2Vc7P1kD5y/OJ7fMn2BedP8EK30/hv0cS8P+2vvdgGoPdavllZJybIZgX0QyfS\n"
"vevMHG/qiigtTlN1S86bivNSOQItBpAwvyg3Fer6pb+PfNQoaKQuFpoCX9qZpfbN\n"
"JZSxFDUKmeBDf62YM0fj/blOdw2ZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAECMt\n"
"UZb35H8TnjGx4XPzco/kbnurMLFFWcuve/DwTsuf10Ia9N4md8LY0UtgIgtyNqWc\n"
"ZwyRMwxONF6ty3wcaIiPbGqiAa55T3YRuPibkRmck9CjrmM9JAtyvqHnpHd2TsBD\n"
"qCV42aXS3onOXDQ1ibuWq0fr0//aj0wo4KV474c=\n"
"-----END CERTIFICATE-----\n";
using webrtc::DTLSIdentityRequestObserver;
class FakeIdentityService : public webrtc::DTLSIdentityServiceInterface,
public talk_base::MessageHandler {
public:
struct Request {
Request(const std::string& common_name,
DTLSIdentityRequestObserver* observer)
: common_name(common_name), observer(observer) {}
std::string common_name;
talk_base::scoped_refptr<DTLSIdentityRequestObserver> observer;
};
typedef talk_base::TypedMessageData<Request> MessageData;
FakeIdentityService() : should_fail_(false) {}
void set_should_fail(bool should_fail) {
should_fail_ = should_fail;
}
// DTLSIdentityServiceInterface implemenation.
virtual bool RequestIdentity(const std::string& identity_name,
const std::string& common_name,
DTLSIdentityRequestObserver* observer) {
MessageData* msg = new MessageData(Request(common_name, observer));
if (should_fail_) {
talk_base::Thread::Current()->Post(this, MSG_FAILURE, msg);
} else {
talk_base::Thread::Current()->Post(this, MSG_SUCCESS, msg);
}
return true;
}
private:
enum {
MSG_SUCCESS,
MSG_FAILURE,
};
// talk_base::MessageHandler implementation.
void OnMessage(talk_base::Message* msg) {
FakeIdentityService::MessageData* message_data =
static_cast<FakeIdentityService::MessageData*>(msg->pdata);
DTLSIdentityRequestObserver* observer = message_data->data().observer.get();
switch (msg->message_id) {
case MSG_SUCCESS: {
std::string cert, key;
GenerateIdentity(message_data->data().common_name, &cert, &key);
observer->OnSuccess(cert, key);
break;
}
case MSG_FAILURE:
observer->OnFailure(0);
break;
}
}
void GenerateIdentity(
const std::string& common_name,
std::string* der_cert,
std::string* der_key) {
talk_base::SSLIdentity::PemToDer("CERTIFICATE", kCERT_PEM, der_cert);
talk_base::SSLIdentity::PemToDer("RSA PRIVATE KEY",
kRSA_PRIVATE_KEY_PEM,
der_key);
}
bool should_fail_;
};
#endif // TALK_APP_WEBRTC_TEST_FAKEDTLSIDENTITYSERVICE_H_

View File

@ -0,0 +1,156 @@
/*
* libjingle
* Copyright 2013, 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.
*
*/
#ifndef TALK_APP_WEBRTC_TEST_FAKEMEDIASTREAMSIGNALING_H_
#define TALK_APP_WEBRTC_TEST_FAKEMEDIASTREAMSIGNALING_H_
#include "talk/app/webrtc/audiotrack.h"
#include "talk/app/webrtc/mediastreamsignaling.h"
#include "talk/app/webrtc/videotrack.h"
static const char kStream1[] = "stream1";
static const char kVideoTrack1[] = "video1";
static const char kAudioTrack1[] = "audio1";
static const char kStream2[] = "stream2";
static const char kVideoTrack2[] = "video2";
static const char kAudioTrack2[] = "audio2";
class FakeMediaStreamSignaling : public webrtc::MediaStreamSignaling,
public webrtc::MediaStreamSignalingObserver {
public:
FakeMediaStreamSignaling() :
webrtc::MediaStreamSignaling(talk_base::Thread::Current(), this) {
}
void SendAudioVideoStream1() {
ClearLocalStreams();
AddLocalStream(CreateStream(kStream1, kAudioTrack1, kVideoTrack1));
}
void SendAudioVideoStream2() {
ClearLocalStreams();
AddLocalStream(CreateStream(kStream2, kAudioTrack2, kVideoTrack2));
}
void SendAudioVideoStream1And2() {
ClearLocalStreams();
AddLocalStream(CreateStream(kStream1, kAudioTrack1, kVideoTrack1));
AddLocalStream(CreateStream(kStream2, kAudioTrack2, kVideoTrack2));
}
void SendNothing() {
ClearLocalStreams();
}
void UseOptionsAudioOnly() {
ClearLocalStreams();
AddLocalStream(CreateStream(kStream2, kAudioTrack2, ""));
}
void UseOptionsVideoOnly() {
ClearLocalStreams();
AddLocalStream(CreateStream(kStream2, "", kVideoTrack2));
}
void ClearLocalStreams() {
while (local_streams()->count() != 0) {
RemoveLocalStream(local_streams()->at(0));
}
}
// Implements MediaStreamSignalingObserver.
virtual void OnAddRemoteStream(webrtc::MediaStreamInterface* stream) {
}
virtual void OnRemoveRemoteStream(webrtc::MediaStreamInterface* stream) {
}
virtual void OnAddDataChannel(webrtc::DataChannelInterface* data_channel) {
}
virtual void OnAddLocalAudioTrack(webrtc::MediaStreamInterface* stream,
webrtc::AudioTrackInterface* audio_track,
uint32 ssrc) {
}
virtual void OnAddLocalVideoTrack(webrtc::MediaStreamInterface* stream,
webrtc::VideoTrackInterface* video_track,
uint32 ssrc) {
}
virtual void OnAddRemoteAudioTrack(webrtc::MediaStreamInterface* stream,
webrtc::AudioTrackInterface* audio_track,
uint32 ssrc) {
}
virtual void OnAddRemoteVideoTrack(webrtc::MediaStreamInterface* stream,
webrtc::VideoTrackInterface* video_track,
uint32 ssrc) {
}
virtual void OnRemoveRemoteAudioTrack(
webrtc::MediaStreamInterface* stream,
webrtc::AudioTrackInterface* audio_track) {
}
virtual void OnRemoveRemoteVideoTrack(
webrtc::MediaStreamInterface* stream,
webrtc::VideoTrackInterface* video_track) {
}
virtual void OnRemoveLocalAudioTrack(
webrtc::MediaStreamInterface* stream,
webrtc::AudioTrackInterface* audio_track) {
}
virtual void OnRemoveLocalVideoTrack(
webrtc::MediaStreamInterface* stream,
webrtc::VideoTrackInterface* video_track) {
}
virtual void OnRemoveLocalStream(webrtc::MediaStreamInterface* stream) {
}
private:
talk_base::scoped_refptr<webrtc::MediaStreamInterface> CreateStream(
const std::string& stream_label,
const std::string& audio_track_id,
const std::string& video_track_id) {
talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream(
webrtc::MediaStream::Create(stream_label));
if (!audio_track_id.empty()) {
talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
webrtc::AudioTrack::Create(audio_track_id, NULL));
stream->AddTrack(audio_track);
}
if (!video_track_id.empty()) {
talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track(
webrtc::VideoTrack::Create(video_track_id, NULL));
stream->AddTrack(video_track);
}
return stream;
}
};
#endif // TALK_APP_WEBRTC_TEST_FAKEMEDIASTREAMSIGNALING_H_

View File

@ -38,6 +38,7 @@ if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
'videotrack.cc',
'webrtcsdp.cc',
'webrtcsession.cc',
'webrtcsessiondescriptionfactory.cc',
],
lin_ccflags = peerconnection_lin_ccflags
)

View File

@ -36,6 +36,7 @@
#include "talk/app/webrtc/mediaconstraintsinterface.h"
#include "talk/app/webrtc/mediastreamsignaling.h"
#include "talk/app/webrtc/peerconnectioninterface.h"
#include "talk/app/webrtc/webrtcsessiondescriptionfactory.h"
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
@ -51,13 +52,8 @@ using cricket::MediaContentDescription;
using cricket::SessionDescription;
using cricket::TransportInfo;
typedef cricket::MediaSessionOptions::Stream Stream;
typedef cricket::MediaSessionOptions::Streams Streams;
namespace webrtc {
static const uint64 kInitSessionVersion = 2;
const char kInternalConstraintPrefix[] = "internal";
// Supported MediaConstraints.
@ -73,10 +69,6 @@ const char MediaConstraintsInterface::kEnableRtpDataChannels[] =
const char MediaConstraintsInterface::kEnableSctpDataChannels[] =
"internalSctpDataChannels";
// Arbitrary constant used as prefix for the identity.
// Chosen to make the certificates more readable.
const char kWebRTCIdentityPrefix[] = "WebRTC";
// Error messages
const char kSetLocalSdpFailed[] = "SetLocalDescription failed: ";
const char kSetRemoteSdpFailed[] = "SetRemoteDescription failed: ";
@ -114,24 +106,6 @@ static bool VerifyMediaDescriptions(
return true;
}
static void CopyCandidatesFromSessionDescription(
const SessionDescriptionInterface* source_desc,
SessionDescriptionInterface* dest_desc) {
if (!source_desc)
return;
for (size_t m = 0; m < source_desc->number_of_mediasections() &&
m < dest_desc->number_of_mediasections(); ++m) {
const IceCandidateCollection* source_candidates =
source_desc->candidates(m);
const IceCandidateCollection* dest_candidates = dest_desc->candidates(m);
for (size_t n = 0; n < source_candidates->count(); ++n) {
const IceCandidateInterface* new_candidate = source_candidates->at(n);
if (!dest_candidates->HasCandidate(new_candidate))
dest_desc->AddCandidate(source_candidates->at(n));
}
}
}
// Checks that each non-rejected content has SDES crypto keys or a DTLS
// fingerprint. Mismatches, such as replying with a DTLS fingerprint to SDES
// keys, will be caught in Transport negotiation, and backstopped by Channel's
@ -167,22 +141,29 @@ static bool VerifyCrypto(const SessionDescription* desc) {
return true;
}
static bool CompareStream(const Stream& stream1, const Stream& stream2) {
return (stream1.id < stream2.id);
}
// Forces |sdesc->crypto_required| to the appropriate state based on the
// current security policy, to ensure a failure occurs if there is an error
// in crypto negotiation.
// Called when processing the local session description.
static void UpdateSessionDescriptionSecurePolicy(
cricket::SecureMediaPolicy secure_policy,
SessionDescription* sdesc) {
if (!sdesc) {
return;
}
static bool SameId(const Stream& stream1, const Stream& stream2) {
return (stream1.id == stream2.id);
}
// Checks if each Stream within the |streams| has unique id.
static bool ValidStreams(const Streams& streams) {
Streams sorted_streams = streams;
std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
Streams::iterator it =
std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
SameId);
return (it == sorted_streams.end());
// Updating the |crypto_required_| in MediaContentDescription to the
// appropriate state based on the current security policy.
for (cricket::ContentInfos::iterator iter = sdesc->contents().begin();
iter != sdesc->contents().end(); ++iter) {
if (cricket::IsMediaContent(&*iter)) {
MediaContentDescription* mdesc =
static_cast<MediaContentDescription*> (iter->description);
if (mdesc) {
mdesc->set_crypto_required(secure_policy == cricket::SEC_REQUIRED);
}
}
}
}
static bool GetAudioSsrcByTrackId(
@ -343,15 +324,17 @@ class IceRestartAnswerLatch {
public:
IceRestartAnswerLatch() : ice_restart_(false) { }
// Returns true if CheckForRemoteIceRestart has been called since last
// time this method was called with a new session description where
// ice password and ufrag has changed.
bool AnswerWithIceRestartLatch() {
// Returns true if CheckForRemoteIceRestart has been called with a new session
// description where ice password and ufrag has changed since last time
// Reset() was called.
bool Get() const {
return ice_restart_;
}
void Reset() {
if (ice_restart_) {
ice_restart_ = false;
return true;
}
return false;
}
void CheckForRemoteIceRestart(
@ -391,7 +374,8 @@ class IceRestartAnswerLatch {
bool ice_restart_;
};
WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
WebRtcSession::WebRtcSession(
cricket::ChannelManager* channel_manager,
talk_base::Thread* signaling_thread,
talk_base::Thread* worker_thread,
cricket::PortAllocator* port_allocator,
@ -404,19 +388,12 @@ WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
// o line MUST be representable with a "64 bit signed integer".
// Due to this constraint session id |sid_| is max limited to LLONG_MAX.
channel_manager_(channel_manager),
session_desc_factory_(channel_manager, &transport_desc_factory_),
mediastream_signaling_(mediastream_signaling),
ice_observer_(NULL),
ice_connection_state_(PeerConnectionInterface::kIceConnectionNew),
// RFC 4566 suggested a Network Time Protocol (NTP) format timestamp
// as the session id and session version. To simplify, it should be fine
// to just use a random number as session id and start version from
// |kInitSessionVersion|.
session_version_(kInitSessionVersion),
older_version_remote_peer_(false),
data_channel_type_(cricket::DCT_NONE),
ice_restart_latch_(new IceRestartAnswerLatch) {
transport_desc_factory_.set_protocol(cricket::ICEPROTO_HYBRID);
}
WebRtcSession::~WebRtcSession() {
@ -436,34 +413,15 @@ WebRtcSession::~WebRtcSession() {
delete saved_candidates_[i];
}
delete identity();
set_identity(NULL);
transport_desc_factory_.set_identity(NULL);
}
bool WebRtcSession::Initialize(const MediaConstraintsInterface* constraints) {
bool WebRtcSession::Initialize(
const MediaConstraintsInterface* constraints,
DTLSIdentityServiceInterface* dtls_identity_service) {
// TODO(perkj): Take |constraints| into consideration. Return false if not all
// mandatory constraints can be fulfilled. Note that |constraints|
// can be null.
// By default SRTP-SDES is enabled in WebRtc.
set_secure_policy(cricket::SEC_REQUIRED);
// Enable DTLS-SRTP if the constraint is set.
bool value;
if (FindConstraint(constraints, MediaConstraintsInterface::kEnableDtlsSrtp,
&value, NULL) && value) {
LOG(LS_INFO) << "DTLS-SRTP enabled; generating identity";
std::string identity_name = kWebRTCIdentityPrefix +
talk_base::ToString(talk_base::CreateRandomId());
transport_desc_factory_.set_identity(talk_base::SSLIdentity::Generate(
identity_name));
LOG(LS_INFO) << "Finished generating identity";
set_identity(transport_desc_factory_.identity());
transport_desc_factory_.set_digest_algorithm(talk_base::DIGEST_SHA_256);
transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
}
// Enable creation of RTP data channels if the kEnableRtpDataChannels is set.
// It takes precendence over the kEnableSctpDataChannels constraint.
if (FindConstraint(
@ -471,23 +429,26 @@ bool WebRtcSession::Initialize(const MediaConstraintsInterface* constraints) {
&value, NULL) && value) {
LOG(LS_INFO) << "Allowing RTP data engine.";
data_channel_type_ = cricket::DCT_RTP;
} else if (
FindConstraint(
} else {
bool sctp_enabled = FindConstraint(
constraints,
MediaConstraintsInterface::kEnableSctpDataChannels,
&value, NULL) && value &&
&value, NULL) && value;
bool dtls_enabled = FindConstraint(
constraints,
MediaConstraintsInterface::kEnableDtlsSrtp,
&value, NULL) && value;
// DTLS has to be enabled to use SCTP.
(transport_desc_factory_.secure() == cricket::SEC_ENABLED)) {
if (sctp_enabled && dtls_enabled) {
LOG(LS_INFO) << "Allowing SCTP data engine.";
data_channel_type_ = cricket::DCT_SCTP;
}
}
if (data_channel_type_ != cricket::DCT_NONE) {
mediastream_signaling_->SetDataChannelFactory(this);
}
// Make sure SessionDescriptions only contains the StreamParams we negotiate.
session_desc_factory_.set_add_legacy_streams(false);
const cricket::VideoCodec default_codec(
JsepSessionDescription::kDefaultVideoCodecId,
JsepSessionDescription::kDefaultVideoCodecName,
@ -497,6 +458,19 @@ bool WebRtcSession::Initialize(const MediaConstraintsInterface* constraints) {
JsepSessionDescription::kDefaultVideoCodecPreference);
channel_manager_->SetDefaultVideoEncoderConfig(
cricket::VideoEncoderConfig(default_codec));
webrtc_session_desc_factory_.reset(new WebRtcSessionDescriptionFactory(
signaling_thread(),
channel_manager_,
mediastream_signaling_,
dtls_identity_service,
this,
id(),
data_channel_type_,
constraints));
webrtc_session_desc_factory_->SignalIdentityReady.connect(
this, &WebRtcSession::OnIdentityReady);
return true;
}
@ -524,113 +498,27 @@ bool WebRtcSession::StartCandidatesAllocation() {
void WebRtcSession::set_secure_policy(
cricket::SecureMediaPolicy secure_policy) {
session_desc_factory_.set_secure(secure_policy);
webrtc_session_desc_factory_->set_secure(secure_policy);
}
SessionDescriptionInterface* WebRtcSession::CreateOffer(
const MediaConstraintsInterface* constraints) {
cricket::MediaSessionOptions options;
if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
LOG(LS_ERROR) << "CreateOffer called with invalid constraints.";
return NULL;
}
if (!ValidStreams(options.streams)) {
LOG(LS_ERROR) << "CreateOffer called with invalid media streams.";
return NULL;
}
if (data_channel_type_ == cricket::DCT_SCTP) {
options.data_channel_type = cricket::DCT_SCTP;
}
SessionDescription* desc(
session_desc_factory_.CreateOffer(options,
BaseSession::local_description()));
// RFC 3264
// When issuing an offer that modifies the session,
// the "o=" line of the new SDP MUST be identical to that in the
// previous SDP, except that the version in the origin field MUST
// increment by one from the previous SDP.
// Just increase the version number by one each time when a new offer
// is created regardless if it's identical to the previous one or not.
// The |session_version_| is a uint64, the wrap around should not happen.
ASSERT(session_version_ + 1 > session_version_);
JsepSessionDescription* offer(new JsepSessionDescription(
JsepSessionDescription::kOffer));
if (!offer->Initialize(desc, id(),
talk_base::ToString(session_version_++))) {
delete offer;
return NULL;
}
if (local_description() && !options.transport_options.ice_restart) {
// Include all local ice candidates in the SessionDescription unless
// the an ice restart has been requested.
CopyCandidatesFromSessionDescription(local_description(), offer);
}
return offer;
cricket::SecureMediaPolicy WebRtcSession::secure_policy() const {
return webrtc_session_desc_factory_->secure();
}
SessionDescriptionInterface* WebRtcSession::CreateAnswer(
void WebRtcSession::CreateOffer(CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) {
if (!remote_description()) {
LOG(LS_ERROR) << "CreateAnswer can't be called before"
<< " SetRemoteDescription.";
return NULL;
}
if (remote_description()->type() != JsepSessionDescription::kOffer) {
LOG(LS_ERROR) << "CreateAnswer failed because remote_description is not an"
<< " offer.";
return NULL;
}
webrtc_session_desc_factory_->CreateOffer(observer, constraints);
}
cricket::MediaSessionOptions options;
if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
LOG(LS_ERROR) << "CreateAnswer called with invalid constraints.";
return NULL;
}
if (!ValidStreams(options.streams)) {
LOG(LS_ERROR) << "CreateAnswer called with invalid media streams.";
return NULL;
}
if (data_channel_type_ == cricket::DCT_SCTP) {
options.data_channel_type = cricket::DCT_SCTP;
}
// According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
// an answer should also contain new ice ufrag and password if an offer has
// been received with new ufrag and password.
options.transport_options.ice_restart =
ice_restart_latch_->AnswerWithIceRestartLatch();
SessionDescription* desc(
session_desc_factory_.CreateAnswer(BaseSession::remote_description(),
options,
BaseSession::local_description()));
// RFC 3264
// If the answer is different from the offer in any way (different IP
// addresses, ports, etc.), the origin line MUST be different in the answer.
// In that case, the version number in the "o=" line of the answer is
// unrelated to the version number in the o line of the offer.
// Get a new version number by increasing the |session_version_answer_|.
// The |session_version_| is a uint64, the wrap around should not happen.
ASSERT(session_version_ + 1 > session_version_);
JsepSessionDescription* answer(new JsepSessionDescription(
JsepSessionDescription::kAnswer));
if (!answer->Initialize(desc, id(),
talk_base::ToString(session_version_++))) {
delete answer;
return NULL;
}
if (local_description() && !options.transport_options.ice_restart) {
// Include all local ice candidates in the SessionDescription unless
// the remote peer has requested an ice restart.
CopyCandidatesFromSessionDescription(local_description(), answer);
}
return answer;
void WebRtcSession::CreateAnswer(CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) {
webrtc_session_desc_factory_->CreateAnswer(observer, constraints);
}
bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
std::string* err_desc) {
cricket::SecureMediaPolicy secure_policy =
webrtc_session_desc_factory_->secure();
// Takes the ownership of |desc| regardless of the result.
talk_base::scoped_ptr<SessionDescriptionInterface> desc_temp(desc);
@ -651,12 +539,10 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
std::string type = desc->type();
return BadLocalSdp(BadStateErrMsg(type, state()), err_desc);
}
if (session_desc_factory_.secure() == cricket::SEC_REQUIRED &&
if (secure_policy == cricket::SEC_REQUIRED &&
!VerifyCrypto(desc->description())) {
return BadLocalSdp(kSdpWithoutCrypto, err_desc);
}
if (action == kAnswer && !VerifyMediaDescriptions(
desc->description(), remote_description()->description())) {
return BadLocalSdp(kMlineMismatch, err_desc);
@ -668,7 +554,7 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
}
// Update the MediaContentDescription crypto settings as per the policy set.
UpdateSessionDescriptionSecurePolicy(desc->description());
UpdateSessionDescriptionSecurePolicy(secure_policy, desc->description());
set_local_description(desc->description()->Copy());
local_desc_.reset(desc_temp.release());
@ -703,6 +589,8 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
std::string* err_desc) {
cricket::SecureMediaPolicy secure_policy =
webrtc_session_desc_factory_->secure();
// Takes the ownership of |desc| regardless of the result.
talk_base::scoped_ptr<SessionDescriptionInterface> desc_temp(desc);
@ -729,7 +617,7 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
return BadRemoteSdp(kMlineMismatch, err_desc);
}
if (session_desc_factory_.secure() == cricket::SEC_REQUIRED &&
if (secure_policy == cricket::SEC_REQUIRED &&
!VerifyCrypto(desc->description())) {
return BadRemoteSdp(kSdpWithoutCrypto, err_desc);
}
@ -762,7 +650,8 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
// Copy all saved candidates.
CopySavedCandidates(desc);
// We retain all received candidates.
CopyCandidatesFromSessionDescription(remote_desc_.get(), desc);
WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
remote_desc_.get(), desc);
// Check if this new SessionDescription contains new ice ufrag and password
// that indicates the remote peer requests ice restart.
ice_restart_latch_->CheckForRemoteIceRestart(remote_desc_.get(),
@ -1057,12 +946,35 @@ talk_base::scoped_refptr<DataChannel> WebRtcSession::CreateDataChannel(
return NULL;
}
}
talk_base::scoped_refptr<DataChannel> channel(
DataChannel::Create(this, label, &new_config));
if (channel == NULL)
return NULL;
if (!mediastream_signaling_->AddDataChannel(channel))
return NULL;
if (data_channel_type_ == cricket::DCT_SCTP) {
if (config == NULL) {
LOG(LS_WARNING) << "Could not send data channel OPEN message"
<< " because of NULL config.";
return NULL;
}
if (data_channel_.get()) {
channel->SetReceiveSsrc(new_config.id);
channel->SetSendSsrc(new_config.id);
channel->ConnectToDataSession();
}
if (!config->negotiated) {
talk_base::Buffer *payload = new talk_base::Buffer;
if (!mediastream_signaling_->WriteDataChannelOpenMessage(
label, *config, payload)) {
LOG(LS_WARNING) << "Could not write data channel OPEN message";
}
// SendControl may queue the message until the data channel's set up,
// or congestion clears.
channel->SendControl(payload);
}
}
return channel;
}
@ -1070,6 +982,22 @@ cricket::DataChannelType WebRtcSession::data_channel_type() const {
return data_channel_type_;
}
bool WebRtcSession::IceRestartPending() const {
return ice_restart_latch_->Get();
}
void WebRtcSession::ResetIceRestartLatch() {
ice_restart_latch_->Reset();
}
void WebRtcSession::OnIdentityReady(talk_base::SSLIdentity* identity) {
SetIdentity(identity);
}
bool WebRtcSession::waiting_for_identity() const {
return webrtc_session_desc_factory_->waiting_for_identity();
}
void WebRtcSession::SetIceConnectionState(
PeerConnectionInterface::IceConnectionState state) {
if (ice_connection_state_ == state) {
@ -1402,7 +1330,12 @@ bool WebRtcSession::CreateDataChannel(const cricket::ContentInfo* content) {
bool rtcp = (data_channel_type_ == cricket::DCT_RTP);
data_channel_.reset(channel_manager_->CreateDataChannel(
this, content->name, rtcp, data_channel_type_));
return (data_channel_ != NULL);
if (!data_channel_.get()) {
return false;
}
data_channel_->SignalDataReceived.connect(
this, &WebRtcSession::OnDataReceived);
return true;
}
void WebRtcSession::CopySavedCandidates(
@ -1418,24 +1351,29 @@ void WebRtcSession::CopySavedCandidates(
saved_candidates_.clear();
}
void WebRtcSession::UpdateSessionDescriptionSecurePolicy(
SessionDescription* sdesc) {
if (!sdesc) {
// Look for OPEN messages and set up data channels in response.
void WebRtcSession::OnDataReceived(
cricket::DataChannel* channel,
const cricket::ReceiveDataParams& params,
const talk_base::Buffer& payload) {
if (params.type != cricket::DMT_CONTROL) {
return;
}
// Updating the |crypto_required_| in MediaContentDescription to the
// appropriate state based on the current security policy.
for (cricket::ContentInfos::iterator iter = sdesc->contents().begin();
iter != sdesc->contents().end(); ++iter) {
if (cricket::IsMediaContent(&*iter)) {
MediaContentDescription* mdesc =
static_cast<MediaContentDescription*>(iter->description);
if (mdesc) {
mdesc->set_crypto_required(
session_desc_factory_.secure() == cricket::SEC_REQUIRED);
}
std::string label;
DataChannelInit config;
if (!mediastream_signaling_->ParseDataChannelOpenMessage(
payload, &label, &config)) {
LOG(LS_WARNING) << "Failed to parse data channel OPEN message.";
return;
}
config.negotiated = true; // This is the negotiation.
if (!mediastream_signaling_->AddDataChannelFromOpenMessage(
label, config)) {
LOG(LS_WARNING) << "Failed to create data channel from OPEN message.";
return;
}
}

View File

@ -39,7 +39,6 @@
#include "talk/base/thread.h"
#include "talk/media/base/mediachannel.h"
#include "talk/p2p/base/session.h"
#include "talk/p2p/base/transportdescriptionfactory.h"
#include "talk/session/media/mediasession.h"
namespace cricket {
@ -59,6 +58,7 @@ namespace webrtc {
class IceRestartAnswerLatch;
class MediaStreamSignaling;
class WebRtcSessionDescriptionFactory;
extern const char kSetLocalSdpFailed[];
extern const char kSetRemoteSdpFailed[];
@ -107,7 +107,8 @@ class WebRtcSession : public cricket::BaseSession,
MediaStreamSignaling* mediastream_signaling);
virtual ~WebRtcSession();
bool Initialize(const MediaConstraintsInterface* constraints);
bool Initialize(const MediaConstraintsInterface* constraints,
DTLSIdentityServiceInterface* dtls_identity_service);
// Deletes the voice, video and data channel and changes the session state
// to STATE_RECEIVEDTERMINATE.
void Terminate();
@ -127,20 +128,16 @@ class WebRtcSession : public cricket::BaseSession,
}
void set_secure_policy(cricket::SecureMediaPolicy secure_policy);
cricket::SecureMediaPolicy secure_policy() const {
return session_desc_factory_.secure();
}
cricket::SecureMediaPolicy secure_policy() const;
// Generic error message callback from WebRtcSession.
// TODO - It may be necessary to supply error code as well.
sigslot::signal0<> SignalError;
SessionDescriptionInterface* CreateOffer(
void CreateOffer(CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints);
SessionDescriptionInterface* CreateAnswer(
void CreateAnswer(CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints);
// The ownership of |desc| will be transferred after this call.
bool SetLocalDescription(SessionDescriptionInterface* desc,
std::string* err_desc);
@ -155,6 +152,9 @@ class WebRtcSession : public cricket::BaseSession,
return remote_desc_.get();
}
void set_secure(cricket::SecureMediaPolicy secure_policy);
cricket::SecureMediaPolicy secure();
// Get the id used as a media stream track's "id" field from ssrc.
virtual bool GetTrackIdBySsrc(uint32 ssrc, std::string* id);
@ -186,6 +186,17 @@ class WebRtcSession : public cricket::BaseSession,
cricket::DataChannelType data_channel_type() const;
bool IceRestartPending() const;
void ResetIceRestartLatch();
// Called when an SSLIdentity is generated or retrieved by
// WebRTCSessionDescriptionFactory. Should happen before setLocalDescription.
void OnIdentityReady(talk_base::SSLIdentity* identity);
// For unit test.
bool waiting_for_identity() const;
private:
// Indicates the type of SessionDescription in a call to SetLocalDescription
// and SetRemoteDescription.
@ -194,6 +205,7 @@ class WebRtcSession : public cricket::BaseSession,
kPrAnswer,
kAnswer,
};
// Invokes ConnectChannels() on transport proxies, which initiates ice
// candidates allocation.
bool StartCandidatesAllocation();
@ -252,11 +264,10 @@ class WebRtcSession : public cricket::BaseSession,
// The |saved_candidates_| will be cleared after this function call.
void CopySavedCandidates(SessionDescriptionInterface* dest_desc);
// Forces |desc->crypto_required| to the appropriate state based on the
// current security policy, to ensure a failure occurs if there is an error
// in crypto negotiation.
// Called when processing the local session description.
void UpdateSessionDescriptionSecurePolicy(cricket::SessionDescription* desc);
void OnDataReceived(
cricket::DataChannel* channel,
const cricket::ReceiveDataParams& params,
const talk_base::Buffer& payload);
bool GetLocalTrackId(uint32 ssrc, std::string* track_id);
bool GetRemoteTrackId(uint32 ssrc, std::string* track_id);
@ -271,8 +282,6 @@ class WebRtcSession : public cricket::BaseSession,
talk_base::scoped_ptr<cricket::VideoChannel> video_channel_;
talk_base::scoped_ptr<cricket::DataChannel> data_channel_;
cricket::ChannelManager* channel_manager_;
cricket::TransportDescriptionFactory transport_desc_factory_;
cricket::MediaSessionDescriptionFactory session_desc_factory_;
MediaStreamSignaling* mediastream_signaling_;
IceObserver* ice_observer_;
PeerConnectionInterface::IceConnectionState ice_connection_state_;
@ -280,7 +289,6 @@ class WebRtcSession : public cricket::BaseSession,
talk_base::scoped_ptr<SessionDescriptionInterface> remote_desc_;
// Candidates that arrived before the remote description was set.
std::vector<IceCandidateInterface*> saved_candidates_;
uint64 session_version_;
// If the remote peer is using a older version of implementation.
bool older_version_remote_peer_;
// Specifies which kind of data channel is allowed. This is controlled
@ -292,6 +300,10 @@ class WebRtcSession : public cricket::BaseSession,
// 3. If both 1&2 are false, data channel is not allowed (DCT_NONE).
cricket::DataChannelType data_channel_type_;
talk_base::scoped_ptr<IceRestartAnswerLatch> ice_restart_latch_;
talk_base::scoped_ptr<WebRtcSessionDescriptionFactory>
webrtc_session_desc_factory_;
sigslot::signal0<> SignalVoiceChannelDestroyed;
sigslot::signal0<> SignalVideoChannelDestroyed;
sigslot::signal0<> SignalDataChannelDestroyed;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,451 @@
/*
* libjingle
* Copyright 2013, 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 "talk/app/webrtc/webrtcsessiondescriptionfactory.h"
#include "talk/app/webrtc/jsep.h"
#include "talk/app/webrtc/jsepsessiondescription.h"
#include "talk/app/webrtc/mediaconstraintsinterface.h"
#include "talk/app/webrtc/mediastreamsignaling.h"
#include "talk/app/webrtc/webrtcsession.h"
namespace webrtc {
namespace {
static const char kFailedDueToIdentityFailed[] =
" failed because DTLS identity request failed";
// Arbitrary constant used as common name for the identity.
// Chosen to make the certificates more readable.
static const char kWebRTCIdentityName[] = "WebRTC";
static const uint64 kInitSessionVersion = 2;
typedef cricket::MediaSessionOptions::Stream Stream;
typedef cricket::MediaSessionOptions::Streams Streams;
static bool CompareStream(const Stream& stream1, const Stream& stream2) {
return (stream1.id < stream2.id);
}
static bool SameId(const Stream& stream1, const Stream& stream2) {
return (stream1.id == stream2.id);
}
// Checks if each Stream within the |streams| has unique id.
static bool ValidStreams(const Streams& streams) {
Streams sorted_streams = streams;
std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
Streams::iterator it =
std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
SameId);
return (it == sorted_streams.end());
}
enum {
MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,
MSG_CREATE_SESSIONDESCRIPTION_FAILED,
MSG_GENERATE_IDENTITY,
};
struct CreateSessionDescriptionMsg : public talk_base::MessageData {
explicit CreateSessionDescriptionMsg(
webrtc::CreateSessionDescriptionObserver* observer)
: observer(observer) {
}
talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
std::string error;
talk_base::scoped_ptr<webrtc::SessionDescriptionInterface> description;
};
} // namespace
// static
void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
const SessionDescriptionInterface* source_desc,
SessionDescriptionInterface* dest_desc) {
if (!source_desc)
return;
for (size_t m = 0; m < source_desc->number_of_mediasections() &&
m < dest_desc->number_of_mediasections(); ++m) {
const IceCandidateCollection* source_candidates =
source_desc->candidates(m);
const IceCandidateCollection* dest_candidates = dest_desc->candidates(m);
for (size_t n = 0; n < source_candidates->count(); ++n) {
const IceCandidateInterface* new_candidate = source_candidates->at(n);
if (!dest_candidates->HasCandidate(new_candidate))
dest_desc->AddCandidate(source_candidates->at(n));
}
}
}
WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory(
talk_base::Thread* signaling_thread,
cricket::ChannelManager* channel_manager,
MediaStreamSignaling* mediastream_signaling,
DTLSIdentityServiceInterface* dtls_identity_service,
WebRtcSession* session,
const std::string& session_id,
cricket::DataChannelType dct,
const MediaConstraintsInterface* constraints)
: signaling_thread_(signaling_thread),
mediastream_signaling_(mediastream_signaling),
session_desc_factory_(channel_manager, &transport_desc_factory_),
// RFC 4566 suggested a Network Time Protocol (NTP) format timestamp
// as the session id and session version. To simplify, it should be fine
// to just use a random number as session id and start version from
// |kInitSessionVersion|.
session_version_(kInitSessionVersion),
identity_service_(dtls_identity_service),
session_(session),
session_id_(session_id),
data_channel_type_(dct),
identity_request_state_(IDENTITY_NOT_NEEDED) {
transport_desc_factory_.set_protocol(cricket::ICEPROTO_HYBRID);
session_desc_factory_.set_add_legacy_streams(false);
// By default SRTP-SDES is enabled in WebRtc.
set_secure(cricket::SEC_REQUIRED);
// Enable DTLS-SRTP if the constraint is set.
bool dtls_enabled = false;
if (!FindConstraint(
constraints, MediaConstraintsInterface::kEnableDtlsSrtp,
&dtls_enabled, NULL) ||
!dtls_enabled) {
return;
}
// DTLS is enabled.
if (identity_service_.get()) {
identity_request_observer_ =
new talk_base::RefCountedObject<WebRtcIdentityRequestObserver>();
identity_request_observer_->SignalRequestFailed.connect(
this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
identity_request_observer_->SignalIdentityReady.connect(
this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
if (identity_service_->RequestIdentity(kWebRTCIdentityName,
kWebRTCIdentityName,
identity_request_observer_)) {
LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
identity_request_state_ = IDENTITY_WAITING;
} else {
LOG(LS_ERROR) << "Failed to send DTLS identity request.";
identity_request_state_ = IDENTITY_FAILED;
}
} else {
identity_request_state_ = IDENTITY_WAITING;
// Do not generate the identity in the constructor since the caller has
// not got a chance to connect to SignalIdentityReady.
signaling_thread_->Post(this, MSG_GENERATE_IDENTITY, NULL);
}
}
WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
transport_desc_factory_.set_identity(NULL);
}
void WebRtcSessionDescriptionFactory::CreateOffer(
CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) {
cricket::MediaSessionOptions options;
std::string error = "CreateOffer";
if (identity_request_state_ == IDENTITY_FAILED) {
error += kFailedDueToIdentityFailed;
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
error += " called with invalid constraints.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (!ValidStreams(options.streams)) {
error += " called with invalid media streams.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (data_channel_type_ == cricket::DCT_SCTP) {
options.data_channel_type = cricket::DCT_SCTP;
}
CreateSessionDescriptionRequest request(
CreateSessionDescriptionRequest::kOffer, observer, options);
if (identity_request_state_ == IDENTITY_WAITING) {
create_session_description_requests_.push(request);
} else {
ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
identity_request_state_ == IDENTITY_NOT_NEEDED);
InternalCreateOffer(request);
}
}
void WebRtcSessionDescriptionFactory::CreateAnswer(
CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) {
std::string error = "CreateAnswer";
if (identity_request_state_ == IDENTITY_FAILED) {
error += kFailedDueToIdentityFailed;
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (!session_->remote_description()) {
error += " can't be called before SetRemoteDescription.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (session_->remote_description()->type() !=
JsepSessionDescription::kOffer) {
error += " failed because remote_description is not an offer.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
cricket::MediaSessionOptions options;
if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
error += " called with invalid constraints.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (!ValidStreams(options.streams)) {
error += " called with invalid media streams.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
}
if (data_channel_type_ == cricket::DCT_SCTP) {
options.data_channel_type = cricket::DCT_SCTP;
}
CreateSessionDescriptionRequest request(
CreateSessionDescriptionRequest::kAnswer, observer, options);
if (identity_request_state_ == IDENTITY_WAITING) {
create_session_description_requests_.push(request);
} else {
ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
identity_request_state_ == IDENTITY_NOT_NEEDED);
InternalCreateAnswer(request);
}
}
void WebRtcSessionDescriptionFactory::set_secure(
cricket::SecureMediaPolicy secure_policy) {
session_desc_factory_.set_secure(secure_policy);
}
cricket::SecureMediaPolicy WebRtcSessionDescriptionFactory::secure() const {
return session_desc_factory_.secure();
}
bool WebRtcSessionDescriptionFactory::waiting_for_identity() const {
return identity_request_state_ == IDENTITY_WAITING;
}
void WebRtcSessionDescriptionFactory::OnMessage(talk_base::Message* msg) {
switch (msg->message_id) {
case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
CreateSessionDescriptionMsg* param =
static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
param->observer->OnSuccess(param->description.release());
delete param;
break;
}
case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
CreateSessionDescriptionMsg* param =
static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
param->observer->OnFailure(param->error);
delete param;
break;
}
case MSG_GENERATE_IDENTITY: {
LOG(LS_INFO) << "Generating identity.";
SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
break;
}
default:
ASSERT(false);
break;
}
}
void WebRtcSessionDescriptionFactory::InternalCreateOffer(
CreateSessionDescriptionRequest request) {
cricket::SessionDescription* desc(
session_desc_factory_.CreateOffer(
request.options,
static_cast<cricket::BaseSession*>(session_)->local_description()));
// RFC 3264
// When issuing an offer that modifies the session,
// the "o=" line of the new SDP MUST be identical to that in the
// previous SDP, except that the version in the origin field MUST
// increment by one from the previous SDP.
// Just increase the version number by one each time when a new offer
// is created regardless if it's identical to the previous one or not.
// The |session_version_| is a uint64, the wrap around should not happen.
ASSERT(session_version_ + 1 > session_version_);
JsepSessionDescription* offer(new JsepSessionDescription(
JsepSessionDescription::kOffer));
if (!offer->Initialize(desc, session_id_,
talk_base::ToString(session_version_++))) {
delete offer;
PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
return;
}
if (session_->local_description() &&
!request.options.transport_options.ice_restart) {
// Include all local ice candidates in the SessionDescription unless
// the an ice restart has been requested.
CopyCandidatesFromSessionDescription(session_->local_description(), offer);
}
PostCreateSessionDescriptionSucceeded(request.observer, offer);
}
void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
CreateSessionDescriptionRequest request) {
// According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
// an answer should also contain new ice ufrag and password if an offer has
// been received with new ufrag and password.
request.options.transport_options.ice_restart = session_->IceRestartPending();
cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
static_cast<cricket::BaseSession*>(session_)->remote_description(),
request.options,
static_cast<cricket::BaseSession*>(session_)->local_description()));
// RFC 3264
// If the answer is different from the offer in any way (different IP
// addresses, ports, etc.), the origin line MUST be different in the answer.
// In that case, the version number in the "o=" line of the answer is
// unrelated to the version number in the o line of the offer.
// Get a new version number by increasing the |session_version_answer_|.
// The |session_version_| is a uint64, the wrap around should not happen.
ASSERT(session_version_ + 1 > session_version_);
JsepSessionDescription* answer(new JsepSessionDescription(
JsepSessionDescription::kAnswer));
if (!answer->Initialize(desc, session_id_,
talk_base::ToString(session_version_++))) {
delete answer;
PostCreateSessionDescriptionFailed(request.observer,
"CreateAnswer failed.");
return;
}
if (session_->local_description() &&
!request.options.transport_options.ice_restart) {
// Include all local ice candidates in the SessionDescription unless
// the remote peer has requested an ice restart.
CopyCandidatesFromSessionDescription(session_->local_description(), answer);
}
session_->ResetIceRestartLatch();
PostCreateSessionDescriptionSucceeded(request.observer, answer);
}
void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
CreateSessionDescriptionObserver* observer, const std::string& error) {
CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
msg->error = error;
signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
}
void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded(
CreateSessionDescriptionObserver* observer,
SessionDescriptionInterface* description) {
CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
msg->description.reset(description);
signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
}
void WebRtcSessionDescriptionFactory::OnIdentityRequestFailed(int error) {
ASSERT(signaling_thread_->IsCurrent());
LOG(LS_ERROR) << "Async identity request failed: error = " << error;
identity_request_state_ = IDENTITY_FAILED;
std::string msg = kFailedDueToIdentityFailed;
while (!create_session_description_requests_.empty()) {
const CreateSessionDescriptionRequest& request =
create_session_description_requests_.front();
PostCreateSessionDescriptionFailed(
request.observer,
((request.type == CreateSessionDescriptionRequest::kOffer) ?
"CreateOffer" : "CreateAnswer") + msg);
create_session_description_requests_.pop();
}
}
void WebRtcSessionDescriptionFactory::OnIdentityReady(
const std::string& der_cert,
const std::string& der_private_key) {
ASSERT(signaling_thread_->IsCurrent());
LOG(LS_VERBOSE) << "Identity is successfully generated.";
std::string pem_cert = talk_base::SSLIdentity::DerToPem(
talk_base::kPemTypeCertificate,
reinterpret_cast<const unsigned char*>(der_cert.data()),
der_cert.length());
std::string pem_key = talk_base::SSLIdentity::DerToPem(
talk_base::kPemTypeRsaPrivateKey,
reinterpret_cast<const unsigned char*>(der_private_key.data()),
der_private_key.length());
talk_base::SSLIdentity* identity =
talk_base::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
SetIdentity(identity);
}
void WebRtcSessionDescriptionFactory::SetIdentity(
talk_base::SSLIdentity* identity) {
identity_request_state_ = IDENTITY_SUCCEEDED;
SignalIdentityReady(identity);
transport_desc_factory_.set_identity(identity);
transport_desc_factory_.set_digest_algorithm(talk_base::DIGEST_SHA_256);
transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
while (!create_session_description_requests_.empty()) {
if (create_session_description_requests_.front().type ==
CreateSessionDescriptionRequest::kOffer) {
InternalCreateOffer(create_session_description_requests_.front());
} else {
InternalCreateAnswer(create_session_description_requests_.front());
}
create_session_description_requests_.pop();
}
}
} // namespace webrtc

View File

@ -0,0 +1,172 @@
/*
* libjingle
* Copyright 2013, 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.
*/
#ifndef TALK_APP_WEBRTC_WEBRTCSESSIONDESCRIPTIONFACTORY_H_
#define TALK_APP_WEBRTC_WEBRTCSESSIONDESCRIPTIONFACTORY_H_
#include "talk/app/webrtc/peerconnectioninterface.h"
#include "talk/base/messagehandler.h"
#include "talk/p2p/base/transportdescriptionfactory.h"
#include "talk/session/media/mediasession.h"
namespace cricket {
class ChannelManager;
class TransportDescriptionFactory;
} // namespace cricket
namespace webrtc {
class CreateSessionDescriptionObserver;
class MediaConstraintsInterface;
class MediaStreamSignaling;
class SessionDescriptionInterface;
class WebRtcSession;
// DTLS identity request callback class.
class WebRtcIdentityRequestObserver : public DTLSIdentityRequestObserver,
public sigslot::has_slots<> {
public:
// DTLSIdentityRequestObserver overrides.
virtual void OnFailure(int error) {
SignalRequestFailed(error);
}
virtual void OnSuccess(const std::string& der_cert,
const std::string& der_private_key) {
SignalIdentityReady(der_cert, der_private_key);
}
sigslot::signal1<int> SignalRequestFailed;
sigslot::signal2<const std::string&, const std::string&> SignalIdentityReady;
};
struct CreateSessionDescriptionRequest {
enum Type {
kOffer,
kAnswer,
};
CreateSessionDescriptionRequest(
Type type,
CreateSessionDescriptionObserver* observer,
const cricket::MediaSessionOptions& options)
: type(type),
observer(observer),
options(options) {}
Type type;
talk_base::scoped_refptr<CreateSessionDescriptionObserver> observer;
cricket::MediaSessionOptions options;
};
// This class is used to create offer/answer session description with regards to
// the async DTLS identity generation for WebRtcSession.
// It queues the create offer/answer request until the DTLS identity
// request has completed, i.e. when OnIdentityRequestFailed or OnIdentityReady
// is called.
class WebRtcSessionDescriptionFactory : public talk_base::MessageHandler,
public sigslot::has_slots<> {
public:
WebRtcSessionDescriptionFactory(
talk_base::Thread* signaling_thread,
cricket::ChannelManager* channel_manager,
MediaStreamSignaling* mediastream_signaling,
DTLSIdentityServiceInterface* dtls_identity_service,
// TODO(jiayl): remove the dependency on session once b/10226852 is fixed.
WebRtcSession* session,
const std::string& session_id,
cricket::DataChannelType dct,
const MediaConstraintsInterface* constraints);
virtual ~WebRtcSessionDescriptionFactory();
static void CopyCandidatesFromSessionDescription(
const SessionDescriptionInterface* source_desc,
SessionDescriptionInterface* dest_desc);
void CreateOffer(
CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints);
void CreateAnswer(
CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints);
void set_secure(cricket::SecureMediaPolicy secure_policy);
cricket::SecureMediaPolicy secure() const;
sigslot::signal1<talk_base::SSLIdentity*> SignalIdentityReady;
// For testing.
bool waiting_for_identity() const;
private:
enum IdentityRequestState {
IDENTITY_NOT_NEEDED,
IDENTITY_WAITING,
IDENTITY_SUCCEEDED,
IDENTITY_FAILED,
};
// MessageHandler implementation.
virtual void OnMessage(talk_base::Message* msg);
void InternalCreateOffer(CreateSessionDescriptionRequest request);
void InternalCreateAnswer(CreateSessionDescriptionRequest request);
void PostCreateSessionDescriptionFailed(
CreateSessionDescriptionObserver* observer,
const std::string& error);
void PostCreateSessionDescriptionSucceeded(
CreateSessionDescriptionObserver* observer,
SessionDescriptionInterface* description);
void OnIdentityRequestFailed(int error);
void OnIdentityReady(const std::string& der_cert,
const std::string& der_private_key);
void SetIdentity(talk_base::SSLIdentity* identity);
std::queue<CreateSessionDescriptionRequest>
create_session_description_requests_;
talk_base::Thread* signaling_thread_;
MediaStreamSignaling* mediastream_signaling_;
cricket::TransportDescriptionFactory transport_desc_factory_;
cricket::MediaSessionDescriptionFactory session_desc_factory_;
uint64 session_version_;
talk_base::scoped_ptr<DTLSIdentityServiceInterface> identity_service_;
talk_base::scoped_refptr<WebRtcIdentityRequestObserver>
identity_request_observer_;
WebRtcSession* session_;
std::string session_id_;
cricket::DataChannelType data_channel_type_;
IdentityRequestState identity_request_state_;
DISALLOW_COPY_AND_ASSIGN(WebRtcSessionDescriptionFactory);
};
} // namespace webrtc
#endif // TALK_APP_WEBRTC_WEBRTCSESSIONDESCRIPTIONFACTORY_H_

View File

@ -43,55 +43,12 @@
#include "pk11pub.h"
#include "sechash.h"
#include "talk/base/base64.h"
#include "talk/base/logging.h"
#include "talk/base/helpers.h"
#include "talk/base/nssstreamadapter.h"
namespace talk_base {
// Helper function to parse PEM-encoded DER.
static bool PemToDer(const std::string& pem_type,
const std::string& pem_string,
std::string* der) {
// Find the inner body. We need this to fulfill the contract of
// returning pem_length.
size_t header = pem_string.find("-----BEGIN " + pem_type + "-----");
if (header == std::string::npos)
return false;
size_t body = pem_string.find("\n", header);
if (body == std::string::npos)
return false;
size_t trailer = pem_string.find("-----END " + pem_type + "-----");
if (trailer == std::string::npos)
return false;
std::string inner = pem_string.substr(body + 1, trailer - (body + 1));
*der = Base64::Decode(inner, Base64::DO_PARSE_WHITE |
Base64::DO_PAD_ANY |
Base64::DO_TERM_BUFFER);
return true;
}
static std::string DerToPem(const std::string& pem_type,
const unsigned char *data,
size_t length) {
std::stringstream result;
result << "-----BEGIN " << pem_type << "-----\n";
std::string tmp;
Base64::EncodeFromArray(data, length, &tmp);
result << tmp;
result << "-----END " << pem_type << "-----\n";
return result.str();
}
NSSKeyPair::~NSSKeyPair() {
if (privkey_)
SECKEY_DestroyPrivateKey(privkey_);
@ -135,7 +92,7 @@ NSSKeyPair *NSSKeyPair::GetReference() {
NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) {
std::string der;
if (!PemToDer("CERTIFICATE", pem_string, &der))
if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der))
return NULL;
SECItem der_cert;
@ -160,7 +117,7 @@ NSSCertificate *NSSCertificate::GetReference() const {
}
std::string NSSCertificate::ToPEMString() const {
return DerToPem("CERTIFICATE",
return SSLIdentity::DerToPem(kPemTypeCertificate,
certificate_->derCert.data,
certificate_->derCert.len);
}
@ -357,8 +314,8 @@ NSSIdentity *NSSIdentity::Generate(const std::string &common_name) {
SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key,
const std::string& certificate) {
std::string private_key_der;
if (!PemToDer(
"RSA PRIVATE KEY", private_key, &private_key_der))
if (!SSLIdentity::PemToDer(
kPemTypeRsaPrivateKey, private_key, &private_key_der))
return NULL;
SECItem private_key_item;

View File

@ -363,6 +363,15 @@ void SocketTest::ConnectWithDnsLookupFailInternal(const IPAddress& loopback) {
EXPECT_EQ(0, client->Connect(bogus_dns_addr));
// Wait for connection to fail (EHOSTNOTFOUND).
bool dns_lookup_finished = false;
WAIT_(client->GetState() == AsyncSocket::CS_CLOSED, kTimeout,
dns_lookup_finished);
if (!dns_lookup_finished) {
LOG(LS_WARNING) << "Skipping test; DNS resolution took longer than 5 "
<< "seconds.";
return;
}
EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));

View File

@ -34,6 +34,8 @@
#include <string>
#include "talk/base/base64.h"
#include "talk/base/logging.h"
#include "talk/base/sslconfig.h"
#if SSL_USE_SCHANNEL
@ -50,6 +52,59 @@
namespace talk_base {
const char kPemTypeCertificate[] = "CERTIFICATE";
const char kPemTypeRsaPrivateKey[] = "RSA PRIVATE KEY";
bool SSLIdentity::PemToDer(const std::string& pem_type,
const std::string& pem_string,
std::string* der) {
// Find the inner body. We need this to fulfill the contract of
// returning pem_length.
size_t header = pem_string.find("-----BEGIN " + pem_type + "-----");
if (header == std::string::npos)
return false;
size_t body = pem_string.find("\n", header);
if (body == std::string::npos)
return false;
size_t trailer = pem_string.find("-----END " + pem_type + "-----");
if (trailer == std::string::npos)
return false;
std::string inner = pem_string.substr(body + 1, trailer - (body + 1));
*der = Base64::Decode(inner, Base64::DO_PARSE_WHITE |
Base64::DO_PAD_ANY |
Base64::DO_TERM_BUFFER);
return true;
}
std::string SSLIdentity::DerToPem(const std::string& pem_type,
const unsigned char* data,
size_t length) {
std::stringstream result;
result << "-----BEGIN " << pem_type << "-----\n";
std::string b64_encoded;
Base64::EncodeFromArray(data, length, &b64_encoded);
// Divide the Base-64 encoded data into 64-character chunks, as per
// 4.3.2.4 of RFC 1421.
static const size_t kChunkSize = 64;
size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize;
for (size_t i = 0, chunk_offset = 0; i < chunks;
++i, chunk_offset += kChunkSize) {
result << b64_encoded.substr(chunk_offset, kChunkSize);
result << "\n";
}
result << "-----END " << pem_type << "-----\n";
return result.str();
}
#if SSL_USE_SCHANNEL
SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) {

View File

@ -64,8 +64,8 @@ class SSLCertificate {
// Compute the digest of the certificate given algorithm
virtual bool ComputeDigest(const std::string &algorithm,
unsigned char *digest, std::size_t size,
std::size_t *length) const = 0;
unsigned char* digest, std::size_t size,
std::size_t* length) const = 0;
};
// Our identity in an SSL negotiation: a keypair and certificate (both
@ -93,8 +93,19 @@ class SSLIdentity {
// Returns a temporary reference to the certificate.
virtual const SSLCertificate& certificate() const = 0;
// Helpers for parsing converting between PEM and DER format.
static bool PemToDer(const std::string& pem_type,
const std::string& pem_string,
std::string* der);
static std::string DerToPem(const std::string& pem_type,
const unsigned char* data,
size_t length);
};
extern const char kPemTypeCertificate[];
extern const char kPemTypeRsaPrivateKey[];
} // namespace talk_base
#endif // TALK_BASE_SSLIDENTITY_H__

View File

@ -32,6 +32,8 @@
#include "talk/base/ssladapter.h"
#include "talk/base/sslidentity.h"
using talk_base::SSLIdentity;
const char kTestCertificate[] = "-----BEGIN CERTIFICATE-----\n"
"MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n"
"BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n"
@ -70,8 +72,8 @@ class SSLIdentityTest : public testing::Test {
}
virtual void SetUp() {
identity1_.reset(talk_base::SSLIdentity::Generate("test1"));
identity2_.reset(talk_base::SSLIdentity::Generate("test2"));
identity1_.reset(SSLIdentity::Generate("test1"));
identity2_.reset(SSLIdentity::Generate("test2"));
ASSERT_TRUE(identity1_);
ASSERT_TRUE(identity2_);
@ -126,8 +128,8 @@ class SSLIdentityTest : public testing::Test {
}
private:
talk_base::scoped_ptr<talk_base::SSLIdentity> identity1_;
talk_base::scoped_ptr<talk_base::SSLIdentity> identity2_;
talk_base::scoped_ptr<SSLIdentity> identity1_;
talk_base::scoped_ptr<SSLIdentity> identity2_;
talk_base::scoped_ptr<talk_base::SSLCertificate> test_cert_;
};
@ -187,8 +189,17 @@ TEST_F(SSLIdentityTest, FromPEMStrings) {
"qCV42aXS3onOXDQ1ibuWq0fr0//aj0wo4KV474c=\n"
"-----END CERTIFICATE-----\n";
talk_base::scoped_ptr<talk_base::SSLIdentity> identity(
talk_base::SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM));
talk_base::scoped_ptr<SSLIdentity> identity(
SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM));
EXPECT_TRUE(identity);
EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString());
}
TEST_F(SSLIdentityTest, PemDerConversion) {
std::string der;
EXPECT_TRUE(SSLIdentity::PemToDer("CERTIFICATE", kTestCertificate, &der));
EXPECT_EQ(kTestCertificate, SSLIdentity::DerToPem(
"CERTIFICATE",
reinterpret_cast<const unsigned char*>(der.data()), der.length()));
}

View File

@ -1158,6 +1158,8 @@
'app/webrtc/webrtcsdp.h',
'app/webrtc/webrtcsession.cc',
'app/webrtc/webrtcsession.h',
'app/webrtc/webrtcsessiondescriptionfactory.cc',
'app/webrtc/webrtcsessiondescriptionfactory.h',
],
}, # target libjingle_peerconnection
],

View File

@ -391,6 +391,8 @@
'app/webrtc/test/fakeaudiocapturemodule.h',
'app/webrtc/test/fakeaudiocapturemodule_unittest.cc',
'app/webrtc/test/fakeconstraints.h',
'app/webrtc/test/fakedtlsidentityservice.h',
'app/webrtc/test/fakemediastreamsignaling.h',
'app/webrtc/test/fakeperiodicvideocapturer.h',
'app/webrtc/test/fakevideotrackrenderer.h',
'app/webrtc/test/mockpeerconnectionobservers.h',

View File

@ -45,6 +45,10 @@ class FeedbackParam {
: id_(id),
param_(param) {
}
explicit FeedbackParam(const std::string& id)
: id_(id),
param_(kParamValueEmpty) {
}
bool operator==(const FeedbackParam& other) const;
const std::string& id() const { return id_; }

View File

@ -134,11 +134,14 @@ static int OnSctpInboundPacket(struct socket* sock, union sctp_sockstore addr,
// Post data to the channel's receiver thread (copying it).
// TODO(ldixon): Unclear if copy is needed as this method is responsible for
// memory cleanup. But this does simplify code.
const uint32 native_ppid = talk_base::HostToNetwork32(rcv.rcv_ppid);
SctpInboundPacket* packet = new SctpInboundPacket();
packet->buffer.SetData(data, length);
packet->params.ssrc = rcv.rcv_sid;
packet->params.seq_num = rcv.rcv_ssn;
packet->params.timestamp = rcv.rcv_tsn;
packet->params.type =
static_cast<cricket::DataMessageType>(native_ppid);
packet->flags = flags;
channel->worker_thread()->Post(channel, MSG_SCTPINBOUNDPACKET,
talk_base::WrapMessageData(packet));
@ -371,7 +374,8 @@ bool SctpDataMediaChannel::AddSendStream(const StreamParams& stream) {
}
StreamParams found_stream;
if (GetStreamBySsrc(send_streams_, stream.first_ssrc(), &found_stream)) {
// TODO(lally): Consider keeping this sorted.
if (GetStreamBySsrc(streams_, stream.first_ssrc(), &found_stream)) {
LOG(LS_WARNING) << debug_name_ << "->AddSendStream(...): "
<< "Not adding data send stream '" << stream.id
<< "' with ssrc=" << stream.first_ssrc()
@ -379,17 +383,17 @@ bool SctpDataMediaChannel::AddSendStream(const StreamParams& stream) {
return false;
}
send_streams_.push_back(stream);
streams_.push_back(stream);
return true;
}
bool SctpDataMediaChannel::RemoveSendStream(uint32 ssrc) {
StreamParams found_stream;
if (!GetStreamBySsrc(send_streams_, ssrc, &found_stream)) {
if (!GetStreamBySsrc(streams_, ssrc, &found_stream)) {
return false;
}
RemoveStreamBySsrc(&send_streams_, ssrc);
RemoveStreamBySsrc(&streams_, ssrc);
return true;
}
@ -401,7 +405,7 @@ bool SctpDataMediaChannel::AddRecvStream(const StreamParams& stream) {
}
StreamParams found_stream;
if (GetStreamBySsrc(recv_streams_, stream.first_ssrc(), &found_stream)) {
if (GetStreamBySsrc(streams_, stream.first_ssrc(), &found_stream)) {
LOG(LS_WARNING) << debug_name_ << "->AddRecvStream(...): "
<< "Not adding data recv stream '" << stream.id
<< "' with ssrc=" << stream.first_ssrc()
@ -409,7 +413,7 @@ bool SctpDataMediaChannel::AddRecvStream(const StreamParams& stream) {
return false;
}
recv_streams_.push_back(stream);
streams_.push_back(stream);
LOG(LS_VERBOSE) << debug_name_ << "->AddRecvStream(...): "
<< "Added data recv stream '" << stream.id
<< "' with ssrc=" << stream.first_ssrc();
@ -417,7 +421,7 @@ bool SctpDataMediaChannel::AddRecvStream(const StreamParams& stream) {
}
bool SctpDataMediaChannel::RemoveRecvStream(uint32 ssrc) {
RemoveStreamBySsrc(&recv_streams_, ssrc);
RemoveStreamBySsrc(&streams_, ssrc);
return true;
}
@ -438,7 +442,8 @@ bool SctpDataMediaChannel::SendData(
}
StreamParams found_stream;
if (!GetStreamBySsrc(send_streams_, params.ssrc, &found_stream)) {
if (params.type != cricket::DMT_CONTROL &&
!GetStreamBySsrc(streams_, params.ssrc, &found_stream)) {
LOG(LS_WARNING) << debug_name_ << "->SendData(...): "
<< "Not sending data because ssrc is unknown: "
<< params.ssrc;
@ -471,7 +476,7 @@ bool SctpDataMediaChannel::SendData(
sndinfo.snd_flags = 0;
// TODO(pthatcher): Once data types are added to SendParams, this can be set
// from SendParams.
sndinfo.snd_ppid = talk_base::HostToNetwork32(PPID_NONE);
sndinfo.snd_ppid = talk_base::HostToNetwork32(params.type);
sndinfo.snd_context = 0;
sndinfo.snd_assoc_id = 0;
ssize_t res = usrsctp_sendv(sock_, payload.data(),
@ -548,9 +553,13 @@ void SctpDataMediaChannel::OnInboundPacketFromSctpToChannel(
void SctpDataMediaChannel::OnDataFromSctpToChannel(
const ReceiveDataParams& params, talk_base::Buffer* buffer) {
StreamParams found_stream;
if (!GetStreamBySsrc(recv_streams_, params.ssrc, &found_stream)) {
if (!GetStreamBySsrc(streams_, params.ssrc, &found_stream)) {
if (params.type == DMT_CONTROL) {
SignalDataReceived(params, buffer->data(), buffer->length());
} else {
LOG(LS_WARNING) << debug_name_ << "->OnDataFromSctpToChannel(...): "
<< "Received packet for unknown ssrc: " << params.ssrc;
}
return;
}

View File

@ -218,8 +218,8 @@ class SctpDataMediaChannel : public DataMediaChannel,
bool sending_;
// receiving_ controls whether inbound packets are thrown away.
bool receiving_;
std::vector<StreamParams> send_streams_;
std::vector<StreamParams> recv_streams_;
// Unified send/receive streams, as each is bidirectional.
std::vector<StreamParams> streams_;
// A human-readable name for debugging messages.
std::string debug_name_;

View File

@ -266,7 +266,9 @@ class FakeWebRtcVoiceEngine
virtual webrtc::AudioProcessing* audio_processing() OVERRIDE {
return NULL;
}
#ifndef USE_WEBRTC_DEV_BRANCH
WEBRTC_STUB(MaxNumOfChannels, ());
#endif
WEBRTC_FUNC(CreateChannel, ()) {
if (fail_create_channel_) {
return -1;

View File

@ -76,6 +76,13 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
const SocketAddress& local_address, int min_port, int max_port, int opts) {
// Fail if TLS is required.
if (opts & PacketSocketFactory::OPT_TLS) {
LOG(LS_ERROR) << "TLS support currently is not available.";
return NULL;
}
talk_base::AsyncSocket* socket =
socket_factory()->CreateAsyncSocket(local_address.family(),
SOCK_STREAM);
@ -92,6 +99,7 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
// If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
if (opts & PacketSocketFactory::OPT_SSLTCP) {
ASSERT(!(opts & PacketSocketFactory::OPT_TLS));
socket = new talk_base::AsyncSSLSocket(socket);
}
@ -108,6 +116,13 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
const SocketAddress& local_address, const SocketAddress& remote_address,
const ProxyInfo& proxy_info, const std::string& user_agent, int opts) {
// Fail if TLS is required.
if (opts & PacketSocketFactory::OPT_TLS) {
LOG(LS_ERROR) << "TLS support currently is not available.";
return NULL;
}
talk_base::AsyncSocket* socket =
socket_factory()->CreateAsyncSocket(local_address.family(), SOCK_STREAM);
if (!socket) {
@ -133,6 +148,7 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
// If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
if (opts & PacketSocketFactory::OPT_SSLTCP) {
ASSERT(!(opts & PacketSocketFactory::OPT_TLS));
socket = new talk_base::AsyncSSLSocket(socket);
}

View File

@ -114,6 +114,8 @@ const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE =
const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH =
{ NS_JINGLE_RTP, LN_BANDWIDTH };
const buzz::StaticQName QN_JINGLE_RTCP_MUX = { NS_JINGLE_RTP, "rtcp-mux" };
const buzz::StaticQName QN_JINGLE_RTCP_FB = { NS_JINGLE_RTP, "rtcp-fb" };
const buzz::StaticQName QN_SUBTYPE = { NS_EMPTY, "subtype" };
const buzz::StaticQName QN_PARAMETER = { NS_JINGLE_RTP, "parameter" };
const buzz::StaticQName QN_JINGLE_RTP_HDREXT =
{ NS_JINGLE_RTP, "rtp-hdrext" };

View File

@ -130,6 +130,8 @@ extern const buzz::StaticQName QN_SSRC;
extern const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE;
extern const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH;
extern const buzz::StaticQName QN_JINGLE_RTCP_MUX;
extern const buzz::StaticQName QN_JINGLE_RTCP_FB;
extern const buzz::StaticQName QN_SUBTYPE;
extern const buzz::StaticQName QN_JINGLE_RTP_HDREXT;
extern const buzz::StaticQName QN_URI;

View File

@ -56,6 +56,10 @@ class DtlsTransport : public Base {
Base::DestroyAllChannels();
}
virtual void SetIdentity_w(talk_base::SSLIdentity* identity) {
identity_ = identity;
}
virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl*
channel) {
talk_base::SSLFingerprint* local_fp =

View File

@ -226,6 +226,9 @@ class P2PTransportChannelTestBase : public testing::Test,
void SetAllocationStepDelay(uint32 delay) {
allocator_->set_step_delay(delay);
}
void SetAllowTcpListen(bool allow_tcp_listen) {
allocator_->set_allow_tcp_listen(allow_tcp_listen);
}
talk_base::FakeNetworkManager network_manager_;
talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
@ -398,6 +401,9 @@ class P2PTransportChannelTestBase : public testing::Test,
void SetAllocationStepDelay(int endpoint, uint32 delay) {
return GetEndpoint(endpoint)->SetAllocationStepDelay(delay);
}
void SetAllowTcpListen(int endpoint, bool allow_tcp_listen) {
return GetEndpoint(endpoint)->SetAllowTcpListen(allow_tcp_listen);
}
void Test(const Result& expected) {
int32 connect_start = talk_base::Time(), connect_time;
@ -1222,6 +1228,44 @@ TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
DestroyChannels();
}
TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) {
AddAddress(0, kPublicAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
SetAllocationStepDelay(0, kMinimumStepDelay);
SetAllocationStepDelay(1, kMinimumStepDelay);
int kOnlyLocalTcpPorts = cricket::PORTALLOCATOR_DISABLE_UDP |
cricket::PORTALLOCATOR_DISABLE_STUN |
cricket::PORTALLOCATOR_DISABLE_RELAY |
cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
// Disable all protocols except TCP.
SetAllocatorFlags(0, kOnlyLocalTcpPorts);
SetAllocatorFlags(1, kOnlyLocalTcpPorts);
SetAllowTcpListen(0, true); // actpass.
SetAllowTcpListen(1, false); // active.
CreateChannels(1);
EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
ep2_ch1()->readable() && ep2_ch1()->writable(),
1000);
EXPECT_TRUE(
ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
std::string kTcpProtocol = "tcp";
EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep1_ch1())->protocol());
EXPECT_EQ(kTcpProtocol, LocalCandidate(ep1_ch1())->protocol());
EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep2_ch1())->protocol());
EXPECT_EQ(kTcpProtocol, LocalCandidate(ep2_ch1())->protocol());
TestSendRecv(1);
DestroyChannels();
}
// Test what happens when we have 2 users behind the same NAT. This can lead
// to interesting behavior because the STUN server will only give out the
// address of the outermost NAT.

View File

@ -37,8 +37,9 @@ class AsyncPacketSocket;
class PacketSocketFactory {
public:
enum Options {
OPT_SSLTCP = 0x01,
OPT_STUN = 0x02,
OPT_SSLTCP = 0x01, // Pseudo-TLS.
OPT_TLS = 0x02,
OPT_STUN = 0x04,
};
PacketSocketFactory() { }

View File

@ -104,9 +104,12 @@ bool StringToProto(const char* value, ProtocolType* proto);
struct ProtocolAddress {
talk_base::SocketAddress address;
ProtocolType proto;
bool secure;
ProtocolAddress(const talk_base::SocketAddress& a, ProtocolType p)
: address(a), proto(p) { }
: address(a), proto(p), secure(false) { }
ProtocolAddress(const talk_base::SocketAddress& a, ProtocolType p, bool sec)
: address(a), proto(p), secure(sec) { }
};
// Represents a local communication mechanism that can be used to create

View File

@ -117,7 +117,8 @@ class PortAllocator : public sigslot::has_slots<> {
flags_(kDefaultPortAllocatorFlags),
min_port_(0),
max_port_(0),
step_delay_(kDefaultStepDelay) {
step_delay_(kDefaultStepDelay),
allow_tcp_listen_(true) {
// This will allow us to have old behavior on non webrtc clients.
}
virtual ~PortAllocator();
@ -155,11 +156,16 @@ class PortAllocator : public sigslot::has_slots<> {
return true;
}
uint32 step_delay() const { return step_delay_; }
void set_step_delay(uint32 delay) {
ASSERT(delay >= kMinimumStepDelay);
step_delay_ = delay;
}
uint32 step_delay() const { return step_delay_; }
bool allow_tcp_listen() const { return allow_tcp_listen_; }
void set_allow_tcp_listen(bool allow_tcp_listen) {
allow_tcp_listen_ = allow_tcp_listen;
}
protected:
virtual PortAllocatorSession* CreateSessionInternal(
@ -177,6 +183,7 @@ class PortAllocator : public sigslot::has_slots<> {
int max_port_;
uint32 step_delay_;
SessionMuxerMap muxers_;
bool allow_tcp_listen_;
};
} // namespace cricket

View File

@ -288,6 +288,11 @@ bool TransportProxy::OnRemoteCandidates(const Candidates& candidates,
return true;
}
void TransportProxy::SetIdentity(
talk_base::SSLIdentity* identity) {
transport_->get()->SetIdentity(identity);
}
std::string BaseSession::StateToString(State state) {
switch (state) {
case Session::STATE_INIT:
@ -368,6 +373,17 @@ BaseSession::~BaseSession() {
delete local_description_;
}
bool BaseSession::SetIdentity(talk_base::SSLIdentity* identity) {
if (identity_)
return false;
identity_ = identity;
for (TransportMap::iterator iter = transports_.begin();
iter != transports_.end(); ++iter) {
iter->second->SetIdentity(identity_);
}
return true;
}
bool BaseSession::PushdownTransportDescription(ContentSource source,
ContentAction action) {
if (source == CS_LOCAL) {

View File

@ -141,6 +141,7 @@ class TransportProxy : public sigslot::has_slots<>,
// Simple functions that thunk down to the same functions on Transport.
void SetRole(TransportRole role);
void SetIdentity(talk_base::SSLIdentity* identity);
bool SetLocalTransportDescription(const TransportDescription& description,
ContentAction action);
bool SetRemoteTransportDescription(const TransportDescription& description,
@ -365,15 +366,16 @@ class BaseSession : public sigslot::has_slots<>,
// This avoids exposing the internal structures used to track them.
virtual bool GetStats(SessionStats* stats);
talk_base::SSLIdentity* identity() { return identity_; }
protected:
// Specifies the identity to use in this session.
bool SetIdentity(talk_base::SSLIdentity* identity);
bool PushdownTransportDescription(ContentSource source,
ContentAction action);
void set_initiator(bool initiator) { initiator_ = initiator; }
talk_base::SSLIdentity* identity() { return identity_; }
// Specifies the identity to use in this session.
void set_identity(talk_base::SSLIdentity* identity) { identity_ = identity; }
const TransportMap& transport_proxies() const { return transports_; }
// Get a TransportProxy by content_name or transport. NULL if not found.
TransportProxy* GetTransportProxy(const std::string& content_name);

View File

@ -100,7 +100,7 @@ Session* SessionManager::CreateSession(
Session* session = new Session(this, local_name, initiator_name,
sid, content_type, client);
session->set_identity(transport_desc_factory_.identity());
session->SetIdentity(transport_desc_factory_.identity());
session_map_[session->id()] = session;
session->SignalRequestSignaling.connect(
this, &SessionManager::OnRequestSignaling);

View File

@ -58,7 +58,8 @@ enum {
MSG_SETROLE = 16,
MSG_SETLOCALDESCRIPTION = 17,
MSG_SETREMOTEDESCRIPTION = 18,
MSG_GETSTATS = 19
MSG_GETSTATS = 19,
MSG_SETIDENTITY = 20,
};
struct ChannelParams : public talk_base::MessageData {
@ -102,6 +103,13 @@ struct StatsParam : public talk_base::MessageData {
bool result;
};
struct IdentityParam : public talk_base::MessageData {
explicit IdentityParam(talk_base::SSLIdentity* identity)
: identity(identity) {}
talk_base::SSLIdentity* identity;
};
Transport::Transport(talk_base::Thread* signaling_thread,
talk_base::Thread* worker_thread,
const std::string& content_name,
@ -133,6 +141,11 @@ void Transport::SetRole(TransportRole role) {
worker_thread()->Send(this, MSG_SETROLE, &param);
}
void Transport::SetIdentity(talk_base::SSLIdentity* identity) {
IdentityParam params(identity);
worker_thread()->Send(this, MSG_SETIDENTITY, &params);
}
bool Transport::SetLocalTransportDescription(
const TransportDescription& description, ContentAction action) {
TransportDescriptionParams params(description, action);
@ -801,6 +814,11 @@ void Transport::OnMessage(talk_base::Message* msg) {
params->result = GetStats_w(params->stats);
}
break;
case MSG_SETIDENTITY: {
IdentityParam* params = static_cast<IdentityParam*>(msg->pdata);
SetIdentity_w(params->identity);
}
break;
}
}

View File

@ -250,6 +250,9 @@ class Transport : public talk_base::MessageHandler,
void SetTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
uint64 tiebreaker() { return tiebreaker_; }
// Must be called before applying local session description.
void SetIdentity(talk_base::SSLIdentity* identity);
TransportProtocol protocol() const { return protocol_; }
// Create, destroy, and lookup the channels of this type by their components.
@ -348,6 +351,8 @@ class Transport : public talk_base::MessageHandler,
return remote_description_.get();
}
virtual void SetIdentity_w(talk_base::SSLIdentity* identity) {}
// Pushes down the transport parameters from the local description, such
// as the ICE ufrag and pwd.
// Derived classes can override, but must call the base as well.

View File

@ -60,12 +60,13 @@ inline bool IsTurnChannelData(uint16 msg_type) {
return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01
}
static int GetRelayPreference(cricket::ProtocolType proto) {
static int GetRelayPreference(cricket::ProtocolType proto, bool secure) {
int relay_preference = ICE_TYPE_PREFERENCE_RELAY;
if (proto == cricket::PROTO_TCP)
if (proto == cricket::PROTO_TCP) {
relay_preference -= 1;
else if (proto == cricket::PROTO_SSLTCP)
relay_preference -= 2;
if (secure)
relay_preference -= 1;
}
ASSERT(relay_preference >= 0);
return relay_preference;
@ -223,9 +224,15 @@ void TurnPort::PrepareAddress() {
socket_.reset(socket_factory()->CreateUdpSocket(
talk_base::SocketAddress(ip(), 0), min_port(), max_port()));
} else if (server_address_.proto == PROTO_TCP) {
int opts = talk_base::PacketSocketFactory::OPT_STUN;
// If secure bit is enabled in server address, use TLS over TCP.
if (server_address_.secure) {
opts |= talk_base::PacketSocketFactory::OPT_TLS;
}
socket_.reset(socket_factory()->CreateClientTcpSocket(
talk_base::SocketAddress(ip(), 0), server_address_.address,
proxy(), user_agent(), talk_base::PacketSocketFactory::OPT_STUN));
proxy(), user_agent(), opts));
}
if (!socket_) {
@ -412,8 +419,12 @@ void TurnPort::OnStunAddress(const talk_base::SocketAddress& address) {
void TurnPort::OnAllocateSuccess(const talk_base::SocketAddress& address) {
connected_ = true;
AddAddress(address, socket_->GetLocalAddress(), "udp",
RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto), true);
AddAddress(address,
socket_->GetLocalAddress(),
"udp",
RELAY_PORT_TYPE,
GetRelayPreference(server_address_.proto, server_address_.secure),
true);
}
void TurnPort::OnAllocateError() {

View File

@ -66,10 +66,10 @@ static const char kTurnUsername[] = "test";
static const char kTurnPassword[] = "test";
static const int kTimeout = 1000;
static const cricket::ProtocolAddress kTurnUdpProtoAddr(kTurnUdpIntAddr,
cricket::PROTO_UDP);
static const cricket::ProtocolAddress kTurnTcpProtoAddr(kTurnTcpIntAddr,
cricket::PROTO_TCP);
static const cricket::ProtocolAddress kTurnUdpProtoAddr(
kTurnUdpIntAddr, cricket::PROTO_UDP);
static const cricket::ProtocolAddress kTurnTcpProtoAddr(
kTurnTcpIntAddr, cricket::PROTO_TCP);
class TurnPortTest : public testing::Test,
public sigslot::has_slots<> {
@ -295,6 +295,7 @@ TEST_F(TurnPortTest, TestTurnConnection) {
TestTurnConnection();
}
// Test that we can establish a TCP connection with TURN server.
TEST_F(TurnPortTest, TestTurnTcpConnection) {
talk_base::AsyncSocket* tcp_server_socket =
CreateServerSocket(kTurnTcpIntAddr);
@ -304,6 +305,18 @@ TEST_F(TurnPortTest, TestTurnTcpConnection) {
TestTurnConnection();
}
// Test that we fail to create a connection when we want to use TLS over TCP.
// This test should be removed once we have TLS support.
TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) {
cricket::ProtocolAddress secure_addr(kTurnTcpProtoAddr.address,
kTurnTcpProtoAddr.proto,
true);
CreateTurnPort(kTurnUsername, kTurnPassword, secure_addr);
turn_port_->PrepareAddress();
EXPECT_TRUE_WAIT(turn_error_, kTimeout);
ASSERT_EQ(0U, turn_port_->Candidates().size());
}
// Run TurnConnectionTest with one-time-use nonce feature.
// Here server will send a 438 STALE_NONCE error message for
// every TURN transaction.

View File

@ -264,9 +264,6 @@ void TurnServer::AcceptConnection(talk_base::AsyncSocket* server_socket) {
talk_base::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr);
if (accepted_socket != NULL) {
ProtocolType proto = server_listen_sockets_[server_socket];
if (proto == PROTO_SSLTCP) {
accepted_socket = new talk_base::AsyncSSLServerSocket(accepted_socket);
}
cricket::AsyncStunTCPSocket* tcp_socket =
new cricket::AsyncStunTCPSocket(accepted_socket, false);

View File

@ -99,13 +99,6 @@ class BasicPortAllocator : public PortAllocator {
const std::string& ice_ufrag,
const std::string& ice_pwd);
bool allow_tcp_listen() const {
return allow_tcp_listen_;
}
void set_allow_tcp_listen(bool allow_tcp_listen) {
allow_tcp_listen_ = allow_tcp_listen;
}
private:
void Construct();

View File

@ -440,6 +440,25 @@ void ParsePayloadTypeParameters(const buzz::XmlElement* element,
}
}
void ParseFeedbackParams(const buzz::XmlElement* element,
FeedbackParams* params) {
for (const buzz::XmlElement* param = element->FirstNamed(QN_JINGLE_RTCP_FB);
param != NULL; param = param->NextNamed(QN_JINGLE_RTCP_FB)) {
std::string type = GetXmlAttr(param, QN_TYPE, buzz::STR_EMPTY);
std::string subtype = GetXmlAttr(param, QN_SUBTYPE, buzz::STR_EMPTY);
if (!type.empty()) {
params->Add(FeedbackParam(type, subtype));
}
}
}
void AddFeedbackParams(const FeedbackParams& additional_params,
FeedbackParams* params) {
for (size_t i = 0; i < additional_params.params().size(); ++i) {
params->Add(additional_params.params()[i]);
}
}
int FindWithDefault(const std::map<std::string, std::string>& map,
const std::string& key, const int def) {
std::map<std::string, std::string>::const_iterator iter = map.find(key);
@ -488,6 +507,7 @@ bool ParseJingleAudioCodec(const buzz::XmlElement* elem, AudioCodec* codec) {
int bitrate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_BITRATE, 0);
*codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
ParseFeedbackParams(elem, &codec->feedback_params);
return true;
}
@ -506,6 +526,7 @@ bool ParseJingleVideoCodec(const buzz::XmlElement* elem, VideoCodec* codec) {
*codec = VideoCodec(id, name, width, height, framerate, 0);
codec->params = paramap;
ParseFeedbackParams(elem, &codec->feedback_params);
return true;
}
@ -517,6 +538,7 @@ bool ParseJingleDataCodec(const buzz::XmlElement* elem, DataCodec* codec) {
std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
*codec = DataCodec(id, name, 0);
ParseFeedbackParams(elem, &codec->feedback_params);
return true;
}
@ -543,12 +565,16 @@ bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
talk_base::scoped_ptr<AudioContentDescription> audio(
new AudioContentDescription());
FeedbackParams content_feedback_params;
ParseFeedbackParams(content_elem, &content_feedback_params);
for (const buzz::XmlElement* payload_elem =
content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
payload_elem != NULL;
payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
AudioCodec codec;
if (ParseJingleAudioCodec(payload_elem, &codec)) {
AddFeedbackParams(content_feedback_params, &codec.feedback_params);
audio->AddCodec(codec);
}
}
@ -579,12 +605,16 @@ bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
talk_base::scoped_ptr<VideoContentDescription> video(
new VideoContentDescription());
FeedbackParams content_feedback_params;
ParseFeedbackParams(content_elem, &content_feedback_params);
for (const buzz::XmlElement* payload_elem =
content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
payload_elem != NULL;
payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
VideoCodec codec;
if (ParseJingleVideoCodec(payload_elem, &codec)) {
AddFeedbackParams(content_feedback_params, &codec.feedback_params);
video->AddCodec(codec);
}
}
@ -645,12 +675,16 @@ bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem,
ParseError* error) {
DataContentDescription* data = new DataContentDescription();
FeedbackParams content_feedback_params;
ParseFeedbackParams(content_elem, &content_feedback_params);
for (const buzz::XmlElement* payload_elem =
content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
payload_elem != NULL;
payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
DataCodec codec;
if (ParseJingleDataCodec(payload_elem, &codec)) {
AddFeedbackParams(content_feedback_params, &codec.feedback_params);
data->AddCodec(codec);
}
}
@ -853,6 +887,18 @@ buzz::XmlElement* CreatePayloadTypeParameterElem(
return elem;
}
void AddRtcpFeedbackElem(buzz::XmlElement* elem,
const FeedbackParams& feedback_params) {
std::vector<FeedbackParam>::const_iterator it;
for (it = feedback_params.params().begin();
it != feedback_params.params().end(); ++it) {
buzz::XmlElement* fb_elem = new buzz::XmlElement(QN_JINGLE_RTCP_FB);
fb_elem->AddAttr(QN_TYPE, it->id());
fb_elem->AddAttr(QN_SUBTYPE, it->param());
elem->AddElement(fb_elem);
}
}
buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
@ -869,6 +915,8 @@ buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
AddXmlAttr(elem, QN_CHANNELS, codec.channels);
}
AddRtcpFeedbackElem(elem, codec.feedback_params);
return elem;
}
@ -883,6 +931,9 @@ buzz::XmlElement* CreateJingleVideoCodecElem(const VideoCodec& codec) {
PAYLOADTYPE_PARAMETER_HEIGHT, codec.height));
elem->AddElement(CreatePayloadTypeParameterElem(
PAYLOADTYPE_PARAMETER_FRAMERATE, codec.framerate));
AddRtcpFeedbackElem(elem, codec.feedback_params);
CodecParameterMap::const_iterator param_iter;
for (param_iter = codec.params.begin(); param_iter != codec.params.end();
++param_iter) {
@ -899,6 +950,8 @@ buzz::XmlElement* CreateJingleDataCodecElem(const DataCodec& codec) {
AddXmlAttr(elem, QN_ID, codec.id);
elem->AddAttr(QN_NAME, codec.name);
AddRtcpFeedbackElem(elem, codec.feedback_params);
return elem;
}

View File

@ -41,6 +41,16 @@
#include "talk/xmllite/xmlprinter.h"
#include "talk/xmpp/constants.h"
using cricket::AudioCodec;
using cricket::AudioContentDescription;
using cricket::Codec;
using cricket::DataCodec;
using cricket::DataContentDescription;
using cricket::FeedbackParam;
using cricket::FeedbackParams;
using cricket::VideoCodec;
using cricket::VideoContentDescription;
// The codecs that our FakeMediaEngine will support. Order is important, since
// the tests check that our messages have codecs in the correct order.
static const cricket::AudioCodec kAudioCodecs[] = {
@ -377,6 +387,42 @@ const std::string kJingleInitiateDifferentPreference(
" </jingle> " \
"</iq> ");
const std::string kJingleInitiateWithRtcpFb(
"<iq xmlns='jabber:client' from='me@domain.com/resource' " \
" to='user@domain.com/resource' type='set' id='123'> " \
" <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
" sid='abcdef' initiator='me@domain.com/resource'> " \
" <content name='test audio'> " \
" <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
" <payload-type id='103' name='ISAC' clockrate='16000'> " \
" <rtcp-fb type='nack'/> " \
" </payload-type> " \
" </description> " \
" <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
" </content> " \
" <content name='test video'> " \
" <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
" <rtcp-fb type='nack'/> " \
" <payload-type id='99' name='H264-SVC'> " \
" <rtcp-fb type='ccm' subtype='fir'/> " \
" <parameter name='height' value='200'/> " \
" <parameter name='width' value='320'/> " \
" <parameter name='framerate' value='30'/> " \
" </payload-type> " \
" </description> " \
" <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
" </content> " \
" <content name='test data'> " \
" <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='data'> " \
" <rtcp-fb type='nack'/> " \
" <payload-type id='127' name='google-data'> " \
" </payload-type> " \
" </description> " \
" <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
" </content> " \
" </jingle> " \
"</iq> ");
const std::string kGingleVideoInitiate(
"<iq xmlns='jabber:client' from='me@domain.com/resource' " \
" to='user@domain.com/resource' type='set' id='123'> " \
@ -1151,7 +1197,7 @@ static cricket::CallOptions VideoCallOptions() {
return options;
}
buzz::XmlElement* CopyElement(const buzz::XmlElement* elem) {
static buzz::XmlElement* CopyElement(const buzz::XmlElement* elem) {
return new buzz::XmlElement(*elem);
}
@ -1164,7 +1210,7 @@ static std::string AddEncryption(std::string stanza, std::string encryption) {
return stanza;
}
int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
static int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
const std::string& expected_name) {
if (parameter) {
const std::string& actual_name =
@ -1181,6 +1227,18 @@ int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
return 0;
}
template <class CodecClass, class DescriptionClass>
static void VerifyCodecFbParams(const FeedbackParams& expected,
const DescriptionClass* desc) {
if (!expected.params().empty()) {
ASSERT_TRUE(desc != NULL);
const std::vector<CodecClass> codecs = desc->codecs();
for (size_t i = 0; i < codecs.size(); ++i) {
EXPECT_EQ(expected, codecs[i].feedback_params);
}
}
}
// Parses and extracts payload and codec info from test XML. Since
// that XML will be in various contents (Gingle and Jingle), we need an
// abstract parser with one concrete implementation per XML content.
@ -1250,6 +1308,20 @@ class JingleSessionTestParser : public MediaSessionTestParser {
return payload_type->NextNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
}
void ParsePayloadTypeFeedbackParameters(const buzz::XmlElement* element,
FeedbackParams* params) {
const buzz::XmlElement* param =
element->FirstNamed(cricket::QN_JINGLE_RTCP_FB);
for (; param != NULL;
param = param->NextNamed(cricket::QN_JINGLE_RTCP_FB)) {
std::string type = param->Attr(cricket::QN_TYPE);
std::string subtype = param->Attr(cricket::QN_SUBTYPE);
if (!type.empty()) {
params->Add(FeedbackParam(type, subtype));
}
}
}
cricket::AudioCodec AudioCodecFromPayloadType(
const buzz::XmlElement* payload_type) {
int id = 0;
@ -1272,7 +1344,9 @@ class JingleSessionTestParser : public MediaSessionTestParser {
channels = atoi(payload_type->Attr(
cricket::QN_CHANNELS).c_str());
return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
AudioCodec codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
return codec;
}
cricket::VideoCodec VideoCodecFromPayloadType(
@ -1301,8 +1375,9 @@ class JingleSessionTestParser : public MediaSessionTestParser {
}
}
}
return cricket::VideoCodec(id, name, width, height, framerate, 0);
VideoCodec codec = VideoCodec(id, name, width, height, framerate, 0);
ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
return codec;
}
cricket::DataCodec DataCodecFromPayloadType(
@ -1315,7 +1390,9 @@ class JingleSessionTestParser : public MediaSessionTestParser {
if (payload_type->HasAttr(cricket::QN_NAME))
name = payload_type->Attr(cricket::QN_NAME);
return cricket::DataCodec(id, name, 0);
DataCodec codec = DataCodec(id, name, 0);
ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
return codec;
}
bool ActionIsTerminate(const buzz::XmlElement* action) {
@ -1482,14 +1559,24 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
fme_ = new cricket::FakeMediaEngine();
fdme_ = new cricket::FakeDataEngine();
FeedbackParams params_nack_fir;
params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamCcm,
cricket::kRtcpFbCcmParamFir));
params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamNack));
FeedbackParams params_nack;
params_nack.Add(FeedbackParam(cricket::kRtcpFbParamNack));
std::vector<cricket::AudioCodec>
audio_codecs(kAudioCodecs, kAudioCodecs + ARRAY_SIZE(kAudioCodecs));
SetCodecFeedbackParams(&audio_codecs, params_nack);
fme_->SetAudioCodecs(audio_codecs);
std::vector<cricket::VideoCodec>
video_codecs(kVideoCodecs, kVideoCodecs + ARRAY_SIZE(kVideoCodecs));
SetCodecFeedbackParams(&video_codecs, params_nack_fir);
fme_->SetVideoCodecs(video_codecs);
std::vector<cricket::DataCodec>
data_codecs(kDataCodecs, kDataCodecs + ARRAY_SIZE(kDataCodecs));
SetCodecFeedbackParams(&data_codecs, params_nack);
fdme_->SetDataCodecs(data_codecs);
client_ = new cricket::MediaSessionClient(
@ -1551,14 +1638,23 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
return parser_->AudioCodecFromPayloadType(payload_type);
}
const cricket::AudioContentDescription* GetFirstAudioContentDescription(
cricket::VideoCodec VideoCodecFromPayloadType(
const buzz::XmlElement* payload_type) {
return parser_->VideoCodecFromPayloadType(payload_type);
}
cricket::DataCodec DataCodecFromPayloadType(
const buzz::XmlElement* payload_type) {
return parser_->DataCodecFromPayloadType(payload_type);
}
const AudioContentDescription* GetFirstAudioContentDescription(
const cricket::SessionDescription* sdesc) {
const cricket::ContentInfo* content =
cricket::GetFirstAudioContent(sdesc);
if (content == NULL)
return NULL;
return static_cast<const cricket::AudioContentDescription*>(
content->description);
return static_cast<const AudioContentDescription*>(content->description);
}
const cricket::VideoContentDescription* GetFirstVideoContentDescription(
@ -1573,7 +1669,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
void CheckCryptoFromGoodIncomingInitiate(const cricket::Session* session) {
ASSERT_TRUE(session != NULL);
const cricket::AudioContentDescription* content =
const AudioContentDescription* content =
GetFirstAudioContentDescription(session->remote_description());
ASSERT_TRUE(content != NULL);
ASSERT_EQ(2U, content->cryptos().size());
@ -1588,7 +1684,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
}
void CheckCryptoForGoodOutgoingAccept(const cricket::Session* session) {
const cricket::AudioContentDescription* content =
const AudioContentDescription* content =
GetFirstAudioContentDescription(session->local_description());
ASSERT_EQ(1U, content->cryptos().size());
ASSERT_EQ(145, content->cryptos()[0].tag);
@ -1597,7 +1693,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
}
void CheckBadCryptoFromIncomingInitiate(const cricket::Session* session) {
const cricket::AudioContentDescription* content =
const AudioContentDescription* content =
GetFirstAudioContentDescription(session->remote_description());
ASSERT_EQ(1U, content->cryptos().size());
ASSERT_EQ(145, content->cryptos()[0].tag);
@ -1607,11 +1703,22 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
}
void CheckNoCryptoForOutgoingAccept(const cricket::Session* session) {
const cricket::AudioContentDescription* content =
const AudioContentDescription* content =
GetFirstAudioContentDescription(session->local_description());
ASSERT_TRUE(content->cryptos().empty());
}
void CheckRtcpFb(const cricket::SessionDescription* sdesc) {
VerifyCodecFbParams<AudioCodec>(expected_audio_fb_params_,
GetFirstAudioContentDescription(sdesc));
VerifyCodecFbParams<VideoCodec>(expected_video_fb_params_,
GetFirstVideoContentDescription(sdesc));
VerifyCodecFbParams<DataCodec>(expected_data_fb_params_,
GetFirstDataContentDescription(sdesc));
}
void CheckVideoBandwidth(int expected_bandwidth,
const cricket::SessionDescription* sdesc) {
const cricket::VideoContentDescription* video =
@ -1638,9 +1745,10 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
buzz::XmlElement* e = PayloadTypeFromContent(content);
ASSERT_TRUE(e != NULL);
cricket::DataCodec codec = parser_->DataCodecFromPayloadType(e);
cricket::DataCodec codec = DataCodecFromPayloadType(e);
EXPECT_EQ(127, codec.id);
EXPECT_EQ("google-data", codec.name);
EXPECT_EQ(expected_data_fb_params_, codec.feedback_params);
CheckDataRtcpMux(true, call_->sessions()[0]->local_description());
CheckDataRtcpMux(true, call_->sessions()[0]->remote_description());
@ -1675,7 +1783,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
}
void CheckAudioSsrcForIncomingAccept(const cricket::Session* session) {
const cricket::AudioContentDescription* audio =
const AudioContentDescription* audio =
GetFirstAudioContentDescription(session->remote_description());
ASSERT_TRUE(audio != NULL);
ASSERT_EQ(kAudioSsrc, audio->first_ssrc());
@ -1716,6 +1824,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
call_->sessions()[0]->remote_description());
CheckVideoRtcpMux(expected_video_rtcp_mux_,
call_->sessions()[0]->remote_description());
CheckRtcpFb(call_->sessions()[0]->remote_description());
if (expect_incoming_crypto_) {
CheckCryptoFromGoodIncomingInitiate(call_->sessions()[0]);
}
@ -1841,7 +1950,23 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
ClearStanzas();
}
void VerifyAudioCodec(const AudioCodec& codec, int id,
const std::string& name, int clockrate,
int bitrate, int channels) {
ASSERT_EQ(id, codec.id);
ASSERT_EQ(name, codec.name);
ASSERT_EQ(clockrate, codec.clockrate);
ASSERT_EQ(bitrate, codec.bitrate);
ASSERT_EQ(channels, codec.channels);
ASSERT_EQ(expected_audio_fb_params_, codec.feedback_params);
}
void TestGoodOutgoingInitiate(const cricket::CallOptions& options) {
if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
// rtcp fb is only implemented for jingle.
ExpectRtcpFb();
}
client_->CreateCall();
ASSERT_TRUE(call_ != NULL);
call_->InitiateSession(buzz::Jid("me@mydomain.com"),
@ -1861,159 +1986,92 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
buzz::XmlElement* e = PayloadTypeFromContent(content);
ASSERT_TRUE(e != NULL);
cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(103, codec.id);
ASSERT_EQ("ISAC", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(0, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 103, "ISAC", 16000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(104, codec.id);
ASSERT_EQ("ISAC", codec.name);
ASSERT_EQ(32000, codec.clockrate);
ASSERT_EQ(0, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 104, "ISAC", 32000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(119, codec.id);
ASSERT_EQ("ISACLC", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(40000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 119, "ISACLC", 16000, 40000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(99, codec.id);
ASSERT_EQ("speex", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(22000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 99, "speex", 16000, 22000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(97, codec.id);
ASSERT_EQ("IPCMWB", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(80000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 97, "IPCMWB", 16000, 80000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(9, codec.id);
ASSERT_EQ("G722", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(64000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 9, "G722", 16000, 64000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(102, codec.id);
ASSERT_EQ("iLBC", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(13300, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 102, "iLBC", 8000, 13300, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(98, codec.id);
ASSERT_EQ("speex", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(11000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 98, "speex", 8000, 11000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(3, codec.id);
ASSERT_EQ("GSM", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(13000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 3, "GSM", 8000, 13000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(100, codec.id);
ASSERT_EQ("EG711U", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(64000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 100, "EG711U", 8000, 64000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(101, codec.id);
ASSERT_EQ("EG711A", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(64000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 101, "EG711A", 8000, 64000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(0, codec.id);
ASSERT_EQ("PCMU", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(64000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 0, "PCMU", 8000, 64000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(8, codec.id);
ASSERT_EQ("PCMA", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(64000, codec.bitrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 8, "PCMA", 8000, 64000, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(126, codec.id);
ASSERT_EQ("CN", codec.name);
ASSERT_EQ(32000, codec.clockrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 126, "CN", 32000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(105, codec.id);
ASSERT_EQ("CN", codec.name);
ASSERT_EQ(16000, codec.clockrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 105, "CN", 16000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(13, codec.id);
ASSERT_EQ("CN", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 13, "CN", 8000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(117, codec.id);
ASSERT_EQ("red", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 117, "red", 8000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e != NULL);
codec = AudioCodecFromPayloadType(e);
ASSERT_EQ(106, codec.id);
ASSERT_EQ("telephone-event", codec.name);
ASSERT_EQ(8000, codec.clockrate);
ASSERT_EQ(1, codec.channels);
VerifyAudioCodec(codec, 106, "telephone-event", 8000, 0, 1);
e = NextFromPayloadType(e);
ASSERT_TRUE(e == NULL);
@ -2073,6 +2131,14 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
ASSERT_EQ(talk_base::ToString(options.video_bandwidth / 1000),
bandwidth->BodyText());
}
buzz::XmlElement* e = PayloadTypeFromContent(content);
ASSERT_TRUE(e != NULL);
VideoCodec codec = VideoCodecFromPayloadType(e);
VideoCodec expected_codec = kVideoCodecs[0];
expected_codec.preference = codec.preference;
expected_codec.feedback_params = expected_video_fb_params_;
EXPECT_EQ(expected_codec, codec);
}
if (options.data_channel_type == cricket::DCT_RTP) {
@ -2705,6 +2771,28 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
expected_video_rtcp_mux_ = rtcp_mux;
}
template <class C>
void SetCodecFeedbackParams(std::vector<C>* codecs,
const FeedbackParams& fb_params) {
for (size_t i = 0; i < codecs->size(); ++i) {
codecs->at(i).feedback_params = fb_params;
}
}
void ExpectRtcpFb() {
FeedbackParams params_nack_fir;
params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamCcm,
cricket::kRtcpFbCcmParamFir));
params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamNack));
FeedbackParams params_nack;
params_nack.Add(FeedbackParam(cricket::kRtcpFbParamNack));
expected_audio_fb_params_ = params_nack;
expected_video_fb_params_ = params_nack_fir;
expected_data_fb_params_ = params_nack;
}
private:
void OnSendStanza(cricket::SessionManager* manager,
const buzz::XmlElement* stanza) {
@ -2749,6 +2837,9 @@ class MediaSessionClientTest : public sigslot::has_slots<> {
bool expect_outgoing_crypto_;
int expected_video_bandwidth_;
bool expected_video_rtcp_mux_;
FeedbackParams expected_audio_fb_params_;
FeedbackParams expected_video_fb_params_;
FeedbackParams expected_data_fb_params_;
cricket::MediaStreams last_streams_added_;
cricket::MediaStreams last_streams_removed_;
};
@ -2763,6 +2854,17 @@ MediaSessionClientTest* JingleTest() {
cricket::PROTOCOL_JINGLE);
}
TEST(MediaSessionTest, JingleGoodInitiateWithRtcpFb) {
talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
talk_base::scoped_ptr<buzz::XmlElement> elem;
cricket::CallOptions options = VideoCallOptions();
options.data_channel_type = cricket::DCT_SCTP;
test->ExpectRtcpFb();
test->TestGoodIncomingInitiate(
kJingleInitiateWithRtcpFb, options, elem.use());
}
TEST(MediaSessionTest, JingleGoodVideoInitiate) {
talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
talk_base::scoped_ptr<buzz::XmlElement> elem;