Parser.js 10 KB

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