• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
unknownFrameTypeSkipped()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 
onlyOneLiteralHeadersFrame()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 
headersWithPriority()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. */
headersFrameThenContinuation()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 
pushPromise()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. */
pushPromiseThenContinuation()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 
readRstStreamFrame()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 
readSettingsFrame()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 
readSettingsFrameInvalidPushValue()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 
readSettingsFrameInvalidSettingId()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 
readSettingsFrameNegativeWindowSize()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 
readSettingsFrameNegativeFrameLength()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 
readSettingsFrameTooShortFrameLength()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 
readSettingsFrameTooLongFrameLength()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 
pingRoundTrip()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 
maxLengthDataFrame()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. */
compressedDataFrameWhenSettingDisabled()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 
readPaddedDataFrame()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 
readPaddedDataFrameZeroPadding()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 
readPaddedHeadersFrame()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 
readPaddedHeadersFrameZeroPadding()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. */
readPaddedHeadersFrameThenContinuation()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 
tooLargeDataFrame()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 
windowUpdateRoundTrip()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 
badWindowSizeIncrement()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 
goAwayWithoutDebugDataRoundTrip()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 
goAwayWithDebugDataRoundTrip()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 
frameSizeError()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 
ackSettingsAppliesMaxFrameSize()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 
streamIdHasReservedBit()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 
literalHeaders(List<Header> sentHeaders)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 
sendHeaderFrames(boolean outFinished, List<Header> headers)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 
sendPushPromiseFrames(int streamId, List<Header> headers)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 
sendPingFrame(boolean ack, int payload1, int payload2)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 
sendGoAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)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 
sendDataFrame(Buffer data)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 
windowUpdate(long windowSizeIncrement)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 
assertHeaderBlock()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