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