ZWaveDeviceConnection.js
function(thisObject, data, rinfo) {
    if (typeof data === "undefined") {
        // no data; abort.
        return;
    }
    if (Object.prototype.toString.call(data) != "[object Uint8Array]") 
    {
        // no data; abort.
        return;
    }

    let HEADER_MINIMUM_SIZE = 2;
    let ZIP_WRAPPED_HEADER_MINIMUM_SIZE = 7;
    if (data.length < HEADER_MINIMUM_SIZE)
    {
        // not enough data for any packet; abort.
        return;
    }

    let isWrappedInZipFrame = false;

    // get the "initial" command class and command; in case of Z/IP-wrapped packets, these are the "header" (wrapping) command class and command
    let offset = 0;
    // command class
    let initialCommandClass = data[offset];
    offset += 1;
    // command
    let initialCommand = data[offset];
    offset += 1;

    if (initialCommandClass !== PrivateCommandClass.Zip || initialCommand !== ZipCommand.ZipPacket)
    {
        // mesage is not enclosed in a Z/IP packet; handle this special case first
        isWrappedInZipFrame = false;

        if (initialCommandClass === PrivateCommandClass.ZipNd && initialCommand === ZipNdCommand.NodeAdvertisement) {
            // allowed command
        } else {
            // if we do not recognize the command as an allowable "non-wrapped" incoming command/response, abort now
            return;
        }
    }
    else {
        // otherwise, the packet is a Z/IP packet; make sure it is at least large enough for a minimal Z/IP packet
        isWrappedInZipFrame = true;

        if (data.length < ZIP_WRAPPED_HEADER_MINIMUM_SIZE)
        {
            // not enough data for minimal Z/IP packet; abort.
            return;
        }
    }

    // parse message
    let ackResponse = null;
    let nackResponse = null;
    let nackWaiting = null;
    let nackQueueFull = null;
    let headerExtensionsIncluded = null;
    let sequenceNumber = null;
    let headerExtensionsLength = null;
    let commandClass = null;
    let command = null;
    let payload = null;
    if (isWrappedInZipFrame) {
        // AckRequest | AckResponse | NackResponse | NackFlags (Waiting, QueueFull, OptionError) | Reserved
        ackResponse = ((data[offset] & ZIP_PACKET_FLAGS0.ACK_RES) != 0);
        nackResponse = ((data[offset] & ZIP_PACKET_FLAGS0.NACK_RES) != 0);
        nackWaiting = ((data[offset] & ZIP_PACKET_FLAGS0.WAIT_RES) != 0);
        nackQueueFull = ((data[offset] & ZIP_PACKET_FLAGS0.NACK_QF) != 0);
        offset += 1;
        // HeaderExtIncluded | ZWaveCmdIncluded | MoreInformation | SecureOrigin | Reserved
        headerExtensionsIncluded = ((data[offset] & ZIP_PACKET_FLAGS1.HDR_EXT_INCL) != 0);
        offset += 1;
        // SequenceNumber
        sequenceNumber = data[offset];
        offset += 1;
        // Res | SourceEndPoint
        offset += 1;
        // BitAddress | DestinationEndpoint
        offset += 1;
        // Header Extensions (optional)
        headerExtensionsLength = 0;
        if (headerExtensionsIncluded) {
            // first byte of header extensions: total length
            headerExtensionsLength = data[offset];
            // NOTE: we are currently ignoring extensions

            offset += headerExtensionsLength;
        }
        //
        if (data.length >= ZIP_WRAPPED_HEADER_MINIMUM_SIZE + headerExtensionsLength + 2) {
            // command class
            commandClass = data[offset];
            offset += 1;
            // command
            command = data[offset];
            offset += 1;
        }
    } else {
        commandClass = initialCommandClass;
        command = initialCommand;
    }
    //
    // payload data
    payload = Buffer.alloc(data.length - offset);
    data.copy(payload, 0, offset, data.length);
    offset += payload.length;

    if (isWrappedInZipFrame) {
        // if this message is an ACK response, trigger any pending ACK 
        if (ackResponse) {
            let ackRegistration = thisObject.getAndRemoveAckRegistration(sequenceNumber);
            if (typeof ackRegistration !== "undefined") {
                if (typeof ackRegistration.timeoutObject !== "undefined") {
                    clearTimeout(ackRegistration.timeoutObject);
                }
                if (!thisObject.checkTimeoutHasOccured(ackRegistration.timeoutTimestamp)) {
                    ackRegistration.callback(true, false); // true = ACK, false = no Z/IP protocol timeout
                }
            }
        }
        else if (nackResponse) {
            if (nackQueueFull) {
                // queue full: retry sending message in ten seconds
                this.updateAckTimeout(sequenceNumber, 12000); // 10 seconds + 2 seconds wait time
                //
                thisObject.dtlsSocket.send(ackRegistration.data.msg, ackRegistration.data.offset, ackRegistration.data.length, ackRegistration.data.port, ackRegistration.data.host,
                    ackRegistration.data.pskIdentity, ackRegistration.data.pskPassword, ackRegistration.data.callback);            
            } else if (nackWaiting) {
                // add 90 seconds to the ACK timeout timer 
                thisObject.updateAckTimeout(sequenceNumber, 90000);
            } else {
                // message was rejected
                let ackRegistration = thisObject.getAndRemoveAckRegistration(sequenceNumber);
                if (typeof ackRegistration !== "undefined") {
                    if (typeof ackRegistration.timeoutObject !== "undefined") {
                        clearTimeout(ackRegistration.timeoutObject);
                    }
                    if (!thisObject.checkTimeoutHasOccured(ackRegistration.timeoutTimestamp)) {
                        ackRegistration.callback(false, false); // false = NAK, false = no Z/IP protocol timeout
                    }
                }       
            } 
        }
    }

    if (commandClass !== null && command !== null) {
        // for data packets: treat this message as a response and check it against the "pending command callbacks" list
        for (let iCallback = 0; iCallback < thisObject.pendingCommandCallbacks.length; iCallback++)
        {
            let commandCallback = thisObject.pendingCommandCallbacks[iCallback];
            if (commandCallback.commandClass === commandClass && commandCallback.command === command && commandCallback.isWrappedInZipFrame === isWrappedInZipFrame)
            {
                // command class and command matches; check timeout
                if (thisObject.checkTimeoutHasOccured(commandCallback.timeoutTimestamp))
                {
                    // command has timed out; skip this entry as its callback will automatically be removed at timeout
                    continue;
                }

                // command class and command match and timeout has not expired; check match identifier if one exists
                if (commandClass == ZWaveIP.CommandClass.NetworkManagementProxy && command == ZWaveIP.NetworkManagementProxyCommand.NodeListReport) {
                    if (payload.length < 1 || payload[0] != commandCallback.matchIdentifier) {
                        // NOTE: matchIdentifier is the sequenceNumber
                        // match identifier does not match; skip this entry
                        continue;
                    }
                }
                else if(commandClass == PrivateCommandClass.ZipNd && command == ZipNdCommand.NodeAdvertisement) {
                    if (payload.length < 2 || payload[1] != commandCallback.matchIdentifier) {
                        // NOTE: matchIdentifier is the nodeId
                        // match identifier does not match; skip this entry
                        continue;
                    }
                }
                // else...

                // if we reach here, the response matches; deactivate the callback timer, remove the callback entry and then call the callback
                //
                let callback = commandCallback.callback;
                // cancel the callback timeout timer
                clearTimeout(commandCallback.timeoutObject);
                // remove the callback from the pending callbacks list
                thisObject.pendingCommandCallbacks.splice(iCallback, 1);
                // call the callback
                commandCallback.callback({commandClass: commandClass, command: command, data: payload}, false /* did not time out */);
                break;
            }
        }
    }

    // if there was no callback registered, simply discard this packet.
}
function(thisObject, data, rinfo) {
    if (typeof data === "undefined") {
        // no data; abort.
        return;
    }
    if (Object.prototype.toString.call(data) != "[object Uint8Array]") 
    {
        // no data; abort.
        return;
    }

    let HEADER_MINIMUM_SIZE = 2;
    let ZIP_WRAPPED_HEADER_MINIMUM_SIZE = 7;
    if (data.length < HEADER_MINIMUM_SIZE)
    {
        // not enough data for any packet; abort.
        return;
    }

    let isWrappedInZipFrame = false;

    // get the "initial" command class and command; in case of Z/IP-wrapped packets, these are the "header" (wrapping) command class and command
    let offset = 0;
    // command class
    let initialCommandClass = data[offset];
    offset += 1;
    // command
    let initialCommand = data[offset];
    offset += 1;

    if (initialCommandClass !== PrivateCommandClass.Zip || initialCommand !== ZipCommand.ZipPacket)
    {
        // mesage is not enclosed in a Z/IP packet; handle this special case first
        isWrappedInZipFrame = false;

        if (initialCommandClass === PrivateCommandClass.ZipNd && initialCommand === ZipNdCommand.NodeAdvertisement) {
            // allowed command
        } else {
            // if we do not recognize the command as an allowable "non-wrapped" incoming command/response, abort now
            return;
        }
    }
    else {
        // otherwise, the packet is a Z/IP packet; make sure it is at least large enough for a minimal Z/IP packet
        isWrappedInZipFrame = true;

        if (data.length < ZIP_WRAPPED_HEADER_MINIMUM_SIZE)
        {
            // not enough data for minimal Z/IP packet; abort.
            return;
        }
    }

    // parse message
    let ackResponse = null;
    let nackResponse = null;
    let nackWaiting = null;
    let nackQueueFull = null;
    let headerExtensionsIncluded = null;
    let sequenceNumber = null;
    let headerExtensionsLength = null;
    let commandClass = null;
    let command = null;
    let payload = null;
    if (isWrappedInZipFrame) {
        // AckRequest | AckResponse | NackResponse | NackFlags (Waiting, QueueFull, OptionError) | Reserved
        ackResponse = ((data[offset] & ZIP_PACKET_FLAGS0.ACK_RES) != 0);
        nackResponse = ((data[offset] & ZIP_PACKET_FLAGS0.NACK_RES) != 0);
        nackWaiting = ((data[offset] & ZIP_PACKET_FLAGS0.WAIT_RES) != 0);
        nackQueueFull = ((data[offset] & ZIP_PACKET_FLAGS0.NACK_QF) != 0);
        offset += 1;
        // HeaderExtIncluded | ZWaveCmdIncluded | MoreInformation | SecureOrigin | Reserved
        headerExtensionsIncluded = ((data[offset] & ZIP_PACKET_FLAGS1.HDR_EXT_INCL) != 0);
        offset += 1;
        // SequenceNumber
        sequenceNumber = data[offset];
        offset += 1;
        // Res | SourceEndPoint
        offset += 1;
        // BitAddress | DestinationEndpoint
        offset += 1;
        // Header Extensions (optional)
        headerExtensionsLength = 0;
        if (headerExtensionsIncluded) {
            // first byte of header extensions: total length
            headerExtensionsLength = data[offset];
            // NOTE: we are currently ignoring extensions

            offset += headerExtensionsLength;
        }
        //
        if (data.length >= ZIP_WRAPPED_HEADER_MINIMUM_SIZE + headerExtensionsLength + 2) {
            // command class
            commandClass = data[offset];
            offset += 1;
            // command
            command = data[offset];
            offset += 1;
        }
    } else {
        commandClass = initialCommandClass;
        command = initialCommand;
    }
    //
    // payload data
    payload = Buffer.alloc(data.length - offset);
    data.copy(payload, 0, offset, data.length);
    offset += payload.length;

    if (isWrappedInZipFrame) {
        // if this message is an ACK response, trigger any pending ACK 
        if (ackResponse) {
            let ackRegistration = thisObject.getAndRemoveAckRegistration(sequenceNumber);
            if (typeof ackRegistration !== "undefined") {
                if (typeof ackRegistration.timeoutObject !== "undefined") {
                    clearTimeout(ackRegistration.timeoutObject);
                }
                if (!thisObject.checkTimeoutHasOccured(ackRegistration.timeoutTimestamp)) {
                    ackRegistration.callback(true, false); // true = ACK, false = no Z/IP protocol timeout
                }
            }
        }
        else if (nackResponse) {
            if (nackQueueFull) {
                // queue full: retry sending message in ten seconds
                this.updateAckTimeout(sequenceNumber, 12000); // 10 seconds + 2 seconds wait time
                //
                thisObject.dtlsSocket.send(ackRegistration.data.msg, ackRegistration.data.offset, ackRegistration.data.length, ackRegistration.data.port, ackRegistration.data.host,
                    ackRegistration.data.pskIdentity, ackRegistration.data.pskPassword, ackRegistration.data.callback);            
            } else if (nackWaiting) {
                // add 90 seconds to the ACK timeout timer 
                thisObject.updateAckTimeout(sequenceNumber, 90000);
            } else {
                // message was rejected
                let ackRegistration = thisObject.getAndRemoveAckRegistration(sequenceNumber);
                if (typeof ackRegistration !== "undefined") {
                    if (typeof ackRegistration.timeoutObject !== "undefined") {
                        clearTimeout(ackRegistration.timeoutObject);
                    }
                    if (!thisObject.checkTimeoutHasOccured(ackRegistration.timeoutTimestamp)) {
                        ackRegistration.callback(false, false); // false = NAK, false = no Z/IP protocol timeout
                    }
                }       
            } 
        }
    }

    if (commandClass !== null && command !== null) {
        // for data packets: treat this message as a response and check it against the "pending command callbacks" list
        for (let iCallback = 0; iCallback < thisObject.pendingCommandCallbacks.length; iCallback++)
        {
            let commandCallback = thisObject.pendingCommandCallbacks[iCallback];
            if (commandCallback.commandClass === commandClass && commandCallback.command === command && commandCallback.isWrappedInZipFrame === isWrappedInZipFrame)
            {
                // command class and command matches; check timeout
                if (thisObject.checkTimeoutHasOccured(commandCallback.timeoutTimestamp))
                {
                    // command has timed out; skip this entry as its callback will automatically be removed at timeout
                    continue;
                }

                // command class and command match and timeout has not expired; check match identifier if one exists
                if (commandClass == ZWaveIP.CommandClass.NetworkManagementProxy && command == ZWaveIP.NetworkManagementProxyCommand.NodeListReport) {
                    if (payload.length < 1 || payload[0] != commandCallback.matchIdentifier) {
                        // NOTE: matchIdentifier is the sequenceNumber
                        // match identifier does not match; skip this entry
                        continue;
                    }
                }
                else if(commandClass == PrivateCommandClass.ZipNd && command == ZipNdCommand.NodeAdvertisement) {
                    if (payload.length < 2 || payload[1] != commandCallback.matchIdentifier) {
                        // NOTE: matchIdentifier is the nodeId
                        // match identifier does not match; skip this entry
                        continue;
                    }
                }
                // else...

                // if we reach here, the response matches; deactivate the callback timer, remove the callback entry and then call the callback
                //
                let callback = commandCallback.callback;
                // cancel the callback timeout timer
                clearTimeout(commandCallback.timeoutObject);
                // remove the callback from the pending callbacks list
                thisObject.pendingCommandCallbacks.splice(iCallback, 1);
                // call the callback
                commandCallback.callback({commandClass: commandClass, command: command, data: payload}, false /* did not time out */);
                break;
            }
        }
    }

    // if there was no callback registered, simply discard this packet.
}
function ( thisObject , data , rinfo ) { if ( typeof data === "undefined" ) { // no data; abort. return ; } if ( Object . prototype . toString . call ( data ) != "[object Uint8Array]" ) { // no data; abort. return ; } let HEADER_MINIMUM_SIZE = 2 ; let ZIP_WRAPPED_HEADER_MINIMUM_SIZE = 7 ; if ( data . length < HEADER_MINIMUM_SIZE ) { // not enough data for any packet; abort. return ; } let isWrappedInZipFrame = false ; // get the "initial" command class and command; in case of Z/IP-wrapped packets, these are the "header" (wrapping) command class and command let offset = 0 ; // command class let initialCommandClass = data [ offset ] ; offset += 1 ; // command let initialCommand = data [ offset ] ; offset += 1 ; if ( initialCommandClass !== PrivateCommandClass . Zip || initialCommand !== ZipCommand . ZipPacket ) { // mesage is not enclosed in a Z/IP packet; handle this special case first isWrappedInZipFrame = false ; if ( initialCommandClass === PrivateCommandClass . ZipNd && initialCommand === ZipNdCommand . NodeAdvertisement ) { // allowed command } else { // if we do not recognize the command as an allowable "non-wrapped" incoming command/response, abort now return ; } } else { // otherwise, the packet is a Z/IP packet; make sure it is at least large enough for a minimal Z/IP packet isWrappedInZipFrame = true ; if ( data . length < ZIP_WRAPPED_HEADER_MINIMUM_SIZE ) { // not enough data for minimal Z/IP packet; abort. return ; } } // parse message let ackResponse = null ; let nackResponse = null ; let nackWaiting = null ; let nackQueueFull = null ; let headerExtensionsIncluded = null ; let sequenceNumber = null ; let headerExtensionsLength = null ; let commandClass = null ; let command = null ; let payload = null ; if ( isWrappedInZipFrame ) { // AckRequest | AckResponse | NackResponse | NackFlags (Waiting, QueueFull, OptionError) | Reserved ackResponse = ( ( data [ offset ] & ZIP_PACKET_FLAGS0 . ACK_RES ) != 0 ) ; nackResponse = ( ( data [ offset ] & ZIP_PACKET_FLAGS0 . NACK_RES ) != 0 ) ; nackWaiting = ( ( data [ offset ] & ZIP_PACKET_FLAGS0 . WAIT_RES ) != 0 ) ; nackQueueFull = ( ( data [ offset ] & ZIP_PACKET_FLAGS0 . NACK_QF ) != 0 ) ; offset += 1 ; // HeaderExtIncluded | ZWaveCmdIncluded | MoreInformation | SecureOrigin | Reserved headerExtensionsIncluded = ( ( data [ offset ] & ZIP_PACKET_FLAGS1 . HDR_EXT_INCL ) != 0 ) ; offset += 1 ; // SequenceNumber sequenceNumber = data [ offset ] ; offset += 1 ; // Res | SourceEndPoint offset += 1 ; // BitAddress | DestinationEndpoint offset += 1 ; // Header Extensions (optional) headerExtensionsLength = 0 ; if ( headerExtensionsIncluded ) { // first byte of header extensions: total length headerExtensionsLength = data [ offset ] ; // NOTE: we are currently ignoring extensions offset += headerExtensionsLength ; } // if ( data . length >= ZIP_WRAPPED_HEADER_MINIMUM_SIZE + headerExtensionsLength + 2 ) { // command class commandClass = data [ offset ] ; offset += 1 ; // command command = data [ offset ] ; offset += 1 ; } } else { commandClass = initialCommandClass ; command = initialCommand ; } // // payload data payload = Buffer . alloc ( data . length - offset ) ; data . copy ( payload , 0 , offset , data . length ) ; offset += payload . length ; if ( isWrappedInZipFrame ) { // if this message is an ACK response, trigger any pending ACK  if ( ackResponse ) { let ackRegistration = thisObject . getAndRemoveAckRegistration ( sequenceNumber ) ; if ( typeof ackRegistration !== "undefined" ) { if ( typeof ackRegistration . timeoutObject !== "undefined" ) { clearTimeout ( ackRegistration . timeoutObject ) ; } if ( ! thisObject . checkTimeoutHasOccured ( ackRegistration . timeoutTimestamp ) ) { ackRegistration . callback ( true , false ) ; // true = ACK, false = no Z/IP protocol timeout } } } else if ( nackResponse ) { if ( nackQueueFull ) { // queue full: retry sending message in ten seconds this . updateAckTimeout ( sequenceNumber , 12000 ) ; // 10 seconds + 2 seconds wait time // thisObject . dtlsSocket . send ( ackRegistration . data . msg , ackRegistration . data . offset , ackRegistration . data . length , ackRegistration . data . port , ackRegistration . data . host , ackRegistration . data . pskIdentity , ackRegistration . data . pskPassword , ackRegistration . data . callback ) ; } else if ( nackWaiting ) { // add 90 seconds to the ACK timeout timer  thisObject . updateAckTimeout ( sequenceNumber , 90000 ) ; } else { // message was rejected let ackRegistration = thisObject . getAndRemoveAckRegistration ( sequenceNumber ) ; if ( typeof ackRegistration !== "undefined" ) { if ( typeof ackRegistration . timeoutObject !== "undefined" ) { clearTimeout ( ackRegistration . timeoutObject ) ; } if ( ! thisObject . checkTimeoutHasOccured ( ackRegistration . timeoutTimestamp ) ) { ackRegistration . callback ( false , false ) ; // false = NAK, false = no Z/IP protocol timeout } } } } } if ( commandClass !== null && command !== null ) { // for data packets: treat this message as a response and check it against the "pending command callbacks" list for ( let iCallback = 0 ; iCallback < thisObject . pendingCommandCallbacks . length ; iCallback ++ ) { let commandCallback = thisObject . pendingCommandCallbacks [ iCallback ] ; if ( commandCallback . commandClass === commandClass && commandCallback . command === command && commandCallback . isWrappedInZipFrame === isWrappedInZipFrame ) { // command class and command matches; check timeout if ( thisObject . checkTimeoutHasOccured ( commandCallback . timeoutTimestamp ) ) { // command has timed out; skip this entry as its callback will automatically be removed at timeout continue ; } // command class and command match and timeout has not expired; check match identifier if one exists if ( commandClass == ZWaveIP . CommandClass . NetworkManagementProxy && command == ZWaveIP . NetworkManagementProxyCommand . NodeListReport ) { if ( payload . length < 1 || payload [ 0 ] != commandCallback . matchIdentifier ) { // NOTE: matchIdentifier is the sequenceNumber // match identifier does not match; skip this entry continue ; } } else if ( commandClass == PrivateCommandClass . ZipNd && command == ZipNdCommand . NodeAdvertisement ) { if ( payload . length < 2 || payload [ 1 ] != commandCallback . matchIdentifier ) { // NOTE: matchIdentifier is the nodeId // match identifier does not match; skip this entry continue ; } } // else... // if we reach here, the response matches; deactivate the callback timer, remove the callback entry and then call the callback // let callback = commandCallback . callback ; // cancel the callback timeout timer clearTimeout ( commandCallback . timeoutObject ) ; // remove the callback from the pending callbacks list thisObject . pendingCommandCallbacks . splice ( iCallback , 1 ) ; // call the callback commandCallback . callback ( { commandClass : commandClass , command : command , data : payload } , false /* did not time out */ ) ; break ; } } } // if there was no callback registered, simply discard this packet. }
* INCOMING (DECRYPTED) MESSAGE HANDLER **
* INCOMING ( DECRYPTED ) MESSAGE HANDLER **