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 */
16package com.squareup.okhttp.internal.framed;
17
18import com.squareup.okhttp.Protocol;
19import java.io.IOException;
20import java.util.List;
21import java.util.logging.Logger;
22import okio.Buffer;
23import okio.BufferedSink;
24import okio.BufferedSource;
25import okio.ByteString;
26import okio.Source;
27import okio.Timeout;
28
29import static com.squareup.okhttp.internal.framed.Http2.FrameLogger.formatHeader;
30import static java.lang.String.format;
31import static java.util.logging.Level.FINE;
32import 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 */
42public 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