1e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/*
2e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Copyright (C) 2011 The Android Open Source Project
3e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller *
4e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
5e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * you may not use this file except in compliance with the License.
6e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * You may obtain a copy of the License at
7e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller *
8e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
9e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller *
10e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Unless required by applicable law or agreed to in writing, software
11e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
12e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * See the License for the specific language governing permissions and
14e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * limitations under the License.
15e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */
16e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpackage com.squareup.okhttp.internal.spdy;
17e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
18e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport com.squareup.okhttp.internal.Util;
19e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.IOException;
20e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.ArrayList;
21e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.Arrays;
22e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.List;
23e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.concurrent.TimeUnit;
24e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Buffer;
25e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.BufferedSink;
26e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.BufferedSource;
27e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Okio;
28e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Source;
29e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport org.junit.After;
30e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport org.junit.Test;
31e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
32e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.TestUtil.headerEntries;
33e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.ErrorCode.CANCEL;
34e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.ErrorCode.PROTOCOL_ERROR;
35e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
36e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
37e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_DATA;
38e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_HEADERS;
39e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_PING;
40e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_RST_STREAM;
41e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_SETTINGS;
42e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_WINDOW_UPDATE;
43e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.assertEquals;
44e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.assertFalse;
45e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.assertTrue;
46e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.fail;
47e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
48e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpublic final class Http2ConnectionTest {
49a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final Variant HTTP_2 = new Http2();
50e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final MockSpdyPeer peer = new MockSpdyPeer();
51e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
52e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @After public void tearDown() throws Exception {
53e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.close();
54e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
55e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
56e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverPingsClientHttp2() throws Exception {
57e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
58e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
59e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
60e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().ping(false, 2, 3);
61e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // PING
62e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
63e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
64e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
65e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connection(peer, HTTP_2);
66e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
67e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
68e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame ping = peer.takeFrame();
69e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_PING, ping.type);
70e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, ping.streamId);
71e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(2, ping.payload1);
72e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3, ping.payload2);
73e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ping.ack);
74e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
75e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
76e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientPingsServerHttp2() throws Exception {
77e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
78e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
79e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
80e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // PING
81e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().ping(true, 1, 5);
82e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
83e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
84e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
85e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
86e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Ping ping = connection.ping();
87e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ping.roundTripTime() > 0);
88e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
89e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
90e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
91e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
92e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, pingFrame.streamId);
93e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(1, pingFrame.payload1);
94e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0x4f4b6f6b, pingFrame.payload2); // connection.ping() sets this.
95e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertFalse(pingFrame.ack);
96e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
97e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
98e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void peerHttp2ServerLowersInitialWindowSize() throws Exception {
99e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
100e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
101e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Settings initial = new Settings();
102e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 1684);
103e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Settings shouldntImpactConnection = new Settings();
104e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    shouldntImpactConnection.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 3368);
105e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
106e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().settings(initial);
107e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // ACK
108e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().settings(shouldntImpactConnection);
109e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // ACK 2
110e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // HEADERS
111e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
112e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
113e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
114e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
115e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Default is 64KiB - 1.
116e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(65535, connection.peerSettings.getInitialWindowSize(-1));
117e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
118e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Verify the peer received the ACK.
119e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame ackFrame = peer.takeFrame();
120e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_SETTINGS, ackFrame.type);
121e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, ackFrame.streamId);
122e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ackFrame.ack);
123e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    ackFrame = peer.takeFrame();
124e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_SETTINGS, ackFrame.type);
125e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, ackFrame.streamId);
126e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ackFrame.ack);
127e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
128e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // This stream was created *after* the connection settings were adjusted.
129e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true);
130e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
131e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3368, connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
132e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(1684, connection.bytesLeftInWriteWindow); // initial wasn't affected.
133e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // New Stream is has the most recent initial window size.
134e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3368, stream.bytesLeftInWriteWindow);
135e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
136e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
137e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void peerHttp2ServerZerosCompressionTable() throws Exception {
138e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    boolean client = false; // Peer is server, so we are client.
139e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Settings settings = new Settings();
140e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    settings.set(Settings.HEADER_TABLE_SIZE, PERSIST_VALUE, 0);
141e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
142e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = sendHttp2SettingsAndCheckForAck(client, settings);
143e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
144e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer's settings were read and applied.
145e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, connection.peerSettings.getHeaderTableSize());
146a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Http2.Reader frameReader = (Http2.Reader) connection.readerRunnable.frameReader;
147e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, frameReader.hpackReader.maxDynamicTableByteCount());
148e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // TODO: when supported, check the frameWriter's compression table is unaffected.
149e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
150e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
151e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void peerHttp2ClientDisablesPush() throws Exception {
152e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    boolean client = false; // Peer is client, so we are server.
153e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Settings settings = new Settings();
154e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    settings.set(Settings.ENABLE_PUSH, 0, 0); // The peer client disables push.
155e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
156e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = sendHttp2SettingsAndCheckForAck(client, settings);
157e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
158e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer's settings were read and applied.
159e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertFalse(connection.peerSettings.getEnablePush(true));
160e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
161e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
162e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void peerIncreasesMaxFrameSize() throws Exception {
163e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    int newMaxFrameSize = 0x4001;
164e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Settings settings = new Settings();
165e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    settings.set(Settings.MAX_FRAME_SIZE, 0, newMaxFrameSize);
166e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
167e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = sendHttp2SettingsAndCheckForAck(true, settings);
168e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
169e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer's settings were read and applied.
170e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(newMaxFrameSize, connection.peerSettings.getMaxFrameSize(-1));
171e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(newMaxFrameSize, connection.frameWriter.maxDataLength());
172e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
173e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
174e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void receiveGoAwayHttp2() throws Exception {
175e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
176e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
177e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
178e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM 3
179e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM 5
180e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().goAway(3, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY);
181e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // PING
182e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().ping(true, 1, 0);
183e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // DATA STREAM 3
184e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
185e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
186e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
187e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
188e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream stream1 = connection.newStream(headerEntries("a", "android"), true, true);
189e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true);
190e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received.
191e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    BufferedSink sink1 = Okio.buffer(stream1.getSink());
192e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    BufferedSink sink2 = Okio.buffer(stream2.getSink());
193e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    sink1.writeUtf8("abc");
194e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
195e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      sink2.writeUtf8("abc");
196e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      sink2.flush();
197e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
198e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (IOException expected) {
199e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage());
200e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
201e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    sink1.writeUtf8("def");
202e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    sink1.close();
203e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
204e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      connection.newStream(headerEntries("c", "cola"), true, true);
205e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
206e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (IOException expected) {
207e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("shutdown", expected.getMessage());
208e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
209e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(stream1.isOpen());
210e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertFalse(stream2.isOpen());
211e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(1, connection.openStreamCount());
212e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
213e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
214e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
215e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, synStream1.type);
216e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame synStream2 = peer.takeFrame();
217e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, synStream2.type);
218e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame ping = peer.takeFrame();
219e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_PING, ping.type);
220e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame data1 = peer.takeFrame();
221e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_DATA, data1.type);
222e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3, data1.streamId);
223e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data));
224e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
225e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
226e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void readSendsWindowUpdateHttp2() throws Exception {
227e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
228e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
229e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    int windowSize = 100;
230e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    int windowUpdateThreshold = 50;
231e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
232e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Write the mocking script.
233e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM
234e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
235e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    for (int i = 0; i < 3; i++) {
236e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      // Send frames of summing to size 50, which is windowUpdateThreshold.
237e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      peer.sendFrame().data(false, 3, data(24), 24);
238e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      peer.sendFrame().data(false, 3, data(25), 25);
239e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      peer.sendFrame().data(false, 3, data(1), 1);
240e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      peer.acceptFrame(); // connection WINDOW UPDATE
241e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      peer.acceptFrame(); // stream WINDOW UPDATE
242e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
243e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().data(true, 3, data(0), 0);
244e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
245e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
246e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Play it back.
247e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
248e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, windowSize);
249e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream stream = connection.newStream(headerEntries("b", "banana"), false, true);
250e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, stream.unacknowledgedBytesRead);
251e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(headerEntries("a", "android"), stream.getResponseHeaders());
252e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Source in = stream.getSource();
253e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Buffer buffer = new Buffer();
254e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    buffer.writeAll(in);
255e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(-1, in.read(buffer, 1));
256e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(150, buffer.size());
257e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
258e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame synStream = peer.takeFrame();
259e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, synStream.type);
260e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    for (int i = 0; i < 3; i++) {
261e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      List<Integer> windowUpdateStreamIds = new ArrayList<>(2);
262e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      for (int j = 0; j < 2; j++) {
263e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        MockSpdyPeer.InFrame windowUpdate = peer.takeFrame();
264e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type);
265e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        windowUpdateStreamIds.add(windowUpdate.streamId);
266e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement);
267e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
268e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertTrue(windowUpdateStreamIds.contains(0)); // connection
269e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertTrue(windowUpdateStreamIds.contains(3)); // stream
270e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
271e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
272e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
273e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private Buffer data(int byteCount) {
274e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    return new Buffer().write(new byte[byteCount]);
275e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
276e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
277e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() throws Exception {
278e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
279e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
280e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Write the mocking script.
281e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM
282e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
283e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().data(true, 3, data(0), 0);
284e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
285e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
286e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Play it back.
287e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
288e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream client = connection.newStream(headerEntries("b", "banana"), false, true);
289e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(-1, client.getSource().read(new Buffer(), 1));
290e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
291e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Verify the peer received what was expected.
292e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame synStream = peer.takeFrame();
293e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, synStream.type);
294e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3, peer.frameCount());
295e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
296e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
297e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() throws Exception {
298e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
299e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
300e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Write the mocking script.
301e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM
302e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // DATA
303e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
304e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
305e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
306e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Play it back.
307e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
308e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream client = connection.newStream(headerEntries("b", "banana"), true, true);
309e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    BufferedSink out = Okio.buffer(client.getSink());
310e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.write(Util.EMPTY_BYTE_ARRAY);
311e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.flush();
312e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.close();
313e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
314e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Verify the peer received what was expected.
315e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, peer.takeFrame().type);
316e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_DATA, peer.takeFrame().type);
317e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(3, peer.frameCount());
318e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
319e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
320e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void maxFrameSizeHonored() throws Exception {
321e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
322e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
323e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    byte[] buff = new byte[peer.maxOutboundDataLength() + 1];
324e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Arrays.fill(buff, (byte) '*');
325e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
326e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
327e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM
328e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
329e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // DATA
330e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // DATA
331e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
332e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
333e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
334e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
335e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true);
336e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    BufferedSink out = Okio.buffer(stream.getSink());
337e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.write(buff);
338e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.flush();
339e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    out.close();
340e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
341e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame synStream = peer.takeFrame();
342e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, synStream.type);
343e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame data = peer.takeFrame();
344e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(peer.maxOutboundDataLength(), data.data.length);
345e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data = peer.takeFrame();
346e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(1, data.data.length);
347e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
348e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
349e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void pushPromiseStream() throws Exception {
350e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
351e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
352e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
353e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_STREAM
354e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
355e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final List<Header> expectedRequestHeaders = Arrays.asList(
356e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_METHOD, "GET"),
357e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_SCHEME, "https"),
358e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_AUTHORITY, "squareup.com"),
359e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_PATH, "/cached")
360e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    );
361e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().pushPromise(3, 2, expectedRequestHeaders);
362e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final List<Header> expectedResponseHeaders = Arrays.asList(
363e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.RESPONSE_STATUS, "200")
364e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    );
365e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(true, 2, expectedResponseHeaders);
366e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().data(true, 3, data(0), 0);
367e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
368e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
369e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    RecordingPushObserver observer = new RecordingPushObserver();
370e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
371e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
372e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connectionBuilder(peer, HTTP_2)
373e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        .pushObserver(observer).build();
374e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyStream client = connection.newStream(headerEntries("b", "banana"), false, true);
375e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(-1, client.getSource().read(new Buffer(), 1));
376e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
377e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
378e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, peer.takeFrame().type);
379e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
380e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(expectedRequestHeaders, observer.takeEvent());
381e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(expectedResponseHeaders, observer.takeEvent());
382e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
383e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
384e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void doublePushPromise() throws Exception {
385e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
386e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
387e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
388e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().pushPromise(3, 2, headerEntries("a", "android"));
389e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // SYN_REPLY
390e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().pushPromise(3, 2, headerEntries("b", "banana"));
391e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // RST_STREAM
392e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
393e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
394e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
395e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connectionBuilder(peer, HTTP_2).build();
396e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connection.newStream(headerEntries("b", "banana"), false, true);
397e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
398e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
399e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_HEADERS, peer.takeFrame().type);
400e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(PROTOCOL_ERROR, peer.takeFrame().errorCode);
401e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
402e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
403e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void pushPromiseStreamsAutomaticallyCancel() throws Exception {
404e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, false);
405e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
406e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // write the mocking script
407e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().pushPromise(3, 2, Arrays.asList(
408e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_METHOD, "GET"),
409e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_SCHEME, "https"),
410e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_AUTHORITY, "squareup.com"),
411e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.TARGET_PATH, "/cached")
412e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    ));
413e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().synReply(true, 2, Arrays.asList(
414e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        new Header(Header.RESPONSE_STATUS, "200")
415e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    ));
416e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // RST_STREAM
417e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
418e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
419e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
420e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connectionBuilder(peer, HTTP_2)
421e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        .pushObserver(PushObserver.CANCEL).build();
422e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
423e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received what was expected
424e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame rstStream = peer.takeFrame();
425e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_RST_STREAM, rstStream.type);
426e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(2, rstStream.streamId);
427e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(CANCEL, rstStream.errorCode);
428e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
429e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
430e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private SpdyConnection sendHttp2SettingsAndCheckForAck(boolean client, Settings settings)
431e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throws IOException, InterruptedException {
432e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.setVariantAndClient(HTTP_2, client);
433e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().settings(settings);
434e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // ACK
435e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.acceptFrame(); // PING
436e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.sendFrame().ping(true, 1, 0);
437e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    peer.play();
438e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
439e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // play it back
440e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    SpdyConnection connection = connection(peer, HTTP_2);
441e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
442e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // verify the peer received the ACK
443e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    MockSpdyPeer.InFrame ackFrame = peer.takeFrame();
444e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(TYPE_SETTINGS, ackFrame.type);
445e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals(0, ackFrame.streamId);
446e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertTrue(ackFrame.ack);
447e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
448e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    connection.ping().roundTripTime(); // Ensure that settings have been applied before returning.
449e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    return connection;
450e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
451e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
452e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private SpdyConnection connection(MockSpdyPeer peer, Variant variant) throws IOException {
453e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    return connectionBuilder(peer, variant).build();
454e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
455e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
456e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private SpdyConnection.Builder connectionBuilder(MockSpdyPeer peer, Variant variant)
457e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throws IOException {
458e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    return new SpdyConnection.Builder(true, peer.openSocket())
459e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        .pushObserver(IGNORE)
460e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        .protocol(variant.getProtocol());
461e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
462e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
463e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  static final PushObserver IGNORE = new PushObserver() {
464e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
465e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public boolean onRequest(int streamId, List<Header> requestHeaders) {
466e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
467e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
468e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
469e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last) {
470e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
471e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
472e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
473e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public boolean onData(int streamId, BufferedSource source, int byteCount,
474e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        boolean last) throws IOException {
475e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      source.skip(byteCount);
476e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
477e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
478e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
479e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public void onReset(int streamId, ErrorCode errorCode) {
480e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
481e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  };
482e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
483e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private static class RecordingPushObserver implements PushObserver {
484e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final List<Object> events = new ArrayList<>();
485e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
486e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    public synchronized Object takeEvent() throws InterruptedException {
487e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      while (events.isEmpty()) {
488e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        wait();
489e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
490e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return events.remove(0);
491e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
492e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
493e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public synchronized boolean onRequest(int streamId, List<Header> requestHeaders) {
494e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals(2, streamId);
495e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      events.add(requestHeaders);
496e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      notifyAll();
497e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
498e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
499e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
500e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public synchronized boolean onHeaders(
501e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        int streamId, List<Header> responseHeaders, boolean last) {
502e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals(2, streamId);
503e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertTrue(last);
504e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      events.add(responseHeaders);
505e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      notifyAll();
506e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
507e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
508e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
509e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public synchronized boolean onData(
510e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        int streamId, BufferedSource source, int byteCount, boolean last) {
511e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      events.add(new AssertionError("onData"));
512e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      notifyAll();
513e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return false;
514e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
515e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
516e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public synchronized void onReset(int streamId, ErrorCode errorCode) {
517e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      events.add(new AssertionError("onReset"));
518e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      notifyAll();
519e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
520e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
521e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller}
522