"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.STK_ENDPOINT: {
            result = Buffer.alloc(view.byteLength - pos);
            for (let i = 0; pos < view.byteLength; i++, pos++) {
                result[i] = 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) {
        logger_1.logger.debug(`Response APS-DATA.confirm seq: ${seqNr} but no matching request`, NS);
        return null;
    }
    const req = driver_1.apsBusyQueue[i];
    //remove from busyqueue
    driver_1.apsBusyQueue.splice(i, 1);
    let strstatus = "unknown";
    const hexstatus = `0x${confirmStatus.toString(16).padStart(2, "0")}`;
    if (confirmStatus in constants_1.ApsStatusCode) {
        strstatus = constants_1.ApsStatusCode[confirmStatus];
    }
    // TODO(mpi): This function should not change the queue and resolve/reject the request promise.
    // It should only do what the name says and remaining parts handled in upper callframe.
    if (confirmStatus === constants_1.ApsStatusCode.Success) {
        req.resolve(confirmStatus);
    }
    else {
        // if the request failed check if we can resend with APS ACK enabled
        const hasTimeLeft = Date.now() - req.ts < req.request.timeout;
        if (req.request.txOptions === 0 && hasTimeLeft && req.request.destAddrMode === constants_1.ApsAddressMode.Nwk) {
            req.request.txOptions = 0x04;
            logger_1.logger.debug(`Resend request with APS ACK enabled seq: ${seqNr}`, NS);
            driver_1.apsQueue.push(req);
        }
        else {
            req.reject(new Error(`Failed APS-DATA.request with confirm status: ${strstatus} (${hexstatus})`));
        }
    }
    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;
    }
}
// The ApplicationID sub-field contains the information about the application used by the GPD.
// ApplicationID = 0b000 indicates the GPD_ID field has the length of 4B and contains the GPD SrcID.
// ApplicationID = 0b010 indicates the GPD_ID field has the length of 8B and contains the GPD IEEE address.
var GpApplicationId;
(function (GpApplicationId) {
    GpApplicationId[GpApplicationId["SrcId4B"] = 0] = "SrcId4B";
    GpApplicationId[GpApplicationId["Lped"] = 1] = "Lped";
    GpApplicationId[GpApplicationId["Ieee8B"] = 2] = "Ieee8B";
})(GpApplicationId || (GpApplicationId = {}));
var GpSecurityLevel;
(function (GpSecurityLevel) {
    GpSecurityLevel[GpSecurityLevel["NoSecurity"] = 0] = "NoSecurity";
    // TODO(mpi): "Reserved" is noted in the available spec but the code defined it as follows:
    GpSecurityLevel[GpSecurityLevel["Reserved"] = 1] = "Reserved";
    GpSecurityLevel[GpSecurityLevel["FrameCounter4BMic4B"] = 2] = "FrameCounter4BMic4B";
    GpSecurityLevel[GpSecurityLevel["EncryptionFrameCounter4BMic4B"] = 3] = "EncryptionFrameCounter4BMic4B";
})(GpSecurityLevel || (GpSecurityLevel = {}));
var ZgpConstants;
(function (ZgpConstants) {
    ZgpConstants[ZgpConstants["GpNwkProtocolVersion"] = 3] = "GpNwkProtocolVersion";
    ZgpConstants[ZgpConstants["GpNwkDataFrame"] = 0] = "GpNwkDataFrame";
    ZgpConstants[ZgpConstants["GpNwkMaintenanceFrame"] = 1] = "GpNwkMaintenanceFrame";
    // biome-ignore lint/suspicious/noDuplicateEnumValues: fix-me
    ZgpConstants[ZgpConstants["GpMinMsduSize"] = 1] = "GpMinMsduSize";
    ZgpConstants[ZgpConstants["GpAutoCommissioningFlag"] = 64] = "GpAutoCommissioningFlag";
    ZgpConstants[ZgpConstants["GpNwkFrameControlExtensionFlag"] = 128] = "GpNwkFrameControlExtensionFlag";
})(ZgpConstants || (ZgpConstants = {}));
function parseGreenPowerDataIndication(view) {
    try {
        let srcId = 0;
        let frameCounter = 0;
        let commandId = 0;
        let commandFrameSize = 0;
        let commandFrame;
        const buf = new buffalo_1.Buffalo(Buffer.from(view.buffer));
        const _fwCommandId = buf.readUInt8();
        const seqNr = buf.readUInt8();
        const fwStatus = buf.readUInt8();
        if (fwStatus !== constants_1.CommandStatus.Success) {
            return null;
        }
        const _frameLength = buf.readUInt16();
        const _payloadLength = buf.readUInt16();
        // payload
        // implementation ported from deCONZ GP code
        const nwkFrameControl = buf.readUInt8();
        // check frame type
        const frameType = nwkFrameControl & 0x03;
        if (frameType !== ZgpConstants.GpNwkDataFrame && frameType !== ZgpConstants.GpNwkMaintenanceFrame) {
            return null;
        }
        // check green power protocol version
        if (((nwkFrameControl >> 2) & 0x03) !== ZgpConstants.GpNwkProtocolVersion) {
            return null;
        }
        // extended frame control
        let nwkExtFrameControl = 0;
        const hasExtensionFlag = nwkFrameControl & ZgpConstants.GpNwkFrameControlExtensionFlag;
        if (hasExtensionFlag) {
            nwkExtFrameControl = buf.readUInt8();
        }
        const options = nwkExtFrameControl | (nwkFrameControl << 8);
        const applicationId = nwkExtFrameControl & 7;
        const securityLevel = (nwkExtFrameControl >> 3) & 3;
        if (applicationId !== GpApplicationId.SrcId4B && applicationId !== GpApplicationId.Ieee8B) {
            return null; // NOTE: GpApplicationId.Lped (1) should be dropped as per spec
        }
        // The GPDSrcID field is present if the Frame Type sub-field is set to 0b00 and the ApplicationID sub-
        // field of the Extended NWK Frame Control field is set to 0b000 (or not present).
        if (applicationId === GpApplicationId.SrcId4B &&
            frameType === ZgpConstants.GpNwkDataFrame /*|| (frameType === ZgpConstants.GpNwkMaintenanceFrame && hasExtensionFlag) */) {
            srcId = buf.readUInt32();
        }
        // TODO(mpi): for applicationId == GpApplicationId.Ieee8B:
        // currently Ieee addresses aren't supported, do they actually appear?!
        // these need be extracted from MAC header which we don't have here (this is only the NWK payload).
        // frame counter filed
        frameCounter = 0;
        let micSize = 0;
        if (hasExtensionFlag && frameType === ZgpConstants.GpNwkDataFrame) {
            if (applicationId === GpApplicationId.Ieee8B) {
                const _endpoint = buf.readUInt8();
            }
            // If the SecurityLevel is set to 0b00, the SecurityKey sub-field is ignored on reception, and the
            // fields Security frame counter and MIC are not present.
            if (securityLevel === GpSecurityLevel.Reserved) {
                micSize = 2; // TODO(mpi) does this actually exists? Check recent specs!
            }
            else if (securityLevel === GpSecurityLevel.FrameCounter4BMic4B || securityLevel === GpSecurityLevel.EncryptionFrameCounter4BMic4B) {
                frameCounter = buf.readUInt32();
                micSize = 4;
            }
        }
        if (!buf.isMore()) {
            return null;
        }
        if (applicationId === GpApplicationId.SrcId4B || applicationId === GpApplicationId.Ieee8B) {
            commandId = buf.readUInt8();
            commandFrameSize = buf.getBuffer().length - buf.getPosition() - micSize;
            //logger.debug(`GPD payload length: ${commandFrameSize}, mic size: ${micSize}`, NS);
            if (commandFrameSize < 0) {
                logger_1.logger.error(`GPD payload length < 0: ${commandFrameSize}`, NS);
                return null;
            }
            commandFrame = Buffer.from(buf.readBuffer(commandFrameSize)); // copy
        }
        // NOTE(mpi): The old adapter treated (view.byteLength < 30) as notification, larger as commissioning?!
        // The controller thus rejected commissioning frames.
        const id = 0; // 0 = notification, 4 = commissioning
        if (commandFrame === undefined) {
            logger_1.logger.debug("GPD discard frame since commandFrame is null?!", NS);
            return null;
        }
        const ind = {
            seqNr,
            id,
            options,
            srcId,
            frameCounter,
            commandId,
            commandFrameSize,
            commandFrame,
        };
        // TODO(mpi): This only tracks one frame, might be a bit optimistic
        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