Rev 910 | Rev 915 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/************************************************************************* Copyright 2010-2015 Brian McKelvey.** 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.***********************************************************************/var bufferUtil = require('./BufferUtil').BufferUtil;const DECODE_HEADER = 1;const WAITING_FOR_16_BIT_LENGTH = 2;const WAITING_FOR_64_BIT_LENGTH = 3;const WAITING_FOR_MASK_KEY = 4;const WAITING_FOR_PAYLOAD = 5;const COMPLETE = 6;// WebSocketConnection will pass shared buffer objects for maskBytes and// frameHeader into the constructor to avoid tons of small memory allocations// for each frame we have to parse. This is only used for parsing frames// we receive off the wire.function WebSocketFrame(maskBytes, frameHeader, config) {this.maskBytes = maskBytes;this.frameHeader = frameHeader;this.config = config;this.maxReceivedFrameSize = config.maxReceivedFrameSize;this.protocolError = false;this.frameTooLarge = false;this.invalidCloseFrameLength = false;this.parseState = DECODE_HEADER;this.closeStatus = -1;}WebSocketFrame.prototype.addData = function(bufferList) {if (this.parseState === DECODE_HEADER) {if (bufferList.length >= 2) {bufferList.joinInto(this.frameHeader, 0, 0, 2);bufferList.advance(2);var firstByte = this.frameHeader[0];var secondByte = this.frameHeader[1];this.fin = Boolean(firstByte & 0x80);this.rsv1 = Boolean(firstByte & 0x40);this.rsv2 = Boolean(firstByte & 0x20);this.rsv3 = Boolean(firstByte & 0x10);this.mask = Boolean(secondByte & 0x80);this.opcode = firstByte & 0x0F;this.length = secondByte & 0x7F;// Control frame sanity checkif (this.opcode >= 0x08) {if (this.length > 125) {this.protocolError = true;this.dropReason = 'Illegal control frame longer than 125 bytes.';return true;}if (!this.fin) {this.protocolError = true;this.dropReason = 'Control frames must not be fragmented.';return true;}}if (this.length === 126) {this.parseState = WAITING_FOR_16_BIT_LENGTH;}else if (this.length === 127) {this.parseState = WAITING_FOR_64_BIT_LENGTH;}else {this.parseState = WAITING_FOR_MASK_KEY;}}}if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {if (bufferList.length >= 2) {bufferList.joinInto(this.frameHeader, 2, 0, 2);bufferList.advance(2);this.length = this.frameHeader.readUInt16BE(2, true);this.parseState = WAITING_FOR_MASK_KEY;}}else if (this.parseState === WAITING_FOR_64_BIT_LENGTH) {if (bufferList.length >= 8) {bufferList.joinInto(this.frameHeader, 2, 0, 8);bufferList.advance(8);var lengthPair = [this.frameHeader.readUInt32BE(2, true),this.frameHeader.readUInt32BE(2+4, true)];if (lengthPair[0] !== 0) {this.protocolError = true;this.dropReason = 'Unsupported 64-bit length frame received';return true;}this.length = lengthPair[1];this.parseState = WAITING_FOR_MASK_KEY;}}if (this.parseState === WAITING_FOR_MASK_KEY) {if (this.mask) {if (bufferList.length >= 4) {bufferList.joinInto(this.maskBytes, 0, 0, 4);bufferList.advance(4);this.parseState = WAITING_FOR_PAYLOAD;}}else {this.parseState = WAITING_FOR_PAYLOAD;}}if (this.parseState === WAITING_FOR_PAYLOAD) {if (this.length > this.maxReceivedFrameSize) {this.frameTooLarge = true;this.dropReason = 'Frame size of ' + this.length.toString(10) +' bytes exceeds maximum accepted frame size';return true;}if (this.length === 0) {this.binaryPayload = new Buffer(0);this.parseState = COMPLETE;return true;}if (bufferList.length >= this.length) {this.binaryPayload = bufferList.take(this.length);bufferList.advance(this.length);if (this.mask) {bufferUtil.unmask(this.binaryPayload, this.maskBytes);// xor(this.binaryPayload, this.maskBytes, 0);}if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSEif (this.length === 1) {// Invalid length for a close frame. Must be zero or at least two.this.binaryPayload = new Buffer(0);this.invalidCloseFrameLength = true;}if (this.length >= 2) {this.closeStatus = this.binaryPayload.readUInt16BE(0, true);this.binaryPayload = this.binaryPayload.slice(2);}}this.parseState = COMPLETE;return true;}}return false;};WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {if (bufferList.length >= this.length) {bufferList.advance(this.length);this.parseState = COMPLETE;return true;}return false;};WebSocketFrame.prototype.toBuffer = function(nullMask) {var maskKey;var headerLength = 2;var data;var outputPos;var firstByte = 0x00;var secondByte = 0x00;if (this.fin) {firstByte |= 0x80;}if (this.rsv1) {firstByte |= 0x40;}if (this.rsv2) {firstByte |= 0x20;}if (this.rsv3) {firstByte |= 0x10;}if (this.mask) {secondByte |= 0x80;}firstByte |= (this.opcode & 0x0F);// the close frame is a special case because the close reason is// prepended to the payload data.if (this.opcode === 0x08) {this.length = 2;if (this.binaryPayload) {this.length += this.binaryPayload.length;}data = new Buffer(this.length);data.writeUInt16BE(this.closeStatus, 0, true);if (this.length > 2) {this.binaryPayload.copy(data, 2);}}else if (this.binaryPayload) {data = this.binaryPayload;this.length = data.length;}else {this.length = 0;}if (this.length <= 125) {// encode the length directly into the two-byte frame headersecondByte |= (this.length & 0x7F);}else if (this.length > 125 && this.length <= 0xFFFF) {// Use 16-bit lengthsecondByte |= 126;headerLength += 2;}else if (this.length > 0xFFFF) {// Use 64-bit lengthsecondByte |= 127;headerLength += 8;}var output = new Buffer(this.length + headerLength + (this.mask ? 4 : 0));// write the frame headeroutput[0] = firstByte;output[1] = secondByte;outputPos = 2;if (this.length > 125 && this.length <= 0xFFFF) {// write 16-bit lengthoutput.writeUInt16BE(this.length, outputPos, true);outputPos += 2;}else if (this.length > 0xFFFF) {// write 64-bit lengthoutput.writeUInt32BE(0x00000000, outputPos, true);output.writeUInt32BE(this.length, outputPos + 4, true);outputPos += 8;}if (this.mask) {maskKey = nullMask ? 0 : (Math.random()*0xFFFFFFFF) | 0;this.maskBytes.writeUInt32BE(maskKey, 0, true);// write the mask keythis.maskBytes.copy(output, outputPos);outputPos += 4;if (data) {bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);}}else if (data) {data.copy(output, outputPos);}return output;};WebSocketFrame.prototype.toString = function() {return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;};module.exports = WebSocketFrame;