diff --git a/p2p/base/connection.cc b/p2p/base/connection.cc index c5e6993c87..1ef42cc76f 100644 --- a/p2p/base/connection.cc +++ b/p2p/base/connection.cc @@ -704,6 +704,28 @@ void Connection::SendStunBindingResponse(const StunMessage* message) { } } + const StunByteStringAttribute* delta = + message->GetByteString(STUN_ATTR_GOOG_DELTA); + if (delta) { + if (field_trials_->answer_goog_delta && goog_delta_consumer_) { + auto ack = (*goog_delta_consumer_)(delta); + if (ack) { + RTC_LOG(LS_INFO) << "Sending GOOG_DELTA_ACK" + << " delta len: " << delta->length(); + response.AddAttribute(std::move(ack)); + } else { + RTC_LOG(LS_ERROR) << "GOOG_DELTA consumer did not return ack!"; + } + } else { + RTC_LOG(LS_WARNING) << "Ignore GOOG_DELTA" + << " len: " << delta->length() + << " answer_goog_delta = " + << field_trials_->answer_goog_delta + << " goog_delta_consumer_ = " + << goog_delta_consumer_.has_value(); + } + } + response.AddMessageIntegrity(local_candidate().password()); response.AddFingerprint(); @@ -933,7 +955,8 @@ int64_t Connection::last_ping_sent() const { return last_ping_sent_; } -void Connection::Ping(int64_t now) { +void Connection::Ping(int64_t now, + std::unique_ptr delta) { RTC_DCHECK_RUN_ON(network_thread_); if (!port_) return; @@ -948,10 +971,11 @@ void Connection::Ping(int64_t now) { nomination = nomination_; } - auto req = - std::make_unique(requests_, this, BuildPingRequest()); + bool has_delta = delta != nullptr; + auto req = std::make_unique( + requests_, this, BuildPingRequest(std::move(delta))); - if (ShouldSendGoogPing(req->msg())) { + if (!has_delta && ShouldSendGoogPing(req->msg())) { auto message = std::make_unique(GOOG_PING_REQUEST, req->id()); message->AddMessageIntegrity32(remote_candidate_.password()); req.reset(new ConnectionRequest(requests_, this, std::move(message))); @@ -966,7 +990,8 @@ void Connection::Ping(int64_t now) { num_pings_sent_++; } -std::unique_ptr Connection::BuildPingRequest() { +std::unique_ptr Connection::BuildPingRequest( + std::unique_ptr delta) { auto message = std::make_unique(STUN_BINDING_REQUEST); // Note that the order of attributes does not impact the parsing on the // receiver side. The attribute is retrieved then by iterating and matching @@ -1022,6 +1047,13 @@ std::unique_ptr Connection::BuildPingRequest() { list->AddTypeAtIndex(kSupportGoogPingVersionRequestIndex, kGoogPingVersion); message->AddAttribute(std::move(list)); } + + if (delta) { + RTC_DCHECK(delta->type() == STUN_ATTR_GOOG_DELTA); + RTC_LOG(LS_INFO) << "Sending GOOG_DELTA: len: " << delta->length(); + message->AddAttribute(std::move(delta)); + } + message->AddMessageIntegrity(remote_candidate_.password()); message->AddFingerprint(); @@ -1393,6 +1425,34 @@ void Connection::OnConnectionRequestResponse(StunRequest* request, cached_stun_binding_ = request->msg()->Clone(); } } + + // Did we send a delta ? + const bool sent_goog_delta = + request->msg()->GetByteString(STUN_ATTR_GOOG_DELTA) != nullptr; + // Did we get a GOOG_DELTA_ACK ? + const StunUInt64Attribute* delta_ack = + response->GetUInt64(STUN_ATTR_GOOG_DELTA_ACK); + + if (goog_delta_ack_consumer_) { + if (sent_goog_delta && delta_ack) { + RTC_LOG(LS_VERBOSE) << "Got GOOG_DELTA_ACK len: " << delta_ack->length(); + (*goog_delta_ack_consumer_)(delta_ack); + } else if (sent_goog_delta) { + // We sent DELTA but did not get a DELTA_ACK. + // This means that remote does not support GOOG_DELTA + RTC_LOG(LS_INFO) << "NO DELTA ACK => disable GOOG_DELTA"; + (*goog_delta_ack_consumer_)( + webrtc::RTCError(webrtc::RTCErrorType::UNSUPPORTED_OPERATION)); + } else if (delta_ack) { + // We did NOT send DELTA but got a DELTA_ACK. + // That is internal error. + RTC_LOG(LS_ERROR) << "DELTA ACK w/o DELTA => disable GOOG_DELTA"; + (*goog_delta_ack_consumer_)( + webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR)); + } + } else if (delta_ack) { + RTC_LOG(LS_ERROR) << "Discard GOOG_DELTA_ACK, no consumer"; + } } void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request, diff --git a/p2p/base/connection.h b/p2p/base/connection.h index 4e6c7d91be..8e439855fa 100644 --- a/p2p/base/connection.h +++ b/p2p/base/connection.h @@ -13,6 +13,7 @@ #include #include +#include #include #include "absl/strings/string_view.h" @@ -205,12 +206,15 @@ class RTC_EXPORT Connection : public CandidatePairInterface { // Called when this connection should try checking writability again. int64_t last_ping_sent() const; - void Ping(int64_t now); + void Ping(int64_t now, + std::unique_ptr delta = nullptr); void ReceivedPingResponse( int rtt, absl::string_view request_id, const absl::optional& nomination = absl::nullopt); - std::unique_ptr BuildPingRequest() RTC_RUN_ON(network_thread_); + std::unique_ptr BuildPingRequest( + std::unique_ptr delta) + RTC_RUN_ON(network_thread_); int64_t last_ping_response_received() const; const absl::optional& last_ping_id_received() const; @@ -319,7 +323,7 @@ class RTC_EXPORT Connection : public CandidatePairInterface { std::unique_ptr BuildPingRequestForTest() { RTC_DCHECK_RUN_ON(network_thread_); - return BuildPingRequest(); + return BuildPingRequest(nullptr); } // Public for unit tests. @@ -333,6 +337,20 @@ class RTC_EXPORT Connection : public CandidatePairInterface { remote_candidate_.set_password(pwd); } + void SetStunDictConsumer( + std::function( + const StunByteStringAttribute*)> goog_delta_consumer, + std::function)> + goog_delta_ack_consumer) { + goog_delta_consumer_ = std::move(goog_delta_consumer); + goog_delta_ack_consumer_ = std::move(goog_delta_ack_consumer); + } + + void ClearStunDictConsumer() { + goog_delta_consumer_ = absl::nullopt; + goog_delta_ack_consumer_ = absl::nullopt; + } + protected: // A ConnectionRequest is a simple STUN ping used to determine writability. class ConnectionRequest; @@ -475,6 +493,13 @@ class RTC_EXPORT Connection : public CandidatePairInterface { const IceFieldTrials* field_trials_; rtc::EventBasedExponentialMovingAverage rtt_estimate_ RTC_GUARDED_BY(network_thread_); + + absl::optional( + const StunByteStringAttribute*)>> + goog_delta_consumer_; + absl::optional< + std::function)>> + goog_delta_ack_consumer_; }; // ProxyConnection defers all the interesting work to the port. diff --git a/p2p/base/ice_transport_internal.h b/p2p/base/ice_transport_internal.h index 55f12382aa..98deb492b0 100644 --- a/p2p/base/ice_transport_internal.h +++ b/p2p/base/ice_transport_internal.h @@ -14,6 +14,7 @@ #include #include +#include #include #include "absl/strings/string_view.h" @@ -24,6 +25,7 @@ #include "p2p/base/connection.h" #include "p2p/base/packet_transport_internal.h" #include "p2p/base/port.h" +#include "p2p/base/stun_dictionary.h" #include "p2p/base/transport_description.h" #include "rtc_base/network_constants.h" #include "rtc_base/system/rtc_export.h" @@ -293,6 +295,11 @@ class RTC_EXPORT IceTransportInternal : public rtc::PacketTransportInternal { virtual absl::optional GetSelectedCandidatePair() const = 0; + virtual absl::optional> + GetDictionaryWriter() { + return absl::nullopt; + } + sigslot::signal1 SignalGatheringState; // Handles sending and receiving of candidates. @@ -330,6 +337,37 @@ class RTC_EXPORT IceTransportInternal : public rtc::PacketTransportInternal { // Invoked when the transport is being destroyed. sigslot::signal1 SignalDestroyed; + + // Invoked when remote dictionary has been updated, + // i.e. modifications to attributes from remote ice agent has + // reflected in our StunDictionaryView. + template + void AddDictionaryViewUpdatedCallback(const void* tag, F&& callback) { + dictionary_view_updated_callback_list_.AddReceiver( + tag, std::forward(callback)); + } + void RemoveDictionaryViewUpdatedCallback(const void* tag) { + dictionary_view_updated_callback_list_.RemoveReceivers(tag); + } + + // Invoked when local dictionary has been synchronized, + // i.e. remote ice agent has reported acknowledged updates from us. + template + void AddDictionaryWriterSyncedCallback(const void* tag, F&& callback) { + dictionary_writer_synced_callback_list_.AddReceiver( + tag, std::forward(callback)); + } + void RemoveDictionaryWriterSyncedCallback(const void* tag) { + dictionary_writer_synced_callback_list_.RemoveReceivers(tag); + } + + protected: + webrtc::CallbackList> + dictionary_view_updated_callback_list_; + webrtc::CallbackList + dictionary_writer_synced_callback_list_; }; } // namespace cricket diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc index af4b800930..d916983fe2 100644 --- a/p2p/base/p2p_transport_channel.cc +++ b/p2p/base/p2p_transport_channel.cc @@ -286,6 +286,13 @@ void P2PTransportChannel::AddConnection(Connection* connection) { connection->set_ice_event_log(&ice_event_log_); connection->SetIceFieldTrials(&ice_field_trials_); + connection->SetStunDictConsumer( + [this](const StunByteStringAttribute* delta) { + return GoogDeltaReceived(delta); + }, + [this](webrtc::RTCErrorOr delta_ack) { + GoogDeltaAckReceived(std::move(delta_ack)); + }); LogCandidatePairConfig(connection, webrtc::IceCandidatePairConfigType::kAdded); @@ -727,7 +734,10 @@ void P2PTransportChannel::ParseFieldTrials( &ice_field_trials_.dead_connection_timeout_ms, // Stop gathering on strongly connected. "stop_gather_on_strongly_connected", - &ice_field_trials_.stop_gather_on_strongly_connected) + &ice_field_trials_.stop_gather_on_strongly_connected, + // GOOG_DELTA + "enable_goog_delta", &ice_field_trials_.enable_goog_delta, + "answer_goog_delta", &ice_field_trials_.answer_goog_delta) ->Parse(field_trials->Lookup("WebRTC-IceFieldTrials")); if (ice_field_trials_.dead_connection_timeout_ms < 30000) { @@ -782,6 +792,10 @@ void P2PTransportChannel::ParseFieldTrials( ice_field_trials_.extra_ice_ping = field_trials->IsEnabled("WebRTC-ExtraICEPing"); + + if (!ice_field_trials_.enable_goog_delta) { + stun_dict_writer_.Disable(); + } } const IceConfig& P2PTransportChannel::config() const { @@ -2058,7 +2072,7 @@ void P2PTransportChannel::PingConnection(Connection* conn) { conn->set_nomination(nomination); conn->set_use_candidate_attr(use_candidate_attr); last_ping_sent_ms_ = rtc::TimeMillis(); - conn->Ping(last_ping_sent_ms_); + conn->Ping(last_ping_sent_ms_, stun_dict_writer_.CreateDelta()); } uint32_t P2PTransportChannel::GetNominationAttr(Connection* conn) const { @@ -2129,11 +2143,12 @@ void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { } } -void P2PTransportChannel::RemoveConnection(const Connection* connection) { +void P2PTransportChannel::RemoveConnection(Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); auto it = absl::c_find(connections_, connection); RTC_DCHECK(it != connections_.end()); connections_.erase(it); + connection->ClearStunDictConsumer(); ice_controller_->OnConnectionDestroyed(connection); } @@ -2307,4 +2322,34 @@ void P2PTransportChannel::LogCandidatePairConfig( conn->ToLogDescription()); } +std::unique_ptr P2PTransportChannel::GoogDeltaReceived( + const StunByteStringAttribute* delta) { + auto error = stun_dict_view_.ApplyDelta(*delta); + if (error.ok()) { + auto& result = error.value(); + RTC_LOG(LS_INFO) << "Applied GOOG_DELTA"; + dictionary_view_updated_callback_list_.Send(this, stun_dict_view_, + result.second); + return std::move(result.first); + } else { + RTC_LOG(LS_ERROR) << "Failed to apply GOOG_DELTA: " + << error.error().message(); + } + return nullptr; +} + +void P2PTransportChannel::GoogDeltaAckReceived( + webrtc::RTCErrorOr error_or_ack) { + if (error_or_ack.ok()) { + RTC_LOG(LS_ERROR) << "Applied GOOG_DELTA_ACK"; + auto ack = error_or_ack.value(); + stun_dict_writer_.ApplyDeltaAck(*ack); + dictionary_writer_synced_callback_list_.Send(this, stun_dict_writer_); + } else { + stun_dict_writer_.Disable(); + RTC_LOG(LS_ERROR) << "Failed GOOG_DELTA_ACK: " + << error_or_ack.error().message(); + } +} + } // namespace cricket diff --git a/p2p/base/p2p_transport_channel.h b/p2p/base/p2p_transport_channel.h index a0729c163d..dc27b202c2 100644 --- a/p2p/base/p2p_transport_channel.h +++ b/p2p/base/p2p_transport_channel.h @@ -61,6 +61,7 @@ #include "p2p/base/port_allocator.h" #include "p2p/base/port_interface.h" #include "p2p/base/regathering_controller.h" +#include "p2p/base/stun_dictionary.h" #include "p2p/base/transport_description.h" #include "rtc_base/async_packet_socket.h" #include "rtc_base/checks.h" @@ -216,7 +217,7 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal, int check_receiving_interval() const; absl::optional network_route() const override; - void RemoveConnection(const Connection* connection); + void RemoveConnection(Connection* connection); // Helper method used only in unittest. rtc::DiffServCodePoint DefaultDscpValue() const; @@ -254,6 +255,11 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal, return ss.Release(); } + absl::optional> + GetDictionaryWriter() override { + return stun_dict_writer_; + } + private: P2PTransportChannel( absl::string_view transport_name, @@ -494,6 +500,10 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal, Candidate candidate, const webrtc::AsyncDnsResolverResult& result); + std::unique_ptr GoogDeltaReceived( + const StunByteStringAttribute*); + void GoogDeltaAckReceived(webrtc::RTCErrorOr); + // Bytes/packets sent/received on this channel. uint64_t bytes_sent_ = 0; uint64_t bytes_received_ = 0; @@ -509,6 +519,12 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal, // Parsed field trials. IceFieldTrials ice_field_trials_; + + // A dictionary of attributes that will be reflected to peer. + StunDictionaryWriter stun_dict_writer_; + + // A dictionary that tracks attributes from peer. + StunDictionaryView stun_dict_view_; }; } // namespace cricket diff --git a/p2p/base/p2p_transport_channel_ice_field_trials.h b/p2p/base/p2p_transport_channel_ice_field_trials.h index f19823b21e..96a7756484 100644 --- a/p2p/base/p2p_transport_channel_ice_field_trials.h +++ b/p2p/base/p2p_transport_channel_ice_field_trials.h @@ -70,6 +70,10 @@ struct IceFieldTrials { bool piggyback_ice_check_acknowledgement = false; bool extra_ice_ping = false; + + // Announce/enable GOOG_DELTA + bool enable_goog_delta = true; // send GOOG DELTA + bool answer_goog_delta = true; // answer GOOG DELTA }; } // namespace cricket diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc index 02cc483d31..ca8ca8de55 100644 --- a/p2p/base/p2p_transport_channel_unittest.cc +++ b/p2p/base/p2p_transport_channel_unittest.cc @@ -65,6 +65,7 @@ using ::testing::DoAll; using ::testing::InSequence; using ::testing::InvokeArgument; using ::testing::InvokeWithoutArgs; +using ::testing::MockFunction; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SaveArg; @@ -3417,6 +3418,38 @@ TEST_F(P2PTransportChannelMultihomedTest, TestVpnOnlyVpn) { kDefaultTimeout, clock); } +TEST_F(P2PTransportChannelMultihomedTest, StunDictionaryPerformsSync) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + // Create channels and let them go writable, as usual. + CreateChannels(); + + MockFunction)> + view_updated_func; + ep2_ch1()->AddDictionaryViewUpdatedCallback( + "tag", view_updated_func.AsStdFunction()); + MockFunction + writer_synced_func; + ep1_ch1()->AddDictionaryWriterSyncedCallback( + "tag", writer_synced_func.AsStdFunction()); + auto& dict_writer = ep1_ch1()->GetDictionaryWriter()->get(); + dict_writer.SetByteString(12)->CopyBytes("keso"); + EXPECT_CALL(view_updated_func, Call) + .WillOnce([&](auto* channel, auto& view, auto keys) { + EXPECT_EQ(keys.size(), 1u); + EXPECT_EQ(keys[0], 12); + EXPECT_EQ(view.GetByteString(12)->string_view(), "keso"); + }); + EXPECT_CALL(writer_synced_func, Call).Times(1); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); +} + // A collection of tests which tests a single P2PTransportChannel by sending // pings. class P2PTransportChannelPingTest : public ::testing::Test, diff --git a/p2p/base/port_unittest.cc b/p2p/base/port_unittest.cc index 1b1c156136..b27afe2f39 100644 --- a/p2p/base/port_unittest.cc +++ b/p2p/base/port_unittest.cc @@ -20,6 +20,7 @@ #include #include +#include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "api/candidate.h" @@ -3833,7 +3834,6 @@ class ConnectionTest : public PortTest { void OnConnectionStateChange(Connection* connection) { num_state_changes_++; } - private: std::unique_ptr lport_; std::unique_ptr rport_; }; @@ -3922,4 +3922,93 @@ TEST_F(ConnectionTest, ConnectionForgetLearnedStateDoesNotTriggerStateChange) { EXPECT_EQ(num_state_changes_, 2); } +// Test normal happy case. +// Sending a delta and getting a delta ack in response. +TEST_F(ConnectionTest, SendReceiveGoogDelta) { + constexpr int64_t ms = 10; + Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); + Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); + + std::unique_ptr delta = + absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA)); + delta->CopyBytes("DELTA"); + + std::unique_ptr delta_ack = + absl::WrapUnique(new StunUInt64Attribute(STUN_ATTR_GOOG_DELTA_ACK, 133)); + + bool received_goog_delta = false; + bool received_goog_delta_ack = false; + lconn->SetStunDictConsumer( + // DeltaReceived + [](const StunByteStringAttribute* delta) + -> std::unique_ptr { return nullptr; }, + // DeltaAckReceived + [&](webrtc::RTCErrorOr error_or_ack) { + received_goog_delta_ack = true; + EXPECT_TRUE(error_or_ack.ok()); + EXPECT_EQ(error_or_ack.value()->value(), 133ull); + }); + + rconn->SetStunDictConsumer( + // DeltaReceived + [&](const StunByteStringAttribute* delta) + -> std::unique_ptr { + received_goog_delta = true; + EXPECT_EQ(delta->string_view(), "DELTA"); + return std::move(delta_ack); + }, + // DeltaAckReceived + [](webrtc::RTCErrorOr error_or__ack) {}); + + lconn->Ping(rtc::TimeMillis(), std::move(delta)); + ASSERT_TRUE_WAIT(lport_->last_stun_msg(), kDefaultTimeout); + ASSERT_TRUE(lport_->last_stun_buf()); + rconn->OnReadPacket(lport_->last_stun_buf()->data(), + lport_->last_stun_buf()->size(), /* packet_time_us */ -1); + EXPECT_TRUE(received_goog_delta); + + clock_.AdvanceTime(webrtc::TimeDelta::Millis(ms)); + ASSERT_TRUE_WAIT(rport_->last_stun_msg(), kDefaultTimeout); + ASSERT_TRUE(rport_->last_stun_buf()); + lconn->OnReadPacket(rport_->last_stun_buf()->data(), + rport_->last_stun_buf()->size(), /* packet_time_us */ -1); + EXPECT_TRUE(received_goog_delta_ack); +} + +// Test that sending a goog delta and not getting +// a delta ack in reply gives an error callback. +TEST_F(ConnectionTest, SendGoogDeltaNoReply) { + constexpr int64_t ms = 10; + Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); + Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); + + std::unique_ptr delta = + absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA)); + delta->CopyBytes("DELTA"); + + bool received_goog_delta_ack_error = false; + lconn->SetStunDictConsumer( + // DeltaReceived + [](const StunByteStringAttribute* delta) + -> std::unique_ptr { return nullptr; }, + // DeltaAckReceived + [&](webrtc::RTCErrorOr error_or_ack) { + received_goog_delta_ack_error = true; + EXPECT_FALSE(error_or_ack.ok()); + }); + + lconn->Ping(rtc::TimeMillis(), std::move(delta)); + ASSERT_TRUE_WAIT(lport_->last_stun_msg(), kDefaultTimeout); + ASSERT_TRUE(lport_->last_stun_buf()); + rconn->OnReadPacket(lport_->last_stun_buf()->data(), + lport_->last_stun_buf()->size(), /* packet_time_us */ -1); + + clock_.AdvanceTime(webrtc::TimeDelta::Millis(ms)); + ASSERT_TRUE_WAIT(rport_->last_stun_msg(), kDefaultTimeout); + ASSERT_TRUE(rport_->last_stun_buf()); + lconn->OnReadPacket(rport_->last_stun_buf()->data(), + rport_->last_stun_buf()->size(), /* packet_time_us */ -1); + EXPECT_TRUE(received_goog_delta_ack_error); +} + } // namespace cricket diff --git a/p2p/base/stun_dictionary.cc b/p2p/base/stun_dictionary.cc index 7e27a37b0a..bf6a1e49c2 100644 --- a/p2p/base/stun_dictionary.cc +++ b/p2p/base/stun_dictionary.cc @@ -233,7 +233,15 @@ size_t StunDictionaryView::GetLength(int key) const { return 0; } +void StunDictionaryWriter::Disable() { + disabled_ = true; +} + void StunDictionaryWriter::Delete(int key) { + if (disabled_) { + return; + } + if (dictionary_) { if (dictionary_->attrs_.find(key) == dictionary_->attrs_.end()) { return; @@ -262,6 +270,9 @@ void StunDictionaryWriter::Delete(int key) { } void StunDictionaryWriter::Set(std::unique_ptr attr) { + if (disabled_) { + return; + } int key = attr->type(); // remove any pending updates. pending_.erase( @@ -284,6 +295,9 @@ void StunDictionaryWriter::Set(std::unique_ptr attr) { // Create an StunByteStringAttribute containing the pending (e.g not ack:ed) // modifications. std::unique_ptr StunDictionaryWriter::CreateDelta() { + if (disabled_) { + return nullptr; + } if (pending_.empty()) { return nullptr; } diff --git a/p2p/base/stun_dictionary.h b/p2p/base/stun_dictionary.h index ae2ad75192..f93a1f151f 100644 --- a/p2p/base/stun_dictionary.h +++ b/p2p/base/stun_dictionary.h @@ -62,7 +62,7 @@ class StunDictionaryView { const StunUInt16ListAttribute* GetUInt16List(int key) const; bool empty() const { return attrs_.empty(); } - int size() const { return attrs_.size(); } + size_t size() const { return attrs_.size(); } int bytes_stored() const { return bytes_stored_; } void set_max_bytes_stored(int max_bytes_stored) { max_bytes_stored_ = max_bytes_stored; @@ -175,9 +175,17 @@ class StunDictionaryWriter { const StunDictionaryView* dictionary() { return dictionary_.get(); } const StunDictionaryView* operator->() { return dictionary_.get(); } + // Disable writer, + // i.e CreateDelta always return null, and no modifications are made. + // This is called if remote peer does not support GOOG_DELTA. + void Disable(); + bool disabled() const { return disabled_; } + private: void Set(std::unique_ptr attr); + bool disabled_ = false; + // version of modification. int64_t version_ = 1;