Bug: webrtc:342905193 No-Try: True Change-Id: Icc968be43b8830038ea9a1f5f604307220457807 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/361021 Auto-Submit: Florent Castelli <orphis@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Florent Castelli <orphis@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42911}
384 lines
12 KiB
C++
384 lines
12 KiB
C++
/*
|
|
* 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 {
|
|
|
|
namespace {
|
|
|
|
StunAttributeValueType GetStunAttributeValueType(int value_type) {
|
|
switch (value_type) {
|
|
case STUN_VALUE_ADDRESS:
|
|
return STUN_VALUE_ADDRESS;
|
|
case STUN_VALUE_XOR_ADDRESS:
|
|
return STUN_VALUE_XOR_ADDRESS;
|
|
case STUN_VALUE_UINT32:
|
|
return STUN_VALUE_UINT32;
|
|
case STUN_VALUE_UINT64:
|
|
return STUN_VALUE_UINT64;
|
|
case STUN_VALUE_BYTE_STRING:
|
|
return STUN_VALUE_BYTE_STRING;
|
|
case STUN_VALUE_ERROR_CODE:
|
|
return STUN_VALUE_ERROR_CODE;
|
|
case STUN_VALUE_UINT16_LIST:
|
|
return STUN_VALUE_UINT16_LIST;
|
|
default:
|
|
return STUN_VALUE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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,
|
|
std::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.array_view());
|
|
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 =
|
|
GetStunAttributeValueType(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 {
|
|
int attribute_type = attr->type();
|
|
attrs_[attribute_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::Disable() {
|
|
disabled_ = true;
|
|
}
|
|
|
|
void StunDictionaryWriter::Delete(int key) {
|
|
if (disabled_) {
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
if (disabled_) {
|
|
return;
|
|
}
|
|
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 (disabled_) {
|
|
return nullptr;
|
|
}
|
|
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
|