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.MediaType; 196c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport com.squareup.okhttp.ResponseBody; 206c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport com.squareup.okhttp.ws.WebSocket; 21e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.EOFException; 22e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.IOException; 23e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.net.ProtocolException; 24e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Buffer; 25e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.BufferedSource; 26e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Okio; 27e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Source; 28e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Timeout; 29e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 30e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_FIN; 31e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV1; 32e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV2; 33e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV3; 34e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_MASK_OPCODE; 35e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B1_FLAG_MASK; 36e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B1_MASK_LENGTH; 37e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_BINARY; 38e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTINUATION; 39e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_CLOSE; 40e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_PING; 41e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_PONG; 42e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_FLAG_CONTROL; 43e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_TEXT; 44e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_LONG; 456c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_BYTE_MAX; 46e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_SHORT; 47e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.toggleMask; 48e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static java.lang.Integer.toHexString; 49e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 50e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/** 51e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * An <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>-compatible WebSocket frame reader. 52e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 53e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpublic final class WebSocketReader { 54e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public interface FrameCallback { 556c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer void onMessage(ResponseBody body) throws IOException; 56e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller void onPing(Buffer buffer); 57e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller void onPong(Buffer buffer); 58a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller void onClose(int code, String reason); 59e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 60e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 61e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final boolean isClient; 62e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final BufferedSource source; 63e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final FrameCallback frameCallback; 64e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 65e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final Source framedMessageSource = new FramedMessageSource(); 66e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 67e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private boolean closed; 68e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private boolean messageClosed; 69e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 70e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Stateful data about the current frame. 71e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private int opcode; 72e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private long frameLength; 73e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private long frameBytesRead; 74e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private boolean isFinalFrame; 75e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private boolean isControlFrame; 76e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private boolean isMasked; 77e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 78e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final byte[] maskKey = new byte[4]; 79e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final byte[] maskBuffer = new byte[2048]; 80e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 81e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public WebSocketReader(boolean isClient, BufferedSource source, FrameCallback frameCallback) { 82a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (source == null) throw new NullPointerException("source == null"); 83a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (frameCallback == null) throw new NullPointerException("frameCallback == null"); 84e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller this.isClient = isClient; 85e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller this.source = source; 86e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller this.frameCallback = frameCallback; 87e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 88e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 89e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** 90e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Process the next protocol frame. 91e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <ul> 92e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <li>If it is a control frame this will result in a single call to {@link FrameCallback}.</li> 93e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <li>If it is a message frame this will result in a single call to {@link 947aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * FrameCallback#onMessage}. If the message spans multiple frames, each interleaved control 95e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * frame will result in a corresponding call to {@link FrameCallback}. 96e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * </ul> 97e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 98e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public void processNextFrame() throws IOException { 99e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readHeader(); 100e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isControlFrame) { 101e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readControlFrame(); 102e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } else { 103e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readMessageFrame(); 104e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 105e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 106e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 107e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private void readHeader() throws IOException { 108a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (closed) throw new IOException("closed"); 109e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 110e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller int b0 = source.readByte() & 0xff; 111e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 112e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller opcode = b0 & B0_MASK_OPCODE; 113e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller isFinalFrame = (b0 & B0_FLAG_FIN) != 0; 114e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller isControlFrame = (b0 & OPCODE_FLAG_CONTROL) != 0; 115e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 116e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Control frames must be final frames (cannot contain continuations). 117e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isControlFrame && !isFinalFrame) { 118e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new ProtocolException("Control frames must be final."); 119e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 120e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 121e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller boolean reservedFlag1 = (b0 & B0_FLAG_RSV1) != 0; 122e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller boolean reservedFlag2 = (b0 & B0_FLAG_RSV2) != 0; 123e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller boolean reservedFlag3 = (b0 & B0_FLAG_RSV3) != 0; 124e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (reservedFlag1 || reservedFlag2 || reservedFlag3) { 125e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Reserved flags are for extensions which we currently do not support. 126e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new ProtocolException("Reserved flags are unsupported."); 127e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 128e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 129e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller int b1 = source.readByte() & 0xff; 130e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 131e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller isMasked = (b1 & B1_FLAG_MASK) != 0; 132e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isMasked == isClient) { 133e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Masked payloads must be read on the server. Unmasked payloads must be read on the client. 134e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new ProtocolException("Client-sent frames must be masked. Server sent must not."); 135e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 136e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 137e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Get frame length, optionally reading from follow-up bytes if indicated by special values. 138e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameLength = b1 & B1_MASK_LENGTH; 139e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (frameLength == PAYLOAD_SHORT) { 140a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller frameLength = source.readShort() & 0xffffL; // Value is unsigned. 141e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } else if (frameLength == PAYLOAD_LONG) { 142e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameLength = source.readLong(); 143a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (frameLength < 0) { 144a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller throw new ProtocolException( 145a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller "Frame length 0x" + Long.toHexString(frameLength) + " > 0x7FFFFFFFFFFFFFFF"); 146a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller } 147e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 148e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameBytesRead = 0; 149e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 1506c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer if (isControlFrame && frameLength > PAYLOAD_BYTE_MAX) { 1516c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer throw new ProtocolException("Control frame must be less than " + PAYLOAD_BYTE_MAX + "B."); 152e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 153e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 154e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isMasked) { 155e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Read the masking key as bytes so that they can be used directly for unmasking. 156e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller source.readFully(maskKey); 157e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 158e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 159e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 160e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private void readControlFrame() throws IOException { 161e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller Buffer buffer = null; 162e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (frameBytesRead < frameLength) { 163e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller buffer = new Buffer(); 164e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 165e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isClient) { 166e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller source.readFully(buffer, frameLength); 167e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } else { 168e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller while (frameBytesRead < frameLength) { 169e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller int toRead = (int) Math.min(frameLength - frameBytesRead, maskBuffer.length); 170e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller int read = source.read(maskBuffer, 0, toRead); 171e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (read == -1) throw new EOFException(); 172e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller toggleMask(maskBuffer, read, maskKey, frameBytesRead); 173e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller buffer.write(maskBuffer, 0, read); 174e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameBytesRead += read; 175e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 176e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 177e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 178e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 179e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller switch (opcode) { 180e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller case OPCODE_CONTROL_PING: 181e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameCallback.onPing(buffer); 182e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 183e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller case OPCODE_CONTROL_PONG: 184e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameCallback.onPong(buffer); 185e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 186e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller case OPCODE_CONTROL_CLOSE: 1876c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer int code = 1000; 188e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller String reason = ""; 189e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (buffer != null) { 1906c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer long bufferSize = buffer.size(); 1916c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer if (bufferSize == 1) { 1926c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer throw new ProtocolException("Malformed close payload length of 1."); 1936c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } else if (bufferSize != 0) { 1946c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer code = buffer.readShort(); 1956c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer if (code < 1000 || code >= 5000) { 1966c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer throw new ProtocolException("Code must be in range [1000,5000): " + code); 1976c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } 1986c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer if ((code >= 1004 && code <= 1006) || (code >= 1012 && code <= 2999)) { 1996c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer throw new ProtocolException("Code " + code + " is reserved and may not be used."); 2006c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } 2016c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer 2026c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer reason = buffer.readUtf8(); 2037aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller } 204e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 205e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameCallback.onClose(code, reason); 206e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller closed = true; 207e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 208e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller default: 2097aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller throw new ProtocolException("Unknown control opcode: " + toHexString(opcode)); 210e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 211e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 212e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 213e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private void readMessageFrame() throws IOException { 2146c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer final MediaType type; 215e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller switch (opcode) { 216e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller case OPCODE_TEXT: 2176c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer type = WebSocket.TEXT; 218e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 219e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller case OPCODE_BINARY: 2206c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer type = WebSocket.BINARY; 221e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 222e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller default: 2237aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller throw new ProtocolException("Unknown opcode: " + toHexString(opcode)); 224e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 225e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 2266c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer final BufferedSource source = Okio.buffer(framedMessageSource); 2276c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer ResponseBody body = new ResponseBody() { 2286c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer @Override public MediaType contentType() { 2296c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer return type; 2306c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } 2316c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer 2326c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer @Override public long contentLength() throws IOException { 2336c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer return -1; 2346c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } 2356c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer 2366c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer @Override public BufferedSource source() throws IOException { 2376c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer return source; 2386c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer } 2396c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer }; 2406c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer 241e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller messageClosed = false; 2426c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer frameCallback.onMessage(body); 243e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (!messageClosed) { 244e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new IllegalStateException("Listener failed to call close on message payload."); 245e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 246e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 247e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 248e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** Read headers and process any control frames until we reach a non-control frame. */ 249e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private void readUntilNonControlFrame() throws IOException { 250e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller while (!closed) { 251e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readHeader(); 252e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (!isControlFrame) { 253e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller break; 254e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 255e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readControlFrame(); 256e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 257e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 258e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 259e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** 260e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * A special source which knows how to read a message body across one or more frames. Control 261e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * frames that occur between fragments will be processed. If the message payload is masked this 262e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * will unmask as it's being processed. 263e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 264e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private final class FramedMessageSource implements Source { 265e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller @Override public long read(Buffer sink, long byteCount) throws IOException { 266a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (closed) throw new IOException("closed"); 267a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller if (messageClosed) throw new IllegalStateException("closed"); 268e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 269e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (frameBytesRead == frameLength) { 270e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isFinalFrame) return -1; // We are exhausted and have no continuations. 271e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 272e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readUntilNonControlFrame(); 273e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (opcode != OPCODE_CONTINUATION) { 274e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode)); 275e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 276e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isFinalFrame && frameLength == 0) { 277e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return -1; // Fast-path for empty final frame. 278e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 279e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 280e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 281e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller long toRead = Math.min(byteCount, frameLength - frameBytesRead); 282e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 283e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller long read; 284e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (isMasked) { 285e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller toRead = Math.min(toRead, maskBuffer.length); 286e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller read = source.read(maskBuffer, 0, (int) toRead); 287e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (read == -1) throw new EOFException(); 288e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller toggleMask(maskBuffer, read, maskKey, frameBytesRead); 289e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller sink.write(maskBuffer, 0, (int) read); 290e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } else { 291e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller read = source.read(sink, toRead); 292e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (read == -1) throw new EOFException(); 293e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 294e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 295e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller frameBytesRead += read; 296e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return read; 297e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 298e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 299e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller @Override public Timeout timeout() { 300e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return source.timeout(); 301e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 302e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 303e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller @Override public void close() throws IOException { 304e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (messageClosed) return; 305e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller messageClosed = true; 306e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (closed) return; 307e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 308e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // Exhaust the remainder of the message, if any. 309e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller source.skip(frameLength - frameBytesRead); 310e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller while (!isFinalFrame) { 311e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller readUntilNonControlFrame(); 312e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller source.skip(frameLength); 313e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 314e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 315e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 316e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller} 317