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
366 lines
9.7 KiB
var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
|
|
var MAX_PACKET_LENGTH = Math.pow(2, 24) - 1;
|
|
var PacketHeader = require('./PacketHeader');
|
|
|
|
module.exports = Parser;
|
|
function Parser(options) {
|
|
options = options || {};
|
|
|
|
this._buffer = new Buffer(0);
|
|
this._longPacketBuffers = [];
|
|
this._offset = 0;
|
|
this._packetEnd = null;
|
|
this._packetHeader = null;
|
|
this._onPacket = options.onPacket || function() {};
|
|
this._nextPacketNumber = 0;
|
|
this._encoding = 'utf-8';
|
|
this._paused = false;
|
|
}
|
|
|
|
Parser.prototype.write = function(buffer) {
|
|
this.append(buffer);
|
|
|
|
while (true) {
|
|
if (this._paused) {
|
|
return;
|
|
}
|
|
|
|
if (!this._packetHeader) {
|
|
if (this._bytesRemaining() < 4) {
|
|
break;
|
|
}
|
|
|
|
this._packetHeader = new PacketHeader(
|
|
this.parseUnsignedNumber(3),
|
|
this.parseUnsignedNumber(1)
|
|
);
|
|
|
|
this._trackAndVerifyPacketNumber(this._packetHeader.number);
|
|
}
|
|
|
|
if (this._bytesRemaining() < this._packetHeader.length) {
|
|
break;
|
|
}
|
|
|
|
this._packetEnd = this._offset + this._packetHeader.length;
|
|
|
|
if (this._packetHeader.length === MAX_PACKET_LENGTH) {
|
|
this._longPacketBuffers.push(this._buffer.slice(this._offset, this._packetEnd));
|
|
|
|
this._advanceToNextPacket();
|
|
continue;
|
|
}
|
|
|
|
this._combineLongPacketBuffers();
|
|
|
|
// Try...finally to ensure exception safety. Unfortunately this is costing
|
|
// us up to ~10% performance in some benchmarks.
|
|
var hadException = true;
|
|
try {
|
|
this._onPacket(this._packetHeader);
|
|
hadException = false;
|
|
} finally {
|
|
this._advanceToNextPacket();
|
|
|
|
// If we had an exception, the parser while loop will be broken out
|
|
// of after the finally block. So we need to make sure 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.append = function(newBuffer) {
|
|
// If resume() is called, we don't pass a buffer to write()
|
|
if (!newBuffer) {
|
|
return;
|
|
}
|
|
|
|
var oldBuffer = this._buffer;
|
|
var bytesRemaining = this._bytesRemaining();
|
|
var newLength = bytesRemaining + newBuffer.length;
|
|
|
|
var combinedBuffer = (this._offset > newLength)
|
|
? oldBuffer.slice(0, newLength)
|
|
: new Buffer(newLength);
|
|
|
|
oldBuffer.copy(combinedBuffer, 0, this._offset);
|
|
newBuffer.copy(combinedBuffer, bytesRemaining);
|
|
|
|
this._buffer = combinedBuffer;
|
|
this._offset = 0;
|
|
};
|
|
|
|
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() {
|
|
return this._buffer[this._offset];
|
|
};
|
|
|
|
Parser.prototype.parseUnsignedNumber = function(bytes) {
|
|
var bytesRead = 0;
|
|
var value = 0;
|
|
|
|
while (bytesRead < bytes) {
|
|
var byte = this._buffer[this._offset++];
|
|
|
|
value += byte * Math.pow(256, bytesRead);
|
|
|
|
bytesRead++;
|
|
}
|
|
|
|
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() {
|
|
var byte = this._buffer[this._offset++];
|
|
|
|
if (byte <= 251) {
|
|
return (byte === 251)
|
|
? null
|
|
: byte;
|
|
}
|
|
|
|
var length;
|
|
if (byte === 252) {
|
|
length = 2;
|
|
} else if (byte === 253) {
|
|
length = 3;
|
|
} else if (byte === 254) {
|
|
length = 8;
|
|
} else {
|
|
throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
|
|
}
|
|
|
|
var value = 0;
|
|
for (var bytesRead = 0; bytesRead < length; bytesRead++) {
|
|
var byte = this._buffer[this._offset++];
|
|
value += Math.pow(256, bytesRead) * byte;
|
|
}
|
|
|
|
if (value >= IEEE_754_BINARY_64_PRECISION) {
|
|
throw new Error(
|
|
'parseLengthCodedNumber: JS precision range exceeded, ' +
|
|
'number is >= 53 bit: "' + value + '"'
|
|
);
|
|
}
|
|
|
|
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) {
|
|
throw new Error('Offset of null terminated string not found.');
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
};
|
|
|
|
Parser.prototype.parsePacketTerminatedString = function() {
|
|
var length = this._packetEnd - this._offset;
|
|
return this.parseString(length);
|
|
};
|
|
|
|
Parser.prototype.parseBuffer = function(length) {
|
|
var response = new Buffer(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) {
|
|
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._bytesRemaining = function() {
|
|
return this._buffer.length - this._offset;
|
|
};
|
|
|
|
Parser.prototype._trackAndVerifyPacketNumber = function(number) {
|
|
if (number !== this._nextPacketNumber) {
|
|
var err = new Error(
|
|
'Packets out of order. Got: ' + number + ' ' +
|
|
'Expected: ' + this._nextPacketNumber
|
|
);
|
|
|
|
err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';
|
|
|
|
throw err;
|
|
}
|
|
|
|
this.incrementPacketNumber();
|
|
};
|
|
|
|
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() {
|
|
return this._longPacketBuffers.reduce(function(length, buffer) {
|
|
return length + buffer.length;
|
|
}, this._packetHeader.length);
|
|
};
|
|
|
|
Parser.prototype._combineLongPacketBuffers = function() {
|
|
if (!this._longPacketBuffers.length) {
|
|
return;
|
|
}
|
|
|
|
var trailingPacketBytes = this._buffer.length - this._packetEnd;
|
|
|
|
var length = this._longPacketBuffers.reduce(function(length, buffer) {
|
|
return length + buffer.length;
|
|
}, this._bytesRemaining());
|
|
|
|
var combinedBuffer = new Buffer(length);
|
|
|
|
var offset = this._longPacketBuffers.reduce(function(offset, buffer) {
|
|
buffer.copy(combinedBuffer, offset);
|
|
return offset + buffer.length;
|
|
}, 0);
|
|
|
|
this._buffer.copy(combinedBuffer, offset, this._offset);
|
|
|
|
this._buffer = combinedBuffer;
|
|
this._longPacketBuffers = [];
|
|
this._offset = 0;
|
|
this._packetEnd = this._buffer.length - trailingPacketBytes;
|
|
};
|
|
|
|
Parser.prototype._advanceToNextPacket = function() {
|
|
this._offset = this._packetEnd;
|
|
this._packetHeader = null;
|
|
this._packetEnd = null;
|
|
};
|