Home Manual Reference Source Test Repository

src/misc/protocols/PLLSP.js

'use strict';
/** @namespace misc.protocols */

/**
 * Packetized Low-Level Secured Protocol
 * <pre>
 * 0                   1                   2                   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +--------------+-------+-------+---------------+----------------+
 * |  Start Byte  | Header|Payload|  Packet type  | Payload length |
 * |    (0x3C)    |    Checksum   |               |                |
 * +-------------------------------- - - - - - - - - - - - - - - - +
 * :                     Payload Data continued ...                :
 * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 * |                     Payload Data continued ...                |
 * +---------------------------------------------------------------+
 * </pre>
 *
 * @memberof misc.protocols
 */
class PLLSP {
    static get STATE_READ_HEADER() { return 0; }
    static get STATE_READ_PAYLOAD() { return 1; }
    static get STATE_WAIT_START() { return 2; }

    /**
     * @param config.bufferSize {Number} - Length of receive buffer
     * @param config.onDataCallback {Function(data, type)} - Pointer on callback which be called when data is ready
     */
    constructor(config) {
        this.config = Object.assign({
            bufferSize: 150,
            onDataCallback: () => { }
        }, config);

        this._buffer = Buffer.alloc(this.config.bufferSize);
        this._bufferSize = 0;
        this._state = PLLSP.STATE_WAIT_START;

        this._headerChecksum = 0;
        this._payloadChecksum = 0;
        this._payloadLength = 0;
        this._packetType = 0;
        this._startByteIndex = 0;
    }

    /**
     * Generate packet
     * @param buffer {Buffer} - Payload for packet
     * @param type {Number} - Type for packet, if it is not defined int('U') will be used
     * @returns {Buffer} - Packet as buffer of bytes
     */
    generate(buffer, type) {
        if (type === undefined || type === null) {
            type = 'U'.charCodeAt(0);
        }

        let packet = Buffer.allocUnsafe(buffer.length + 4);

        // Copy payload
        buffer.copy(packet, 4);

        // Set start byte
        packet.writeUInt8(0x3C, 0);

        // Set checksum
        let headerChecksum = buffer.length + type;
        let payloadChecksum = 0;
        for (let i = 0; i < buffer.length; i++) {
            payloadChecksum += buffer.readUInt8(i);
        }
        packet.writeUInt8(((headerChecksum & 0x0F) << 4) | (payloadChecksum & 0x0F), 1);

        // Set packet type
        packet.writeUInt8(type, 2);

        // Set length
        packet.writeUInt8(buffer.length, 3);

        return packet;
    }

    /**
     * Push received data to receive buffer
     * @param chunkBuffer {Buffer} - Chunk buffer
     */
    push(chunkBuffer) {
        // Append `tempBuffer` to `buffer`
        if (chunkBuffer !== null) {
            if (chunkBuffer.length + this._bufferSize < this._buffer.length) {
                chunkBuffer.copy(this._buffer, this._bufferSize);
                this._bufferSize += chunkBuffer.length;
            } else {
                // To much junk, delete whole buffer
                chunkBuffer.copy(this._buffer, 0);
                this._bufferSize = chunkBuffer.length;
                this._state = PLLSP.STATE_WAIT_START
            }
        }

        // Try to find start byte and start pumping a packet with data
        if (this._state === PLLSP.STATE_WAIT_START) {
            this._startByteIndex = this._buffer.indexOf(0x3C);
            if (this._startByteIndex >= 0 && this._startByteIndex < this._bufferSize) {
                this._state = PLLSP.STATE_READ_HEADER;
            }
        }

        // Try to read a header
        if (this._state === PLLSP.STATE_READ_HEADER) {
            if (this._bufferSize - this._startByteIndex >= 4) {
                // Extract header from packet
                this._headerChecksum = (this._buffer.readUInt8(this._startByteIndex + 1) & 0xF0) >> 4;
                this._payloadChecksum = this._buffer.readUInt8(this._startByteIndex + 1) & 0x0F;
                this._packetType = this._buffer.readUInt8(this._startByteIndex + 2);
                this._payloadLength = this._buffer.readUInt8(this._startByteIndex + 3);

                // Check header checksum
                if (((this._payloadLength + this._packetType) & 0x0F) !== this._headerChecksum ||
                    this._payloadLength + 4 + this._startByteIndex >= this._buffer.length) {
                    this._bufferSize -= 1;
                    this._buffer.copy(this._buffer, 0, this._startByteIndex + 1, this._bufferSize + this._startByteIndex + 1);
                    this._state = PLLSP.STATE_WAIT_START;
                } else {
                    this._state = PLLSP.STATE_READ_PAYLOAD;
                }
            }
        }

        // Read the payload
        if (this._state === PLLSP.STATE_READ_PAYLOAD) {
            if (this._bufferSize - this._startByteIndex >= this._payloadLength + 4) {
                // Check payload checksum and if it is OK send event
                let generatedPayloadChecksum = 0;
                for (let i = 0; i < this._payloadLength; i++) {
                    generatedPayloadChecksum += this._buffer.readUInt8(i + 4 + this._startByteIndex);
                }
                if ((generatedPayloadChecksum & 0x0F) === this._payloadChecksum) {
                    let packetPayload = Buffer.allocUnsafe(this._payloadLength);
                    this._buffer.copy(packetPayload, 0, this._startByteIndex + 4, this._startByteIndex + this._payloadLength + 4);

                    this.config.onDataCallback(packetPayload, this._packetType);

                    // Prepare for next packet
                    this._bufferSize -= (this._payloadLength + 4);
                    this._buffer.copy(
                        this._buffer,
                        0,
                        this._startByteIndex + this._payloadLength + 4,
                        this._payloadLength + 4 + this._bufferSize);
                } else {
                    this._bufferSize -= 1;
                    this._buffer.copy(this._buffer, 0, this._startByteIndex + 1, this._bufferSize + this._startByteIndex + 1);
                }

                this._state = PLLSP.STATE_WAIT_START;

                // If there is two packets in single chunk try to export it
                this.push(null);
            }
        }
    }
}

module.exports = PLLSP;