'use strict'; 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;