xref: /unit/src/nodejs/unit-http/websocket_connection.js (revision 2617:18a10bb7346d)
11132Smax.romanov@nginx.com/************************************************************************
21132Smax.romanov@nginx.com *  Copyright 2010-2015 Brian McKelvey.
31132Smax.romanov@nginx.com *
41132Smax.romanov@nginx.com *  Licensed under the Apache License, Version 2.0 (the "License");
51132Smax.romanov@nginx.com *  you may not use this file except in compliance with the License.
61132Smax.romanov@nginx.com *  You may obtain a copy of the License at
71132Smax.romanov@nginx.com *
81132Smax.romanov@nginx.com *      http://www.apache.org/licenses/LICENSE-2.0
91132Smax.romanov@nginx.com *
101132Smax.romanov@nginx.com *  Unless required by applicable law or agreed to in writing, software
111132Smax.romanov@nginx.com *  distributed under the License is distributed on an "AS IS" BASIS,
121132Smax.romanov@nginx.com *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131132Smax.romanov@nginx.com *  See the License for the specific language governing permissions and
141132Smax.romanov@nginx.com *  limitations under the License.
151132Smax.romanov@nginx.com ***********************************************************************/
161132Smax.romanov@nginx.com
171132Smax.romanov@nginx.comvar util = require('util');
181132Smax.romanov@nginx.comvar utils = require('./utils');
191132Smax.romanov@nginx.comvar unit_lib = require('./build/Release/unit-http');
201132Smax.romanov@nginx.comvar EventEmitter = require('events').EventEmitter;
211132Smax.romanov@nginx.comvar WebSocketFrame = require('./websocket_frame');
221132Smax.romanov@nginx.comvar bufferAllocUnsafe = utils.bufferAllocUnsafe;
231132Smax.romanov@nginx.comvar bufferFromString = utils.bufferFromString;
241132Smax.romanov@nginx.com
251132Smax.romanov@nginx.com// Connected, fully-open, ready to send and receive frames
261132Smax.romanov@nginx.comconst STATE_OPEN = 'open';
271132Smax.romanov@nginx.com// Received a close frame from the remote peer
281132Smax.romanov@nginx.comconst STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close';
291132Smax.romanov@nginx.com// Sent close frame to remote peer.  No further data can be sent.
301132Smax.romanov@nginx.comconst STATE_ENDING = 'ending';
311132Smax.romanov@nginx.com// Connection is fully closed.  No further data can be sent or received.
321132Smax.romanov@nginx.comconst STATE_CLOSED = 'closed';
331132Smax.romanov@nginx.com
341132Smax.romanov@nginx.comvar idCounter = 0;
351132Smax.romanov@nginx.com
361132Smax.romanov@nginx.comfunction WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) {
371132Smax.romanov@nginx.com    this._debug = utils.BufferingLogger('websocket:connection', ++idCounter);
381132Smax.romanov@nginx.com    this._debug('constructor');
39*2617Szelenkov@nginx.com
401132Smax.romanov@nginx.com    if (this._debug.enabled) {
411132Smax.romanov@nginx.com        instrumentSocketForDebugging(this, socket);
421132Smax.romanov@nginx.com    }
43*2617Szelenkov@nginx.com
441132Smax.romanov@nginx.com    // Superclass Constructor
451132Smax.romanov@nginx.com    EventEmitter.call(this);
461132Smax.romanov@nginx.com
471132Smax.romanov@nginx.com    this._pingListenerCount = 0;
481132Smax.romanov@nginx.com    this.on('newListener', function(ev) {
491132Smax.romanov@nginx.com        if (ev === 'ping'){
501132Smax.romanov@nginx.com            this._pingListenerCount++;
511132Smax.romanov@nginx.com        }
521132Smax.romanov@nginx.com      }).on('removeListener', function(ev) {
531132Smax.romanov@nginx.com        if (ev === 'ping') {
541132Smax.romanov@nginx.com            this._pingListenerCount--;
551132Smax.romanov@nginx.com        }
561132Smax.romanov@nginx.com    });
571132Smax.romanov@nginx.com
581132Smax.romanov@nginx.com    this.config = config;
591132Smax.romanov@nginx.com    this.socket = socket;
601132Smax.romanov@nginx.com    this.protocol = protocol;
611132Smax.romanov@nginx.com    this.extensions = extensions;
621132Smax.romanov@nginx.com    this.remoteAddress = socket.remoteAddress;
631132Smax.romanov@nginx.com    this.closeReasonCode = -1;
641132Smax.romanov@nginx.com    this.closeDescription = null;
651132Smax.romanov@nginx.com    this.closeEventEmitted = false;
661132Smax.romanov@nginx.com
671132Smax.romanov@nginx.com    // We have to mask outgoing packets if we're acting as a WebSocket client.
681132Smax.romanov@nginx.com    this.maskOutgoingPackets = maskOutgoingPackets;
691132Smax.romanov@nginx.com
701132Smax.romanov@nginx.com    this.fragmentationSize = 0; // data received so far...
711132Smax.romanov@nginx.com    this.frameQueue = [];
721132Smax.romanov@nginx.com
731132Smax.romanov@nginx.com    // Various bits of connection state
741132Smax.romanov@nginx.com    this.connected = true;
751132Smax.romanov@nginx.com    this.state = STATE_OPEN;
761132Smax.romanov@nginx.com    this.waitingForCloseResponse = false;
771132Smax.romanov@nginx.com    // Received TCP FIN, socket's readable stream is finished.
781132Smax.romanov@nginx.com    this.receivedEnd = false;
791132Smax.romanov@nginx.com
801132Smax.romanov@nginx.com    this.closeTimeout = this.config.closeTimeout;
811132Smax.romanov@nginx.com    this.assembleFragments = this.config.assembleFragments;
821132Smax.romanov@nginx.com    this.maxReceivedMessageSize = this.config.maxReceivedMessageSize;
831132Smax.romanov@nginx.com
841132Smax.romanov@nginx.com    this.outputBufferFull = false;
851132Smax.romanov@nginx.com    this.inputPaused = false;
861132Smax.romanov@nginx.com    this._closeTimerHandler = this.handleCloseTimer.bind(this);
871132Smax.romanov@nginx.com
881132Smax.romanov@nginx.com    // Disable nagle algorithm?
891132Smax.romanov@nginx.com    this.socket.setNoDelay(this.config.disableNagleAlgorithm);
901132Smax.romanov@nginx.com
911132Smax.romanov@nginx.com    // Make sure there is no socket inactivity timeout
921132Smax.romanov@nginx.com    this.socket.setTimeout(0);
931132Smax.romanov@nginx.com
941132Smax.romanov@nginx.com    // The HTTP Client seems to subscribe to socket error events
951132Smax.romanov@nginx.com    // and re-dispatch them in such a way that doesn't make sense
961132Smax.romanov@nginx.com    // for users of our client, so we want to make sure nobody
971132Smax.romanov@nginx.com    // else is listening for error events on the socket besides us.
981132Smax.romanov@nginx.com    this.socket.removeAllListeners('error');
991132Smax.romanov@nginx.com
1001132Smax.romanov@nginx.com    this._set_sock(this.socket);
1011132Smax.romanov@nginx.com}
1021132Smax.romanov@nginx.com
1031132Smax.romanov@nginx.comWebSocketConnection.prototype._set_sock = unit_lib.websocket_set_sock;
1041132Smax.romanov@nginx.comWebSocketConnection.prototype._end = unit_lib.response_end;
1051132Smax.romanov@nginx.com
1061132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_NORMAL = 1000;
1071132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
1081132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
1091132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
1101132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value.  Undefined meaning.
1111132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
1121132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
1131132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
1141132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
1151132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
1161132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
1171132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
1181132Smax.romanov@nginx.comWebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire
1191132Smax.romanov@nginx.com
1201132Smax.romanov@nginx.comWebSocketConnection.CLOSE_DESCRIPTIONS = {
1211132Smax.romanov@nginx.com    1000: 'Normal connection closure',
1221132Smax.romanov@nginx.com    1001: 'Remote peer is going away',
1231132Smax.romanov@nginx.com    1002: 'Protocol error',
1241132Smax.romanov@nginx.com    1003: 'Unprocessable input',
1251132Smax.romanov@nginx.com    1004: 'Reserved',
1261132Smax.romanov@nginx.com    1005: 'Reason not provided',
1271132Smax.romanov@nginx.com    1006: 'Abnormal closure, no further detail available',
1281132Smax.romanov@nginx.com    1007: 'Invalid data received',
1291132Smax.romanov@nginx.com    1008: 'Policy violation',
1301132Smax.romanov@nginx.com    1009: 'Message too big',
1311132Smax.romanov@nginx.com    1010: 'Extension requested by client is required',
1321132Smax.romanov@nginx.com    1011: 'Internal Server Error',
1331132Smax.romanov@nginx.com    1015: 'TLS Handshake Failed'
1341132Smax.romanov@nginx.com};
1351132Smax.romanov@nginx.com
1361132Smax.romanov@nginx.comfunction validateCloseReason(code) {
1371132Smax.romanov@nginx.com    if (code < 1000) {
1381132Smax.romanov@nginx.com        // Status codes in the range 0-999 are not used
1391132Smax.romanov@nginx.com        return false;
1401132Smax.romanov@nginx.com    }
1411132Smax.romanov@nginx.com    if (code >= 1000 && code <= 2999) {
1421132Smax.romanov@nginx.com        // Codes from 1000 - 2999 are reserved for use by the protocol.  Only
1431132Smax.romanov@nginx.com        // a few codes are defined, all others are currently illegal.
1441132Smax.romanov@nginx.com        return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1;
1451132Smax.romanov@nginx.com    }
1461132Smax.romanov@nginx.com    if (code >= 3000 && code <= 3999) {
1471132Smax.romanov@nginx.com        // Reserved for use by libraries, frameworks, and applications.
1481132Smax.romanov@nginx.com        // Should be registered with IANA.  Interpretation of these codes is
1491132Smax.romanov@nginx.com        // undefined by the WebSocket protocol.
1501132Smax.romanov@nginx.com        return true;
1511132Smax.romanov@nginx.com    }
1521132Smax.romanov@nginx.com    if (code >= 4000 && code <= 4999) {
1531132Smax.romanov@nginx.com        // Reserved for private use.  Interpretation of these codes is
1541132Smax.romanov@nginx.com        // undefined by the WebSocket protocol.
1551132Smax.romanov@nginx.com        return true;
1561132Smax.romanov@nginx.com    }
1571132Smax.romanov@nginx.com    if (code >= 5000) {
1581132Smax.romanov@nginx.com        return false;
1591132Smax.romanov@nginx.com    }
1601132Smax.romanov@nginx.com}
1611132Smax.romanov@nginx.com
1621132Smax.romanov@nginx.comutil.inherits(WebSocketConnection, EventEmitter);
1631132Smax.romanov@nginx.com
1641132Smax.romanov@nginx.comWebSocketConnection.prototype._addSocketEventListeners = function() {
1651132Smax.romanov@nginx.com    this.socket.on('error', this.handleSocketError.bind(this));
1661132Smax.romanov@nginx.com    this.socket.on('end', this.handleSocketEnd.bind(this));
1671132Smax.romanov@nginx.com    this.socket.on('close', this.handleSocketClose.bind(this));
1681132Smax.romanov@nginx.com};
1691132Smax.romanov@nginx.com
1701132Smax.romanov@nginx.comWebSocketConnection.prototype.handleSocketError = function(error) {
1711132Smax.romanov@nginx.com    this._debug('handleSocketError: %j', error);
1721132Smax.romanov@nginx.com    if (this.state === STATE_CLOSED) {
1731132Smax.romanov@nginx.com        // See https://github.com/theturtle32/WebSocket-Node/issues/288
1741132Smax.romanov@nginx.com        this._debug('  --- Socket \'error\' after \'close\'');
1751132Smax.romanov@nginx.com        return;
1761132Smax.romanov@nginx.com    }
1771132Smax.romanov@nginx.com    this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
1781132Smax.romanov@nginx.com    this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code;
1791132Smax.romanov@nginx.com    this.connected = false;
1801132Smax.romanov@nginx.com    this.state = STATE_CLOSED;
1811132Smax.romanov@nginx.com    this.fragmentationSize = 0;
1821132Smax.romanov@nginx.com    if (utils.eventEmitterListenerCount(this, 'error') > 0) {
1831132Smax.romanov@nginx.com        this.emit('error', error);
1841132Smax.romanov@nginx.com    }
1851132Smax.romanov@nginx.com    this.socket.destroy(error);
1861132Smax.romanov@nginx.com    this._debug.printOutput();
1871132Smax.romanov@nginx.com
1881132Smax.romanov@nginx.com    this._end();
1891132Smax.romanov@nginx.com};
1901132Smax.romanov@nginx.com
1911132Smax.romanov@nginx.comWebSocketConnection.prototype.handleSocketEnd = function() {
1921132Smax.romanov@nginx.com    this._debug('handleSocketEnd: received socket end.  state = %s', this.state);
1931132Smax.romanov@nginx.com    this.receivedEnd = true;
1941132Smax.romanov@nginx.com    if (this.state === STATE_CLOSED) {
1951132Smax.romanov@nginx.com        // When using the TLS module, sometimes the socket will emit 'end'
1961132Smax.romanov@nginx.com        // after it emits 'close'.  I don't think that's correct behavior,
1971132Smax.romanov@nginx.com        // but we should deal with it gracefully by ignoring it.
1981132Smax.romanov@nginx.com        this._debug('  --- Socket \'end\' after \'close\'');
1991132Smax.romanov@nginx.com        return;
2001132Smax.romanov@nginx.com    }
2011132Smax.romanov@nginx.com    if (this.state !== STATE_PEER_REQUESTED_CLOSE &&
2021132Smax.romanov@nginx.com        this.state !== STATE_ENDING) {
2031132Smax.romanov@nginx.com      this._debug('  --- UNEXPECTED socket end.');
2041132Smax.romanov@nginx.com      this.socket.end();
2051132Smax.romanov@nginx.com
2061132Smax.romanov@nginx.com      this._end();
2071132Smax.romanov@nginx.com    }
2081132Smax.romanov@nginx.com};
2091132Smax.romanov@nginx.com
2101132Smax.romanov@nginx.comWebSocketConnection.prototype.handleSocketClose = function(hadError) {
2111132Smax.romanov@nginx.com    this._debug('handleSocketClose: received socket close');
2121132Smax.romanov@nginx.com    this.socketHadError = hadError;
2131132Smax.romanov@nginx.com    this.connected = false;
2141132Smax.romanov@nginx.com    this.state = STATE_CLOSED;
2151132Smax.romanov@nginx.com    // If closeReasonCode is still set to -1 at this point then we must
2161132Smax.romanov@nginx.com    // not have received a close frame!!
2171132Smax.romanov@nginx.com    if (this.closeReasonCode === -1) {
2181132Smax.romanov@nginx.com        this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
2191132Smax.romanov@nginx.com        this.closeDescription = 'Connection dropped by remote peer.';
2201132Smax.romanov@nginx.com    }
2211132Smax.romanov@nginx.com    this.clearCloseTimer();
2221132Smax.romanov@nginx.com    if (!this.closeEventEmitted) {
2231132Smax.romanov@nginx.com        this.closeEventEmitted = true;
2241132Smax.romanov@nginx.com        this._debug('-- Emitting WebSocketConnection close event');
2251132Smax.romanov@nginx.com        this.emit('close', this.closeReasonCode, this.closeDescription);
2261132Smax.romanov@nginx.com    }
2271132Smax.romanov@nginx.com};
2281132Smax.romanov@nginx.com
2291132Smax.romanov@nginx.comWebSocketConnection.prototype.close = function(reasonCode, description) {
2301132Smax.romanov@nginx.com    if (this.connected) {
2311132Smax.romanov@nginx.com        this._debug('close: Initating clean WebSocket close sequence.');
2321132Smax.romanov@nginx.com        if ('number' !== typeof reasonCode) {
2331132Smax.romanov@nginx.com            reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
2341132Smax.romanov@nginx.com        }
2351132Smax.romanov@nginx.com        if (!validateCloseReason(reasonCode)) {
2361132Smax.romanov@nginx.com            throw new Error('Close code ' + reasonCode + ' is not valid.');
2371132Smax.romanov@nginx.com        }
2381132Smax.romanov@nginx.com        if ('string' !== typeof description) {
2391132Smax.romanov@nginx.com            description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
2401132Smax.romanov@nginx.com        }
2411132Smax.romanov@nginx.com        this.closeReasonCode = reasonCode;
2421132Smax.romanov@nginx.com        this.closeDescription = description;
2431132Smax.romanov@nginx.com        this.setCloseTimer();
2441132Smax.romanov@nginx.com        this.sendCloseFrame(this.closeReasonCode, this.closeDescription);
2451132Smax.romanov@nginx.com        this.state = STATE_ENDING;
2461132Smax.romanov@nginx.com        this.connected = false;
2471132Smax.romanov@nginx.com    }
2481132Smax.romanov@nginx.com};
2491132Smax.romanov@nginx.com
2501132Smax.romanov@nginx.comWebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) {
2511132Smax.romanov@nginx.com    this._debug('drop');
2521132Smax.romanov@nginx.com    if (typeof(reasonCode) !== 'number') {
2531132Smax.romanov@nginx.com        reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
2541132Smax.romanov@nginx.com    }
2551132Smax.romanov@nginx.com
2561132Smax.romanov@nginx.com    if (typeof(description) !== 'string') {
2571132Smax.romanov@nginx.com        // If no description is provided, try to look one up based on the
2581132Smax.romanov@nginx.com        // specified reasonCode.
2591132Smax.romanov@nginx.com        description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
2601132Smax.romanov@nginx.com    }
2611132Smax.romanov@nginx.com
2621132Smax.romanov@nginx.com    this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s',
2631132Smax.romanov@nginx.com        skipCloseFrame, reasonCode, description
2641132Smax.romanov@nginx.com    );
2651132Smax.romanov@nginx.com
2661132Smax.romanov@nginx.com    this.closeReasonCode = reasonCode;
2671132Smax.romanov@nginx.com    this.closeDescription = description;
2681132Smax.romanov@nginx.com    this.frameQueue = [];
2691132Smax.romanov@nginx.com    this.fragmentationSize = 0;
2701132Smax.romanov@nginx.com    if (!skipCloseFrame) {
2711132Smax.romanov@nginx.com        this.sendCloseFrame(reasonCode, description);
2721132Smax.romanov@nginx.com    }
2731132Smax.romanov@nginx.com    this.connected = false;
2741132Smax.romanov@nginx.com    this.state = STATE_CLOSED;
2751132Smax.romanov@nginx.com    this.clearCloseTimer();
2761132Smax.romanov@nginx.com
2771132Smax.romanov@nginx.com    if (!this.closeEventEmitted) {
2781132Smax.romanov@nginx.com        this.closeEventEmitted = true;
2791132Smax.romanov@nginx.com        this._debug('Emitting WebSocketConnection close event');
2801132Smax.romanov@nginx.com        this.emit('close', this.closeReasonCode, this.closeDescription);
2811132Smax.romanov@nginx.com    }
2821132Smax.romanov@nginx.com
2831132Smax.romanov@nginx.com    this._debug('Drop: destroying socket');
2841132Smax.romanov@nginx.com    this.socket.destroy();
2851132Smax.romanov@nginx.com
2861132Smax.romanov@nginx.com    this._end();
2871132Smax.romanov@nginx.com};
2881132Smax.romanov@nginx.com
2891132Smax.romanov@nginx.comWebSocketConnection.prototype.setCloseTimer = function() {
2901132Smax.romanov@nginx.com    this._debug('setCloseTimer');
2911132Smax.romanov@nginx.com    this.clearCloseTimer();
2921132Smax.romanov@nginx.com    this._debug('Setting close timer');
2931132Smax.romanov@nginx.com    this.waitingForCloseResponse = true;
2941132Smax.romanov@nginx.com    this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
2951132Smax.romanov@nginx.com};
2961132Smax.romanov@nginx.com
2971132Smax.romanov@nginx.comWebSocketConnection.prototype.clearCloseTimer = function() {
2981132Smax.romanov@nginx.com    this._debug('clearCloseTimer');
2991132Smax.romanov@nginx.com    if (this.closeTimer) {
3001132Smax.romanov@nginx.com        this._debug('Clearing close timer');
3011132Smax.romanov@nginx.com        clearTimeout(this.closeTimer);
3021132Smax.romanov@nginx.com        this.waitingForCloseResponse = false;
3031132Smax.romanov@nginx.com        this.closeTimer = null;
3041132Smax.romanov@nginx.com    }
3051132Smax.romanov@nginx.com};
3061132Smax.romanov@nginx.com
3071132Smax.romanov@nginx.comWebSocketConnection.prototype.handleCloseTimer = function() {
3081132Smax.romanov@nginx.com    this._debug('handleCloseTimer');
3091132Smax.romanov@nginx.com    this.closeTimer = null;
3101132Smax.romanov@nginx.com    if (this.waitingForCloseResponse) {
3111132Smax.romanov@nginx.com        this._debug('Close response not received from client.  Forcing socket end.');
3121132Smax.romanov@nginx.com        this.waitingForCloseResponse = false;
3131132Smax.romanov@nginx.com        this.state = STATE_CLOSED;
3141132Smax.romanov@nginx.com        this.socket.end();
3151132Smax.romanov@nginx.com
3161132Smax.romanov@nginx.com        this._end();
3171132Smax.romanov@nginx.com    }
3181132Smax.romanov@nginx.com};
3191132Smax.romanov@nginx.com
3201132Smax.romanov@nginx.comWebSocketConnection.prototype.processFrame = function(frame) {
3211132Smax.romanov@nginx.com    if (!this.connected) {
3221132Smax.romanov@nginx.com        return;
3231132Smax.romanov@nginx.com    }
3241132Smax.romanov@nginx.com
3251132Smax.romanov@nginx.com    this._debug('processFrame');
3261132Smax.romanov@nginx.com    this._debug(' -- frame: %s', frame);
3271132Smax.romanov@nginx.com
3281132Smax.romanov@nginx.com    // Any non-control opcode besides 0x00 (continuation) received in the
3291132Smax.romanov@nginx.com    // middle of a fragmented message is illegal.
3301132Smax.romanov@nginx.com    if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) {
3311132Smax.romanov@nginx.com        this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
3321132Smax.romanov@nginx.com          'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' +
3331132Smax.romanov@nginx.com          'received in middle of fragmented message.');
3341132Smax.romanov@nginx.com        return;
3351132Smax.romanov@nginx.com    }
3361132Smax.romanov@nginx.com
3371132Smax.romanov@nginx.com    switch(frame.opcode) {
3381132Smax.romanov@nginx.com        case 0x02: // WebSocketFrame.BINARY_FRAME
3391132Smax.romanov@nginx.com            this._debug('-- Binary Frame');
3401132Smax.romanov@nginx.com            if (this.assembleFragments) {
3411132Smax.romanov@nginx.com                if (frame.fin) {
3421132Smax.romanov@nginx.com                    // Complete single-frame message received
3431132Smax.romanov@nginx.com                    this._debug('---- Emitting \'message\' event');
3441132Smax.romanov@nginx.com                    this.emit('message', {
3451132Smax.romanov@nginx.com                        type: 'binary',
3461132Smax.romanov@nginx.com                        binaryData: frame.binaryPayload
3471132Smax.romanov@nginx.com                    });
3481132Smax.romanov@nginx.com                }
3491132Smax.romanov@nginx.com                else {
3501132Smax.romanov@nginx.com                    // beginning of a fragmented message
3511132Smax.romanov@nginx.com                    this.frameQueue.push(frame);
3521132Smax.romanov@nginx.com                    this.fragmentationSize = frame.length;
3531132Smax.romanov@nginx.com                }
3541132Smax.romanov@nginx.com            }
3551132Smax.romanov@nginx.com            break;
3561132Smax.romanov@nginx.com        case 0x01: // WebSocketFrame.TEXT_FRAME
3571132Smax.romanov@nginx.com            this._debug('-- Text Frame');
3581132Smax.romanov@nginx.com            if (this.assembleFragments) {
3591132Smax.romanov@nginx.com                if (frame.fin) {
3601132Smax.romanov@nginx.com                    // Complete single-frame message received
3611132Smax.romanov@nginx.com                    this._debug('---- Emitting \'message\' event');
3621132Smax.romanov@nginx.com                    this.emit('message', {
3631132Smax.romanov@nginx.com                        type: 'utf8',
3641132Smax.romanov@nginx.com                        utf8Data: frame.binaryPayload.toString('utf8')
3651132Smax.romanov@nginx.com                    });
3661132Smax.romanov@nginx.com                }
3671132Smax.romanov@nginx.com                else {
3681132Smax.romanov@nginx.com                    // beginning of a fragmented message
3691132Smax.romanov@nginx.com                    this.frameQueue.push(frame);
3701132Smax.romanov@nginx.com                    this.fragmentationSize = frame.length;
3711132Smax.romanov@nginx.com                }
3721132Smax.romanov@nginx.com            }
3731132Smax.romanov@nginx.com            break;
3741132Smax.romanov@nginx.com        case 0x00: // WebSocketFrame.CONTINUATION
3751132Smax.romanov@nginx.com            this._debug('-- Continuation Frame');
3761132Smax.romanov@nginx.com            if (this.assembleFragments) {
3771132Smax.romanov@nginx.com                if (this.frameQueue.length === 0) {
3781132Smax.romanov@nginx.com                    this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
3791132Smax.romanov@nginx.com                      'Unexpected Continuation Frame');
3801132Smax.romanov@nginx.com                    return;
3811132Smax.romanov@nginx.com                }
3821132Smax.romanov@nginx.com
3831132Smax.romanov@nginx.com                this.fragmentationSize += frame.length;
3841132Smax.romanov@nginx.com
3851132Smax.romanov@nginx.com                if (this.fragmentationSize > this.maxReceivedMessageSize) {
3861132Smax.romanov@nginx.com                    this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,
3871132Smax.romanov@nginx.com                      'Maximum message size exceeded.');
3881132Smax.romanov@nginx.com                    return;
3891132Smax.romanov@nginx.com                }
3901132Smax.romanov@nginx.com
3911132Smax.romanov@nginx.com                this.frameQueue.push(frame);
3921132Smax.romanov@nginx.com
3931132Smax.romanov@nginx.com                if (frame.fin) {
3941132Smax.romanov@nginx.com                    // end of fragmented message, so we process the whole
3951132Smax.romanov@nginx.com                    // message now.  We also have to decode the utf-8 data
3961132Smax.romanov@nginx.com                    // for text frames after combining all the fragments.
3971132Smax.romanov@nginx.com                    var bytesCopied = 0;
3981132Smax.romanov@nginx.com                    var binaryPayload = bufferAllocUnsafe(this.fragmentationSize);
3991132Smax.romanov@nginx.com                    var opcode = this.frameQueue[0].opcode;
4001132Smax.romanov@nginx.com                    this.frameQueue.forEach(function (currentFrame) {
4011132Smax.romanov@nginx.com                        currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
4021132Smax.romanov@nginx.com                        bytesCopied += currentFrame.binaryPayload.length;
4031132Smax.romanov@nginx.com                    });
4041132Smax.romanov@nginx.com                    this.frameQueue = [];
4051132Smax.romanov@nginx.com                    this.fragmentationSize = 0;
4061132Smax.romanov@nginx.com
4071132Smax.romanov@nginx.com                    switch (opcode) {
4081132Smax.romanov@nginx.com                        case 0x02: // WebSocketOpcode.BINARY_FRAME
4091132Smax.romanov@nginx.com                            this.emit('message', {
4101132Smax.romanov@nginx.com                                type: 'binary',
4111132Smax.romanov@nginx.com                                binaryData: binaryPayload
4121132Smax.romanov@nginx.com                            });
4131132Smax.romanov@nginx.com                            break;
4141132Smax.romanov@nginx.com                        case 0x01: // WebSocketOpcode.TEXT_FRAME
4151132Smax.romanov@nginx.com                            this.emit('message', {
4161132Smax.romanov@nginx.com                                type: 'utf8',
4171132Smax.romanov@nginx.com                                utf8Data: binaryPayload.toString('utf8')
4181132Smax.romanov@nginx.com                            });
4191132Smax.romanov@nginx.com                            break;
4201132Smax.romanov@nginx.com                        default:
4211132Smax.romanov@nginx.com                            this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
4221132Smax.romanov@nginx.com                              'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16));
4231132Smax.romanov@nginx.com                            return;
4241132Smax.romanov@nginx.com                    }
4251132Smax.romanov@nginx.com                }
4261132Smax.romanov@nginx.com            }
4271132Smax.romanov@nginx.com            break;
4281132Smax.romanov@nginx.com        case 0x09: // WebSocketFrame.PING
4291132Smax.romanov@nginx.com            this._debug('-- Ping Frame');
4301132Smax.romanov@nginx.com
4311132Smax.romanov@nginx.com            if (this._pingListenerCount > 0) {
4321132Smax.romanov@nginx.com                // logic to emit the ping frame: this is only done when a listener is known to exist
4331132Smax.romanov@nginx.com                // Expose a function allowing the user to override the default ping() behavior
4341132Smax.romanov@nginx.com                var cancelled = false;
435*2617Szelenkov@nginx.com                var cancel = function() {
436*2617Szelenkov@nginx.com                  cancelled = true;
4371132Smax.romanov@nginx.com                };
4381132Smax.romanov@nginx.com                this.emit('ping', cancel, frame.binaryPayload);
4391132Smax.romanov@nginx.com
4401132Smax.romanov@nginx.com                // Only send a pong if the client did not indicate that he would like to cancel
4411132Smax.romanov@nginx.com                if (!cancelled) {
4421132Smax.romanov@nginx.com                    this.pong(frame.binaryPayload);
4431132Smax.romanov@nginx.com                }
4441132Smax.romanov@nginx.com            }
4451132Smax.romanov@nginx.com            else {
4461132Smax.romanov@nginx.com                this.pong(frame.binaryPayload);
4471132Smax.romanov@nginx.com            }
4481132Smax.romanov@nginx.com
4491132Smax.romanov@nginx.com            break;
4501132Smax.romanov@nginx.com        case 0x0A: // WebSocketFrame.PONG
4511132Smax.romanov@nginx.com            this._debug('-- Pong Frame');
4521132Smax.romanov@nginx.com            this.emit('pong', frame.binaryPayload);
4531132Smax.romanov@nginx.com            break;
4541132Smax.romanov@nginx.com        case 0x08: // WebSocketFrame.CONNECTION_CLOSE
4551132Smax.romanov@nginx.com            this._debug('-- Close Frame');
4561132Smax.romanov@nginx.com            if (this.waitingForCloseResponse) {
4571132Smax.romanov@nginx.com                // Got response to our request to close the connection.
4581132Smax.romanov@nginx.com                // Close is complete, so we just hang up.
4591132Smax.romanov@nginx.com                this._debug('---- Got close response from peer.  Completing closing handshake.');
4601132Smax.romanov@nginx.com                this.clearCloseTimer();
4611132Smax.romanov@nginx.com                this.waitingForCloseResponse = false;
4621132Smax.romanov@nginx.com                this.state = STATE_CLOSED;
4631132Smax.romanov@nginx.com                this.socket.end();
4641132Smax.romanov@nginx.com
4651132Smax.romanov@nginx.com                this._end();
4661132Smax.romanov@nginx.com                return;
4671132Smax.romanov@nginx.com            }
4681132Smax.romanov@nginx.com
4691132Smax.romanov@nginx.com            this._debug('---- Closing handshake initiated by peer.');
4701132Smax.romanov@nginx.com            // Got request from other party to close connection.
4711132Smax.romanov@nginx.com            // Send back acknowledgement and then hang up.
4721132Smax.romanov@nginx.com            this.state = STATE_PEER_REQUESTED_CLOSE;
4731132Smax.romanov@nginx.com            var respondCloseReasonCode;
4741132Smax.romanov@nginx.com
4751132Smax.romanov@nginx.com            // Make sure the close reason provided is legal according to
4761132Smax.romanov@nginx.com            // the protocol spec.  Providing no close status is legal.
4771132Smax.romanov@nginx.com            // WebSocketFrame sets closeStatus to -1 by default, so if it
4781132Smax.romanov@nginx.com            // is still -1, then no status was provided.
4791132Smax.romanov@nginx.com            if (frame.invalidCloseFrameLength) {
4801132Smax.romanov@nginx.com                this.closeReasonCode = 1005; // 1005 = No reason provided.
4811132Smax.romanov@nginx.com                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
4821132Smax.romanov@nginx.com            }
4831132Smax.romanov@nginx.com            else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) {
4841132Smax.romanov@nginx.com                this.closeReasonCode = frame.closeStatus;
4851132Smax.romanov@nginx.com                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
4861132Smax.romanov@nginx.com            }
4871132Smax.romanov@nginx.com            else {
4881132Smax.romanov@nginx.com                this.closeReasonCode = frame.closeStatus;
4891132Smax.romanov@nginx.com                respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
4901132Smax.romanov@nginx.com            }
4911132Smax.romanov@nginx.com
4921132Smax.romanov@nginx.com            // If there is a textual description in the close frame, extract it.
4931132Smax.romanov@nginx.com            if (frame.binaryPayload.length > 1) {
4941132Smax.romanov@nginx.com                this.closeDescription = frame.binaryPayload.toString('utf8');
4951132Smax.romanov@nginx.com            }
4961132Smax.romanov@nginx.com            else {
4971132Smax.romanov@nginx.com                this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode];
4981132Smax.romanov@nginx.com            }
4991132Smax.romanov@nginx.com            this._debug(
5001132Smax.romanov@nginx.com                '------ Remote peer %s - code: %d - %s - close frame payload length: %d',
5011132Smax.romanov@nginx.com                this.remoteAddress, this.closeReasonCode,
5021132Smax.romanov@nginx.com                this.closeDescription, frame.length
5031132Smax.romanov@nginx.com            );
5041132Smax.romanov@nginx.com            this._debug('------ responding to remote peer\'s close request.');
5051132Smax.romanov@nginx.com            this.drop(respondCloseReasonCode, null);
5061132Smax.romanov@nginx.com            this.connected = false;
5071132Smax.romanov@nginx.com            break;
5081132Smax.romanov@nginx.com        default:
5091132Smax.romanov@nginx.com            this._debug('-- Unrecognized Opcode %d', frame.opcode);
5101132Smax.romanov@nginx.com            this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
5111132Smax.romanov@nginx.com              'Unrecognized Opcode: 0x' + frame.opcode.toString(16));
5121132Smax.romanov@nginx.com            break;
5131132Smax.romanov@nginx.com    }
5141132Smax.romanov@nginx.com};
5151132Smax.romanov@nginx.com
5161132Smax.romanov@nginx.comWebSocketConnection.prototype.send = function(data, cb) {
5171132Smax.romanov@nginx.com    this._debug('send');
5181132Smax.romanov@nginx.com    if (Buffer.isBuffer(data)) {
5191132Smax.romanov@nginx.com        this.sendBytes(data, cb);
5201132Smax.romanov@nginx.com    }
5211132Smax.romanov@nginx.com    else if (typeof(data['toString']) === 'function') {
5221132Smax.romanov@nginx.com        this.sendUTF(data, cb);
5231132Smax.romanov@nginx.com    }
5241132Smax.romanov@nginx.com    else {
5251132Smax.romanov@nginx.com        throw new Error('Data provided must either be a Node Buffer or implement toString()');
5261132Smax.romanov@nginx.com    }
5271132Smax.romanov@nginx.com};
5281132Smax.romanov@nginx.com
5291132Smax.romanov@nginx.comWebSocketConnection.prototype.sendUTF = function(data, cb) {
5301132Smax.romanov@nginx.com    data = bufferFromString(data.toString(), 'utf8');
5311132Smax.romanov@nginx.com    this._debug('sendUTF: %d bytes', data.length);
5321132Smax.romanov@nginx.com
5331132Smax.romanov@nginx.com    var frame = new WebSocketFrame();
5341132Smax.romanov@nginx.com    frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
5351132Smax.romanov@nginx.com    frame.binaryPayload = data;
5361132Smax.romanov@nginx.com
5371132Smax.romanov@nginx.com    this.fragmentAndSend(frame, cb);
5381132Smax.romanov@nginx.com};
5391132Smax.romanov@nginx.com
5401132Smax.romanov@nginx.comWebSocketConnection.prototype.sendBytes = function(data, cb) {
5411132Smax.romanov@nginx.com    this._debug('sendBytes');
5421132Smax.romanov@nginx.com    if (!Buffer.isBuffer(data)) {
5431132Smax.romanov@nginx.com        throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()');
5441132Smax.romanov@nginx.com    }
5451132Smax.romanov@nginx.com
5461132Smax.romanov@nginx.com    var frame = new WebSocketFrame();
5471132Smax.romanov@nginx.com    frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
5481132Smax.romanov@nginx.com    frame.binaryPayload = data;
5491132Smax.romanov@nginx.com
5501132Smax.romanov@nginx.com    this.fragmentAndSend(frame, cb);
5511132Smax.romanov@nginx.com};
5521132Smax.romanov@nginx.com
5531132Smax.romanov@nginx.comWebSocketConnection.prototype.ping = function(data) {
5541132Smax.romanov@nginx.com    this._debug('ping');
5551132Smax.romanov@nginx.com
5561132Smax.romanov@nginx.com    var frame = new WebSocketFrame();
5571132Smax.romanov@nginx.com    frame.opcode = 0x09; // WebSocketOpcode.PING
5581132Smax.romanov@nginx.com    frame.fin = true;
5591132Smax.romanov@nginx.com
5601132Smax.romanov@nginx.com    if (data) {
5611132Smax.romanov@nginx.com        if (!Buffer.isBuffer(data)) {
5621132Smax.romanov@nginx.com            data = bufferFromString(data.toString(), 'utf8');
5631132Smax.romanov@nginx.com        }
5641132Smax.romanov@nginx.com        if (data.length > 125) {
5651132Smax.romanov@nginx.com            this._debug('WebSocket: Data for ping is longer than 125 bytes.  Truncating.');
5661132Smax.romanov@nginx.com            data = data.slice(0,124);
5671132Smax.romanov@nginx.com        }
5681132Smax.romanov@nginx.com        frame.binaryPayload = data;
5691132Smax.romanov@nginx.com    }
5701132Smax.romanov@nginx.com
5711132Smax.romanov@nginx.com    this.sendFrame(frame);
5721132Smax.romanov@nginx.com};
5731132Smax.romanov@nginx.com
5741132Smax.romanov@nginx.com// Pong frames have to echo back the contents of the data portion of the
5751132Smax.romanov@nginx.com// ping frame exactly, byte for byte.
5761132Smax.romanov@nginx.comWebSocketConnection.prototype.pong = function(binaryPayload) {
5771132Smax.romanov@nginx.com    this._debug('pong');
5781132Smax.romanov@nginx.com
5791132Smax.romanov@nginx.com    var frame = new WebSocketFrame();
5801132Smax.romanov@nginx.com    frame.opcode = 0x0A; // WebSocketOpcode.PONG
5811132Smax.romanov@nginx.com    if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) {
5821132Smax.romanov@nginx.com        this._debug('WebSocket: Data for pong is longer than 125 bytes.  Truncating.');
5831132Smax.romanov@nginx.com        binaryPayload = binaryPayload.slice(0,124);
5841132Smax.romanov@nginx.com    }
5851132Smax.romanov@nginx.com    frame.binaryPayload = binaryPayload;
5861132Smax.romanov@nginx.com    frame.fin = true;
5871132Smax.romanov@nginx.com
5881132Smax.romanov@nginx.com    this.sendFrame(frame);
5891132Smax.romanov@nginx.com};
5901132Smax.romanov@nginx.com
5911132Smax.romanov@nginx.comWebSocketConnection.prototype.fragmentAndSend = function(frame, cb) {
5921132Smax.romanov@nginx.com    this._debug('fragmentAndSend');
5931132Smax.romanov@nginx.com    if (frame.opcode > 0x07) {
5941132Smax.romanov@nginx.com        throw new Error('You cannot fragment control frames.');
5951132Smax.romanov@nginx.com    }
5961132Smax.romanov@nginx.com
5971132Smax.romanov@nginx.com    var threshold = this.config.fragmentationThreshold;
5981132Smax.romanov@nginx.com    var length = frame.binaryPayload.length;
5991132Smax.romanov@nginx.com
6001132Smax.romanov@nginx.com    // Send immediately if fragmentation is disabled or the message is not
6011132Smax.romanov@nginx.com    // larger than the fragmentation threshold.
6021132Smax.romanov@nginx.com    if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) {
6031132Smax.romanov@nginx.com        frame.fin = true;
6041132Smax.romanov@nginx.com        this.sendFrame(frame, cb);
6051132Smax.romanov@nginx.com        return;
6061132Smax.romanov@nginx.com    }
6071132Smax.romanov@nginx.com
6081132Smax.romanov@nginx.com    var numFragments = Math.ceil(length / threshold);
6091132Smax.romanov@nginx.com    var sentFragments = 0;
6101132Smax.romanov@nginx.com    var sentCallback = function fragmentSentCallback(err) {
6111132Smax.romanov@nginx.com        if (err) {
6121132Smax.romanov@nginx.com            if (typeof cb === 'function') {
6131132Smax.romanov@nginx.com                // pass only the first error
6141132Smax.romanov@nginx.com                cb(err);
6151132Smax.romanov@nginx.com                cb = null;
6161132Smax.romanov@nginx.com            }
6171132Smax.romanov@nginx.com            return;
6181132Smax.romanov@nginx.com        }
6191132Smax.romanov@nginx.com        ++sentFragments;
6201132Smax.romanov@nginx.com        if ((sentFragments === numFragments) && (typeof cb === 'function')) {
6211132Smax.romanov@nginx.com            cb();
6221132Smax.romanov@nginx.com        }
6231132Smax.romanov@nginx.com    };
6241132Smax.romanov@nginx.com    for (var i=1; i <= numFragments; i++) {
6251132Smax.romanov@nginx.com        var currentFrame = new WebSocketFrame();
6261132Smax.romanov@nginx.com
6271132Smax.romanov@nginx.com        // continuation opcode except for first frame.
6281132Smax.romanov@nginx.com        currentFrame.opcode = (i === 1) ? frame.opcode : 0x00;
6291132Smax.romanov@nginx.com
6301132Smax.romanov@nginx.com        // fin set on last frame only
6311132Smax.romanov@nginx.com        currentFrame.fin = (i === numFragments);
6321132Smax.romanov@nginx.com
6331132Smax.romanov@nginx.com        // length is likely to be shorter on the last fragment
6341132Smax.romanov@nginx.com        var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold;
6351132Smax.romanov@nginx.com        var sliceStart = threshold * (i-1);
6361132Smax.romanov@nginx.com
6371132Smax.romanov@nginx.com        // Slice the right portion of the original payload
6381132Smax.romanov@nginx.com        currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength);
6391132Smax.romanov@nginx.com
6401132Smax.romanov@nginx.com        this.sendFrame(currentFrame, sentCallback);
6411132Smax.romanov@nginx.com    }
6421132Smax.romanov@nginx.com};
6431132Smax.romanov@nginx.com
6441132Smax.romanov@nginx.comWebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) {
6451132Smax.romanov@nginx.com    if (typeof(reasonCode) !== 'number') {
6461132Smax.romanov@nginx.com        reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
6471132Smax.romanov@nginx.com    }
6481132Smax.romanov@nginx.com
6491132Smax.romanov@nginx.com    this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description);
6501132Smax.romanov@nginx.com
6511132Smax.romanov@nginx.com    if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; }
6521132Smax.romanov@nginx.com
6531132Smax.romanov@nginx.com    var frame = new WebSocketFrame();
6541132Smax.romanov@nginx.com    frame.fin = true;
6551132Smax.romanov@nginx.com    frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
6561132Smax.romanov@nginx.com    frame.closeStatus = reasonCode;
6571132Smax.romanov@nginx.com    if (typeof(description) === 'string') {
6581132Smax.romanov@nginx.com        frame.binaryPayload = bufferFromString(description, 'utf8');
6591132Smax.romanov@nginx.com    }
6601132Smax.romanov@nginx.com
6611132Smax.romanov@nginx.com    this.sendFrame(frame, cb);
6621132Smax.romanov@nginx.com    this.socket.end();
6631132Smax.romanov@nginx.com};
6641132Smax.romanov@nginx.com
6651132Smax.romanov@nginx.comWebSocketConnection.prototype._send_frame = unit_lib.websocket_send_frame;
6661132Smax.romanov@nginx.com
6671132Smax.romanov@nginx.comWebSocketConnection.prototype.sendFrame = function(frame, cb) {
6681132Smax.romanov@nginx.com    this._debug('sendFrame');
6691132Smax.romanov@nginx.com
6701132Smax.romanov@nginx.com    frame.mask = this.maskOutgoingPackets;
6711132Smax.romanov@nginx.com
6721132Smax.romanov@nginx.com    this._send_frame(frame);
6731132Smax.romanov@nginx.com
6741132Smax.romanov@nginx.com    if (typeof cb === 'function') {
6751132Smax.romanov@nginx.com        cb();
6761132Smax.romanov@nginx.com    }
6771132Smax.romanov@nginx.com
6781132Smax.romanov@nginx.com    var flushed = 0; // this.socket.write(frame.toBuffer(), cb);
6791132Smax.romanov@nginx.com    this.outputBufferFull = !flushed;
6801132Smax.romanov@nginx.com    return flushed;
6811132Smax.romanov@nginx.com};
6821132Smax.romanov@nginx.com
6831132Smax.romanov@nginx.commodule.exports = WebSocketConnection;
684