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:
parent
b38d9d2b6f
commit
b17806a4cf
@ -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;
|
||||
}
|
||||
|
||||
@ -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
343
p2p/base/stun_dictionary.cc
Normal 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
196
p2p/base/stun_dictionary.h
Normal 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_
|
||||
337
p2p/base/stun_dictionary_unittest.cc
Normal file
337
p2p/base/stun_dictionary_unittest.cc
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user