netkitty
Version:
Network tools kit
362 lines (352 loc) • 19.7 kB
text/typescript
import {ProtocolJSONSchema} from '../../schema/ProtocolJSONSchema'
import {BaseHeader} from '../abstracts/BaseHeader'
import TLV from 'node-tlv'
import {HexToUInt16} from '../../helper/HexToNumber'
import {UInt16ToBERHex} from '../../helper/NumberToBERHex'
import {UInt16ToHex, UInt32ToHex, UInt8ToHex} from '../../helper/NumberToHex'
import {StringContentEncodingEnum} from '../lib/StringContentEncodingEnum'
import {BufferToUInt16, BufferToUInt32, BufferToUInt64, BufferToUInt8} from '../../helper/BufferToNumber'
import {UInt16ToBuffer} from '../../helper/NumberToBuffer'
type ASDUItem = {
svID: string
dataSet?: string
smpCnt: number
confRev: number
refrTm?: string
smpSynch: number
smpRate?: number
sample: string
smpMod?: number
}
export class IEC61850SampledValues 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',
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()
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)
}
}
}
},
svPdu: {
type: 'object',
label: 'Sampled Values PDU',
decode: (): void => {
const buffer: Buffer = this.readBytes(8, (this.instance.length.getValue()) - 8)
this.TLVInstance = TLV.parse(buffer)
this.TLVChild = this.TLVInstance.getChild()
this.instance.svPdu.setValue({})
},
encode: (): void => {
let buffer: Buffer = Buffer.from([])
this.TLVChild.forEach(TLVItem => buffer = Buffer.concat([buffer, TLVItem.bTag, TLVItem.bLength, TLVItem.bValue]))
const svPduTLV: TLV = new TLV(0x60, buffer)
const svPduBuffer: Buffer = Buffer.concat([svPduTLV.bTag, svPduTLV.bLength, svPduTLV.bValue])
this.writeBytes(8, svPduBuffer)
/**
* Update the length only if it is not set
* Update length(APPID's length + Length's length + Reserved1's length + Reserved2's length + svPdu's length)
*/
if (this.instance.length.getValue() > 0) return
this.instance.length.setValue(2 + 2 + 2 + 2 + svPduBuffer.length)
},
properties: {
noASDU: {
type: 'integer',
minimum: 1,
maximum: 65535,
label: 'Number of ASDU',
decode: (): void => {
const noASDUTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0x80)
if (!noASDUTLV) return this.recordError(this.instance.svPdu.noASDU.getPath(), 'Not Found')
const noASDUNum: number = HexToUInt16(noASDUTLV.getValue('hex'))
if (!noASDUNum) this.recordError(this.instance.svPdu.noASDU.getPath(), 'Number of ASDU should be greater or equal to 1')
this.instance.svPdu.noASDU.setValue(noASDUNum)
},
encode: (): void => {
const noASDU: number = this.instance.svPdu.noASDU.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'noASDU is not set'))
this.TLVChild.push(new TLV(0x80, UInt16ToBERHex(noASDU)))
}
},
seqASDU: {
type: 'array',
label: 'Sequence ASDU',
items: {
type: 'object',
properties: {
svID: {
type: 'string',
contentEncoding: StringContentEncodingEnum.ASCII,
label: 'SvID'
},
dataSet: {
type: 'string',
contentEncoding: StringContentEncodingEnum.ASCII,
label: 'DatSet'
},
smpCnt: {
type: 'integer',
label: 'SmpCnt'
},
confRev: {
type: 'integer',
label: 'ConfRev'
},
refrTm: {
type: 'string',
contentEncoding: StringContentEncodingEnum.BIGINT,
label: 'RefrTm'
},
smpSynch: {
type: 'integer',
label: 'SmpSynch'
},
smpRate: {
type: 'integer',
label: 'SmpRate'
},
sample: {
type: 'string',
contentEncoding: StringContentEncodingEnum.HEX,
label: 'Sample'
},
smpMod: {
type: 'integer',
label: 'SmpMod'
}
}
},
decode: (): void => {
const seqASDU: ASDUItem[] = []
const seqASDUTLV: TLV | undefined = this.TLVChild.find(tlv => tlv.getTag('number') === 0xa2)
if (!seqASDUTLV) {
this.instance.svPdu.seqASDU.setValue(seqASDU)
return
}
const ASDUTLVs: TLV[] = seqASDUTLV.getChild()
ASDUTLVs.forEach((ASDUTLV: TLV, index: number): void => {
const errorNodePath: string = this.instance.svPdu.seqASDU.getPath(index)
if (ASDUTLV.getTag('number') !== 0x30) this.recordError(errorNodePath, 'Invalid ASDU item tag')
const ASDUAttributeTLVs: TLV[] = ASDUTLV.getChild()
let svID: string
let dataSet: string
let smpCnt: number
let confRev: number
let refrTm: string
let smpSynch: number
let smpRate: number
let sample: string
let smpMod: number
//svID
const svIDTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x80)
if (svIDTLV) {
svID = svIDTLV.getValue('buffer').toString('ascii')
} else {
this.recordError(errorNodePath, 'svID Not Found')
}
//dataSet (optional)
const dataSetTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x81)
if (dataSetTLV) {
dataSet = dataSetTLV.getValue('buffer').toString('ascii')
}
//smpCnt
const smpCntTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x82)
if (smpCntTLV) {
smpCnt = BufferToUInt16(smpCntTLV.getValue('buffer'))
} else {
this.recordError(errorNodePath, 'smpCnt Not Found')
}
//confRev
const confRevTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x83)
if (confRevTLV) {
confRev = BufferToUInt32(confRevTLV.getValue('buffer'))
} else {
this.recordError(errorNodePath, 'confRev Not Found')
}
//refrTm (optional)
const refrTmTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x84)
if (refrTmTLV) {
refrTm = BufferToUInt64(refrTmTLV.getValue('buffer')).toString()
}
//smpSynch
const smpSynchTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x85)
if (smpSynchTLV) {
smpSynch = BufferToUInt8(smpSynchTLV.getValue('buffer'))
} else {
this.recordError(errorNodePath, 'smpSynch Not Found')
}
//smpRate (optional)
const smpRateTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x86)
if (smpRateTLV) {
smpRate = BufferToUInt16(smpRateTLV.getValue('buffer'))
}
//sample
const sampleTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x87)
if (sampleTLV) {
sample = sampleTLV.getValue('hex')
} else {
this.recordError(errorNodePath, 'sample Not Found')
}
//smpMod (optional)
const smpModTLV: TLV | undefined = ASDUAttributeTLVs.find(tlv => tlv.getTag('number') === 0x86)
if (smpModTLV) {
smpMod = BufferToUInt16(smpModTLV.getValue('buffer'))
}
seqASDU.push({
svID: svID!,
dataSet: dataSet!,
smpCnt: smpCnt!,
confRev: confRev!,
refrTm: refrTm!,
smpSynch: smpSynch!,
smpRate: smpRate!,
sample: sample!,
smpMod: smpMod!
})
})
this.instance.svPdu.seqASDU.setValue(seqASDU)
},
encode: (): void => {
const seqASDU: ASDUItem[] = this.instance.svPdu.seqASDU.isUndefined() ? [] : this.instance.svPdu.seqASDU.getValue()
const seqASDUTLVs: TLV[] = []
seqASDU.forEach((seqASDUItem: ASDUItem, index: number): void => {
const errorNodePath: string = this.instance.svPdu.seqASDU.getPath(index)
const seqASDUItemTLVs: TLV[] = []
//svID
if (seqASDUItem.svID !== undefined) {
seqASDUItemTLVs.push(new TLV(0x80, Buffer.from(seqASDUItem.svID ? seqASDUItem.svID : '', 'ascii')))
} else {
this.recordError(errorNodePath, 'No svID')
}
//dataSet
if (seqASDUItem.dataSet !== undefined) seqASDUItemTLVs.push(new TLV(0x81, Buffer.from(seqASDUItem.dataSet ? seqASDUItem.dataSet : '', 'ascii')))
// smpCnt
if (seqASDUItem.smpCnt !== undefined) {
seqASDUItemTLVs.push(new TLV(0x82, UInt16ToHex(seqASDUItem.smpCnt)))
} else {
this.recordError(errorNodePath, 'No smpCnt')
}
// confRev
if (seqASDUItem.confRev !== undefined) {
seqASDUItemTLVs.push(new TLV(0x83, UInt32ToHex(seqASDUItem.confRev)))
} else {
this.recordError(errorNodePath, 'No confRev')
}
// refrTm
if (seqASDUItem.refrTm !== undefined) seqASDUItemTLVs.push(new TLV(0x84, Buffer.from(BigInt(seqASDUItem.refrTm).toString(16).padStart(8 * 2, '0'), 'hex')))
// smpSynch
if (seqASDUItem.smpSynch !== undefined) {
seqASDUItemTLVs.push(new TLV(0x85, UInt8ToHex(seqASDUItem.smpSynch)))
} else {
this.recordError(errorNodePath, 'No smpSynch')
}
// smpRate
if (seqASDUItem.smpRate !== undefined) seqASDUItemTLVs.push(new TLV(0x86, UInt16ToHex(seqASDUItem.smpRate)))
// sample
if (seqASDUItem.sample !== undefined) {
seqASDUItemTLVs.push(new TLV(0x87, Buffer.from(seqASDUItem.sample, 'hex')))
} else {
this.recordError(errorNodePath, 'No sample')
}
// smpMod
if (seqASDUItem.smpMod !== undefined) seqASDUItemTLVs.push(new TLV(0x88, UInt16ToHex(seqASDUItem.smpMod)))
if (!seqASDUItemTLVs.length) return
let seqASDUItemBuffer: Buffer = Buffer.from([])
seqASDUItemTLVs.forEach(seqASDUItemTLV => seqASDUItemBuffer = Buffer.concat([seqASDUItemBuffer, seqASDUItemTLV.bTag, seqASDUItemTLV.bLength, seqASDUItemTLV.bValue]))
seqASDUTLVs.push(new TLV(0x30, seqASDUItemBuffer))
})
let seqASDUBuffer: Buffer = Buffer.from([])
seqASDUTLVs.forEach(seqASDUTLV => seqASDUBuffer = Buffer.concat([seqASDUBuffer, seqASDUTLV.bTag, seqASDUTLV.bLength, seqASDUTLV.bValue]))
this.TLVChild.push(new TLV(0xa2, seqASDUBuffer))
}
}
}
}
}
}
public readonly id: string = 'sv'
public readonly name: string = 'IEC61850 Sampled Values'
public readonly nickname: string = 'SV'
public match(): boolean {
if (!this.prevCodecModule) return false
return this.prevCodecModule.instance.etherType.getValue() === UInt16ToHex(0x88ba)
}
}