/* * Copyright 2017 Google 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.google.firebase.database.tubesock; import java.io.DataInputStream; import java.io.IOException; import java.net.SocketTimeoutException; /** * This class encapsulates the receiving and decoding of websocket frames. It is run from the thread * started by the websocket class. It does some best-effort error detection for violations of the * websocket spec. */ class WebSocketReceiver { private DataInputStream input = null; private WebSocket websocket = null; private WebSocketEventHandler eventHandler = null; private byte[] inputHeader = new byte[112]; private MessageBuilderFactory.Builder pendingBuilder; private volatile boolean stop = false; WebSocketReceiver(WebSocket websocket) { this.websocket = websocket; } void setInput(DataInputStream input) { this.input = input; } void run() { this.eventHandler = websocket.getEventHandler(); while (!stop) { try { if (Thread.interrupted()) { throw new InterruptedException(); } int offset = 0; offset += read(inputHeader, offset, 1); boolean fin = (inputHeader[0] & 0x80) != 0; boolean rsv = (inputHeader[0] & 0x70) != 0; if (rsv) { throw new WebSocketException("Invalid frame received"); } else { final byte opcode = (byte) (inputHeader[0] & 0xf); offset += read(inputHeader, offset, 1); byte length = inputHeader[1]; long payloadLength = 0; if (length < 126) { payloadLength = length; } else if (length == 126) { offset += read(inputHeader, offset, 2); payloadLength = ((0xff & inputHeader[2]) << 8) | (0xff & inputHeader[3]); } else if (length == 127) { // Does work up to MAX_VALUE of long (2^63-1) after that minus values are // returned. // However frames with such a high payload length are vastly unrealistic. // TODO: add Limit for WebSocket Payload Length. offset += read(inputHeader, offset, 8); // Parse the bytes we just read payloadLength = parseLong(inputHeader, offset - 8); } byte[] payload = new byte[(int) payloadLength]; read(payload, 0, (int) payloadLength); if (opcode == WebSocket.OPCODE_CLOSE) { websocket.onCloseOpReceived(); } else if (opcode == WebSocket.OPCODE_PONG) { // NOTE: as a client, we don't expect PONGs. No-op } else if (opcode == WebSocket.OPCODE_TEXT || opcode == WebSocket.OPCODE_BINARY || opcode == WebSocket.OPCODE_PING || opcode == WebSocket.OPCODE_NONE) { // It's some form of application data. Decode the payload appendBytes(fin, opcode, payload); } else { // Unsupported opcode throw new WebSocketException("Unsupported opcode: " + opcode); } } } catch (SocketTimeoutException sto) { continue; } catch (IOException ioe) { handleError(new WebSocketException("IO Error", ioe)); } catch (InterruptedException e) { handleError(new WebSocketException("Receiver interrupted", e)); } catch (WebSocketException e) { handleError(e); } } } private void appendBytes(boolean fin, byte opcode, byte[] data) { // A ping can show up in the middle of another fragmented message if (opcode == WebSocket.OPCODE_PING) { if (fin) { handlePing(data); } else { throw new WebSocketException("PING must not fragment across frames"); } } else { if (pendingBuilder != null && opcode != WebSocket.OPCODE_NONE) { throw new WebSocketException("Failed to continue outstanding frame"); } else if (pendingBuilder == null && opcode == WebSocket.OPCODE_NONE) { // Trying to continue something, but there's nothing to continue throw new WebSocketException( "Received continuing frame, but there's nothing to " + "continue"); } else { if (pendingBuilder == null) { // We aren't continuing another message pendingBuilder = MessageBuilderFactory.builder(opcode); } if (!pendingBuilder.appendBytes(data)) { throw new WebSocketException("Failed to decode frame"); } else if (fin) { WebSocketMessage message = pendingBuilder.toMessage(); pendingBuilder = null; // The message assembly could still fail if (message == null) { throw new WebSocketException("Failed to decode whole message"); } else { eventHandler.onMessage(message); } } } } } private void handlePing(byte[] payload) { if (payload.length <= 125) { websocket.pong(payload); } else { throw new WebSocketException("PING frame too long"); } } private long parseLong(byte[] buffer, int offset) { // Copied from DataInputStream#readLong return (((long) buffer[offset + 0] << 56) + ((long) (buffer[offset + 1] & 255) << 48) + ((long) (buffer[offset + 2] & 255) << 40) + ((long) (buffer[offset + 3] & 255) << 32) + ((long) (buffer[offset + 4] & 255) << 24) + ((buffer[offset + 5] & 255) << 16) + ((buffer[offset + 6] & 255) << 8) + ((buffer[offset + 7] & 255) << 0)); } private int read(byte[] buffer, int offset, int length) throws IOException { input.readFully(buffer, offset, length); return length; } void stopit() { stop = true; } boolean isRunning() { return !stop; } private void handleError(WebSocketException e) { stopit(); websocket.handleReceiverError(e); } }