From 78187525665490922748d79377bcb351579e03c0 Mon Sep 17 00:00:00 2001 From: "wu@webrtc.org" Date: Mon, 7 Oct 2013 23:32:02 +0000 Subject: [PATCH] Update libjingle to 53856368. R=mallinath@webrtc.org Review URL: https://webrtc-codereview.appspot.com/2366004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4941 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/app/webrtc/datachannel.cc | 72 ++++----- talk/app/webrtc/datachannel.h | 39 +++-- talk/app/webrtc/datachannel_unittest.cc | 147 +++++++----------- talk/app/webrtc/peerconnection.cc | 4 +- .../webrtc/peerconnectionfactory_unittest.cc | 14 +- talk/app/webrtc/webrtcsdp.cc | 34 +++- talk/app/webrtc/webrtcsdp_unittest.cc | 44 +++++- talk/app/webrtc/webrtcsession.cc | 40 ++++- talk/app/webrtc/webrtcsession.h | 11 +- talk/base/latebindingsymboltable.h | 3 + talk/base/logging.cc | 3 - talk/base/logging.h | 7 + talk/base/md5digest_unittest.cc | 2 +- talk/base/network_unittest.cc | 56 ++++++- talk/base/openssladapter.cc | 2 +- talk/base/profiler.cc | 18 ++- talk/base/profiler.h | 3 + talk/base/sha1digest_unittest.cc | 2 +- talk/base/stream.cc | 100 ++++++++++++ talk/base/stream.h | 29 ++++ talk/base/thread_unittest.cc | 1 - talk/media/base/constants.cc | 1 + talk/media/base/constants.h | 1 + talk/media/base/fakemediaengine.h | 3 + talk/media/base/filemediaengine.h | 3 + talk/media/base/hybridvideoengine.h | 14 ++ talk/media/base/mediaengine.h | 10 ++ talk/media/devices/libudevsymboltable.cc | 30 ++++ talk/media/devices/libudevsymboltable.h | 8 + talk/media/devices/linuxdeviceinfo.cc | 3 +- talk/media/devices/linuxdevicemanager.cc | 5 +- talk/media/sctp/sctpdataengine.cc | 42 ++++- talk/media/sctp/sctpdataengine.h | 11 +- talk/media/webrtc/webrtcvideoengine.cc | 42 ++++- talk/media/webrtc/webrtcvideoengine.h | 1 + talk/media/webrtc/webrtcvoiceengine.cc | 27 +++- talk/media/webrtc/webrtcvoiceengine.h | 1 + .../webrtc/webrtcvoiceengine_unittest.cc | 20 +++ talk/session/media/channel.cc | 10 ++ talk/session/media/channel.h | 9 ++ talk/session/media/mediasession.cc | 8 +- talk/session/media/mediasession_unittest.cc | 16 +- 42 files changed, 694 insertions(+), 202 deletions(-) diff --git a/talk/app/webrtc/datachannel.cc b/talk/app/webrtc/datachannel.cc index 9409fd7a2..3de001b54 100644 --- a/talk/app/webrtc/datachannel.cc +++ b/talk/app/webrtc/datachannel.cc @@ -28,7 +28,7 @@ #include -#include "talk/app/webrtc/webrtcsession.h" +#include "talk/app/webrtc/mediastreamprovider.h" #include "talk/base/logging.h" #include "talk/base/refcount.h" @@ -38,24 +38,29 @@ static size_t kMaxQueuedReceivedDataPackets = 100; static size_t kMaxQueuedSendDataPackets = 100; talk_base::scoped_refptr DataChannel::Create( - WebRtcSession* session, + DataChannelProviderInterface* provider, + cricket::DataChannelType dct, const std::string& label, const DataChannelInit* config) { talk_base::scoped_refptr channel( - new talk_base::RefCountedObject(session, label)); + new talk_base::RefCountedObject(provider, dct, label)); if (!channel->Init(config)) { return NULL; } return channel; } -DataChannel::DataChannel(WebRtcSession* session, const std::string& label) +DataChannel::DataChannel( + DataChannelProviderInterface* provider, + cricket::DataChannelType dct, + const std::string& label) : label_(label), observer_(NULL), state_(kConnecting), was_ever_writable_(false), - session_(session), - data_session_(NULL), + connected_to_provider_(false), + data_channel_type_(dct), + provider_(provider), send_ssrc_set_(false), send_ssrc_(0), receive_ssrc_set_(false), @@ -64,7 +69,7 @@ DataChannel::DataChannel(WebRtcSession* session, const std::string& label) bool DataChannel::Init(const DataChannelInit* config) { if (config) { - if (session_->data_channel_type() == cricket::DCT_RTP && + if (data_channel_type_ == cricket::DCT_RTP && (config->reliable || config->id != -1 || config->maxRetransmits != -1 || @@ -72,7 +77,7 @@ bool DataChannel::Init(const DataChannelInit* config) { LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to " << "invalid DataChannelInit."; return false; - } else if (session_->data_channel_type() == cricket::DCT_SCTP) { + } else if (data_channel_type_ == cricket::DCT_SCTP) { if (config->id < -1 || config->maxRetransmits < -1 || config->maxRetransmitTime < -1) { @@ -111,7 +116,7 @@ void DataChannel::UnregisterObserver() { } bool DataChannel::reliable() const { - if (session_->data_channel_type() == cricket::DCT_RTP) { + if (data_channel_type_ == cricket::DCT_RTP) { return false; } else { return config_.maxRetransmits == -1 && @@ -165,14 +170,15 @@ void DataChannel::QueueControl(const talk_base::Buffer* buffer) { } bool DataChannel::SendControl(const talk_base::Buffer* buffer) { + if (data_channel_type_ == cricket::DCT_RTP) { + delete buffer; + return false; + } + 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; @@ -180,8 +186,7 @@ bool DataChannel::SendControl(const talk_base::Buffer* buffer) { send_params.type = cricket::DMT_CONTROL; cricket::SendDataResult send_result; - bool retval = session_->data_channel()->SendData( - send_params, *buffer, &send_result); + bool retval = provider_->SendData(send_params, *buffer, &send_result); if (!retval && send_result == cricket::SDR_BLOCK) { // Link is congested. Queue for later. QueueControl(buffer); @@ -193,7 +198,7 @@ bool DataChannel::SendControl(const talk_base::Buffer* buffer) { void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) { if (receive_ssrc_set_) { - ASSERT(session_->data_channel_type() == cricket::DCT_RTP || + ASSERT(data_channel_type_ == cricket::DCT_RTP || !send_ssrc_set_ || receive_ssrc_ == send_ssrc_); return; @@ -210,7 +215,7 @@ void DataChannel::RemotePeerRequestClose() { void DataChannel::SetSendSsrc(uint32 send_ssrc) { if (send_ssrc_set_) { - ASSERT(session_->data_channel_type() == cricket::DCT_RTP || + ASSERT(data_channel_type_ == cricket::DCT_RTP || !receive_ssrc_set_ || receive_ssrc_ == send_ssrc_); return; @@ -221,7 +226,7 @@ void DataChannel::SetSendSsrc(uint32 send_ssrc) { } // The underlaying data engine is closing. -// This function make sure the DataChannel is disconneced and change state to +// This function makes sure the DataChannel is disconnected and changes state to // kClosed. void DataChannel::OnDataEngineClose() { DoClose(); @@ -277,7 +282,7 @@ void DataChannel::UpdateState() { switch (state_) { case kConnecting: { if (HasNegotiationCompleted()) { - if (!IsConnectedToDataSession()) { + if (!connected_to_provider_) { ConnectToDataSession(); } if (was_ever_writable_) { @@ -293,7 +298,7 @@ void DataChannel::UpdateState() { break; } case kClosing: { - if (IsConnectedToDataSession()) { + if (connected_to_provider_) { DisconnectFromDataSession(); } if (HasNegotiationCompleted()) { @@ -314,28 +319,12 @@ void DataChannel::SetState(DataState state) { } void DataChannel::ConnectToDataSession() { - if (!session_->data_channel()) { - LOG(LS_ERROR) << "The DataEngine does not exist."; - ASSERT(session_->data_channel() != NULL); - return; - } - - data_session_ = session_->data_channel(); - data_session_->SignalReadyToSendData.connect(this, - &DataChannel::OnChannelReady); - data_session_->SignalDataReceived.connect(this, &DataChannel::OnDataReceived); - cricket::StreamParams params = - cricket::StreamParams::CreateLegacy(id()); - data_session_->AddRecvStream(params); - data_session_->AddSendStream(params); + connected_to_provider_ = provider_->ConnectDataChannel(this); } void DataChannel::DisconnectFromDataSession() { - data_session_->RemoveSendStream(id()); - data_session_->RemoveRecvStream(id()); - data_session_->SignalReadyToSendData.disconnect(this); - data_session_->SignalDataReceived.disconnect(this); - data_session_ = NULL; + provider_->DisconnectDataChannel(this); + connected_to_provider_ = false; } void DataChannel::DeliverQueuedReceivedData() { @@ -409,15 +398,14 @@ bool DataChannel::InternalSendWithoutQueueing( cricket::SendDataParams send_params; send_params.ssrc = send_ssrc_; - if (session_->data_channel_type() == cricket::DCT_SCTP) { + if (data_channel_type_ == cricket::DCT_SCTP) { send_params.ordered = config_.ordered; send_params.max_rtx_count = config_.maxRetransmits; send_params.max_rtx_ms = config_.maxRetransmitTime; } send_params.type = buffer.binary ? cricket::DMT_BINARY : cricket::DMT_TEXT; - return session_->data_channel()->SendData(send_params, buffer.data, - send_result); + return provider_->SendData(send_params, buffer.data, send_result); } bool DataChannel::QueueSendData(const DataBuffer& buffer) { diff --git a/talk/app/webrtc/datachannel.h b/talk/app/webrtc/datachannel.h index 3ce3c1b5b..a09c7f6d6 100644 --- a/talk/app/webrtc/datachannel.h +++ b/talk/app/webrtc/datachannel.h @@ -35,11 +35,24 @@ #include "talk/app/webrtc/proxy.h" #include "talk/base/scoped_ref_ptr.h" #include "talk/base/sigslot.h" +#include "talk/media/base/mediachannel.h" #include "talk/session/media/channel.h" namespace webrtc { -class WebRtcSession; +class DataChannel; + +class DataChannelProviderInterface { + public: + virtual bool SendData(const cricket::SendDataParams& params, + const talk_base::Buffer& payload, + cricket::SendDataResult* result) = 0; + virtual bool ConnectDataChannel(DataChannel* data_channel) = 0; + virtual void DisconnectDataChannel(DataChannel* data_channel) = 0; + + protected: + virtual ~DataChannelProviderInterface() {} +}; // DataChannel is a an implementation of the DataChannelInterface based on // libjingle's data engine. It provides an implementation of unreliable data @@ -60,7 +73,8 @@ class DataChannel : public DataChannelInterface, public sigslot::has_slots<> { public: static talk_base::scoped_refptr Create( - WebRtcSession* session, + DataChannelProviderInterface* client, + cricket::DataChannelType dct, const std::string& label, const DataChannelInit* config); @@ -105,24 +119,26 @@ class DataChannel : public DataChannelInterface, // 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(); - - bool Init(const DataChannelInit* config); - bool HasNegotiationCompleted(); // Sigslots from cricket::DataChannel void OnDataReceived(cricket::DataChannel* channel, const cricket::ReceiveDataParams& params, const talk_base::Buffer& payload); + protected: + DataChannel(DataChannelProviderInterface* client, + cricket::DataChannelType dct, + const std::string& label); + virtual ~DataChannel(); + + bool Init(const DataChannelInit* config); + bool HasNegotiationCompleted(); + private: void DoClose(); void UpdateState(); void SetState(DataState state); void DisconnectFromDataSession(); - bool IsConnectedToDataSession() { return data_session_ != NULL; } void DeliverQueuedControlData(); void QueueControl(const talk_base::Buffer* buffer); void ClearQueuedControlData(); @@ -139,8 +155,9 @@ class DataChannel : public DataChannelInterface, DataChannelObserver* observer_; DataState state_; bool was_ever_writable_; - WebRtcSession* session_; - cricket::DataChannel* data_session_; + bool connected_to_provider_; + cricket::DataChannelType data_channel_type_; + DataChannelProviderInterface* provider_; bool send_ssrc_set_; uint32 send_ssrc_; bool receive_ssrc_set_; diff --git a/talk/app/webrtc/datachannel_unittest.cc b/talk/app/webrtc/datachannel_unittest.cc index 2b0a9fe5a..4d669078d 100644 --- a/talk/app/webrtc/datachannel_unittest.cc +++ b/talk/app/webrtc/datachannel_unittest.cc @@ -26,118 +26,78 @@ */ #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/test/fakedtlsidentityservice.h" -#include "talk/app/webrtc/webrtcsession.h" #include "talk/base/gunit.h" -#include "talk/media/base/fakemediaengine.h" -#include "talk/media/devices/fakedevicemanager.h" -#include "talk/session/media/channelmanager.h" -using webrtc::CreateSessionDescriptionObserver; -using webrtc::MediaConstraintsInterface; -using webrtc::SessionDescriptionInterface; +using webrtc::DataChannel; -const uint32 kFakeSsrc = 1; - -class CreateSessionDescriptionObserverForTest - : public talk_base::RefCountedObject { +class FakeDataChannelClient : public webrtc::DataChannelProviderInterface { public: - virtual void OnSuccess(SessionDescriptionInterface* desc) { - description_.reset(desc); + FakeDataChannelClient() : send_blocked_(false) {} + virtual ~FakeDataChannelClient() {} + + virtual bool SendData(const cricket::SendDataParams& params, + const talk_base::Buffer& payload, + cricket::SendDataResult* result) OVERRIDE { + if (send_blocked_) { + *result = cricket::SDR_BLOCK; + return false; + } + last_send_data_params_ = params; + return true; } - virtual void OnFailure(const std::string& error) {} - - SessionDescriptionInterface* description() { return description_.get(); } - - SessionDescriptionInterface* ReleaseDescription() { - return description_.release(); + virtual bool ConnectDataChannel(DataChannel* data_channel) OVERRIDE { + return true; } + virtual void DisconnectDataChannel(DataChannel* data_channel) OVERRIDE {} - protected: - ~CreateSessionDescriptionObserverForTest() {} + void set_send_blocked(bool blocked) { send_blocked_ = blocked; } + cricket::SendDataParams last_send_data_params() { + return last_send_data_params_; + } private: - talk_base::scoped_ptr description_; + cricket::SendDataParams last_send_data_params_; + bool send_blocked_; }; class SctpDataChannelTest : public testing::Test { protected: SctpDataChannelTest() - : media_engine_(new cricket::FakeMediaEngine), - data_engine_(new cricket::FakeDataEngine), - channel_manager_( - new cricket::ChannelManager(media_engine_, - data_engine_, - new cricket::FakeDeviceManager(), - new cricket::CaptureManager(), - talk_base::Thread::Current())), - media_stream_signaling_( - new webrtc::MediaStreamSignaling(talk_base::Thread::Current(), - NULL, channel_manager_.get())), - session_(channel_manager_.get(), - talk_base::Thread::Current(), - talk_base::Thread::Current(), - NULL, - media_stream_signaling_.get()), - webrtc_data_channel_(NULL) {} - - virtual void SetUp() { - if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) { - return; - } - channel_manager_->Init(); - webrtc::FakeConstraints constraints; - constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, true); - constraints.AddMandatory(MediaConstraintsInterface::kEnableSctpDataChannels, - true); - ASSERT_TRUE(session_.Initialize(&constraints, - new FakeIdentityService())); - webrtc_data_channel_ = webrtc::DataChannel::Create(&session_, "test", NULL); - ASSERT_TRUE(media_stream_signaling_->AddDataChannel(webrtc_data_channel_)); - - talk_base::scoped_refptr observer - = new CreateSessionDescriptionObserverForTest(); - session_.CreateOffer(observer.get(), NULL); - EXPECT_TRUE_WAIT(observer->description() != NULL, 2000); - ASSERT_TRUE(observer->description() != NULL); - ASSERT_TRUE(session_.SetLocalDescription(observer->ReleaseDescription(), - NULL)); - // Connect to the media channel. - webrtc_data_channel_->SetSendSsrc(kFakeSsrc); - webrtc_data_channel_->SetReceiveSsrc(kFakeSsrc); - session_.data_channel()->SignalReadyToSendData(true); + : webrtc_data_channel_( + DataChannel::Create(&client_, cricket::DCT_SCTP, "test", &init_)) { } - void SetSendBlocked(bool blocked) { - bool was_blocked = data_engine_->GetChannel(0)->is_send_blocked(); - data_engine_->GetChannel(0)->set_send_blocked(blocked); - if (!blocked && was_blocked) { - session_.data_channel()->SignalReadyToSendData(true); - } + void SetChannelReady() { + webrtc_data_channel_->OnChannelReady(true); } - cricket::FakeMediaEngine* media_engine_; - cricket::FakeDataEngine* data_engine_; - talk_base::scoped_ptr channel_manager_; - talk_base::scoped_ptr media_stream_signaling_; - webrtc::WebRtcSession session_; - talk_base::scoped_refptr webrtc_data_channel_; + + webrtc::DataChannelInit init_; + FakeDataChannelClient client_; + talk_base::scoped_refptr webrtc_data_channel_; }; +// Tests the state of the data channel. +TEST_F(SctpDataChannelTest, StateTransition) { + EXPECT_EQ(webrtc::DataChannelInterface::kConnecting, + webrtc_data_channel_->state()); + SetChannelReady(); + EXPECT_EQ(webrtc::DataChannelInterface::kOpen, webrtc_data_channel_->state()); + webrtc_data_channel_->Close(); + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, + webrtc_data_channel_->state()); +} + // Tests that DataChannel::buffered_amount() is correct after the channel is // blocked. TEST_F(SctpDataChannelTest, BufferedAmountWhenBlocked) { - if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) { - return; - } + SetChannelReady(); webrtc::DataBuffer buffer("abcd"); EXPECT_TRUE(webrtc_data_channel_->Send(buffer)); EXPECT_EQ(0U, webrtc_data_channel_->buffered_amount()); - SetSendBlocked(true); + client_.set_send_blocked(true); + const int number_of_packets = 3; for (int i = 0; i < number_of_packets; ++i) { EXPECT_TRUE(webrtc_data_channel_->Send(buffer)); @@ -149,13 +109,20 @@ TEST_F(SctpDataChannelTest, BufferedAmountWhenBlocked) { // Tests that the queued data are sent when the channel transitions from blocked // to unblocked. TEST_F(SctpDataChannelTest, QueuedDataSentWhenUnblocked) { - if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) { - return; - } + SetChannelReady(); webrtc::DataBuffer buffer("abcd"); - SetSendBlocked(true); + client_.set_send_blocked(true); EXPECT_TRUE(webrtc_data_channel_->Send(buffer)); - SetSendBlocked(false); + client_.set_send_blocked(false); + SetChannelReady(); EXPECT_EQ(0U, webrtc_data_channel_->buffered_amount()); } + +// Tests that the queued control message is sent when channel is ready. +TEST_F(SctpDataChannelTest, QueuedControlMessageSent) { + talk_base::Buffer* buffer = new talk_base::Buffer("abcd", 4); + EXPECT_TRUE(webrtc_data_channel_->SendControl(buffer)); + SetChannelReady(); + EXPECT_EQ(cricket::DMT_CONTROL, client_.last_send_data_params().type); +} diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index 9eaf915c3..a736fa2ed 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -172,8 +172,10 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration, } std::string address = tokens[1]; int port = kDefaultStunPort; - if (service_type == TURNS) + if (service_type == TURNS) { port = kDefaultStunTlsPort; + turn_transport_type = kTcpTransportType; + } if (tokens.size() > kMinIceUriTokens) { if (!talk_base::FromString(tokens[2], &port)) { diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc index 182c01c46..e3def6c24 100644 --- a/talk/app/webrtc/peerconnectionfactory_unittest.cc +++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc @@ -61,6 +61,8 @@ static const char kTurnIceServerWithTransport[] = "turn:test@hello.com?transport=tcp"; static const char kSecureTurnIceServer[] = "turns:test@hello.com?transport=tcp"; +static const char kSecureTurnIceServerWithoutTransportParam[] = + "turns:test_no_transport@hello.com"; static const char kTurnIceServerWithNoUsernameInUri[] = "turn:test.com:1234"; static const char kTurnPassword[] = "turnpassword"; @@ -242,6 +244,9 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) { ice_server.uri = kSecureTurnIceServer; ice_server.password = kTurnPassword; ice_servers.push_back(ice_server); + ice_server.uri = kSecureTurnIceServerWithoutTransportParam; + ice_server.password = kTurnPassword; + ice_servers.push_back(ice_server); talk_base::scoped_refptr pc( factory_->CreatePeerConnection(ice_servers, NULL, allocator_factory_.get(), @@ -249,9 +254,14 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) { &observer_)); EXPECT_TRUE(pc.get() != NULL); TurnConfigurations turn_configs; - webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn( + webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1( "hello.com", kDefaultStunTlsPort, "test", kTurnPassword, "tcp", true); - turn_configs.push_back(turn); + turn_configs.push_back(turn1); + // TURNS with transport param should be default to tcp. + webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn2( + "hello.com", kDefaultStunTlsPort, "test_no_transport", + kTurnPassword, "tcp", true); + turn_configs.push_back(turn2); VerifyTurnConfigurations(turn_configs); } diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc index 60c427d16..ae171178e 100644 --- a/talk/app/webrtc/webrtcsdp.cc +++ b/talk/app/webrtc/webrtcsdp.cc @@ -145,6 +145,7 @@ static const char kAttributeFingerprint[] = "fingerprint"; static const char kAttributeSetup[] = "setup"; static const char kAttributeFmtp[] = "fmtp"; static const char kAttributeRtpmap[] = "rtpmap"; +static const char kAttributeSctpmap[] = "sctpmap"; static const char kAttributeRtcp[] = "rtcp"; static const char kAttributeIceUfrag[] = "ice-ufrag"; static const char kAttributeIcePwd[] = "ice-pwd"; @@ -210,7 +211,7 @@ static const int kIsacWbDefaultRate = 32000; // From acm_common_defs.h static const int kIsacSwbDefaultRate = 56000; // From acm_common_defs.h static const int kDefaultSctpFmt = 5000; -static const char kDefaultSctpFmtProtocol[] = "webrtc-datachannel"; +static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel"; struct SsrcInfo { SsrcInfo() @@ -1268,10 +1269,14 @@ void BuildMediaDescription(const ContentInfo* content_info, } void BuildSctpContentAttributes(std::string* message) { - cricket::DataCodec sctp_codec(kDefaultSctpFmt, kDefaultSctpFmtProtocol, 0); - sctp_codec.SetParam(kCodecParamSctpProtocol, kDefaultSctpFmtProtocol); - sctp_codec.SetParam(kCodecParamSctpStreams, cricket::kMaxSctpSid + 1); - AddFmtpLine(sctp_codec, message); + // draft-ietf-mmusic-sctp-sdp-04 + // a=sctpmap:sctpmap-number protocol [streams] + std::ostringstream os; + InitAttrLine(kAttributeSctpmap, &os); + os << kSdpDelimiterColon << kDefaultSctpFmt << kSdpDelimiterSpace + << kDefaultSctpmapProtocol << kSdpDelimiterSpace + << (cricket::kMaxSctpSid + 1); + AddLine(os.str(), message); } void BuildRtpContentAttributes( @@ -2111,10 +2116,25 @@ bool ParseMediaDescription(const std::string& message, codec_preference, static_cast(content.get())); } else if (HasAttribute(line, kMediaTypeData)) { - content.reset(ParseContentDescription( + DataContentDescription* desc = + ParseContentDescription( message, cricket::MEDIA_TYPE_DATA, mline_index, protocol, codec_preference, pos, &content_name, - &transport, candidates, error)); + &transport, candidates, error); + + if (protocol == cricket::kMediaProtocolDtlsSctp) { + // Add the SCTP Port number as a pseudo-codec "port" parameter + cricket::DataCodec codec_port( + cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, + 0); + codec_port.SetParam(cricket::kCodecParamPort, fields[3]); + LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number " + << fields[3]; + desc->AddCodec(codec_port); + } + + content.reset(desc); + // We should always use the default bandwidth for RTP-based data // channels. Don't allow SDP to set the bandwidth, because that // would give JS the opportunity to "break the Internet". diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc index b1505aae7..6f2e38835 100644 --- a/talk/app/webrtc/webrtcsdp_unittest.cc +++ b/talk/app/webrtc/webrtcsdp_unittest.cc @@ -76,6 +76,7 @@ using webrtc::SessionDescriptionInterface; typedef std::vector AudioCodecs; typedef std::vector Candidates; +static const uint32 kDefaultSctpPort = 5000; static const char kSessionTime[] = "t=0 0\r\n"; static const uint32 kCandidatePriority = 2130706432U; // pref = 1.0 static const char kCandidateUfragVoice[] = "ufrag_voice"; @@ -281,7 +282,7 @@ static const char kSdpSctpDataChannelString[] = "a=ice-ufrag:ufrag_data\r\n" "a=ice-pwd:pwd_data\r\n" "a=mid:data_content_name\r\n" - "a=fmtp:5000 protocol=webrtc-datachannel; streams=65536\r\n"; + "a=sctpmap:5000 webrtc-datachannel 65536\r\n"; static const char kSdpSctpDataChannelWithCandidatesString[] = "m=application 2345 DTLS/SCTP 5000\r\n" @@ -296,7 +297,7 @@ static const char kSdpSctpDataChannelWithCandidatesString[] = "a=ice-ufrag:ufrag_data\r\n" "a=ice-pwd:pwd_data\r\n" "a=mid:data_content_name\r\n" - "a=fmtp:5000 protocol=webrtc-datachannel; streams=65536\r\n"; + "a=sctpmap:5000 webrtc-datachannel 65536\r\n"; // One candidate reference string as per W3c spec. @@ -974,6 +975,10 @@ class WebRtcSdpTest : public testing::Test { new DataContentDescription()); data_desc_ = data.get(); data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp); + DataCodec codec(cricket::kGoogleSctpDataCodecId, + cricket::kGoogleSctpDataCodecName, 0); + codec.SetParam(cricket::kCodecParamPort, kDefaultSctpPort); + data_desc_->AddCodec(codec); desc_.AddContent(kDataContentName, NS_JINGLE_DRAFT_SCTP, data.release()); EXPECT_TRUE(desc_.AddTransportInfo( TransportInfo(kDataContentName, @@ -1761,6 +1766,41 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannels) { EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); } +TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) { + AddSctpDataChannel(); + const uint16 kUnusualSctpPort = 9556; + char default_portstr[16]; + char unusual_portstr[16]; + talk_base::sprintfn(default_portstr, sizeof(default_portstr), "%d", + kDefaultSctpPort); + talk_base::sprintfn(unusual_portstr, sizeof(unusual_portstr), "%d", + kUnusualSctpPort); + + JsepSessionDescription jdesc(kDummyString); + // take our pre-built session description and change the SCTP port. + cricket::SessionDescription* mutant = desc_.Copy(); + DataContentDescription* dcdesc = static_cast( + mutant->GetContentDescriptionByName(kDataContentName)); + std::vector codecs(dcdesc->codecs()); + EXPECT_EQ(codecs.size(), 1UL); + EXPECT_EQ(codecs[0].id, cricket::kGoogleSctpDataCodecId); + codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort); + + // note: mutant's owned by jdesc now. + ASSERT_TRUE(jdesc.Initialize(mutant, kSessionId, kSessionVersion)); + mutant = NULL; + + std::string sdp_with_data = kSdpString; + sdp_with_data.append(kSdpSctpDataChannelString); + talk_base::replace_substrs(default_portstr, strlen(default_portstr), + unusual_portstr, strlen(unusual_portstr), + &sdp_with_data); + JsepSessionDescription jdesc_output(kDummyString); + + EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output)); + EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); +} + TEST_F(WebRtcSdpTest, DeserializeSdpWithRtpDataChannelsAndBandwidth) { AddRtpDataChannel(); JsepSessionDescription jdesc(kDummyString); diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc index ff331a9e0..04cbe029f 100644 --- a/talk/app/webrtc/webrtcsession.cc +++ b/talk/app/webrtc/webrtcsession.cc @@ -927,9 +927,43 @@ sigslot::signal0<>* WebRtcSession::GetOnDestroyedSignal() { return &SignalVoiceChannelDestroyed; } +bool WebRtcSession::SendData(const cricket::SendDataParams& params, + const talk_base::Buffer& payload, + cricket::SendDataResult* result) { + if (!data_channel_.get()) { + LOG(LS_ERROR) << "SendData called when data_channel_ is NULL."; + return false; + } + return data_channel_->SendData(params, payload, result); +} + +bool WebRtcSession::ConnectDataChannel(DataChannel* webrtc_data_channel) { + if (!data_channel_.get()) { + LOG(LS_ERROR) << "ConnectDataChannel called when data_channel_ is NULL."; + return false; + } + + data_channel_->SignalReadyToSendData.connect(webrtc_data_channel, + &DataChannel::OnChannelReady); + data_channel_->SignalDataReceived.connect(webrtc_data_channel, + &DataChannel::OnDataReceived); + cricket::StreamParams params = + cricket::StreamParams::CreateLegacy(webrtc_data_channel->id()); + data_channel_->AddRecvStream(params); + data_channel_->AddSendStream(params); + return true; +} + +void WebRtcSession::DisconnectDataChannel(DataChannel* webrtc_data_channel) { + data_channel_->RemoveSendStream(webrtc_data_channel->id()); + data_channel_->RemoveRecvStream(webrtc_data_channel->id()); + data_channel_->SignalReadyToSendData.disconnect(webrtc_data_channel); + data_channel_->SignalDataReceived.disconnect(webrtc_data_channel); +} + talk_base::scoped_refptr WebRtcSession::CreateDataChannel( - const std::string& label, - const DataChannelInit* config) { + const std::string& label, + const DataChannelInit* config) { if (state() == STATE_RECEIVEDTERMINATE) { return NULL; } @@ -953,7 +987,7 @@ talk_base::scoped_refptr WebRtcSession::CreateDataChannel( } talk_base::scoped_refptr channel( - DataChannel::Create(this, label, &new_config)); + DataChannel::Create(this, data_channel_type_, label, &new_config)); if (channel == NULL) return NULL; if (!mediastream_signaling_->AddDataChannel(channel)) diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h index eee579d19..e4610283c 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -99,7 +99,8 @@ class WebRtcSession : public cricket::BaseSession, public AudioProviderInterface, public DataChannelFactory, public VideoProviderInterface, - public DtmfProviderInterface { + public DtmfProviderInterface, + public DataChannelProviderInterface { public: WebRtcSession(cricket::ChannelManager* channel_manager, talk_base::Thread* signaling_thread, @@ -181,6 +182,14 @@ class WebRtcSession : public cricket::BaseSession, int code, int duration); virtual sigslot::signal0<>* GetOnDestroyedSignal(); + // Implements DataChannelProviderInterface. + virtual bool SendData(const cricket::SendDataParams& params, + const talk_base::Buffer& payload, + cricket::SendDataResult* result) OVERRIDE; + virtual bool ConnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE; + virtual void DisconnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE; + + talk_base::scoped_refptr CreateDataChannel( const std::string& label, const DataChannelInit* config); diff --git a/talk/base/latebindingsymboltable.h b/talk/base/latebindingsymboltable.h index a53648bfe..f4ad5a609 100644 --- a/talk/base/latebindingsymboltable.h +++ b/talk/base/latebindingsymboltable.h @@ -67,6 +67,9 @@ class LateBindingSymbolTable { bool LoadFromPath(const char *dll_path); void Unload(); + // Gets the raw OS handle to the DLL. Be careful what you do with it. + DllHandle GetDllHandle() const { return handle_; } + private: void ClearSymbols(); diff --git a/talk/base/logging.cc b/talk/base/logging.cc index 6653d3451..4c7eae172 100644 --- a/talk/base/logging.cc +++ b/talk/base/logging.cc @@ -123,8 +123,6 @@ LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev, LogErrorContext err_ctx, int err, const char* module) : severity_(sev), warn_slow_logs_delay_(WARN_SLOW_LOGS_DELAY) { - // Android's logging facility keeps track of timestamp and thread. -#ifndef ANDROID if (timestamp_) { uint32 time = TimeSince(LogStartTime()); // Also ensure WallClockStartTime is initialized, so that it matches @@ -141,7 +139,6 @@ LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev, print_stream_ << "[" << std::hex << id << std::dec << "] "; #endif // WIN32 } -#endif // !ANDROID if (severity_ >= ctx_sev_) { print_stream_ << Describe(sev) << "(" << DescribeFile(file) diff --git a/talk/base/logging.h b/talk/base/logging.h index 2f341fa78..b563302b2 100644 --- a/talk/base/logging.h +++ b/talk/base/logging.h @@ -41,6 +41,8 @@ // LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity // type (basically, it just doesn't prepend the namespace). // LOG_F(sev) Like LOG(), but includes the name of the current function. +// LOG_T(sev) Like LOG(), but includes the this pointer. +// LOG_T_F(sev) Like LOG_F(), but includes the this pointer. // LOG_GLE(M)(sev [, mod]) attempt to add a string description of the // HRESULT returned by GetLastError. The "M" variant allows searching of a // DLL's string table for the error description. @@ -328,6 +330,9 @@ inline bool LogCheckLevel(LoggingSeverity sev) { talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ .stream() +#define LOG_T(sev) LOG(sev) << this << ": " +#define LOG_T_F(level) LOG_F(level) << this << ": " + #else // !LOGGING // Hopefully, the compiler will optimize away some of this code. @@ -348,6 +353,8 @@ inline bool LogCheckLevel(LoggingSeverity sev) { talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ .stream() +#define LOG_T(sev) LOG(sev) << this << ": " +#define LOG_T_F(level) LOG_F(level) << this << " " #endif // !LOGGING #define LOG_ERRNO_EX(sev, err) \ diff --git a/talk/base/md5digest_unittest.cc b/talk/base/md5digest_unittest.cc index 40b19e5a3..9232b40ae 100644 --- a/talk/base/md5digest_unittest.cc +++ b/talk/base/md5digest_unittest.cc @@ -38,7 +38,7 @@ std::string Md5(const std::string& input) { TEST(Md5DigestTest, TestSize) { Md5Digest md5; - EXPECT_EQ(16U, Md5Digest::kSize); + EXPECT_EQ(16, static_cast(Md5Digest::kSize)); EXPECT_EQ(16U, md5.Size()); } diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc index d12a1eb8f..ce19120fe 100644 --- a/talk/base/network_unittest.cc +++ b/talk/base/network_unittest.cc @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2004--2011, Google Inc. + * Copyright 2004 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,6 +37,9 @@ #endif #endif #include "talk/base/gunit.h" +#ifdef WIN32 +#include "talk/base/logging.h" // For LOG_GLE +#endif namespace talk_base { @@ -130,7 +133,7 @@ TEST_F(NetworkTest, TestCreateNetworks) { IPAddress ip = (*it)->ip(); SocketAddress bindaddress(ip, 0); bindaddress.SetScopeID((*it)->scope_id()); - // TODO: Make this use talk_base::AsyncSocket once it supports IPv6. + // TODO(thaloun): Use talk_base::AsyncSocket once it supports IPv6. int fd = static_cast(socket(ip.family(), SOCK_STREAM, IPPROTO_TCP)); if (fd > 0) { size_t ipsize = bindaddress.ToSockAddrStorage(&storage); @@ -138,6 +141,9 @@ TEST_F(NetworkTest, TestCreateNetworks) { int success = ::bind(fd, reinterpret_cast(&storage), static_cast(ipsize)); +#ifdef WIN32 + if (success) LOG_GLE(LS_ERROR) << "Socket bind failed."; +#endif EXPECT_EQ(0, success); #ifdef WIN32 closesocket(fd); @@ -486,7 +492,7 @@ TEST_F(NetworkTest, TestIPv6Toggle) { NetworkManager::NetworkList list; #ifndef WIN32 // There should be at least one IPv6 network (fe80::/64 should be in there). - // TODO: Disabling this test on windows for the moment as the test + // TODO(thaloun): Disabling this test on windows for the moment as the test // machines don't seem to have IPv6 installed on them at all. manager.set_ipv6_enabled(true); list = GetNetworks(manager, true); @@ -534,5 +540,49 @@ TEST_F(NetworkTest, TestConvertIfAddrsNoAddress) { } #endif // defined(POSIX) +#if defined(LINUX) +// If you want to test non-default routes, you can do the following on a linux +// machine: +// 1) Load the dummy network driver: +// sudo modprobe dummy +// sudo ifconfig dummy0 127.0.0.1 +// 2) Run this test and confirm the output says it found a dummy route (and +// passes). +// 3) When done: +// sudo rmmmod dummy +TEST_F(NetworkTest, TestIgnoreNonDefaultRoutes) { + BasicNetworkManager manager; + NetworkManager::NetworkList list; + list = GetNetworks(manager, false); + bool found_dummy = false; + LOG(LS_INFO) << "Looking for dummy network: "; + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + found_dummy |= (*it)->name().find("dummy0") != std::string::npos; + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } + if (!found_dummy) { + LOG(LS_INFO) << "No dummy found, quitting."; + return; + } + LOG(LS_INFO) << "Found dummy, running again while ignoring non-default " + << "routes."; + manager.set_ignore_non_default_routes(true); + list = GetNetworks(manager, false); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + EXPECT_TRUE((*it)->name().find("dummy0") == std::string::npos); + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +} +#endif } // namespace talk_base diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc index 8e33a031c..af92f0c45 100644 --- a/talk/base/openssladapter.cc +++ b/talk/base/openssladapter.cc @@ -234,7 +234,7 @@ bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) { if (!InitializeSSLThread() || !SSL_library_init()) return false; #if !defined(ADDRESS_SANITIZER) || !defined(OSX) - // Loading the error strings crashed mac_asan. Omit this debugging aid there. + // Loading the error strings crashes mac_asan. Omit this debugging aid there. SSL_load_error_strings(); #endif ERR_load_BIO_strings(); diff --git a/talk/base/profiler.cc b/talk/base/profiler.cc index 9d0f32bb2..68bcfe4d0 100644 --- a/talk/base/profiler.cc +++ b/talk/base/profiler.cc @@ -130,12 +130,7 @@ void Profiler::ReportToLog(const char* file, int line, for (iterator it = events_.begin(); it != events_.end(); ++it) { if (event_prefix.empty() || it->first.find(event_prefix) == 0) { LogMessage(file, line, severity_to_use).stream() - << it->first << " count=" << it->second.event_count() - << " total=" << FormattedTime(it->second.total_time()) - << " mean=" << FormattedTime(it->second.mean()) - << " min=" << FormattedTime(it->second.minimum()) - << " max=" << FormattedTime(it->second.maximum()) - << " sd=" << it->second.standard_deviation(); + << it->first << " " << it->second; } } LogMessage(file, line, severity_to_use).stream() @@ -168,4 +163,15 @@ bool Profiler::Clear() { return result; } +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event) { + stream << "count=" << profiler_event.event_count() + << " total=" << FormattedTime(profiler_event.total_time()) + << " mean=" << FormattedTime(profiler_event.mean()) + << " min=" << FormattedTime(profiler_event.minimum()) + << " max=" << FormattedTime(profiler_event.maximum()) + << " sd=" << profiler_event.standard_deviation(); + return stream; +} + } // namespace talk_base diff --git a/talk/base/profiler.h b/talk/base/profiler.h index 1198b8e26..91ad6a541 100644 --- a/talk/base/profiler.h +++ b/talk/base/profiler.h @@ -164,6 +164,9 @@ class ProfilerScope { DISALLOW_COPY_AND_ASSIGN(ProfilerScope); }; +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event); + } // namespace talk_base #endif // TALK_BASE_PROFILER_H_ diff --git a/talk/base/sha1digest_unittest.cc b/talk/base/sha1digest_unittest.cc index 5ab681924..d6c14b6d2 100644 --- a/talk/base/sha1digest_unittest.cc +++ b/talk/base/sha1digest_unittest.cc @@ -38,7 +38,7 @@ std::string Sha1(const std::string& input) { TEST(Sha1DigestTest, TestSize) { Sha1Digest sha1; - EXPECT_EQ(20U, Sha1Digest::kSize); + EXPECT_EQ(20, static_cast(Sha1Digest::kSize)); EXPECT_EQ(20U, sha1.Size()); } diff --git a/talk/base/stream.cc b/talk/base/stream.cc index 20adfcfa9..996908d58 100644 --- a/talk/base/stream.cc +++ b/talk/base/stream.cc @@ -523,6 +523,106 @@ void FileStream::DoClose() { fclose(file_); } +CircularFileStream::CircularFileStream(size_t max_size) + : max_write_size_(max_size), + position_(0), + marked_position_(max_size / 2), + last_write_position_(0), + read_segment_(READ_LATEST), + read_segment_available_(0) { +} + +bool CircularFileStream::Open( + const std::string& filename, const char* mode, int* error) { + if (!FileStream::Open(filename.c_str(), mode, error)) + return false; + + if (strchr(mode, "r") != NULL) { // Opened in read mode. + // Check if the buffer has been overwritten and determine how to read the + // log in time sequence. + size_t file_size; + GetSize(&file_size); + if (file_size == position_) { + // The buffer has not been overwritten yet. Read 0 .. file_size + read_segment_ = READ_LATEST; + read_segment_available_ = file_size; + } else { + // The buffer has been over written. There are three segments: The first + // one is 0 .. marked_position_, which is the marked earliest log. The + // second one is position_ .. file_size, which is the middle log. The + // last one is marked_position_ .. position_, which is the latest log. + read_segment_ = READ_MARKED; + read_segment_available_ = marked_position_; + last_write_position_ = position_; + } + + // Read from the beginning. + position_ = 0; + SetPosition(position_); + } + + return true; +} + +StreamResult CircularFileStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (read_segment_available_ == 0) { + size_t file_size; + switch (read_segment_) { + case READ_MARKED: // Finished READ_MARKED and start READ_MIDDLE. + read_segment_ = READ_MIDDLE; + position_ = last_write_position_; + SetPosition(position_); + GetSize(&file_size); + read_segment_available_ = file_size - position_; + break; + + case READ_MIDDLE: // Finished READ_MIDDLE and start READ_LATEST. + read_segment_ = READ_LATEST; + position_ = marked_position_; + SetPosition(position_); + read_segment_available_ = last_write_position_ - position_; + break; + + default: // Finished READ_LATEST and return EOS. + return talk_base::SR_EOS; + } + } + + size_t local_read; + if (!read) read = &local_read; + + size_t to_read = talk_base::_min(buffer_len, read_segment_available_); + talk_base::StreamResult result + = talk_base::FileStream::Read(buffer, to_read, read, error); + if (result == talk_base::SR_SUCCESS) { + read_segment_available_ -= *read; + position_ += *read; + } + return result; +} + +StreamResult CircularFileStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (position_ >= max_write_size_) { + ASSERT(position_ == max_write_size_); + position_ = marked_position_; + SetPosition(position_); + } + + size_t local_written; + if (!written) written = &local_written; + + size_t to_eof = max_write_size_ - position_; + size_t to_write = talk_base::_min(data_len, to_eof); + talk_base::StreamResult result + = talk_base::FileStream::Write(data, to_write, written, error); + if (result == talk_base::SR_SUCCESS) { + position_ += *written; + } + return result; +} + AsyncWriteStream::~AsyncWriteStream() { write_thread_->Clear(this, 0, NULL); ClearBufferAndWrite(); diff --git a/talk/base/stream.h b/talk/base/stream.h index 6700c402d..457396cdf 100644 --- a/talk/base/stream.h +++ b/talk/base/stream.h @@ -468,6 +468,35 @@ class FileStream : public StreamInterface { DISALLOW_EVIL_CONSTRUCTORS(FileStream); }; +// A stream that caps the output at a certain size, dropping content from the +// middle of the logical stream and maintaining equal parts of the start/end of +// the logical stream. +class CircularFileStream : public FileStream { + public: + explicit CircularFileStream(size_t max_size); + + virtual bool Open(const std::string& filename, const char* mode, int* error); + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + enum ReadSegment { + READ_MARKED, // Read 0 .. marked_position_ + READ_MIDDLE, // Read position_ .. file_size + READ_LATEST, // Read marked_position_ .. position_ if the buffer was + // overwritten or 0 .. position_ otherwise. + }; + + size_t max_write_size_; + size_t position_; + size_t marked_position_; + size_t last_write_position_; + ReadSegment read_segment_; + size_t read_segment_available_; +}; + // A stream which pushes writes onto a separate thread and // returns from the write call immediately. diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc index f114ff161..148e27b15 100644 --- a/talk/base/thread_unittest.cc +++ b/talk/base/thread_unittest.cc @@ -159,7 +159,6 @@ class Functor2 { bool* flag_; }; - // See: https://code.google.com/p/webrtc/issues/detail?id=2409 TEST(ThreadTest, DISABLED_Main) { const SocketAddress addr("127.0.0.1", 0); diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc index 5529c2abc..bbe5eb759 100644 --- a/talk/media/base/constants.cc +++ b/talk/media/base/constants.cc @@ -85,6 +85,7 @@ const char* kRtcpFbCcmParamFir = "fir"; const char* kCodecParamMaxBitrate = "x-google-max-bitrate"; const char* kCodecParamMinBitrate = "x-google-min-bitrate"; const char* kCodecParamMaxQuantization = "x-google-max-quantization"; +const char* kCodecParamPort = "x-google-port"; const int kGoogleRtpDataCodecId = 101; const char kGoogleRtpDataCodecName[] = "google-data"; diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h index afcfb13ff..80af77ee2 100644 --- a/talk/media/base/constants.h +++ b/talk/media/base/constants.h @@ -99,6 +99,7 @@ extern const char* kRtcpFbCcmParamFir; extern const char* kCodecParamMaxBitrate; extern const char* kCodecParamMinBitrate; extern const char* kCodecParamMaxQuantization; +extern const char* kCodecParamPort; // We put the data codec names here so callers of // DataEngine::CreateChannel don't have to import rtpdataengine.h or diff --git a/talk/media/base/fakemediaengine.h b/talk/media/base/fakemediaengine.h index 257c1981d..c44db68cd 100644 --- a/talk/media/base/fakemediaengine.h +++ b/talk/media/base/fakemediaengine.h @@ -837,6 +837,9 @@ class FakeVideoEngine : public FakeBaseEngine { default_encoder_config_ = config; return true; } + VideoEncoderConfig GetDefaultEncoderConfig() const { + return default_encoder_config_; + } const VideoEncoderConfig& default_encoder_config() const { return default_encoder_config_; } diff --git a/talk/media/base/filemediaengine.h b/talk/media/base/filemediaengine.h index b7a7ac17d..9cdfe8306 100644 --- a/talk/media/base/filemediaengine.h +++ b/talk/media/base/filemediaengine.h @@ -93,6 +93,9 @@ class FileMediaEngine : public MediaEngineInterface { virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return true; } + virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const { + return VideoEncoderConfig(); + } virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) { return true; } diff --git a/talk/media/base/hybridvideoengine.h b/talk/media/base/hybridvideoengine.h index 7bef97ebd..ab62cc7e8 100644 --- a/talk/media/base/hybridvideoengine.h +++ b/talk/media/base/hybridvideoengine.h @@ -204,6 +204,20 @@ class HybridVideoEngine : public HybridVideoEngineInterface { } return true; } + VideoEncoderConfig GetDefaultEncoderConfig() const { + // This looks pretty strange, but, in practice, it'll do sane things if + // GetDefaultEncoderConfig is only called after SetDefaultEncoderConfig, + // since both engines should be essentially equivalent at that point. If it + // hasn't been called, though, we'll use the first meaningful encoder + // config, or the config from the second video engine if neither are + // meaningful. + VideoEncoderConfig config = video1_.GetDefaultEncoderConfig(); + if (config.max_codec.width != 0) { + return config; + } else { + return video2_.GetDefaultEncoderConfig(); + } + } const std::vector& codecs() const { return codecs_; } diff --git a/talk/media/base/mediaengine.h b/talk/media/base/mediaengine.h index c9baeb2ec..f9165728d 100644 --- a/talk/media/base/mediaengine.h +++ b/talk/media/base/mediaengine.h @@ -98,6 +98,10 @@ class MediaEngineInterface { // and encode video. virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) = 0; + // Gets the default (maximum) codec/resolution and encoder option used to + // capture and encode video, as set by SetDefaultVideoEncoderConfig or the + // default from the video engine if not previously set. + virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const = 0; // Device selection // TODO(tschmelcher): Add method for selecting the soundclip device. @@ -203,6 +207,9 @@ class CompositeMediaEngine : public MediaEngineInterface { virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return video_.SetDefaultEncoderConfig(config); } + virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const { + return video_.GetDefaultEncoderConfig(); + } virtual bool SetSoundDevices(const Device* in_device, const Device* out_device) { @@ -327,6 +334,9 @@ class NullVideoEngine { return NULL; } bool SetOptions(const VideoOptions& options) { return true; } + VideoEncoderConfig GetDefaultEncoderConfig() const { + return VideoEncoderConfig(); + } bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) { return true; } diff --git a/talk/media/devices/libudevsymboltable.cc b/talk/media/devices/libudevsymboltable.cc index b1d9d31e6..9620cd2f2 100644 --- a/talk/media/devices/libudevsymboltable.cc +++ b/talk/media/devices/libudevsymboltable.cc @@ -27,6 +27,10 @@ #include "talk/media/devices/libudevsymboltable.h" +#include + +#include "talk/base/logging.h" + namespace cricket { #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME @@ -37,4 +41,30 @@ namespace cricket { #undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST #undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME +bool IsWrongLibUDevAbiVersion(talk_base::DllHandle libudev_0) { + talk_base::DllHandle libudev_1 = dlopen("libudev.so.1", RTLD_NOW|RTLD_NOLOAD); + bool unsafe_symlink = (libudev_0 == libudev_1); + if (unsafe_symlink) { + // .0 and .1 are distinct ABIs, so if they point to the same thing then one + // of them must be wrong. Probably the old has been symlinked to the new in + // a misguided attempt at backwards compatibility. + LOG(LS_ERROR) << "libudev.so.0 and libudev.so.1 unsafely point to the" + " same thing; not using libudev"; + } else if (libudev_1) { + // If libudev.so.1 is resident but distinct from libudev.so.0, then some + // system library loaded the new ABI separately. This is not a problem for + // LateBindingSymbolTable because its symbol look-ups are restricted to its + // DllHandle, but having libudev.so.0 resident may cause problems for that + // system library because symbol names are not namespaced by DLL. + LOG(LS_WARNING) + << "libudev.so.1 is resident but distinct from libudev.so.0"; + } + if (libudev_1) { + // Release the refcount that we acquired above. (Does not unload the DLL; + // whoever loaded it still needs it.) + dlclose(libudev_1); + } + return unsafe_symlink; +} + } // namespace cricket diff --git a/talk/media/devices/libudevsymboltable.h b/talk/media/devices/libudevsymboltable.h index 046a4b0e8..aa8c59035 100644 --- a/talk/media/devices/libudevsymboltable.h +++ b/talk/media/devices/libudevsymboltable.h @@ -66,6 +66,14 @@ namespace cricket { #undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME #undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +// libudev has changed ABIs to libudev.so.1 in recent distros and lots of users +// and/or software (including Google Chrome) are symlinking the old to the new. +// The entire point of ABI versions is that you can't safely do that, and +// it has caused crashes in the wild. This function checks if the DllHandle that +// we got back for libudev.so.0 is actually for libudev.so.1. If so, the library +// cannot safely be used. +bool IsWrongLibUDevAbiVersion(talk_base::DllHandle libudev_0); + } // namespace cricket #endif // TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_ diff --git a/talk/media/devices/linuxdeviceinfo.cc b/talk/media/devices/linuxdeviceinfo.cc index 7b5200112..b1bc9ddb1 100644 --- a/talk/media/devices/linuxdeviceinfo.cc +++ b/talk/media/devices/linuxdeviceinfo.cc @@ -52,7 +52,8 @@ class ScopedLibUdev { ScopedLibUdev() {} bool Init() { - return libudev_.Load(); + return libudev_.Load() && + !IsWrongLibUDevAbiVersion(libudev_.GetDllHandle()); } LibUDevSymbolTable libudev_; diff --git a/talk/media/devices/linuxdevicemanager.cc b/talk/media/devices/linuxdevicemanager.cc index 2096eeb27..e3e55ff79 100644 --- a/talk/media/devices/linuxdevicemanager.cc +++ b/talk/media/devices/linuxdevicemanager.cc @@ -306,8 +306,9 @@ bool LinuxDeviceWatcher::Start() { // We deliberately return true in the failure paths here because libudev is // not a critical component of a Linux system so it may not be present/usable, // and we don't want to halt LinuxDeviceManager initialization in such a case. - if (!libudev_.Load()) { - LOG(LS_WARNING) << "libudev not present/usable; LinuxDeviceWatcher disabled"; + if (!libudev_.Load() || IsWrongLibUDevAbiVersion(libudev_.GetDllHandle())) { + LOG(LS_WARNING) + << "libudev not present/usable; LinuxDeviceWatcher disabled"; return true; } udev_ = libudev_.udev_new()(); diff --git a/talk/media/sctp/sctpdataengine.cc b/talk/media/sctp/sctpdataengine.cc index 3d450c686..2e8e90c19 100644 --- a/talk/media/sctp/sctpdataengine.cc +++ b/talk/media/sctp/sctpdataengine.cc @@ -242,8 +242,8 @@ DataMediaChannel* SctpDataEngine::CreateChannel( SctpDataMediaChannel::SctpDataMediaChannel(talk_base::Thread* thread) : worker_thread_(thread), - local_port_(kSctpDefaultPort), - remote_port_(kSctpDefaultPort), + local_port_(-1), + remote_port_(-1), sock_(NULL), sending_(false), receiving_(false), @@ -344,6 +344,12 @@ void SctpDataMediaChannel::CloseSctpSocket() { bool SctpDataMediaChannel::Connect() { LOG(LS_VERBOSE) << debug_name_ << "->Connect()."; + if (remote_port_ < 0) { + remote_port_ = kSctpDefaultPort; + } + if (local_port_ < 0) { + local_port_ = kSctpDefaultPort; + } // If we already have a socket connection, just return. if (sock_) { @@ -677,6 +683,38 @@ void SctpDataMediaChannel::OnNotificationAssocChange( } } +// Puts the specified |param| from the codec identified by |id| into |dest| +// and returns true. Or returns false if it wasn't there, leaving |dest| +// untouched. +static bool GetCodecIntParameter(const std::vector& codecs, + int id, const std::string& name, + const std::string& param, int* dest) { + std::string value; + Codec match_pattern; + match_pattern.id = id; + match_pattern.name = name; + for (size_t i = 0; i < codecs.size(); ++i) { + if (codecs[i].Matches(match_pattern)) { + if (codecs[i].GetParam(param, &value)) { + *dest = talk_base::FromString(value); + return true; + } + } + } + return false; +} + +bool SctpDataMediaChannel::SetSendCodecs(const std::vector& codecs) { + return GetCodecIntParameter( + codecs, kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, kCodecParamPort, + &remote_port_); +} + +bool SctpDataMediaChannel::SetRecvCodecs(const std::vector& codecs) { + return GetCodecIntParameter( + codecs, kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, kCodecParamPort, + &local_port_); +} void SctpDataMediaChannel::OnPacketFromSctpToNetwork( talk_base::Buffer* buffer) { diff --git a/talk/media/sctp/sctpdataengine.h b/talk/media/sctp/sctpdataengine.h index 429016e0e..cadf78c20 100644 --- a/talk/media/sctp/sctpdataengine.h +++ b/talk/media/sctp/sctpdataengine.h @@ -168,12 +168,8 @@ class SctpDataMediaChannel : public DataMediaChannel, const std::vector& extensions) { return true; } virtual bool SetSendRtpHeaderExtensions( const std::vector& extensions) { return true; } - virtual bool SetSendCodecs(const std::vector& codecs) { - return true; - } - virtual bool SetRecvCodecs(const std::vector& codecs) { - return true; - } + virtual bool SetSendCodecs(const std::vector& codecs); + virtual bool SetRecvCodecs(const std::vector& codecs); virtual void OnRtcpReceived(talk_base::Buffer* packet) {} virtual void OnReadyToSend(bool ready) {} @@ -211,7 +207,8 @@ class SctpDataMediaChannel : public DataMediaChannel, talk_base::Thread* worker_thread_; // The local and remote SCTP port to use. These are passed along the wire // and the listener and connector must be using the same port. It is not - // related to the ports at the IP level. + // related to the ports at the IP level. If set to -1, we default to + // kSctpDefaultPort. int local_port_; int remote_port_; struct socket* sock_; // The socket created by usrsctp_socket(...). diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc index cdc172727..3f4667dce 100644 --- a/talk/media/webrtc/webrtcvideoengine.cc +++ b/talk/media/webrtc/webrtcvideoengine.cc @@ -318,20 +318,32 @@ class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver { virtual void IncomingRate(const int videoChannel, const unsigned int framerate, const unsigned int bitrate) { + talk_base::CritScope cs(&crit_); ASSERT(video_channel_ == videoChannel); framerate_ = framerate; bitrate_ = bitrate; } virtual void RequestNewKeyFrame(const int videoChannel) { + talk_base::CritScope cs(&crit_); ASSERT(video_channel_ == videoChannel); ++firs_requested_; } - int framerate() const { return framerate_; } - int bitrate() const { return bitrate_; } - int firs_requested() const { return firs_requested_; } + int framerate() const { + talk_base::CritScope cs(&crit_); + return framerate_; + } + int bitrate() const { + talk_base::CritScope cs(&crit_); + return bitrate_; + } + int firs_requested() const { + talk_base::CritScope cs(&crit_); + return firs_requested_; + } private: + mutable talk_base::CriticalSection crit_; int video_channel_; int framerate_; int bitrate_; @@ -350,15 +362,23 @@ class WebRtcEncoderObserver : public webrtc::ViEEncoderObserver { virtual void OutgoingRate(const int videoChannel, const unsigned int framerate, const unsigned int bitrate) { + talk_base::CritScope cs(&crit_); ASSERT(video_channel_ == videoChannel); framerate_ = framerate; bitrate_ = bitrate; } - int framerate() const { return framerate_; } - int bitrate() const { return bitrate_; } + int framerate() const { + talk_base::CritScope cs(&crit_); + return framerate_; + } + int bitrate() const { + talk_base::CritScope cs(&crit_); + return bitrate_; + } private: + mutable talk_base::CriticalSection crit_; int video_channel_; int framerate_; int bitrate_; @@ -914,6 +934,17 @@ bool WebRtcVideoEngine::SetDefaultEncoderConfig( return SetDefaultCodec(config.max_codec); } +VideoEncoderConfig WebRtcVideoEngine::GetDefaultEncoderConfig() const { + ASSERT(!video_codecs_.empty()); + VideoCodec max_codec(kVideoCodecPrefs[0].payload_type, + kVideoCodecPrefs[0].name, + video_codecs_[0].width, + video_codecs_[0].height, + video_codecs_[0].framerate, + 0); + return VideoEncoderConfig(max_codec); +} + // SetDefaultCodec may be called while the capturer is running. For example, a // test call is started in a page with QVGA default codec, and then a real call // is started in another page with VGA default codec. This is the corner case @@ -924,6 +955,7 @@ bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) { return false; } + ASSERT(!video_codecs_.empty()); default_codec_format_ = VideoFormat( video_codecs_[0].width, video_codecs_[0].height, diff --git a/talk/media/webrtc/webrtcvideoengine.h b/talk/media/webrtc/webrtcvideoengine.h index 248975351..dba8cc966 100644 --- a/talk/media/webrtc/webrtcvideoengine.h +++ b/talk/media/webrtc/webrtcvideoengine.h @@ -106,6 +106,7 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, int GetCapabilities(); bool SetOptions(const VideoOptions &options); bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); + VideoEncoderConfig GetDefaultEncoderConfig() const; WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel); diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc index 0e964729e..f3dcf3b85 100644 --- a/talk/media/webrtc/webrtcvoiceengine.cc +++ b/talk/media/webrtc/webrtcvoiceengine.cc @@ -2158,6 +2158,11 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { return false; uint32 ssrc = sp.first_ssrc(); + if (ssrc == 0) { + LOG(LS_WARNING) << "AddRecvStream with 0 ssrc is not supported."; + return false; + } + if (receive_channels_.find(ssrc) != receive_channels_.end()) { LOG(LS_ERROR) << "Stream already exists with ssrc " << ssrc; return false; @@ -2181,6 +2186,21 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { return false; } + if (!ConfigureRecvChannel(channel)) { + DeleteChannel(channel); + return false; + } + + receive_channels_.insert( + std::make_pair(ssrc, WebRtcVoiceChannelInfo(channel, NULL))); + + LOG(LS_INFO) << "New audio stream " << ssrc + << " registered to VoiceEngine channel #" + << channel << "."; + return true; +} + +bool WebRtcVoiceMediaChannel::ConfigureRecvChannel(int channel) { // Configure to use external transport, like our default channel. if (engine()->voe()->network()->RegisterExternalTransport( channel, *this) == -1) { @@ -2237,13 +2257,6 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { } SetNack(channel, nack_enabled_); - receive_channels_.insert( - std::make_pair(ssrc, WebRtcVoiceChannelInfo(channel, NULL))); - - // TODO(juberti): We should rollback the add if SetPlayout fails. - LOG(LS_INFO) << "New audio stream " << ssrc - << " registered to VoiceEngine channel #" - << channel << "."; return SetPlayout(channel, playout_); } diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h index 6e11485fc..9e8ef86ee 100644 --- a/talk/media/webrtc/webrtcvoiceengine.h +++ b/talk/media/webrtc/webrtcvoiceengine.h @@ -381,6 +381,7 @@ class WebRtcVoiceMediaChannel bool ChangeSend(SendFlags send); bool ChangeSend(int channel, SendFlags send); void ConfigureSendChannel(int channel); + bool ConfigureRecvChannel(int channel); bool DeleteChannel(int channel); bool InConferenceMode() const { return options_.conference_mode.GetWithDefaultIfUnset(false); diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc index 2b2d36a40..883ff4856 100644 --- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc @@ -2044,6 +2044,26 @@ TEST_F(WebRtcVoiceEngineTestFake, StreamCleanup) { EXPECT_EQ(0, voe_.GetNumChannels()); } +TEST_F(WebRtcVoiceEngineTestFake, TestAddRecvStreamFailWithZeroSsrc) { + EXPECT_TRUE(SetupEngine()); + EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(0))); +} + +TEST_F(WebRtcVoiceEngineTestFake, TestNoLeakingWhenAddRecvStreamFail) { + EXPECT_TRUE(SetupEngine()); + // Stream 1 reuses default channel. + EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1))); + // Manually delete default channel to simulate a failure. + int default_channel = voe_.GetLastChannel(); + EXPECT_EQ(0, voe_.DeleteChannel(default_channel)); + // Add recv stream 2 should fail because default channel is gone. + EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2))); + int new_channel = voe_.GetLastChannel(); + EXPECT_NE(default_channel, new_channel); + // The last created channel should have already been deleted. + EXPECT_EQ(-1, voe_.DeleteChannel(new_channel)); +} + // Test the InsertDtmf on default send stream as caller. TEST_F(WebRtcVoiceEngineTestFake, InsertDtmfOnDefaultSendStreamAsCaller) { TestInsertDtmf(0, true); diff --git a/talk/session/media/channel.cc b/talk/session/media/channel.cc index cfe58a71d..e00ba16d0 100644 --- a/talk/session/media/channel.cc +++ b/talk/session/media/channel.cc @@ -411,6 +411,7 @@ BaseChannel::BaseChannel(talk_base::Thread* thread, BaseChannel::~BaseChannel() { ASSERT(worker_thread_ == talk_base::Thread::Current()); + Deinit(); StopConnectionMonitor(); FlushRtcpMessages(); // Send any outstanding RTCP packets. Clear(); // eats any outstanding messages or packets @@ -455,6 +456,10 @@ bool BaseChannel::Init(TransportChannel* transport_channel, return true; } +void BaseChannel::Deinit() { + media_channel_->SetInterface(NULL); +} + // Can be called from thread other than worker thread bool BaseChannel::Enable(bool enable) { Send(enable ? MSG_ENABLE : MSG_DISABLE); @@ -1466,6 +1471,7 @@ VoiceChannel::~VoiceChannel() { StopMediaMonitor(); // this can't be done in the base class, since it calls a virtual DisableMedia_w(); + Deinit(); } bool VoiceChannel::Init() { @@ -1961,6 +1967,8 @@ VideoChannel::~VideoChannel() { StopMediaMonitor(); // this can't be done in the base class, since it calls a virtual DisableMedia_w(); + + Deinit(); } bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) { @@ -2464,6 +2472,8 @@ DataChannel::~DataChannel() { StopMediaMonitor(); // this can't be done in the base class, since it calls a virtual DisableMedia_w(); + + Deinit(); } bool DataChannel::Init() { diff --git a/talk/session/media/channel.h b/talk/session/media/channel.h index 0d66be9a9..ccfdd07fe 100644 --- a/talk/session/media/channel.h +++ b/talk/session/media/channel.h @@ -66,6 +66,12 @@ enum SinkType { // BaseChannel contains logic common to voice and video, including // enable/mute, marshaling calls to a worker thread, and // connection and media monitors. +// +// WARNING! SUBCLASSES MUST CALL Deinit() IN THEIR DESTRUCTORS! +// This is required to avoid a data race between the destructor modifying the +// vtable, and the media channel's thread using BaseChannel as the +// NetworkInterface. + class BaseChannel : public talk_base::MessageHandler, public sigslot::has_slots<>, public MediaChannel::NetworkInterface { @@ -76,6 +82,9 @@ class BaseChannel virtual ~BaseChannel(); bool Init(TransportChannel* transport_channel, TransportChannel* rtcp_transport_channel); + // Deinit may be called multiple times and is simply ignored if it's alreay + // done. + void Deinit(); talk_base::Thread* worker_thread() const { return worker_thread_; } BaseSession* session() const { return session_; } diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc index 799fe5f5c..3d1c46842 100644 --- a/talk/session/media/mediasession.cc +++ b/talk/session/media/mediasession.cc @@ -599,6 +599,7 @@ static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group, return false; } + bool common_cryptos_needed = false; // Get the common cryptos. const ContentNames& content_names = bundle_group.content_names(); CryptoParamsVec common_cryptos; @@ -607,6 +608,11 @@ static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group, if (!IsRtpContent(sdesc, *it)) { continue; } + // The common cryptos are needed if any of the content does not have DTLS + // enabled. + if (!sdesc->GetTransportInfoByName(*it)->description.secure()) { + common_cryptos_needed = true; + } if (it == content_names.begin()) { // Initial the common_cryptos with the first content in the bundle group. if (!GetCryptosByName(sdesc, *it, &common_cryptos)) { @@ -625,7 +631,7 @@ static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group, } } - if (common_cryptos.empty()) { + if (common_cryptos.empty() && common_cryptos_needed) { return false; } diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc index f2e576ca9..f014e78fe 100644 --- a/talk/session/media/mediasession_unittest.cc +++ b/talk/session/media/mediasession_unittest.cc @@ -536,8 +536,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol()); } -// Create a typical data offer, and ensure it matches what we expect. -TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataOffer) { +// Create a RTP data offer, and ensure it matches what we expect. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) { MediaSessionOptions opts; opts.data_channel_type = cricket::DCT_RTP; f1_.set_secure(SEC_ENABLED); @@ -571,6 +571,18 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataOffer) { EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol()); } +// Create an SCTP data offer with bundle without error. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) { + MediaSessionOptions opts; + opts.has_audio = false; + opts.bundle_enabled = true; + opts.data_channel_type = cricket::DCT_SCTP; + f1_.set_secure(SEC_ENABLED); + talk_base::scoped_ptr offer(f1_.CreateOffer(opts, NULL)); + EXPECT_TRUE(offer.get() != NULL); + EXPECT_TRUE(offer->GetContentByName("data") != NULL); +} + // Create an audio, video offer without legacy StreamParams. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithoutLegacyStreams) {