Websocket
[VSoRC/.git] / node_modules / websocket / lib / WebSocketFrame.js
1 /************************************************************************
2  *  Copyright 2010-2015 Brian McKelvey.
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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  ***********************************************************************/
16
17 var bufferUtil = require('./BufferUtil').BufferUtil;
18 var bufferAllocUnsafe = require('./utils').bufferAllocUnsafe;
19
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;
25 const COMPLETE = 6;
26
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;
34     this.config = config;
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;
41 }
42
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];
50
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);
56
57             this.opcode  = firstByte  & 0x0F;
58             this.length = secondByte & 0x7F;
59
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.';
65                     return true;
66                 }
67                 if (!this.fin) {
68                     this.protocolError = true;
69                     this.dropReason = 'Control frames must not be fragmented.';
70                     return true;
71                 }
72             }
73
74             if (this.length === 126) {
75                 this.parseState = WAITING_FOR_16_BIT_LENGTH;
76             }
77             else if (this.length === 127) {
78                 this.parseState = WAITING_FOR_64_BIT_LENGTH;
79             }
80             else {
81                 this.parseState = WAITING_FOR_MASK_KEY;
82             }
83         }
84     }
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;
91         }
92     }
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);
97             var lengthPair = [
98               this.frameHeader.readUInt32BE(2),
99               this.frameHeader.readUInt32BE(2+4)
100             ];
101
102             if (lengthPair[0] !== 0) {
103                 this.protocolError = true;
104                 this.dropReason = 'Unsupported 64-bit length frame received';
105                 return true;
106             }
107             this.length = lengthPair[1];
108             this.parseState = WAITING_FOR_MASK_KEY;
109         }
110     }
111
112     if (this.parseState === WAITING_FOR_MASK_KEY) {
113         if (this.mask) {
114             if (bufferList.length >= 4) {
115                 bufferList.joinInto(this.maskBytes, 0, 0, 4);
116                 bufferList.advance(4);
117                 this.parseState = WAITING_FOR_PAYLOAD;
118             }
119         }
120         else {
121             this.parseState = WAITING_FOR_PAYLOAD;
122         }
123     }
124
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';
130             return true;
131         }
132
133         if (this.length === 0) {
134             this.binaryPayload = bufferAllocUnsafe(0);
135             this.parseState = COMPLETE;
136             return true;
137         }
138         if (bufferList.length >= this.length) {
139             this.binaryPayload = bufferList.take(this.length);
140             bufferList.advance(this.length);
141             if (this.mask) {
142                 bufferUtil.unmask(this.binaryPayload, this.maskBytes);
143                 // xor(this.binaryPayload, this.maskBytes, 0);
144             }
145
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;
151                 }
152                 if (this.length >= 2) {
153                     this.closeStatus = this.binaryPayload.readUInt16BE(0);
154                     this.binaryPayload = this.binaryPayload.slice(2);
155                 }
156             }
157
158             this.parseState = COMPLETE;
159             return true;
160         }
161     }
162     return false;
163 };
164
165 WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
166     if (bufferList.length >= this.length) {
167         bufferList.advance(this.length);
168         this.parseState = COMPLETE;
169         return true;
170     }
171     return false;
172 };
173
174 WebSocketFrame.prototype.toBuffer = function(nullMask) {
175     var maskKey;
176     var headerLength = 2;
177     var data;
178     var outputPos;
179     var firstByte = 0x00;
180     var secondByte = 0x00;
181
182     if (this.fin) {
183         firstByte |= 0x80;
184     }
185     if (this.rsv1) {
186         firstByte |= 0x40;
187     }
188     if (this.rsv2) {
189         firstByte |= 0x20;
190     }
191     if (this.rsv3) {
192         firstByte |= 0x10;
193     }
194     if (this.mask) {
195         secondByte |= 0x80;
196     }
197
198     firstByte |= (this.opcode & 0x0F);
199
200     // the close frame is a special case because the close reason is
201     // prepended to the payload data.
202     if (this.opcode === 0x08) {
203         this.length = 2;
204         if (this.binaryPayload) {
205             this.length += this.binaryPayload.length;
206         }
207         data = bufferAllocUnsafe(this.length);
208         data.writeUInt16BE(this.closeStatus, 0);
209         if (this.length > 2) {
210             this.binaryPayload.copy(data, 2);
211         }
212     }
213     else if (this.binaryPayload) {
214         data = this.binaryPayload;
215         this.length = data.length;
216     }
217     else {
218         this.length = 0;
219     }
220
221     if (this.length <= 125) {
222         // encode the length directly into the two-byte frame header
223         secondByte |= (this.length & 0x7F);
224     }
225     else if (this.length > 125 && this.length <= 0xFFFF) {
226         // Use 16-bit length
227         secondByte |= 126;
228         headerLength += 2;
229     }
230     else if (this.length > 0xFFFF) {
231         // Use 64-bit length
232         secondByte |= 127;
233         headerLength += 8;
234     }
235
236     var output = bufferAllocUnsafe(this.length + headerLength + (this.mask ? 4 : 0));
237
238     // write the frame header
239     output[0] = firstByte;
240     output[1] = secondByte;
241
242     outputPos = 2;
243
244     if (this.length > 125 && this.length <= 0xFFFF) {
245         // write 16-bit length
246         output.writeUInt16BE(this.length, outputPos);
247         outputPos += 2;
248     }
249     else if (this.length > 0xFFFF) {
250         // write 64-bit length
251         output.writeUInt32BE(0x00000000, outputPos);
252         output.writeUInt32BE(this.length, outputPos + 4);
253         outputPos += 8;
254     }
255
256     if (this.mask) {
257         maskKey = nullMask ? 0 : ((Math.random() * 0xFFFFFFFF) >>> 0);
258         this.maskBytes.writeUInt32BE(maskKey, 0);
259
260         // write the mask key
261         this.maskBytes.copy(output, outputPos);
262         outputPos += 4;
263
264         if (data) {
265           bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);
266         }
267     }
268     else if (data) {
269         data.copy(output, outputPos);
270     }
271
272     return output;
273 };
274
275 WebSocketFrame.prototype.toString = function() {
276     return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;
277 };
278
279
280 module.exports = WebSocketFrame;