123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- var PacketHeader = require('./PacketHeader');
- var BigNumber = require('bignumber.js');
- var Buffer = require('safe-buffer').Buffer;
- var BufferList = require('./BufferList');
- var MAX_PACKET_LENGTH = Math.pow(2, 24) - 1;
- var MUL_32BIT = Math.pow(2, 32);
- var PACKET_HEADER_LENGTH = 4;
- module.exports = Parser;
- function Parser(options) {
- options = options || {};
- this._supportBigNumbers = options.config && options.config.supportBigNumbers;
- this._buffer = Buffer.alloc(0);
- this._nextBuffers = new BufferList();
- this._longPacketBuffers = new BufferList();
- this._offset = 0;
- this._packetEnd = null;
- this._packetHeader = null;
- this._packetOffset = null;
- this._onError = options.onError || function(err) { throw err; };
- this._onPacket = options.onPacket || function() {};
- this._nextPacketNumber = 0;
- this._encoding = 'utf-8';
- this._paused = false;
- }
- Parser.prototype.write = function write(chunk) {
- this._nextBuffers.push(chunk);
- while (!this._paused) {
- var packetHeader = this._tryReadPacketHeader();
- if (!packetHeader) {
- break;
- }
- if (!this._combineNextBuffers(packetHeader.length)) {
- break;
- }
- this._parsePacket(packetHeader);
- }
- };
- Parser.prototype.append = function append(chunk) {
- if (!chunk || chunk.length === 0) {
- return;
- }
- // Calculate slice ranges
- var sliceEnd = this._buffer.length;
- var sliceStart = this._packetOffset === null
- ? this._offset
- : this._packetOffset;
- var sliceLength = sliceEnd - sliceStart;
- // Get chunk data
- var buffer = null;
- var chunks = !(chunk instanceof Array || Array.isArray(chunk)) ? [chunk] : chunk;
- var length = 0;
- var offset = 0;
- for (var i = 0; i < chunks.length; i++) {
- length += chunks[i].length;
- }
- if (sliceLength !== 0) {
- // Create a new Buffer
- buffer = Buffer.allocUnsafe(sliceLength + length);
- offset = 0;
- // Copy data slice
- offset += this._buffer.copy(buffer, 0, sliceStart, sliceEnd);
- // Copy chunks
- for (var i = 0; i < chunks.length; i++) {
- offset += chunks[i].copy(buffer, offset);
- }
- } else if (chunks.length > 1) {
- // Create a new Buffer
- buffer = Buffer.allocUnsafe(length);
- offset = 0;
- // Copy chunks
- for (var i = 0; i < chunks.length; i++) {
- offset += chunks[i].copy(buffer, offset);
- }
- } else {
- // Buffer is the only chunk
- buffer = chunks[0];
- }
- // Adjust data-tracking pointers
- this._buffer = buffer;
- this._offset = this._offset - sliceStart;
- this._packetEnd = this._packetEnd !== null
- ? this._packetEnd - sliceStart
- : null;
- this._packetOffset = this._packetOffset !== null
- ? this._packetOffset - sliceStart
- : null;
- };
- Parser.prototype.pause = function() {
- this._paused = true;
- };
- Parser.prototype.resume = function() {
- this._paused = false;
- // nextTick() to avoid entering write() multiple times within the same stack
- // which would cause problems as write manipulates the state of the object.
- process.nextTick(this.write.bind(this));
- };
- Parser.prototype.peak = function peak(offset) {
- return this._buffer[this._offset + (offset >>> 0)];
- };
- Parser.prototype.parseUnsignedNumber = function parseUnsignedNumber(bytes) {
- if (bytes === 1) {
- return this._buffer[this._offset++];
- }
- var buffer = this._buffer;
- var offset = this._offset + bytes - 1;
- var value = 0;
- if (bytes > 4) {
- var err = new Error('parseUnsignedNumber: Supports only up to 4 bytes');
- err.offset = (this._offset - this._packetOffset - 1);
- err.code = 'PARSER_UNSIGNED_TOO_LONG';
- throw err;
- }
- while (offset >= this._offset) {
- value = ((value << 8) | buffer[offset]) >>> 0;
- offset--;
- }
- this._offset += bytes;
- return value;
- };
- Parser.prototype.parseLengthCodedString = function() {
- var length = this.parseLengthCodedNumber();
- if (length === null) {
- return null;
- }
- return this.parseString(length);
- };
- Parser.prototype.parseLengthCodedBuffer = function() {
- var length = this.parseLengthCodedNumber();
- if (length === null) {
- return null;
- }
- return this.parseBuffer(length);
- };
- Parser.prototype.parseLengthCodedNumber = function parseLengthCodedNumber() {
- if (this._offset >= this._buffer.length) {
- var err = new Error('Parser: read past end');
- err.offset = (this._offset - this._packetOffset);
- err.code = 'PARSER_READ_PAST_END';
- throw err;
- }
- var bits = this._buffer[this._offset++];
- if (bits <= 250) {
- return bits;
- }
- switch (bits) {
- case 251:
- return null;
- case 252:
- return this.parseUnsignedNumber(2);
- case 253:
- return this.parseUnsignedNumber(3);
- case 254:
- break;
- default:
- var err = new Error('Unexpected first byte' + (bits ? ': 0x' + bits.toString(16) : ''));
- err.offset = (this._offset - this._packetOffset - 1);
- err.code = 'PARSER_BAD_LENGTH_BYTE';
- throw err;
- }
- var low = this.parseUnsignedNumber(4);
- var high = this.parseUnsignedNumber(4);
- var value;
- if (high >>> 21) {
- value = BigNumber(MUL_32BIT).times(high).plus(low).toString();
- if (this._supportBigNumbers) {
- return value;
- }
- var err = new Error(
- 'parseLengthCodedNumber: JS precision range exceeded, ' +
- 'number is >= 53 bit: "' + value + '"'
- );
- err.offset = (this._offset - this._packetOffset - 8);
- err.code = 'PARSER_JS_PRECISION_RANGE_EXCEEDED';
- throw err;
- }
- value = low + (MUL_32BIT * high);
- return value;
- };
- Parser.prototype.parseFiller = function(length) {
- return this.parseBuffer(length);
- };
- Parser.prototype.parseNullTerminatedBuffer = function() {
- var end = this._nullByteOffset();
- var value = this._buffer.slice(this._offset, end);
- this._offset = end + 1;
- return value;
- };
- Parser.prototype.parseNullTerminatedString = function() {
- var end = this._nullByteOffset();
- var value = this._buffer.toString(this._encoding, this._offset, end);
- this._offset = end + 1;
- return value;
- };
- Parser.prototype._nullByteOffset = function() {
- var offset = this._offset;
- while (this._buffer[offset] !== 0x00) {
- offset++;
- if (offset >= this._buffer.length) {
- var err = new Error('Offset of null terminated string not found.');
- err.offset = (this._offset - this._packetOffset);
- err.code = 'PARSER_MISSING_NULL_BYTE';
- throw err;
- }
- }
- return offset;
- };
- Parser.prototype.parsePacketTerminatedBuffer = function parsePacketTerminatedBuffer() {
- var length = this._packetEnd - this._offset;
- return this.parseBuffer(length);
- };
- Parser.prototype.parsePacketTerminatedString = function() {
- var length = this._packetEnd - this._offset;
- return this.parseString(length);
- };
- Parser.prototype.parseBuffer = function(length) {
- var response = Buffer.alloc(length);
- this._buffer.copy(response, 0, this._offset, this._offset + length);
- this._offset += length;
- return response;
- };
- Parser.prototype.parseString = function(length) {
- var offset = this._offset;
- var end = offset + length;
- var value = this._buffer.toString(this._encoding, offset, end);
- this._offset = end;
- return value;
- };
- Parser.prototype.parseGeometryValue = function() {
- var buffer = this.parseLengthCodedBuffer();
- var offset = 4;
- if (buffer === null || !buffer.length) {
- return null;
- }
- function parseGeometry() {
- var result = null;
- var byteOrder = buffer.readUInt8(offset); offset += 1;
- var wkbType = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
- switch (wkbType) {
- case 1: // WKBPoint
- var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- result = {x: x, y: y};
- break;
- case 2: // WKBLineString
- var numPoints = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
- result = [];
- for (var i = numPoints; i > 0; i--) {
- var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- result.push({x: x, y: y});
- }
- break;
- case 3: // WKBPolygon
- var numRings = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
- result = [];
- for (var i = numRings; i > 0; i--) {
- var numPoints = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
- var line = [];
- for (var j = numPoints; j > 0; j--) {
- var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
- line.push({x: x, y: y});
- }
- result.push(line);
- }
- break;
- case 4: // WKBMultiPoint
- case 5: // WKBMultiLineString
- case 6: // WKBMultiPolygon
- case 7: // WKBGeometryCollection
- var num = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
- var result = [];
- for (var i = num; i > 0; i--) {
- result.push(parseGeometry());
- }
- break;
- }
- return result;
- }
- return parseGeometry();
- };
- Parser.prototype.reachedPacketEnd = function() {
- return this._offset === this._packetEnd;
- };
- Parser.prototype.incrementPacketNumber = function() {
- var currentPacketNumber = this._nextPacketNumber;
- this._nextPacketNumber = (this._nextPacketNumber + 1) % 256;
- return currentPacketNumber;
- };
- Parser.prototype.resetPacketNumber = function() {
- this._nextPacketNumber = 0;
- };
- Parser.prototype.packetLength = function packetLength() {
- if (!this._packetHeader) {
- return null;
- }
- return this._packetHeader.length + this._longPacketBuffers.size;
- };
- Parser.prototype._combineNextBuffers = function _combineNextBuffers(bytes) {
- var length = this._buffer.length - this._offset;
- if (length >= bytes) {
- return true;
- }
- if ((length + this._nextBuffers.size) < bytes) {
- return false;
- }
- var buffers = [];
- var bytesNeeded = bytes - length;
- while (bytesNeeded > 0) {
- var buffer = this._nextBuffers.shift();
- buffers.push(buffer);
- bytesNeeded -= buffer.length;
- }
- this.append(buffers);
- return true;
- };
- Parser.prototype._combineLongPacketBuffers = function _combineLongPacketBuffers() {
- if (!this._longPacketBuffers.size) {
- return;
- }
- // Calculate bytes
- var remainingBytes = this._buffer.length - this._offset;
- var trailingPacketBytes = this._buffer.length - this._packetEnd;
- // Create buffer
- var buf = null;
- var buffer = Buffer.allocUnsafe(remainingBytes + this._longPacketBuffers.size);
- var offset = 0;
- // Copy long buffers
- while ((buf = this._longPacketBuffers.shift())) {
- offset += buf.copy(buffer, offset);
- }
- // Copy remaining bytes
- this._buffer.copy(buffer, offset, this._offset);
- this._buffer = buffer;
- this._offset = 0;
- this._packetEnd = this._buffer.length - trailingPacketBytes;
- this._packetOffset = 0;
- };
- Parser.prototype._parsePacket = function _parsePacket(packetHeader) {
- this._packetEnd = this._offset + packetHeader.length;
- this._packetOffset = this._offset;
- if (packetHeader.length === MAX_PACKET_LENGTH) {
- this._longPacketBuffers.push(this._buffer.slice(this._packetOffset, this._packetEnd));
- this._advanceToNextPacket();
- return;
- }
- this._combineLongPacketBuffers();
- var hadException = true;
- try {
- this._onPacket(packetHeader);
- hadException = false;
- } catch (err) {
- if (!err || typeof err.code !== 'string' || err.code.substr(0, 7) !== 'PARSER_') {
- throw err; // Rethrow non-MySQL errors
- }
- // Pass down parser errors
- this._onError(err);
- hadException = false;
- } finally {
- this._advanceToNextPacket();
- // If there was an exception, the parser while loop will be broken out
- // of after the finally block. So schedule a blank write to re-enter it
- // to continue parsing any bytes that may already have been received.
- if (hadException) {
- process.nextTick(this.write.bind(this));
- }
- }
- };
- Parser.prototype._tryReadPacketHeader = function _tryReadPacketHeader() {
- if (this._packetHeader) {
- return this._packetHeader;
- }
- if (!this._combineNextBuffers(PACKET_HEADER_LENGTH)) {
- return null;
- }
- this._packetHeader = new PacketHeader(
- this.parseUnsignedNumber(3),
- this.parseUnsignedNumber(1)
- );
- if (this._packetHeader.number !== this._nextPacketNumber) {
- var err = new Error(
- 'Packets out of order. Got: ' + this._packetHeader.number + ' ' +
- 'Expected: ' + this._nextPacketNumber
- );
- err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';
- err.fatal = true;
- this._onError(err);
- }
- this.incrementPacketNumber();
- return this._packetHeader;
- };
- Parser.prototype._advanceToNextPacket = function() {
- this._offset = this._packetEnd;
- this._packetHeader = null;
- this._packetEnd = null;
- this._packetOffset = null;
- };
|