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.internal.Util;
19import java.io.IOException;
20import java.util.Arrays;
21import java.util.List;
22import okio.Buffer;
23import okio.BufferedSink;
24import okio.BufferedSource;
25import okio.ByteString;
26import okio.GzipSink;
27import okio.Okio;
28import org.junit.Test;
29
30import static com.squareup.okhttp.TestUtil.headerEntries;
31import static com.squareup.okhttp.internal.framed.Http2.FLAG_COMPRESSED;
32import static com.squareup.okhttp.internal.framed.Http2.FLAG_END_HEADERS;
33import static com.squareup.okhttp.internal.framed.Http2.FLAG_END_STREAM;
34import static com.squareup.okhttp.internal.framed.Http2.FLAG_NONE;
35import static com.squareup.okhttp.internal.framed.Http2.FLAG_PADDED;
36import static com.squareup.okhttp.internal.framed.Http2.FLAG_PRIORITY;
37import static org.junit.Assert.assertEquals;
38import static org.junit.Assert.assertFalse;
39import static org.junit.Assert.assertTrue;
40import static org.junit.Assert.fail;
41
42public 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