View Javadoc
1   /*
2    * Copyright 2021 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  
17  package io.netty.handler.codec.http3;
18  
19  import java.util.Arrays;
20  
21  import static io.netty.handler.codec.http3.QpackHeaderField.ENTRY_OVERHEAD;
22  import static io.netty.handler.codec.http3.QpackUtil.MAX_HEADER_TABLE_SIZE;
23  import static io.netty.handler.codec.http3.QpackUtil.MIN_HEADER_TABLE_SIZE;
24  import static io.netty.handler.codec.http3.QpackUtil.toIntOrThrow;
25  import static java.lang.Math.floorDiv;
26  
27  final class QpackDecoderDynamicTable {
28      private static final QpackException GET_ENTRY_ILLEGAL_INDEX_VALUE =
29              QpackException.newStatic(QpackDecoderDynamicTable.class, "getEntry(...)",
30                      "QPACK - illegal decoder dynamic table index value");
31      private static final QpackException HEADER_TOO_LARGE =
32              QpackException.newStatic(QpackDecoderDynamicTable.class, "add(...)", "QPACK - header entry too large.");
33  
34      // a circular queue of header fields
35      private QpackHeaderField[] fields;
36      private int head;
37      private int tail;
38      private long size;
39      private long capacity = -1; // ensure setCapacity creates the array
40      private int insertCount;
41  
42      int length() {
43          return head < tail ? fields.length - tail + head : head - tail;
44      }
45  
46      long size() {
47          return size;
48      }
49  
50      int insertCount() {
51          return insertCount;
52      }
53  
54      QpackHeaderField getEntry(int index) throws QpackException {
55          if (index < 0 || fields == null || index >= fields.length) {
56              throw GET_ENTRY_ILLEGAL_INDEX_VALUE;
57          }
58          QpackHeaderField entry = fields[index];
59          if (entry == null) {
60              throw GET_ENTRY_ILLEGAL_INDEX_VALUE;
61          }
62          return entry;
63      }
64  
65      QpackHeaderField getEntryRelativeEncodedField(int index) throws QpackException {
66          // https://www.rfc-editor.org/rfc/rfc9204.html#name-relative-indexing
67          return getEntry(moduloIndex(index));
68      }
69  
70      QpackHeaderField getEntryRelativeEncoderInstructions(int index) throws QpackException {
71          // https://www.rfc-editor.org/rfc/rfc9204.html#name-relative-indexing
72          // Name index is the relative index, relative to the last added entry.
73          return getEntry(index > tail ? fields.length - index + tail : tail - index);
74      }
75  
76      void add(QpackHeaderField header) throws QpackException {
77          long headerSize = header.size();
78          if (headerSize > capacity) {
79              throw HEADER_TOO_LARGE;
80          }
81          while (capacity - size < headerSize) {
82              remove();
83          }
84          insertCount++;
85          fields[getAndIncrementHead()] = header;
86          size += headerSize;
87      }
88  
89      private void remove() {
90          QpackHeaderField removed = fields[tail];
91          if (removed == null) {
92              return;
93          }
94          size -= removed.size();
95          fields[getAndIncrementTail()] = null;
96      }
97  
98      void clear() {
99          if (fields != null) {
100             Arrays.fill(fields, null);
101         }
102         head = 0;
103         tail = 0;
104         size = 0;
105     }
106 
107     void setCapacity(long capacity) throws QpackException {
108         if (capacity < MIN_HEADER_TABLE_SIZE || capacity > MAX_HEADER_TABLE_SIZE) {
109             throw new IllegalArgumentException("capacity is invalid: " + capacity);
110         }
111         // initially capacity will be -1 so init won't return here
112         if (this.capacity == capacity) {
113             return;
114         }
115         this.capacity = capacity;
116 
117         if (capacity == 0) {
118             clear();
119         } else {
120             // initially size will be 0 so remove won't be called
121             while (size > capacity) {
122                 remove();
123             }
124         }
125 
126         int maxEntries = toIntOrThrow(2 * floorDiv(capacity, ENTRY_OVERHEAD));
127 
128         // check if capacity change requires us to reallocate the array
129         if (fields != null && fields.length == maxEntries) {
130             return;
131         }
132 
133         QpackHeaderField[] tmp = new QpackHeaderField[maxEntries];
134 
135         // initially length will be 0 so there will be no copy
136         int len = length();
137         if (fields != null && tail != head) {
138             if (head > tail) {
139                 System.arraycopy(fields, tail, tmp, 0, head - tail);
140             } else {
141                 System.arraycopy(fields, 0, tmp, 0, head);
142                 System.arraycopy(fields, tail, tmp, head, fields.length - tail);
143             }
144         }
145 
146         tail = 0;
147         head = tail + len;
148         fields = tmp;
149     }
150 
151     private int getAndIncrementHead() {
152         int val = this.head;
153         this.head = safeIncrementIndex(val);
154         return val;
155     }
156 
157     private int getAndIncrementTail() {
158         int val = this.tail;
159         this.tail = safeIncrementIndex(val);
160         return val;
161     }
162 
163     private int safeIncrementIndex(int index) {
164         return ++index % fields.length;
165     }
166 
167     private int moduloIndex(int index) {
168         return fields == null ? index : index % fields.length;
169     }
170 }