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