diff --git a/talk/app/webrtc/datachannel.cc b/talk/app/webrtc/datachannel.cc index 9195f8d47d..2b45845756 100644 --- a/talk/app/webrtc/datachannel.cc +++ b/talk/app/webrtc/datachannel.cc @@ -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(); diff --git a/talk/app/webrtc/datachannel.h b/talk/app/webrtc/datachannel.h index 440ee15ca5..28fe3446d5 100644 --- a/talk/app/webrtc/datachannel.h +++ b/talk/app/webrtc/datachannel.h @@ -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 queued_control_data_; std::queue queued_received_data_; std::deque queued_send_data_; }; diff --git a/talk/app/webrtc/datachannel_unittest.cc b/talk/app/webrtc/datachannel_unittest.cc index a2f898ed94..2b2609753a 100644 --- a/talk/app/webrtc/datachannel_unittest.cc +++ b/talk/app/webrtc/datachannel_unittest.cc @@ -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 { + 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 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 channel_manager_; - talk_base::scoped_ptr ms_signaling_; + talk_base::scoped_ptr media_stream_signaling_; webrtc::WebRtcSession session_; talk_base::scoped_refptr webrtc_data_channel_; }; diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc index 1397a7fc03..c199f1e9c7 100644 --- a/talk/app/webrtc/mediastreamsignaling.cc +++ b/talk/app/webrtc/mediastreamsignaling.cc @@ -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 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(label.length())); + buffer.WriteUInt16(static_cast(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) { diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h index 9ead8b096c..f2437d8fcf 100644 --- a/talk/app/webrtc/mediastreamsignaling.h +++ b/talk/app/webrtc/mediastreamsignaling.h @@ -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& 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_; diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index c91b7c89f7..2be76f439a 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -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 candidate; }; -struct CreateSessionDescriptionMsg : public talk_base::MessageData { - explicit CreateSessionDescriptionMsg( - webrtc::CreateSessionDescriptionObserver* observer) - : observer(observer) { - } - - talk_base::scoped_refptr observer; - std::string error; - talk_base::scoped_ptr 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 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 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 stun_config; std::vector 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(msg->pdata); - param->observer->OnSuccess(param->description.release()); - delete param; - break; - } - case MSG_CREATE_SESSIONDESCRIPTION_FAILED: { - CreateSessionDescriptionMsg* param = - static_cast(msg->pdata); - param->observer->OnFailure(param->error); - delete param; - break; - } case MSG_SET_SESSIONDESCRIPTION_SUCCESS: { SetSessionDescriptionMsg* param = static_cast(msg->pdata); diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h index 28aa9d82cc..9cc9f3868d 100644 --- a/talk/app/webrtc/peerconnection.h +++ b/talk/app/webrtc/peerconnection.h @@ -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 local_streams(); virtual talk_base::scoped_refptr 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 { diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc index 316b044087..9c5c126b1a 100644 --- a/talk/app/webrtc/peerconnectionfactory.cc +++ b/talk/app/webrtc/peerconnectionfactory.cc @@ -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 peerconnection; const webrtc::PeerConnectionInterface::IceServers& configuration; const webrtc::MediaConstraintsInterface* constraints; scoped_refptr allocator_factory; + webrtc::DTLSIdentityServiceInterface* dtls_identity_service; webrtc::PeerConnectionObserver* observer; }; @@ -199,11 +202,13 @@ void PeerConnectionFactory::OnMessage(talk_base::Message* msg) { } case MSG_CREATE_PEERCONNECTION: { CreatePeerConnectionParams* pdata = - static_cast(msg->pdata); - pdata->peerconnection = CreatePeerConnection_s(pdata->configuration, - pdata->constraints, - pdata->allocator_factory, - pdata->observer); + static_cast (msg->pdata); + pdata->peerconnection = CreatePeerConnection_s( + pdata->configuration, + pdata->constraints, + pdata->allocator_factory, + pdata->dtls_identity_service, + pdata->observer); break; } case MSG_CREATE_AUDIOSOURCE: { @@ -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, ¶ms); 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 pc( @@ -306,6 +313,7 @@ PeerConnectionFactory::CreatePeerConnection_s( configuration, constraints, allocator_factory ? allocator_factory : allocator_factory_.get(), + dtls_identity_service, observer)) { return NULL; } diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h index c0e15e375e..7faf609424 100644 --- a/talk/app/webrtc/peerconnectionfactory.h +++ b/talk/app/webrtc/peerconnectionfactory.h @@ -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); diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc index d0e5df3ceb..182c01c46d 100644 --- a/talk/app/webrtc/peerconnectionfactory_unittest.cc +++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc @@ -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); } diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h index 9a7cdd0c8e..bd86f3ed2f 100644 --- a/talk/app/webrtc/peerconnectioninterface.h +++ b/talk/app/webrtc/peerconnectioninterface.h @@ -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 diff --git a/talk/app/webrtc/portallocatorfactory.cc b/talk/app/webrtc/portallocatorfactory.cc index 71cd8358e6..32008f6b5b 100644 --- a/talk/app/webrtc/portallocatorfactory.cc +++ b/talk/app/webrtc/portallocatorfactory.cc @@ -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 { diff --git a/talk/app/webrtc/test/fakedtlsidentityservice.h b/talk/app/webrtc/test/fakedtlsidentityservice.h new file mode 100644 index 0000000000..16e1654217 --- /dev/null +++ b/talk/app/webrtc/test/fakedtlsidentityservice.h @@ -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 observer; + }; + typedef talk_base::TypedMessageData 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(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_ diff --git a/talk/app/webrtc/test/fakemediastreamsignaling.h b/talk/app/webrtc/test/fakemediastreamsignaling.h new file mode 100644 index 0000000000..2f6289dd6e --- /dev/null +++ b/talk/app/webrtc/test/fakemediastreamsignaling.h @@ -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 CreateStream( + const std::string& stream_label, + const std::string& audio_track_id, + const std::string& video_track_id) { + talk_base::scoped_refptr stream( + webrtc::MediaStream::Create(stream_label)); + + if (!audio_track_id.empty()) { + talk_base::scoped_refptr audio_track( + webrtc::AudioTrack::Create(audio_track_id, NULL)); + stream->AddTrack(audio_track); + } + + if (!video_track_id.empty()) { + talk_base::scoped_refptr video_track( + webrtc::VideoTrack::Create(video_track_id, NULL)); + stream->AddTrack(video_track); + } + return stream; + } +}; + +#endif // TALK_APP_WEBRTC_TEST_FAKEMEDIASTREAMSIGNALING_H_ diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons index 0cbe756c37..9b1af3cead 100644 --- a/talk/app/webrtc/webrtc.scons +++ b/talk/app/webrtc/webrtc.scons @@ -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 ) diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc index 4f5b22be91..b056757df4 100644 --- a/talk/app/webrtc/webrtcsession.cc +++ b/talk/app/webrtc/webrtcsession.cc @@ -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 (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,11 +374,12 @@ class IceRestartAnswerLatch { bool ice_restart_; }; -WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager, - talk_base::Thread* signaling_thread, - talk_base::Thread* worker_thread, - cricket::PortAllocator* port_allocator, - MediaStreamSignaling* mediastream_signaling) +WebRtcSession::WebRtcSession( + cricket::ChannelManager* channel_manager, + talk_base::Thread* signaling_thread, + talk_base::Thread* worker_thread, + cricket::PortAllocator* port_allocator, + MediaStreamSignaling* mediastream_signaling) : cricket::BaseSession(signaling_thread, worker_thread, port_allocator, talk_base::ToString(talk_base::CreateRandomId64() & LLONG_MAX), @@ -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( - constraints, - MediaConstraintsInterface::kEnableSctpDataChannels, - &value, NULL) && value && - // DTLS has to be enabled to use SCTP. - (transport_desc_factory_.secure() == cricket::SEC_ENABLED)) { - LOG(LS_INFO) << "Allowing SCTP data engine."; - data_channel_type_ = cricket::DCT_SCTP; + } else { + bool sctp_enabled = FindConstraint( + constraints, + MediaConstraintsInterface::kEnableSctpDataChannels, + &value, NULL) && value; + bool dtls_enabled = FindConstraint( + constraints, + MediaConstraintsInterface::kEnableDtlsSrtp, + &value, NULL) && value; + + // DTLS has to be enabled to use SCTP. + 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( - 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; - } +void WebRtcSession::CreateOffer(CreateSessionDescriptionObserver* observer, + const MediaConstraintsInterface* constraints) { + 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 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 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 WebRtcSession::CreateDataChannel( return NULL; } } + talk_base::scoped_refptr 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) { @@ -1401,8 +1329,13 @@ bool WebRtcSession::CreateVideoChannel(const cricket::ContentInfo* content) { 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); + this, content->name, rtcp, data_channel_type_)); + 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(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; } } diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h index d3997f3ff6..202ca66e12 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -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( - const MediaConstraintsInterface* constraints); - - SessionDescriptionInterface* CreateAnswer( - const MediaConstraintsInterface* constraints); - + void CreateOffer(CreateSessionDescriptionObserver* observer, + const MediaConstraintsInterface* constraints); + 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 video_channel_; talk_base::scoped_ptr 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 remote_desc_; // Candidates that arrived before the remote description was set. std::vector 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 ice_restart_latch_; + + talk_base::scoped_ptr + webrtc_session_desc_factory_; + sigslot::signal0<> SignalVoiceChannelDestroyed; sigslot::signal0<> SignalVideoChannelDestroyed; sigslot::signal0<> SignalDataChannelDestroyed; diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc index 71691df2f3..7c8af5216c 100644 --- a/talk/app/webrtc/webrtcsession_unittest.cc +++ b/talk/app/webrtc/webrtcsession_unittest.cc @@ -32,7 +32,10 @@ #include "talk/app/webrtc/streamcollection.h" #include "talk/app/webrtc/videotrack.h" #include "talk/app/webrtc/test/fakeconstraints.h" +#include "talk/app/webrtc/test/fakedtlsidentityservice.h" +#include "talk/app/webrtc/test/fakemediastreamsignaling.h" #include "talk/app/webrtc/webrtcsession.h" +#include "talk/app/webrtc/webrtcsessiondescriptionfactory.h" #include "talk/base/fakenetwork.h" #include "talk/base/firewallsocketserver.h" #include "talk/base/gunit.h" @@ -69,6 +72,10 @@ using cricket::TransportInfo; using talk_base::SocketAddress; using talk_base::scoped_ptr; using webrtc::CreateSessionDescription; +using webrtc::CreateSessionDescriptionObserver; +using webrtc::CreateSessionDescriptionRequest; +using webrtc::DTLSIdentityRequestObserver; +using webrtc::DTLSIdentityServiceInterface; using webrtc::FakeConstraints; using webrtc::IceCandidateCollection; using webrtc::JsepIceCandidate; @@ -76,6 +83,7 @@ using webrtc::JsepSessionDescription; using webrtc::PeerConnectionInterface; using webrtc::SessionDescriptionInterface; using webrtc::StreamCollection; +using webrtc::WebRtcSession; using webrtc::kMlineMismatch; using webrtc::kSdpWithoutCrypto; using webrtc::kSessionError; @@ -91,14 +99,6 @@ static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); static const char kSessionVersion[] = "1"; -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"; - // Media index of candidates belonging to the first media content. static const int kMediaContentIndex0 = 0; static const char kMediaContentName0[] = "audio"; @@ -202,118 +202,36 @@ class WebRtcSessionForTest : public webrtc::WebRtcSession { using webrtc::WebRtcSession::SetVideoSend; }; -class FakeMediaStreamSignaling : public webrtc::MediaStreamSignaling, - public webrtc::MediaStreamSignalingObserver { +class WebRtcSessionCreateSDPObserverForTest + : public talk_base::RefCountedObject { public: - FakeMediaStreamSignaling() : - webrtc::MediaStreamSignaling(talk_base::Thread::Current(), this) { + enum State { + kInit, + kFailed, + kSucceeded, + }; + WebRtcSessionCreateSDPObserverForTest() + : description_(NULL), state_(kInit) {} + + // CreateSessionDescriptionObserver implementation. + virtual void OnSuccess(SessionDescriptionInterface* desc) { + description_ = desc; + state_ = kSucceeded; + } + virtual void OnFailure(const std::string& error) { + state_ = kFailed; } - void SendAudioVideoStream1() { - ClearLocalStreams(); - AddLocalStream(CreateStream(kStream1, kAudioTrack1, kVideoTrack1)); - } + SessionDescriptionInterface* description() { return description_; } - void SendAudioVideoStream2() { - ClearLocalStreams(); - AddLocalStream(CreateStream(kStream2, kAudioTrack2, kVideoTrack2)); - } + State state() const { return state_; } - 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) { - } + protected: + ~WebRtcSessionCreateSDPObserverForTest() {} private: - talk_base::scoped_refptr CreateStream( - const std::string& stream_label, - const std::string& audio_track_id, - const std::string& video_track_id) { - talk_base::scoped_refptr stream( - webrtc::MediaStream::Create(stream_label)); - - if (!audio_track_id.empty()) { - talk_base::scoped_refptr audio_track( - webrtc::AudioTrack::Create(audio_track_id, NULL)); - stream->AddTrack(audio_track); - } - - if (!video_track_id.empty()) { - talk_base::scoped_refptr video_track( - webrtc::VideoTrack::Create(video_track_id, NULL)); - stream->AddTrack(video_track); - } - return stream; - } - - cricket::MediaSessionOptions options_; + SessionDescriptionInterface* description_; + State state_; }; class FakeAudioRenderer : public cricket::AudioRenderer { @@ -367,7 +285,7 @@ class WebRtcSessionTest : public testing::Test { network_manager_.AddInterface(addr); } - void Init() { + void Init(DTLSIdentityServiceInterface* identity_service) { ASSERT_TRUE(session_.get() == NULL); session_.reset(new WebRtcSessionForTest( channel_manager_.get(), talk_base::Thread::Current(), @@ -380,7 +298,7 @@ class WebRtcSessionTest : public testing::Test { EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew, observer_.ice_gathering_state_); - EXPECT_TRUE(session_->Initialize(constraints_.get())); + EXPECT_TRUE(session_->Initialize(constraints_.get(), identity_service)); } void InitWithDtmfCodec() { @@ -389,7 +307,7 @@ class WebRtcSessionTest : public testing::Test { codecs.push_back(kTelephoneEventCodec); media_engine_->SetAudioCodecs(codecs); desc_factory_->set_audio_codecs(codecs); - Init(); + Init(NULL); } void InitWithDtls() { @@ -397,20 +315,51 @@ class WebRtcSessionTest : public testing::Test { constraints_->AddOptional( webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true); - Init(); + Init(NULL); + } + + void InitWithAsyncDtls(bool identity_request_should_fail) { + constraints_.reset(new FakeConstraints()); + constraints_->AddOptional( + webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true); + FakeIdentityService* identity_service = new FakeIdentityService(); + identity_service->set_should_fail(identity_request_should_fail); + Init(identity_service); } // Creates a local offer and applies it. Starts ice. // Call mediastream_signaling_.UseOptionsWithStreamX() before this function // to decide which streams to create. void InitiateCall() { - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); EXPECT_TRUE_WAIT(PeerConnectionInterface::kIceGatheringNew != observer_.ice_gathering_state_, kIceCandidatesTimeout); } + SessionDescriptionInterface* CreateOffer( + const webrtc::MediaConstraintsInterface* constraints) { + talk_base::scoped_refptr + observer = new WebRtcSessionCreateSDPObserverForTest(); + session_->CreateOffer(observer, constraints); + EXPECT_TRUE_WAIT( + observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit, + 1000); + return observer->description(); + } + + SessionDescriptionInterface* CreateAnswer( + const webrtc::MediaConstraintsInterface* constraints) { + talk_base::scoped_refptr observer + = new WebRtcSessionCreateSDPObserverForTest(); + session_->CreateAnswer(observer, constraints); + EXPECT_TRUE_WAIT( + observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit, + 1000); + return observer->description(); + } + bool ChannelsExist() { return (session_->voice_channel() != NULL && session_->video_channel() != NULL); @@ -520,9 +469,8 @@ class WebRtcSessionTest : public testing::Test { ASSERT_TRUE(offer != NULL); VerifyNoCryptoParams(offer->description(), false); SetRemoteDescriptionExpectError("Called with a SDP without crypto enabled", - offer); - const webrtc::SessionDescriptionInterface* answer = - session_->CreateAnswer(NULL); + offer); + const webrtc::SessionDescriptionInterface* answer = CreateAnswer(NULL); // Answer should be NULL as no crypto params in offer. ASSERT_TRUE(answer == NULL); } @@ -536,8 +484,7 @@ class WebRtcSessionTest : public testing::Test { ASSERT_TRUE(offer.get() != NULL); VerifyCryptoParams(offer->description()); SetRemoteDescriptionWithoutError(offer.release()); - scoped_ptr answer( - session_->CreateAnswer(NULL)); + scoped_ptr answer(CreateAnswer(NULL)); ASSERT_TRUE(answer.get() != NULL); VerifyCryptoParams(answer->description()); } @@ -577,7 +524,7 @@ class WebRtcSessionTest : public testing::Test { void CreateAndSetRemoteOfferAndLocalAnswer() { SessionDescriptionInterface* offer = CreateRemoteOffer(); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); } void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) { @@ -709,11 +656,11 @@ class WebRtcSessionTest : public testing::Test { void TestSessionCandidatesWithBundleRtcpMux(bool bundle, bool rtcp_mux) { AddInterface(kClientAddr1); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); FakeConstraints constraints; constraints.SetMandatoryUseRtpMux(bundle); - SessionDescriptionInterface* offer = session_->CreateOffer(&constraints); + SessionDescriptionInterface* offer = CreateOffer(&constraints); // SetLocalDescription and SetRemoteDescriptions takes ownership of offer // and answer. SetLocalDescriptionWithoutError(offer); @@ -729,7 +676,6 @@ class WebRtcSessionTest : public testing::Test { // for rtp and rtcp. expected_candidate_num = 4; // Disable rtcp-mux from the answer - const std::string kRtcpMux = "a=rtcp-mux"; const std::string kXRtcpMux = "a=xrtcp-mux"; talk_base::replace_substrs(kRtcpMux.c_str(), kRtcpMux.length(), @@ -760,7 +706,7 @@ class WebRtcSessionTest : public testing::Test { if (can) { InitWithDtmfCodec(); } else { - Init(); + Init(NULL); } mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); @@ -780,9 +726,9 @@ class WebRtcSessionTest : public testing::Test { // The Gathering state should go: New -> Gathering -> Completed. void TestLoopbackCall() { AddInterface(kClientAddr1); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew, observer_.ice_gathering_state_); @@ -864,10 +810,48 @@ class WebRtcSessionTest : public testing::Test { webrtc::DataChannelInit dci; dci.reliable = false; session_->CreateDataChannel("datachannel", &dci); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); } + void VerifyMultipleAsyncCreateDescription( + bool success, CreateSessionDescriptionRequest::Type type) { + InitWithAsyncDtls(!success); + + if (type == CreateSessionDescriptionRequest::kAnswer) { + cricket::MediaSessionOptions options; + scoped_ptr offer( + CreateRemoteOffer(options, cricket::SEC_REQUIRED)); + ASSERT_TRUE(offer.get() != NULL); + SetRemoteDescriptionWithoutError(offer.release()); + } + + const int kNumber = 3; + talk_base::scoped_refptr + observers[kNumber]; + for (int i = 0; i < kNumber; ++i) { + observers[i] = new WebRtcSessionCreateSDPObserverForTest(); + if (type == CreateSessionDescriptionRequest::kOffer) { + session_->CreateOffer(observers[i], NULL); + } else { + session_->CreateAnswer(observers[i], NULL); + } + } + + WebRtcSessionCreateSDPObserverForTest::State expected_state = + success ? WebRtcSessionCreateSDPObserverForTest::kSucceeded : + WebRtcSessionCreateSDPObserverForTest::kFailed; + + for (int i = 0; i < kNumber; ++i) { + EXPECT_EQ_WAIT(expected_state, observers[i]->state(), 1000); + if (success) { + EXPECT_TRUE(observers[i]->description() != NULL); + } else { + EXPECT_TRUE(observers[i]->description() == NULL); + } + } + } + cricket::FakeMediaEngine* media_engine_; cricket::FakeDataEngine* data_engine_; cricket::FakeDeviceManager* device_manager_; @@ -891,13 +875,19 @@ class WebRtcSessionTest : public testing::Test { }; TEST_F(WebRtcSessionTest, TestInitialize) { - Init(); + Init(NULL); } TEST_F(WebRtcSessionTest, TestInitializeWithDtls) { InitWithDtls(); } +// Verifies that WebRtcSession uses SEC_REQUIRED by default. +TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) { + Init(NULL); + EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy()); +} + TEST_F(WebRtcSessionTest, TestSessionCandidates) { TestSessionCandidatesWithBundleRtcpMux(false, false); } @@ -915,7 +905,7 @@ TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundleRtcpMux) { TEST_F(WebRtcSessionTest, TestMultihomeCandidates) { AddInterface(kClientAddr1); AddInterface(kClientAddr2); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); InitiateCall(); EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); @@ -927,7 +917,7 @@ TEST_F(WebRtcSessionTest, TestStunError) { AddInterface(kClientAddr1); AddInterface(kClientAddr2); fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr1); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); InitiateCall(); // Since kClientAddr1 is blocked, not expecting stun candidates for it. @@ -939,9 +929,9 @@ TEST_F(WebRtcSessionTest, TestStunError) { // Test creating offers and receive answers and make sure the // media engine creates the expected send and receive streams. TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); const std::string session_id_orig = offer->session_id(); const std::string session_version_orig = offer->session_version(); SetLocalDescriptionWithoutError(offer); @@ -967,7 +957,7 @@ TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) { // Create new offer without send streams. mediastream_signaling_.SendNothing(); - offer = session_->CreateOffer(NULL); + offer = CreateOffer(NULL); // Verify the session id is the same and the session version is // increased. @@ -994,13 +984,13 @@ TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) { // Test receiving offers and creating answers and make sure the // media engine creates the expected send and receive streams. TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream2(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); const std::string session_id_orig = answer->session_id(); @@ -1021,12 +1011,12 @@ TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) { EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id); mediastream_signaling_.SendAudioVideoStream1And2(); - offer = session_->CreateOffer(NULL); + offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); // Answer by turning off all send streams. mediastream_signaling_.SendNothing(); - answer = session_->CreateAnswer(NULL); + answer = CreateAnswer(NULL); // Verify the session id is the same and the session version is // increased. @@ -1050,7 +1040,7 @@ TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) { // Test we will return fail when apply an offer that doesn't have // crypto enabled. TEST_F(WebRtcSessionTest, SetNonCryptoOffer) { - Init(); + Init(NULL); cricket::MediaSessionOptions options; options.has_video = true; JsepSessionDescription* offer = CreateRemoteOffer( @@ -1068,7 +1058,7 @@ TEST_F(WebRtcSessionTest, SetNonCryptoOffer) { // Test we will return fail when apply an answer that doesn't have // crypto enabled. TEST_F(WebRtcSessionTest, SetLocalNonCryptoAnswer) { - Init(); + Init(NULL); SessionDescriptionInterface* offer = NULL; SessionDescriptionInterface* answer = NULL; CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer); @@ -1081,7 +1071,7 @@ TEST_F(WebRtcSessionTest, SetLocalNonCryptoAnswer) { // Test we will return fail when apply an answer that doesn't have // crypto enabled. TEST_F(WebRtcSessionTest, SetRemoteNonCryptoAnswer) { - Init(); + Init(NULL); SessionDescriptionInterface* offer = NULL; SessionDescriptionInterface* answer = NULL; CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer); @@ -1096,7 +1086,7 @@ TEST_F(WebRtcSessionTest, CreateSetDtlsOffer) { MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); InitWithDtls(); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); ASSERT_TRUE(offer != NULL); VerifyFingerprintStatus(offer->description(), true); // SetLocalDescription will take the ownership of the offer. @@ -1119,7 +1109,7 @@ TEST_F(WebRtcSessionTest, ReceiveDtlsOfferCreateAnswer) { SetRemoteDescriptionWithoutError(offer); // Verify that we get a crypto fingerprint in the answer. - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); ASSERT_TRUE(answer != NULL); VerifyFingerprintStatus(answer->description(), true); // Check that we don't have an a=crypto line in the answer. @@ -1146,7 +1136,7 @@ TEST_F(WebRtcSessionTest, ReceiveNoDtlsOfferCreateAnswer) { SetRemoteDescriptionWithoutError(offer); // Verify that we don't get a crypto fingerprint in the answer. - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); ASSERT_TRUE(answer != NULL); VerifyFingerprintStatus(answer->description(), false); @@ -1155,45 +1145,45 @@ TEST_F(WebRtcSessionTest, ReceiveNoDtlsOfferCreateAnswer) { } TEST_F(WebRtcSessionTest, TestSetLocalOfferTwice) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer2 = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer2 = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer2); } TEST_F(WebRtcSessionTest, TestSetRemoteOfferTwice) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* offer2 = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer2 = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer2); } TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); - offer = session_->CreateOffer(NULL); + offer = CreateOffer(NULL); SetRemoteDescriptionExpectError( "Called with type in wrong state, type: offer state: STATE_SENTINITIATE", offer); } TEST_F(WebRtcSessionTest, TestSetRemoteAndLocalOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); - offer = session_->CreateOffer(NULL); + offer = CreateOffer(NULL); SetLocalDescriptionExpectError( "Called with type in wrong state, type: " "offer state: STATE_RECEIVEDINITIATE", @@ -1201,32 +1191,32 @@ TEST_F(WebRtcSessionTest, TestSetRemoteAndLocalOffer) { } TEST_F(WebRtcSessionTest, TestSetLocalPrAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); SessionDescriptionInterface* offer = CreateRemoteOffer(); SetRemoteDescriptionExpectState(offer, BaseSession::STATE_RECEIVEDINITIATE); JsepSessionDescription* pranswer = static_cast( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); pranswer->set_type(SessionDescriptionInterface::kPrAnswer); SetLocalDescriptionExpectState(pranswer, BaseSession::STATE_SENTPRACCEPT); mediastream_signaling_.SendAudioVideoStream1(); JsepSessionDescription* pranswer2 = static_cast( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); pranswer2->set_type(SessionDescriptionInterface::kPrAnswer); SetLocalDescriptionExpectState(pranswer2, BaseSession::STATE_SENTPRACCEPT); mediastream_signaling_.SendAudioVideoStream2(); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionExpectState(answer, BaseSession::STATE_SENTACCEPT); } TEST_F(WebRtcSessionTest, TestSetRemotePrAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionExpectState(offer, BaseSession::STATE_SENTINITIATE); JsepSessionDescription* pranswer = @@ -1251,10 +1241,10 @@ TEST_F(WebRtcSessionTest, TestSetRemotePrAnswer) { } TEST_F(WebRtcSessionTest, TestSetLocalAnswerWithoutOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer.get()); SetLocalDescriptionExpectError( @@ -1263,10 +1253,10 @@ TEST_F(WebRtcSessionTest, TestSetLocalAnswerWithoutOffer) { } TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithoutOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendNothing(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer.get()); SetRemoteDescriptionExpectError( @@ -1275,7 +1265,7 @@ TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithoutOffer) { } TEST_F(WebRtcSessionTest, TestAddRemoteCandidate) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); cricket::Candidate candidate; @@ -1285,7 +1275,7 @@ TEST_F(WebRtcSessionTest, TestAddRemoteCandidate) { // Fail since we have not set a offer description. EXPECT_FALSE(session_->ProcessIceMessage(&ice_candidate1)); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); // Candidate should be allowed to add before remote description. EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); @@ -1322,7 +1312,7 @@ TEST_F(WebRtcSessionTest, TestAddRemoteCandidate) { // Test that a remote candidate is added to the remote session description and // that it is retained if the remote session description is changed. TEST_F(WebRtcSessionTest, TestRemoteCandidatesAddedToSessionDescription) { - Init(); + Init(NULL); cricket::Candidate candidate1; candidate1.set_component(1); JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0, @@ -1375,7 +1365,7 @@ TEST_F(WebRtcSessionTest, TestRemoteCandidatesAddedToSessionDescription) { // that they are retained if the local session description is changed. TEST_F(WebRtcSessionTest, TestLocalCandidatesAddedToSessionDescription) { AddInterface(kClientAddr1); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); @@ -1410,14 +1400,14 @@ TEST_F(WebRtcSessionTest, TestLocalCandidatesAddedToSessionDescription) { // Test that we can set a remote session description with remote candidates. TEST_F(WebRtcSessionTest, TestSetRemoteSessionDescriptionWithCandidates) { - Init(); + Init(NULL); cricket::Candidate candidate1; candidate1.set_component(1); JsepIceCandidate ice_candidate(kMediaContentName0, kMediaContentIndex0, candidate1); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); EXPECT_TRUE(offer->AddCandidate(&ice_candidate)); SetRemoteDescriptionWithoutError(offer); @@ -1431,7 +1421,7 @@ TEST_F(WebRtcSessionTest, TestSetRemoteSessionDescriptionWithCandidates) { ASSERT_EQ(1u, candidates->count()); EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index()); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); } @@ -1439,7 +1429,7 @@ TEST_F(WebRtcSessionTest, TestSetRemoteSessionDescriptionWithCandidates) { // been gathered. TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { AddInterface(kClientAddr1); - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); // Ice is started but candidates are not provided until SetLocalDescription // is called. @@ -1453,7 +1443,7 @@ TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { kIceCandidatesTimeout); talk_base::scoped_ptr local_offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); ASSERT_TRUE(local_offer->candidates(kMediaContentIndex0) != NULL); EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex0)->count()); ASSERT_TRUE(local_offer->candidates(kMediaContentIndex1) != NULL); @@ -1461,7 +1451,7 @@ TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { SessionDescriptionInterface* remote_offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(remote_offer); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); ASSERT_TRUE(answer->candidates(kMediaContentIndex0) != NULL); EXPECT_LT(0u, answer->candidates(kMediaContentIndex0)->count()); ASSERT_TRUE(answer->candidates(kMediaContentIndex1) != NULL); @@ -1472,10 +1462,10 @@ TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { // Verifies TransportProxy and media channels are created with content names // present in the SessionDescription. TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); // CreateOffer creates session description with the content names "audio" and // "video". Goal is to modify these content names and verify transport channel @@ -1505,7 +1495,7 @@ TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { SetRemoteDescriptionWithoutError(modified_offer); SessionDescriptionInterface* answer = - session_->CreateAnswer(NULL); + CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); EXPECT_TRUE(session_->GetTransportProxy("audio_content_name") != NULL); @@ -1517,9 +1507,9 @@ TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { // Test that an offer contains the correct media content descriptions based on // the send streams when no constraints have been set. TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraintsOrStreams) { - Init(); + Init(NULL); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); ASSERT_TRUE(offer != NULL); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); @@ -1531,11 +1521,11 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraintsOrStreams) { // Test that an offer contains the correct media content descriptions based on // the send streams when no constraints have been set. TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) { - Init(); + Init(NULL); // Test Audio only offer. mediastream_signaling_.UseOptionsAudioOnly(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); EXPECT_TRUE(content != NULL); @@ -1544,7 +1534,7 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) { // Test Audio / Video offer. mediastream_signaling_.SendAudioVideoStream1(); - offer.reset(session_->CreateOffer(NULL)); + offer.reset(CreateOffer(NULL)); content = cricket::GetFirstAudioContent(offer->description()); EXPECT_TRUE(content != NULL); content = cricket::GetFirstVideoContent(offer->description()); @@ -1554,13 +1544,13 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) { // Test that an offer contains no media content descriptions if // kOfferToReceiveVideo and kOfferToReceiveAudio constraints are set to false. TEST_F(WebRtcSessionTest, CreateOfferWithConstraintsWithoutStreams) { - Init(); + Init(NULL); webrtc::FakeConstraints constraints_no_receive; constraints_no_receive.SetMandatoryReceiveAudio(false); constraints_no_receive.SetMandatoryReceiveVideo(false); talk_base::scoped_ptr offer( - session_->CreateOffer(&constraints_no_receive)); + CreateOffer(&constraints_no_receive)); ASSERT_TRUE(offer != NULL); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); @@ -1572,11 +1562,11 @@ TEST_F(WebRtcSessionTest, CreateOfferWithConstraintsWithoutStreams) { // Test that an offer contains only audio media content descriptions if // kOfferToReceiveAudio constraints are set to true. TEST_F(WebRtcSessionTest, CreateAudioOnlyOfferWithConstraints) { - Init(); + Init(NULL); webrtc::FakeConstraints constraints_audio_only; constraints_audio_only.SetMandatoryReceiveAudio(true); talk_base::scoped_ptr offer( - session_->CreateOffer(&constraints_audio_only)); + CreateOffer(&constraints_audio_only)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); @@ -1588,13 +1578,13 @@ TEST_F(WebRtcSessionTest, CreateAudioOnlyOfferWithConstraints) { // Test that an offer contains audio and video media content descriptions if // kOfferToReceiveAudio and kOfferToReceiveVideo constraints are set to true. TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) { - Init(); + Init(NULL); // Test Audio / Video offer. webrtc::FakeConstraints constraints_audio_video; constraints_audio_video.SetMandatoryReceiveAudio(true); constraints_audio_video.SetMandatoryReceiveVideo(true); talk_base::scoped_ptr offer( - session_->CreateOffer(&constraints_audio_video)); + CreateOffer(&constraints_audio_video)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); @@ -1609,23 +1599,23 @@ TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) { // Test that an answer can not be created if the last remote description is not // an offer. TEST_F(WebRtcSessionTest, CreateAnswerWithoutAnOffer) { - Init(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + Init(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); SetRemoteDescriptionWithoutError(answer); - EXPECT_TRUE(session_->CreateAnswer(NULL) == NULL); + EXPECT_TRUE(CreateAnswer(NULL) == NULL); } // Test that an answer contains the correct media content descriptions when no // constraints have been set. TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraintsOrStreams) { - Init(); + Init(NULL); // Create a remote offer with audio and video content. talk_base::scoped_ptr offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(offer.release()); talk_base::scoped_ptr answer( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(answer->description()); ASSERT_TRUE(content != NULL); @@ -1639,7 +1629,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraintsOrStreams) { // Test that an answer contains the correct media content descriptions when no // constraints have been set and the offer only contain audio. TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { - Init(); + Init(NULL); // Create a remote offer with audio only. cricket::MediaSessionOptions options; options.has_audio = true; @@ -1651,7 +1641,7 @@ TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { SetRemoteDescriptionWithoutError(offer.release()); talk_base::scoped_ptr answer( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(answer->description()); ASSERT_TRUE(content != NULL); @@ -1663,14 +1653,14 @@ TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { // Test that an answer contains the correct media content descriptions when no // constraints have been set. TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraints) { - Init(); + Init(NULL); // Create a remote offer with audio and video content. talk_base::scoped_ptr offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(offer.release()); // Test with a stream with tracks. mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr answer( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(answer->description()); ASSERT_TRUE(content != NULL); @@ -1684,7 +1674,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraints) { // Test that an answer contains the correct media content descriptions when // constraints have been set but no stream is sent. TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) { - Init(); + Init(NULL); // Create a remote offer with audio and video content. talk_base::scoped_ptr offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(offer.release()); @@ -1694,7 +1684,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) { constraints_no_receive.SetMandatoryReceiveVideo(false); talk_base::scoped_ptr answer( - session_->CreateAnswer(&constraints_no_receive)); + CreateAnswer(&constraints_no_receive)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(answer->description()); ASSERT_TRUE(content != NULL); @@ -1708,7 +1698,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) { // Test that an answer contains the correct media content descriptions when // constraints have been set and streams are sent. TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) { - Init(); + Init(NULL); // Create a remote offer with audio and video content. talk_base::scoped_ptr offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(offer.release()); @@ -1720,7 +1710,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) { // Test with a stream with tracks. mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr answer( - session_->CreateAnswer(&constraints_no_receive)); + CreateAnswer(&constraints_no_receive)); // TODO(perkj): Should the direction be set to SEND_ONLY? const cricket::ContentInfo* content = @@ -1736,11 +1726,11 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) { TEST_F(WebRtcSessionTest, CreateOfferWithoutCNCodecs) { AddCNCodecs(); - Init(); + Init(NULL); webrtc::FakeConstraints constraints; constraints.SetOptionalVAD(false); talk_base::scoped_ptr offer( - session_->CreateOffer(&constraints)); + CreateOffer(&constraints)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); EXPECT_TRUE(content != NULL); @@ -1749,7 +1739,7 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutCNCodecs) { TEST_F(WebRtcSessionTest, CreateAnswerWithoutCNCodecs) { AddCNCodecs(); - Init(); + Init(NULL); // Create a remote offer with audio and video content. talk_base::scoped_ptr offer(CreateRemoteOffer()); SetRemoteDescriptionWithoutError(offer.release()); @@ -1757,7 +1747,7 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithoutCNCodecs) { webrtc::FakeConstraints constraints; constraints.SetOptionalVAD(false); talk_base::scoped_ptr answer( - session_->CreateAnswer(&constraints)); + CreateAnswer(&constraints)); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(answer->description()); ASSERT_TRUE(content != NULL); @@ -1767,12 +1757,12 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithoutCNCodecs) { // This test verifies the call setup when remote answer with audio only and // later updates with video. TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { - Init(); + Init(NULL); EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL); EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); cricket::MediaSessionOptions options; options.has_video = false; @@ -1825,11 +1815,11 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { // This test verifies the call setup when remote answer with video only and // later updates with audio. TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) { - Init(); + Init(NULL); EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL); EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); cricket::MediaSessionOptions options; options.has_audio = false; @@ -1877,39 +1867,33 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) { EXPECT_EQ(kVideoTrack2, video_channel_->send_streams()[0].id); } -TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) { - Init(); - EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy()); -} - TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); VerifyCryptoParams(offer->description()); SetRemoteDescriptionWithoutError(offer.release()); - scoped_ptr answer( - session_->CreateAnswer(NULL)); + scoped_ptr answer(CreateAnswer(NULL)); VerifyCryptoParams(answer->description()); } TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) { - Init(); + Init(NULL); session_->set_secure_policy(cricket::SEC_DISABLED); mediastream_signaling_.SendAudioVideoStream1(); scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); VerifyNoCryptoParams(offer->description(), false); } TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) { - Init(); + Init(NULL); VerifyAnswerFromNonCryptoOffer(); } TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) { - Init(); + Init(NULL); VerifyAnswerFromCryptoOffer(); } @@ -1917,11 +1901,11 @@ TEST_F(WebRtcSessionTest, VerifyBundleFlagInPA) { // This test verifies BUNDLE flag in PortAllocator, if BUNDLE information in // local description is removed by the application, BUNDLE flag should be // disabled in PortAllocator. By default BUNDLE is enabled in the WebRtc. - Init(); + Init(NULL); EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); cricket::SessionDescription* offer_copy = offer->description()->Copy(); offer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); @@ -1934,13 +1918,13 @@ TEST_F(WebRtcSessionTest, VerifyBundleFlagInPA) { } TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); FakeConstraints constraints; constraints.SetMandatoryUseRtpMux(true); - SessionDescriptionInterface* offer = session_->CreateOffer(&constraints); + SessionDescriptionInterface* offer = CreateOffer(&constraints); SetLocalDescriptionWithoutError(offer); mediastream_signaling_.SendAudioVideoStream2(); talk_base::scoped_ptr answer( @@ -1972,13 +1956,13 @@ TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) { // This test verifies that SetLocalDescription and SetRemoteDescription fails // if BUNDLE is enabled but rtcp-mux is disabled in m-lines. TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { - WebRtcSessionTest::Init(); + WebRtcSessionTest::Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); FakeConstraints constraints; constraints.SetMandatoryUseRtpMux(true); - SessionDescriptionInterface* offer = session_->CreateOffer(&constraints); + SessionDescriptionInterface* offer = CreateOffer(&constraints); std::string offer_str; offer->ToString(&offer_str); // Disable rtcp-mux @@ -2000,7 +1984,7 @@ TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { } TEST_F(WebRtcSessionTest, SetAudioPlayout) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); @@ -2025,7 +2009,7 @@ TEST_F(WebRtcSessionTest, SetAudioPlayout) { } TEST_F(WebRtcSessionTest, SetAudioSend) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); @@ -2052,7 +2036,7 @@ TEST_F(WebRtcSessionTest, SetAudioSend) { } TEST_F(WebRtcSessionTest, SetVideoPlayout) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0); @@ -2069,7 +2053,7 @@ TEST_F(WebRtcSessionTest, SetVideoPlayout) { } TEST_F(WebRtcSessionTest, SetVideoSend) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0); @@ -2094,7 +2078,7 @@ TEST_F(WebRtcSessionTest, CanInsertDtmf) { TEST_F(WebRtcSessionTest, InsertDtmf) { // Setup - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); CreateAndSetRemoteOfferAndLocalAnswer(); FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); @@ -2120,9 +2104,9 @@ TEST_F(WebRtcSessionTest, InsertDtmf) { // This test verifies the |initiator| flag when session initiates the call. TEST_F(WebRtcSessionTest, TestInitiatorFlagAsOriginator) { - Init(); + Init(NULL); EXPECT_FALSE(session_->initiator()); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); SetLocalDescriptionWithoutError(offer); EXPECT_TRUE(session_->initiator()); @@ -2132,11 +2116,11 @@ TEST_F(WebRtcSessionTest, TestInitiatorFlagAsOriginator) { // This test verifies the |initiator| flag when session receives the call. TEST_F(WebRtcSessionTest, TestInitiatorFlagAsReceiver) { - Init(); + Init(NULL); EXPECT_FALSE(session_->initiator()); SessionDescriptionInterface* offer = CreateRemoteOffer(); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); EXPECT_FALSE(session_->initiator()); SetLocalDescriptionWithoutError(answer); @@ -2146,11 +2130,11 @@ TEST_F(WebRtcSessionTest, TestInitiatorFlagAsReceiver) { // This test verifies the ice protocol type at initiator of the call // if |a=ice-options:google-ice| is present in answer. TEST_F(WebRtcSessionTest, TestInitiatorGIceInAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); talk_base::scoped_ptr answer( - CreateRemoteAnswer(offer)); + CreateRemoteAnswer(offer)); SetLocalDescriptionWithoutError(offer); std::string sdp; EXPECT_TRUE(answer->ToString(&sdp)); @@ -2168,9 +2152,9 @@ TEST_F(WebRtcSessionTest, TestInitiatorGIceInAnswer) { // This test verifies the ice protocol type at initiator of the call // if ICE RFC5245 is supported in answer. TEST_F(WebRtcSessionTest, TestInitiatorIceInAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); SetLocalDescriptionWithoutError(offer); @@ -2182,12 +2166,12 @@ TEST_F(WebRtcSessionTest, TestInitiatorIceInAnswer) { // This test verifies the ice protocol type at receiver side of the call if // receiver decides to use google-ice. TEST_F(WebRtcSessionTest, TestReceiverGIceInOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); talk_base::scoped_ptr answer( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); std::string sdp; EXPECT_TRUE(answer->ToString(&sdp)); // Adding ice-options to the session level. @@ -2204,11 +2188,11 @@ TEST_F(WebRtcSessionTest, TestReceiverGIceInOffer) { // This test verifies the ice protocol type at receiver side of the call if // receiver decides to use ice RFC 5245. TEST_F(WebRtcSessionTest, TestReceiverIceInOffer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); VerifyTransportType("audio", cricket::ICEPROTO_RFC5245); VerifyTransportType("video", cricket::ICEPROTO_RFC5245); @@ -2217,10 +2201,10 @@ TEST_F(WebRtcSessionTest, TestReceiverIceInOffer) { // This test verifies the session state when ICE RFC5245 in offer and // ICE google-ice in answer. TEST_F(WebRtcSessionTest, TestIceOfferGIceOnlyAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); std::string offer_str; offer->ToString(&offer_str); // Disable google-ice @@ -2249,9 +2233,9 @@ TEST_F(WebRtcSessionTest, TestIceOfferGIceOnlyAnswer) { // Verifing local offer and remote answer have matching m-lines as per RFC 3264. TEST_F(WebRtcSessionTest, TestIncorrectMLinesInRemoteAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); SetLocalDescriptionWithoutError(offer); talk_base::scoped_ptr answer( CreateRemoteAnswer(session_->local_description())); @@ -2288,11 +2272,11 @@ TEST_F(WebRtcSessionTest, TestIncorrectMLinesInRemoteAnswer) { // Verifying remote offer and local answer have matching m-lines as per // RFC 3264. TEST_F(WebRtcSessionTest, TestIncorrectMLinesInLocalAnswer) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); SessionDescriptionInterface* offer = CreateRemoteOffer(); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); cricket::SessionDescription* answer_copy = answer->description()->Copy(); answer_copy->RemoveContentByName("video"); @@ -2309,7 +2293,7 @@ TEST_F(WebRtcSessionTest, TestIncorrectMLinesInLocalAnswer) { // This test verifies that WebRtcSession does not start candidate allocation // before SetLocalDescription is called. TEST_F(WebRtcSessionTest, TestIceStartAfterSetLocalDescriptionOnly) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); SessionDescriptionInterface* offer = CreateRemoteOffer(); cricket::Candidate candidate; @@ -2331,7 +2315,7 @@ TEST_F(WebRtcSessionTest, TestIceStartAfterSetLocalDescriptionOnly) { EXPECT_TRUE(observer_.mline_0_candidates_.empty()); EXPECT_TRUE(observer_.mline_1_candidates_.empty()); - SessionDescriptionInterface* answer = session_->CreateAnswer(NULL); + SessionDescriptionInterface* answer = CreateAnswer(NULL); SetLocalDescriptionWithoutError(answer); EXPECT_TRUE(session_->GetTransportProxy("audio")->negotiated()); EXPECT_TRUE(session_->GetTransportProxy("video")->negotiated()); @@ -2341,10 +2325,10 @@ TEST_F(WebRtcSessionTest, TestIceStartAfterSetLocalDescriptionOnly) { // This test verifies that crypto parameter is updated in local session // description as per security policy set in MediaSessionDescriptionFactory. TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescription) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); // Making sure SetLocalDescription correctly sets crypto value in // SessionDescription object after de-serialization of sdp string. The value @@ -2360,11 +2344,11 @@ TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescription) { // This test verifies the crypto parameter when security is disabled. TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); session_->set_secure_policy(cricket::SEC_DISABLED); talk_base::scoped_ptr offer( - session_->CreateOffer(NULL)); + CreateOffer(NULL)); // Making sure SetLocalDescription correctly sets crypto value in // SessionDescription object after de-serialization of sdp string. The value @@ -2381,7 +2365,7 @@ TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) { // This test verifies that an answer contains new ufrag and password if an offer // with new ufrag and password is received. TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { - Init(); + Init(NULL); cricket::MediaSessionOptions options; options.has_audio = true; options.has_video = true; @@ -2391,34 +2375,49 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { mediastream_signaling_.SendAudioVideoStream1(); talk_base::scoped_ptr answer( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); SetLocalDescriptionWithoutError(answer.release()); // Receive an offer with new ufrag and password. options.transport_options.ice_restart = true; talk_base::scoped_ptr updated_offer1( - CreateRemoteOffer(options, - session_->remote_description())); + CreateRemoteOffer(options, session_->remote_description())); SetRemoteDescriptionWithoutError(updated_offer1.release()); talk_base::scoped_ptr updated_answer1( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); CompareIceUfragAndPassword(updated_answer1->description(), session_->local_description()->description(), false); SetLocalDescriptionWithoutError(updated_answer1.release()); +} - // Receive yet an offer without changed ufrag or password. +// This test verifies that an answer contains old ufrag and password if an offer +// with old ufrag and password is received. +TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) { + Init(NULL); + cricket::MediaSessionOptions options; + options.has_audio = true; + options.has_video = true; + talk_base::scoped_ptr offer( + CreateRemoteOffer(options)); + SetRemoteDescriptionWithoutError(offer.release()); + + mediastream_signaling_.SendAudioVideoStream1(); + talk_base::scoped_ptr answer( + CreateAnswer(NULL)); + SetLocalDescriptionWithoutError(answer.release()); + + // Receive an offer without changed ufrag or password. options.transport_options.ice_restart = false; talk_base::scoped_ptr updated_offer2( - CreateRemoteOffer(options, - session_->remote_description())); + CreateRemoteOffer(options, session_->remote_description())); SetRemoteDescriptionWithoutError(updated_offer2.release()); talk_base::scoped_ptr updated_answer2( - session_->CreateAnswer(NULL)); + CreateAnswer(NULL)); CompareIceUfragAndPassword(updated_answer2->description(), session_->local_description()->description(), @@ -2428,9 +2427,9 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { } TEST_F(WebRtcSessionTest, TestSessionContentError) { - Init(); + Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - SessionDescriptionInterface* offer = session_->CreateOffer(NULL); + SessionDescriptionInterface* offer = CreateOffer(NULL); const std::string session_id_orig = offer->session_id(); const std::string session_version_orig = offer->session_version(); SetLocalDescriptionWithoutError(offer); @@ -2456,7 +2455,7 @@ TEST_F(WebRtcSessionTest, TestIceStatesBasic) { // Regression-test for a crash which should have been an error. TEST_F(WebRtcSessionTest, TestNoStateTransitionPendingError) { - Init(); + Init(NULL); cricket::MediaSessionOptions options; options.has_audio = true; options.has_video = true; @@ -2474,7 +2473,7 @@ TEST_F(WebRtcSessionTest, TestRtpDataChannel) { constraints_.reset(new FakeConstraints()); constraints_->AddOptional( webrtc::MediaConstraintsInterface::kEnableRtpDataChannels, true); - Init(); + Init(NULL); SetLocalDescriptionWithDataChannel(); EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type()); @@ -2490,7 +2489,7 @@ TEST_F(WebRtcSessionTest, TestRtpDataChannelConstraintTakesPrecedence) { webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true); constraints_->AddOptional( webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true); - Init(); + Init(NULL); SetLocalDescriptionWithDataChannel(); EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type()); @@ -2500,7 +2499,7 @@ TEST_F(WebRtcSessionTest, TestSctpDataChannelWithoutDtls) { constraints_.reset(new FakeConstraints()); constraints_->AddOptional( webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true); - Init(); + Init(NULL); SetLocalDescriptionWithDataChannel(); EXPECT_EQ(cricket::DCT_NONE, data_engine_->last_channel_type()); @@ -2514,11 +2513,96 @@ TEST_F(WebRtcSessionTest, TestSctpDataChannelWithDtls) { webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true); constraints_->AddOptional( webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true); - Init(); + Init(NULL); SetLocalDescriptionWithDataChannel(); EXPECT_EQ(cricket::DCT_SCTP, data_engine_->last_channel_type()); } + +// Verifies that CreateOffer succeeds when CreateOffer is called before async +// identity generation is finished. +TEST_F(WebRtcSessionTest, TestCreateOfferBeforeIdentityRequestReturnSuccess) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + InitWithAsyncDtls(false); + + EXPECT_TRUE(session_->waiting_for_identity()); + talk_base::scoped_ptr offer(CreateOffer(NULL)); + EXPECT_TRUE(offer != NULL); +} + +// Verifies that CreateAnswer succeeds when CreateOffer is called before async +// identity generation is finished. +TEST_F(WebRtcSessionTest, TestCreateAnswerBeforeIdentityRequestReturnSuccess) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + InitWithAsyncDtls(false); + + cricket::MediaSessionOptions options; + scoped_ptr offer( + CreateRemoteOffer(options, cricket::SEC_REQUIRED)); + ASSERT_TRUE(offer.get() != NULL); + SetRemoteDescriptionWithoutError(offer.release()); + + talk_base::scoped_ptr answer(CreateAnswer(NULL)); + EXPECT_TRUE(answer != NULL); +} + +// Verifies that CreateOffer succeeds when CreateOffer is called after async +// identity generation is finished. +TEST_F(WebRtcSessionTest, TestCreateOfferAfterIdentityRequestReturnSuccess) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + InitWithAsyncDtls(false); + + EXPECT_TRUE_WAIT(!session_->waiting_for_identity(), 1000); + talk_base::scoped_ptr offer(CreateOffer(NULL)); + EXPECT_TRUE(offer != NULL); +} + +// Verifies that CreateOffer fails when CreateOffer is called after async +// identity generation fails. +TEST_F(WebRtcSessionTest, TestCreateOfferAfterIdentityRequestReturnFailure) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + InitWithAsyncDtls(true); + + EXPECT_TRUE_WAIT(!session_->waiting_for_identity(), 1000); + talk_base::scoped_ptr offer(CreateOffer(NULL)); + EXPECT_TRUE(offer == NULL); +} + +// Verifies that CreateOffer succeeds when Multiple CreateOffer calls are made +// before async identity generation is finished. +TEST_F(WebRtcSessionTest, + TestMultipleCreateOfferBeforeIdentityRequestReturnSuccess) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + VerifyMultipleAsyncCreateDescription( + true, CreateSessionDescriptionRequest::kOffer); +} + +// Verifies that CreateOffer fails when Multiple CreateOffer calls are made +// before async identity generation fails. +TEST_F(WebRtcSessionTest, + TestMultipleCreateOfferBeforeIdentityRequestReturnFailure) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + VerifyMultipleAsyncCreateDescription( + false, CreateSessionDescriptionRequest::kOffer); +} + +// Verifies that CreateAnswer succeeds when Multiple CreateAnswer calls are made +// before async identity generation is finished. +TEST_F(WebRtcSessionTest, + TestMultipleCreateAnswerBeforeIdentityRequestReturnSuccess) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + VerifyMultipleAsyncCreateDescription( + true, CreateSessionDescriptionRequest::kAnswer); +} + +// Verifies that CreateAnswer fails when Multiple CreateAnswer calls are made +// before async identity generation fails. +TEST_F(WebRtcSessionTest, + TestMultipleCreateAnswerBeforeIdentityRequestReturnFailure) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + VerifyMultipleAsyncCreateDescription( + false, CreateSessionDescriptionRequest::kAnswer); +} // TODO(bemasc): Add a TestIceStatesBundle with BUNDLE enabled. That test // currently fails because upon disconnection and reconnection OnIceComplete is // called more than once without returning to IceGatheringGathering. diff --git a/talk/app/webrtc/webrtcsessiondescriptionfactory.cc b/talk/app/webrtc/webrtcsessiondescriptionfactory.cc new file mode 100644 index 0000000000..2021085aa2 --- /dev/null +++ b/talk/app/webrtc/webrtcsessiondescriptionfactory.cc @@ -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 observer; + std::string error; + talk_base::scoped_ptr 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(); + + 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(msg->pdata); + param->observer->OnSuccess(param->description.release()); + delete param; + break; + } + case MSG_CREATE_SESSIONDESCRIPTION_FAILED: { + CreateSessionDescriptionMsg* param = + static_cast(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(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(session_)->remote_description(), + request.options, + static_cast(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(der_cert.data()), + der_cert.length()); + std::string pem_key = talk_base::SSLIdentity::DerToPem( + talk_base::kPemTypeRsaPrivateKey, + reinterpret_cast(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 diff --git a/talk/app/webrtc/webrtcsessiondescriptionfactory.h b/talk/app/webrtc/webrtcsessiondescriptionfactory.h new file mode 100644 index 0000000000..0c96c69b63 --- /dev/null +++ b/talk/app/webrtc/webrtcsessiondescriptionfactory.h @@ -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 SignalRequestFailed; + sigslot::signal2 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 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 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 + 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 identity_service_; + talk_base::scoped_refptr + 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_ diff --git a/talk/base/nssidentity.cc b/talk/base/nssidentity.cc index b58788880d..c660aee0a5 100644 --- a/talk/base/nssidentity.cc +++ b/talk/base/nssidentity.cc @@ -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,9 +117,9 @@ NSSCertificate *NSSCertificate::GetReference() const { } std::string NSSCertificate::ToPEMString() const { - return DerToPem("CERTIFICATE", - certificate_->derCert.data, - certificate_->derCert.len); + return SSLIdentity::DerToPem(kPemTypeCertificate, + certificate_->derCert.data, + certificate_->derCert.len); } bool NSSCertificate::GetDigestLength(const std::string &algorithm, @@ -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; diff --git a/talk/base/socket_unittest.cc b/talk/base/socket_unittest.cc index ad06c1e220..eafab81f40 100644 --- a/talk/base/socket_unittest.cc +++ b/talk/base/socket_unittest.cc @@ -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)); diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc index 497805200c..8f704dc300 100644 --- a/talk/base/sslidentity.cc +++ b/talk/base/sslidentity.cc @@ -34,6 +34,8 @@ #include +#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) { diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h index b63c06647c..b9425f78fd 100644 --- a/talk/base/sslidentity.h +++ b/talk/base/sslidentity.h @@ -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__ diff --git a/talk/base/sslidentity_unittest.cc b/talk/base/sslidentity_unittest.cc index 2194517efd..6970426781 100644 --- a/talk/base/sslidentity_unittest.cc +++ b/talk/base/sslidentity_unittest.cc @@ -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 identity1_; - talk_base::scoped_ptr identity2_; + talk_base::scoped_ptr identity1_; + talk_base::scoped_ptr identity2_; talk_base::scoped_ptr test_cert_; }; @@ -187,8 +189,17 @@ TEST_F(SSLIdentityTest, FromPEMStrings) { "qCV42aXS3onOXDQ1ibuWq0fr0//aj0wo4KV474c=\n" "-----END CERTIFICATE-----\n"; - talk_base::scoped_ptr identity( - talk_base::SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM)); + talk_base::scoped_ptr 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(der.data()), der.length())); +} diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 6b4f1333d2..15d92d10d9 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -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 ], diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index 9d57503908..9ea81afd06 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -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', diff --git a/talk/media/base/codec.h b/talk/media/base/codec.h index d7030017c6..56fc975adb 100644 --- a/talk/media/base/codec.h +++ b/talk/media/base/codec.h @@ -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_; } diff --git a/talk/media/sctp/sctpdataengine.cc b/talk/media/sctp/sctpdataengine.cc index 71ef73c831..2f9b117b91 100644 --- a/talk/media/sctp/sctpdataengine.cc +++ b/talk/media/sctp/sctpdataengine.cc @@ -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(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)) { - LOG(LS_WARNING) << debug_name_ << "->OnDataFromSctpToChannel(...): " - << "Received packet for unknown ssrc: " << params.ssrc; + 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; } diff --git a/talk/media/sctp/sctpdataengine.h b/talk/media/sctp/sctpdataengine.h index b0d44d3b2e..d62eff1aea 100644 --- a/talk/media/sctp/sctpdataengine.h +++ b/talk/media/sctp/sctpdataengine.h @@ -218,8 +218,8 @@ class SctpDataMediaChannel : public DataMediaChannel, bool sending_; // receiving_ controls whether inbound packets are thrown away. bool receiving_; - std::vector send_streams_; - std::vector recv_streams_; + // Unified send/receive streams, as each is bidirectional. + std::vector streams_; // A human-readable name for debugging messages. std::string debug_name_; diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h index 3a8cfb3fc7..4ed38d1d3f 100644 --- a/talk/media/webrtc/fakewebrtcvoiceengine.h +++ b/talk/media/webrtc/fakewebrtcvoiceengine.h @@ -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; diff --git a/talk/p2p/base/basicpacketsocketfactory.cc b/talk/p2p/base/basicpacketsocketfactory.cc index 714210470a..565aed3c04 100644 --- a/talk/p2p/base/basicpacketsocketfactory.cc +++ b/talk/p2p/base/basicpacketsocketfactory.cc @@ -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); } diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc index 992fe2dbf1..12336d4101 100644 --- a/talk/p2p/base/constants.cc +++ b/talk/p2p/base/constants.cc @@ -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" }; diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h index 14558014eb..f7e5671b26 100644 --- a/talk/p2p/base/constants.h +++ b/talk/p2p/base/constants.h @@ -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; diff --git a/talk/p2p/base/dtlstransport.h b/talk/p2p/base/dtlstransport.h index 5c5253d661..a6e3b82c1f 100644 --- a/talk/p2p/base/dtlstransport.h +++ b/talk/p2p/base/dtlstransport.h @@ -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 = diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc index 04961092ae..e331eac959 100644 --- a/talk/p2p/base/p2ptransportchannel_unittest.cc +++ b/talk/p2p/base/p2ptransportchannel_unittest.cc @@ -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 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. diff --git a/talk/p2p/base/packetsocketfactory.h b/talk/p2p/base/packetsocketfactory.h index b8a9846359..882a974173 100644 --- a/talk/p2p/base/packetsocketfactory.h +++ b/talk/p2p/base/packetsocketfactory.h @@ -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() { } diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h index 5e795ece86..c1021c72bb 100644 --- a/talk/p2p/base/port.h +++ b/talk/p2p/base/port.h @@ -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 diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h index 7568d45f2f..348102165f 100644 --- a/talk/p2p/base/portallocator.h +++ b/talk/p2p/base/portallocator.h @@ -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 diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc index 86428901cf..d70aaf5016 100644 --- a/talk/p2p/base/session.cc +++ b/talk/p2p/base/session.cc @@ -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) { diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h index 5f06518151..af34240654 100644 --- a/talk/p2p/base/session.h +++ b/talk/p2p/base/session.h @@ -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); diff --git a/talk/p2p/base/sessionmanager.cc b/talk/p2p/base/sessionmanager.cc index 8f4ddf2a1f..7aa52b3596 100644 --- a/talk/p2p/base/sessionmanager.cc +++ b/talk/p2p/base/sessionmanager.cc @@ -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); diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc index d2b7965fc5..1402472b48 100644 --- a/talk/p2p/base/transport.cc +++ b/talk/p2p/base/transport.cc @@ -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, ¶m); } +void Transport::SetIdentity(talk_base::SSLIdentity* identity) { + IdentityParam params(identity); + worker_thread()->Send(this, MSG_SETIDENTITY, ¶ms); +} + 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(msg->pdata); + SetIdentity_w(params->identity); + } + break; } } diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h index 698ec321be..b712406aa3 100644 --- a/talk/p2p/base/transport.h +++ b/talk/p2p/base/transport.h @@ -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. diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc index 6c1165d04a..a302b71352 100644 --- a/talk/p2p/base/turnport.cc +++ b/talk/p2p/base/turnport.cc @@ -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() { diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc index 42ae312b70..6304ce6789 100644 --- a/talk/p2p/base/turnport_unittest.cc +++ b/talk/p2p/base/turnport_unittest.cc @@ -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. diff --git a/talk/p2p/base/turnserver.cc b/talk/p2p/base/turnserver.cc index 565a087720..8260f3dbe7 100644 --- a/talk/p2p/base/turnserver.cc +++ b/talk/p2p/base/turnserver.cc @@ -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); diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h index 0d5f642855..1fc6ebe10f 100644 --- a/talk/p2p/client/basicportallocator.h +++ b/talk/p2p/client/basicportallocator.h @@ -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(); diff --git a/talk/session/media/mediasessionclient.cc b/talk/session/media/mediasessionclient.cc index 265906f478..b54891e8cc 100644 --- a/talk/session/media/mediasessionclient.cc +++ b/talk/session/media/mediasessionclient.cc @@ -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& map, const std::string& key, const int def) { std::map::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 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 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::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; } diff --git a/talk/session/media/mediasessionclient_unittest.cc b/talk/session/media/mediasessionclient_unittest.cc index 644e1a61c1..1ad93722bd 100644 --- a/talk/session/media/mediasessionclient_unittest.cc +++ b/talk/session/media/mediasessionclient_unittest.cc @@ -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( " " \ " "); +const std::string kJingleInitiateWithRtcpFb( + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " "); + const std::string kGingleVideoInitiate( " " \ @@ -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,8 +1210,8 @@ static std::string AddEncryption(std::string stanza, std::string encryption) { return stanza; } -int IntFromJingleCodecParameter(const buzz::XmlElement* parameter, - const std::string& expected_name) { +static int IntFromJingleCodecParameter(const buzz::XmlElement* parameter, + const std::string& expected_name) { if (parameter) { const std::string& actual_name = parameter->Attr(cricket::QN_PAYLOADTYPE_PARAMETER_NAME); @@ -1181,6 +1227,18 @@ int IntFromJingleCodecParameter(const buzz::XmlElement* parameter, return 0; } +template +static void VerifyCodecFbParams(const FeedbackParams& expected, + const DescriptionClass* desc) { + if (!expected.params().empty()) { + ASSERT_TRUE(desc != NULL); + const std::vector 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 audio_codecs(kAudioCodecs, kAudioCodecs + ARRAY_SIZE(kAudioCodecs)); + SetCodecFeedbackParams(&audio_codecs, params_nack); fme_->SetAudioCodecs(audio_codecs); std::vector video_codecs(kVideoCodecs, kVideoCodecs + ARRAY_SIZE(kVideoCodecs)); + SetCodecFeedbackParams(&video_codecs, params_nack_fir); fme_->SetVideoCodecs(video_codecs); std::vector 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( - content->description); + return static_cast(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(expected_audio_fb_params_, + GetFirstAudioContentDescription(sdesc)); + + VerifyCodecFbParams(expected_video_fb_params_, + GetFirstVideoContentDescription(sdesc)); + + VerifyCodecFbParams(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]); } @@ -1739,7 +1848,7 @@ class MediaSessionClientTest : public sigslot::has_slots<> { CheckCryptoForGoodOutgoingAccept(call_->sessions()[0]); } - if (options.data_channel_type == cricket::DCT_RTP) { + if (options.data_channel_type == cricket::DCT_RTP) { CheckDataRtcpMux(true, call_->sessions()[0]->local_description()); CheckDataRtcpMux(true, call_->sessions()[0]->remote_description()); // TODO(pthatcher): Check rtcpmux and crypto? @@ -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 + void SetCodecFeedbackParams(std::vector* 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 test(JingleTest()); + talk_base::scoped_ptr 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 test(JingleTest()); talk_base::scoped_ptr elem;