Subversion Repositories qbpwcf-lib(archive)

Rev

Rev 915 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 liveuser 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
 
19
const DECODE_HEADER = 1;
20
const WAITING_FOR_16_BIT_LENGTH = 2;
21
const WAITING_FOR_64_BIT_LENGTH = 3;
22
const WAITING_FOR_MASK_KEY = 4;
23
const WAITING_FOR_PAYLOAD = 5;
24
const COMPLETE = 6;
25
 
26
// WebSocketConnection will pass shared buffer objects for maskBytes and
27
// frameHeader into the constructor to avoid tons of small memory allocations
28
// for each frame we have to parse.  This is only used for parsing frames
29
// we receive off the wire.
30
function WebSocketFrame(maskBytes, frameHeader, config) {
31
    this.maskBytes = maskBytes;
32
    this.frameHeader = frameHeader;
33
    this.config = config;
34
    this.maxReceivedFrameSize = config.maxReceivedFrameSize;
35
    this.protocolError = false;
36
    this.frameTooLarge = false;
37
    this.invalidCloseFrameLength = false;
38
    this.parseState = DECODE_HEADER;
39
    this.closeStatus = -1;
40
}
41
 
42
WebSocketFrame.prototype.addData = function(bufferList) {
43
    if (this.parseState === DECODE_HEADER) {
44
        if (bufferList.length >= 2) {
45
            bufferList.joinInto(this.frameHeader, 0, 0, 2);
46
            bufferList.advance(2);
47
            var firstByte = this.frameHeader[0];
48
            var secondByte = this.frameHeader[1];
49
 
50
            this.fin     = Boolean(firstByte  & 0x80);
51
            this.rsv1    = Boolean(firstByte  & 0x40);
52
            this.rsv2    = Boolean(firstByte  & 0x20);
53
            this.rsv3    = Boolean(firstByte  & 0x10);
54
            this.mask    = Boolean(secondByte & 0x80);
55
 
56
            this.opcode  = firstByte  & 0x0F;
57
            this.length = secondByte & 0x7F;
58
 
59
            // Control frame sanity check
60
            if (this.opcode >= 0x08) {
61
                if (this.length > 125) {
62
                    this.protocolError = true;
63
                    this.dropReason = 'Illegal control frame longer than 125 bytes.';
64
                    return true;
65
                }
66
                if (!this.fin) {
67
                    this.protocolError = true;
68
                    this.dropReason = 'Control frames must not be fragmented.';
69
                    return true;
70
                }
71
            }
72
 
73
            if (this.length === 126) {
74
                this.parseState = WAITING_FOR_16_BIT_LENGTH;
75
            }
76
            else if (this.length === 127) {
77
                this.parseState = WAITING_FOR_64_BIT_LENGTH;
78
            }
79
            else {
80
                this.parseState = WAITING_FOR_MASK_KEY;
81
            }
82
        }
83
    }
84
    if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {
85
        if (bufferList.length >= 2) {
86
            bufferList.joinInto(this.frameHeader, 2, 0, 2);
87
            bufferList.advance(2);
88
            this.length = this.frameHeader.readUInt16BE(2, true);
89
            this.parseState = WAITING_FOR_MASK_KEY;
90
        }
91
    }
92
    else if (this.parseState === WAITING_FOR_64_BIT_LENGTH) {
93
        if (bufferList.length >= 8) {
94
            bufferList.joinInto(this.frameHeader, 2, 0, 8);
95
            bufferList.advance(8);
96
            var lengthPair = [
97
              this.frameHeader.readUInt32BE(2, true),
98
              this.frameHeader.readUInt32BE(2+4, true)
99
            ];
100
 
101
            if (lengthPair[0] !== 0) {
102
                this.protocolError = true;
103
                this.dropReason = 'Unsupported 64-bit length frame received';
104
                return true;
105
            }
106
            this.length = lengthPair[1];
107
            this.parseState = WAITING_FOR_MASK_KEY;
108
        }
109
    }
110
 
111
    if (this.parseState === WAITING_FOR_MASK_KEY) {
112
        if (this.mask) {
113
            if (bufferList.length >= 4) {
114
                bufferList.joinInto(this.maskBytes, 0, 0, 4);
115
                bufferList.advance(4);
116
                this.parseState = WAITING_FOR_PAYLOAD;
117
            }
118
        }
119
        else {
120
            this.parseState = WAITING_FOR_PAYLOAD;
121
        }
122
    }
123
 
124
    if (this.parseState === WAITING_FOR_PAYLOAD) {
125
        if (this.length > this.maxReceivedFrameSize) {
126
            this.frameTooLarge = true;
127
            this.dropReason = 'Frame size of ' + this.length.toString(10) +
128
                              ' bytes exceeds maximum accepted frame size';
129
            return true;
130
        }
131
 
132
        if (this.length === 0) {
133
            this.binaryPayload = new Buffer(0);
134
            this.parseState = COMPLETE;
135
            return true;
136
        }
137
        if (bufferList.length >= this.length) {
138
            this.binaryPayload = bufferList.take(this.length);
139
            bufferList.advance(this.length);
140
            if (this.mask) {
141
                bufferUtil.unmask(this.binaryPayload, this.maskBytes);
142
                // xor(this.binaryPayload, this.maskBytes, 0);
143
            }
144
 
145
            if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
146
                if (this.length === 1) {
147
                    // Invalid length for a close frame.  Must be zero or at least two.
148
                    this.binaryPayload = new Buffer(0);
149
                    this.invalidCloseFrameLength = true;
150
                }
151
                if (this.length >= 2) {
152
                    this.closeStatus = this.binaryPayload.readUInt16BE(0, true);
153
                    this.binaryPayload = this.binaryPayload.slice(2);
154
                }
155
            }
156
 
157
            this.parseState = COMPLETE;
158
            return true;
159
        }
160
    }
161
    return false;
162
};
163
 
164
WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
165
    if (bufferList.length >= this.length) {
166
        bufferList.advance(this.length);
167
        this.parseState = COMPLETE;
168
        return true;
169
    }
170
    return false;
171
};
172
 
173
WebSocketFrame.prototype.toBuffer = function(nullMask) {
174
    var maskKey;
175
    var headerLength = 2;
176
    var data;
177
    var outputPos;
178
    var firstByte = 0x00;
179
    var secondByte = 0x00;
180
 
181
    if (this.fin) {
182
        firstByte |= 0x80;
183
    }
184
    if (this.rsv1) {
185
        firstByte |= 0x40;
186
    }
187
    if (this.rsv2) {
188
        firstByte |= 0x20;
189
    }
190
    if (this.rsv3) {
191
        firstByte |= 0x10;
192
    }
193
    if (this.mask) {
194
        secondByte |= 0x80;
195
    }
196
 
197
    firstByte |= (this.opcode & 0x0F);
198
 
199
    // the close frame is a special case because the close reason is
200
    // prepended to the payload data.
201
    if (this.opcode === 0x08) {
202
        this.length = 2;
203
        if (this.binaryPayload) {
204
            this.length += this.binaryPayload.length;
205
        }
206
        data = new Buffer(this.length);
207
        data.writeUInt16BE(this.closeStatus, 0, true);
208
        if (this.length > 2) {
209
            this.binaryPayload.copy(data, 2);
210
        }
211
    }
212
    else if (this.binaryPayload) {
213
        data = this.binaryPayload;
214
        this.length = data.length;
215
    }
216
    else {
217
        this.length = 0;
218
    }
219
 
220
    if (this.length <= 125) {
221
        // encode the length directly into the two-byte frame header
222
        secondByte |= (this.length & 0x7F);
223
    }
224
    else if (this.length > 125 && this.length <= 0xFFFF) {
225
        // Use 16-bit length
226
        secondByte |= 126;
227
        headerLength += 2;
228
    }
229
    else if (this.length > 0xFFFF) {
230
        // Use 64-bit length
231
        secondByte |= 127;
232
        headerLength += 8;
233
    }
234
 
235
    var output = new Buffer(this.length + headerLength + (this.mask ? 4 : 0));
236
 
237
    // write the frame header
238
    output[0] = firstByte;
239
    output[1] = secondByte;
240
 
241
    outputPos = 2;
242
 
243
    if (this.length > 125 && this.length <= 0xFFFF) {
244
        // write 16-bit length
245
        output.writeUInt16BE(this.length, outputPos, true);
246
        outputPos += 2;
247
    }
248
    else if (this.length > 0xFFFF) {
249
        // write 64-bit length
250
        output.writeUInt32BE(0x00000000, outputPos, true);
251
        output.writeUInt32BE(this.length, outputPos + 4, true);
252
        outputPos += 8;
253
    }
254
 
255
    if (this.mask) {
256
        maskKey = nullMask ? 0 : (Math.random()*0xFFFFFFFF) | 0;
257
        this.maskBytes.writeUInt32BE(maskKey, 0, true);
258
 
259
        // write the mask key
260
        this.maskBytes.copy(output, outputPos);
261
        outputPos += 4;
262
 
263
        if (data) {
264
          bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);
265
        }
266
    }
267
    else if (data) {
268
        data.copy(output, outputPos);
269
    }
270
 
271
    return output;
272
};
273
 
274
WebSocketFrame.prototype.toString = function() {
275
    return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;
276
};
277
 
278
 
279
module.exports = WebSocketFrame;