WebSocketReader.java revision a2cab72aa5ff730ba2ae987b45398faafffeb505
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
18e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.EOFException;
19e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.io.IOException;
20e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.net.ProtocolException;
21e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Buffer;
22e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.BufferedSource;
23e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Okio;
24e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Source;
25e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.Timeout;
26e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
27a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport static com.squareup.okhttp.ws.WebSocket.PayloadType;
28e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_FIN;
29e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV1;
30e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV2;
31e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_FLAG_RSV3;
32e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B0_MASK_OPCODE;
33e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B1_FLAG_MASK;
34e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.B1_MASK_LENGTH;
35e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_BINARY;
36e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTINUATION;
37e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_CLOSE;
38e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_PING;
39e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_CONTROL_PONG;
40e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_FLAG_CONTROL;
41e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.OPCODE_TEXT;
42e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_LONG;
43e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_MAX;
44e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.PAYLOAD_SHORT;
45e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static com.squareup.okhttp.internal.ws.WebSocketProtocol.toggleMask;
46e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport static java.lang.Integer.toHexString;
47e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
48e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/**
49e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * An <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>-compatible WebSocket frame reader.
50e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */
51e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpublic final class WebSocketReader {
52e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  public interface FrameCallback {
53e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    void onMessage(BufferedSource source, PayloadType type) throws IOException;
54e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    void onPing(Buffer buffer);
55e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    void onPong(Buffer buffer);
56a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    void onClose(int code, String reason);
57e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
58e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
59e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final boolean isClient;
60e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final BufferedSource source;
61e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final FrameCallback frameCallback;
62e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
63e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final Source framedMessageSource = new FramedMessageSource();
64e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
65e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private boolean closed;
66e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private boolean messageClosed;
67e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
68e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  // Stateful data about the current frame.
69e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private int opcode;
70e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private long frameLength;
71e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private long frameBytesRead;
72e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private boolean isFinalFrame;
73e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private boolean isControlFrame;
74e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private boolean isMasked;
75e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
76e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final byte[] maskKey = new byte[4];
77e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final byte[] maskBuffer = new byte[2048];
78e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
79e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  public WebSocketReader(boolean isClient, BufferedSource source, FrameCallback frameCallback) {
80a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    if (source == null) throw new NullPointerException("source == null");
81a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    if (frameCallback == null) throw new NullPointerException("frameCallback == null");
82e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    this.isClient = isClient;
83e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    this.source = source;
84e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    this.frameCallback = frameCallback;
85e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
86e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
87e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  /**
88e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * Process the next protocol frame.
89e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * <ul>
90e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * <li>If it is a control frame this will result in a single call to {@link FrameCallback}.</li>
91e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * <li>If it is a message frame this will result in a single call to {@link
92e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * WebSocketListener#onMessage}. If the message spans multiple frames, each interleaved control
93e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * frame will result in a corresponding call to {@link FrameCallback}.
94e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * </ul>
95e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   */
96e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  public void processNextFrame() throws IOException {
97e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    readHeader();
98e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (isControlFrame) {
99e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      readControlFrame();
100e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } else {
101e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      readMessageFrame();
102e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
103e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
104e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
105e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private void readHeader() throws IOException {
106a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    if (closed) throw new IOException("closed");
107e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
108e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    int b0 = source.readByte() & 0xff;
109e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
110e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    opcode = b0 & B0_MASK_OPCODE;
111e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    isFinalFrame = (b0 & B0_FLAG_FIN) != 0;
112e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    isControlFrame = (b0 & OPCODE_FLAG_CONTROL) != 0;
113e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
114e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Control frames must be final frames (cannot contain continuations).
115e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (isControlFrame && !isFinalFrame) {
116e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throw new ProtocolException("Control frames must be final.");
117e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
118e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
119e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    boolean reservedFlag1 = (b0 & B0_FLAG_RSV1) != 0;
120e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    boolean reservedFlag2 = (b0 & B0_FLAG_RSV2) != 0;
121e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    boolean reservedFlag3 = (b0 & B0_FLAG_RSV3) != 0;
122e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (reservedFlag1 || reservedFlag2 || reservedFlag3) {
123e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      // Reserved flags are for extensions which we currently do not support.
124e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throw new ProtocolException("Reserved flags are unsupported.");
125e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
126e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
127e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    int b1 = source.readByte() & 0xff;
128e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
129e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    isMasked = (b1 & B1_FLAG_MASK) != 0;
130e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (isMasked == isClient) {
131e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      // Masked payloads must be read on the server. Unmasked payloads must be read on the client.
132e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throw new ProtocolException("Client-sent frames must be masked. Server sent must not.");
133e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
134e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
135e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    // Get frame length, optionally reading from follow-up bytes if indicated by special values.
136e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    frameLength = b1 & B1_MASK_LENGTH;
137e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (frameLength == PAYLOAD_SHORT) {
138a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      frameLength = source.readShort() & 0xffffL; // Value is unsigned.
139e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    } else if (frameLength == PAYLOAD_LONG) {
140e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      frameLength = source.readLong();
141a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      if (frameLength < 0) {
142a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller        throw new ProtocolException(
143a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller            "Frame length 0x" + Long.toHexString(frameLength) + " > 0x7FFFFFFFFFFFFFFF");
144a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      }
145e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
146e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    frameBytesRead = 0;
147e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
148e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (isControlFrame && frameLength > PAYLOAD_MAX) {
149e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throw new ProtocolException("Control frame must be less than " + PAYLOAD_MAX + "B.");
150e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
151e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
152e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (isMasked) {
153e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      // Read the masking key as bytes so that they can be used directly for unmasking.
154e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      source.readFully(maskKey);
155e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
156e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
157e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
158e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private void readControlFrame() throws IOException {
159e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    Buffer buffer = null;
160e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (frameBytesRead < frameLength) {
161e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      buffer = new Buffer();
162e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
163e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (isClient) {
164e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        source.readFully(buffer, frameLength);
165e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      } else {
166e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        while (frameBytesRead < frameLength) {
167e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          int toRead = (int) Math.min(frameLength - frameBytesRead, maskBuffer.length);
168e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          int read = source.read(maskBuffer, 0, toRead);
169e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          if (read == -1) throw new EOFException();
170e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          toggleMask(maskBuffer, read, maskKey, frameBytesRead);
171e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          buffer.write(maskBuffer, 0, read);
172e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          frameBytesRead += read;
173e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        }
174e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
175e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
176e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
177e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    switch (opcode) {
178e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      case OPCODE_CONTROL_PING:
179e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        frameCallback.onPing(buffer);
180e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
181e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      case OPCODE_CONTROL_PONG:
182e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        frameCallback.onPong(buffer);
183e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
184e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      case OPCODE_CONTROL_CLOSE:
185e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        int code = 0;
186e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        String reason = "";
187e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (buffer != null) {
188e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          code = buffer.readShort();
189e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          reason = buffer.readUtf8();
190e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        }
191e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        frameCallback.onClose(code, reason);
192e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        closed = true;
193e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
194e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      default:
195e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        throw new IllegalStateException("Unknown control opcode: " + toHexString(opcode));
196e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
197e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
198e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
199e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private void readMessageFrame() throws IOException {
200e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    PayloadType type;
201e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    switch (opcode) {
202e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      case OPCODE_TEXT:
203e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        type = PayloadType.TEXT;
204e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
205e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      case OPCODE_BINARY:
206e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        type = PayloadType.BINARY;
207e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
208e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      default:
209e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        throw new IllegalStateException("Unknown opcode: " + toHexString(opcode));
210e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
211e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
212e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    messageClosed = false;
213e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    frameCallback.onMessage(Okio.buffer(framedMessageSource), type);
214e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    if (!messageClosed) {
215e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      throw new IllegalStateException("Listener failed to call close on message payload.");
216e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
217e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
218e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
219e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  /** Read headers and process any control frames until we reach a non-control frame. */
220e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private void readUntilNonControlFrame() throws IOException {
221e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    while (!closed) {
222e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      readHeader();
223e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (!isControlFrame) {
224e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        break;
225e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
226e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      readControlFrame();
227e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
228e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
229e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
230e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  /**
231e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * A special source which knows how to read a message body across one or more frames. Control
232e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * frames that occur between fragments will be processed. If the message payload is masked this
233e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   * will unmask as it's being processed.
234e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller   */
235e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  private final class FramedMessageSource implements Source {
236e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public long read(Buffer sink, long byteCount) throws IOException {
237a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      if (closed) throw new IOException("closed");
238a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      if (messageClosed) throw new IllegalStateException("closed");
239e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
240e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (frameBytesRead == frameLength) {
241e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (isFinalFrame) return -1; // We are exhausted and have no continuations.
242e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
243e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        readUntilNonControlFrame();
244e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (opcode != OPCODE_CONTINUATION) {
245e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode));
246e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        }
247e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (isFinalFrame && frameLength == 0) {
248e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller          return -1; // Fast-path for empty final frame.
249e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        }
250e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
251e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
252e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      long toRead = Math.min(byteCount, frameLength - frameBytesRead);
253e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
254e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      long read;
255e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (isMasked) {
256e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        toRead = Math.min(toRead, maskBuffer.length);
257e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        read = source.read(maskBuffer, 0, (int) toRead);
258e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (read == -1) throw new EOFException();
259e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        toggleMask(maskBuffer, read, maskKey, frameBytesRead);
260e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        sink.write(maskBuffer, 0, (int) read);
261e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      } else {
262e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        read = source.read(sink, toRead);
263e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        if (read == -1) throw new EOFException();
264e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
265e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
266e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      frameBytesRead += read;
267e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return read;
268e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
269e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
270e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public Timeout timeout() {
271e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      return source.timeout();
272e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
273e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
274e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    @Override public void close() throws IOException {
275e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (messageClosed) return;
276e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      messageClosed = true;
277e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      if (closed) return;
278e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller
279e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      // Exhaust the remainder of the message, if any.
280e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      source.skip(frameLength - frameBytesRead);
281e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      while (!isFinalFrame) {
282e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        readUntilNonControlFrame();
283e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller        source.skip(frameLength);
284e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller      }
285e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller    }
286e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller  }
287e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller}
288