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.

194 lines
4.1 KiB

7 years ago
  1. /*!
  2. * ws: a node.js websocket client
  3. * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
  4. * MIT Licensed
  5. */
  6. var util = require('util');
  7. /**
  8. * State constants
  9. */
  10. var EMPTY = 0
  11. , BODY = 1;
  12. var BINARYLENGTH = 2
  13. , BINARYBODY = 3;
  14. /**
  15. * Hixie Receiver implementation
  16. */
  17. function Receiver () {
  18. if (this instanceof Receiver === false) {
  19. throw new TypeError("Classes can't be function-called");
  20. }
  21. this.state = EMPTY;
  22. this.buffers = [];
  23. this.messageEnd = -1;
  24. this.spanLength = 0;
  25. this.dead = false;
  26. this.onerror = function() {};
  27. this.ontext = function() {};
  28. this.onbinary = function() {};
  29. this.onclose = function() {};
  30. this.onping = function() {};
  31. this.onpong = function() {};
  32. }
  33. module.exports = Receiver;
  34. /**
  35. * Add new data to the parser.
  36. *
  37. * @api public
  38. */
  39. Receiver.prototype.add = function(data) {
  40. if (this.dead) return;
  41. var self = this;
  42. function doAdd() {
  43. if (self.state === EMPTY) {
  44. if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) {
  45. self.reset();
  46. self.onclose();
  47. return;
  48. }
  49. if (data[0] === 0x80) {
  50. self.messageEnd = 0;
  51. self.state = BINARYLENGTH;
  52. data = data.slice(1);
  53. } else {
  54. if (data[0] !== 0x00) {
  55. self.error('payload must start with 0x00 byte', true);
  56. return;
  57. }
  58. data = data.slice(1);
  59. self.state = BODY;
  60. }
  61. }
  62. if (self.state === BINARYLENGTH) {
  63. var i = 0;
  64. while ((i < data.length) && (data[i] & 0x80)) {
  65. self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f);
  66. ++i;
  67. }
  68. if (i < data.length) {
  69. self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f);
  70. self.state = BINARYBODY;
  71. ++i;
  72. }
  73. if (i > 0)
  74. data = data.slice(i);
  75. }
  76. if (self.state === BINARYBODY) {
  77. var dataleft = self.messageEnd - self.spanLength;
  78. if (data.length >= dataleft) {
  79. // consume the whole buffer to finish the frame
  80. self.buffers.push(data);
  81. self.spanLength += dataleft;
  82. self.messageEnd = dataleft;
  83. return self.parse();
  84. }
  85. // frame's not done even if we consume it all
  86. self.buffers.push(data);
  87. self.spanLength += data.length;
  88. return;
  89. }
  90. self.buffers.push(data);
  91. if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) {
  92. self.spanLength += self.messageEnd;
  93. return self.parse();
  94. }
  95. else self.spanLength += data.length;
  96. }
  97. while(data) data = doAdd();
  98. };
  99. /**
  100. * Releases all resources used by the receiver.
  101. *
  102. * @api public
  103. */
  104. Receiver.prototype.cleanup = function() {
  105. this.dead = true;
  106. this.state = EMPTY;
  107. this.buffers = [];
  108. };
  109. /**
  110. * Process buffered data.
  111. *
  112. * @api public
  113. */
  114. Receiver.prototype.parse = function() {
  115. var output = new Buffer(this.spanLength);
  116. var outputIndex = 0;
  117. for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) {
  118. var buffer = this.buffers[bi];
  119. buffer.copy(output, outputIndex);
  120. outputIndex += buffer.length;
  121. }
  122. var lastBuffer = this.buffers[this.buffers.length - 1];
  123. if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd);
  124. if (this.state !== BODY) --this.messageEnd;
  125. var tail = null;
  126. if (this.messageEnd < lastBuffer.length - 1) {
  127. tail = lastBuffer.slice(this.messageEnd + 1);
  128. }
  129. this.reset();
  130. this.ontext(output.toString('utf8'));
  131. return tail;
  132. };
  133. /**
  134. * Handles an error
  135. *
  136. * @api private
  137. */
  138. Receiver.prototype.error = function (reason, terminate) {
  139. if (this.dead) return;
  140. this.reset();
  141. if(typeof reason == 'string'){
  142. this.onerror(new Error(reason), terminate);
  143. }
  144. else if(reason.constructor == Error){
  145. this.onerror(reason, terminate);
  146. }
  147. else{
  148. this.onerror(new Error("An error occured"),terminate);
  149. }
  150. return this;
  151. };
  152. /**
  153. * Reset parser state
  154. *
  155. * @api private
  156. */
  157. Receiver.prototype.reset = function (reason) {
  158. if (this.dead) return;
  159. this.state = EMPTY;
  160. this.buffers = [];
  161. this.messageEnd = -1;
  162. this.spanLength = 0;
  163. };
  164. /**
  165. * Internal api
  166. */
  167. function bufferIndex(buffer, byte) {
  168. for (var i = 0, l = buffer.length; i < l; ++i) {
  169. if (buffer[i] === byte) return i;
  170. }
  171. return -1;
  172. }