1 /************************************************************************
2 * Copyright 2010-2015 Brian McKelvey.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ***********************************************************************/
17 var bufferUtil = require('./BufferUtil').BufferUtil;
18 var bufferAllocUnsafe = require('./utils').bufferAllocUnsafe;
20 const DECODE_HEADER = 1;
21 const WAITING_FOR_16_BIT_LENGTH = 2;
22 const WAITING_FOR_64_BIT_LENGTH = 3;
23 const WAITING_FOR_MASK_KEY = 4;
24 const WAITING_FOR_PAYLOAD = 5;
27 // WebSocketConnection will pass shared buffer objects for maskBytes and
28 // frameHeader into the constructor to avoid tons of small memory allocations
29 // for each frame we have to parse. This is only used for parsing frames
30 // we receive off the wire.
31 function WebSocketFrame(maskBytes, frameHeader, config) {
32 this.maskBytes = maskBytes;
33 this.frameHeader = frameHeader;
35 this.maxReceivedFrameSize = config.maxReceivedFrameSize;
36 this.protocolError = false;
37 this.frameTooLarge = false;
38 this.invalidCloseFrameLength = false;
39 this.parseState = DECODE_HEADER;
40 this.closeStatus = -1;
43 WebSocketFrame.prototype.addData = function(bufferList) {
44 if (this.parseState === DECODE_HEADER) {
45 if (bufferList.length >= 2) {
46 bufferList.joinInto(this.frameHeader, 0, 0, 2);
47 bufferList.advance(2);
48 var firstByte = this.frameHeader[0];
49 var secondByte = this.frameHeader[1];
51 this.fin = Boolean(firstByte & 0x80);
52 this.rsv1 = Boolean(firstByte & 0x40);
53 this.rsv2 = Boolean(firstByte & 0x20);
54 this.rsv3 = Boolean(firstByte & 0x10);
55 this.mask = Boolean(secondByte & 0x80);
57 this.opcode = firstByte & 0x0F;
58 this.length = secondByte & 0x7F;
60 // Control frame sanity check
61 if (this.opcode >= 0x08) {
62 if (this.length > 125) {
63 this.protocolError = true;
64 this.dropReason = 'Illegal control frame longer than 125 bytes.';
68 this.protocolError = true;
69 this.dropReason = 'Control frames must not be fragmented.';
74 if (this.length === 126) {
75 this.parseState = WAITING_FOR_16_BIT_LENGTH;
77 else if (this.length === 127) {
78 this.parseState = WAITING_FOR_64_BIT_LENGTH;
81 this.parseState = WAITING_FOR_MASK_KEY;
85 if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {
86 if (bufferList.length >= 2) {
87 bufferList.joinInto(this.frameHeader, 2, 0, 2);
88 bufferList.advance(2);
89 this.length = this.frameHeader.readUInt16BE(2);
90 this.parseState = WAITING_FOR_MASK_KEY;
93 else if (this.parseState === WAITING_FOR_64_BIT_LENGTH) {
94 if (bufferList.length >= 8) {
95 bufferList.joinInto(this.frameHeader, 2, 0, 8);
96 bufferList.advance(8);
98 this.frameHeader.readUInt32BE(2),
99 this.frameHeader.readUInt32BE(2+4)
102 if (lengthPair[0] !== 0) {
103 this.protocolError = true;
104 this.dropReason = 'Unsupported 64-bit length frame received';
107 this.length = lengthPair[1];
108 this.parseState = WAITING_FOR_MASK_KEY;
112 if (this.parseState === WAITING_FOR_MASK_KEY) {
114 if (bufferList.length >= 4) {
115 bufferList.joinInto(this.maskBytes, 0, 0, 4);
116 bufferList.advance(4);
117 this.parseState = WAITING_FOR_PAYLOAD;
121 this.parseState = WAITING_FOR_PAYLOAD;
125 if (this.parseState === WAITING_FOR_PAYLOAD) {
126 if (this.length > this.maxReceivedFrameSize) {
127 this.frameTooLarge = true;
128 this.dropReason = 'Frame size of ' + this.length.toString(10) +
129 ' bytes exceeds maximum accepted frame size';
133 if (this.length === 0) {
134 this.binaryPayload = bufferAllocUnsafe(0);
135 this.parseState = COMPLETE;
138 if (bufferList.length >= this.length) {
139 this.binaryPayload = bufferList.take(this.length);
140 bufferList.advance(this.length);
142 bufferUtil.unmask(this.binaryPayload, this.maskBytes);
143 // xor(this.binaryPayload, this.maskBytes, 0);
146 if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
147 if (this.length === 1) {
148 // Invalid length for a close frame. Must be zero or at least two.
149 this.binaryPayload = bufferAllocUnsafe(0);
150 this.invalidCloseFrameLength = true;
152 if (this.length >= 2) {
153 this.closeStatus = this.binaryPayload.readUInt16BE(0);
154 this.binaryPayload = this.binaryPayload.slice(2);
158 this.parseState = COMPLETE;
165 WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
166 if (bufferList.length >= this.length) {
167 bufferList.advance(this.length);
168 this.parseState = COMPLETE;
174 WebSocketFrame.prototype.toBuffer = function(nullMask) {
176 var headerLength = 2;
179 var firstByte = 0x00;
180 var secondByte = 0x00;
198 firstByte |= (this.opcode & 0x0F);
200 // the close frame is a special case because the close reason is
201 // prepended to the payload data.
202 if (this.opcode === 0x08) {
204 if (this.binaryPayload) {
205 this.length += this.binaryPayload.length;
207 data = bufferAllocUnsafe(this.length);
208 data.writeUInt16BE(this.closeStatus, 0);
209 if (this.length > 2) {
210 this.binaryPayload.copy(data, 2);
213 else if (this.binaryPayload) {
214 data = this.binaryPayload;
215 this.length = data.length;
221 if (this.length <= 125) {
222 // encode the length directly into the two-byte frame header
223 secondByte |= (this.length & 0x7F);
225 else if (this.length > 125 && this.length <= 0xFFFF) {
230 else if (this.length > 0xFFFF) {
236 var output = bufferAllocUnsafe(this.length + headerLength + (this.mask ? 4 : 0));
238 // write the frame header
239 output[0] = firstByte;
240 output[1] = secondByte;
244 if (this.length > 125 && this.length <= 0xFFFF) {
245 // write 16-bit length
246 output.writeUInt16BE(this.length, outputPos);
249 else if (this.length > 0xFFFF) {
250 // write 64-bit length
251 output.writeUInt32BE(0x00000000, outputPos);
252 output.writeUInt32BE(this.length, outputPos + 4);
257 maskKey = nullMask ? 0 : ((Math.random() * 0xFFFFFFFF) >>> 0);
258 this.maskBytes.writeUInt32BE(maskKey, 0);
260 // write the mask key
261 this.maskBytes.copy(output, outputPos);
265 bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);
269 data.copy(output, outputPos);
275 WebSocketFrame.prototype.toString = function() {
276 return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;
280 module.exports = WebSocketFrame;