1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.http2.hpack;
20
21 import java.nio.ByteBuffer;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.eclipse.jetty.http.HttpField;
28 import org.eclipse.jetty.http.HttpHeader;
29 import org.eclipse.jetty.http.HttpMethod;
30 import org.eclipse.jetty.http.HttpScheme;
31 import org.eclipse.jetty.util.ArrayQueue;
32 import org.eclipse.jetty.util.ArrayTernaryTrie;
33 import org.eclipse.jetty.util.StringUtil;
34 import org.eclipse.jetty.util.Trie;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37
38
39
40
41
42
43
44
45
46
47 public class HpackContext
48 {
49 public static final Logger LOG = Log.getLogger(HpackContext.class);
50
51 public static final String[][] STATIC_TABLE =
52 {
53 {null,null},
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 };
116
117 private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
118 private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
119 private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
120 private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
121 static
122 {
123 Set<String> added = new HashSet<>();
124 for (int i=1;i<STATIC_TABLE.length;i++)
125 {
126 StaticEntry entry=null;
127
128 String name = STATIC_TABLE[i][0];
129 String value = STATIC_TABLE[i][1];
130 HttpHeader header = HttpHeader.CACHE.get(name);
131 if (header!=null && value!=null)
132 {
133 switch (header)
134 {
135 case C_METHOD:
136 {
137
138 HttpMethod method = HttpMethod.CACHE.get(value);
139 if (method!=null)
140 entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,method));
141 break;
142 }
143
144 case C_SCHEME:
145 {
146
147 HttpScheme scheme = HttpScheme.CACHE.get(value);
148 if (scheme!=null)
149 entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,scheme));
150 break;
151 }
152
153 case C_STATUS:
154 {
155 entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,Integer.valueOf(value)));
156 break;
157 }
158
159 default:
160 break;
161 }
162 }
163
164 if (entry==null)
165 entry=new StaticEntry(i,header==null?new HttpField(STATIC_TABLE[i][0],value):new HttpField(header,name,value));
166
167
168 __staticTable[i]=entry;
169
170 if (entry._field.getValue()!=null)
171 __staticFieldMap.put(entry._field,entry);
172
173 if (!added.contains(entry._field.getName()))
174 {
175 added.add(entry._field.getName());
176 __staticNameMap.put(entry._field.getName(),entry);
177 if (__staticNameMap.get(entry._field.getName())==null)
178 throw new IllegalStateException("name trie too small");
179 }
180 }
181
182 for (HttpHeader h : HttpHeader.values())
183 {
184 StaticEntry entry = __staticNameMap.get(h.asString());
185 if (entry!=null)
186 __staticTableByHeader[h.ordinal()]=entry;
187 }
188 }
189
190 private int _maxDynamicTableSizeInBytes;
191 private int _dynamicTableSizeInBytes;
192 private final DynamicTable _dynamicTable;
193 private final Map<HttpField,Entry> _fieldMap = new HashMap<>();
194 private final Map<String,Entry> _nameMap = new HashMap<>();
195
196 HpackContext(int maxDynamicTableSize)
197 {
198 _maxDynamicTableSizeInBytes=maxDynamicTableSize;
199 int guesstimateEntries = 10+maxDynamicTableSize/(32+10+10);
200 _dynamicTable=new DynamicTable(guesstimateEntries,guesstimateEntries+10);
201 if (LOG.isDebugEnabled())
202 LOG.debug(String.format("HdrTbl[%x] created max=%d",hashCode(),maxDynamicTableSize));
203 }
204
205 public void resize(int newMaxDynamicTableSize)
206 {
207 if (LOG.isDebugEnabled())
208 LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d",hashCode(),_maxDynamicTableSizeInBytes,newMaxDynamicTableSize));
209 _maxDynamicTableSizeInBytes=newMaxDynamicTableSize;
210 int guesstimateEntries = 10+newMaxDynamicTableSize/(32+10+10);
211 evict();
212 _dynamicTable.resizeUnsafe(guesstimateEntries);
213 }
214
215 public Entry get(HttpField field)
216 {
217 Entry entry = _fieldMap.get(field);
218 if (entry==null)
219 entry=__staticFieldMap.get(field);
220 return entry;
221 }
222
223 public Entry get(String name)
224 {
225 Entry entry = __staticNameMap.get(name);
226 if (entry!=null)
227 return entry;
228 return _nameMap.get(StringUtil.asciiToLowerCase(name));
229 }
230
231 public Entry get(int index)
232 {
233 if (index<__staticTable.length)
234 return __staticTable[index];
235
236 int d=_dynamicTable.size()-index+__staticTable.length-1;
237
238 if (d>=0)
239 return _dynamicTable.getUnsafe(d);
240 return null;
241 }
242
243 public Entry get(HttpHeader header)
244 {
245 Entry e = __staticTableByHeader[header.ordinal()];
246 if (e==null)
247 return get(header.asString());
248 return e;
249 }
250
251 public static Entry getStatic(HttpHeader header)
252 {
253 return __staticTableByHeader[header.ordinal()];
254 }
255
256 public Entry add(HttpField field)
257 {
258 int slot=_dynamicTable.getNextSlotUnsafe();
259 Entry entry=new Entry(slot,field);
260 int size = entry.getSize();
261 if (size>_maxDynamicTableSizeInBytes)
262 {
263 if (LOG.isDebugEnabled())
264 LOG.debug(String.format("HdrTbl[%x] !added size %d>%d",hashCode(),size,_maxDynamicTableSizeInBytes));
265 return null;
266 }
267 _dynamicTableSizeInBytes+=size;
268 _dynamicTable.addUnsafe(entry);
269 _fieldMap.put(field,entry);
270 _nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry);
271
272 if (LOG.isDebugEnabled())
273 LOG.debug(String.format("HdrTbl[%x] added %s",hashCode(),entry));
274 evict();
275 return entry;
276 }
277
278
279
280
281 public int size()
282 {
283 return _dynamicTable.size();
284 }
285
286
287
288
289 public int getDynamicTableSize()
290 {
291 return _dynamicTableSizeInBytes;
292 }
293
294
295
296
297 public int getMaxDynamicTableSize()
298 {
299 return _maxDynamicTableSizeInBytes;
300 }
301
302 public int index(Entry entry)
303 {
304 if (entry._slot<0)
305 return 0;
306 if (entry.isStatic())
307 return entry._slot;
308
309 return _dynamicTable.index(entry)+__staticTable.length-1;
310 }
311
312 public static int staticIndex(HttpHeader header)
313 {
314 if (header==null)
315 return 0;
316 Entry entry=__staticNameMap.get(header.asString());
317 if (entry==null)
318 return 0;
319 return entry.getSlot();
320 }
321
322 private void evict()
323 {
324 while (_dynamicTableSizeInBytes>_maxDynamicTableSizeInBytes)
325 {
326 Entry entry = _dynamicTable.pollUnsafe();
327 if (LOG.isDebugEnabled())
328 LOG.debug(String.format("HdrTbl[%x] evict %s",hashCode(),entry));
329 _dynamicTableSizeInBytes-=entry.getSize();
330 entry._slot=-1;
331 _fieldMap.remove(entry.getHttpField());
332 String lc=StringUtil.asciiToLowerCase(entry.getHttpField().getName());
333 if (entry==_nameMap.get(lc))
334 _nameMap.remove(lc);
335 }
336 if (LOG.isDebugEnabled())
337 LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes));
338 }
339
340 @Override
341 public String toString()
342 {
343 return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes);
344 }
345
346
347
348
349
350
351 private class DynamicTable extends ArrayQueue<HpackContext.Entry>
352 {
353
354
355
356
357
358 private DynamicTable(int initCapacity, int growBy)
359 {
360 super(initCapacity,growBy);
361 }
362
363
364
365
366
367 @Override
368 protected void resizeUnsafe(int newCapacity)
369 {
370
371 super.resizeUnsafe(newCapacity);
372 for (int s=0;s<_nextSlot;s++)
373 ((Entry)_elements[s])._slot=s;
374 }
375
376
377
378
379
380 @Override
381 public boolean enqueue(Entry e)
382 {
383 return super.enqueue(e);
384 }
385
386
387
388
389
390 @Override
391 public Entry dequeue()
392 {
393 return super.dequeue();
394 }
395
396
397
398
399
400
401 private int index(Entry entry)
402 {
403 return entry._slot>=_nextE?_size-entry._slot+_nextE:_nextSlot-entry._slot;
404 }
405
406 }
407
408
409
410
411
412
413 public static class Entry
414 {
415 final HttpField _field;
416 int _slot;
417
418 Entry()
419 {
420 _slot=0;
421 _field=null;
422 }
423
424 Entry(int index,String name, String value)
425 {
426 _slot=index;
427 _field=new HttpField(name,value);
428 }
429
430 Entry(int slot, HttpField field)
431 {
432 _slot=slot;
433 _field=field;
434 }
435
436 public int getSize()
437 {
438 return 32+_field.getName().length()+_field.getValue().length();
439 }
440
441 public HttpField getHttpField()
442 {
443 return _field;
444 }
445
446 public boolean isStatic()
447 {
448 return false;
449 }
450
451 public byte[] getStaticHuffmanValue()
452 {
453 return null;
454 }
455
456 public int getSlot()
457 {
458 return _slot;
459 }
460
461 public String toString()
462 {
463 return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_slot,_field,hashCode());
464 }
465 }
466
467 public static class StaticEntry extends Entry
468 {
469 private final byte[] _huffmanValue;
470 private final byte _encodedField;
471
472 StaticEntry(int index,HttpField field)
473 {
474 super(index,field);
475 String value = field.getValue();
476 if (value!=null && value.length()>0)
477 {
478 int huffmanLen = Huffman.octetsNeeded(value);
479 int lenLen = NBitInteger.octectsNeeded(7,huffmanLen);
480 _huffmanValue = new byte[1+lenLen+huffmanLen];
481 ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
482
483
484 buffer.put((byte)0x80);
485
486 NBitInteger.encode(buffer,7,huffmanLen);
487
488 Huffman.encode(buffer,value);
489 }
490 else
491 _huffmanValue=null;
492
493 _encodedField=(byte)(0x80|index);
494 }
495
496 @Override
497 public boolean isStatic()
498 {
499 return true;
500 }
501
502 @Override
503 public byte[] getStaticHuffmanValue()
504 {
505 return _huffmanValue;
506 }
507
508 public byte getEncodedField()
509 {
510 return _encodedField;
511 }
512 }
513
514
515 }