UNPKG

netkitty

Version:
870 lines (861 loc) 51 kB
import {ProtocolJSONSchema} from '../../schema/ProtocolJSONSchema' import {BaseHeader} from '../abstracts/BaseHeader' import TLV from 'node-tlv' import { HexToFloat32, HexToInt16, HexToInt32, HexToInt64, HexToInt8, HexToUInt16, HexToUInt32, HexToUInt8 } from '../../helper/HexToNumber' import { Float32ToBERHex, Int16ToBERHex, Int32ToBERHex, Int64ToBERHex, Int8ToBERHex, UInt16ToBERHex, UInt32ToBERHex, UInt8ToBERHex } from '../../helper/NumberToBERHex' import {UInt16ToHex} from '../../helper/NumberToHex' import {StringContentEncodingEnum} from '../lib/StringContentEncodingEnum' import {UInt32ToBERBuffer} from '../../helper/NumberToBERBuffer' import {BufferToUInt16} from '../../helper/BufferToNumber' import {UInt16ToBuffer} from '../../helper/NumberToBuffer' import {GetBERIntegerLengthFromBuffer} from '../lib/GetBERIntegerLengthFromBuffer' enum ItemDataType { Boolean = 'Boolean', INT8 = 'INT8', INT16 = 'INT16', INT32 = 'INT32', INT64 = 'INT64', INT8U = 'INT8U', INT16U = 'INT16U', INT32U = 'INT32U', FLOAT32 = 'FLOAT32', CODEDENUM = 'CODED-ENUM', OCTETSTRING = 'OCTET-STRING', VISIBLESTRING = 'VISIBLE-STRING', TimeStamp = 'TimeStamp', Quality = 'Quality', Structure = 'Structure' } type BooleanDataItem = { dataType: ItemDataType.Boolean value: boolean } type INT8DataItem = { dataType: ItemDataType.INT8 value: number } type INT16DataItem = { dataType: ItemDataType.INT16 value: number } type INT32DataItem = { dataType: ItemDataType.INT32 value: number } type INT64DataItem = { dataType: ItemDataType.INT64 value: bigint } type INT8UDataItem = { dataType: ItemDataType.INT8U value: number } type INT16UDataItem = { dataType: ItemDataType.INT16U value: number } type INT32UDataItem = { dataType: ItemDataType.INT32U value: number } type FLOAT32DataItem = { dataType: ItemDataType.FLOAT32 value: number } type CODEDENUMDataItem = { dataType: ItemDataType.CODEDENUM value: number } type OCTETSTRINGDataItem = { dataType: ItemDataType.OCTETSTRING value: string } type VISIBLESTRINGDataItem = { dataType: ItemDataType.VISIBLESTRING value: string } type TimeStampDataItem = { dataType: ItemDataType.TimeStamp value: string } type QualityDataItem = { dataType: ItemDataType.Quality value: string } type StructureDataItem = { dataType: ItemDataType.Structure value: DataItem[] } type DataItem = BooleanDataItem | INT8DataItem | INT16DataItem | INT32DataItem | INT64DataItem | INT8UDataItem | INT16UDataItem | INT32UDataItem | FLOAT32DataItem | CODEDENUMDataItem | OCTETSTRINGDataItem | VISIBLESTRINGDataItem | TimeStampDataItem | QualityDataItem | StructureDataItem export class Goose extends BaseHeader { protected TLVInstance: TLV protected TLVChild: TLV[] = [] public SCHEMA: ProtocolJSONSchema = { type: 'object', properties: { appid: { type: 'integer', minimum: 0, maximum: 65535, label: 'APPID', decode: (): void => { this.instance.appid.setValue(BufferToUInt16(this.readBytes(0, 2))) }, encode: (): void => { const APPID: number = this.instance.appid.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) this.instance.appid.setValue(APPID) this.writeBytes(0, UInt16ToBuffer(APPID)) } }, length: { type: 'integer', minimum: 0, maximum: 65535, label: 'Length', decode: (): void => { this.instance.length.setValue(BufferToUInt16(this.readBytes(2, 2))) }, encode: (): void => { const length: number = this.instance.length.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (length > 0) { this.instance.length.setValue(length) this.writeBytes(2, UInt16ToBuffer(length)) } else { this.addPostSelfEncodeHandler((): void => { const finalLength: number = parseInt(this.instance.length.getValue().toString()) this.writeBytes(2, UInt16ToBuffer(finalLength)) }) } } }, reserved1: { type: 'object', label: 'Reserved1', properties: { simulated: { type: 'boolean', label: 'Simulated', decode: (): void => { this.instance.reserved1.simulated.setValue(!!this.readBits(4, 2, 0, 1)) }, encode: (): void => { const simulated: boolean = this.instance.reserved1.simulated.getValue(false) this.writeBits(4, 2, 0, 1, simulated ? 1 : 0) } }, reserved: { type: 'number', minimum: 0, maximum: 32767, label: 'Reserved', decode: (): void => { this.instance.reserved1.reserved.setValue(this.readBits(4, 2, 1, 16)) }, encode: (): void => { const reserved: number = this.instance.reserved1.reserved.getValue(0) this.writeBits(4, 2, 1, 16, reserved) } } } }, reserved2: { type: 'object', label: 'Reserved2', properties: { reserved: { type: 'number', minimum: 0, maximum: 65535, label: 'Reserved', decode: (): void => { this.instance.reserved2.reserved.setValue(this.readBits(4, 2, 0, 16)) }, encode: (): void => { const reserved: number = this.instance.reserved2.reserved.getValue(0) this.writeBits(4, 2, 0, 16, reserved) } } } }, goosePdu: { type: 'object', label: 'GOOSE PDU', decode: (): void => { let readTLVBufferLength: number = this.packet.length - this.startPos - 8 while (!this.TLVInstance) { try { const buffer: Buffer = this.readBytes(8, readTLVBufferLength, true) this.TLVInstance = TLV.parse(buffer) const tagWithLength: string[] = buffer.toString('hex').replace(this.TLVInstance.bValue.toString('hex'), '#').split('#') const tagWithLengthBytes: number = Math.max(...tagWithLength.map((value: string): number => Math.ceil(value.length / 2))) const TLVBufferLength: number = this.TLVInstance.bValue.length + tagWithLengthBytes this.TLVInstance = TLV.parse(this.readBytes(8, TLVBufferLength, false)) } catch (e) { readTLVBufferLength -= 1 if (!readTLVBufferLength) break } } this.TLVChild = this.TLVInstance ? this.TLVInstance.getChild() : [] }, encode: (): void => { let buffers: Buffer = Buffer.from([]) this.TLVChild.forEach(item => buffers = Buffer.concat([buffers, item.bTag, item.bLength, item.bValue])) const goosePduTLV: TLV = new TLV(0x61, buffers) const goosePduBuffer: Buffer = Buffer.concat([goosePduTLV.bTag, goosePduTLV.bLength, goosePduTLV.bValue]) this.writeBytes(8, goosePduBuffer) /** * Update the length only if it is not set * Update length(APPID's length + Length's length + Reserved1's length + Reserved2's length + goosePdu's length) */ if (this.instance.length.getValue() > 0) return this.instance.length.setValue(2 + 2 + 2 + 2 + goosePduBuffer.length) }, properties: { gocbRef: { type: 'string', maxLength: 129, label: 'GoCBReference', contentEncoding: StringContentEncodingEnum.ASCII, decode: (): void => { const gocbRefTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x80) if (!gocbRefTLV) return this.recordError(this.instance.goosePdu.gocbRef.getPath(), 'Not Found') this.instance.goosePdu.gocbRef.setValue(gocbRefTLV.getValue('buffer').toString('ascii')) }, encode: (): void => { const gocbRefValue: string = this.instance.goosePdu.gocbRef.getValue('', (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (!gocbRefValue) return let gocbRefBuffer: Buffer = Buffer.from(gocbRefValue, 'ascii') if (gocbRefBuffer.length > 129) { this.recordError(this.instance.goosePdu.gocbRef.getPath(), 'This VisibleString shall have a maximum size of 129 octets') gocbRefBuffer = gocbRefBuffer.subarray(0, 129) } this.TLVChild.push(new TLV(0x80, gocbRefBuffer)) } }, timeAllowedtoLive: { type: 'number', minimum: 0, maximum: 4294967295, label: 'TimeAllowedtoLive', decode: (): void => { const timeAllowedtoLiveString: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x81)?.getValue('hex') if (!timeAllowedtoLiveString) return this.recordError(this.instance.goosePdu.timeAllowedtoLive.getPath(), 'Not Found') this.instance.goosePdu.timeAllowedtoLive.setValue(HexToUInt32(timeAllowedtoLiveString)) }, encode: (): void => { let timeAllowedtoLiveValue: number = this.instance.goosePdu.timeAllowedtoLive.getValue(-1, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (timeAllowedtoLiveValue === -1) return if (timeAllowedtoLiveValue < 1 || timeAllowedtoLiveValue > 4294967295) { this.recordError(this.instance.goosePdu.timeAllowedtoLive.getPath(), 'This INTEGER value shall have a range of 1 to 4294967295') timeAllowedtoLiveValue = 4294967295 } const timeAllowedtoLiveTLV: TLV = new TLV(0x81, UInt32ToBERBuffer(timeAllowedtoLiveValue)) this.TLVChild.push(timeAllowedtoLiveTLV) } }, datSet: { type: 'string', maxLength: 129, label: 'DatSet', contentEncoding: StringContentEncodingEnum.ASCII, decode: (): void => { const datSetTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x82) if (!datSetTLV) return this.recordError(this.instance.goosePdu.datSet.getPath(), 'Not Found') this.instance.goosePdu.datSet.setValue(datSetTLV.getValue('buffer').toString('ascii')) }, encode: (): void => { const datSetValue: string = this.instance.goosePdu.datSet.getValue('', (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (!datSetValue) return let datSetBuffer: Buffer = Buffer.from(datSetValue, 'ascii') if (datSetBuffer.length > 129) { this.recordError(this.instance.goosePdu.datSet.getPath(), 'This VisibleString shall have a maximum size of 129 octets') datSetBuffer = datSetBuffer.subarray(0, 129) } this.TLVChild.push(new TLV(0x82, datSetBuffer)) } }, goID: { type: 'string', maxLength: 65, label: 'GoID', contentEncoding: StringContentEncodingEnum.ASCII, decode: (): void => { const goIDTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x83) if (!goIDTLV) return this.recordError(this.instance.goosePdu.goID.getPath(), 'Not Found') this.instance.goosePdu.goID.setValue(goIDTLV.getValue('buffer').toString('ascii')) }, encode: (): void => { const goIDValue: string = this.instance.goosePdu.goID.getValue('', (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (!goIDValue) return let goIDBuffer: Buffer = Buffer.from(goIDValue, 'ascii') if (goIDBuffer.length > 65) { this.recordError(this.instance.goosePdu.goID.getPath(), 'This VisibleString shall have a maximum size of 65 octets') goIDBuffer = goIDBuffer.subarray(0, 65) } this.TLVChild.push(new TLV(0x83, goIDBuffer)) } }, t: { type: 'string', label: 'TimeStamp', contentEncoding: StringContentEncodingEnum.BIGINT, decode: (): void => { const tStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x84)?.getValue('hex') if (!tStr) return this.recordError(this.instance.goosePdu.t.getPath(), 'Not Found') this.instance.goosePdu.t.setValue(BigInt(`0x${tStr}`).toString()) }, encode: (): void => { const tStr: string = this.instance.goosePdu.t.getValue('', (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (!tStr) return const tBigIntValue: bigint = BigInt(tStr) const tTLV: TLV = new TLV(0x84, Buffer.from(tBigIntValue.toString(16).padStart(8 * 2, '0'), 'hex')) this.TLVChild.push(tTLV) } }, stNum: { type: 'integer', minimum: 0, maximum: 4294967295, label: 'StNum', decode: (): void => { const stNumStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x85)?.getValue('hex') if (!stNumStr) return this.recordError(this.instance.goosePdu.stNum.getPath(), 'Not Found') this.instance.goosePdu.stNum.setValue(HexToUInt32(stNumStr)) if (this.instance.goosePdu.stNum.getValue() < 1 || this.instance.goosePdu.stNum.getValue() > 4294967295) this.recordError(this.instance.goosePdu.stNum.getPath(), 'This INTEGER value shall have a range of 1 to 4294967295') }, encode: (): void => { let stNumValue: number = this.instance.goosePdu.stNum.getValue(-1, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (stNumValue < 0) return if (stNumValue > 4294967295) { stNumValue = 4294967295 this.recordError(this.instance.goosePdu.stNum.getPath(), 'This INTEGER value shall have a range of 1 to 4294967295') } if (stNumValue === undefined) return this.recordError(this.instance.goosePdu.stNum.getPath(), 'Not Found') const stNumTLV: TLV = new TLV(0x85, UInt32ToBERBuffer(stNumValue)) this.TLVChild.push(stNumTLV) } }, sqNum: { type: 'integer', minimum: 0, maximum: 4294967295, label: 'SqNum', decode: (): void => { const sqNumStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x86)?.getValue('hex') if (!sqNumStr) return this.recordError(this.instance.goosePdu.sqNum.getPath(), 'Not Found') this.instance.goosePdu.sqNum.setValue(HexToUInt32(sqNumStr)) }, encode: (): void => { let sqNumValue: number = this.instance.goosePdu.sqNum.getValue(-1, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (sqNumValue < 0) return if (sqNumValue > 4294967295) { sqNumValue = 4294967295 this.recordError(this.instance.goosePdu.sqNum.getPath(), 'This INTEGER value shall have a range of 1 to 4294967295') } if (sqNumValue === undefined) return this.recordError(this.instance.goosePdu.sqNum.getPath(), 'Not Found') const sqNumTLV: TLV = new TLV(0x86, UInt32ToBERBuffer(sqNumValue)) this.TLVChild.push(sqNumTLV) } }, simulation: { type: 'boolean', label: 'Simulation', decode: (): void => { const simulationStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x87)?.getValue('hex') if (!simulationStr) return this.recordError(this.instance.goosePdu.simulation.getPath(), 'Not Found') const simulationNum: number = parseInt(simulationStr, 16) if (simulationNum > 1) this.recordError(this.instance.goosePdu.simulation.getPath(), 'This Boolean shall have a range of TRUE, FALSE') this.instance.goosePdu.simulation.setValue(!!simulationNum) }, encode: (): void => { const simulationValue: boolean | undefined = this.instance.goosePdu.simulation.getValue(undefined, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (simulationValue === undefined) return const simulationNum: number = simulationValue ? 1 : 0 const simulationTLV: TLV = new TLV(0x87, Buffer.from(simulationNum.toString(16).padStart(2, '0'), 'hex')) this.TLVChild.push(simulationTLV) } }, confRev: { type: 'integer', label: 'ConfRev', decode: (): void => { const confRevStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x88)?.getValue('hex') if (!confRevStr) return this.recordError(this.instance.goosePdu.confRev.getPath(), 'Not Found') this.instance.goosePdu.confRev.setValue(HexToUInt32(confRevStr)) }, encode: (): void => { let confRevValue: number = this.instance.goosePdu.confRev.getValue(-1, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (confRevValue > 4294967295) { confRevValue = 4294967295 this.recordError(this.instance.goosePdu.confRev.getPath(), 'This INTEGER value shall have a range of 0 to 4294967295') } if (confRevValue === -1) return const confRevTLV: TLV = new TLV(0x88, UInt32ToBERBuffer(confRevValue)) this.TLVChild.push(confRevTLV) } }, ndsCom: { type: 'boolean', label: 'NdsCom', decode: (): void => { const ndsComStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x89)?.getValue('hex') if (!ndsComStr) return this.recordError(this.instance.goosePdu.ndsCom.getPath(), 'Not Found') const ndsComNum: number = parseInt(ndsComStr, 16) if (ndsComNum > 1) this.recordError(this.instance.goosePdu.ndsCom.getPath(), 'This Boolean shall have a range of TRUE, FALSE') this.instance.goosePdu.ndsCom.setValue(!!ndsComNum) }, encode: (): void => { const ndsComValue: boolean | undefined = this.instance.goosePdu.ndsCom.getValue(undefined, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (ndsComValue === undefined) return const ndsComNum: number = ndsComValue ? 1 : 0 const ndsComTLV: TLV = new TLV(0x89, Buffer.from(ndsComNum.toString(16).padStart(2, '0'), 'hex')) this.TLVChild.push(ndsComTLV) } }, numDatSetEntries: { type: 'integer', label: 'NumDatSetEntries', decode: (): void => { const numDatSetEntriesStr: string | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x8A)?.getValue('hex') if (!numDatSetEntriesStr) return this.recordError(this.instance.goosePdu.numDatSetEntries.getPath(), 'Not Found') this.instance.goosePdu.numDatSetEntries.setValue(parseInt(numDatSetEntriesStr, 16)) }, encode: (): void => { const numDatSetEntriesValue: number | undefined = this.instance.goosePdu.numDatSetEntries.getValue(undefined, (nodePath: string): void => this.recordError(nodePath, 'Not Found')) if (numDatSetEntriesValue === undefined) return const numDatSetEntriesTLV: TLV = new TLV(0x8A, UInt32ToBERBuffer(numDatSetEntriesValue)) this.TLVChild.push(numDatSetEntriesTLV) } }, allData: { type: 'array', label: 'AllData', items: { anyOf: [ { type: 'object', label: ItemDataType.Boolean, properties: { dataType: {type: 'string', enum: [ItemDataType.Boolean]}, value: {type: 'boolean'} } }, { type: 'object', label: ItemDataType.INT8, properties: { dataType: {type: 'string', enum: [ItemDataType.INT8]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.INT16, properties: { dataType: {type: 'string', enum: [ItemDataType.INT16]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.INT32, properties: { dataType: {type: 'string', enum: [ItemDataType.INT32]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.INT64, properties: { dataType: {type: 'string', enum: [ItemDataType.INT64]}, value: {type: 'string', contentEncoding: StringContentEncodingEnum.BIGINT} } }, { type: 'object', label: ItemDataType.INT8U, properties: { dataType: {type: 'string', enum: [ItemDataType.INT8U]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.INT16U, properties: { dataType: {type: 'string', enum: [ItemDataType.INT16U]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.INT32U, properties: { dataType: {type: 'string', enum: [ItemDataType.INT32U]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.FLOAT32, properties: { dataType: {type: 'string', enum: [ItemDataType.FLOAT32]}, value: {type: 'number'} } }, { type: 'object', label: ItemDataType.CODEDENUM, properties: { dataType: {type: 'string', enum: [ItemDataType.CODEDENUM]}, value: {type: 'integer'} } }, { type: 'object', label: ItemDataType.OCTETSTRING, properties: { dataType: {type: 'string', enum: [ItemDataType.OCTETSTRING]}, value: {type: 'string', contentEncoding: StringContentEncodingEnum.HEX} } }, { type: 'object', label: ItemDataType.VISIBLESTRING, properties: { dataType: {type: 'string', enum: [ItemDataType.VISIBLESTRING]}, value: {type: 'string', contentEncoding: StringContentEncodingEnum.ASCII} } }, { type: 'object', properties: { dataType: {type: 'string', enum: [ItemDataType.TimeStamp]}, value: {type: 'string'} } }, { type: 'object', label: ItemDataType.Quality, properties: { dataType: {type: 'string', enum: [ItemDataType.Quality]}, value: {type: 'string', contentEncoding: StringContentEncodingEnum.BINARY} } }, { type: 'object', label: ItemDataType.Structure, properties: { dataType: {type: 'string', enum: [ItemDataType.Structure]}, value: { $ref: '#/properties/goosePdu/properties/allData' } } } ] }, /** * +-----------------------------------+-------------------+-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ * | Data types according to IEC 61850-7-2 | ASN.1 Tag for Data | ASN.1 Length | Comments | * +-----------------------------------+-------------------+-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ * | Boolean | 0x83 | 1 | 8 Bit set to 0 FALSE; anything else = TRUE | * | INT8 | 0x85 | 2 | 8 Bit Big Endian; signed | * | INT16 | 0x85 | 3 | 16 Bit Big Endian; signed | * | INT32 | 0x85 | 5 | 32 Bit Big Endian; signed | * | INT64 | 0x85 | 9 | 64Bit Big Endian; signed | * | INT8U | 0x86 | 2 | 8 Bit Big Endian; unsigned | * | INT16U | 0x86 | 3 | 16 Bit Big Endian; unsigned | * | INT24U | - | - | Not used | * | INT32U | 0x86 | 5 | 32 Bit Big Endian; unsigned | * | FLOAT32 | 0x87 | 4 | 32 Bit IEEE Floating Point (IEEE 754) | * | CODED-ENUM | 0x84 | 2 | Bit-string; depending on CODED ENUM definition – most of the time, can be encoded with 2 bytes (1st Byte = number of unused bit, 2nd Byte = Value) | * | OCTET-STRING | 0x89 | 20 | 20 Bytes ASCII Text, Null terminated | * | VISIBLE-STRING | 0x8a | 35 | 35 Bytes ASCII Text, Null terminated | * | TimeStamp | 0x91 | 8 | 64 Bit TimeStamp as defined in 8.1.3.7 IEC 6 | * | Quality | 0x84 | 3 | Bit-string | * +-----------------------------------+-------------------+-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ */ decode: (): void => { const allDataTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0xAB) const dataTLVs: TLV[] = allDataTLV ? allDataTLV.getChild() : [] const allData: DataItem[] = this.decodeDataTLVItem(dataTLVs) if (allData.length) this.instance.goosePdu.allData.setValue(allData) }, encode: (): void => { const allData: DataItem[] = this.instance.goosePdu.allData.getValue([], (nodePath: string): void => this.recordError(nodePath, 'Not Found')) const dataItemTLVs: TLV[] = this.encodeDataTLVItem(allData) if (dataItemTLVs.length) { const allDataTLV: TLV = new TLV(0xAB, Buffer.concat(dataItemTLVs.map((dataItemTLV: TLV): Buffer => Buffer.concat([dataItemTLV.bTag, dataItemTLV.bLength, dataItemTLV.bValue])))) this.TLVChild.push(allDataTLV) } } } } } } } /** * Decode data TLV items * @param dataTLVs * @protected */ protected decodeDataTLVItem(dataTLVs: TLV[]): DataItem[] { const dataItems: DataItem[] = [] dataTLVs.forEach((dataTLV: TLV): void => { const length: number = dataTLV.getLength('number') const value: Buffer = dataTLV.getValue('buffer') switch (dataTLV.getTag('number')) { case 0x83: { dataItems.push({ dataType: ItemDataType.Boolean, value: !!parseInt(value.toString('hex'), 16) }) } break case 0x84: { switch (length) { case 2: { dataItems.push({ dataType: ItemDataType.CODEDENUM, value: parseInt(value.toString('hex'), 16) }) } break case 3: { dataItems.push({ dataType: ItemDataType.Quality, value: parseInt(value.toString('hex'), 16).toString(2).padStart(24, '0') }) } break } } break case 0x85: { const integerRealLength: number = GetBERIntegerLengthFromBuffer(value) if (integerRealLength === 1) { dataItems.push({ dataType: ItemDataType.INT8, value: HexToInt8(value.toString('hex')) }) } else if (integerRealLength === 2) { dataItems.push({ dataType: ItemDataType.INT16, value: HexToInt16(value.toString('hex')) }) } else if (integerRealLength === 4) { dataItems.push({ dataType: ItemDataType.INT32, value: HexToInt32(value.toString('hex')) }) } else if (integerRealLength === 8) { dataItems.push({ dataType: ItemDataType.INT64, value: HexToInt64(value.toString('hex')) }) } } break case 0x86: { const unsignedIntegerRealLength: number = GetBERIntegerLengthFromBuffer(value) switch (unsignedIntegerRealLength) { case 1: { dataItems.push({ dataType: ItemDataType.INT8U, value: HexToUInt8(value.toString('hex')) }) } break case 2: { dataItems.push({ dataType: ItemDataType.INT16U, value: HexToUInt16(value.toString('hex')) }) } break case 4: { dataItems.push({ dataType: ItemDataType.INT32U, value: HexToUInt32(value.toString('hex')) }) } break } } break case 0x87: { dataItems.push({ dataType: ItemDataType.FLOAT32, value: HexToFloat32(value.toString('hex')) }) } break case 0x89: { dataItems.push({ dataType: ItemDataType.OCTETSTRING, value: value.toString('hex') }) } break case 0x8a: { dataItems.push({ dataType: ItemDataType.VISIBLESTRING, value: value.toString('ascii') }) } break case 0x91: { dataItems.push({ dataType: ItemDataType.TimeStamp, value: parseInt(value.toString('hex'), 16).toString() }) } break case 0xa2: { const subDataTLVs: TLV[] = TLV.parseList(value) dataItems.push({ dataType: ItemDataType.Structure, value: this.decodeDataTLVItem(subDataTLVs) }) } break } }) return dataItems } /** * Encode data TLV items * @param dataItems * @protected */ protected encodeDataTLVItem(dataItems: DataItem[]): TLV[] { return dataItems .map((dataItem: DataItem, index: number): TLV | null => { const errorNodePath: string = this.instance.goosePdu.allData.getPath(index) switch (dataItem.dataType) { case ItemDataType.Boolean: { const booleanIntValue: number = dataItem.value ? 1 : 0 return new TLV(0x83, Buffer.from(booleanIntValue.toString(16).padStart(2, '0'), 'hex')) } case ItemDataType.INT8: { let intValue: number = dataItem.value if (isNaN(intValue)) this.recordError(errorNodePath, 'Invalid INT8 value') intValue = intValue ? intValue : 0 if (intValue < -128 || intValue > 127) this.recordError(errorNodePath, 'Invalid INT8 value') return new TLV(0x85, Buffer.from(Int8ToBERHex(intValue), 'hex')) } case ItemDataType.INT16: { let intValue: number = dataItem.value if (isNaN(intValue)) this.recordError(errorNodePath, 'Invalid INT16 value') intValue = intValue ? intValue : 0 if (intValue < -32768 || intValue > 32767) this.recordError(errorNodePath, 'Invalid INT16 value') return new TLV(0x85, Buffer.from(Int16ToBERHex(intValue), 'hex')) } case ItemDataType.INT32: { let intValue: number = dataItem.value if (isNaN(intValue)) this.recordError(errorNodePath, 'Invalid INT32 value') intValue = intValue ? intValue : 0 if (intValue < -2147483648 || intValue > 2147483647) this.recordError(errorNodePath, 'Invalid INT32 value') return new TLV(0x85, Buffer.from(Int32ToBERHex(intValue), 'hex')) } case ItemDataType.INT64: { const intValue: bigint = BigInt(dataItem.value) if (intValue < BigInt('-9223372036854775808') || intValue > BigInt('9223372036854775807')) this.recordError(errorNodePath, 'Invalid INT64 value') return new TLV(0x85, Buffer.from(Int64ToBERHex(intValue), 'hex')) } case ItemDataType.INT8U: { let uintValue: number = dataItem.value if (isNaN(uintValue)) this.recordError(errorNodePath, 'Invalid INT8U value') uintValue = uintValue ? uintValue : 0 if (uintValue < 0 || uintValue > 255) this.recordError(errorNodePath, 'Invalid INT8U value') return new TLV(0x86, Buffer.from(UInt8ToBERHex(uintValue), 'hex')) } case ItemDataType.INT16U: { let uintValue: number = dataItem.value if (isNaN(uintValue)) this.recordError(errorNodePath, 'Invalid INT16U value') uintValue = uintValue ? uintValue : 0 if (uintValue < 0 || uintValue > 65535) this.recordError(errorNodePath, 'Invalid INT16U value') return new TLV(0x86, Buffer.from(UInt16ToBERHex(uintValue), 'hex')) } case ItemDataType.INT32U: { let uintValue: number = dataItem.value if (isNaN(uintValue)) this.recordError(errorNodePath, 'Invalid INT32U value') uintValue = uintValue ? uintValue : 0 if (uintValue < 0 || uintValue > 4294967295) this.recordError(errorNodePath, 'Invalid INT32U value') return new TLV(0x86, Buffer.from(UInt32ToBERHex(uintValue), 'hex')) } case ItemDataType.FLOAT32: { let float32Value: number = dataItem.value if (isNaN(float32Value)) this.recordError(errorNodePath, 'Invalid FLOAT32 value') float32Value = float32Value ? float32Value : 0 return new TLV(0x87, Buffer.from(Float32ToBERHex(float32Value), 'hex')) } case ItemDataType.CODEDENUM: { let codedEnumValue: number = dataItem.value if (isNaN(codedEnumValue)) this.recordError(errorNodePath, 'Invalid CODED-ENUM value') codedEnumValue = codedEnumValue ? codedEnumValue : 0 return new TLV(0x84, Buffer.from(UInt16ToBERHex(codedEnumValue), 'hex')) } case ItemDataType.OCTETSTRING: { const hexText: string = dataItem.value if (!hexText) { this.recordError(errorNodePath, 'Empty OCTET-STRING, ignored') return null } if (hexText.length > 20) this.recordError(errorNodePath, 'OCTET-STRING too long') return new TLV(0x89, Buffer.from(hexText, 'hex').subarray(0, 20)) } case ItemDataType.VISIBLESTRING: { const asciiText: string = dataItem.value if (!asciiText) { this.recordError(errorNodePath, 'Empty VISIBLE-STRING, ignored') return null } if (asciiText.length > 35) this.recordError(errorNodePath, 'VISIBLE-STRING too long') const hex: string = Buffer.from(asciiText, 'ascii').toString('hex').padStart(35 * 2) return new TLV(0x8a, Buffer.from(hex, 'hex').subarray(0, 35)) } case ItemDataType.TimeStamp: { let timestamp: number = parseInt(dataItem.value) if (isNaN(timestamp)) this.recordError(errorNodePath, 'Invalid TimeStamp value') timestamp = timestamp ? timestamp : 0 return new TLV(0x91, Buffer.from(timestamp.toString(16), 'hex')) } case ItemDataType.Quality: { const bitString: string = dataItem.value let intValue: number = parseInt(bitString, 2) if (isNaN(intValue)) this.recordError(errorNodePath, 'Invalid Quality value') intValue = intValue ? intValue : 0