194 lines
5.4 KiB
JavaScript
194 lines
5.4 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const PacketNodeEncoded = require('./packet-node-encoded');
|
||
|
const PacketIconvEncoded = require('./packet-node-iconv');
|
||
|
|
||
|
const Utils = require('../misc/utils');
|
||
|
|
||
|
/**
|
||
|
* MySQL packet parser
|
||
|
* see : https://mariadb.com/kb/en/library/0-packet/
|
||
|
*/
|
||
|
class PacketInputStream {
|
||
|
constructor(unexpectedPacket, receiveQueue, out, opts, info) {
|
||
|
this.unexpectedPacket = unexpectedPacket;
|
||
|
this.opts = opts;
|
||
|
this.receiveQueue = receiveQueue;
|
||
|
this.info = info;
|
||
|
this.out = out;
|
||
|
|
||
|
//in case packet is not complete
|
||
|
this.header = Buffer.allocUnsafe(4);
|
||
|
this.headerLen = 0;
|
||
|
this.packetLen = null;
|
||
|
this.remainingLen = null;
|
||
|
|
||
|
this.parts = null;
|
||
|
this.partsTotalLen = 0;
|
||
|
this.changeEncoding(this.opts.collation);
|
||
|
this.changeDebug(this.opts.logPackets, this.opts.debug);
|
||
|
this.opts.on('collation', this.changeEncoding.bind(this));
|
||
|
this.opts.on('debug', this.changeDebug.bind(this));
|
||
|
}
|
||
|
|
||
|
changeEncoding(collation) {
|
||
|
this.encoding = collation.charset;
|
||
|
this.packetConstructor = Buffer.isEncoding(this.encoding)
|
||
|
? PacketNodeEncoded
|
||
|
: PacketIconvEncoded;
|
||
|
}
|
||
|
|
||
|
changeDebug(logPackets, debug) {
|
||
|
this.logPackets = logPackets;
|
||
|
this.debug = debug;
|
||
|
this.receivePacket =
|
||
|
this.logPackets || this.debug ? this.receivePacketDebug : this.receivePacketBasic;
|
||
|
}
|
||
|
|
||
|
receivePacketDebug(packet) {
|
||
|
let cmd = this.currentCmd();
|
||
|
|
||
|
if (packet) {
|
||
|
const packetStr = Utils.log(this.opts, packet.buf, packet.pos, packet.end, this.header);
|
||
|
if (this.opts.logPackets) {
|
||
|
this.info.addPacket(
|
||
|
'<== conn:' +
|
||
|
(this.info.threadId ? this.info.threadId : -1) +
|
||
|
' ' +
|
||
|
(cmd
|
||
|
? cmd.onPacketReceive
|
||
|
? cmd.constructor.name + '.' + cmd.onPacketReceive.name
|
||
|
: cmd.constructor.name
|
||
|
: 'no command') +
|
||
|
' (' +
|
||
|
packet.pos +
|
||
|
',' +
|
||
|
packet.end +
|
||
|
'))\n' +
|
||
|
packetStr
|
||
|
);
|
||
|
}
|
||
|
if (this.opts.debug) {
|
||
|
console.log(
|
||
|
'<== conn:%d %s (%d,%d)\n%s',
|
||
|
this.info.threadId ? this.info.threadId : -1,
|
||
|
cmd
|
||
|
? cmd.onPacketReceive
|
||
|
? cmd.constructor.name + '.' + cmd.onPacketReceive.name
|
||
|
: cmd.constructor.name
|
||
|
: 'no command',
|
||
|
packet.pos,
|
||
|
packet.end,
|
||
|
packetStr
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!cmd) {
|
||
|
this.unexpectedPacket(packet);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
cmd.sequenceNo = this.header[3];
|
||
|
cmd.onPacketReceive(packet, this.out, this.opts, this.info);
|
||
|
if (!cmd.onPacketReceive) this.receiveQueue.shift();
|
||
|
}
|
||
|
|
||
|
receivePacketBasic(packet) {
|
||
|
let cmd = this.currentCmd();
|
||
|
if (!cmd) {
|
||
|
this.unexpectedPacket(packet);
|
||
|
return;
|
||
|
}
|
||
|
cmd.sequenceNo = this.header[3];
|
||
|
cmd.onPacketReceive(packet, this.out, this.opts, this.info);
|
||
|
if (!cmd.onPacketReceive) this.receiveQueue.shift();
|
||
|
}
|
||
|
|
||
|
resetHeader() {
|
||
|
this.remainingLen = null;
|
||
|
this.headerLen = 0;
|
||
|
}
|
||
|
|
||
|
currentCmd() {
|
||
|
let cmd;
|
||
|
while ((cmd = this.receiveQueue.peek())) {
|
||
|
if (cmd.onPacketReceive) return cmd;
|
||
|
this.receiveQueue.shift();
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
onData(chunk) {
|
||
|
let pos = 0;
|
||
|
let length;
|
||
|
const chunkLen = chunk.length;
|
||
|
|
||
|
do {
|
||
|
//read header
|
||
|
if (this.remainingLen) {
|
||
|
length = this.remainingLen;
|
||
|
} else if (this.headerLen === 0 && chunkLen - pos >= 4) {
|
||
|
this.header[0] = chunk[pos];
|
||
|
this.header[1] = chunk[pos + 1];
|
||
|
this.header[2] = chunk[pos + 2];
|
||
|
this.header[3] = chunk[pos + 3];
|
||
|
pos += 4;
|
||
|
this.headerLen = 4;
|
||
|
this.packetLen = this.header[0] + (this.header[1] << 8) + (this.header[2] << 16);
|
||
|
length = this.packetLen;
|
||
|
} else {
|
||
|
length = null;
|
||
|
while (chunkLen - pos > 0) {
|
||
|
this.header[this.headerLen++] = chunk[pos++];
|
||
|
if (this.headerLen === 4) {
|
||
|
this.packetLen = this.header[0] + (this.header[1] << 8) + (this.header[2] << 16);
|
||
|
length = this.packetLen;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (length) {
|
||
|
if (chunkLen - pos >= length) {
|
||
|
const buf = chunk.slice(pos, pos + length);
|
||
|
pos += length;
|
||
|
if (this.parts) {
|
||
|
this.parts.push(buf);
|
||
|
this.partsTotalLen += length;
|
||
|
|
||
|
if (this.packetLen < 0xffffff) {
|
||
|
let buf = Buffer.concat(this.parts, this.partsTotalLen);
|
||
|
this.parts = null;
|
||
|
const packet = new this.packetConstructor(buf, 0, this.partsTotalLen, this.encoding);
|
||
|
this.receivePacket(packet);
|
||
|
}
|
||
|
} else {
|
||
|
if (this.packetLen < 0xffffff) {
|
||
|
const packet = new this.packetConstructor(buf, 0, length, this.encoding);
|
||
|
this.receivePacket(packet);
|
||
|
} else {
|
||
|
this.parts = [buf];
|
||
|
this.partsTotalLen = length;
|
||
|
}
|
||
|
}
|
||
|
this.resetHeader();
|
||
|
} else {
|
||
|
const buf = chunk.slice(pos, chunkLen);
|
||
|
if (!this.parts) {
|
||
|
this.parts = [buf];
|
||
|
this.partsTotalLen = chunkLen - pos;
|
||
|
} else {
|
||
|
this.parts.push(buf);
|
||
|
this.partsTotalLen += chunkLen - pos;
|
||
|
}
|
||
|
this.remainingLen = length - (chunkLen - pos);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
} while (pos < chunkLen);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = PacketInputStream;
|