1 /*
2 * Copyright 2020 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.http3;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufAllocator;
20 import io.netty.handler.codec.quic.QuicStreamChannel;
21 import io.netty.util.ReferenceCountUtil;
22 import io.netty.util.collection.LongObjectHashMap;
23 import io.netty.util.internal.ObjectUtil;
24
25 import javax.annotation.Nullable;
26 import java.util.ArrayDeque;
27 import java.util.Arrays;
28 import java.util.Map;
29 import java.util.Queue;
30
31 import static io.netty.handler.codec.http3.Http3CodecUtils.closeOnFailure;
32 import static io.netty.handler.codec.http3.QpackHeaderField.sizeOf;
33 import static io.netty.handler.codec.http3.QpackUtil.encodePrefixedInteger;
34
35 /**
36 * A QPACK encoder.
37 */
38 final class QpackEncoder {
39 private static final QpackException INVALID_SECTION_ACKNOWLEDGMENT =
40 QpackException.newStatic(QpackDecoder.class, "sectionAcknowledgment(...)",
41 "QPACK - section acknowledgment received for unknown stream.");
42 private static final int DYNAMIC_TABLE_ENCODE_NOT_DONE = -1;
43 private static final int DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE = -2;
44 /**
45 * Maximum number of field sections for which we will track dynamic-table references while waiting for the
46 * peer's Section Acknowledgment or Stream Cancellation instruction. Both instructions are optional per
47 * <a href="https://www.rfc-editor.org/rfc/rfc9204.html#section-2.2.2.2">RFC 9204, section 2.2.2.2</a>, so a
48 * remote peer that never sends them (while still acknowledging dynamic-table insertions) must not be able to
49 * grow this per-connection state without bound. Once the limit is reached we stop referencing the dynamic
50 * table for new field sections (falling back to literal encoding, which is always legal, see
51 * <a href="https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.4">section 4.5.4</a>) until enough
52 * outstanding sections are acknowledged or cancelled to free up tracking capacity again.
53 */
54 // Visible for tests
55 static final int MAX_OUTSTANDING_SECTIONS = 10_000;
56
57 private final QpackHuffmanEncoder huffmanEncoder;
58 private final QpackEncoderDynamicTable dynamicTable;
59 private final QpackSensitivityDetector sensitivityDetector;
60 private int maxBlockedStreams;
61 private int blockedStreams;
62 private LongObjectHashMap<Queue<Indices>> streamSectionTrackers;
63 private int outstandingSections;
64
65 QpackEncoder(@Nullable QpackSensitivityDetector sensitivityDetector) {
66 this(new QpackEncoderDynamicTable(), sensitivityDetector);
67 }
68
69 QpackEncoder(QpackEncoderDynamicTable dynamicTable, @Nullable QpackSensitivityDetector sensitivityDetector) {
70 huffmanEncoder = new QpackHuffmanEncoder();
71 this.dynamicTable = ObjectUtil.checkNotNull(dynamicTable, "dynamicTable");
72 this.sensitivityDetector = sensitivityDetector == null ?
73 QpackSensitivityDetector.NEVER_SENSITIVE : sensitivityDetector;
74 }
75
76 /**
77 * Encode the header field into the header block.
78 *
79 * <p>Fields for which {@link QpackSensitivityDetector#isSensitive(CharSequence, CharSequence)}
80 * returns {@code true} are encoded as literals with the
81 * <a href="https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.4">"Never Indexed"</a>
82 * ({@code N=1}) flag set, and are never inserted into the dynamic table.</p>
83 */
84 void encodeHeaders(QpackAttributes qpackAttributes, ByteBuf out, ByteBufAllocator allocator, long streamId,
85 Http3Headers headers) {
86 final int base = dynamicTable.insertCount();
87 // Allocate a new buffer as we have to go back and write a variable length base and required insert count
88 // later.
89 ByteBuf tmp = allocator.buffer();
90 try {
91 int maxDynamicTblIdx = -1;
92 int requiredInsertCount = 0;
93 Indices dynamicTableIndices = null;
94 for (Map.Entry<CharSequence, CharSequence> header : headers) {
95 CharSequence name = header.getKey();
96 CharSequence value = header.getValue();
97 int dynamicTblIdx;
98 if (sensitivityDetector.isSensitive(name, value)) {
99 encodeSensitiveHeader(tmp, name, value);
100 dynamicTblIdx = DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
101 } else {
102 dynamicTblIdx = encodeHeader(qpackAttributes, tmp, base, name, value);
103 }
104 if (dynamicTblIdx >= 0) {
105 int req = dynamicTable.addReferenceToEntry(name, value, dynamicTblIdx);
106 if (dynamicTblIdx > maxDynamicTblIdx) {
107 maxDynamicTblIdx = dynamicTblIdx;
108 requiredInsertCount = req;
109 }
110 if (dynamicTableIndices == null) {
111 dynamicTableIndices = new Indices();
112 }
113 dynamicTableIndices.add(dynamicTblIdx);
114 }
115 }
116
117 // Track all the indices that we need to ack later.
118 if (dynamicTableIndices != null) {
119 assert streamSectionTrackers != null;
120 streamSectionTrackers.computeIfAbsent(streamId, __ -> new ArrayDeque<>())
121 .add(dynamicTableIndices);
122 outstandingSections++;
123 }
124
125 // https://www.rfc-editor.org/rfc/rfc9204.html#name-encoded-field-section-prefi
126 // 0 1 2 3 4 5 6 7
127 // +---+---+---+---+---+---+---+---+
128 // | Required Insert Count (8+) |
129 // +---+---------------------------+
130 // | S | Delta Base (7+) |
131 // +---+---------------------------+
132 encodePrefixedInteger(out, (byte) 0b0, 8, dynamicTable.encodedRequiredInsertCount(requiredInsertCount));
133 if (base >= requiredInsertCount) {
134 encodePrefixedInteger(out, (byte) 0b0, 7, base - requiredInsertCount);
135 } else {
136 encodePrefixedInteger(out, (byte) 0b1000_0000, 7, requiredInsertCount - base - 1);
137 }
138 out.writeBytes(tmp);
139 } finally {
140 tmp.release();
141 }
142 }
143
144 void configureDynamicTable(QpackAttributes attributes, long maxTableCapacity, int blockedStreams)
145 throws QpackException {
146 if (maxTableCapacity > 0) {
147 assert attributes.encoderStreamAvailable();
148 final QuicStreamChannel encoderStream = attributes.encoderStream();
149 dynamicTable.maxTableCapacity(maxTableCapacity);
150 final ByteBuf tableCapacity = encoderStream.alloc().buffer(8);
151 // https://www.rfc-editor.org/rfc/rfc9204.html#name-set-dynamic-table-capacity
152 // 0 1 2 3 4 5 6 7
153 // +---+---+---+---+---+---+---+---+
154 // | 0 | 0 | 1 | Capacity (5+) |
155 // +---+---+---+-------------------+
156 encodePrefixedInteger(tableCapacity, (byte) 0b0010_0000, 5, maxTableCapacity);
157 closeOnFailure(encoderStream.writeAndFlush(tableCapacity));
158
159 streamSectionTrackers = new LongObjectHashMap<>();
160 maxBlockedStreams = blockedStreams;
161 }
162 }
163
164 /**
165 * <a href="https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-section-acknowledgment">
166 * Section acknowledgment</a> for the passed {@code streamId}.
167 *
168 * @param streamId For which the header fields section is acknowledged.
169 */
170 void sectionAcknowledgment(long streamId) throws QpackException {
171 // If a configureDynamicTable(...) was called with a maxTableCapacity of 0 we will have not instanced
172 // streamSectionTrackers. The remote peer might still (incorrectly) send a section acknowledgment for a
173 // stream, so this must be treated as a protocol error instead of throwing an NPE.
174 // See https://www.rfc-editor.org/rfc/rfc9204.html#section-4.4.1
175 if (streamSectionTrackers == null) {
176 throw INVALID_SECTION_ACKNOWLEDGMENT;
177 }
178 final Queue<Indices> tracker = streamSectionTrackers.get(streamId);
179 if (tracker == null) {
180 throw INVALID_SECTION_ACKNOWLEDGMENT;
181 }
182
183 Indices dynamicTableIndices = tracker.poll();
184
185 if (tracker.isEmpty()) {
186 streamSectionTrackers.remove(streamId);
187 }
188
189 if (dynamicTableIndices == null) {
190 throw INVALID_SECTION_ACKNOWLEDGMENT;
191 }
192 outstandingSections--;
193
194 dynamicTableIndices.forEach(dynamicTable::acknowledgeInsertCountOnAck);
195 }
196
197 /**
198 * <a href="https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-stream-cancellation">
199 * Stream cancellation</a> for the passed {@code streamId}.
200 *
201 * @param streamId which is cancelled.
202 */
203 void streamCancellation(long streamId) throws QpackException {
204 // If a configureDynamicTable(...) was called with a maxTableCapacity of 0 we will have not instanced
205 // streamSectionTrackers. The remote peer might still send a stream cancellation for a stream, while it
206 // is optional. See https://www.rfc-editor.org/rfc/rfc9204.html#section-2.2.2.2
207 if (streamSectionTrackers == null) {
208 return;
209 }
210 final Queue<Indices> tracker = streamSectionTrackers.remove(streamId);
211 if (tracker != null) {
212 for (;;) {
213 Indices dynamicTableIndices = tracker.poll();
214 if (dynamicTableIndices == null) {
215 break;
216 }
217 outstandingSections--;
218 dynamicTableIndices.forEach(dynamicTable::acknowledgeInsertCountOnCancellation);
219 }
220 }
221 }
222
223 /**
224 * <a href="https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-insert-count-increment">
225 * Insert count increment</a>.
226 *
227 * @param increment for the known received count.
228 */
229 void insertCountIncrement(int increment) throws QpackException {
230 dynamicTable.incrementKnownReceivedCount(increment);
231 }
232
233 /**
234 * Encode a header field that the {@link QpackSensitivityDetector} flagged as sensitive.
235 *
236 * <p>Sensitive fields are never inserted into the dynamic table and are encoded
237 * with the {@code "Never Indexed"} flag ({@code N=1}) so that intermediaries
238 * also avoid indexing them — see
239 * <a href="https://www.rfc-editor.org/rfc/rfc9204.html#section-7.1">RFC 9204 7.1</a>.
240 * </p>
241 */
242 private void encodeSensitiveHeader(ByteBuf out, CharSequence name, CharSequence value) {
243 final int index = QpackStaticTable.findFieldIndex(name, value);
244 if (index == QpackStaticTable.NOT_FOUND) {
245 encodeLiteral(out, name, value, true);
246 } else if ((index & QpackStaticTable.MASK_NAME_REF) == QpackStaticTable.MASK_NAME_REF) {
247 // Name-only match, reuse the cached lookup instead of calling getIndex(name) again.
248 encodeLiteralWithNameRefStaticTable(out, index ^ QpackStaticTable.MASK_NAME_REF, value, true);
249 } else {
250 // Exact (name, value) match in the static table, an indexed static reference
251 // does not leak more information than what is already public, and the
252 // intermediary cannot gain any compression benefit by inserting a copy into
253 // its own dynamic table (the entry is already there as part of the static table).
254 encodeIndexedStaticTable(out, index);
255 }
256 }
257
258 /**
259 * Encode the header field into the header block.
260 * @param qpackAttributes {@link QpackAttributes} for the channel.
261 * @param out {@link ByteBuf} to which encoded header field is to be written.
262 * @param base Base for the dynamic table index.
263 * @param name for the header field.
264 * @param value for the header field.
265 * @return Index in the dynamic table if the header field was encoded as a reference to the dynamic table,
266 * {@link #DYNAMIC_TABLE_ENCODE_NOT_DONE } otherwise.
267 */
268 private int encodeHeader(QpackAttributes qpackAttributes, ByteBuf out, int base, CharSequence name,
269 CharSequence value) {
270 int index = QpackStaticTable.findFieldIndex(name, value);
271 if (index == QpackStaticTable.NOT_FOUND) {
272 if (isDynamicTableUnavailable(qpackAttributes)) {
273 encodeLiteral(out, name, value, false);
274 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
275 }
276 return encodeWithDynamicTable(qpackAttributes, out, base, name, value);
277 } else if ((index & QpackStaticTable.MASK_NAME_REF) == QpackStaticTable.MASK_NAME_REF) {
278 int dynamicTblIdx = tryEncodeWithDynamicTable(qpackAttributes, out, base, name, value);
279 if (dynamicTblIdx >= 0) {
280 return dynamicTblIdx;
281 }
282 final int nameIdx = index ^ QpackStaticTable.MASK_NAME_REF;
283 if (dynamicTblIdx == DYNAMIC_TABLE_ENCODE_NOT_DONE) {
284 dynamicTblIdx = tryAddToDynamicTable(qpackAttributes, true, nameIdx, name, value);
285 }
286 if (dynamicTblIdx >= 0) {
287 if (dynamicTblIdx >= base) {
288 encodePostBaseIndexed(out, base, dynamicTblIdx);
289 } else {
290 encodeIndexedDynamicTable(out, base, dynamicTblIdx);
291 }
292 return dynamicTblIdx;
293 }
294 encodeLiteralWithNameRefStaticTable(out, nameIdx, value, false);
295 } else {
296 encodeIndexedStaticTable(out, index);
297 }
298 return isDynamicTableUnavailable(qpackAttributes) ? DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE :
299 DYNAMIC_TABLE_ENCODE_NOT_DONE;
300 }
301
302 /**
303 * Encode the header field using dynamic table, if possible.
304 *
305 * @param qpackAttributes {@link QpackAttributes} for the channel.
306 * @param out {@link ByteBuf} to which encoded header field is to be written.
307 * @param base Base for the dynamic table index.
308 * @param name for the header field.
309 * @param value for the header field.
310 * @return Index in the dynamic table if the header field was encoded as a reference to the dynamic table,
311 * {@link #DYNAMIC_TABLE_ENCODE_NOT_DONE } otherwise.
312 */
313 private int encodeWithDynamicTable(QpackAttributes qpackAttributes, ByteBuf out, int base, CharSequence name,
314 CharSequence value) {
315 int idx = tryEncodeWithDynamicTable(qpackAttributes, out, base, name, value);
316 if (idx >= 0) {
317 return idx;
318 }
319
320 if (idx == DYNAMIC_TABLE_ENCODE_NOT_DONE) {
321 idx = tryAddToDynamicTable(qpackAttributes, false, -1, name, value);
322 if (idx >= 0) {
323 if (idx >= base) {
324 encodePostBaseIndexed(out, base, idx);
325 } else {
326 encodeIndexedDynamicTable(out, base, idx);
327 }
328 return idx;
329 }
330 }
331 encodeLiteral(out, name, value, false);
332 return idx;
333 }
334
335 /**
336 * Try to encode the header field using dynamic table, otherwise do not encode.
337 *
338 * @param qpackAttributes {@link QpackAttributes} for the channel.
339 * @param out {@link ByteBuf} to which encoded header field is to be written.
340 * @param base Base for the dynamic table index.
341 * @param name for the header field.
342 * @param value for the header field.
343 * @return Index in the dynamic table if the header field was encoded as a reference to the dynamic table.
344 * {@link #DYNAMIC_TABLE_ENCODE_NOT_DONE } if encoding was not done. {@link #DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE }
345 * if dynamic table encoding is not possible (size constraint) and hence should not be tried for this header.
346 */
347 private int tryEncodeWithDynamicTable(QpackAttributes qpackAttributes, ByteBuf out, int base, CharSequence name,
348 CharSequence value) {
349 if (isDynamicTableUnavailable(qpackAttributes)) {
350 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
351 }
352 assert qpackAttributes.encoderStreamAvailable();
353 final QuicStreamChannel encoderStream = qpackAttributes.encoderStream();
354
355 int idx = dynamicTable.getEntryIndex(name, value);
356 if (idx == QpackEncoderDynamicTable.NOT_FOUND) {
357 return DYNAMIC_TABLE_ENCODE_NOT_DONE;
358 }
359 if (idx >= 0) {
360 if (dynamicTable.requiresDuplication(idx, sizeOf(name, value))) {
361 idx = dynamicTable.add(name, value, sizeOf(name, value));
362 assert idx >= 0;
363 // https://www.rfc-editor.org/rfc/rfc9204.html#section-4.3.4
364 // 0 1 2 3 4 5 6 7
365 // +---+---+---+---+---+---+---+---+
366 // | 0 | 0 | 0 | Index (5+) |
367 // +---+---+---+-------------------+
368 ByteBuf duplicate = encoderStream.alloc().buffer(8);
369 encodePrefixedInteger(duplicate, (byte) 0b0000_0000, 5,
370 dynamicTable.relativeIndexForEncoderInstructions(idx));
371 closeOnFailure(encoderStream.writeAndFlush(duplicate));
372 if (mayNotBlockStream()) {
373 // Add to the table but do not use the entry in the header block to avoid blocking.
374 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
375 }
376 blockedStreams++;
377 } else if (!dynamicTable.isKnownReceived(idx)) {
378 // The decoder may not have received this entry yet, so referencing it may block the stream.
379 // See https://www.rfc-editor.org/rfc/rfc9204.html#section-2.1.2
380 if (mayNotBlockStream()) {
381 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
382 }
383 blockedStreams++;
384 }
385 if (idx >= base) {
386 encodePostBaseIndexed(out, base, idx);
387 } else {
388 encodeIndexedDynamicTable(out, base, idx);
389 }
390 } else { // name match
391 idx = -(idx + 1);
392 int addIdx = tryAddToDynamicTable(qpackAttributes, false,
393 dynamicTable.relativeIndexForEncoderInstructions(idx), name, value);
394 if (addIdx < 0) {
395 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
396 }
397 idx = addIdx;
398
399 if (idx >= base) {
400 encodeLiteralWithPostBaseNameRef(out, base, idx, value);
401 } else {
402 encodeLiteralWithNameRefDynamicTable(out, base, idx, value);
403 }
404 }
405 return idx;
406 }
407
408 /**
409 * Try adding the header field to the dynamic table.
410 *
411 * @param qpackAttributes {@link QpackAttributes} for the channel.
412 * @param staticTableNameRef if {@code nameIdx} is an index in the static table.
413 * @param nameIdx Index of the name if {@code > 0}.
414 * @param name for the header field.
415 * @param value for the header field.
416 * @return Index in the dynamic table if the header field was encoded as a reference to the dynamic table,
417 * {@link #DYNAMIC_TABLE_ENCODE_NOT_DONE} otherwise.
418 */
419 private int tryAddToDynamicTable(QpackAttributes qpackAttributes, boolean staticTableNameRef, int nameIdx,
420 CharSequence name, CharSequence value) {
421 if (isDynamicTableUnavailable(qpackAttributes)) {
422 return DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE;
423 }
424 assert qpackAttributes.encoderStreamAvailable();
425 final QuicStreamChannel encoderStream = qpackAttributes.encoderStream();
426
427 int idx = dynamicTable.add(name, value, sizeOf(name, value));
428 if (idx >= 0) {
429 ByteBuf insert = null;
430 try {
431 if (nameIdx >= 0) {
432 // 2 prefixed integers (name index and value length) each requires a maximum of 8 bytes
433 insert = encoderStream.alloc().buffer(value.length() + 16);
434 // https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-name-reference
435 // 0 1 2 3 4 5 6 7
436 // +---+---+---+---+---+---+---+---+
437 // | 1 | T | Name Index (6+) |
438 // +---+---+-----------------------+
439 encodePrefixedInteger(insert, (byte) (staticTableNameRef ? 0b1100_0000 : 0b1000_0000), 6, nameIdx);
440 } else {
441 // 2 prefixed integers (name and value length) each requires a maximum of 8 bytes
442 insert = encoderStream.alloc().buffer(name.length() + value.length() + 16);
443 // https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-literal-name
444 // 0 1 2 3 4 5 6 7
445 // +---+---+---+---+---+---+---+---+
446 // | 0 | 1 | H | Name Length (5+) |
447 // +---+---+---+-------------------+
448 // | Name String (Length bytes) |
449 // +---+---------------------------+
450 // Names are always Huffman-encoded (H = 1). RFC 9204 makes
451 // Huffman a pure size/CPU tradeoff and does not tie it to the
452 // sensitivity of the field; whether intermediaries may index
453 // the field is controlled separately by the N bit on the
454 // matching literal field line.
455 encodeLengthPrefixedHuffmanEncodedLiteral(insert, (byte) 0b0110_0000, 5, name);
456 }
457 // 0 1 2 3 4 5 6 7
458 // +---+---+-----------------------+
459 // | H | Value Length (7+) |
460 // +---+---------------------------+
461 // | Value String (Length bytes) |
462 // +-------------------------------+
463 encodeStringLiteral(insert, value);
464 } catch (Exception e) {
465 ReferenceCountUtil.release(insert);
466 return DYNAMIC_TABLE_ENCODE_NOT_DONE;
467 }
468 closeOnFailure(encoderStream.writeAndFlush(insert));
469 if (mayNotBlockStream()) {
470 // Add to the table but do not use the entry in the header block to avoid blocking.
471 return DYNAMIC_TABLE_ENCODE_NOT_DONE;
472 }
473 blockedStreams++;
474 }
475 return idx;
476 }
477
478 private void encodeIndexedStaticTable(ByteBuf out, int index) {
479 // https://www.rfc-editor.org/rfc/rfc9204.html#name-indexed-field-line
480 // 0 1 2 3 4 5 6 7
481 // +---+---+---+---+---+---+---+---+
482 // | 1 | T | Index (6+) |
483 // +---+---+-----------------------+
484 encodePrefixedInteger(out, (byte) 0b1100_0000, 6, index);
485 }
486
487 private void encodeIndexedDynamicTable(ByteBuf out, int base, int index) {
488 // https://www.rfc-editor.org/rfc/rfc9204.html#name-indexed-field-line
489 // 0 1 2 3 4 5 6 7
490 // +---+---+---+---+---+---+---+---+
491 // | 1 | T | Index (6+) |
492 // +---+---+-----------------------+
493 encodePrefixedInteger(out, (byte) 0b1000_0000, 6, base - index - 1);
494 }
495
496 private void encodePostBaseIndexed(ByteBuf out, int base, int index) {
497 // https://www.rfc-editor.org/rfc/rfc9204.html#name-indexed-field-line-with-pos
498 // 0 1 2 3 4 5 6 7
499 // +---+---+---+---+---+---+---+---+
500 // | 0 | 0 | 0 | 1 | Index (4+) |
501 // +---+---+---+---+---------------+
502 encodePrefixedInteger(out, (byte) 0b0001_0000, 4, index - base);
503 }
504
505 private void encodeLiteralWithNameRefStaticTable(ByteBuf out, int nameIndex, CharSequence value,
506 boolean neverIndex) {
507 // https://www.rfc-editor.org/rfc/rfc9204.html#name-literal-field-line-with-nam
508 // 0 1 2 3 4 5 6 7
509 // +---+---+---+---+---+---+---+---+
510 // | 0 | 1 | N | T |Name Index (4+)|
511 // +---+---+---+---+---------------+
512 // | H | Value Length (7+) |
513 // +---+---------------------------+
514 // | Value String (Length bytes) |
515 // +-------------------------------+
516 //
517 // T = 1 (static table). N is driven by the sensitivity detector.
518 final byte prefix = (byte) (0b0101_0000 | (neverIndex ? 0b0010_0000 : 0));
519 encodePrefixedInteger(out, prefix, 4, nameIndex);
520 encodeStringLiteral(out, value);
521 }
522
523 private void encodeLiteralWithNameRefDynamicTable(ByteBuf out, int base, int nameIndex, CharSequence value) {
524 // https://www.rfc-editor.org/rfc/rfc9204.html#name-literal-field-line-with-nam
525 // 0 1 2 3 4 5 6 7
526 // +---+---+---+---+---+---+---+---+
527 // | 0 | 1 | N | T |Name Index (4+)|
528 // +---+---+---+---+---------------+
529 // | H | Value Length (7+) |
530 // +---+---------------------------+
531 // | Value String (Length bytes) |
532 // +-------------------------------+
533 //
534 // T = 0 (dynamic table). Sensitive headers are routed through
535 // encodeSensitiveHeader() which bypasses the dynamic table entirely, so
536 // anything reaching this method is non-sensitive and N is always 0.
537 encodePrefixedInteger(out, (byte) 0b0100_0000, 4, base - nameIndex - 1);
538 encodeStringLiteral(out, value);
539 }
540
541 private void encodeLiteralWithPostBaseNameRef(ByteBuf out, int base, int nameIndex, CharSequence value) {
542 // https://www.rfc-editor.org/rfc/rfc9204.html#name-literal-field-line-with-pos
543 // 0 1 2 3 4 5 6 7
544 // +---+---+---+---+---+---+---+---+
545 // | 0 | 0 | 0 | 0 | N |NameIdx(3+)|
546 // +---+---+---+---+---+-----------+
547 // | H | Value Length (7+) |
548 // +---+---------------------------+
549 // | Value String (Length bytes) |
550 // +-------------------------------+
551 //
552 // Same as above post-base references only exist for entries in the
553 // encoder's dynamic table, so sensitive headers never reach here and
554 // N is always 0.
555 encodePrefixedInteger(out, (byte) 0, 3, nameIndex - base);
556 encodeStringLiteral(out, value);
557 }
558
559 private void encodeLiteral(ByteBuf out, CharSequence name, CharSequence value, boolean neverIndex) {
560 // https://www.rfc-editor.org/rfc/rfc9204.html#name-literal-field-line-with-lit
561 // 0 1 2 3 4 5 6 7
562 // +---+---+---+---+---+---+---+---+
563 // | 0 | 0 | 1 | N | H |NameLen(3+)|
564 // +---+---+---+---+---+-----------+
565 // | Name String (Length bytes) |
566 // +---+---------------------------+
567 // | H | Value Length (7+) |
568 // +---+---------------------------+
569 // | Value String (Length bytes) |
570 // +-------------------------------+
571 //
572 // H = 1 (Huffman) is always set for the name length prefix.
573 final byte prefix = (byte) (0b0010_1000 | (neverIndex ? 0b0001_0000 : 0));
574 encodeLengthPrefixedHuffmanEncodedLiteral(out, prefix, 3, name);
575 encodeStringLiteral(out, value);
576 }
577
578 /**
579 * Encode string literal according to Section 5.2.
580 * <a href="https://tools.ietf.org/html/rfc7541#section-5.2">Section 5.2</a>.
581 */
582 private void encodeStringLiteral(ByteBuf out, CharSequence value) {
583 // 0 1 2 3 4 5 6 7
584 // +---+---+---+---+---+---+---+---+
585 // | H | String Length (7+) |
586 // +---+---------------------------+
587 // | String Data (Length octets) |
588 // +-------------------------------+
589 // String values are always Huffman-encoded (H = 1). RFC 9204 treats the
590 // H bit as a pure size/CPU choice; whether intermediaries may index the
591 // field is controlled separately by the N bit on the literal field line.
592 encodeLengthPrefixedHuffmanEncodedLiteral(out, (byte) 0b1000_0000, 7, value);
593 }
594
595 /**
596 * Encode a string literal.
597 */
598 private void encodeLengthPrefixedHuffmanEncodedLiteral(ByteBuf out, byte mask, int prefix, CharSequence value) {
599 int huffmanLength = huffmanEncoder.getEncodedLength(value);
600 encodePrefixedInteger(out, mask, prefix, huffmanLength);
601 huffmanEncoder.encode(out, value);
602 }
603
604 private boolean mayNotBlockStream() {
605 return blockedStreams >= maxBlockedStreams - 1;
606 }
607
608 /**
609 * Whether the dynamic table must not be used to encode the next header field: either because it was disabled
610 * for this connection, or because we are already tracking {@link #MAX_OUTSTANDING_SECTIONS} field sections
611 * that are awaiting a Section Acknowledgment or Stream Cancellation instruction from the peer.
612 *
613 * @param qpackAttributes the attributes used.
614 * @return {@code true} if the dynamic table can't be used, {@code false} otherwise.
615 */
616 private boolean isDynamicTableUnavailable(QpackAttributes qpackAttributes) {
617 return qpackAttributes.dynamicTableDisabled() || outstandingSections >= MAX_OUTSTANDING_SECTIONS;
618 }
619
620 // Visible for tests
621 int outstandingSectionCount() {
622 return outstandingSections;
623 }
624
625 private static final class Indices {
626 private int idx;
627 // Let's just assume 4 indices for now that we will store here as max.
628 private int[] array = new int[4];
629
630 void add(int index) {
631 if (idx == array.length) {
632 // Double it if needed.
633 array = Arrays.copyOf(array, array.length << 1);
634 }
635 array[idx++] = index;
636 }
637
638 void forEach(IndexConsumer consumer) throws QpackException {
639 for (int i = 0; i < idx; i++) {
640 consumer.accept(array[i]);
641 }
642 }
643
644 @FunctionalInterface
645 interface IndexConsumer {
646 void accept(int idx) throws QpackException;
647 }
648 }
649 }