A Twitch.tv viewer reward and games system.
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.

366 lines
9.7 KiB

12 years ago
  1. var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
  2. var MAX_PACKET_LENGTH = Math.pow(2, 24) - 1;
  3. var PacketHeader = require('./PacketHeader');
  4. module.exports = Parser;
  5. function Parser(options) {
  6. options = options || {};
  7. this._buffer = new Buffer(0);
  8. this._longPacketBuffers = [];
  9. this._offset = 0;
  10. this._packetEnd = null;
  11. this._packetHeader = null;
  12. this._onPacket = options.onPacket || function() {};
  13. this._nextPacketNumber = 0;
  14. this._encoding = 'utf-8';
  15. this._paused = false;
  16. }
  17. Parser.prototype.write = function(buffer) {
  18. this.append(buffer);
  19. while (true) {
  20. if (this._paused) {
  21. return;
  22. }
  23. if (!this._packetHeader) {
  24. if (this._bytesRemaining() < 4) {
  25. break;
  26. }
  27. this._packetHeader = new PacketHeader(
  28. this.parseUnsignedNumber(3),
  29. this.parseUnsignedNumber(1)
  30. );
  31. this._trackAndVerifyPacketNumber(this._packetHeader.number);
  32. }
  33. if (this._bytesRemaining() < this._packetHeader.length) {
  34. break;
  35. }
  36. this._packetEnd = this._offset + this._packetHeader.length;
  37. if (this._packetHeader.length === MAX_PACKET_LENGTH) {
  38. this._longPacketBuffers.push(this._buffer.slice(this._offset, this._packetEnd));
  39. this._advanceToNextPacket();
  40. continue;
  41. }
  42. this._combineLongPacketBuffers();
  43. // Try...finally to ensure exception safety. Unfortunately this is costing
  44. // us up to ~10% performance in some benchmarks.
  45. var hadException = true;
  46. try {
  47. this._onPacket(this._packetHeader);
  48. hadException = false;
  49. } finally {
  50. this._advanceToNextPacket();
  51. // If we had an exception, the parser while loop will be broken out
  52. // of after the finally block. So we need to make sure to re-enter it
  53. // to continue parsing any bytes that may already have been received.
  54. if (hadException) {
  55. process.nextTick(this.write.bind(this));
  56. }
  57. }
  58. }
  59. };
  60. Parser.prototype.append = function(newBuffer) {
  61. // If resume() is called, we don't pass a buffer to write()
  62. if (!newBuffer) {
  63. return;
  64. }
  65. var oldBuffer = this._buffer;
  66. var bytesRemaining = this._bytesRemaining();
  67. var newLength = bytesRemaining + newBuffer.length;
  68. var combinedBuffer = (this._offset > newLength)
  69. ? oldBuffer.slice(0, newLength)
  70. : new Buffer(newLength);
  71. oldBuffer.copy(combinedBuffer, 0, this._offset);
  72. newBuffer.copy(combinedBuffer, bytesRemaining);
  73. this._buffer = combinedBuffer;
  74. this._offset = 0;
  75. };
  76. Parser.prototype.pause = function() {
  77. this._paused = true;
  78. };
  79. Parser.prototype.resume = function() {
  80. this._paused = false;
  81. // nextTick() to avoid entering write() multiple times within the same stack
  82. // which would cause problems as write manipulates the state of the object.
  83. process.nextTick(this.write.bind(this));
  84. };
  85. Parser.prototype.peak = function() {
  86. return this._buffer[this._offset];
  87. };
  88. Parser.prototype.parseUnsignedNumber = function(bytes) {
  89. var bytesRead = 0;
  90. var value = 0;
  91. while (bytesRead < bytes) {
  92. var byte = this._buffer[this._offset++];
  93. value += byte * Math.pow(256, bytesRead);
  94. bytesRead++;
  95. }
  96. return value;
  97. };
  98. Parser.prototype.parseLengthCodedString = function() {
  99. var length = this.parseLengthCodedNumber();
  100. if (length === null) {
  101. return null;
  102. }
  103. return this.parseString(length);
  104. };
  105. Parser.prototype.parseLengthCodedBuffer = function() {
  106. var length = this.parseLengthCodedNumber();
  107. if (length === null) {
  108. return null;
  109. }
  110. return this.parseBuffer(length);
  111. };
  112. Parser.prototype.parseLengthCodedNumber = function() {
  113. var byte = this._buffer[this._offset++];
  114. if (byte <= 251) {
  115. return (byte === 251)
  116. ? null
  117. : byte;
  118. }
  119. var length;
  120. if (byte === 252) {
  121. length = 2;
  122. } else if (byte === 253) {
  123. length = 3;
  124. } else if (byte === 254) {
  125. length = 8;
  126. } else {
  127. throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
  128. }
  129. var value = 0;
  130. for (var bytesRead = 0; bytesRead < length; bytesRead++) {
  131. var byte = this._buffer[this._offset++];
  132. value += Math.pow(256, bytesRead) * byte;
  133. }
  134. if (value >= IEEE_754_BINARY_64_PRECISION) {
  135. throw new Error(
  136. 'parseLengthCodedNumber: JS precision range exceeded, ' +
  137. 'number is >= 53 bit: "' + value + '"'
  138. );
  139. }
  140. return value;
  141. };
  142. Parser.prototype.parseFiller = function(length) {
  143. return this.parseBuffer(length);
  144. };
  145. Parser.prototype.parseNullTerminatedBuffer = function() {
  146. var end = this._nullByteOffset();
  147. var value = this._buffer.slice(this._offset, end);
  148. this._offset = end + 1;
  149. return value;
  150. };
  151. Parser.prototype.parseNullTerminatedString = function() {
  152. var end = this._nullByteOffset();
  153. var value = this._buffer.toString(this._encoding, this._offset, end)
  154. this._offset = end + 1;
  155. return value;
  156. };
  157. Parser.prototype._nullByteOffset = function() {
  158. var offset = this._offset;
  159. while (this._buffer[offset] !== 0x00) {
  160. offset++;
  161. if (offset >= this._buffer.length) {
  162. throw new Error('Offset of null terminated string not found.');
  163. }
  164. }
  165. return offset;
  166. };
  167. Parser.prototype.parsePacketTerminatedString = function() {
  168. var length = this._packetEnd - this._offset;
  169. return this.parseString(length);
  170. };
  171. Parser.prototype.parseBuffer = function(length) {
  172. var response = new Buffer(length);
  173. this._buffer.copy(response, 0, this._offset, this._offset + length);
  174. this._offset += length;
  175. return response;
  176. };
  177. Parser.prototype.parseString = function(length) {
  178. var offset = this._offset;
  179. var end = offset + length;
  180. var value = this._buffer.toString(this._encoding, offset, end);
  181. this._offset = end;
  182. return value;
  183. };
  184. Parser.prototype.parseGeometryValue = function() {
  185. var buffer = this.parseLengthCodedBuffer();
  186. var offset = 4;
  187. if (buffer === null) {
  188. return null;
  189. }
  190. function parseGeometry() {
  191. var result = null;
  192. var byteOrder = buffer.readUInt8(offset); offset += 1;
  193. var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
  194. switch(wkbType) {
  195. case 1: // WKBPoint
  196. var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  197. var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  198. result = {x: x, y: y};
  199. break;
  200. case 2: // WKBLineString
  201. var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
  202. result = [];
  203. for(var i=numPoints;i>0;i--) {
  204. var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  205. var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  206. result.push({x: x, y: y});
  207. }
  208. break;
  209. case 3: // WKBPolygon
  210. var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
  211. result = [];
  212. for(var i=numRings;i>0;i--) {
  213. var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
  214. var line = [];
  215. for(var j=numPoints;j>0;j--) {
  216. var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  217. var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
  218. line.push({x: x, y: y});
  219. }
  220. result.push(line);
  221. }
  222. break;
  223. case 4: // WKBMultiPoint
  224. case 5: // WKBMultiLineString
  225. case 6: // WKBMultiPolygon
  226. case 7: // WKBGeometryCollection
  227. var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
  228. var result = [];
  229. for(var i=num;i>0;i--) {
  230. result.push(parseGeometry());
  231. }
  232. break;
  233. }
  234. return result;
  235. }
  236. return parseGeometry();
  237. }
  238. Parser.prototype.reachedPacketEnd = function() {
  239. return this._offset === this._packetEnd;
  240. };
  241. Parser.prototype._bytesRemaining = function() {
  242. return this._buffer.length - this._offset;
  243. };
  244. Parser.prototype._trackAndVerifyPacketNumber = function(number) {
  245. if (number !== this._nextPacketNumber) {
  246. var err = new Error(
  247. 'Packets out of order. Got: ' + number + ' ' +
  248. 'Expected: ' + this._nextPacketNumber
  249. );
  250. err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';
  251. throw err;
  252. }
  253. this.incrementPacketNumber();
  254. };
  255. Parser.prototype.incrementPacketNumber = function() {
  256. var currentPacketNumber = this._nextPacketNumber;
  257. this._nextPacketNumber = (this._nextPacketNumber + 1) % 256;
  258. return currentPacketNumber;
  259. };
  260. Parser.prototype.resetPacketNumber = function() {
  261. this._nextPacketNumber = 0;
  262. };
  263. Parser.prototype.packetLength = function() {
  264. return this._longPacketBuffers.reduce(function(length, buffer) {
  265. return length + buffer.length;
  266. }, this._packetHeader.length);
  267. };
  268. Parser.prototype._combineLongPacketBuffers = function() {
  269. if (!this._longPacketBuffers.length) {
  270. return;
  271. }
  272. var trailingPacketBytes = this._buffer.length - this._packetEnd;
  273. var length = this._longPacketBuffers.reduce(function(length, buffer) {
  274. return length + buffer.length;
  275. }, this._bytesRemaining());
  276. var combinedBuffer = new Buffer(length);
  277. var offset = this._longPacketBuffers.reduce(function(offset, buffer) {
  278. buffer.copy(combinedBuffer, offset);
  279. return offset + buffer.length;
  280. }, 0);
  281. this._buffer.copy(combinedBuffer, offset, this._offset);
  282. this._buffer = combinedBuffer;
  283. this._longPacketBuffers = [];
  284. this._offset = 0;
  285. this._packetEnd = this._buffer.length - trailingPacketBytes;
  286. };
  287. Parser.prototype._advanceToNextPacket = function() {
  288. this._offset = this._packetEnd;
  289. this._packetHeader = null;
  290. this._packetEnd = null;
  291. };