Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java')
-rw-r--r--jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java515
1 files changed, 515 insertions, 0 deletions
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java
new file mode 100644
index 0000000000..89fbf00e4a
--- /dev/null
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java
@@ -0,0 +1,515 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** HPACK - Header Compression for HTTP/2
+ * <p>This class maintains the compression context for a single HTTP/2
+ * connection. Specifically it holds the static and dynamic Header Field Tables
+ * and the associated sizes and limits.
+ * </p>
+ * <p>It is compliant with draft 11 of the specification</p>
+ */
+public class HpackContext
+{
+ public static final Logger LOG = Log.getLogger(HpackContext.class);
+
+ public static final String[][] STATIC_TABLE =
+ {
+ {null,null},
+ /* 1 */ {":authority",null},
+ /* 2 */ {":method","GET"},
+ /* 3 */ {":method","POST"},
+ /* 4 */ {":path","/"},
+ /* 5 */ {":path","/index.html"},
+ /* 6 */ {":scheme","http"},
+ /* 7 */ {":scheme","https"},
+ /* 8 */ {":status","200"},
+ /* 9 */ {":status","204"},
+ /* 10 */ {":status","206"},
+ /* 11 */ {":status","304"},
+ /* 12 */ {":status","400"},
+ /* 13 */ {":status","404"},
+ /* 14 */ {":status","500"},
+ /* 15 */ {"accept-charset",null},
+ /* 16 */ {"accept-encoding","gzip, deflate"},
+ /* 17 */ {"accept-language",null},
+ /* 18 */ {"accept-ranges",null},
+ /* 19 */ {"accept",null},
+ /* 20 */ {"access-control-allow-origin",null},
+ /* 21 */ {"age",null},
+ /* 22 */ {"allow",null},
+ /* 23 */ {"authorization",null},
+ /* 24 */ {"cache-control",null},
+ /* 25 */ {"content-disposition",null},
+ /* 26 */ {"content-encoding",null},
+ /* 27 */ {"content-language",null},
+ /* 28 */ {"content-length",null},
+ /* 29 */ {"content-location",null},
+ /* 30 */ {"content-range",null},
+ /* 31 */ {"content-type",null},
+ /* 32 */ {"cookie",null},
+ /* 33 */ {"date",null},
+ /* 34 */ {"etag",null},
+ /* 35 */ {"expect",null},
+ /* 36 */ {"expires",null},
+ /* 37 */ {"from",null},
+ /* 38 */ {"host",null},
+ /* 39 */ {"if-match",null},
+ /* 40 */ {"if-modified-since",null},
+ /* 41 */ {"if-none-match",null},
+ /* 42 */ {"if-range",null},
+ /* 43 */ {"if-unmodified-since",null},
+ /* 44 */ {"last-modified",null},
+ /* 45 */ {"link",null},
+ /* 46 */ {"location",null},
+ /* 47 */ {"max-forwards",null},
+ /* 48 */ {"proxy-authenticate",null},
+ /* 49 */ {"proxy-authorization",null},
+ /* 50 */ {"range",null},
+ /* 51 */ {"referer",null},
+ /* 52 */ {"refresh",null},
+ /* 53 */ {"retry-after",null},
+ /* 54 */ {"server",null},
+ /* 55 */ {"set-cookie",null},
+ /* 56 */ {"strict-transport-security",null},
+ /* 57 */ {"transfer-encoding",null},
+ /* 58 */ {"user-agent",null},
+ /* 59 */ {"vary",null},
+ /* 60 */ {"via",null},
+ /* 61 */ {"www-authenticate",null},
+ };
+
+ private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
+ private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
+ private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
+ private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
+ static
+ {
+ Set<String> added = new HashSet<>();
+ for (int i=1;i<STATIC_TABLE.length;i++)
+ {
+ StaticEntry entry=null;
+
+ String name = STATIC_TABLE[i][0];
+ String value = STATIC_TABLE[i][1];
+ HttpHeader header = HttpHeader.CACHE.get(name);
+ if (header!=null && value!=null)
+ {
+ switch (header)
+ {
+ case C_METHOD:
+ {
+
+ HttpMethod method = HttpMethod.CACHE.get(value);
+ if (method!=null)
+ entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,method));
+ break;
+ }
+
+ case C_SCHEME:
+ {
+
+ HttpScheme scheme = HttpScheme.CACHE.get(value);
+ if (scheme!=null)
+ entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,scheme));
+ break;
+ }
+
+ case C_STATUS:
+ {
+ entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,Integer.valueOf(value)));
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (entry==null)
+ entry=new StaticEntry(i,header==null?new HttpField(STATIC_TABLE[i][0],value):new HttpField(header,name,value));
+
+
+ __staticTable[i]=entry;
+
+ if (entry._field.getValue()!=null)
+ __staticFieldMap.put(entry._field,entry);
+
+ if (!added.contains(entry._field.getName()))
+ {
+ added.add(entry._field.getName());
+ __staticNameMap.put(entry._field.getName(),entry);
+ if (__staticNameMap.get(entry._field.getName())==null)
+ throw new IllegalStateException("name trie too small");
+ }
+ }
+
+ for (HttpHeader h : HttpHeader.values())
+ {
+ StaticEntry entry = __staticNameMap.get(h.asString());
+ if (entry!=null)
+ __staticTableByHeader[h.ordinal()]=entry;
+ }
+ }
+
+ private int _maxDynamicTableSizeInBytes;
+ private int _dynamicTableSizeInBytes;
+ private final DynamicTable _dynamicTable;
+ private final Map<HttpField,Entry> _fieldMap = new HashMap<>();
+ private final Map<String,Entry> _nameMap = new HashMap<>();
+
+ HpackContext(int maxDynamicTableSize)
+ {
+ _maxDynamicTableSizeInBytes=maxDynamicTableSize;
+ int guesstimateEntries = 10+maxDynamicTableSize/(32+10+10);
+ _dynamicTable=new DynamicTable(guesstimateEntries,guesstimateEntries+10);
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] created max=%d",hashCode(),maxDynamicTableSize));
+ }
+
+ public void resize(int newMaxDynamicTableSize)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d",hashCode(),_maxDynamicTableSizeInBytes,newMaxDynamicTableSize));
+ _maxDynamicTableSizeInBytes=newMaxDynamicTableSize;
+ int guesstimateEntries = 10+newMaxDynamicTableSize/(32+10+10);
+ evict();
+ _dynamicTable.resizeUnsafe(guesstimateEntries);
+ }
+
+ public Entry get(HttpField field)
+ {
+ Entry entry = _fieldMap.get(field);
+ if (entry==null)
+ entry=__staticFieldMap.get(field);
+ return entry;
+ }
+
+ public Entry get(String name)
+ {
+ Entry entry = __staticNameMap.get(name);
+ if (entry!=null)
+ return entry;
+ return _nameMap.get(StringUtil.asciiToLowerCase(name));
+ }
+
+ public Entry get(int index)
+ {
+ if (index<__staticTable.length)
+ return __staticTable[index];
+
+ int d=_dynamicTable.size()-index+__staticTable.length-1;
+
+ if (d>=0)
+ return _dynamicTable.getUnsafe(d);
+ return null;
+ }
+
+ public Entry get(HttpHeader header)
+ {
+ Entry e = __staticTableByHeader[header.ordinal()];
+ if (e==null)
+ return get(header.asString());
+ return e;
+ }
+
+ public static Entry getStatic(HttpHeader header)
+ {
+ return __staticTableByHeader[header.ordinal()];
+ }
+
+ public Entry add(HttpField field)
+ {
+ int slot=_dynamicTable.getNextSlotUnsafe();
+ Entry entry=new Entry(slot,field);
+ int size = entry.getSize();
+ if (size>_maxDynamicTableSizeInBytes)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] !added size %d>%d",hashCode(),size,_maxDynamicTableSizeInBytes));
+ return null;
+ }
+ _dynamicTableSizeInBytes+=size;
+ _dynamicTable.addUnsafe(entry);
+ _fieldMap.put(field,entry);
+ _nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] added %s",hashCode(),entry));
+ evict();
+ return entry;
+ }
+
+ /**
+ * @return Current dynamic table size in entries
+ */
+ public int size()
+ {
+ return _dynamicTable.size();
+ }
+
+ /**
+ * @return Current Dynamic table size in Octets
+ */
+ public int getDynamicTableSize()
+ {
+ return _dynamicTableSizeInBytes;
+ }
+
+ /**
+ * @return Max Dynamic table size in Octets
+ */
+ public int getMaxDynamicTableSize()
+ {
+ return _maxDynamicTableSizeInBytes;
+ }
+
+ public int index(Entry entry)
+ {
+ if (entry._slot<0)
+ return 0;
+ if (entry.isStatic())
+ return entry._slot;
+
+ return _dynamicTable.index(entry)+__staticTable.length-1;
+ }
+
+ public static int staticIndex(HttpHeader header)
+ {
+ if (header==null)
+ return 0;
+ Entry entry=__staticNameMap.get(header.asString());
+ if (entry==null)
+ return 0;
+ return entry.getSlot();
+ }
+
+ private void evict()
+ {
+ while (_dynamicTableSizeInBytes>_maxDynamicTableSizeInBytes)
+ {
+ Entry entry = _dynamicTable.pollUnsafe();
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] evict %s",hashCode(),entry));
+ _dynamicTableSizeInBytes-=entry.getSize();
+ entry._slot=-1;
+ _fieldMap.remove(entry.getHttpField());
+ String lc=StringUtil.asciiToLowerCase(entry.getHttpField().getName());
+ if (entry==_nameMap.get(lc))
+ _nameMap.remove(lc);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes));
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ private class DynamicTable extends ArrayQueue<HpackContext.Entry>
+ {
+ /* ------------------------------------------------------------ */
+ /**
+ * @param initCapacity
+ * @param growBy
+ */
+ private DynamicTable(int initCapacity, int growBy)
+ {
+ super(initCapacity,growBy);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#growUnsafe()
+ */
+ @Override
+ protected void resizeUnsafe(int newCapacity)
+ {
+ // Relay on super.growUnsafe to pack all entries 0 to _nextSlot
+ super.resizeUnsafe(newCapacity);
+ for (int s=0;s<_nextSlot;s++)
+ ((Entry)_elements[s])._slot=s;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#enqueue(java.lang.Object)
+ */
+ @Override
+ public boolean enqueue(Entry e)
+ {
+ return super.enqueue(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#dequeue()
+ */
+ @Override
+ public Entry dequeue()
+ {
+ return super.dequeue();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param entry
+ * @return
+ */
+ private int index(Entry entry)
+ {
+ return entry._slot>=_nextE?_size-entry._slot+_nextE:_nextSlot-entry._slot;
+ }
+
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Entry
+ {
+ final HttpField _field;
+ int _slot;
+
+ Entry()
+ {
+ _slot=0;
+ _field=null;
+ }
+
+ Entry(int index,String name, String value)
+ {
+ _slot=index;
+ _field=new HttpField(name,value);
+ }
+
+ Entry(int slot, HttpField field)
+ {
+ _slot=slot;
+ _field=field;
+ }
+
+ public int getSize()
+ {
+ return 32+_field.getName().length()+_field.getValue().length();
+ }
+
+ public HttpField getHttpField()
+ {
+ return _field;
+ }
+
+ public boolean isStatic()
+ {
+ return false;
+ }
+
+ public byte[] getStaticHuffmanValue()
+ {
+ return null;
+ }
+
+ public int getSlot()
+ {
+ return _slot;
+ }
+
+ public String toString()
+ {
+ return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_slot,_field,hashCode());
+ }
+ }
+
+ public static class StaticEntry extends Entry
+ {
+ private final byte[] _huffmanValue;
+ private final byte _encodedField;
+
+ StaticEntry(int index,HttpField field)
+ {
+ super(index,field);
+ String value = field.getValue();
+ if (value!=null && value.length()>0)
+ {
+ int huffmanLen = Huffman.octetsNeeded(value);
+ int lenLen = NBitInteger.octectsNeeded(7,huffmanLen);
+ _huffmanValue = new byte[1+lenLen+huffmanLen];
+ ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
+
+ // Indicate Huffman
+ buffer.put((byte)0x80);
+ // Add huffman length
+ NBitInteger.encode(buffer,7,huffmanLen);
+ // Encode value
+ Huffman.encode(buffer,value);
+ }
+ else
+ _huffmanValue=null;
+
+ _encodedField=(byte)(0x80|index);
+ }
+
+ @Override
+ public boolean isStatic()
+ {
+ return true;
+ }
+
+ @Override
+ public byte[] getStaticHuffmanValue()
+ {
+ return _huffmanValue;
+ }
+
+ public byte getEncodedField()
+ {
+ return _encodedField;
+ }
+ }
+
+
+}

Back to the top