From b4c30a3a6cb31500fff95ec54ca702561659cba2 Mon Sep 17 00:00:00 2001 From: Stefan Feuerhahn Date: Fri, 29 Sep 2017 17:22:20 +0200 Subject: [PATCH] refactored parsing reports on client side, added toString() method to reports --- configuration.gradle | 2 +- src/docs/asciidoc/openiec61850-doc.adoc | 7 +- .../openiec61850/ClientAssociation.java | 136 +++++++++--------- .../java/org/openmuc/openiec61850/Report.java | 72 +++++++--- 4 files changed, 124 insertions(+), 93 deletions(-) diff --git a/configuration.gradle b/configuration.gradle index 5a61998..e2edae5 100644 --- a/configuration.gradle +++ b/configuration.gradle @@ -83,7 +83,7 @@ def projectName = 'OpenIEC61850' dependencies { compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.openmuc', name: 'jasn1-61850mod', version: '1.8.2' + compile group: 'org.openmuc', name: 'jasn1-61850mod', version: '1.8.3-SNAPSHOT' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' compile group: 'com.toedter', name: 'jcalendar', version: '1.4' } diff --git a/src/docs/asciidoc/openiec61850-doc.adoc b/src/docs/asciidoc/openiec61850-doc.adoc index 39210ce..15f7a02 100644 --- a/src/docs/asciidoc/openiec61850-doc.adoc +++ b/src/docs/asciidoc/openiec61850-doc.adoc @@ -270,9 +270,10 @@ A report control block contains the following fields: ** *DatSet* - The reference of the data set associated with this RCB. It must be located in the same logical node as the RCB. Members of this data set will be reported whenever the - configured events occure. The reference set here must contain a - dollar sign instead of a dot to separate the logical node from the - data set name, e.g.: 'LDevice1/LNode$DataSetName' + configured events occure. The data set reference as it is set in an + RCB must contain a dollar sign instead of a dot to separate the + logical node from the data set name, e.g.: + 'LDevice1/LNode$DataSetName' ** *OptFlds* - A bitstring where each bit indicates whether an optional field is included in the reports caused by this RCB. The diff --git a/src/main/java/org/openmuc/openiec61850/ClientAssociation.java b/src/main/java/org/openmuc/openiec61850/ClientAssociation.java index 65ed088..54ebd73 100644 --- a/src/main/java/org/openmuc/openiec61850/ClientAssociation.java +++ b/src/main/java/org/openmuc/openiec61850/ClientAssociation.java @@ -32,7 +32,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.openmuc.jasn1.ber.BerByteArrayOutputStream; -import org.openmuc.jasn1.ber.types.BerBitString; import org.openmuc.jasn1.ber.types.BerBoolean; import org.openmuc.jasn1.ber.types.BerInteger; import org.openmuc.jasn1.ber.types.BerNull; @@ -1437,9 +1436,15 @@ public final class ClientAssociation { } /** - * Sets the selected values of the given report control block. Note that all these parameters may only be set if - * reporting for this report control block is not enabled and if it is not reserved by another client. The - * parameters PurgeBuf, EntryId are only applicable if the given rcb is of type BRCB. + * Sets the selected values of the given report control block. Note that all these parameters may only be set if the + * RCB has been reserved but reporting has not been enabled yet. + *

+ * The data set reference as it is set in an RCB must contain a dollar sign instead of a dot to separate the logical + * node from the data set name, e.g.: 'LDevice1/LNode$DataSetName'. Therefore his method will check the reference + * for a dot and if necessary convert it to a '$' sign before sending the request to the server. + *

+ * The parameters PurgeBuf, EntryId are only applicable if the given rcb is of type BRCB. + * * * @param rcb * the report control block @@ -1474,6 +1479,7 @@ public final class ClientAssociation { parametersToSet.add(rcb.getRptId()); } if (setDatSet == true) { + rcb.getDatSet().setValue(rcb.getDatSet().getStringValue().replace('.', '$')); parametersToSet.add(rcb.getDatSet()); } if (setOptFlds == true) { @@ -1534,6 +1540,7 @@ public final class ClientAssociation { } List listRes = unconfirmedServ.getInformationReport().getListOfAccessResult().getAccessResult(); + int index = 0; if (listRes.get(index).getSuccess().getVisibleString() == null) { @@ -1541,132 +1548,127 @@ public final class ClientAssociation { "processReport: report does not contain RptID"); } - String rptId; - BdaOptFlds optFlds; - Integer sqNum = null; - Integer subSqNum = null; - boolean moreSegmentsFollow = false; - String dataSetRef = null; - boolean bufOvfl = false; - Long confRev = null; - BdaEntryTime timeOfEntry = null; - BdaOctetString entryId = null; - - DataSet dataSet = null; - - rptId = listRes.get(index++).getSuccess().getVisibleString().toString(); + String rptId = listRes.get(index++).getSuccess().getVisibleString().toString(); if (listRes.get(index).getSuccess().getBitString() == null) { throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, "processReport: report does not contain OptFlds"); } - optFlds = new BdaOptFlds(new ObjectReference("none"), null); + BdaOptFlds optFlds = new BdaOptFlds(new ObjectReference("none"), null); optFlds.setValue(listRes.get(index++).getSuccess().getBitString().value); + Integer sqNum = null; if (optFlds.isSequenceNumber()) { sqNum = listRes.get(index++).getSuccess().getUnsigned().intValue(); } + BdaEntryTime timeOfEntry = null; if (optFlds.isReportTimestamp()) { timeOfEntry = new BdaEntryTime(new ObjectReference("none"), null, "", false, false); timeOfEntry.setValueFromMmsDataObj(listRes.get(index++).getSuccess()); } + String dataSetRef = null; if (optFlds.isDataSetName()) { dataSetRef = (listRes.get(index++).getSuccess().getVisibleString().toString()); } + else { + for (Urcb urcb : serverModel.getUrcbs()) { + if ((urcb.getRptId() != null && urcb.getRptId().getStringValue().equals(rptId)) + || urcb.getReference().toString().equals(rptId)) { + dataSetRef = urcb.getDatSet().getStringValue(); + break; + } + } + if (dataSetRef == null) { + for (Brcb brcb : serverModel.getBrcbs()) { + if ((brcb.getRptId() != null && brcb.getRptId().getStringValue().equals(rptId)) + || brcb.getReference().toString().equals(rptId)) { + dataSetRef = brcb.getDatSet().getStringValue(); + break; + } + } + } + } + if (dataSetRef == null) { + throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, + "unable to find RCB that matches the given RptID in the report."); + } + dataSetRef = dataSetRef.replace('$', '.'); + + DataSet dataSet = serverModel.getDataSet(dataSetRef); + if (dataSet == null) { + throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, + "unable to find data set that matches the given data set reference of the report."); + } + Boolean bufOvfl = null; if (optFlds.isBufferOverflow()) { bufOvfl = (listRes.get(index++).getSuccess().getBool().value); } + BdaOctetString entryId = null; if (optFlds.isEntryId()) { entryId = new BdaOctetString(new ObjectReference("none"), null, "", 8, false, false); entryId.setValue(listRes.get(index++).getSuccess().getOctetString().value); } + Long confRev = null; if (optFlds.isConfigRevision()) { confRev = listRes.get(index++).getSuccess().getUnsigned().longValue(); } + Integer subSqNum = null; + boolean moreSegmentsFollow = false; if (optFlds.isSegmentation()) { subSqNum = listRes.get(index++).getSuccess().getUnsigned().intValue(); moreSegmentsFollow = listRes.get(index++).getSuccess().getBool().value; } - BerBitString inclusionBitString = listRes.get(index++).getSuccess().getBitString(); + boolean[] inclusionBitString = listRes.get(index++).getSuccess().getBitString().getValueAsBooleans(); + int numMembersReported = 0; + for (boolean bit : inclusionBitString) { + if (bit) { + numMembersReported++; + } + } if (optFlds.isDataReference()) { // this is just to move the index to the right place // The next part will process the changes to the values // without the dataRefs - for (int i = 0; i < inclusionBitString.numBits; i++) { - if ((inclusionBitString.value[i / 8] & (1 << (7 - i % 8))) == (1 << (7 - i % 8))) { - index++; - } - } + index += numMembersReported; } - if (dataSetRef == null) { - for (Urcb urcb : serverModel.getUrcbs()) { - if ((urcb.getRptId() != null && urcb.getRptId().getStringValue().equals(rptId)) - || urcb.getReference().toString().equals(rptId)) { - dataSetRef = urcb.getDatSet().getStringValue(); - break; - } - } - } - - if (dataSetRef == null) { - for (Brcb brcb : serverModel.getBrcbs()) { - if ((brcb.getRptId() != null && brcb.getRptId().getStringValue().equals(rptId)) - || brcb.getReference().toString().equals(rptId)) { - dataSetRef = brcb.getDatSet().getStringValue(); - break; - } - } - } - - if (dataSetRef == null) { - throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, - "unable to find URCB that matches the given RptID in the report."); - } - - // updating of data set copy - original stays the same - dataSet = serverModel.getDataSet(dataSetRef.replace('$', '.')).copy(); - int shiftNum = 0; - for (ModelNode child : dataSet.getMembers()) { - if ((inclusionBitString.value[shiftNum / 8] & (1 << (7 - shiftNum % 8))) == (1 << (7 - shiftNum % 8))) { - + List reportedDataSetMembers = new ArrayList<>(numMembersReported); + int dataSetIndex = 0; + for (FcModelNode dataSetMember : dataSet.getMembers()) { + if (inclusionBitString[dataSetIndex]) { AccessResult accessRes = listRes.get(index++); - child.setValueFromMmsDataObj(accessRes.getSuccess()); + FcModelNode dataSetMemberCopy = (FcModelNode) dataSetMember.copy(); + dataSetMemberCopy.setValueFromMmsDataObj(accessRes.getSuccess()); + reportedDataSetMembers.add(dataSetMemberCopy); } - shiftNum++; + dataSetIndex++; } List reasonCodes = null; if (optFlds.isReasonForInclusion()) { reasonCodes = new ArrayList<>(dataSet.getMembers().size()); for (int i = 0; i < dataSet.getMembers().size(); i++) { - - if ((inclusionBitString.value[i / 8] & (1 << (7 - i % 8))) == (1 << (7 - i % 8))) { - + if (inclusionBitString[dataSetIndex]) { BdaReasonForInclusion reasonForInclusion = new BdaReasonForInclusion(null); - reasonCodes.add(reasonForInclusion); - byte[] reason = listRes.get(index++).getSuccess().getBitString().value; - reasonForInclusion.setValue(reason); - } } } - return new Report(rptId, optFlds, sqNum, subSqNum, moreSegmentsFollow, dataSetRef, bufOvfl, confRev, - timeOfEntry, entryId, inclusionBitString.value, reasonCodes, dataSet); + return new Report(rptId, sqNum, subSqNum, moreSegmentsFollow, dataSetRef, bufOvfl, confRev, timeOfEntry, + entryId, inclusionBitString, reportedDataSetMembers, reasonCodes); } diff --git a/src/main/java/org/openmuc/openiec61850/Report.java b/src/main/java/org/openmuc/openiec61850/Report.java index 62af411..7162d86 100644 --- a/src/main/java/org/openmuc/openiec61850/Report.java +++ b/src/main/java/org/openmuc/openiec61850/Report.java @@ -21,24 +21,22 @@ import java.util.List; public class Report { private final String rptId; - private final BdaOptFlds optFlds; private final Integer sqNum; private final Integer subSqNum; private final boolean moreSegmentsFollow; private final String dataSetRef; - private final boolean bufOvfl; + private final Boolean bufOvfl; private final Long confRev; private final BdaEntryTime timeOfEntry; private final BdaOctetString entryId; - private final byte[] inclusionBitString; + private final boolean[] inclusionBitString; + private final List values; private final List reasonCodes; - private final DataSet dataSet; - public Report(String rptId, BdaOptFlds optFlds, Integer sqNum, Integer subSqNum, boolean moreSegmentsFollow, - String dataSetRef, boolean bufOvfl, Long confRev, BdaEntryTime timeOfEntry, BdaOctetString entryId, - byte[] inclusionBitString, List reasonCodes, DataSet dataSet) { + public Report(String rptId, Integer sqNum, Integer subSqNum, boolean moreSegmentsFollow, String dataSetRef, + Boolean bufOvfl, Long confRev, BdaEntryTime timeOfEntry, BdaOctetString entryId, + boolean[] inclusionBitString, List values, List reasonCodes) { this.rptId = rptId; - this.optFlds = optFlds; this.sqNum = sqNum; this.subSqNum = subSqNum; this.moreSegmentsFollow = moreSegmentsFollow; @@ -48,18 +46,14 @@ public class Report { this.timeOfEntry = timeOfEntry; this.entryId = entryId; this.inclusionBitString = inclusionBitString; + this.values = values; this.reasonCodes = reasonCodes; - this.dataSet = dataSet; } public String getRptId() { return rptId; } - public BdaOptFlds getOptFlds() { - return optFlds; - } - /** * Sequence numberThe parameter MoreSegmentsFollow indicates that more report segments with the same sequence number * follow, counted up for every {@code Report} instance generated @@ -100,7 +94,7 @@ public class Report { * * @return true if buffer overflow is true */ - public boolean isBufOvfl() { + public Boolean getBufOvfl() { return bufOvfl; } @@ -126,7 +120,7 @@ public class Report { * * @return the inclusion bit string as a byte array */ - public byte[] getInclusionBitString() { + public boolean[] getInclusionBitString() { return inclusionBitString; } @@ -139,13 +133,47 @@ public class Report { return reasonCodes; } - /** - * Gets the data set associated with this report. - * - * @return the data set associated with this report. - */ - public DataSet getDataSet() { - return dataSet; + public List getValues() { + return values; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Report ID: ").append(rptId); + sb.append("\nData set reference: ").append(dataSetRef); + + if (sqNum != null) { + sb.append("\nSequence number: ").append(sqNum); + } + if (subSqNum != null) { + sb.append("\nSubsequence number: ").append(subSqNum); + if (moreSegmentsFollow) { + sb.append(" (more segments follow)"); + } + } + if (timeOfEntry != null) { + sb.append("\nTime of entry (unix timestamp): ").append(timeOfEntry.getTimestampValue()); + } + if (bufOvfl != null) { + sb.append("\nBuffer overflow: ").append(bufOvfl); + } + if (entryId != null) { + sb.append("\nEntry ID: ").append(HexConverter.toHexString(entryId.getValue())); + } + if (confRev != null) { + sb.append("\nConfiguration revision: ").append(confRev.toString()); + } + sb.append("\nReported data set members:"); + int index = 0; + for (FcModelNode reportedDataSetMember : values) { + sb.append("\n").append(reportedDataSetMember.toString()); + if (reasonCodes != null) { + sb.append(", reason: ").append(reasonCodes.get(index)); + } + } + + return sb.toString(); } }