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