549 lines
15 KiB
JavaScript
Executable File
549 lines
15 KiB
JavaScript
Executable File
// James Taylor - 466598
|
|
// Framing module - Decodes and packages data
|
|
|
|
function framer() {
|
|
|
|
var connections = require("./connections");
|
|
// used for the server to emit events
|
|
var emitter = "";
|
|
var connection;
|
|
|
|
|
|
// Ensures the full length of the frame has been recieved
|
|
this.checkRecievedData = function(data, connectionID) {
|
|
|
|
// checks to see if more frames are expected
|
|
if (connection.returnWaitState(connectionID) == true) {
|
|
connection.addWaitArray(data, connectionID);
|
|
var concatenatedArray = connection.returnWaitArray(connectionID);
|
|
|
|
// Set length of expected data
|
|
if ((concatenatedArray[1] & 127) <= 125) {
|
|
var setLength = (concatenatedArray[1] & 127) + 6;
|
|
connection.addFullWaitLength(connectionID, setLength);
|
|
}
|
|
if ((concatenatedArray[1] & 127) == 126) {
|
|
if (concatenatedArray.length > 4) {
|
|
var setLength = returnLength(concatenatedArray);
|
|
connection.addFullWaitLength(connectionID, setLength);
|
|
}
|
|
}
|
|
if ((concatenatedArray[1] & 127) == 127) {
|
|
if (concatenatedArray.length > 10) {
|
|
var setLength = returnLength(concatenatedArray);
|
|
connection.addFullWaitLength(connectionID, setLength);
|
|
}
|
|
|
|
}
|
|
if (concatenatedArray.length == connection.returnFullWaitLength(connectionID)) {
|
|
// Reset wait variables
|
|
connection.changeWaitState(connectionID, false);
|
|
connection.clearWaitArray(connectionID);
|
|
connection.clearWaitLength(connectionID);
|
|
|
|
// Pass on message for decoding
|
|
decode(concatenatedArray, connectionID);
|
|
}
|
|
}
|
|
else {
|
|
if (data[0] == 129 || data[0] == 130 || data[0] == 136 || data[0] == 137 || data[0] == 138) {
|
|
if (data[0] == 138) {
|
|
function splitFrames(data) {
|
|
var checkLength = returnLength(data);
|
|
if (data.length > checkLength) {
|
|
var newFragment = data.slice(0, checkLength);
|
|
decode(newFragment, connectionID);
|
|
var unhandledFragment = data.slice(checkLength);
|
|
splitFrames(unhandledFragment);
|
|
}
|
|
else {
|
|
checkFullFrameRecieved(data, connectionID);
|
|
decode(data, connectionID);
|
|
}
|
|
}
|
|
splitFrames(data);
|
|
}
|
|
else if (data[0] == 136) {
|
|
decode(data, connectionID);
|
|
return;
|
|
}
|
|
else {
|
|
checkFullFrameRecieved(data,connectionID);
|
|
}
|
|
if (connection.returnWaitState(connectionID) == false) {
|
|
decode(data, connectionID);
|
|
}
|
|
else {
|
|
connection.addWaitArray(data, connectionID);
|
|
}
|
|
}
|
|
else if (data[0] == 1 || data[0] == 2 || data[0] == 128 || data[0] == 0) { // fragmentation
|
|
// if continuation frame is sent with no message to continue
|
|
if (data[0] == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
// if continuation frame is sent with its FIN set but it has no message to continue
|
|
else if (data[0] == 128 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
else if (data.length < 6) {
|
|
connection.changeWaitState(connectionID, true);
|
|
connection.addWaitArray(data, connectionID);
|
|
}
|
|
else {
|
|
function checkFragments(data) {
|
|
//check frame is fragmented, if not divide it
|
|
var checkLength = returnLength(data);
|
|
if (data.length > checkLength) {
|
|
var newFragment = data.slice(0, checkLength);
|
|
handleFragmentation(newFragment, connectionID);
|
|
var unhandledFragment = data.slice(checkLength);
|
|
checkFragments(unhandledFragment);
|
|
}
|
|
else {
|
|
handleFragmentation(data, connectionID);
|
|
}
|
|
}
|
|
checkFragments(data);
|
|
}
|
|
}
|
|
else {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handles fragmentation cases
|
|
function handleFragmentation(data, connectionID) {
|
|
var opcode = data[0];
|
|
|
|
// Pings may be sent in the middle of fragments
|
|
if (opcode == 137) {
|
|
decode(data, connectionID);
|
|
return;
|
|
}
|
|
else if (opcode == 136) {
|
|
decode(data, connectionID);
|
|
return;
|
|
}
|
|
if (opcode == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
else if ((opcode == 1 || opcode == 2 || opcode == 129) && (connection.fragmentArray("get", data, connectionID).length != 0)) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
|
|
// remember the opcode for sending later
|
|
if (opcode == 1) {
|
|
connection.fragmentOpcode("set", 129, connectionID);
|
|
}
|
|
else if (opcode == 2) {
|
|
connection.fragmentOpcode("set", 130, connectionID);
|
|
}
|
|
// unmask the message
|
|
var maskLocation = 2;
|
|
var dataLength = data[1] & 127;
|
|
if (dataLength == 126) {
|
|
maskLocation = 4;
|
|
}
|
|
else if (dataLength == 127) {
|
|
maskLocation = 10;
|
|
}
|
|
var unmaskedData = [];
|
|
var maskingKey = data.slice(maskLocation, maskLocation + 4);
|
|
var dataLocation = maskLocation + 4;
|
|
for (var i = 0; dataLocation < data.length; i++) {
|
|
var newData = data[dataLocation] ^ maskingKey[i % 4];
|
|
unmaskedData.push(newData);
|
|
dataLocation++;
|
|
}
|
|
|
|
|
|
var buf = new Buffer(unmaskedData);
|
|
connection.fragmentArray("set", buf, connectionID);
|
|
|
|
// Message with FIN set to 1, package fragments for sending
|
|
if (opcode == 128) {
|
|
var dataBuffer = connection.fragmentArray("get", buf, connectionID);
|
|
connection.fragmentArray("reset", buf, connectionID);
|
|
opcode = connection.fragmentOpcode("get", 0, connectionID);
|
|
|
|
// Construct the first bytes of the message
|
|
var newData = [];
|
|
newData[0] = opcode;
|
|
setPayloadLength(newData, dataBuffer.length);
|
|
|
|
var buf = new Buffer(newData);
|
|
var bufList = [];
|
|
bufList[0] = buf;
|
|
bufList[1] = dataBuffer;
|
|
var packagedMessage = Buffer.concat(bufList);
|
|
connection.sendFrame(packagedMessage, connectionID);
|
|
}
|
|
}
|
|
|
|
// Used to correctly handle the data recieved from the client
|
|
function decode(data, connectionID) {
|
|
|
|
// The type of data (Opcode)
|
|
var opcode = data[0];
|
|
// The length of the data must be greater than 5 to be a valid frame
|
|
if (data.length <= 5) {
|
|
connection.closeConnection(connectionID, 1008);
|
|
console.log("Frame Length too short!");
|
|
return;
|
|
}
|
|
|
|
// make the initial check on the RSV numbers
|
|
if (invalidRSV(opcode)) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
|
|
if (opcode == 129) { // text message
|
|
emitter.emit("message", data, connectionID);
|
|
var decodedData = unmaskMessage(data);
|
|
var packagedData = packageData(decodedData);
|
|
var encodedBuffer = new Buffer(packagedData);
|
|
connection.sendFrame(encodedBuffer, connectionID);
|
|
}
|
|
else if (opcode == 1 || opcode == 2 || opcode == 0 || opcode == 128) { // fragmented messages
|
|
handleFragmentation(data, connectionID);
|
|
}
|
|
else if (opcode == 130) { // binary frame
|
|
emitter.emit("message", data, connectionID);
|
|
var decodedData = unmaskMessage(data);
|
|
var packagedData = packageData(decodedData);
|
|
var encodedBuffer = new Buffer(packagedData);
|
|
connection.sendFrame(encodedBuffer, connectionID);
|
|
}
|
|
else if (opcode == 136) { // close frame
|
|
// Check for valid close code and close the connection
|
|
validateCloseFrame(connectionID, data);
|
|
}
|
|
else if (opcode == 137) { // ping frame
|
|
if (data.length <= 131) {
|
|
var decodedData = unmaskMessage(data);
|
|
var packagedData = constructPongFrame(decodedData);
|
|
var encodedBuffer = new Buffer(packagedData);
|
|
connection.sendFrame(encodedBuffer, connectionID);
|
|
}
|
|
else {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
}
|
|
else if (opcode == 138) { // pong frame
|
|
console.log("Pong recieved");
|
|
}
|
|
else {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Used to package the data in the correct framing format for WebSockets
|
|
function packageData(data) {
|
|
// extract the opcode from the data array then remove it from the array
|
|
var opcode = data.pop();
|
|
var newData = [];
|
|
newData[0] = opcode;
|
|
|
|
var payloadLength = data.length;
|
|
|
|
if (payloadLength <= 125) {
|
|
var dataLocation = 2;
|
|
newData[1] = payloadLength;
|
|
}
|
|
else if (payloadLength >= 126 && payloadLength <= 65535) {
|
|
var dataLocation = 4;
|
|
newData[1] = 126;
|
|
newData[2] = payloadLength >>> 8;
|
|
newData[3] = payloadLength & 255;
|
|
}
|
|
else if (payloadLength >= 65536) {
|
|
var dataLocation = 10;
|
|
newData[1] = 127;
|
|
|
|
// The first 32 bits
|
|
newData[9] = (payloadLength) & 255;
|
|
newData[8] = (payloadLength >> 8) & 255;
|
|
newData[7] = (payloadLength >> 16) & 255;
|
|
newData[6] = (payloadLength >> 24) & 255;
|
|
|
|
// if the number is greater than 32bit
|
|
if (payloadLength >= 4294967296) {
|
|
// Get the higher 64 bit
|
|
var sixtyFourBit = payloadLength.toString(2);
|
|
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
|
|
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
|
|
|
|
newData[5] = (secondThirtyTwoBits) & 255;
|
|
newData[4] = (secondThirtyTwoBits >> 8) & 255;
|
|
newData[3] = (secondThirtyTwoBits >> 16) & 255;
|
|
newData[2] = (secondThirtyTwoBits >> 24) & 255;
|
|
}
|
|
else { // if number is less than 32bit
|
|
newData[5] = 0;
|
|
newData[4] = 0;
|
|
newData[3] = 0;
|
|
newData[2] = 0;
|
|
}
|
|
|
|
}
|
|
|
|
for (var i = 0; i < payloadLength; i++) {
|
|
newData[dataLocation] = data[i];
|
|
dataLocation++;
|
|
}
|
|
return newData;
|
|
|
|
}
|
|
|
|
// unmasks a message
|
|
function unmaskMessage(data) {
|
|
// The payload length
|
|
var payloadLength = data[1];
|
|
|
|
// The location of the masking key
|
|
// Defualt 2
|
|
var locationOfMask = 2;
|
|
|
|
// Find the length of the message being sent, remove the masking key using & 127
|
|
var payloadLength = payloadLength & 127;
|
|
if (payloadLength == 126) {
|
|
// If the length of the payload is 126, 2 extra bytes represent the length
|
|
locationOfMask = 4;
|
|
}
|
|
else if (payloadLength == 127) {
|
|
// If length of the payload is 127, 8 extra bytes represent the length
|
|
locationOfMask = 10;
|
|
}
|
|
|
|
// Mask is 4 bytes long
|
|
var maskingKey = data.slice(locationOfMask, locationOfMask + 4);
|
|
|
|
var dataLocation = locationOfMask + 4;
|
|
|
|
// new array to hold decodedData
|
|
var decodedData = [];
|
|
|
|
var logString = "";
|
|
for (var i = 0; dataLocation < data.length; i++) {
|
|
var newData = data[dataLocation] ^ maskingKey[i % 4];
|
|
decodedData.push(newData);
|
|
dataLocation++;
|
|
}
|
|
|
|
decodedData.push(data[0]);
|
|
|
|
return decodedData;
|
|
|
|
}
|
|
|
|
// Used to set the correct payload length for packaging data
|
|
function setPayloadLength(newData, payloadLength) {
|
|
if (payloadLength <= 125) {
|
|
var dataLocation = 2;
|
|
newData[1] = payloadLength;
|
|
}
|
|
else if (payloadLength >= 126 && payloadLength <= 65535) {
|
|
var dataLocation = 4;
|
|
newData[1] = 126;
|
|
newData[2] = payloadLength >>> 8;
|
|
newData[3] = payloadLength & 255;
|
|
}
|
|
else if (payloadLength >= 65536) {
|
|
var dataLocation = 10;
|
|
newData[1] = 127;
|
|
|
|
// The first 32 bits
|
|
newData[9] = (payloadLength) & 255;
|
|
newData[8] = (payloadLength >> 8) & 255;
|
|
newData[7] = (payloadLength >> 16) & 255;
|
|
newData[6] = (payloadLength >> 24) & 255;
|
|
|
|
// if the number is greater than 32bit
|
|
if (payloadLength >= 4294967296) {
|
|
var sixtyFourBit = payloadLength.toString(2);
|
|
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
|
|
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
|
|
newData[5] = (secondThirtyTwoBits) & 255;
|
|
newData[4] = (secondThirtyTwoBits >> 8) & 255;
|
|
newData[3] = (secondThirtyTwoBits >> 16) & 255;
|
|
newData[2] = (secondThirtyTwoBits >> 24) & 255;
|
|
}
|
|
else { // if number is less than 32bit
|
|
newData[5] = 0;
|
|
newData[4] = 0;
|
|
newData[3] = 0;
|
|
newData[2] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates a Pong frame
|
|
function constructPongFrame(data) {
|
|
var pingOpcode = data.pop();
|
|
var newData = [];
|
|
newData[0] = 138;
|
|
newData[1] = data.length;
|
|
var dataLocation = 2;
|
|
|
|
var i = 0;
|
|
while (i < data.length) {
|
|
newData[dataLocation] = data[i];
|
|
dataLocation++;
|
|
i++;
|
|
}
|
|
return newData;
|
|
}
|
|
|
|
// Used to check the full frame has been recieved
|
|
function checkFullFrameRecieved(data, connectionID) {
|
|
var payloadLength = data[1] & 127; //remove mask bit
|
|
if (payloadLength <= 125) {
|
|
if ((data.length - 6) == payloadLength) {
|
|
//console.log("Correct Frame Size");
|
|
}
|
|
else {
|
|
connection.changeWaitState(connectionID, true);
|
|
}
|
|
}
|
|
else if (payloadLength == 126) {
|
|
var dataRecieved = data.slice(8, (data.length));
|
|
var payOne = data[2] << 8;
|
|
var payTwo = data[3];
|
|
var dataLength = payOne + payTwo;
|
|
if (dataLength == dataRecieved.length) {
|
|
//console.log("Correct Frame Size");
|
|
}
|
|
else {
|
|
connection.changeWaitState(connectionID, true);
|
|
}
|
|
}
|
|
else if (payloadLength == 127) {
|
|
var dataRecieved = data.slice(14, (data.length));
|
|
var dataLength = "";
|
|
for (var i = 2; i < 10; i++) {
|
|
if (data[i] == 0) {
|
|
var str ="00000000";
|
|
}
|
|
else {
|
|
var newByte = data[i];
|
|
var str = newByte.toString(2);
|
|
}
|
|
dataLength += str;
|
|
}
|
|
dataLength = parseInt(dataLength, 2);
|
|
if (dataLength == dataRecieved.length) {
|
|
//console.log("Frame Recieved");
|
|
}
|
|
else {
|
|
connection.changeWaitState(connectionID, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns the expected length of the data as specified by the Payload Length byte (2nd byte);
|
|
function returnLength(data) {
|
|
if ((data[1] & 127) <= 125) {
|
|
return (data[1] & 127) + 6;
|
|
}
|
|
else if ((data[1] & 127) == 126) {
|
|
var dataLength = (data[2] << 8) + data[3];
|
|
return dataLength + 8;
|
|
}
|
|
else if ((data[1] & 127) == 127) {
|
|
var dataLength = "";
|
|
for (var i = 2; i < 10; i++) {
|
|
if (data[i] == 0) {
|
|
var str ="00000000";
|
|
}
|
|
else {
|
|
var newByte = data[i];
|
|
var str = newByte.toString(2);
|
|
}
|
|
dataLength += str;
|
|
}
|
|
dataLength = parseInt(dataLength, 2);
|
|
return dataLength + 14;
|
|
}
|
|
}
|
|
|
|
// Checks that RSV1, RSV2 and RSV3 are all set to 0
|
|
function invalidRSV(firstByte) {
|
|
var check = firstByte;
|
|
// remove opcode and FIN;
|
|
check = check >> 4;
|
|
check = check & 7;
|
|
|
|
if (check == 0) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Validates a close frame
|
|
function validateCloseFrame(connectionID, data) {
|
|
if (data.length == 6) { // No close code
|
|
connection.closeConnection(connectionID, 1000);
|
|
return;
|
|
}
|
|
else if (data.length == 7) { // Only half close code
|
|
connection.closeConnection(connectionID, 1002);
|
|
return;
|
|
}
|
|
// CLOSE CODE CHECKING
|
|
var mask = data.slice(2, 6);
|
|
var maskedCode = data.slice(6, 8);
|
|
var unmaskedCode = [];
|
|
for (var i = 0; i < maskedCode.length; i++) {
|
|
unmaskedCode[i] = maskedCode[i] ^ mask[i % 4];
|
|
}
|
|
unmaskedCode[0] = unmaskedCode[0] << 8;
|
|
var statusCode = unmaskedCode[0] + unmaskedCode[1];
|
|
|
|
if (statusCode <= 999 || statusCode >= 5000) {
|
|
connection.closeConnection(connectionID, 1002);
|
|
}
|
|
else if (statusCode >= 3000 && statusCode <= 4999) {
|
|
connection.closeConnection(connectionID, 1000);
|
|
}
|
|
else {
|
|
switch(statusCode) {
|
|
case 1000: connection.closeConnection(connectionID, 1000); break;
|
|
case 1001: connection.closeConnection(connectionID, 1000); break;
|
|
case 1002: connection.closeConnection(connectionID, 1000); break;
|
|
case 1003: connection.closeConnection(connectionID, 1000); break;
|
|
case 1007: connection.closeConnection(connectionID, 1000); break;
|
|
case 1008: connection.closeConnection(connectionID, 1000); break;
|
|
case 1009: connection.closeConnection(connectionID, 1000); break;
|
|
case 1010: connection.closeConnection(connectionID, 1000); break;
|
|
case 1011: connection.closeConnection(connectionID, 1000); break;
|
|
default: connection.closeConnection(connectionID, 1002);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
this.setEmitter = function(object) {
|
|
emitter = object;
|
|
}
|
|
|
|
this.setConnectionList = function(list) {
|
|
connection = list;
|
|
}
|
|
|
|
this.unmaskMessageCaller = function(message) {
|
|
return unmaskMessage(message);
|
|
}
|
|
|
|
this.packageDataCaller = function(data) {
|
|
return packageData(data);
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = framer; |