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.spdy; 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.spdy.Http2.FLAG_COMPRESSED; 32 import static com.squareup.okhttp.internal.spdy.Http2.FLAG_END_HEADERS; 33 import static com.squareup.okhttp.internal.spdy.Http2.FLAG_END_STREAM; 34 import static com.squareup.okhttp.internal.spdy.Http2.FLAG_NONE; 35 import static com.squareup.okhttp.internal.spdy.Http2.FLAG_PADDED; 36 import static com.squareup.okhttp.internal.spdy.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