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