blob: 1674511a43fba4a1ccfaed6a64670009c279a6ba [file] [log] [blame]
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.internal.ws;
import com.squareup.okhttp.ws.WebSocketRecorder;
import java.io.EOFException;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import okio.Buffer;
import okio.BufferedSource;
import okio.ByteString;
import org.junit.After;
import org.junit.Test;
import static com.squareup.okhttp.ws.WebSocket.PayloadType;
import static com.squareup.okhttp.ws.WebSocketRecorder.MessageDelegate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public final class WebSocketReaderTest {
private final Buffer data = new Buffer();
private final WebSocketRecorder callback = new WebSocketRecorder();
private final Random random = new Random(0);
// Mutually exclusive. Use the one corresponding to the peer whose behavior you wish to test.
private final WebSocketReader serverReader = new WebSocketReader(false, data, callback);
private final WebSocketReader clientReader = new WebSocketReader(true, data, callback);
@After public void tearDown() {
callback.assertExhausted();
}
@Test public void controlFramesMustBeFinal() throws IOException {
data.write(ByteString.decodeHex("0a00")); // Empty ping.
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Control frames must be final.", e.getMessage());
}
}
@Test public void reservedFlagsAreUnsupported() throws IOException {
data.write(ByteString.decodeHex("9a00")); // Empty ping, flag 1 set.
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Reserved flags are unsupported.", e.getMessage());
}
data.clear();
data.write(ByteString.decodeHex("aa00")); // Empty ping, flag 2 set.
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Reserved flags are unsupported.", e.getMessage());
}
data.clear();
data.write(ByteString.decodeHex("ca00")); // Empty ping, flag 3 set.
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Reserved flags are unsupported.", e.getMessage());
}
}
@Test public void clientSentFramesMustBeMasked() throws IOException {
data.write(ByteString.decodeHex("8100"));
try {
serverReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Client-sent frames must be masked. Server sent must not.", e.getMessage());
}
}
@Test public void serverSentFramesMustNotBeMasked() throws IOException {
data.write(ByteString.decodeHex("8180"));
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Client-sent frames must be masked. Server sent must not.", e.getMessage());
}
}
@Test public void controlFramePayloadMax() throws IOException {
data.write(ByteString.decodeHex("8a7e007e"));
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Control frame must be less than 125B.", e.getMessage());
}
}
@Test public void clientSimpleHello() throws IOException {
data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
clientReader.processNextFrame();
callback.assertTextMessage("Hello");
}
@Test public void serverSimpleHello() throws IOException {
data.write(ByteString.decodeHex("818537fa213d7f9f4d5158")); // Hello
serverReader.processNextFrame();
callback.assertTextMessage("Hello");
}
@Test public void clientFramePayloadShort() throws IOException {
data.write(ByteString.decodeHex("817E000548656c6c6f")); // Hello
clientReader.processNextFrame();
callback.assertTextMessage("Hello");
}
@Test public void clientFramePayloadLong() throws IOException {
data.write(ByteString.decodeHex("817f000000000000000548656c6c6f")); // Hello
clientReader.processNextFrame();
callback.assertTextMessage("Hello");
}
@Test public void clientFramePayloadTooLongThrows() throws IOException {
data.write(ByteString.decodeHex("817f8000000000000000"));
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Frame length 0x8000000000000000 > 0x7FFFFFFFFFFFFFFF", e.getMessage());
}
}
@Test public void serverHelloTwoChunks() throws IOException {
data.write(ByteString.decodeHex("818537fa213d7f9f4d")); // Hel
final Buffer sink = new Buffer();
callback.setNextMessageDelegate(new MessageDelegate() {
@Override public void onMessage(BufferedSource payload, PayloadType type) throws IOException {
payload.readFully(sink, 3); // Read "Hel"
data.write(ByteString.decodeHex("5158")); // lo
payload.readFully(sink, 2); // Read "lo"
payload.close();
}
});
serverReader.processNextFrame();
assertEquals("Hello", sink.readUtf8());
}
@Test public void clientTwoFrameHello() throws IOException {
data.write(ByteString.decodeHex("010348656c")); // Hel
data.write(ByteString.decodeHex("80026c6f")); // lo
clientReader.processNextFrame();
callback.assertTextMessage("Hello");
}
@Test public void clientTwoFrameHelloWithPongs() throws IOException {
data.write(ByteString.decodeHex("010348656c")); // Hel
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("80026c6f")); // lo
clientReader.processNextFrame();
callback.assertPong(null);
callback.assertPong(null);
callback.assertPong(null);
callback.assertPong(null);
callback.assertTextMessage("Hello");
}
@Test public void clientIncompleteMessageBodyThrows() throws IOException {
data.write(ByteString.decodeHex("810548656c")); // Length = 5, "Hel"
try {
clientReader.processNextFrame();
fail();
} catch (EOFException ignored) {
}
}
@Test public void clientIncompleteControlFrameBodyThrows() throws IOException {
data.write(ByteString.decodeHex("8a0548656c")); // Length = 5, "Hel"
try {
clientReader.processNextFrame();
fail();
} catch (EOFException ignored) {
}
}
@Test public void serverIncompleteMessageBodyThrows() throws IOException {
data.write(ByteString.decodeHex("818537fa213d7f9f4d")); // Length = 5, "Hel"
try {
serverReader.processNextFrame();
fail();
} catch (EOFException ignored) {
}
}
@Test public void serverIncompleteControlFrameBodyThrows() throws IOException {
data.write(ByteString.decodeHex("8a8537fa213d7f9f4d")); // Length = 5, "Hel"
try {
serverReader.processNextFrame();
fail();
} catch (EOFException ignored) {
}
}
@Test public void clientSimpleBinary() throws IOException {
byte[] bytes = binaryData(256);
data.write(ByteString.decodeHex("827E0100")).write(bytes);
clientReader.processNextFrame();
callback.assertBinaryMessage(bytes);
}
@Test public void clientTwoFrameBinary() throws IOException {
byte[] bytes = binaryData(200);
data.write(ByteString.decodeHex("0264")).write(bytes, 0, 100);
data.write(ByteString.decodeHex("8064")).write(bytes, 100, 100);
clientReader.processNextFrame();
callback.assertBinaryMessage(bytes);
}
@Test public void twoFrameNotContinuation() throws IOException {
byte[] bytes = binaryData(200);
data.write(ByteString.decodeHex("0264")).write(bytes, 0, 100);
data.write(ByteString.decodeHex("8264")).write(bytes, 100, 100);
try {
clientReader.processNextFrame();
fail();
} catch (ProtocolException e) {
assertEquals("Expected continuation opcode. Got: 2", e.getMessage());
}
}
@Test public void noCloseErrors() throws IOException {
data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
callback.setNextMessageDelegate(new MessageDelegate() {
@Override public void onMessage(BufferedSource payload, PayloadType type) throws IOException {
payload.readAll(new Buffer());
}
});
try {
clientReader.processNextFrame();
fail();
} catch (IllegalStateException e) {
assertEquals("Listener failed to call close on message payload.", e.getMessage());
}
}
@Test public void closeExhaustsMessage() throws IOException {
data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
data.write(ByteString.decodeHex("810448657921")); // Hey!
final Buffer sink = new Buffer();
callback.setNextMessageDelegate(new MessageDelegate() {
@Override public void onMessage(BufferedSource payload, PayloadType type) throws IOException {
payload.read(sink, 3);
payload.close();
}
});
clientReader.processNextFrame();
assertEquals("Hel", sink.readUtf8());
clientReader.processNextFrame();
callback.assertTextMessage("Hey!");
}
@Test public void closeExhaustsMessageOverControlFrames() throws IOException {
data.write(ByteString.decodeHex("010348656c")); // Hel
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("8a00")); // Pong
data.write(ByteString.decodeHex("80026c6f")); // lo
data.write(ByteString.decodeHex("810448657921")); // Hey!
final Buffer sink = new Buffer();
callback.setNextMessageDelegate(new MessageDelegate() {
@Override public void onMessage(BufferedSource payload, PayloadType type) throws IOException {
payload.read(sink, 2);
payload.close();
}
});
clientReader.processNextFrame();
assertEquals("He", sink.readUtf8());
callback.assertPong(null);
callback.assertPong(null);
clientReader.processNextFrame();
callback.assertTextMessage("Hey!");
}
@Test public void closedMessageSourceThrows() throws IOException {
data.write(ByteString.decodeHex("810548656c6c6f")); // Hello
final AtomicReference<Exception> exception = new AtomicReference<>();
callback.setNextMessageDelegate(new MessageDelegate() {
@Override public void onMessage(BufferedSource payload, PayloadType type) throws IOException {
payload.close();
try {
payload.readAll(new Buffer());
fail();
} catch (IllegalStateException e) {
exception.set(e);
}
}
});
clientReader.processNextFrame();
assertNotNull(exception.get());
}
@Test public void emptyPingCallsCallback() throws IOException {
data.write(ByteString.decodeHex("8900")); // Empty ping
clientReader.processNextFrame();
callback.assertPing(null);
}
@Test public void pingCallsCallback() throws IOException {
data.write(ByteString.decodeHex("890548656c6c6f")); // Ping with "Hello"
clientReader.processNextFrame();
callback.assertPing(new Buffer().writeUtf8("Hello"));
}
@Test public void emptyCloseCallsCallback() throws IOException {
data.write(ByteString.decodeHex("8800")); // Empty close
clientReader.processNextFrame();
callback.assertClose(0, "");
}
@Test public void closeCallsCallback() throws IOException {
data.write(ByteString.decodeHex("880703e848656c6c6f")); // Close with code and reason
clientReader.processNextFrame();
callback.assertClose(1000, "Hello");
}
private byte[] binaryData(int length) {
byte[] junk = new byte[length];
random.nextBytes(junk);
return junk;
}
}