uhr/fritteliuhr/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Http20Draft06.java

386 lines
14 KiB
Java

/*
* 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.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
/**
* Read and write http/2 v06 frames.
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
*/
final class Http20Draft06 implements Variant {
private static final byte[] CONNECTION_HEADER;
static {
try {
CONNECTION_HEADER = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}
static final int TYPE_DATA = 0x0;
static final int TYPE_HEADERS = 0x1;
static final int TYPE_PRIORITY = 0x2;
static final int TYPE_RST_STREAM = 0x3;
static final int TYPE_SETTINGS = 0x4;
static final int TYPE_PUSH_PROMISE = 0x5;
static final int TYPE_PING = 0x6;
static final int TYPE_GOAWAY = 0x7;
static final int TYPE_WINDOW_UPDATE = 0x9;
static final int TYPE_CONTINUATION = 0xa;
static final int FLAG_END_STREAM = 0x1;
/** Used for headers, push-promise and continuation. */
static final int FLAG_END_HEADERS = 0x4;
static final int FLAG_PRIORITY = 0x8;
static final int FLAG_PONG = 0x1;
static final int FLAG_END_FLOW_CONTROL = 0x1;
@Override public FrameReader newReader(InputStream in, boolean client) {
return new Reader(in, client);
}
@Override public FrameWriter newWriter(OutputStream out, boolean client) {
return new Writer(out, client);
}
static final class Reader implements FrameReader {
private final DataInputStream in;
private final boolean client;
private final Hpack.Reader hpackReader;
Reader(InputStream in, boolean client) {
this.in = new DataInputStream(in);
this.client = client;
this.hpackReader = new Hpack.Reader(this.in, client);
}
@Override public void readConnectionHeader() throws IOException {
if (client) return; // Nothing to read; servers don't send connection headers!
byte[] connectionHeader = new byte[CONNECTION_HEADER.length];
in.readFully(connectionHeader);
if (!Arrays.equals(connectionHeader, CONNECTION_HEADER)) {
throw ioException("Expected a connection header but was "
+ Arrays.toString(connectionHeader));
}
}
@Override public boolean nextFrame(Handler handler) throws IOException {
int w1;
try {
w1 = in.readInt();
} catch (IOException e) {
return false; // This might be a normal socket close.
}
int w2 = in.readInt();
int length = (w1 & 0xffff0000) >> 16;
int type = (w1 & 0xff00) >> 8;
int flags = w1 & 0xff;
// boolean r = (w2 & 0x80000000) != 0; // Reserved.
int streamId = (w2 & 0x7fffffff);
switch (type) {
case TYPE_DATA:
readData(handler, flags, length, streamId);
return true;
case TYPE_HEADERS:
readHeaders(handler, flags, length, streamId);
return true;
case TYPE_PRIORITY:
readPriority(handler, flags, length, streamId);
return true;
case TYPE_RST_STREAM:
readRstStream(handler, flags, length, streamId);
return true;
case TYPE_SETTINGS:
readSettings(handler, flags, length, streamId);
return true;
case TYPE_PUSH_PROMISE:
readPushPromise(handler, flags, length, streamId);
return true;
case TYPE_PING:
readPing(handler, flags, length, streamId);
return true;
case TYPE_GOAWAY:
readGoAway(handler, flags, length, streamId);
return true;
case TYPE_WINDOW_UPDATE:
readWindowUpdate(handler, flags, length, streamId);
return true;
}
throw new UnsupportedOperationException("TODO");
}
private void readHeaders(Handler handler, int flags, int length, int streamId)
throws IOException {
if (streamId == 0) throw ioException("TYPE_HEADERS streamId == 0");
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
while (true) {
hpackReader.readHeaders(length);
if ((flags & FLAG_END_HEADERS) != 0) {
hpackReader.emitReferenceSet();
List<String> namesAndValues = hpackReader.getAndReset();
int priority = -1; // TODO: priority
handler.headers(false, inFinished, streamId, -1, priority, namesAndValues,
HeadersMode.HTTP_20_HEADERS);
return;
}
// Read another continuation frame.
int w1 = in.readInt();
int w2 = in.readInt();
length = (w1 & 0xffff0000) >> 16;
int newType = (w1 & 0xff00) >> 8;
flags = w1 & 0xff;
// TODO: remove in draft 8: CONTINUATION no longer sets END_STREAM
inFinished = (flags & FLAG_END_STREAM) != 0;
// boolean u = (w2 & 0x80000000) != 0; // Unused.
int newStreamId = (w2 & 0x7fffffff);
if (newType != TYPE_CONTINUATION) {
throw ioException("TYPE_CONTINUATION didn't have FLAG_END_HEADERS");
}
if (newStreamId != streamId) throw ioException("TYPE_CONTINUATION streamId changed");
}
}
private void readData(Handler handler, int flags, int length, int streamId) throws IOException {
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
handler.data(inFinished, streamId, in, length);
}
private void readPriority(Handler handler, int flags, int length, int streamId)
throws IOException {
if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length);
if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
int w1 = in.readInt();
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
int priority = (w1 & 0x7fffffff);
handler.priority(streamId, priority);
}
private void readRstStream(Handler handler, int flags, int length, int streamId)
throws IOException {
if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
int errorCodeInt = in.readInt();
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
if (errorCode == null) {
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
}
handler.rstStream(streamId, errorCode);
}
private void readSettings(Handler handler, int flags, int length, int streamId)
throws IOException {
if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
Settings settings = new Settings();
for (int i = 0; i < length; i += 8) {
int w1 = in.readInt();
int value = in.readInt();
// int r = (w1 & 0xff000000) >>> 24; // Reserved.
int id = w1 & 0xffffff;
settings.set(id, 0, value);
}
handler.settings(false, settings);
}
private void readPushPromise(Handler handler, int flags, int length, int streamId) {
// TODO:
}
private void readPing(Handler handler, int flags, int length, int streamId) throws IOException {
if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
int payload1 = in.readInt();
int payload2 = in.readInt();
boolean reply = (flags & FLAG_PONG) != 0;
handler.ping(reply, payload1, payload2);
}
private void readGoAway(Handler handler, int flags, int length, int streamId)
throws IOException {
if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
int lastStreamId = in.readInt();
int errorCodeInt = in.readInt();
int opaqueDataLength = length - 8;
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
if (errorCode == null) {
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
}
if (Util.skipByReading(in, opaqueDataLength) != opaqueDataLength) {
throw new IOException("TYPE_GOAWAY opaque data was truncated");
}
handler.goAway(lastStreamId, errorCode);
}
private void readWindowUpdate(Handler handler, int flags, int length, int streamId)
throws IOException {
int w1 = in.readInt();
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
int windowSizeIncrement = (w1 & 0x7fffffff);
boolean endFlowControl = (flags & FLAG_END_FLOW_CONTROL) != 0;
handler.windowUpdate(streamId, windowSizeIncrement, endFlowControl);
}
private static IOException ioException(String message, Object... args) throws IOException {
throw new IOException(String.format(message, args));
}
@Override public void close() throws IOException {
in.close();
}
}
static final class Writer implements FrameWriter {
private final DataOutputStream out;
private final boolean client;
private final ByteArrayOutputStream hpackBuffer;
private final Hpack.Writer hpackWriter;
Writer(OutputStream out, boolean client) {
this.out = new DataOutputStream(out);
this.client = client;
this.hpackBuffer = new ByteArrayOutputStream();
this.hpackWriter = new Hpack.Writer(hpackBuffer);
}
@Override public synchronized void flush() throws IOException {
out.flush();
}
@Override public synchronized void connectionHeader() throws IOException {
if (!client) return; // Nothing to write; servers don't send connection headers!
out.write(CONNECTION_HEADER);
}
@Override public synchronized void synStream(boolean outFinished, boolean inFinished,
int streamId, int associatedStreamId, int priority, int slot, List<String> nameValueBlock)
throws IOException {
if (inFinished) throw new UnsupportedOperationException();
headers(outFinished, streamId, priority, nameValueBlock);
}
@Override public synchronized void synReply(boolean outFinished, int streamId,
List<String> nameValueBlock) throws IOException {
headers(outFinished, streamId, -1, nameValueBlock);
}
@Override public synchronized void headers(int streamId, List<String> nameValueBlock)
throws IOException {
headers(false, streamId, -1, nameValueBlock);
}
private void headers(boolean outFinished, int streamId, int priority,
List<String> nameValueBlock) throws IOException {
hpackBuffer.reset();
hpackWriter.writeHeaders(nameValueBlock);
int type = TYPE_HEADERS;
// TODO: implement CONTINUATION
int length = hpackBuffer.size();
int flags = FLAG_END_HEADERS;
if (outFinished) flags |= FLAG_END_STREAM;
if (priority != -1) flags |= FLAG_PRIORITY;
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
if (priority != -1) out.writeInt(priority & 0x7fffffff);
hpackBuffer.writeTo(out);
}
@Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
throws IOException {
throw new UnsupportedOperationException("TODO");
}
@Override public void data(boolean outFinished, int streamId, byte[] data) throws IOException {
data(outFinished, streamId, data, 0, data.length);
}
@Override public synchronized void data(boolean outFinished, int streamId, byte[] data,
int offset, int byteCount) throws IOException {
int type = TYPE_DATA;
int flags = 0;
if (outFinished) flags |= FLAG_END_STREAM;
out.writeInt((byteCount & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
out.write(data, offset, byteCount);
}
@Override public synchronized void settings(Settings settings) throws IOException {
int type = TYPE_SETTINGS;
int length = settings.size() * 8;
int flags = 0;
int streamId = 0;
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
for (int i = 0; i < Settings.COUNT; i++) {
if (!settings.isSet(i)) continue;
out.writeInt(i & 0xffffff);
out.writeInt(settings.get(i));
}
}
@Override public synchronized void noop() throws IOException {
throw new UnsupportedOperationException();
}
@Override public synchronized void ping(boolean reply, int payload1, int payload2)
throws IOException {
// TODO
}
@Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
throws IOException {
// TODO
}
@Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
throws IOException {
// TODO
}
@Override public void close() throws IOException {
out.close();
}
}
}