/*
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman
*
* This file is part of the SmallMind Code Project.
*
* The SmallMind Code Project is free software, you can redistribute
* it and/or modify it under either, at your discretion...
*
* 1) The terms of GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* ...or...
*
* 2) The terms of the Apache License, Version 2.0.
*
* The SmallMind Code Project is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License or Apache License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and the Apache License along with the SmallMind Code Project. If not, see
* <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>.
*
* Additional permission under the GNU Affero GPL version 3 section 7
* ------------------------------------------------------------------
* If you modify this Program, or any covered work, by linking or
* combining it with other code, such other code is not for that reason
* alone subject to any of the requirements of the GNU Affero GPL
* version 3.
*/
package org.smallmind.web.websocket;
import java.util.concurrent.ThreadLocalRandom;
public class Frame {
public static byte[] ping (byte[] message)
throws WebSocketException {
return control(OpCode.PING, message);
}
public static byte[] pong (byte[] message)
throws WebSocketException {
return control(OpCode.PONG, message);
}
public static byte[] close (byte[] status, String reason)
throws WebSocketException {
byte[] message;
if (reason == null) {
message = status;
} else {
byte[] reasonsBytes = reason.getBytes();
message = new byte[reasonsBytes.length + 2];
System.arraycopy(status, 0, message, 0, 2);
System.arraycopy(reasonsBytes, 0, message, 2, reasonsBytes.length);
}
return control(OpCode.CLOSE, message);
}
private static byte[] control (OpCode opCode, byte[] message)
throws WebSocketException {
if (message.length > 125) {
throw new WebSocketException("Control frame data length exceeds 125 bytes");
}
return data(opCode, message);
}
public static byte[] text (String message) {
return data(OpCode.TEXT, message.getBytes());
}
public static byte[] binary (byte[] message) {
return data(OpCode.BINARY, message);
}
private static byte[] data (OpCode opCode, byte[] message) {
int start = (message.length < 126) ? 6 : (message.length < 65536) ? 8 : 14;
byte[] out = new byte[message.length + start];
byte[] mask = new byte[4];
ThreadLocalRandom.current().nextBytes(mask);
out[0] = (byte)(0x80 | opCode.getCode());
if (message.length < 126) {
out[1] = (byte)(0x80 | message.length);
System.arraycopy(mask, 0, out, 2, 4);
} else if (message.length < 65536) {
out[1] = (byte)(0x80 | 126);
out[2] = (byte)(message.length >>> 8);
out[3] = (byte)(message.length & 0xFF);
System.arraycopy(mask, 0, out, 4, 4);
} else {
out[1] = (byte)(0x80 | 127);
// largest array will never be more than 2^31-1
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 0;
out[6] = (byte)(message.length >>> 24);
out[7] = (byte)(message.length >>> 16);
out[8] = (byte)(message.length >>> 8);
out[9] = (byte)(message.length & 0xFF);
System.arraycopy(mask, 0, out, 10, 4);
}
for (int index = 0; index < message.length; index++) {
out[index + start] = (byte)(message[index] ^ mask[index % 4]);
}
return out;
}
public static Fragment decode (byte[] buffer)
throws SyntaxException {
OpCode opCode;
boolean fin;
int start;
byte[] message;
byte length;
if ((opCode = OpCode.convert(buffer[0])) == null) {
throw new SyntaxException("Unknown op code(%d)", buffer[0] & 0xF);
}
fin = (buffer[0] & 0x80) != 0;
if ((length = (byte)(buffer[1] & 0x7F)) < 126) {
start = 2;
message = new byte[length];
} else if (length == 126) {
message = new byte[((buffer[2] & 0xFF) << 8) + (buffer[3] & 0xFF)];
start = 4;
} else {
message = new byte[((buffer[6] & 0xFF) << 24) + ((buffer[7] & 0xFF) << 16) + ((buffer[8] & 0xFF) << 8) + (buffer[9] & 0xFF)];
start = 10;
}
System.arraycopy(buffer, start, message, 0, message.length);
return new Fragment(fin, opCode, message);
}
}