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