Add StunDictionary - patch 2

This patch hooks up the StunDictionary to Connection
and P2PTransportChannel.

Bug: webrtc:15392
Change-Id: Ibeea4d8706ebd42f2353d9d300631c02bf0d484d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/315100
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40519}
This commit is contained in:
Jonas Oreland 2023-08-07 13:39:22 +02:00 committed by WebRTC LUCI CQ
parent 2bfa071d09
commit fc92d33327
10 changed files with 346 additions and 14 deletions

View File

@ -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<StunByteStringAttribute> 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<ConnectionRequest>(requests_, this, BuildPingRequest());
bool has_delta = delta != nullptr;
auto req = std::make_unique<ConnectionRequest>(
requests_, this, BuildPingRequest(std::move(delta)));
if (ShouldSendGoogPing(req->msg())) {
if (!has_delta && ShouldSendGoogPing(req->msg())) {
auto message = std::make_unique<IceMessage>(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<IceMessage> Connection::BuildPingRequest() {
std::unique_ptr<IceMessage> Connection::BuildPingRequest(
std::unique_ptr<StunByteStringAttribute> delta) {
auto message = std::make_unique<IceMessage>(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<IceMessage> 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,

View File

@ -13,6 +13,7 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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<StunByteStringAttribute> delta = nullptr);
void ReceivedPingResponse(
int rtt,
absl::string_view request_id,
const absl::optional<uint32_t>& nomination = absl::nullopt);
std::unique_ptr<IceMessage> BuildPingRequest() RTC_RUN_ON(network_thread_);
std::unique_ptr<IceMessage> BuildPingRequest(
std::unique_ptr<StunByteStringAttribute> delta)
RTC_RUN_ON(network_thread_);
int64_t last_ping_response_received() const;
const absl::optional<std::string>& last_ping_id_received() const;
@ -319,7 +323,7 @@ class RTC_EXPORT Connection : public CandidatePairInterface {
std::unique_ptr<IceMessage> 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<std::unique_ptr<StunAttribute>(
const StunByteStringAttribute*)> goog_delta_consumer,
std::function<void(webrtc::RTCErrorOr<const StunUInt64Attribute*>)>
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<std::function<std::unique_ptr<StunAttribute>(
const StunByteStringAttribute*)>>
goog_delta_consumer_;
absl::optional<
std::function<void(webrtc::RTCErrorOr<const StunUInt64Attribute*>)>>
goog_delta_ack_consumer_;
};
// ProxyConnection defers all the interesting work to the port.

View File

@ -14,6 +14,7 @@
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#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<const CandidatePair> GetSelectedCandidatePair()
const = 0;
virtual absl::optional<std::reference_wrapper<StunDictionaryWriter>>
GetDictionaryWriter() {
return absl::nullopt;
}
sigslot::signal1<IceTransportInternal*> 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<IceTransportInternal*> SignalDestroyed;
// Invoked when remote dictionary has been updated,
// i.e. modifications to attributes from remote ice agent has
// reflected in our StunDictionaryView.
template <typename F>
void AddDictionaryViewUpdatedCallback(const void* tag, F&& callback) {
dictionary_view_updated_callback_list_.AddReceiver(
tag, std::forward<F>(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 <typename F>
void AddDictionaryWriterSyncedCallback(const void* tag, F&& callback) {
dictionary_writer_synced_callback_list_.AddReceiver(
tag, std::forward<F>(callback));
}
void RemoveDictionaryWriterSyncedCallback(const void* tag) {
dictionary_writer_synced_callback_list_.RemoveReceivers(tag);
}
protected:
webrtc::CallbackList<IceTransportInternal*,
const StunDictionaryView&,
rtc::ArrayView<uint16_t>>
dictionary_view_updated_callback_list_;
webrtc::CallbackList<IceTransportInternal*, const StunDictionaryWriter&>
dictionary_writer_synced_callback_list_;
};
} // namespace cricket

View File

@ -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<const StunUInt64Attribute*> 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<StunAttribute> 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<const StunUInt64Attribute*> 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

View File

@ -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<rtc::NetworkRoute> 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<std::reference_wrapper<StunDictionaryWriter>>
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<StunAttribute> GoogDeltaReceived(
const StunByteStringAttribute*);
void GoogDeltaAckReceived(webrtc::RTCErrorOr<const StunUInt64Attribute*>);
// 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

View File

@ -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

View File

@ -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<void(IceTransportInternal*, const StunDictionaryView&,
rtc::ArrayView<uint16_t>)>
view_updated_func;
ep2_ch1()->AddDictionaryViewUpdatedCallback(
"tag", view_updated_func.AsStdFunction());
MockFunction<void(IceTransportInternal*, const StunDictionaryWriter&)>
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,

View File

@ -20,6 +20,7 @@
#include <utility>
#include <vector>
#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<TestPort> lport_;
std::unique_ptr<TestPort> 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<StunByteStringAttribute> delta =
absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA));
delta->CopyBytes("DELTA");
std::unique_ptr<StunAttribute> 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<StunAttribute> { return nullptr; },
// DeltaAckReceived
[&](webrtc::RTCErrorOr<const StunUInt64Attribute*> 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<StunAttribute> {
received_goog_delta = true;
EXPECT_EQ(delta->string_view(), "DELTA");
return std::move(delta_ack);
},
// DeltaAckReceived
[](webrtc::RTCErrorOr<const StunUInt64Attribute*> 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<char>(),
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<char>(),
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<StunByteStringAttribute> 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<StunAttribute> { return nullptr; },
// DeltaAckReceived
[&](webrtc::RTCErrorOr<const StunUInt64Attribute*> 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<char>(),
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<char>(),
rport_->last_stun_buf()->size(), /* packet_time_us */ -1);
EXPECT_TRUE(received_goog_delta_ack_error);
}
} // namespace cricket

View File

@ -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<StunAttribute> attr) {
if (disabled_) {
return;
}
int key = attr->type();
// remove any pending updates.
pending_.erase(
@ -284,6 +295,9 @@ void StunDictionaryWriter::Set(std::unique_ptr<StunAttribute> attr) {
// Create an StunByteStringAttribute containing the pending (e.g not ack:ed)
// modifications.
std::unique_ptr<StunByteStringAttribute> StunDictionaryWriter::CreateDelta() {
if (disabled_) {
return nullptr;
}
if (pending_.empty()) {
return nullptr;
}

View File

@ -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<StunAttribute> attr);
bool disabled_ = false;
// version of modification.
int64_t version_ = 1;