• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 #ifndef NET_DCSCTP_PACKET_TLV_TRAIT_H_
11 #define NET_DCSCTP_PACKET_TLV_TRAIT_H_
12 
13 #include <stdint.h>
14 #include <string.h>
15 
16 #include <algorithm>
17 #include <cstddef>
18 #include <cstdint>
19 #include <string>
20 #include <vector>
21 
22 #include "absl/types/optional.h"
23 #include "api/array_view.h"
24 #include "net/dcsctp/packet/bounded_byte_reader.h"
25 #include "net/dcsctp/packet/bounded_byte_writer.h"
26 
27 namespace dcsctp {
28 namespace tlv_trait_impl {
29 // Logging functions, only to be used by TLVTrait, which is a templated class.
30 void ReportInvalidSize(size_t actual_size, size_t expected_size);
31 void ReportInvalidType(int actual_type, int expected_type);
32 void ReportInvalidFixedLengthField(size_t value, size_t expected);
33 void ReportInvalidVariableLengthField(size_t value, size_t available);
34 void ReportInvalidPadding(size_t padding_bytes);
35 void ReportInvalidLengthMultiple(size_t length, size_t alignment);
36 }  // namespace tlv_trait_impl
37 
38 // Various entities in SCTP are padded data blocks, with a type and length
39 // field at fixed offsets, all stored in a 4-byte header.
40 //
41 // See e.g. https://tools.ietf.org/html/rfc4960#section-3.2 and
42 // https://tools.ietf.org/html/rfc4960#section-3.2.1
43 //
44 // These are helper classes for writing and parsing that data, which in SCTP is
45 // called Type-Length-Value, or TLV.
46 //
47 // This templated class is configurable - a struct passed in as template
48 // parameter with the following expected members:
49 //   * kType                    - The type field's value
50 //   * kTypeSizeInBytes         - The type field's width in bytes.
51 //                                Either 1 or 2.
52 //   * kHeaderSize              - The fixed size header
53 //   * kVariableLengthAlignment - The size alignment on the variable data. Set
54 //                                to zero (0) if no variable data is used.
55 //
56 // This class is to be used as a trait
57 // (https://en.wikipedia.org/wiki/Trait_(computer_programming)) that adds a few
58 // public and protected members and which a class inherits from when it
59 // represents a type-length-value object.
60 template <typename Config>
61 class TLVTrait {
62  private:
63   static constexpr size_t kTlvHeaderSize = 4;
64 
65  protected:
66   static constexpr size_t kHeaderSize = Config::kHeaderSize;
67 
68   static_assert(Config::kTypeSizeInBytes == 1 || Config::kTypeSizeInBytes == 2,
69                 "kTypeSizeInBytes must be 1 or 2");
70   static_assert(Config::kHeaderSize >= kTlvHeaderSize,
71                 "HeaderSize must be >= 4 bytes");
72   static_assert((Config::kHeaderSize % 4 == 0),
73                 "kHeaderSize must be an even multiple of 4 bytes");
74   static_assert((Config::kVariableLengthAlignment == 0 ||
75                  Config::kVariableLengthAlignment == 1 ||
76                  Config::kVariableLengthAlignment == 2 ||
77                  Config::kVariableLengthAlignment == 4 ||
78                  Config::kVariableLengthAlignment == 8),
79                 "kVariableLengthAlignment must be an allowed value");
80 
81   // Validates the data with regards to size, alignment and type.
82   // If valid, returns a bounded buffer.
ParseTLV(rtc::ArrayView<const uint8_t> data)83   static absl::optional<BoundedByteReader<Config::kHeaderSize>> ParseTLV(
84       rtc::ArrayView<const uint8_t> data) {
85     if (data.size() < Config::kHeaderSize) {
86       tlv_trait_impl::ReportInvalidSize(data.size(), Config::kHeaderSize);
87       return absl::nullopt;
88     }
89     BoundedByteReader<kTlvHeaderSize> tlv_header(data);
90 
91     const int type = (Config::kTypeSizeInBytes == 1)
92                          ? tlv_header.template Load8<0>()
93                          : tlv_header.template Load16<0>();
94 
95     if (type != Config::kType) {
96       tlv_trait_impl::ReportInvalidType(type, Config::kType);
97       return absl::nullopt;
98     }
99     const uint16_t length = tlv_header.template Load16<2>();
100     if (Config::kVariableLengthAlignment == 0) {
101       // Don't expect any variable length data at all.
102       if (length != Config::kHeaderSize || data.size() != Config::kHeaderSize) {
103         tlv_trait_impl::ReportInvalidFixedLengthField(length,
104                                                       Config::kHeaderSize);
105         return absl::nullopt;
106       }
107     } else {
108       // Expect variable length data - verify its size alignment.
109       if (length > data.size() || length < Config::kHeaderSize) {
110         tlv_trait_impl::ReportInvalidVariableLengthField(length, data.size());
111         return absl::nullopt;
112       }
113       const size_t padding = data.size() - length;
114       if (padding > 3) {
115         // https://tools.ietf.org/html/rfc4960#section-3.2
116         // "This padding MUST NOT be more than 3 bytes in total"
117         tlv_trait_impl::ReportInvalidPadding(padding);
118         return absl::nullopt;
119       }
120       if (!ValidateLengthAlignment(length, Config::kVariableLengthAlignment)) {
121         tlv_trait_impl::ReportInvalidLengthMultiple(
122             length, Config::kVariableLengthAlignment);
123         return absl::nullopt;
124       }
125     }
126     return BoundedByteReader<Config::kHeaderSize>(data.subview(0, length));
127   }
128 
129   // Allocates space for data with a static header size, as defined by
130   // `Config::kHeaderSize` and a variable footer, as defined by `variable_size`
131   // (which may be 0) and writes the type and length in the header.
132   static BoundedByteWriter<Config::kHeaderSize> AllocateTLV(
133       std::vector<uint8_t>& out,
134       size_t variable_size = 0) {
135     const size_t offset = out.size();
136     const size_t size = Config::kHeaderSize + variable_size;
137     out.resize(offset + size);
138 
139     BoundedByteWriter<kTlvHeaderSize> tlv_header(
140         rtc::ArrayView<uint8_t>(out.data() + offset, kTlvHeaderSize));
141     if (Config::kTypeSizeInBytes == 1) {
142       tlv_header.template Store8<0>(static_cast<uint8_t>(Config::kType));
143     } else {
144       tlv_header.template Store16<0>(Config::kType);
145     }
146     tlv_header.template Store16<2>(size);
147 
148     return BoundedByteWriter<Config::kHeaderSize>(
149         rtc::ArrayView<uint8_t>(out.data() + offset, size));
150   }
151 
152  private:
ValidateLengthAlignment(uint16_t length,size_t alignment)153   static bool ValidateLengthAlignment(uint16_t length, size_t alignment) {
154     // This is to avoid MSVC believing there could be a "mod by zero", when it
155     // certainly can't.
156     if (alignment == 0) {
157       return true;
158     }
159     return (length % alignment) == 0;
160   }
161 };
162 
163 }  // namespace dcsctp
164 
165 #endif  // NET_DCSCTP_PACKET_TLV_TRAIT_H_
166