netkitty
Version:
Network tools kit
870 lines (861 loc) • 51 kB
text/typescript
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