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;
|
return STUN_VALUE_BYTE_STRING;
|
||||||
case STUN_ATTR_GOOG_MISC_INFO:
|
case STUN_ATTR_GOOG_MISC_INFO:
|
||||||
return STUN_VALUE_UINT16_LIST;
|
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:
|
default:
|
||||||
return STUN_VALUE_UNKNOWN;
|
return STUN_VALUE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,8 @@ rtc_library("rtc_p2p") {
|
|||||||
"base/pseudo_tcp.h",
|
"base/pseudo_tcp.h",
|
||||||
"base/regathering_controller.cc",
|
"base/regathering_controller.cc",
|
||||||
"base/regathering_controller.h",
|
"base/regathering_controller.h",
|
||||||
|
"base/stun_dictionary.cc",
|
||||||
|
"base/stun_dictionary.h",
|
||||||
"base/stun_port.cc",
|
"base/stun_port.cc",
|
||||||
"base/stun_port.h",
|
"base/stun_port.h",
|
||||||
"base/stun_request.cc",
|
"base/stun_request.cc",
|
||||||
@ -272,6 +274,7 @@ if (rtc_include_tests) {
|
|||||||
"base/port_unittest.cc",
|
"base/port_unittest.cc",
|
||||||
"base/pseudo_tcp_unittest.cc",
|
"base/pseudo_tcp_unittest.cc",
|
||||||
"base/regathering_controller_unittest.cc",
|
"base/regathering_controller_unittest.cc",
|
||||||
|
"base/stun_dictionary_unittest.cc",
|
||||||
"base/stun_port_unittest.cc",
|
"base/stun_port_unittest.cc",
|
||||||
"base/stun_request_unittest.cc",
|
"base/stun_request_unittest.cc",
|
||||||
"base/stun_server_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