diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java index d2d5c65a6..b393d5c8f 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java @@ -24,7 +24,6 @@ import java.math.BigInteger; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -99,52 +98,123 @@ public List configureNetworkForAllControlBlocks(SCL scd, CBCom cb @Override public List configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom, List subnetworksToReuse) { - Map appIdsAndMacsToReuse = subnetworksToReuse != null && !subnetworksToReuse.isEmpty() ? - computeAppIdsAndMacToReuse(scd, subnetworksToReuse) + Map appIdsToReuse = subnetworksToReuse != null && !subnetworksToReuse.isEmpty() ? + computeAppIdsToReuse(scd, subnetworksToReuse) + : Collections.emptyMap(); + Map macsToReuse = subnetworksToReuse != null && !subnetworksToReuse.isEmpty() ? + computeMacToReuse(scd, subnetworksToReuse) : Collections.emptyMap(); return Stream.concat( - configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.GOOSE), - configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.SV)) + configureNetworkForControlBlocks(scd, appIdsToReuse, macsToReuse, cbCom, TCBType.GOOSE), + configureNetworkForControlBlocks(scd, appIdsToReuse, macsToReuse, cbCom, TCBType.SV)) .toList(); } - private Stream configureNetworkForControlBlocks(SCL scl, Map appIdsAndMacsToReuse, CBCom cbCom, TCBType tcbType) { + private Stream configureNetworkForControlBlocks(SCL scl, Map appIdsToReuse, Map macsToReuse, CBCom cbCom, TCBType tcbType) { CbComSettings cbComSettings; try { cbComSettings = parseCbCom(cbCom, tcbType); } catch (ScdException ex) { return Stream.of(SclReportItem.error("Control Block Communication setting files", ex.getMessage())); } - List appIdToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::appId).toList(); - List macToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::mac).toList(); + List appIdToReuse = appIdsToReuse.values().stream().map(AppId::appId).toList(); + List macToReuse = macsToReuse.values().stream().map(Mac::mac).toList(); PrimitiveIterator.OfLong appIdIterator = cbComSettings.appIds().filter(appId -> !appIdToReuse.contains(appId)).iterator(); List macAddresseList = cbComSettings.macAddresses().filter(mac -> !macToReuse.contains(mac)).boxed().toList(); - AtomicInteger macAddresseIndex = new AtomicInteger(0); return scl.getIED().stream() - .flatMap(tied -> - tied.getAccessPoint() - .stream() - .filter(tAccessPoint -> tAccessPoint.isSetServer() && tAccessPoint.getServer().isSetLDevice()) - .flatMap(tAccessPoint -> tAccessPoint.getServer().getLDevice().stream() - .map(tlDevice -> new IedApLd(tied, tAccessPoint.getName(), tlDevice)) - ) + .filter(tied -> tied.getAccessPoint().stream() + .anyMatch(tAccessPoint -> tAccessPoint.isSetServer() && tAccessPoint.getServer().isSetLDevice())) + .flatMap(tied -> { + List excludedMacAddresses = findExcludedMacAddresses(scl, tied, tcbType); + Iterator authorizedMacAdressList = macAddresseList.stream().filter(mac -> !excludedMacAddresses.contains(mac)).iterator(); + return ldeviceService.getLdevices(tied) + .flatMap(lDevice -> controlService.getControls(lDevice.getLN0(), ControlBlockEnum.from(tcbType).getControlBlockClass()) + .map(tControl -> { + TAccessPoint accessPoint = tied.getAccessPoint().stream().filter(ap -> ap.getServer().getLDevice().stream().anyMatch(tlDevice -> tlDevice.getLdName().equals(lDevice.getLdName()))).findFirst().orElseThrow(); + String apName = accessPoint.getName(); + IedApLd iedApLd = new IedApLd(tied, apName, lDevice); + CriteriaOrError criteriaOrError = getCriteria(tied, tcbType, tControl.getName()); + if (criteriaOrError.errorMessage != null) { + return Optional.of(SclReportItem.error(""" + /SCL/IED[@name="%s"]""".formatted(tied.getName()), criteriaOrError.errorMessage)); + } + + Settings settings = cbComSettings.settingsByCriteria.get(criteriaOrError.criteria); + if (settings == null) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these " + criteriaOrError.criteria); + } + AppId reuseAppId = appIdsToReuse.get(new CbKey(tied.getName(), lDevice.getInst(), tControl.getName())); + Mac reuseMac = macsToReuse.get(new CbKey(tied.getName(), lDevice.getInst(), tControl.getName())); + TCommunication tCommunication = scl.getCommunication(); + + Optional optionalTConnectedAP = subNetworkService.getSubNetworks(tCommunication) + .flatMap(tSubNetwork -> connectedAPService.getFilteredConnectedAP(tSubNetwork, connectedAP -> tied.getName().equals(connectedAP.getIedName()) && apName.equals(connectedAP.getApName()))) + .findFirst(); + if (optionalTConnectedAP.isEmpty()) { + return newError(iedApLd, tControl, "Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint"); + } + return configureControlBlockNetwork(tCommunication, settings, appIdIterator, authorizedMacAdressList, tControl, iedApLd, reuseAppId, reuseMac); + }).flatMap(Optional::stream)); + }); + } + + private List findExcludedMacAddresses(SCL scl, TIED ied, TCBType tcbType) { + //We remove the addresses that are in a ExtRef of the IED. + List directLinkMacAdresses = getExtRefAddress(scl, ied).collect(Collectors.toCollection(ArrayList::new)); + + //We search for every IED that have an ExtRef that comes from the current IED and remove every address they have in their CB and ExtRef + List iedWithExtRefFromCurrentCB = scl.getIED().stream() + .filter(tied -> !tied.getName().equals(ied.getName())) + .filter(tied -> ldeviceService.getLdevices(tied) + .filter(lDevice -> lDevice.getLN0().isSetInputs()) + .flatMap(tlDevice -> tlDevice.getLN0().getInputs().getExtRef().stream()) + .anyMatch(tExtRef -> ied.getName().equals(tExtRef.getIedName())) ) - .filter(iedApLd -> iedApLd.lDevice().isSetLN0()) - .flatMap(iedApLd -> controlService.getControls(iedApLd.lDevice().getLN0(), ControlBlockEnum.from(tcbType).getControlBlockClass()) - .map(tControl -> { - CriteriaOrError criteriaOrError = getCriteria(iedApLd.ied(), tcbType, tControl.getName()); - if (criteriaOrError.errorMessage != null) { - return Optional.of(SclReportItem.error(iedApLd.getXPath(), criteriaOrError.errorMessage)); - } - Settings settings = cbComSettings.settingsByCriteria.get(criteriaOrError.criteria); - if (settings == null) { - return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these " + criteriaOrError.criteria); - } - AppIdAndMac reuseAppIdAndMac = appIdsAndMacsToReuse.get(new CbKey(iedApLd.ied.getName(), iedApLd.lDevice.getInst(), tControl.getName())); - return configureControlBlockNetwork(scl.getCommunication(), settings, appIdIterator, macAddresseList, macAddresseIndex, tControl, iedApLd, reuseAppIdAndMac); - }) - .flatMap(Optional::stream) - ); + .toList(); + + //addresses from CB + List addressesFromCBIed = getAddressFromListOfIed(scl, tcbType, iedWithExtRefFromCurrentCB).toList(); + + List addressesFromExtRefIed = iedWithExtRefFromCurrentCB.stream() + .flatMap(tied -> getExtRefAddress(scl, tied)) + .toList(); + + directLinkMacAdresses.addAll(addressesFromCBIed); + directLinkMacAdresses.addAll(addressesFromExtRefIed); + return directLinkMacAdresses; + } + + private Stream getExtRefAddress(SCL scl, TIED tied) { + return ldeviceService.getLdevices(tied) + .filter(lDevice -> lDevice.getLN0().isSetInputs()) + .flatMap(tlDevice -> tlDevice.getLN0().getInputs().getExtRef().stream()) + .filter(TExtRef::isSetSrcCBName) + .map(tExtRef -> new CbKey(tExtRef.getIedName(), tExtRef.getLdInst(), tExtRef.getSrcCBName())) + .flatMap(cbKey -> scl.getCommunication().getSubNetwork().stream() + .flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream() + .filter(tConnectedAP -> tConnectedAP.getIedName().equals(cbKey.iedName())) + .flatMap(tConnectedAP -> Stream.concat(tConnectedAP.getGSE().stream(), tConnectedAP.getSMV().stream())) + .filter(cBlock -> cBlock.getCbName().equals(cbKey.cbName()) && cBlock.getLdInst().equals(cbKey.LDInst())))) + .filter(TControlBlock::isSetAddress) + .flatMap(cBlock -> { + return Utils.extractFromP(MAC_ADDRESS_P_TYPE, cBlock.getAddress().getP()).stream(); + }) + .map(Utils::macAddressToLong); + } + + private Stream getAddressFromListOfIed(SCL scl, TCBType tcbType, List iedsToGetCBFrom) { + Set iedNames = iedsToGetCBFrom.stream().map(TIED::getName).collect(Collectors.toSet()); + return scl.getCommunication().getSubNetwork().stream() + .flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream()) + .filter(tConnectedAP -> iedNames.contains(tConnectedAP.getIedName())) + .flatMap(tConnectedAP -> switch (tcbType) { + case GOOSE -> tConnectedAP.getGSE().stream(); + case SV -> tConnectedAP.getSMV().stream(); + default -> throw new IllegalArgumentException("Unsupported ControlBlock type " + tcbType); + }) + .filter(TControlBlock::isSetAddress) + .flatMap(cBlock -> Utils.extractFromP(MAC_ADDRESS_P_TYPE, cBlock.getAddress().getP()).stream()) + .map(Utils::macAddressToLong); } private CbComSettings parseCbCom(CBCom cbCom, TCBType tcbType) { @@ -170,9 +240,9 @@ private CbComSettings parseCbCom(CBCom cbCom, TCBType tcbType) { return new CbComSettings(appIds, macAddresses, settingsByCriteria); } - private Optional configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, List macAddressList, AtomicInteger macAddresseIndex, TControl tControl, IedApLd iedApLd, AppIdAndMac reuseAppIdAndMac) { + private Optional configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, Iterator macAddressesAuthorized, TControl tControl, IedApLd iedApLd, AppId reuseAppId, Mac reuseMac) { Optional optionalTConnectedAP = subNetworkService.getSubNetworks(tCommunication) - .flatMap(tSubNetwork -> connectedAPService.getFilteredConnectedAP(tSubNetwork, connectedAP -> iedApLd.ied.getName().equals(connectedAP.getIedName()) && iedApLd.apName.equals(connectedAP.getApName()))) + .flatMap(tSubNetwork -> connectedAPService.getFilteredConnectedAP(tSubNetwork, connectedAP -> iedApLd.ied().getName().equals(connectedAP.getIedName()) && iedApLd.apName().equals(connectedAP.getApName()))) .findFirst(); if (optionalTConnectedAP.isEmpty()) { return newError(iedApLd, tControl, "Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint"); @@ -180,20 +250,29 @@ private Optional configureControlBlockNetwork(TCommunication tCom if (settings.vlanId() == null) { return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings"); } - AppIdAndMac appIdAndMac; - if (reuseAppIdAndMac != null) { - appIdAndMac = reuseAppIdAndMac; + AppId appId; + Mac mac; + if (reuseAppId != null) { + appId = reuseAppId; } else { if (!appIdIterator.hasNext()) { return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted"); } - // For the MAC adress assignation, we restart from the begining if we reach the end of the range - appIdAndMac = new AppIdAndMac(appIdIterator.nextLong(), macAddressList.get(macAddresseIndex.getAndIncrement() % macAddressList.size())); + appId = new AppId(appIdIterator.next()); + } + + if (reuseMac != null) { + mac = reuseMac; + } else { + if (!macAddressesAuthorized.hasNext()) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of mac addresses is exhausted"); + } + mac = new Mac(macAddressesAuthorized.next()); } List listOfPs = new ArrayList<>(); - listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdAndMac.appId(), APPID_LENGTH))); - listOfPs.add(newP(MAC_ADDRESS_P_TYPE, Utils.longToMacAddress(appIdAndMac.mac()))); + listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appId.appId(), APPID_LENGTH))); + listOfPs.add(newP(MAC_ADDRESS_P_TYPE, Utils.longToMacAddress(mac.mac()))); listOfPs.add(newP(VLAN_ID_P_TYPE, Utils.toHex(settings.vlanId(), VLAN_ID_LENGTH))); if (settings.vlanPriority() != null) { listOfPs.add(newP(VLAN_PRIORITY_P_TYPE, String.valueOf(settings.vlanPriority()))); @@ -208,7 +287,27 @@ private Optional configureControlBlockNetwork(TCommunication tCom return Optional.empty(); } - private Map computeAppIdsAndMacToReuse(SCL scd, List subnetworksToReuse) { + private Map computeAppIdsToReuse(SCL scd, List subnetworksToReuse) { + List allControlBlocksInScd = scd.getIED().stream() + .flatMap(tIed -> ldeviceService.getLdevices(tIed) + .filter(TLDevice::isSetLN0) + .flatMap(tlDevice -> Stream.concat(tlDevice.getLN0().getGSEControl().stream(), tlDevice.getLN0().getSampledValueControl().stream()) + .map(tControlWithIEDName -> new CbKey(tIed.getName(), tlDevice.getInst(), tControlWithIEDName.getName())) + )) + .toList(); + return subnetworksToReuse.stream() + .flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream()) + .flatMap(tConnectedAP -> Stream.concat(tConnectedAP.getGSE().stream(), tConnectedAP.getSMV().stream()) + .flatMap(tControlBlock -> AppId.from(tControlBlock.getAddress()) + .map(appId-> new SimpleEntry<>(new CbKey(tConnectedAP.getIedName(), tControlBlock.getLdInst(), tControlBlock.getCbName()), appId)) + .stream() + ) + ) + .filter(entry -> allControlBlocksInScd.contains(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private Map computeMacToReuse(SCL scd, List subnetworksToReuse) { List allControlBlocksInScd = scd.getIED().stream() .flatMap(tIed -> ldeviceService.getLdevices(tIed) .filter(TLDevice::isSetLN0) @@ -219,8 +318,8 @@ private Map computeAppIdsAndMacToReuse(SCL scd, List tSubNetwork.getConnectedAP().stream()) .flatMap(tConnectedAP -> Stream.concat(tConnectedAP.getGSE().stream(), tConnectedAP.getSMV().stream()) - .flatMap(tControlBlock -> AppIdAndMac.from(tControlBlock.getAddress()) - .map(appIdAndMac -> new SimpleEntry<>(new CbKey(tConnectedAP.getIedName(), tControlBlock.getLdInst(), tControlBlock.getCbName()), appIdAndMac)) + .flatMap(tControlBlock -> Mac.from(tControlBlock.getAddress()) + .map(mac -> new SimpleEntry<>(new CbKey(tConnectedAP.getIedName(), tControlBlock.getLdInst(), tControlBlock.getCbName()), mac)) .stream() ) ) @@ -444,21 +543,34 @@ record CbKey(String iedName, String LDInst, String cbName) { } /** - * Pair of APPID and MAC-Address + * APPID * * @param appId APPID - * @param mac MAC-Address */ - record AppIdAndMac(long appId, long mac) { - static Optional from(TAddress address) { + record AppId(long appId) { + static Optional from(TAddress address) { if (address == null) { return Optional.empty(); } return Utils.extractFromP(APPID_P_TYPE, address.getP()) .map(appId -> Integer.parseInt(appId, HEXADECIMAL_BASE)) - .flatMap(appId -> Utils.extractFromP(MAC_ADDRESS_P_TYPE, address.getP()) + .map(AppId::new); + } + } + + /** + * MAC-Address + * + * @param mac MAC-Address + */ + record Mac(long mac) { + static Optional from(TAddress address) { + if (address == null) { + return Optional.empty(); + } + return Utils.extractFromP(MAC_ADDRESS_P_TYPE, address.getP()) .map(Utils::macAddressToLong) - .map(macAddress -> new AppIdAndMac(appId, macAddress))); + .map(Mac::new); } } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java index 8fb16296f..456ba63a1 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java @@ -373,12 +373,11 @@ void configureNetworkForAllControlBlocks_should_create_GSE_with_incremental_appi } @Test - void configureNetworkForAllControlBlocks_should_restart_Mac_adress() { + void configureNetworkForAllControlBlocks_should_have_unique_mac_in_ied() { // Given - SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_unique_mac_in_ied.xml"); CBCom cbCom = createCbCom(); - cbCom.getMacRanges().getMacRange().getFirst().setStart("01-0C-CD-01-00-00"); - cbCom.getMacRanges().getMacRange().getFirst().setEnd("01-0C-CD-01-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("01-0C-CD-01-00-05"); // When List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); @@ -401,12 +400,174 @@ void configureNetworkForAllControlBlocks_should_restart_Mac_adress() { Tuple.tuple("VLAN-ID", "12D"), Tuple.tuple("VLAN-PRIORITY", "1"), Tuple.tuple("APPID", "0002"), - Tuple.tuple("MAC-Address", "01-0C-CD-01-00-00"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-02"), Tuple.tuple("VLAN-ID", "12E"), - Tuple.tuple("VLAN-PRIORITY", "2") + Tuple.tuple("VLAN-PRIORITY", "2"), + Tuple.tuple("APPID", "0003"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-03"), + Tuple.tuple("VLAN-ID", "12D"), + Tuple.tuple("VLAN-PRIORITY", "1"), + Tuple.tuple("APPID", "0004"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-04"), + Tuple.tuple("VLAN-ID", "12D"), + Tuple.tuple("VLAN-PRIORITY", "1") ); } + @Test + void configureNetworkForAllControlBlocks_should_reuse_mac() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_reuse_mac_address.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1", "00-00-00-00-00-01"), + Tuple.tuple("CB2", "00-00-00-00-00-02"), + Tuple.tuple("CB3", "00-00-00-00-00-01")); + } + + @Test + void configureNetworkForAllControlBlocks_should_exclude_only_concerned_addresses() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_exclude_only_concerned_address_of_ied_having_extref.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1a", "00-00-00-00-00-01"), + Tuple.tuple("CB1b", "00-00-00-00-00-02"), + Tuple.tuple("CB2", "00-00-00-00-00-02")); + } + + @Test + void configureNetworkForAllControlBlocks_should_get_only_mac_used_by_extref_of_extref_ied() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_exclude_only_mac_addresses_used_by_extref_of_extref_ied.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1a", "00-00-00-00-00-01"), + Tuple.tuple("CB1b", "00-00-00-00-00-02"), + Tuple.tuple("CB2", "00-00-00-00-00-02")); + } + + @Test + void configureNetworkForAllControlBlocks_should_exclude_mac() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_extref.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1", "00-00-00-00-00-01"), + Tuple.tuple("CB2", "00-00-00-00-00-02")); + } + + @Test + void configureNetworkForAllControlBlocks_should_exclude_mac_of_all_extref_of_target_ied() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_extref_of_target_ied.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1", "00-00-00-00-00-01"), + Tuple.tuple("CB2", "00-00-00-00-00-02"), + Tuple.tuple("CB3", "00-00-00-00-00-03")); + } + + @Test + void configureNetworkForAllControlBlocks_should_exclude_mac_of_all_cb_of_target_ied() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_cb_of_target_ied.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setStart("00-00-00-00-00-01"); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("00-00-00-00-00-03"); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(scd.getCommunication().getSubNetwork()) + .flatExtracting(TSubNetwork::getConnectedAP) + .flatExtracting(TConnectedAP::getGSE) + .extracting(TControlBlock::getCbName, this::extractMacAddress) + .containsExactlyInAnyOrder( + Tuple.tuple("CB1", "00-00-00-00-00-01"), + Tuple.tuple("CB3", "00-00-00-00-00-02")); + } + + private String extractMacAddress(TControlBlock ctrlb) { + if (ctrlb.getAddress() == null) { + throw new IllegalArgumentException("GSE/SMV must have an address"); + } + return ctrlb.getAddress().getP().stream() + .filter(p -> p.getType().equals("MAC-Address")) + .map(TP::getValue) + .findFirst() + .orElseThrow(); + } + + @Test + void configureNetworkForAllControlBlocks_should_not_address_if_not_enought_mac_available() { + // Given + SCL scd = SclTestMarshaller.getSCLFromResource("scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_unique_mac_in_ied.xml"); + CBCom cbCom = createCbCom(); + cbCom.getMacRanges().getMacRange().getFirst().setEnd("01-0C-CD-01-00-03"); + + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + + // Then + assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isFalse(); + assertThat(sclReportItems).hasSize(1) + .extracting(SclReportItem::message, SclReportItem::xpath) + .containsExactly(Tuple.tuple("Cannot configure communication for this ControlBlock because range of mac addresses is exhausted", + """ + /SCL/IED[@name="IED_NAME4"]/AccessPoint[@name="AP_NAME"]/Server/LDevice[@inst="LD_INST41"]/LN0/GSEControl[@name="CB_LD_INST41_GMI"]""")); + } + @ParameterizedTest @MethodSource("provideConfigureNetworkForAllControlBlocksErrors") void configureNetworkForAllControlBlocks_should_fail_when_no_settings_for_this_controlBlock(CBCom cbCom, String expectedMessage, String expectedXPath) { @@ -629,8 +790,12 @@ void configureNetworkForAllControlBlocks_when_missing_IED_privates_should_return //Then assertThat(sclReportItems).hasSize(3); assertThat(sclReportItems).extracting(SclReportItem::isError, SclReportItem::xpath) - .containsOnly(Tuple.tuple(true, """ - /SCL/IED[@name="IED_NAME2"]/AccessPoint[@name="AP_NAME"]/Server/LDevice[@inst="LD_INST21"]""")); + .contains(Tuple.tuple(true, """ + /SCL/IED[@name="IED_NAME2"]"""), + Tuple.tuple(true, """ + /SCL/IED[@name="IED_NAME2"]"""), + Tuple.tuple(true, """ + /SCL/IED[@name="IED_NAME2"]""")); assertThat(sclReportItems).extracting(SclReportItem::message).allSatisfy(message -> assertThat(message).containsAnyOf("COMPAS-ICDHeader", "COMPAS-SystemVersion")); } diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_unique_mac_in_ied.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_unique_mac_in_ied.xml new file mode 100644 index 000000000..a9e623484 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_unique_mac_in_ied.xml @@ -0,0 +1,235 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + IED_NAME1 + + + IED_NAME1 + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + IED_NAME1 + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + IED_NAME1 + + + IED_NAME1 + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_cb_of_target_ied.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_cb_of_target_ied.xml new file mode 100644 index 000000000..eb7679221 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_cb_of_target_ied.xml @@ -0,0 +1,90 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_extref_of_target_ied.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_extref_of_target_ied.xml new file mode 100644 index 000000000..3136d0871 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_all_extref_of_target_ied.xml @@ -0,0 +1,92 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_extref.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_extref.xml new file mode 100644 index 000000000..4da0bdc48 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_mac_address_of_extref.xml @@ -0,0 +1,90 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_concerned_address_of_ied_having_extref.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_concerned_address_of_ied_having_extref.xml new file mode 100644 index 000000000..6da873699 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_concerned_address_of_ied_having_extref.xml @@ -0,0 +1,112 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_mac_addresses_used_by_extref_of_extref_ied.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_mac_addresses_used_by_extref_of_extref_ied.xml new file mode 100644 index 000000000..e97c0ac9e --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_exclude_only_mac_addresses_used_by_extref_of_extref_ied.xml @@ -0,0 +1,113 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_reuse_mac_address.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_reuse_mac_address.xml new file mode 100644 index 000000000..82b02d560 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/test_reuse_mac_address.xml @@ -0,0 +1,112 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + +