• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.squareup.okhttp.internal.spdy;
2 
3 import com.squareup.okhttp.internal.BitArray;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collections;
8 import java.util.LinkedHashMap;
9 import java.util.List;
10 import java.util.Map;
11 import okio.BufferedSource;
12 import okio.ByteString;
13 import okio.OkBuffer;
14 import okio.Okio;
15 import okio.Source;
16 
17 /**
18  * Read and write HPACK v05.
19  *
20  * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05
21  *
22  * This implementation uses an array for the header table with a bitset for
23  * references.  Dynamic entries are added to the array, starting in the last
24  * position moving forward.  When the array fills, it is doubled.
25  */
26 final class HpackDraft05 {
27   private static final int PREFIX_6_BITS = 0x3f;
28   private static final int PREFIX_7_BITS = 0x7f;
29 
30   private static final Header[] STATIC_HEADER_TABLE = new Header[] {
31       new Header(Header.TARGET_AUTHORITY, ""),
32       new Header(Header.TARGET_METHOD, "GET"),
33       new Header(Header.TARGET_METHOD, "POST"),
34       new Header(Header.TARGET_PATH, "/"),
35       new Header(Header.TARGET_PATH, "/index.html"),
36       new Header(Header.TARGET_SCHEME, "http"),
37       new Header(Header.TARGET_SCHEME, "https"),
38       new Header(Header.RESPONSE_STATUS, "200"),
39       new Header(Header.RESPONSE_STATUS, "500"),
40       new Header(Header.RESPONSE_STATUS, "404"),
41       new Header(Header.RESPONSE_STATUS, "403"),
42       new Header(Header.RESPONSE_STATUS, "400"),
43       new Header(Header.RESPONSE_STATUS, "401"),
44       new Header("accept-charset", ""),
45       new Header("accept-encoding", ""),
46       new Header("accept-language", ""),
47       new Header("accept-ranges", ""),
48       new Header("accept", ""),
49       new Header("access-control-allow-origin", ""),
50       new Header("age", ""),
51       new Header("allow", ""),
52       new Header("authorization", ""),
53       new Header("cache-control", ""),
54       new Header("content-disposition", ""),
55       new Header("content-encoding", ""),
56       new Header("content-language", ""),
57       new Header("content-length", ""),
58       new Header("content-location", ""),
59       new Header("content-range", ""),
60       new Header("content-type", ""),
61       new Header("cookie", ""),
62       new Header("date", ""),
63       new Header("etag", ""),
64       new Header("expect", ""),
65       new Header("expires", ""),
66       new Header("from", ""),
67       new Header("host", ""),
68       new Header("if-match", ""),
69       new Header("if-modified-since", ""),
70       new Header("if-none-match", ""),
71       new Header("if-range", ""),
72       new Header("if-unmodified-since", ""),
73       new Header("last-modified", ""),
74       new Header("link", ""),
75       new Header("location", ""),
76       new Header("max-forwards", ""),
77       new Header("proxy-authenticate", ""),
78       new Header("proxy-authorization", ""),
79       new Header("range", ""),
80       new Header("referer", ""),
81       new Header("refresh", ""),
82       new Header("retry-after", ""),
83       new Header("server", ""),
84       new Header("set-cookie", ""),
85       new Header("strict-transport-security", ""),
86       new Header("transfer-encoding", ""),
87       new Header("user-agent", ""),
88       new Header("vary", ""),
89       new Header("via", ""),
90       new Header("www-authenticate", "")
91   };
92 
HpackDraft05()93   private HpackDraft05() {
94   }
95 
96   // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2
97   static final class Reader {
98     private final Huffman.Codec huffmanCodec;
99 
100     private final List<Header> emittedHeaders = new ArrayList<Header>();
101     private final BufferedSource source;
102     private int maxHeaderTableByteCount;
103 
104     // Visible for testing.
105     Header[] headerTable = new Header[8];
106     // Array is populated back to front, so new entries always have lowest index.
107     int nextHeaderIndex = headerTable.length - 1;
108     int headerCount = 0;
109 
110     /**
111      * Set bit positions indicate {@code headerTable[pos]} should be emitted.
112      */
113     // Using a BitArray as it has left-shift operator.
114     BitArray referencedHeaders = new BitArray.FixedCapacity();
115 
116     /**
117      * Set bit positions indicate {@code headerTable[pos]} was already emitted.
118      */
119     BitArray emittedReferencedHeaders = new BitArray.FixedCapacity();
120     int headerTableByteCount = 0;
121 
Reader(boolean client, int maxHeaderTableByteCount, Source source)122     Reader(boolean client, int maxHeaderTableByteCount, Source source) {
123       this.huffmanCodec = client ? Huffman.Codec.RESPONSE : Huffman.Codec.REQUEST;
124       this.maxHeaderTableByteCount = maxHeaderTableByteCount;
125       this.source = Okio.buffer(source);
126     }
127 
maxHeaderTableByteCount()128     int maxHeaderTableByteCount() {
129       return maxHeaderTableByteCount;
130     }
131 
132     /**
133      * Called by the reader when the peer sent a new header table size setting.
134      * <p>
135      * Evicts entries or clears the table as needed.
136      */
maxHeaderTableByteCount(int newMaxHeaderTableByteCount)137     void maxHeaderTableByteCount(int newMaxHeaderTableByteCount) {
138       this.maxHeaderTableByteCount = newMaxHeaderTableByteCount;
139       if (maxHeaderTableByteCount < headerTableByteCount) {
140         if (maxHeaderTableByteCount == 0) {
141           clearHeaderTable();
142         } else {
143           evictToRecoverBytes(headerTableByteCount - maxHeaderTableByteCount);
144         }
145       }
146     }
147 
clearHeaderTable()148     private void clearHeaderTable() {
149       clearReferenceSet();
150       Arrays.fill(headerTable, null);
151       nextHeaderIndex = headerTable.length - 1;
152       headerCount = 0;
153       headerTableByteCount = 0;
154     }
155 
156     /** Returns the count of entries evicted. */
evictToRecoverBytes(int bytesToRecover)157     private int evictToRecoverBytes(int bytesToRecover) {
158       int entriesToEvict = 0;
159       if (bytesToRecover > 0) {
160         // determine how many headers need to be evicted.
161         for (int j = headerTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
162           bytesToRecover -= headerTable[j].hpackSize;
163           headerTableByteCount -= headerTable[j].hpackSize;
164           headerCount--;
165           entriesToEvict++;
166         }
167         referencedHeaders.shiftLeft(entriesToEvict);
168         emittedReferencedHeaders.shiftLeft(entriesToEvict);
169         System.arraycopy(headerTable, nextHeaderIndex + 1, headerTable,
170             nextHeaderIndex + 1 + entriesToEvict, headerCount);
171         nextHeaderIndex += entriesToEvict;
172       }
173       return entriesToEvict;
174     }
175 
176     /**
177      * Read {@code byteCount} bytes of headers from the source stream into the
178      * set of emitted headers.
179      */
readHeaders()180     void readHeaders() throws IOException {
181       while (!source.exhausted()) {
182         int b = source.readByte() & 0xff;
183         if (b == 0x80) { // 10000000
184           clearReferenceSet();
185         } else if ((b & 0x80) == 0x80) { // 1NNNNNNN
186           int index = readInt(b, PREFIX_7_BITS);
187           readIndexedHeader(index - 1);
188         } else { // 0NNNNNNN
189           if (b == 0x40) { // 01000000
190             readLiteralHeaderWithoutIndexingNewName();
191           } else if ((b & 0x40) == 0x40) {  // 01NNNNNN
192             int index = readInt(b, PREFIX_6_BITS);
193             readLiteralHeaderWithoutIndexingIndexedName(index - 1);
194           } else if (b == 0) { // 00000000
195             readLiteralHeaderWithIncrementalIndexingNewName();
196           } else if ((b & 0xc0) == 0) { // 00NNNNNN
197             int index = readInt(b, PREFIX_6_BITS);
198             readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
199           } else {
200             // TODO: we should throw something that we can coerce to a PROTOCOL_ERROR
201             throw new AssertionError("unhandled byte: " + Integer.toBinaryString(b));
202           }
203         }
204       }
205     }
206 
clearReferenceSet()207     private void clearReferenceSet() {
208       referencedHeaders.clear();
209       emittedReferencedHeaders.clear();
210     }
211 
emitReferenceSet()212     void emitReferenceSet() {
213       for (int i = headerTable.length - 1; i != nextHeaderIndex; --i) {
214         if (referencedHeaders.get(i) && !emittedReferencedHeaders.get(i)) {
215           emittedHeaders.add(headerTable[i]);
216         }
217       }
218     }
219 
220     /**
221      * Returns all headers emitted since they were last cleared, then clears the
222      * emitted headers.
223      */
getAndReset()224     List<Header> getAndReset() {
225       List<Header> result = new ArrayList<Header>(emittedHeaders);
226       emittedHeaders.clear();
227       emittedReferencedHeaders.clear();
228       return result;
229     }
230 
readIndexedHeader(int index)231     private void readIndexedHeader(int index) {
232       if (isStaticHeader(index)) {
233         Header staticEntry = STATIC_HEADER_TABLE[index - headerCount];
234         if (maxHeaderTableByteCount == 0) {
235           emittedHeaders.add(staticEntry);
236         } else {
237           insertIntoHeaderTable(-1, staticEntry);
238         }
239       } else {
240         int headerTableIndex = headerTableIndex(index);
241         if (!referencedHeaders.get(headerTableIndex)) { // When re-referencing, emit immediately.
242           emittedHeaders.add(headerTable[headerTableIndex]);
243           emittedReferencedHeaders.set(headerTableIndex);
244         }
245         referencedHeaders.toggle(headerTableIndex);
246       }
247     }
248 
249     // referencedHeaders is relative to nextHeaderIndex + 1.
headerTableIndex(int index)250     private int headerTableIndex(int index) {
251       return nextHeaderIndex + 1 + index;
252     }
253 
readLiteralHeaderWithoutIndexingIndexedName(int index)254     private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
255       ByteString name = getName(index);
256       ByteString value = readByteString(false);
257       emittedHeaders.add(new Header(name, value));
258     }
259 
readLiteralHeaderWithoutIndexingNewName()260     private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
261       ByteString name = readByteString(true);
262       ByteString value = readByteString(false);
263       emittedHeaders.add(new Header(name, value));
264     }
265 
readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)266     private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
267         throws IOException {
268       ByteString name = getName(nameIndex);
269       ByteString value = readByteString(false);
270       insertIntoHeaderTable(-1, new Header(name, value));
271     }
272 
readLiteralHeaderWithIncrementalIndexingNewName()273     private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
274       ByteString name = readByteString(true);
275       ByteString value = readByteString(false);
276       insertIntoHeaderTable(-1, new Header(name, value));
277     }
278 
getName(int index)279     private ByteString getName(int index) {
280       if (isStaticHeader(index)) {
281         return STATIC_HEADER_TABLE[index - headerCount].name;
282       } else {
283         return headerTable[headerTableIndex(index)].name;
284       }
285     }
286 
isStaticHeader(int index)287     private boolean isStaticHeader(int index) {
288       return index >= headerCount;
289     }
290 
291     /** index == -1 when new. */
insertIntoHeaderTable(int index, Header entry)292     private void insertIntoHeaderTable(int index, Header entry) {
293       int delta = entry.hpackSize;
294       if (index != -1) { // Index -1 == new header.
295         delta -= headerTable[headerTableIndex(index)].hpackSize;
296       }
297 
298       // if the new or replacement header is too big, drop all entries.
299       if (delta > maxHeaderTableByteCount) {
300         clearHeaderTable();
301         // emit the large header to the callback.
302         emittedHeaders.add(entry);
303         return;
304       }
305 
306       // Evict headers to the required length.
307       int bytesToRecover = (headerTableByteCount + delta) - maxHeaderTableByteCount;
308       int entriesEvicted = evictToRecoverBytes(bytesToRecover);
309 
310       if (index == -1) { // Adding a value to the header table.
311         if (headerCount + 1 > headerTable.length) { // Need to grow the header table.
312           Header[] doubled = new Header[headerTable.length * 2];
313           System.arraycopy(headerTable, 0, doubled, headerTable.length, headerTable.length);
314           if (doubled.length == 64) {
315             referencedHeaders = ((BitArray.FixedCapacity) referencedHeaders).toVariableCapacity();
316             emittedReferencedHeaders =
317                 ((BitArray.FixedCapacity) emittedReferencedHeaders).toVariableCapacity();
318           }
319           referencedHeaders.shiftLeft(headerTable.length);
320           emittedReferencedHeaders.shiftLeft(headerTable.length);
321           nextHeaderIndex = headerTable.length - 1;
322           headerTable = doubled;
323         }
324         index = nextHeaderIndex--;
325         referencedHeaders.set(index);
326         headerTable[index] = entry;
327         headerCount++;
328       } else { // Replace value at same position.
329         index += headerTableIndex(index) + entriesEvicted;
330         referencedHeaders.set(index);
331         headerTable[index] = entry;
332       }
333       headerTableByteCount += delta;
334     }
335 
readByte()336     private int readByte() throws IOException {
337       return source.readByte() & 0xff;
338     }
339 
readInt(int firstByte, int prefixMask)340     int readInt(int firstByte, int prefixMask) throws IOException {
341       int prefix = firstByte & prefixMask;
342       if (prefix < prefixMask) {
343         return prefix; // This was a single byte value.
344       }
345 
346       // This is a multibyte value. Read 7 bits at a time.
347       int result = prefixMask;
348       int shift = 0;
349       while (true) {
350         int b = readByte();
351         if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
352           result += (b & 0x7f) << shift;
353           shift += 7;
354         } else {
355           result += b << shift; // Last byte.
356           break;
357         }
358       }
359       return result;
360     }
361 
362     /**
363      * Reads a potentially Huffman encoded string byte string. When
364      * {@code asciiLowercase} is true, bytes will be converted to lowercase.
365      */
readByteString(boolean asciiLowercase)366     ByteString readByteString(boolean asciiLowercase) throws IOException {
367       int firstByte = readByte();
368       boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
369       int length = readInt(firstByte, PREFIX_7_BITS);
370 
371       ByteString byteString = source.readByteString(length);
372 
373       if (huffmanDecode) {
374         byteString = huffmanCodec.decode(byteString); // TODO: streaming Huffman!
375       }
376 
377       if (asciiLowercase) {
378         byteString = byteString.toAsciiLowercase();
379       }
380 
381       return byteString;
382     }
383   }
384 
385   private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex();
386 
nameToFirstIndex()387   private static Map<ByteString, Integer> nameToFirstIndex() {
388     Map<ByteString, Integer> result =
389         new LinkedHashMap<ByteString, Integer>(STATIC_HEADER_TABLE.length);
390     for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
391       if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
392         result.put(STATIC_HEADER_TABLE[i].name, i);
393       }
394     }
395     return Collections.unmodifiableMap(result);
396   }
397 
398   static final class Writer {
399     private final OkBuffer out;
400 
Writer(OkBuffer out)401     Writer(OkBuffer out) {
402       this.out = out;
403     }
404 
writeHeaders(List<Header> headerBlock)405     void writeHeaders(List<Header> headerBlock) throws IOException {
406       // TODO: implement index tracking
407       for (int i = 0, size = headerBlock.size(); i < size; i++) {
408         ByteString name = headerBlock.get(i).name;
409         Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
410         if (staticIndex != null) {
411           // Literal Header Field without Indexing - Indexed Name.
412           writeInt(staticIndex + 1, PREFIX_6_BITS, 0x40);
413           writeByteString(headerBlock.get(i).value);
414         } else {
415           out.writeByte(0x40); // Literal Header without Indexing - New Name.
416           writeByteString(name);
417           writeByteString(headerBlock.get(i).value);
418         }
419       }
420     }
421 
422     // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.1
writeInt(int value, int prefixMask, int bits)423     void writeInt(int value, int prefixMask, int bits) throws IOException {
424       // Write the raw value for a single byte value.
425       if (value < prefixMask) {
426         out.writeByte(bits | value);
427         return;
428       }
429 
430       // Write the mask to start a multibyte value.
431       out.writeByte(bits | prefixMask);
432       value -= prefixMask;
433 
434       // Write 7 bits at a time 'til we're done.
435       while (value >= 0x80) {
436         int b = value & 0x7f;
437         out.writeByte(b | 0x80);
438         value >>>= 7;
439       }
440       out.writeByte(value);
441     }
442 
writeByteString(ByteString data)443     void writeByteString(ByteString data) throws IOException {
444       writeInt(data.size(), PREFIX_7_BITS, 0);
445       out.write(data);
446     }
447   }
448 }
449