mirror of
https://github.com/square/okhttp.git
synced 2026-01-18 20:40:58 +03:00
Update to HTTP/2 Draft 10 and HPACK Draft 6.
This commit is contained in:
@@ -24,9 +24,9 @@ import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HttpOverHttp20Draft09Test extends HttpOverSpdyTest {
|
||||
public class HttpOverHttp20Draft10Test extends HttpOverSpdyTest {
|
||||
|
||||
public HttpOverHttp20Draft09Test() {
|
||||
public HttpOverHttp20Draft10Test() {
|
||||
super(Protocol.HTTP_2);
|
||||
this.hostHeader = ":authority";
|
||||
}
|
||||
@@ -27,24 +27,25 @@ import static com.squareup.okhttp.internal.Util.headerEntries;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class HpackDraft05Test {
|
||||
public class HpackDraft06Test {
|
||||
|
||||
private final Buffer bytesIn = new Buffer();
|
||||
private HpackDraft05.Reader hpackReader;
|
||||
private HpackDraft06.Reader hpackReader;
|
||||
private Buffer bytesOut = new Buffer();
|
||||
private HpackDraft05.Writer hpackWriter;
|
||||
private HpackDraft06.Writer hpackWriter;
|
||||
|
||||
@Before public void reset() {
|
||||
hpackReader = newReader(bytesIn);
|
||||
hpackWriter = new HpackDraft05.Writer(bytesOut);
|
||||
hpackWriter = new HpackDraft06.Writer(bytesOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable-length quantity special cases strings which are longer than 127
|
||||
* bytes. Values such as cookies can be 4KiB, and should be possible to send.
|
||||
*
|
||||
* <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2
|
||||
* <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#section-4.1.2
|
||||
*/
|
||||
@Test public void largeHeaderValue() throws IOException {
|
||||
char[] value = new char[4096];
|
||||
@@ -185,7 +186,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.1
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.1.1
|
||||
*/
|
||||
@Test public void readLiteralHeaderFieldWithIndexing() throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
@@ -239,7 +240,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.2
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.1.2
|
||||
*/
|
||||
@Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException {
|
||||
List<Header> headerBlock = headerEntries(":path", "/sample/path");
|
||||
@@ -263,7 +264,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.3
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.1.3
|
||||
*/
|
||||
@Test public void readIndexedHeaderField() throws IOException {
|
||||
bytesIn.writeByte(0x82); // == Indexed - Add ==
|
||||
@@ -282,8 +283,69 @@ public class HpackDraft05Test {
|
||||
assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset());
|
||||
}
|
||||
|
||||
// Example taken from twitter/hpack DecoderTest.testIllegalIndex
|
||||
@Test public void readIndexedHeaderFieldTooLargeIndex() throws IOException {
|
||||
bytesIn.writeByte(0xff); // == Indexed - Add ==
|
||||
bytesIn.writeByte(0x00); // idx = 127
|
||||
|
||||
try {
|
||||
hpackReader.readHeaders();
|
||||
fail();
|
||||
} catch (IOException e) {
|
||||
assertEquals("Header index too large 127", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Example taken from twitter/hpack DecoderTest.testInsidiousIndex
|
||||
@Test public void readIndexedHeaderFieldInsidiousIndex() throws IOException {
|
||||
bytesIn.writeByte(0xff); // == Indexed - Add ==
|
||||
bytesIn.writeByte(0x80); // idx = -2147483521
|
||||
bytesIn.writeByte(0x80);
|
||||
bytesIn.writeByte(0x80);
|
||||
bytesIn.writeByte(0x80);
|
||||
bytesIn.writeByte(0x08);
|
||||
|
||||
try {
|
||||
hpackReader.readHeaders();
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("input must be between 0 and 63: -2147483514", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Example taken from twitter/hpack DecoderTest.testIllegalEncodeContextUpdate
|
||||
@Test public void readHeaderTableStateChangeInvalid() throws IOException {
|
||||
bytesIn.writeByte(0x80); // header table state change
|
||||
bytesIn.writeByte(0x81); // should be 0x80 for empty!
|
||||
|
||||
try {
|
||||
hpackReader.readHeaders();
|
||||
fail();
|
||||
} catch (IOException e) {
|
||||
assertEquals("Invalid header table state change -127", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Example taken from twitter/hpack DecoderTest.testInsidiousMaxHeaderSize
|
||||
@Test public void readHeaderTableStateChangeInsidiousMaxHeaderByteCount() throws IOException {
|
||||
bytesIn.writeByte(0x80); // header table state change
|
||||
bytesIn.writeByte(0x7F); // encoded -1879048193
|
||||
bytesIn.writeByte(0x80);
|
||||
bytesIn.writeByte(0xFF);
|
||||
bytesIn.writeByte(0xFF);
|
||||
bytesIn.writeByte(0xFF);
|
||||
bytesIn.writeByte(0x08);
|
||||
|
||||
try {
|
||||
hpackReader.readHeaders();
|
||||
fail();
|
||||
} catch (IOException e) {
|
||||
assertEquals("Invalid header table byte count -1879048193", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-3.2.1
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#section-3.2.1
|
||||
*/
|
||||
@Test public void toggleIndex() throws IOException {
|
||||
// Static table entries are copied to the top of the reference set.
|
||||
@@ -349,7 +411,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.4
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.1.4
|
||||
*/
|
||||
@Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException {
|
||||
bytesIn.writeByte(0x82); // == Indexed - Add ==
|
||||
@@ -366,7 +428,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.2
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.2
|
||||
*/
|
||||
@Test public void readRequestExamplesWithoutHuffman() throws IOException {
|
||||
Buffer out = firstRequestWithoutHuffman();
|
||||
@@ -494,6 +556,7 @@ public class HpackDraft05Test {
|
||||
Buffer out = new Buffer();
|
||||
|
||||
out.writeByte(0x80); // == Empty reference set ==
|
||||
out.writeByte(0x80); // idx = 0, flag = 1
|
||||
out.writeByte(0x85); // == Indexed - Add ==
|
||||
// idx = 5 -> :method: GET
|
||||
out.writeByte(0x8c); // == Indexed - Add ==
|
||||
@@ -568,7 +631,7 @@ public class HpackDraft05Test {
|
||||
}
|
||||
|
||||
/**
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.3
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-D.3
|
||||
*/
|
||||
@Test public void readRequestExamplesWithHuffman() throws IOException {
|
||||
Buffer out = firstRequestWithHuffman();
|
||||
@@ -705,6 +768,7 @@ public class HpackDraft05Test {
|
||||
Buffer out = new Buffer();
|
||||
|
||||
out.writeByte(0x80); // == Empty reference set ==
|
||||
out.writeByte(0x80); // idx = 0, flag = 1
|
||||
out.writeByte(0x85); // == Indexed - Add ==
|
||||
// idx = 5 -> :method: GET
|
||||
out.writeByte(0x8c); // == Indexed - Add ==
|
||||
@@ -848,8 +912,8 @@ public class HpackDraft05Test {
|
||||
assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false));
|
||||
}
|
||||
|
||||
private HpackDraft05.Reader newReader(Buffer source) {
|
||||
return new HpackDraft05.Reader(false, 4096, source);
|
||||
private HpackDraft06.Reader newReader(Buffer source) {
|
||||
return new HpackDraft06.Reader(4096, source);
|
||||
}
|
||||
|
||||
private Buffer byteStream(int... bytes) {
|
||||
@@ -1,515 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.headerEntries;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class Http20Draft09Test {
|
||||
static final int expectedStreamId = 15;
|
||||
|
||||
@Test public void unknownFrameTypeIgnored() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
frame.writeShort(4); // has a 4-byte field
|
||||
frame.writeByte(99); // type 99
|
||||
frame.writeByte(0); // no flags
|
||||
frame.writeInt(expectedStreamId);
|
||||
frame.writeInt(111111111); // custom data
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the unknown frame.
|
||||
fr.nextFrame(new BaseTestHandler());
|
||||
}
|
||||
|
||||
@Test public void onlyOneLiteralHeadersFrame() throws IOException {
|
||||
final List<Header> sentHeaders = headerEntries("name", "value");
|
||||
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
// Write the headers frame, specifying no more frames are expected.
|
||||
{
|
||||
Buffer headerBytes = literalHeaders(sentHeaders);
|
||||
frame.writeShort((int) headerBytes.size());
|
||||
frame.writeByte(Http20Draft09.TYPE_HEADERS);
|
||||
frame.writeByte(Http20Draft09.FLAG_END_HEADERS | Http20Draft09.FLAG_END_STREAM);
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.write(headerBytes, headerBytes.size());
|
||||
}
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the headers frame.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
|
||||
@Override
|
||||
public void headers(boolean outFinished, boolean inFinished, int streamId,
|
||||
int associatedStreamId, int priority, List<Header> headerBlock,
|
||||
HeadersMode headersMode) {
|
||||
assertFalse(outFinished);
|
||||
assertTrue(inFinished);
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(-1, associatedStreamId);
|
||||
assertEquals(-1, priority);
|
||||
assertEquals(sentHeaders, headerBlock);
|
||||
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void headersWithPriority() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final List<Header> sentHeaders = headerEntries("name", "value");
|
||||
|
||||
{ // Write the headers frame, specifying priority flag and value.
|
||||
Buffer headerBytes = literalHeaders(sentHeaders);
|
||||
frame.writeShort((int) (headerBytes.size() + 4));
|
||||
frame.writeByte(Http20Draft09.TYPE_HEADERS);
|
||||
frame.writeByte(Http20Draft09.FLAG_END_HEADERS | Http20Draft09.FLAG_PRIORITY);
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.writeInt(0); // Highest priority is 0.
|
||||
frame.write(headerBytes, headerBytes.size());
|
||||
}
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the headers frame.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
|
||||
@Override
|
||||
public void headers(boolean outFinished, boolean inFinished, int streamId,
|
||||
int associatedStreamId, int priority, List<Header> nameValueBlock,
|
||||
HeadersMode headersMode) {
|
||||
assertFalse(outFinished);
|
||||
assertFalse(inFinished);
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(-1, associatedStreamId);
|
||||
assertEquals(0, priority);
|
||||
assertEquals(sentHeaders, nameValueBlock);
|
||||
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Headers are compressed, then framed. */
|
||||
@Test public void headersFrameThenContinuation() throws IOException {
|
||||
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
// Decoding the first header will cross frame boundaries.
|
||||
Buffer headerBlock = literalHeaders(headerEntries("foo", "barrr", "baz", "qux"));
|
||||
{ // Write the first headers frame.
|
||||
frame.writeShort((int) (headerBlock.size() / 2));
|
||||
frame.writeByte(Http20Draft09.TYPE_HEADERS);
|
||||
frame.writeByte(0); // no flags
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.write(headerBlock, headerBlock.size() / 2);
|
||||
}
|
||||
|
||||
{ // Write the continuation frame, specifying no more frames are expected.
|
||||
frame.writeShort((int) headerBlock.size());
|
||||
frame.writeByte(Http20Draft09.TYPE_CONTINUATION);
|
||||
frame.writeByte(Http20Draft09.FLAG_END_HEADERS);
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.write(headerBlock, headerBlock.size());
|
||||
}
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Reading the above frames should result in a concatenated headerBlock.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
|
||||
@Override
|
||||
public void headers(boolean outFinished, boolean inFinished, int streamId,
|
||||
int associatedStreamId, int priority, List<Header> headerBlock,
|
||||
HeadersMode headersMode) {
|
||||
assertFalse(outFinished);
|
||||
assertFalse(inFinished);
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(-1, associatedStreamId);
|
||||
assertEquals(-1, priority);
|
||||
assertEquals(headerEntries("foo", "barrr", "baz", "qux"), headerBlock);
|
||||
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void pushPromise() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final int expectedPromisedStreamId = 11;
|
||||
|
||||
final List<Header> pushPromise = Arrays.asList(
|
||||
new Header(Header.TARGET_METHOD, "GET"),
|
||||
new Header(Header.TARGET_SCHEME, "https"),
|
||||
new Header(Header.TARGET_AUTHORITY, "squareup.com"),
|
||||
new Header(Header.TARGET_PATH, "/")
|
||||
);
|
||||
|
||||
{ // Write the push promise frame, specifying the associated stream ID.
|
||||
Buffer headerBytes = literalHeaders(pushPromise);
|
||||
frame.writeShort((int) (headerBytes.size() + 4));
|
||||
frame.writeByte(Http20Draft09.TYPE_PUSH_PROMISE);
|
||||
frame.writeByte(Http20Draft09.FLAG_END_PUSH_PROMISE);
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.writeInt(expectedPromisedStreamId & 0x7fffffff);
|
||||
frame.write(headerBytes, headerBytes.size());
|
||||
}
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the headers frame.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
@Override
|
||||
public void pushPromise(int streamId, int promisedStreamId, List<Header> headerBlock) {
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(expectedPromisedStreamId, promisedStreamId);
|
||||
assertEquals(pushPromise, headerBlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Headers are compressed, then framed. */
|
||||
@Test public void pushPromiseThenContinuation() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final int expectedPromisedStreamId = 11;
|
||||
|
||||
final List<Header> pushPromise = Arrays.asList(
|
||||
new Header(Header.TARGET_METHOD, "GET"),
|
||||
new Header(Header.TARGET_SCHEME, "https"),
|
||||
new Header(Header.TARGET_AUTHORITY, "squareup.com"),
|
||||
new Header(Header.TARGET_PATH, "/")
|
||||
);
|
||||
|
||||
// Decoding the first header will cross frame boundaries.
|
||||
Buffer headerBlock = literalHeaders(pushPromise);
|
||||
int firstFrameLength = (int) (headerBlock.size() - 1);
|
||||
{ // Write the first headers frame.
|
||||
frame.writeShort(firstFrameLength + 4);
|
||||
frame.writeByte(Http20Draft09.TYPE_PUSH_PROMISE);
|
||||
frame.writeByte(0); // no flags
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.writeInt(expectedPromisedStreamId & 0x7fffffff);
|
||||
frame.write(headerBlock, firstFrameLength);
|
||||
}
|
||||
|
||||
{ // Write the continuation frame, specifying no more frames are expected.
|
||||
frame.writeShort(1);
|
||||
frame.writeByte(Http20Draft09.TYPE_CONTINUATION);
|
||||
frame.writeByte(Http20Draft09.FLAG_END_HEADERS);
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.write(headerBlock, 1);
|
||||
}
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Reading the above frames should result in a concatenated headerBlock.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
@Override
|
||||
public void pushPromise(int streamId, int promisedStreamId, List<Header> headerBlock) {
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(expectedPromisedStreamId, promisedStreamId);
|
||||
assertEquals(pushPromise, headerBlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void readRstStreamFrame() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
frame.writeShort(4);
|
||||
frame.writeByte(Http20Draft09.TYPE_RST_STREAM);
|
||||
frame.writeByte(0); // No flags
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.writeInt(ErrorCode.COMPRESSION_ERROR.httpCode);
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the reset frame.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
@Override public void rstStream(int streamId, ErrorCode errorCode) {
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(ErrorCode.COMPRESSION_ERROR, errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void readSettingsFrame() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final int reducedTableSizeBytes = 16;
|
||||
|
||||
frame.writeShort(16); // 2 settings * 4 bytes for the code and 4 for the value.
|
||||
frame.writeByte(Http20Draft09.TYPE_SETTINGS);
|
||||
frame.writeByte(0); // No flags
|
||||
frame.writeInt(0 & 0x7fffffff); // Settings are always on the connection stream 0.
|
||||
frame.writeInt(Settings.HEADER_TABLE_SIZE & 0xffffff);
|
||||
frame.writeInt(reducedTableSizeBytes);
|
||||
frame.writeInt(Settings.ENABLE_PUSH & 0xffffff);
|
||||
frame.writeInt(0);
|
||||
|
||||
final Http20Draft09.Reader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
// Consume the settings frame.
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
@Override public void settings(boolean clearPrevious, Settings settings) {
|
||||
assertFalse(clearPrevious); // No clearPrevious in http/2.
|
||||
assertEquals(reducedTableSizeBytes, settings.getHeaderTableSize());
|
||||
assertEquals(false, settings.getEnablePush(true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void pingRoundTrip() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final int expectedPayload1 = 7;
|
||||
final int expectedPayload2 = 8;
|
||||
|
||||
// Compose the expected PING frame.
|
||||
frame.writeShort(8); // length
|
||||
frame.writeByte(Http20Draft09.TYPE_PING);
|
||||
frame.writeByte(Http20Draft09.FLAG_ACK);
|
||||
frame.writeInt(0); // connection-level
|
||||
frame.writeInt(expectedPayload1);
|
||||
frame.writeInt(expectedPayload2);
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertEquals(frame, sendPingFrame(true, expectedPayload1, expectedPayload2));
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the ping frame.
|
||||
@Override public void ping(boolean ack, int payload1, int payload2) {
|
||||
assertTrue(ack);
|
||||
assertEquals(expectedPayload1, payload1);
|
||||
assertEquals(expectedPayload2, payload2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void maxLengthDataFrame() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final byte[] expectedData = new byte[16383];
|
||||
Arrays.fill(expectedData, (byte) 2);
|
||||
|
||||
// Write the data frame.
|
||||
frame.writeShort(expectedData.length);
|
||||
frame.writeByte(Http20Draft09.TYPE_DATA);
|
||||
frame.writeByte(0); // no flags
|
||||
frame.writeInt(expectedStreamId & 0x7fffffff);
|
||||
frame.write(expectedData);
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertEquals(frame, sendDataFrame(new Buffer().write(expectedData)));
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() {
|
||||
@Override public void data(
|
||||
boolean inFinished, int streamId, BufferedSource source, int length) throws IOException {
|
||||
assertFalse(inFinished);
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(16383, length);
|
||||
ByteString data = source.readByteString(length);
|
||||
for (byte b : data.toByteArray()){
|
||||
assertEquals(2, b);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void tooLargeDataFrame() throws IOException {
|
||||
try {
|
||||
sendDataFrame(new Buffer().write(new byte[0x1000000]));
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("FRAME_SIZE_ERROR length > 16383: 16777216", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void windowUpdateRoundTrip() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final long expectedWindowSizeIncrement = 0x7fffffff;
|
||||
|
||||
// Compose the expected window update frame.
|
||||
frame.writeShort(4); // length
|
||||
frame.writeByte(Http20Draft09.TYPE_WINDOW_UPDATE);
|
||||
frame.writeByte(0); // No flags.
|
||||
frame.writeInt(expectedStreamId);
|
||||
frame.writeInt((int) expectedWindowSizeIncrement);
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertEquals(frame, windowUpdate(expectedWindowSizeIncrement));
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the window update frame.
|
||||
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
|
||||
assertEquals(expectedStreamId, streamId);
|
||||
assertEquals(expectedWindowSizeIncrement, windowSizeIncrement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void badWindowSizeIncrement() throws IOException {
|
||||
try {
|
||||
windowUpdate(0);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: 0",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
windowUpdate(0x80000000L);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: 2147483648",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void goAwayWithoutDebugDataRoundTrip() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
|
||||
|
||||
// Compose the expected GOAWAY frame without debug data.
|
||||
frame.writeShort(8); // Without debug data there's only 2 32-bit fields.
|
||||
frame.writeByte(Http20Draft09.TYPE_GOAWAY);
|
||||
frame.writeByte(0); // no flags.
|
||||
frame.writeInt(0); // connection-scope
|
||||
frame.writeInt(expectedStreamId); // last good stream.
|
||||
frame.writeInt(expectedError.httpCode);
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertEquals(frame, sendGoAway(expectedStreamId, expectedError, Util.EMPTY_BYTE_ARRAY));
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
|
||||
@Override public void goAway(
|
||||
int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
assertEquals(expectedStreamId, lastGoodStreamId);
|
||||
assertEquals(expectedError, errorCode);
|
||||
assertEquals(0, debugData.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void goAwayWithDebugDataRoundTrip() throws IOException {
|
||||
Buffer frame = new Buffer();
|
||||
|
||||
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
|
||||
final ByteString expectedData = ByteString.encodeUtf8("abcdefgh");
|
||||
|
||||
// Compose the expected GOAWAY frame without debug data.
|
||||
frame.writeShort(8 + expectedData.size());
|
||||
frame.writeByte(Http20Draft09.TYPE_GOAWAY);
|
||||
frame.writeByte(0); // no flags.
|
||||
frame.writeInt(0); // connection-scope
|
||||
frame.writeInt(0); // never read any stream!
|
||||
frame.writeInt(expectedError.httpCode);
|
||||
frame.write(expectedData.toByteArray());
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertEquals(frame, sendGoAway(0, expectedError, expectedData.toByteArray()));
|
||||
|
||||
FrameReader fr = new Http20Draft09.Reader(frame, 4096, false);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
|
||||
@Override public void goAway(
|
||||
int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
assertEquals(0, lastGoodStreamId);
|
||||
assertEquals(expectedError, errorCode);
|
||||
assertEquals(expectedData, debugData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void frameSizeError() throws IOException {
|
||||
Http20Draft09.Writer writer = new Http20Draft09.Writer(new Buffer(), true);
|
||||
|
||||
try {
|
||||
writer.frameHeader(16384, Http20Draft09.TYPE_DATA, Http20Draft09.FLAG_NONE, 0);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("FRAME_SIZE_ERROR length > 16383: 16384", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void streamIdHasReservedBit() throws IOException {
|
||||
Http20Draft09.Writer writer = new Http20Draft09.Writer(new Buffer(), true);
|
||||
|
||||
try {
|
||||
int streamId = 3;
|
||||
streamId |= 1L << 31; // set reserved bit
|
||||
writer.frameHeader(16383, Http20Draft09.TYPE_DATA, Http20Draft09.FLAG_NONE, streamId);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("reserved bit set: -2147483645", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer literalHeaders(List<Header> sentHeaders) throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
new HpackDraft05.Writer(out).writeHeaders(sentHeaders);
|
||||
return out;
|
||||
}
|
||||
|
||||
private Buffer sendPingFrame(boolean ack, int payload1, int payload2) throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
new Http20Draft09.Writer(out, true).ping(ack, payload1, payload2);
|
||||
return out;
|
||||
}
|
||||
|
||||
private Buffer sendGoAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
|
||||
throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
new Http20Draft09.Writer(out, true).goAway(lastGoodStreamId, errorCode, debugData);
|
||||
return out;
|
||||
}
|
||||
|
||||
private Buffer sendDataFrame(Buffer data) throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
new Http20Draft09.Writer(out, true).dataFrame(expectedStreamId, Http20Draft09.FLAG_NONE, data,
|
||||
(int) data.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
private Buffer windowUpdate(long windowSizeIncrement) throws IOException {
|
||||
Buffer out = new Buffer();
|
||||
new Http20Draft09.Writer(out, true).windowUpdate(expectedStreamId, windowSizeIncrement);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,18 +43,13 @@ public class HuffmanTest {
|
||||
}
|
||||
|
||||
private void assertRoundTrip(byte[] buf) throws IOException {
|
||||
assertRoundTrip(Huffman.Codec.REQUEST, buf);
|
||||
assertRoundTrip(Huffman.Codec.RESPONSE, buf);
|
||||
}
|
||||
|
||||
private static void assertRoundTrip(Huffman.Codec codec, byte[] buf) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
|
||||
codec.encode(buf, dos);
|
||||
assertEquals(baos.size(), codec.encodedLength(buf));
|
||||
Huffman.get().encode(buf, dos);
|
||||
assertEquals(baos.size(), Huffman.get().encodedLength(buf));
|
||||
|
||||
byte[] decodedBytes = codec.decode(baos.toByteArray());
|
||||
byte[] decodedBytes = Huffman.get().decode(baos.toByteArray());
|
||||
assertTrue(Arrays.equals(buf, decodedBytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ public final class MockSpdyPeer implements Closeable {
|
||||
|
||||
@Override
|
||||
public void pushPromise(int streamId, int associatedStreamId, List<Header> headerBlock) {
|
||||
this.type = Http20Draft09.TYPE_PUSH_PROMISE;
|
||||
this.type = Http20Draft10.TYPE_PUSH_PROMISE;
|
||||
this.streamId = streamId;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.headerBlock = headerBlock;
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class SettingsTest {
|
||||
@Test public void setFields() {
|
||||
Settings settings = new Settings();
|
||||
|
||||
// WARNING: clash on flags between spdy/3 and http/2!
|
||||
// WARNING: clash on flags between spdy/3 and HTTP/2!
|
||||
assertEquals(-3, settings.getUploadBandwidth(-3));
|
||||
assertEquals(-1, settings.getHeaderTableSize());
|
||||
settings.set(UPLOAD_BANDWIDTH, 0, 42);
|
||||
@@ -45,7 +45,7 @@ public final class SettingsTest {
|
||||
settings.set(Settings.HEADER_TABLE_SIZE, 0, 8096);
|
||||
assertEquals(8096, settings.getHeaderTableSize());
|
||||
|
||||
// WARNING: clash on flags between spdy/3 and http/2!
|
||||
// WARNING: clash on flags between spdy/3 and HTTP/2!
|
||||
assertEquals(-3, settings.getDownloadBandwidth(-3));
|
||||
assertEquals(true, settings.getEnablePush(true));
|
||||
settings.set(DOWNLOAD_BANDWIDTH, 0, 53);
|
||||
|
||||
@@ -55,7 +55,7 @@ import static org.junit.Assert.fail;
|
||||
|
||||
public final class SpdyConnectionTest {
|
||||
private static final Variant SPDY3 = new Spdy3();
|
||||
private static final Variant HTTP_20_DRAFT_09 = new Http20Draft09();
|
||||
private static final Variant HTTP_20_DRAFT_09 = new Http20Draft10();
|
||||
private final MockSpdyPeer peer = new MockSpdyPeer();
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
@@ -336,7 +336,7 @@ public final class SpdyConnectionTest {
|
||||
// verify the peer's settings were read and applied.
|
||||
synchronized (connection) {
|
||||
assertEquals(0, connection.peerSettings.getHeaderTableSize());
|
||||
Http20Draft09.Reader frameReader = (Http20Draft09.Reader) connection.frameReader;
|
||||
Http20Draft10.Reader frameReader = (Http20Draft10.Reader) connection.frameReader;
|
||||
assertEquals(0, frameReader.hpackReader.maxHeaderTableByteCount());
|
||||
// TODO: when supported, check the frameWriter's compression table is unaffected.
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable {
|
||||
*
|
||||
* <p><strong>This is an evolving set.</strong> Future releases may drop
|
||||
* support for transitional protocols (like spdy/3.1), in favor of their
|
||||
* successors (spdy/4 or http/2.0). The http/1.1 transport will never be
|
||||
* successors (spdy/4 or hTTP/2). The http/1.1 transport will never be
|
||||
* dropped.
|
||||
*
|
||||
* <p>If multiple protocols are specified, <a
|
||||
|
||||
@@ -34,7 +34,7 @@ import okio.ByteString;
|
||||
* indicate how HTTP messages are framed.
|
||||
*/
|
||||
public enum Protocol {
|
||||
HTTP_2("HTTP-draft-09/2.0", true),
|
||||
HTTP_2("h2-10", true),
|
||||
SPDY_3("spdy/3.1", true),
|
||||
HTTP_11("http/1.1", false);
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class SpdyTransport implements Transport {
|
||||
result.add(new Header(VERSION, version));
|
||||
result.add(new Header(TARGET_HOST, host));
|
||||
} else if (Protocol.HTTP_2 == protocol) {
|
||||
result.add(new Header(TARGET_AUTHORITY, host));
|
||||
result.add(new Header(TARGET_AUTHORITY, host)); // Optional in HTTP/2
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
// TODO: revisit for http/2 draft 9
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-7
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-7
|
||||
public enum ErrorCode {
|
||||
/** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
|
||||
NO_ERROR(0, -1, 0),
|
||||
@@ -34,6 +33,12 @@ public enum ErrorCode {
|
||||
|
||||
COMPRESSION_ERROR(9, -1, -1),
|
||||
|
||||
CONNECT_ERROR(10, -1, -1),
|
||||
|
||||
ENHANCE_YOUR_CALM(11, -1, -1),
|
||||
|
||||
INADEQUATE_SECURITY(12, -1, -1),
|
||||
|
||||
INVALID_CREDENTIALS(-1, 10, -1);
|
||||
|
||||
public final int httpCode;
|
||||
|
||||
@@ -76,7 +76,7 @@ public interface FrameReader extends Closeable {
|
||||
* sending this message. If {@code lastGoodStreamId} is zero, the peer
|
||||
* processed no frames.
|
||||
* @param errorCode reason for closing the connection.
|
||||
* @param debugData only valid for http/2; opaque debug data to send.
|
||||
* @param debugData only valid for HTTP/2; opaque debug data to send.
|
||||
*/
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public interface FrameWriter extends Closeable {
|
||||
* @param lastGoodStreamId the last stream ID processed, or zero if no
|
||||
* streams were processed.
|
||||
* @param errorCode reason for closing the connection.
|
||||
* @param debugData only valid for http/2; opaque debug data to send.
|
||||
* @param debugData only valid for HTTP/2; opaque debug data to send.
|
||||
*/
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public final class Header {
|
||||
public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
|
||||
public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
|
||||
public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
|
||||
public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // http/2
|
||||
public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
|
||||
public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
|
||||
public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@ import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* Read and write HPACK v05.
|
||||
* Read and write HPACK v06.
|
||||
*
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06
|
||||
*
|
||||
* This implementation uses an array for the header table with a bitset for
|
||||
* references. Dynamic entries are added to the array, starting in the last
|
||||
* position moving forward. When the array fills, it is doubled.
|
||||
*/
|
||||
final class HpackDraft05 {
|
||||
final class HpackDraft06 {
|
||||
private static final int PREFIX_6_BITS = 0x3f;
|
||||
private static final int PREFIX_7_BITS = 0x7f;
|
||||
|
||||
@@ -90,12 +90,11 @@ final class HpackDraft05 {
|
||||
new Header("www-authenticate", "")
|
||||
};
|
||||
|
||||
private HpackDraft05() {
|
||||
private HpackDraft06() {
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#section-3.2
|
||||
static final class Reader {
|
||||
private final Huffman.Codec huffmanCodec;
|
||||
|
||||
private final List<Header> emittedHeaders = new ArrayList<Header>();
|
||||
private final BufferedSource source;
|
||||
@@ -119,8 +118,7 @@ final class HpackDraft05 {
|
||||
BitArray emittedReferencedHeaders = new BitArray.FixedCapacity();
|
||||
int headerTableByteCount = 0;
|
||||
|
||||
Reader(boolean client, int maxHeaderTableByteCount, Source source) {
|
||||
this.huffmanCodec = client ? Huffman.Codec.RESPONSE : Huffman.Codec.REQUEST;
|
||||
Reader(int maxHeaderTableByteCount, Source source) {
|
||||
this.maxHeaderTableByteCount = maxHeaderTableByteCount;
|
||||
this.source = Okio.buffer(source);
|
||||
}
|
||||
@@ -181,7 +179,22 @@ final class HpackDraft05 {
|
||||
while (!source.exhausted()) {
|
||||
int b = source.readByte() & 0xff;
|
||||
if (b == 0x80) { // 10000000
|
||||
clearReferenceSet();
|
||||
b = source.readByte();
|
||||
if ((b & 0x80) == 0x80) { // 1NNNNNNN
|
||||
if ((b & PREFIX_7_BITS) == 0) {
|
||||
clearReferenceSet();
|
||||
} else {
|
||||
throw new IOException("Invalid header table state change " + b);
|
||||
}
|
||||
} else {
|
||||
int headerTableByteCount = b != PREFIX_7_BITS ? b : readInt(b, PREFIX_7_BITS);
|
||||
if (headerTableByteCount < 0) {
|
||||
throw new IOException("Invalid header table byte count " + headerTableByteCount);
|
||||
}
|
||||
maxHeaderTableByteCount = Math.min(headerTableByteCount, maxHeaderTableByteCount);
|
||||
int toEvict = headerTableByteCount - maxHeaderTableByteCount;
|
||||
if (toEvict > 0) evictToRecoverBytes(toEvict);
|
||||
}
|
||||
} else if ((b & 0x80) == 0x80) { // 1NNNNNNN
|
||||
int index = readInt(b, PREFIX_7_BITS);
|
||||
readIndexedHeader(index - 1);
|
||||
@@ -228,9 +241,13 @@ final class HpackDraft05 {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readIndexedHeader(int index) {
|
||||
private void readIndexedHeader(int index) throws IOException {
|
||||
if (isStaticHeader(index)) {
|
||||
Header staticEntry = STATIC_HEADER_TABLE[index - headerCount];
|
||||
index -= headerCount;
|
||||
if (index > STATIC_HEADER_TABLE.length - 1) {
|
||||
throw new IOException("Header index too large " + (index + 1));
|
||||
}
|
||||
Header staticEntry = STATIC_HEADER_TABLE[index];
|
||||
if (maxHeaderTableByteCount == 0) {
|
||||
emittedHeaders.add(staticEntry);
|
||||
} else {
|
||||
@@ -371,7 +388,7 @@ final class HpackDraft05 {
|
||||
ByteString byteString = source.readByteString(length);
|
||||
|
||||
if (huffmanDecode) {
|
||||
byteString = huffmanCodec.decode(byteString); // TODO: streaming Huffman!
|
||||
byteString = Huffman.get().decode(byteString); // TODO: streaming Huffman!
|
||||
}
|
||||
|
||||
if (asciiLowercase) {
|
||||
@@ -419,7 +436,7 @@ final class HpackDraft05 {
|
||||
}
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.1
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#section-4.1.1
|
||||
void writeInt(int value, int prefixMask, int bits) throws IOException {
|
||||
// Write the raw value for a single byte value.
|
||||
if (value < prefixMask) {
|
||||
@@ -26,10 +26,10 @@ import okio.Source;
|
||||
import okio.Timeout;
|
||||
|
||||
/**
|
||||
* Read and write http/2 v09 frames.
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-09
|
||||
* Read and write HTTP/2 v10 frames.
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-10
|
||||
*/
|
||||
public final class Http20Draft09 implements Variant {
|
||||
public final class Http20Draft10 implements Variant {
|
||||
|
||||
@Override public Protocol getProtocol() {
|
||||
return Protocol.HTTP_2;
|
||||
@@ -46,15 +46,18 @@ public final class Http20Draft09 implements Variant {
|
||||
static final byte TYPE_PUSH_PROMISE = 0x5;
|
||||
static final byte TYPE_PING = 0x6;
|
||||
static final byte TYPE_GOAWAY = 0x7;
|
||||
static final byte TYPE_WINDOW_UPDATE = 0x9;
|
||||
static final byte TYPE_CONTINUATION = 0xa;
|
||||
static final byte TYPE_WINDOW_UPDATE = 0x8;
|
||||
static final byte TYPE_CONTINUATION = 0x9;
|
||||
|
||||
static final byte FLAG_NONE = 0x0;
|
||||
static final byte FLAG_ACK = 0x1;
|
||||
static final byte FLAG_END_STREAM = 0x1;
|
||||
static final byte FLAG_END_SEGMENT = 0x2;
|
||||
static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
|
||||
static final byte FLAG_END_PUSH_PROMISE = 0x4;
|
||||
static final byte FLAG_PRIORITY = 0x8;
|
||||
static final byte FLAG_PAD_LOW = 0x10;
|
||||
static final byte FLAG_PAD_HIGH = 0x20;
|
||||
|
||||
@Override public FrameReader newReader(BufferedSource source, boolean client) {
|
||||
return new Reader(source, 4096, client);
|
||||
@@ -74,13 +77,13 @@ public final class Http20Draft09 implements Variant {
|
||||
private final boolean client;
|
||||
|
||||
// Visible for testing.
|
||||
final HpackDraft05.Reader hpackReader;
|
||||
final HpackDraft06.Reader hpackReader;
|
||||
|
||||
Reader(BufferedSource source, int headerTableSize, boolean client) {
|
||||
this.source = source;
|
||||
this.client = client;
|
||||
this.continuation = new ContinuationSource(this.source);
|
||||
this.hpackReader = new HpackDraft05.Reader(client, headerTableSize, continuation);
|
||||
this.hpackReader = new HpackDraft06.Reader(headerTableSize, continuation);
|
||||
}
|
||||
|
||||
@Override public void readConnectionHeader() throws IOException {
|
||||
@@ -146,8 +149,7 @@ public final class Http20Draft09 implements Variant {
|
||||
break;
|
||||
|
||||
default:
|
||||
// Implementations MUST ignore frames of unsupported or unrecognized types.
|
||||
source.skip(length);
|
||||
throw ioException("PROTOCOL_ERROR: unknown frame type %s", type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -158,21 +160,27 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
boolean endStream = (flags & FLAG_END_STREAM) != 0;
|
||||
|
||||
short padding = readPadding(source, flags);
|
||||
|
||||
int priority = -1;
|
||||
if ((flags & FLAG_PRIORITY) != 0) {
|
||||
priority = source.readInt() & 0x7fffffff;
|
||||
length -= 4; // account for above read.
|
||||
}
|
||||
|
||||
List<Header> headerBlock = readHeaderBlock(length, flags, streamId);
|
||||
length = lengthWithoutPadding(length, flags, padding);
|
||||
|
||||
List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
|
||||
|
||||
handler.headers(false, endStream, streamId, -1, priority, headerBlock,
|
||||
HeadersMode.HTTP_20_HEADERS);
|
||||
}
|
||||
|
||||
private List<Header> readHeaderBlock(short length, byte flags, int streamId)
|
||||
private List<Header> readHeaderBlock(short length, short padding, byte flags, int streamId)
|
||||
throws IOException {
|
||||
|
||||
continuation.length = continuation.left = length;
|
||||
continuation.padding = padding;
|
||||
continuation.flags = flags;
|
||||
continuation.streamId = streamId;
|
||||
|
||||
@@ -187,7 +195,12 @@ public final class Http20Draft09 implements Variant {
|
||||
throws IOException {
|
||||
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||
// TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
|
||||
|
||||
short padding = readPadding(source, flags);
|
||||
length = lengthWithoutPadding(length, flags, padding);
|
||||
|
||||
handler.data(inFinished, streamId, source, length);
|
||||
source.skip(padding);
|
||||
}
|
||||
|
||||
private void readPriority(Handler handler, short length, byte flags, int streamId)
|
||||
@@ -221,13 +234,32 @@ public final class Http20Draft09 implements Variant {
|
||||
return;
|
||||
}
|
||||
|
||||
if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
|
||||
if (length % 5 != 0) throw ioException("TYPE_SETTINGS length %% 5 != 0: %s", length);
|
||||
Settings settings = new Settings();
|
||||
for (int i = 0; i < length; i += 8) {
|
||||
int w1 = source.readInt();
|
||||
for (int i = 0; i < length; i += 5) {
|
||||
int id = source.readByte();
|
||||
int value = source.readInt();
|
||||
// int r = (w1 & 0xff000000) >>> 24; // Reserved.
|
||||
int id = w1 & 0xffffff;
|
||||
|
||||
switch (id) {
|
||||
case 1: // SETTINGS_HEADER_TABLE_SIZE
|
||||
break;
|
||||
case 2: // SETTINGS_ENABLE_PUSH
|
||||
if (value != 0 && value != 1) {
|
||||
throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
|
||||
}
|
||||
break;
|
||||
case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
id = 4; // Renumbered in draft 10.
|
||||
break;
|
||||
case 4: // SETTINGS_INITIAL_WINDOW_SIZE
|
||||
id = 7; // Renumbered in draft 10.
|
||||
if (value < 0) {
|
||||
throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
|
||||
}
|
||||
settings.set(id, 0, value);
|
||||
}
|
||||
handler.settings(false, settings);
|
||||
@@ -243,7 +275,8 @@ public final class Http20Draft09 implements Variant {
|
||||
}
|
||||
int promisedStreamId = source.readInt() & 0x7fffffff;
|
||||
length -= 4; // account for above read.
|
||||
List<Header> headerBlock = readHeaderBlock(length, flags, streamId);
|
||||
short padding = 0; // no padding for push promise.
|
||||
List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
|
||||
handler.pushPromise(streamId, promisedStreamId, headerBlock);
|
||||
}
|
||||
|
||||
@@ -292,14 +325,14 @@ public final class Http20Draft09 implements Variant {
|
||||
private final BufferedSink sink;
|
||||
private final boolean client;
|
||||
private final Buffer hpackBuffer;
|
||||
private final HpackDraft05.Writer hpackWriter;
|
||||
private final HpackDraft06.Writer hpackWriter;
|
||||
private boolean closed;
|
||||
|
||||
Writer(BufferedSink sink, boolean client) {
|
||||
this.sink = sink;
|
||||
this.client = client;
|
||||
this.hpackBuffer = new Buffer();
|
||||
this.hpackWriter = new HpackDraft05.Writer(hpackBuffer);
|
||||
this.hpackWriter = new HpackDraft06.Writer(hpackBuffer);
|
||||
}
|
||||
|
||||
@Override public synchronized void flush() throws IOException {
|
||||
@@ -411,14 +444,17 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
@Override public synchronized void settings(Settings settings) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
int length = settings.size() * 8;
|
||||
int length = settings.size() * 5;
|
||||
byte type = TYPE_SETTINGS;
|
||||
byte flags = FLAG_NONE;
|
||||
int streamId = 0;
|
||||
frameHeader(length, type, flags, streamId);
|
||||
for (int i = 0; i < Settings.COUNT; i++) {
|
||||
if (!settings.isSet(i)) continue;
|
||||
sink.writeInt(i & 0xffffff);
|
||||
int id = i;
|
||||
if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
|
||||
else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
|
||||
sink.writeByte(id);
|
||||
sink.writeInt(settings.get(i));
|
||||
}
|
||||
sink.flush();
|
||||
@@ -493,16 +529,17 @@ public final class Http20Draft09 implements Variant {
|
||||
/**
|
||||
* Decompression of the header block occurs above the framing layer. This
|
||||
* class lazily reads continuation frames as they are needed by {@link
|
||||
* HpackDraft05.Reader#readHeaders()}.
|
||||
* HpackDraft06.Reader#readHeaders()}.
|
||||
*/
|
||||
static final class ContinuationSource implements Source {
|
||||
private final BufferedSource source;
|
||||
|
||||
int length;
|
||||
short length;
|
||||
byte flags;
|
||||
int streamId;
|
||||
|
||||
int left;
|
||||
short left;
|
||||
short padding;
|
||||
|
||||
public ContinuationSource(BufferedSource source) {
|
||||
this.source = source;
|
||||
@@ -510,6 +547,8 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
@Override public long read(Buffer sink, long byteCount) throws IOException {
|
||||
while (left == 0) {
|
||||
source.skip(padding);
|
||||
padding = 0;
|
||||
if ((flags & FLAG_END_HEADERS) != 0) return -1;
|
||||
readContinuationHeader();
|
||||
// TODO: test case for empty continuation header?
|
||||
@@ -532,12 +571,41 @@ public final class Http20Draft09 implements Variant {
|
||||
int previousStreamId = streamId;
|
||||
int w1 = source.readInt();
|
||||
int w2 = source.readInt();
|
||||
length = left = (short) ((w1 & 0x3fff0000) >> 16);
|
||||
length = (short) ((w1 & 0x3fff0000) >> 16);
|
||||
byte type = (byte) ((w1 & 0xff00) >> 8);
|
||||
flags = (byte) (w1 & 0xff);
|
||||
padding = readPadding(source, flags);
|
||||
length = left = lengthWithoutPadding(length, flags, padding);
|
||||
streamId = (w2 & 0x7fffffff);
|
||||
if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
|
||||
if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
|
||||
}
|
||||
}
|
||||
|
||||
private static short readPadding(BufferedSource source, byte flags) throws IOException {
|
||||
if ((flags & FLAG_PAD_HIGH) != 0 && (flags & FLAG_PAD_LOW) == 0) {
|
||||
throw ioException("PROTOCOL_ERROR FLAG_PAD_HIGH set without FLAG_PAD_LOW");
|
||||
}
|
||||
int padding = 0;
|
||||
if ((flags & FLAG_PAD_HIGH) != 0) {
|
||||
padding = source.readShort() & 0xffff;
|
||||
} else if ((flags & FLAG_PAD_LOW) != 0) {
|
||||
padding = source.readByte() & 0xff;
|
||||
}
|
||||
if (padding > 16383) throw ioException("PROTOCOL_ERROR padding > 16383: %s", padding);
|
||||
return (short) padding;
|
||||
}
|
||||
|
||||
private static short lengthWithoutPadding(short length, byte flags, short padding)
|
||||
throws IOException {
|
||||
if ((flags & FLAG_PAD_HIGH) != 0) { // account for reading the padding length.
|
||||
length -= 2;
|
||||
} else if ((flags & FLAG_PAD_LOW) != 0) {
|
||||
length--;
|
||||
}
|
||||
if (padding > length) {
|
||||
throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
|
||||
}
|
||||
return (short) (length - padding);
|
||||
}
|
||||
}
|
||||
@@ -30,132 +30,172 @@ import okio.ByteString;
|
||||
* </ul>
|
||||
*/
|
||||
class Huffman {
|
||||
enum Codec {
|
||||
REQUEST(REQUEST_CODES, REQUEST_CODE_LENGTHS),
|
||||
RESPONSE(RESPONSE_CODES, RESPONSE_CODE_LENGTHS);
|
||||
|
||||
private final Node root = new Node();
|
||||
private final int[] codes;
|
||||
private final byte[] lengths;
|
||||
// Appendix C: Huffman Codes
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06#appendix-C
|
||||
private static final int[] CODES = {
|
||||
0x7ffffba, 0x7ffffbb, 0x7ffffbc, 0x7ffffbd, 0x7ffffbe, 0x7ffffbf, 0x7ffffc0, 0x7ffffc1,
|
||||
0x7ffffc2, 0x7ffffc3, 0x7ffffc4, 0x7ffffc5, 0x7ffffc6, 0x7ffffc7, 0x7ffffc8, 0x7ffffc9,
|
||||
0x7ffffca, 0x7ffffcb, 0x7ffffcc, 0x7ffffcd, 0x7ffffce, 0x7ffffcf, 0x7ffffd0, 0x7ffffd1,
|
||||
0x7ffffd2, 0x7ffffd3, 0x7ffffd4, 0x7ffffd5, 0x7ffffd6, 0x7ffffd7, 0x7ffffd8, 0x7ffffd9, 0xe8,
|
||||
0xffc, 0x3ffa, 0x7ffc, 0x7ffd, 0x24, 0x6e, 0x7ffe, 0x7fa, 0x7fb, 0x3fa, 0x7fc, 0xe9, 0x25,
|
||||
0x4, 0x0, 0x5, 0x6, 0x7, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x1ec, 0xea, 0x3fffe, 0x2d,
|
||||
0x1fffc, 0x1ed, 0x3ffb, 0x6f, 0xeb, 0xec, 0xed, 0xee, 0x70, 0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x3fb,
|
||||
0x1f2, 0xef, 0x1f3, 0x1f4, 0x1f5, 0x1f6, 0x1f7, 0xf0, 0xf1, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
|
||||
0x3fc, 0x3ffc, 0x7ffffda, 0x1ffc, 0x3ffd, 0x2e, 0x7fffe, 0x8, 0x2f, 0x9, 0x30, 0x1, 0x31,
|
||||
0x32, 0x33, 0xa, 0x71, 0x72, 0xb, 0x34, 0xc, 0xd, 0xe, 0xf2, 0xf, 0x10, 0x11, 0x35, 0x73,
|
||||
0x36, 0xf3, 0xf4, 0xf5, 0x1fffd, 0x7fd, 0x1fffe, 0xffd, 0x7ffffdb, 0x7ffffdc, 0x7ffffdd,
|
||||
0x7ffffde, 0x7ffffdf, 0x7ffffe0, 0x7ffffe1, 0x7ffffe2, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5,
|
||||
0x7ffffe6, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0x7ffffec, 0x7ffffed,
|
||||
0x7ffffee, 0x7ffffef, 0x7fffff0, 0x7fffff1, 0x7fffff2, 0x7fffff3, 0x7fffff4, 0x7fffff5,
|
||||
0x7fffff6, 0x7fffff7, 0x7fffff8, 0x7fffff9, 0x7fffffa, 0x7fffffb, 0x7fffffc, 0x7fffffd,
|
||||
0x7fffffe, 0x7ffffff, 0x3ffff80, 0x3ffff81, 0x3ffff82, 0x3ffff83, 0x3ffff84, 0x3ffff85,
|
||||
0x3ffff86, 0x3ffff87, 0x3ffff88, 0x3ffff89, 0x3ffff8a, 0x3ffff8b, 0x3ffff8c, 0x3ffff8d,
|
||||
0x3ffff8e, 0x3ffff8f, 0x3ffff90, 0x3ffff91, 0x3ffff92, 0x3ffff93, 0x3ffff94, 0x3ffff95,
|
||||
0x3ffff96, 0x3ffff97, 0x3ffff98, 0x3ffff99, 0x3ffff9a, 0x3ffff9b, 0x3ffff9c, 0x3ffff9d,
|
||||
0x3ffff9e, 0x3ffff9f, 0x3ffffa0, 0x3ffffa1, 0x3ffffa2, 0x3ffffa3, 0x3ffffa4, 0x3ffffa5,
|
||||
0x3ffffa6, 0x3ffffa7, 0x3ffffa8, 0x3ffffa9, 0x3ffffaa, 0x3ffffab, 0x3ffffac, 0x3ffffad,
|
||||
0x3ffffae, 0x3ffffaf, 0x3ffffb0, 0x3ffffb1, 0x3ffffb2, 0x3ffffb3, 0x3ffffb4, 0x3ffffb5,
|
||||
0x3ffffb6, 0x3ffffb7, 0x3ffffb8, 0x3ffffb9, 0x3ffffba, 0x3ffffbb, 0x3ffffbc, 0x3ffffbd,
|
||||
0x3ffffbe, 0x3ffffbf, 0x3ffffc0, 0x3ffffc1, 0x3ffffc2, 0x3ffffc3, 0x3ffffc4, 0x3ffffc5,
|
||||
0x3ffffc6, 0x3ffffc7, 0x3ffffc8, 0x3ffffc9, 0x3ffffca, 0x3ffffcb, 0x3ffffcc, 0x3ffffcd,
|
||||
0x3ffffce, 0x3ffffcf, 0x3ffffd0, 0x3ffffd1, 0x3ffffd2, 0x3ffffd3, 0x3ffffd4, 0x3ffffd5,
|
||||
0x3ffffd6, 0x3ffffd7, 0x3ffffd8, 0x3ffffd9, 0x3ffffda, 0x3ffffdb
|
||||
};
|
||||
|
||||
/**
|
||||
* @param codes Index designates the symbol this code represents.
|
||||
* @param lengths Index designates the symbol this code represents.
|
||||
*/
|
||||
Codec(int[] codes, byte[] lengths) {
|
||||
buildTree(codes, lengths);
|
||||
this.codes = codes;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
private static final byte[] CODE_LENGTHS = {
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 12, 14, 15, 15, 6, 7, 15, 11, 11, 10, 11, 8, 6, 5, 4,
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 9, 8, 18, 6, 17, 9, 14, 7, 8, 8, 8, 8, 7, 9, 9, 9, 9, 10, 9, 8,
|
||||
9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 10, 14, 27, 13, 14, 6, 19, 5, 6, 5, 6, 4, 6, 6, 6, 5, 7,
|
||||
7, 5, 6, 5, 5, 5, 8, 5, 5, 5, 6, 7, 6, 8, 8, 8, 17, 11, 17, 12, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26
|
||||
};
|
||||
|
||||
void encode(byte[] data, OutputStream out) throws IOException {
|
||||
long current = 0;
|
||||
int n = 0;
|
||||
private static final Huffman INSTANCE = new Huffman();
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int b = data[i] & 0xFF;
|
||||
int code = codes[b];
|
||||
int nbits = lengths[b];
|
||||
public static Huffman get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
current <<= nbits;
|
||||
current |= code;
|
||||
n += nbits;
|
||||
private final Node root = new Node();
|
||||
|
||||
while (n >= 8) {
|
||||
n -= 8;
|
||||
out.write(((int) (current >> n)));
|
||||
}
|
||||
}
|
||||
private Huffman() {
|
||||
buildTree();
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
current <<= (8 - n);
|
||||
current |= (0xFF >>> n);
|
||||
out.write((int) current);
|
||||
void encode(byte[] data, OutputStream out) throws IOException {
|
||||
long current = 0;
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int b = data[i] & 0xFF;
|
||||
int code = CODES[b];
|
||||
int nbits = CODE_LENGTHS[b];
|
||||
|
||||
current <<= nbits;
|
||||
current |= code;
|
||||
n += nbits;
|
||||
|
||||
while (n >= 8) {
|
||||
n -= 8;
|
||||
out.write(((int) (current >> n)));
|
||||
}
|
||||
}
|
||||
|
||||
int encodedLength(byte[] bytes) {
|
||||
long len = 0;
|
||||
if (n > 0) {
|
||||
current <<= (8 - n);
|
||||
current |= (0xFF >>> n);
|
||||
out.write((int) current);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int b = bytes[i] & 0xFF;
|
||||
len += lengths[b];
|
||||
}
|
||||
int encodedLength(byte[] bytes) {
|
||||
long len = 0;
|
||||
|
||||
return (int) ((len + 7) >> 3);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int b = bytes[i] & 0xFF;
|
||||
len += CODE_LENGTHS[b];
|
||||
}
|
||||
|
||||
ByteString decode(ByteString buf) throws IOException {
|
||||
return ByteString.of(decode(buf.toByteArray()));
|
||||
}
|
||||
return (int) ((len + 7) >> 3);
|
||||
}
|
||||
|
||||
byte[] decode(byte[] buf) throws IOException {
|
||||
// FIXME
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Node node = root;
|
||||
int current = 0;
|
||||
int nbits = 0;
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
int b = buf[i] & 0xFF;
|
||||
current = (current << 8) | b;
|
||||
nbits += 8;
|
||||
while (nbits >= 8) {
|
||||
int c = (current >>> (nbits - 8)) & 0xFF;
|
||||
node = node.children[c];
|
||||
if (node.children == null) {
|
||||
// terminal node
|
||||
baos.write(node.symbol);
|
||||
nbits -= node.terminalBits;
|
||||
node = root;
|
||||
} else {
|
||||
// non-terminal node
|
||||
nbits -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
ByteString decode(ByteString buf) throws IOException {
|
||||
return ByteString.of(decode(buf.toByteArray()));
|
||||
}
|
||||
|
||||
while (nbits > 0) {
|
||||
int c = (current << (8 - nbits)) & 0xFF;
|
||||
byte[] decode(byte[] buf) throws IOException {
|
||||
// FIXME
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Node node = root;
|
||||
int current = 0;
|
||||
int nbits = 0;
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
int b = buf[i] & 0xFF;
|
||||
current = (current << 8) | b;
|
||||
nbits += 8;
|
||||
while (nbits >= 8) {
|
||||
int c = (current >>> (nbits - 8)) & 0xFF;
|
||||
node = node.children[c];
|
||||
if (node.children != null || node.terminalBits > nbits) {
|
||||
break;
|
||||
if (node.children == null) {
|
||||
// terminal node
|
||||
baos.write(node.symbol);
|
||||
nbits -= node.terminalBits;
|
||||
node = root;
|
||||
} else {
|
||||
// non-terminal node
|
||||
nbits -= 8;
|
||||
}
|
||||
baos.write(node.symbol);
|
||||
nbits -= node.terminalBits;
|
||||
node = root;
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private void buildTree(int[] codes, byte[] lengths) {
|
||||
for (int i = 0; i < lengths.length; i++) {
|
||||
addCode(i, codes[i], lengths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCode(int sym, int code, byte len) {
|
||||
Node terminal = new Node(sym, len);
|
||||
|
||||
Node current = root;
|
||||
while (len > 8) {
|
||||
len -= 8;
|
||||
int i = ((code >>> len) & 0xFF);
|
||||
if (current.children == null) {
|
||||
throw new IllegalStateException("invalid dictionary: prefix not unique");
|
||||
}
|
||||
if (current.children[i] == null) {
|
||||
current.children[i] = new Node();
|
||||
}
|
||||
current = current.children[i];
|
||||
while (nbits > 0) {
|
||||
int c = (current << (8 - nbits)) & 0xFF;
|
||||
node = node.children[c];
|
||||
if (node.children != null || node.terminalBits > nbits) {
|
||||
break;
|
||||
}
|
||||
baos.write(node.symbol);
|
||||
nbits -= node.terminalBits;
|
||||
node = root;
|
||||
}
|
||||
|
||||
int shift = 8 - len;
|
||||
int start = (code << shift) & 0xFF;
|
||||
int end = 1 << shift;
|
||||
for (int i = start; i < start + end; i++) {
|
||||
current.children[i] = terminal;
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private void buildTree() {
|
||||
for (int i = 0; i < CODE_LENGTHS.length; i++) {
|
||||
addCode(i, CODES[i], CODE_LENGTHS[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCode(int sym, int code, byte len) {
|
||||
Node terminal = new Node(sym, len);
|
||||
|
||||
Node current = root;
|
||||
while (len > 8) {
|
||||
len -= 8;
|
||||
int i = ((code >>> len) & 0xFF);
|
||||
if (current.children == null) {
|
||||
throw new IllegalStateException("invalid dictionary: prefix not unique");
|
||||
}
|
||||
if (current.children[i] == null) {
|
||||
current.children[i] = new Node();
|
||||
}
|
||||
current = current.children[i];
|
||||
}
|
||||
|
||||
int shift = 8 - len;
|
||||
int start = (code << shift) & 0xFF;
|
||||
int end = 1 << shift;
|
||||
for (int i = start; i < start + end; i++) {
|
||||
current.children[i] = terminal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,95 +230,4 @@ class Huffman {
|
||||
this.terminalBits = b == 0 ? 8 : b;
|
||||
}
|
||||
}
|
||||
|
||||
// Appendix C: Huffman Codes For Requests
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-C
|
||||
private static final int[] REQUEST_CODES = {
|
||||
0x7ffffba, 0x7ffffbb, 0x7ffffbc, 0x7ffffbd, 0x7ffffbe, 0x7ffffbf, 0x7ffffc0, 0x7ffffc1,
|
||||
0x7ffffc2, 0x7ffffc3, 0x7ffffc4, 0x7ffffc5, 0x7ffffc6, 0x7ffffc7, 0x7ffffc8, 0x7ffffc9,
|
||||
0x7ffffca, 0x7ffffcb, 0x7ffffcc, 0x7ffffcd, 0x7ffffce, 0x7ffffcf, 0x7ffffd0, 0x7ffffd1,
|
||||
0x7ffffd2, 0x7ffffd3, 0x7ffffd4, 0x7ffffd5, 0x7ffffd6, 0x7ffffd7, 0x7ffffd8, 0x7ffffd9, 0xe8,
|
||||
0xffc, 0x3ffa, 0x7ffc, 0x7ffd, 0x24, 0x6e, 0x7ffe, 0x7fa, 0x7fb, 0x3fa, 0x7fc, 0xe9, 0x25,
|
||||
0x4, 0x0, 0x5, 0x6, 0x7, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x1ec, 0xea, 0x3fffe, 0x2d,
|
||||
0x1fffc, 0x1ed, 0x3ffb, 0x6f, 0xeb, 0xec, 0xed, 0xee, 0x70, 0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x3fb,
|
||||
0x1f2, 0xef, 0x1f3, 0x1f4, 0x1f5, 0x1f6, 0x1f7, 0xf0, 0xf1, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
|
||||
0x3fc, 0x3ffc, 0x7ffffda, 0x1ffc, 0x3ffd, 0x2e, 0x7fffe, 0x8, 0x2f, 0x9, 0x30, 0x1, 0x31,
|
||||
0x32, 0x33, 0xa, 0x71, 0x72, 0xb, 0x34, 0xc, 0xd, 0xe, 0xf2, 0xf, 0x10, 0x11, 0x35, 0x73,
|
||||
0x36, 0xf3, 0xf4, 0xf5, 0x1fffd, 0x7fd, 0x1fffe, 0xffd, 0x7ffffdb, 0x7ffffdc, 0x7ffffdd,
|
||||
0x7ffffde, 0x7ffffdf, 0x7ffffe0, 0x7ffffe1, 0x7ffffe2, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5,
|
||||
0x7ffffe6, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0x7ffffec, 0x7ffffed,
|
||||
0x7ffffee, 0x7ffffef, 0x7fffff0, 0x7fffff1, 0x7fffff2, 0x7fffff3, 0x7fffff4, 0x7fffff5,
|
||||
0x7fffff6, 0x7fffff7, 0x7fffff8, 0x7fffff9, 0x7fffffa, 0x7fffffb, 0x7fffffc, 0x7fffffd,
|
||||
0x7fffffe, 0x7ffffff, 0x3ffff80, 0x3ffff81, 0x3ffff82, 0x3ffff83, 0x3ffff84, 0x3ffff85,
|
||||
0x3ffff86, 0x3ffff87, 0x3ffff88, 0x3ffff89, 0x3ffff8a, 0x3ffff8b, 0x3ffff8c, 0x3ffff8d,
|
||||
0x3ffff8e, 0x3ffff8f, 0x3ffff90, 0x3ffff91, 0x3ffff92, 0x3ffff93, 0x3ffff94, 0x3ffff95,
|
||||
0x3ffff96, 0x3ffff97, 0x3ffff98, 0x3ffff99, 0x3ffff9a, 0x3ffff9b, 0x3ffff9c, 0x3ffff9d,
|
||||
0x3ffff9e, 0x3ffff9f, 0x3ffffa0, 0x3ffffa1, 0x3ffffa2, 0x3ffffa3, 0x3ffffa4, 0x3ffffa5,
|
||||
0x3ffffa6, 0x3ffffa7, 0x3ffffa8, 0x3ffffa9, 0x3ffffaa, 0x3ffffab, 0x3ffffac, 0x3ffffad,
|
||||
0x3ffffae, 0x3ffffaf, 0x3ffffb0, 0x3ffffb1, 0x3ffffb2, 0x3ffffb3, 0x3ffffb4, 0x3ffffb5,
|
||||
0x3ffffb6, 0x3ffffb7, 0x3ffffb8, 0x3ffffb9, 0x3ffffba, 0x3ffffbb, 0x3ffffbc, 0x3ffffbd,
|
||||
0x3ffffbe, 0x3ffffbf, 0x3ffffc0, 0x3ffffc1, 0x3ffffc2, 0x3ffffc3, 0x3ffffc4, 0x3ffffc5,
|
||||
0x3ffffc6, 0x3ffffc7, 0x3ffffc8, 0x3ffffc9, 0x3ffffca, 0x3ffffcb, 0x3ffffcc, 0x3ffffcd,
|
||||
0x3ffffce, 0x3ffffcf, 0x3ffffd0, 0x3ffffd1, 0x3ffffd2, 0x3ffffd3, 0x3ffffd4, 0x3ffffd5,
|
||||
0x3ffffd6, 0x3ffffd7, 0x3ffffd8, 0x3ffffd9, 0x3ffffda, 0x3ffffdb
|
||||
};
|
||||
|
||||
private static final byte[] REQUEST_CODE_LENGTHS = {
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 12, 14, 15, 15, 6, 7, 15, 11, 11, 10, 11, 8, 6, 5, 4,
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 9, 8, 18, 6, 17, 9, 14, 7, 8, 8, 8, 8, 7, 9, 9, 9, 9, 10, 9, 8,
|
||||
9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 10, 14, 27, 13, 14, 6, 19, 5, 6, 5, 6, 4, 6, 6, 6, 5, 7,
|
||||
7, 5, 6, 5, 5, 5, 8, 5, 5, 5, 6, 7, 6, 8, 8, 8, 17, 11, 17, 12, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26
|
||||
};
|
||||
|
||||
// Appendix D: Huffman Codes For Responses
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-D
|
||||
private static final int[] RESPONSE_CODES = {
|
||||
0x1ffffbc, 0x1ffffbd, 0x1ffffbe, 0x1ffffbf, 0x1ffffc0, 0x1ffffc1, 0x1ffffc2, 0x1ffffc3,
|
||||
0x1ffffc4, 0x1ffffc5, 0x1ffffc6, 0x1ffffc7, 0x1ffffc8, 0x1ffffc9, 0x1ffffca, 0x1ffffcb,
|
||||
0x1ffffcc, 0x1ffffcd, 0x1ffffce, 0x1ffffcf, 0x1ffffd0, 0x1ffffd1, 0x1ffffd2, 0x1ffffd3,
|
||||
0x1ffffd4, 0x1ffffd5, 0x1ffffd6, 0x1ffffd7, 0x1ffffd8, 0x1ffffd9, 0x1ffffda, 0x1ffffdb, 0x0,
|
||||
0xffa, 0x6a, 0x1ffa, 0x3ffc, 0x1ec, 0x3f8, 0x1ffb, 0x1ed, 0x1ee, 0xffb, 0x7fa, 0x22, 0x23,
|
||||
0x24, 0x6b, 0x1, 0x2, 0x3, 0x8, 0x9, 0xa, 0x25, 0x26, 0xb, 0xc, 0xd, 0x1ef, 0xfffa, 0x6c,
|
||||
0x1ffc, 0xffc, 0xfffb, 0x6d, 0xea, 0xeb, 0xec, 0xed, 0xee, 0x27, 0x1f0, 0xef, 0xf0, 0x3f9,
|
||||
0x1f1, 0x28, 0xf1, 0xf2, 0x1f2, 0x3fa, 0x1f3, 0x29, 0xe, 0x1f4, 0x1f5, 0xf3, 0x3fb, 0x1f6,
|
||||
0x3fc, 0x7fb, 0x1ffd, 0x7fc, 0x7ffc, 0x1f7, 0x1fffe, 0xf, 0x6e, 0x2a, 0x2b, 0x10, 0x6f, 0x70,
|
||||
0x71, 0x2c, 0x1f8, 0x1f9, 0x72, 0x2d, 0x2e, 0x2f, 0x30, 0x1fa, 0x31, 0x32, 0x33, 0x34, 0x73,
|
||||
0xf4, 0x74, 0xf5, 0x1fb, 0xfffc, 0x3ffd, 0xfffd, 0xfffe, 0x1ffffdc, 0x1ffffdd, 0x1ffffde,
|
||||
0x1ffffdf, 0x1ffffe0, 0x1ffffe1, 0x1ffffe2, 0x1ffffe3, 0x1ffffe4, 0x1ffffe5, 0x1ffffe6,
|
||||
0x1ffffe7, 0x1ffffe8, 0x1ffffe9, 0x1ffffea, 0x1ffffeb, 0x1ffffec, 0x1ffffed, 0x1ffffee,
|
||||
0x1ffffef, 0x1fffff0, 0x1fffff1, 0x1fffff2, 0x1fffff3, 0x1fffff4, 0x1fffff5, 0x1fffff6,
|
||||
0x1fffff7, 0x1fffff8, 0x1fffff9, 0x1fffffa, 0x1fffffb, 0x1fffffc, 0x1fffffd, 0x1fffffe,
|
||||
0x1ffffff, 0xffff80, 0xffff81, 0xffff82, 0xffff83, 0xffff84, 0xffff85, 0xffff86, 0xffff87,
|
||||
0xffff88, 0xffff89, 0xffff8a, 0xffff8b, 0xffff8c, 0xffff8d, 0xffff8e, 0xffff8f, 0xffff90,
|
||||
0xffff91, 0xffff92, 0xffff93, 0xffff94, 0xffff95, 0xffff96, 0xffff97, 0xffff98, 0xffff99,
|
||||
0xffff9a, 0xffff9b, 0xffff9c, 0xffff9d, 0xffff9e, 0xffff9f, 0xffffa0, 0xffffa1, 0xffffa2,
|
||||
0xffffa3, 0xffffa4, 0xffffa5, 0xffffa6, 0xffffa7, 0xffffa8, 0xffffa9, 0xffffaa, 0xffffab,
|
||||
0xffffac, 0xffffad, 0xffffae, 0xffffaf, 0xffffb0, 0xffffb1, 0xffffb2, 0xffffb3, 0xffffb4,
|
||||
0xffffb5, 0xffffb6, 0xffffb7, 0xffffb8, 0xffffb9, 0xffffba, 0xffffbb, 0xffffbc, 0xffffbd,
|
||||
0xffffbe, 0xffffbf, 0xffffc0, 0xffffc1, 0xffffc2, 0xffffc3, 0xffffc4, 0xffffc5, 0xffffc6,
|
||||
0xffffc7, 0xffffc8, 0xffffc9, 0xffffca, 0xffffcb, 0xffffcc, 0xffffcd, 0xffffce, 0xffffcf,
|
||||
0xffffd0, 0xffffd1, 0xffffd2, 0xffffd3, 0xffffd4, 0xffffd5, 0xffffd6, 0xffffd7, 0xffffd8,
|
||||
0xffffd9, 0xffffda, 0xffffdb, 0xffffdc
|
||||
};
|
||||
|
||||
private static final byte[] RESPONSE_CODE_LENGTHS = {
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 4, 12, 7, 13, 14, 9, 10, 13, 9, 9, 12, 11, 6, 6, 6, 7, 4,
|
||||
4, 4, 5, 5, 5, 6, 6, 5, 5, 5, 9, 16, 7, 13, 12, 16, 7, 8, 8, 8, 8, 8, 6, 9, 8, 8, 10, 9, 6, 8,
|
||||
8, 9, 10, 9, 6, 5, 9, 9, 8, 10, 9, 10, 11, 13, 11, 15, 9, 17, 5, 7, 6, 6, 5, 7, 7, 7, 6, 9, 9,
|
||||
7, 6, 6, 6, 6, 9, 6, 6, 6, 6, 7, 8, 7, 8, 9, 16, 14, 16, 16, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ final class Settings {
|
||||
|
||||
/** spdy/3: Sender's estimate of max incoming kbps. */
|
||||
static final int UPLOAD_BANDWIDTH = 1;
|
||||
/** http/2: Size in bytes of the table used to decode the sender's header blocks. */
|
||||
/** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */
|
||||
static final int HEADER_TABLE_SIZE = 1;
|
||||
/** spdy/3: Sender's estimate of max outgoing kbps. */
|
||||
static final int DOWNLOAD_BANDWIDTH = 2;
|
||||
/** http/2: An endpoint must not send a PUSH_PROMISE frame when this is 0. */
|
||||
/** HTTP/2: An endpoint must not send a PUSH_PROMISE frame when this is 0. */
|
||||
static final int ENABLE_PUSH = 2;
|
||||
/** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
|
||||
static final int ROUND_TRIP_TIME = 3;
|
||||
@@ -134,7 +134,7 @@ final class Settings {
|
||||
return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
|
||||
}
|
||||
|
||||
/** http/2 only. Returns -1 if unset. */
|
||||
/** HTTP/2 only. Returns -1 if unset. */
|
||||
int getHeaderTableSize() {
|
||||
int bit = 1 << HEADER_TABLE_SIZE;
|
||||
return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1;
|
||||
@@ -146,8 +146,8 @@ final class Settings {
|
||||
return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
|
||||
}
|
||||
|
||||
/** http/2 only. */
|
||||
// TODO: honor this setting in http/2.
|
||||
/** HTTP/2 only. */
|
||||
// TODO: honor this setting in HTTP/2.
|
||||
boolean getEnablePush(boolean defaultValue) {
|
||||
int bit = 1 << ENABLE_PUSH;
|
||||
return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1;
|
||||
@@ -159,7 +159,7 @@ final class Settings {
|
||||
return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
|
||||
}
|
||||
|
||||
// TODO: honor this setting in spdy/3 and http/2.
|
||||
// TODO: honor this setting in spdy/3 and HTTP/2.
|
||||
int getMaxConcurrentStreams(int defaultValue) {
|
||||
int bit = 1 << MAX_CONCURRENT_STREAMS;
|
||||
return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
|
||||
@@ -188,7 +188,7 @@ final class Settings {
|
||||
return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
|
||||
}
|
||||
|
||||
// TODO: honor this setting in spdy/3 and http/2.
|
||||
// TODO: honor this setting in spdy/3 and HTTP/2.
|
||||
boolean isFlowControlDisabled() {
|
||||
int bit = 1 << FLOW_CONTROL_OPTIONS;
|
||||
int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
|
||||
|
||||
@@ -143,7 +143,7 @@ public final class SpdyConnection implements Closeable {
|
||||
|
||||
Variant variant;
|
||||
if (protocol == Protocol.HTTP_2) {
|
||||
variant = new Http20Draft09();
|
||||
variant = new Http20Draft10();
|
||||
} else if (protocol == Protocol.SPDY_3) {
|
||||
variant = new Spdy3();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user