Home | History | Annotate | Download | only in framed
      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.framed;
     17 
     18 import com.squareup.okhttp.Protocol;
     19 import java.io.IOException;
     20 import java.util.List;
     21 import java.util.logging.Logger;
     22 import okio.Buffer;
     23 import okio.BufferedSink;
     24 import okio.BufferedSource;
     25 import okio.ByteString;
     26 import okio.Source;
     27 import okio.Timeout;
     28 
     29 import static com.squareup.okhttp.internal.framed.Http2.FrameLogger.formatHeader;
     30 import static java.lang.String.format;
     31 import static java.util.logging.Level.FINE;
     32 import static okio.ByteString.EMPTY;
     33 
     34 /**
     35  * Read and write HTTP/2 frames.
     36  * <p>
     37  * This implementation assumes we do not send an increased
     38  * {@link Settings#getMaxFrameSize frame size setting} to the peer. Hence, we
     39  * expect all frames to have a max length of {@link #INITIAL_MAX_FRAME_SIZE}.
     40  * <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-17
     41  */
     42 public final class Http2 implements Variant {
     43   private static final Logger logger = Logger.getLogger(FrameLogger.class.getName());
     44 
     45   @Override public Protocol getProtocol() {
     46     return Protocol.HTTP_2;
     47   }
     48 
     49   private static final ByteString CONNECTION_PREFACE
     50       = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
     51 
     52   /** The initial max frame size, applied independently writing to, or reading from the peer. */
     53   static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384
     54 
     55   static final byte TYPE_DATA = 0x0;
     56   static final byte TYPE_HEADERS = 0x1;
     57   static final byte TYPE_PRIORITY = 0x2;
     58   static final byte TYPE_RST_STREAM = 0x3;
     59   static final byte TYPE_SETTINGS = 0x4;
     60   static final byte TYPE_PUSH_PROMISE = 0x5;
     61   static final byte TYPE_PING = 0x6;
     62   static final byte TYPE_GOAWAY = 0x7;
     63   static final byte TYPE_WINDOW_UPDATE = 0x8;
     64   static final byte TYPE_CONTINUATION = 0x9;
     65 
     66   static final byte FLAG_NONE = 0x0;
     67   static final byte FLAG_ACK = 0x1; // Used for settings and ping.
     68   static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
     69   static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
     70   static final byte FLAG_END_PUSH_PROMISE = 0x4;
     71   static final byte FLAG_PADDED = 0x8; // Used for headers and data.
     72   static final byte FLAG_PRIORITY = 0x20; // Used for headers.
     73   static final byte FLAG_COMPRESSED = 0x20; // Used for data.
     74 
     75   /**
     76    * Creates a frame reader with max header table size of 4096 and data frame
     77    * compression disabled.
     78    */
     79   @Override public FrameReader newReader(BufferedSource source, boolean client) {
     80     return new Reader(source, 4096, client);
     81   }
     82 
     83   @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
     84     return new Writer(sink, client);
     85   }
     86 
     87   static final class Reader implements FrameReader {
     88     private final BufferedSource source;
     89     private final ContinuationSource continuation;
     90     private final boolean client;
     91 
     92     // Visible for testing.
     93     final Hpack.Reader hpackReader;
     94 
     95     Reader(BufferedSource source, int headerTableSize, boolean client) {
     96       this.source = source;
     97       this.client = client;
     98       this.continuation = new ContinuationSource(this.source);
     99       this.hpackReader = new Hpack.Reader(headerTableSize, continuation);
    100     }
    101 
    102     @Override public void readConnectionPreface() throws IOException {
    103       if (client) return; // Nothing to read; servers doesn't send a connection preface!
    104       ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
    105       if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
    106       if (!CONNECTION_PREFACE.equals(connectionPreface)) {
    107         throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
    108       }
    109     }
    110 
    111     @Override public boolean nextFrame(Handler handler) throws IOException {
    112       try {
    113         source.require(9); // Frame header size
    114       } catch (IOException e) {
    115         return false; // This might be a normal socket close.
    116       }
    117 
    118       /*  0                   1                   2                   3
    119        *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    120        * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    121        * |                 Length (24)                   |
    122        * +---------------+---------------+---------------+
    123        * |   Type (8)    |   Flags (8)   |
    124        * +-+-+-----------+---------------+-------------------------------+
    125        * |R|                 Stream Identifier (31)                      |
    126        * +=+=============================================================+
    127        * |                   Frame Payload (0...)                      ...
    128        * +---------------------------------------------------------------+
    129        */
    130       int length = readMedium(source);
    131       if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
    132         throw ioException("FRAME_SIZE_ERROR: %s", length);
    133       }
    134       byte type = (byte) (source.readByte() & 0xff);
    135       byte flags = (byte) (source.readByte() & 0xff);
    136       int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
    137       if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
    138 
    139       switch (type) {
    140         case TYPE_DATA:
    141           readData(handler, length, flags, streamId);
    142           break;
    143 
    144         case TYPE_HEADERS:
    145           readHeaders(handler, length, flags, streamId);
    146           break;
    147 
    148         case TYPE_PRIORITY:
    149           readPriority(handler, length, flags, streamId);
    150           break;
    151 
    152         case TYPE_RST_STREAM:
    153           readRstStream(handler, length, flags, streamId);
    154           break;
    155 
    156         case TYPE_SETTINGS:
    157           readSettings(handler, length, flags, streamId);
    158           break;
    159 
    160         case TYPE_PUSH_PROMISE:
    161           readPushPromise(handler, length, flags, streamId);
    162           break;
    163 
    164         case TYPE_PING:
    165           readPing(handler, length, flags, streamId);
    166           break;
    167 
    168         case TYPE_GOAWAY:
    169           readGoAway(handler, length, flags, streamId);
    170           break;
    171 
    172         case TYPE_WINDOW_UPDATE:
    173           readWindowUpdate(handler, length, flags, streamId);
    174           break;
    175 
    176         default:
    177           // Implementations MUST discard frames that have unknown or unsupported types.
    178           source.skip(length);
    179       }
    180       return true;
    181     }
    182 
    183     private void readHeaders(Handler handler, int length, byte flags, int streamId)
    184         throws IOException {
    185       if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
    186 
    187       boolean endStream = (flags & FLAG_END_STREAM) != 0;
    188 
    189       short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
    190 
    191       if ((flags & FLAG_PRIORITY) != 0) {
    192         readPriority(handler, streamId);
    193         length -= 5; // account for above read.
    194       }
    195 
    196       length = lengthWithoutPadding(length, flags, padding);
    197 
    198       List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
    199 
    200       handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
    201     }
    202 
    203     private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)
    204         throws IOException {
    205       continuation.length = continuation.left = length;
    206       continuation.padding = padding;
    207       continuation.flags = flags;
    208       continuation.streamId = streamId;
    209 
    210       // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
    211       // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5
    212       hpackReader.readHeaders();
    213       return hpackReader.getAndResetHeaderList();
    214     }
    215 
    216     private void readData(Handler handler, int length, byte flags, int streamId)
    217         throws IOException {
    218       // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
    219       boolean inFinished = (flags & FLAG_END_STREAM) != 0;
    220       boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
    221       if (gzipped) {
    222         throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
    223       }
    224 
    225       short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
    226       length = lengthWithoutPadding(length, flags, padding);
    227 
    228       handler.data(inFinished, streamId, source, length);
    229       source.skip(padding);
    230     }
    231 
    232     private void readPriority(Handler handler, int length, byte flags, int streamId)
    233         throws IOException {
    234       if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
    235       if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
    236       readPriority(handler, streamId);
    237     }
    238 
    239     private void readPriority(Handler handler, int streamId) throws IOException {
    240       int w1 = source.readInt();
    241       boolean exclusive = (w1 & 0x80000000) != 0;
    242       int streamDependency = (w1 & 0x7fffffff);
    243       int weight = (source.readByte() & 0xff) + 1;
    244       handler.priority(streamId, streamDependency, weight, exclusive);
    245     }
    246 
    247     private void readRstStream(Handler handler, int length, byte flags, int streamId)
    248         throws IOException {
    249       if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
    250       if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
    251       int errorCodeInt = source.readInt();
    252       ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
    253       if (errorCode == null) {
    254         throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
    255       }
    256       handler.rstStream(streamId, errorCode);
    257     }
    258 
    259     private void readSettings(Handler handler, int length, byte flags, int streamId)
    260         throws IOException {
    261       if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
    262       if ((flags & FLAG_ACK) != 0) {
    263         if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
    264         handler.ackSettings();
    265         return;
    266       }
    267 
    268       if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
    269       Settings settings = new Settings();
    270       for (int i = 0; i < length; i += 6) {
    271         short id = source.readShort();
    272         int value = source.readInt();
    273 
    274         switch (id) {
    275           case 1: // SETTINGS_HEADER_TABLE_SIZE
    276             break;
    277           case 2: // SETTINGS_ENABLE_PUSH
    278             if (value != 0 && value != 1) {
    279               throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
    280             }
    281             break;
    282           case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
    283             id = 4; // Renumbered in draft 10.
    284             break;
    285           case 4: // SETTINGS_INITIAL_WINDOW_SIZE
    286             id = 7; // Renumbered in draft 10.
    287             if (value < 0) {
    288               throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
    289             }
    290             break;
    291           case 5: // SETTINGS_MAX_FRAME_SIZE
    292             if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
    293               throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value);
    294             }
    295             break;
    296           case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
    297             break; // Advisory only, so ignored.
    298           default:
    299             throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
    300         }
    301         settings.set(id, 0, value);
    302       }
    303       handler.settings(false, settings);
    304       if (settings.getHeaderTableSize() >= 0) {
    305         hpackReader.headerTableSizeSetting(settings.getHeaderTableSize());
    306       }
    307     }
    308 
    309     private void readPushPromise(Handler handler, int length, byte flags, int streamId)
    310         throws IOException {
    311       if (streamId == 0) {
    312         throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
    313       }
    314       short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
    315       int promisedStreamId = source.readInt() & 0x7fffffff;
    316       length -= 4; // account for above read.
    317       length = lengthWithoutPadding(length, flags, padding);
    318       List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
    319       handler.pushPromise(streamId, promisedStreamId, headerBlock);
    320     }
    321 
    322     private void readPing(Handler handler, int length, byte flags, int streamId)
    323         throws IOException {
    324       if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
    325       if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
    326       int payload1 = source.readInt();
    327       int payload2 = source.readInt();
    328       boolean ack = (flags & FLAG_ACK) != 0;
    329       handler.ping(ack, payload1, payload2);
    330     }
    331 
    332     private void readGoAway(Handler handler, int length, byte flags, int streamId)
    333         throws IOException {
    334       if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
    335       if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
    336       int lastStreamId = source.readInt();
    337       int errorCodeInt = source.readInt();
    338       int opaqueDataLength = length - 8;
    339       ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
    340       if (errorCode == null) {
    341         throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
    342       }
    343       ByteString debugData = EMPTY;
    344       if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
    345         debugData = source.readByteString(opaqueDataLength);
    346       }
    347       handler.goAway(lastStreamId, errorCode, debugData);
    348     }
    349 
    350     private void readWindowUpdate(Handler handler, int length, byte flags, int streamId)
    351         throws IOException {
    352       if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
    353       long increment = (source.readInt() & 0x7fffffffL);
    354       if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
    355       handler.windowUpdate(streamId, increment);
    356     }
    357 
    358     @Override public void close() throws IOException {
    359       source.close();
    360     }
    361   }
    362 
    363   static final class Writer implements FrameWriter {
    364     private final BufferedSink sink;
    365     private final boolean client;
    366     private final Buffer hpackBuffer;
    367     private final Hpack.Writer hpackWriter;
    368     private int maxFrameSize;
    369     private boolean closed;
    370 
    371     Writer(BufferedSink sink, boolean client) {
    372       this.sink = sink;
    373       this.client = client;
    374       this.hpackBuffer = new Buffer();
    375       this.hpackWriter = new Hpack.Writer(hpackBuffer);
    376       this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
    377     }
    378 
    379     @Override public synchronized void flush() throws IOException {
    380       if (closed) throw new IOException("closed");
    381       sink.flush();
    382     }
    383 
    384     @Override public synchronized void ackSettings(Settings peerSettings) throws IOException {
    385       if (closed) throw new IOException("closed");
    386       this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize);
    387       int length = 0;
    388       byte type = TYPE_SETTINGS;
    389       byte flags = FLAG_ACK;
    390       int streamId = 0;
    391       frameHeader(streamId, length, type, flags);
    392       sink.flush();
    393     }
    394 
    395     @Override public synchronized void connectionPreface() throws IOException {
    396       if (closed) throw new IOException("closed");
    397       if (!client) return; // Nothing to write; servers don't send connection headers!
    398       if (logger.isLoggable(FINE)) {
    399         logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
    400       }
    401       sink.write(CONNECTION_PREFACE.toByteArray());
    402       sink.flush();
    403     }
    404 
    405     @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
    406         int streamId, int associatedStreamId, List<Header> headerBlock)
    407         throws IOException {
    408       if (inFinished) throw new UnsupportedOperationException();
    409       if (closed) throw new IOException("closed");
    410       headers(outFinished, streamId, headerBlock);
    411     }
    412 
    413     @Override public synchronized void synReply(boolean outFinished, int streamId,
    414         List<Header> headerBlock) throws IOException {
    415       if (closed) throw new IOException("closed");
    416       headers(outFinished, streamId, headerBlock);
    417     }
    418 
    419     @Override public synchronized void headers(int streamId, List<Header> headerBlock)
    420         throws IOException {
    421       if (closed) throw new IOException("closed");
    422       headers(false, streamId, headerBlock);
    423     }
    424 
    425     @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
    426         List<Header> requestHeaders) throws IOException {
    427       if (closed) throw new IOException("closed");
    428       hpackWriter.writeHeaders(requestHeaders);
    429 
    430       long byteCount = hpackBuffer.size();
    431       int length = (int) Math.min(maxFrameSize - 4, byteCount);
    432       byte type = TYPE_PUSH_PROMISE;
    433       byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
    434       frameHeader(streamId, length + 4, type, flags);
    435       sink.writeInt(promisedStreamId & 0x7fffffff);
    436       sink.write(hpackBuffer, length);
    437 
    438       if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
    439     }
    440 
    441     void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
    442       if (closed) throw new IOException("closed");
    443       hpackWriter.writeHeaders(headerBlock);
    444 
    445       long byteCount = hpackBuffer.size();
    446       int length = (int) Math.min(maxFrameSize, byteCount);
    447       byte type = TYPE_HEADERS;
    448       byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
    449       if (outFinished) flags |= FLAG_END_STREAM;
    450       frameHeader(streamId, length, type, flags);
    451       sink.write(hpackBuffer, length);
    452 
    453       if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
    454     }
    455 
    456     private void writeContinuationFrames(int streamId, long byteCount) throws IOException {
    457       while (byteCount > 0) {
    458         int length = (int) Math.min(maxFrameSize, byteCount);
    459         byteCount -= length;
    460         frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0);
    461         sink.write(hpackBuffer, length);
    462       }
    463     }
    464 
    465     @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
    466         throws IOException {
    467       if (closed) throw new IOException("closed");
    468       if (errorCode.httpCode == -1) throw new IllegalArgumentException();
    469 
    470       int length = 4;
    471       byte type = TYPE_RST_STREAM;
    472       byte flags = FLAG_NONE;
    473       frameHeader(streamId, length, type, flags);
    474       sink.writeInt(errorCode.httpCode);
    475       sink.flush();
    476     }
    477 
    478     @Override public int maxDataLength() {
    479       return maxFrameSize;
    480     }
    481 
    482     @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
    483         int byteCount) throws IOException {
    484       if (closed) throw new IOException("closed");
    485       byte flags = FLAG_NONE;
    486       if (outFinished) flags |= FLAG_END_STREAM;
    487       dataFrame(streamId, flags, source, byteCount);
    488     }
    489 
    490     void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
    491       byte type = TYPE_DATA;
    492       frameHeader(streamId, byteCount, type, flags);
    493       if (byteCount > 0) {
    494         sink.write(buffer, byteCount);
    495       }
    496     }
    497 
    498     @Override public synchronized void settings(Settings settings) throws IOException {
    499       if (closed) throw new IOException("closed");
    500       int length = settings.size() * 6;
    501       byte type = TYPE_SETTINGS;
    502       byte flags = FLAG_NONE;
    503       int streamId = 0;
    504       frameHeader(streamId, length, type, flags);
    505       for (int i = 0; i < Settings.COUNT; i++) {
    506         if (!settings.isSet(i)) continue;
    507         int id = i;
    508         if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
    509         else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
    510         sink.writeShort(id);
    511         sink.writeInt(settings.get(i));
    512       }
    513       sink.flush();
    514     }
    515 
    516     @Override public synchronized void ping(boolean ack, int payload1, int payload2)
    517         throws IOException {
    518       if (closed) throw new IOException("closed");
    519       int length = 8;
    520       byte type = TYPE_PING;
    521       byte flags = ack ? FLAG_ACK : FLAG_NONE;
    522       int streamId = 0;
    523       frameHeader(streamId, length, type, flags);
    524       sink.writeInt(payload1);
    525       sink.writeInt(payload2);
    526       sink.flush();
    527     }
    528 
    529     @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
    530         byte[] debugData) throws IOException {
    531       if (closed) throw new IOException("closed");
    532       if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
    533       int length = 8 + debugData.length;
    534       byte type = TYPE_GOAWAY;
    535       byte flags = FLAG_NONE;
    536       int streamId = 0;
    537       frameHeader(streamId, length, type, flags);
    538       sink.writeInt(lastGoodStreamId);
    539       sink.writeInt(errorCode.httpCode);
    540       if (debugData.length > 0) {
    541         sink.write(debugData);
    542       }
    543       sink.flush();
    544     }
    545 
    546     @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
    547         throws IOException {
    548       if (closed) throw new IOException("closed");
    549       if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
    550         throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
    551             windowSizeIncrement);
    552       }
    553       int length = 4;
    554       byte type = TYPE_WINDOW_UPDATE;
    555       byte flags = FLAG_NONE;
    556       frameHeader(streamId, length, type, flags);
    557       sink.writeInt((int) windowSizeIncrement);
    558       sink.flush();
    559     }
    560 
    561     @Override public synchronized void close() throws IOException {
    562       closed = true;
    563       sink.close();
    564     }
    565 
    566     void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {
    567       if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags));
    568       if (length > maxFrameSize) {
    569         throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length);
    570       }
    571       if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
    572       writeMedium(sink, length);
    573       sink.writeByte(type & 0xff);
    574       sink.writeByte(flags & 0xff);
    575       sink.writeInt(streamId & 0x7fffffff);
    576     }
    577   }
    578 
    579   private static IllegalArgumentException illegalArgument(String message, Object... args) {
    580     throw new IllegalArgumentException(format(message, args));
    581   }
    582 
    583   private static IOException ioException(String message, Object... args) throws IOException {
    584     throw new IOException(format(message, args));
    585   }
    586 
    587   /**
    588    * Decompression of the header block occurs above the framing layer. This
    589    * class lazily reads continuation frames as they are needed by {@link
    590    * Hpack.Reader#readHeaders()}.
    591    */
    592   static final class ContinuationSource implements Source {
    593     private final BufferedSource source;
    594 
    595     int length;
    596     byte flags;
    597     int streamId;
    598 
    599     int left;
    600     short padding;
    601 
    602     public ContinuationSource(BufferedSource source) {
    603       this.source = source;
    604     }
    605 
    606     @Override public long read(Buffer sink, long byteCount) throws IOException {
    607       while (left == 0) {
    608         source.skip(padding);
    609         padding = 0;
    610         if ((flags & FLAG_END_HEADERS) != 0) return -1;
    611         readContinuationHeader();
    612         // TODO: test case for empty continuation header?
    613       }
    614 
    615       long read = source.read(sink, Math.min(byteCount, left));
    616       if (read == -1) return -1;
    617       left -= read;
    618       return read;
    619     }
    620 
    621     @Override public Timeout timeout() {
    622       return source.timeout();
    623     }
    624 
    625     @Override public void close() throws IOException {
    626     }
    627 
    628     private void readContinuationHeader() throws IOException {
    629       int previousStreamId = streamId;
    630 
    631       length = left = readMedium(source);
    632       byte type = (byte) (source.readByte() & 0xff);
    633       flags = (byte) (source.readByte() & 0xff);
    634       if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
    635       streamId = (source.readInt() & 0x7fffffff);
    636       if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
    637       if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
    638     }
    639   }
    640 
    641   private static int lengthWithoutPadding(int length, byte flags, short padding)
    642       throws IOException {
    643     if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length.
    644     if (padding > length) {
    645       throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
    646     }
    647     return (short) (length - padding);
    648   }
    649 
    650   /**
    651    * Logs a human-readable representation of HTTP/2 frame headers.
    652    *
    653    * <p>The format is:
    654    *
    655    * <pre>
    656    *   direction streamID length type flags
    657    * </pre>
    658    * Where direction is {@code <<} for inbound and {@code >>} for outbound.
    659    *
    660    * <p> For example, the following would indicate a HEAD request sent from
    661    * the client.
    662    * <pre>
    663    * {@code
    664    *   << 0x0000000f    12 HEADERS       END_HEADERS|END_STREAM
    665    * }
    666    * </pre>
    667    */
    668   static final class FrameLogger {
    669 
    670     static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) {
    671       String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type);
    672       String formattedFlags = formatFlags(type, flags);
    673       return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length,
    674           formattedType, formattedFlags);
    675     }
    676 
    677     /**
    678      * Looks up valid string representing flags from the table. Invalid
    679      * combinations are represented in binary.
    680      */
    681     // Visible for testing.
    682     static String formatFlags(byte type, byte flags) {
    683       if (flags == 0) return "";
    684       switch (type) { // Special case types that have 0 or 1 flag.
    685         case TYPE_SETTINGS:
    686         case TYPE_PING:
    687           return flags == FLAG_ACK ? "ACK" : BINARY[flags];
    688         case TYPE_PRIORITY:
    689         case TYPE_RST_STREAM:
    690         case TYPE_GOAWAY:
    691         case TYPE_WINDOW_UPDATE:
    692           return BINARY[flags];
    693       }
    694       String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags];
    695       // Special case types that have overlap flag values.
    696       if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) {
    697         return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation.
    698       } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) {
    699         return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation.
    700       }
    701       return result;
    702     }
    703 
    704     /** Lookup table for valid frame types. */
    705     private static final String[] TYPES = new String[] {
    706         "DATA",
    707         "HEADERS",
    708         "PRIORITY",
    709         "RST_STREAM",
    710         "SETTINGS",
    711         "PUSH_PROMISE",
    712         "PING",
    713         "GOAWAY",
    714         "WINDOW_UPDATE",
    715         "CONTINUATION"
    716     };
    717 
    718     /**
    719      * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid
    720      * combinations are represented in binary.
    721      */
    722     private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20.
    723     private static final String[] BINARY = new String[256];
    724 
    725     static {
    726       for (int i = 0; i < BINARY.length; i++) {
    727         BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0');
    728       }
    729 
    730       FLAGS[FLAG_NONE] = "";
    731       FLAGS[FLAG_END_STREAM] = "END_STREAM";
    732 
    733       int[] prefixFlags = new int[] {FLAG_END_STREAM};
    734 
    735       FLAGS[FLAG_PADDED] = "PADDED";
    736       for (int prefixFlag : prefixFlags) {
    737          FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED";
    738       }
    739 
    740       FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE.
    741       FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED.
    742       FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS.
    743       int[] frameFlags =
    744           new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY};
    745 
    746       for (int frameFlag : frameFlags) {
    747         for (int prefixFlag : prefixFlags) {
    748           FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag];
    749           FLAGS[prefixFlag | frameFlag | FLAG_PADDED] =
    750               FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED";
    751         }
    752       }
    753 
    754       for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation.
    755         if (FLAGS[i] == null) FLAGS[i] = BINARY[i];
    756       }
    757     }
    758   }
    759 
    760   private static int readMedium(BufferedSource source) throws IOException {
    761     return (source.readByte() & 0xff) << 16
    762         |  (source.readByte() & 0xff) <<  8
    763         |  (source.readByte() & 0xff);
    764   }
    765 
    766   private static void writeMedium(BufferedSink sink, int i) throws IOException {
    767     sink.writeByte((i >>> 16) & 0xff);
    768     sink.writeByte((i >>>  8) & 0xff);
    769     sink.writeByte(i          & 0xff);
    770   }
    771 }
    772