X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fwebsocket%2Flib%2FWebSocketConnection.js;fp=node_modules%2Fwebsocket%2Flib%2FWebSocketConnection.js;h=0000000000000000000000000000000000000000;hp=9f2750cc785a294c63585f2c99f16fae669a9c23;hb=5e96dd57ddd883604e87f62bdddcb111c63a6e1a;hpb=acb5f682a2b75b972710cabd81658f63071324b0 diff --git a/node_modules/websocket/lib/WebSocketConnection.js b/node_modules/websocket/lib/WebSocketConnection.js deleted file mode 100644 index 9f2750c..0000000 --- a/node_modules/websocket/lib/WebSocketConnection.js +++ /dev/null @@ -1,896 +0,0 @@ -/************************************************************************ - * Copyright 2010-2015 Brian McKelvey. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ***********************************************************************/ - -var util = require('util'); -var utils = require('./utils'); -var EventEmitter = require('events').EventEmitter; -var WebSocketFrame = require('./WebSocketFrame'); -var BufferList = require('../vendor/FastBufferList'); -var Validation = require('./Validation').Validation; -var bufferAllocUnsafe = utils.bufferAllocUnsafe; -var bufferFromString = utils.bufferFromString; - -// Connected, fully-open, ready to send and receive frames -const STATE_OPEN = 'open'; -// Received a close frame from the remote peer -const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close'; -// Sent close frame to remote peer. No further data can be sent. -const STATE_ENDING = 'ending'; -// Connection is fully closed. No further data can be sent or received. -const STATE_CLOSED = 'closed'; - -var setImmediateImpl = ('setImmediate' in global) ? - global.setImmediate.bind(global) : - process.nextTick.bind(process); - -var idCounter = 0; - -function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) { - this._debug = utils.BufferingLogger('websocket:connection', ++idCounter); - this._debug('constructor'); - - if (this._debug.enabled) { - instrumentSocketForDebugging(this, socket); - } - - // Superclass Constructor - EventEmitter.call(this); - - this._pingListenerCount = 0; - this.on('newListener', function(ev) { - if (ev === 'ping'){ - this._pingListenerCount++; - } - }).on('removeListener', function(ev) { - if (ev === 'ping') { - this._pingListenerCount--; - } - }); - - this.config = config; - this.socket = socket; - this.protocol = protocol; - this.extensions = extensions; - this.remoteAddress = socket.remoteAddress; - this.closeReasonCode = -1; - this.closeDescription = null; - this.closeEventEmitted = false; - - // We have to mask outgoing packets if we're acting as a WebSocket client. - this.maskOutgoingPackets = maskOutgoingPackets; - - // We re-use the same buffers for the mask and frame header for all frames - // received on each connection to avoid a small memory allocation for each - // frame. - this.maskBytes = bufferAllocUnsafe(4); - this.frameHeader = bufferAllocUnsafe(10); - - // the BufferList will handle the data streaming in - this.bufferList = new BufferList(); - - // Prepare for receiving first frame - this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - this.fragmentationSize = 0; // data received so far... - this.frameQueue = []; - - // Various bits of connection state - this.connected = true; - this.state = STATE_OPEN; - this.waitingForCloseResponse = false; - // Received TCP FIN, socket's readable stream is finished. - this.receivedEnd = false; - - this.closeTimeout = this.config.closeTimeout; - this.assembleFragments = this.config.assembleFragments; - this.maxReceivedMessageSize = this.config.maxReceivedMessageSize; - - this.outputBufferFull = false; - this.inputPaused = false; - this.receivedDataHandler = this.processReceivedData.bind(this); - this._closeTimerHandler = this.handleCloseTimer.bind(this); - - // Disable nagle algorithm? - this.socket.setNoDelay(this.config.disableNagleAlgorithm); - - // Make sure there is no socket inactivity timeout - this.socket.setTimeout(0); - - if (this.config.keepalive && !this.config.useNativeKeepalive) { - if (typeof(this.config.keepaliveInterval) !== 'number') { - throw new Error('keepaliveInterval must be specified and numeric ' + - 'if keepalive is true.'); - } - this._keepaliveTimerHandler = this.handleKeepaliveTimer.bind(this); - this.setKeepaliveTimer(); - - if (this.config.dropConnectionOnKeepaliveTimeout) { - if (typeof(this.config.keepaliveGracePeriod) !== 'number') { - throw new Error('keepaliveGracePeriod must be specified and ' + - 'numeric if dropConnectionOnKeepaliveTimeout ' + - 'is true.'); - } - this._gracePeriodTimerHandler = this.handleGracePeriodTimer.bind(this); - } - } - else if (this.config.keepalive && this.config.useNativeKeepalive) { - if (!('setKeepAlive' in this.socket)) { - throw new Error('Unable to use native keepalive: unsupported by ' + - 'this version of Node.'); - } - this.socket.setKeepAlive(true, this.config.keepaliveInterval); - } - - // The HTTP Client seems to subscribe to socket error events - // and re-dispatch them in such a way that doesn't make sense - // for users of our client, so we want to make sure nobody - // else is listening for error events on the socket besides us. - this.socket.removeAllListeners('error'); -} - -WebSocketConnection.CLOSE_REASON_NORMAL = 1000; -WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001; -WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002; -WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003; -WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning. -WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire -WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire -WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007; -WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008; -WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009; -WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010; -WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011; -WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire - -WebSocketConnection.CLOSE_DESCRIPTIONS = { - 1000: 'Normal connection closure', - 1001: 'Remote peer is going away', - 1002: 'Protocol error', - 1003: 'Unprocessable input', - 1004: 'Reserved', - 1005: 'Reason not provided', - 1006: 'Abnormal closure, no further detail available', - 1007: 'Invalid data received', - 1008: 'Policy violation', - 1009: 'Message too big', - 1010: 'Extension requested by client is required', - 1011: 'Internal Server Error', - 1015: 'TLS Handshake Failed' -}; - -function validateCloseReason(code) { - if (code < 1000) { - // Status codes in the range 0-999 are not used - return false; - } - if (code >= 1000 && code <= 2999) { - // Codes from 1000 - 2999 are reserved for use by the protocol. Only - // a few codes are defined, all others are currently illegal. - return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1; - } - if (code >= 3000 && code <= 3999) { - // Reserved for use by libraries, frameworks, and applications. - // Should be registered with IANA. Interpretation of these codes is - // undefined by the WebSocket protocol. - return true; - } - if (code >= 4000 && code <= 4999) { - // Reserved for private use. Interpretation of these codes is - // undefined by the WebSocket protocol. - return true; - } - if (code >= 5000) { - return false; - } -} - -util.inherits(WebSocketConnection, EventEmitter); - -WebSocketConnection.prototype._addSocketEventListeners = function() { - this.socket.on('error', this.handleSocketError.bind(this)); - this.socket.on('end', this.handleSocketEnd.bind(this)); - this.socket.on('close', this.handleSocketClose.bind(this)); - this.socket.on('drain', this.handleSocketDrain.bind(this)); - this.socket.on('pause', this.handleSocketPause.bind(this)); - this.socket.on('resume', this.handleSocketResume.bind(this)); - this.socket.on('data', this.handleSocketData.bind(this)); -}; - -// set or reset the keepalive timer when data is received. -WebSocketConnection.prototype.setKeepaliveTimer = function() { - this._debug('setKeepaliveTimer'); - if (!this.config.keepalive || this.config.useNativeKeepalive) { return; } - this.clearKeepaliveTimer(); - this.clearGracePeriodTimer(); - this._keepaliveTimeoutID = setTimeout(this._keepaliveTimerHandler, this.config.keepaliveInterval); -}; - -WebSocketConnection.prototype.clearKeepaliveTimer = function() { - if (this._keepaliveTimeoutID) { - clearTimeout(this._keepaliveTimeoutID); - } -}; - -// No data has been received within config.keepaliveTimeout ms. -WebSocketConnection.prototype.handleKeepaliveTimer = function() { - this._debug('handleKeepaliveTimer'); - this._keepaliveTimeoutID = null; - this.ping(); - - // If we are configured to drop connections if the client doesn't respond - // then set the grace period timer. - if (this.config.dropConnectionOnKeepaliveTimeout) { - this.setGracePeriodTimer(); - } - else { - // Otherwise reset the keepalive timer to send the next ping. - this.setKeepaliveTimer(); - } -}; - -WebSocketConnection.prototype.setGracePeriodTimer = function() { - this._debug('setGracePeriodTimer'); - this.clearGracePeriodTimer(); - this._gracePeriodTimeoutID = setTimeout(this._gracePeriodTimerHandler, this.config.keepaliveGracePeriod); -}; - -WebSocketConnection.prototype.clearGracePeriodTimer = function() { - if (this._gracePeriodTimeoutID) { - clearTimeout(this._gracePeriodTimeoutID); - } -}; - -WebSocketConnection.prototype.handleGracePeriodTimer = function() { - this._debug('handleGracePeriodTimer'); - // If this is called, the client has not responded and is assumed dead. - this._gracePeriodTimeoutID = null; - this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL, 'Peer not responding.', true); -}; - -WebSocketConnection.prototype.handleSocketData = function(data) { - this._debug('handleSocketData'); - // Reset the keepalive timer when receiving data of any kind. - this.setKeepaliveTimer(); - - // Add received data to our bufferList, which efficiently holds received - // data chunks in a linked list of Buffer objects. - this.bufferList.write(data); - - this.processReceivedData(); -}; - -WebSocketConnection.prototype.processReceivedData = function() { - this._debug('processReceivedData'); - // If we're not connected, we should ignore any data remaining on the buffer. - if (!this.connected) { return; } - - // Receiving/parsing is expected to be halted when paused. - if (this.inputPaused) { return; } - - var frame = this.currentFrame; - - // WebSocketFrame.prototype.addData returns true if all data necessary to - // parse the frame was available. It returns false if we are waiting for - // more data to come in on the wire. - if (!frame.addData(this.bufferList)) { this._debug('-- insufficient data for frame'); return; } - - var self = this; - - // Handle possible parsing errors - if (frame.protocolError) { - // Something bad happened.. get rid of this client. - this._debug('-- protocol error'); - process.nextTick(function() { - self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, frame.dropReason); - }); - return; - } - else if (frame.frameTooLarge) { - this._debug('-- frame too large'); - process.nextTick(function() { - self.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, frame.dropReason); - }); - return; - } - - // For now since we don't support extensions, all RSV bits are illegal - if (frame.rsv1 || frame.rsv2 || frame.rsv3) { - this._debug('-- illegal rsv flag'); - process.nextTick(function() { - self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, - 'Unsupported usage of rsv bits without negotiated extension.'); - }); - return; - } - - if (!this.assembleFragments) { - this._debug('-- emitting frame'); - process.nextTick(function() { self.emit('frame', frame); }); - } - - process.nextTick(function() { self.processFrame(frame); }); - - this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - - // If there's data remaining, schedule additional processing, but yield - // for now so that other connections have a chance to have their data - // processed. We use setImmediate here instead of process.nextTick to - // explicitly indicate that we wish for other I/O to be handled first. - if (this.bufferList.length > 0) { - setImmediateImpl(this.receivedDataHandler); - } -}; - -WebSocketConnection.prototype.handleSocketError = function(error) { - this._debug('handleSocketError: %j', error); - if (this.state === STATE_CLOSED) { - // See https://github.com/theturtle32/WebSocket-Node/issues/288 - this._debug(' --- Socket \'error\' after \'close\''); - return; - } - this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; - this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code; - this.connected = false; - this.state = STATE_CLOSED; - this.fragmentationSize = 0; - if (utils.eventEmitterListenerCount(this, 'error') > 0) { - this.emit('error', error); - } - this.socket.destroy(error); - this._debug.printOutput(); -}; - -WebSocketConnection.prototype.handleSocketEnd = function() { - this._debug('handleSocketEnd: received socket end. state = %s', this.state); - this.receivedEnd = true; - if (this.state === STATE_CLOSED) { - // When using the TLS module, sometimes the socket will emit 'end' - // after it emits 'close'. I don't think that's correct behavior, - // but we should deal with it gracefully by ignoring it. - this._debug(' --- Socket \'end\' after \'close\''); - return; - } - if (this.state !== STATE_PEER_REQUESTED_CLOSE && - this.state !== STATE_ENDING) { - this._debug(' --- UNEXPECTED socket end.'); - this.socket.end(); - } -}; - -WebSocketConnection.prototype.handleSocketClose = function(hadError) { - this._debug('handleSocketClose: received socket close'); - this.socketHadError = hadError; - this.connected = false; - this.state = STATE_CLOSED; - // If closeReasonCode is still set to -1 at this point then we must - // not have received a close frame!! - if (this.closeReasonCode === -1) { - this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; - this.closeDescription = 'Connection dropped by remote peer.'; - } - this.clearCloseTimer(); - this.clearKeepaliveTimer(); - this.clearGracePeriodTimer(); - if (!this.closeEventEmitted) { - this.closeEventEmitted = true; - this._debug('-- Emitting WebSocketConnection close event'); - this.emit('close', this.closeReasonCode, this.closeDescription); - } -}; - -WebSocketConnection.prototype.handleSocketDrain = function() { - this._debug('handleSocketDrain: socket drain event'); - this.outputBufferFull = false; - this.emit('drain'); -}; - -WebSocketConnection.prototype.handleSocketPause = function() { - this._debug('handleSocketPause: socket pause event'); - this.inputPaused = true; - this.emit('pause'); -}; - -WebSocketConnection.prototype.handleSocketResume = function() { - this._debug('handleSocketResume: socket resume event'); - this.inputPaused = false; - this.emit('resume'); - this.processReceivedData(); -}; - -WebSocketConnection.prototype.pause = function() { - this._debug('pause: pause requested'); - this.socket.pause(); -}; - -WebSocketConnection.prototype.resume = function() { - this._debug('resume: resume requested'); - this.socket.resume(); -}; - -WebSocketConnection.prototype.close = function(reasonCode, description) { - if (this.connected) { - this._debug('close: Initating clean WebSocket close sequence.'); - if ('number' !== typeof reasonCode) { - reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; - } - if (!validateCloseReason(reasonCode)) { - throw new Error('Close code ' + reasonCode + ' is not valid.'); - } - if ('string' !== typeof description) { - description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; - } - this.closeReasonCode = reasonCode; - this.closeDescription = description; - this.setCloseTimer(); - this.sendCloseFrame(this.closeReasonCode, this.closeDescription); - this.state = STATE_ENDING; - this.connected = false; - } -}; - -WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) { - this._debug('drop'); - if (typeof(reasonCode) !== 'number') { - reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; - } - - if (typeof(description) !== 'string') { - // If no description is provided, try to look one up based on the - // specified reasonCode. - description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; - } - - this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s', - skipCloseFrame, reasonCode, description - ); - - this.closeReasonCode = reasonCode; - this.closeDescription = description; - this.frameQueue = []; - this.fragmentationSize = 0; - if (!skipCloseFrame) { - this.sendCloseFrame(reasonCode, description); - } - this.connected = false; - this.state = STATE_CLOSED; - this.clearCloseTimer(); - this.clearKeepaliveTimer(); - this.clearGracePeriodTimer(); - - if (!this.closeEventEmitted) { - this.closeEventEmitted = true; - this._debug('Emitting WebSocketConnection close event'); - this.emit('close', this.closeReasonCode, this.closeDescription); - } - - this._debug('Drop: destroying socket'); - this.socket.destroy(); -}; - -WebSocketConnection.prototype.setCloseTimer = function() { - this._debug('setCloseTimer'); - this.clearCloseTimer(); - this._debug('Setting close timer'); - this.waitingForCloseResponse = true; - this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout); -}; - -WebSocketConnection.prototype.clearCloseTimer = function() { - this._debug('clearCloseTimer'); - if (this.closeTimer) { - this._debug('Clearing close timer'); - clearTimeout(this.closeTimer); - this.waitingForCloseResponse = false; - this.closeTimer = null; - } -}; - -WebSocketConnection.prototype.handleCloseTimer = function() { - this._debug('handleCloseTimer'); - this.closeTimer = null; - if (this.waitingForCloseResponse) { - this._debug('Close response not received from client. Forcing socket end.'); - this.waitingForCloseResponse = false; - this.state = STATE_CLOSED; - this.socket.end(); - } -}; - -WebSocketConnection.prototype.processFrame = function(frame) { - this._debug('processFrame'); - this._debug(' -- frame: %s', frame); - - // Any non-control opcode besides 0x00 (continuation) received in the - // middle of a fragmented message is illegal. - if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) { - this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, - 'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' + - 'received in middle of fragmented message.'); - return; - } - - switch(frame.opcode) { - case 0x02: // WebSocketFrame.BINARY_FRAME - this._debug('-- Binary Frame'); - if (this.assembleFragments) { - if (frame.fin) { - // Complete single-frame message received - this._debug('---- Emitting \'message\' event'); - this.emit('message', { - type: 'binary', - binaryData: frame.binaryPayload - }); - } - else { - // beginning of a fragmented message - this.frameQueue.push(frame); - this.fragmentationSize = frame.length; - } - } - break; - case 0x01: // WebSocketFrame.TEXT_FRAME - this._debug('-- Text Frame'); - if (this.assembleFragments) { - if (frame.fin) { - if (!Validation.isValidUTF8(frame.binaryPayload)) { - this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA, - 'Invalid UTF-8 Data Received'); - return; - } - // Complete single-frame message received - this._debug('---- Emitting \'message\' event'); - this.emit('message', { - type: 'utf8', - utf8Data: frame.binaryPayload.toString('utf8') - }); - } - else { - // beginning of a fragmented message - this.frameQueue.push(frame); - this.fragmentationSize = frame.length; - } - } - break; - case 0x00: // WebSocketFrame.CONTINUATION - this._debug('-- Continuation Frame'); - if (this.assembleFragments) { - if (this.frameQueue.length === 0) { - this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, - 'Unexpected Continuation Frame'); - return; - } - - this.fragmentationSize += frame.length; - - if (this.fragmentationSize > this.maxReceivedMessageSize) { - this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, - 'Maximum message size exceeded.'); - return; - } - - this.frameQueue.push(frame); - - if (frame.fin) { - // end of fragmented message, so we process the whole - // message now. We also have to decode the utf-8 data - // for text frames after combining all the fragments. - var bytesCopied = 0; - var binaryPayload = bufferAllocUnsafe(this.fragmentationSize); - var opcode = this.frameQueue[0].opcode; - this.frameQueue.forEach(function (currentFrame) { - currentFrame.binaryPayload.copy(binaryPayload, bytesCopied); - bytesCopied += currentFrame.binaryPayload.length; - }); - this.frameQueue = []; - this.fragmentationSize = 0; - - switch (opcode) { - case 0x02: // WebSocketOpcode.BINARY_FRAME - this.emit('message', { - type: 'binary', - binaryData: binaryPayload - }); - break; - case 0x01: // WebSocketOpcode.TEXT_FRAME - if (!Validation.isValidUTF8(binaryPayload)) { - this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA, - 'Invalid UTF-8 Data Received'); - return; - } - this.emit('message', { - type: 'utf8', - utf8Data: binaryPayload.toString('utf8') - }); - break; - default: - this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, - 'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16)); - return; - } - } - } - break; - case 0x09: // WebSocketFrame.PING - this._debug('-- Ping Frame'); - - if (this._pingListenerCount > 0) { - // logic to emit the ping frame: this is only done when a listener is known to exist - // Expose a function allowing the user to override the default ping() behavior - var cancelled = false; - var cancel = function() { - cancelled = true; - }; - this.emit('ping', cancel, frame.binaryPayload); - - // Only send a pong if the client did not indicate that he would like to cancel - if (!cancelled) { - this.pong(frame.binaryPayload); - } - } - else { - this.pong(frame.binaryPayload); - } - - break; - case 0x0A: // WebSocketFrame.PONG - this._debug('-- Pong Frame'); - this.emit('pong', frame.binaryPayload); - break; - case 0x08: // WebSocketFrame.CONNECTION_CLOSE - this._debug('-- Close Frame'); - if (this.waitingForCloseResponse) { - // Got response to our request to close the connection. - // Close is complete, so we just hang up. - this._debug('---- Got close response from peer. Completing closing handshake.'); - this.clearCloseTimer(); - this.waitingForCloseResponse = false; - this.state = STATE_CLOSED; - this.socket.end(); - return; - } - - this._debug('---- Closing handshake initiated by peer.'); - // Got request from other party to close connection. - // Send back acknowledgement and then hang up. - this.state = STATE_PEER_REQUESTED_CLOSE; - var respondCloseReasonCode; - - // Make sure the close reason provided is legal according to - // the protocol spec. Providing no close status is legal. - // WebSocketFrame sets closeStatus to -1 by default, so if it - // is still -1, then no status was provided. - if (frame.invalidCloseFrameLength) { - this.closeReasonCode = 1005; // 1005 = No reason provided. - respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; - } - else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) { - this.closeReasonCode = frame.closeStatus; - respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; - } - else { - this.closeReasonCode = frame.closeStatus; - respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; - } - - // If there is a textual description in the close frame, extract it. - if (frame.binaryPayload.length > 1) { - if (!Validation.isValidUTF8(frame.binaryPayload)) { - this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA, - 'Invalid UTF-8 Data Received'); - return; - } - this.closeDescription = frame.binaryPayload.toString('utf8'); - } - else { - this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode]; - } - this._debug( - '------ Remote peer %s - code: %d - %s - close frame payload length: %d', - this.remoteAddress, this.closeReasonCode, - this.closeDescription, frame.length - ); - this._debug('------ responding to remote peer\'s close request.'); - this.sendCloseFrame(respondCloseReasonCode, null); - this.connected = false; - break; - default: - this._debug('-- Unrecognized Opcode %d', frame.opcode); - this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, - 'Unrecognized Opcode: 0x' + frame.opcode.toString(16)); - break; - } -}; - -WebSocketConnection.prototype.send = function(data, cb) { - this._debug('send'); - if (Buffer.isBuffer(data)) { - this.sendBytes(data, cb); - } - else if (typeof(data['toString']) === 'function') { - this.sendUTF(data, cb); - } - else { - throw new Error('Data provided must either be a Node Buffer or implement toString()'); - } -}; - -WebSocketConnection.prototype.sendUTF = function(data, cb) { - data = bufferFromString(data.toString(), 'utf8'); - this._debug('sendUTF: %d bytes', data.length); - var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME - frame.binaryPayload = data; - this.fragmentAndSend(frame, cb); -}; - -WebSocketConnection.prototype.sendBytes = function(data, cb) { - this._debug('sendBytes'); - if (!Buffer.isBuffer(data)) { - throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()'); - } - var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME - frame.binaryPayload = data; - this.fragmentAndSend(frame, cb); -}; - -WebSocketConnection.prototype.ping = function(data) { - this._debug('ping'); - var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - frame.opcode = 0x09; // WebSocketOpcode.PING - frame.fin = true; - if (data) { - if (!Buffer.isBuffer(data)) { - data = bufferFromString(data.toString(), 'utf8'); - } - if (data.length > 125) { - this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.'); - data = data.slice(0,124); - } - frame.binaryPayload = data; - } - this.sendFrame(frame); -}; - -// Pong frames have to echo back the contents of the data portion of the -// ping frame exactly, byte for byte. -WebSocketConnection.prototype.pong = function(binaryPayload) { - this._debug('pong'); - var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - frame.opcode = 0x0A; // WebSocketOpcode.PONG - if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) { - this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.'); - binaryPayload = binaryPayload.slice(0,124); - } - frame.binaryPayload = binaryPayload; - frame.fin = true; - this.sendFrame(frame); -}; - -WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) { - this._debug('fragmentAndSend'); - if (frame.opcode > 0x07) { - throw new Error('You cannot fragment control frames.'); - } - - var threshold = this.config.fragmentationThreshold; - var length = frame.binaryPayload.length; - - // Send immediately if fragmentation is disabled or the message is not - // larger than the fragmentation threshold. - if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) { - frame.fin = true; - this.sendFrame(frame, cb); - return; - } - - var numFragments = Math.ceil(length / threshold); - var sentFragments = 0; - var sentCallback = function fragmentSentCallback(err) { - if (err) { - if (typeof cb === 'function') { - // pass only the first error - cb(err); - cb = null; - } - return; - } - ++sentFragments; - if ((sentFragments === numFragments) && (typeof cb === 'function')) { - cb(); - } - }; - for (var i=1; i <= numFragments; i++) { - var currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - - // continuation opcode except for first frame. - currentFrame.opcode = (i === 1) ? frame.opcode : 0x00; - - // fin set on last frame only - currentFrame.fin = (i === numFragments); - - // length is likely to be shorter on the last fragment - var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold; - var sliceStart = threshold * (i-1); - - // Slice the right portion of the original payload - currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength); - - this.sendFrame(currentFrame, sentCallback); - } -}; - -WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) { - if (typeof(reasonCode) !== 'number') { - reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; - } - - this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description); - - if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; } - - var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); - frame.fin = true; - frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE - frame.closeStatus = reasonCode; - if (typeof(description) === 'string') { - frame.binaryPayload = bufferFromString(description, 'utf8'); - } - - this.sendFrame(frame, cb); - this.socket.end(); -}; - -WebSocketConnection.prototype.sendFrame = function(frame, cb) { - this._debug('sendFrame'); - frame.mask = this.maskOutgoingPackets; - var flushed = this.socket.write(frame.toBuffer(), cb); - this.outputBufferFull = !flushed; - return flushed; -}; - -module.exports = WebSocketConnection; - - - -function instrumentSocketForDebugging(connection, socket) { - /* jshint loopfunc: true */ - if (!connection._debug.enabled) { return; } - - var originalSocketEmit = socket.emit; - socket.emit = function(event) { - connection._debug('||| Socket Event \'%s\'', event); - originalSocketEmit.apply(this, arguments); - }; - - for (var key in socket) { - if ('function' !== typeof(socket[key])) { continue; } - if (['emit'].indexOf(key) !== -1) { continue; } - (function(key) { - var original = socket[key]; - if (key === 'on') { - socket[key] = function proxyMethod__EventEmitter__On() { - connection._debug('||| Socket method called: %s (%s)', key, arguments[0]); - return original.apply(this, arguments); - }; - return; - } - socket[key] = function proxyMethod() { - connection._debug('||| Socket method called: %s', key); - return original.apply(this, arguments); - }; - })(key); - } -}