Spaces:
Running
Running
; | |
const Packet = require('./packets/packet.js'); | |
const MAX_PACKET_LENGTH = 16777215; | |
function readPacketLength(b, off) { | |
const b0 = b[off]; | |
const b1 = b[off + 1]; | |
const b2 = b[off + 2]; | |
if (b1 + b2 === 0) { | |
return b0; | |
} | |
return b0 + (b1 << 8) + (b2 << 16); | |
} | |
class PacketParser { | |
constructor(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; | |
} | |
_flushLargePacket4() { | |
const numPackets = this.largePacketParts.length; | |
this.largePacketParts.unshift(Buffer.from([0, 0, 0, 0])); // insert header | |
const body = Buffer.concat(this.largePacketParts); | |
const packet = new Packet(this.firstPacketSequenceId, body, 0, body.length); | |
this.largePacketParts.length = 0; | |
packet.numPackets = numPackets; | |
this.onPacket(packet); | |
} | |
_flushLargePacket7() { | |
const numPackets = this.largePacketParts.length; | |
this.largePacketParts.unshift(Buffer.from([0, 0, 0, 0, 0, 0, 0])); // insert header | |
const body = Buffer.concat(this.largePacketParts); | |
this.largePacketParts.length = 0; | |
const packet = new Packet(this.firstPacketSequenceId, body, 0, body.length); | |
packet.numPackets = numPackets; | |
this.onPacket(packet); | |
} | |
executeStart(chunk) { | |
let start = 0; | |
const end = chunk.length; | |
while (end - start >= 3) { | |
this.length = readPacketLength(chunk, start); | |
if (end - start >= this.length + this.packetHeaderLength) { | |
// at least one full packet | |
const 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; | |
} | |
} | |
} | |
executePayload(chunk) { | |
let start = 0; | |
const end = chunk.length; | |
const remainingPayload = | |
this.length - this.bufferLength + this.packetHeaderLength - 3; | |
if (end - start >= remainingPayload) { | |
// last chunk for payload | |
const payload = Buffer.allocUnsafe(this.length + this.packetHeaderLength); | |
let offset = 3; | |
for (let 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); | |
const 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; | |
} | |
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)); | |
} | |
this.execute = PacketParser.prototype.executeHeader3; | |
return null; | |
} | |
executeHeader3(chunk) { | |
this.length += chunk[0] << 16; | |
this.execute = PacketParser.prototype.executePayload; | |
return this.executePayload(chunk.slice(1)); | |
} | |
} | |
module.exports = PacketParser; | |