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