"use strict";
/* v8 ignore start */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.frameParserEvents = void 0;
const node_events_1 = require("node:events");
const buffalo_1 = require("../../../buffalo");
const logger_1 = require("../../../utils/logger");
const Zdo = __importStar(require("../../../zspec/zdo"));
const constants_1 = require("./constants");
const driver_1 = require("./driver");
const NS = "zh:deconz:frameparser";
const littleEndian = true;
const lastReceivedGpInd = { srcId: 0, commandId: 0, frameCounter: 0 };
exports.frameParserEvents = new node_events_1.EventEmitter();
function parseReadParameterResponse(view) {
    const seqNumber = view.getUint8(1);
    const status = view.getUint8(2);
    const parameterId = view.getUint8(7);
    let pos = 8;
    let result = null;
    if (status !== constants_1.CommandStatus.Success) {
        if (parameterId in constants_1.ParamId) {
            logger_1.logger.debug(`Received read parameter response for ${constants_1.ParamId[parameterId]}, seq: ${seqNumber}, status: ${status}`, NS);
        }
        return result;
    }
    switch (parameterId) {
        case constants_1.ParamId.MAC_ADDRESS: {
            result = view.getBigUint64(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.APS_TRUST_CENTER_ADDRESS: {
            result = view.getBigUint64(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.NWK_PANID: {
            result = view.getUint16(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.STK_PROTOCOL_VERSION: {
            result = view.getUint16(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.NWK_NETWORK_ADDRESS: {
            result = view.getUint16(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.NWK_EXTENDED_PANID: {
            result = view.getBigUint64(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.APS_USE_EXTENDED_PANID: {
            result = view.getBigUint64(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.STK_NETWORK_KEY: {
            result = Buffer.alloc(16);
            pos += 1; // key index
            for (let i = 0; i < 16; i++) {
                result[i] = view.getUint8(pos);
                pos += 1;
            }
            break;
        }
        case constants_1.ParamId.STK_CURRENT_CHANNEL: {
            result = view.getUint8(pos);
            break;
        }
        case constants_1.ParamId.APS_CHANNEL_MASK: {
            result = view.getUint32(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.STK_FRAME_COUNTER: {
            result = view.getUint32(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.STK_PERMIT_JOIN: {
            result = view.getUint8(pos);
            break;
        }
        case constants_1.ParamId.DEV_WATCHDOG_TTL: {
            result = view.getUint32(pos, littleEndian);
            break;
        }
        case constants_1.ParamId.STK_NWK_UPDATE_ID: {
            result = view.getUint8(pos);
            break;
        }
        default:
            //throw new Error(`unknown parameter id ${parameterId}`);
            logger_1.logger.debug(`unknown parameter id ${parameterId}`, NS);
            break;
    }
    if (parameterId in constants_1.ParamId) {
        let p = result;
        if (parameterId === constants_1.ParamId.STK_NETWORK_KEY) {
            // don't show in logs
            p = "<hidden>";
        }
        else if (typeof result === "bigint") {
            p = `0x${result.toString(16).padStart(16, "0")}`;
        }
        logger_1.logger.debug(`Received read parameter response for ${constants_1.ParamId[parameterId]}, seq: ${seqNumber}, status: ${status}, parameter: ${p}`, NS);
    }
    return result;
}
function parseReadFirmwareResponse(view) {
    const fw = view.getUint32(5, littleEndian);
    logger_1.logger.debug(`read firmware version response - version: 0x${fw.toString(16)}`, NS);
    return fw;
}
function parseDeviceStateResponse(view) {
    const deviceState = view.getUint8(5);
    exports.frameParserEvents.emit("deviceStateUpdated", deviceState);
    return deviceState;
}
function parseChangeNetworkStateResponse(view) {
    const status = view.getUint8(2);
    const state = view.getUint8(5);
    logger_1.logger.debug(`change network state - status: ${status} new state: ${state}`, NS);
    return state;
}
function parseApsConfirmResponse(view) {
    const buf = new buffalo_1.Buffalo(Buffer.from(view.buffer));
    const commandId = buf.readUInt8();
    const seqNr = buf.readUInt8();
    const status = buf.readUInt8();
    if (status !== constants_1.CommandStatus.Success) {
        logger_1.logger.debug(`Response APS-DATA.confirm seq: ${seqNr} status: ${constants_1.CommandStatus[status]} (error)`, NS);
        return null;
    }
    const frameLength = buf.readUInt16();
    const payloadLength = buf.readUInt16();
    // payload
    const deviceState = buf.readUInt8();
    const requestId = buf.readUInt8();
    const destAddrMode = buf.readUInt8();
    let destAddr64;
    let destAddr16;
    let destEndpoint;
    let destAddr = "";
    if (destAddrMode === constants_1.ApsAddressMode.Nwk) {
        destAddr16 = buf.readUInt16();
        destAddr = destAddr16.toString(16).padStart(4, "0");
        destEndpoint = buf.readUInt8();
    }
    else if (destAddrMode === constants_1.ApsAddressMode.Group) {
        destAddr16 = buf.readUInt16();
        destAddr = destAddr16.toString(16).padStart(4, "0");
    }
    else if (destAddrMode === constants_1.ApsAddressMode.Ieee) {
        destAddr64 = buf.readUInt64().toString(16).padStart(16, "0");
        destAddr = destAddr64;
        destEndpoint = buf.readUInt8();
    }
    else {
        logger_1.logger.debug(`Response APS-DATA.confirm seq: ${seqNr} unsupported address mode: ${destAddrMode}`, NS);
    }
    const srcEndpoint = buf.readUInt8();
    const confirmStatus = buf.readUInt8();
    // resolve APS-DATA.request promise
    const i = driver_1.apsBusyQueue.findIndex((r) => r.request && r.request.requestId === requestId);
    if (i < 0) {
        return null;
    }
    const req = driver_1.apsBusyQueue[i];
    let strstatus = "unknown";
    const hexstatus = `0x${confirmStatus.toString(16).padStart(2, "0")}`;
    if (confirmStatus in constants_1.ApsStatusCode) {
        strstatus = constants_1.ApsStatusCode[confirmStatus];
    }
    if (confirmStatus === constants_1.ApsStatusCode.Success) {
        req.resolve(confirmStatus);
    }
    else {
        req.reject(new Error(`Failed APS-DATA.request with confirm status: ${strstatus} (${hexstatus})`));
    }
    //remove from busyqueue
    driver_1.apsBusyQueue.splice(i, 1);
    logger_1.logger.debug(`APS-DATA.confirm  destAddr: 0x${destAddr} APS request id: ${requestId} confirm status: ${strstatus} ${hexstatus}`, NS);
    exports.frameParserEvents.emit("deviceStateUpdated", deviceState);
    return {
        commandId,
        seqNr,
        status,
        frameLength,
        payloadLength,
        deviceState,
        requestId,
        destAddrMode,
        destAddr16,
        destAddr64,
        destEndpoint,
        srcEndpoint,
        confirmStatus,
    };
}
// TODO(mpi): The ../buffalo/buffalo.ts already provides this, so we should reuse it instead of a own implementation?!
class UDataView {
    littleEndian = true;
    pos = 0;
    view;
    constructor(view, littleEndian) {
        this.view = view;
        this.littleEndian = littleEndian;
    }
    getI8() {
        if (this.pos + 1 <= this.view.byteLength) {
            this.pos += 1;
            return this.view.getInt8(this.pos - 1);
        }
        throw new RangeError();
    }
    getU8() {
        if (this.pos + 1 <= this.view.byteLength) {
            this.pos += 1;
            return this.view.getUint8(this.pos - 1);
        }
        throw new RangeError();
    }
    getU16() {
        if (this.pos + 2 <= this.view.byteLength) {
            this.pos += 2;
            return this.view.getUint16(this.pos - 2, this.littleEndian);
        }
        throw new RangeError();
    }
    getU32() {
        if (this.pos + 4 <= this.view.byteLength) {
            this.pos += 4;
            return this.view.getUint16(this.pos - 4, this.littleEndian);
        }
        throw new RangeError();
    }
    getU64() {
        if (this.pos + 8 <= this.view.byteLength) {
            this.pos += 8;
            return this.view.getBigUint64(this.pos - 8, this.littleEndian);
        }
        throw new RangeError();
    }
}
function parseApsDataIndicationResponse(inview) {
    // min 28 bytelength
    try {
        const uview = new UDataView(inview, true);
        const commandId = uview.getU8();
        const seqNr = uview.getU8();
        const status = uview.getU8();
        if (status !== constants_1.CommandStatus.Success) {
            logger_1.logger.debug(`Response APS-DATA.indication seq: ${seqNr} status: ${constants_1.CommandStatus[status]}`, NS);
            return null;
        }
        const frameLength = uview.getU16();
        const payloadLength = uview.getU16();
        //------ start of payload ----------------------------------
        const deviceState = uview.getU8();
        const destAddrMode = uview.getU8();
        let destAddr64;
        let destAddr16;
        let destAddr;
        if (destAddrMode === constants_1.ApsAddressMode.Ieee) {
            destAddr64 = uview.getU64().toString(16).padStart(16, "0");
            destAddr16 = 0xfffe;
            destAddr = destAddr64;
        }
        else if (destAddrMode === constants_1.ApsAddressMode.Nwk || destAddrMode === constants_1.ApsAddressMode.Group) {
            destAddr16 = uview.getU16();
            destAddr = destAddr16.toString(16);
        }
        else {
            throw new Error(`unsupported destination address mode: ${destAddrMode}`);
        }
        const destEndpoint = uview.getU8();
        const srcAddrMode = uview.getU8();
        let srcAddr64;
        let srcAddr16 = 0xfffe;
        let srcAddr;
        if (srcAddrMode === constants_1.ApsAddressMode.Nwk || srcAddrMode === constants_1.ApsAddressMode.NwkAndIeee) {
            srcAddr16 = uview.getU16();
            srcAddr = srcAddr16.toString(16);
            if (srcAddrMode === constants_1.ApsAddressMode.NwkAndIeee) {
                srcAddr64 = uview.getU64().toString(16).padStart(16, "0");
            }
        }
        else {
            throw new Error(`unsupported source address mode: ${srcAddrMode}`);
        }
        //  else if (srcAddrMode === PARAM.PARAM.addressMode.IEEE_ADDR) {
        //     srcAddr64 = uview.getU64().toString(16).padStart(16, '0');
        //     srcAddr = srcAddr64;
        // }
        const srcEndpoint = uview.getU8();
        const profileId = uview.getU16();
        const clusterId = uview.getU16();
        const asduLength = uview.getU16();
        const asdu = new Uint8Array(asduLength);
        for (let i = 0; i < asduLength; i++) {
            asdu[i] = uview.getU8();
        }
        // The following two bytes depends on protocol version 2 or 3
        // for now just discard
        uview.getU16();
        const lqi = uview.getU8();
        // version >= 2
        let rssi = 0;
        try {
            rssi = uview.getI8();
        }
        catch (_) { }
        logger_1.logger.debug(`Response APS-DATA.indication seq: ${seqNr} srcAddr: 0x${srcAddr} destAddr: 0x${destAddr} profile id: 0x${profileId.toString(16).padStart(4, "0")} cluster id: 0x${clusterId.toString(16).padStart(4, "0")} lqi: ${lqi}`, NS);
        //logger.debug(`Response payload: [${Array.from(asdu).map(x =>x.toString(16).padStart(2, '0')).join(' ')}]`, NS);
        exports.frameParserEvents.emit("deviceStateUpdated", deviceState);
        const asduPayload = Buffer.from(asdu);
        const response = {
            commandId,
            seqNr,
            status,
            frameLength,
            payloadLength,
            deviceState,
            destAddrMode,
            destAddr16,
            destAddr64,
            destEndpoint,
            srcAddrMode,
            srcAddr16,
            srcAddr64,
            srcEndpoint,
            profileId,
            clusterId,
            asduLength,
            asduPayload,
            lqi,
            rssi,
            zdo: profileId === Zdo.ZDO_PROFILE_ID ? Zdo.Buffalo.readResponse(true, clusterId, asduPayload) : undefined,
        };
        exports.frameParserEvents.emit("receivedDataPayload", response);
        return response;
    }
    catch (error) {
        logger_1.logger.debug(`Response APS-DATA.indication error: ${error}`, NS);
        return null;
    }
}
function parseApsDataRequestResponse(view) {
    try {
        const status = view.getUint8(2);
        const requestId = view.getUint8(8);
        const deviceState = view.getUint8(7);
        logger_1.logger.debug(`Response APS-DATA.request APS request id: ${requestId} status: ${constants_1.CommandStatus[status]}`, NS);
        exports.frameParserEvents.emit("deviceStateUpdated", deviceState);
        return deviceState;
    }
    catch (error) {
        logger_1.logger.debug(`parseEnqueueSendDataResponse - ${error}`, NS);
        return null;
    }
}
function parseWriteParameterResponse(view) {
    try {
        const status = view.getUint8(2);
        const parameterId = view.getUint8(7);
        if (parameterId in constants_1.ParamId) {
            // should always be true
            logger_1.logger.debug(`Write parameter response parameter: ${constants_1.ParamId[parameterId]}, status: ${constants_1.CommandStatus[status]}`, NS);
        }
        return parameterId;
    }
    catch (error) {
        logger_1.logger.debug(`parseWriteParameterResponse - ${error}`, NS);
        return null;
    }
}
function parseDeviceStateChangedNotification(view) {
    try {
        const deviceState = view.getUint8(5);
        exports.frameParserEvents.emit("deviceStateUpdated", deviceState);
        return deviceState;
    }
    catch (error) {
        logger_1.logger.debug(`parsedeviceStateUpdated - ${error}`, NS);
        return null;
    }
}
function parseGreenPowerDataIndication(view) {
    try {
        let id;
        let rspId;
        let options;
        let srcId;
        let frameCounter;
        let commandId;
        let commandFrameSize;
        let commandFrame;
        const seqNr = view.getUint8(1);
        if (view.byteLength < 30) {
            logger_1.logger.debug("GP data notification", NS);
            id = 0x00; // 0 = notification, 4 = commissioning
            rspId = 0x01; // 1 = pairing, 2 = commissioning
            options = 0;
            view.getUint16(7, littleEndian); // frame ctrl field(7) ext.fcf(8)
            srcId = view.getUint32(9, littleEndian);
            frameCounter = view.getUint32(13, littleEndian);
            commandId = view.getUint8(17);
            commandFrameSize = view.byteLength - 18 - 6; // cut 18 from begin and 4 (sec mic) and 2 from end (cfc)
            commandFrame = Buffer.from(view.buffer.slice(18, commandFrameSize + 18));
        }
        else {
            logger_1.logger.debug("GP commissioning notification", NS);
            id = 0x04; // 0 = notification, 4 = commissioning
            rspId = 0x01; // 1 = pairing, 2 = commissioning
            options = view.getUint16(14, littleEndian); // opt(14) ext.opt(15)
            srcId = view.getUint32(8, littleEndian);
            frameCounter = view.getUint32(36, littleEndian);
            commandId = view.getUint8(12);
            commandFrameSize = view.byteLength - 13 - 2; // cut 13 from begin and 2 from end (cfc)
            commandFrame = Buffer.from(view.buffer.slice(13, commandFrameSize + 13));
        }
        const ind = {
            rspId,
            seqNr,
            id,
            options,
            srcId,
            frameCounter,
            commandId,
            commandFrameSize,
            commandFrame,
        };
        if (!(lastReceivedGpInd.srcId === srcId && lastReceivedGpInd.commandId === commandId && lastReceivedGpInd.frameCounter === frameCounter)) {
            lastReceivedGpInd.srcId = srcId;
            lastReceivedGpInd.commandId = commandId;
            lastReceivedGpInd.frameCounter = frameCounter;
            //logger.debug(`GP_DATA_INDICATION - src id: ${srcId} cmd id: ${commandId} frameCounter: ${frameCounter}`, NS);
            logger_1.logger.debug(`GP_DATA_INDICATION - src id: 0x${srcId.toString(16)} cmd id: 0x${commandId.toString(16)} frameCounter: 0x${frameCounter.toString(16)}`, NS);
            exports.frameParserEvents.emit("receivedGreenPowerIndication", ind);
        }
        return ind;
    }
    catch (error) {
        logger_1.logger.debug(`GREEN_POWER INDICATION - ${error}`, NS);
        return null;
    }
}
function parseMacPollCommand(_view) {
    //logger.debug("Received command MAC_POLL", NS);
    return constants_1.FirmwareCommand.MacPollIndication;
}
function parseBeaconRequest(_view) {
    logger_1.logger.debug("Received Beacon Request", NS);
    return constants_1.FirmwareCommand.Beacon;
}
function parseDebugLog(view) {
    let dbg = "";
    const buf = new buffalo_1.Buffalo(Buffer.from(view.buffer));
    /* const commandId = */ buf.readUInt8();
    /* const seqNr = */ buf.readUInt8();
    const status = buf.readUInt8();
    if (status !== constants_1.CommandStatus.Success) {
        // unlikely
        return null;
    }
    /* const frameLength = */ buf.readUInt16();
    const payloadLength = buf.readUInt16();
    for (let i = 0; i < payloadLength && buf.isMore(); i++) {
        const ch = buf.readUInt8();
        if (ch >= 32 && ch <= 127) {
            dbg += String.fromCharCode(ch);
        }
    }
    if (dbg.length !== 0) {
        logger_1.logger.debug(`firmware log: ${dbg}`, NS);
    }
    return null;
}
function parseUnknownCommand(view) {
    const id = view.getUint8(0);
    if (id in constants_1.FirmwareCommand) {
        logger_1.logger.debug(`received unsupported command: ${constants_1.FirmwareCommand[id]} id: 0x${id.toString(16).padStart(2, "0")}`, NS);
    }
    else {
        logger_1.logger.debug(`received unknown command: id: 0x${id.toString(16).padStart(2, "0")}`, NS);
    }
    return id;
}
function getParserForCommandId(id) {
    switch (id) {
        case constants_1.FirmwareCommand.ReadParameter:
            return parseReadParameterResponse;
        case constants_1.FirmwareCommand.WriteParameter:
            return parseWriteParameterResponse;
        case constants_1.FirmwareCommand.FirmwareVersion:
            return parseReadFirmwareResponse;
        case constants_1.FirmwareCommand.Status:
            return parseDeviceStateResponse;
        case constants_1.FirmwareCommand.ApsDataIndication:
            return parseApsDataIndicationResponse;
        case constants_1.FirmwareCommand.ApsDataRequest:
            return parseApsDataRequestResponse;
        case constants_1.FirmwareCommand.ApsDataConfirm:
            return parseApsConfirmResponse;
        case constants_1.FirmwareCommand.StatusChangeIndication:
            return parseDeviceStateChangedNotification;
        case constants_1.FirmwareCommand.ChangeNetworkState:
            return parseChangeNetworkStateResponse;
        case constants_1.FirmwareCommand.ZgpDataIndication:
            return parseGreenPowerDataIndication;
        case constants_1.FirmwareCommand.MacPollIndication:
            return parseMacPollCommand;
        case constants_1.FirmwareCommand.Beacon:
            return parseBeaconRequest;
        case constants_1.FirmwareCommand.DebugLog:
            return parseDebugLog;
        default:
            return parseUnknownCommand;
        //throw new Error(`unknown command id ${id}`);
    }
}
function processFrame(frame) {
    const [seqNumber, status, command, commandId] = parseFrame(frame);
    // logger.debug(`Process frame with cmd: 0x${commandId.toString(16).padStart(2,'0')}, seq: ${seqNumber} status: ${status}`, NS);
    let queue = driver_1.busyQueue;
    if (commandId === constants_1.FirmwareCommand.ApsDataRequest) {
        queue = driver_1.apsBusyQueue;
    }
    const i = queue.findIndex((r) => r.seqNumber === seqNumber && r.commandId === commandId);
    if (i < 0) {
        return;
    }
    const req = queue[i];
    if (commandId === constants_1.FirmwareCommand.ApsDataRequest) {
        if (status === constants_1.CommandStatus.Success) {
            // wait for APS-DATA.confirm to arrive
            return;
        }
        // TODO(mpi): Within the timeout we should reschedule the APS-DATA.request (given that network state = connected)
        // continue to reject as there will be no APS-DATA.confirm
    }
    //remove from busyqueue
    queue.splice(i, 1);
    if (status === constants_1.CommandStatus.Success) {
        req.resolve(command);
    }
    else if (status === constants_1.CommandStatus.Unsupported && commandId === constants_1.FirmwareCommand.ReadParameter) {
        // resolve anyway to let higher layer handle unsupported
        req.resolve(command);
    }
    else {
        let cmdName;
        if (commandId in constants_1.FirmwareCommand) {
            cmdName = constants_1.FirmwareCommand[commandId];
        }
        else {
            cmdName = `0x${commandId.toString(16).padStart(2, "0")}`;
        }
        req.reject(new Error(`Command ${cmdName} failed with status: ${constants_1.CommandStatus[status]}`));
    }
}
function parseFrame(frame) {
    // at this point frame.buffer.length is at least 5 bytes long
    const view = new DataView(frame.buffer);
    const commandId = view.getUint8(0);
    const seqNumber = view.getUint8(1);
    const status = view.getUint8(2);
    const parser = getParserForCommandId(commandId);
    return [seqNumber, status, parser(view), commandId];
}
exports.default = processFrame;
//# sourceMappingURL=frameParser.js.map