Coverage Report

Created: 2026-03-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/trafficserver/include/proxy/hdrs/XPACK.h
Line
Count
Source
1
/** @file
2
 *
3
 *  A brief file description
4
 *
5
 *  @section license License
6
 *
7
 *  Licensed to the Apache Software Foundation (ASF) under one
8
 *  or more contributor license agreements.  See the NOTICE file
9
 *  distributed with this work for additional information
10
 *  regarding copyright ownership.  The ASF licenses this file
11
 *  to you under the Apache License, Version 2.0 (the
12
 *  "License"); you may not use this file except in compliance
13
 *  with the License.  You may obtain a copy of the License at
14
 *
15
 *      http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 *  Unless required by applicable law or agreed to in writing, software
18
 *  distributed under the License is distributed on an "AS IS" BASIS,
19
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 *  See the License for the specific language governing permissions and
21
 *  limitations under the License.
22
 */
23
24
#pragma once
25
26
#include <cstdint>
27
#include <string_view>
28
#include "tscore/Arena.h"
29
30
const static int XPACK_ERROR_COMPRESSION_ERROR   = -1;
31
const static int XPACK_ERROR_SIZE_EXCEEDED_ERROR = -2;
32
33
// These primitives are shared with HPACK and QPACK.
34
int64_t xpack_encode_integer(uint8_t *buf_start, const uint8_t *buf_end, uint64_t value, uint8_t n);
35
int64_t xpack_decode_integer(uint64_t &dst, const uint8_t *buf_start, const uint8_t *buf_end, uint8_t n);
36
int64_t xpack_encode_string(uint8_t *buf_start, const uint8_t *buf_end, const char *value, uint64_t value_len, uint8_t n = 7);
37
int64_t xpack_decode_string(Arena &arena, char **str, uint64_t &str_length, const uint8_t *buf_start, const uint8_t *buf_end,
38
                            uint8_t n = 7);
39
40
struct XpackLookupResult {
41
  uint32_t index                                        = 0;
42
  enum class MatchType { NONE, NAME, EXACT } match_type = MatchType::NONE;
43
};
44
45
struct XpackDynamicTableEntry {
46
  uint32_t    index     = 0;
47
  uint32_t    offset    = 0;
48
  uint32_t    name_len  = 0;
49
  uint32_t    value_len = 0;
50
  uint32_t    ref_count = 0;
51
  const char *wks       = nullptr;
52
};
53
54
/** The memory containing the header fields. */
55
class XpackDynamicTableStorage
56
{
57
  friend class ExpandCapacityContext;
58
59
public:
60
  /** The storage for a dynamic table.
61
   *
62
   * @param[in] size The capacity of the table for header fields.
63
   */
64
  XpackDynamicTableStorage(uint32_t size);
65
  ~XpackDynamicTableStorage();
66
67
  /** Obtain the HTTP field name and value at @a offset bytes.
68
   *
69
   * @param[in] offset The offset from the start of the allocation from which to
70
   * obtain the header field.
71
   * @param[out] name A pointer to contain the name of the header field.
72
   * @param[out] name_len The length of the name.
73
   * @param[out] value A pointer to contain the value of the header field.
74
   * @param[out] value_len The length of the value.
75
   */
76
  void read(uint32_t offset, const char **name, uint32_t name_len, const char **value, uint32_t value_len) const;
77
78
  /** Writ the HTTP field at the head of the allocated data
79
   *
80
   * @param[in] name The HTTP field name to write.
81
   * @param[in] name_len The length of the name.
82
   * @param[in] value The HTTP field value to write.
83
   * @param[in] value_len The length of the value.
84
   *
85
   * @return The offset from the start of the allocation where the header field
86
   * was written.
87
   */
88
  uint32_t write(const char *name, uint32_t name_len, const char *value, uint32_t value_len);
89
90
  /** The amount of written bytes.
91
   *
92
   * The amount of written, unerased data. This is the difference between @a
93
   * _head and @a _tail.
94
   *
95
   * @return The number of written bytes.
96
   */
97
  uint32_t size() const;
98
99
private:
100
  /** Start expanding the capacity.
101
   *
102
   * Expanding the capacity is a two step process in which @a
103
   * _start_expanding_capacity is used to prepare for the expansion. This
104
   * populates @a _old_data with a pointer to the current @a _data pointer and
105
   * then allocates a new buffer per @a new_max_size and sets @a _data to that.
106
   * The caller then reinserts the current headers into the new buffer. Once
107
   * that is complete, the caller calls @a _finish_expanding_capacity to free
108
   * the old buffer.
109
   *
110
   * Handling these two phases should only be done by ExpandCapacityContext,
111
   * therefore these methods are private and only accessible to
112
   * ExpandCapacityContext via a friend relationship.
113
   *
114
   * @note XpackDynamicTableStorage only supports expanding the buffer. This
115
   * preserves offsets used by XpackDynamicTableEntry. Thus this function will
116
   * only return true when @a new_max_size is greater than the current capacity.
117
   *
118
   * The caller will need to reinsert all header fields after expanding the
119
   * capacity.
120
   *
121
   * @param[in] new_max_size The new maximum size of the table.
122
   * @return true if the capacity was expanded, false otherwise.
123
   */
124
  bool _start_expanding_capacity(uint32_t new_max_size);
125
126
  /** Finish expanding the capacity by freeing @a old_data. */
127
  void _finish_expanding_capacity();
128
129
private:
130
  /** The amount of space above @a size allocated as a buffer. */
131
  uint32_t _overwrite_threshold = 0;
132
133
  /** The space allocated and populated for the header fields. */
134
  uint8_t *_data = nullptr;
135
136
  /** When in an expansion phase, this points to the old memory.
137
   *
138
   * See the documentation in @a _start_expanding_capacity.
139
   */
140
  uint8_t *_old_data = nullptr;
141
142
  /** The size of allocated space for @a data.
143
   *
144
   * This is set to twice the requested space provided as @a size to the
145
   * constructor. This is done to avoid buffer wrapping.
146
   */
147
  uint32_t _capacity = 0;
148
149
  /** A pointer to the last byte written.
150
   *
151
   * @a _head is initialized to the last allocated byte. As header field data is
152
   * populated in the allocated space, this is advanced to the last byte
153
   * written. Thus the next write will start at the byte just after @a _head.
154
   */
155
  uint32_t _head = 0;
156
};
157
158
/** Define a context for expanding XpackDynamicTableStorage.
159
 *
160
 * Construction and destruction starts the expansion and finishes it, respectively.
161
 */
162
class ExpandCapacityContext
163
{
164
public:
165
  /** Begin the storage expansion phase to the @a new_max_size. */
166
0
  ExpandCapacityContext(XpackDynamicTableStorage &storage, uint32_t new_max_size) : _storage{storage}
167
0
  {
168
0
    this->_ok_to_expand = this->_storage._start_expanding_capacity(new_max_size);
169
0
  }
170
  /** End the storage expansion phase, cleaning up the old storage memory. */
171
0
  ~ExpandCapacityContext() { this->_storage._finish_expanding_capacity(); }
172
173
  // No copying or moving.
174
  ExpandCapacityContext(const ExpandCapacityContext &)            = delete;
175
  ExpandCapacityContext &operator=(const ExpandCapacityContext &) = delete;
176
  ExpandCapacityContext(ExpandCapacityContext &&)                 = delete;
177
  ExpandCapacityContext &operator=(ExpandCapacityContext &&)      = delete;
178
179
  /** Copy the field data from the old memory to the new one.
180
   * @param[in] old_offset The offset of data in the old memory.
181
   * @param[in] len The length of data to copy.
182
   * @return The offset of the copied data in the new memory.
183
   */
184
  uint32_t copy_field(uint32_t old_offset, uint32_t len);
185
186
  /** Indicate whether the expansion should proceed.
187
   * Return whether @a _storage indicates that the new max size is valid for
188
   * expanding the storage. If not, the expansion should not proceed.
189
   *
190
   * @return true if the new size is valid, false otherwise.
191
   */
192
  bool
193
  ok_to_expand() const
194
0
  {
195
0
    return this->_ok_to_expand;
196
0
  }
197
198
private:
199
  XpackDynamicTableStorage &_storage;
200
  bool                      _ok_to_expand = false;
201
};
202
203
class XpackDynamicTable
204
{
205
public:
206
  XpackDynamicTable(uint32_t size);
207
  ~XpackDynamicTable();
208
209
  const XpackLookupResult lookup(uint32_t absolute_index, const char **name, size_t *name_len, const char **value,
210
                                 size_t *value_len) const;
211
  const XpackLookupResult lookup(const char *name, size_t name_len, const char *value, size_t value_len) const;
212
  const XpackLookupResult lookup(const std::string_view name, const std::string_view value) const;
213
  const XpackLookupResult lookup_relative(uint32_t relative_index, const char **name, size_t *name_len, const char **value,
214
                                          size_t *value_len) const;
215
  const XpackLookupResult lookup_relative(const char *name, size_t name_len, const char *value, size_t value_len) const;
216
  const XpackLookupResult lookup_relative(const std::string_view name, const std::string_view value) const;
217
  const XpackLookupResult insert_entry(const char *name, size_t name_len, const char *value, size_t value_len);
218
  const XpackLookupResult insert_entry(const std::string_view name, const std::string_view value);
219
  const XpackLookupResult duplicate_entry(uint32_t current_index);
220
  bool                    should_duplicate(uint32_t index);
221
  bool                    update_maximum_size(uint32_t max_size);
222
  uint32_t                size() const;
223
  uint32_t                maximum_size() const;
224
  void                    ref_entry(uint32_t index);
225
  void                    unref_entry(uint32_t index);
226
  bool                    is_empty() const;
227
  uint32_t                largest_index() const;
228
  uint32_t                count() const;
229
230
private:
231
  static constexpr uint8_t ADDITIONAL_32_BYTES = 32;
232
  uint32_t                 _maximum_size       = 0;
233
  uint32_t                 _available          = 0;
234
  uint32_t                 _entries_inserted   = 0;
235
236
  struct XpackDynamicTableEntry *_entries      = nullptr;
237
  uint32_t                       _max_entries  = 0;
238
  uint32_t                       _entries_head = 0;
239
  uint32_t                       _entries_tail = 0;
240
  XpackDynamicTableStorage       _storage;
241
242
  /** Expand @a _storage to the new size.
243
   *
244
   * This takes care of expanding @a _storage's size and handles updating the
245
   * new offsets for each entry that this expansion requires.
246
   *
247
   * @param[in] new_storage_size The new size to expand @a _storage to.
248
   */
249
  void _expand_storage_size(uint32_t new_storage_size);
250
251
  /** Evict entries to obtain the extra space needed.
252
   *
253
   * The type of reuired_size is uint64 so that we can handle a size that is bigger than the table capacity.
254
   * Passing a value more than UINT32_MAX evicts every entry and returns false.
255
   *
256
   * @param[in] extra_space_needed The amount of space needed to be freed.
257
   * @return true if the required space was freed, false otherwise.
258
   */
259
  bool _make_space(uint64_t extra_space_needed);
260
261
  /** Calcurates the index number for _entries, which is a kind of circular buffer.
262
   *
263
   * @param[in] base The place to start indexing from. Passing @a _tail
264
   * references the start of the buffer, while @a _head references the end of
265
   * the buffer.
266
   *
267
   * @param[in] offset The offset from the base. A value of 1 means the first
268
   * entry from @a base. Thus a value of @a _tail for @a base and 1 for @a
269
   * offset references the first entry in the buffer.
270
   */
271
  uint32_t _calc_index(uint32_t base, int64_t offset) const;
272
};