import { ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { FragmentType , NVM3_CODE_LARGE_SHIFT , NVM3_CODE_SMALL_SHIFT , NVM3_COUNTER_SIZE , NVM3_OBJ_FRAGTYPE_MASK , NVM3_OBJ_FRAGTYPE_SHIFT , NVM3_OBJ_HEADER_SIZE_LARGE , NVM3_OBJ_HEADER_SIZE_SMALL , NVM3_OBJ_KEY_MASK , NVM3_OBJ_KEY_SHIFT , NVM3_OBJ_LARGE_LEN_MASK , NVM3_OBJ_TYPE_MASK , NVM3_WORD_SIZE , ObjectType , } from "./consts" ; import { computeBergerCode , computeBergerCodeMulti , validateBergerCodeMulti , } from "./utils" ; export interface NVM3Object { type : ObjectType ; fragmentType : FragmentType ; key : number ; data ? : Buffer ; } export function readObject ( buffer , offset ) { let headerSize = 4 ; const hdr1 = buffer . readUInt32LE ( offset ) ; if ( hdr1 === 0xffffffff ) return ; const key = ( hdr1 >> NVM3_OBJ_KEY_SHIFT ) & NVM3_OBJ_KEY_MASK ; let objType = hdr1 & NVM3_OBJ_TYPE_MASK ; let fragmentLength = 0 ; let hdr2 ; const isLarge = objType === ObjectType . DataLarge || objType === ObjectType . CounterLarge ; if ( isLarge ) { hdr2 = buffer . readUInt32LE ( offset + 4 ) ; headerSize += 4 ; fragmentLength = hdr2 & NVM3_OBJ_LARGE_LEN_MASK ; } else if ( objType > ObjectType . DataSmall ) { fragmentLength = objType - ObjectType . DataSmall ; objType = ObjectType . DataSmall ; } else if ( objType === ObjectType . CounterSmall ) { fragmentLength = NVM3_COUNTER_SIZE ; } const fragmentType = isLarge ? ( hdr1 >>> NVM3_OBJ_FRAGTYPE_SHIFT ) & NVM3_OBJ_FRAGTYPE_MASK : FragmentType . None ; if ( isLarge ) { validateBergerCodeMulti ( [ hdr1 , hdr2 ! ] , 32 + NVM3_CODE_LARGE_SHIFT ) ; } else { validateBergerCodeMulti ( [ hdr1 ] , NVM3_CODE_SMALL_SHIFT ) ; } if ( buffer . length < offset + headerSize + fragmentLength ) { throw new ZWaveError ( "Incomplete object in buffer!" , ZWaveErrorCodes . NVM_InvalidFormat ) ; } let data ; if ( fragmentLength > 0 ) { data = buffer . slice ( offset + headerSize , offset + headerSize + fragmentLength ) ; } const alignedLength = ( fragmentLength + NVM3_WORD_SIZE - 1 ) & ~ ( NVM3_WORD_SIZE - 1 ) ; const bytesRead = headerSize + alignedLength ; const obj = { type : objType , fragmentType , key , data } ; return { object : obj , bytesRead } ; } export function readObjects ( buffer ) { let offset = 0 ; const objects = [ ] ; while ( offset < buffer . length ) { const result = readObject ( buffer , offset ) ; if ( ! result ) break ; const { object , bytesRead } = result ; objects . push ( object ) ; offset += bytesRead ; } return { objects , bytesRead : offset } ; } export function writeObject ( obj ) { const isLarge = obj . type === ObjectType . DataLarge || obj . type === ObjectType . CounterLarge ; const headerSize = isLarge ? NVM3_OBJ_HEADER_SIZE_LARGE : NVM3_OBJ_HEADER_SIZE_SMALL ; const dataLength = obj . data ?. length ?? 0 ; const ret = Buffer . allocUnsafe ( dataLength + headerSize ) ; if ( isLarge ) { let hdr2 = dataLength & NVM3_OBJ_LARGE_LEN_MASK ; const hdr1 = ( obj . type & NVM3_OBJ_TYPE_MASK ) | ( ( obj . key & NVM3_OBJ_KEY_MASK ) << NVM3_OBJ_KEY_SHIFT ) | ( ( obj . fragmentType & NVM3_OBJ_FRAGTYPE_MASK ) << NVM3_OBJ_FRAGTYPE_SHIFT ) ; const bergerCode = computeBergerCodeMulti ( [ hdr1 , hdr2 ] , 32 + NVM3_CODE_LARGE_SHIFT ) ; hdr2 |= bergerCode << NVM3_CODE_LARGE_SHIFT ; ret . writeInt32LE ( hdr1 , 0 ) ; ret . writeInt32LE ( hdr2 , 4 ) ; } else { let typeAndLen = obj . type ; if ( typeAndLen === ObjectType . DataSmall && dataLength > 0 ) { typeAndLen += dataLength ; } let hdr1 = ( typeAndLen & NVM3_OBJ_TYPE_MASK ) | ( ( obj . key & NVM3_OBJ_KEY_MASK ) << NVM3_OBJ_KEY_SHIFT ) ; const bergerCode = computeBergerCode ( hdr1 , NVM3_CODE_SMALL_SHIFT ) ; hdr1 |= bergerCode << NVM3_CODE_SMALL_SHIFT ; ret . writeInt32LE ( hdr1 , 0 ) ; } if ( obj . data ) { obj . data . copy ( ret , headerSize ) ; } return ret ; } export function fragmentLargeObject ( obj , maxFirstFragmentSizeWithHeader , maxFragmentSizeWithHeader ) { const ret = [ ] ; if ( obj . data ! . length + NVM3_OBJ_HEADER_SIZE_LARGE <= maxFirstFragmentSizeWithHeader ) { return [ obj ] ; } let offset = 0 ; while ( offset < obj . data ! . length ) { const fragmentSize = offset === 0 ? maxFirstFragmentSizeWithHeader - NVM3_OBJ_HEADER_SIZE_LARGE : maxFragmentSizeWithHeader - NVM3_OBJ_HEADER_SIZE_LARGE ; const data = obj . data ! . slice ( offset , offset + fragmentSize ) ; ret . push ( { type : obj . type , key : obj . key , fragmentType : offset === 0 ? FragmentType . First : data . length + NVM3_OBJ_HEADER_SIZE_LARGE < maxFragmentSizeWithHeader ? FragmentType . Last : FragmentType . Next , data } ) ; offset += fragmentSize ; } return ret ; } /**
 * Takes the raw list of objects from the pages ring buffer and compresses
 * them so that each object is only stored once.
 */ export function compressObjects ( objects ) { const ret = new Map < number , NVM3Object > ( ) ; outer : for ( let i = 0 ; i < objects . length ; i ++ ) { const obj = objects [ i ] ; if ( obj . type === ObjectType . Deleted ) { ret . delete ( obj . key ) ; continue ; } else if ( obj . fragmentType === FragmentType . None ) { ret . set ( obj . key , obj ) ; continue ; } else if ( obj . fragmentType !== FragmentType . First || ! obj . data ) { continue ; } const parts = [ obj . data ] ; for ( let j = i + 1 ; j < objects . length ; j ++ ) { const next = objects [ j ] ; if ( next . key !== obj . key || ! next . data ) { continue outer ; } else if ( next . fragmentType === FragmentType . Next ) { parts . push ( next . data ) ; } else if ( next . fragmentType === FragmentType . Last ) { parts . push ( next . data ) ; break ; } } ret . set ( obj . key , { key : obj . key , fragmentType : FragmentType . None , type : obj . type , data : Buffer . concat ( parts ) } ) ; } return ret ; }