WebSocketConnection.js 34 KB

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