1/* 2 * Copyright (C) 2011 The Android Open Source Project 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.spdy; 17 18import com.squareup.okhttp.internal.Util; 19import java.io.IOException; 20import java.io.InterruptedIOException; 21import java.util.ArrayList; 22import java.util.Arrays; 23import java.util.List; 24import java.util.concurrent.TimeUnit; 25import java.util.concurrent.atomic.AtomicInteger; 26import okio.BufferedSink; 27import okio.BufferedSource; 28import okio.ByteString; 29import okio.OkBuffer; 30import okio.Okio; 31import okio.Source; 32import org.junit.After; 33import org.junit.Test; 34 35import static com.squareup.okhttp.internal.Util.headerEntries; 36import static com.squareup.okhttp.internal.spdy.ErrorCode.CANCEL; 37import static com.squareup.okhttp.internal.spdy.ErrorCode.INTERNAL_ERROR; 38import static com.squareup.okhttp.internal.spdy.ErrorCode.INVALID_STREAM; 39import static com.squareup.okhttp.internal.spdy.ErrorCode.PROTOCOL_ERROR; 40import static com.squareup.okhttp.internal.spdy.ErrorCode.REFUSED_STREAM; 41import static com.squareup.okhttp.internal.spdy.ErrorCode.STREAM_IN_USE; 42import static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE; 43import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE; 44import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_DATA; 45import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_GOAWAY; 46import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_HEADERS; 47import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_PING; 48import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_RST_STREAM; 49import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_SETTINGS; 50import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_WINDOW_UPDATE; 51import static org.junit.Assert.assertEquals; 52import static org.junit.Assert.assertFalse; 53import static org.junit.Assert.assertTrue; 54import static org.junit.Assert.fail; 55 56public final class SpdyConnectionTest { 57 private static final Variant SPDY3 = new Spdy3(); 58 private static final Variant HTTP_20_DRAFT_09 = new Http20Draft09(); 59 private final MockSpdyPeer peer = new MockSpdyPeer(); 60 61 @After public void tearDown() throws Exception { 62 peer.close(); 63 } 64 65 @Test public void clientCreatesStreamAndServerReplies() throws Exception { 66 // write the mocking script 67 peer.acceptFrame(); // SYN_STREAM 68 peer.sendFrame() 69 .synReply(false, 1, headerEntries("a", "android")); 70 peer.sendFrame().data(true, 1, new OkBuffer().writeUtf8("robot")); 71 peer.acceptFrame(); // DATA 72 peer.play(); 73 74 // play it back 75 SpdyConnection connection = connection(peer, SPDY3); 76 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 77 assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); 78 assertStreamData("robot", stream.getSource()); 79 BufferedSink out = Okio.buffer(stream.getSink()); 80 out.writeUtf8("c3po"); 81 out.close(); 82 assertEquals(0, connection.openStreamCount()); 83 84 // verify the peer received what was expected 85 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 86 assertEquals(TYPE_HEADERS, synStream.type); 87 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 88 assertFalse(synStream.inFinished); 89 assertFalse(synStream.outFinished); 90 assertEquals(1, synStream.streamId); 91 assertEquals(0, synStream.associatedStreamId); 92 assertEquals(headerEntries("b", "banana"), synStream.headerBlock); 93 MockSpdyPeer.InFrame requestData = peer.takeFrame(); 94 assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); 95 } 96 97 @Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception { 98 peer.acceptFrame(); // SYN_STREAM 99 peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); 100 peer.play(); 101 102 SpdyConnection connection = connection(peer, SPDY3); 103 SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, false); 104 assertEquals(1, connection.openStreamCount()); 105 assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); 106 assertEquals(0, connection.openStreamCount()); 107 } 108 109 @Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception { 110 // write the mocking script 111 peer.acceptFrame(); // SYN_STREAM 112 peer.acceptFrame(); // PING 113 peer.sendFrame().synReply(true, 1, headerEntries("a", "android")); 114 peer.sendFrame().ping(true, 1, 0); 115 peer.play(); 116 117 // play it back 118 SpdyConnection connection = connection(peer, SPDY3); 119 connection.newStream(headerEntries("b", "banana"), false, true); 120 assertEquals(1, connection.openStreamCount()); 121 connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. 122 assertEquals(0, connection.openStreamCount()); 123 124 // verify the peer received what was expected 125 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 126 assertEquals(TYPE_HEADERS, synStream.type); 127 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 128 MockSpdyPeer.InFrame ping = peer.takeFrame(); 129 assertEquals(TYPE_PING, ping.type); 130 } 131 132 @Test public void serverCreatesStreamAndClientReplies() throws Exception { 133 final List<Header> pushHeaders = headerEntries( 134 ":scheme", "https", 135 ":host", "localhost:8888", 136 ":method", "GET", 137 ":path", "/index.html", 138 ":status", "200", 139 ":version", "HTTP/1.1", 140 "content-type", "text/html"); 141 // write the mocking script 142 peer.sendFrame().synStream(false, false, 2, 0, 5, 129, pushHeaders); 143 peer.acceptFrame(); // SYN_REPLY 144 peer.play(); 145 146 // play it back 147 final AtomicInteger receiveCount = new AtomicInteger(); 148 IncomingStreamHandler handler = new IncomingStreamHandler() { 149 @Override public void receive(SpdyStream stream) throws IOException { 150 receiveCount.incrementAndGet(); 151 assertEquals(pushHeaders, stream.getRequestHeaders()); 152 assertEquals(null, stream.getErrorCode()); 153 assertEquals(5, stream.getPriority()); 154 stream.reply(headerEntries("b", "banana"), true); 155 } 156 }; 157 new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); 158 159 // verify the peer received what was expected 160 MockSpdyPeer.InFrame reply = peer.takeFrame(); 161 assertEquals(TYPE_HEADERS, reply.type); 162 assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); 163 assertFalse(reply.inFinished); 164 assertEquals(2, reply.streamId); 165 assertEquals(headerEntries("b", "banana"), reply.headerBlock); 166 assertEquals(1, receiveCount.get()); 167 } 168 169 @Test public void replyWithNoData() throws Exception { 170 // write the mocking script 171 peer.sendFrame().synStream(false, false, 2, 0, 0, 0, headerEntries("a", "android")); 172 peer.acceptFrame(); // SYN_REPLY 173 peer.play(); 174 175 // play it back 176 final AtomicInteger receiveCount = new AtomicInteger(); 177 IncomingStreamHandler handler = new IncomingStreamHandler() { 178 @Override public void receive(SpdyStream stream) throws IOException { 179 stream.reply(headerEntries("b", "banana"), false); 180 receiveCount.incrementAndGet(); 181 } 182 }; 183 184 connectionBuilder(peer, SPDY3).handler(handler).build(); 185 186 // verify the peer received what was expected 187 MockSpdyPeer.InFrame reply = peer.takeFrame(); 188 assertEquals(TYPE_HEADERS, reply.type); 189 assertTrue(reply.inFinished); 190 assertEquals(headerEntries("b", "banana"), reply.headerBlock); 191 assertEquals(1, receiveCount.get()); 192 assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); 193 } 194 195 @Test public void serverPingsClient() throws Exception { 196 // write the mocking script 197 peer.sendFrame().ping(false, 2, 0); 198 peer.acceptFrame(); // PING 199 peer.play(); 200 201 // play it back 202 connection(peer, SPDY3); 203 204 // verify the peer received what was expected 205 MockSpdyPeer.InFrame ping = peer.takeFrame(); 206 assertEquals(0, ping.streamId); 207 assertEquals(2, ping.payload1); 208 assertEquals(0, ping.payload2); // ignored in spdy! 209 assertTrue(ping.ack); 210 } 211 212 @Test public void serverPingsClientHttp2() throws Exception { 213 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 214 215 // write the mocking script 216 peer.sendFrame().ping(false, 2, 3); 217 peer.acceptFrame(); // PING 218 peer.play(); 219 220 // play it back 221 connection(peer, HTTP_20_DRAFT_09); 222 223 // verify the peer received what was expected 224 MockSpdyPeer.InFrame ping = peer.takeFrame(); 225 assertEquals(TYPE_PING, ping.type); 226 assertEquals(0, ping.streamId); 227 assertEquals(2, ping.payload1); 228 assertEquals(3, ping.payload2); 229 assertTrue(ping.ack); 230 } 231 232 @Test public void clientPingsServer() throws Exception { 233 // write the mocking script 234 peer.acceptFrame(); // PING 235 peer.sendFrame().ping(true, 1, 5); // payload2 ignored in spdy! 236 peer.play(); 237 238 // play it back 239 SpdyConnection connection = connection(peer, SPDY3); 240 Ping ping = connection.ping(); 241 assertTrue(ping.roundTripTime() > 0); 242 assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); 243 244 // verify the peer received what was expected 245 MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); 246 assertEquals(TYPE_PING, pingFrame.type); 247 assertEquals(1, pingFrame.payload1); 248 assertEquals(0, pingFrame.payload2); 249 assertFalse(pingFrame.ack); 250 } 251 252 @Test public void clientPingsServerHttp2() throws Exception { 253 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 254 255 // write the mocking script 256 peer.acceptFrame(); // PING 257 peer.sendFrame().ping(true, 1, 5); 258 peer.play(); 259 260 // play it back 261 SpdyConnection connection = connection(peer, HTTP_20_DRAFT_09); 262 Ping ping = connection.ping(); 263 assertTrue(ping.roundTripTime() > 0); 264 assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); 265 266 // verify the peer received what was expected 267 MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); 268 assertEquals(0, pingFrame.streamId); 269 assertEquals(1, pingFrame.payload1); 270 assertEquals(0x4f4b6f6b, pingFrame.payload2); // connection.ping() sets this. 271 assertFalse(pingFrame.ack); 272 } 273 274 @Test public void peerHttp2ServerLowersInitialWindowSize() throws Exception { 275 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 276 277 Settings initial = new Settings(); 278 initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 1684); 279 Settings shouldntImpactConnection = new Settings(); 280 shouldntImpactConnection.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 3368); 281 282 peer.sendFrame().settings(initial); 283 peer.acceptFrame(); // ACK 284 peer.sendFrame().settings(shouldntImpactConnection); 285 peer.acceptFrame(); // ACK 2 286 peer.acceptFrame(); // HEADERS 287 peer.play(); 288 289 SpdyConnection connection = connection(peer, HTTP_20_DRAFT_09); 290 291 // verify the peer received the ACK 292 MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); 293 assertEquals(TYPE_SETTINGS, ackFrame.type); 294 assertEquals(0, ackFrame.streamId); 295 assertTrue(ackFrame.ack); 296 ackFrame = peer.takeFrame(); 297 assertEquals(TYPE_SETTINGS, ackFrame.type); 298 assertEquals(0, ackFrame.streamId); 299 assertTrue(ackFrame.ack); 300 301 // This stream was created *after* the connection settings were adjusted. 302 SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true); 303 304 assertEquals(3368, connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE)); 305 assertEquals(1684, connection.bytesLeftInWriteWindow); // initial wasn't affected. 306 // New Stream is has the most recent initial window size. 307 assertEquals(3368, stream.bytesLeftInWriteWindow); 308 } 309 310 @Test public void unexpectedPingIsNotReturned() throws Exception { 311 // write the mocking script 312 peer.sendFrame().ping(false, 2, 0); 313 peer.acceptFrame(); // PING 314 peer.sendFrame().ping(true, 3, 0); // This ping will not be returned. 315 peer.sendFrame().ping(false, 4, 0); 316 peer.acceptFrame(); // PING 317 peer.play(); 318 319 // play it back 320 connection(peer, SPDY3); 321 322 // verify the peer received what was expected 323 MockSpdyPeer.InFrame ping2 = peer.takeFrame(); 324 assertEquals(2, ping2.payload1); 325 MockSpdyPeer.InFrame ping4 = peer.takeFrame(); 326 assertEquals(4, ping4.payload1); 327 } 328 329 @Test public void peerHttp2ServerZerosCompressionTable() throws Exception { 330 boolean client = false; // Peer is server, so we are client. 331 Settings settings = new Settings(); 332 settings.set(Settings.HEADER_TABLE_SIZE, PERSIST_VALUE, 0); 333 334 SpdyConnection connection = sendHttp2SettingsAndCheckForAck(client, settings); 335 336 // verify the peer's settings were read and applied. 337 synchronized (connection) { 338 assertEquals(0, connection.peerSettings.getHeaderTableSize()); 339 Http20Draft09.Reader frameReader = (Http20Draft09.Reader) connection.frameReader; 340 assertEquals(0, frameReader.hpackReader.maxHeaderTableByteCount()); 341 // TODO: when supported, check the frameWriter's compression table is unaffected. 342 } 343 } 344 345 @Test public void peerHttp2ClientDisablesPush() throws Exception { 346 boolean client = false; // Peer is client, so we are server. 347 Settings settings = new Settings(); 348 settings.set(Settings.ENABLE_PUSH, 0, 0); // The peer client disables push. 349 350 SpdyConnection connection = sendHttp2SettingsAndCheckForAck(client, settings); 351 352 // verify the peer's settings were read and applied. 353 synchronized (connection) { 354 assertFalse(connection.peerSettings.getEnablePush(true)); 355 } 356 } 357 358 @Test public void serverSendsSettingsToClient() throws Exception { 359 // write the mocking script 360 Settings settings = new Settings(); 361 settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); 362 peer.sendFrame().settings(settings); 363 peer.sendFrame().ping(false, 2, 0); 364 peer.acceptFrame(); // PING 365 peer.play(); 366 367 // play it back 368 SpdyConnection connection = connection(peer, SPDY3); 369 370 peer.takeFrame(); // Guarantees that the peer Settings frame has been processed. 371 synchronized (connection) { 372 assertEquals(10, connection.peerSettings.getMaxConcurrentStreams(-1)); 373 } 374 } 375 376 @Test public void multipleSettingsFramesAreMerged() throws Exception { 377 // write the mocking script 378 Settings settings1 = new Settings(); 379 settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); 380 settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); 381 settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300); 382 peer.sendFrame().settings(settings1); 383 Settings settings2 = new Settings(); 384 settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); 385 settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); 386 settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); 387 peer.sendFrame().settings(settings2); 388 peer.sendFrame().ping(false, 2, 0); 389 peer.acceptFrame(); 390 peer.play(); 391 392 // play it back 393 SpdyConnection connection = connection(peer, SPDY3); 394 395 peer.takeFrame(); // Guarantees that the Settings frame has been processed. 396 synchronized (connection) { 397 assertEquals(100, connection.peerSettings.getUploadBandwidth(-1)); 398 assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.UPLOAD_BANDWIDTH)); 399 assertEquals(400, connection.peerSettings.getDownloadBandwidth(-1)); 400 assertEquals(0, connection.peerSettings.flags(Settings.DOWNLOAD_BANDWIDTH)); 401 assertEquals(500, connection.peerSettings.getDownloadRetransRate(-1)); 402 assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.DOWNLOAD_RETRANS_RATE)); 403 assertEquals(600, connection.peerSettings.getMaxConcurrentStreams(-1)); 404 assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.MAX_CONCURRENT_STREAMS)); 405 } 406 } 407 408 @Test public void clearSettingsBeforeMerge() throws Exception { 409 // write the mocking script 410 Settings settings1 = new Settings(); 411 settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); 412 settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); 413 settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300); 414 peer.sendFrame().settings(settings1); 415 peer.sendFrame().ping(false, 2, 0); 416 peer.acceptFrame(); 417 peer.play(); 418 419 // play it back 420 SpdyConnection connection = connection(peer, SPDY3); 421 422 peer.takeFrame(); // Guarantees that the Settings frame has been processed. 423 424 // fake a settings frame with clear flag set. 425 Settings settings2 = new Settings(); 426 settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); 427 connection.readerRunnable.settings(true, settings2); 428 429 synchronized (connection) { 430 assertEquals(-1, connection.peerSettings.getUploadBandwidth(-1)); 431 assertEquals(-1, connection.peerSettings.getDownloadBandwidth(-1)); 432 assertEquals(-1, connection.peerSettings.getDownloadRetransRate(-1)); 433 assertEquals(600, connection.peerSettings.getMaxConcurrentStreams(-1)); 434 } 435 } 436 437 @Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception { 438 // write the mocking script 439 peer.sendFrame().data(true, 41, new OkBuffer().writeUtf8("bogus")); 440 peer.acceptFrame(); // RST_STREAM 441 peer.sendFrame().ping(false, 2, 0); 442 peer.acceptFrame(); // PING 443 peer.play(); 444 445 // play it back 446 connection(peer, SPDY3); 447 448 // verify the peer received what was expected 449 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 450 assertEquals(TYPE_RST_STREAM, rstStream.type); 451 assertEquals(41, rstStream.streamId); 452 assertEquals(INVALID_STREAM, rstStream.errorCode); 453 MockSpdyPeer.InFrame ping = peer.takeFrame(); 454 assertEquals(2, ping.payload1); 455 } 456 457 @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception { 458 // write the mocking script 459 peer.sendFrame().synReply(false, 41, headerEntries("a", "android")); 460 peer.acceptFrame(); // RST_STREAM 461 peer.sendFrame().ping(false, 2, 0); 462 peer.acceptFrame(); // PING 463 peer.play(); 464 465 // play it back 466 connection(peer, SPDY3); 467 468 // verify the peer received what was expected 469 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 470 assertEquals(TYPE_RST_STREAM, rstStream.type); 471 assertEquals(41, rstStream.streamId); 472 assertEquals(INVALID_STREAM, rstStream.errorCode); 473 MockSpdyPeer.InFrame ping = peer.takeFrame(); 474 assertEquals(2, ping.payload1); 475 } 476 477 @Test public void clientClosesClientOutputStream() throws Exception { 478 // write the mocking script 479 peer.acceptFrame(); // SYN_STREAM 480 peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); 481 peer.acceptFrame(); // TYPE_DATA 482 peer.acceptFrame(); // TYPE_DATA with FLAG_FIN 483 peer.acceptFrame(); // PING 484 peer.sendFrame().ping(true, 1, 0); 485 peer.play(); 486 487 // play it back 488 SpdyConnection connection = connection(peer, SPDY3); 489 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, false); 490 BufferedSink out = Okio.buffer(stream.getSink()); 491 out.writeUtf8("square"); 492 out.flush(); 493 assertEquals(1, connection.openStreamCount()); 494 out.close(); 495 try { 496 out.writeUtf8("round"); 497 fail(); 498 } catch (Exception expected) { 499 assertEquals("closed", expected.getMessage()); 500 } 501 connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. 502 assertEquals(0, connection.openStreamCount()); 503 504 // verify the peer received what was expected 505 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 506 assertEquals(TYPE_HEADERS, synStream.type); 507 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 508 assertFalse(synStream.inFinished); 509 assertTrue(synStream.outFinished); 510 MockSpdyPeer.InFrame data = peer.takeFrame(); 511 assertEquals(TYPE_DATA, data.type); 512 assertFalse(data.inFinished); 513 assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); 514 MockSpdyPeer.InFrame fin = peer.takeFrame(); 515 assertEquals(TYPE_DATA, fin.type); 516 assertTrue(fin.inFinished); 517 MockSpdyPeer.InFrame ping = peer.takeFrame(); 518 assertEquals(TYPE_PING, ping.type); 519 assertEquals(1, ping.payload1); 520 } 521 522 @Test public void serverClosesClientOutputStream() throws Exception { 523 // write the mocking script 524 peer.acceptFrame(); // SYN_STREAM 525 peer.sendFrame().rstStream(1, CANCEL); 526 peer.acceptFrame(); // PING 527 peer.sendFrame().ping(true, 1, 0); 528 peer.play(); 529 530 // play it back 531 SpdyConnection connection = connection(peer, SPDY3); 532 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true); 533 BufferedSink out = Okio.buffer(stream.getSink()); 534 connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. 535 try { 536 out.writeUtf8("square"); 537 out.flush(); 538 fail(); 539 } catch (IOException expected) { 540 assertEquals("stream was reset: CANCEL", expected.getMessage()); 541 } 542 try { 543 out.close(); 544 fail(); 545 } catch (IOException expected) { 546 // Close throws because buffered data wasn't flushed. 547 } 548 assertEquals(0, connection.openStreamCount()); 549 550 // verify the peer received what was expected 551 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 552 assertEquals(TYPE_HEADERS, synStream.type); 553 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 554 assertFalse(synStream.inFinished); 555 assertFalse(synStream.outFinished); 556 MockSpdyPeer.InFrame ping = peer.takeFrame(); 557 assertEquals(TYPE_PING, ping.type); 558 assertEquals(1, ping.payload1); 559 } 560 561 /** 562 * Test that the client sends a RST_STREAM if doing so won't disrupt the 563 * output stream. 564 */ 565 @Test public void clientClosesClientInputStream() throws Exception { 566 // write the mocking script 567 peer.acceptFrame(); // SYN_STREAM 568 peer.acceptFrame(); // RST_STREAM 569 peer.play(); 570 571 // play it back 572 SpdyConnection connection = connection(peer, SPDY3); 573 SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true); 574 Source in = stream.getSource(); 575 BufferedSink out = Okio.buffer(stream.getSink()); 576 in.close(); 577 try { 578 in.read(new OkBuffer(), 1); 579 fail(); 580 } catch (IOException expected) { 581 assertEquals("stream closed", expected.getMessage()); 582 } 583 try { 584 out.writeUtf8("a"); 585 out.flush(); 586 fail(); 587 } catch (IOException expected) { 588 assertEquals("stream finished", expected.getMessage()); 589 } 590 assertEquals(0, connection.openStreamCount()); 591 592 // verify the peer received what was expected 593 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 594 assertEquals(TYPE_HEADERS, synStream.type); 595 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 596 assertTrue(synStream.inFinished); 597 assertFalse(synStream.outFinished); 598 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 599 assertEquals(TYPE_RST_STREAM, rstStream.type); 600 assertEquals(CANCEL, rstStream.errorCode); 601 } 602 603 /** 604 * Test that the client doesn't send a RST_STREAM if doing so will disrupt 605 * the output stream. 606 */ 607 @Test public void clientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception { 608 // write the mocking script 609 peer.acceptFrame(); // SYN_STREAM 610 peer.acceptFrame(); // DATA 611 peer.acceptFrame(); // DATA with FLAG_FIN 612 peer.acceptFrame(); // RST_STREAM 613 peer.play(); 614 615 // play it back 616 SpdyConnection connection = connection(peer, SPDY3); 617 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true); 618 Source source = stream.getSource(); 619 BufferedSink out = Okio.buffer(stream.getSink()); 620 source.close(); 621 try { 622 source.read(new OkBuffer(), 1); 623 fail(); 624 } catch (IOException expected) { 625 assertEquals("stream closed", expected.getMessage()); 626 } 627 out.writeUtf8("square"); 628 out.flush(); 629 out.close(); 630 assertEquals(0, connection.openStreamCount()); 631 632 // verify the peer received what was expected 633 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 634 assertEquals(TYPE_HEADERS, synStream.type); 635 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 636 assertFalse(synStream.inFinished); 637 assertFalse(synStream.outFinished); 638 MockSpdyPeer.InFrame data = peer.takeFrame(); 639 assertEquals(TYPE_DATA, data.type); 640 assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); 641 MockSpdyPeer.InFrame fin = peer.takeFrame(); 642 assertEquals(TYPE_DATA, fin.type); 643 assertTrue(fin.inFinished); 644 assertFalse(fin.outFinished); 645 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 646 assertEquals(TYPE_RST_STREAM, rstStream.type); 647 assertEquals(CANCEL, rstStream.errorCode); 648 } 649 650 @Test public void serverClosesClientInputStream() throws Exception { 651 // write the mocking script 652 peer.acceptFrame(); // SYN_STREAM 653 peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); 654 peer.sendFrame().data(true, 1, new OkBuffer().writeUtf8("square")); 655 peer.play(); 656 657 // play it back 658 SpdyConnection connection = connection(peer, SPDY3); 659 SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true); 660 Source source = stream.getSource(); 661 assertStreamData("square", source); 662 assertEquals(0, connection.openStreamCount()); 663 664 // verify the peer received what was expected 665 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 666 assertEquals(TYPE_HEADERS, synStream.type); 667 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 668 assertTrue(synStream.inFinished); 669 assertFalse(synStream.outFinished); 670 } 671 672 @Test public void remoteDoubleSynReply() throws Exception { 673 // write the mocking script 674 peer.acceptFrame(); // SYN_STREAM 675 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 676 peer.acceptFrame(); // PING 677 peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); 678 peer.sendFrame().ping(true, 1, 0); 679 peer.acceptFrame(); // RST_STREAM 680 peer.play(); 681 682 // play it back 683 SpdyConnection connection = connection(peer, SPDY3); 684 SpdyStream stream = connection.newStream(headerEntries("c", "cola"), true, true); 685 assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); 686 connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received. 687 try { 688 stream.getSource().read(new OkBuffer(), 1); 689 fail(); 690 } catch (IOException expected) { 691 assertEquals("stream was reset: STREAM_IN_USE", expected.getMessage()); 692 } 693 694 // verify the peer received what was expected 695 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 696 assertEquals(TYPE_HEADERS, synStream.type); 697 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 698 MockSpdyPeer.InFrame ping = peer.takeFrame(); 699 assertEquals(TYPE_PING, ping.type); 700 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 701 assertEquals(TYPE_RST_STREAM, rstStream.type); 702 assertEquals(1, rstStream.streamId); 703 assertEquals(STREAM_IN_USE, rstStream.errorCode); 704 } 705 706 @Test public void remoteDoubleSynStream() throws Exception { 707 // write the mocking script 708 peer.sendFrame().synStream(false, false, 2, 0, 0, 0, headerEntries("a", "android")); 709 peer.acceptFrame(); // SYN_REPLY 710 peer.sendFrame().synStream(false, false, 2, 0, 0, 0, headerEntries("b", "banana")); 711 peer.acceptFrame(); // RST_STREAM 712 peer.play(); 713 714 // play it back 715 final AtomicInteger receiveCount = new AtomicInteger(); 716 IncomingStreamHandler handler = new IncomingStreamHandler() { 717 @Override public void receive(SpdyStream stream) throws IOException { 718 receiveCount.incrementAndGet(); 719 assertEquals(headerEntries("a", "android"), stream.getRequestHeaders()); 720 assertEquals(null, stream.getErrorCode()); 721 stream.reply(headerEntries("c", "cola"), true); 722 } 723 }; 724 new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); 725 726 // verify the peer received what was expected 727 MockSpdyPeer.InFrame reply = peer.takeFrame(); 728 assertEquals(TYPE_HEADERS, reply.type); 729 assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); 730 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 731 assertEquals(TYPE_RST_STREAM, rstStream.type); 732 assertEquals(2, rstStream.streamId); 733 assertEquals(PROTOCOL_ERROR, rstStream.errorCode); 734 assertEquals(1, receiveCount.intValue()); 735 } 736 737 @Test public void remoteSendsDataAfterInFinished() throws Exception { 738 // write the mocking script 739 peer.acceptFrame(); // SYN_STREAM 740 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 741 peer.sendFrame().data(true, 1, new OkBuffer().writeUtf8("robot")); 742 peer.sendFrame().data(true, 1, new OkBuffer().writeUtf8("c3po")); // Ignored. 743 peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. 744 peer.acceptFrame(); // PING 745 peer.play(); 746 747 // play it back 748 SpdyConnection connection = connection(peer, SPDY3); 749 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 750 assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); 751 assertStreamData("robot", stream.getSource()); 752 753 // verify the peer received what was expected 754 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 755 assertEquals(TYPE_HEADERS, synStream.type); 756 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 757 MockSpdyPeer.InFrame ping = peer.takeFrame(); 758 assertEquals(TYPE_PING, ping.type); 759 assertEquals(2, ping.payload1); 760 } 761 762 @Test public void clientDoesNotLimitFlowControl() throws Exception { 763 // write the mocking script 764 peer.acceptFrame(); // SYN_STREAM 765 peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); 766 peer.sendFrame().data(false, 1, new OkBuffer().write(new byte[64 * 1024 + 1])); 767 peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. 768 peer.acceptFrame(); // PING 769 peer.play(); 770 771 // play it back 772 SpdyConnection connection = connection(peer, SPDY3); 773 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true); 774 assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); 775 776 // verify the peer received what was expected 777 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 778 assertEquals(TYPE_HEADERS, synStream.type); 779 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 780 MockSpdyPeer.InFrame ping = peer.takeFrame(); 781 assertEquals(TYPE_PING, ping.type); 782 assertEquals(2, ping.payload1); 783 } 784 785 @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception { 786 // write the mocking script 787 peer.acceptFrame(); // SYN_STREAM 788 peer.sendFrame().rstStream(1, REFUSED_STREAM); 789 peer.sendFrame().ping(false, 2, 0); 790 peer.acceptFrame(); // PING 791 peer.play(); 792 793 // play it back 794 SpdyConnection connection = connection(peer, SPDY3); 795 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true); 796 try { 797 stream.getResponseHeaders(); 798 fail(); 799 } catch (IOException expected) { 800 assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); 801 } 802 assertEquals(0, connection.openStreamCount()); 803 804 // verify the peer received what was expected 805 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 806 assertEquals(TYPE_HEADERS, synStream.type); 807 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 808 MockSpdyPeer.InFrame ping = peer.takeFrame(); 809 assertEquals(TYPE_PING, ping.type); 810 assertEquals(2, ping.payload1); 811 } 812 813 814 @Test public void receiveGoAway() throws Exception { 815 receiveGoAway(SPDY3); 816 } 817 818 @Test public void receiveGoAwayHttp2() throws Exception { 819 receiveGoAway(HTTP_20_DRAFT_09); 820 } 821 822 private void receiveGoAway(Variant variant) throws Exception { 823 peer.setVariantAndClient(variant, false); 824 825 // write the mocking script 826 peer.acceptFrame(); // SYN_STREAM 1 827 peer.acceptFrame(); // SYN_STREAM 3 828 peer.sendFrame().goAway(1, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY); 829 peer.acceptFrame(); // PING 830 peer.sendFrame().ping(true, 1, 0); 831 peer.acceptFrame(); // DATA STREAM 1 832 peer.play(); 833 834 // play it back 835 SpdyConnection connection = connection(peer, variant); 836 SpdyStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); 837 SpdyStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); 838 connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received. 839 BufferedSink sink1 = Okio.buffer(stream1.getSink()); 840 BufferedSink sink2 = Okio.buffer(stream2.getSink()); 841 sink1.writeUtf8("abc"); 842 try { 843 sink2.writeUtf8("abc"); 844 sink2.flush(); 845 fail(); 846 } catch (IOException expected) { 847 assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); 848 } 849 sink1.writeUtf8("def"); 850 sink1.close(); 851 try { 852 connection.newStream(headerEntries("c", "cola"), true, true); 853 fail(); 854 } catch (IOException expected) { 855 assertEquals("shutdown", expected.getMessage()); 856 } 857 assertEquals(1, connection.openStreamCount()); 858 859 // verify the peer received what was expected 860 MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); 861 assertEquals(TYPE_HEADERS, synStream1.type); 862 MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); 863 assertEquals(TYPE_HEADERS, synStream2.type); 864 MockSpdyPeer.InFrame ping = peer.takeFrame(); 865 assertEquals(TYPE_PING, ping.type); 866 MockSpdyPeer.InFrame data1 = peer.takeFrame(); 867 assertEquals(TYPE_DATA, data1.type); 868 assertEquals(1, data1.streamId); 869 assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); 870 } 871 872 @Test public void sendGoAway() throws Exception { 873 // write the mocking script 874 peer.acceptFrame(); // SYN_STREAM 1 875 peer.acceptFrame(); // GOAWAY 876 peer.acceptFrame(); // PING 877 peer.sendFrame().synStream(false, false, 2, 0, 0, 0, headerEntries("b", "b")); // Should be ignored! 878 peer.sendFrame().ping(true, 1, 0); 879 peer.play(); 880 881 // play it back 882 SpdyConnection connection = connection(peer, SPDY3); 883 connection.newStream(headerEntries("a", "android"), true, true); 884 Ping ping = connection.ping(); 885 connection.shutdown(PROTOCOL_ERROR); 886 assertEquals(1, connection.openStreamCount()); 887 ping.roundTripTime(); // Prevent the peer from exiting prematurely. 888 889 // verify the peer received what was expected 890 MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); 891 assertEquals(TYPE_HEADERS, synStream1.type); 892 MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); 893 assertEquals(TYPE_PING, pingFrame.type); 894 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 895 assertEquals(TYPE_GOAWAY, goaway.type); 896 assertEquals(0, goaway.streamId); 897 assertEquals(PROTOCOL_ERROR, goaway.errorCode); 898 } 899 900 @Test public void noPingsAfterShutdown() throws Exception { 901 // write the mocking script 902 peer.acceptFrame(); // GOAWAY 903 peer.play(); 904 905 // play it back 906 SpdyConnection connection = connection(peer, SPDY3); 907 connection.shutdown(INTERNAL_ERROR); 908 try { 909 connection.ping(); 910 fail(); 911 } catch (IOException expected) { 912 assertEquals("shutdown", expected.getMessage()); 913 } 914 915 // verify the peer received what was expected 916 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 917 assertEquals(TYPE_GOAWAY, goaway.type); 918 assertEquals(INTERNAL_ERROR, goaway.errorCode); 919 } 920 921 @Test public void close() throws Exception { 922 // write the mocking script 923 peer.acceptFrame(); // SYN_STREAM 924 peer.acceptFrame(); // GOAWAY 925 peer.acceptFrame(); // RST_STREAM 926 peer.play(); 927 928 // play it back 929 SpdyConnection connection = connection(peer, SPDY3); 930 SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true); 931 assertEquals(1, connection.openStreamCount()); 932 connection.close(); 933 assertEquals(0, connection.openStreamCount()); 934 try { 935 connection.newStream(headerEntries("b", "banana"), true, true); 936 fail(); 937 } catch (IOException expected) { 938 assertEquals("shutdown", expected.getMessage()); 939 } 940 BufferedSink sink = Okio.buffer(stream.getSink()); 941 try { 942 sink.writeByte(0); 943 sink.flush(); 944 fail(); 945 } catch (IOException expected) { 946 assertEquals("stream was reset: CANCEL", expected.getMessage()); 947 } 948 try { 949 stream.getSource().read(new OkBuffer(), 1); 950 fail(); 951 } catch (IOException expected) { 952 assertEquals("stream was reset: CANCEL", expected.getMessage()); 953 } 954 955 // verify the peer received what was expected 956 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 957 assertEquals(TYPE_HEADERS, synStream.type); 958 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 959 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 960 assertEquals(TYPE_GOAWAY, goaway.type); 961 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 962 assertEquals(TYPE_RST_STREAM, rstStream.type); 963 assertEquals(1, rstStream.streamId); 964 } 965 966 @Test public void closeCancelsPings() throws Exception { 967 // write the mocking script 968 peer.acceptFrame(); // PING 969 peer.acceptFrame(); // GOAWAY 970 peer.play(); 971 972 // play it back 973 SpdyConnection connection = connection(peer, SPDY3); 974 Ping ping = connection.ping(); 975 connection.close(); 976 assertEquals(-1, ping.roundTripTime()); 977 } 978 979 @Test public void readTimeoutExpires() throws Exception { 980 // write the mocking script 981 peer.acceptFrame(); // SYN_STREAM 982 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 983 peer.acceptFrame(); // PING 984 peer.sendFrame().ping(true, 1, 0); 985 peer.play(); 986 987 // play it back 988 SpdyConnection connection = connection(peer, SPDY3); 989 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 990 stream.setReadTimeout(1000); 991 Source source = stream.getSource(); 992 long startNanos = System.nanoTime(); 993 try { 994 source.read(new OkBuffer(), 1); 995 fail(); 996 } catch (IOException expected) { 997 } 998 long elapsedNanos = System.nanoTime() - startNanos; 999 assertEquals(1000d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); 1000 assertEquals(1, connection.openStreamCount()); 1001 connection.ping().roundTripTime(); // Prevent the peer from exiting prematurely. 1002 1003 // verify the peer received what was expected 1004 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1005 assertEquals(TYPE_HEADERS, synStream.type); 1006 } 1007 1008 @Test public void headers() throws Exception { 1009 // write the mocking script 1010 peer.acceptFrame(); // SYN_STREAM 1011 peer.acceptFrame(); // PING 1012 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1013 peer.sendFrame().headers(1, headerEntries("c", "c3po")); 1014 peer.sendFrame().ping(true, 1, 0); 1015 peer.play(); 1016 1017 // play it back 1018 SpdyConnection connection = connection(peer, SPDY3); 1019 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1020 connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. 1021 assertEquals(headerEntries("a", "android", "c", "c3po"), stream.getResponseHeaders()); 1022 1023 // verify the peer received what was expected 1024 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1025 assertEquals(TYPE_HEADERS, synStream.type); 1026 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 1027 MockSpdyPeer.InFrame ping = peer.takeFrame(); 1028 assertEquals(TYPE_PING, ping.type); 1029 } 1030 1031 @Test public void headersBeforeReply() throws Exception { 1032 // write the mocking script 1033 peer.acceptFrame(); // SYN_STREAM 1034 peer.acceptFrame(); // PING 1035 peer.sendFrame().headers(1, headerEntries("c", "c3po")); 1036 peer.acceptFrame(); // RST_STREAM 1037 peer.sendFrame().ping(true, 1, 0); 1038 peer.play(); 1039 1040 // play it back 1041 SpdyConnection connection = connection(peer, SPDY3); 1042 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1043 connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. 1044 try { 1045 stream.getResponseHeaders(); 1046 fail(); 1047 } catch (IOException expected) { 1048 assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); 1049 } 1050 1051 // verify the peer received what was expected 1052 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1053 assertEquals(TYPE_HEADERS, synStream.type); 1054 assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); 1055 MockSpdyPeer.InFrame ping = peer.takeFrame(); 1056 assertEquals(TYPE_PING, ping.type); 1057 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 1058 assertEquals(TYPE_RST_STREAM, rstStream.type); 1059 assertEquals(PROTOCOL_ERROR, rstStream.errorCode); 1060 } 1061 1062 @Test public void readSendsWindowUpdate() throws Exception { 1063 readSendsWindowUpdate(SPDY3); 1064 } 1065 1066 @Test public void readSendsWindowUpdateHttp2() throws Exception { 1067 readSendsWindowUpdate(HTTP_20_DRAFT_09); 1068 } 1069 1070 private void readSendsWindowUpdate(Variant variant) 1071 throws IOException, InterruptedException { 1072 peer.setVariantAndClient(variant, false); 1073 1074 int windowUpdateThreshold = DEFAULT_INITIAL_WINDOW_SIZE / 2; 1075 1076 // Write the mocking script. 1077 peer.acceptFrame(); // SYN_STREAM 1078 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1079 for (int i = 0; i < 3; i++) { 1080 // Send frames summing to windowUpdateThreshold. 1081 for (int sent = 0, count; sent < windowUpdateThreshold; sent += count) { 1082 count = Math.min(variant.maxFrameSize(), windowUpdateThreshold - sent); 1083 peer.sendFrame().data(false, 1, data(count)); 1084 } 1085 peer.acceptFrame(); // connection WINDOW UPDATE 1086 peer.acceptFrame(); // stream WINDOW UPDATE 1087 } 1088 peer.sendFrame().data(true, 1, data(0)); 1089 peer.play(); 1090 1091 // Play it back. 1092 SpdyConnection connection = connection(peer, variant); 1093 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), false, true); 1094 assertEquals(0, stream.unacknowledgedBytesRead); 1095 assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); 1096 Source in = stream.getSource(); 1097 OkBuffer buffer = new OkBuffer(); 1098 while (in.read(buffer, 1024) != -1) { 1099 if (buffer.size() == 3 * windowUpdateThreshold) break; 1100 } 1101 assertEquals(-1, in.read(buffer, 1)); 1102 1103 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1104 assertEquals(TYPE_HEADERS, synStream.type); 1105 for (int i = 0; i < 3; i++) { 1106 List<Integer> windowUpdateStreamIds = new ArrayList(2); 1107 for (int j = 0; j < 2; j++) { 1108 MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); 1109 assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); 1110 windowUpdateStreamIds.add(windowUpdate.streamId); 1111 assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); 1112 } 1113 assertTrue(windowUpdateStreamIds.contains(0)); // connection 1114 assertTrue(windowUpdateStreamIds.contains(1)); // stream 1115 } 1116 } 1117 1118 private OkBuffer data(int byteCount) { 1119 return new OkBuffer().write(new byte[byteCount]); 1120 } 1121 1122 @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdate() throws Exception { 1123 serverSendsEmptyDataClientDoesntSendWindowUpdate(SPDY3); 1124 } 1125 1126 @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() throws Exception { 1127 serverSendsEmptyDataClientDoesntSendWindowUpdate(HTTP_20_DRAFT_09); 1128 } 1129 1130 private void serverSendsEmptyDataClientDoesntSendWindowUpdate(Variant variant) 1131 throws IOException, InterruptedException { 1132 peer.setVariantAndClient(variant, false); 1133 1134 // Write the mocking script. 1135 peer.acceptFrame(); // SYN_STREAM 1136 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1137 peer.sendFrame().data(true, 1, data(0)); 1138 peer.play(); 1139 1140 // Play it back. 1141 SpdyConnection connection = connection(peer, variant); 1142 SpdyStream client = connection.newStream(headerEntries("b", "banana"), false, true); 1143 assertEquals(-1, client.getSource().read(new OkBuffer(), 1)); 1144 1145 // Verify the peer received what was expected. 1146 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1147 assertEquals(TYPE_HEADERS, synStream.type); 1148 assertEquals(3, peer.frameCount()); 1149 } 1150 1151 @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdate() throws Exception { 1152 clientSendsEmptyDataServerDoesntSendWindowUpdate(SPDY3); 1153 } 1154 1155 @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() throws Exception { 1156 clientSendsEmptyDataServerDoesntSendWindowUpdate(HTTP_20_DRAFT_09); 1157 } 1158 1159 private void clientSendsEmptyDataServerDoesntSendWindowUpdate(Variant variant) 1160 throws IOException, InterruptedException { 1161 peer.setVariantAndClient(variant, false); 1162 1163 // Write the mocking script. 1164 peer.acceptFrame(); // SYN_STREAM 1165 peer.acceptFrame(); // DATA 1166 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1167 peer.play(); 1168 1169 // Play it back. 1170 SpdyConnection connection = connection(peer, variant); 1171 SpdyStream client = connection.newStream(headerEntries("b", "banana"), true, true); 1172 BufferedSink out = Okio.buffer(client.getSink()); 1173 out.write(Util.EMPTY_BYTE_ARRAY); 1174 out.flush(); 1175 out.close(); 1176 1177 // Verify the peer received what was expected. 1178 assertEquals(TYPE_HEADERS, peer.takeFrame().type); 1179 assertEquals(TYPE_DATA, peer.takeFrame().type); 1180 assertEquals(3, peer.frameCount()); 1181 } 1182 1183 @Test public void writeAwaitsWindowUpdate() throws Exception { 1184 int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize()); 1185 1186 // Write the mocking script. This accepts more data frames than necessary! 1187 peer.acceptFrame(); // SYN_STREAM 1188 for (int i = 0; i < framesThatFillWindow; i++) { 1189 peer.acceptFrame(); // DATA 1190 } 1191 peer.acceptFrame(); // DATA we won't be able to flush until a window update. 1192 peer.play(); 1193 1194 // Play it back. 1195 SpdyConnection connection = connection(peer, SPDY3); 1196 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1197 BufferedSink out = Okio.buffer(stream.getSink()); 1198 out.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]); 1199 out.flush(); 1200 1201 // Check that we've filled the window for both the stream and also the connection. 1202 assertEquals(0, connection.bytesLeftInWriteWindow); 1203 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1204 1205 out.writeByte('a'); 1206 assertFlushBlocks(out); 1207 1208 // receiving a window update on the connection isn't enough. 1209 connection.readerRunnable.windowUpdate(0, 1); 1210 assertFlushBlocks(out); 1211 1212 // receiving a window update on the stream will unblock the stream. 1213 connection.readerRunnable.windowUpdate(1, 1); 1214 out.flush(); 1215 1216 // Verify the peer received what was expected. 1217 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1218 assertEquals(TYPE_HEADERS, synStream.type); 1219 for (int i = 0; i < framesThatFillWindow; i++) { 1220 MockSpdyPeer.InFrame data = peer.takeFrame(); 1221 assertEquals(TYPE_DATA, data.type); 1222 } 1223 } 1224 1225 @Test public void initialSettingsWithWindowSizeAdjustsConnection() throws Exception { 1226 int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize()); 1227 1228 // Write the mocking script. This accepts more data frames than necessary! 1229 peer.acceptFrame(); // SYN_STREAM 1230 for (int i = 0; i < framesThatFillWindow; i++) { 1231 peer.acceptFrame(); // DATA on stream 1 1232 } 1233 peer.acceptFrame(); // DATA on stream 2 1234 peer.play(); 1235 1236 // Play it back. 1237 SpdyConnection connection = connection(peer, SPDY3); 1238 SpdyStream stream = connection.newStream(headerEntries("a", "apple"), true, true); 1239 BufferedSink out = Okio.buffer(stream.getSink()); 1240 out.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]); 1241 out.flush(); 1242 1243 // write 1 more than the window size 1244 out.writeByte('a'); 1245 assertFlushBlocks(out); 1246 1247 // Check that we've filled the window for both the stream and also the connection. 1248 assertEquals(0, connection.bytesLeftInWriteWindow); 1249 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1250 1251 // Receiving a Settings with a larger window size will unblock the streams. 1252 Settings initial = new Settings(); 1253 initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, DEFAULT_INITIAL_WINDOW_SIZE + 1); 1254 connection.readerRunnable.settings(false, initial); 1255 1256 assertEquals(1, connection.bytesLeftInWriteWindow); 1257 assertEquals(1, connection.getStream(1).bytesLeftInWriteWindow); 1258 1259 // The stream should no longer be blocked. 1260 out.flush(); 1261 1262 assertEquals(0, connection.bytesLeftInWriteWindow); 1263 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1264 1265 // Settings after the initial do not affect the connection window size. 1266 Settings next = new Settings(); 1267 next.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, DEFAULT_INITIAL_WINDOW_SIZE + 2); 1268 connection.readerRunnable.settings(false, next); 1269 1270 assertEquals(0, connection.bytesLeftInWriteWindow); // connection wasn't affected. 1271 assertEquals(1, connection.getStream(1).bytesLeftInWriteWindow); 1272 } 1273 1274 @Test public void testTruncatedDataFrame() throws Exception { 1275 // write the mocking script 1276 peer.acceptFrame(); // SYN_STREAM 1277 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1278 peer.sendTruncatedFrame(8 + 100).data(false, 1, data(1024)); 1279 peer.play(); 1280 1281 // play it back 1282 SpdyConnection connection = connection(peer, SPDY3); 1283 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1284 assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); 1285 Source in = stream.getSource(); 1286 try { 1287 Okio.buffer(in).readByteString(101); 1288 fail(); 1289 } catch (IOException expected) { 1290 assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); 1291 } 1292 } 1293 1294 @Test public void blockedStreamDoesntStarveNewStream() throws Exception { 1295 int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, SPDY3.maxFrameSize()); 1296 1297 // Write the mocking script. This accepts more data frames than necessary! 1298 peer.acceptFrame(); // SYN_STREAM on stream 1 1299 for (int i = 0; i < framesThatFillWindow; i++) { 1300 peer.acceptFrame(); // DATA on stream 1 1301 } 1302 peer.acceptFrame(); // SYN_STREAM on stream 2 1303 peer.acceptFrame(); // DATA on stream 2 1304 peer.play(); 1305 1306 // Play it back. 1307 SpdyConnection connection = connection(peer, SPDY3); 1308 SpdyStream stream1 = connection.newStream(headerEntries("a", "apple"), true, true); 1309 BufferedSink out1 = Okio.buffer(stream1.getSink()); 1310 out1.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]); 1311 out1.flush(); 1312 1313 // Check that we've filled the window for both the stream and also the connection. 1314 assertEquals(0, connection.bytesLeftInWriteWindow); 1315 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1316 1317 // receiving a window update on the the connection will unblock new streams. 1318 connection.readerRunnable.windowUpdate(0, 3); 1319 1320 assertEquals(3, connection.bytesLeftInWriteWindow); 1321 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1322 1323 // Another stream should be able to send data even though 1 is blocked. 1324 SpdyStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); 1325 BufferedSink out2 = Okio.buffer(stream2.getSink()); 1326 out2.writeUtf8("foo"); 1327 out2.flush(); 1328 1329 assertEquals(0, connection.bytesLeftInWriteWindow); 1330 assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); 1331 assertEquals(DEFAULT_INITIAL_WINDOW_SIZE - 3, connection.getStream(3).bytesLeftInWriteWindow); 1332 } 1333 1334 @Test public void maxFrameSizeHonored() throws Exception { 1335 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 1336 1337 byte[] buff = new byte[HTTP_20_DRAFT_09.maxFrameSize() + 1]; 1338 Arrays.fill(buff, (byte) '*'); 1339 1340 // write the mocking script 1341 peer.acceptFrame(); // SYN_STREAM 1342 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1343 peer.acceptFrame(); // DATA 1 1344 peer.acceptFrame(); // DATA 2 1345 peer.play(); 1346 1347 // play it back 1348 SpdyConnection connection = connection(peer, HTTP_20_DRAFT_09); 1349 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1350 BufferedSink out = Okio.buffer(stream.getSink()); 1351 out.write(buff); 1352 out.flush(); 1353 out.close(); 1354 1355 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 1356 assertEquals(TYPE_HEADERS, synStream.type); 1357 MockSpdyPeer.InFrame data = peer.takeFrame(); 1358 assertEquals(HTTP_20_DRAFT_09.maxFrameSize(), data.data.length); 1359 data = peer.takeFrame(); 1360 assertEquals(1, data.data.length); 1361 } 1362 1363 /** https://github.com/square/okhttp/issues/333 */ 1364 @Test public void headerBlockHasTrailingCompressedBytes512() throws Exception { 1365 // This specially-formatted frame has trailing deflated bytes after the name value block. 1366 String frame = "gAMAAgAAAgkAAAABeLvjxqfCYgAAAAD//2IAAAAA//9iAAAAAP//YgQAAAD//2IAAAAA//9iAAAAAP/" 1367 + "/YgAAAAD//2IEAAAA//9KBAAAAP//YgAAAAD//2IAAAAA//9iAAAAAP//sgEAAAD//2IAAAAA\n//9iBAAAAP//Y" 1368 + "gIAAAD//2IGAAAA//9iAQAAAP//YgUAAAD//2IDAAAA//9iBwAAAP//4gAAAAD//+IEAAAA///iAgAAAP//4gYAA" 1369 + "AD//+IBAAAA///iBQAAAP//4gMAAAD//+IHAAAA//8SAAAAAP//EgQAAAD//xICAAAA//8SBgAAAP//EgEAAAD//" 1370 + "xIFAAAA//8SAwAAAP//EgcAAAD//5IAAAAA//+SBAAAAP//kgIAAAD//5IGAAAA//+SAQAAAP//kgUAAAD//5IDA" 1371 + "AAA//+SBwAAAP//UgAAAAD//1IEAAAA//9SAgAAAP//UgYAAAD//1IBAAAA//9SBQAAAP//UgMAAAD//1IHAAAA/" 1372 + "//SAAAAAP//0gQAAAD//9ICAAAA///SBgAAAP//0gEAAAD//9IFAAAA///SAwAAAP//0gcAAAD//zIAAAAA//8yB" 1373 + "AAAAP//MgIAAAD//zIGAAAA//8yAQAAAP//MgUAAAD//zIDAAAA//8yBwAAAP//sgAAAAD//7IEAAAA//+yAgAAA" 1374 + "P//sgYAAAD//w=="; 1375 headerBlockHasTrailingCompressedBytes(frame, 60); 1376 } 1377 1378 @Test public void headerBlockHasTrailingCompressedBytes2048() throws Exception { 1379 // This specially-formatted frame has trailing deflated bytes after the name value block. 1380 String frame = "gAMAAgAAB/sAAAABeLvjxqfCAqYjRhAGJmxGxUQAAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAA" 1381 + "AAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP/" 1382 + "/SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQ" 1383 + "AAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD" 1384 + "//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0o" 1385 + "EAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAA" 1386 + "A//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9" 1387 + "KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAA" 1388 + "AAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP/" 1389 + "/SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQ" 1390 + "AAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD" 1391 + "//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0o" 1392 + "EAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAA" 1393 + "A//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9" 1394 + "KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAA" 1395 + "AAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP/" 1396 + "/SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQ" 1397 + "AAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD" 1398 + "//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0o" 1399 + "EAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAA" 1400 + "A//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9" 1401 + "KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAA" 1402 + "AAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP/" 1403 + "/SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQ" 1404 + "AAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD" 1405 + "//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0o" 1406 + "EAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAA" 1407 + "A//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9" 1408 + "KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAA" 1409 + "AAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP/" 1410 + "/SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQAAAD//0oEAAAA//9KBAAAAP//SgQ" 1411 + "AAAD//0oEAAAA//8="; 1412 headerBlockHasTrailingCompressedBytes(frame, 289); 1413 } 1414 1415 private void headerBlockHasTrailingCompressedBytes(String frame, int length) throws IOException { 1416 // write the mocking script 1417 peer.acceptFrame(); // SYN_STREAM 1418 peer.sendFrame(ByteString.decodeBase64(frame).toByteArray()); 1419 peer.sendFrame().data(true, 1, new OkBuffer().writeUtf8("robot")); 1420 peer.acceptFrame(); // DATA 1421 peer.play(); 1422 1423 // play it back 1424 SpdyConnection connection = connection(peer, SPDY3); 1425 SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true); 1426 assertEquals("a", stream.getResponseHeaders().get(0).name.utf8()); 1427 assertEquals(length, stream.getResponseHeaders().get(0).value.size()); 1428 assertStreamData("robot", stream.getSource()); 1429 } 1430 1431 @Test public void pushPromiseStream() throws Exception { 1432 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 1433 1434 // write the mocking script 1435 peer.acceptFrame(); // SYN_STREAM 1436 peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); 1437 final List<Header> expectedRequestHeaders = Arrays.asList( 1438 new Header(Header.TARGET_METHOD, "GET"), 1439 new Header(Header.TARGET_SCHEME, "https"), 1440 new Header(Header.TARGET_AUTHORITY, "squareup.com"), 1441 new Header(Header.TARGET_PATH, "/cached") 1442 ); 1443 peer.sendFrame().pushPromise(1, 2, expectedRequestHeaders); 1444 final List<Header> expectedResponseHeaders = Arrays.asList( 1445 new Header(Header.RESPONSE_STATUS, "200") 1446 ); 1447 peer.sendFrame().synReply(true, 2, expectedResponseHeaders); 1448 peer.sendFrame().data(true, 1, data(0)); 1449 peer.play(); 1450 1451 final List events = new ArrayList(); 1452 PushObserver observer = new PushObserver() { 1453 1454 @Override public boolean onRequest(int streamId, List<Header> requestHeaders) { 1455 assertEquals(2, streamId); 1456 events.add(requestHeaders); 1457 return false; 1458 } 1459 1460 @Override public boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last) { 1461 assertEquals(2, streamId); 1462 assertTrue(last); 1463 events.add(responseHeaders); 1464 return false; 1465 } 1466 1467 @Override public boolean onData(int streamId, BufferedSource source, int byteCount, 1468 boolean last) throws IOException { 1469 events.add(new AssertionError("onData")); 1470 return false; 1471 } 1472 1473 @Override public void onReset(int streamId, ErrorCode errorCode) { 1474 events.add(new AssertionError("onReset")); 1475 } 1476 }; 1477 1478 // play it back 1479 SpdyConnection connection = connectionBuilder(peer, HTTP_20_DRAFT_09) 1480 .pushObserver(observer).build(); 1481 SpdyStream client = connection.newStream(headerEntries("b", "banana"), false, true); 1482 assertEquals(-1, client.getSource().read(new OkBuffer(), 1)); 1483 1484 // verify the peer received what was expected 1485 assertEquals(TYPE_HEADERS, peer.takeFrame().type); 1486 1487 assertEquals(2, events.size()); 1488 assertEquals(expectedRequestHeaders, events.get(0)); 1489 assertEquals(expectedResponseHeaders, events.get(1)); 1490 } 1491 1492 @Test public void doublePushPromise() throws Exception { 1493 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 1494 1495 // write the mocking script 1496 peer.sendFrame().pushPromise(1,2, headerEntries("a", "android")); 1497 peer.acceptFrame(); // SYN_REPLY 1498 peer.sendFrame().pushPromise(1, 2, headerEntries("b", "banana")); 1499 peer.acceptFrame(); // RST_STREAM 1500 peer.play(); 1501 1502 // play it back 1503 SpdyConnection connection = connectionBuilder(peer, HTTP_20_DRAFT_09).build(); 1504 connection.newStream(headerEntries("b", "banana"), false, true); 1505 1506 // verify the peer received what was expected 1507 assertEquals(TYPE_HEADERS, peer.takeFrame().type); 1508 assertEquals(PROTOCOL_ERROR, peer.takeFrame().errorCode); 1509 } 1510 1511 @Test public void pushPromiseStreamsAutomaticallyCancel() throws Exception { 1512 peer.setVariantAndClient(HTTP_20_DRAFT_09, false); 1513 1514 // write the mocking script 1515 peer.sendFrame().pushPromise(1, 2, Arrays.asList( 1516 new Header(Header.TARGET_METHOD, "GET"), 1517 new Header(Header.TARGET_SCHEME, "https"), 1518 new Header(Header.TARGET_AUTHORITY, "squareup.com"), 1519 new Header(Header.TARGET_PATH, "/cached") 1520 )); 1521 peer.sendFrame().synReply(true, 2, Arrays.asList( 1522 new Header(Header.RESPONSE_STATUS, "200") 1523 )); 1524 peer.acceptFrame(); // RST_STREAM 1525 peer.play(); 1526 1527 // play it back 1528 connectionBuilder(peer, HTTP_20_DRAFT_09) 1529 .pushObserver(PushObserver.CANCEL).build(); 1530 1531 // verify the peer received what was expected 1532 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 1533 assertEquals(TYPE_RST_STREAM, rstStream.type); 1534 assertEquals(2, rstStream.streamId); 1535 assertEquals(CANCEL, rstStream.errorCode); 1536 } 1537 1538 private SpdyConnection sendHttp2SettingsAndCheckForAck(boolean client, Settings settings) 1539 throws IOException, InterruptedException { 1540 peer.setVariantAndClient(HTTP_20_DRAFT_09, client); 1541 peer.sendFrame().settings(settings); 1542 peer.acceptFrame(); // ACK 1543 peer.play(); 1544 1545 // play it back 1546 SpdyConnection connection = connection(peer, HTTP_20_DRAFT_09); 1547 1548 // verify the peer received the ACK 1549 MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); 1550 assertEquals(TYPE_SETTINGS, ackFrame.type); 1551 assertEquals(0, ackFrame.streamId); 1552 assertTrue(ackFrame.ack); 1553 return connection; 1554 } 1555 1556 private SpdyConnection connection(MockSpdyPeer peer, Variant variant) throws IOException { 1557 return connectionBuilder(peer, variant).build(); 1558 } 1559 1560 private SpdyConnection.Builder connectionBuilder(MockSpdyPeer peer, Variant variant) 1561 throws IOException { 1562 return new SpdyConnection.Builder(true, peer.openSocket()) 1563 .pushObserver(IGNORE) 1564 .protocol(variant.getProtocol()); 1565 } 1566 1567 private void assertStreamData(String expected, Source source) throws IOException { 1568 OkBuffer buffer = new OkBuffer(); 1569 while (source.read(buffer, Long.MAX_VALUE) != -1) { 1570 } 1571 String actual = buffer.readUtf8(buffer.size()); 1572 assertEquals(expected, actual); 1573 } 1574 1575 private void assertFlushBlocks(BufferedSink out) throws IOException { 1576 interruptAfterDelay(500); 1577 try { 1578 out.flush(); 1579 fail(); 1580 } catch (InterruptedIOException expected) { 1581 } 1582 } 1583 1584 /** Interrupts the current thread after {@code delayMillis}. */ 1585 private void interruptAfterDelay(final long delayMillis) { 1586 final Thread toInterrupt = Thread.currentThread(); 1587 new Thread("interrupting cow") { 1588 @Override public void run() { 1589 try { 1590 Thread.sleep(delayMillis); 1591 toInterrupt.interrupt(); 1592 } catch (InterruptedException e) { 1593 throw new AssertionError(); 1594 } 1595 } 1596 }.start(); 1597 } 1598 1599 static int roundUp(int num, int divisor) { 1600 return (num + divisor - 1) / divisor; 1601 } 1602 1603 static final PushObserver IGNORE = new PushObserver() { 1604 1605 @Override public boolean onRequest(int streamId, List<Header> requestHeaders) { 1606 return false; 1607 } 1608 1609 @Override public boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last) { 1610 return false; 1611 } 1612 1613 @Override public boolean onData(int streamId, BufferedSource source, int byteCount, 1614 boolean last) throws IOException { 1615 source.skip(byteCount); 1616 return false; 1617 } 1618 1619 @Override public void onReset(int streamId, ErrorCode errorCode) { 1620 } 1621 }; 1622} 1623