166 lines
6.5 KiB
C++
166 lines
6.5 KiB
C++
/*
|
|
* Copyright (c) 2021 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 NET_DCSCTP_PACKET_TLV_TRAIT_H_
|
|
#define NET_DCSCTP_PACKET_TLV_TRAIT_H_
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "api/array_view.h"
|
|
#include "net/dcsctp/packet/bounded_byte_reader.h"
|
|
#include "net/dcsctp/packet/bounded_byte_writer.h"
|
|
|
|
namespace dcsctp {
|
|
namespace tlv_trait_impl {
|
|
// Logging functions, only to be used by TLVTrait, which is a templated class.
|
|
void ReportInvalidSize(size_t actual_size, size_t expected_size);
|
|
void ReportInvalidType(int actual_type, int expected_type);
|
|
void ReportInvalidFixedLengthField(size_t value, size_t expected);
|
|
void ReportInvalidVariableLengthField(size_t value, size_t available);
|
|
void ReportInvalidPadding(size_t padding_bytes);
|
|
void ReportInvalidLengthMultiple(size_t length, size_t alignment);
|
|
} // namespace tlv_trait_impl
|
|
|
|
// Various entities in SCTP are padded data blocks, with a type and length
|
|
// field at fixed offsets, all stored in a 4-byte header.
|
|
//
|
|
// See e.g. https://tools.ietf.org/html/rfc4960#section-3.2 and
|
|
// https://tools.ietf.org/html/rfc4960#section-3.2.1
|
|
//
|
|
// These are helper classes for writing and parsing that data, which in SCTP is
|
|
// called Type-Length-Value, or TLV.
|
|
//
|
|
// This templated class is configurable - a struct passed in as template
|
|
// parameter with the following expected members:
|
|
// * kType - The type field's value
|
|
// * kTypeSizeInBytes - The type field's width in bytes.
|
|
// Either 1 or 2.
|
|
// * kHeaderSize - The fixed size header
|
|
// * kVariableLengthAlignment - The size alignment on the variable data. Set
|
|
// to zero (0) if no variable data is used.
|
|
//
|
|
// This class is to be used as a trait
|
|
// (https://en.wikipedia.org/wiki/Trait_(computer_programming)) that adds a few
|
|
// public and protected members and which a class inherits from when it
|
|
// represents a type-length-value object.
|
|
template <typename Config>
|
|
class TLVTrait {
|
|
private:
|
|
static constexpr size_t kTlvHeaderSize = 4;
|
|
|
|
protected:
|
|
static constexpr size_t kHeaderSize = Config::kHeaderSize;
|
|
|
|
static_assert(Config::kTypeSizeInBytes == 1 || Config::kTypeSizeInBytes == 2,
|
|
"kTypeSizeInBytes must be 1 or 2");
|
|
static_assert(Config::kHeaderSize >= kTlvHeaderSize,
|
|
"HeaderSize must be >= 4 bytes");
|
|
static_assert((Config::kHeaderSize % 4 == 0),
|
|
"kHeaderSize must be an even multiple of 4 bytes");
|
|
static_assert((Config::kVariableLengthAlignment == 0 ||
|
|
Config::kVariableLengthAlignment == 1 ||
|
|
Config::kVariableLengthAlignment == 2 ||
|
|
Config::kVariableLengthAlignment == 4 ||
|
|
Config::kVariableLengthAlignment == 8),
|
|
"kVariableLengthAlignment must be an allowed value");
|
|
|
|
// Validates the data with regards to size, alignment and type.
|
|
// If valid, returns a bounded buffer.
|
|
static absl::optional<BoundedByteReader<Config::kHeaderSize>> ParseTLV(
|
|
rtc::ArrayView<const uint8_t> data) {
|
|
if (data.size() < Config::kHeaderSize) {
|
|
tlv_trait_impl::ReportInvalidSize(data.size(), Config::kHeaderSize);
|
|
return absl::nullopt;
|
|
}
|
|
BoundedByteReader<kTlvHeaderSize> tlv_header(data);
|
|
|
|
const int type = (Config::kTypeSizeInBytes == 1)
|
|
? tlv_header.template Load8<0>()
|
|
: tlv_header.template Load16<0>();
|
|
|
|
if (type != Config::kType) {
|
|
tlv_trait_impl::ReportInvalidType(type, Config::kType);
|
|
return absl::nullopt;
|
|
}
|
|
const uint16_t length = tlv_header.template Load16<2>();
|
|
if (Config::kVariableLengthAlignment == 0) {
|
|
// Don't expect any variable length data at all.
|
|
if (length != Config::kHeaderSize || data.size() != Config::kHeaderSize) {
|
|
tlv_trait_impl::ReportInvalidFixedLengthField(length,
|
|
Config::kHeaderSize);
|
|
return absl::nullopt;
|
|
}
|
|
} else {
|
|
// Expect variable length data - verify its size alignment.
|
|
if (length > data.size() || length < Config::kHeaderSize) {
|
|
tlv_trait_impl::ReportInvalidVariableLengthField(length, data.size());
|
|
return absl::nullopt;
|
|
}
|
|
const size_t padding = data.size() - length;
|
|
if (padding > 3) {
|
|
// https://tools.ietf.org/html/rfc4960#section-3.2
|
|
// "This padding MUST NOT be more than 3 bytes in total"
|
|
tlv_trait_impl::ReportInvalidPadding(padding);
|
|
return absl::nullopt;
|
|
}
|
|
if (!ValidateLengthAlignment(length, Config::kVariableLengthAlignment)) {
|
|
tlv_trait_impl::ReportInvalidLengthMultiple(
|
|
length, Config::kVariableLengthAlignment);
|
|
return absl::nullopt;
|
|
}
|
|
}
|
|
return BoundedByteReader<Config::kHeaderSize>(data.subview(0, length));
|
|
}
|
|
|
|
// Allocates space for data with a static header size, as defined by
|
|
// `Config::kHeaderSize` and a variable footer, as defined by `variable_size`
|
|
// (which may be 0) and writes the type and length in the header.
|
|
static BoundedByteWriter<Config::kHeaderSize> AllocateTLV(
|
|
std::vector<uint8_t>& out,
|
|
size_t variable_size = 0) {
|
|
const size_t offset = out.size();
|
|
const size_t size = Config::kHeaderSize + variable_size;
|
|
out.resize(offset + size);
|
|
|
|
BoundedByteWriter<kTlvHeaderSize> tlv_header(
|
|
rtc::ArrayView<uint8_t>(out.data() + offset, kTlvHeaderSize));
|
|
if (Config::kTypeSizeInBytes == 1) {
|
|
tlv_header.template Store8<0>(static_cast<uint8_t>(Config::kType));
|
|
} else {
|
|
tlv_header.template Store16<0>(Config::kType);
|
|
}
|
|
tlv_header.template Store16<2>(size);
|
|
|
|
return BoundedByteWriter<Config::kHeaderSize>(
|
|
rtc::ArrayView<uint8_t>(out.data() + offset, size));
|
|
}
|
|
|
|
private:
|
|
static bool ValidateLengthAlignment(uint16_t length, size_t alignment) {
|
|
// This is to avoid MSVC believing there could be a "mod by zero", when it
|
|
// certainly can't.
|
|
if (alignment == 0) {
|
|
return true;
|
|
}
|
|
return (length % alignment) == 0;
|
|
}
|
|
};
|
|
|
|
} // namespace dcsctp
|
|
|
|
#endif // NET_DCSCTP_PACKET_TLV_TRAIT_H_
|