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