/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 | | }; |