View Javadoc
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 }