Add StunDictionary

This patch adds a StunDictionary.
The dictionary has a reader and a writer.
A writer can update a reader by creating a delta.
The delta is applied by the reader, and the ack is applied by the
writer.

Using this mechanism, two ice agents can (in the future) communicate
properties w/o manually needing to add new code.

The delta and delta-ack attributes has been allocated at IANA.

Bug: webrtc:15392
Change-Id: Icdbaf157004258b26ffa0c1f922e083b1ed23899
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/314901
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40513}
This commit is contained in:
Jonas Oreland 2023-08-04 12:26:37 +02:00 committed by WebRTC LUCI CQ
parent b38d9d2b6f
commit b17806a4cf
5 changed files with 883 additions and 0 deletions

View File

@ -722,6 +722,10 @@ StunAttributeValueType StunMessage::GetAttributeValueType(int type) const {
return STUN_VALUE_BYTE_STRING;
case STUN_ATTR_GOOG_MISC_INFO:
return STUN_VALUE_UINT16_LIST;
case STUN_ATTR_GOOG_DELTA:
return STUN_VALUE_BYTE_STRING;
case STUN_ATTR_GOOG_DELTA_ACK:
return STUN_VALUE_UINT64;
default:
return STUN_VALUE_UNKNOWN;
}

View File

@ -67,6 +67,8 @@ rtc_library("rtc_p2p") {
"base/pseudo_tcp.h",
"base/regathering_controller.cc",
"base/regathering_controller.h",
"base/stun_dictionary.cc",
"base/stun_dictionary.h",
"base/stun_port.cc",
"base/stun_port.h",
"base/stun_request.cc",
@ -272,6 +274,7 @@ if (rtc_include_tests) {
"base/port_unittest.cc",
"base/pseudo_tcp_unittest.cc",
"base/regathering_controller_unittest.cc",
"base/stun_dictionary_unittest.cc",
"base/stun_port_unittest.cc",
"base/stun_request_unittest.cc",
"base/stun_server_unittest.cc",

343
p2p/base/stun_dictionary.cc Normal file
View File

@ -0,0 +1,343 @@
/*
* Copyright 2020 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/stun_dictionary.h"
#include <algorithm>
#include <deque>
#include <utility>
#include "rtc_base/logging.h"
namespace cricket {
const StunAddressAttribute* StunDictionaryView::GetAddress(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_ADDRESS);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunAddressAttribute*>(attr);
}
const StunUInt32Attribute* StunDictionaryView::GetUInt32(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT32);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt32Attribute*>(attr);
}
const StunUInt64Attribute* StunDictionaryView::GetUInt64(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT64);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt64Attribute*>(attr);
}
const StunByteStringAttribute* StunDictionaryView::GetByteString(
int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_BYTE_STRING);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunByteStringAttribute*>(attr);
}
const StunUInt16ListAttribute* StunDictionaryView::GetUInt16List(
int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT16_LIST);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt16ListAttribute*>(attr);
}
const StunAttribute* StunDictionaryView::GetOrNull(
int key,
absl::optional<StunAttributeValueType> type) const {
const auto it = attrs_.find(key);
if (it == attrs_.end()) {
return nullptr;
}
if (type && it->second->value_type() != *type) {
RTC_LOG(LS_WARNING) << "Get key: " << key << " with type: " << *type
<< " found different type: "
<< it->second->value_type();
return nullptr;
}
return (*it).second.get();
}
webrtc::RTCErrorOr<
std::pair<uint64_t, std::deque<std::unique_ptr<StunAttribute>>>>
StunDictionaryView::ParseDelta(const StunByteStringAttribute& delta) {
rtc::ByteBufferReader buf(delta.bytes(), delta.length());
uint16_t magic;
if (!buf.ReadUInt16(&magic)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read magic number");
}
if (magic != kDeltaMagic) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Invalid magic number");
}
uint16_t delta_version;
if (!buf.ReadUInt16(&delta_version)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read version");
}
if (delta_version != kDeltaVersion) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Unsupported delta version");
}
// Now read all the attributes
std::deque<std::unique_ptr<StunAttribute>> attrs;
while (buf.Length()) {
uint16_t key, length, value_type;
if (!buf.ReadUInt16(&key)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute key");
}
if (!buf.ReadUInt16(&length)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute length");
}
if (!buf.ReadUInt16(&value_type)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read value type");
}
StunAttributeValueType value_type_enum =
static_cast<StunAttributeValueType>(value_type);
std::unique_ptr<StunAttribute> attr(
StunAttribute::Create(value_type_enum, key, length, nullptr));
if (!attr) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to create attribute");
}
if (attr->length() != length) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Inconsistent attribute length");
}
if (!attr->Read(&buf)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute content");
}
attrs.push_back(std::move(attr));
}
// The first attribute should be the version...
if (attrs.empty()) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Empty delta!");
}
if (attrs[0]->type() != kVersionKey ||
attrs[0]->value_type() != STUN_VALUE_UINT64) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Missing version!");
}
uint64_t version_in_delta =
reinterpret_cast<const StunUInt64Attribute*>(attrs[0].get())->value();
attrs.pop_front();
return std::make_pair(std::max(version_in_delta, version_in_delta),
std::move(attrs));
}
// Apply a delta return an StunUInt64Attribute to ack the update.
webrtc::RTCErrorOr<
std::pair<std::unique_ptr<StunUInt64Attribute>, std::vector<uint16_t>>>
StunDictionaryView::ApplyDelta(const StunByteStringAttribute& delta) {
auto parsed_delta = ParseDelta(delta);
if (!parsed_delta.ok()) {
return webrtc::RTCError(parsed_delta.error());
}
uint64_t version_in_delta = parsed_delta.value().first;
// Check that update does not overflow max_bytes_stored_.
int new_bytes_stored = bytes_stored_;
for (auto& attr : parsed_delta.value().second) {
auto old_version = version_per_key_.find(attr->type());
if (old_version == version_per_key_.end() ||
version_in_delta > old_version->second) {
size_t new_length = attr->length();
size_t old_length = GetLength(attr->type());
if (old_version == version_per_key_.end()) {
new_length += sizeof(int64_t);
}
new_bytes_stored = new_bytes_stored + new_length - old_length;
if (new_bytes_stored <= 0) {
RTC_LOG(LS_WARNING)
<< "attr: " << attr->type() << " old_length: " << old_length
<< " new_length: " << new_length
<< " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER);
}
if (new_bytes_stored > max_bytes_stored_) {
RTC_LOG(LS_INFO) << "attr: " << attr->type()
<< " old_length: " << old_length
<< " new_length: " << new_length
<< " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
}
}
}
if (new_bytes_stored > max_bytes_stored_) {
RTC_LOG(LS_INFO) << " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
return webrtc::RTCError(webrtc::RTCErrorType::RESOURCE_EXHAUSTED);
}
// Apply the update.
std::vector<uint16_t> keys;
for (auto& attr : parsed_delta.value().second) {
if (version_in_delta > version_per_key_[attr->type()]) {
version_per_key_[attr->type()] = version_in_delta;
keys.push_back(attr->type());
if (attr->value_type() == STUN_VALUE_BYTE_STRING && attr->length() == 0) {
attrs_.erase(attr->type());
} else {
attrs_[attr->type()] = std::move(attr);
}
}
}
bytes_stored_ = new_bytes_stored;
return std::make_pair(std::make_unique<StunUInt64Attribute>(
STUN_ATTR_GOOG_DELTA_ACK, version_in_delta),
std::move(keys));
}
size_t StunDictionaryView::GetLength(int key) const {
auto attr = GetOrNull(key);
if (attr != nullptr) {
return attr->length();
}
return 0;
}
void StunDictionaryWriter::Delete(int key) {
if (dictionary_) {
if (dictionary_->attrs_.find(key) == dictionary_->attrs_.end()) {
return;
}
}
// remove any pending updates.
pending_.erase(
std::remove_if(pending_.begin(), pending_.end(),
[key](const auto& p) { return p.second->type() == key; }),
pending_.end());
// Create tombstone.
auto tombstone = std::make_unique<StunByteStringAttribute>(key);
// add a pending entry.
pending_.push_back(std::make_pair(++version_, tombstone.get()));
// store the tombstone.
tombstones_[key] = std::move(tombstone);
if (dictionary_) {
// remove value
dictionary_->attrs_.erase(key);
}
}
void StunDictionaryWriter::Set(std::unique_ptr<StunAttribute> attr) {
int key = attr->type();
// remove any pending updates.
pending_.erase(
std::remove_if(pending_.begin(), pending_.end(),
[key](const auto& p) { return p.second->type() == key; }),
pending_.end());
// remove any existing key.
tombstones_.erase(key);
// create pending entry.
pending_.push_back(std::make_pair(++version_, attr.get()));
if (dictionary_) {
// store attribute.
dictionary_->attrs_[key] = std::move(attr);
}
}
// Create an StunByteStringAttribute containing the pending (e.g not ack:ed)
// modifications.
std::unique_ptr<StunByteStringAttribute> StunDictionaryWriter::CreateDelta() {
if (pending_.empty()) {
return nullptr;
}
rtc::ByteBufferWriter buf;
buf.WriteUInt16(StunDictionaryView::kDeltaMagic); // 0,1
buf.WriteUInt16(StunDictionaryView::kDeltaVersion); // 2,3
// max version in Delta.
buf.WriteUInt16(StunDictionaryView::kVersionKey); // 4,5
buf.WriteUInt16(8); // 6,7
buf.WriteUInt16(STUN_VALUE_UINT64); // 8,9
buf.WriteUInt64(pending_.back().first); // 10-17
// attributes
for (const auto& attr : pending_) {
buf.WriteUInt16(attr.second->type());
buf.WriteUInt16(static_cast<uint16_t>(attr.second->length()));
buf.WriteUInt16(attr.second->value_type());
if (!attr.second->Write(&buf)) {
RTC_LOG(LS_ERROR) << "Failed to write key: " << attr.second->type();
return nullptr;
}
}
return std::make_unique<StunByteStringAttribute>(STUN_ATTR_GOOG_DELTA,
buf.Data(), buf.Length());
}
// Apply a delta ack, i.e prune list of pending changes.
void StunDictionaryWriter::ApplyDeltaAck(const StunUInt64Attribute& ack) {
uint64_t acked_version = ack.value();
auto entries_to_remove = std::remove_if(
pending_.begin(), pending_.end(),
[acked_version](const auto& p) { return p.first <= acked_version; });
// remove tombstones.
for (auto it = entries_to_remove; it != pending_.end(); ++it) {
tombstones_.erase((*it).second->type());
}
pending_.erase(entries_to_remove, pending_.end());
}
// Check if a key has a pending change (i.e a change
// that has not been acked).
bool StunDictionaryWriter::Pending(int key) const {
for (const auto& attr : pending_) {
if (attr.second->type() == key) {
return true;
}
}
return false;
}
int StunDictionaryWriter::Pending() const {
return pending_.size();
}
} // namespace cricket

196
p2p/base/stun_dictionary.h Normal file
View File

@ -0,0 +1,196 @@
/*
* Copyright 2020 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_STUN_DICTIONARY_H_
#define P2P_BASE_STUN_DICTIONARY_H_
#include <deque>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "api/rtc_error.h"
#include "api/transport/stun.h"
namespace cricket {
// A StunDictionaryView is a dictionary of StunAttributes.
// - the StunAttributes can be read using the |Get|-methods.
// - the dictionary is updated by using the |ApplyDelta|-method.
//
// A StunDictionaryWriter is used to create |delta|s for the |ApplyDelta|-method
// - It keeps track of which updates has been applied at StunDictionaryView.
// - It optionally keeps a local StunDictionaryView contains modification made
// `locally`
//
// A pair StunDictionaryView(A)/StunDictionaryWriter(B) are linked so that
// modifications to B is transfered to A using the STUN_ATTR_GOOG_DELTA
// (StunByteStringAttribute) and the modification is ack:ed using
// STUN_ATTR_GOOG_DELTA_ACK (StunUInt64Attribute).
//
// Note:
// 1) It is possible to update one StunDictionaryView from multiple writers,
// but this only works of the different writers write disjoint keys (which
// is not checked/enforced by these classes).
// 2) The opposite, one writer updating multiple StunDictionaryView, is not
// possible.
class StunDictionaryView {
public:
// A reserved key used to transport the version number
static constexpr uint16_t kVersionKey = 0xFFFF;
// A magic number used when transporting deltas.
static constexpr uint16_t kDeltaMagic = 0x7788;
// The version number for the delta format.
static constexpr uint16_t kDeltaVersion = 0x1;
// Gets the desired attribute value, or NULL if no such attribute type exists.
// The pointer returned is guaranteed to be valid until ApplyDelta is called.
const StunAddressAttribute* GetAddress(int key) const;
const StunUInt32Attribute* GetUInt32(int key) const;
const StunUInt64Attribute* GetUInt64(int key) const;
const StunByteStringAttribute* GetByteString(int key) const;
const StunUInt16ListAttribute* GetUInt16List(int key) const;
bool empty() const { return attrs_.empty(); }
int 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;
}
// Apply a delta and return
// a pair with
// - StunUInt64Attribute to ack the |delta|.
// - vector of keys that was modified.
webrtc::RTCErrorOr<
std::pair<std::unique_ptr<StunUInt64Attribute>, std::vector<uint16_t>>>
ApplyDelta(const StunByteStringAttribute& delta);
private:
friend class StunDictionaryWriter;
const StunAttribute* GetOrNull(
int key,
absl::optional<StunAttributeValueType> = absl::nullopt) const;
size_t GetLength(int key) const;
static webrtc::RTCErrorOr<
std::pair<uint64_t, std::deque<std::unique_ptr<StunAttribute>>>>
ParseDelta(const StunByteStringAttribute& delta);
std::map<uint16_t, std::unique_ptr<StunAttribute>> attrs_;
std::map<uint16_t, uint64_t> version_per_key_;
int max_bytes_stored_ = 16384;
int bytes_stored_ = 0;
};
class StunDictionaryWriter {
public:
StunDictionaryWriter() {
dictionary_ = std::make_unique<StunDictionaryView>();
}
explicit StunDictionaryWriter(
std::unique_ptr<StunDictionaryView> dictionary) {
dictionary_ = std::move(dictionary);
}
// A pending modification.
template <typename T>
class Modification {
public:
~Modification() { commit(); }
T* operator->() { return attr_.get(); }
void abort() { attr_ = nullptr; }
void commit() {
if (attr_) {
writer_->Set(std::move(attr_));
}
}
private:
friend class StunDictionaryWriter;
Modification(StunDictionaryWriter* writer, std::unique_ptr<T> attr)
: writer_(writer), attr_(std::move(attr)) {}
StunDictionaryWriter* writer_;
std::unique_ptr<T> attr_;
Modification(const Modification<T>&) =
delete; // not copyable (but movable).
Modification& operator=(Modification<T>&) =
delete; // not copyable (but movable).
};
// Record a modification.
Modification<StunAddressAttribute> SetAddress(int key) {
return Modification<StunAddressAttribute>(
this, StunAttribute::CreateAddress(key));
}
Modification<StunUInt32Attribute> SetUInt32(int key) {
return Modification<StunUInt32Attribute>(this,
StunAttribute::CreateUInt32(key));
}
Modification<StunUInt64Attribute> SetUInt64(int key) {
return Modification<StunUInt64Attribute>(this,
StunAttribute::CreateUInt64(key));
}
Modification<StunByteStringAttribute> SetByteString(int key) {
return Modification<StunByteStringAttribute>(
this, StunAttribute::CreateByteString(key));
}
Modification<StunUInt16ListAttribute> SetUInt16List(int key) {
return Modification<StunUInt16ListAttribute>(
this, StunAttribute::CreateUInt16ListAttribute(key));
}
// Delete a key.
void Delete(int key);
// Check if a key has a pending change (i.e a change
// that has not been acked).
bool Pending(int key) const;
// Return number of of pending modifications.
int Pending() const;
// Create an StunByteStringAttribute containing the pending (e.g not ack:ed)
// modifications.
std::unique_ptr<StunByteStringAttribute> CreateDelta();
// Apply an delta ack.
void ApplyDeltaAck(const StunUInt64Attribute&);
// Return pointer to (optional) StunDictionaryView.
const StunDictionaryView* dictionary() { return dictionary_.get(); }
const StunDictionaryView* operator->() { return dictionary_.get(); }
private:
void Set(std::unique_ptr<StunAttribute> attr);
// version of modification.
int64_t version_ = 1;
// (optional) StunDictionaryView.
std::unique_ptr<StunDictionaryView> dictionary_;
// sorted list of changes that has not been yet been ack:ed.
std::vector<std::pair<uint64_t, const StunAttribute*>> pending_;
// tombstones, i.e values that has been deleted but not yet acked.
std::map<uint16_t, std::unique_ptr<StunAttribute>> tombstones_;
};
} // namespace cricket
#endif // P2P_BASE_STUN_DICTIONARY_H_

View File

@ -0,0 +1,337 @@
/*
* Copyright 2020 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/stun_dictionary.h"
#include <string>
#include "rtc_base/gunit.h"
#include "rtc_base/logging.h"
#include "test/gtest.h"
namespace {
void Sync(cricket::StunDictionaryView& dictionary,
cricket::StunDictionaryWriter& writer) {
int pending = writer.Pending();
auto delta = writer.CreateDelta();
if (delta == nullptr) {
EXPECT_EQ(pending, 0);
} else {
EXPECT_NE(pending, 0);
auto delta_ack = dictionary.ApplyDelta(*delta);
if (!delta_ack.ok()) {
RTC_LOG(LS_ERROR) << "delta_ack.error(): " << delta_ack.error().message();
}
EXPECT_TRUE(delta_ack.ok());
ASSERT_NE(delta_ack.value().first.get(), nullptr);
writer.ApplyDeltaAck(*delta_ack.value().first);
EXPECT_FALSE(writer.Pending());
}
}
void XorToggle(cricket::StunByteStringAttribute& attr, size_t byte) {
ASSERT_TRUE(attr.length() > byte);
uint8_t val = attr.GetByte(byte);
uint8_t new_val = val ^ (128 - (byte & 255));
attr.SetByte(byte, new_val);
}
std::unique_ptr<cricket::StunByteStringAttribute> Crop(
const cricket::StunByteStringAttribute& attr,
int new_length) {
auto new_attr =
std::make_unique<cricket::StunByteStringAttribute>(attr.type());
std::string content = std::string(attr.string_view());
content.erase(new_length);
new_attr->CopyBytes(content);
return new_attr;
}
} // namespace
namespace cricket {
constexpr int kKey1 = 100;
TEST(StunDictionary, CreateEmptyDictionaryWriter) {
StunDictionaryView dictionary;
StunDictionaryWriter writer;
EXPECT_TRUE(dictionary.empty());
EXPECT_TRUE(writer->empty());
EXPECT_EQ(writer.Pending(), 0);
EXPECT_EQ(writer.CreateDelta().get(), nullptr);
}
TEST(StunDictionary, SetAndGet) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
EXPECT_EQ(writer->GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(writer->GetUInt64(kKey1), nullptr);
EXPECT_EQ(writer->GetByteString(kKey1), nullptr);
EXPECT_EQ(writer->GetAddress(kKey1), nullptr);
EXPECT_EQ(writer->GetUInt16List(kKey1), nullptr);
}
TEST(StunDictionary, SetAndApply) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
StunDictionaryView dictionary;
EXPECT_TRUE(dictionary.empty());
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.bytes_stored(), 12);
}
TEST(StunDictionary, SetSetAndApply) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
writer.SetUInt32(kKey1)->SetValue(29);
StunDictionaryView dictionary;
EXPECT_TRUE(dictionary.empty());
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 29u);
EXPECT_EQ(dictionary.bytes_stored(), 12);
}
TEST(StunDictionary, SetAndApplyAndSetAndApply) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
StunDictionaryView dictionary;
EXPECT_TRUE(dictionary.empty());
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.bytes_stored(), 12);
writer.SetUInt32(kKey1)->SetValue(29);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 29u);
EXPECT_EQ(dictionary.bytes_stored(), 12);
}
TEST(StunDictionary, ChangeType) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
EXPECT_EQ(writer->GetUInt32(kKey1)->value(), 27u);
writer.SetUInt64(kKey1)->SetValue(29);
EXPECT_EQ(writer->GetUInt32(kKey1), nullptr);
EXPECT_EQ(writer->GetUInt64(kKey1)->value(), 29ull);
}
TEST(StunDictionary, ChangeTypeApply) {
StunDictionaryWriter writer;
writer.SetUInt32(kKey1)->SetValue(27);
EXPECT_EQ(writer->GetUInt32(kKey1)->value(), 27u);
StunDictionaryView dictionary;
EXPECT_TRUE(dictionary.empty());
Sync(dictionary, writer);
EXPECT_EQ(writer->GetUInt32(kKey1)->value(), 27u);
writer.SetUInt64(kKey1)->SetValue(29);
EXPECT_EQ(writer->GetUInt32(kKey1), nullptr);
EXPECT_EQ(writer->GetUInt64(kKey1)->value(), 29ull);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1), nullptr);
EXPECT_EQ(dictionary.GetUInt64(kKey1)->value(), 29ull);
EXPECT_EQ(dictionary.bytes_stored(), 16);
}
TEST(StunDictionary, Pending) {
StunDictionaryWriter writer;
EXPECT_EQ(writer.Pending(), 0);
EXPECT_FALSE(writer.Pending(kKey1));
writer.SetUInt32(kKey1)->SetValue(27);
EXPECT_EQ(writer.Pending(), 1);
EXPECT_TRUE(writer.Pending(kKey1));
writer.SetUInt32(kKey1)->SetValue(29);
EXPECT_EQ(writer.Pending(), 1);
EXPECT_TRUE(writer.Pending(kKey1));
writer.SetUInt32(kKey1 + 1)->SetValue(31);
EXPECT_EQ(writer.Pending(), 2);
EXPECT_TRUE(writer.Pending(kKey1));
EXPECT_TRUE(writer.Pending(kKey1 + 1));
StunDictionaryView dictionary;
Sync(dictionary, writer);
EXPECT_EQ(writer.Pending(), 0);
EXPECT_FALSE(writer.Pending(kKey1));
writer.SetUInt32(kKey1)->SetValue(32);
EXPECT_EQ(writer.Pending(), 1);
EXPECT_TRUE(writer.Pending(kKey1));
}
TEST(StunDictionary, Delete) {
StunDictionaryWriter writer;
StunDictionaryView dictionary;
writer.SetUInt32(kKey1)->SetValue(27);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.bytes_stored(), 12);
writer.Delete(kKey1);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1), nullptr);
EXPECT_EQ(dictionary.bytes_stored(), 8);
writer.Delete(kKey1);
EXPECT_EQ(writer.Pending(), 0);
}
TEST(StunDictionary, MultiWriter) {
StunDictionaryWriter writer1;
StunDictionaryWriter writer2;
StunDictionaryView dictionary;
writer1.SetUInt32(kKey1)->SetValue(27);
Sync(dictionary, writer1);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
writer2.SetUInt32(kKey1 + 1)->SetValue(28);
Sync(dictionary, writer2);
EXPECT_EQ(dictionary.GetUInt32(kKey1 + 1)->value(), 28u);
writer1.Delete(kKey1);
Sync(dictionary, writer1);
EXPECT_EQ(dictionary.GetUInt32(kKey1), nullptr);
writer2.Delete(kKey1 + 1);
Sync(dictionary, writer2);
EXPECT_EQ(dictionary.GetUInt32(kKey1 + 1), nullptr);
}
TEST(StunDictionary, BytesStoredIsCountedCorrectlyAfterMultipleUpdates) {
StunDictionaryWriter writer;
StunDictionaryView dictionary;
for (int i = 0; i < 10; i++) {
writer.SetUInt32(kKey1)->SetValue(27);
writer.SetUInt64(kKey1 + 1)->SetValue(28);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.bytes_stored(), 28);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1)->value(), 28ull);
writer.Delete(kKey1);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.bytes_stored(), 24);
EXPECT_EQ(dictionary.GetUInt32(kKey1), nullptr);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1)->value(), 28ull);
writer.Delete(kKey1 + 1);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.bytes_stored(), 16);
EXPECT_EQ(dictionary.GetUInt32(kKey1), nullptr);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1), nullptr);
}
}
TEST(StunDictionary, MaxBytesStoredCausesErrorOnOverflow) {
StunDictionaryWriter writer;
StunDictionaryView dictionary;
dictionary.set_max_bytes_stored(30);
writer.SetUInt32(kKey1)->SetValue(27);
writer.SetUInt64(kKey1 + 1)->SetValue(28);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.bytes_stored(), 28);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1)->value(), 28ull);
writer.SetByteString(kKey1 + 2)->CopyBytes("k");
{
auto delta = writer.CreateDelta();
auto delta_ack = dictionary.ApplyDelta(*delta);
EXPECT_FALSE(delta_ack.ok());
}
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1)->value(), 28ull);
EXPECT_EQ(dictionary.GetByteString(kKey1 + 2), nullptr);
writer.Delete(kKey1 + 1);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1), nullptr);
EXPECT_EQ(dictionary.GetByteString(kKey1 + 2)->string_view(), "k");
}
TEST(StunDictionary, DataTypes) {
StunDictionaryWriter writer;
StunDictionaryView dictionary;
rtc::SocketAddress addr("127.0.0.1", 8080);
writer.SetUInt32(kKey1)->SetValue(27);
writer.SetUInt64(kKey1 + 1)->SetValue(28);
writer.SetAddress(kKey1 + 2)->SetAddress(addr);
writer.SetByteString(kKey1 + 3)->CopyBytes("keso");
writer.SetUInt16List(kKey1 + 4)->AddTypeAtIndex(0, 7);
Sync(dictionary, writer);
EXPECT_EQ(dictionary.GetUInt32(kKey1)->value(), 27u);
EXPECT_EQ(dictionary.GetUInt64(kKey1 + 1)->value(), 28ull);
EXPECT_EQ(dictionary.GetAddress(kKey1 + 2)->GetAddress(), addr);
EXPECT_EQ(dictionary.GetByteString(kKey1 + 3)->string_view(), "keso");
EXPECT_EQ(dictionary.GetUInt16List(kKey1 + 4)->GetType(0), 7);
}
TEST(StunDictionary, ParseError) {
StunDictionaryWriter writer;
StunDictionaryView dictionary;
rtc::SocketAddress addr("127.0.0.1", 8080);
writer.SetUInt32(kKey1)->SetValue(27);
writer.SetUInt64(kKey1 + 1)->SetValue(28);
writer.SetAddress(kKey1 + 2)->SetAddress(addr);
writer.SetByteString(kKey1 + 3)->CopyBytes("keso");
writer.SetUInt16List(kKey1 + 4)->AddTypeAtIndex(0, 7);
auto delta = writer.CreateDelta();
// The first 10 bytes are in the header...
// any modification makes parsing fail.
for (int i = 0; i < 10; i++) {
XorToggle(*delta, i);
EXPECT_FALSE(dictionary.ApplyDelta(*delta).ok());
XorToggle(*delta, i); // toogle back
}
// Remove bytes from the delta.
for (size_t i = 0; i < delta->length(); i++) {
// The delta does not contain a footer,
// so it it possible to Crop at special values (attribute boundaries)
// and apply will still work.
const std::vector<int> valid_crop_length = {18, 28, 42, 56, 66, 74};
bool valid = std::find(valid_crop_length.begin(), valid_crop_length.end(),
i) != valid_crop_length.end();
auto cropped_delta = Crop(*delta, i);
if (valid) {
EXPECT_TRUE(dictionary.ApplyDelta(*cropped_delta).ok());
} else {
EXPECT_FALSE(dictionary.ApplyDelta(*cropped_delta).ok());
}
}
}
} // namespace cricket