aboutsummaryrefslogtreecommitdiff
path: root/net/dcsctp/packet/tlv_trait.h
blob: a3c728efd77f0d1a39326970da77e03424307a87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
 *  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 actual_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.template Load8<0>()
                         : tlv_header.template Load16<0>();

    if (type != Config::kType) {
      tlv_trait_impl::ReportInvalidType(type, Config::kType);
      return absl::nullopt;
    }
    const uint16_t length = tlv_header.template 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() || length < Config::kHeaderSize) {
        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 (!ValidateLengthAlignment(length, Config::kVariableLengthAlignment)) {
        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.template Store8<0>(static_cast<uint8_t>(Config::kType));
    } else {
      tlv_header.template Store16<0>(Config::kType);
    }
    tlv_header.template Store16<2>(size);

    return BoundedByteWriter<Config::kHeaderSize>(
        rtc::ArrayView<uint8_t>(out.data() + offset, size));
  }

 private:
  static bool ValidateLengthAlignment(uint16_t length, size_t alignment) {
    // This is to avoid MSVC believing there could be a "mod by zero", when it
    // certainly can't.
    if (alignment == 0) {
      return true;
    }
    return (length % alignment) == 0;
  }
};

}  // namespace dcsctp

#endif  // NET_DCSCTP_PACKET_TLV_TRAIT_H_