dcsctp: Add TLV trait
Various entities in SCTP are padded data blocks, with a type and length field at fixed offsets, all stored in a 4-byte header. This is called the Type-Length-Value format, or TLV for short. See e.g. https://tools.ietf.org/html/rfc4960#section-3.2 and https://tools.ietf.org/html/rfc4960#section-3.2.1 This templated class, which is used as a trait[1], is configurable - a struct passed in as template parameter. [1] https://en.wikipedia.org/wiki/Trait_(computer_programming) Bug: webrtc:12614 Change-Id: I52c2b5056931aba5fb23419406314136b5a4f650 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213180 Commit-Queue: Victor Boivie <boivie@webrtc.org> Reviewed-by: Tommi <tommi@webrtc.org> Cr-Commit-Position: refs/heads/master@{#33602}
This commit is contained in:
parent
2e3832e0d0
commit
3dffa81541
@ -25,12 +25,28 @@ rtc_source_set("bounded_io") {
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("tlv_trait") {
|
||||
deps = [
|
||||
":bounded_io",
|
||||
"../../../api:array_view",
|
||||
"../../../rtc_base",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ]
|
||||
sources = [
|
||||
"tlv_trait.cc",
|
||||
"tlv_trait.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (rtc_include_tests) {
|
||||
rtc_library("dcsctp_packet_unittests") {
|
||||
testonly = true
|
||||
|
||||
deps = [
|
||||
":bounded_io",
|
||||
":tlv_trait",
|
||||
"../../../api:array_view",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:gunit_helpers",
|
||||
@ -40,6 +56,7 @@ if (rtc_include_tests) {
|
||||
sources = [
|
||||
"bounded_byte_reader_test.cc",
|
||||
"bounded_byte_writer_test.cc",
|
||||
"tlv_trait_test.cc",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
46
net/dcsctp/packet/tlv_trait.cc
Normal file
46
net/dcsctp/packet/tlv_trait.cc
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "net/dcsctp/packet/tlv_trait.h"
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace dcsctp {
|
||||
namespace tlv_trait_impl {
|
||||
void ReportInvalidSize(size_t actual_size, size_t expected_size) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid size (" << actual_size
|
||||
<< ", expected minimum " << expected_size << " bytes)";
|
||||
}
|
||||
|
||||
void ReportInvalidType(int actual_type, int expected_type) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid type (" << actual_type << ", expected "
|
||||
<< expected_type << ")";
|
||||
}
|
||||
|
||||
void ReportInvalidFixedLengthField(size_t value, size_t expected) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid length field (" << value << ", expected "
|
||||
<< expected << " bytes)";
|
||||
}
|
||||
|
||||
void ReportInvalidVariableLengthField(size_t value, size_t available) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid length field (" << value << ", available "
|
||||
<< available << " bytes)";
|
||||
}
|
||||
|
||||
void ReportInvalidPadding(size_t padding_bytes) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid padding (" << padding_bytes << " bytes)";
|
||||
}
|
||||
|
||||
void ReportInvalidLengthMultiple(size_t length, size_t alignment) {
|
||||
RTC_DLOG(LS_WARNING) << "Invalid length field (" << length
|
||||
<< ", expected an even multiple of " << alignment
|
||||
<< " bytes)";
|
||||
}
|
||||
} // namespace tlv_trait_impl
|
||||
} // namespace dcsctp
|
||||
154
net/dcsctp/packet/tlv_trait.h
Normal file
154
net/dcsctp/packet/tlv_trait.h
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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 acutal_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.Load8<0>()
|
||||
: tlv_header.Load16<0>();
|
||||
|
||||
if (type != Config::kType) {
|
||||
tlv_trait_impl::ReportInvalidType(type, Config::kType);
|
||||
return absl::nullopt;
|
||||
}
|
||||
const uint16_t length = tlv_header.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()) {
|
||||
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 ((length % Config::kVariableLengthAlignment) != 0) {
|
||||
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.Store8<0>(static_cast<uint8_t>(Config::kType));
|
||||
} else {
|
||||
tlv_header.Store16<0>(Config::kType);
|
||||
}
|
||||
tlv_header.Store16<2>(size);
|
||||
|
||||
return BoundedByteWriter<Config::kHeaderSize>(
|
||||
rtc::ArrayView<uint8_t>(out.data() + offset, size));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_PACKET_TLV_TRAIT_H_
|
||||
126
net/dcsctp/packet/tlv_trait_test.cc
Normal file
126
net/dcsctp/packet/tlv_trait_test.cc
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "net/dcsctp/packet/tlv_trait.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "rtc_base/buffer.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/gunit.h"
|
||||
#include "test/gmock.h"
|
||||
|
||||
namespace dcsctp {
|
||||
namespace {
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::SizeIs;
|
||||
|
||||
struct OneByteTypeConfig {
|
||||
static constexpr int kTypeSizeInBytes = 1;
|
||||
static constexpr int kType = 0x49;
|
||||
static constexpr size_t kHeaderSize = 12;
|
||||
static constexpr int kVariableLengthAlignment = 4;
|
||||
};
|
||||
|
||||
class OneByteChunk : public TLVTrait<OneByteTypeConfig> {
|
||||
public:
|
||||
static constexpr size_t kVariableSize = 4;
|
||||
|
||||
void SerializeTo(std::vector<uint8_t>& out) {
|
||||
BoundedByteWriter<OneByteTypeConfig::kHeaderSize> writer =
|
||||
AllocateTLV(out, kVariableSize);
|
||||
writer.Store32<4>(0x01020304);
|
||||
writer.Store16<8>(0x0506);
|
||||
writer.Store16<10>(0x0708);
|
||||
|
||||
uint8_t variable_data[kVariableSize] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
writer.CopyToVariableData(rtc::ArrayView<const uint8_t>(variable_data));
|
||||
}
|
||||
|
||||
static absl::optional<BoundedByteReader<OneByteTypeConfig::kHeaderSize>>
|
||||
Parse(rtc::ArrayView<const uint8_t> data) {
|
||||
return ParseTLV(data);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TlvDataTest, CanWriteOneByteTypeTlvs) {
|
||||
std::vector<uint8_t> out;
|
||||
OneByteChunk().SerializeTo(out);
|
||||
|
||||
EXPECT_THAT(out, SizeIs(OneByteTypeConfig::kHeaderSize +
|
||||
OneByteChunk::kVariableSize));
|
||||
EXPECT_THAT(out, ElementsAre(0x49, 0x00, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF));
|
||||
}
|
||||
|
||||
TEST(TlvDataTest, CanReadOneByteTypeTlvs) {
|
||||
uint8_t data[] = {0x49, 0x00, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||
|
||||
absl::optional<BoundedByteReader<OneByteTypeConfig::kHeaderSize>> reader =
|
||||
OneByteChunk::Parse(data);
|
||||
ASSERT_TRUE(reader.has_value());
|
||||
EXPECT_EQ(reader->Load32<4>(), 0x01020304U);
|
||||
EXPECT_EQ(reader->Load16<8>(), 0x0506U);
|
||||
EXPECT_EQ(reader->Load16<10>(), 0x0708U);
|
||||
EXPECT_THAT(reader->variable_data(), ElementsAre(0xDE, 0xAD, 0xBE, 0xEF));
|
||||
}
|
||||
|
||||
struct TwoByteTypeConfig {
|
||||
static constexpr int kTypeSizeInBytes = 2;
|
||||
static constexpr int kType = 31337;
|
||||
static constexpr size_t kHeaderSize = 8;
|
||||
static constexpr int kVariableLengthAlignment = 4;
|
||||
};
|
||||
|
||||
class TwoByteChunk : public TLVTrait<TwoByteTypeConfig> {
|
||||
public:
|
||||
static constexpr size_t kVariableSize = 8;
|
||||
|
||||
void SerializeTo(std::vector<uint8_t>& out) {
|
||||
BoundedByteWriter<TwoByteTypeConfig::kHeaderSize> writer =
|
||||
AllocateTLV(out, kVariableSize);
|
||||
writer.Store32<4>(0x01020304U);
|
||||
|
||||
uint8_t variable_data[] = {0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||
writer.CopyToVariableData(rtc::ArrayView<const uint8_t>(variable_data));
|
||||
}
|
||||
|
||||
static absl::optional<BoundedByteReader<TwoByteTypeConfig::kHeaderSize>>
|
||||
Parse(rtc::ArrayView<const uint8_t> data) {
|
||||
return ParseTLV(data);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TlvDataTest, CanWriteTwoByteTypeTlvs) {
|
||||
std::vector<uint8_t> out;
|
||||
|
||||
TwoByteChunk().SerializeTo(out);
|
||||
|
||||
EXPECT_THAT(out, SizeIs(TwoByteTypeConfig::kHeaderSize +
|
||||
TwoByteChunk::kVariableSize));
|
||||
EXPECT_THAT(out, ElementsAre(0x7A, 0x69, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF));
|
||||
}
|
||||
|
||||
TEST(TlvDataTest, CanReadTwoByteTypeTlvs) {
|
||||
uint8_t data[] = {0x7A, 0x69, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||
|
||||
absl::optional<BoundedByteReader<TwoByteTypeConfig::kHeaderSize>> reader =
|
||||
TwoByteChunk::Parse(data);
|
||||
EXPECT_TRUE(reader.has_value());
|
||||
EXPECT_EQ(reader->Load32<4>(), 0x01020304U);
|
||||
EXPECT_THAT(reader->variable_data(),
|
||||
ElementsAre(0x05, 0x06, 0x07, 0x08, 0xDE, 0xAD, 0xBE, 0xEF));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace dcsctp
|
||||
Loading…
x
Reference in New Issue
Block a user