import { validatePayload , ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { getEnumMemberName , TypedClassDecorator } from "@zwave-js/shared" ; enum S2ExtensionType { SPAN = 0x01 , MPAN = 0x02 , MGRP = 0x03 , MOS = 0x04 } const METADATA_S2ExtensionMap = Symbol ( "S2ExtensionMap" ) ; const METADATA_S2Extension = Symbol ( "S2Extension" ) ; type S2ExtensionMap = Map < S2ExtensionType , Constructable < Security2Extension > > ; export type Constructable < T extends Security2Extension > = typeof Security2Extension & { new ( options ) : T ; } ; /**
 * Looks up the S2 extension constructor for a given S2 extension type
 */ export function getS2ExtensionConstructor ( type ) { const map = Reflect . getMetadata ( METADATA_S2ExtensionMap , Security2Extension ) as S2ExtensionMap | undefined ; return map ?. get ( type ) ; } /**
 * Defines the command class associated with a Z-Wave message
 */ export function extensionType ( type ) { return ( extensionClass ) => { Reflect . defineMetadata ( METADATA_S2Extension , type , extensionClass ) ; const map = Reflect . getMetadata ( METADATA_S2ExtensionMap , Security2Extension ) || new Map ( ) ; map . set ( type , extensionClass as unknown as Constructable < Security2Extension > ) ; Reflect . defineMetadata ( METADATA_S2ExtensionMap , map , Security2Extension ) ; } ; } /**
 * Retrieves the command class defined for a Z-Wave message class
 */ export function getExtensionType < T extends Security2Extension > ( ext ) { const constr = ext . constructor ; const ret = Reflect . getMetadata ( METADATA_S2Extension , constr ) ; if ( ret == undefined ) { throw new ZWaveError ( `No S2 extension type defined for ${ constr . name }!` , ZWaveErrorCodes . CC_Invalid ) ; } return ret ; } interface Security2ExtensionCreationOptions { critical : boolean ; payload ? : Buffer ; } interface Security2ExtensionDeserializationOptions { data : Buffer ; } type Security2ExtensionOptions = Security2ExtensionCreationOptions | Security2ExtensionDeserializationOptions ; function gotDeserializationOptions ( options ) { return "data" in options && Buffer . isBuffer ( options . data ) ; } export class Security2Extension { public constructor ( options ) { if ( gotDeserializationOptions ( options ) ) { validatePayload ( options . data . length >= 2 ) ; const totalLength = options . data [ 0 ] ; validatePayload ( options . data . length >= totalLength ) ; const controlByte = options . data [ 1 ] ; this . moreToFollow = ! ! ( controlByte & 128 ) ; this . critical = ! ! ( controlByte & 64 ) ; this . type = controlByte & 63 ; this . payload = options . data . slice ( 2 , totalLength ) ; } else { this . type = getExtensionType ( this ) ; this . critical = options . critical ; this . payload = options . payload ?? Buffer . allocUnsafe ( 0 ) ; } } public type : S2ExtensionType ; public critical : boolean ; public readonly moreToFollow ? : boolean ; public payload : Buffer ; public isEncrypted ( ) { return false ; } public serialize ( moreToFollow ) { return Buffer . concat ( [ Buffer . from ( [ 2 + this . payload . length , ( moreToFollow ? 128 : 0 ) | ( this . critical ? 64 : 0 ) | ( this . type & 63 ) , ] ) , this . payload , ] ) ; } /** Returns the number of bytes the first extension in the buffer occupies */ public static getExtensionLength ( data ) { return data [ 0 ] ; } /** Returns the number of bytes the serialized extension will occupy */ public computeLength ( ) { return 2 + this . payload . length ; } /**
     * Retrieves the correct constructor for the next extension in the given Buffer.
     * It is assumed that the buffer has been checked beforehand
     */ public static getConstructor ( data ) { const type = data [ 1 ] & 63 ; return getS2ExtensionConstructor ( type ) ?? Security2Extension ; } /** Creates an instance of the S2 extension that is serialized in the given buffer */ public static from ( data ) { const Constructor = Security2Extension . getConstructor ( data ) ; const ret = new Constructor ( { data } ) ; return ret ; } public toLogEntry ( ) { let ret = `
· type: ${ getEnumMemberName ( S2ExtensionType , this . type ) }` ; if ( this . payload . length > 0 ) { ret += `
  payload: 0x${ this . payload . toString ( "hex" ) }` ; } return ret ; } } interface SPANExtensionOptions { senderEI : Buffer ; } @ extensionType ( S2ExtensionType . SPAN ) export class SPANExtension extends Security2Extension { public constructor ( options ) { if ( gotDeserializationOptions ( options ) ) { super ( options ) ; validatePayload ( this . payload . length === 16 ) ; this . senderEI = this . payload ; } else { super ( { critical : true } ) ; if ( options . senderEI . length !== 16 ) { throw new ZWaveError ( "The sender's entropy must be a 16-byte buffer!" , ZWaveErrorCodes . Argument_Invalid ) ; } this . senderEI = options . senderEI ; } } public senderEI : Buffer ; public serialize ( moreToFollow ) { this . payload = this . senderEI ; return super . serialize ( moreToFollow ) ; } public toLogEntry ( ) { let ret = super . toLogEntry ( ) . replace ( /^  payload:.+$/m , "" ) ; ret += `  sender EI: 0x${ this . senderEI . toString ( "hex" ) }` ; return ret ; } } interface MPANExtensionOptions { groupId : number ; innerMPANState : Buffer ; } @ extensionType ( S2ExtensionType . MPAN ) export class MPANExtension extends Security2Extension { public constructor ( options ) { if ( gotDeserializationOptions ( options ) ) { super ( options ) ; validatePayload ( this . payload . length === 17 ) ; this . groupId = this . payload [ 0 ] ; this . innerMPANState = this . payload . slice ( 1 ) ; } else { if ( options . innerMPANState . length !== 16 ) { throw new ZWaveError ( "The inner MPAN state must be a 16-byte buffer!" , ZWaveErrorCodes . Argument_Invalid ) ; } super ( { critical : true } ) ; this . groupId = options . groupId ; this . innerMPANState = options . innerMPANState ; } } public groupId : number ; public innerMPANState : Buffer ; public isEncrypted ( ) { return true ; } public serialize ( moreToFollow ) { this . payload = Buffer . concat ( [ Buffer . from ( [ this . groupId ] ) , this . innerMPANState , ] ) ; return super . serialize ( moreToFollow ) ; } public toLogEntry ( ) { let ret = super . toLogEntry ( ) . replace ( /^  payload:.+$/m , "" ) ; ret += `
  group ID: ${ this . groupId }
  MPAN state: 0x${ this . innerMPANState . toString ( "hex" ) }` ; return ret ; } } interface MGRPExtensionOptions { groupId : number ; } @ extensionType ( S2ExtensionType . MGRP ) export class MGRPExtension extends Security2Extension { public constructor ( options ) { if ( gotDeserializationOptions ( options ) ) { super ( options ) ; validatePayload ( this . payload . length === 1 ) ; this . groupId = this . payload [ 0 ] ; } else { super ( { critical : true } ) ; this . groupId = options . groupId ; } } public groupId : number ; public serialize ( moreToFollow ) { this . payload = Buffer . from ( [ this . groupId ] ) ; return super . serialize ( moreToFollow ) ; } public toLogEntry ( ) { let ret = super . toLogEntry ( ) . replace ( /^  payload:.+$/m , "" ) ; ret += `  group ID: ${ this . groupId }` ; return ret ; } } @ extensionType ( S2ExtensionType . MOS ) export class MOSExtension extends Security2Extension { public constructor ( options ? ) { if ( options && gotDeserializationOptions ( options ) ) { super ( options ) ; } else { super ( { critical : false } ) ; } } }