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 util = require('util');
18
var utils = require('./utils');
19
var EventEmitter = require('events').EventEmitter;
20
var WebSocketFrame = require('./WebSocketFrame');
21
var BufferList = require('../vendor/FastBufferList');
22
var Validation = require('./Validation').Validation;
23
 
24
// Connected, fully-open, ready to send and receive frames
25
const STATE_OPEN = 'open';
26
// Received a close frame from the remote peer
27
const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close';
28
// Sent close frame to remote peer.  No further data can be sent.
29
const STATE_ENDING = 'ending';
30
// Connection is fully closed.  No further data can be sent or received.
31
const STATE_CLOSED = 'closed';
32
 
33
var setImmediateImpl = ('setImmediate' in global) ?
34
                            global.setImmediate.bind(global) :
35
                            process.nextTick.bind(process);
36
 
37
var idCounter = 0;
38
 
39
function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) {
40
    this._debug = utils.BufferingLogger('websocket:connection', ++idCounter);
41
    this._debug('constructor');
42
 
43
    if (this._debug.enabled) {
44
        instrumentSocketForDebugging(this, socket);
45
    }
46
 
47
    // Superclass Constructor
48
    EventEmitter.call(this);
49
 
50
    this._pingListenerCount = 0;
51
    this.on('newListener', function(ev) {
52
        if (ev === 'ping'){
53
            this._pingListenerCount++;
54
        }
55
      }).on('removeListener', function(ev) {
56
        if (ev === 'ping') {
57
            this._pingListenerCount--;
58
        }
59
    });
60
 
61
    this.config = config;
62
    this.socket = socket;
63
    this.protocol = protocol;
64
    this.extensions = extensions;
65
    this.remoteAddress = socket.remoteAddress;
66
    this.closeReasonCode = -1;
67
    this.closeDescription = null;
68
    this.closeEventEmitted = false;
69
 
70
    // We have to mask outgoing packets if we're acting as a WebSocket client.
71
    this.maskOutgoingPackets = maskOutgoingPackets;
72
 
73
    // We re-use the same buffers for the mask and frame header for all frames
74
    // received on each connection to avoid a small memory allocation for each
75
    // frame.
76
    this.maskBytes = new Buffer(4);
77
    this.frameHeader = new Buffer(10);
78
 
79
    // the BufferList will handle the data streaming in
80
    this.bufferList = new BufferList();
81
 
82
    // Prepare for receiving first frame
83
    this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
84
    this.fragmentationSize = 0; // data received so far...
85
    this.frameQueue = [];
86
 
87
    // Various bits of connection state
88
    this.connected = true;
89
    this.state = STATE_OPEN;
90
    this.waitingForCloseResponse = false;
91
    // Received TCP FIN, socket's readable stream is finished.
92
    this.receivedEnd = false;
93
 
94
    this.closeTimeout = this.config.closeTimeout;
95
    this.assembleFragments = this.config.assembleFragments;
96
    this.maxReceivedMessageSize = this.config.maxReceivedMessageSize;
97
 
98
    this.outputBufferFull = false;
99
    this.inputPaused = false;
100
    this.receivedDataHandler = this.processReceivedData.bind(this);
101
    this._closeTimerHandler = this.handleCloseTimer.bind(this);
102
 
103
    // Disable nagle algorithm?
104
    this.socket.setNoDelay(this.config.disableNagleAlgorithm);
105
 
106
    // Make sure there is no socket inactivity timeout
107
    this.socket.setTimeout(0);
108
 
109
    if (this.config.keepalive && !this.config.useNativeKeepalive) {
110
        if (typeof(this.config.keepaliveInterval) !== 'number') {
111
            throw new Error('keepaliveInterval must be specified and numeric ' +
112
                            'if keepalive is true.');
113
        }
114
        this._keepaliveTimerHandler = this.handleKeepaliveTimer.bind(this);
115
        this.setKeepaliveTimer();
116
 
117
        if (this.config.dropConnectionOnKeepaliveTimeout) {
118
            if (typeof(this.config.keepaliveGracePeriod) !== 'number') {
119
                throw new Error('keepaliveGracePeriod  must be specified and ' +
120
                                'numeric if dropConnectionOnKeepaliveTimeout ' +
121
                                'is true.');
122
            }
123
            this._gracePeriodTimerHandler = this.handleGracePeriodTimer.bind(this);
124
        }
125
    }
126
    else if (this.config.keepalive && this.config.useNativeKeepalive) {
127
        if (!('setKeepAlive' in this.socket)) {
128
            throw new Error('Unable to use native keepalive: unsupported by ' +
129
                            'this version of Node.');
130
        }
131
        this.socket.setKeepAlive(true, this.config.keepaliveInterval);
132
    }
133
 
134
    // The HTTP Client seems to subscribe to socket error events
135
    // and re-dispatch them in such a way that doesn't make sense
136
    // for users of our client, so we want to make sure nobody
137
    // else is listening for error events on the socket besides us.
138
    this.socket.removeAllListeners('error');
139
}
140
 
141
WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
142
WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
143
WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
144
WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
145
WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value.  Undefined meaning.
146
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
147
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
148
WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
149
WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
150
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
151
WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
152
WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
153
WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire
154
 
155
WebSocketConnection.CLOSE_DESCRIPTIONS = {
156
    1000: 'Normal connection closure',
157
    1001: 'Remote peer is going away',
158
    1002: 'Protocol error',
159
    1003: 'Unprocessable input',
160
    1004: 'Reserved',
161
    1005: 'Reason not provided',
162
    1006: 'Abnormal closure, no further detail available',
163
    1007: 'Invalid data received',
164
    1008: 'Policy violation',
165
    1009: 'Message too big',
166
    1010: 'Extension requested by client is required',
167
    1011: 'Internal Server Error',
168
    1015: 'TLS Handshake Failed'
169
};
170
 
171
function validateCloseReason(code) {
172
    if (code < 1000) {
173
        // Status codes in the range 0-999 are not used
174
        return false;
175
    }
176
    if (code >= 1000 && code <= 2999) {
177
        // Codes from 1000 - 2999 are reserved for use by the protocol.  Only
178
        // a few codes are defined, all others are currently illegal.
179
        return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].indexOf(code) !== -1;
180
    }
181
    if (code >= 3000 && code <= 3999) {
182
        // Reserved for use by libraries, frameworks, and applications.
183
        // Should be registered with IANA.  Interpretation of these codes is
184
        // undefined by the WebSocket protocol.
185
        return true;
186
    }
187
    if (code >= 4000 && code <= 4999) {
188
        // Reserved for private use.  Interpretation of these codes is
189
        // undefined by the WebSocket protocol.
190
        return true;
191
    }
192
    if (code >= 5000) {
193
        return false;
194
    }
195
}
196
 
197
util.inherits(WebSocketConnection, EventEmitter);
198
 
199
WebSocketConnection.prototype._addSocketEventListeners = function() {
200
    this.socket.on('error', this.handleSocketError.bind(this));
201
    this.socket.on('end', this.handleSocketEnd.bind(this));
202
    this.socket.on('close', this.handleSocketClose.bind(this));
203
    this.socket.on('drain', this.handleSocketDrain.bind(this));
204
    this.socket.on('pause', this.handleSocketPause.bind(this));
205
    this.socket.on('resume', this.handleSocketResume.bind(this));
206
    this.socket.on('data', this.handleSocketData.bind(this));
207
};
208
 
209
// set or reset the keepalive timer when data is received.
210
WebSocketConnection.prototype.setKeepaliveTimer = function() {
211
    this._debug('setKeepaliveTimer');
212
    if (!this.config.keepalive) { return; }
213
    this.clearKeepaliveTimer();
214
    this.clearGracePeriodTimer();
215
    this._keepaliveTimeoutID = setTimeout(this._keepaliveTimerHandler, this.config.keepaliveInterval);
216
};
217
 
218
WebSocketConnection.prototype.clearKeepaliveTimer = function() {
219
    if (this._keepaliveTimeoutID) {
220
        clearTimeout(this._keepaliveTimeoutID);
221
    }
222
};
223
 
224
// No data has been received within config.keepaliveTimeout ms.
225
WebSocketConnection.prototype.handleKeepaliveTimer = function() {
226
    this._debug('handleKeepaliveTimer');
227
    this._keepaliveTimeoutID = null;
228
    this.ping();
229
 
230
    // If we are configured to drop connections if the client doesn't respond
231
    // then set the grace period timer.
232
    if (this.config.dropConnectionOnKeepaliveTimeout) {
233
        this.setGracePeriodTimer();
234
    }
235
    else {
236
        // Otherwise reset the keepalive timer to send the next ping.
237
        this.setKeepaliveTimer();
238
    }
239
};
240
 
241
WebSocketConnection.prototype.setGracePeriodTimer = function() {
242
    this._debug('setGracePeriodTimer');
243
    this.clearGracePeriodTimer();
244
    this._gracePeriodTimeoutID = setTimeout(this._gracePeriodTimerHandler, this.config.keepaliveGracePeriod);
245
};
246
 
247
WebSocketConnection.prototype.clearGracePeriodTimer = function() {
248
    if (this._gracePeriodTimeoutID) {
249
        clearTimeout(this._gracePeriodTimeoutID);
250
    }
251
};
252
 
253
WebSocketConnection.prototype.handleGracePeriodTimer = function() {
254
    this._debug('handleGracePeriodTimer');
255
    // If this is called, the client has not responded and is assumed dead.
256
    this._gracePeriodTimeoutID = null;
257
    this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL, 'Peer not responding.', true);
258
};
259
 
260
WebSocketConnection.prototype.handleSocketData = function(data) {
261
    this._debug('handleSocketData');
262
    // Reset the keepalive timer when receiving data of any kind.
263
    this.setKeepaliveTimer();
264
 
265
    // Add received data to our bufferList, which efficiently holds received
266
    // data chunks in a linked list of Buffer objects.
267
    this.bufferList.write(data);
268
 
269
    this.processReceivedData();
270
};
271
 
272
WebSocketConnection.prototype.processReceivedData = function() {
273
    this._debug('processReceivedData');
274
    // If we're not connected, we should ignore any data remaining on the buffer.
275
    if (!this.connected) { return; }
276
 
277
    // Receiving/parsing is expected to be halted when paused.
278
    if (this.inputPaused) { return; }
279
 
280
    var frame = this.currentFrame;
281
 
282
    // WebSocketFrame.prototype.addData returns true if all data necessary to
283
    // parse the frame was available.  It returns false if we are waiting for
284
    // more data to come in on the wire.
285
    if (!frame.addData(this.bufferList)) { this._debug('-- insufficient data for frame'); return; }
286
 
287
    var self = this;
288
 
289
    // Handle possible parsing errors
290
    if (frame.protocolError) {
291
        // Something bad happened.. get rid of this client.
292
        this._debug('-- protocol error');
293
        process.nextTick(function() {
294
            self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, frame.dropReason);
295
        });
296
        return;
297
    }
298
    else if (frame.frameTooLarge) {
299
        this._debug('-- frame too large');
300
        process.nextTick(function() {
301
            self.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, frame.dropReason);
302
        });
303
        return;
304
    }
305
 
306
    // For now since we don't support extensions, all RSV bits are illegal
307
    if (frame.rsv1 || frame.rsv2 || frame.rsv3) {
308
        this._debug('-- illegal rsv flag');
309
        process.nextTick(function() {
310
            self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
311
              'Unsupported usage of rsv bits without negotiated extension.');
312
        });
313
        return;
314
    }
315
 
316
    if (!this.assembleFragments) {
317
        this._debug('-- emitting frame');
318
        process.nextTick(function() { self.emit('frame', frame); });
319
    }
320
 
321
    process.nextTick(function() { self.processFrame(frame); });
322
 
323
    this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
324
 
325
    // If there's data remaining, schedule additional processing, but yield
326
    // for now so that other connections have a chance to have their data
327
    // processed.  We use setImmediate here instead of process.nextTick to
328
    // explicitly indicate that we wish for other I/O to be handled first.
329
    if (this.bufferList.length > 0) {
330
        setImmediateImpl(this.receivedDataHandler);
331
    }
332
};
333
 
334
WebSocketConnection.prototype.handleSocketError = function(error) {
335
    this._debug('handleSocketError: %j', error);
336
    this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
337
    this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code;
338
    this.connected = false;
339
    this.state = STATE_CLOSED;
340
    this.fragmentationSize = 0;
341
    if (utils.eventEmitterListenerCount(this, 'error') > 0) {
342
        this.emit('error', error);
343
    }
344
    this.socket.destroy(error);
345
    this._debug.printOutput();
346
};
347
 
348
WebSocketConnection.prototype.handleSocketEnd = function() {
349
    this._debug('handleSocketEnd: received socket end.  state = %s', this.state);
350
    this.receivedEnd = true;
351
    if (this.state === STATE_CLOSED) {
352
        // When using the TLS module, sometimes the socket will emit 'end'
353
        // after it emits 'close'.  I don't think that's correct behavior,
354
        // but we should deal with it gracefully by ignoring it.
355
        this._debug('  --- Socket \'end\' after \'close\'');
356
        return;
357
    }
358
    if (this.state !== STATE_PEER_REQUESTED_CLOSE &&
359
        this.state !== STATE_ENDING) {
360
      this._debug('  --- UNEXPECTED socket end.');
361
      this.socket.end();
362
    }
363
};
364
 
365
WebSocketConnection.prototype.handleSocketClose = function(hadError) {
366
    this._debug('handleSocketClose: received socket close');
367
    this.socketHadError = hadError;
368
    this.connected = false;
369
    this.state = STATE_CLOSED;
370
    // If closeReasonCode is still set to -1 at this point then we must
371
    // not have received a close frame!!
372
    if (this.closeReasonCode === -1) {
373
        this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
374
        this.closeDescription = 'Connection dropped by remote peer.';
375
    }
376
    this.clearCloseTimer();
377
    this.clearKeepaliveTimer();
378
    this.clearGracePeriodTimer();
379
    if (!this.closeEventEmitted) {
380
        this.closeEventEmitted = true;
381
        this._debug('-- Emitting WebSocketConnection close event');
382
        this.emit('close', this.closeReasonCode, this.closeDescription);
383
    }
384
};
385
 
386
WebSocketConnection.prototype.handleSocketDrain = function() {
387
    this._debug('handleSocketDrain: socket drain event');
388
    this.outputBufferFull = false;
389
    this.emit('drain');
390
};
391
 
392
WebSocketConnection.prototype.handleSocketPause = function() {
393
    this._debug('handleSocketPause: socket pause event');
394
    this.inputPaused = true;
395
    this.emit('pause');
396
};
397
 
398
WebSocketConnection.prototype.handleSocketResume = function() {
399
    this._debug('handleSocketResume: socket resume event');
400
    this.inputPaused = false;
401
    this.emit('resume');
402
    this.processReceivedData();
403
};
404
 
405
WebSocketConnection.prototype.pause = function() {
406
    this._debug('pause: pause requested');
407
    this.socket.pause();
408
};
409
 
410
WebSocketConnection.prototype.resume = function() {
411
    this._debug('resume: resume requested');
412
    this.socket.resume();
413
};
414
 
415
WebSocketConnection.prototype.close = function(reasonCode, description) {
416
    if (this.connected) {
417
        this._debug('close: Initating clean WebSocket close sequence.');
418
        if ('number' !== typeof reasonCode) {
419
            reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
420
        }
421
        if (!validateCloseReason(reasonCode)) {
422
            throw new Error('Close code ' + reasonCode + ' is not valid.');
423
        }
424
        if ('string' !== typeof description) {
425
            description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
426
        }
427
        this.closeReasonCode = reasonCode;
428
        this.closeDescription = description;
429
        this.setCloseTimer();
430
        this.sendCloseFrame(this.closeReasonCode, this.closeDescription);
431
        this.state = STATE_ENDING;
432
        this.connected = false;
433
    }
434
};
435
 
436
WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) {
437
    this._debug('drop');
438
    if (typeof(reasonCode) !== 'number') {
439
        reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
440
    }
441
 
442
    if (typeof(description) !== 'string') {
443
        // If no description is provided, try to look one up based on the
444
        // specified reasonCode.
445
        description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
446
    }
447
 
448
    this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s',
449
        skipCloseFrame, reasonCode, description
450
    );
451
 
452
    this.closeReasonCode = reasonCode;
453
    this.closeDescription = description;
454
    this.frameQueue = [];
455
    this.fragmentationSize = 0;
456
    if (!skipCloseFrame) {
457
        this.sendCloseFrame(reasonCode, description);
458
    }
459
    this.connected = false;
460
    this.state = STATE_CLOSED;
461
    this.clearCloseTimer();
462
    this.clearKeepaliveTimer();
463
    this.clearGracePeriodTimer();
464
 
465
    if (!this.closeEventEmitted) {
466
        this.closeEventEmitted = true;
467
        this._debug('Emitting WebSocketConnection close event');
468
        this.emit('close', this.closeReasonCode, this.closeDescription);
469
    }
470
 
471
    this._debug('Drop: destroying socket');
472
    this.socket.destroy();
473
};
474
 
475
WebSocketConnection.prototype.setCloseTimer = function() {
476
    this._debug('setCloseTimer');
477
    this.clearCloseTimer();
478
    this._debug('Setting close timer');
479
    this.waitingForCloseResponse = true;
480
    this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
481
};
482
 
483
WebSocketConnection.prototype.clearCloseTimer = function() {
484
    this._debug('clearCloseTimer');
485
    if (this.closeTimer) {
486
        this._debug('Clearing close timer');
487
        clearTimeout(this.closeTimer);
488
        this.waitingForCloseResponse = false;
489
        this.closeTimer = null;
490
    }
491
};
492
 
493
WebSocketConnection.prototype.handleCloseTimer = function() {
494
    this._debug('handleCloseTimer');
495
    this.closeTimer = null;
496
    if (this.waitingForCloseResponse) {
497
        this._debug('Close response not received from client.  Forcing socket end.');
498
        this.waitingForCloseResponse = false;
499
        this.state = STATE_CLOSED;
500
        this.socket.end();
501
    }
502
};
503
 
504
WebSocketConnection.prototype.processFrame = function(frame) {
505
    this._debug('processFrame');
506
    this._debug(' -- frame: %s', frame);
507
 
508
    // Any non-control opcode besides 0x00 (continuation) received in the
509
    // middle of a fragmented message is illegal.
510
    if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) {
511
        this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
512
          'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' +
513
          'received in middle of fragmented message.');
514
        return;
515
    }
516
 
517
    switch(frame.opcode) {
518
        case 0x02: // WebSocketFrame.BINARY_FRAME
519
            this._debug('-- Binary Frame');
520
            if (this.assembleFragments) {
521
                if (frame.fin) {
522
                    // Complete single-frame message received
523
                    this._debug('---- Emitting \'message\' event');
524
                    this.emit('message', {
525
                        type: 'binary',
526
                        binaryData: frame.binaryPayload
527
                    });
528
                }
529
                else {
530
                    // beginning of a fragmented message
531
                    this.frameQueue.push(frame);
532
                    this.fragmentationSize = frame.length;
533
                }
534
            }
535
            break;
536
        case 0x01: // WebSocketFrame.TEXT_FRAME
537
            this._debug('-- Text Frame');
538
            if (this.assembleFragments) {
539
                if (frame.fin) {
540
                    if (!Validation.isValidUTF8(frame.binaryPayload)) {
541
                        this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
542
                          'Invalid UTF-8 Data Received');
543
                        return;
544
                    }
545
                    // Complete single-frame message received
546
                    this._debug('---- Emitting \'message\' event');
547
                    this.emit('message', {
548
                        type: 'utf8',
549
                        utf8Data: frame.binaryPayload.toString('utf8')
550
                    });
551
                }
552
                else {
553
                    // beginning of a fragmented message
554
                    this.frameQueue.push(frame);
555
                    this.fragmentationSize = frame.length;
556
                }
557
            }
558
            break;
559
        case 0x00: // WebSocketFrame.CONTINUATION
560
            this._debug('-- Continuation Frame');
561
            if (this.assembleFragments) {
562
                if (this.frameQueue.length === 0) {
563
                    this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
564
                      'Unexpected Continuation Frame');
565
                    return;
566
                }
567
 
568
                this.fragmentationSize += frame.length;
569
 
570
                if (this.fragmentationSize > this.maxReceivedMessageSize) {
571
                    this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,
572
                      'Maximum message size exceeded.');
573
                    return;
574
                }
575
 
576
                this.frameQueue.push(frame);
577
 
578
                if (frame.fin) {
579
                    // end of fragmented message, so we process the whole
580
                    // message now.  We also have to decode the utf-8 data
581
                    // for text frames after combining all the fragments.
582
                    var bytesCopied = 0;
583
                    var binaryPayload = new Buffer(this.fragmentationSize);
584
                    var opcode = this.frameQueue[0].opcode;
585
                    this.frameQueue.forEach(function (currentFrame) {
586
                        currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
587
                        bytesCopied += currentFrame.binaryPayload.length;
588
                    });
589
                    this.frameQueue = [];
590
                    this.fragmentationSize = 0;
591
 
592
                    switch (opcode) {
593
                        case 0x02: // WebSocketOpcode.BINARY_FRAME
594
                            this.emit('message', {
595
                                type: 'binary',
596
                                binaryData: binaryPayload
597
                            });
598
                            break;
599
                        case 0x01: // WebSocketOpcode.TEXT_FRAME
600
                            if (!Validation.isValidUTF8(binaryPayload)) {
601
                                this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
602
                                  'Invalid UTF-8 Data Received');
603
                                return;
604
                            }
605
                            this.emit('message', {
606
                                type: 'utf8',
607
                                utf8Data: binaryPayload.toString('utf8')
608
                            });
609
                            break;
610
                        default:
611
                            this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
612
                              'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16));
613
                            return;
614
                    }
615
                }
616
            }
617
            break;
618
        case 0x09: // WebSocketFrame.PING
619
            this._debug('-- Ping Frame');
620
 
621
            if (this._pingListenerCount > 0) {
622
                // logic to emit the ping frame: this is only done when a listener is known to exist
623
                // Expose a function allowing the user to override the default ping() behavior
624
                var cancelled = false;
625
                var cancel = function() { 
626
                  cancelled = true; 
627
                };
628
                this.emit('ping', cancel, frame.binaryPayload);
629
 
630
                // Only send a pong if the client did not indicate that he would like to cancel
631
                if (!cancelled) {
632
                    this.pong(frame.binaryPayload);
633
                }
634
            }
635
            else {
636
                this.pong(frame.binaryPayload);
637
            }
638
 
639
            break;
640
        case 0x0A: // WebSocketFrame.PONG
641
            this._debug('-- Pong Frame');
642
            this.emit('pong', frame.binaryPayload);
643
            break;
644
        case 0x08: // WebSocketFrame.CONNECTION_CLOSE
645
            this._debug('-- Close Frame');
646
            if (this.waitingForCloseResponse) {
647
                // Got response to our request to close the connection.
648
                // Close is complete, so we just hang up.
649
                this._debug('---- Got close response from peer.  Completing closing handshake.');
650
                this.clearCloseTimer();
651
                this.waitingForCloseResponse = false;
652
                this.state = STATE_CLOSED;
653
                this.socket.end();
654
                return;
655
            }
656
 
657
            this._debug('---- Closing handshake initiated by peer.');
658
            // Got request from other party to close connection.
659
            // Send back acknowledgement and then hang up.
660
            this.state = STATE_PEER_REQUESTED_CLOSE;
661
            var respondCloseReasonCode;
662
 
663
            // Make sure the close reason provided is legal according to
664
            // the protocol spec.  Providing no close status is legal.
665
            // WebSocketFrame sets closeStatus to -1 by default, so if it
666
            // is still -1, then no status was provided.
667
            if (frame.invalidCloseFrameLength) {
668
                this.closeReasonCode = 1005; // 1005 = No reason provided.
669
                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
670
            }
671
            else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) {
672
                this.closeReasonCode = frame.closeStatus;
673
                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
674
            }
675
            else {
676
                this.closeReasonCode = frame.closeStatus;
677
                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
678
            }
679
 
680
            // If there is a textual description in the close frame, extract it.
681
            if (frame.binaryPayload.length > 1) {
682
                if (!Validation.isValidUTF8(frame.binaryPayload)) {
683
                    this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
684
                      'Invalid UTF-8 Data Received');
685
                    return;
686
                }
687
                this.closeDescription = frame.binaryPayload.toString('utf8');
688
            }
689
            else {
690
                this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode];
691
            }
692
            this._debug(
693
                '------ Remote peer %s - code: %d - %s - close frame payload length: %d',
694
                this.remoteAddress, this.closeReasonCode,
695
                this.closeDescription, frame.length
696
            );
697
            this._debug('------ responding to remote peer\'s close request.');
698
            this.sendCloseFrame(respondCloseReasonCode, null);
699
            this.connected = false;
700
            break;
701
        default:
702
            this._debug('-- Unrecognized Opcode %d', frame.opcode);
703
            this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
704
              'Unrecognized Opcode: 0x' + frame.opcode.toString(16));
705
            break;
706
    }
707
};
708
 
709
WebSocketConnection.prototype.send = function(data, cb) {
710
    this._debug('send');
711
    if (Buffer.isBuffer(data)) {
712
        this.sendBytes(data, cb);
713
    }
714
    else if (typeof(data['toString']) === 'function') {
715
        this.sendUTF(data, cb);
716
    }
717
    else {
718
        throw new Error('Data provided must either be a Node Buffer or implement toString()');
719
    }
720
};
721
 
722
WebSocketConnection.prototype.sendUTF = function(data, cb) {
723
    data = new Buffer(data.toString(), 'utf8');
724
    this._debug('sendUTF: %d bytes', data.length);
725
    var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
726
    frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
727
    frame.binaryPayload = data;
728
    this.fragmentAndSend(frame, cb);
729
};
730
 
731
WebSocketConnection.prototype.sendBytes = function(data, cb) {
732
    this._debug('sendBytes');
733
    if (!Buffer.isBuffer(data)) {
734
        throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()');
735
    }
736
    var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
737
    frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
738
    frame.binaryPayload = data;
739
    this.fragmentAndSend(frame, cb);
740
};
741
 
742
WebSocketConnection.prototype.ping = function(data) {
743
    this._debug('ping');
744
    var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
745
    frame.opcode = 0x09; // WebSocketOpcode.PING
746
    frame.fin = true;
747
    if (data) {
748
        if (!Buffer.isBuffer(data)) {
749
            data = new Buffer(data.toString(), 'utf8');
750
        }
751
        if (data.length > 125) {
752
            this._debug('WebSocket: Data for ping is longer than 125 bytes.  Truncating.');
753
            data = data.slice(0,124);
754
        }
755
        frame.binaryPayload = data;
756
    }
757
    this.sendFrame(frame);
758
};
759
 
760
// Pong frames have to echo back the contents of the data portion of the
761
// ping frame exactly, byte for byte.
762
WebSocketConnection.prototype.pong = function(binaryPayload) {
763
    this._debug('pong');
764
    var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
765
    frame.opcode = 0x0A; // WebSocketOpcode.PONG
766
    if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) {
767
        this._debug('WebSocket: Data for pong is longer than 125 bytes.  Truncating.');
768
        binaryPayload = binaryPayload.slice(0,124);
769
    }
770
    frame.binaryPayload = binaryPayload;
771
    frame.fin = true;
772
    this.sendFrame(frame);
773
};
774
 
775
WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) {
776
    this._debug('fragmentAndSend');
777
    if (frame.opcode > 0x07) {
778
        throw new Error('You cannot fragment control frames.');
779
    }
780
 
781
    var threshold = this.config.fragmentationThreshold;
782
    var length = frame.binaryPayload.length;
783
 
784
    // Send immediately if fragmentation is disabled or the message is not
785
    // larger than the fragmentation threshold.
786
    if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) {
787
        frame.fin = true;
788
        this.sendFrame(frame, cb);
789
        return;
790
    }
791
 
792
    var numFragments = Math.ceil(length / threshold);
793
    var sentFragments = 0;
794
    var sentCallback = function fragmentSentCallback(err) {
795
        if (err) {
796
            if (typeof cb === 'function') {
797
                // pass only the first error
798
                cb(err);
799
                cb = null;
800
            }
801
            return;
802
        }
803
        ++sentFragments;
804
        if ((sentFragments === numFragments) && (typeof cb === 'function')) {
805
            cb();
806
        }
807
    };
808
    for (var i=1; i <= numFragments; i++) {
809
        var currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
810
 
811
        // continuation opcode except for first frame.
812
        currentFrame.opcode = (i === 1) ? frame.opcode : 0x00;
813
 
814
        // fin set on last frame only
815
        currentFrame.fin = (i === numFragments);
816
 
817
        // length is likely to be shorter on the last fragment
818
        var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold;
819
        var sliceStart = threshold * (i-1);
820
 
821
        // Slice the right portion of the original payload
822
        currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength);
823
 
824
        this.sendFrame(currentFrame, sentCallback);
825
    }
826
};
827
 
828
WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) {
829
    if (typeof(reasonCode) !== 'number') {
830
        reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
831
    }
832
 
833
    this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description);
834
 
835
    if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; }
836
 
837
    var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
838
    frame.fin = true;
839
    frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
840
    frame.closeStatus = reasonCode;
841
    if (typeof(description) === 'string') {
842
        frame.binaryPayload = new Buffer(description, 'utf8');
843
    }
844
 
845
    this.sendFrame(frame, cb);
846
    this.socket.end();
847
};
848
 
849
WebSocketConnection.prototype.sendFrame = function(frame, cb) {
850
    this._debug('sendFrame');
851
    frame.mask = this.maskOutgoingPackets;
852
    var flushed = this.socket.write(frame.toBuffer(), cb);
853
    this.outputBufferFull = !flushed;
854
    return flushed;
855
};
856
 
857
module.exports = WebSocketConnection;
858
 
859
 
860
 
861
function instrumentSocketForDebugging(connection, socket) {
862
    /* jshint loopfunc: true */
863
    if (!connection._debug.enabled) { return; }
864
 
865
    var originalSocketEmit = socket.emit;
866
    socket.emit = function(event) {
867
        connection._debug('||| Socket Event  \'%s\'', event);
868
        originalSocketEmit.apply(this, arguments);
869
    };
870
 
871
    for (var key in socket) {
872
        if ('function' !== typeof(socket[key])) { continue; }
873
        if (['emit'].indexOf(key) !== -1) { continue; }
874
        (function(key) {
875
            var original = socket[key];
876
            if (key === 'on') {
877
                socket[key] = function proxyMethod__EventEmitter__On() {
878
                    connection._debug('||| Socket method called:  %s (%s)', key, arguments[0]);
879
                    return original.apply(this, arguments);
880
                };
881
                return;
882
            }
883
            socket[key] = function proxyMethod() {
884
                connection._debug('||| Socket method called:  %s', key);
885
                return original.apply(this, arguments);
886
            };
887
        })(key);
888
    }
889
}