blob: b234f98a47c819c675252eb4a46f2837dbb9599c [file] [log] [blame]
/*
* 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