1e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/*
2e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Copyright (C) 2014 Square, Inc.
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.ws;
17e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
186c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport com.squareup.okhttp.ResponseBody;
19a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport com.squareup.okhttp.ws.WebSocketRecorder;
20e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.EOFException;
21e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.IOException;
22e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.net.ProtocolException;
23e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.Random;
24e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.concurrent.atomic.AtomicReference;
256c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport java.util.regex.Pattern;
26e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Buffer;
27e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.BufferedSource;
28e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.ByteString;
29e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport org.junit.After;
30e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport org.junit.Test;
31e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
32a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport static com.squareup.okhttp.ws.WebSocketRecorder.MessageDelegate;
33e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.assertEquals;
34e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.assertNotNull;
356c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport static org.junit.Assert.assertTrue;
36e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static org.junit.Assert.fail;
37e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
38a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerpublic final class WebSocketReaderTest {
39e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final Buffer data = new Buffer();
40e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final WebSocketRecorder callback = new WebSocketRecorder();
41e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final Random random = new Random(0);
42e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
43e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  // Mutually exclusive. Use the one corresponding to the peer whose behavior you wish to test.
44e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final WebSocketReader serverReader = new WebSocketReader(false, data, callback);
45e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final WebSocketReader clientReader = new WebSocketReader(true, data, callback);
46e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
47e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @After public void tearDown() {
48e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertExhausted();
49e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
50e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
51e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void controlFramesMustBeFinal() throws IOException {
52e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("0a00")); // Empty ping.
53e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
54e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
55e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
56e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
57e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Control frames must be final.", e.getMessage());
58e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
59e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
60e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
61e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void reservedFlagsAreUnsupported() throws IOException {
62e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("9a00")); // Empty ping, flag 1 set.
63e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
64e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
65e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
66e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
67e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Reserved flags are unsupported.", e.getMessage());
68e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
69e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.clear();
70e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("aa00")); // Empty ping, flag 2 set.
71e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
72e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
73e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
74e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
75e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Reserved flags are unsupported.", e.getMessage());
76e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
77e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.clear();
78e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("ca00")); // Empty ping, flag 3 set.
79e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
80e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
81e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
82e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
83e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Reserved flags are unsupported.", e.getMessage());
84e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
85e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
86e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
87e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientSentFramesMustBeMasked() throws IOException {
88e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8100"));
89e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
90e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      serverReader.processNextFrame();
91e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
92e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
93e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Client-sent frames must be masked. Server sent must not.", e.getMessage());
94e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
95e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
96e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
97e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverSentFramesMustNotBeMasked() throws IOException {
98e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8180"));
99e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
100e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
101e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
102e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
103e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Client-sent frames must be masked. Server sent must not.", e.getMessage());
104e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
105e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
106e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
107e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void controlFramePayloadMax() throws IOException {
108e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a7e007e"));
109e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
110e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
111e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
112e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
113e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Control frame must be less than 125B.", e.getMessage());
114e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
115e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
116e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
117e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientSimpleHello() throws IOException {
118e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
119e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
120e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hello");
121e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
122e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
123e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverSimpleHello() throws IOException {
124e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("818537fa213d7f9f4d5158")); // Hello
125e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    serverReader.processNextFrame();
126e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hello");
127e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
128e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
129a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void clientFramePayloadShort() throws IOException {
130a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    data.write(ByteString.decodeHex("817E000548656c6c6f")); // Hello
131a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    clientReader.processNextFrame();
132a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    callback.assertTextMessage("Hello");
133a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
134a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
135a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void clientFramePayloadLong() throws IOException {
136a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    data.write(ByteString.decodeHex("817f000000000000000548656c6c6f")); // Hello
137a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    clientReader.processNextFrame();
138a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    callback.assertTextMessage("Hello");
139a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
140a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
141a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void clientFramePayloadTooLongThrows() throws IOException {
142a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    data.write(ByteString.decodeHex("817f8000000000000000"));
143a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    try {
144a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      clientReader.processNextFrame();
145a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      fail();
146a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    } catch (ProtocolException e) {
147a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      assertEquals("Frame length 0x8000000000000000 > 0x7FFFFFFFFFFFFFFF", e.getMessage());
148a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
149a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
150a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
151e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverHelloTwoChunks() throws IOException {
152e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("818537fa213d7f9f4d")); // Hel
153e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
154e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final Buffer sink = new Buffer();
155e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.setNextMessageDelegate(new MessageDelegate() {
1566c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void onMessage(ResponseBody message) throws IOException {
1576c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        BufferedSource source = message.source();
1586c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        source.readFully(sink, 3); // Read "Hel"
159e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        data.write(ByteString.decodeHex("5158")); // lo
1606c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        source.readFully(sink, 2); // Read "lo"
1616c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        source.close();
162e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
163e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    });
164e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    serverReader.processNextFrame();
165e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
166e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals("Hello", sink.readUtf8());
167e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
168e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
169e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientTwoFrameHello() throws IOException {
170e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("010348656c")); // Hel
171e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("80026c6f")); // lo
172e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
173e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hello");
174e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
175e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
176e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientTwoFrameHelloWithPongs() throws IOException {
177e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("010348656c")); // Hel
178e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
179e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
180e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
181e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
182e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("80026c6f")); // lo
183e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
184e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
185e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
186e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
187e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
188e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hello");
189e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
190e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
191e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientIncompleteMessageBodyThrows() throws IOException {
192e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810548656c")); // Length = 5, "Hel"
193e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
194e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
195e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
196e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (EOFException ignored) {
197e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
198e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
199e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
200e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientIncompleteControlFrameBodyThrows() throws IOException {
201e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a0548656c")); // Length = 5, "Hel"
202e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
203e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
204e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
205e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (EOFException ignored) {
206e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
207e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
208e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
209e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverIncompleteMessageBodyThrows() throws IOException {
210e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("818537fa213d7f9f4d")); // Length = 5, "Hel"
211e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
212e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      serverReader.processNextFrame();
213e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
214e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (EOFException ignored) {
215e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
216e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
217e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
218e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void serverIncompleteControlFrameBodyThrows() throws IOException {
219e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a8537fa213d7f9f4d")); // Length = 5, "Hel"
220e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
221e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      serverReader.processNextFrame();
222e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
223e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (EOFException ignored) {
224e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
225e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
226e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
227e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientSimpleBinary() throws IOException {
228e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    byte[] bytes = binaryData(256);
229e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("827E0100")).write(bytes);
230e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
231e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertBinaryMessage(bytes);
232e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
233e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
234e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void clientTwoFrameBinary() throws IOException {
235e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    byte[] bytes = binaryData(200);
236e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("0264")).write(bytes, 0, 100);
237e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8064")).write(bytes, 100, 100);
238e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
239e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertBinaryMessage(bytes);
240e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
241e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
242e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void twoFrameNotContinuation() throws IOException {
243e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    byte[] bytes = binaryData(200);
244e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("0264")).write(bytes, 0, 100);
245e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8264")).write(bytes, 100, 100);
246e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
247e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
248e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
249e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (ProtocolException e) {
250e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Expected continuation opcode. Got: 2", e.getMessage());
251e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
252e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
253e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
254e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void noCloseErrors() throws IOException {
255e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
256e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.setNextMessageDelegate(new MessageDelegate() {
2576c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void onMessage(ResponseBody body) throws IOException {
2586c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        body.source().readAll(new Buffer());
259e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
260e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    });
261e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    try {
262e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      clientReader.processNextFrame();
263e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      fail();
264e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } catch (IllegalStateException e) {
265e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      assertEquals("Listener failed to call close on message payload.", e.getMessage());
266e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
267e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
268e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
269e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void closeExhaustsMessage() throws IOException {
270e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
271e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810448657921")); // Hey!
272e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
273e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final Buffer sink = new Buffer();
274e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.setNextMessageDelegate(new MessageDelegate() {
2756c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void onMessage(ResponseBody message) throws IOException {
2766c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        message.source().read(sink, 3);
2776c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        message.close();
278e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
279e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    });
280e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
281e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
282e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals("Hel", sink.readUtf8());
283e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
284e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
285e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hey!");
286e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
287e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
288e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void closeExhaustsMessageOverControlFrames() throws IOException {
289e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("010348656c")); // Hel
290e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
291e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8a00")); // Pong
292e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("80026c6f")); // lo
293e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810448657921")); // Hey!
294e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
295e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final Buffer sink = new Buffer();
296e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.setNextMessageDelegate(new MessageDelegate() {
2976c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void onMessage(ResponseBody message) throws IOException {
2986c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        message.source().read(sink, 2);
2996c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        message.close();
300e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
301e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    });
302e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
303e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
304e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertEquals("He", sink.readUtf8());
305e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
306e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPong(null);
307e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
308e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
309e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertTextMessage("Hey!");
310e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
311e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
312e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void closedMessageSourceThrows() throws IOException {
313e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
314e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
315e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    final AtomicReference<Exception> exception = new AtomicReference<>();
316e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.setNextMessageDelegate(new MessageDelegate() {
3176c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void onMessage(ResponseBody message) throws IOException {
3186c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        message.close();
319e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        try {
3206c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer          message.source().readAll(new Buffer());
321e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          fail();
322e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        } catch (IllegalStateException e) {
323e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          exception.set(e);
324e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        }
325e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
326e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    });
327e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
328e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
329e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    assertNotNull(exception.get());
330e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
331e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
332e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void emptyPingCallsCallback() throws IOException {
333e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8900")); // Empty ping
334e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
335e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPing(null);
336e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
337e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
338e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void pingCallsCallback() throws IOException {
339e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("890548656c6c6f")); // Ping with "Hello"
340e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
341e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertPing(new Buffer().writeUtf8("Hello"));
342e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
343e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
344e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void emptyCloseCallsCallback() throws IOException {
345e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("8800")); // Empty close
346e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
3476c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    callback.assertClose(1000, "");
3486c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
3496c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
3506c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  @Test public void closeLengthOfOneThrows() throws IOException {
3516c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("880100")); // Close with invalid 1-byte payload
3526c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    try {
3536c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      clientReader.processNextFrame();
3546c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      fail();
3556c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    } catch (ProtocolException e) {
3566c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      assertEquals("Malformed close payload length of 1.", e.getMessage());
3576c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
358e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
359e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
360e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  @Test public void closeCallsCallback() throws IOException {
361e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    data.write(ByteString.decodeHex("880703e848656c6c6f")); // Close with code and reason
362e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    clientReader.processNextFrame();
363e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    callback.assertClose(1000, "Hello");
364e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
365e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
3666c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  @Test public void closeOutOfRangeThrows() throws IOException {
3676c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("88020001")); // Close with code 1
3686c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    try {
3696c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      clientReader.processNextFrame();
3706c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      fail();
3716c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    } catch (ProtocolException e) {
3726c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      assertEquals("Code must be in range [1000,5000): 1", e.getMessage());
3736c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
3746c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("88021388")); // Close with code 5000
3756c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    try {
3766c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      clientReader.processNextFrame();
3776c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      fail();
3786c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    } catch (ProtocolException e) {
3796c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      assertEquals("Code must be in range [1000,5000): 5000", e.getMessage());
3806c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
3816c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
3826c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
3836c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  @Test public void closeReservedSetThrows() throws IOException {
3846c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("880203ec")); // Close with code 1004
3856c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("880203ed")); // Close with code 1005
3866c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    data.write(ByteString.decodeHex("880203ee")); // Close with code 1006
3876c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    for (int i = 1012; i <= 2999; i++) {
3886c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      data.write(ByteString.decodeHex("8802" + String.format("%04X", i))); // Close with code 'i'
3896c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
3906c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
3916c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    int count = 0;
3926c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    for (; !data.exhausted(); count++) {
3936c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      try {
3946c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        clientReader.processNextFrame();
3956c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        fail();
3966c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      } catch (ProtocolException e) {
3976c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        String message = e.getMessage();
3986c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        assertTrue(message, Pattern.matches("Code \\d+ is reserved and may not be used.", message));
3996c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      }
4006c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
4016c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    assertEquals(1991, count);
4026c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
4036c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
404e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private byte[] binaryData(int length) {
405e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    byte[] junk = new byte[length];
406e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    random.nextBytes(junk);
407e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    return junk;
408e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
409e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller}
410