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 */ 16 17package com.squareup.okhttp.internal.spdy; 18 19import com.squareup.okhttp.internal.Util; 20import java.io.ByteArrayOutputStream; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.InterruptedIOException; 24import java.io.OutputStream; 25import java.util.Arrays; 26import java.util.concurrent.TimeUnit; 27import java.util.concurrent.atomic.AtomicInteger; 28import org.junit.After; 29import org.junit.Test; 30 31import static com.squareup.okhttp.internal.Util.UTF_8; 32import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE; 33import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_FIN; 34import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_UNIDIRECTIONAL; 35import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_INTERNAL_ERROR; 36import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_PROTOCOL_ERROR; 37import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_DATA; 38import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_GOAWAY; 39import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_NOOP; 40import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_PING; 41import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_RST_STREAM; 42import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_REPLY; 43import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_STREAM; 44import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_WINDOW_UPDATE; 45import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_FLOW_CONTROL_ERROR; 46import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_INVALID_STREAM; 47import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_PROTOCOL_ERROR; 48import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_REFUSED_STREAM; 49import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_STREAM_IN_USE; 50import static com.squareup.okhttp.internal.spdy.SpdyStream.WINDOW_UPDATE_THRESHOLD; 51import static org.junit.Assert.assertEquals; 52import static org.junit.Assert.assertTrue; 53import static org.junit.Assert.fail; 54 55public final class SpdyConnectionTest { 56 private static final IncomingStreamHandler REJECT_INCOMING_STREAMS = new IncomingStreamHandler() { 57 @Override public void receive(SpdyStream stream) throws IOException { 58 throw new AssertionError(); 59 } 60 }; 61 private final MockSpdyPeer peer = new MockSpdyPeer(); 62 63 @After public void tearDown() throws Exception { 64 peer.close(); 65 } 66 67 @Test public void clientCreatesStreamAndServerReplies() throws Exception { 68 // write the mocking script 69 peer.acceptFrame(); // SYN_STREAM 70 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 71 peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8")); 72 peer.acceptFrame(); // DATA 73 peer.play(); 74 75 // play it back 76 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 77 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 78 assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); 79 assertStreamData("robot", stream.getInputStream()); 80 writeAndClose(stream, "c3po"); 81 assertEquals(0, connection.openStreamCount()); 82 83 // verify the peer received what was expected 84 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 85 assertEquals(TYPE_SYN_STREAM, synStream.type); 86 assertEquals(0, synStream.flags); 87 assertEquals(1, synStream.streamId); 88 assertEquals(0, synStream.associatedStreamId); 89 assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock); 90 MockSpdyPeer.InFrame requestData = peer.takeFrame(); 91 assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); 92 } 93 94 @Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception { 95 peer.acceptFrame(); // SYN_STREAM 96 peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); 97 peer.play(); 98 99 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 100 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, false); 101 assertEquals(1, connection.openStreamCount()); 102 assertEquals(Arrays.asList("b", "banana"), stream.getResponseHeaders()); 103 assertEquals(0, connection.openStreamCount()); 104 } 105 106 @Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception { 107 // write the mocking script 108 peer.acceptFrame(); // SYN_STREAM 109 peer.acceptFrame(); // PING 110 peer.sendFrame().synReply(FLAG_FIN, 1, Arrays.asList("a", "android")); 111 peer.sendFrame().ping(0, 1); 112 peer.play(); 113 114 // play it back 115 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 116 connection.newStream(Arrays.asList("b", "banana"), false, true); 117 assertEquals(1, connection.openStreamCount()); 118 connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. 119 assertEquals(0, connection.openStreamCount()); 120 121 // verify the peer received what was expected 122 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 123 assertEquals(TYPE_SYN_STREAM, synStream.type); 124 MockSpdyPeer.InFrame ping = peer.takeFrame(); 125 assertEquals(TYPE_PING, ping.type); 126 } 127 128 @Test public void serverCreatesStreamAndClientReplies() throws Exception { 129 // write the mocking script 130 peer.sendFrame().synStream(0, 2, 0, 5, 129, Arrays.asList("a", "android")); 131 peer.acceptFrame(); // SYN_REPLY 132 peer.play(); 133 134 // play it back 135 final AtomicInteger receiveCount = new AtomicInteger(); 136 IncomingStreamHandler handler = new IncomingStreamHandler() { 137 @Override public void receive(SpdyStream stream) throws IOException { 138 receiveCount.incrementAndGet(); 139 assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders()); 140 assertEquals(-1, stream.getRstStatusCode()); 141 assertEquals(5, stream.getPriority()); 142 assertEquals(129, stream.getSlot()); 143 stream.reply(Arrays.asList("b", "banana"), true); 144 } 145 }; 146 new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); 147 148 // verify the peer received what was expected 149 MockSpdyPeer.InFrame reply = peer.takeFrame(); 150 assertEquals(TYPE_SYN_REPLY, reply.type); 151 assertEquals(0, reply.flags); 152 assertEquals(2, reply.streamId); 153 assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); 154 assertEquals(1, receiveCount.get()); 155 } 156 157 @Test public void replyWithNoData() throws Exception { 158 // write the mocking script 159 peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android")); 160 peer.acceptFrame(); // SYN_REPLY 161 peer.play(); 162 163 // play it back 164 final AtomicInteger receiveCount = new AtomicInteger(); 165 IncomingStreamHandler handler = new IncomingStreamHandler() { 166 @Override public void receive(SpdyStream stream) throws IOException { 167 stream.reply(Arrays.asList("b", "banana"), false); 168 receiveCount.incrementAndGet(); 169 } 170 }; 171 new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); 172 173 // verify the peer received what was expected 174 MockSpdyPeer.InFrame reply = peer.takeFrame(); 175 assertEquals(TYPE_SYN_REPLY, reply.type); 176 assertEquals(FLAG_FIN, reply.flags); 177 assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); 178 assertEquals(1, receiveCount.get()); 179 } 180 181 @Test public void noop() throws Exception { 182 // write the mocking script 183 peer.acceptFrame(); // NOOP 184 peer.play(); 185 186 // play it back 187 SpdyConnection connection = 188 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 189 .build(); 190 connection.noop(); 191 192 // verify the peer received what was expected 193 MockSpdyPeer.InFrame ping = peer.takeFrame(); 194 assertEquals(TYPE_NOOP, ping.type); 195 assertEquals(0, ping.flags); 196 } 197 198 @Test public void serverPingsClient() throws Exception { 199 // write the mocking script 200 peer.sendFrame().ping(0, 2); 201 peer.acceptFrame(); // PING 202 peer.play(); 203 204 // play it back 205 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build(); 206 207 // verify the peer received what was expected 208 MockSpdyPeer.InFrame ping = peer.takeFrame(); 209 assertEquals(TYPE_PING, ping.type); 210 assertEquals(0, ping.flags); 211 assertEquals(2, ping.streamId); 212 } 213 214 @Test public void clientPingsServer() throws Exception { 215 // write the mocking script 216 peer.acceptFrame(); // PING 217 peer.sendFrame().ping(0, 1); 218 peer.play(); 219 220 // play it back 221 SpdyConnection connection = 222 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 223 .build(); 224 Ping ping = connection.ping(); 225 assertTrue(ping.roundTripTime() > 0); 226 assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); 227 228 // verify the peer received what was expected 229 MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); 230 assertEquals(TYPE_PING, pingFrame.type); 231 assertEquals(0, pingFrame.flags); 232 assertEquals(1, pingFrame.streamId); 233 } 234 235 @Test public void unexpectedPingIsNotReturned() throws Exception { 236 // write the mocking script 237 peer.sendFrame().ping(0, 2); 238 peer.acceptFrame(); // PING 239 peer.sendFrame().ping(0, 3); // This ping will not be returned. 240 peer.sendFrame().ping(0, 4); 241 peer.acceptFrame(); // PING 242 peer.play(); 243 244 // play it back 245 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build(); 246 247 // verify the peer received what was expected 248 MockSpdyPeer.InFrame ping2 = peer.takeFrame(); 249 assertEquals(2, ping2.streamId); 250 MockSpdyPeer.InFrame ping4 = peer.takeFrame(); 251 assertEquals(4, ping4.streamId); 252 } 253 254 @Test public void serverSendsSettingsToClient() throws Exception { 255 // write the mocking script 256 Settings settings = new Settings(); 257 settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); 258 peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings); 259 peer.sendFrame().ping(0, 2); 260 peer.acceptFrame(); // PING 261 peer.play(); 262 263 // play it back 264 SpdyConnection connection = 265 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 266 .build(); 267 268 peer.takeFrame(); // Guarantees that the Settings frame has been processed. 269 synchronized (connection) { 270 assertEquals(10, connection.settings.getMaxConcurrentStreams(-1)); 271 } 272 } 273 274 @Test public void multipleSettingsFramesAreMerged() throws Exception { 275 // write the mocking script 276 Settings settings1 = new Settings(); 277 settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); 278 settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); 279 settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300); 280 peer.sendFrame().settings(0, settings1); 281 Settings settings2 = new Settings(); 282 settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); 283 settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); 284 settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); 285 peer.sendFrame().settings(0, settings2); 286 peer.sendFrame().ping(0, 2); 287 peer.acceptFrame(); 288 peer.play(); 289 290 // play it back 291 SpdyConnection connection = 292 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 293 .build(); 294 295 peer.takeFrame(); // Guarantees that the Settings frame has been processed. 296 synchronized (connection) { 297 assertEquals(100, connection.settings.getUploadBandwidth(-1)); 298 assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.UPLOAD_BANDWIDTH)); 299 assertEquals(400, connection.settings.getDownloadBandwidth(-1)); 300 assertEquals(0, connection.settings.flags(Settings.DOWNLOAD_BANDWIDTH)); 301 assertEquals(500, connection.settings.getDownloadRetransRate(-1)); 302 assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.DOWNLOAD_RETRANS_RATE)); 303 assertEquals(600, connection.settings.getMaxConcurrentStreams(-1)); 304 assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.MAX_CONCURRENT_STREAMS)); 305 } 306 } 307 308 @Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception { 309 // write the mocking script 310 peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8")); 311 peer.acceptFrame(); // RST_STREAM 312 peer.sendFrame().ping(0, 2); 313 peer.acceptFrame(); // PING 314 peer.play(); 315 316 // play it back 317 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build(); 318 319 // verify the peer received what was expected 320 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 321 assertEquals(TYPE_RST_STREAM, rstStream.type); 322 assertEquals(0, rstStream.flags); 323 assertEquals(42, rstStream.streamId); 324 assertEquals(RST_INVALID_STREAM, rstStream.statusCode); 325 MockSpdyPeer.InFrame ping = peer.takeFrame(); 326 assertEquals(2, ping.streamId); 327 } 328 329 @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception { 330 // write the mocking script 331 peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android")); 332 peer.acceptFrame(); // RST_STREAM 333 peer.sendFrame().ping(0, 2); 334 peer.acceptFrame(); // PING 335 peer.play(); 336 337 // play it back 338 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build(); 339 340 // verify the peer received what was expected 341 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 342 assertEquals(TYPE_RST_STREAM, rstStream.type); 343 assertEquals(0, rstStream.flags); 344 assertEquals(42, rstStream.streamId); 345 assertEquals(RST_INVALID_STREAM, rstStream.statusCode); 346 MockSpdyPeer.InFrame ping = peer.takeFrame(); 347 assertEquals(2, ping.streamId); 348 } 349 350 @Test public void clientClosesClientOutputStream() throws Exception { 351 // write the mocking script 352 peer.acceptFrame(); // SYN_STREAM 353 peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); 354 peer.acceptFrame(); // TYPE_DATA 355 peer.acceptFrame(); // TYPE_DATA with FLAG_FIN 356 peer.acceptFrame(); // PING 357 peer.sendFrame().ping(0, 1); 358 peer.play(); 359 360 // play it back 361 SpdyConnection connection = 362 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 363 .build(); 364 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, false); 365 OutputStream out = stream.getOutputStream(); 366 out.write("square".getBytes(UTF_8)); 367 out.flush(); 368 assertEquals(1, connection.openStreamCount()); 369 out.close(); 370 try { 371 out.write("round".getBytes(UTF_8)); 372 fail(); 373 } catch (Exception expected) { 374 assertEquals("stream closed", expected.getMessage()); 375 } 376 connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. 377 assertEquals(0, connection.openStreamCount()); 378 379 // verify the peer received what was expected 380 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 381 assertEquals(TYPE_SYN_STREAM, synStream.type); 382 assertEquals(FLAG_UNIDIRECTIONAL, synStream.flags); 383 MockSpdyPeer.InFrame data = peer.takeFrame(); 384 assertEquals(TYPE_DATA, data.type); 385 assertEquals(0, data.flags); 386 assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); 387 MockSpdyPeer.InFrame fin = peer.takeFrame(); 388 assertEquals(TYPE_DATA, fin.type); 389 assertEquals(FLAG_FIN, fin.flags); 390 MockSpdyPeer.InFrame ping = peer.takeFrame(); 391 assertEquals(TYPE_PING, ping.type); 392 assertEquals(1, ping.streamId); 393 } 394 395 @Test public void serverClosesClientOutputStream() throws Exception { 396 // write the mocking script 397 peer.acceptFrame(); // SYN_STREAM 398 peer.sendFrame().rstStream(1, SpdyStream.RST_CANCEL); 399 peer.acceptFrame(); // PING 400 peer.sendFrame().ping(0, 1); 401 peer.acceptFrame(); // DATA 402 peer.play(); 403 404 // play it back 405 SpdyConnection connection = 406 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 407 .build(); 408 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); 409 OutputStream out = stream.getOutputStream(); 410 connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. 411 try { 412 out.write("square".getBytes(UTF_8)); 413 fail(); 414 } catch (IOException expected) { 415 assertEquals("stream was reset: CANCEL", expected.getMessage()); 416 } 417 out.close(); 418 assertEquals(0, connection.openStreamCount()); 419 420 // verify the peer received what was expected 421 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 422 assertEquals(TYPE_SYN_STREAM, synStream.type); 423 assertEquals(0, synStream.flags); 424 MockSpdyPeer.InFrame ping = peer.takeFrame(); 425 assertEquals(TYPE_PING, ping.type); 426 assertEquals(1, ping.streamId); 427 MockSpdyPeer.InFrame data = peer.takeFrame(); 428 assertEquals(TYPE_DATA, data.type); 429 assertEquals(1, data.streamId); 430 assertEquals(FLAG_FIN, data.flags); 431 } 432 433 /** 434 * Test that the client sends a RST_STREAM if doing so won't disrupt the 435 * output stream. 436 */ 437 @Test public void clientClosesClientInputStream() throws Exception { 438 // write the mocking script 439 peer.acceptFrame(); // SYN_STREAM 440 peer.acceptFrame(); // RST_STREAM 441 peer.play(); 442 443 // play it back 444 SpdyConnection connection = 445 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 446 .build(); 447 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); 448 InputStream in = stream.getInputStream(); 449 OutputStream out = stream.getOutputStream(); 450 in.close(); 451 try { 452 in.read(); 453 fail(); 454 } catch (IOException expected) { 455 assertEquals("stream closed", expected.getMessage()); 456 } 457 try { 458 out.write('a'); 459 fail(); 460 } catch (IOException expected) { 461 assertEquals("stream finished", expected.getMessage()); 462 } 463 assertEquals(0, connection.openStreamCount()); 464 465 // verify the peer received what was expected 466 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 467 assertEquals(TYPE_SYN_STREAM, synStream.type); 468 assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); 469 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 470 assertEquals(TYPE_RST_STREAM, rstStream.type); 471 assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); 472 } 473 474 /** 475 * Test that the client doesn't send a RST_STREAM if doing so will disrupt 476 * the output stream. 477 */ 478 @Test public void clientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception { 479 // write the mocking script 480 peer.acceptFrame(); // SYN_STREAM 481 peer.acceptFrame(); // DATA 482 peer.acceptFrame(); // DATA with FLAG_FIN 483 peer.acceptFrame(); // RST_STREAM 484 peer.play(); 485 486 // play it back 487 SpdyConnection connection = 488 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 489 .build(); 490 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); 491 InputStream in = stream.getInputStream(); 492 OutputStream out = stream.getOutputStream(); 493 in.close(); 494 try { 495 in.read(); 496 fail(); 497 } catch (IOException expected) { 498 assertEquals("stream closed", expected.getMessage()); 499 } 500 out.write("square".getBytes(UTF_8)); 501 out.flush(); 502 out.close(); 503 assertEquals(0, connection.openStreamCount()); 504 505 // verify the peer received what was expected 506 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 507 assertEquals(TYPE_SYN_STREAM, synStream.type); 508 assertEquals(0, synStream.flags); 509 MockSpdyPeer.InFrame data = peer.takeFrame(); 510 assertEquals(TYPE_DATA, data.type); 511 assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); 512 MockSpdyPeer.InFrame fin = peer.takeFrame(); 513 assertEquals(TYPE_DATA, fin.type); 514 assertEquals(FLAG_FIN, fin.flags); 515 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 516 assertEquals(TYPE_RST_STREAM, rstStream.type); 517 assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); 518 } 519 520 @Test public void serverClosesClientInputStream() throws Exception { 521 // write the mocking script 522 peer.acceptFrame(); // SYN_STREAM 523 peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); 524 peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8)); 525 peer.play(); 526 527 // play it back 528 SpdyConnection connection = 529 new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS) 530 .build(); 531 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); 532 InputStream in = stream.getInputStream(); 533 assertStreamData("square", in); 534 assertEquals(0, connection.openStreamCount()); 535 536 // verify the peer received what was expected 537 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 538 assertEquals(TYPE_SYN_STREAM, synStream.type); 539 assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); 540 } 541 542 @Test public void remoteDoubleSynReply() throws Exception { 543 // write the mocking script 544 peer.acceptFrame(); // SYN_STREAM 545 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 546 peer.acceptFrame(); // PING 547 peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); 548 peer.sendFrame().ping(0, 1); 549 peer.acceptFrame(); // RST_STREAM 550 peer.play(); 551 552 // play it back 553 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 554 SpdyStream stream = connection.newStream(Arrays.asList("c", "cola"), true, true); 555 assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); 556 connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received. 557 try { 558 stream.getInputStream().read(); 559 fail(); 560 } catch (IOException expected) { 561 assertEquals("stream was reset: STREAM_IN_USE", expected.getMessage()); 562 } 563 564 // verify the peer received what was expected 565 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 566 assertEquals(TYPE_SYN_STREAM, synStream.type); 567 MockSpdyPeer.InFrame ping = peer.takeFrame(); 568 assertEquals(TYPE_PING, ping.type); 569 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 570 assertEquals(TYPE_RST_STREAM, rstStream.type); 571 assertEquals(1, rstStream.streamId); 572 assertEquals(0, rstStream.flags); 573 assertEquals(RST_STREAM_IN_USE, rstStream.statusCode); 574 } 575 576 @Test public void remoteDoubleSynStream() throws Exception { 577 // write the mocking script 578 peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android")); 579 peer.acceptFrame(); // SYN_REPLY 580 peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "banana")); 581 peer.acceptFrame(); // RST_STREAM 582 peer.play(); 583 584 // play it back 585 final AtomicInteger receiveCount = new AtomicInteger(); 586 IncomingStreamHandler handler = new IncomingStreamHandler() { 587 @Override public void receive(SpdyStream stream) throws IOException { 588 receiveCount.incrementAndGet(); 589 assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders()); 590 assertEquals(-1, stream.getRstStatusCode()); 591 stream.reply(Arrays.asList("c", "cola"), true); 592 } 593 }; 594 new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); 595 596 // verify the peer received what was expected 597 MockSpdyPeer.InFrame reply = peer.takeFrame(); 598 assertEquals(TYPE_SYN_REPLY, reply.type); 599 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 600 assertEquals(TYPE_RST_STREAM, rstStream.type); 601 assertEquals(2, rstStream.streamId); 602 assertEquals(0, rstStream.flags); 603 assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); 604 assertEquals(1, receiveCount.intValue()); 605 } 606 607 @Test public void remoteSendsDataAfterInFinished() throws Exception { 608 // write the mocking script 609 peer.acceptFrame(); // SYN_STREAM 610 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 611 peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8")); 612 peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "c3po".getBytes("UTF-8")); // Ignored. 613 peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded. 614 peer.acceptFrame(); // PING 615 peer.play(); 616 617 // play it back 618 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 619 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 620 assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); 621 assertStreamData("robot", stream.getInputStream()); 622 623 // verify the peer received what was expected 624 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 625 assertEquals(TYPE_SYN_STREAM, synStream.type); 626 MockSpdyPeer.InFrame ping = peer.takeFrame(); 627 assertEquals(TYPE_PING, ping.type); 628 assertEquals(2, ping.streamId); 629 assertEquals(0, ping.flags); 630 } 631 632 @Test public void remoteSendsTooMuchData() throws Exception { 633 // write the mocking script 634 peer.acceptFrame(); // SYN_STREAM 635 peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); 636 peer.sendFrame().data(0, 1, new byte[64 * 1024 + 1]); 637 peer.acceptFrame(); // RST_STREAM 638 peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded. 639 peer.acceptFrame(); // PING 640 peer.play(); 641 642 // play it back 643 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 644 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); 645 assertEquals(Arrays.asList("b", "banana"), stream.getResponseHeaders()); 646 647 // verify the peer received what was expected 648 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 649 assertEquals(TYPE_SYN_STREAM, synStream.type); 650 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 651 assertEquals(TYPE_RST_STREAM, rstStream.type); 652 assertEquals(1, rstStream.streamId); 653 assertEquals(0, rstStream.flags); 654 assertEquals(RST_FLOW_CONTROL_ERROR, rstStream.statusCode); 655 MockSpdyPeer.InFrame ping = peer.takeFrame(); 656 assertEquals(TYPE_PING, ping.type); 657 assertEquals(2, ping.streamId); 658 } 659 660 @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception { 661 // write the mocking script 662 peer.acceptFrame(); // SYN_STREAM 663 peer.sendFrame().rstStream(1, RST_REFUSED_STREAM); 664 peer.sendFrame().ping(0, 2); 665 peer.acceptFrame(); // PING 666 peer.play(); 667 668 // play it back 669 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 670 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); 671 try { 672 stream.getResponseHeaders(); 673 fail(); 674 } catch (IOException expected) { 675 assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); 676 } 677 assertEquals(0, connection.openStreamCount()); 678 679 // verify the peer received what was expected 680 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 681 assertEquals(TYPE_SYN_STREAM, synStream.type); 682 MockSpdyPeer.InFrame ping = peer.takeFrame(); 683 assertEquals(TYPE_PING, ping.type); 684 assertEquals(2, ping.streamId); 685 assertEquals(0, ping.flags); 686 } 687 688 @Test public void receiveGoAway() throws Exception { 689 // write the mocking script 690 peer.acceptFrame(); // SYN_STREAM 1 691 peer.acceptFrame(); // SYN_STREAM 3 692 peer.sendFrame().goAway(0, 1, GOAWAY_PROTOCOL_ERROR); 693 peer.acceptFrame(); // PING 694 peer.sendFrame().ping(0, 1); 695 peer.acceptFrame(); // DATA STREAM 1 696 peer.play(); 697 698 // play it back 699 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 700 SpdyStream stream1 = connection.newStream(Arrays.asList("a", "android"), true, true); 701 SpdyStream stream2 = connection.newStream(Arrays.asList("b", "banana"), true, true); 702 connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received. 703 stream1.getOutputStream().write("abc".getBytes(UTF_8)); 704 try { 705 stream2.getOutputStream().write("abc".getBytes(UTF_8)); 706 fail(); 707 } catch (IOException expected) { 708 assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); 709 } 710 stream1.getOutputStream().write("def".getBytes(UTF_8)); 711 stream1.getOutputStream().close(); 712 try { 713 connection.newStream(Arrays.asList("c", "cola"), true, true); 714 fail(); 715 } catch (IOException expected) { 716 assertEquals("shutdown", expected.getMessage()); 717 } 718 assertEquals(1, connection.openStreamCount()); 719 720 // verify the peer received what was expected 721 MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); 722 assertEquals(TYPE_SYN_STREAM, synStream1.type); 723 MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); 724 assertEquals(TYPE_SYN_STREAM, synStream2.type); 725 MockSpdyPeer.InFrame ping = peer.takeFrame(); 726 assertEquals(TYPE_PING, ping.type); 727 MockSpdyPeer.InFrame data1 = peer.takeFrame(); 728 assertEquals(TYPE_DATA, data1.type); 729 assertEquals(1, data1.streamId); 730 assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); 731 } 732 733 @Test public void sendGoAway() throws Exception { 734 // write the mocking script 735 peer.acceptFrame(); // SYN_STREAM 1 736 peer.acceptFrame(); // GOAWAY 737 peer.acceptFrame(); // PING 738 peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "b")); // Should be ignored! 739 peer.sendFrame().ping(0, 1); 740 peer.play(); 741 742 // play it back 743 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 744 connection.newStream(Arrays.asList("a", "android"), true, true); 745 Ping ping = connection.ping(); 746 connection.shutdown(GOAWAY_PROTOCOL_ERROR); 747 assertEquals(1, connection.openStreamCount()); 748 ping.roundTripTime(); // Prevent the peer from exiting prematurely. 749 750 // verify the peer received what was expected 751 MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); 752 assertEquals(TYPE_SYN_STREAM, synStream1.type); 753 MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); 754 assertEquals(TYPE_PING, pingFrame.type); 755 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 756 assertEquals(TYPE_GOAWAY, goaway.type); 757 assertEquals(0, goaway.streamId); 758 assertEquals(GOAWAY_PROTOCOL_ERROR, goaway.statusCode); 759 } 760 761 @Test public void noPingsAfterShutdown() throws Exception { 762 // write the mocking script 763 peer.acceptFrame(); // GOAWAY 764 peer.play(); 765 766 // play it back 767 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 768 connection.shutdown(GOAWAY_INTERNAL_ERROR); 769 try { 770 connection.ping(); 771 fail(); 772 } catch (IOException expected) { 773 assertEquals("shutdown", expected.getMessage()); 774 } 775 776 // verify the peer received what was expected 777 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 778 assertEquals(TYPE_GOAWAY, goaway.type); 779 assertEquals(GOAWAY_INTERNAL_ERROR, goaway.statusCode); 780 } 781 782 @Test public void close() throws Exception { 783 // write the mocking script 784 peer.acceptFrame(); // SYN_STREAM 785 peer.acceptFrame(); // GOAWAY 786 peer.acceptFrame(); // RST_STREAM 787 peer.play(); 788 789 // play it back 790 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 791 SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); 792 assertEquals(1, connection.openStreamCount()); 793 connection.close(); 794 assertEquals(0, connection.openStreamCount()); 795 try { 796 connection.newStream(Arrays.asList("b", "banana"), true, true); 797 fail(); 798 } catch (IOException expected) { 799 assertEquals("shutdown", expected.getMessage()); 800 } 801 try { 802 stream.getOutputStream().write(0); 803 fail(); 804 } catch (IOException expected) { 805 assertEquals("stream was reset: CANCEL", expected.getMessage()); 806 } 807 try { 808 stream.getInputStream().read(); 809 fail(); 810 } catch (IOException expected) { 811 assertEquals("stream was reset: CANCEL", expected.getMessage()); 812 } 813 814 // verify the peer received what was expected 815 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 816 assertEquals(TYPE_SYN_STREAM, synStream.type); 817 MockSpdyPeer.InFrame goaway = peer.takeFrame(); 818 assertEquals(TYPE_GOAWAY, goaway.type); 819 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 820 assertEquals(TYPE_RST_STREAM, rstStream.type); 821 assertEquals(1, rstStream.streamId); 822 } 823 824 @Test public void closeCancelsPings() throws Exception { 825 // write the mocking script 826 peer.acceptFrame(); // PING 827 peer.acceptFrame(); // GOAWAY 828 peer.play(); 829 830 // play it back 831 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 832 Ping ping = connection.ping(); 833 connection.close(); 834 assertEquals(-1, ping.roundTripTime()); 835 } 836 837 @Test public void readTimeoutExpires() throws Exception { 838 // write the mocking script 839 peer.acceptFrame(); // SYN_STREAM 840 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 841 peer.acceptFrame(); // PING 842 peer.sendFrame().ping(0, 1); 843 peer.play(); 844 845 // play it back 846 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 847 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 848 stream.setReadTimeout(1000); 849 InputStream in = stream.getInputStream(); 850 long startNanos = System.nanoTime(); 851 try { 852 in.read(); 853 fail(); 854 } catch (IOException expected) { 855 } 856 long elapsedNanos = System.nanoTime() - startNanos; 857 assertEquals(1000d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); 858 assertEquals(1, connection.openStreamCount()); 859 connection.ping().roundTripTime(); // Prevent the peer from exiting prematurely. 860 861 // verify the peer received what was expected 862 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 863 assertEquals(TYPE_SYN_STREAM, synStream.type); 864 } 865 866 @Test public void headers() throws Exception { 867 // write the mocking script 868 peer.acceptFrame(); // SYN_STREAM 869 peer.acceptFrame(); // PING 870 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 871 peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); 872 peer.sendFrame().ping(0, 1); 873 peer.play(); 874 875 // play it back 876 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 877 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 878 connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. 879 assertEquals(Arrays.asList("a", "android", "c", "c3po"), stream.getResponseHeaders()); 880 881 // verify the peer received what was expected 882 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 883 assertEquals(TYPE_SYN_STREAM, synStream.type); 884 MockSpdyPeer.InFrame ping = peer.takeFrame(); 885 assertEquals(TYPE_PING, ping.type); 886 } 887 888 @Test public void headersBeforeReply() throws Exception { 889 // write the mocking script 890 peer.acceptFrame(); // SYN_STREAM 891 peer.acceptFrame(); // PING 892 peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); 893 peer.acceptFrame(); // RST_STREAM 894 peer.sendFrame().ping(0, 1); 895 peer.play(); 896 897 // play it back 898 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 899 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 900 connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. 901 try { 902 stream.getResponseHeaders(); 903 fail(); 904 } catch (IOException expected) { 905 assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); 906 } 907 908 // verify the peer received what was expected 909 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 910 assertEquals(TYPE_SYN_STREAM, synStream.type); 911 MockSpdyPeer.InFrame ping = peer.takeFrame(); 912 assertEquals(TYPE_PING, ping.type); 913 MockSpdyPeer.InFrame rstStream = peer.takeFrame(); 914 assertEquals(TYPE_RST_STREAM, rstStream.type); 915 assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); 916 } 917 918 @Test public void readSendsWindowUpdate() throws Exception { 919 // Write the mocking script. 920 peer.acceptFrame(); // SYN_STREAM 921 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 922 for (int i = 0; i < 3; i++) { 923 peer.sendFrame().data(0, 1, new byte[WINDOW_UPDATE_THRESHOLD]); 924 peer.acceptFrame(); // WINDOW UPDATE 925 } 926 peer.sendFrame().data(FLAG_FIN, 1, new byte[0]); 927 peer.play(); 928 929 // Play it back. 930 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 931 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 932 assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); 933 InputStream in = stream.getInputStream(); 934 int total = 0; 935 byte[] buffer = new byte[1024]; 936 int count; 937 while ((count = in.read(buffer)) != -1) { 938 total += count; 939 if (total == 3 * WINDOW_UPDATE_THRESHOLD) break; 940 } 941 assertEquals(-1, in.read()); 942 943 // Verify the peer received what was expected. 944 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 945 assertEquals(TYPE_SYN_STREAM, synStream.type); 946 for (int i = 0; i < 3; i++) { 947 MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); 948 assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); 949 assertEquals(1, windowUpdate.streamId); 950 assertEquals(WINDOW_UPDATE_THRESHOLD, windowUpdate.deltaWindowSize); 951 } 952 } 953 954 @Test public void writeAwaitsWindowUpdate() throws Exception { 955 // Write the mocking script. This accepts more data frames than necessary! 956 peer.acceptFrame(); // SYN_STREAM 957 for (int i = 0; i < Settings.DEFAULT_INITIAL_WINDOW_SIZE / 1024; i++) { 958 peer.acceptFrame(); // DATA 959 } 960 peer.play(); 961 962 // Play it back. 963 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 964 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 965 OutputStream out = stream.getOutputStream(); 966 out.write(new byte[Settings.DEFAULT_INITIAL_WINDOW_SIZE]); 967 interruptAfterDelay(500); 968 try { 969 out.write('a'); 970 out.flush(); 971 fail(); 972 } catch (InterruptedIOException expected) { 973 } 974 975 // Verify the peer received what was expected. 976 MockSpdyPeer.InFrame synStream = peer.takeFrame(); 977 assertEquals(TYPE_SYN_STREAM, synStream.type); 978 MockSpdyPeer.InFrame data = peer.takeFrame(); 979 assertEquals(TYPE_DATA, data.type); 980 } 981 982 @Test public void testTruncatedDataFrame() throws Exception { 983 // write the mocking script 984 peer.acceptFrame(); // SYN_STREAM 985 peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); 986 peer.sendTruncatedFrame(8 + 100).data(0, 1, new byte[1024]); 987 peer.play(); 988 989 // play it back 990 SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); 991 SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); 992 assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); 993 InputStream in = stream.getInputStream(); 994 try { 995 Util.readFully(in, new byte[101]); 996 fail(); 997 } catch (IOException expected) { 998 assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); 999 } 1000 } 1001 1002 private void writeAndClose(SpdyStream stream, String data) throws IOException { 1003 OutputStream out = stream.getOutputStream(); 1004 out.write(data.getBytes("UTF-8")); 1005 out.close(); 1006 } 1007 1008 private void assertStreamData(String expected, InputStream inputStream) throws IOException { 1009 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1010 byte[] buffer = new byte[1024]; 1011 for (int count; (count = inputStream.read(buffer)) != -1; ) { 1012 bytesOut.write(buffer, 0, count); 1013 } 1014 String actual = bytesOut.toString("UTF-8"); 1015 assertEquals(expected, actual); 1016 } 1017 1018 /** Interrupts the current thread after {@code delayMillis}. */ 1019 private void interruptAfterDelay(final long delayMillis) { 1020 final Thread toInterrupt = Thread.currentThread(); 1021 new Thread("interrupting cow") { 1022 @Override public void run() { 1023 try { 1024 Thread.sleep(delayMillis); 1025 toInterrupt.interrupt(); 1026 } catch (InterruptedException e) { 1027 throw new AssertionError(); 1028 } 1029 } 1030 }.start(); 1031 } 1032} 1033