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.internal.Util;
     19 import java.io.IOException;
     20 import java.util.Arrays;
     21 import java.util.List;
     22 import okio.Buffer;
     23 import okio.BufferedSink;
     24 import okio.BufferedSource;
     25 import okio.ByteString;
     26 import okio.GzipSink;
     27 import okio.Okio;
     28 import org.junit.Test;
     29 
     30 import static com.squareup.okhttp.TestUtil.headerEntries;
     31 import static com.squareup.okhttp.internal.framed.Http2.FLAG_COMPRESSED;
     32 import static com.squareup.okhttp.internal.framed.Http2.FLAG_END_HEADERS;
     33 import static com.squareup.okhttp.internal.framed.Http2.FLAG_END_STREAM;
     34 import static com.squareup.okhttp.internal.framed.Http2.FLAG_NONE;
     35 import static com.squareup.okhttp.internal.framed.Http2.FLAG_PADDED;
     36 import static com.squareup.okhttp.internal.framed.Http2.FLAG_PRIORITY;
     37 import static org.junit.Assert.assertEquals;
     38 import static org.junit.Assert.assertFalse;
     39 import static org.junit.Assert.assertTrue;
     40 import static org.junit.Assert.fail;
     41 
     42 public class Http2Test {
     43   final Buffer frame = new Buffer();
     44   final FrameReader fr = new Http2.Reader(frame, 4096, false);
     45   final int expectedStreamId = 15;
     46 
     47   @Test public void unknownFrameTypeSkipped() throws IOException {
     48     writeMedium(frame, 4); // has a 4-byte field
     49     frame.writeByte(99); // type 99
     50     frame.writeByte(Http2.FLAG_NONE);
     51     frame.writeInt(expectedStreamId);
     52     frame.writeInt(111111111); // custom data
     53 
     54     fr.nextFrame(new BaseTestHandler()); // Should not callback.
     55   }
     56 
     57   @Test public void onlyOneLiteralHeadersFrame() throws IOException {
     58     final List<Header> sentHeaders = headerEntries("name", "value");
     59 
     60     Buffer headerBytes = literalHeaders(sentHeaders);
     61     writeMedium(frame, (int) headerBytes.size());
     62     frame.writeByte(Http2.TYPE_HEADERS);
     63     frame.writeByte(FLAG_END_HEADERS | FLAG_END_STREAM);
     64     frame.writeInt(expectedStreamId & 0x7fffffff);
     65     frame.writeAll(headerBytes);
     66 
     67     assertEquals(frame, sendHeaderFrames(true, sentHeaders)); // Check writer sends the same bytes.
     68 
     69     fr.nextFrame(new BaseTestHandler() {
     70       @Override
     71       public void headers(boolean outFinished, boolean inFinished, int streamId,
     72           int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
     73         assertFalse(outFinished);
     74         assertTrue(inFinished);
     75         assertEquals(expectedStreamId, streamId);
     76         assertEquals(-1, associatedStreamId);
     77         assertEquals(sentHeaders, headerBlock);
     78         assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
     79       }
     80     });
     81   }
     82 
     83   @Test public void headersWithPriority() throws IOException {
     84     final List<Header> sentHeaders = headerEntries("name", "value");
     85 
     86     Buffer headerBytes = literalHeaders(sentHeaders);
     87     writeMedium(frame, (int) (headerBytes.size() + 5));
     88     frame.writeByte(Http2.TYPE_HEADERS);
     89     frame.writeByte(FLAG_END_HEADERS | FLAG_PRIORITY);
     90     frame.writeInt(expectedStreamId & 0x7fffffff);
     91     frame.writeInt(0); // Independent stream.
     92     frame.writeByte(255); // Heaviest weight, zero-indexed.
     93     frame.writeAll(headerBytes);
     94 
     95     fr.nextFrame(new BaseTestHandler() {
     96       @Override public void priority(int streamId, int streamDependency, int weight,
     97           boolean exclusive) {
     98         assertEquals(0, streamDependency);
     99         assertEquals(256, weight);
    100         assertFalse(exclusive);
    101       }
    102 
    103       @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
    104           int associatedStreamId, List<Header> nameValueBlock,
    105           HeadersMode headersMode) {
    106         assertFalse(outFinished);
    107         assertFalse(inFinished);
    108         assertEquals(expectedStreamId, streamId);
    109         assertEquals(-1, associatedStreamId);
    110         assertEquals(sentHeaders, nameValueBlock);
    111         assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
    112       }
    113     });
    114   }
    115 
    116   /** Headers are compressed, then framed. */
    117   @Test public void headersFrameThenContinuation() throws IOException {
    118     final List<Header> sentHeaders = largeHeaders();
    119 
    120     Buffer headerBlock = literalHeaders(sentHeaders);
    121 
    122     // Write the first headers frame.
    123     writeMedium(frame, Http2.INITIAL_MAX_FRAME_SIZE);
    124     frame.writeByte(Http2.TYPE_HEADERS);
    125     frame.writeByte(Http2.FLAG_NONE);
    126     frame.writeInt(expectedStreamId & 0x7fffffff);
    127     frame.write(headerBlock, Http2.INITIAL_MAX_FRAME_SIZE);
    128 
    129     // Write the continuation frame, specifying no more frames are expected.
    130     writeMedium(frame, (int) headerBlock.size());
    131     frame.writeByte(Http2.TYPE_CONTINUATION);
    132     frame.writeByte(FLAG_END_HEADERS);
    133     frame.writeInt(expectedStreamId & 0x7fffffff);
    134     frame.writeAll(headerBlock);
    135 
    136     assertEquals(frame, sendHeaderFrames(false, sentHeaders)); // Check writer sends the same bytes.
    137 
    138     // Reading the above frames should result in a concatenated headerBlock.
    139     fr.nextFrame(new BaseTestHandler() {
    140       @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
    141           int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
    142         assertFalse(outFinished);
    143         assertFalse(inFinished);
    144         assertEquals(expectedStreamId, streamId);
    145         assertEquals(-1, associatedStreamId);
    146         assertEquals(sentHeaders, headerBlock);
    147         assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
    148       }
    149     });
    150   }
    151 
    152   @Test public void pushPromise() throws IOException {
    153     final int expectedPromisedStreamId = 11;
    154 
    155     final List<Header> pushPromise = Arrays.asList(
    156         new Header(Header.TARGET_METHOD, "GET"),
    157         new Header(Header.TARGET_SCHEME, "https"),
    158         new Header(Header.TARGET_AUTHORITY, "squareup.com"),
    159         new Header(Header.TARGET_PATH, "/")
    160     );
    161 
    162     // Write the push promise frame, specifying the associated stream ID.
    163     Buffer headerBytes = literalHeaders(pushPromise);
    164     writeMedium(frame, (int) (headerBytes.size() + 4));
    165     frame.writeByte(Http2.TYPE_PUSH_PROMISE);
    166     frame.writeByte(Http2.FLAG_END_PUSH_PROMISE);
    167     frame.writeInt(expectedStreamId & 0x7fffffff);
    168     frame.writeInt(expectedPromisedStreamId & 0x7fffffff);
    169     frame.writeAll(headerBytes);
    170 
    171     assertEquals(frame, sendPushPromiseFrames(expectedPromisedStreamId, pushPromise));
    172 
    173     fr.nextFrame(new BaseTestHandler() {
    174       @Override
    175       public void pushPromise(int streamId, int promisedStreamId, List<Header> headerBlock) {
    176         assertEquals(expectedStreamId, streamId);
    177         assertEquals(expectedPromisedStreamId, promisedStreamId);
    178         assertEquals(pushPromise, headerBlock);
    179       }
    180     });
    181   }
    182 
    183   /** Headers are compressed, then framed. */
    184   @Test public void pushPromiseThenContinuation() throws IOException {
    185     final int expectedPromisedStreamId = 11;
    186     final List<Header> pushPromise = largeHeaders();
    187 
    188     // Decoding the first header will cross frame boundaries.
    189     Buffer headerBlock = literalHeaders(pushPromise);
    190 
    191     // Write the first headers frame.
    192     writeMedium(frame, Http2.INITIAL_MAX_FRAME_SIZE);
    193     frame.writeByte(Http2.TYPE_PUSH_PROMISE);
    194     frame.writeByte(Http2.FLAG_NONE);
    195     frame.writeInt(expectedStreamId & 0x7fffffff);
    196     frame.writeInt(expectedPromisedStreamId & 0x7fffffff);
    197     frame.write(headerBlock, Http2.INITIAL_MAX_FRAME_SIZE - 4);
    198 
    199     // Write the continuation frame, specifying no more frames are expected.
    200     writeMedium(frame, (int) headerBlock.size());
    201     frame.writeByte(Http2.TYPE_CONTINUATION);
    202     frame.writeByte(FLAG_END_HEADERS);
    203     frame.writeInt(expectedStreamId & 0x7fffffff);
    204     frame.writeAll(headerBlock);
    205 
    206     assertEquals(frame, sendPushPromiseFrames(expectedPromisedStreamId, pushPromise));
    207 
    208     // Reading the above frames should result in a concatenated headerBlock.
    209     fr.nextFrame(new BaseTestHandler() {
    210       @Override
    211       public void pushPromise(int streamId, int promisedStreamId, List<Header> headerBlock) {
    212         assertEquals(expectedStreamId, streamId);
    213         assertEquals(expectedPromisedStreamId, promisedStreamId);
    214         assertEquals(pushPromise, headerBlock);
    215       }
    216     });
    217   }
    218 
    219   @Test public void readRstStreamFrame() throws IOException {
    220     writeMedium(frame, 4);
    221     frame.writeByte(Http2.TYPE_RST_STREAM);
    222     frame.writeByte(Http2.FLAG_NONE);
    223     frame.writeInt(expectedStreamId & 0x7fffffff);
    224     frame.writeInt(ErrorCode.COMPRESSION_ERROR.httpCode);
    225 
    226     fr.nextFrame(new BaseTestHandler() {
    227       @Override public void rstStream(int streamId, ErrorCode errorCode) {
    228         assertEquals(expectedStreamId, streamId);
    229         assertEquals(ErrorCode.COMPRESSION_ERROR, errorCode);
    230       }
    231     });
    232   }
    233 
    234   @Test public void readSettingsFrame() throws IOException {
    235     final int reducedTableSizeBytes = 16;
    236 
    237     writeMedium(frame, 12); // 2 settings * 6 bytes (2 for the code and 4 for the value).
    238     frame.writeByte(Http2.TYPE_SETTINGS);
    239     frame.writeByte(Http2.FLAG_NONE);
    240     frame.writeInt(0); // Settings are always on the connection stream 0.
    241     frame.writeShort(1); // SETTINGS_HEADER_TABLE_SIZE
    242     frame.writeInt(reducedTableSizeBytes);
    243     frame.writeShort(2); // SETTINGS_ENABLE_PUSH
    244     frame.writeInt(0);
    245 
    246     fr.nextFrame(new BaseTestHandler() {
    247       @Override public void settings(boolean clearPrevious, Settings settings) {
    248         assertFalse(clearPrevious); // No clearPrevious in HTTP/2.
    249         assertEquals(reducedTableSizeBytes, settings.getHeaderTableSize());
    250         assertEquals(false, settings.getEnablePush(true));
    251       }
    252     });
    253   }
    254 
    255   @Test public void readSettingsFrameInvalidPushValue() throws IOException {
    256     writeMedium(frame, 6); // 2 for the code and 4 for the value
    257     frame.writeByte(Http2.TYPE_SETTINGS);
    258     frame.writeByte(Http2.FLAG_NONE);
    259     frame.writeInt(0); // Settings are always on the connection stream 0.
    260     frame.writeShort(2);
    261     frame.writeInt(2);
    262 
    263     try {
    264       fr.nextFrame(new BaseTestHandler());
    265       fail();
    266     } catch (IOException e) {
    267       assertEquals("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1", e.getMessage());
    268     }
    269   }
    270 
    271   @Test public void readSettingsFrameInvalidSettingId() throws IOException {
    272     writeMedium(frame, 6); // 2 for the code and 4 for the value
    273     frame.writeByte(Http2.TYPE_SETTINGS);
    274     frame.writeByte(Http2.FLAG_NONE);
    275     frame.writeInt(0); // Settings are always on the connection stream 0.
    276     frame.writeShort(7); // old number for SETTINGS_INITIAL_WINDOW_SIZE
    277     frame.writeInt(1);
    278 
    279     try {
    280       fr.nextFrame(new BaseTestHandler());
    281       fail();
    282     } catch (IOException e) {
    283       assertEquals("PROTOCOL_ERROR invalid settings id: 7", e.getMessage());
    284     }
    285   }
    286 
    287   @Test public void readSettingsFrameNegativeWindowSize() throws IOException {
    288     writeMedium(frame, 6); // 2 for the code and 4 for the value
    289     frame.writeByte(Http2.TYPE_SETTINGS);
    290     frame.writeByte(Http2.FLAG_NONE);
    291     frame.writeInt(0); // Settings are always on the connection stream 0.
    292     frame.writeShort(4); // SETTINGS_INITIAL_WINDOW_SIZE
    293     frame.writeInt(Integer.MIN_VALUE);
    294 
    295     try {
    296       fr.nextFrame(new BaseTestHandler());
    297       fail();
    298     } catch (IOException e) {
    299       assertEquals("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1", e.getMessage());
    300     }
    301   }
    302 
    303   @Test public void readSettingsFrameNegativeFrameLength() throws IOException {
    304     writeMedium(frame, 6); // 2 for the code and 4 for the value
    305     frame.writeByte(Http2.TYPE_SETTINGS);
    306     frame.writeByte(Http2.FLAG_NONE);
    307     frame.writeInt(0); // Settings are always on the connection stream 0.
    308     frame.writeShort(5); // SETTINGS_MAX_FRAME_SIZE
    309     frame.writeInt(Integer.MIN_VALUE);
    310 
    311     try {
    312       fr.nextFrame(new BaseTestHandler());
    313       fail();
    314     } catch (IOException e) {
    315       assertEquals("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: -2147483648", e.getMessage());
    316     }
    317   }
    318 
    319   @Test public void readSettingsFrameTooShortFrameLength() throws IOException {
    320     writeMedium(frame, 6); // 2 for the code and 4 for the value
    321     frame.writeByte(Http2.TYPE_SETTINGS);
    322     frame.writeByte(Http2.FLAG_NONE);
    323     frame.writeInt(0); // Settings are always on the connection stream 0.
    324     frame.writeShort(5); // SETTINGS_MAX_FRAME_SIZE
    325     frame.writeInt((int) Math.pow(2, 14) - 1);
    326 
    327     try {
    328       fr.nextFrame(new BaseTestHandler());
    329       fail();
    330     } catch (IOException e) {
    331       assertEquals("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: 16383", e.getMessage());
    332     }
    333   }
    334 
    335   @Test public void readSettingsFrameTooLongFrameLength() throws IOException {
    336     writeMedium(frame, 6); // 2 for the code and 4 for the value
    337     frame.writeByte(Http2.TYPE_SETTINGS);
    338     frame.writeByte(Http2.FLAG_NONE);
    339     frame.writeInt(0); // Settings are always on the connection stream 0.
    340     frame.writeShort(5); // SETTINGS_MAX_FRAME_SIZE
    341     frame.writeInt((int) Math.pow(2, 24));
    342 
    343     try {
    344       fr.nextFrame(new BaseTestHandler());
    345       fail();
    346     } catch (IOException e) {
    347       assertEquals("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: 16777216", e.getMessage());
    348     }
    349   }
    350 
    351   @Test public void pingRoundTrip() throws IOException {
    352     final int expectedPayload1 = 7;
    353     final int expectedPayload2 = 8;
    354 
    355     writeMedium(frame, 8); // length
    356     frame.writeByte(Http2.TYPE_PING);
    357     frame.writeByte(Http2.FLAG_ACK);
    358     frame.writeInt(0); // connection-level
    359     frame.writeInt(expectedPayload1);
    360     frame.writeInt(expectedPayload2);
    361 
    362     // Check writer sends the same bytes.
    363     assertEquals(frame, sendPingFrame(true, expectedPayload1, expectedPayload2));
    364 
    365     fr.nextFrame(new BaseTestHandler() {
    366       @Override public void ping(boolean ack, int payload1, int payload2) {
    367         assertTrue(ack);
    368         assertEquals(expectedPayload1, payload1);
    369         assertEquals(expectedPayload2, payload2);
    370       }
    371     });
    372   }
    373 
    374   @Test public void maxLengthDataFrame() throws IOException {
    375     final byte[] expectedData = new byte[Http2.INITIAL_MAX_FRAME_SIZE];
    376     Arrays.fill(expectedData, (byte) 2);
    377 
    378     writeMedium(frame, expectedData.length);
    379     frame.writeByte(Http2.TYPE_DATA);
    380     frame.writeByte(Http2.FLAG_NONE);
    381     frame.writeInt(expectedStreamId & 0x7fffffff);
    382     frame.write(expectedData);
    383 
    384     // Check writer sends the same bytes.
    385     assertEquals(frame, sendDataFrame(new Buffer().write(expectedData)));
    386 
    387     fr.nextFrame(new BaseTestHandler() {
    388       @Override public void data(boolean inFinished, int streamId, BufferedSource source,
    389           int length) throws IOException {
    390         assertFalse(inFinished);
    391         assertEquals(expectedStreamId, streamId);
    392         assertEquals(Http2.INITIAL_MAX_FRAME_SIZE, length);
    393         ByteString data = source.readByteString(length);
    394         for (byte b : data.toByteArray()) {
    395           assertEquals(2, b);
    396         }
    397       }
    398     });
    399   }
    400 
    401   /** We do not send SETTINGS_COMPRESS_DATA = 1, nor want to. Let's make sure we error. */
    402   @Test public void compressedDataFrameWhenSettingDisabled() throws IOException {
    403     byte[] expectedData = new byte[Http2.INITIAL_MAX_FRAME_SIZE];
    404     Arrays.fill(expectedData, (byte) 2);
    405     Buffer zipped = gzip(expectedData);
    406     int zippedSize = (int) zipped.size();
    407 
    408     writeMedium(frame, zippedSize);
    409     frame.writeByte(Http2.TYPE_DATA);
    410     frame.writeByte(FLAG_COMPRESSED);
    411     frame.writeInt(expectedStreamId & 0x7fffffff);
    412     zipped.readAll(frame);
    413 
    414     try {
    415       fr.nextFrame(new BaseTestHandler());
    416       fail();
    417     } catch (IOException e) {
    418       assertEquals("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA",
    419           e.getMessage());
    420     }
    421   }
    422 
    423   @Test public void readPaddedDataFrame() throws IOException {
    424     int dataLength = 1123;
    425     byte[] expectedData = new byte[dataLength];
    426     Arrays.fill(expectedData, (byte) 2);
    427 
    428     int paddingLength = 254;
    429     byte[] padding = new byte[paddingLength];
    430     Arrays.fill(padding, (byte) 0);
    431 
    432     writeMedium(frame, dataLength + paddingLength + 1);
    433     frame.writeByte(Http2.TYPE_DATA);
    434     frame.writeByte(FLAG_PADDED);
    435     frame.writeInt(expectedStreamId & 0x7fffffff);
    436     frame.writeByte(paddingLength);
    437     frame.write(expectedData);
    438     frame.write(padding);
    439 
    440     fr.nextFrame(assertData());
    441     assertTrue(frame.exhausted()); // Padding was skipped.
    442   }
    443 
    444   @Test public void readPaddedDataFrameZeroPadding() throws IOException {
    445     int dataLength = 1123;
    446     byte[] expectedData = new byte[dataLength];
    447     Arrays.fill(expectedData, (byte) 2);
    448 
    449     writeMedium(frame, dataLength + 1);
    450     frame.writeByte(Http2.TYPE_DATA);
    451     frame.writeByte(FLAG_PADDED);
    452     frame.writeInt(expectedStreamId & 0x7fffffff);
    453     frame.writeByte(0);
    454     frame.write(expectedData);
    455 
    456     fr.nextFrame(assertData());
    457   }
    458 
    459   @Test public void readPaddedHeadersFrame() throws IOException {
    460     int paddingLength = 254;
    461     byte[] padding = new byte[paddingLength];
    462     Arrays.fill(padding, (byte) 0);
    463 
    464     Buffer headerBlock = literalHeaders(headerEntries("foo", "barrr", "baz", "qux"));
    465     writeMedium(frame, (int) headerBlock.size() + paddingLength + 1);
    466     frame.writeByte(Http2.TYPE_HEADERS);
    467     frame.writeByte(FLAG_END_HEADERS | FLAG_PADDED);
    468     frame.writeInt(expectedStreamId & 0x7fffffff);
    469     frame.writeByte(paddingLength);
    470     frame.writeAll(headerBlock);
    471     frame.write(padding);
    472 
    473     fr.nextFrame(assertHeaderBlock());
    474     assertTrue(frame.exhausted()); // Padding was skipped.
    475   }
    476 
    477   @Test public void readPaddedHeadersFrameZeroPadding() throws IOException {
    478     Buffer headerBlock = literalHeaders(headerEntries("foo", "barrr", "baz", "qux"));
    479     writeMedium(frame, (int) headerBlock.size() + 1);
    480     frame.writeByte(Http2.TYPE_HEADERS);
    481     frame.writeByte(FLAG_END_HEADERS | FLAG_PADDED);
    482     frame.writeInt(expectedStreamId & 0x7fffffff);
    483     frame.writeByte(0);
    484     frame.writeAll(headerBlock);
    485 
    486     fr.nextFrame(assertHeaderBlock());
    487   }
    488 
    489   /** Headers are compressed, then framed. */
    490   @Test public void readPaddedHeadersFrameThenContinuation() throws IOException {
    491     int paddingLength = 254;
    492     byte[] padding = new byte[paddingLength];
    493     Arrays.fill(padding, (byte) 0);
    494 
    495     // Decoding the first header will cross frame boundaries.
    496     Buffer headerBlock = literalHeaders(headerEntries("foo", "barrr", "baz", "qux"));
    497 
    498     // Write the first headers frame.
    499     writeMedium(frame, (int) (headerBlock.size() / 2) + paddingLength + 1);
    500     frame.writeByte(Http2.TYPE_HEADERS);
    501     frame.writeByte(FLAG_PADDED);
    502     frame.writeInt(expectedStreamId & 0x7fffffff);
    503     frame.writeByte(paddingLength);
    504     frame.write(headerBlock, headerBlock.size() / 2);
    505     frame.write(padding);
    506 
    507     // Write the continuation frame, specifying no more frames are expected.
    508     writeMedium(frame, (int) headerBlock.size());
    509     frame.writeByte(Http2.TYPE_CONTINUATION);
    510     frame.writeByte(FLAG_END_HEADERS);
    511     frame.writeInt(expectedStreamId & 0x7fffffff);
    512     frame.writeAll(headerBlock);
    513 
    514     fr.nextFrame(assertHeaderBlock());
    515     assertTrue(frame.exhausted());
    516   }
    517 
    518   @Test public void tooLargeDataFrame() throws IOException {
    519     try {
    520       sendDataFrame(new Buffer().write(new byte[0x1000000]));
    521       fail();
    522     } catch (IllegalArgumentException e) {
    523       assertEquals("FRAME_SIZE_ERROR length > 16384: 16777216", e.getMessage());
    524     }
    525   }
    526 
    527   @Test public void windowUpdateRoundTrip() throws IOException {
    528     final long expectedWindowSizeIncrement = 0x7fffffff;
    529 
    530     writeMedium(frame, 4); // length
    531     frame.writeByte(Http2.TYPE_WINDOW_UPDATE);
    532     frame.writeByte(Http2.FLAG_NONE);
    533     frame.writeInt(expectedStreamId);
    534     frame.writeInt((int) expectedWindowSizeIncrement);
    535 
    536     // Check writer sends the same bytes.
    537     assertEquals(frame, windowUpdate(expectedWindowSizeIncrement));
    538 
    539     fr.nextFrame(new BaseTestHandler() {
    540       @Override public void windowUpdate(int streamId, long windowSizeIncrement) {
    541         assertEquals(expectedStreamId, streamId);
    542         assertEquals(expectedWindowSizeIncrement, windowSizeIncrement);
    543       }
    544     });
    545   }
    546 
    547   @Test public void badWindowSizeIncrement() throws IOException {
    548     try {
    549       windowUpdate(0);
    550       fail();
    551     } catch (IllegalArgumentException e) {
    552       assertEquals("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: 0",
    553           e.getMessage());
    554     }
    555     try {
    556       windowUpdate(0x80000000L);
    557       fail();
    558     } catch (IllegalArgumentException e) {
    559       assertEquals("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: 2147483648",
    560           e.getMessage());
    561     }
    562   }
    563 
    564   @Test public void goAwayWithoutDebugDataRoundTrip() throws IOException {
    565     final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
    566 
    567     writeMedium(frame, 8); // Without debug data there's only 2 32-bit fields.
    568     frame.writeByte(Http2.TYPE_GOAWAY);
    569     frame.writeByte(Http2.FLAG_NONE);
    570     frame.writeInt(0); // connection-scope
    571     frame.writeInt(expectedStreamId); // last good stream.
    572     frame.writeInt(expectedError.httpCode);
    573 
    574     // Check writer sends the same bytes.
    575     assertEquals(frame, sendGoAway(expectedStreamId, expectedError, Util.EMPTY_BYTE_ARRAY));
    576 
    577     fr.nextFrame(new BaseTestHandler() {
    578       @Override public void goAway(
    579           int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
    580         assertEquals(expectedStreamId, lastGoodStreamId);
    581         assertEquals(expectedError, errorCode);
    582         assertEquals(0, debugData.size());
    583       }
    584     });
    585   }
    586 
    587   @Test public void goAwayWithDebugDataRoundTrip() throws IOException {
    588     final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
    589     final ByteString expectedData = ByteString.encodeUtf8("abcdefgh");
    590 
    591     // Compose the expected GOAWAY frame without debug data.
    592     writeMedium(frame, 8 + expectedData.size());
    593     frame.writeByte(Http2.TYPE_GOAWAY);
    594     frame.writeByte(Http2.FLAG_NONE);
    595     frame.writeInt(0); // connection-scope
    596     frame.writeInt(0); // never read any stream!
    597     frame.writeInt(expectedError.httpCode);
    598     frame.write(expectedData.toByteArray());
    599 
    600     // Check writer sends the same bytes.
    601     assertEquals(frame, sendGoAway(0, expectedError, expectedData.toByteArray()));
    602 
    603     fr.nextFrame(new BaseTestHandler() {
    604       @Override public void goAway(
    605           int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
    606         assertEquals(0, lastGoodStreamId);
    607         assertEquals(expectedError, errorCode);
    608         assertEquals(expectedData, debugData);
    609       }
    610     });
    611   }
    612 
    613   @Test public void frameSizeError() throws IOException {
    614     Http2.Writer writer = new Http2.Writer(new Buffer(), true);
    615 
    616     try {
    617       writer.frameHeader(0, 16777216, Http2.TYPE_DATA, FLAG_NONE);
    618       fail();
    619     } catch (IllegalArgumentException e) {
    620       // TODO: real max is based on settings between 16384 and 16777215
    621       assertEquals("FRAME_SIZE_ERROR length > 16384: 16777216", e.getMessage());
    622     }
    623   }
    624 
    625   @Test public void ackSettingsAppliesMaxFrameSize() throws IOException {
    626     int newMaxFrameSize = 16777215;
    627 
    628     Http2.Writer writer = new Http2.Writer(new Buffer(), true);
    629 
    630     writer.ackSettings(new Settings().set(Settings.MAX_FRAME_SIZE, 0, newMaxFrameSize));
    631 
    632     assertEquals(newMaxFrameSize, writer.maxDataLength());
    633     writer.frameHeader(0, newMaxFrameSize, Http2.TYPE_DATA, FLAG_NONE);
    634   }
    635 
    636   @Test public void streamIdHasReservedBit() throws IOException {
    637     Http2.Writer writer = new Http2.Writer(new Buffer(), true);
    638 
    639     try {
    640       int streamId = 3;
    641       streamId |= 1L << 31; // set reserved bit
    642       writer.frameHeader(streamId, Http2.INITIAL_MAX_FRAME_SIZE, Http2.TYPE_DATA, FLAG_NONE);
    643       fail();
    644     } catch (IllegalArgumentException e) {
    645       assertEquals("reserved bit set: -2147483645", e.getMessage());
    646     }
    647   }
    648 
    649   private Buffer literalHeaders(List<Header> sentHeaders) throws IOException {
    650     Buffer out = new Buffer();
    651     new Hpack.Writer(out).writeHeaders(sentHeaders);
    652     return out;
    653   }
    654 
    655   private Buffer sendHeaderFrames(boolean outFinished, List<Header> headers) throws IOException {
    656     Buffer out = new Buffer();
    657     new Http2.Writer(out, true).headers(outFinished, expectedStreamId, headers);
    658     return out;
    659   }
    660 
    661   private Buffer sendPushPromiseFrames(int streamId, List<Header> headers) throws IOException {
    662     Buffer out = new Buffer();
    663     new Http2.Writer(out, true).pushPromise(expectedStreamId, streamId, headers);
    664     return out;
    665   }
    666 
    667   private Buffer sendPingFrame(boolean ack, int payload1, int payload2) throws IOException {
    668     Buffer out = new Buffer();
    669     new Http2.Writer(out, true).ping(ack, payload1, payload2);
    670     return out;
    671   }
    672 
    673   private Buffer sendGoAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
    674       throws IOException {
    675     Buffer out = new Buffer();
    676     new Http2.Writer(out, true).goAway(lastGoodStreamId, errorCode, debugData);
    677     return out;
    678   }
    679 
    680   private Buffer sendDataFrame(Buffer data) throws IOException {
    681     Buffer out = new Buffer();
    682     new Http2.Writer(out, true).dataFrame(expectedStreamId, FLAG_NONE, data,
    683         (int) data.size());
    684     return out;
    685   }
    686 
    687   private Buffer windowUpdate(long windowSizeIncrement) throws IOException {
    688     Buffer out = new Buffer();
    689     new Http2.Writer(out, true).windowUpdate(expectedStreamId, windowSizeIncrement);
    690     return out;
    691   }
    692 
    693   private FrameReader.Handler assertHeaderBlock() {
    694     return new BaseTestHandler() {
    695       @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
    696           int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
    697         assertFalse(outFinished);
    698         assertFalse(inFinished);
    699         assertEquals(expectedStreamId, streamId);
    700         assertEquals(-1, associatedStreamId);
    701         assertEquals(headerEntries("foo", "barrr", "baz", "qux"), headerBlock);
    702         assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
    703       }
    704     };
    705   }
    706 
    707   private FrameReader.Handler assertData() {
    708     return new BaseTestHandler() {
    709       @Override public void data(boolean inFinished, int streamId, BufferedSource source,
    710           int length) throws IOException {
    711         assertFalse(inFinished);
    712         assertEquals(expectedStreamId, streamId);
    713         assertEquals(1123, length);
    714         ByteString data = source.readByteString(length);
    715         for (byte b : data.toByteArray()) {
    716           assertEquals(2, b);
    717         }
    718       }
    719     };
    720   }
    721 
    722   private static Buffer gzip(byte[] data) throws IOException {
    723     Buffer buffer = new Buffer();
    724     Okio.buffer(new GzipSink(buffer)).write(data).close();
    725     return buffer;
    726   }
    727 
    728   /** Create a sufficiently large header set to overflow Http20Draft12.INITIAL_MAX_FRAME_SIZE bytes. */
    729   private static List<Header> largeHeaders() {
    730     String[] nameValues = new String[32];
    731     char[] chars = new char[512];
    732     for (int i = 0; i < nameValues.length;) {
    733       Arrays.fill(chars, (char) i);
    734       nameValues[i++] = nameValues[i++] = String.valueOf(chars);
    735     }
    736     return headerEntries(nameValues);
    737   }
    738 
    739   private static void writeMedium(BufferedSink sink, int i) throws IOException {
    740     sink.writeByte((i >>> 16) & 0xff);
    741     sink.writeByte((i >>>  8) & 0xff);
    742     sink.writeByte( i         & 0xff);
    743   }
    744 }
    745