From 6d64382b0a37387bafbf3616d9d39d79720740ba Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Sat, 3 Feb 2024 16:55:02 +0100 Subject: [PATCH] feat: describe Services --- describe.ts | 5 +- describe/Services.spec.ts | 513 ++++++++++++++++++++++++++++ describe/Services.ts | 684 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1201 insertions(+), 1 deletion(-) create mode 100644 describe/Services.spec.ts create mode 100644 describe/Services.ts diff --git a/describe.ts b/describe.ts index 07e1ae4b..44227632 100644 --- a/describe.ts +++ b/describe.ts @@ -8,6 +8,7 @@ import { LNodeType, LNodeTypeDescription } from "./describe/LNodeType.js"; import { LN, LNDescription } from "./describe/LN.js"; import { LN0, LN0Description } from "./describe/LN0.js"; import { Server, ServerDescription } from "./describe/Server.js"; +import { Services, ServicesDescription } from "./describe/Services.js"; export type Description = | PrivateDescription @@ -20,7 +21,8 @@ export type Description = | LNDescription | LN0Description | LDeviceDescription - | ServerDescription; + | ServerDescription + | ServicesDescription; const sclElementDescriptors: Partial< Record Description | undefined> > = { @@ -34,6 +36,7 @@ const sclElementDescriptors: Partial< LN0, LDevice, Server, + Services, }; export function describe(element: Element): Description | undefined { diff --git a/describe/Services.spec.ts b/describe/Services.spec.ts new file mode 100644 index 00000000..2b6ee2db --- /dev/null +++ b/describe/Services.spec.ts @@ -0,0 +1,513 @@ +import { expect } from "chai"; + +import { Services } from "./Services.js"; + +const scl = new DOMParser().parseFromString( + ` + + + + + + + + + + + + + + + + + + + + 4000 + 4800 + 4000 + 4800 + 0.000002 + 0.000001 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4800 + 4000 + 4800 + 4000 + 0.000001 + 0.000002 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + "application/xml", +); + +const baseServices = scl.querySelector('AccessPoint[name="AP1"]>Services')!; +const equalServices = scl.querySelector('AccessPoint[name="AP2"]>Services')!; +const diffServices = scl.querySelector('AccessPoint[name="AP3"]>Services')!; +const oDiffServices = scl.querySelector('AccessPoint[name="AP4"]>Services')!; +const emptyServices = scl.querySelector("IED>Services")!; + +describe("Description for SCL schema type LDevice", () => { + it("default nameLength attribute to 32", () => + expect(Services(emptyServices)?.nameLength).to.equal(43)); + + it("return nameLength attribute", () => + expect(Services(equalServices)?.nameLength).to.equal(32)); + + it("return existing ServicesYesNo as true", () => { + expect(Services(baseServices).getDirectory).to.be.true; + expect(Services(baseServices).getDataObjectDefinition).to.be.true; + expect(Services(baseServices).dataObjectDirectory).to.be.true; + expect(Services(baseServices).getDataSetValue).to.be.true; + expect(Services(baseServices).setDataSetValue).to.be.true; + expect(Services(baseServices).dataSetDirectory).to.be.true; + expect(Services(baseServices).readWrite).to.be.true; + expect(Services(baseServices).timerActivatedControl).to.be.true; + expect(Services(baseServices).getCBValues).to.be.true; + expect(Services(baseServices).gSEDir).to.be.true; + expect(Services(baseServices).confLdName).to.be.true; + }); + + it("return missing ServicesYesNo as false", () => { + expect(Services(diffServices).getDirectory).to.be.false; + expect(Services(diffServices).getDataObjectDefinition).to.be.false; + expect(Services(diffServices).dataObjectDirectory).to.be.false; + expect(Services(diffServices).getDataSetValue).to.be.false; + expect(Services(diffServices).setDataSetValue).to.be.false; + expect(Services(diffServices).dataSetDirectory).to.be.false; + expect(Services(diffServices).readWrite).to.be.false; + expect(Services(diffServices).timerActivatedControl).to.be.false; + expect(Services(diffServices).getCBValues).to.be.false; + expect(Services(diffServices).gSEDir).to.be.false; + expect(Services(diffServices).confLdName).to.be.false; + }); + + it("return DynAssociation with or without max attribute", () => { + expect(Services(emptyServices)?.dynAssociation).to.be.undefined; + expect(Services(diffServices)?.dynAssociation?.max).to.be.undefined; + expect(Services(baseServices)?.dynAssociation?.max).to.be.equal(5); + }); + + it("return SettingGroups with or without children SGEdit and ConfSG", () => { + expect(Services(emptyServices)?.settingGroups).to.be.undefined; + + expect(Services(diffServices)?.settingGroups?.sGEdit).to.be.undefined; + expect(Services(diffServices)?.settingGroups?.confSG).to.be.undefined; + + expect(Services(baseServices)?.settingGroups?.sGEdit?.resvTms).to.be.false; + expect(Services(baseServices)?.settingGroups?.confSG?.resvTms).to.be.false; + + expect(Services(oDiffServices)?.settingGroups?.confSG?.resvTms).to.be.true; + expect(Services(oDiffServices)?.settingGroups?.sGEdit?.resvTms).to.be.true; + }); + + it("return optional ConfDataSet ", () => { + expect(Services(emptyServices)?.confDataSet).to.be.undefined; + + expect(Services(diffServices)?.confDataSet?.modify).to.be.false; + expect(Services(diffServices)?.confDataSet?.maxAttributes).to.be.undefined; + expect(Services(diffServices)?.confDataSet?.max).to.equal(5); + + expect(Services(baseServices)?.confDataSet?.max).to.be.equal(0); + expect(Services(baseServices)?.confDataSet?.modify).to.be.true; + expect(Services(baseServices)?.confDataSet?.maxAttributes).to.be.equal(0); + }); + + it("return optional DynDataSet ", () => { + expect(Services(emptyServices)?.dynDataSet).to.be.undefined; + + expect(Services(diffServices)?.dynDataSet?.maxAttributes).to.be.undefined; + expect(Services(diffServices)?.dynDataSet?.max).to.equal(5); + + expect(Services(baseServices)?.dynDataSet?.max).to.be.equal(0); + expect(Services(baseServices)?.dynDataSet?.maxAttributes).to.be.equal(0); + }); + + it("return optional ConfReportControl ", () => { + expect(Services(emptyServices)?.confReportControl).to.be.undefined; + + expect(Services(diffServices)?.confReportControl?.bufConf).to.be.true; + expect(Services(diffServices)?.confReportControl?.bufMode).to.equal( + "buffered", + ); + expect(Services(diffServices)?.confReportControl?.maxBuf).to.equal(3); + expect(Services(diffServices)?.confReportControl?.max).to.equal(5); + + expect(Services(baseServices)?.confReportControl?.max).to.be.equal(0); + expect(Services(baseServices)?.confReportControl?.bufMode).to.be.equal( + "both", + ); + }); + + it("return optional ReportSettings ", () => { + expect(Services(emptyServices)?.reportSettings).to.be.undefined; + + expect(Services(diffServices)?.reportSettings?.cbName).to.be.equal("Conf"); + expect(Services(diffServices)?.reportSettings?.datSet).to.be.equal("Dyn"); + expect(Services(diffServices)?.reportSettings?.rptID).to.be.equal("Conf"); + expect(Services(diffServices)?.reportSettings?.optFields).to.be.equal( + "Dyn", + ); + expect(Services(diffServices)?.reportSettings?.bufTime).to.be.equal("Conf"); + expect(Services(diffServices)?.reportSettings?.trgOps).to.be.equal("Dyn"); + expect(Services(diffServices)?.reportSettings?.intgPd).to.be.equal("Conf"); + expect(Services(diffServices)?.reportSettings?.resvTms).to.be.true; + expect(Services(diffServices)?.reportSettings?.owner).to.be.true; + + expect(Services(baseServices)?.reportSettings?.cbName).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.datSet).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.rptID).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.optFields).to.be.equal( + "Fix", + ); + expect(Services(baseServices)?.reportSettings?.trgOps).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.bufTime).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.intgPd).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.resvTms).to.be.false; + expect(Services(baseServices)?.reportSettings?.owner).to.be.false; + }); + + it("return optional LogSettings ", () => { + expect(Services(emptyServices)?.reportSettings).to.be.undefined; + + expect(Services(diffServices)?.logSettings?.cbName).to.be.equal("Conf"); + expect(Services(diffServices)?.logSettings?.datSet).to.be.equal("Dyn"); + expect(Services(diffServices)?.logSettings?.logEna).to.be.equal("Conf"); + expect(Services(diffServices)?.logSettings?.trgOps).to.be.equal("Dyn"); + expect(Services(diffServices)?.logSettings?.intgPd).to.be.equal("Conf"); + + expect(Services(baseServices)?.logSettings?.cbName).to.be.equal("Fix"); + expect(Services(baseServices)?.logSettings?.datSet).to.be.equal("Fix"); + expect(Services(baseServices)?.logSettings?.logEna).to.be.equal("Fix"); + expect(Services(baseServices)?.logSettings?.trgOps).to.be.equal("Fix"); + expect(Services(baseServices)?.reportSettings?.intgPd).to.be.equal("Fix"); + }); + + it("return optional GSESettings ", () => { + expect(Services(emptyServices)?.gSESettings).to.be.undefined; + + expect(Services(diffServices)?.gSESettings?.cbName).to.be.equal("Conf"); + expect(Services(diffServices)?.gSESettings?.datSet).to.be.equal("Dyn"); + expect(Services(diffServices)?.gSESettings?.appID).to.be.equal("Conf"); + expect(Services(diffServices)?.gSESettings?.dataLabel).to.be.equal("Dyn"); + expect(Services(diffServices)?.gSESettings?.kdaParticipant).to.be.true; + expect(Services(diffServices)?.gSESettings?.mcSecurity?.signature).to.be + .true; + expect(Services(diffServices)?.gSESettings?.mcSecurity?.encryption).to.be + .true; + + expect(Services(baseServices)?.gSESettings?.cbName).to.be.equal("Fix"); + expect(Services(baseServices)?.gSESettings?.datSet).to.be.equal("Fix"); + expect(Services(baseServices)?.gSESettings?.appID).to.be.equal("Fix"); + expect(Services(baseServices)?.gSESettings?.dataLabel).to.be.equal("Fix"); + expect(Services(baseServices)?.gSESettings?.kdaParticipant).to.be.false; + expect(Services(baseServices)?.gSESettings?.mcSecurity?.signature).to.be + .false; + expect(Services(baseServices)?.gSESettings?.mcSecurity?.encryption).to.be + .false; + + expect(Services(oDiffServices)?.gSESettings?.mcSecurity).to.be.undefined; + }); + + it("return optional SMVSettings ", () => { + expect(Services(emptyServices)?.sMVSettings).to.be.undefined; + + expect(Services(diffServices)?.sMVSettings?.cbName).to.be.equal("Conf"); + expect(Services(diffServices)?.sMVSettings?.datSet).to.be.equal("Dyn"); + expect(Services(diffServices)?.sMVSettings?.svID).to.be.equal("Conf"); + expect(Services(diffServices)?.sMVSettings?.nofASDU).to.be.equal("Dyn"); + expect(Services(diffServices)?.sMVSettings?.optFields).to.be.equal("Conf"); + expect(Services(diffServices)?.sMVSettings?.smpRate).to.be.equal("Dyn"); + expect(Services(diffServices)?.sMVSettings?.samplesPerSec).to.be.true; + expect(Services(diffServices)?.sMVSettings?.pdcTimeStamp).to.be.true; + expect(Services(diffServices)?.sMVSettings?.synchSrcId).to.be.true; + expect(Services(diffServices)?.gSESettings?.kdaParticipant).to.be.true; + expect(Services(diffServices)?.gSESettings?.mcSecurity?.signature).to.be + .true; + expect(Services(diffServices)?.gSESettings?.mcSecurity?.encryption).to.be + .true; + + expect(Services(baseServices)?.sMVSettings?.cbName).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.datSet).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.svID).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.nofASDU).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.optFields).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.smpRate).to.be.equal("Fix"); + expect(Services(baseServices)?.sMVSettings?.samplesPerSec).to.be.false; + expect(Services(baseServices)?.sMVSettings?.pdcTimeStamp).to.be.false; + expect(Services(baseServices)?.sMVSettings?.synchSrcId).to.be.false; + expect(Services(baseServices)?.gSESettings?.kdaParticipant).to.be.false; + expect(Services(baseServices)?.gSESettings?.mcSecurity?.signature).to.be + .false; + expect(Services(baseServices)?.gSESettings?.mcSecurity?.encryption).to.be + .false; + + expect(Services(oDiffServices)?.sMVSettings?.mcSecurity).to.be.undefined; + }); + + it("return optional GOOSE ", () => { + expect(Services(emptyServices)?.gOOSE).to.be.undefined; + + expect(Services(diffServices)?.gOOSE?.max).to.be.equal(6); + expect(Services(diffServices)?.gOOSE?.fixedOffs).to.be.true; + expect(Services(diffServices)?.gOOSE?.goose).to.be.false; + expect(Services(diffServices)?.gOOSE?.rGOOSE).to.be.true; + + expect(Services(baseServices)?.gOOSE?.max).to.be.equal(0); + expect(Services(baseServices)?.gOOSE?.fixedOffs).to.be.false; + expect(Services(baseServices)?.gOOSE?.goose).to.be.true; + expect(Services(baseServices)?.gOOSE?.rGOOSE).to.be.false; + }); + + it("return optional SMVsc ", () => { + expect(Services(emptyServices)?.sMVsc).to.be.undefined; + + expect(Services(diffServices)?.sMVsc?.max).to.be.equal(7); + expect(Services(diffServices)?.sMVsc?.delivery).to.equal("both"); + expect(Services(diffServices)?.sMVsc?.deliveryConf).to.be.true; + expect(Services(diffServices)?.sMVsc?.sv).to.be.false; + expect(Services(diffServices)?.sMVsc?.rSV).to.be.true; + + expect(Services(baseServices)?.sMVsc?.max).to.be.equal(0); + expect(Services(baseServices)?.sMVsc?.delivery).to.equal("multicast"); + expect(Services(baseServices)?.sMVsc?.deliveryConf).to.be.false; + expect(Services(baseServices)?.sMVsc?.sv).to.be.true; + expect(Services(baseServices)?.sMVsc?.rSV).to.be.false; + }); + + it("return optional FileHandling ", () => { + expect(Services(emptyServices)?.fileHandling).to.be.undefined; + + expect(Services(diffServices)?.fileHandling?.mms).to.false; + expect(Services(diffServices)?.fileHandling?.ftp).to.true; + expect(Services(diffServices)?.fileHandling?.ftps).to.be.true; + + expect(Services(baseServices)?.fileHandling?.mms).to.true; + expect(Services(baseServices)?.fileHandling?.ftp).to.false; + expect(Services(baseServices)?.fileHandling?.ftps).to.be.false; + }); + + it("return optional ConfLNs ", () => { + expect(Services(emptyServices)?.confLNs).to.be.undefined; + + expect(Services(diffServices)?.confLNs?.fixPrefix).to.true; + expect(Services(diffServices)?.confLNs?.fixLnInst).to.true; + + expect(Services(baseServices)?.confLNs?.fixPrefix).to.false; + expect(Services(baseServices)?.confLNs?.fixLnInst).to.false; + }); + + it("return optional ClientServices ", () => { + expect(Services(emptyServices)?.clientServices).to.be.undefined; + + expect(Services(diffServices)?.clientServices?.goose).to.true; + expect(Services(diffServices)?.clientServices?.gsse).to.true; + expect(Services(diffServices)?.clientServices?.bufReport).to.true; + expect(Services(diffServices)?.clientServices?.unbufReport).to.true; + expect(Services(diffServices)?.clientServices?.readLog).to.true; + expect(Services(diffServices)?.clientServices?.sv).to.true; + expect(Services(diffServices)?.clientServices?.supportsLdName).to.true; + expect(Services(diffServices)?.clientServices?.rGOOSE).to.true; + expect(Services(diffServices)?.clientServices?.rSV).to.true; + expect(Services(diffServices)?.clientServices?.noIctBinding).to.true; + expect(Services(diffServices)?.clientServices?.maxAttributes).to.equal(7); + expect(Services(diffServices)?.clientServices?.maxReports).to.equal(8); + expect(Services(diffServices)?.clientServices?.maxGOOSE).to.be.equal(9); + expect(Services(diffServices)?.clientServices?.maxSMV).to.equal(10); + + expect(Services(baseServices)?.clientServices?.goose).to.false; + expect(Services(baseServices)?.clientServices?.gsse).to.false; + expect(Services(baseServices)?.clientServices?.bufReport).to.false; + expect(Services(baseServices)?.clientServices?.unbufReport).to.false; + expect(Services(baseServices)?.clientServices?.readLog).to.false; + expect(Services(baseServices)?.clientServices?.sv).to.false; + expect(Services(baseServices)?.clientServices?.supportsLdName).to.false; + expect(Services(baseServices)?.clientServices?.rGOOSE).to.false; + expect(Services(baseServices)?.clientServices?.rSV).to.false; + expect(Services(baseServices)?.clientServices?.noIctBinding).to.false; + expect(Services(baseServices)?.clientServices?.maxAttributes).to.be + .undefined; + expect(Services(baseServices)?.clientServices?.maxGOOSE).to.be.undefined; + expect(Services(baseServices)?.clientServices?.maxSMV).to.be.undefined; + expect(Services(baseServices)?.clientServices?.maxReports).to.be.undefined; + }); + + it("return optional SupSubscription ", () => { + expect(Services(emptyServices)?.supSubscription).to.be.undefined; + + expect(Services(diffServices)?.supSubscription?.maxGo).to.equal(7); + expect(Services(diffServices)?.supSubscription?.maxSv).to.equal(8); + + expect(Services(baseServices)?.supSubscription?.maxGo).to.equal(0); + expect(Services(baseServices)?.supSubscription?.maxSv).to.equal(0); + }); + + it("return optional ConfSigRef ", () => { + expect(Services(emptyServices)?.confSigRef).to.be.undefined; + expect(Services(diffServices)?.confSigRef?.max).to.equal(9); + expect(Services(baseServices)?.confSigRef?.max).to.equal(0); + }); + + it("return optional ValueHandling ", () => { + expect(Services(emptyServices)?.valueHandling).to.be.undefined; + expect(Services(diffServices)?.valueHandling?.setToRO).to.be.true; + expect(Services(baseServices)?.valueHandling?.setToRO).to.false; + }); + + it("return optional RedProt ", () => { + expect(Services(emptyServices)?.redProt).to.be.undefined; + + expect(Services(diffServices)?.redProt?.hsr).to.true; + expect(Services(diffServices)?.redProt?.prp).to.true; + expect(Services(diffServices)?.redProt?.rstp).to.true; + + expect(Services(baseServices)?.redProt?.hsr).to.false; + expect(Services(baseServices)?.redProt?.prp).to.false; + expect(Services(baseServices)?.redProt?.rstp).to.false; + }); + + it("return optional TimeSyncProt ", () => { + expect(Services(emptyServices)?.timeSyncProt).to.be.undefined; + + expect(Services(diffServices)?.timeSyncProt?.sntp).to.false; + expect(Services(diffServices)?.timeSyncProt?.iec61850_9_3).to.true; + expect(Services(diffServices)?.timeSyncProt?.c37_238).to.true; + expect(Services(diffServices)?.timeSyncProt?.other).to.true; + + expect(Services(baseServices)?.timeSyncProt?.sntp).to.true; + expect(Services(baseServices)?.timeSyncProt?.iec61850_9_3).to.false; + expect(Services(baseServices)?.timeSyncProt?.c37_238).to.false; + expect(Services(baseServices)?.timeSyncProt?.other).to.false; + }); + + it("return optional CommProt ", () => { + expect(Services(emptyServices)?.commProt).to.be.undefined; + expect(Services(diffServices)?.commProt?.ipv6).to.be.true; + expect(Services(baseServices)?.commProt?.ipv6).to.false; + }); + + it("returns same description with semantically equal LDevice's", () => + expect(JSON.stringify(Services(baseServices))).to.equal( + JSON.stringify(Services(equalServices)), + )); + + it("returns different description with unequal LDevice elements", () => + expect(JSON.stringify(Services(baseServices))).to.not.equal( + JSON.stringify(Services(diffServices)), + )); +}); diff --git a/describe/Services.ts b/describe/Services.ts new file mode 100644 index 00000000..2b8966c5 --- /dev/null +++ b/describe/Services.ts @@ -0,0 +1,684 @@ +interface CommProt { + /** attribute ipv6 defaulting to false */ + ipv6: boolean; +} + +interface RedProt { + /** attribute hsr defaulting to false */ + hsr: boolean; + /** attribute prp defaulting to false */ + prp: boolean; + /** attribute rstp defaulting to false */ + rstp: boolean; +} + +interface ValueHandling { + /** attribute setToRO defaulting to true */ + setToRO: boolean; +} + +interface SupSubscription { + /** attribute maxGo */ + maxGo: number; + /** attribute maxSv */ + maxSv: number; +} + +interface TimeSyncProt { + /** attribute sntp defaulting to true */ + sntp: boolean; + /** attribute iec61850_9_3 defaulting to false */ + iec61850_9_3: boolean; + /** attribute c37_238 defaulting to false */ + c37_238: boolean; + /** attribute other defaulting to false */ + other: boolean; +} + +interface ClientServices { + /** attribute goose defaulting to false */ + goose: boolean; + /** attribute gsse defaulting to false */ + gsse: boolean; + /** attribute bufReport defaulting to false */ + bufReport: boolean; + /** attribute unbufReport defaulting to false */ + unbufReport: boolean; + /** attribute readLog defaulting to false */ + readLog: boolean; + /** attribute sv defaulting to false */ + sv: boolean; + /** attribute supportsLdName defaulting to false */ + supportsLdName: boolean; + /** attribute maxAttributes */ + maxAttributes?: number; + /** attribute maxReports */ + maxReports?: number; + /** attribute maxGOOSE */ + maxGOOSE?: number; + /** attribute maxSMV */ + maxSMV?: number; + /** attribute rGOOSE defaulting to false */ + rGOOSE: boolean; + /** attribute rSV defaulting to false */ + rSV: boolean; + /** attribute noIctBinding defaulting to false */ + noIctBinding: boolean; + /** child McSecurity */ + mcSecurity?: McSecurity; + /** child TimeSyncProt */ + timeSyncProt?: TimeSyncProt; +} + +interface ConfLNs { + /** attribute fixPrefix defaulting to false */ + fixPrefix: boolean; + /** attribute fixLnInst defaulting to false */ + fixLnInst: boolean; +} + +interface FileHandling { + /** attribute mms defaulting to true */ + mms: boolean; + /** attribute ftp defaulting to false */ + ftp: boolean; + /** attribute ftps defaulting to false */ + ftps: boolean; +} + +interface ServiceWithMax { + /** ServicesWithMax attributes max */ + max: number; +} + +interface SMVsc extends ServiceWithMax { + /** attribute delivery defaulting to "multicast" */ + delivery: string; + /** attribute deliveryConf defaulting to false */ + deliveryConf: boolean; + /** attribute sv defaulting to false */ + sv: boolean; + /** attribute rSV defaulting to false */ + rSV: boolean; +} + +interface GOOSEcapabilities extends ServiceWithMax { + /** attribute fixedOffs defaulting to false */ + fixedOffs: boolean; + /** attribute goose defaulting to true */ + goose: boolean; + /** attribute rGOOSE defaulting to false */ + rGOOSE: boolean; +} + +interface McSecurity { + /** attribute signature defaulting false */ + signature: boolean; + /** attribute encryption defaulting false */ + encryption: boolean; +} + +interface ServiceSettings { + /** attribute cbName defaulting to "Fix" */ + cbName: string; + /** attribute datSet defaulting to "Fix" */ + datSet: string; +} + +interface SMVSettings extends ServiceSettings { + /** attribute svID defaulting to "Fix" */ + svID: string; + /** attribute optFields defaulting to "Fix" */ + optFields: string; + /** attribute smpRate defaulting to "Fix" */ + smpRate: string; + /** attribute samplesPerSec defaulting to false */ + samplesPerSec: boolean; + /** attribute pdcTimeStamp defaulting to false */ + pdcTimeStamp: boolean; + /** attribute synchSrcId defaulting to false */ + synchSrcId: boolean; + /** attribute nofASDU defaulting to "Fix" */ + nofASDU: string; + /** attribute kdaParticipant defaulting to false */ + kdaParticipant: boolean; + /** children SmpRate */ + SmpRate: number[]; + /** children SamplesPerSec */ + SamplesPerSec: number[]; + /** children SecPerSamples*/ + SecPerSamples: number[]; + /** child element McSecurity */ + mcSecurity?: McSecurity; +} + +interface GSESettings extends ServiceSettings { + /** attribute appID defaulting to "Fix" */ + appID: string; + /** attribute dataLabel defaulting to "Fix" */ + dataLabel: string; + /** attribute kdaParticipant defaulting to false */ + kdaParticipant: boolean; + /** child element McSecurity */ + mcSecurity?: McSecurity; +} + +interface LogSettings extends ServiceSettings { + /** attribute logEna defaulting to "Fix" */ + logEna: string; + /** attribute trgOps defaulting to "Fix" */ + trgOps: string; + /** attribute intgPd defaulting to "Fix" */ + intgPd: string; +} + +interface ReportSettings extends ServiceSettings { + /** attribute rptID defaulting to "Fix" */ + rptID: string; + /** attribute optFields defaulting to "Fix" */ + optFields: string; + /** attribute bufTime defaulting to "Fix" */ + bufTime: string; + /** attribute trgOps defaulting to "Fix" */ + trgOps: string; + /** attribute intgPd defaulting to "Fix" */ + intgPd: string; + /** attribute resvTms defaulting to false */ + resvTms: boolean; + /** attribute owner defaulting to false */ + owner: boolean; +} + +interface ServiceConfReportControl extends ServiceWithMax { + /** bufMode attribute defaulting to both */ + bufMode: string; + /** bufConf attribute defaulting to false */ + bufConf: boolean; + /** maxBuf attribute defaulting to max */ + maxBuf: number; +} + +interface ServiceWithMaxAndMaxAttributes extends ServiceWithMax { + maxAttributes?: number; +} + +interface ServiceForConfDataSet extends ServiceWithMaxAndMaxAttributes { + /** ServiceForConfDataSet attributes defaulting to true */ + modify: boolean; +} + +interface ResvTms { + /** resvTms attribute defaulting false */ + resvTms: boolean; +} + +interface SettingGroups { + /** SettingGroups child SGEdit */ + sGEdit?: ResvTms; + /** SettingGroups child ConfSG */ + confSG?: ResvTms; +} + +interface ServiceWithOptionalMax { + max?: number; +} + +export interface ServicesDescription { + /** Services attribute nameLength defaulting to 32 */ + nameLength: number; + /** Services child DynAssociation */ + dynAssociation?: ServiceWithOptionalMax; + /** Services child SettingGroups */ + settingGroups?: SettingGroups; + /** Services child GetDirectory */ + getDirectory: boolean; + /** Services child GetDataObjectDefinition */ + getDataObjectDefinition: boolean; + /** Services child DataObjectDirectory */ + dataObjectDirectory: boolean; + /** Services child GetDataSetValue */ + getDataSetValue: boolean; + /** Services child SetDataSetValue */ + setDataSetValue: boolean; + /** Services child DataSetDirectory */ + dataSetDirectory: boolean; + /** Services child ConfDataSet */ + confDataSet?: ServiceForConfDataSet; + /** Services child DynDataSet */ + dynDataSet?: ServiceWithMaxAndMaxAttributes; + /** Services child ReadWrite */ + readWrite: boolean; + /** Services child TimerActivatedControl */ + timerActivatedControl: boolean; + /** Services child ConfReportControl */ + confReportControl?: ServiceConfReportControl; + /** Services child GetCBValues */ + getCBValues: boolean; + /** Services child ConfLogControl */ + confLogControl?: ServiceWithMax; + /** Services child ReportSettings */ + reportSettings?: ReportSettings; + /** Services child LogSettings */ + logSettings?: LogSettings; + /** Services child GSESettings */ + gSESettings?: GSESettings; + /** Services child SMVSettings */ + sMVSettings?: SMVSettings; + /** Services child GSEDir */ + gSEDir: boolean; + /** Services child GOOSE */ + gOOSE?: GOOSEcapabilities; + /** Services child SMVsc */ + sMVsc?: SMVsc; + /** Services child FileHandling */ + fileHandling?: FileHandling; + /** Services child ConfLNs */ + confLNs?: ConfLNs; + /** Services child ClientServices */ + clientServices?: ClientServices; + /** Services child ConfLdName */ + confLdName: boolean; + /** Services child SupSubscription */ + supSubscription?: SupSubscription; + /** Services child ConfSigRef */ + confSigRef?: ServiceWithMax; + /** Services child ValueHandling */ + valueHandling?: ValueHandling; + /** Services child RedProt */ + redProt?: RedProt; + /** Services child TimeSyncProt */ + timeSyncProt?: TimeSyncProt; + /** Services child CommProt */ + commProt?: CommProt; +} + +function commProt(element: Element): CommProt { + return { + ipv6: element.getAttribute("ipv6") === "true" ? true : false, + }; +} + +function redProt(element: Element): RedProt { + return { + hsr: element.getAttribute("hsr") === "true" ? true : false, + prp: element.getAttribute("prp") === "true" ? true : false, + rstp: element.getAttribute("rstp") === "true" ? true : false, + }; +} + +function valueHandling(element: Element): ValueHandling { + return { + setToRO: element.getAttribute("setToRO") === "true" ? true : false, + }; +} + +function supSubscription(element: Element): SupSubscription { + return { + maxGo: parseInt(element.getAttribute("maxGo") ?? "0", 10), + maxSv: parseInt(element.getAttribute("maxSv") ?? "0", 10), + }; +} + +function timeSyncProt(element: Element): TimeSyncProt { + return { + sntp: element.getAttribute("sntp") === "false" ? false : true, + iec61850_9_3: + element.getAttribute("iec61850_9_3") === "true" ? true : false, + c37_238: element.getAttribute("c37_238") === "true" ? true : false, + other: element.getAttribute("other") === "true" ? true : false, + }; +} + +function clientServices(element: Element): ClientServices { + const clientServices: ClientServices = { + goose: element.getAttribute("goose") === "true" ? true : false, + gsse: element.getAttribute("gsse") === "true" ? true : false, + bufReport: element.getAttribute("bufReport") === "true" ? true : false, + unbufReport: element.getAttribute("unbufReport") === "true" ? true : false, + readLog: element.getAttribute("readLog") === "true" ? true : false, + sv: element.getAttribute("sv") === "true" ? true : false, + supportsLdName: + element.getAttribute("supportsLdName") === "true" ? true : false, + rGOOSE: element.getAttribute("rGOOSE") === "true" ? true : false, + rSV: element.getAttribute("rSV") === "true" ? true : false, + noIctBinding: + element.getAttribute("noIctBinding") === "true" ? true : false, + }; + + const maxAttributes = element.getAttribute("maxAttributes"); + if (maxAttributes) clientServices.maxAttributes = parseInt(maxAttributes, 10); + + const maxReports = element.getAttribute("maxReports"); + if (maxReports) clientServices.maxReports = parseInt(maxReports, 10); + + const maxGOOSE = element.getAttribute("maxGOOSE"); + if (maxGOOSE) clientServices.maxGOOSE = parseInt(maxGOOSE, 10); + + const maxSMV = element.getAttribute("maxSMV"); + if (maxSMV) clientServices.maxSMV = parseInt(maxSMV, 10); + + const mcSecurityElement = element.querySelector(":scope > McSecurity"); + if (mcSecurityElement) + clientServices.mcSecurity = mcSecurity(mcSecurityElement); + + const timeSyncProtElement = element.querySelector(":scope > TimeSyncProt"); + if (timeSyncProtElement) + clientServices.timeSyncProt = timeSyncProt(timeSyncProtElement); + + return clientServices; +} + +function confLNs(element: Element): ConfLNs { + return { + fixPrefix: element.getAttribute("fixPrefix") === "true" ? true : false, + fixLnInst: element.getAttribute("fixLnInst") === "true" ? true : false, + }; +} + +function fileHandling(element: Element): FileHandling { + return { + mms: element.getAttribute("mms") === "false" ? false : true, + ftp: element.getAttribute("ftp") === "true" ? true : false, + ftps: element.getAttribute("ftps") === "true" ? true : false, + }; +} + +function serviceWithMax(element: Element): ServiceWithMax { + return { max: parseInt(element.getAttribute("max") ?? "0", 10) }; +} + +function sMVsc(element: Element): SMVsc { + return { + ...serviceWithMax(element), + delivery: element.getAttribute("delivery") ?? "multicast", + deliveryConf: + element.getAttribute("deliveryConf") === "true" ? true : false, + sv: element.getAttribute("sv") === "false" ? false : true, + rSV: element.getAttribute("rSV") === "true" ? true : false, + }; +} + +function gOOSEcapabilities(element: Element): GOOSEcapabilities { + return { + ...serviceWithMax(element), + fixedOffs: element.getAttribute("fixedOffs") === "true" ? true : false, + goose: element.getAttribute("goose") === "false" ? false : true, + rGOOSE: element.getAttribute("rGOOSE") === "true" ? true : false, + }; +} + +function mcSecurity(element: Element): McSecurity { + return { + signature: element.getAttribute("signature") === "true" ? true : false, + encryption: element.getAttribute("encryption") === "true" ? true : false, + }; +} + +function serviceSettings(element: Element): ServiceSettings { + return { + cbName: element.getAttribute("cbName") ?? "Fix", + datSet: element.getAttribute("datSet") ?? "Fix", + }; +} + +function rate(element: Element, childTag: string): number[] { + return Array.from(element.querySelectorAll(`:scope > ${childTag}`)) + .map((child) => parseFloat(child.textContent ?? "0")) + .sort(); +} + +function sMVSettings(element: Element): SMVSettings { + const sMVSettings: SMVSettings = { + ...serviceSettings(element), + svID: element.getAttribute("svID") ?? "Fix", + optFields: element.getAttribute("optFields") ?? "Fix", + smpRate: element.getAttribute("smpRate") ?? "Fix", + nofASDU: element.getAttribute("nofASDU") ?? "Fix", + samplesPerSec: + element.getAttribute("samplesPerSec") === "true" ? true : false, + pdcTimeStamp: + element.getAttribute("pdcTimeStamp") === "true" ? true : false, + synchSrcId: element.getAttribute("synchSrcId") === "true" ? true : false, + kdaParticipant: + element.getAttribute("kdaParticipant") === "true" ? true : false, + SmpRate: rate(element, "SmpRate"), + SamplesPerSec: rate(element, "SamplesPerSec"), + SecPerSamples: rate(element, "SecPerSamples"), + }; + + const mcSecurityElement = element.querySelector(":scope > McSecurity"); + if (mcSecurityElement) sMVSettings.mcSecurity = mcSecurity(mcSecurityElement); + + return sMVSettings; +} + +function gSESettings(element: Element): GSESettings { + const gSESettings: GSESettings = { + ...serviceSettings(element), + appID: element.getAttribute("appID") ?? "Fix", + dataLabel: element.getAttribute("dataLabel") ?? "Fix", + kdaParticipant: + element.getAttribute("kdaParticipant") === "true" ? true : false, + }; + + const mcSecurityElement = element.querySelector(":scope > McSecurity"); + if (mcSecurityElement) gSESettings.mcSecurity = mcSecurity(mcSecurityElement); + + return gSESettings; +} + +function logSettings(element: Element): LogSettings { + return { + ...serviceSettings(element), + logEna: element.getAttribute("logEna") ?? "Fix", + trgOps: element.getAttribute("trgOps") ?? "Fix", + intgPd: element.getAttribute("intgPd") ?? "Fix", + }; +} + +function reportSettings(element: Element): ReportSettings { + return { + ...serviceSettings(element), + rptID: element.getAttribute("rptID") ?? "Fix", + optFields: element.getAttribute("optFields") ?? "Fix", + bufTime: element.getAttribute("bufTime") ?? "Fix", + trgOps: element.getAttribute("trgOps") ?? "Fix", + intgPd: element.getAttribute("intgPd") ?? "Fix", + resvTms: element.getAttribute("resvTms") === "true" ? true : false, + owner: element.getAttribute("owner") === "true" ? true : false, + }; +} + +function serviceConfReportControl(element: Element): ServiceConfReportControl { + const max = serviceWithMax(element).max; + const serviceConfReportControl: ServiceConfReportControl = { + max, + bufMode: element.getAttribute("bufMode") ?? "both", + bufConf: element.getAttribute("bufConf") === "true" ? true : false, + maxBuf: element.getAttribute("maxBuf") + ? parseInt(element.getAttribute("maxBuf")!, 10) + : max, + }; + + return serviceConfReportControl; +} + +function serviceWithMaxAndMaxAttributes( + element: Element, +): ServiceWithMaxAndMaxAttributes { + const serviceWithMaxAndMaxAttributes: ServiceWithMaxAndMaxAttributes = { + ...serviceWithMax(element), + }; + const maxAttributes = element.getAttribute("maxAttributes"); + if (maxAttributes) + serviceWithMaxAndMaxAttributes.maxAttributes = parseInt(maxAttributes, 10); + + return serviceWithMaxAndMaxAttributes; +} + +function serviceForConfDataSet(element: Element): ServiceForConfDataSet { + return { + ...serviceWithMaxAndMaxAttributes(element), + modify: element.getAttribute("modify") === "false" ? false : true, + }; +} + +function resvTms(element: Element): ResvTms { + return { + resvTms: element.getAttribute("resvTms") === "true" ? true : false, + }; +} + +function settingGroups(element: Element): SettingGroups { + const settingGroups: SettingGroups = {}; + + const sGEditElement = element.querySelector(":scope > SGEdit"); + if (sGEditElement) settingGroups.sGEdit = resvTms(sGEditElement); + + const confSGElement = element.querySelector(":scope > ConfSG"); + if (confSGElement) settingGroups.confSG = resvTms(confSGElement); + + return settingGroups; +} + +function servicesWithOptionalMax(element: Element): ServiceWithOptionalMax { + const servicesWithOptionalMax: ServiceWithOptionalMax = {}; + + const max = element.getAttribute("max"); + if (max) servicesWithOptionalMax.max = parseInt(max, 10); + + return servicesWithOptionalMax; +} + +export function Services(element: Element): ServicesDescription { + const servicesDescription: ServicesDescription = { + nameLength: parseInt(element.getAttribute("nameLength") ?? "32", 10), + getDirectory: element.querySelector(":scope > GetDirectory") ? true : false, + getDataObjectDefinition: element.querySelector( + ":scope > GetDataObjectDefinition", + ) + ? true + : false, + dataObjectDirectory: element.querySelector(":scope > DataObjectDirectory") + ? true + : false, + getDataSetValue: element.querySelector(":scope > GetDataSetValue") + ? true + : false, + setDataSetValue: element.querySelector(":scope > SetDataSetValue") + ? true + : false, + dataSetDirectory: element.querySelector(":scope > DataSetDirectory") + ? true + : false, + readWrite: element.querySelector(":scope > ReadWrite") ? true : false, + timerActivatedControl: element.querySelector( + ":scope > TimerActivatedControl", + ) + ? true + : false, + getCBValues: element.querySelector(":scope > GetCBValues") ? true : false, + gSEDir: element.querySelector(":scope > GSEDir") ? true : false, + confLdName: element.querySelector(":scope > ConfLdName") ? true : false, + }; + + const dynAssociationElement = element.querySelector( + ":scope > DynAssociation", + ); + if (dynAssociationElement) + servicesDescription.dynAssociation = servicesWithOptionalMax( + dynAssociationElement, + ); + + const settingGroupsElement = element.querySelector(":scope > SettingGroups"); + if (settingGroupsElement) + servicesDescription.settingGroups = settingGroups(settingGroupsElement); + + const confDataSetElement = element.querySelector(":scope > ConfDataSet"); + if (confDataSetElement) + servicesDescription.confDataSet = serviceForConfDataSet(confDataSetElement); + + const dynDataSetElement = element.querySelector(":scope > DynDataSet"); + if (dynDataSetElement) + servicesDescription.dynDataSet = + serviceWithMaxAndMaxAttributes(dynDataSetElement); + + const confReportControlElement = element.querySelector( + ":scope > ConfReportControl", + ); + if (confReportControlElement) + servicesDescription.confReportControl = serviceConfReportControl( + confReportControlElement, + ); + + const confLogControlElement = element.querySelector( + ":scope > ConfLogControl", + ); + if (confLogControlElement) + servicesDescription.confLogControl = serviceWithMax(confLogControlElement); + + const reportSettingsElement = element.querySelector( + ":scope > ReportSettings", + ); + if (reportSettingsElement) + servicesDescription.reportSettings = reportSettings(reportSettingsElement); + + const logSettingsElement = element.querySelector(":scope > LogSettings"); + if (logSettingsElement) + servicesDescription.logSettings = logSettings(logSettingsElement); + + const gSESettingsElement = element.querySelector(":scope > GSESettings"); + if (gSESettingsElement) + servicesDescription.gSESettings = gSESettings(gSESettingsElement); + + const sMVSettingsElement = element.querySelector(":scope > SMVSettings"); + if (sMVSettingsElement) + servicesDescription.sMVSettings = sMVSettings(sMVSettingsElement); + + const gOOSEElement = element.querySelector(":scope > GOOSE"); + if (gOOSEElement) servicesDescription.gOOSE = gOOSEcapabilities(gOOSEElement); + + const sMVscElement = element.querySelector(":scope > SMVsc"); + if (sMVscElement) servicesDescription.sMVsc = sMVsc(sMVscElement); + + const fileHandlingElement = element.querySelector(":scope > FileHandling"); + if (fileHandlingElement) + servicesDescription.fileHandling = fileHandling(fileHandlingElement); + + const confLNsElement = element.querySelector(":scope > ConfLNs"); + if (confLNsElement) servicesDescription.confLNs = confLNs(confLNsElement); + + const clientServicesElement = element.querySelector( + ":scope > ClientServices", + ); + if (clientServicesElement) + servicesDescription.clientServices = clientServices(clientServicesElement); + + const supSubscriptionElement = element.querySelector( + ":scope > SupSubscription", + ); + if (supSubscriptionElement) + servicesDescription.supSubscription = supSubscription( + supSubscriptionElement, + ); + + const confSigRefElement = element.querySelector(":scope > ConfSigRef"); + if (confSigRefElement) + servicesDescription.confSigRef = serviceWithMax(confSigRefElement); + + const valueHandlingElement = element.querySelector(":scope > ValueHandling"); + if (valueHandlingElement) + servicesDescription.valueHandling = valueHandling(valueHandlingElement); + + const redProtElement = element.querySelector(":scope > RedProt"); + if (redProtElement) servicesDescription.redProt = redProt(redProtElement); + + const timeSyncProtElement = element.querySelector(":scope > TimeSyncProt"); + if (timeSyncProtElement) + servicesDescription.timeSyncProt = timeSyncProt(timeSyncProtElement); + + const commProtElement = element.querySelector(":scope > CommProt"); + if (commProtElement) servicesDescription.commProt = commProt(commProtElement); + + return servicesDescription; +}