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