Parser.js 12 KB

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