mysql2
Version:
fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS
195 lines (178 loc) • 5.96 kB
JavaScript
var Buffer = require('safe-buffer').Buffer;
var Packet = require('./packets/packet.js');
function PacketParser(onPacket, packetHeaderLength) {
// 4 for normal packets, 7 for comprssed protocol packets
if (typeof packetHeaderLength == 'undefined') {
packetHeaderLength = 4;
}
// array of last payload chunks
// only used when current payload is not complete
this.buffer = [];
// total length of chunks on buffer
this.bufferLength = 0;
this.packetHeaderLength = packetHeaderLength;
// incomplete header state: number of header bytes received
this.headerLen = 0;
// expected payload length
this.length = 0;
this.largePacketParts = [];
this.firstPacketSequenceId = 0;
this.onPacket = onPacket;
this.execute = PacketParser.prototype.executeStart;
this._flushLargePacket = packetHeaderLength == 7
? this._flushLargePacket7
: this._flushLargePacket4;
}
function readPacketLength(b, off) {
var b0 = b[off];
var b1 = b[off + 1];
var b2 = b[off + 2];
if (b1 + b2 === 0) {
return b0;
}
return b0 + (b1 << 8) + (b2 << 16);
}
var MAX_PACKET_LENGTH = 16777215;
PacketParser.prototype._flushLargePacket4 = function _flushLargePacket() {
var numPackets = this.largePacketParts.length;
this.largePacketParts.unshift(Buffer.from([0, 0, 0, 0])); // insert header
var body = Buffer.concat(this.largePacketParts);
var packet = new Packet(this.firstPacketSequenceId, body, 0, body.length);
this.largePacketParts.length = 0;
packet.numPackets = numPackets;
this.onPacket(packet);
};
PacketParser.prototype._flushLargePacket7 = function _flushLargePacket() {
var numPackets = this.largePacketParts.length;
this.largePacketParts.unshift(Buffer.from([0, 0, 0, 0, 0, 0, 0])); // insert header
var body = Buffer.concat(this.largePacketParts);
this.largePacketParts.length = 0;
var packet = new Packet(this.firstPacketSequenceId, body, 0, body.length);
packet.numPackets = numPackets;
this.onPacket(packet);
};
PacketParser.prototype.executeStart = function executeStart(chunk) {
var start = 0;
var end = chunk.length;
while (end - start >= 3) {
this.length = readPacketLength(chunk, start);
if (end - start >= this.length + this.packetHeaderLength) {
// at least one full packet
var sequenceId = chunk[start + 3];
if (
this.length < MAX_PACKET_LENGTH &&
this.largePacketParts.length === 0
) {
this.onPacket(
new Packet(
sequenceId,
chunk,
start,
start + this.packetHeaderLength + this.length
)
);
} else {
// first large packet - remember it's id
if (this.largePacketParts.length === 0) {
this.firstPacketSequenceId = sequenceId;
}
this.largePacketParts.push(
chunk.slice(
start + this.packetHeaderLength,
start + this.packetHeaderLength + this.length
)
);
if (this.length < MAX_PACKET_LENGTH) {
this._flushLargePacket();
}
}
start += this.packetHeaderLength + this.length;
} else {
// payload is incomplete
this.buffer = [chunk.slice(start + 3, end)];
this.bufferLength = end - start - 3;
this.execute = PacketParser.prototype.executePayload;
return;
}
}
if (end - start > 0) {
// there is start of length header, but it's not full 3 bytes
this.headerLen = end - start; // 1 or 2 bytes
this.length = chunk[start];
if (this.headerLen == 2) {
this.length = chunk[start] + (chunk[start + 1] << 8);
this.execute = PacketParser.prototype.executeHeader3;
} else {
this.execute = PacketParser.prototype.executeHeader2;
}
}
};
PacketParser.prototype.executePayload = function executePayload(chunk) {
var start = 0;
var end = chunk.length;
var remainingPayload =
this.length - this.bufferLength + this.packetHeaderLength - 3;
if (end - start >= remainingPayload) {
// last chunk for payload
var payload = Buffer.allocUnsafe(this.length + this.packetHeaderLength);
var offset = 3;
for (var i = 0; i < this.buffer.length; ++i) {
this.buffer[i].copy(payload, offset);
offset += this.buffer[i].length;
}
chunk.copy(payload, offset, start, start + remainingPayload);
var sequenceId = payload[3];
if (this.length < MAX_PACKET_LENGTH && this.largePacketParts.length === 0) {
this.onPacket(
new Packet(
sequenceId,
payload,
0,
this.length + this.packetHeaderLength
)
);
} else {
// first large packet - remember it's id
if (this.largePacketParts.length === 0) {
this.firstPacketSequenceId = sequenceId;
}
this.largePacketParts.push(
payload.slice(
this.packetHeaderLength,
this.packetHeaderLength + this.length
)
);
if (this.length < MAX_PACKET_LENGTH) {
this._flushLargePacket();
}
}
this.buffer = [];
this.bufferLength = 0;
this.execute = PacketParser.prototype.executeStart;
start += remainingPayload;
if (end - start > 0) {
return this.execute(chunk.slice(start, end));
}
} else {
this.buffer.push(chunk);
this.bufferLength += chunk.length;
}
return null;
};
PacketParser.prototype.executeHeader2 = function executeHeader2(chunk) {
this.length += chunk[0] << 8;
if (chunk.length > 1) {
this.length += chunk[1] << 16;
this.execute = PacketParser.prototype.executePayload;
return this.executePayload(chunk.slice(2));
} else {
this.execute = PacketParser.prototype.executeHeader3;
}
return null;
};
PacketParser.prototype.executeHeader3 = function executeHeader3(chunk) {
this.length += chunk[0] << 16;
this.execute = PacketParser.prototype.executePayload;
return this.executePayload(chunk.slice(1));
};
module.exports = PacketParser;