Home | History | Annotate | Download | only in spdy
      1 /*
      2  * Copyright (C) 2013 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.squareup.okhttp.internal.spdy;
     17 
     18 import com.squareup.okhttp.Protocol;
     19 import java.io.IOException;
     20 import java.util.List;
     21 import okio.BufferedSink;
     22 import okio.BufferedSource;
     23 import okio.ByteString;
     24 import okio.Deadline;
     25 import okio.OkBuffer;
     26 import okio.Source;
     27 
     28 /**
     29  * Read and write http/2 v09 frames.
     30  * http://tools.ietf.org/html/draft-ietf-httpbis-http2-09
     31  */
     32 public final class Http20Draft09 implements Variant {
     33 
     34   @Override public Protocol getProtocol() {
     35     return Protocol.HTTP_2;
     36   }
     37 
     38   private static final ByteString CONNECTION_HEADER
     39       = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
     40 
     41   static final byte TYPE_DATA = 0x0;
     42   static final byte TYPE_HEADERS = 0x1;
     43   static final byte TYPE_PRIORITY = 0x2;
     44   static final byte TYPE_RST_STREAM = 0x3;
     45   static final byte TYPE_SETTINGS = 0x4;
     46   static final byte TYPE_PUSH_PROMISE = 0x5;
     47   static final byte TYPE_PING = 0x6;
     48   static final byte TYPE_GOAWAY = 0x7;
     49   static final byte TYPE_WINDOW_UPDATE = 0x9;
     50   static final byte TYPE_CONTINUATION = 0xa;
     51 
     52   static final byte FLAG_NONE = 0x0;
     53   static final byte FLAG_ACK = 0x1;
     54   static final byte FLAG_END_STREAM = 0x1;
     55   static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
     56   static final byte FLAG_END_PUSH_PROMISE = 0x4;
     57   static final byte FLAG_PRIORITY = 0x8;
     58 
     59   @Override public FrameReader newReader(BufferedSource source, boolean client) {
     60     return new Reader(source, 4096, client);
     61   }
     62 
     63   @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
     64     return new Writer(sink, client);
     65   }
     66 
     67   @Override public int maxFrameSize() {
     68     return 16383;
     69   }
     70 
     71   static final class Reader implements FrameReader {
     72     private final BufferedSource source;
     73     private final ContinuationSource continuation;
     74     private final boolean client;
     75 
     76     // Visible for testing.
     77     final HpackDraft05.Reader hpackReader;
     78 
     79     Reader(BufferedSource source, int headerTableSize, boolean client) {
     80       this.source = source;
     81       this.client = client;
     82       this.continuation = new ContinuationSource(this.source);
     83       this.hpackReader = new HpackDraft05.Reader(client, headerTableSize, continuation);
     84     }
     85 
     86     @Override public void readConnectionHeader() throws IOException {
     87       if (client) return; // Nothing to read; servers don't send connection headers!
     88       ByteString connectionHeader = source.readByteString(CONNECTION_HEADER.size());
     89       if (!CONNECTION_HEADER.equals(connectionHeader)) {
     90         throw ioException("Expected a connection header but was %s", connectionHeader.utf8());
     91       }
     92     }
     93 
     94     @Override public boolean nextFrame(Handler handler) throws IOException {
     95       int w1;
     96       int w2;
     97       try {
     98         w1 = source.readInt();
     99         w2 = source.readInt();
    100       } catch (IOException e) {
    101         return false; // This might be a normal socket close.
    102       }
    103 
    104       // boolean r = (w1 & 0xc0000000) != 0; // Reserved: Ignore first 2 bits.
    105       short length = (short) ((w1 & 0x3fff0000) >> 16); // 14-bit unsigned == max 16383
    106       byte type = (byte) ((w1 & 0xff00) >> 8);
    107       byte flags = (byte) (w1 & 0xff);
    108       // boolean r = (w2 & 0x80000000) != 0; // Reserved: Ignore first bit.
    109       int streamId = (w2 & 0x7fffffff); // 31-bit opaque identifier.
    110 
    111       switch (type) {
    112         case TYPE_DATA:
    113           readData(handler, length, flags, streamId);
    114           break;
    115 
    116         case TYPE_HEADERS:
    117           readHeaders(handler, length, flags, streamId);
    118           break;
    119 
    120         case TYPE_PRIORITY:
    121           readPriority(handler, length, flags, streamId);
    122           break;
    123 
    124         case TYPE_RST_STREAM:
    125           readRstStream(handler, length, flags, streamId);
    126           break;
    127 
    128         case TYPE_SETTINGS:
    129           readSettings(handler, length, flags, streamId);
    130           break;
    131 
    132         case TYPE_PUSH_PROMISE:
    133           readPushPromise(handler, length, flags, streamId);
    134           break;
    135 
    136         case TYPE_PING:
    137           readPing(handler, length, flags, streamId);
    138           break;
    139 
    140         case TYPE_GOAWAY:
    141           readGoAway(handler, length, flags, streamId);
    142           break;
    143 
    144         case TYPE_WINDOW_UPDATE:
    145           readWindowUpdate(handler, length, flags, streamId);
    146           break;
    147 
    148         default:
    149           // Implementations MUST ignore frames of unsupported or unrecognized types.
    150           source.skip(length);
    151       }
    152       return true;
    153     }
    154 
    155     private void readHeaders(Handler handler, short length, byte flags, int streamId)
    156         throws IOException {
    157       if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
    158 
    159       boolean endStream = (flags & FLAG_END_STREAM) != 0;
    160 
    161       int priority = -1;
    162       if ((flags & FLAG_PRIORITY) != 0) {
    163         priority = source.readInt() & 0x7fffffff;
    164         length -= 4; // account for above read.
    165       }
    166 
    167       List<Header> headerBlock = readHeaderBlock(length, flags, streamId);
    168 
    169       handler.headers(false, endStream, streamId, -1, priority, headerBlock,
    170           HeadersMode.HTTP_20_HEADERS);
    171     }
    172 
    173     private List<Header> readHeaderBlock(short length, byte flags, int streamId)
    174         throws IOException {
    175       continuation.length = continuation.left = length;
    176       continuation.flags = flags;
    177       continuation.streamId = streamId;
    178 
    179       hpackReader.readHeaders();
    180       hpackReader.emitReferenceSet();
    181       // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
    182       // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3
    183       return hpackReader.getAndReset();
    184     }
    185 
    186     private void readData(Handler handler, short length, byte flags, int streamId)
    187         throws IOException {
    188       boolean inFinished = (flags & FLAG_END_STREAM) != 0;
    189       // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
    190       handler.data(inFinished, streamId, source, length);
    191     }
    192 
    193     private void readPriority(Handler handler, short length, byte flags, int streamId)
    194         throws IOException {
    195       if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length);
    196       if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
    197       int w1 = source.readInt();
    198       // boolean r = (w1 & 0x80000000) != 0; // Reserved.
    199       int priority = (w1 & 0x7fffffff);
    200       handler.priority(streamId, priority);
    201     }
    202 
    203     private void readRstStream(Handler handler, short length, byte flags, int streamId)
    204         throws IOException {
    205       if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
    206       if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
    207       int errorCodeInt = source.readInt();
    208       ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
    209       if (errorCode == null) {
    210         throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
    211       }
    212       handler.rstStream(streamId, errorCode);
    213     }
    214 
    215     private void readSettings(Handler handler, short length, byte flags, int streamId)
    216         throws IOException {
    217       if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
    218       if ((flags & FLAG_ACK) != 0) {
    219         if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
    220         handler.ackSettings();
    221         return;
    222       }
    223 
    224       if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
    225       Settings settings = new Settings();
    226       for (int i = 0; i < length; i += 8) {
    227         int w1 = source.readInt();
    228         int value = source.readInt();
    229         // int r = (w1 & 0xff000000) >>> 24; // Reserved.
    230         int id = w1 & 0xffffff;
    231         settings.set(id, 0, value);
    232       }
    233       handler.settings(false, settings);
    234       if (settings.getHeaderTableSize() >= 0) {
    235         hpackReader.maxHeaderTableByteCount(settings.getHeaderTableSize());
    236       }
    237     }
    238 
    239     private void readPushPromise(Handler handler, short length, byte flags, int streamId)
    240         throws IOException {
    241       if (streamId == 0) {
    242         throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
    243       }
    244       int promisedStreamId = source.readInt() & 0x7fffffff;
    245       length -= 4; // account for above read.
    246       List<Header> headerBlock = readHeaderBlock(length, flags, streamId);
    247       handler.pushPromise(streamId, promisedStreamId, headerBlock);
    248     }
    249 
    250     private void readPing(Handler handler, short length, byte flags, int streamId)
    251         throws IOException {
    252       if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
    253       if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
    254       int payload1 = source.readInt();
    255       int payload2 = source.readInt();
    256       boolean ack = (flags & FLAG_ACK) != 0;
    257       handler.ping(ack, payload1, payload2);
    258     }
    259 
    260     private void readGoAway(Handler handler, short length, byte flags, int streamId)
    261         throws IOException {
    262       if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
    263       if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
    264       int lastStreamId = source.readInt();
    265       int errorCodeInt = source.readInt();
    266       int opaqueDataLength = length - 8;
    267       ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
    268       if (errorCode == null) {
    269         throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
    270       }
    271       ByteString debugData = ByteString.EMPTY;
    272       if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
    273         debugData = source.readByteString(opaqueDataLength);
    274       }
    275       handler.goAway(lastStreamId, errorCode, debugData);
    276     }
    277 
    278     private void readWindowUpdate(Handler handler, short length, byte flags, int streamId)
    279         throws IOException {
    280       if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
    281       long increment = (source.readInt() & 0x7fffffff);
    282       if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
    283       handler.windowUpdate(streamId, increment);
    284     }
    285 
    286     @Override public void close() throws IOException {
    287       source.close();
    288     }
    289   }
    290 
    291   static final class Writer implements FrameWriter {
    292     private final BufferedSink sink;
    293     private final boolean client;
    294     private final OkBuffer hpackBuffer;
    295     private final HpackDraft05.Writer hpackWriter;
    296     private boolean closed;
    297 
    298     Writer(BufferedSink sink, boolean client) {
    299       this.sink = sink;
    300       this.client = client;
    301       this.hpackBuffer = new OkBuffer();
    302       this.hpackWriter = new HpackDraft05.Writer(hpackBuffer);
    303     }
    304 
    305     @Override public synchronized void flush() throws IOException {
    306       if (closed) throw new IOException("closed");
    307       sink.flush();
    308     }
    309 
    310     @Override public synchronized void ackSettings() throws IOException {
    311       if (closed) throw new IOException("closed");
    312       int length = 0;
    313       byte type = TYPE_SETTINGS;
    314       byte flags = FLAG_ACK;
    315       int streamId = 0;
    316       frameHeader(length, type, flags, streamId);
    317       sink.flush();
    318     }
    319 
    320     @Override public synchronized void connectionHeader() throws IOException {
    321       if (closed) throw new IOException("closed");
    322       if (!client) return; // Nothing to write; servers don't send connection headers!
    323       sink.write(CONNECTION_HEADER.toByteArray());
    324       sink.flush();
    325     }
    326 
    327     @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
    328         int streamId, int associatedStreamId, int priority, int slot, List<Header> headerBlock)
    329         throws IOException {
    330       if (inFinished) throw new UnsupportedOperationException();
    331       if (closed) throw new IOException("closed");
    332       headers(outFinished, streamId, priority, headerBlock);
    333     }
    334 
    335     @Override public synchronized void synReply(boolean outFinished, int streamId,
    336         List<Header> headerBlock) throws IOException {
    337       if (closed) throw new IOException("closed");
    338       headers(outFinished, streamId, -1, headerBlock);
    339     }
    340 
    341     @Override public synchronized void headers(int streamId, List<Header> headerBlock)
    342         throws IOException {
    343       if (closed) throw new IOException("closed");
    344       headers(false, streamId, -1, headerBlock);
    345     }
    346 
    347     @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
    348         List<Header> requestHeaders) throws IOException {
    349       if (closed) throw new IOException("closed");
    350       if (hpackBuffer.size() != 0) throw new IllegalStateException();
    351       hpackWriter.writeHeaders(requestHeaders);
    352 
    353       int length = (int) (4 + hpackBuffer.size());
    354       byte type = TYPE_PUSH_PROMISE;
    355       byte flags = FLAG_END_HEADERS;
    356       frameHeader(length, type, flags, streamId); // TODO: CONTINUATION
    357       sink.writeInt(promisedStreamId & 0x7fffffff);
    358       sink.write(hpackBuffer, hpackBuffer.size());
    359     }
    360 
    361     private void headers(boolean outFinished, int streamId, int priority,
    362         List<Header> headerBlock) throws IOException {
    363       if (closed) throw new IOException("closed");
    364       if (hpackBuffer.size() != 0) throw new IllegalStateException();
    365       hpackWriter.writeHeaders(headerBlock);
    366 
    367       int length = (int) hpackBuffer.size();
    368       byte type = TYPE_HEADERS;
    369       byte flags = FLAG_END_HEADERS;
    370       if (outFinished) flags |= FLAG_END_STREAM;
    371       if (priority != -1) flags |= FLAG_PRIORITY;
    372       if (priority != -1) length += 4;
    373       frameHeader(length, type, flags, streamId); // TODO: CONTINUATION
    374       if (priority != -1) sink.writeInt(priority & 0x7fffffff);
    375       sink.write(hpackBuffer, hpackBuffer.size());
    376     }
    377 
    378     @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
    379         throws IOException {
    380       if (closed) throw new IOException("closed");
    381       if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
    382 
    383       int length = 4;
    384       byte type = TYPE_RST_STREAM;
    385       byte flags = FLAG_NONE;
    386       frameHeader(length, type, flags, streamId);
    387       sink.writeInt(errorCode.httpCode);
    388       sink.flush();
    389     }
    390 
    391     @Override public synchronized void data(boolean outFinished, int streamId, OkBuffer source)
    392         throws IOException {
    393       data(outFinished, streamId, source, (int) source.size());
    394     }
    395 
    396     @Override public synchronized void data(boolean outFinished, int streamId, OkBuffer source,
    397         int byteCount) throws IOException {
    398       if (closed) throw new IOException("closed");
    399       byte flags = FLAG_NONE;
    400       if (outFinished) flags |= FLAG_END_STREAM;
    401       dataFrame(streamId, flags, source, byteCount);
    402     }
    403 
    404     void dataFrame(int streamId, byte flags, OkBuffer buffer, int byteCount) throws IOException {
    405       byte type = TYPE_DATA;
    406       frameHeader(byteCount, type, flags, streamId);
    407       if (byteCount > 0) {
    408         sink.write(buffer, byteCount);
    409       }
    410     }
    411 
    412     @Override public synchronized void settings(Settings settings) throws IOException {
    413       if (closed) throw new IOException("closed");
    414       int length = settings.size() * 8;
    415       byte type = TYPE_SETTINGS;
    416       byte flags = FLAG_NONE;
    417       int streamId = 0;
    418       frameHeader(length, type, flags, streamId);
    419       for (int i = 0; i < Settings.COUNT; i++) {
    420         if (!settings.isSet(i)) continue;
    421         sink.writeInt(i & 0xffffff);
    422         sink.writeInt(settings.get(i));
    423       }
    424       sink.flush();
    425     }
    426 
    427     @Override public synchronized void ping(boolean ack, int payload1, int payload2)
    428         throws IOException {
    429       if (closed) throw new IOException("closed");
    430       int length = 8;
    431       byte type = TYPE_PING;
    432       byte flags = ack ? FLAG_ACK : FLAG_NONE;
    433       int streamId = 0;
    434       frameHeader(length, type, flags, streamId);
    435       sink.writeInt(payload1);
    436       sink.writeInt(payload2);
    437       sink.flush();
    438     }
    439 
    440     @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
    441         byte[] debugData) throws IOException {
    442       if (closed) throw new IOException("closed");
    443       if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
    444       int length = 8 + debugData.length;
    445       byte type = TYPE_GOAWAY;
    446       byte flags = FLAG_NONE;
    447       int streamId = 0;
    448       frameHeader(length, type, flags, streamId);
    449       sink.writeInt(lastGoodStreamId);
    450       sink.writeInt(errorCode.httpCode);
    451       if (debugData.length > 0) {
    452         sink.write(debugData);
    453       }
    454       sink.flush();
    455     }
    456 
    457     @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
    458         throws IOException {
    459       if (closed) throw new IOException("closed");
    460       if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
    461         throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
    462             windowSizeIncrement);
    463       }
    464       int length = 4;
    465       byte type = TYPE_WINDOW_UPDATE;
    466       byte flags = FLAG_NONE;
    467       frameHeader(length, type, flags, streamId);
    468       sink.writeInt((int) windowSizeIncrement);
    469       sink.flush();
    470     }
    471 
    472     @Override public synchronized void close() throws IOException {
    473       closed = true;
    474       sink.close();
    475     }
    476 
    477     void frameHeader(int length, byte type, byte flags, int streamId) throws IOException {
    478       if (length > 16383) throw illegalArgument("FRAME_SIZE_ERROR length > 16383: %s", length);
    479       if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
    480       sink.writeInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
    481       sink.writeInt(streamId & 0x7fffffff);
    482     }
    483   }
    484 
    485   private static IllegalArgumentException illegalArgument(String message, Object... args) {
    486     throw new IllegalArgumentException(String.format(message, args));
    487   }
    488 
    489   private static IOException ioException(String message, Object... args) throws IOException {
    490     throw new IOException(String.format(message, args));
    491   }
    492 
    493   /**
    494    * Decompression of the header block occurs above the framing layer. This
    495    * class lazily reads continuation frames as they are needed by {@link
    496    * HpackDraft05.Reader#readHeaders()}.
    497    */
    498   static final class ContinuationSource implements Source {
    499     private final BufferedSource source;
    500 
    501     int length;
    502     byte flags;
    503     int streamId;
    504 
    505     int left;
    506 
    507     public ContinuationSource(BufferedSource source) {
    508       this.source = source;
    509     }
    510 
    511     @Override public long read(OkBuffer sink, long byteCount) throws IOException {
    512       while (left == 0) {
    513         if ((flags & FLAG_END_HEADERS) != 0) return -1;
    514         readContinuationHeader();
    515         // TODO: test case for empty continuation header?
    516       }
    517 
    518       long read = source.read(sink, Math.min(byteCount, left));
    519       if (read == -1) return -1;
    520       left -= read;
    521       return read;
    522     }
    523 
    524     @Override public Source deadline(Deadline deadline) {
    525       source.deadline(deadline);
    526       return this;
    527     }
    528 
    529     @Override public void close() throws IOException {
    530     }
    531 
    532     private void readContinuationHeader() throws IOException {
    533       int previousStreamId = streamId;
    534       int w1 = source.readInt();
    535       int w2 = source.readInt();
    536       length = left = (short) ((w1 & 0x3fff0000) >> 16);
    537       byte type = (byte) ((w1 & 0xff00) >> 8);
    538       flags = (byte) (w1 & 0xff);
    539       streamId = (w2 & 0x7fffffff);
    540       if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
    541       if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
    542     }
    543   }
    544 }
    545