dcsctp: Add bounded byte reader and writer

Packets, chunks, parameters and error causes - the SCTP entities
that are sent on the wire - are buffers with fields that are stored
in big endian and that generally consist of a fixed header size, and
a variable sized part, that can e.g. be encoded sub-fields or
serialized strings.

The BoundedByteReader and BoundedByteWriter utilities make it easy
to read those fields with as much aid from the compiler as possible,
by having compile-time assertions that fields are not accessed
outside the buffer's span.

There are some byte reading functionality already in modules/rtp_rtcp,
but that module would be a bit unfortunate to depend on, and doesn't
have the compile time bounds checking that is the biggest feature of
this abstraction of an rtc::ArrayView.

Bug: webrtc:12614
Change-Id: I9fc641aff22221018dda9add4e2c44853c0f64f0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/212967
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33597}
This commit is contained in:
Victor Boivie
2021-03-25 13:11:03 +01:00
committed by Commit Bot
parent ff0fb4a5fa
commit 7d3c49a171
8 changed files with 356 additions and 0 deletions

View File

@ -55,6 +55,7 @@ if (!build_with_chromium) {
"modules/remote_bitrate_estimator:rtp_to_text",
"modules/rtp_rtcp:test_packet_masks_metrics",
"modules/video_capture:video_capture_internal_impl",
"net/dcsctp:dcsctp_unittests",
"pc:peerconnection_unittests",
"pc:rtc_pc_unittests",
"rtc_tools:rtp_generator",

View File

@ -16,6 +16,7 @@ include_rules = [
"-infra",
"-logging",
"-media",
"-net",
"-modules",
"-out",
"-p2p",

19
net/dcsctp/BUILD.gn Normal file
View File

@ -0,0 +1,19 @@
# 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.
import("../../webrtc.gni")
if (rtc_include_tests) {
rtc_test("dcsctp_unittests") {
testonly = true
deps = [
"../../test:test_main",
"packet:dcsctp_packet_unittests",
]
}
}

View File

@ -0,0 +1,45 @@
# 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.
import("../../../webrtc.gni")
group("packet") {
deps = [ ":bounded_io" ]
}
rtc_source_set("bounded_io") {
deps = [
"../../../api:array_view",
"../../../rtc_base",
"../../../rtc_base:checks",
"../../../rtc_base:rtc_base_approved",
]
sources = [
"bounded_byte_reader.h",
"bounded_byte_writer.h",
]
}
if (rtc_include_tests) {
rtc_library("dcsctp_packet_unittests") {
testonly = true
deps = [
":bounded_io",
"../../../api:array_view",
"../../../rtc_base:checks",
"../../../rtc_base:gunit_helpers",
"../../../rtc_base:rtc_base_approved",
"../../../test:test_support",
]
sources = [
"bounded_byte_reader_test.cc",
"bounded_byte_writer_test.cc",
]
}
}

View File

@ -0,0 +1,99 @@
/*
* 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_BOUNDED_BYTE_READER_H_
#define NET_DCSCTP_PACKET_BOUNDED_BYTE_READER_H_
#include <cstdint>
#include "api/array_view.h"
namespace dcsctp {
// TODO(boivie): These generic functions - and possibly this entire class -
// could be a candidate to have added to rtc_base/. They should use compiler
// intrinsics as well.
namespace internal {
// Loads a 8-bit unsigned word at `data`.
inline uint8_t LoadBigEndian8(const uint8_t* data) {
return data[0];
}
// Loads a 16-bit unsigned word at `data`.
inline uint16_t LoadBigEndian16(const uint8_t* data) {
return (data[0] << 8) | data[1];
}
// Loads a 32-bit unsigned word at `data`.
inline uint32_t LoadBigEndian32(const uint8_t* data) {
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
}
} // namespace internal
// BoundedByteReader wraps an ArrayView and divides it into two parts; A fixed
// size - which is the template parameter - and a variable size, which is what
// remains in `data` after the `FixedSize`.
//
// The BoundedByteReader provides methods to load/read big endian numbers from
// the FixedSize portion of the buffer, and these are read with static bounds
// checking, to avoid out-of-bounds accesses without a run-time penalty.
//
// The variable sized portion can either be used to create sub-readers, which
// themselves would provide compile-time bounds-checking, or the entire variable
// sized portion can be retrieved as an ArrayView.
template <int FixedSize>
class BoundedByteReader {
public:
explicit BoundedByteReader(rtc::ArrayView<const uint8_t> data) : data_(data) {
RTC_DCHECK(data.size() >= FixedSize);
}
template <size_t offset>
uint8_t Load8() const {
static_assert(offset + sizeof(uint8_t) <= FixedSize, "Out-of-bounds");
return internal::LoadBigEndian8(&data_[offset]);
}
template <size_t offset>
uint16_t Load16() const {
static_assert(offset + sizeof(uint16_t) <= FixedSize, "Out-of-bounds");
static_assert((offset % sizeof(uint16_t)) == 0, "Unaligned access");
return internal::LoadBigEndian16(&data_[offset]);
}
template <size_t offset>
uint32_t Load32() const {
static_assert(offset + sizeof(uint32_t) <= FixedSize, "Out-of-bounds");
static_assert((offset % sizeof(uint32_t)) == 0, "Unaligned access");
return internal::LoadBigEndian32(&data_[offset]);
}
template <size_t SubSize>
BoundedByteReader<SubSize> sub_reader(size_t variable_offset) const {
RTC_DCHECK(FixedSize + variable_offset + SubSize <= data_.size());
rtc::ArrayView<const uint8_t> sub_span =
data_.subview(FixedSize + variable_offset, SubSize);
return BoundedByteReader<SubSize>(sub_span);
}
size_t variable_data_size() const { return data_.size() - FixedSize; }
rtc::ArrayView<const uint8_t> variable_data() const {
return data_.subview(FixedSize, data_.size() - FixedSize);
}
private:
const rtc::ArrayView<const uint8_t> data_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_PACKET_BOUNDED_BYTE_READER_H_

View File

@ -0,0 +1,43 @@
/*
* 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/bounded_byte_reader.h"
#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;
TEST(BoundedByteReaderTest, CanLoadData) {
uint8_t data[14] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4};
BoundedByteReader<8> reader(data);
EXPECT_EQ(reader.variable_data_size(), 6U);
EXPECT_EQ(reader.Load32<0>(), 0x01020304U);
EXPECT_EQ(reader.Load32<4>(), 0x05060708U);
EXPECT_EQ(reader.Load16<4>(), 0x0506U);
EXPECT_EQ(reader.Load8<4>(), 0x05U);
EXPECT_EQ(reader.Load8<5>(), 0x06U);
BoundedByteReader<6> sub = reader.sub_reader<6>(0);
EXPECT_EQ(sub.Load16<0>(), 0x0900U);
EXPECT_EQ(sub.Load32<0>(), 0x09000102U);
EXPECT_EQ(sub.Load16<4>(), 0x0304U);
EXPECT_THAT(reader.variable_data(), ElementsAre(9, 0, 1, 2, 3, 4));
}
} // namespace
} // namespace dcsctp

View File

@ -0,0 +1,100 @@
/*
* 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_BOUNDED_BYTE_WRITER_H_
#define NET_DCSCTP_PACKET_BOUNDED_BYTE_WRITER_H_
#include <algorithm>
#include "api/array_view.h"
namespace dcsctp {
// TODO(boivie): These generic functions - and possibly this entire class -
// could be a candidate to have added to rtc_base/. They should use compiler
// intrinsics as well.
namespace internal {
// Stores a 8-bit unsigned word at `data`.
inline void StoreBigEndian8(uint8_t* data, uint8_t val) {
data[0] = val;
}
// Stores a 16-bit unsigned word at `data`.
inline void StoreBigEndian16(uint8_t* data, uint16_t val) {
data[0] = val >> 8;
data[1] = val;
}
// Stores a 32-bit unsigned word at `data`.
inline void StoreBigEndian32(uint8_t* data, uint32_t val) {
data[0] = val >> 24;
data[1] = val >> 16;
data[2] = val >> 8;
data[3] = val;
}
} // namespace internal
// BoundedByteWriter wraps an ArrayView and divides it into two parts; A fixed
// size - which is the template parameter - and a variable size, which is what
// remains in `data` after the `FixedSize`.
//
// The BoundedByteWriter provides methods to write big endian numbers to the
// FixedSize portion of the buffer, and these are written with static bounds
// checking, to avoid out-of-bounds accesses without a run-time penalty.
//
// The variable sized portion can either be used to create sub-writers, which
// themselves would provide compile-time bounds-checking, or data can be copied
// to it.
template <int FixedSize>
class BoundedByteWriter {
public:
explicit BoundedByteWriter(rtc::ArrayView<uint8_t> data) : data_(data) {
RTC_DCHECK(data.size() >= FixedSize);
}
template <size_t offset>
void Store8(uint8_t value) {
static_assert(offset + sizeof(uint8_t) <= FixedSize, "Out-of-bounds");
internal::StoreBigEndian8(&data_[offset], value);
}
template <size_t offset>
void Store16(uint16_t value) {
static_assert(offset + sizeof(uint16_t) <= FixedSize, "Out-of-bounds");
static_assert((offset % sizeof(uint16_t)) == 0, "Unaligned access");
internal::StoreBigEndian16(&data_[offset], value);
}
template <size_t offset>
void Store32(uint32_t value) {
static_assert(offset + sizeof(uint32_t) <= FixedSize, "Out-of-bounds");
static_assert((offset % sizeof(uint32_t)) == 0, "Unaligned access");
internal::StoreBigEndian32(&data_[offset], value);
}
template <size_t SubSize>
BoundedByteWriter<SubSize> sub_writer(size_t variable_offset) {
RTC_DCHECK(FixedSize + variable_offset + SubSize <= data_.size());
return BoundedByteWriter<SubSize>(
data_.subview(FixedSize + variable_offset, SubSize));
}
void CopyToVariableData(rtc::ArrayView<const uint8_t> source) {
memcpy(data_.data() + FixedSize, source.data(),
std::min(source.size(), data_.size() - FixedSize));
}
private:
rtc::ArrayView<uint8_t> data_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_PACKET_BOUNDED_BYTE_WRITER_H_

View File

@ -0,0 +1,48 @@
/*
* 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/bounded_byte_writer.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;
TEST(BoundedByteWriterTest, CanWriteData) {
std::vector<uint8_t> data(14);
BoundedByteWriter<8> writer(data);
writer.Store32<0>(0x01020304);
writer.Store16<4>(0x0506);
writer.Store8<6>(0x07);
writer.Store8<7>(0x08);
uint8_t variable_data[] = {0, 0, 0, 0, 3, 0};
writer.CopyToVariableData(variable_data);
BoundedByteWriter<6> sub = writer.sub_writer<6>(0);
sub.Store32<0>(0x09000000);
sub.Store16<2>(0x0102);
BoundedByteWriter<2> sub2 = writer.sub_writer<2>(4);
sub2.Store8<1>(0x04);
EXPECT_THAT(data, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4));
}
} // namespace
} // namespace dcsctp