You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
5.6 KiB

  1. /*!
  2. * ws: a node.js websocket client
  3. * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
  4. * MIT Licensed
  5. */
  6. var events = require('events')
  7. , util = require('util')
  8. , EventEmitter = events.EventEmitter
  9. , ErrorCodes = require('./ErrorCodes')
  10. , bufferUtil = require('./BufferUtil').BufferUtil;
  11. /**
  12. * HyBi Sender implementation
  13. */
  14. function Sender(socket) {
  15. this._socket = socket;
  16. this.firstFragment = true;
  17. }
  18. /**
  19. * Inherits from EventEmitter.
  20. */
  21. util.inherits(Sender, events.EventEmitter);
  22. /**
  23. * Sends a close instruction to the remote party.
  24. *
  25. * @api public
  26. */
  27. Sender.prototype.close = function(code, data, mask) {
  28. if (typeof code !== 'undefined') {
  29. if (typeof code !== 'number' ||
  30. !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
  31. }
  32. code = code || 1000;
  33. var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
  34. writeUInt16BE.call(dataBuffer, code, 0);
  35. if (dataBuffer.length > 2) dataBuffer.write(data, 2);
  36. this.frameAndSend(0x8, dataBuffer, true, mask);
  37. }
  38. /**
  39. * Sends a ping message to the remote party.
  40. *
  41. * @api public
  42. */
  43. Sender.prototype.ping = function(data, options) {
  44. var mask = options && options.mask;
  45. this.frameAndSend(0x9, data || '', true, mask);
  46. }
  47. /**
  48. * Sends a pong message to the remote party.
  49. *
  50. * @api public
  51. */
  52. Sender.prototype.pong = function(data, options) {
  53. var mask = options && options.mask;
  54. this.frameAndSend(0xa, data || '', true, mask);
  55. }
  56. /**
  57. * Sends text or binary data to the remote party.
  58. *
  59. * @api public
  60. */
  61. Sender.prototype.send = function(data, options, cb) {
  62. var finalFragment = options && options.fin === false ? false : true;
  63. var mask = options && options.mask;
  64. var opcode = options && options.binary ? 2 : 1;
  65. if (this.firstFragment === false) opcode = 0;
  66. else this.firstFragment = false;
  67. if (finalFragment) this.firstFragment = true
  68. this.frameAndSend(opcode, data, finalFragment, mask, cb);
  69. }
  70. /**
  71. * Frames and sends a piece of data according to the HyBi WebSocket protocol.
  72. *
  73. * @api private
  74. */
  75. Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, cb) {
  76. var canModifyData = false;
  77. if (!data) {
  78. try {
  79. this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb);
  80. }
  81. catch (e) {
  82. if (typeof cb == 'function') cb(e);
  83. else this.emit('error', e);
  84. }
  85. return;
  86. }
  87. if (!Buffer.isBuffer(data)) {
  88. canModifyData = true;
  89. if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) {
  90. data = getArrayBuffer(data);
  91. } else {
  92. data = new Buffer(data);
  93. }
  94. }
  95. var dataLength = data.length
  96. , dataOffset = maskData ? 6 : 2
  97. , secondByte = dataLength;
  98. if (dataLength >= 65536) {
  99. dataOffset += 8;
  100. secondByte = 127;
  101. }
  102. else if (dataLength > 125) {
  103. dataOffset += 2;
  104. secondByte = 126;
  105. }
  106. var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData);
  107. var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset;
  108. var outputBuffer = new Buffer(totalLength);
  109. outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
  110. switch (secondByte) {
  111. case 126:
  112. writeUInt16BE.call(outputBuffer, dataLength, 2);
  113. break;
  114. case 127:
  115. writeUInt32BE.call(outputBuffer, 0, 2);
  116. writeUInt32BE.call(outputBuffer, dataLength, 6);
  117. }
  118. if (maskData) {
  119. outputBuffer[1] = secondByte | 0x80;
  120. var mask = this._randomMask || (this._randomMask = getRandomMask());
  121. outputBuffer[dataOffset - 4] = mask[0];
  122. outputBuffer[dataOffset - 3] = mask[1];
  123. outputBuffer[dataOffset - 2] = mask[2];
  124. outputBuffer[dataOffset - 1] = mask[3];
  125. if (mergeBuffers) {
  126. bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
  127. try {
  128. this._socket.write(outputBuffer, 'binary', cb);
  129. }
  130. catch (e) {
  131. if (typeof cb == 'function') cb(e);
  132. else this.emit('error', e);
  133. }
  134. }
  135. else {
  136. bufferUtil.mask(data, mask, data, 0, dataLength);
  137. try {
  138. this._socket.write(outputBuffer, 'binary');
  139. this._socket.write(data, 'binary', cb);
  140. }
  141. catch (e) {
  142. if (typeof cb == 'function') cb(e);
  143. else this.emit('error', e);
  144. }
  145. }
  146. }
  147. else {
  148. outputBuffer[1] = secondByte;
  149. if (mergeBuffers) {
  150. data.copy(outputBuffer, dataOffset);
  151. try {
  152. this._socket.write(outputBuffer, 'binary', cb);
  153. }
  154. catch (e) {
  155. if (typeof cb == 'function') cb(e);
  156. else this.emit('error', e);
  157. }
  158. }
  159. else {
  160. try {
  161. this._socket.write(outputBuffer, 'binary');
  162. this._socket.write(data, 'binary', cb);
  163. }
  164. catch (e) {
  165. if (typeof cb == 'function') cb(e);
  166. else this.emit('error', e);
  167. }
  168. }
  169. }
  170. }
  171. module.exports = Sender;
  172. function writeUInt16BE(value, offset) {
  173. this[offset] = (value & 0xff00)>>8;
  174. this[offset+1] = value & 0xff;
  175. }
  176. function writeUInt32BE(value, offset) {
  177. this[offset] = (value & 0xff000000)>>24;
  178. this[offset+1] = (value & 0xff0000)>>16;
  179. this[offset+2] = (value & 0xff00)>>8;
  180. this[offset+3] = value & 0xff;
  181. }
  182. function getArrayBuffer(data) {
  183. // data is either an ArrayBuffer or ArrayBufferView.
  184. var array = new Uint8Array(data.buffer || data)
  185. , l = data.byteLength || data.length
  186. , o = data.byteOffset || 0
  187. , buffer = new Buffer(l);
  188. for (var i = 0; i < l; ++i) {
  189. buffer[i] = array[o+i];
  190. }
  191. return buffer;
  192. }
  193. function getRandomMask() {
  194. return new Buffer([
  195. ~~(Math.random() * 255),
  196. ~~(Math.random() * 255),
  197. ~~(Math.random() * 255),
  198. ~~(Math.random() * 255)
  199. ]);
  200. }