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}
diff --git a/BUILD.gn b/BUILD.gn
index 60e8fa4..533262e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -55,6 +55,7 @@
         "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",
diff --git a/api/DEPS b/api/DEPS
index 9665938..2e46029 100644
--- a/api/DEPS
+++ b/api/DEPS
@@ -16,6 +16,7 @@
   "-infra",
   "-logging",
   "-media",
+  "-net",
   "-modules",
   "-out",
   "-p2p",
diff --git a/net/dcsctp/BUILD.gn b/net/dcsctp/BUILD.gn
new file mode 100644
index 0000000..0ccc897
--- /dev/null
+++ b/net/dcsctp/BUILD.gn
@@ -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",
+    ]
+  }
+}
diff --git a/net/dcsctp/packet/BUILD.gn b/net/dcsctp/packet/BUILD.gn
new file mode 100644
index 0000000..d546726
--- /dev/null
+++ b/net/dcsctp/packet/BUILD.gn
@@ -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",
+    ]
+  }
+}
diff --git a/net/dcsctp/packet/bounded_byte_reader.h b/net/dcsctp/packet/bounded_byte_reader.h
new file mode 100644
index 0000000..b876488
--- /dev/null
+++ b/net/dcsctp/packet/bounded_byte_reader.h
@@ -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_
diff --git a/net/dcsctp/packet/bounded_byte_reader_test.cc b/net/dcsctp/packet/bounded_byte_reader_test.cc
new file mode 100644
index 0000000..2fb4a86
--- /dev/null
+++ b/net/dcsctp/packet/bounded_byte_reader_test.cc
@@ -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
diff --git a/net/dcsctp/packet/bounded_byte_writer.h b/net/dcsctp/packet/bounded_byte_writer.h
new file mode 100644
index 0000000..4e547b0
--- /dev/null
+++ b/net/dcsctp/packet/bounded_byte_writer.h
@@ -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_
diff --git a/net/dcsctp/packet/bounded_byte_writer_test.cc b/net/dcsctp/packet/bounded_byte_writer_test.cc
new file mode 100644
index 0000000..3cea0a2
--- /dev/null
+++ b/net/dcsctp/packet/bounded_byte_writer_test.cc
@@ -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