/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.websocket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* WebSocket frame as per RFC6455.
*/
class Frame {
public static final byte OPCODE_TEXT_FRAME = 0x1;
public static final byte OPCODE_BINARY_FRAME = 0x2;
public static final byte OPCODE_CONNECTION_CLOSE = 0x8;
public static final byte OPCODE_CONNECTION_PING = 0x9;
public static final byte OPCODE_CONNECTION_PONG = 0xA;
public boolean fin;
public boolean rsv1;
public boolean rsv2;
public boolean rsv3;
public byte opcode;
public boolean hasMask;
public long payloadLen;
public byte[] maskingKey;
public byte[] payloadData;
public void readFrom(BufferedInputStream input) throws IOException {
decodeFirstByte(readByteOrThrow(input));
byte maskAndFirstLengthBits = readByteOrThrow(input);
hasMask = (maskAndFirstLengthBits & 0x80) != 0;
payloadLen = decodeLength((byte)(maskAndFirstLengthBits & ~0x80), input);
maskingKey = hasMask ? decodeMaskingKey(input) : null;
payloadData = new byte[(int)payloadLen];
readBytesOrThrow(input, payloadData, 0, (int)payloadLen);
MaskingHelper.unmask(maskingKey, payloadData, 0, (int)payloadLen);
}
public void writeTo(BufferedOutputStream output) throws IOException {
output.write(encodeFirstByte());
byte[] lengthAndMaskBit = encodeLength(payloadLen);
if (hasMask) {
lengthAndMaskBit[0] |= 0x80;
}
output.write(lengthAndMaskBit, 0, lengthAndMaskBit.length);
if (hasMask) {
throw new UnsupportedOperationException("Writing masked data not implemented");
}
output.write(payloadData, 0, (int) payloadLen);
}
private void decodeFirstByte(byte b) {
fin = (b & 0x80) != 0;
rsv1 = (b & 0x40) != 0;
rsv2 = (b & 0x20) != 0;
rsv3 = (b & 0x10) != 0;
opcode = (byte)(b & 0xf);
}
private byte encodeFirstByte() {
byte b = 0;
if (fin) {
b |= 0x80;
}
if (rsv1) {
b |= 0x40;
}
if (rsv2) {
b |= 0x20;
}
if (rsv3) {
b |= 0x10;
}
b |= (opcode & 0xf);
return b;
}
private long decodeLength(byte firstLenByte, InputStream in) throws IOException {
if (firstLenByte <= 125) {
return firstLenByte;
} else if (firstLenByte == 126) {
return (readByteOrThrow(in) & 0xff) << 8 | (readByteOrThrow(in) & 0xff);
} else if (firstLenByte == 127) {
long len = 0;
for (int i = 0; i < 8; i++) {
len |= (readByteOrThrow(in) & 0xff);
len <<= 8;
}
return len;
} else {
throw new IOException("Unexpected length byte: " + firstLenByte);
}
}
private static byte[] encodeLength(long len) {
if (len <= 125) {
return new byte[] { (byte)len };
} else if (len <= 0xffff) {
return new byte[] {
126,
(byte)((len >> 8) & 0xff),
(byte)((len) & 0xff)
};
} else {
return new byte[] {
127,
(byte)((len >> 56) & 0xff),
(byte)((len >> 48) & 0xff),
(byte)((len >> 40) & 0xff),
(byte)((len >> 32) & 0xff),
(byte)((len >> 24) & 0xff),
(byte)((len >> 16) & 0xff),
(byte)((len >> 8) & 0xff),
(byte)((len) & 0xff)
};
}
}
private static byte[] decodeMaskingKey(InputStream in) throws IOException {
byte[] key = new byte[4];
readBytesOrThrow(in, key, 0, key.length);
return key;
}
private static void readBytesOrThrow(InputStream in, byte[] buf, int offset, int count)
throws IOException {
while (count > 0) {
int n = in.read(buf, offset, count);
if (n == -1) {
throw new EOFException();
}
count -= n;
offset += n;
}
}
private static byte readByteOrThrow(InputStream in) throws IOException {
int b = in.read();
if (b == -1) {
throw new EOFException();
}
return (byte)b;
}
}