subBasicDataAttributes = new ArrayList<>();
+ subBasicDataAttributes.add(this);
+ return subBasicDataAttributes;
+ }
+
+ public abstract void setValueFrom(BasicDataAttribute bda);
+
+ void setMirror(BasicDataAttribute bda) {
+ mirror = bda;
+ }
+
+ public String getValueString() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaBitString.java b/src/main/java/com/beanit/iec61850bean/BdaBitString.java
new file mode 100644
index 0000000..458a93f
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaBitString.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerBitString;
+import com.beanit.iec61850bean.internal.HexString;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+
+public abstract class BdaBitString extends BasicDataAttribute {
+
+ final int maxNumBits;
+ volatile byte[] value;
+
+ protected BdaBitString(
+ ObjectReference objectReference,
+ Fc fc,
+ String sAddr,
+ int maxNumBits,
+ boolean dchg,
+ boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ this.maxNumBits = maxNumBits;
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value.length != ((maxNumBits - 1) / 8 + 1)) {
+ throw new IllegalArgumentException("value does not have correct length.");
+ }
+ this.value = value;
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaBitString) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public int getMaxNumBits() {
+ return maxNumBits;
+ }
+
+ /** Initializes BIT_STRING with all zeros */
+ @Override
+ public void setDefault() {
+ value = new byte[(maxNumBits - 1) / 8 + 1];
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setBitString(new BerBitString(value, maxNumBits));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getBitString() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: bit_string");
+ }
+ if (data.getBitString().numBits > maxNumBits) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT,
+ objectReference
+ + ": bit_string is bigger than type's size: "
+ + data.getBitString().numBits
+ + ">"
+ + maxNumBits);
+ }
+ value = data.getBitString().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setBitString(new Integer32(maxNumBits * -1));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + HexString.fromBytes(value);
+ }
+
+ @Override
+ public String getValueString() {
+ return HexString.fromBytes(value);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaBoolean.java b/src/main/java/com/beanit/iec61850bean/BdaBoolean.java
new file mode 100644
index 0000000..2f90c8d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaBoolean.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerNull;
+import com.beanit.iec61850bean.internal.BerBoolean;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+
+public final class BdaBoolean extends BasicDataAttribute {
+
+ private volatile boolean value;
+
+ public BdaBoolean(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.BOOLEAN;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaBoolean) bda).getValue();
+ }
+
+ public boolean getValue() {
+ return value;
+ }
+
+ public void setValue(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = false;
+ }
+
+ @Override
+ public BdaBoolean copy() {
+ BdaBoolean copy = new BdaBoolean(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setBool(new BerBoolean(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getBool() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: boolean");
+ }
+ value = data.getBool().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setBool(new BerNull());
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaCheck.java b/src/main/java/com/beanit/iec61850bean/BdaCheck.java
new file mode 100644
index 0000000..501883a
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaCheck.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+/** Check packed list according to 61850-7-2 */
+public final class BdaCheck extends BdaBitString {
+
+ public BdaCheck(ObjectReference objectReference) {
+ super(objectReference, Fc.CO, null, 2, false, false);
+ basicType = BdaType.CHECK;
+ setDefault();
+ }
+
+ public boolean getSynchrocheck() {
+ return ((value[0] & 0x80) == 0x80);
+ }
+
+ public void setSynchrocheck(boolean synchrocheck) {
+ if (synchrocheck) {
+ value[0] = (byte) (value[0] | 0x80);
+ } else {
+ value[0] = (byte) (value[0] & 0x7f);
+ }
+ }
+
+ public boolean getInterlockCheck() {
+ return ((value[0] & 0x40) == 0x40);
+ }
+
+ public void setInterlockCheck(boolean interlockCheck) {
+ if (interlockCheck) {
+ value[0] = (byte) (value[0] | 0x40);
+ } else {
+ value[0] = (byte) (value[0] & 0xbf);
+ }
+ }
+
+ @Override
+ public BdaCheck copy() {
+ BdaCheck copy = new BdaCheck(objectReference);
+
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + String.format("0x%x", value[0]);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaDoubleBitPos.java b/src/main/java/com/beanit/iec61850bean/BdaDoubleBitPos.java
new file mode 100644
index 0000000..3fcd92e
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaDoubleBitPos.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaDoubleBitPos extends BdaBitString {
+
+ public BdaDoubleBitPos(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, 2, dchg, dupd);
+ basicType = BdaType.DOUBLE_BIT_POS;
+ setDefault();
+ }
+
+ /** Sets the value to DoubleBitPos.OFF */
+ @Override
+ public void setDefault() {
+ value = new byte[] {0x40};
+ }
+
+ @Override
+ public BdaDoubleBitPos copy() {
+ BdaDoubleBitPos copy = new BdaDoubleBitPos(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public DoubleBitPos getDoubleBitPos() {
+
+ if ((value[0] & 0xC0) == 0xC0) {
+ return DoubleBitPos.BAD_STATE;
+ }
+
+ if ((value[0] & 0x80) == 0x80) {
+ return DoubleBitPos.ON;
+ }
+
+ if ((value[0] & 0x40) == 0x40) {
+ return DoubleBitPos.OFF;
+ }
+
+ return DoubleBitPos.INTERMEDIATE_STATE;
+ }
+
+ public void setDoubleBitPos(DoubleBitPos doubleBitPos) {
+ if (doubleBitPos == DoubleBitPos.BAD_STATE) {
+ value[0] = (byte) 0xC0;
+ } else if (doubleBitPos == DoubleBitPos.ON) {
+ value[0] = (byte) 0x80;
+ } else if (doubleBitPos == DoubleBitPos.OFF) {
+ value[0] = (byte) 0x40;
+ } else {
+ value[0] = (byte) 0;
+ }
+ }
+
+ @Override
+ public String getValueString() {
+ return getDoubleBitPos().toString();
+ }
+
+ public enum DoubleBitPos {
+ INTERMEDIATE_STATE(0),
+ OFF(1),
+ ON(2),
+ BAD_STATE(3);
+ private final int value;
+
+ DoubleBitPos(int value) {
+ this.value = value;
+ }
+
+ public int getIntValue() {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaEntryTime.java b/src/main/java/com/beanit/iec61850bean/BdaEntryTime.java
new file mode 100644
index 0000000..6273318
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaEntryTime.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.BerBoolean;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TimeOfDay;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+
+/**
+ * BdaEntryTime stores time in terms of days and ms since 1984.
+ *
+ * @author Stefan Feuerhahn
+ */
+public final class BdaEntryTime extends BasicDataAttribute {
+
+ private volatile byte[] value;
+
+ public BdaEntryTime(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.ENTRY_TIME;
+ setDefault();
+ }
+
+ /**
+ * Set the value of this object to the given timestamp, where timestamp is the number of ms since
+ * epoch 1970-01-01 00:00:00 UTC. Note that timestamps before 1984 are not valid as they cannot be
+ * stored.
+ *
+ * @param timestamp the number of ms since epoch 1970-01-01
+ */
+ public void setTimestamp(long timestamp) {
+ long msSince1984 = timestamp - 441763200000l;
+ int days = (int) (msSince1984 / 86400000);
+ int ms = (int) (msSince1984 % 86400000);
+ value =
+ new byte[] {
+ (byte) (ms >> 24),
+ (byte) (ms >> 16),
+ (byte) (ms >> 8),
+ (byte) ms,
+ (byte) (days >> 8),
+ (byte) days
+ };
+ }
+
+ public long getTimestampValue() {
+ if (value.length != 6) {
+ return -1;
+ }
+ return (((value[0] & 0xffl) << 24)
+ + ((value[1] & 0xffl) << 16)
+ + ((value[2] & 0xffl) << 8)
+ + (value[3] & 0xffl)
+ + (((value[4] & 0xffl) << 8) + (value[5] & 0xffl)) * 86400000l)
+ + 441763200000l;
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaEntryTime) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ /** Sets EntryTime to byte[6] with all zeros */
+ @Override
+ public void setDefault() {
+ value = new byte[6];
+ }
+
+ @Override
+ public BdaEntryTime copy() {
+ BdaEntryTime copy = new BdaEntryTime(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ if (value == null) {
+ return null;
+ }
+ Data data = new Data();
+ data.setBinaryTime(new TimeOfDay(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getBinaryTime() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: binary_time/EntryTime");
+ }
+ value = data.getBinaryTime().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setBinaryTime(new BerBoolean(true));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + getTimestampValue();
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + getTimestampValue();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaFloat32.java b/src/main/java/com/beanit/iec61850bean/BdaFloat32.java
new file mode 100644
index 0000000..199fc02
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaFloat32.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.FloatingPoint;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+import java.nio.ByteBuffer;
+
+public final class BdaFloat32 extends BasicDataAttribute {
+
+ private volatile byte[] value;
+
+ public BdaFloat32(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.FLOAT32;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaFloat32) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ public Float getFloat() {
+ return Float.intBitsToFloat(
+ ((0xff & value[1]) << 24)
+ | ((0xff & value[2]) << 16)
+ | ((0xff & value[3]) << 8)
+ | ((0xff & value[4]) << 0));
+ }
+
+ public void setFloat(Float value) {
+ this.value = ByteBuffer.allocate(1 + 4).put((byte) 8).putFloat(value).array();
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[] {8, 0, 0, 0, 0};
+ }
+
+ @Override
+ public BdaFloat32 copy() {
+ BdaFloat32 copy = new BdaFloat32(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ if (value == null) {
+ return null;
+ }
+ Data data = new Data();
+ data.setFloatingPoint(new FloatingPoint(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getFloatingPoint() == null || data.getFloatingPoint().value.length != 5) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT, "expected type: floating_point as an octet string of size 5");
+ }
+ value = data.getFloatingPoint().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription.FloatingPoint floatingPointTypeDescription =
+ new TypeDescription.FloatingPoint();
+ floatingPointTypeDescription.setFormatWidth(new Unsigned8(32));
+ floatingPointTypeDescription.setExponentWidth(new Unsigned8(8));
+
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setFloatingPoint(floatingPointTypeDescription);
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + getFloat();
+ }
+
+ @Override
+ public String getValueString() {
+ return getFloat().toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaFloat64.java b/src/main/java/com/beanit/iec61850bean/BdaFloat64.java
new file mode 100644
index 0000000..4d60704
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaFloat64.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.FloatingPoint;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public final class BdaFloat64 extends BasicDataAttribute {
+
+ private volatile byte[] value = new byte[] {11, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ public BdaFloat64(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.FLOAT64;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaFloat64) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value != null && value.length != 9) {
+ throw new IllegalArgumentException("value does not have length 9");
+ }
+ this.value = value;
+ }
+
+ public Double getDouble() {
+ if (value == null) {
+ return null;
+ }
+ return Double.longBitsToDouble(
+ ((0xffL & value[1]) << 56)
+ | ((0xffL & value[2]) << 48)
+ | ((0xffL & value[3]) << 40)
+ | ((0xffL & value[4]) << 32)
+ | ((0xffL & value[5]) << 24)
+ | ((0xffL & value[6]) << 16)
+ | ((0xffL & value[7]) << 8)
+ | ((0xffL & value[8]) << 0));
+ }
+
+ public void setDouble(Double value) {
+ this.value = ByteBuffer.allocate(1 + 8).put((byte) 11).putDouble(value).array();
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[] {11, 0, 0, 0, 0, 0, 0, 0, 0};
+ }
+
+ @Override
+ public BdaFloat64 copy() {
+ BdaFloat64 copy = new BdaFloat64(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ if (value == null) {
+ return null;
+ }
+ Data data = new Data();
+ data.setFloatingPoint(new FloatingPoint(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getFloatingPoint() == null || data.getFloatingPoint().value.length != 9) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT, "expected type: floating_point as an octet string of size 9");
+ }
+ value = data.getFloatingPoint().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription.FloatingPoint floatingPointTypeDescription =
+ new TypeDescription.FloatingPoint();
+ floatingPointTypeDescription.setFormatWidth(new Unsigned8(64));
+ floatingPointTypeDescription.setExponentWidth(new Unsigned8(11));
+
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setFloatingPoint(floatingPointTypeDescription);
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + getDouble();
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + Arrays.toString(value);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt128.java b/src/main/java/com/beanit/iec61850bean/BdaInt128.java
new file mode 100644
index 0000000..f8105d8
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt128.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt128 extends BasicDataAttribute {
+
+ private volatile long value;
+
+ public BdaInt128(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT128;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt128) bda).getValue();
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt128 copy() {
+ BdaInt128 copy = new BdaInt128(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setInteger(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getInteger() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: integer");
+ }
+ value = data.getInteger().value.longValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setInteger(new Unsigned8(128));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt16.java b/src/main/java/com/beanit/iec61850bean/BdaInt16.java
new file mode 100644
index 0000000..6ec3a5b
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt16.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt16 extends BasicDataAttribute {
+
+ private volatile short value;
+
+ public BdaInt16(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT16;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt16) bda).getValue();
+ }
+
+ public short getValue() {
+ return value;
+ }
+
+ public void setValue(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt16 copy() {
+ BdaInt16 copy = new BdaInt16(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setInteger(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getInteger() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: integer");
+ }
+ value = data.getInteger().value.shortValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setInteger(new Unsigned8(16));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt16U.java b/src/main/java/com/beanit/iec61850bean/BdaInt16U.java
new file mode 100644
index 0000000..590c6ae
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt16U.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt16U extends BasicDataAttribute {
+
+ private volatile int value;
+
+ public BdaInt16U(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT16U;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt16U) bda).getValue();
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt16U copy() {
+ BdaInt16U copy = new BdaInt16U(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setUnsigned(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getUnsigned() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: unsigned");
+ }
+ value = data.getUnsigned().value.intValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setUnsigned(new Unsigned8(16));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt32.java b/src/main/java/com/beanit/iec61850bean/BdaInt32.java
new file mode 100644
index 0000000..5484aad
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt32.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt32 extends BasicDataAttribute {
+
+ private volatile int value;
+
+ public BdaInt32(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT32;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt32) bda).getValue();
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt32 copy() {
+ BdaInt32 copy = new BdaInt32(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setInteger(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getInteger() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: integer");
+ }
+ value = data.getInteger().value.intValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setInteger(new Unsigned8(32));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt32U.java b/src/main/java/com/beanit/iec61850bean/BdaInt32U.java
new file mode 100644
index 0000000..d0e8ebd
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt32U.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt32U extends BasicDataAttribute {
+
+ private volatile long value;
+
+ public BdaInt32U(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT32U;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt32U) bda).getValue();
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt32U copy() {
+ BdaInt32U copy = new BdaInt32U(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setUnsigned(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getUnsigned() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: unsigned");
+ }
+ value = data.getUnsigned().value.longValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setUnsigned(new Unsigned8(32));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt64.java b/src/main/java/com/beanit/iec61850bean/BdaInt64.java
new file mode 100644
index 0000000..a6631cd
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt64.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt64 extends BasicDataAttribute {
+
+ private volatile long value;
+
+ public BdaInt64(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT64;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt64) bda).getValue();
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt64 copy() {
+ BdaInt64 copy = new BdaInt64(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setInteger(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getInteger() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: integer");
+ }
+ value = data.getInteger().value.longValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setInteger(new Unsigned8(64));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt8.java b/src/main/java/com/beanit/iec61850bean/BdaInt8.java
new file mode 100644
index 0000000..dfdc8c0
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt8.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt8 extends BasicDataAttribute {
+
+ private volatile byte value;
+
+ public BdaInt8(ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT8;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt8) bda).getValue();
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public void setValue(byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt8 copy() {
+ BdaInt8 copy = new BdaInt8(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setInteger(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getInteger() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: integer");
+ }
+ value = data.getInteger().value.byteValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setInteger(new Unsigned8(8));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+
+ @Override
+ public String getValueString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaInt8U.java b/src/main/java/com/beanit/iec61850bean/BdaInt8U.java
new file mode 100644
index 0000000..73211ce
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaInt8U.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned8;
+
+public final class BdaInt8U extends BasicDataAttribute {
+
+ private volatile short value;
+
+ public BdaInt8U(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.INT8U;
+ setDefault();
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ value = ((BdaInt8U) bda).getValue();
+ }
+
+ public short getValue() {
+ return value;
+ }
+
+ public void setValue(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setDefault() {
+ value = 0;
+ }
+
+ @Override
+ public BdaInt8U copy() {
+ BdaInt8U copy = new BdaInt8U(objectReference, fc, sAddr, dchg, dupd);
+ copy.setValue(value);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setUnsigned(new BerInteger(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getUnsigned() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: unsigned");
+ }
+ value = data.getUnsigned().value.shortValue();
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setUnsigned(new Unsigned8(8));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaOctetString.java b/src/main/java/com/beanit/iec61850bean/BdaOctetString.java
new file mode 100644
index 0000000..ef0f427
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaOctetString.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerOctetString;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import java.util.Arrays;
+
+public final class BdaOctetString extends BasicDataAttribute {
+
+ private final int maxLength;
+ private volatile byte[] value;
+
+ public BdaOctetString(
+ ObjectReference objectReference,
+ Fc fc,
+ String sAddr,
+ int maxLength,
+ boolean dchg,
+ boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.OCTET_STRING;
+ this.maxLength = maxLength;
+ setDefault();
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value != null && value.length > maxLength) {
+ throw new IllegalArgumentException(
+ "OCTET_STRING value size exceeds maxLength of " + maxLength);
+ }
+ this.value = value;
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaOctetString) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[0];
+ }
+
+ @Override
+ public BdaOctetString copy() {
+ BdaOctetString copy = new BdaOctetString(objectReference, fc, sAddr, maxLength, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setOctetString(new BerOctetString(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getOctetString() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: octet_string");
+ }
+ value = data.getOctetString().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setOctetString(new Integer32(maxLength * -1));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + Arrays.toString(value);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaOptFlds.java b/src/main/java/com/beanit/iec61850bean/BdaOptFlds.java
new file mode 100644
index 0000000..de5870c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaOptFlds.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaOptFlds extends BdaBitString {
+
+ public BdaOptFlds(ObjectReference objectReference, Fc fc) {
+ super(objectReference, fc, null, 10, false, false);
+ basicType = BdaType.OPTFLDS;
+ setDefault();
+ }
+
+ @Override
+ public void setDefault() {
+ /* default of buffer overflow is true by default in IEC 61850-6 sec. 9.3.8 */
+ value = new byte[] {0x02, 0x00};
+ }
+
+ @Override
+ public BdaOptFlds copy() {
+ BdaOptFlds copy = new BdaOptFlds(objectReference, fc);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public boolean isSequenceNumber() {
+ return (value[0] & 0x40) == 0x40;
+ }
+
+ public void setSequenceNumber(boolean sequenceNumber) {
+ if (sequenceNumber) {
+ value[0] = (byte) (value[0] | 0x40);
+ } else {
+ value[0] = (byte) (value[0] & 0xbf);
+ }
+ }
+
+ public boolean isReportTimestamp() {
+ return (value[0] & 0x20) == 0x20;
+ }
+
+ public void setReportTimestamp(boolean reportTimestamp) {
+ if (reportTimestamp) {
+ value[0] = (byte) (value[0] | 0x20);
+ } else {
+ value[0] = (byte) (value[0] & 0xdf);
+ }
+ }
+
+ public boolean isReasonForInclusion() {
+ return (value[0] & 0x10) == 0x10;
+ }
+
+ public void setReasonForInclusion(boolean reasonForInclusion) {
+ if (reasonForInclusion) {
+ value[0] = (byte) (value[0] | 0x10);
+ } else {
+ value[0] = (byte) (value[0] & 0xef);
+ }
+ }
+
+ /**
+ * Will the data set reference (not just the name) be included in the report.
+ *
+ * @return true if the data set reference (not just the name) will be included in the report
+ */
+ public boolean isDataSetName() {
+ return (value[0] & 0x08) == 0x08;
+ }
+
+ public void setDataSetName(boolean dataSetName) {
+ if (dataSetName) {
+ value[0] = (byte) (value[0] | 0x08);
+ } else {
+ value[0] = (byte) (value[0] & 0xf7);
+ }
+ }
+
+ public boolean isDataReference() {
+ return (value[0] & 0x04) == 0x04;
+ }
+
+ public void setDataReference(boolean dataReference) {
+ if (dataReference) {
+ value[0] = (byte) (value[0] | 0x04);
+ } else {
+ value[0] = (byte) (value[0] & 0xfb);
+ }
+ }
+
+ public boolean isBufferOverflow() {
+ return (value[0] & 0x02) == 0x02;
+ }
+
+ public void setBufferOverflow(boolean bufferOverflow) {
+ if (bufferOverflow) {
+ value[0] = (byte) (value[0] | 0x02);
+ } else {
+ value[0] = (byte) (value[0] & 0xfd);
+ }
+ }
+
+ public boolean isEntryId() {
+ return (value[0] & 0x01) == 0x01;
+ }
+
+ public void setEntryId(boolean entryId) {
+ if (entryId) {
+ value[0] = (byte) (value[0] | 0x01);
+ } else {
+ value[0] = (byte) (value[0] & 0xfe);
+ }
+ }
+
+ public boolean isConfigRevision() {
+ return (value[1] & 0x80) == 0x80;
+ }
+
+ public void setConfigRevision(boolean configRevision) {
+ if (configRevision) {
+ value[1] = (byte) (value[1] | 0x80);
+ } else {
+ value[1] = (byte) (value[1] & 0x7f);
+ }
+ }
+
+ public boolean isSegmentation() {
+ return (value[1] & 0x40) == 0x40;
+ }
+
+ public void setSegmentation(boolean segmentation) {
+ if (segmentation) {
+ value[1] = (byte) (value[1] | 0x40);
+ } else {
+ value[1] = (byte) (value[1] & 0xbf);
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaQuality.java b/src/main/java/com/beanit/iec61850bean/BdaQuality.java
new file mode 100644
index 0000000..67c5523
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaQuality.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaQuality extends BdaBitString {
+
+ public BdaQuality(ObjectReference objectReference, Fc fc, String sAddr, boolean qchg) {
+ super(objectReference, fc, sAddr, 13, qchg, false);
+ this.qchg = qchg;
+ basicType = BdaType.QUALITY;
+ setDefault();
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[] {0x00, 0x00};
+ }
+
+ @Override
+ public BdaQuality copy() {
+ BdaQuality copy = new BdaQuality(objectReference, fc, sAddr, qchg);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public Validity getValidity() {
+ if ((value[0] & 0xC0) == 0xC0) {
+ return Validity.QUESTIONABLE;
+ }
+
+ if ((value[0] & 0x80) == 0x80) {
+ return Validity.RESERVED;
+ }
+
+ if ((value[0] & 0x40) == 0x40) {
+ return Validity.INVALID;
+ }
+
+ return Validity.GOOD;
+ }
+
+ public void setValidity(Validity validity) {
+ if (validity == Validity.QUESTIONABLE) {
+ value[0] = (byte) (value[0] | 0xC0);
+ } else if (validity == Validity.RESERVED) {
+ value[0] = (byte) (value[0] | 0x80);
+ value[0] = (byte) (value[0] & 0xbf);
+ } else if (validity == Validity.INVALID) {
+ value[0] = (byte) (value[0] & 0x7f);
+ value[0] = (byte) (value[0] | 0x40);
+ } else {
+ value[0] = (byte) (value[0] & 0x03);
+ }
+ }
+
+ public boolean isOverflow() {
+ return (value[0] & 0x20) == 0x20;
+ }
+
+ public void setOverflow(boolean overflow) {
+ if (overflow) {
+ value[0] = (byte) (value[0] | 0x20);
+ } else {
+ value[0] = (byte) (value[0] & 0xdf);
+ }
+ }
+
+ public boolean isOutOfRange() {
+ return (value[0] & 0x10) == 0x10;
+ }
+
+ public void setOutOfRange(boolean outOfRange) {
+ if (outOfRange) {
+ value[0] = (byte) (value[0] | 0x10);
+ } else {
+ value[0] = (byte) (value[0] & 0xef);
+ }
+ }
+
+ public boolean isBadReference() {
+ return (value[0] & 0x08) == 0x08;
+ }
+
+ public void setBadReference(boolean badReference) {
+ if (badReference) {
+ value[0] = (byte) (value[0] | 0x08);
+ } else {
+ value[0] = (byte) (value[0] & 0xf7);
+ }
+ }
+
+ public boolean isOscillatory() {
+ return (value[0] & 0x04) == 0x04;
+ }
+
+ public void setOscillatory(boolean oscillatory) {
+ if (oscillatory) {
+ value[0] = (byte) (value[0] | 0x04);
+ } else {
+ value[0] = (byte) (value[0] & 0xfb);
+ }
+ }
+
+ public boolean isFailure() {
+ return (value[0] & 0x02) == 0x02;
+ }
+
+ public void setFailure(boolean failure) {
+ if (failure) {
+ value[0] = (byte) (value[0] | 0x02);
+ } else {
+ value[0] = (byte) (value[0] & 0xfd);
+ }
+ }
+
+ public boolean isOldData() {
+ return (value[0] & 0x01) == 0x01;
+ }
+
+ public void setOldData(boolean oldData) {
+ if (oldData) {
+ value[0] = (byte) (value[0] | 0x01);
+ } else {
+ value[0] = (byte) (value[0] & 0xfe);
+ }
+ }
+
+ public boolean isInconsistent() {
+ return (value[1] & 0x80) == 0x80;
+ }
+
+ public void setInconsistent(boolean inconsistent) {
+ if (inconsistent) {
+ value[1] = (byte) (value[0] | 0x80);
+ } else {
+ value[1] = (byte) (value[0] & 0x7f);
+ }
+ }
+
+ public boolean isInaccurate() {
+ return (value[1] & 0x40) == 0x40;
+ }
+
+ public void setInaccurate(boolean inaccurate) {
+ if (inaccurate) {
+ value[1] = (byte) (value[0] | 0x40);
+ } else {
+ value[1] = (byte) (value[0] & 0xbf);
+ }
+ }
+
+ public boolean isSubstituted() {
+ return (value[1] & 0x20) == 0x20;
+ }
+
+ public void setSubstituted(boolean substituted) {
+ if (substituted) {
+ value[1] = (byte) (value[0] | 0x20);
+ } else {
+ value[1] = (byte) (value[0] & 0xdf);
+ }
+ }
+
+ public boolean isTest() {
+ return (value[1] & 0x10) == 0x10;
+ }
+
+ public void setTest(boolean test) {
+ if (test) {
+ value[1] = (byte) (value[0] | 0x10);
+ } else {
+ value[1] = (byte) (value[0] & 0xef);
+ }
+ }
+
+ public boolean isOperatorBlocked() {
+ return (value[1] & 0x08) == 0x08;
+ }
+
+ public void setOperatorBlocked(boolean operatorBlocked) {
+ if (operatorBlocked) {
+ value[1] = (byte) (value[0] | 0x08);
+ } else {
+ value[1] = (byte) (value[0] & 0xf7);
+ }
+ }
+
+ @Override
+ public String getValueString() {
+ return getValidity().toString();
+ }
+
+ public enum Validity {
+ GOOD(0),
+ INVALID(1),
+ RESERVED(2),
+ QUESTIONABLE(3);
+ private final int value;
+
+ Validity(int value) {
+ this.value = value;
+ }
+
+ public int getIntValue() {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaReasonForInclusion.java b/src/main/java/com/beanit/iec61850bean/BdaReasonForInclusion.java
new file mode 100644
index 0000000..b6e02c4
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaReasonForInclusion.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaReasonForInclusion extends BdaBitString {
+
+ public BdaReasonForInclusion(ObjectReference objectReference) {
+ super(objectReference, null, null, 7, false, false);
+ basicType = BdaType.REASON_FOR_INCLUSION;
+ setDefault();
+ }
+
+ @Override
+ public BdaReasonForInclusion copy() {
+ BdaReasonForInclusion copy = new BdaReasonForInclusion(objectReference);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public boolean isDataChange() {
+ return (value[0] & 0x40) == 0x40;
+ }
+
+ public void setDataChange(boolean dataChange) {
+ if (dataChange) {
+ value[0] = (byte) (value[0] | 0x40);
+ } else {
+ value[0] = (byte) (value[0] & 0xbf);
+ }
+ }
+
+ public boolean isQualityChange() {
+ return (value[0] & 0x20) == 0x20;
+ }
+
+ public void setQualityChange(boolean qualityChange) {
+ if (qualityChange) {
+ value[0] = (byte) (value[0] | 0x20);
+ } else {
+ value[0] = (byte) (value[0] & 0xdf);
+ }
+ }
+
+ public boolean isDataUpdate() {
+ return (value[0] & 0x10) == 0x10;
+ }
+
+ public void setDataUpdate(boolean dataUpdate) {
+ if (dataUpdate) {
+ value[0] = (byte) (value[0] | 0x10);
+ } else {
+ value[0] = (byte) (value[0] & 0xef);
+ }
+ }
+
+ public boolean isIntegrity() {
+ return (value[0] & 0x08) == 0x08;
+ }
+
+ public void setIntegrity(boolean integrity) {
+ if (integrity) {
+ value[0] = (byte) (value[0] | 0x08);
+ } else {
+ value[0] = (byte) (value[0] & 0xf7);
+ }
+ }
+
+ public boolean isGeneralInterrogation() {
+ return (value[0] & 0x04) == 0x04;
+ }
+
+ public void setGeneralInterrogation(boolean generalInterrogation) {
+ if (generalInterrogation) {
+ value[0] = (byte) (value[0] | 0x04);
+ } else {
+ value[0] = (byte) (value[0] & 0xfb);
+ }
+ }
+
+ public boolean isApplicationTrigger() {
+ return (value[0] & 0x02) == 0x02;
+ }
+
+ public void setApplicationTrigger(boolean applicationTrigger) {
+ if (applicationTrigger) {
+ value[0] = (byte) (value[0] | 0x02);
+ } else {
+ value[0] = (byte) (value[0] & 0xfd);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ if (isDataChange()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("data-change");
+ }
+
+ if (isDataUpdate()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("data-update");
+ }
+
+ if (isQualityChange()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("quality-change");
+ }
+
+ if (isIntegrity()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("integrity");
+ }
+
+ if (isGeneralInterrogation()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("general-interrogation");
+ }
+
+ if (isApplicationTrigger()) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append("application-trigger");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaTapCommand.java b/src/main/java/com/beanit/iec61850bean/BdaTapCommand.java
new file mode 100644
index 0000000..8a3e040
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaTapCommand.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaTapCommand extends BdaBitString {
+
+ public BdaTapCommand(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, 2, dchg, dupd);
+ basicType = BdaType.TAP_COMMAND;
+ setDefault();
+ }
+
+ /** Sets the value to TapCommand.STOP */
+ @Override
+ public void setDefault() {
+ value = new byte[] {0x00};
+ }
+
+ @Override
+ public BdaTapCommand copy() {
+ BdaTapCommand copy = new BdaTapCommand(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public TapCommand getTapCommand() {
+
+ if ((value[0] & 0xC0) == 0xC0) {
+ return TapCommand.RESERVED;
+ }
+
+ if ((value[0] & 0x80) == 0x80) {
+ return TapCommand.HIGHER;
+ }
+
+ if ((value[0] & 0x40) == 0x40) {
+ return TapCommand.LOWER;
+ }
+
+ return TapCommand.STOP;
+ }
+
+ public void setTapCommand(TapCommand tapCommand) {
+ if (tapCommand == TapCommand.RESERVED) {
+ value[0] = (byte) 0xC0;
+ } else if (tapCommand == TapCommand.HIGHER) {
+ value[0] = (byte) 0x80;
+ } else if (tapCommand == TapCommand.LOWER) {
+ value[0] = (byte) 0x40;
+ } else {
+ value[0] = (byte) 0x00;
+ }
+ }
+
+ public enum TapCommand {
+ STOP(0),
+ LOWER(1),
+ HIGHER(2),
+ RESERVED(3);
+ private final int value;
+
+ TapCommand(int value) {
+ this.value = value;
+ }
+
+ public int getIntValue() {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaTimestamp.java b/src/main/java/com/beanit/iec61850bean/BdaTimestamp.java
new file mode 100644
index 0000000..3e328e9
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaTimestamp.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.asn1bean.ber.types.BerNull;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.UtcTime;
+import java.time.Instant;
+import java.util.Date;
+
+public final class BdaTimestamp extends BasicDataAttribute {
+
+ private volatile byte[] value;
+
+ public BdaTimestamp(
+ ObjectReference objectReference, Fc fc, String sAddr, boolean dchg, boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.TIMESTAMP;
+ setDefault();
+ }
+
+ /**
+ * The SecondSinceEpoch shall be the interval in seconds continuously counted from the epoch
+ * 1970-01-01 00:00:00 UTC
+ */
+
+ /**
+ * Returns the value as the number of seconds since epoch 1970-01-01 00:00:00 UTC
+ *
+ * @return the number of seconds since epoch 1970-01-01 00:00:00 UTC
+ */
+ private long getSecondsSinceEpoch() {
+ return ((0xffL & value[0]) << 24
+ | (0xffL & value[1]) << 16
+ | (0xffL & value[2]) << 8
+ | (0xffL & value[3]));
+ }
+
+ /**
+ * The attribute FractionOfSecond shall be the fraction of the current second when the value of
+ * the TimeStamp has been determined. The fraction of second shall be calculated as
+ * (SUM from I = 0 to 23 of bi*2**–(I+1) s).
NOTE 1 The resolution is the smallest unit by
+ * which the time stamp is updated. The 24 bits of the integer provides 1 out of 16777216 counts
+ * as the smallest unit; calculated by 1/2**24 which equals approximately 60 ns.
+ *
+ * NOTE 2 The resolution of a time stamp may be 1/2**1 (= 0,5 s) if only the first bit is used;
+ * or may be 1/2**2 (= 0,25 s) if the first two bits are used; or may be approximately 60 ns if
+ * all 24 bits are used. The resolution provided by an IED is outside the scope of this standard.
+ *
+ * @return the fraction of seconds
+ */
+ private int getFractionOfSecond() {
+ return ((0xff & value[4]) << 16 | (0xff & value[5]) << 8 | (0xff & value[6]));
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaTimestamp) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public Instant getInstant() {
+ if (value == null || value.length == 0) {
+ return null;
+ }
+ long time =
+ getSecondsSinceEpoch() * 1000L
+ + (long) (((float) getFractionOfSecond()) / (1 << 24) * 1000 + 0.5);
+ return Instant.ofEpochMilli(time);
+ }
+
+ public void setInstant(Instant instant) {
+ setInstant(instant, true, false, false, 10);
+ }
+
+ public void setInstant(
+ Instant instant,
+ boolean leapSecondsKnown,
+ boolean clockFailure,
+ boolean clockNotSynchronized,
+ int timeAccuracy) {
+ if (value == null) {
+ value = new byte[8];
+ }
+
+ int secondsSinceEpoch = (int) (instant.toEpochMilli() / 1000L);
+ int fractionOfSecond = (int) ((instant.toEpochMilli() % 1000L) / 1000.0 * (1 << 24));
+
+ int timeQuality = timeAccuracy & 0x1f;
+ if (leapSecondsKnown) {
+ timeQuality = timeQuality | 0x80;
+ }
+ if (clockFailure) {
+ timeQuality = timeQuality | 0x40;
+ }
+ if (clockNotSynchronized) {
+ timeQuality = timeQuality | 0x20;
+ }
+
+ value =
+ new byte[] {
+ (byte) ((secondsSinceEpoch >> 24) & 0xff),
+ (byte) ((secondsSinceEpoch >> 16) & 0xff),
+ (byte) ((secondsSinceEpoch >> 8) & 0xff),
+ (byte) (secondsSinceEpoch & 0xff),
+ (byte) ((fractionOfSecond >> 16) & 0xff),
+ (byte) ((fractionOfSecond >> 8) & 0xff),
+ (byte) (fractionOfSecond & 0xff),
+ (byte) timeQuality
+ };
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value == null) {
+ this.value = new byte[8];
+ }
+ this.value = value;
+ }
+
+ /**
+ * The value TRUE of the attribute LeapSecondsKnown shall indicate that the value for
+ * SecondSinceEpoch takes into account all leap seconds occurred. If it is FALSE then the value
+ * does not take into account the leap seconds that occurred before the initialization of the time
+ * source of the device.
+ *
+ * @return TRUE of the attribute LeapSecondsKnown shall indicate that the value for
+ * SecondSinceEpoch takes into account all leap seconds occurred
+ */
+ public boolean getLeapSecondsKnown() {
+ return ((value[7] & 0x80) != 0);
+ }
+
+ /**
+ * The attribute clockFailure shall indicate that the time source of the sending device is
+ * unreliable. The value of the TimeStamp shall be ignored.
+ *
+ * @return true if the time source of the sending device is unreliable
+ */
+ public boolean getClockFailure() {
+ return ((value[7] & 0x40) != 0);
+ }
+
+ /**
+ * The attribute clockNotSynchronized shall indicate that the time source of the sending device is
+ * not synchronized with the external UTC time.
+ *
+ * @return true if the time source of the sending device is not synchronized
+ */
+ public boolean getClockNotSynchronized() {
+ return ((value[7] & 0x20) != 0);
+ }
+
+ /**
+ * The attribute TimeAccuracy shall represent the time accuracy class of the time source of the
+ * sending device relative to the external UTC time. The timeAccuracy classes shall represent the
+ * number of significant bits in the FractionOfSecond
+ *
+ *
If the time is set via Java {@link Date} objects, the accuracy is 1 ms, that is a
+ * timeAccuracy value of 10.
+ *
+ * @return the time accuracy
+ */
+ public int getTimeAccuracy() {
+ return ((value[7] & 0x1f));
+ }
+
+ /** Sets Timestamp the empty byte array (indicating an invalid Timestamp) */
+ @Override
+ public void setDefault() {
+ value = new byte[8];
+ }
+
+ /** Sets Timestamp to current time */
+ public void setCurrentTime() {
+ setInstant(Instant.now());
+ }
+
+ @Override
+ public BdaTimestamp copy() {
+ BdaTimestamp copy = new BdaTimestamp(objectReference, fc, sAddr, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setUtcTime(new UtcTime(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getUtcTime() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: utc_time/timestamp");
+ }
+ value = data.getUtcTime().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setUtcTime(new BerNull());
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getReference().toString() + ": " + getInstant();
+ }
+
+ @Override
+ public String getValueString() {
+ return getInstant().toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaTriggerConditions.java b/src/main/java/com/beanit/iec61850bean/BdaTriggerConditions.java
new file mode 100644
index 0000000..48e0b8d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaTriggerConditions.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class BdaTriggerConditions extends BdaBitString {
+
+ public BdaTriggerConditions(ObjectReference objectReference, Fc fc) {
+ super(objectReference, fc, null, 6, false, false);
+ basicType = BdaType.TRIGGER_CONDITIONS;
+ setDefault();
+ }
+
+ @Override
+ public void setDefault() {
+ /* default of GI is true by default in IEC 61850-6 sec. 9.3.8 */
+ value = new byte[] {0x04};
+ }
+
+ @Override
+ public BdaTriggerConditions copy() {
+ BdaTriggerConditions copy = new BdaTriggerConditions(objectReference, fc);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ public boolean isDataChange() {
+ return (value[0] & 0x40) == 0x40;
+ }
+
+ public void setDataChange(boolean dataChange) {
+ if (dataChange) {
+ value[0] = (byte) (value[0] | 0x40);
+ } else {
+ value[0] = (byte) (value[0] & 0xbf);
+ }
+ }
+
+ public boolean isQualityChange() {
+ return (value[0] & 0x20) == 0x20;
+ }
+
+ public void setQualityChange(boolean qualityChange) {
+ if (qualityChange) {
+ value[0] = (byte) (value[0] | 0x20);
+ } else {
+ value[0] = (byte) (value[0] & 0xdf);
+ }
+ }
+
+ public boolean isDataUpdate() {
+ return (value[0] & 0x10) == 0x10;
+ }
+
+ public void setDataUpdate(boolean dataUpdate) {
+ if (dataUpdate) {
+ value[0] = (byte) (value[0] | 0x10);
+ } else {
+ value[0] = (byte) (value[0] & 0xef);
+ }
+ }
+
+ public boolean isIntegrity() {
+ return (value[0] & 0x08) == 0x08;
+ }
+
+ public void setIntegrity(boolean integrity) {
+ if (integrity) {
+ value[0] = (byte) (value[0] | 0x08);
+ } else {
+ value[0] = (byte) (value[0] & 0xf7);
+ }
+ }
+
+ public boolean isGeneralInterrogation() {
+ return (value[0] & 0x04) == 0x04;
+ }
+
+ public void setGeneralInterrogation(boolean generalInterrogation) {
+ if (generalInterrogation) {
+ value[0] = (byte) (value[0] | 0x04);
+ } else {
+ value[0] = (byte) (value[0] & 0xfb);
+ }
+ }
+
+ @Override
+ public String toString() {
+
+ return super.toString()
+ + ", data change: "
+ + isDataChange()
+ + ", data update: "
+ + isDataUpdate()
+ + ", quality change:"
+ + isQualityChange()
+ + ", integrity period: "
+ + isIntegrity()
+ + ", GI: "
+ + isGeneralInterrogation();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaType.java b/src/main/java/com/beanit/iec61850bean/BdaType.java
new file mode 100644
index 0000000..4e7e2fe
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaType.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+/**
+ * This Enumeration includes all possible Types for IEC 61850 leave nodes ( {@link
+ * BasicDataAttribute}). This includes BasicTypes and CommonACSITypes as defined in part 7-2.
+ */
+public enum BdaType {
+ BOOLEAN,
+ INT8,
+ INT16,
+ INT32,
+ INT64,
+ INT128,
+ INT8U,
+ INT16U,
+ INT32U,
+ FLOAT32,
+ FLOAT64,
+ OCTET_STRING,
+ VISIBLE_STRING,
+ UNICODE_STRING,
+ TIMESTAMP,
+ ENTRY_TIME,
+ CHECK,
+ QUALITY,
+ DOUBLE_BIT_POS,
+ TAP_COMMAND,
+ TRIGGER_CONDITIONS,
+ OPTFLDS,
+ REASON_FOR_INCLUSION
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaUnicodeString.java b/src/main/java/com/beanit/iec61850bean/BdaUnicodeString.java
new file mode 100644
index 0000000..914268a
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaUnicodeString.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.MMSString;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+
+public final class BdaUnicodeString extends BasicDataAttribute {
+
+ private final int maxLength;
+ private byte[] value;
+
+ public BdaUnicodeString(
+ ObjectReference objectReference,
+ Fc fc,
+ String sAddr,
+ int maxLength,
+ boolean dchg,
+ boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.UNICODE_STRING;
+ this.maxLength = maxLength;
+ setDefault();
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value == null || value.length > maxLength) {
+ throw new IllegalArgumentException(
+ "Value was null or UNICODE_STRING value size exceeds maxLength of " + maxLength);
+ }
+ this.value = value;
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaUnicodeString) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[0];
+ }
+
+ @Override
+ public BdaUnicodeString copy() {
+ BdaUnicodeString copy = new BdaUnicodeString(objectReference, fc, sAddr, maxLength, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setMMSString(new MMSString(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getMMSString() == null) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT, "expected type: mms_string/unicode_string");
+ }
+ value = data.getMMSString().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setMMSString(new Integer32(maxLength * -1));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ if (value == null) {
+ return getReference().toString() + ": null";
+ }
+ return getReference().toString() + ": " + new String(value, UTF_8);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/BdaVisibleString.java b/src/main/java/com/beanit/iec61850bean/BdaVisibleString.java
new file mode 100644
index 0000000..5aa052f
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/BdaVisibleString.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.asn1bean.ber.types.string.BerVisibleString;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+
+public final class BdaVisibleString extends BasicDataAttribute {
+
+ private final int maxLength;
+ private byte[] value;
+
+ public BdaVisibleString(
+ ObjectReference objectReference,
+ Fc fc,
+ String sAddr,
+ int maxLength,
+ boolean dchg,
+ boolean dupd) {
+ super(objectReference, fc, sAddr, dchg, dupd);
+ basicType = BdaType.VISIBLE_STRING;
+ this.maxLength = maxLength;
+ setDefault();
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ if (value == null || value.length > maxLength) {
+ throw new IllegalArgumentException(
+ "value was null or VISIBLE_STRING value size exceeds maxLength of " + maxLength);
+ }
+ this.value = value;
+ }
+
+ public void setValue(String value) {
+ setValue(value.getBytes(UTF_8));
+ }
+
+ @Override
+ public void setValueFrom(BasicDataAttribute bda) {
+ byte[] srcValue = ((BdaVisibleString) bda).getValue();
+ if (value.length != srcValue.length) {
+ value = new byte[srcValue.length];
+ }
+ System.arraycopy(srcValue, 0, value, 0, srcValue.length);
+ }
+
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ public String getStringValue() {
+ return new String(value, UTF_8);
+ }
+
+ @Override
+ public void setDefault() {
+ value = new byte[0];
+ }
+
+ @Override
+ public BdaVisibleString copy() {
+ BdaVisibleString copy = new BdaVisibleString(objectReference, fc, sAddr, maxLength, dchg, dupd);
+ byte[] valueCopy = new byte[value.length];
+ System.arraycopy(value, 0, valueCopy, 0, value.length);
+ copy.setValue(valueCopy);
+ if (mirror == null) {
+ copy.mirror = this;
+ } else {
+ copy.mirror = mirror;
+ }
+ return copy;
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data data = new Data();
+ data.setVisibleString(new BerVisibleString(value));
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getVisibleString() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: visible_string");
+ }
+ value = data.getVisibleString().value;
+ }
+
+ @Override
+ TypeDescription getMmsTypeSpec() {
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setVisibleString(new Integer32(maxLength * -1));
+ return typeDescription;
+ }
+
+ @Override
+ public String toString() {
+ if (value == null) {
+ return getReference().toString() + ": null";
+ }
+ if (value.length == 0 || value[0] == (byte) 0) {
+ return getReference().toString() + ": ''";
+ }
+ return getReference().toString() + ": " + new String(value, UTF_8);
+ }
+
+ @Override
+ public String getValueString() {
+ return new String(value, UTF_8);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/Brcb.java b/src/main/java/com/beanit/iec61850bean/Brcb.java
new file mode 100644
index 0000000..14d51f8
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/Brcb.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Brcb extends Rcb {
+
+ public Brcb(ObjectReference objectReference, List children) {
+ super(objectReference, Fc.BR, children);
+ }
+
+ public BdaBoolean getPurgeBuf() {
+ return (BdaBoolean) children.get("PurgeBuf");
+ }
+
+ public BdaOctetString getEntryId() {
+ return (BdaOctetString) children.get("EntryID");
+ }
+
+ public BdaEntryTime getTimeOfEntry() {
+ return (BdaEntryTime) children.get("TimeOfEntry");
+ }
+
+ /**
+ * Gets the ResvTms attribute. This attribute is optional. Will return NULL if the attribute is
+ * not available.
+ *
+ * @return the ResvTms attribute, null if not available.
+ */
+ public BdaInt16 getResvTms() {
+ return (BdaInt16) children.get("ResvTms");
+ }
+
+ @Override
+ public FcDataObject copy() {
+ List childCopies = new ArrayList<>(children.size());
+ for (ModelNode childNode : children.values()) {
+ childCopies.add((FcModelNode) childNode.copy());
+ }
+ Brcb brcb = new Brcb(objectReference, childCopies);
+ brcb.dataSet = dataSet;
+ return brcb;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ClientAssociation.java b/src/main/java/com/beanit/iec61850bean/ClientAssociation.java
new file mode 100644
index 0000000..866ae46
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ClientAssociation.java
@@ -0,0 +1,2147 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.asn1bean.ber.ReverseByteArrayOutputStream;
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.asn1bean.ber.types.BerNull;
+import com.beanit.asn1bean.ber.types.string.BerGraphicString;
+import com.beanit.asn1bean.ber.types.string.BerVisibleString;
+import com.beanit.iec61850bean.internal.BerBoolean;
+import com.beanit.iec61850bean.internal.mms.asn1.AccessResult;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedRequestPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedResponsePDU;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedServiceRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedServiceResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.DefineNamedVariableListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.DeleteNamedVariableListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.DeleteNamedVariableListRequest.ListOfVariableListName;
+import com.beanit.iec61850bean.internal.mms.asn1.DeleteNamedVariableListResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.DirectoryEntry;
+import com.beanit.iec61850bean.internal.mms.asn1.FileCloseRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.FileDeleteRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.FileDirectoryRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.FileDirectoryResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.FileName;
+import com.beanit.iec61850bean.internal.mms.asn1.FileOpenRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.FileReadRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListRequest.ObjectScope;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNamedVariableListAttributesRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNamedVariableListAttributesResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetVariableAccessAttributesRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.InitiateRequestPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.InitiateResponsePDU;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer16;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer8;
+import com.beanit.iec61850bean.internal.mms.asn1.MMSpdu;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectClass;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import com.beanit.iec61850bean.internal.mms.asn1.ParameterSupportOptions;
+import com.beanit.iec61850bean.internal.mms.asn1.ReadRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.ReadResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.RejectPDU.RejectReason;
+import com.beanit.iec61850bean.internal.mms.asn1.ServiceError.ErrorClass;
+import com.beanit.iec61850bean.internal.mms.asn1.ServiceSupportOptions;
+import com.beanit.iec61850bean.internal.mms.asn1.UnconfirmedPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.UnconfirmedService;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned32;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableAccessSpecification;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableDefs;
+import com.beanit.iec61850bean.internal.mms.asn1.WriteRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.WriteRequest.ListOfData;
+import com.beanit.iec61850bean.internal.mms.asn1.WriteResponse;
+import com.beanit.josistack.AcseAssociation;
+import com.beanit.josistack.ByteBufferInputStream;
+import com.beanit.josistack.ClientAcseSap;
+import com.beanit.josistack.DecodingException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Represents an association/connection to an IEC 61850 MMS server. An instance of
+ * ClientAssociation
is obtained using ClientSap
. An association object can be
+ * used to execute the IEC 61850 ACSI services. Note that not all ACSI services have a corresponding
+ * function in this API. For example all GetDirectory and GetDefinition services are covered by
+ * retrieveModel()
. The control services can be executed by using getDataValues and
+ * setDataValues on the control objects in the data model.
+ */
+public final class ClientAssociation {
+
+ private static final Integer16 version = new Integer16(new byte[] {(byte) 0x01, (byte) 0x01});
+ private static final ParameterSupportOptions proposedParameterCbbBitString =
+ new ParameterSupportOptions(new byte[] {0x03, 0x05, (byte) 0xf1, 0x00});
+ private final ClientReceiver clientReceiver;
+ private final BlockingQueue incomingResponses = new LinkedBlockingQueue<>();
+ private final ReverseByteArrayOutputStream reverseOStream =
+ new ReverseByteArrayOutputStream(500, true);
+ ServerModel serverModel;
+ private AcseAssociation acseAssociation = null;
+ private int responseTimeout;
+
+ private int invokeId = 0;
+ private byte[] servicesSupported = null;
+
+ private int negotiatedMaxPduSize;
+ private ClientEventListener reportListener = null;
+
+ private boolean closed = false;
+
+ ClientAssociation(
+ InetAddress address,
+ int port,
+ InetAddress localAddr,
+ int localPort,
+ String authenticationParameter,
+ ClientAcseSap acseSap,
+ int proposedMaxMmsPduSize,
+ int proposedMaxServOutstandingCalling,
+ int proposedMaxServOutstandingCalled,
+ int proposedDataStructureNestingLevel,
+ byte[] servicesSupportedCalling,
+ int responseTimeout,
+ int messageFragmentTimeout,
+ ClientEventListener reportListener)
+ throws IOException {
+
+ this.responseTimeout = responseTimeout;
+
+ acseSap.tSap.setMessageFragmentTimeout(messageFragmentTimeout);
+ acseSap.tSap.setMessageTimeout(responseTimeout);
+
+ negotiatedMaxPduSize = proposedMaxMmsPduSize;
+
+ this.reportListener = reportListener;
+
+ associate(
+ address,
+ port,
+ localAddr,
+ localPort,
+ authenticationParameter,
+ acseSap,
+ proposedMaxMmsPduSize,
+ proposedMaxServOutstandingCalling,
+ proposedMaxServOutstandingCalled,
+ proposedDataStructureNestingLevel,
+ servicesSupportedCalling);
+
+ acseAssociation.setMessageTimeout(0);
+
+ clientReceiver = new ClientReceiver(negotiatedMaxPduSize);
+ clientReceiver.start();
+ }
+
+ private static ServiceError mmsDataAccessErrorToServiceError(BerInteger dataAccessError) {
+
+ switch (dataAccessError.value.intValue()) {
+ case 1:
+ return new ServiceError(
+ ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT, "MMS DataAccessError: hardware-fault");
+ case 2:
+ return new ServiceError(
+ ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT,
+ "MMS DataAccessError: temporarily-unavailable");
+ case 3:
+ return new ServiceError(
+ ServiceError.ACCESS_VIOLATION, "MMS DataAccessError: object-access-denied");
+ case 5:
+ return new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT, "MMS DataAccessError: invalid-address");
+ case 7:
+ return new ServiceError(
+ ServiceError.TYPE_CONFLICT, "MMS DataAccessError: type-inconsistent");
+ case 10:
+ return new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE, "MMS DataAccessError: object-non-existent");
+ case 11:
+ return new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT, "MMS DataAccessError: object-value-invalid");
+ default:
+ return new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "MMS DataAccessError: " + dataAccessError.value);
+ }
+ }
+
+ private static void testForErrorResponse(MMSpdu mmsResponsePdu) throws ServiceError {
+ if (mmsResponsePdu.getConfirmedErrorPDU() == null) {
+ return;
+ }
+
+ ErrorClass errClass = mmsResponsePdu.getConfirmedErrorPDU().getServiceError().getErrorClass();
+
+ if (errClass != null) {
+ if (errClass.getAccess() != null) {
+ if (errClass.getAccess().value.intValue() == 3) {
+ throw new ServiceError(
+ ServiceError.ACCESS_VIOLATION,
+ "MMS confirmed error: class: \"access\", error code: \"object-access-denied\"");
+ } else if (errClass.getAccess().value.intValue() == 2) {
+
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "MMS confirmed error: class: \"access\", error code: \"object-non-existent\"");
+ }
+
+ } else if (errClass.getFile() != null) {
+ if (errClass.getFile().value.intValue() == 7) {
+
+ throw new ServiceError(
+ ServiceError.FILE_NONE_EXISTENT,
+ "MMS confirmed error: class: \"file\", error code: \"file-non-existent\"");
+ }
+ }
+ }
+
+ if (mmsResponsePdu.getConfirmedErrorPDU().getServiceError().getAdditionalDescription()
+ != null) {
+ throw new ServiceError(
+ ServiceError.UNKNOWN,
+ "MMS confirmed error. Description: "
+ + mmsResponsePdu
+ .getConfirmedErrorPDU()
+ .getServiceError()
+ .getAdditionalDescription()
+ .toString());
+ }
+ throw new ServiceError(ServiceError.UNKNOWN, "MMS confirmed error.");
+ }
+
+ private static void testForRejectResponse(MMSpdu mmsResponsePdu) throws ServiceError {
+ if (mmsResponsePdu.getRejectPDU() == null) {
+ return;
+ }
+
+ RejectReason rejectReason = mmsResponsePdu.getRejectPDU().getRejectReason();
+ if (rejectReason != null) {
+ if (rejectReason.getPduError() != null) {
+ if (rejectReason.getPduError().value.intValue() == 1) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "MMS reject: type: \"pdu-error\", reject code: \"invalid-pdu\"");
+ }
+ }
+ }
+ throw new ServiceError(ServiceError.UNKNOWN, "MMS confirmed error.");
+ }
+
+ private static void testForInitiateErrorResponse(MMSpdu mmsResponsePdu) throws ServiceError {
+ if (mmsResponsePdu.getInitiateErrorPDU() != null) {
+
+ ErrorClass errClass = mmsResponsePdu.getInitiateErrorPDU().getErrorClass();
+ if (errClass != null) {
+ if (errClass.getVmdState() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"vmd_state\" with val: " + errClass.getVmdState().value);
+ }
+ if (errClass.getApplicationReference() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"application_reference\" with val: "
+ + errClass.getApplicationReference().value);
+ }
+ if (errClass.getDefinition() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"definition\" with val: " + errClass.getDefinition().value);
+ }
+ if (errClass.getResource() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"resource\" with val: " + errClass.getResource().value);
+ }
+ if (errClass.getService() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"service\" with val: " + errClass.getService().value);
+ }
+ if (errClass.getServicePreempt() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"service_preempt\" with val: " + errClass.getServicePreempt().value);
+ }
+ if (errClass.getTimeResolution() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"time_resolution\" with val: " + errClass.getTimeResolution().value);
+ }
+ if (errClass.getAccess() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"access\" with val: " + errClass.getAccess().value);
+ }
+ if (errClass.getInitiate() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"initiate\" with val: " + errClass.getInitiate().value);
+ }
+ if (errClass.getConclude() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"conclude\" with val: " + errClass.getConclude());
+ }
+ if (errClass.getCancel() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"cancel\" with val: " + errClass.getCancel().value);
+ }
+ if (errClass.getFile() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"file\" with val: " + errClass.getFile().value);
+ }
+ if (errClass.getOthers() != null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "error class \"others\" with val: " + errClass.getOthers().value);
+ }
+ }
+
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, "unknown error class");
+ }
+ }
+
+ private static MMSpdu constructInitRequestPdu(
+ int proposedMaxPduSize,
+ int proposedMaxServOutstandingCalling,
+ int proposedMaxServOutstandingCalled,
+ int proposedDataStructureNestingLevel,
+ byte[] servicesSupportedCalling) {
+
+ InitiateRequestPDU.InitRequestDetail initRequestDetail =
+ new InitiateRequestPDU.InitRequestDetail();
+ initRequestDetail.setProposedVersionNumber(version);
+ initRequestDetail.setProposedParameterCBB(proposedParameterCbbBitString);
+ initRequestDetail.setServicesSupportedCalling(
+ new ServiceSupportOptions(servicesSupportedCalling, 85));
+
+ InitiateRequestPDU initiateRequestPdu = new InitiateRequestPDU();
+ initiateRequestPdu.setLocalDetailCalling(new Integer32(proposedMaxPduSize));
+ initiateRequestPdu.setProposedMaxServOutstandingCalling(
+ new Integer16(proposedMaxServOutstandingCalling));
+ initiateRequestPdu.setProposedMaxServOutstandingCalled(
+ new Integer16(proposedMaxServOutstandingCalled));
+ initiateRequestPdu.setProposedDataStructureNestingLevel(
+ new Integer8(proposedDataStructureNestingLevel));
+ initiateRequestPdu.setInitRequestDetail(initRequestDetail);
+
+ MMSpdu initiateRequestMMSpdu = new MMSpdu();
+ initiateRequestMMSpdu.setInitiateRequestPDU(initiateRequestPdu);
+
+ return initiateRequestMMSpdu;
+ }
+
+ /**
+ * Gets the response timeout. The response timeout is used whenever a request is sent to the
+ * server. The client will wait for this amount of time for the server's response before throwing
+ * a ServiceError.TIMEOUT. Responses received after the timeout will be automatically discarded.
+ *
+ * @return the response timeout in milliseconds.
+ */
+ public int getResponseTimeout() {
+ return responseTimeout;
+ }
+
+ /**
+ * Sets the response timeout. The response timeout is used whenever a request is sent to the
+ * server. The client will wait for this amount of time for the server's response before throwing
+ * a ServiceError.TIMEOUT. Responses received after the timeout will be automatically discarded.
+ *
+ * @param timeout the response timeout in milliseconds.
+ */
+ public void setResponseTimeout(int timeout) {
+ responseTimeout = timeout;
+ }
+
+ private int getInvokeId() {
+ invokeId = (invokeId + 1) % 2147483647;
+ return invokeId;
+ }
+
+ /**
+ * Gets the ServicesSupported. The 11-byte array of bit flags is available after InitiateResponse
+ * is received from the server, otherwise null is returned
+ *
+ * @return the ServicesSupported byte array, or null.
+ */
+ public byte[] getServicesSupported() {
+ return servicesSupported;
+ }
+
+ private ConfirmedServiceResponse encodeWriteReadDecode(ConfirmedServiceRequest serviceRequest)
+ throws ServiceError, IOException {
+
+ int currentInvokeId = getInvokeId();
+
+ ConfirmedRequestPDU confirmedRequestPdu = new ConfirmedRequestPDU();
+ confirmedRequestPdu.setInvokeID(new Unsigned32(currentInvokeId));
+ confirmedRequestPdu.setService(serviceRequest);
+
+ MMSpdu requestPdu = new MMSpdu();
+ requestPdu.setConfirmedRequestPDU(confirmedRequestPdu);
+
+ reverseOStream.reset();
+
+ try {
+ requestPdu.encode(reverseOStream);
+ } catch (Exception e) {
+ IOException e2 = new IOException("Error encoding MmsPdu.", e);
+ clientReceiver.close(e2);
+ throw e2;
+ }
+
+ clientReceiver.setResponseExpected(currentInvokeId);
+ try {
+ acseAssociation.send(reverseOStream.getByteBuffer());
+ } catch (IOException e) {
+ IOException e2 = new IOException("Error sending packet.", e);
+ clientReceiver.close(e2);
+ throw e2;
+ }
+
+ MMSpdu decodedResponsePdu = null;
+
+ try {
+ if (responseTimeout == 0) {
+ decodedResponsePdu = incomingResponses.take();
+ } else {
+ decodedResponsePdu = incomingResponses.poll(responseTimeout, TimeUnit.MILLISECONDS);
+ }
+ } catch (InterruptedException e) {
+ // TODO can this ever be interrupted?
+ }
+
+ if (decodedResponsePdu == null) {
+ decodedResponsePdu = clientReceiver.removeExpectedResponse();
+ if (decodedResponsePdu == null) {
+ throw new ServiceError(ServiceError.TIMEOUT);
+ }
+ }
+
+ if (decodedResponsePdu.getConfirmedRequestPDU() != null) {
+ incomingResponses.add(decodedResponsePdu);
+ throw new IOException("connection was closed", clientReceiver.getLastIOException());
+ }
+
+ testForInitiateErrorResponse(decodedResponsePdu);
+ testForErrorResponse(decodedResponsePdu);
+ testForRejectResponse(decodedResponsePdu);
+
+ ConfirmedResponsePDU confirmedResponsePdu = decodedResponsePdu.getConfirmedResponsePDU();
+ if (confirmedResponsePdu == null) {
+ throw new IllegalStateException("Response PDU is not a confirmed response pdu");
+ }
+
+ return confirmedResponsePdu.getService();
+ }
+
+ private void associate(
+ InetAddress address,
+ int port,
+ InetAddress localAddr,
+ int localPort,
+ String authenticationParameter,
+ ClientAcseSap acseSap,
+ int proposedMaxPduSize,
+ int proposedMaxServOutstandingCalling,
+ int proposedMaxServOutstandingCalled,
+ int proposedDataStructureNestingLevel,
+ byte[] servicesSupportedCalling)
+ throws IOException {
+
+ MMSpdu initiateRequestMMSpdu =
+ constructInitRequestPdu(
+ proposedMaxPduSize,
+ proposedMaxServOutstandingCalling,
+ proposedMaxServOutstandingCalled,
+ proposedDataStructureNestingLevel,
+ servicesSupportedCalling);
+
+ ReverseByteArrayOutputStream reverseOStream = new ReverseByteArrayOutputStream(500, true);
+ initiateRequestMMSpdu.encode(reverseOStream);
+
+ try {
+ acseAssociation =
+ acseSap.associate(
+ address,
+ port,
+ localAddr,
+ localPort,
+ authenticationParameter,
+ reverseOStream.getByteBuffer());
+
+ ByteBuffer initResponse = acseAssociation.getAssociateResponseAPdu();
+
+ MMSpdu initiateResponseMmsPdu = new MMSpdu();
+
+ initiateResponseMmsPdu.decode(new ByteBufferInputStream(initResponse), null);
+
+ handleInitiateResponse(
+ initiateResponseMmsPdu,
+ proposedMaxPduSize,
+ proposedMaxServOutstandingCalling,
+ proposedMaxServOutstandingCalled,
+ proposedDataStructureNestingLevel);
+ } catch (IOException e) {
+ if (acseAssociation != null) {
+ acseAssociation.close();
+ }
+ throw e;
+ }
+ }
+
+ private void handleInitiateResponse(
+ MMSpdu responsePdu,
+ int proposedMaxPduSize,
+ int proposedMaxServOutstandingCalling,
+ int proposedMaxServOutstandingCalled,
+ int proposedDataStructureNestingLevel)
+ throws IOException {
+
+ if (responsePdu.getInitiateErrorPDU() != null) {
+ throw new IOException(
+ "Got response error of class: " + responsePdu.getInitiateErrorPDU().getErrorClass());
+ }
+
+ if (responsePdu.getInitiateResponsePDU() == null) {
+ acseAssociation.disconnect();
+ throw new IOException("Error decoding InitiateResponse Pdu");
+ }
+
+ InitiateResponsePDU initiateResponsePdu = responsePdu.getInitiateResponsePDU();
+
+ if (initiateResponsePdu.getLocalDetailCalled() != null) {
+ negotiatedMaxPduSize = initiateResponsePdu.getLocalDetailCalled().intValue();
+ }
+
+ int negotiatedMaxServOutstandingCalling =
+ initiateResponsePdu.getNegotiatedMaxServOutstandingCalling().intValue();
+ int negotiatedMaxServOutstandingCalled =
+ initiateResponsePdu.getNegotiatedMaxServOutstandingCalled().intValue();
+
+ int negotiatedDataStructureNestingLevel;
+ if (initiateResponsePdu.getNegotiatedDataStructureNestingLevel() != null) {
+ negotiatedDataStructureNestingLevel =
+ initiateResponsePdu.getNegotiatedDataStructureNestingLevel().intValue();
+ } else {
+ negotiatedDataStructureNestingLevel = proposedDataStructureNestingLevel;
+ }
+
+ if (negotiatedMaxPduSize < ClientSap.MINIMUM_MMS_PDU_SIZE
+ || negotiatedMaxPduSize > proposedMaxPduSize
+ || negotiatedMaxServOutstandingCalling > proposedMaxServOutstandingCalling
+ || negotiatedMaxServOutstandingCalling < 0
+ || negotiatedMaxServOutstandingCalled > proposedMaxServOutstandingCalled
+ || negotiatedMaxServOutstandingCalled < 0
+ || negotiatedDataStructureNestingLevel > proposedDataStructureNestingLevel
+ || negotiatedDataStructureNestingLevel < 0) {
+ acseAssociation.disconnect();
+ throw new IOException("Error negotiating parameters");
+ }
+
+ int version =
+ initiateResponsePdu.getInitResponseDetail().getNegotiatedVersionNumber().intValue();
+ if (version != 1) {
+ throw new IOException("Unsupported version number was negotiated.");
+ }
+
+ servicesSupported =
+ initiateResponsePdu.getInitResponseDetail().getServicesSupportedCalled().value;
+ if ((servicesSupported[0] & 0x40) != 0x40) {
+ throw new IOException("Obligatory services are not supported by the server.");
+ }
+ }
+
+ /**
+ * Set the server model instead of retrieving it from the server device.
+ *
+ * @param model the server model
+ */
+ public void setServerModel(ServerModel model) {
+ this.serverModel = model;
+ }
+
+ /**
+ * Triggers all GetDirectory and GetDefinition ACSI services needed to get the complete server
+ * model. Because in MMS SubDataObjects cannot be distinguished from Constructed Data Attributes
+ * they will always be represented as Constructed Data Attributes in the returned model.
+ *
+ * @return the ServerModel that is the root node of the complete server model.
+ * @throws ServiceError if a ServiceError occurs while calling any of the ASCI services.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public ServerModel retrieveModel() throws ServiceError, IOException {
+
+ List ldNames = retrieveLogicalDevices();
+ List> lnNames = new ArrayList<>(ldNames.size());
+
+ for (int i = 0; i < ldNames.size(); i++) {
+ lnNames.add(retrieveLogicalNodeNames(ldNames.get(i)));
+ }
+ List lds = new ArrayList<>();
+ for (int i = 0; i < ldNames.size(); i++) {
+ List lns = new ArrayList<>();
+ for (int j = 0; j < lnNames.get(i).size(); j++) {
+ lns.add(
+ retrieveDataDefinitions(
+ new ObjectReference(ldNames.get(i) + "/" + lnNames.get(i).get(j))));
+ }
+ lds.add(new LogicalDevice(new ObjectReference(ldNames.get(i)), lns));
+ }
+
+ serverModel = new ServerModel(lds, null);
+
+ updateDataSets();
+
+ return serverModel;
+ }
+
+ private List retrieveLogicalDevices() throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructGetServerDirectoryRequest();
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ return decodeGetServerDirectoryResponse(confirmedServiceResponse);
+ }
+
+ private ConfirmedServiceRequest constructGetServerDirectoryRequest() {
+ ObjectClass objectClass = new ObjectClass();
+ objectClass.setBasicObjectClass(new BerInteger(9));
+
+ GetNameListRequest.ObjectScope objectScope = new GetNameListRequest.ObjectScope();
+ objectScope.setVmdSpecific(new BerNull());
+
+ GetNameListRequest getNameListRequest = new GetNameListRequest();
+ getNameListRequest.setObjectClass(objectClass);
+ getNameListRequest.setObjectScope(objectScope);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setGetNameList(getNameListRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private List decodeGetServerDirectoryResponse(
+ ConfirmedServiceResponse confirmedServiceResponse) throws ServiceError {
+
+ if (confirmedServiceResponse.getGetNameList() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding Get Server Directory Response Pdu");
+ }
+
+ List identifiers =
+ confirmedServiceResponse.getGetNameList().getListOfIdentifier().getIdentifier();
+ ArrayList objectRefs = new ArrayList<>(); // ObjectReference[identifiers.size()];
+
+ for (BerVisibleString identifier : identifiers) {
+ objectRefs.add(identifier.toString());
+ }
+
+ return objectRefs;
+ }
+
+ private List retrieveLogicalNodeNames(String ld) throws ServiceError, IOException {
+ List lns = new ArrayList<>();
+ String continueAfterRef = "";
+ do {
+ ConfirmedServiceRequest serviceRequest =
+ constructGetDirectoryRequest(ld, continueAfterRef, true);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ continueAfterRef = decodeGetDirectoryResponse(confirmedServiceResponse, lns);
+
+ } while (!continueAfterRef.isEmpty());
+ return lns;
+ }
+
+ private ConfirmedServiceRequest constructGetDirectoryRequest(
+ String ldRef, String continueAfter, boolean logicalDevice) {
+
+ ObjectClass objectClass = new ObjectClass();
+
+ if (logicalDevice) {
+ objectClass.setBasicObjectClass(new BerInteger(0));
+ } else { // for data sets
+ objectClass.setBasicObjectClass(new BerInteger(2));
+ }
+
+ GetNameListRequest getNameListRequest;
+
+ ObjectScope objectScopeChoiceType = new ObjectScope();
+ objectScopeChoiceType.setDomainSpecific(new Identifier(ldRef.getBytes(UTF_8)));
+
+ getNameListRequest = new GetNameListRequest();
+ getNameListRequest.setObjectClass(objectClass);
+ getNameListRequest.setObjectScope(objectScopeChoiceType);
+ if (!continueAfter.isEmpty()) {
+ getNameListRequest.setContinueAfter(new Identifier(continueAfter.getBytes(UTF_8)));
+ }
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setGetNameList(getNameListRequest);
+ return confirmedServiceRequest;
+ }
+
+ /**
+ * Decodes an MMS response which contains the structure of a LD and its LNs including names of
+ * DOs.
+ */
+ private String decodeGetDirectoryResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, List lns) throws ServiceError {
+
+ if (confirmedServiceResponse.getGetNameList() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeGetLDDirectoryResponse: Error decoding server response");
+ }
+
+ GetNameListResponse getNameListResponse = confirmedServiceResponse.getGetNameList();
+
+ List identifiers = getNameListResponse.getListOfIdentifier().getIdentifier();
+
+ if (identifiers.size() == 0) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "decodeGetLDDirectoryResponse: Instance not available");
+ }
+
+ BerVisibleString identifier = null;
+ Iterator it = identifiers.iterator();
+
+ String idString;
+
+ while (it.hasNext()) {
+ identifier = it.next();
+ idString = identifier.toString();
+
+ if (idString.indexOf('$') == -1) {
+ lns.add(idString);
+ }
+ }
+
+ if (getNameListResponse.getMoreFollows() != null
+ && getNameListResponse.getMoreFollows().value == false) {
+ return "";
+ } else {
+ return identifier.toString();
+ }
+ }
+
+ private LogicalNode retrieveDataDefinitions(ObjectReference lnRef)
+ throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructGetDataDefinitionRequest(lnRef);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ return decodeGetDataDefinitionResponse(confirmedServiceResponse, lnRef);
+ }
+
+ private ConfirmedServiceRequest constructGetDataDefinitionRequest(ObjectReference lnRef) {
+
+ ObjectName.DomainSpecific domainSpec = new ObjectName.DomainSpecific();
+ domainSpec.setDomainID(new Identifier(lnRef.get(0).getBytes(UTF_8)));
+ domainSpec.setItemID(new Identifier(lnRef.get(1).getBytes(UTF_8)));
+
+ ObjectName objectName = new ObjectName();
+ objectName.setDomainSpecific(domainSpec);
+
+ GetVariableAccessAttributesRequest getVariableAccessAttributesRequest =
+ new GetVariableAccessAttributesRequest();
+ getVariableAccessAttributesRequest.setName(objectName);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setGetVariableAccessAttributes(getVariableAccessAttributesRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private LogicalNode decodeGetDataDefinitionResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, ObjectReference lnRef)
+ throws ServiceError {
+
+ return DataDefinitionResParser.parseGetDataDefinitionResponse(confirmedServiceResponse, lnRef);
+ }
+
+ /**
+ * The implementation of the GetDataValues ACSI service. Will send an MMS read request for the
+ * given model node. After a successful return, the Basic Data Attributes of the passed model node
+ * will contain the values read. If one of the Basic Data Attributes cannot be read then none of
+ * the values will be read and a ServiceError
will be thrown.
+ *
+ * @param modelNode the functionally constrained model node that is to be read.
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void getDataValues(FcModelNode modelNode) throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructGetDataValuesRequest(modelNode);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ decodeGetDataValuesResponse(confirmedServiceResponse, modelNode);
+ }
+
+ private boolean decodeGetFileDirectoryResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, List files)
+ throws ServiceError {
+ if (confirmedServiceResponse.getFileDirectory() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding GetFileDirectoryResponsePdu");
+ }
+
+ FileDirectoryResponse fileDirectoryRes = confirmedServiceResponse.getFileDirectory();
+
+ List entries = fileDirectoryRes.getListOfDirectoryEntry().getDirectoryEntry();
+
+ for (DirectoryEntry entry : entries) {
+ List graphicStrings = entry.getFileName().getBerGraphicString();
+
+ StringBuilder filename = new StringBuilder();
+
+ for (BerGraphicString bgs : graphicStrings) {
+ filename.append(bgs.toString());
+ }
+
+ long fileSize = entry.getFileAttributes().getSizeOfFile().longValue();
+
+ Calendar lastModified = null;
+
+ try {
+
+ if (entry.getFileAttributes().getLastModified() != null) {
+ lastModified = entry.getFileAttributes().getLastModified().asCalendar();
+ }
+
+ } catch (ParseException e) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding GetFileDirectoryResponsePdu");
+ }
+
+ FileInformation fileInfo = new FileInformation(filename.toString(), fileSize, lastModified);
+
+ files.add(fileInfo);
+ }
+
+ boolean moreFollows =
+ (fileDirectoryRes.getMoreFollows() != null) && fileDirectoryRes.getMoreFollows().value;
+
+ return moreFollows;
+ }
+
+ /**
+ * Read the file directory of the server
+ *
+ * @param directoryName name of a directory or empty string for the root directory
+ * @return the list of available
+ * @throws ServiceError if a ServiceError is returned by the server or parsing of response failed.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public List getFileDirectory(String directoryName)
+ throws ServiceError, IOException {
+ List files = new ArrayList<>();
+
+ boolean moreFollows = true;
+
+ String continueAfter = null;
+
+ while (moreFollows) {
+
+ FileDirectoryRequest fileDirectoryRequest = new FileDirectoryRequest();
+
+ BerGraphicString berGraphicString = new BerGraphicString(directoryName.getBytes(UTF_8));
+
+ FileName fileSpecification = new FileName();
+ fileSpecification.getBerGraphicString().add(berGraphicString);
+
+ fileDirectoryRequest.setFileSpecification(fileSpecification);
+
+ if (continueAfter != null) {
+ FileName continueAfterSpecification = new FileName();
+
+ continueAfterSpecification
+ .getBerGraphicString()
+ .add(new BerGraphicString(continueAfter.getBytes(UTF_8)));
+
+ fileDirectoryRequest.setContinueAfter(continueAfterSpecification);
+ }
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setFileDirectory(fileDirectoryRequest);
+
+ ConfirmedServiceResponse confirmedServiceResponse =
+ encodeWriteReadDecode(confirmedServiceRequest);
+
+ moreFollows = decodeGetFileDirectoryResponse(confirmedServiceResponse, files);
+
+ if (moreFollows) {
+ continueAfter = files.get(files.size() - 1).getFilename();
+ }
+ }
+
+ return files;
+ }
+
+ /**
+ * Delete a file from the server
+ *
+ * @param filename name of the file to delete
+ * @throws ServiceError if a ServiceError is returned by the server
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void deleteFile(String filename) throws ServiceError, IOException {
+ FileDeleteRequest fileDeleteRequest = new FileDeleteRequest();
+
+ fileDeleteRequest.getBerGraphicString().add(new BerGraphicString(filename.getBytes(UTF_8)));
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setFileDelete(fileDeleteRequest);
+
+ ConfirmedServiceResponse confirmedServiceResponse =
+ encodeWriteReadDecode(confirmedServiceRequest);
+
+ if (confirmedServiceResponse.getFileDelete() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding DeleteFileResponsePdu");
+ }
+ }
+
+ private Integer32 openFile(String filename) throws ServiceError, IOException {
+ FileOpenRequest fileOpenRequest = new FileOpenRequest();
+
+ FileName fileSpecification = new FileName();
+ fileSpecification.getBerGraphicString().add(new BerGraphicString(filename.getBytes(UTF_8)));
+
+ fileOpenRequest.setFileName(fileSpecification);
+ fileOpenRequest.setInitialPosition(new Unsigned32(0));
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setFileOpen(fileOpenRequest);
+
+ ConfirmedServiceResponse confirmedServiceResponse =
+ encodeWriteReadDecode(confirmedServiceRequest);
+
+ if (confirmedServiceResponse.getFileOpen() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding FileOpenResponsePdu");
+ }
+
+ Integer32 frsmId = confirmedServiceResponse.getFileOpen().getFrsmID();
+
+ return frsmId;
+ }
+
+ private boolean readNextFileDataBlock(Integer32 frsmId, GetFileListener listener)
+ throws ServiceError, IOException {
+ FileReadRequest fileReadRequest = new FileReadRequest(frsmId.longValue());
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setFileRead(fileReadRequest);
+
+ ConfirmedServiceResponse confirmedServiceResponse =
+ encodeWriteReadDecode(confirmedServiceRequest);
+
+ if (confirmedServiceResponse.getFileRead() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding FileReadResponsePdu");
+ }
+
+ byte[] fileData = confirmedServiceResponse.getFileRead().getFileData().value;
+
+ boolean moreFollows = true;
+
+ if (confirmedServiceResponse.getFileRead().getMoreFollows() != null) {
+ moreFollows = confirmedServiceResponse.getFileRead().getMoreFollows().value;
+ }
+
+ if (listener != null) {
+ boolean continueRead = listener.dataReceived(fileData, moreFollows);
+
+ if (moreFollows == true) {
+ moreFollows = continueRead;
+ }
+ }
+
+ return moreFollows;
+ }
+
+ private void closeFile(Integer32 frsmId) throws ServiceError, IOException {
+ FileCloseRequest fileCloseRequest = new FileCloseRequest(frsmId.longValue());
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setFileClose(fileCloseRequest);
+
+ ConfirmedServiceResponse confirmedServiceResponse =
+ encodeWriteReadDecode(confirmedServiceRequest);
+
+ if (confirmedServiceResponse.getFileClose() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding FileCloseResponsePdu");
+ }
+ }
+
+ /**
+ * Read a file from the server
+ *
+ * @param filename name of the file to delete
+ * @param listener callback handler to receive fall data
+ * @throws ServiceError if a ServiceError is returned by the server
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void getFile(String filename, GetFileListener listener) throws ServiceError, IOException {
+ Integer32 frsmId = openFile(filename);
+
+ boolean moreFollows = true;
+
+ while (moreFollows) {
+ moreFollows = readNextFileDataBlock(frsmId, listener);
+ }
+
+ closeFile(frsmId);
+ }
+
+ /**
+ * Will update all data inside the model except for control variables (those that have FC=CO).
+ * Control variables are not meant to be read. Update is done by calling getDataValues on the
+ * FCDOs below the Logical Nodes.
+ *
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void getAllDataValues() throws ServiceError, IOException {
+ for (ModelNode logicalDevice : serverModel.getChildren()) {
+ for (ModelNode logicalNode : logicalDevice.getChildren()) {
+ for (ModelNode dataObject : logicalNode.getChildren()) {
+ FcModelNode fcdo = (FcModelNode) dataObject;
+ if (fcdo.getFc() != Fc.CO && fcdo.getFc() != Fc.SE) {
+ try {
+ getDataValues(fcdo);
+ } catch (ServiceError e) {
+ throw new ServiceError(
+ e.getErrorCode(),
+ "service error retrieving "
+ + fcdo.getReference()
+ + "["
+ + fcdo.getFc()
+ + "]"
+ + ", "
+ + e.getMessage(),
+ e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private ConfirmedServiceRequest constructGetDataValuesRequest(FcModelNode modelNode) {
+ VariableAccessSpecification varAccessSpec = constructVariableAccessSpecification(modelNode);
+
+ ReadRequest readRequest = new ReadRequest();
+ readRequest.setVariableAccessSpecification(varAccessSpec);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setRead(readRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private void decodeGetDataValuesResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, ModelNode modelNode) throws ServiceError {
+
+ if (confirmedServiceResponse.getRead() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding GetDataValuesReponsePdu");
+ }
+
+ List listOfAccessResults =
+ confirmedServiceResponse.getRead().getListOfAccessResult().getAccessResult();
+
+ if (listOfAccessResults.size() != 1) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE, "Multiple results received.");
+ }
+
+ AccessResult accRes = listOfAccessResults.get(0);
+
+ if (accRes.getFailure() != null) {
+ throw mmsDataAccessErrorToServiceError(accRes.getFailure());
+ }
+ modelNode.setValueFromMmsDataObj(accRes.getSuccess());
+ }
+
+ /**
+ * The implementation of the SetDataValues ACSI service. Will send an MMS write request with the
+ * values of all Basic Data Attributes of the given model node. Will simply return if all values
+ * have been successfully written. If one of the Basic Data Attributes could not be written then a
+ * ServiceError
will be thrown. In this case it is not possible to find out which of
+ * several Basic Data Attributes could not be written.
+ *
+ * @param modelNode the functionally constrained model node that is to be written.
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void setDataValues(FcModelNode modelNode) throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructSetDataValuesRequest(modelNode);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ decodeSetDataValuesResponse(confirmedServiceResponse);
+ }
+
+ private ConfirmedServiceRequest constructSetDataValuesRequest(FcModelNode modelNode)
+ throws ServiceError {
+
+ VariableAccessSpecification variableAccessSpecification =
+ constructVariableAccessSpecification(modelNode);
+
+ ListOfData listOfData = new ListOfData();
+ List dataList = listOfData.getData();
+ dataList.add(modelNode.getMmsDataObj());
+
+ WriteRequest writeRequest = new WriteRequest();
+ writeRequest.setListOfData(listOfData);
+ writeRequest.setVariableAccessSpecification(variableAccessSpecification);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setWrite(writeRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private VariableAccessSpecification constructVariableAccessSpecification(FcModelNode modelNode) {
+ VariableDefs listOfVariable = new VariableDefs();
+
+ List variableDefsSeqOf = listOfVariable.getSEQUENCE();
+ variableDefsSeqOf.add(modelNode.getMmsVariableDef());
+
+ VariableAccessSpecification variableAccessSpecification = new VariableAccessSpecification();
+ variableAccessSpecification.setListOfVariable(listOfVariable);
+
+ return variableAccessSpecification;
+ }
+
+ private void decodeSetDataValuesResponse(ConfirmedServiceResponse confirmedServiceResponse)
+ throws ServiceError {
+
+ WriteResponse writeResponse = confirmedServiceResponse.getWrite();
+
+ if (writeResponse == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "SetDataValuesResponse: improper response");
+ }
+
+ WriteResponse.CHOICE subChoice = writeResponse.getCHOICE().get(0);
+
+ if (subChoice.getFailure() != null) {
+ throw mmsDataAccessErrorToServiceError(subChoice.getFailure());
+ }
+ }
+
+ /**
+ * This function will get the definition of all persistent DataSets from the server and update the
+ * DataSets in the ServerModel that was returned by {@code retrieveModel} or set using {@code
+ * setServerModel}. It will delete DataSets that have been deleted since the last update and add
+ * any new DataSets
+ *
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal association error occurs. The association object will be closed
+ * and can no longer be used after this exception is thrown.
+ */
+ public void updateDataSets() throws ServiceError, IOException {
+
+ if (serverModel == null) {
+ throw new IllegalStateException(
+ "Before calling this function you have to get the ServerModel using the retrieveModel() function");
+ }
+
+ Collection lds = serverModel.getChildren();
+
+ for (ModelNode ld : lds) {
+ ConfirmedServiceRequest serviceRequest =
+ constructGetDirectoryRequest(ld.getName(), "", false);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ decodeAndRetrieveDsNamesAndDefinitions(confirmedServiceResponse, (LogicalDevice) ld);
+ }
+ }
+
+ private void decodeAndRetrieveDsNamesAndDefinitions(
+ ConfirmedServiceResponse confirmedServiceResponse, LogicalDevice ld)
+ throws ServiceError, IOException {
+
+ if (confirmedServiceResponse.getGetNameList() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeGetDataSetResponse: Error decoding server response");
+ }
+
+ GetNameListResponse getNameListResponse = confirmedServiceResponse.getGetNameList();
+
+ List identifiers = getNameListResponse.getListOfIdentifier().getIdentifier();
+
+ if (identifiers.size() == 0) {
+ return;
+ }
+
+ for (Identifier identifier : identifiers) {
+ // TODO delete DataSets that no longer exist
+ getDataSetDirectory(identifier, ld);
+ }
+
+ if (getNameListResponse.getMoreFollows() != null
+ && getNameListResponse.getMoreFollows().value == true) {
+ throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT);
+ }
+ }
+
+ private void getDataSetDirectory(Identifier dsId, LogicalDevice ld)
+ throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructGetDataSetDirectoryRequest(dsId, ld);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ decodeGetDataSetDirectoryResponse(confirmedServiceResponse, dsId, ld);
+ }
+
+ private ConfirmedServiceRequest constructGetDataSetDirectoryRequest(
+ Identifier dsId, LogicalDevice ld) throws ServiceError {
+ ObjectName.DomainSpecific domainSpecificObjectName = new ObjectName.DomainSpecific();
+ domainSpecificObjectName.setDomainID(new Identifier(ld.getName().getBytes(UTF_8)));
+ domainSpecificObjectName.setItemID(dsId);
+
+ GetNamedVariableListAttributesRequest dataSetObj = new GetNamedVariableListAttributesRequest();
+ dataSetObj.setDomainSpecific(domainSpecificObjectName);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setGetNamedVariableListAttributes(dataSetObj);
+
+ return confirmedServiceRequest;
+ }
+
+ private void decodeGetDataSetDirectoryResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, BerVisibleString dsId, LogicalDevice ld)
+ throws ServiceError {
+
+ if (confirmedServiceResponse.getGetNamedVariableListAttributes() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeGetDataSetDirectoryResponse: Error decoding server response");
+ }
+
+ GetNamedVariableListAttributesResponse getNamedVariableListAttResponse =
+ confirmedServiceResponse.getGetNamedVariableListAttributes();
+ boolean deletable = getNamedVariableListAttResponse.getMmsDeletable().value;
+ List variables =
+ getNamedVariableListAttResponse.getListOfVariable().getSEQUENCE();
+
+ if (variables.size() == 0) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "decodeGetDataSetDirectoryResponse: Instance not available");
+ }
+
+ List dsMems = new ArrayList<>();
+
+ for (VariableDefs.SEQUENCE variableDef : variables) {
+
+ FcModelNode member;
+ // TODO remove this try catch statement once all possible FCs are
+ // supported
+ // it is only there so that Functional Constraints such as GS will
+ // be ignored and DataSet cotaining elements with these FCs are
+ // ignored and not created.
+ try {
+ member = serverModel.getNodeFromVariableDef(variableDef);
+ } catch (ServiceError e) {
+ return;
+ }
+ if (member == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "decodeGetDataSetDirectoryResponse: data set memeber does not exist, you might have to call retrieveModel first");
+ }
+ dsMems.add(member);
+ }
+
+ String dsObjRef = ld.getName() + "/" + dsId.toString().replace('$', '.');
+
+ DataSet dataSet = new DataSet(dsObjRef, dsMems, deletable);
+
+ if (ld.getChild(dsId.toString().substring(0, dsId.toString().indexOf('$'))) == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "decodeGetDataSetDirectoryResponse: LN for returned DataSet is not available");
+ }
+
+ DataSet existingDs = serverModel.getDataSet(dsObjRef);
+ if (existingDs == null) {
+ serverModel.addDataSet(dataSet);
+ } else if (!existingDs.isDeletable()) {
+ return;
+ } else {
+ serverModel.removeDataSet(dsObjRef);
+ serverModel.addDataSet(dataSet);
+ }
+ }
+
+ /**
+ * The client should create the data set first and add it to either the non-persistent list or to
+ * the model. Then it should call this method for creation on the server side
+ *
+ * @param dataSet the data set to be created on the server side
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal IO error occurs. The association object will be closed and can
+ * no longer be used after this exception is thrown.
+ */
+ public void createDataSet(DataSet dataSet) throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructCreateDataSetRequest(dataSet);
+ encodeWriteReadDecode(serviceRequest);
+ handleCreateDataSetResponse(dataSet);
+ }
+
+ /**
+ * dsRef = either LD/LN.DataSetName (persistent) or @DataSetname (non-persistent) Names in
+ * dsMemberRef should be in the form: LD/LNName.DoName or LD/LNName.DoName.DaName
+ */
+ private ConfirmedServiceRequest constructCreateDataSetRequest(DataSet dataSet)
+ throws ServiceError {
+
+ VariableDefs listOfVariable = new VariableDefs();
+
+ List variableDefs = listOfVariable.getSEQUENCE();
+ for (FcModelNode dsMember : dataSet) {
+ variableDefs.add(dsMember.getMmsVariableDef());
+ }
+
+ DefineNamedVariableListRequest createDSRequest = new DefineNamedVariableListRequest();
+ createDSRequest.setVariableListName(dataSet.getMmsObjectName());
+ createDSRequest.setListOfVariable(listOfVariable);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setDefineNamedVariableList(createDSRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private void handleCreateDataSetResponse(DataSet dataSet) throws ServiceError {
+ serverModel.addDataSet(dataSet);
+ }
+
+ public void deleteDataSet(DataSet dataSet) throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructDeleteDataSetRequest(dataSet);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ decodeDeleteDataSetResponse(confirmedServiceResponse, dataSet);
+ }
+
+ private ConfirmedServiceRequest constructDeleteDataSetRequest(DataSet dataSet)
+ throws ServiceError {
+
+ ListOfVariableListName listOfVariableListName = new ListOfVariableListName();
+
+ List objectList = listOfVariableListName.getObjectName();
+ objectList.add(dataSet.getMmsObjectName());
+
+ DeleteNamedVariableListRequest requestDeleteDS = new DeleteNamedVariableListRequest();
+ requestDeleteDS.setListOfVariableListName(listOfVariableListName);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setDeleteNamedVariableList(requestDeleteDS);
+
+ return confirmedServiceRequest;
+ }
+
+ private void decodeDeleteDataSetResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, DataSet dataSet) throws ServiceError {
+
+ if (confirmedServiceResponse.getDeleteNamedVariableList() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeDeleteDataSetResponse: Error decoding server response");
+ }
+
+ DeleteNamedVariableListResponse deleteNamedVariableListResponse =
+ confirmedServiceResponse.getDeleteNamedVariableList();
+
+ if (deleteNamedVariableListResponse.getNumberDeleted().intValue() != 1) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, "number deleted not 1");
+ }
+
+ if (serverModel.removeDataSet(dataSet.getReferenceStr()) == null) {
+ throw new ServiceError(ServiceError.UNKNOWN, "unable to delete dataset locally");
+ }
+ }
+
+ /**
+ * The implementation of the GetDataSetValues ACSI service. After a successful return, the Basic
+ * Data Attributes of the data set members will contain the values read. If one of the data set
+ * members could not be read, this will be indicated in the returned list. The returned list will
+ * have the same size as the member list of the data set. For each member it will contain
+ * null
if reading was successful and a ServiceError if reading of this member failed.
+ *
+ * @param dataSet the DataSet that is to be read.
+ * @return a list indicating ServiceErrors that may have occurred.
+ * @throws IOException if a fatal IO error occurs. The association object will be closed and can
+ * no longer be used after this exception is thrown.
+ */
+ public List getDataSetValues(DataSet dataSet) throws IOException {
+
+ ConfirmedServiceResponse confirmedServiceResponse;
+ try {
+ ConfirmedServiceRequest serviceRequest = constructGetDataSetValuesRequest(dataSet);
+ confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ } catch (ServiceError e) {
+ int dataSetSize = dataSet.getMembers().size();
+ List serviceErrors = new ArrayList<>(dataSetSize);
+ for (int i = 0; i < dataSetSize; i++) {
+ serviceErrors.add(e);
+ }
+ return serviceErrors;
+ }
+ return decodeGetDataSetValuesResponse(confirmedServiceResponse, dataSet);
+ }
+
+ private ConfirmedServiceRequest constructGetDataSetValuesRequest(DataSet dataSet)
+ throws ServiceError {
+
+ VariableAccessSpecification varAccSpec = new VariableAccessSpecification();
+ varAccSpec.setVariableListName(dataSet.getMmsObjectName());
+
+ ReadRequest getDataSetValuesRequest = new ReadRequest();
+ getDataSetValuesRequest.setSpecificationWithResult(new BerBoolean(true));
+ getDataSetValuesRequest.setVariableAccessSpecification(varAccSpec);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setRead(getDataSetValuesRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private List decodeGetDataSetValuesResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, DataSet ds) {
+
+ int dataSetSize = ds.getMembers().size();
+ List serviceErrors = new ArrayList<>(dataSetSize);
+
+ if (confirmedServiceResponse.getRead() == null) {
+ ServiceError serviceError =
+ new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding GetDataValuesReponsePdu");
+ for (int i = 0; i < dataSetSize; i++) {
+ serviceErrors.add(serviceError);
+ }
+ return serviceErrors;
+ }
+
+ ReadResponse readResponse = confirmedServiceResponse.getRead();
+ List listOfAccessResults = readResponse.getListOfAccessResult().getAccessResult();
+
+ if (listOfAccessResults.size() != ds.getMembers().size()) {
+ ServiceError serviceError =
+ new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Number of AccessResults does not match the number of DataSet members.");
+ for (int i = 0; i < dataSetSize; i++) {
+ serviceErrors.add(serviceError);
+ }
+ return serviceErrors;
+ }
+
+ Iterator accessResultIterator = listOfAccessResults.iterator();
+
+ for (FcModelNode dsMember : ds) {
+ AccessResult accessResult = accessResultIterator.next();
+ if (accessResult.getSuccess() != null) {
+ try {
+ dsMember.setValueFromMmsDataObj(accessResult.getSuccess());
+ } catch (ServiceError e) {
+ serviceErrors.add(e);
+ }
+ serviceErrors.add(null);
+ } else {
+ serviceErrors.add(mmsDataAccessErrorToServiceError(accessResult.getFailure()));
+ }
+ }
+
+ return serviceErrors;
+ }
+
+ public List setDataSetValues(DataSet dataSet) throws ServiceError, IOException {
+ ConfirmedServiceRequest serviceRequest = constructSetDataSetValues(dataSet);
+ ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
+ return decodeSetDataSetValuesResponse(confirmedServiceResponse);
+ }
+
+ private ConfirmedServiceRequest constructSetDataSetValues(DataSet dataSet) throws ServiceError {
+ VariableAccessSpecification varAccessSpec = new VariableAccessSpecification();
+ varAccessSpec.setVariableListName(dataSet.getMmsObjectName());
+
+ ListOfData listOfData = new ListOfData();
+ List dataList = listOfData.getData();
+
+ for (ModelNode member : dataSet) {
+ dataList.add(member.getMmsDataObj());
+ }
+
+ WriteRequest writeRequest = new WriteRequest();
+ writeRequest.setVariableAccessSpecification(varAccessSpec);
+ writeRequest.setListOfData(listOfData);
+
+ ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
+ confirmedServiceRequest.setWrite(writeRequest);
+
+ return confirmedServiceRequest;
+ }
+
+ private List decodeSetDataSetValuesResponse(
+ ConfirmedServiceResponse confirmedServiceResponse) throws ServiceError {
+
+ if (confirmedServiceResponse.getWrite() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Error decoding SetDataSetValuesReponsePdu");
+ }
+
+ WriteResponse writeResponse = confirmedServiceResponse.getWrite();
+ List writeResChoiceType = writeResponse.getCHOICE();
+ List serviceErrors = new ArrayList<>(writeResChoiceType.size());
+
+ for (WriteResponse.CHOICE accessResult : writeResChoiceType) {
+ if (accessResult.getSuccess() != null) {
+ serviceErrors.add(null);
+ } else {
+ serviceErrors.add(mmsDataAccessErrorToServiceError(accessResult.getFailure()));
+ }
+ }
+ return serviceErrors;
+ }
+
+ public void getRcbValues(Rcb rcb) throws ServiceError, IOException {
+ getDataValues(rcb);
+ }
+
+ public void reserveUrcb(Urcb urcb) throws ServiceError, IOException {
+ BdaBoolean resvBda = urcb.getResv();
+ resvBda.setValue(true);
+ setDataValues(resvBda);
+ }
+
+ public void reserveBrcb(Brcb brcb, short resvTime) throws ServiceError, IOException {
+ BdaInt16 resvTmsBda = brcb.getResvTms();
+ resvTmsBda.setValue(resvTime);
+ setDataValues(resvTmsBda);
+ }
+
+ public void cancelUrcbReservation(Urcb urcb) throws ServiceError, IOException {
+ BdaBoolean resvBda = urcb.getResv();
+ resvBda.setValue(false);
+ setDataValues(resvBda);
+ }
+
+ public void enableReporting(Rcb rcb) throws ServiceError, IOException {
+ BdaBoolean rptEnaBda = rcb.getRptEna();
+ rptEnaBda.setValue(true);
+ setDataValues(rptEnaBda);
+ }
+
+ public void disableReporting(Rcb rcb) throws ServiceError, IOException {
+ BdaBoolean rptEnaBda = rcb.getRptEna();
+ rptEnaBda.setValue(false);
+ setDataValues(rptEnaBda);
+ }
+
+ public void startGi(Rcb rcb) throws ServiceError, IOException {
+ BdaBoolean rptGiBda = (BdaBoolean) rcb.getChild("GI");
+ rptGiBda.setValue(true);
+ setDataValues(rptGiBda);
+ }
+
+ /**
+ * 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
+ * @param setRptId whether to set the report ID
+ * @param setDatSet whether to set the data set
+ * @param setOptFlds whether to set the optional fields
+ * @param setBufTm whether to set the buffer time
+ * @param setTrgOps whether to set the trigger options
+ * @param setIntgPd whether to set the integrity period
+ * @param setPurgeBuf whether to set purge buffer
+ * @param setEntryId whether to set the entry ID
+ * @return a list indicating ServiceErrors that may have occurred.
+ * @throws IOException if a fatal IO error occurs. The association object will be closed and can
+ * no longer be used after this exception is thrown.
+ */
+ public List setRcbValues(
+ Rcb rcb,
+ boolean setRptId,
+ boolean setDatSet,
+ boolean setOptFlds,
+ boolean setBufTm,
+ boolean setTrgOps,
+ boolean setIntgPd,
+ boolean setPurgeBuf,
+ boolean setEntryId)
+ throws IOException {
+
+ List parametersToSet = new ArrayList<>(6);
+
+ if (setRptId == true) {
+ parametersToSet.add(rcb.getRptId());
+ }
+ if (setDatSet == true) {
+ rcb.getDatSet().setValue(rcb.getDatSet().getStringValue().replace('.', '$'));
+ parametersToSet.add(rcb.getDatSet());
+ }
+ if (setOptFlds == true) {
+ parametersToSet.add(rcb.getOptFlds());
+ }
+ if (setBufTm == true) {
+ parametersToSet.add(rcb.getBufTm());
+ }
+ if (setTrgOps == true) {
+ parametersToSet.add(rcb.getTrgOps());
+ }
+ if (setIntgPd == true) {
+ parametersToSet.add(rcb.getIntgPd());
+ }
+ if (rcb instanceof Brcb) {
+ Brcb brcb = (Brcb) rcb;
+ if (setPurgeBuf == true) {
+ parametersToSet.add(brcb.getPurgeBuf());
+ }
+ if (setEntryId == true) {
+ parametersToSet.add(brcb.getEntryId());
+ }
+ }
+
+ List serviceErrors = new ArrayList<>(parametersToSet.size());
+
+ for (FcModelNode child : parametersToSet) {
+ try {
+ setDataValues(child);
+ serviceErrors.add(null);
+ } catch (ServiceError e) {
+ serviceErrors.add(e);
+ }
+ }
+
+ return serviceErrors;
+ }
+
+ private Report processReport(MMSpdu mmsPdu) throws ServiceError {
+
+ if (mmsPdu.getUnconfirmedPDU() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "getReport: Error decoding server response");
+ }
+
+ UnconfirmedPDU unconfirmedRes = mmsPdu.getUnconfirmedPDU();
+
+ if (unconfirmedRes.getService() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "getReport: Error decoding server response");
+ }
+
+ UnconfirmedService unconfirmedServ = unconfirmedRes.getService();
+
+ if (unconfirmedServ.getInformationReport() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "getReport: Error decoding server response");
+ }
+
+ List listRes =
+ unconfirmedServ.getInformationReport().getListOfAccessResult().getAccessResult();
+
+ int index = 0;
+
+ if (listRes.get(index).getSuccess().getVisibleString() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "processReport: report does not contain RptID");
+ }
+
+ 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");
+ }
+
+ 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;
+ }
+
+ 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
+ index += numMembersReported;
+ }
+
+ List reportedDataSetMembers = new ArrayList<>(numMembersReported);
+ int dataSetIndex = 0;
+ for (FcModelNode dataSetMember : dataSet.getMembers()) {
+ if (inclusionBitString[dataSetIndex]) {
+ AccessResult accessRes = listRes.get(index++);
+ FcModelNode dataSetMemberCopy = (FcModelNode) dataSetMember.copy();
+ dataSetMemberCopy.setValueFromMmsDataObj(accessRes.getSuccess());
+ reportedDataSetMembers.add(dataSetMemberCopy);
+ }
+ dataSetIndex++;
+ }
+
+ List reasonCodes = null;
+ if (optFlds.isReasonForInclusion()) {
+ reasonCodes = new ArrayList<>(dataSet.getMembers().size());
+ for (int i = 0; i < dataSet.getMembers().size(); i++) {
+ if (inclusionBitString[i]) {
+ BdaReasonForInclusion reasonForInclusion = new BdaReasonForInclusion(null);
+ reasonCodes.add(reasonForInclusion);
+ byte[] reason = listRes.get(index++).getSuccess().getBitString().value;
+ reasonForInclusion.setValue(reason);
+ }
+ }
+ }
+
+ return new Report(
+ rptId,
+ sqNum,
+ subSqNum,
+ moreSegmentsFollow,
+ dataSetRef,
+ bufOvfl,
+ confRev,
+ timeOfEntry,
+ entryId,
+ inclusionBitString,
+ reportedDataSetMembers,
+ reasonCodes);
+ }
+
+ /**
+ * Performs the Select ACSI Service of the control model on the given controllable Data Object
+ * (DO). By selecting a controllable DO you can reserve it for exclusive control/operation. This
+ * service is only applicable if the ctlModel Data Attribute is set to "sbo-with-normal-security"
+ * (2).
+ *
+ * The selection is canceled in one of the following events:
+ *
+ *
+ * - The "Cancel" ACSI service is issued.
+ *
- The sboTimemout (select before operate timeout) runs out. If the given controlDataObject
+ * contains a sboTimeout Data Attribute it is possible to change the timeout after which the
+ * selection/reservation is automatically canceled by the server. Otherwise the timeout is a
+ * local issue of the server.
+ *
- The connection to the server is closed.
+ *
- An operate service failed because of some error
+ *
- The sboClass is set to "operate-once" then the selection is also canceled after a
+ * successful operate service.
+ *
+ *
+ * @param controlDataObject needs to be a controllable Data Object that contains a Data Attribute
+ * named "SBO".
+ * @return false if the selection/reservation was not successful (because it is already selected
+ * by another client). Otherwise true is returned.
+ * @throws ServiceError if a ServiceError is returned by the server.
+ * @throws IOException if a fatal IO error occurs. The association object will be closed and can
+ * no longer be used after this exception is thrown.
+ */
+ public boolean select(FcModelNode controlDataObject) throws ServiceError, IOException {
+ BdaVisibleString sbo;
+ try {
+ sbo = (BdaVisibleString) controlDataObject.getChild("SBO");
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "ModelNode needs to conain a child node named SBO in order to select");
+ }
+
+ getDataValues(sbo);
+
+ return sbo.getValue().length != 0;
+ }
+
+ /**
+ * Executes the Operate ACSI Service on the given controllable Data Object (DO). The following
+ * subnodes of the given control DO should be set according your needs before calling this
+ * function. (Note that you can probably leave most attributes with their default value):
+ *
+ *
+ * - Oper.ctlVal - has to be set to actual control value that is to be written using the
+ * operate service.
+ *
- Oper.operTm (type: BdaTimestamp) - is an optional sub data attribute of Oper (thus it may
+ * not exist). If it exists it can be used to set the timestamp when the operation shall be
+ * performed by the server. Thus the server will delay execution of the operate command
+ * until the given date is reached. Can be set to an empty byte array (new byte[0]) or null
+ * so that the server executes the operate command immediately. This is also the default.
+ *
- Oper.check (type: BdaCheck) is used to tell the server whether to perform the
+ * synchrocheck and interlockcheck. By default they are turned off.
+ *
- Oper.orign - contains the two data attributes orCat (origin category, type: BdaInt8) and
+ * orIdent (origin identifier, type BdaOctetString). Origin is optionally reflected in the
+ * status Data Attribute controlDO.origin. By reading this data attribute other clients can
+ * see who executed the last operate command. The default value for orCat is 0
+ * ("not-supported") and the default value for orIdent is ""(the empty string).
+ *
- Oper.Test (BdaBoolean) - if true this operate command is sent for test purposes only.
+ * Default is false.
+ *
+ *
+ * All other operate parameters are automatically handled by this function.
+ *
+ * @param controlDataObject needs to be a controllable Data Object that contains a Data Attribute
+ * named "Oper".
+ * @throws ServiceError if a ServiceError is returned by the server
+ * @throws IOException if a fatal IO error occurs. The association object will be closed and can
+ * no longer be used after this exception is thrown.
+ */
+ public void operate(FcModelNode controlDataObject) throws ServiceError, IOException {
+ ConstructedDataAttribute oper;
+ try {
+ oper = (ConstructedDataAttribute) controlDataObject.getChild("Oper");
+ } catch (Exception e) {
+ throw new IllegalArgumentException("ModelNode needs to conain a child node named \"Oper\".");
+ }
+
+ ((BdaInt8U) oper.getChild("ctlNum")).setValue((short) 1);
+ ((BdaTimestamp) oper.getChild("T")).setInstant(Instant.now());
+
+ setDataValues(oper);
+ }
+
+ public boolean isOpen() {
+ return !closed;
+ }
+
+ /** Will close the connection simply by closing the TCP socket. */
+ public void close() {
+ clientReceiver.close(new IOException("Connection closed by client"));
+ }
+
+ /** Will send a disconnect request first and then close the TCP socket. */
+ public void disconnect() {
+ clientReceiver.disconnect();
+ }
+
+ final class ClientReceiver extends Thread {
+
+ private final ByteBuffer pduBuffer;
+ private Integer expectedResponseId;
+ private IOException lastIOException = null;
+
+ public ClientReceiver(int maxMmsPduSize) {
+ pduBuffer = ByteBuffer.allocate(maxMmsPduSize + 400);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+
+ pduBuffer.clear();
+ byte[] buffer;
+ try {
+ buffer = acseAssociation.receive(pduBuffer);
+ } catch (TimeoutException e) {
+ // Illegal state: A timeout exception was thrown.
+ throw new IllegalStateException();
+ } catch (DecodingException e) {
+ // Error decoding the OSI headers of the received packet
+ continue;
+ }
+
+ MMSpdu decodedResponsePdu = new MMSpdu();
+ try {
+ decodedResponsePdu.decode(new ByteArrayInputStream(buffer), null);
+ } catch (IOException e) {
+ // Error decoding the received MMS PDU
+ continue;
+ }
+
+ if (decodedResponsePdu.getUnconfirmedPDU() != null) {
+ if (decodedResponsePdu
+ .getUnconfirmedPDU()
+ .getService()
+ .getInformationReport()
+ .getVariableAccessSpecification()
+ .getListOfVariable()
+ != null) {
+ // Discarding LastApplError Report
+ } else {
+ if (reportListener != null) {
+ final Report report = processReport(decodedResponsePdu);
+
+ Thread t1 =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ reportListener.newReport(report);
+ }
+ });
+ t1.start();
+ } else {
+ // discarding report because no ReportListener was registered.
+ }
+ }
+ } else if (decodedResponsePdu.getRejectPDU() != null) {
+ synchronized (incomingResponses) {
+ if (expectedResponseId == null) {
+ // Discarding Reject MMS PDU because no listener for request was found.
+ continue;
+ } else if (decodedResponsePdu.getRejectPDU().getOriginalInvokeID().value.intValue()
+ != expectedResponseId) {
+ // Discarding Reject MMS PDU because no listener with fitting invokeID was found.
+ continue;
+ } else {
+ try {
+ incomingResponses.put(decodedResponsePdu);
+ } catch (InterruptedException e) {
+ // TODO can this ever be interrupted?
+ }
+ }
+ }
+ } else if (decodedResponsePdu.getConfirmedErrorPDU() != null) {
+ synchronized (incomingResponses) {
+ if (expectedResponseId == null) {
+ // Discarding ConfirmedError MMS PDU because no listener for request was found.
+ continue;
+ } else if (decodedResponsePdu.getConfirmedErrorPDU().getInvokeID().value.intValue()
+ != expectedResponseId) {
+ // Discarding ConfirmedError MMS PDU because no listener with fitting invokeID was
+ // found.
+ continue;
+ } else {
+ try {
+ incomingResponses.put(decodedResponsePdu);
+ } catch (InterruptedException e) {
+ // TODO can this ever be interrupted?
+ }
+ }
+ }
+ } else {
+ synchronized (incomingResponses) {
+ if (expectedResponseId == null) {
+ // Discarding ConfirmedResponse MMS PDU because no listener for request was found.
+ continue;
+ } else if (decodedResponsePdu.getConfirmedResponsePDU().getInvokeID().value.intValue()
+ != expectedResponseId) {
+ // Discarding ConfirmedResponse MMS PDU because no listener with fitting invokeID
+ // was
+ // found.
+ continue;
+ } else {
+ try {
+ incomingResponses.put(decodedResponsePdu);
+ } catch (InterruptedException e) {
+ // TODO can this ever be interrupted?
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ close(e);
+ } catch (Exception e) {
+ close(new IOException("unexpected exception while receiving", e));
+ }
+ }
+
+ public void setResponseExpected(int invokeId) {
+ expectedResponseId = invokeId;
+ }
+
+ private void disconnect() {
+ synchronized (this) {
+ if (closed == false) {
+ closed = true;
+ acseAssociation.disconnect();
+ lastIOException = new IOException("Connection disconnected by client");
+ if (reportListener != null) {
+ Thread t1 =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ reportListener.associationClosed(lastIOException);
+ }
+ });
+ t1.start();
+ }
+
+ MMSpdu mmsPdu = new MMSpdu();
+ mmsPdu.setConfirmedRequestPDU(new ConfirmedRequestPDU());
+ try {
+ incomingResponses.put(mmsPdu);
+ } catch (InterruptedException e1) {
+ // TODO can this ever be interrupted?
+ }
+ }
+ }
+ }
+
+ private void close(IOException e) {
+ synchronized (this) {
+ if (closed == false) {
+ closed = true;
+ acseAssociation.close();
+ lastIOException = e;
+ if (reportListener != null) {
+ Thread t1 =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ reportListener.associationClosed(lastIOException);
+ }
+ });
+ t1.start();
+ }
+
+ MMSpdu mmsPdu = new MMSpdu();
+ mmsPdu.setConfirmedRequestPDU(new ConfirmedRequestPDU());
+ try {
+ incomingResponses.put(mmsPdu);
+ } catch (InterruptedException e1) {
+ // TODO can this ever be interrupted?
+ }
+ }
+ }
+ }
+
+ IOException getLastIOException() {
+ return lastIOException;
+ }
+
+ MMSpdu removeExpectedResponse() {
+ synchronized (incomingResponses) {
+ expectedResponseId = null;
+ return incomingResponses.poll();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ClientEventListener.java b/src/main/java/com/beanit/iec61850bean/ClientEventListener.java
new file mode 100644
index 0000000..4507aaf
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ClientEventListener.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.io.IOException;
+
+/**
+ * The listener interface for receiving incoming reports and association closed events. A listener
+ * is registered through the {@link ClientSap#associate(java.net.InetAddress, int, String,
+ * ClientEventListener) associate} method.
+ *
+ * @author Stefan Feuerhahn
+ */
+public interface ClientEventListener {
+
+ /**
+ * Invoked when a new report arrives. Note that the implementation of this method needs to be
+ * thread safe as it can be called in parallel if a new report arrives while an old one is still
+ * being processed.
+ *
+ * @param report the report that arrived.
+ */
+ void newReport(Report report);
+
+ /**
+ * Invoked when an IOException occurred for the association. An IOException implies that the
+ * ClientAssociation that feeds this listener was automatically closed and can no longer be used
+ * to receive reports.
+ *
+ * @param e the exception that occured.
+ */
+ void associationClosed(IOException e);
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ClientSap.java b/src/main/java/com/beanit/iec61850bean/ClientSap.java
new file mode 100644
index 0000000..c67f771
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ClientSap.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.josistack.ClientAcseSap;
+import java.io.IOException;
+import java.net.InetAddress;
+import javax.net.SocketFactory;
+
+/**
+ * The ClientSap
class represents the IEC 61850 service access point for client
+ * applications. A client application that wants to connect to a server should first create an
+ * instance of ClientSap
. Next all the necessary configuration parameters can be set.
+ * Finally the associate
function is called to connect to the server. An instance of
+ * ClientSap
can be used to create an unlimited number of associations. Changing the
+ * parameters of a ClientSap has no affect on associations that have already been created.
+ */
+public final class ClientSap {
+
+ static final int MINIMUM_MMS_PDU_SIZE = 64;
+ private static final int MAXIMUM_MMS_PDU_SIZE = 65000;
+
+ private static final byte[] DEFAULT_TSEL_LOCAL = new byte[] {0, 0};
+ private static final byte[] DEFAULT_TSEL_REMOTE = new byte[] {0, 1};
+ private static final int DEFAULT_TPDU_SIZE_PARAMETER = 10; // size = 1024
+ private final ClientAcseSap acseSap;
+ private int proposedMaxServOutstandingCalling = 5;
+ private int proposedMaxServOutstandingCalled = 5;
+ private int proposedDataStructureNestingLevel = 10;
+ private int proposedMaxMmsPduSize = 65000;
+ private byte[] servicesSupportedCalling =
+ new byte[] {(byte) 0xee, 0x1c, 0, 0, 0x04, 0x08, 0, 0, 0x79, (byte) 0xef, 0x18};
+ private int messageFragmentTimeout = 10000;
+ private int responseTimeout = 20000;
+
+ /** Use this constructor to create a default client SAP. */
+ public ClientSap() {
+ acseSap = new ClientAcseSap();
+ acseSap.tSap.tSelLocal = DEFAULT_TSEL_LOCAL;
+ acseSap.tSap.tSelRemote = DEFAULT_TSEL_REMOTE;
+ acseSap.tSap.setMaxTPDUSizeParam(DEFAULT_TPDU_SIZE_PARAMETER);
+ }
+
+ /**
+ * Use this constructor to create a client SAP that uses the given SocketFactory
to
+ * connect to servers. You could pass an SSLSocketFactory to enable SSL.
+ *
+ * @param socketFactory the socket factory to construct the socket
+ */
+ public ClientSap(SocketFactory socketFactory) {
+ acseSap = new ClientAcseSap(socketFactory);
+ acseSap.tSap.tSelLocal = DEFAULT_TSEL_LOCAL;
+ acseSap.tSap.tSelRemote = DEFAULT_TSEL_REMOTE;
+ acseSap.tSap.setMaxTPDUSizeParam(DEFAULT_TPDU_SIZE_PARAMETER);
+ }
+
+ /**
+ * Gets the maximum MMS PDU size.
+ *
+ * @return the maximum MMS PDU size.
+ */
+ public int getMaxMmsPduSize() {
+ return proposedMaxMmsPduSize;
+ }
+
+ /**
+ * Sets the maximum MMS PDU size in bytes that the client association will support. The client
+ * proposes this value to the server during association. If the server requires the use of a
+ * smaller maximum MMS PDU size, then the smaller size will be accepted by the client. The default
+ * size is 65000.
+ *
+ * @param size cannot be less than 64. The upper limit is 65000 so that segmentation at the lower
+ * transport layer is avoided. The Transport Layer's maximum PDU size is 65531.
+ */
+ public void setMaxMmsPduSize(int size) {
+ if (size >= MINIMUM_MMS_PDU_SIZE && size <= MAXIMUM_MMS_PDU_SIZE) {
+ proposedMaxMmsPduSize = size;
+ } else {
+ throw new IllegalArgumentException("maximum size is out of bound");
+ }
+ }
+
+ public void setProposedMaxServOutstandingCalling(int proposedMaxServOutstandingCalling) {
+ this.proposedMaxServOutstandingCalling = proposedMaxServOutstandingCalling;
+ }
+
+ public void setProposedMaxServOutstandingCalled(int proposedMaxServOutstandingCalled) {
+ this.proposedMaxServOutstandingCalled = proposedMaxServOutstandingCalled;
+ }
+
+ public void setProposedDataStructureNestingLevel(int proposedDataStructureNestingLevel) {
+ this.proposedDataStructureNestingLevel = proposedDataStructureNestingLevel;
+ }
+
+ /**
+ * Gets the ServicesSupportedCalling parameter.
+ *
+ * @return the ServicesSupportedCalling parameter.
+ */
+ public byte[] getServicesSupportedCalling() {
+ return servicesSupportedCalling;
+ }
+
+ /**
+ * Sets the SevicesSupportedCalling parameter. The given parameter is sent to the server but has
+ * no affect on the functionality of this client.
+ *
+ * @param services the ServicesSupportedCalling parameter
+ */
+ public void setServicesSupportedCalling(byte[] services) {
+ if (services.length != 11) {
+ throw new IllegalArgumentException("The services parameter needs to be of lenth 11");
+ }
+ servicesSupportedCalling = services;
+ }
+
+ /**
+ * Sets the remote/called Session-Selector (S-SEL). The default remote S-SEL is byte[] { 0, 1 }.
+ *
+ * @param sSelRemote the remote/called S-SEL.
+ */
+ public void setSSelRemote(byte[] sSelRemote) {
+ acseSap.sSelRemote = sSelRemote;
+ }
+
+ /**
+ * Sets the local/calling Session-Selector (S-SEL). The default local S-SEL is byte[] { 0, 1 }.
+ *
+ * @param sSelLocal the local/calling S-SEL.
+ */
+ public void setSSelLocal(byte[] sSelLocal) {
+ acseSap.sSelLocal = sSelLocal;
+ }
+
+ /**
+ * Sets the remote/called Presentation-Selector (P-SEL). The default remote P-SEL is byte[] { 0,
+ * 0, 0, 1 }.
+ *
+ * @param pSelRemote the remote/called P-SEL.
+ */
+ public void setPSelRemote(byte[] pSelRemote) {
+ acseSap.pSelRemote = pSelRemote;
+ }
+
+ /**
+ * Sets the local/calling Presentation-Selector (P-SEL). The default local P-SEL is byte[] { 0, 0,
+ * 0, 1 }.
+ *
+ * @param pSelLocal the local/calling P-SEL.
+ */
+ public void setPSelLocal(byte[] pSelLocal) {
+ acseSap.pSelLocal = pSelLocal;
+ }
+
+ /**
+ * Sets the remote/called Transport-Selector (T-SEL). It is optionally transmitted in the OSI
+ * Transport Layer connection request (CR). The default remote T-SEL is byte[] { 0, 1 }.
+ *
+ * @param tSelRemote the remote/called T-SEL. If null the T-SEL will be omitted. No maximum size
+ * is defined by the standard.
+ */
+ public void setTSelRemote(byte[] tSelRemote) {
+ acseSap.tSap.tSelRemote = tSelRemote;
+ }
+
+ /**
+ * Sets the local/calling Transport-Selector (T-SEL). It is optionally transmitted in the OSI
+ * Transport Layer connection request (CR). The default local T-SEL byte[] { 0, 0 }.
+ *
+ * @param tSelLocal the local/calling T-SEL. If null the T-SEL will be omitted. No maximum size is
+ * defined by the standard.
+ */
+ public void setTSelLocal(byte[] tSelLocal) {
+ acseSap.tSap.tSelLocal = tSelLocal;
+ }
+
+ /**
+ * Set the maxTPDUSize. The default maxTPduSize is 65531 (see RFC 1006).
+ *
+ * @param maxTPduSizeParam The maximum length is equal to 2^(maxTPduSizeParam) octets. Note that
+ * the actual TSDU size that can be transfered is equal to TPduSize-3. Default is 65531 octets
+ * (see RFC 1006), 7 <= maxTPduSizeParam <= 16, needs to be set before listening or
+ * connecting
+ */
+ public void setMaxTPduSizeParameter(int maxTPduSizeParam) {
+ acseSap.tSap.setMaxTPDUSizeParam(maxTPduSizeParam);
+ }
+
+ /**
+ * Sets the remote/called Application Process Title. The default value is int[] { 1, 1, 999, 1, 1
+ * }
+ *
+ * @param title the remote/called AP title.
+ */
+ public void setApTitleCalled(int[] title) {
+ acseSap.setApTitleCalled(title);
+ }
+
+ /**
+ * Sets the local/calling Application Process Title. The default value is int[] { 1, 1, 999, 1 }
+ *
+ * @param title the local/calling AP title.
+ */
+ public void setApTitleCalling(int[] title) {
+ acseSap.setApTitleCalling(title);
+ }
+
+ /**
+ * Sets the remote/called Application Entity Qualifier. The default value is 12.
+ *
+ * @param qualifier the remote/called AE Qualifier
+ */
+ public void setAeQualifierCalled(int qualifier) {
+ acseSap.setAeQualifierCalled(qualifier);
+ }
+
+ /**
+ * Sets the local/calling Application Entity Qualifier. The default value is 12.
+ *
+ * @param qualifier the local/calling AE Qualifier
+ */
+ public void setAeQualifierCalling(int qualifier) {
+ acseSap.setAeQualifierCalling(qualifier);
+ }
+
+ /**
+ * Sets the default response timeout of the ClientAssociation
that is created using
+ * this ClientSap.
+ *
+ * @param timeout the response timeout in milliseconds. The default is 20000.
+ */
+ public void setResponseTimeout(int timeout) {
+ responseTimeout = timeout;
+ }
+
+ /**
+ * Sets the message fragment timeout. This is the timeout that the socket timeout is set to after
+ * the first byte of a message has been received. A request function (e.g. setDataValues()) will
+ * throw an IOException if the socket throws this timeout because the association/connection
+ * cannot recover from this kind of error.
+ *
+ * @param timeout the timeout in milliseconds. The default is 10000.
+ */
+ public void setMessageFragmentTimeout(int timeout) {
+ messageFragmentTimeout = timeout;
+ }
+
+ /**
+ * Connects to the IEC 61850 MMS server at the given address and port and returns the resulting
+ * association object.
+ *
+ * @param address the address to connect to.
+ * @param port the port to connect to. Usually the MMS port is 102.
+ * @param authenticationParameter an optional authentication parameters that is transmitted. It
+ * will be omitted if equal to null.
+ * @param reportListener the listener to be notified of incoming reports
+ * @return the association object
+ * @throws IOException if any kind of error occurs trying build up the association
+ */
+ public ClientAssociation associate(
+ InetAddress address,
+ int port,
+ String authenticationParameter,
+ ClientEventListener reportListener)
+ throws IOException {
+ return associate(address, port, authenticationParameter, null, -1, reportListener);
+ }
+
+ /**
+ * Connects to the IEC 61850 MMS server at the given address and port and returns the resulting
+ * association object.
+ *
+ * @param address the address to connect to
+ * @param port the port to connect to. Usually the MMS port is 102.
+ * @param authenticationParameter an optional authentication parameters that is transmitted. It
+ * will be omitted if equal to null.
+ * @param localAddr the local address to use
+ * @param localPort the local port to use
+ * @param reportListener the listener to be notified of incoming reports
+ * @return the association object.
+ * @throws IOException if any kind of error occurs trying build up the association
+ */
+ public ClientAssociation associate(
+ InetAddress address,
+ int port,
+ String authenticationParameter,
+ InetAddress localAddr,
+ int localPort,
+ ClientEventListener reportListener)
+ throws IOException {
+
+ if (port < 0 || port > 65535) {
+ throw new IllegalArgumentException("invalid port");
+ }
+
+ if (address == null) {
+ throw new IllegalArgumentException("address may not be null");
+ }
+
+ if (acseSap.sSelRemote == null) {
+ throw new IllegalArgumentException("sSelRemote may not be null");
+ }
+
+ if (acseSap.sSelRemote.length != 2) {
+ throw new IllegalArgumentException("sSelRemote lenght must be two");
+ }
+
+ if (acseSap.sSelLocal == null) {
+ throw new IllegalArgumentException("sSelLocal may not be null");
+ }
+
+ if (acseSap.sSelLocal.length != 2) {
+ throw new IllegalArgumentException("sSelLocal lenght must be two");
+ }
+
+ ClientAssociation clientAssociation =
+ new ClientAssociation(
+ address,
+ port,
+ localAddr,
+ localPort,
+ authenticationParameter,
+ acseSap,
+ proposedMaxMmsPduSize,
+ proposedMaxServOutstandingCalling,
+ proposedMaxServOutstandingCalled,
+ proposedDataStructureNestingLevel,
+ servicesSupportedCalling,
+ responseTimeout,
+ messageFragmentTimeout,
+ reportListener);
+
+ return clientAssociation;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ConstructedDataAttribute.java b/src/main/java/com/beanit/iec61850bean/ConstructedDataAttribute.java
new file mode 100644
index 0000000..12c8810
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ConstructedDataAttribute.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Data.Structure;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public final class ConstructedDataAttribute extends FcModelNode {
+
+ public ConstructedDataAttribute(
+ ObjectReference objectReference, Fc fc, List children) {
+ this.objectReference = objectReference;
+ this.fc = fc;
+ this.children = new LinkedHashMap<>((int) ((children.size() / 0.75) + 1));
+ for (ModelNode child : children) {
+ this.children.put(child.getName(), child);
+ child.setParent(this);
+ }
+ }
+
+ @Override
+ public ConstructedDataAttribute copy() {
+ List subDataAttributesCopy = new ArrayList<>();
+ for (ModelNode subDA : children.values()) {
+ subDataAttributesCopy.add((FcModelNode) subDA.copy());
+ }
+ return new ConstructedDataAttribute(getReference(), fc, subDataAttributesCopy);
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Structure structure = new Structure();
+ List seq = structure.getData();
+
+ for (ModelNode modelNode : getChildren()) {
+ Data child = modelNode.getMmsDataObj();
+ if (child == null) {
+ throw new IllegalArgumentException(
+ "Unable to convert Child: " + modelNode.objectReference + " to MMS Data Object.");
+ }
+ seq.add(child);
+ }
+ if (seq.size() == 0) {
+ throw new IllegalArgumentException(
+ "Converting ModelNode: "
+ + objectReference
+ + " to MMS Data Object resulted in Sequence of size zero.");
+ }
+
+ Data data = new Data();
+ data.setStructure(structure);
+
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getStructure() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: structure");
+ }
+ if (data.getStructure().getData().size() != children.size()) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT,
+ "expected type: structure with " + children.size() + " elements");
+ }
+
+ Iterator iterator = data.getStructure().getData().iterator();
+ for (ModelNode child : children.values()) {
+ child.setValueFromMmsDataObj(iterator.next());
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/DataDefinitionResParser.java b/src/main/java/com/beanit/iec61850bean/DataDefinitionResParser.java
new file mode 100644
index 0000000..e9ee458
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/DataDefinitionResParser.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedServiceResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetVariableAccessAttributesResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription.Structure.Components;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeSpecification;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+final class DataDefinitionResParser {
+
+ static LogicalNode parseGetDataDefinitionResponse(
+ ConfirmedServiceResponse confirmedServiceResponse, ObjectReference lnRef)
+ throws ServiceError {
+
+ if (confirmedServiceResponse.getGetVariableAccessAttributes() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeGetDataDefinitionResponse: Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ GetVariableAccessAttributesResponse varAccAttrs =
+ confirmedServiceResponse.getGetVariableAccessAttributes();
+ TypeDescription typeSpec = varAccAttrs.getTypeDescription();
+ if (typeSpec.getStructure() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "decodeGetDataDefinitionResponse: Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ Components structure = typeSpec.getStructure().getComponents();
+
+ List fcDataObjects = new ArrayList<>();
+
+ Fc fc;
+ for (TypeDescription.Structure.Components.SEQUENCE fcComponent : structure.getSEQUENCE()) {
+ if (fcComponent.getComponentName() == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ if (fcComponent.getComponentType().getTypeDescription().getStructure() == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ String fcString = fcComponent.getComponentName().toString();
+ if (fcString.equals("LG")
+ || fcString.equals("GO")
+ || fcString.equals("GS")
+ || fcString.equals("MS")
+ || fcString.equals("US")) {
+ continue;
+ }
+
+ fc = Fc.fromString(fcComponent.getComponentName().toString());
+ Components subStructure =
+ fcComponent.getComponentType().getTypeDescription().getStructure().getComponents();
+
+ fcDataObjects.addAll(getFcDataObjectsFromSubStructure(lnRef, fc, subStructure));
+ }
+
+ LogicalNode ln = new LogicalNode(lnRef, fcDataObjects);
+
+ return ln;
+ }
+
+ private static List getFcDataObjectsFromSubStructure(
+ ObjectReference lnRef, Fc fc, Components components) throws ServiceError {
+
+ List structComponents = components.getSEQUENCE();
+ List dataObjects = new ArrayList<>(structComponents.size());
+
+ for (TypeDescription.Structure.Components.SEQUENCE doComp : structComponents) {
+ if (doComp.getComponentName() == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Error decoding GetDataDefinitionResponsePdu");
+ }
+ if (doComp.getComponentType().getTypeDescription() == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ ObjectReference doRef =
+ new ObjectReference(lnRef + "." + doComp.getComponentName().toString());
+ List children =
+ getDoSubModelNodesFromSubStructure(
+ doRef,
+ fc,
+ doComp.getComponentType().getTypeDescription().getStructure().getComponents());
+ if (fc == Fc.RP) {
+ dataObjects.add(new Urcb(doRef, children));
+ } else if (fc == Fc.BR) {
+ dataObjects.add(new Brcb(doRef, children));
+ } else {
+ dataObjects.add(new FcDataObject(doRef, fc, children));
+ }
+ }
+
+ return dataObjects;
+ }
+
+ private static List getDoSubModelNodesFromSubStructure(
+ ObjectReference parentRef, Fc fc, Components structure) throws ServiceError {
+
+ Collection structComponents =
+ structure.getSEQUENCE();
+ List dataObjects = new ArrayList<>(structComponents.size());
+
+ for (TypeDescription.Structure.Components.SEQUENCE component : structComponents) {
+ if (component.getComponentName() == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Error decoding GetDataDefinitionResponsePdu");
+ }
+
+ String childName = component.getComponentName().toString();
+ dataObjects.add(
+ getModelNodesFromTypeSpecification(
+ new ObjectReference(parentRef + "." + childName), fc, component.getComponentType()));
+ }
+ return dataObjects;
+ }
+
+ private static FcModelNode getModelNodesFromTypeSpecification(
+ ObjectReference ref, Fc fc, TypeSpecification mmsTypeSpec) throws ServiceError {
+
+ if (mmsTypeSpec.getTypeDescription().getArray() != null) {
+
+ int numArrayElements =
+ mmsTypeSpec.getTypeDescription().getArray().getNumberOfElements().intValue();
+ List arrayChildren = new ArrayList<>(numArrayElements);
+ for (int i = 0; i < numArrayElements; i++) {
+ arrayChildren.add(
+ getModelNodesFromTypeSpecification(
+ new ObjectReference(ref + "(" + i + ")"),
+ fc,
+ mmsTypeSpec.getTypeDescription().getArray().getElementType()));
+ }
+
+ return new Array(ref, fc, arrayChildren);
+ }
+
+ if (mmsTypeSpec.getTypeDescription().getStructure() != null) {
+ List children =
+ getDoSubModelNodesFromSubStructure(
+ ref, fc, mmsTypeSpec.getTypeDescription().getStructure().getComponents());
+ return (new ConstructedDataAttribute(ref, fc, children));
+ }
+
+ // it is a single element
+ BasicDataAttribute bt = convertMmsBasicTypeSpec(ref, fc, mmsTypeSpec.getTypeDescription());
+ if (bt == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "decodeGetDataDefinitionResponse: Unknown data type received " + ref);
+ }
+ return (bt);
+ }
+
+ private static BasicDataAttribute convertMmsBasicTypeSpec(
+ ObjectReference ref, Fc fc, TypeDescription mmsTypeSpec) throws ServiceError {
+
+ if (mmsTypeSpec.getBool() != null) {
+ return new BdaBoolean(ref, fc, null, false, false);
+ }
+ if (mmsTypeSpec.getBitString() != null) {
+ int bitStringMaxLength = Math.abs(mmsTypeSpec.getBitString().intValue());
+
+ if (bitStringMaxLength == 13) {
+ return new BdaQuality(ref, fc, null, false);
+ } else if (bitStringMaxLength == 10) {
+ return new BdaOptFlds(ref, fc);
+ } else if (bitStringMaxLength == 6) {
+ return new BdaTriggerConditions(ref, fc);
+ } else if (bitStringMaxLength == 2) {
+ if (fc == Fc.CO) {
+ // if name == ctlVal
+ if (ref.getName().charAt(1) == 't') {
+ return new BdaTapCommand(ref, fc, null, false, false);
+ }
+ // name == Check
+ else {
+ return new BdaCheck(ref);
+ }
+ } else {
+ return new BdaDoubleBitPos(ref, fc, null, false, false);
+ }
+ }
+ return null;
+ } else if (mmsTypeSpec.getInteger() != null) {
+ switch (mmsTypeSpec.getInteger().intValue()) {
+ case 8:
+ return new BdaInt8(ref, fc, null, false, false);
+ case 16:
+ return new BdaInt16(ref, fc, null, false, false);
+ case 32:
+ return new BdaInt32(ref, fc, null, false, false);
+ case 64:
+ return new BdaInt64(ref, fc, null, false, false);
+ case 128:
+ return new BdaInt128(ref, fc, null, false, false);
+ }
+ } else if (mmsTypeSpec.getUnsigned() != null) {
+ switch (mmsTypeSpec.getUnsigned().intValue()) {
+ case 8:
+ return new BdaInt8U(ref, fc, null, false, false);
+ case 16:
+ return new BdaInt16U(ref, fc, null, false, false);
+ case 32:
+ return new BdaInt32U(ref, fc, null, false, false);
+ }
+ } else if (mmsTypeSpec.getFloatingPoint() != null) {
+ int floatSize = mmsTypeSpec.getFloatingPoint().getFormatWidth().intValue();
+ if (floatSize == 32) {
+ return new BdaFloat32(ref, fc, null, false, false);
+ } else if (floatSize == 64) {
+ return new BdaFloat64(ref, fc, null, false, false);
+ }
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "FLOAT of size: " + floatSize + " is not supported.");
+ } else if (mmsTypeSpec.getOctetString() != null) {
+ int stringSize = mmsTypeSpec.getOctetString().intValue();
+ if (stringSize > 255 || stringSize < -255) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "OCTET_STRING of size: " + stringSize + " is not supported.");
+ }
+ return new BdaOctetString(ref, fc, null, Math.abs(stringSize), false, false);
+
+ } else if (mmsTypeSpec.getVisibleString() != null) {
+ int stringSize = mmsTypeSpec.getVisibleString().intValue();
+ if (stringSize > 255 || stringSize < -255) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "VISIBLE_STRING of size: " + stringSize + " is not supported.");
+ }
+ return new BdaVisibleString(ref, fc, null, Math.abs(stringSize), false, false);
+ } else if (mmsTypeSpec.getMMSString() != null) {
+ int stringSize = mmsTypeSpec.getMMSString().intValue();
+ if (stringSize > 255 || stringSize < -255) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "UNICODE_STRING of size: " + stringSize + " is not supported.");
+ }
+ return new BdaUnicodeString(ref, fc, null, Math.abs(stringSize), false, false);
+ } else if (mmsTypeSpec.getUtcTime() != null) {
+ return new BdaTimestamp(ref, fc, null, false, false);
+ } else if (mmsTypeSpec.getBinaryTime() != null) {
+ return new BdaEntryTime(ref, fc, null, false, false);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/DataSet.java b/src/main/java/com/beanit/iec61850bean/DataSet.java
new file mode 100644
index 0000000..7ca483d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/DataSet.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class DataSet implements Iterable {
+
+ private final String dataSetReference;
+ private final List members;
+ private final Map> membersMap = new EnumMap<>(Fc.class);
+ private final boolean deletable;
+ private ObjectName mmsObjectName = null;
+
+ public DataSet(String dataSetReference, List members) {
+ this(dataSetReference, members, true);
+ }
+
+ public DataSet(String dataSetReference, List members, boolean deletable) {
+ if (!dataSetReference.startsWith("@") && dataSetReference.indexOf('/') == -1) {
+ throw new IllegalArgumentException(
+ "DataSet reference "
+ + dataSetReference
+ + " is invalid. Must either start with @ or contain a slash.");
+ }
+ this.members = new ArrayList<>();
+ this.dataSetReference = dataSetReference;
+ this.deletable = deletable;
+
+ for (Fc myfc : Fc.values()) {
+ membersMap.put(myfc, new LinkedHashMap());
+ }
+
+ for (FcModelNode member : members) {
+ this.members.add(member);
+ membersMap.get(member.getFc()).put(member.getReference().toString(), member);
+ }
+ }
+
+ public String getReferenceStr() {
+ return dataSetReference;
+ }
+
+ public DataSet copy() {
+ List membersCopy = new ArrayList<>(members.size());
+ for (FcModelNode node : members) {
+ membersCopy.add((FcModelNode) node.copy());
+ }
+ return new DataSet(dataSetReference, membersCopy, deletable);
+ }
+
+ public FcModelNode getMember(ObjectReference memberReference, Fc fc) {
+ if (fc != null) {
+ return membersMap.get(fc).get(memberReference.toString());
+ }
+ for (FcModelNode member : members) {
+ if (member.getReference().toString().equals(memberReference.toString())) {
+ return member;
+ }
+ }
+ return null;
+ }
+
+ public FcModelNode getMember(int index) {
+ return members.get(index);
+ }
+
+ public List getMembers() {
+ return members;
+ }
+
+ /**
+ * Those DataSets defined in the SCL file are not deletable. All other DataSets are deletable.
+ * Note that no Reports/Logs may be using the DataSet otherwise it cannot be deleted (but this
+ * function will still return true).
+ *
+ * @return true if deletable
+ */
+ public boolean isDeletable() {
+ return deletable;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return members.iterator();
+ }
+
+ public List getBasicDataAttributes() {
+ List subBasicDataAttributes = new ArrayList<>();
+ for (ModelNode member : members) {
+ subBasicDataAttributes.addAll(member.getBasicDataAttributes());
+ }
+ return subBasicDataAttributes;
+ }
+
+ ObjectName getMmsObjectName() {
+
+ if (mmsObjectName != null) {
+ return mmsObjectName;
+ }
+
+ if (dataSetReference.charAt(0) == '@') {
+ mmsObjectName = new ObjectName();
+ mmsObjectName.setAaSpecific(new Identifier(dataSetReference.getBytes(UTF_8)));
+ return mmsObjectName;
+ }
+
+ int slash = dataSetReference.indexOf('/');
+ String domainID = dataSetReference.substring(0, slash);
+ String itemID = dataSetReference.substring(slash + 1).replace('.', '$');
+
+ ObjectName.DomainSpecific domainSpecificObjectName = new ObjectName.DomainSpecific();
+ domainSpecificObjectName.setDomainID(new Identifier(domainID.getBytes(UTF_8)));
+ domainSpecificObjectName.setItemID(new Identifier(itemID.getBytes(UTF_8)));
+
+ mmsObjectName = new ObjectName();
+ mmsObjectName.setDomainSpecific(domainSpecificObjectName);
+
+ return mmsObjectName;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getReferenceStr());
+ for (FcModelNode member : members) {
+ sb.append("\n");
+ sb.append(member.toString());
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/Fc.java b/src/main/java/com/beanit/iec61850bean/Fc.java
new file mode 100644
index 0000000..fd8ff9c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/Fc.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public enum Fc {
+
+ // The following FCs are not part of this enum because they are not really
+ // FCs and only defined in part 8-1:
+ // RP (report), LG (log), BR (buffered report), GO, GS, MS, US
+
+ // FCs according to IEC 61850-7-2:
+ /** Status information */
+ ST,
+ /** Measurands - analogue values */
+ MX,
+ /** Setpoint */
+ SP,
+ /** Substitution */
+ SV,
+ /** Configuration */
+ CF,
+ /** Description */
+ DC,
+ /** Setting group */
+ SG,
+ /** Setting group editable */
+ SE,
+ /** Service response / Service tracking */
+ SR,
+ /** Operate received */
+ OR,
+ /** Blocking */
+ BL,
+ /** Extended definition */
+ EX,
+ /** Control, deprecated but kept here for backward compatibility */
+ CO,
+ /** Unbuffered Reporting */
+ RP,
+ /** Buffered Reporting */
+ BR;
+
+ public static Fc fromString(String fc) {
+ try {
+ return Fc.valueOf(fc);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/FcDataObject.java b/src/main/java/com/beanit/iec61850bean/FcDataObject.java
new file mode 100644
index 0000000..8b0cf5a
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/FcDataObject.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * This class represents a functionally constraint DataObject. That means it has unique reference
+ * and FunctionalConstraint. A DataObject as defined in part 7-3 is made up of 1..n FcDataObjects
+ * where n is the number of different FunctionalConstraints that the children of the DataObject
+ * have. An FcDataObject can have children of types FcDataObject, Array, ConstructedDataAttribute or
+ * BasicDataAttribute.
+ *
+ * @author Stefan Feuerhahn
+ */
+public class FcDataObject extends FcModelNode {
+
+ public FcDataObject(ObjectReference objectReference, Fc fc, List children) {
+
+ this.children = new LinkedHashMap<>((int) ((children.size() / 0.75) + 1));
+ this.objectReference = objectReference;
+ for (ModelNode child : children) {
+ this.children.put(child.getReference().getName(), child);
+ child.setParent(this);
+ }
+ this.fc = fc;
+ }
+
+ @Override
+ public FcDataObject copy() {
+ List childCopies = new ArrayList<>(children.size());
+ for (ModelNode childNode : children.values()) {
+ childCopies.add((FcModelNode) childNode.copy());
+ }
+ return new FcDataObject(objectReference, fc, childCopies);
+ }
+
+ @Override
+ Data getMmsDataObj() {
+ Data.Structure dataStructure = new Data.Structure();
+ List seq = dataStructure.getData();
+
+ for (ModelNode modelNode : getChildren()) {
+ Data child = modelNode.getMmsDataObj();
+ if (child == null) {
+ throw new IllegalArgumentException(
+ "Unable to convert Child: " + modelNode.objectReference + " to MMS Data Object.");
+ }
+ seq.add(child);
+ }
+ if (seq.size() == 0) {
+ throw new IllegalArgumentException(
+ "Converting ModelNode: "
+ + objectReference
+ + " to MMS Data Object resulted in Sequence of size zero.");
+ }
+
+ Data data = new Data();
+ data.setStructure(dataStructure);
+
+ return data;
+ }
+
+ @Override
+ void setValueFromMmsDataObj(Data data) throws ServiceError {
+ if (data.getStructure() == null) {
+ throw new ServiceError(ServiceError.TYPE_CONFLICT, "expected type: structure");
+ }
+ if (data.getStructure().getData().size() != children.size()) {
+ throw new ServiceError(
+ ServiceError.TYPE_CONFLICT,
+ "expected type: structure with " + children.size() + " elements");
+ }
+
+ Iterator iterator = data.getStructure().getData().iterator();
+ for (ModelNode child : children.values()) {
+ child.setValueFromMmsDataObj(iterator.next());
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/FcModelNode.java b/src/main/java/com/beanit/iec61850bean/FcModelNode.java
new file mode 100644
index 0000000..e414979
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/FcModelNode.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.iec61850bean.internal.mms.asn1.AlternateAccess;
+import com.beanit.iec61850bean.internal.mms.asn1.AlternateAccessSelection;
+import com.beanit.iec61850bean.internal.mms.asn1.AlternateAccessSelection.SelectAccess;
+import com.beanit.iec61850bean.internal.mms.asn1.AlternateAccessSelection.SelectAccess.Component;
+import com.beanit.iec61850bean.internal.mms.asn1.BasicIdentifier;
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned32;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableDefs;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableSpecification;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public abstract class FcModelNode extends ModelNode {
+
+ Fc fc;
+ private VariableDefs.SEQUENCE variableDef = null;
+ private ServerAssociation selected = null;
+ private TimerTask task = null;
+
+ public Fc getFc() {
+ return fc;
+ }
+
+ boolean select(ServerAssociation association, Timer timer) {
+ if (selected != null) {
+ if (selected != association) {
+ return false;
+ }
+ } else {
+ selected = association;
+ association.selects.add(this);
+ }
+
+ ModelNode sboTimeoutNode =
+ association.serverModel.findModelNode(objectReference, Fc.CF).getChild("sboTimeout");
+
+ if (sboTimeoutNode == null) {
+ return true;
+ }
+
+ long sboTimeout = ((BdaInt32U) sboTimeoutNode).getValue();
+
+ if (sboTimeout == 0) {
+ return true;
+ }
+
+ class SelectResetTask extends TimerTask {
+ ServerAssociation association;
+
+ SelectResetTask(ServerAssociation association) {
+ this.association = association;
+ }
+
+ @Override
+ public void run() {
+ synchronized (association.serverModel) {
+ if (task == this) {
+ task = null;
+ deselectAndRemove(association);
+ }
+ }
+ }
+ }
+
+ if (task != null) {
+ task.cancel();
+ }
+
+ task = new SelectResetTask(association);
+ timer.schedule(task, sboTimeout);
+
+ return true;
+ }
+
+ void deselectAndRemove(ServerAssociation association) {
+ selected = null;
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+ association.selects.remove(this);
+ }
+
+ void deselect() {
+ selected = null;
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+ }
+
+ boolean isSelected() {
+ return selected != null;
+ }
+
+ boolean isSelectedBy(ServerAssociation association) {
+ return selected == association;
+ }
+
+ VariableDefs.SEQUENCE getMmsVariableDef() {
+
+ if (variableDef != null) {
+ return variableDef;
+ }
+
+ AlternateAccess alternateAccess = null;
+
+ StringBuilder preArrayIndexItemId = new StringBuilder(objectReference.get(1));
+ preArrayIndexItemId.append("$");
+ preArrayIndexItemId.append(fc);
+
+ int arrayIndexPosition = objectReference.getArrayIndexPosition();
+ if (arrayIndexPosition != -1) {
+
+ for (int i = 2; i < arrayIndexPosition; i++) {
+ preArrayIndexItemId.append("$");
+ preArrayIndexItemId.append(objectReference.get(i));
+ }
+
+ alternateAccess = new AlternateAccess();
+ List subSeqOfAlternateAccess = alternateAccess.getCHOICE();
+ Unsigned32 indexBerInteger =
+ new Unsigned32(Integer.parseInt(objectReference.get(arrayIndexPosition)));
+
+ if (arrayIndexPosition < (objectReference.size() - 1)) {
+ // this reference points to a sub-node of an array element
+
+ StringBuilder postArrayIndexItemId =
+ new StringBuilder(objectReference.get(arrayIndexPosition + 1));
+
+ for (int i = (arrayIndexPosition + 2); i < objectReference.size(); i++) {
+ postArrayIndexItemId.append("$");
+ postArrayIndexItemId.append(objectReference.get(i));
+ }
+
+ BasicIdentifier subIndexReference =
+ new BasicIdentifier(postArrayIndexItemId.toString().getBytes(UTF_8));
+
+ AlternateAccessSelection.SelectAccess subIndexReferenceSelectAccess =
+ new AlternateAccessSelection.SelectAccess();
+ Component component = new Component();
+ component.setBasic(subIndexReference);
+ subIndexReferenceSelectAccess.setComponent(component);
+
+ AlternateAccessSelection subIndexReferenceAlternateAccessSelection =
+ new AlternateAccessSelection();
+ subIndexReferenceAlternateAccessSelection.setSelectAccess(subIndexReferenceSelectAccess);
+
+ AlternateAccess.CHOICE subIndexReferenceAlternateAccessSubChoice =
+ new AlternateAccess.CHOICE();
+ subIndexReferenceAlternateAccessSubChoice.setUnnamed(
+ subIndexReferenceAlternateAccessSelection);
+
+ AlternateAccess subIndexReferenceAlternateAccess = new AlternateAccess();
+
+ List subIndexReferenceAlternateAccessSubSeqOf =
+ subIndexReferenceAlternateAccess.getCHOICE();
+ subIndexReferenceAlternateAccessSubSeqOf.add(subIndexReferenceAlternateAccessSubChoice);
+
+ // set array index:
+
+ AlternateAccessSelection.SelectAlternateAccess.AccessSelection indexAccessSelectionChoice =
+ new AlternateAccessSelection.SelectAlternateAccess.AccessSelection();
+ indexAccessSelectionChoice.setIndex(indexBerInteger);
+
+ AlternateAccessSelection.SelectAlternateAccess indexAndLowerReferenceSelectAlternateAccess =
+ new AlternateAccessSelection.SelectAlternateAccess();
+ indexAndLowerReferenceSelectAlternateAccess.setAccessSelection(indexAccessSelectionChoice);
+ indexAndLowerReferenceSelectAlternateAccess.setAlternateAccess(
+ subIndexReferenceAlternateAccess);
+
+ AlternateAccessSelection indexAndLowerReferenceAlternateAccessSelection =
+ new AlternateAccessSelection();
+ indexAndLowerReferenceAlternateAccessSelection.setSelectAlternateAccess(
+ indexAndLowerReferenceSelectAlternateAccess);
+
+ AlternateAccess.CHOICE indexAndLowerReferenceAlternateAccessChoice =
+ new AlternateAccess.CHOICE();
+ indexAndLowerReferenceAlternateAccessChoice.setUnnamed(
+ indexAndLowerReferenceAlternateAccessSelection);
+
+ subSeqOfAlternateAccess.add(indexAndLowerReferenceAlternateAccessChoice);
+
+ } else {
+ SelectAccess selectAccess = new SelectAccess();
+ selectAccess.setIndex(indexBerInteger);
+
+ AlternateAccessSelection alternateAccessSelection = new AlternateAccessSelection();
+ alternateAccessSelection.setSelectAccess(selectAccess);
+
+ AlternateAccess.CHOICE alternateAccessChoice = new AlternateAccess.CHOICE();
+ alternateAccessChoice.setUnnamed(alternateAccessSelection);
+
+ subSeqOfAlternateAccess.add(alternateAccessChoice);
+ }
+
+ } else {
+
+ for (int i = 2; i < objectReference.size(); i++) {
+ preArrayIndexItemId.append("$");
+ preArrayIndexItemId.append(objectReference.get(i));
+ }
+ }
+
+ ObjectName.DomainSpecific domainSpecificObjectName = new ObjectName.DomainSpecific();
+ domainSpecificObjectName.setDomainID(new Identifier(objectReference.get(0).getBytes(UTF_8)));
+ domainSpecificObjectName.setItemID(
+ new Identifier(preArrayIndexItemId.toString().getBytes(UTF_8)));
+
+ ObjectName objectName = new ObjectName();
+ objectName.setDomainSpecific(domainSpecificObjectName);
+
+ VariableSpecification varSpec = new VariableSpecification();
+ varSpec.setName(objectName);
+
+ variableDef = new VariableDefs.SEQUENCE();
+ variableDef.setAlternateAccess(alternateAccess);
+ variableDef.setVariableSpecification(varSpec);
+
+ return variableDef;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getReference().toString()).append(" [").append(fc).append("]");
+ for (ModelNode childNode : children.values()) {
+ sb.append("\n");
+ sb.append(childNode.toString());
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/FileInformation.java b/src/main/java/com/beanit/iec61850bean/FileInformation.java
new file mode 100644
index 0000000..750abb0
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/FileInformation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.Calendar;
+
+/** Contains file information received by the GetFileDirectory service */
+public class FileInformation {
+
+ private final String filename;
+
+ private final long fileSize;
+
+ private final Calendar lastModified;
+
+ public FileInformation(String filename, long fileSize, Calendar lastModified) {
+ super();
+ this.filename = filename;
+ this.fileSize = fileSize;
+ this.lastModified = lastModified;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ /**
+ * Get the time stamp of last modification. As it is an optional attribute the return value can be
+ * null
+ *
+ * @return the time stamp of last modification, or null if the time stamp is not present
+ */
+ public Calendar getLastModified() {
+ return lastModified;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/GetFileListener.java b/src/main/java/com/beanit/iec61850bean/GetFileListener.java
new file mode 100644
index 0000000..426febe
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/GetFileListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+/** Callback handler for GetFile service */
+public interface GetFileListener {
+ /**
+ * Is called when a new block of file data is received
+ *
+ * @param fileData block of file data received
+ * @param moreFollows true if more data blocks will follow, false otherwise
+ * @return true to continue the GetFile service, false to cancel
+ */
+ boolean dataReceived(byte[] fileData, boolean moreFollows);
+}
diff --git a/src/main/java/com/beanit/iec61850bean/LogicalDevice.java b/src/main/java/com/beanit/iec61850bean/LogicalDevice.java
new file mode 100644
index 0000000..c67bfe6
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/LogicalDevice.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public final class LogicalDevice extends ModelNode {
+
+ public LogicalDevice(ObjectReference objectReference, List logicalNodes) {
+ children = new LinkedHashMap<>((int) ((logicalNodes.size() / 0.75) + 1));
+ this.objectReference = objectReference;
+ for (LogicalNode logicalNode : logicalNodes) {
+ children.put(logicalNode.getReference().getName(), logicalNode);
+ logicalNode.setParent(this);
+ }
+ }
+
+ @Override
+ public LogicalDevice copy() {
+ List childCopies = new ArrayList<>(children.size());
+ for (ModelNode childNode : children.values()) {
+ childCopies.add((LogicalNode) childNode.copy());
+ }
+ return new LogicalDevice(objectReference, childCopies);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/LogicalNode.java b/src/main/java/com/beanit/iec61850bean/LogicalNode.java
new file mode 100644
index 0000000..c5f9e2a
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/LogicalNode.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class LogicalNode extends ModelNode {
+
+ private final Map> fcDataObjects = new EnumMap<>(Fc.class);
+
+ private final Map urcbs = new HashMap<>();
+ private final Map brcbs = new HashMap<>();
+
+ public LogicalNode(ObjectReference objectReference, List fcDataObjects) {
+ children = new LinkedHashMap<>();
+ for (Fc fc : Fc.values()) {
+ this.fcDataObjects.put(fc, new LinkedHashMap());
+ }
+
+ this.objectReference = objectReference;
+
+ for (FcDataObject fcDataObject : fcDataObjects) {
+ children.put(
+ fcDataObject.getReference().getName() + fcDataObject.fc.toString(), fcDataObject);
+ this.fcDataObjects
+ .get(fcDataObject.getFc())
+ .put(fcDataObject.getReference().getName(), fcDataObject);
+ fcDataObject.setParent(this);
+ if (fcDataObject.getFc() == Fc.RP) {
+ addUrcb((Urcb) fcDataObject, false);
+ } else if (fcDataObject.getFc() == Fc.BR) {
+ addBrcb((Brcb) fcDataObject);
+ }
+ }
+ }
+
+ @Override
+ public LogicalNode copy() {
+
+ List dataObjectsCopy = new ArrayList<>();
+ for (ModelNode obj : children.values()) {
+ dataObjectsCopy.add((FcDataObject) obj.copy());
+ }
+
+ LogicalNode copy = new LogicalNode(objectReference, dataObjectsCopy);
+ return copy;
+ }
+
+ public List getChildren(Fc fc) {
+ Map requestedDataObjectsMap = fcDataObjects.get(fc);
+ if (requestedDataObjectsMap == null) {
+ return null;
+ }
+
+ Collection fcChildren = requestedDataObjectsMap.values();
+ if (fcChildren.size() == 0) {
+ return null;
+ } else {
+ return new ArrayList<>(fcChildren);
+ }
+ }
+
+ void addUrcb(Urcb urcb, boolean addDataSet) {
+ urcbs.put(urcb.getReference().getName(), urcb);
+ if (addDataSet) {
+ String dataSetRef = urcb.getDatSet().getStringValue();
+ if (dataSetRef != null) {
+ urcb.dataSet =
+ ((ServerModel) getParent().getParent()).getDataSet(dataSetRef.replace('$', '.'));
+ }
+ }
+ }
+
+ public Collection getUrcbs() {
+ return urcbs.values();
+ }
+
+ public Urcb getUrcb(String urcbName) {
+ return urcbs.get(urcbName);
+ }
+
+ void addBrcb(Brcb brcb) {
+ brcbs.put(brcb.getReference().getName(), brcb);
+ }
+
+ public Brcb getBrcb(String brcbName) {
+ return brcbs.get(brcbName);
+ }
+
+ public Collection getBrcbs() {
+ return brcbs.values();
+ }
+
+ @Override
+ public ModelNode getChild(String childName, Fc fc) {
+ if (fc != null) {
+ return fcDataObjects.get(fc).get(childName);
+ }
+ for (Map map : fcDataObjects.values()) {
+ FcDataObject fcDataObject = map.get(childName);
+ if (fcDataObject != null) {
+ return fcDataObject;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getReference().toString());
+ for (Map fcChildNodes : fcDataObjects.values()) {
+ for (ModelNode childNode : fcChildNodes.values()) {
+ sb.append("\n");
+ sb.append(childNode.toString());
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ModelNode.java b/src/main/java/com/beanit/iec61850bean/ModelNode.java
new file mode 100644
index 0000000..4aed617
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ModelNode.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription.Structure;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription.Structure.Components;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeSpecification;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public abstract class ModelNode implements Iterable {
+
+ protected ObjectReference objectReference;
+ protected Map children;
+ ModelNode parent;
+
+ /**
+ * Returns a copy of model node with all of its children. Creates new BasicDataAttribute values
+ * but reuses ObjectReferences, FunctionalConstraints.
+ *
+ * @return a copy of model node with all of its children.
+ */
+ public abstract ModelNode copy();
+
+ /**
+ * Returns the child node with the given name. Will always return null if called on a logical node
+ * because a logical node need the functional constraint to uniquely identify a child. For logical
+ * nodes use getChild(String name, Fc fc)
instead.
+ *
+ * @param name the name of the requested child node
+ * @return the child node with the given name.
+ */
+ public ModelNode getChild(String name) {
+ return getChild(name, null);
+ }
+
+ /**
+ * Returns the child node with the given name and functional constraint. The fc is ignored if this
+ * function is called on any model node other than logical node.
+ *
+ * @param name the name of the requested child node
+ * @param fc the functional constraint of the requested child node
+ * @return the child node with the given name and functional constrain
+ */
+ public ModelNode getChild(String name, Fc fc) {
+ return children.get(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection getChildren() {
+ if (children == null) {
+ return null;
+ }
+ return children.values();
+ }
+
+ protected Iterator> getIterators() {
+ List> iterators = new ArrayList<>();
+ if (children != null) {
+ iterators.add(children.values().iterator());
+ }
+ return iterators.iterator();
+ }
+
+ /**
+ * Returns the reference of the model node.
+ *
+ * @return the reference of the model node.
+ */
+ public ObjectReference getReference() {
+ return objectReference;
+ }
+
+ /**
+ * Returns the name of the model node.
+ *
+ * @return the name of the model node.
+ */
+ public String getName() {
+ return objectReference.getName();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return children.values().iterator();
+ }
+
+ /**
+ * Returns a list of all leaf nodes (basic data attributes) contained in the subtree of this model
+ * node.
+ *
+ * @return a list of all leaf nodes (basic data attributes) contained in the subtree of this model
+ * node.
+ */
+ public List getBasicDataAttributes() {
+ List subBasicDataAttributes = new ArrayList<>();
+ for (ModelNode child : children.values()) {
+ subBasicDataAttributes.addAll(child.getBasicDataAttributes());
+ }
+ return subBasicDataAttributes;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getReference().toString());
+ for (ModelNode childNode : children.values()) {
+ sb.append("\n");
+ sb.append(childNode.toString());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the parent node of this node.
+ *
+ * @return the parent node of this node.
+ */
+ public ModelNode getParent() {
+ return parent;
+ }
+
+ void setParent(ModelNode parent) {
+ this.parent = parent;
+ }
+
+ Data getMmsDataObj() {
+ return null;
+ }
+
+ void setValueFromMmsDataObj(Data data) throws ServiceError {}
+
+ TypeDescription getMmsTypeSpec() {
+
+ Components componentsSequenceType = new Components();
+
+ List structComponents =
+ componentsSequenceType.getSEQUENCE();
+ for (ModelNode child : children.values()) {
+ TypeSpecification typeSpecification = new TypeSpecification();
+ typeSpecification.setTypeDescription(child.getMmsTypeSpec());
+
+ TypeDescription.Structure.Components.SEQUENCE component =
+ new TypeDescription.Structure.Components.SEQUENCE();
+ component.setComponentName(new Identifier(child.getName().getBytes(UTF_8)));
+ component.setComponentType(typeSpecification);
+
+ structComponents.add(component);
+ }
+
+ Structure structure = new Structure();
+ structure.setComponents(componentsSequenceType);
+
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setStructure(structure);
+
+ return typeDescription;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ObjectReference.java b/src/main/java/com/beanit/iec61850bean/ObjectReference.java
new file mode 100644
index 0000000..a2a0742
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ObjectReference.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/** ObjectReference syntax: LDName/LNName.DOName[.Name[. ...]] */
+public final class ObjectReference implements Iterable {
+
+ private final String objectReference;
+ private List nodeNames = null;
+
+ // if the ObjectReference contains an array index this variable will save
+ // its position in the nodeNames List
+ private int arrayIndexPosition = -1;
+
+ public ObjectReference(String objectReference) {
+ if (objectReference == null || objectReference.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+ this.objectReference = objectReference;
+ }
+
+ /**
+ * Returns name part of the reference.
+ *
+ * @return the name
+ */
+ public String getName() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return nodeNames.get(nodeNames.size() - 1);
+ }
+
+ public String getLdName() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+
+ return nodeNames.get(0);
+ }
+
+ @Override
+ public String toString() {
+ return objectReference;
+ }
+
+ public boolean isLogicalDeviceRef() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return (nodeNames.size() == 1);
+ }
+
+ public boolean isLogicalNodeRef() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return (nodeNames.size() == 2);
+ }
+
+ public boolean isDataRef() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return (nodeNames.size() > 2);
+ }
+
+ int getArrayIndexPosition() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return arrayIndexPosition;
+ }
+
+ @Override
+ public Iterator iterator() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return nodeNames.iterator();
+ }
+
+ public String get(int i) {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return nodeNames.get(i);
+ }
+
+ public int size() {
+ if (nodeNames == null) {
+ parseForNameList();
+ }
+ return nodeNames.size();
+ }
+
+ private void parseForNameList() {
+
+ nodeNames = new ArrayList<>();
+
+ int lastDelim = -1;
+ int nextDelim = objectReference.indexOf('/');
+ if (nextDelim == -1) {
+ nodeNames.add(objectReference.substring(lastDelim + 1));
+ return;
+ }
+
+ nodeNames.add(objectReference.substring(lastDelim + 1, nextDelim));
+
+ int dotIndex = -1;
+ int openingbracketIndex = -1;
+ int closingbracketIndex = -1;
+ while (true) {
+ lastDelim = nextDelim;
+ if (dotIndex == -1) {
+ dotIndex = objectReference.indexOf('.', lastDelim + 1);
+ if (dotIndex == -1) {
+ dotIndex = objectReference.length();
+ }
+ }
+ if (openingbracketIndex == -1) {
+ openingbracketIndex = objectReference.indexOf('(', lastDelim + 1);
+ if (openingbracketIndex == -1) {
+ openingbracketIndex = objectReference.length();
+ }
+ }
+ if (closingbracketIndex == -1) {
+ closingbracketIndex = objectReference.indexOf(')', lastDelim + 1);
+ if (closingbracketIndex == -1) {
+ closingbracketIndex = objectReference.length();
+ }
+ }
+
+ if (dotIndex == openingbracketIndex && dotIndex == closingbracketIndex) {
+ nodeNames.add(objectReference.substring(lastDelim + 1));
+ return;
+ }
+
+ if (dotIndex < openingbracketIndex && dotIndex < closingbracketIndex) {
+ nextDelim = dotIndex;
+ dotIndex = -1;
+ } else if (openingbracketIndex < dotIndex && openingbracketIndex < closingbracketIndex) {
+ nextDelim = openingbracketIndex;
+ openingbracketIndex = -1;
+ arrayIndexPosition = nodeNames.size() + 1;
+ } else if (closingbracketIndex < dotIndex && closingbracketIndex < openingbracketIndex) {
+ if (closingbracketIndex == (objectReference.length() - 1)) {
+ nodeNames.add(objectReference.substring(lastDelim + 1, closingbracketIndex));
+ return;
+ }
+ nextDelim = closingbracketIndex + 1;
+ closingbracketIndex = -1;
+ dotIndex = -1;
+ nodeNames.add(objectReference.substring(lastDelim + 1, nextDelim - 1));
+ continue;
+ }
+ nodeNames.add(objectReference.substring(lastDelim + 1, nextDelim));
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/Rcb.java b/src/main/java/com/beanit/iec61850bean/Rcb.java
new file mode 100644
index 0000000..fc3db99
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/Rcb.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.List;
+
+public abstract class Rcb extends FcDataObject {
+
+ DataSet dataSet;
+
+ protected Rcb(ObjectReference objectReference, Fc fc, List children) {
+ super(objectReference, fc, children);
+ }
+
+ /**
+ * Gets the RptID (Report ID). The RptID will be included in every report sent by the server. If
+ * it is equal to NULL, then the RptID sent will be equal to the reference of the RCB.
+ *
+ * @return the report ID
+ */
+ public BdaVisibleString getRptId() {
+ return (BdaVisibleString) children.get("RptID");
+ }
+
+ /**
+ * Gets the boolean value which states whether reporting is enabled.
+ *
+ * @return BdaBoolean that contains true as value if reporting is enabled.
+ */
+ public BdaBoolean getRptEna() {
+ return (BdaBoolean) children.get("RptEna");
+ }
+
+ /**
+ * Gets the object reference of the DataSet that is to be monitored for reporting events.
+ *
+ * @return the object reference of the DataSet
+ */
+ public BdaVisibleString getDatSet() {
+ return (BdaVisibleString) children.get("DatSet");
+ }
+
+ /**
+ * Configuration revision The attribute ConfRev shall represent a count of the number of times
+ * that the configuration of the DATA-SET referenced by DatSet has been changed. Changes that
+ * shall be counted are:
+ *
+ *
+ * - any deletion of a member of the DATA-SET;
+ *
- the reordering of members of the DATA-SET; and
+ *
- Successful SetBRCBValues of the DatSet attribute where the DatSet attribute value
+ * changes.
+ *
+ *
+ * The counter shall be incremented when the configuration changes. At configuration time, the
+ * configuration tool will be responsible for incrementing/maintaining the ConfRev value. When
+ * configuration changes occur due to SetBRCBValues, the IED shall be responsible for incrementing
+ * the value of ConfRev.
+ *
+ * @return the configuration revision
+ */
+ public BdaInt32U getConfRev() {
+ return (BdaInt32U) children.get("ConfRev");
+ }
+
+ /**
+ * Gets the optional fields parameter which specifies which optional fields should be included in
+ * the reports sent by this RCB.
+ *
+ * @return the optional fields parameter
+ */
+ public BdaOptFlds getOptFlds() {
+ return (BdaOptFlds) children.get("OptFlds");
+ }
+
+ /**
+ * Gets the buffer time - The attribute BufTm (see Figure 27) shall specify the time interval in
+ * milliseconds for the buffering of internal notifications caused by data-change (dchg),
+ * quality-change (qchg), data update (dupd) by the BRCB for inclusion into a single report.
+ *
+ * @return the buffer time
+ */
+ public BdaInt32U getBufTm() {
+ return (BdaInt32U) children.get("BufTm");
+ }
+
+ /**
+ * Gets the sequence number - The attribute SqNum shall specify the sequence number for each BRCB
+ * that has report enable set to TRUE. This number is to be incremented by the BRCB for each
+ * report generated and sent. The increment shall occur once the BRCB has formatted the report and
+ * requested for transmission.
+ *
+ * @return the sequence number
+ */
+ public BdaInt8U getSqNum() {
+ return (BdaInt8U) children.get("SqNum");
+ }
+
+ public BdaTriggerConditions getTrgOps() {
+ return (BdaTriggerConditions) children.get("TrgOps");
+ }
+
+ public BdaInt32U getIntgPd() {
+ return (BdaInt32U) children.get("IntgPd");
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/Report.java b/src/main/java/com/beanit/iec61850bean/Report.java
new file mode 100644
index 0000000..80f1c42
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/Report.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.HexString;
+import java.util.List;
+
+public class Report {
+
+ private final String rptId;
+ private final Integer sqNum;
+ private final Integer subSqNum;
+ private final boolean moreSegmentsFollow;
+ private final String dataSetRef;
+ private final Boolean bufOvfl;
+ private final Long confRev;
+ private final BdaEntryTime timeOfEntry;
+ private final BdaOctetString entryId;
+ private final boolean[] inclusionBitString;
+ private final List values;
+ private final List reasonCodes;
+
+ 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.sqNum = sqNum;
+ this.subSqNum = subSqNum;
+ this.moreSegmentsFollow = moreSegmentsFollow;
+ this.dataSetRef = dataSetRef;
+ this.bufOvfl = bufOvfl;
+ this.confRev = confRev;
+ this.timeOfEntry = timeOfEntry;
+ this.entryId = entryId;
+ this.inclusionBitString = inclusionBitString;
+ this.values = values;
+ this.reasonCodes = reasonCodes;
+ }
+
+ public String getRptId() {
+ return rptId;
+ }
+
+ /**
+ * Sequence numberThe parameter MoreSegmentsFollow indicates that more report segments with the
+ * same sequence number follow, counted up for every {@code Report} instance generated
+ *
+ * @return the sequence number
+ */
+ public Integer getSqNum() {
+ return sqNum;
+ }
+
+ /**
+ * For the case of long reports that do not fit into one message, a single report shall be divided
+ * into subreports. Each segment – of one report – shall be numbered with the same sequence number
+ * and a unique SubSqNum.
+ *
+ * @return the subsequence number
+ */
+ public Integer getSubSqNum() {
+ return subSqNum;
+ }
+
+ /**
+ * The parameter MoreSegmentsFollow indicates that more report segments with the same sequence
+ * number follow
+ *
+ * @return true if more segments follow
+ */
+ public boolean isMoreSegmentsFollow() {
+ return moreSegmentsFollow;
+ }
+
+ public String getDataSetRef() {
+ return dataSetRef;
+ }
+
+ /**
+ * The parameter BufOvfl shall indicate to the client that entries within the buffer may have been
+ * lost. The detection of possible loss of information occurs when a client requests a
+ * resynchronization to a non-existent entry or to the first entry in the queue.
+ *
+ * @return true if buffer overflow is true
+ */
+ public Boolean getBufOvfl() {
+ return bufOvfl;
+ }
+
+ public Long getConfRev() {
+ return confRev;
+ }
+
+ /**
+ * The parameter TimeOfEntry shall specify the time when the EntryID was created
+ *
+ * @return the time of entry
+ */
+ public BdaEntryTime getTimeOfEntry() {
+ return timeOfEntry;
+ }
+
+ public BdaOctetString getEntryId() {
+ return entryId;
+ }
+
+ /**
+ * Indicator of data set members included in the report
+ *
+ * @return the inclusion bit string as a byte array
+ */
+ public boolean[] getInclusionBitString() {
+ return inclusionBitString;
+ }
+
+ /**
+ * Gets the reasons for inclusion
+ *
+ * @return the reasons for inclusion
+ */
+ public List getReasonCodes() {
+ return reasonCodes;
+ }
+
+ 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(HexString.fromBytes(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));
+ }
+ index++;
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ReportEntryData.java b/src/main/java/com/beanit/iec61850bean/ReportEntryData.java
new file mode 100644
index 0000000..a2678d1
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ReportEntryData.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public class ReportEntryData {
+
+ /** Not specified in IEC61850 but useful for data persistence */
+ private long id;
+ /** Reference to to {@link DataSet}-member */
+ private String dataRef;
+ /** Attribute value to be reported */
+ private ModelNode value;
+ /** Trigger that caused the data to be put into the report */
+ // private TriggerConditions reasonCode;
+ private ReasonCode reasonCode;
+ /** Backreference to report */
+ private Report report;
+
+ public String getDataRef() {
+ return dataRef;
+ }
+
+ public void setDataRef(String dataRef) {
+ this.dataRef = dataRef;
+ }
+
+ public ModelNode getValue() {
+ return value;
+ }
+
+ public void setValue(ModelNode value) {
+ this.value = value;
+ }
+
+ public ReasonCode getReasonCode() {
+ return reasonCode;
+ }
+
+ public void setReasonCode(ReasonCode reasonCode) {
+ this.reasonCode = reasonCode;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Report getReport() {
+ return report;
+ }
+
+ public void setReport(Report report) {
+ this.report = report;
+ }
+
+ public enum ReasonCode {
+ DCHG,
+ QCHG,
+ DUPD,
+ INTEGRITY,
+ GI,
+ APPTRIGGER
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/SclParseException.java b/src/main/java/com/beanit/iec61850bean/SclParseException.java
new file mode 100644
index 0000000..656d4c1
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/SclParseException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class SclParseException extends Exception {
+
+ private static final long serialVersionUID = 8499804369026418082L;
+
+ public SclParseException(String string) {
+ super(string);
+ }
+
+ public SclParseException(Exception e) {
+ super(e);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/SclParser.java b/src/main/java/com/beanit/iec61850bean/SclParser.java
new file mode 100644
index 0000000..adc5a10
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/SclParser.java
@@ -0,0 +1,1110 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.iec61850bean.internal.scl.AbstractDataAttribute;
+import com.beanit.iec61850bean.internal.scl.Bda;
+import com.beanit.iec61850bean.internal.scl.Da;
+import com.beanit.iec61850bean.internal.scl.DaType;
+import com.beanit.iec61850bean.internal.scl.Do;
+import com.beanit.iec61850bean.internal.scl.DoType;
+import com.beanit.iec61850bean.internal.scl.EnumType;
+import com.beanit.iec61850bean.internal.scl.EnumVal;
+import com.beanit.iec61850bean.internal.scl.LnSubDef;
+import com.beanit.iec61850bean.internal.scl.LnType;
+import com.beanit.iec61850bean.internal.scl.Sdo;
+import com.beanit.iec61850bean.internal.scl.TypeDefinitions;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class SclParser {
+
+ private final Map dataSetsMap = new HashMap<>();
+ private final List dataSetDefs = new ArrayList<>();
+ private TypeDefinitions typeDefinitions;
+ private Document doc;
+ private String iedName;
+ private List serverModels = new ArrayList<>();
+ private boolean useResvTmsAttributes = false;
+
+ private SclParser() {}
+
+ public static List parse(InputStream is) throws SclParseException {
+ SclParser sclParser = new SclParser();
+ sclParser.parseStream(is);
+ return sclParser.serverModels;
+ }
+
+ public static List parse(String sclFilePath) throws SclParseException {
+ try {
+ return parse(new FileInputStream(sclFilePath));
+ } catch (FileNotFoundException e) {
+ throw new SclParseException(e);
+ }
+ }
+
+ private void parseStream(InputStream icdFileStream) throws SclParseException {
+
+ typeDefinitions = new TypeDefinitions();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(true);
+
+ try {
+ doc = factory.newDocumentBuilder().parse(icdFileStream);
+ } catch (Exception e) {
+ throw new SclParseException(e);
+ }
+
+ Node rootNode = doc.getDocumentElement();
+
+ if (!"SCL".equals(rootNode.getNodeName())) {
+ throw new SclParseException("Root node in SCL file is not of type \"SCL\"");
+ }
+
+ readTypeDefinitions();
+
+ NodeList iedList = doc.getElementsByTagName("IED");
+ if (iedList.getLength() == 0) {
+ throw new SclParseException("No IED section found!");
+ }
+
+ for (int z = 0; z < iedList.getLength(); z++) {
+ Node iedNode = iedList.item(z);
+
+ useResvTmsAttributes = false;
+
+ Node nameAttribute = iedNode.getAttributes().getNamedItem("name");
+
+ iedName = nameAttribute.getNodeValue();
+ if ((iedName == null) || (iedName.length() == 0)) {
+ throw new SclParseException("IED must have a name!");
+ }
+
+ NodeList iedElements = iedNode.getChildNodes();
+
+ for (int i = 0; i < iedElements.getLength(); i++) {
+ Node element = iedElements.item(i);
+ String nodeName = element.getNodeName();
+ if ("AccessPoint".equals(nodeName)) {
+ ServerSap serverSap = createAccessPoint(element);
+ if (serverSap != null) {
+ serverModels.add(serverSap.serverModel);
+ }
+ } else if ("Services".equals(nodeName)) {
+ NodeList servicesElements = element.getChildNodes();
+ for (int j = 0; j < servicesElements.getLength(); j++) {
+ if ("ReportSettings".equals(servicesElements.item(j).getNodeName())) {
+ Node resvTmsAttribute =
+ servicesElements.item(j).getAttributes().getNamedItem("resvTms");
+ if (resvTmsAttribute != null) {
+ useResvTmsAttributes = resvTmsAttribute.getNodeValue().equalsIgnoreCase("true");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void readTypeDefinitions() throws SclParseException {
+
+ NodeList dttSections = doc.getElementsByTagName("DataTypeTemplates");
+
+ if (dttSections.getLength() != 1) {
+ throw new SclParseException("Only one DataTypeSection allowed");
+ }
+
+ Node dtt = dttSections.item(0);
+
+ NodeList dataTypes = dtt.getChildNodes();
+
+ for (int i = 0; i < dataTypes.getLength(); i++) {
+ Node element = dataTypes.item(i);
+
+ String nodeName = element.getNodeName();
+
+ if (nodeName.equals("LNodeType")) {
+ typeDefinitions.putLNodeType(new LnType(element));
+ } else if (nodeName.equals("DOType")) {
+ typeDefinitions.putDOType(new DoType(element));
+ } else if (nodeName.equals("DAType")) {
+ typeDefinitions.putDAType(new DaType(element));
+ } else if (nodeName.equals("EnumType")) {
+ typeDefinitions.putEnumType(new EnumType(element));
+ }
+ }
+ }
+
+ private ServerSap createAccessPoint(Node iedServer) throws SclParseException {
+ ServerSap serverSap = null;
+
+ NodeList elements = iedServer.getChildNodes();
+
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node element = elements.item(i);
+
+ if (element.getNodeName().equals("Server")) {
+
+ ServerModel server = createServerModel(element);
+
+ Node namedItem = iedServer.getAttributes().getNamedItem("name");
+ if (namedItem == null) {
+ throw new SclParseException("AccessPoint has no name attribute!");
+ }
+ // TODO save this name?
+ serverSap = new ServerSap(102, 0, null, server, null);
+
+ break;
+ }
+ }
+
+ return serverSap;
+ }
+
+ private ServerModel createServerModel(Node serverXMLNode) throws SclParseException {
+
+ NodeList elements = serverXMLNode.getChildNodes();
+ List logicalDevices = new ArrayList<>(elements.getLength());
+
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node element = elements.item(i);
+
+ if (element.getNodeName().equals("LDevice")) {
+ logicalDevices.add(createNewLDevice(element));
+ }
+ }
+
+ ServerModel serverModel = new ServerModel(logicalDevices, null);
+
+ dataSetsMap.clear();
+
+ for (LnSubDef dataSetDef : dataSetDefs) {
+ DataSet dataSet = createDataSet(serverModel, dataSetDef.logicalNode, dataSetDef.defXmlNode);
+ dataSetsMap.put(dataSet.getReferenceStr(), dataSet);
+ }
+
+ serverModel.addDataSets(dataSetsMap.values());
+
+ dataSetDefs.clear();
+
+ return serverModel;
+ }
+
+ private LogicalDevice createNewLDevice(Node ldXmlNode) throws SclParseException {
+
+ String inst = null;
+ String ldName = null;
+
+ NamedNodeMap attributes = ldXmlNode.getAttributes();
+
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node node = attributes.item(i);
+ String nodeName = node.getNodeName();
+
+ if (nodeName.equals("inst")) {
+ inst = node.getNodeValue();
+ } else if (nodeName.equals("ldName")) {
+ ldName = node.getNodeValue();
+ }
+ }
+
+ if (inst == null) {
+ throw new SclParseException("Required attribute \"inst\" in logical device not found!");
+ }
+
+ NodeList elements = ldXmlNode.getChildNodes();
+ List logicalNodes = new ArrayList<>();
+
+ String ref;
+ if ((ldName != null) && (ldName.length() != 0)) {
+ ref = ldName;
+ } else {
+ ref = iedName + inst;
+ }
+
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node element = elements.item(i);
+
+ if (element.getNodeName().equals("LN") || element.getNodeName().equals("LN0")) {
+ logicalNodes.add(createNewLogicalNode(element, ref));
+ }
+ }
+
+ LogicalDevice lDevice = new LogicalDevice(new ObjectReference(ref), logicalNodes);
+
+ return lDevice;
+ }
+
+ private LogicalNode createNewLogicalNode(Node lnXmlNode, String parentRef)
+ throws SclParseException {
+
+ // attributes not needed: desc
+
+ String inst = null;
+ String lnClass = null;
+ String lnType = null;
+ String prefix = "";
+
+ NamedNodeMap attributes = lnXmlNode.getAttributes();
+
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node node = attributes.item(i);
+ String nodeName = node.getNodeName();
+
+ if (nodeName.equals("inst")) {
+ inst = node.getNodeValue();
+ } else if (nodeName.equals("lnType")) {
+ lnType = node.getNodeValue();
+ } else if (nodeName.equals("lnClass")) {
+ lnClass = node.getNodeValue();
+ } else if (nodeName.equals("prefix")) {
+ prefix = node.getNodeValue();
+ }
+ }
+
+ if (inst == null) {
+ throw new SclParseException("Required attribute \"inst\" not found!");
+ }
+ if (lnType == null) {
+ throw new SclParseException("Required attribute \"lnType\" not found!");
+ }
+ if (lnClass == null) {
+ throw new SclParseException("Required attribute \"lnClass\" not found!");
+ }
+
+ String ref = parentRef + '/' + prefix + lnClass + inst;
+
+ LnType lnTypeDef = typeDefinitions.getLNodeType(lnType);
+
+ List dataObjects = new ArrayList<>();
+
+ if (lnTypeDef == null) {
+ throw new SclParseException("LNType " + lnType + " not defined!");
+ }
+ for (Do dobject : lnTypeDef.dos) {
+
+ // look for DOI node with the name of the DO
+ Node doiNodeFound = null;
+ for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
+ Node childNode = lnXmlNode.getChildNodes().item(i);
+ if ("DOI".equals(childNode.getNodeName())) {
+
+ NamedNodeMap doiAttributes = childNode.getAttributes();
+ Node nameAttribute = doiAttributes.getNamedItem("name");
+ if (nameAttribute != null && nameAttribute.getNodeValue().equals(dobject.getName())) {
+ doiNodeFound = childNode;
+ }
+ }
+ }
+
+ dataObjects.addAll(
+ createFcDataObjects(dobject.getName(), ref, dobject.getType(), doiNodeFound));
+ }
+
+ // look for ReportControl
+ for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
+ Node childNode = lnXmlNode.getChildNodes().item(i);
+ if ("ReportControl".equals(childNode.getNodeName())) {
+ dataObjects.addAll(createReportControlBlocks(childNode, ref));
+ }
+ }
+
+ LogicalNode lNode = new LogicalNode(new ObjectReference(ref), dataObjects);
+
+ // look for DataSet definitions
+ for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
+ Node childNode = lnXmlNode.getChildNodes().item(i);
+ if ("DataSet".equals(childNode.getNodeName())) {
+ dataSetDefs.add(new LnSubDef(childNode, lNode));
+ }
+ }
+ return lNode;
+ }
+
+ private DataSet createDataSet(ServerModel serverModel, LogicalNode lNode, Node dsXmlNode)
+ throws SclParseException {
+
+ Node nameAttribute = dsXmlNode.getAttributes().getNamedItem("name");
+ if (nameAttribute == null) {
+ throw new SclParseException("DataSet must have a name");
+ }
+
+ String name = nameAttribute.getNodeValue();
+
+ List dsMembers = new ArrayList<>();
+
+ for (int i = 0; i < dsXmlNode.getChildNodes().getLength(); i++) {
+ Node fcdaXmlNode = dsXmlNode.getChildNodes().item(i);
+ if ("FCDA".equals(fcdaXmlNode.getNodeName())) {
+
+ // For the definition of FCDA see Table 22 part6 ed2
+
+ String ldInst = null;
+ String prefix = "";
+ String lnClass = null;
+ String lnInst = "";
+ String doName = "";
+ String daName = "";
+ Fc fc = null;
+
+ NamedNodeMap attributes = fcdaXmlNode.getAttributes();
+
+ for (int j = 0; j < attributes.getLength(); j++) {
+ Node node = attributes.item(j);
+ String nodeName = node.getNodeName();
+
+ if (nodeName.equals("ldInst")) {
+ ldInst = node.getNodeValue();
+ } else if (nodeName.equals("lnInst")) {
+ lnInst = node.getNodeValue();
+ } else if (nodeName.equals("lnClass")) {
+ lnClass = node.getNodeValue();
+ } else if (nodeName.equals("prefix")) {
+ prefix = node.getNodeValue();
+ } else if (nodeName.equals("doName")) {
+ doName = node.getNodeValue();
+ } else if (nodeName.equals("daName")) {
+ if (!node.getNodeValue().isEmpty()) {
+ daName = "." + node.getNodeValue();
+ }
+ } else if (nodeName.equals("fc")) {
+ fc = Fc.fromString(node.getNodeValue());
+ if (fc == null) {
+ throw new SclParseException("FCDA contains invalid FC: " + node.getNodeValue());
+ }
+ }
+ }
+
+ if (ldInst == null) {
+ throw new SclParseException(
+ "Required attribute \"ldInst\" not found in FCDA: " + nameAttribute + "!");
+ }
+
+ if (lnClass == null) {
+ throw new SclParseException("Required attribute \"lnClass\" not found in FCDA!");
+ }
+ if (fc == null) {
+ throw new SclParseException("Required attribute \"fc\" not found in FCDA!");
+ }
+ if (!doName.isEmpty()) {
+
+ String objectReference =
+ iedName + ldInst + "/" + prefix + lnClass + lnInst + "." + doName + daName;
+
+ ModelNode fcdaNode = serverModel.findModelNode(objectReference, fc);
+
+ if (fcdaNode == null) {
+ throw new SclParseException(
+ "Specified FCDA: "
+ + objectReference
+ + " in DataSet: "
+ + nameAttribute
+ + " not found in Model.");
+ }
+ dsMembers.add((FcModelNode) fcdaNode);
+ } else {
+ String objectReference = iedName + ldInst + "/" + prefix + lnClass + lnInst;
+ ModelNode logicalNode = serverModel.findModelNode(objectReference, null);
+ if (logicalNode == null) {
+ throw new SclParseException(
+ "Specified FCDA: "
+ + objectReference
+ + " in DataSet: "
+ + nameAttribute
+ + " not found in Model.");
+ }
+ List fcDataObjects = ((LogicalNode) logicalNode).getChildren(fc);
+ for (FcDataObject dataObj : fcDataObjects) {
+ dsMembers.add(dataObj);
+ }
+ }
+ }
+ }
+
+ DataSet dataSet = new DataSet(lNode.getReference().toString() + '.' + name, dsMembers, false);
+ return dataSet;
+ }
+
+ private List createReportControlBlocks(Node xmlNode, String parentRef)
+ throws SclParseException {
+
+ Fc fc = Fc.RP;
+ NamedNodeMap rcbNodeAttributes = xmlNode.getAttributes();
+ Node attribute = rcbNodeAttributes.getNamedItem("buffered");
+ if (attribute != null && "true".equalsIgnoreCase(attribute.getNodeValue())) {
+ fc = Fc.BR;
+ }
+
+ Node nameAttribute = rcbNodeAttributes.getNamedItem("name");
+ if (nameAttribute == null) {
+ throw new SclParseException("Report Control Block has no name attribute.");
+ }
+
+ int maxInstances = 1;
+ for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
+ Node childNode = xmlNode.getChildNodes().item(i);
+
+ if ("RptEnabled".equals(childNode.getNodeName())) {
+ Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
+ if (rptEnabledMaxAttr != null) {
+ maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
+ if (maxInstances < 1 || maxInstances > 99) {
+ throw new SclParseException(
+ "Report Control Block max instances should be between 1 and 99 but is: "
+ + maxInstances);
+ }
+ }
+ }
+ }
+
+ List rcbInstances = new ArrayList<>(maxInstances);
+
+ for (int z = 1; z <= maxInstances; z++) {
+
+ ObjectReference reportObjRef;
+
+ if (maxInstances == 1) {
+
+ reportObjRef = new ObjectReference(parentRef + "." + nameAttribute.getNodeValue());
+ } else {
+ reportObjRef =
+ new ObjectReference(
+ parentRef + "." + nameAttribute.getNodeValue() + String.format("%02d", z));
+ }
+
+ BdaTriggerConditions trigOps =
+ new BdaTriggerConditions(new ObjectReference(reportObjRef + ".TrgOps"), fc);
+ BdaOptFlds optFields = new BdaOptFlds(new ObjectReference(reportObjRef + ".OptFlds"), fc);
+ for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
+ Node childNode = xmlNode.getChildNodes().item(i);
+ if (childNode.getNodeName().equals("TrgOps")) {
+
+ NamedNodeMap attributes = childNode.getAttributes();
+
+ if (attributes != null) {
+ for (int j = 0; j < attributes.getLength(); j++) {
+ Node node = attributes.item(j);
+ String nodeName = node.getNodeName();
+
+ if ("dchg".equals(nodeName)) {
+ trigOps.setDataChange(node.getNodeValue().equalsIgnoreCase("true"));
+ } else if ("qchg".equals(nodeName)) {
+ trigOps.setQualityChange(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if ("dupd".equals(nodeName)) {
+ trigOps.setDataUpdate(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if ("period".equals(nodeName)) {
+ trigOps.setIntegrity(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if ("gi".equals(nodeName)) {
+ trigOps.setGeneralInterrogation(node.getNodeValue().equalsIgnoreCase("true"));
+ }
+ }
+ }
+ } else if ("OptFields".equals(childNode.getNodeName())) {
+
+ NamedNodeMap attributes = childNode.getAttributes();
+
+ if (attributes != null) {
+ for (int j = 0; j < attributes.getLength(); j++) {
+
+ Node node = attributes.item(j);
+ String nodeName = node.getNodeName();
+
+ if ("seqNum".equals(nodeName)) {
+ optFields.setSequenceNumber(node.getNodeValue().equalsIgnoreCase("true"));
+ } else if ("timeStamp".equals(nodeName)) {
+ optFields.setReportTimestamp(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if ("reasonCode".equals(nodeName)) {
+ optFields.setReasonForInclusion(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if ("dataSet".equals(nodeName)) {
+ optFields.setDataSetName(node.getNodeValue().equalsIgnoreCase("true"));
+
+ }
+ // not supported for now
+ // else if (nodeName.equals("dataRef")) {
+ // optFields.setDataReference(node.getNodeValue().equals("true"));
+ //
+ // }
+ else if (nodeName.equals("bufOvfl")) {
+ optFields.setBufferOverflow(node.getNodeValue().equalsIgnoreCase("true"));
+
+ } else if (nodeName.equals("entryID")) {
+ optFields.setEntryId(node.getNodeValue().equalsIgnoreCase("true"));
+ }
+ // not supported for now:
+ // else if (nodeName.equals("configRef")) {
+ // optFields.setConfigRevision(node.getNodeValue().equals("true"));
+ // }
+ }
+ }
+ } else if ("RptEnabled".equals(childNode.getNodeName())) {
+ Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
+ if (rptEnabledMaxAttr != null) {
+ maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
+ if (maxInstances < 1 || maxInstances > 99) {
+ throw new SclParseException(
+ "Report Control Block max instances should be between 1 and 99 but is: "
+ + maxInstances);
+ }
+ }
+ }
+ }
+
+ if (fc == Fc.RP) {
+ optFields.setEntryId(false);
+ optFields.setBufferOverflow(false);
+ }
+
+ List children = new ArrayList<>();
+
+ BdaVisibleString rptId =
+ new BdaVisibleString(
+ new ObjectReference(reportObjRef.toString() + ".RptID"), fc, "", 129, false, false);
+ attribute = rcbNodeAttributes.getNamedItem("rptID");
+ if (attribute != null) {
+ rptId.setValue(attribute.getNodeValue().getBytes(UTF_8));
+ } else {
+ rptId.setValue(reportObjRef.toString());
+ }
+
+ children.add(rptId);
+
+ children.add(
+ new BdaBoolean(
+ new ObjectReference(reportObjRef.toString() + ".RptEna"), fc, "", false, false));
+
+ if (fc == Fc.RP) {
+ children.add(
+ new BdaBoolean(
+ new ObjectReference(reportObjRef.toString() + ".Resv"), fc, "", false, false));
+ }
+
+ BdaVisibleString datSet =
+ new BdaVisibleString(
+ new ObjectReference(reportObjRef.toString() + ".DatSet"), fc, "", 129, false, false);
+
+ attribute = xmlNode.getAttributes().getNamedItem("datSet");
+ if (attribute != null) {
+ String nodeValue = attribute.getNodeValue();
+ String dataSetName = parentRef + "$" + nodeValue;
+ datSet.setValue(dataSetName.getBytes(UTF_8));
+ }
+ children.add(datSet);
+
+ BdaInt32U confRef =
+ new BdaInt32U(
+ new ObjectReference(reportObjRef.toString() + ".ConfRev"), fc, "", false, false);
+ attribute = xmlNode.getAttributes().getNamedItem("confRev");
+ if (attribute == null) {
+ throw new SclParseException(
+ "Report Control Block does not contain mandatory attribute confRev");
+ }
+ confRef.setValue(Long.parseLong(attribute.getNodeValue()));
+ children.add(confRef);
+
+ children.add(optFields);
+
+ BdaInt32U bufTm =
+ new BdaInt32U(
+ new ObjectReference(reportObjRef.toString() + ".BufTm"), fc, "", false, false);
+ attribute = xmlNode.getAttributes().getNamedItem("bufTime");
+ if (attribute != null) {
+ bufTm.setValue(Long.parseLong(attribute.getNodeValue()));
+ }
+ children.add(bufTm);
+
+ children.add(
+ new BdaInt8U(
+ new ObjectReference(reportObjRef.toString() + ".SqNum"), fc, "", false, false));
+
+ children.add(trigOps);
+
+ BdaInt32U intgPd =
+ new BdaInt32U(
+ new ObjectReference(reportObjRef.toString() + ".IntgPd"), fc, "", false, false);
+ attribute = xmlNode.getAttributes().getNamedItem("intgPd");
+ if (attribute != null) {
+ intgPd.setValue(Long.parseLong(attribute.getNodeValue()));
+ }
+ children.add(intgPd);
+
+ children.add(
+ new BdaBoolean(
+ new ObjectReference(reportObjRef.toString() + ".GI"), fc, "", false, false));
+
+ Rcb rcb = null;
+
+ if (fc == Fc.BR) {
+
+ children.add(
+ new BdaBoolean(
+ new ObjectReference(reportObjRef.toString() + ".PurgeBuf"), fc, "", false, false));
+
+ children.add(
+ new BdaOctetString(
+ new ObjectReference(reportObjRef.toString() + ".EntryID"),
+ fc,
+ "",
+ 8,
+ false,
+ false));
+
+ children.add(
+ new BdaEntryTime(
+ new ObjectReference(reportObjRef.toString() + ".TimeOfEntry"),
+ fc,
+ "",
+ false,
+ false));
+
+ if (useResvTmsAttributes) {
+ children.add(
+ new BdaInt16(
+ new ObjectReference(reportObjRef.toString() + ".ResvTms"), fc, "", false, false));
+ }
+
+ children.add(
+ new BdaOctetString(
+ new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64, false, false));
+
+ rcb = new Brcb(reportObjRef, children);
+
+ } else {
+ children.add(
+ new BdaOctetString(
+ new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64, false, false));
+
+ rcb = new Urcb(reportObjRef, children);
+ }
+
+ rcbInstances.add(rcb);
+ }
+
+ return rcbInstances;
+ }
+
+ private List createFcDataObjects(
+ String name, String parentRef, String doTypeID, Node doiNode) throws SclParseException {
+
+ DoType doType = typeDefinitions.getDOType(doTypeID);
+
+ if (doType == null) {
+ throw new SclParseException("DO type " + doTypeID + " not defined!");
+ }
+
+ String ref = parentRef + '.' + name;
+
+ List childNodes = new ArrayList<>();
+
+ for (Da dattr : doType.das) {
+
+ // look for DAI node with the name of the DA
+ Node iNodeFound = findINode(doiNode, dattr.getName());
+
+ if (dattr.getCount() >= 1) {
+ childNodes.add(createArrayOfDataAttributes(ref + '.' + dattr.getName(), dattr, iNodeFound));
+ } else {
+ childNodes.add(
+ createDataAttribute(
+ ref + '.' + dattr.getName(),
+ dattr.getFc(),
+ dattr,
+ iNodeFound,
+ false,
+ false,
+ false));
+ }
+ }
+
+ for (Sdo sdo : doType.sdos) {
+
+ // parsing Arrays of SubDataObjects is ignored for now because no SCL file was found to test
+ // against. The
+ // only DO that contains an Array of SDOs is Harmonic Value (HMV). The Kalkitech SCL Manager
+ // handles the
+ // array of SDOs in HMV as an array of DAs.
+
+ Node iNodeFound = findINode(doiNode, sdo.getName());
+
+ childNodes.addAll(createFcDataObjects(sdo.getName(), ref, sdo.getType(), iNodeFound));
+ }
+
+ Map> subFCDataMap = new LinkedHashMap<>();
+
+ for (Fc fc : Fc.values()) {
+ subFCDataMap.put(fc, new ArrayList<>());
+ }
+
+ for (ModelNode childNode : childNodes) {
+ subFCDataMap.get(((FcModelNode) childNode).getFc()).add((FcModelNode) childNode);
+ }
+
+ List fcDataObjects = new ArrayList<>();
+ ObjectReference objectReference = new ObjectReference(ref);
+
+ for (Fc fc : Fc.values()) {
+ if (subFCDataMap.get(fc).size() > 0) {
+ fcDataObjects.add(new FcDataObject(objectReference, fc, subFCDataMap.get(fc)));
+ }
+ }
+
+ return fcDataObjects;
+ }
+
+ private Node findINode(Node iNode, String dattrName) {
+
+ if (iNode == null) {
+ return null;
+ }
+
+ for (int i = 0; i < iNode.getChildNodes().getLength(); i++) {
+ Node childNode = iNode.getChildNodes().item(i);
+ if (childNode.getAttributes() != null) {
+ Node nameAttribute = childNode.getAttributes().getNamedItem("name");
+ if (nameAttribute != null && nameAttribute.getNodeValue().equals(dattrName)) {
+ return childNode;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Array createArrayOfDataAttributes(String ref, Da dataAttribute, Node iXmlNode)
+ throws SclParseException {
+
+ Fc fc = dataAttribute.getFc();
+ int size = dataAttribute.getCount();
+
+ List arrayItems = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ // TODO go down the iXmlNode using the ix attribute?
+ arrayItems.add(
+ createDataAttribute(
+ ref + '(' + i + ')',
+ fc,
+ dataAttribute,
+ iXmlNode,
+ dataAttribute.isDchg(),
+ dataAttribute.isDupd(),
+ dataAttribute.isQchg()));
+ }
+
+ return new Array(new ObjectReference(ref), fc, arrayItems);
+ }
+
+ /** returns a ConstructedDataAttribute or BasicDataAttribute */
+ private FcModelNode createDataAttribute(
+ String ref,
+ Fc fc,
+ AbstractDataAttribute dattr,
+ Node iXmlNode,
+ boolean dchg,
+ boolean dupd,
+ boolean qchg)
+ throws SclParseException {
+
+ if (dattr instanceof Da) {
+ Da dataAttribute = (Da) dattr;
+ dchg = dataAttribute.isDchg();
+ dupd = dataAttribute.isDupd();
+ qchg = dataAttribute.isQchg();
+ }
+
+ String bType = dattr.getbType();
+
+ if (bType.equals("Struct")) {
+ DaType datype = typeDefinitions.getDaType(dattr.getType());
+
+ if (datype == null) {
+ throw new SclParseException("DAType " + dattr.getbType() + " not declared!");
+ }
+
+ List subDataAttributes = new ArrayList<>();
+ for (Bda bda : datype.bdas) {
+
+ Node iNodeFound = findINode(iXmlNode, bda.getName());
+
+ subDataAttributes.add(
+ createDataAttribute(ref + '.' + bda.getName(), fc, bda, iNodeFound, dchg, dupd, qchg));
+ }
+ return new ConstructedDataAttribute(new ObjectReference(ref), fc, subDataAttributes);
+ }
+
+ String val = null;
+ String sAddr = null;
+ if (iXmlNode != null) {
+ NamedNodeMap attributeMap = iXmlNode.getAttributes();
+ Node sAddrAttribute = attributeMap.getNamedItem("sAddr");
+ if (sAddrAttribute != null) {
+ sAddr = sAddrAttribute.getNodeValue();
+ }
+
+ NodeList elements = iXmlNode.getChildNodes();
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node node = elements.item(i);
+ if (node.getNodeName().equals("Val")) {
+ val = node.getTextContent();
+ }
+ }
+ if (val == null) {
+ // insert value from DA element
+ val = dattr.value;
+ }
+ }
+
+ if (bType.equals("BOOLEAN")) {
+ BdaBoolean bda = new BdaBoolean(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ if (val.equalsIgnoreCase("true") || val.equals("1")) {
+ bda.setValue(true);
+ } else if (val.equalsIgnoreCase("false") || val.equals("0")) {
+ bda.setValue(false);
+ } else {
+ throw new SclParseException("invalid boolean configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT8")) {
+ BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Byte.parseByte(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT8 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT16")) {
+ BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Short.parseShort(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT16 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT32")) {
+ BdaInt32 bda = new BdaInt32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Integer.parseInt(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT32 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT64")) {
+ BdaInt64 bda = new BdaInt64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Long.parseLong(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT64 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT128")) {
+ BdaInt128 bda = new BdaInt128(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Long.parseLong(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT128 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT8U")) {
+ BdaInt8U bda = new BdaInt8U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Short.parseShort(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT8U configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT16U")) {
+ BdaInt16U bda = new BdaInt16U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Integer.parseInt(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT16U configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("INT32U")) {
+ BdaInt32U bda = new BdaInt32U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setValue(Long.parseLong(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid INT32U configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("FLOAT32")) {
+ BdaFloat32 bda = new BdaFloat32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setFloat(Float.parseFloat(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid FLOAT32 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.equals("FLOAT64")) {
+ BdaFloat64 bda = new BdaFloat64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ try {
+ bda.setDouble(Double.parseDouble(val));
+ } catch (NumberFormatException e) {
+ throw new SclParseException("invalid FLOAT64 configured value: " + val);
+ }
+ }
+ return bda;
+ } else if (bType.startsWith("VisString")) {
+ BdaVisibleString bda =
+ new BdaVisibleString(
+ new ObjectReference(ref),
+ fc,
+ sAddr,
+ Integer.parseInt(dattr.getbType().substring(9)),
+ dchg,
+ dupd);
+ if (val != null) {
+ bda.setValue(val.getBytes(UTF_8));
+ }
+ return bda;
+ } else if (bType.startsWith("Unicode")) {
+ BdaUnicodeString bda =
+ new BdaUnicodeString(
+ new ObjectReference(ref),
+ fc,
+ sAddr,
+ Integer.parseInt(dattr.getbType().substring(7)),
+ dchg,
+ dupd);
+ if (val != null) {
+ bda.setValue(val.getBytes(UTF_8));
+ }
+ return bda;
+ } else if (bType.startsWith("Octet")) {
+ BdaOctetString bda =
+ new BdaOctetString(
+ new ObjectReference(ref),
+ fc,
+ sAddr,
+ Integer.parseInt(dattr.getbType().substring(5)),
+ dchg,
+ dupd);
+ if (val != null) {
+ // TODO
+ // throw new SclParseException("parsing configured value for octet string is not supported
+ // yet.");
+ }
+ return bda;
+ } else if (bType.equals("Quality")) {
+ return new BdaQuality(new ObjectReference(ref), fc, sAddr, qchg);
+ } else if (bType.equals("Check")) {
+ return new BdaCheck(new ObjectReference(ref));
+ } else if (bType.equals("Dbpos")) {
+ return new BdaDoubleBitPos(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ } else if (bType.equals("Tcmd")) {
+ return new BdaTapCommand(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ } else if (bType.equals("OptFlds")) {
+ return new BdaOptFlds(new ObjectReference(ref), fc);
+ } else if (bType.equals("TrgOps")) {
+ return new BdaTriggerConditions(new ObjectReference(ref), fc);
+ } else if (bType.equals("EntryID")) {
+ return new BdaOctetString(new ObjectReference(ref), fc, sAddr, 8, dchg, dupd);
+ } else if (bType.equals("EntryTime")) {
+ return new BdaEntryTime(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ } else if (bType.equals("PhyComAddr")) {
+ // TODO not correct!
+ return new BdaOctetString(new ObjectReference(ref), fc, sAddr, 6, dchg, dupd);
+ } else if (bType.equals("Timestamp")) {
+ BdaTimestamp bda = new BdaTimestamp(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ // TODO
+ throw new SclParseException("parsing configured value for TIMESTAMP is not supported yet.");
+ }
+ return bda;
+ } else if (bType.equals("Enum")) {
+ String type = dattr.getType();
+ if (type == null) {
+ throw new SclParseException("The exact type of the enumeration is not set.");
+ }
+ EnumType enumType = typeDefinitions.getEnumType(type);
+
+ if (enumType == null) {
+ throw new SclParseException("Definition of enum type: " + type + " not found.");
+ }
+
+ if (enumType.max > 127 || enumType.min < -128) {
+ BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ for (EnumVal enumVal : enumType.getValues()) {
+ if (val.equals(enumVal.getId())) {
+ bda.setValue((short) enumVal.getOrd());
+ return bda;
+ }
+ }
+ throw new SclParseException("unknown enum value: " + val);
+ }
+ return bda;
+ } else {
+ BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
+ if (val != null) {
+ for (EnumVal enumVal : enumType.getValues()) {
+ if (val.equals(enumVal.getId())) {
+ bda.setValue((byte) enumVal.getOrd());
+ return bda;
+ }
+ }
+ throw new SclParseException("unknown enum value: " + val);
+ }
+ return bda;
+ }
+ } else if (bType.equals("ObjRef")) {
+ BdaVisibleString bda =
+ new BdaVisibleString(new ObjectReference(ref), fc, sAddr, 129, dchg, dupd);
+ if (val != null) {
+ bda.setValue(val.getBytes(UTF_8));
+ }
+ return bda;
+ } else {
+ throw new SclParseException("Invalid bType: " + bType);
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServerAssociation.java b/src/main/java/com/beanit/iec61850bean/ServerAssociation.java
new file mode 100644
index 0000000..dde88d3
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServerAssociation.java
@@ -0,0 +1,1651 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.asn1bean.ber.ReverseByteArrayOutputStream;
+import com.beanit.asn1bean.ber.types.BerInteger;
+import com.beanit.asn1bean.ber.types.BerNull;
+import com.beanit.asn1bean.ber.types.string.BerVisibleString;
+import com.beanit.iec61850bean.internal.BerBoolean;
+import com.beanit.iec61850bean.internal.NamedThreadFactory;
+import com.beanit.iec61850bean.internal.mms.asn1.AccessResult;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedErrorPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedRequestPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedResponsePDU;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedServiceRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.ConfirmedServiceResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.DataAccessError;
+import com.beanit.iec61850bean.internal.mms.asn1.DefineNamedVariableListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.DefineNamedVariableListResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.DeleteNamedVariableListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.DeleteNamedVariableListResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNameListResponse.ListOfIdentifier;
+import com.beanit.iec61850bean.internal.mms.asn1.GetNamedVariableListAttributesResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.GetVariableAccessAttributesRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.GetVariableAccessAttributesResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.InitiateRequestPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.InitiateResponsePDU;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer16;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer32;
+import com.beanit.iec61850bean.internal.mms.asn1.Integer8;
+import com.beanit.iec61850bean.internal.mms.asn1.MMSpdu;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName.DomainSpecific;
+import com.beanit.iec61850bean.internal.mms.asn1.ParameterSupportOptions;
+import com.beanit.iec61850bean.internal.mms.asn1.ReadRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.ReadResponse;
+import com.beanit.iec61850bean.internal.mms.asn1.ReadResponse.ListOfAccessResult;
+import com.beanit.iec61850bean.internal.mms.asn1.ServiceError.ErrorClass;
+import com.beanit.iec61850bean.internal.mms.asn1.ServiceSupportOptions;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription.Structure;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeDescription.Structure.Components;
+import com.beanit.iec61850bean.internal.mms.asn1.TypeSpecification;
+import com.beanit.iec61850bean.internal.mms.asn1.Unsigned32;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableAccessSpecification;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableDefs;
+import com.beanit.iec61850bean.internal.mms.asn1.WriteRequest;
+import com.beanit.iec61850bean.internal.mms.asn1.WriteResponse;
+import com.beanit.josistack.AcseAssociation;
+import com.beanit.josistack.ByteBufferInputStream;
+import com.beanit.josistack.DecodingException;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeoutException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class ServerAssociation {
+
+ private static final Logger logger = LoggerFactory.getLogger(ServerAssociation.class);
+
+ private static final WriteResponse.CHOICE writeSuccess = new WriteResponse.CHOICE();
+ private static String[] mmsFcs = {
+ "MX", "ST", "CO", "CF", "DC", "SP", "SG", "RP", "LG", "BR", "GO", "GS", "SV", "SE", "EX", "SR",
+ "OR", "BL"
+ };
+
+ static {
+ writeSuccess.setSuccess(new BerNull());
+ }
+
+ final ServerModel serverModel;
+ private final ServerSap serverSap;
+ private final ReverseByteArrayOutputStream reverseOStream =
+ new ReverseByteArrayOutputStream(500, true);
+ ScheduledExecutorService executor = null;
+ HashMap nonPersistentDataSets = new HashMap<>();
+ List selects = new ArrayList<>();
+ List rsvdURCBs = new ArrayList<>();
+ private AcseAssociation acseAssociation = null;
+ private int negotiatedMaxPduSize;
+ private ByteBuffer pduBuffer;
+ private boolean insertRef;
+ private String continueAfter;
+
+ public ServerAssociation(ServerSap serverSap) {
+ this.serverSap = serverSap;
+ serverModel = serverSap.serverModel;
+ executor =
+ Executors.newScheduledThreadPool(
+ 2, new NamedThreadFactory("iec61850bean-server-connection"));
+ }
+
+ private static void insertMmsRef(ModelNode node, List mmsRefs, String parentRef) {
+ String ref = parentRef + '$' + node.getName();
+ mmsRefs.add(ref);
+ if (!(node instanceof Array)) {
+ for (ModelNode childNode : node) {
+ insertMmsRef(childNode, mmsRefs, ref);
+ }
+ }
+ }
+
+ private static String convertToDataSetReference(ObjectName mmsObjectName) {
+ if (mmsObjectName.getDomainSpecific() != null) {
+ return mmsObjectName.getDomainSpecific().getDomainID().toString()
+ + "/"
+ + mmsObjectName.getDomainSpecific().getItemID().toString().replace('$', '.');
+ } else if (mmsObjectName.getAaSpecific() != null) {
+ // format is "@DataSetName"
+ return mmsObjectName.getAaSpecific().toString();
+ }
+ return null;
+ }
+
+ public void handleNewAssociation(AcseAssociation acseAssociation, ByteBuffer associationRequest) {
+
+ this.acseAssociation = acseAssociation;
+
+ try {
+ associate(acseAssociation, associationRequest);
+ } catch (IOException e) {
+ logger.warn("Error during association build up", e);
+ return;
+ }
+
+ handleConnection();
+ }
+
+ private void associate(AcseAssociation acseAssociation, ByteBuffer associationRequest)
+ throws IOException {
+
+ MMSpdu mmsPdu = new MMSpdu();
+
+ mmsPdu.decode(new ByteBufferInputStream(associationRequest), null);
+
+ MMSpdu initiateResponseMmsPdu = constructAssociationResponsePdu(mmsPdu.getInitiateRequestPDU());
+
+ initiateResponseMmsPdu.encode(reverseOStream);
+
+ acseAssociation.accept(reverseOStream.getByteBuffer());
+ }
+
+ private MMSpdu constructAssociationResponsePdu(InitiateRequestPDU associationRequestMMSpdu) {
+
+ negotiatedMaxPduSize = serverSap.getMaxMmsPduSize();
+
+ if (associationRequestMMSpdu.getLocalDetailCalling() != null) {
+ int proposedMaxMmsPduSize = associationRequestMMSpdu.getLocalDetailCalling().intValue();
+ if (negotiatedMaxPduSize > proposedMaxMmsPduSize
+ && proposedMaxMmsPduSize >= ServerSap.MINIMUM_MMS_PDU_SIZE) {
+ negotiatedMaxPduSize = proposedMaxMmsPduSize;
+ }
+ }
+
+ int negotiatedMaxServOutstandingCalling = serverSap.getProposedMaxServOutstandingCalling();
+ int proposedMaxServOutstandingCalling =
+ associationRequestMMSpdu.getProposedMaxServOutstandingCalling().intValue();
+
+ if (negotiatedMaxServOutstandingCalling > proposedMaxServOutstandingCalling
+ && proposedMaxServOutstandingCalling > 0) {
+ negotiatedMaxServOutstandingCalling = proposedMaxServOutstandingCalling;
+ }
+
+ int negotiatedMaxServOutstandingCalled = serverSap.getProposedMaxServOutstandingCalled();
+ int proposedMaxServOutstandingCalled =
+ associationRequestMMSpdu.getProposedMaxServOutstandingCalled().intValue();
+
+ if (negotiatedMaxServOutstandingCalled > proposedMaxServOutstandingCalled
+ && proposedMaxServOutstandingCalled > 0) {
+ negotiatedMaxServOutstandingCalled = proposedMaxServOutstandingCalled;
+ }
+
+ int negotiatedDataStructureNestingLevel = serverSap.getProposedDataStructureNestingLevel();
+
+ if (associationRequestMMSpdu.getProposedDataStructureNestingLevel() != null) {
+ int proposedDataStructureNestingLevel =
+ associationRequestMMSpdu.getProposedDataStructureNestingLevel().intValue();
+ if (negotiatedDataStructureNestingLevel > proposedDataStructureNestingLevel) {
+ negotiatedDataStructureNestingLevel = proposedDataStructureNestingLevel;
+ }
+ }
+
+ pduBuffer = ByteBuffer.allocate(negotiatedMaxPduSize + 500);
+
+ byte[] negotiatedParameterCbbBitString = serverSap.cbbBitString;
+
+ byte[] servicesSupportedCalledBitString = serverSap.servicesSupportedCalled;
+
+ InitiateResponsePDU.InitResponseDetail initRespDetail =
+ new InitiateResponsePDU.InitResponseDetail();
+ initRespDetail.setNegotiatedVersionNumber(new Integer16(1));
+ initRespDetail.setNegotiatedParameterCBB(
+ new ParameterSupportOptions(
+ negotiatedParameterCbbBitString, negotiatedParameterCbbBitString.length * 8 - 5));
+ initRespDetail.setServicesSupportedCalled(
+ new ServiceSupportOptions(
+ servicesSupportedCalledBitString, servicesSupportedCalledBitString.length * 8 - 3));
+
+ InitiateResponsePDU initRespPdu = new InitiateResponsePDU();
+ initRespPdu.setLocalDetailCalled(new Integer32(negotiatedMaxPduSize));
+ initRespPdu.setNegotiatedMaxServOutstandingCalling(
+ new Integer16(negotiatedMaxServOutstandingCalling));
+ initRespPdu.setNegotiatedMaxServOutstandingCalled(
+ new Integer16(negotiatedMaxServOutstandingCalled));
+ initRespPdu.setNegotiatedDataStructureNestingLevel(
+ new Integer8(negotiatedDataStructureNestingLevel));
+ initRespPdu.setInitResponseDetail(initRespDetail);
+
+ MMSpdu initiateResponseMMSpdu = new MMSpdu();
+ initiateResponseMMSpdu.setInitiateResponsePDU(initRespPdu);
+
+ return initiateResponseMMSpdu;
+ }
+
+ private void handleConnection() {
+
+ while (true) {
+
+ MMSpdu mmsRequestPdu = listenForMmsRequest(acseAssociation);
+ if (mmsRequestPdu == null) {
+ return;
+ }
+
+ ConfirmedRequestPDU confirmedRequestPdu = mmsRequestPdu.getConfirmedRequestPDU();
+ // Do not have to check whether confirmedRequestPdu is null because that was already done by
+ // listenForMmsRequest()
+
+ if (confirmedRequestPdu.getInvokeID() == null) {
+ // cannot respond with ServiceError because no InvokeID was received
+ logger.warn("Got unexpected MMS PDU or no invokeID");
+ continue;
+ }
+ int invokeId = confirmedRequestPdu.getInvokeID().intValue();
+
+ try {
+ if (confirmedRequestPdu.getService() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Got an invalid MMS packet: confirmedServiceRequest empty");
+ }
+
+ ConfirmedServiceRequest confirmedServiceRequest = confirmedRequestPdu.getService();
+
+ ConfirmedServiceResponse confirmedServiceResponse = new ConfirmedServiceResponse();
+
+ if (confirmedServiceRequest.getGetNameList() != null) {
+
+ GetNameListRequest getNameListRequest = confirmedServiceRequest.getGetNameList();
+ GetNameListResponse response = null;
+
+ if (getNameListRequest.getObjectClass().getBasicObjectClass() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Got an invalid MMS packet: ObjectClass was not selected in GetNameList request");
+ }
+
+ long basicObjectClass =
+ getNameListRequest.getObjectClass().getBasicObjectClass().longValue();
+ if (basicObjectClass == 9) {
+ logger.debug("Got a GetServerDirectory (MMS GetNameList[DOMAIN]) request");
+ response = handleGetServerDirectoryRequest();
+ } else if (basicObjectClass == 0) {
+ logger.debug("Got a Get{LD|LN}Directory (MMS GetNameList[NAMED_VARIABLE]) request");
+ response = handleGetDirectoryRequest(getNameListRequest);
+ } else if (basicObjectClass == 2) {
+ logger.debug(
+ "Got a GetLogicalNodeDirectory[DataSet] (MMS GetNameList[NAMED_VARIABLE_LIST]) request");
+ response = handleGetDataSetNamesRequest(getNameListRequest);
+ } else {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT,
+ "Unable to handle Get directory request for basic object class: "
+ + basicObjectClass);
+ }
+ // else if (basicObjectClass == 8) {
+ // logger.debug("Got a GetLogicalNodeDirectory[Log] (MMS GetNameList[JOURNAL]) request");
+ // response =
+ // handleGetNameListJournalRequest(getNameListRequest);
+ // }
+
+ confirmedServiceResponse.setGetNameList(response);
+
+ } else if (confirmedServiceRequest.getGetVariableAccessAttributes() != null) {
+ logger.debug(
+ "Got a GetDataDirectory/GetDataDefinition (MMS GetVariableAccessAttributes) request");
+ GetVariableAccessAttributesResponse response =
+ handleGetVariableAccessAttributesRequest(
+ confirmedServiceRequest.getGetVariableAccessAttributes());
+
+ confirmedServiceResponse.setGetVariableAccessAttributes(response);
+
+ } else if (confirmedServiceRequest.getRead() != null) {
+ // GetDataValues, GetDataSetValues, GetBRCBValues and GetURCBValues map to this
+ ReadResponse response = handleGetDataValuesRequest(confirmedServiceRequest.getRead());
+
+ confirmedServiceResponse.setRead(response);
+ } else if (confirmedServiceRequest.getWrite() != null) {
+ logger.debug("Got a Write request");
+
+ WriteResponse response = handleSetDataValuesRequest(confirmedServiceRequest.getWrite());
+
+ confirmedServiceResponse.setWrite(response);
+
+ }
+ // for Data Sets
+ else if (confirmedServiceRequest.getDefineNamedVariableList() != null) {
+ logger.debug("Got a CreateDataSet request");
+
+ DefineNamedVariableListResponse response =
+ handleCreateDataSetRequest(confirmedServiceRequest.getDefineNamedVariableList());
+
+ confirmedServiceResponse.setDefineNamedVariableList(response);
+ } else if (confirmedServiceRequest.getGetNamedVariableListAttributes() != null) {
+ logger.debug("Got a GetDataSetDirectory request");
+ GetNamedVariableListAttributesResponse response =
+ handleGetDataSetDirectoryRequest(
+ confirmedServiceRequest.getGetNamedVariableListAttributes());
+
+ confirmedServiceResponse.setGetNamedVariableListAttributes(response);
+
+ } else if (confirmedServiceRequest.getDeleteNamedVariableList() != null) {
+ logger.debug("Got a DeleteDataSet request");
+ DeleteNamedVariableListResponse response =
+ handleDeleteDataSetRequest(confirmedServiceRequest.getDeleteNamedVariableList());
+
+ confirmedServiceResponse.setDeleteNamedVariableList(response);
+ } else {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "invalid MMS packet: unknown request type.");
+ }
+
+ ConfirmedResponsePDU confirmedResponsePdu = new ConfirmedResponsePDU();
+ confirmedResponsePdu.setInvokeID(confirmedRequestPdu.getInvokeID());
+ confirmedResponsePdu.setService(confirmedServiceResponse);
+
+ MMSpdu mmsResponsePdu = new MMSpdu();
+ mmsResponsePdu.setConfirmedResponsePDU(confirmedResponsePdu);
+
+ if (!sendAnMmsPdu(mmsResponsePdu)) {
+ return;
+ }
+ } catch (ServiceError e) {
+ logger.warn(e.getMessage());
+ if (!sendAnMmsPdu(createServiceErrorResponse(e, invokeId))) {
+ return;
+ }
+ }
+ }
+ }
+
+ void cleanUpConnection() {
+ synchronized (serverModel) {
+ for (FcModelNode selectedCdo : selects) {
+ selectedCdo.deselect();
+ }
+ for (Urcb rsvdUrcb : rsvdURCBs) {
+ synchronized (rsvdUrcb) {
+ if (rsvdUrcb.enabled) {
+ rsvdUrcb.disable();
+ }
+ rsvdUrcb.reserved = null;
+ rsvdUrcb.getResv().setValue(false);
+ }
+ }
+ }
+ }
+
+ boolean sendAnMmsPdu(MMSpdu mmsResponsePdu) {
+
+ synchronized (reverseOStream) {
+ reverseOStream.reset();
+ try {
+ mmsResponsePdu.encode(reverseOStream);
+ } catch (IOException e1) {
+ logger.error("IOException while encoding MMS PDU. Closing association.", e1);
+ return false;
+ }
+ try {
+ acseAssociation.send(reverseOStream.getByteBuffer());
+ } catch (IOException e) {
+ logger.warn("IOException while sending MMS PDU. Closing association.", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private MMSpdu listenForMmsRequest(AcseAssociation acseAssociation) {
+
+ while (true) {
+ MMSpdu mmsRequestPdu;
+ byte[] buffer;
+ pduBuffer.clear();
+ try {
+ buffer = acseAssociation.receive(pduBuffer);
+ } catch (EOFException e) {
+ logger.debug("Connection was closed by client.");
+ return null;
+ } catch (SocketTimeoutException e) {
+ logger.warn(
+ "Message fragment timeout occured while receiving request. Closing association.", e);
+ return null;
+ } catch (IOException e) {
+ logger.warn(
+ "IOException at lower layers while listening for incoming request. Closing association.",
+ e);
+ return null;
+ } catch (DecodingException e) {
+ logger.error("Error decoding request at OSI layers.", e);
+ continue;
+ } catch (TimeoutException e) {
+ logger.error(
+ "Illegal state: message timeout while receiving request though this timeout should 0 and never be thrown",
+ e);
+ return null;
+ }
+ mmsRequestPdu = new MMSpdu();
+
+ try {
+ mmsRequestPdu.decode(new ByteArrayInputStream(buffer), null);
+ } catch (IOException e) {
+ logger.warn("IOException decoding received MMS request PDU.", e);
+ continue;
+ }
+
+ if (mmsRequestPdu.getConfirmedRequestPDU() == null) {
+ if (mmsRequestPdu.getConcludeRequestPDU() != null) {
+ logger.debug("Got Conclude request, will close connection");
+ return null;
+ } else {
+ logger.warn("Got unexpected MMS PDU, will ignore it");
+ continue;
+ }
+ }
+
+ return mmsRequestPdu;
+ }
+ }
+
+ private MMSpdu createServiceErrorResponse(ServiceError e, int invokeId) {
+
+ ErrorClass errClass = new ErrorClass();
+
+ switch (e.getErrorCode()) {
+ case ServiceError.NO_ERROR:
+ break;
+ case ServiceError.INSTANCE_NOT_AVAILABLE:
+ errClass.setAccess(new BerInteger(e.getErrorCode()));
+ break;
+ case ServiceError.INSTANCE_IN_USE:
+ errClass.setDefinition(new BerInteger(e.getErrorCode()));
+ break;
+ case ServiceError.ACCESS_VIOLATION:
+ errClass.setAccess(new BerInteger(e.getErrorCode()));
+ break;
+ case ServiceError.ACCESS_NOT_ALLOWED_IN_CURRENT_STATE:
+ errClass.setOthers(new BerInteger(e.getErrorCode()));
+ break;
+ case ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT:
+ errClass.setFile(new BerInteger(2));
+ break;
+ case ServiceError.TYPE_CONFLICT:
+ errClass.setFile(new BerInteger(4));
+ break;
+ default:
+ errClass.setOthers(new BerInteger(e.getErrorCode()));
+ }
+ com.beanit.iec61850bean.internal.mms.asn1.ServiceError asn1ServiceError;
+
+ asn1ServiceError = new com.beanit.iec61850bean.internal.mms.asn1.ServiceError();
+ asn1ServiceError.setErrorClass(errClass);
+ asn1ServiceError.setAdditionalDescription(new BerVisibleString(e.getMessage()));
+
+ ConfirmedErrorPDU confirmedErrorPDU = new ConfirmedErrorPDU();
+ confirmedErrorPDU.setInvokeID(new Unsigned32(invokeId));
+ confirmedErrorPDU.setServiceError(asn1ServiceError);
+
+ MMSpdu mmsPdu = new MMSpdu();
+ mmsPdu.setConfirmedErrorPDU(confirmedErrorPDU);
+
+ return mmsPdu;
+ }
+
+ private GetNameListResponse handleGetServerDirectoryRequest() throws ServiceError {
+
+ ListOfIdentifier listOfIdentifier = new ListOfIdentifier();
+ List identifiers = listOfIdentifier.getIdentifier();
+
+ for (ModelNode ld : serverModel) {
+ identifiers.add(new Identifier(ld.getName().getBytes(UTF_8)));
+ }
+
+ GetNameListResponse getNameListResponse = new GetNameListResponse();
+ getNameListResponse.setListOfIdentifier(listOfIdentifier);
+ getNameListResponse.setMoreFollows(new BerBoolean(false));
+
+ return getNameListResponse;
+ }
+
+ private GetNameListResponse handleGetDirectoryRequest(GetNameListRequest getNameListRequest)
+ throws ServiceError {
+
+ // the ObjectScope can be vmdSpecific,domainSpecific, or aaSpecific. vmdSpecific and aaSpecific
+ // are not part of
+ // 61850-8-1 but are used by some IEC 61850 clients anyways. This stack will return an empty
+ // list on vmdSpecific
+ // and aaSpecific requests.
+ if (getNameListRequest.getObjectScope().getAaSpecific() != null
+ || getNameListRequest.getObjectScope().getVmdSpecific() != null) {
+ ListOfIdentifier listOfIden = new ListOfIdentifier();
+ listOfIden.getIdentifier();
+ GetNameListResponse getNameListResponse = new GetNameListResponse();
+ getNameListResponse.setListOfIdentifier(listOfIden);
+ getNameListResponse.setMoreFollows(new BerBoolean(false));
+ return getNameListResponse;
+ }
+
+ String mmsDomainId = getNameListRequest.getObjectScope().getDomainSpecific().toString();
+
+ ModelNode logicalDeviceMn = serverModel.getChild(mmsDomainId);
+
+ if (logicalDeviceMn == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "Got an invalid MMS request: given Domain name in GetNameList request is not a Logical Device name");
+ }
+
+ LogicalDevice logicalDevice = (LogicalDevice) logicalDeviceMn;
+
+ insertRef = true;
+
+ if (getNameListRequest.getContinueAfter() != null) {
+ continueAfter = getNameListRequest.getContinueAfter().toString();
+ insertRef = false;
+ }
+
+ List mmsReferences = new ArrayList<>();
+
+ for (ModelNode logicalNodeMn : logicalDevice) {
+ LogicalNode logicalNode = (LogicalNode) logicalNodeMn;
+ mmsReferences.add(logicalNode.getName());
+
+ for (String mmsFC : mmsFcs) {
+ Fc fc = Fc.fromString(mmsFC);
+ if (fc != null) {
+
+ List fcDataObjects = logicalNode.getChildren(fc);
+ if (fcDataObjects != null) {
+ mmsReferences.add(logicalNode.getName() + "$" + mmsFC);
+ for (FcDataObject dataObject : fcDataObjects) {
+ insertMmsRef(dataObject, mmsReferences, logicalNode.getName() + "$" + mmsFC);
+ }
+ }
+ }
+ }
+ }
+
+ ListOfIdentifier listOfIden = new ListOfIdentifier();
+ List identifiers = listOfIden.getIdentifier();
+
+ int identifierSize = 0;
+ boolean moreFollows = false;
+ for (String mmsReference : mmsReferences) {
+ if (insertRef == true) {
+ if (identifierSize > negotiatedMaxPduSize - 200) {
+ moreFollows = true;
+ logger.debug(" ->maxMMSPduSize of " + negotiatedMaxPduSize + " Bytes reached");
+ break;
+ }
+
+ Identifier identifier;
+
+ identifier = new Identifier(mmsReference.getBytes(UTF_8));
+
+ identifiers.add(identifier);
+ identifierSize += mmsReference.length() + 2;
+ } else {
+ if (mmsReference.equals(continueAfter)) {
+ insertRef = true;
+ }
+ }
+ }
+
+ GetNameListResponse getNameListResponse = new GetNameListResponse();
+ getNameListResponse.setListOfIdentifier(listOfIden);
+ getNameListResponse.setMoreFollows(new BerBoolean(moreFollows));
+
+ return getNameListResponse;
+ }
+
+ /**
+ * GetVariableAccessAttributes (GetDataDefinition/GetDataDirectory) can be called with different
+ * kinds of references. Examples: 1. DGEN1 2. DGEN1$CF 3. DGEN1$CF$GnBlk
+ */
+ private GetVariableAccessAttributesResponse handleGetVariableAccessAttributesRequest(
+ GetVariableAccessAttributesRequest getVariableAccessAttributesRequest) throws ServiceError {
+ if (getVariableAccessAttributesRequest.getName() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Got an invalid MMS packet: name is not selected in GetVariableAccessAttributesRequest");
+ }
+
+ DomainSpecific domainSpecific =
+ getVariableAccessAttributesRequest.getName().getDomainSpecific();
+
+ if (domainSpecific == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "Got an invalid MMS packet: Domain specific is not selected in GetVariableAccessAttributesRequest");
+ }
+
+ ModelNode modelNode = serverModel.getChild(domainSpecific.getDomainID().toString());
+
+ if (modelNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+
+ String itemIdString = domainSpecific.getItemID().toString();
+
+ int index1 = itemIdString.indexOf('$');
+
+ LogicalNode logicalNode = null;
+
+ if (index1 != -1) {
+ logicalNode = (LogicalNode) modelNode.getChild(itemIdString.substring(0, index1));
+ if (logicalNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ int index2 = itemIdString.indexOf('$', index1 + 2);
+ if (index2 != -1) {
+ Fc fc = Fc.fromString(itemIdString.substring(index1 + 1, index2));
+ if (fc == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ index1 = itemIdString.indexOf('$', index2 + 2);
+ ModelNode subNode;
+ if (index1 == -1) {
+ subNode = logicalNode.getChild(itemIdString.substring(index2 + 1), fc);
+ if (subNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ } else {
+ subNode = logicalNode.getChild(itemIdString.substring(index2 + 1, index1), fc);
+ if (subNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ index2 = itemIdString.indexOf('$', index1 + 2);
+ while (index2 != -1) {
+ subNode = subNode.getChild(itemIdString.substring(index1 + 1, index2));
+ if (subNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest
+ .getName()
+ .getDomainSpecific()
+ .getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ index1 = index2;
+ index2 = itemIdString.indexOf('$', index1 + 2);
+ }
+ subNode = subNode.getChild(itemIdString.substring(index1 + 1));
+ if (subNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+ }
+
+ GetVariableAccessAttributesResponse getVariableAccessAttributesResponse =
+ new GetVariableAccessAttributesResponse();
+ getVariableAccessAttributesResponse.setMmsDeletable(new BerBoolean(false));
+ getVariableAccessAttributesResponse.setTypeDescription(subNode.getMmsTypeSpec());
+
+ return getVariableAccessAttributesResponse;
+ } else {
+ Fc fc = Fc.fromString(itemIdString.substring(index1 + 1));
+
+ if (fc == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+
+ List fcDataObjects = logicalNode.getChildren(fc);
+
+ if (fcDataObjects == null || fcDataObjects.size() == 0) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+
+ Components comp = new Components();
+ List doStructComponents = comp.getSEQUENCE();
+ for (ModelNode child : fcDataObjects) {
+ TypeSpecification typeSpecification = new TypeSpecification();
+ typeSpecification.setTypeDescription(child.getMmsTypeSpec());
+
+ TypeDescription.Structure.Components.SEQUENCE structComponent =
+ new TypeDescription.Structure.Components.SEQUENCE();
+ structComponent.setComponentName(new Identifier(child.getName().getBytes(UTF_8)));
+ structComponent.setComponentType(typeSpecification);
+ doStructComponents.add(structComponent);
+ }
+
+ Structure struct = new Structure();
+ struct.setComponents(comp);
+
+ TypeDescription typeDescription = new TypeDescription();
+ typeDescription.setStructure(struct);
+
+ GetVariableAccessAttributesResponse getVariableAccessAttributesResponse =
+ new GetVariableAccessAttributesResponse();
+ getVariableAccessAttributesResponse.setMmsDeletable(new BerBoolean(false));
+ getVariableAccessAttributesResponse.setTypeDescription(typeDescription);
+
+ return getVariableAccessAttributesResponse;
+ }
+ }
+
+ logicalNode = (LogicalNode) modelNode.getChild(itemIdString);
+ if (logicalNode == null) {
+ throw new ServiceError(
+ ServiceError.INSTANCE_NOT_AVAILABLE,
+ "GetVariableAccessAttributes (GetDataDefinition): no object with domainId "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getDomainID()
+ + " and ItemID "
+ + getVariableAccessAttributesRequest.getName().getDomainSpecific().getItemID()
+ + " was found.");
+ }
+
+ Components components = new Components();
+ List structComponents = components.getSEQUENCE();
+
+ for (String mmsFc : mmsFcs) {
+ Fc fc = Fc.fromString(mmsFc);
+ if (fc != null) {
+
+ Collection fcDataObjects = logicalNode.getChildren(fc);
+ if (fcDataObjects == null) {
+ continue;
+ }
+
+ Components comp = new Components();
+ List doStructComponents = comp.getSEQUENCE();
+
+ for (ModelNode child : fcDataObjects) {
+ TypeSpecification typeSpecification = new TypeSpecification();
+ typeSpecification.setTypeDescription(child.getMmsTypeSpec());
+
+ TypeDescription.Structure.Components.SEQUENCE doStructComponent =
+ new TypeDescription.Structure.Components.SEQUENCE();
+ doStructComponent.setComponentName(new Identifier(child.getName().getBytes(UTF_8)));
+ doStructComponent.setComponentType(typeSpecification);
+
+ doStructComponents.add(doStructComponent);
+ }
+
+ Structure struct = new Structure();
+ struct.setComponents(comp);
+
+ TypeDescription fcTypeSpec = new TypeDescription();
+ fcTypeSpec.setStructure(struct);
+
+ TypeSpecification typeSpecification = new TypeSpecification();
+ typeSpecification.setTypeDescription(fcTypeSpec);
+
+ TypeDescription.Structure.Components.SEQUENCE structCom =
+ new TypeDescription.Structure.Components.SEQUENCE();
+ structCom.setComponentName(new Identifier(mmsFc.getBytes(UTF_8)));
+ structCom.setComponentType(typeSpecification);
+
+ structComponents.add(structCom);
+ }
+ }
+
+ Structure struct = new Structure();
+ struct.setComponents(components);
+
+ TypeDescription typeSpec = new TypeDescription();
+ typeSpec.setStructure(struct);
+
+ GetVariableAccessAttributesResponse getVariableAccessAttributesResponse =
+ new GetVariableAccessAttributesResponse();
+ getVariableAccessAttributesResponse.setMmsDeletable(new BerBoolean(false));
+ getVariableAccessAttributesResponse.setTypeDescription(typeSpec);
+
+ return getVariableAccessAttributesResponse;
+ }
+
+ private ReadResponse handleGetDataValuesRequest(ReadRequest mmsReadRequest) throws ServiceError {
+
+ VariableAccessSpecification variableAccessSpecification =
+ mmsReadRequest.getVariableAccessSpecification();
+
+ if (mmsReadRequest.getSpecificationWithResult() == null
+ || mmsReadRequest.getSpecificationWithResult().value == false) {
+
+ if (variableAccessSpecification.getListOfVariable() == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "handleGetDataValuesRequest: Got an invalid MMS packet");
+ }
+
+ List listOfVariable =
+ variableAccessSpecification.getListOfVariable().getSEQUENCE();
+
+ if (listOfVariable.size() < 1) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleGetDataValuesRequest: less than one variableAccessSpecification is not allowed");
+ }
+
+ ListOfAccessResult listOfAccessResult = new ListOfAccessResult();
+ List accessResults = listOfAccessResult.getAccessResult();
+
+ synchronized (serverModel) {
+ for (VariableDefs.SEQUENCE variableDef : listOfVariable) {
+
+ FcModelNode modelNode = serverModel.getNodeFromVariableDef(variableDef);
+
+ if (modelNode == null) {
+ logger.debug("Got a GetDataValues request for a non existent model node.");
+ // 10 indicates error "object-non-existent"
+ AccessResult accessResult = new AccessResult();
+ accessResult.setFailure(new DataAccessError(10L));
+ accessResults.add(accessResult);
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Got a GetDataValues request for node: " + modelNode);
+ if (!(modelNode instanceof BasicDataAttribute)) {
+ for (BasicDataAttribute bda : modelNode.getBasicDataAttributes()) {
+ logger.debug("sub BDA is:" + bda);
+ }
+ }
+ }
+ accessResults.add(getReadResult(modelNode));
+ }
+ }
+ }
+
+ ReadResponse readResponse = new ReadResponse();
+ readResponse.setListOfAccessResult(listOfAccessResult);
+ return readResponse;
+ } else {
+ logger.debug("Got a GetDataSetValues request.");
+
+ String dataSetReference =
+ convertToDataSetReference(variableAccessSpecification.getVariableListName());
+
+ if (dataSetReference == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleGetDataSetValuesRequest: DataSet name incorrect");
+ }
+
+ ListOfAccessResult listOfAccessResult = new ListOfAccessResult();
+ List accessResults = listOfAccessResult.getAccessResult();
+
+ if (dataSetReference.startsWith("@")) {
+ DataSet dataSet = nonPersistentDataSets.get(dataSetReference);
+ if (dataSet == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleGetDataSetValuesRequest: a DataSet with the given reference does not exist");
+ }
+
+ for (FcModelNode dsMember : dataSet) {
+ accessResults.add(getReadResult(dsMember));
+ }
+ } else {
+ synchronized (serverModel) {
+ DataSet dataSet = serverModel.getDataSet(dataSetReference);
+
+ if (dataSet == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleGetDataSetValuesRequest: a DataSet with the given reference does not exist");
+ }
+
+ for (FcModelNode dsMember : dataSet) {
+ accessResults.add(getReadResult(dsMember));
+ }
+ }
+ }
+ ReadResponse readResponse = new ReadResponse();
+ readResponse.setListOfAccessResult(listOfAccessResult);
+ return readResponse;
+ }
+ }
+
+ private AccessResult getReadResult(FcModelNode modelNode) {
+
+ AccessResult accessResult = new AccessResult();
+
+ if (modelNode.getFc() == Fc.CO && modelNode.getName().equals("SBO")) {
+ // if (modelNode.getName().equals("SBO")) {
+ FcModelNode cdcParent = (FcModelNode) modelNode.getParent();
+ ModelNode ctlModelNode =
+ serverModel.findModelNode(cdcParent.getReference(), Fc.CF).getChild("ctlModel");
+ if (ctlModelNode == null
+ || !(ctlModelNode instanceof BdaInt8)
+ || ((BdaInt8) ctlModelNode).getValue() != 2) {
+ logger.warn(
+ "Selecting controle DO fails because ctlModel is not set to \"sbo-with-normal-security\"");
+ // 3 indicates error "object_access_denied"
+ accessResult.setFailure(new DataAccessError(3L));
+ return accessResult;
+ }
+ if (!cdcParent.select(this, serverSap.timer)) {
+ Data data = new Data();
+ data.setVisibleString(new BerVisibleString(""));
+ accessResult.setSuccess(data);
+ return accessResult;
+ }
+ Data data = new Data();
+ data.setVisibleString(new BerVisibleString("success"));
+ accessResult.setSuccess(data);
+ return accessResult;
+
+ // }
+ // else {
+ // logger.warn("A client tried to read a control variable other than SBO. This is not
+ // allowed.");
+ // // 3 indicates error "object_access_denied"
+ // return new AccessResult(new BerInteger(3L), null);
+ // }
+
+ }
+
+ Data data = modelNode.getMmsDataObj();
+
+ if (data == null) {
+ // 11 indicates error "object_value_invalid"
+ accessResult.setFailure(new DataAccessError(11L));
+ return accessResult;
+ }
+
+ accessResult.setSuccess(data);
+ return accessResult;
+ }
+
+ private WriteResponse handleSetDataValuesRequest(WriteRequest mmsWriteRequest)
+ throws ServiceError {
+
+ VariableAccessSpecification variableAccessSpecification =
+ mmsWriteRequest.getVariableAccessSpecification();
+
+ List listOfData = mmsWriteRequest.getListOfData().getData();
+
+ WriteResponse writeResponse = new WriteResponse();
+ List mmsResponseValues = writeResponse.getCHOICE();
+
+ if (variableAccessSpecification.getListOfVariable() != null) {
+ logger.debug("Got a SetDataValues request.");
+
+ List listOfVariable =
+ variableAccessSpecification.getListOfVariable().getSEQUENCE();
+
+ if (listOfVariable.size() < 1
+ || listOfData.size() < 1
+ || listOfVariable.size() != listOfData.size()) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleSetDataValuesRequest: less than one variableAccessSpecification or data element is not allowed, or listOfData ne listOfVar");
+ }
+
+ Iterator mmsDataIterator = listOfData.iterator();
+
+ List totalBdasToBeWritten = new ArrayList<>();
+ int[] numBdas = new int[listOfData.size()];
+
+ int i = -1;
+ synchronized (serverModel) {
+ for (VariableDefs.SEQUENCE variableDef : listOfVariable) {
+ i++;
+ Data mmsData = mmsDataIterator.next();
+
+ FcModelNode modelNode = serverModel.getNodeFromVariableDef(variableDef);
+
+ if (modelNode == null) {
+ // 10 indicates error "object-non-existent"
+ WriteResponse.CHOICE writeResponseChoice = new WriteResponse.CHOICE();
+ writeResponseChoice.setFailure(new DataAccessError(10L));
+ mmsResponseValues.add(writeResponseChoice);
+ } else {
+
+ getFirstWriteResults(
+ mmsResponseValues, totalBdasToBeWritten, numBdas, i, modelNode, mmsData);
+ }
+ }
+
+ writeAndFillMissingWriteResults(mmsResponseValues, totalBdasToBeWritten, numBdas);
+ }
+
+ } else if (variableAccessSpecification.getVariableListName() != null) {
+ logger.debug("Got a SetDataSetValues request.");
+
+ String dataSetRef =
+ convertToDataSetReference(variableAccessSpecification.getVariableListName());
+
+ // TODO handle non-persisten DataSets too
+
+ DataSet dataSet = serverModel.getDataSet(dataSetRef);
+
+ Iterator mmsDataIterator = listOfData.iterator();
+
+ List totalBdasToBeWritten = new ArrayList<>();
+ int[] numBdas = new int[listOfData.size()];
+
+ int i = -1;
+ synchronized (serverModel) {
+ for (FcModelNode dataSetMember : dataSet) {
+ i++;
+ Data mmsData = mmsDataIterator.next();
+
+ getFirstWriteResults(
+ mmsResponseValues, totalBdasToBeWritten, numBdas, i, dataSetMember, mmsData);
+ }
+
+ writeAndFillMissingWriteResults(mmsResponseValues, totalBdasToBeWritten, numBdas);
+ }
+
+ } else {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "handleSetDataValuesRequest: invalid MMS request");
+ }
+
+ return writeResponse;
+ }
+
+ private void writeAndFillMissingWriteResults(
+ List mmsResponseValues,
+ List totalBdasToBeWritten,
+ int[] numBdas) {
+ int i;
+ if (totalBdasToBeWritten.size() != 0) {
+ List serviceErrors = serverSap.serverEventListener.write(totalBdasToBeWritten);
+ ListIterator mmsResponseIterator = mmsResponseValues.listIterator();
+ if (serviceErrors == null || serviceErrors.size() != totalBdasToBeWritten.size()) {
+ while (mmsResponseIterator.hasNext()) {
+ if (mmsResponseIterator.next() == null) {
+ mmsResponseIterator.set(writeSuccess);
+ }
+ }
+ for (BasicDataAttribute bda : totalBdasToBeWritten) {
+ bda.mirror.setValueFrom(bda);
+ }
+ } else {
+ i = -1;
+ Iterator serviceErrorIterator = serviceErrors.iterator();
+ Iterator bdaToBeWrittenIterator = totalBdasToBeWritten.iterator();
+ while (mmsResponseIterator.hasNext()) {
+ i++;
+ if (mmsResponseIterator.next() == null) {
+ for (int j = 0; j < numBdas[i]; j++) {
+ ServiceError serviceError = serviceErrorIterator.next();
+ BasicDataAttribute bda = bdaToBeWrittenIterator.next();
+ if (serviceError != null) {
+ WriteResponse.CHOICE writeResponseChoice = new WriteResponse.CHOICE();
+ writeResponseChoice.setFailure(
+ new DataAccessError(serviceErrorToMmsError(serviceError)));
+ mmsResponseIterator.set(writeResponseChoice);
+ } else {
+ bda.mirror.setValueFrom(bda);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void getFirstWriteResults(
+ List mmsResponseValues,
+ List totalBdasToBeWritten,
+ int[] numBdas,
+ int i,
+ FcModelNode fcModelNode,
+ Data mmsData) {
+ WriteResponse.CHOICE writeResult = getWriteResult(fcModelNode, mmsData);
+ if (writeResult == null) {
+ FcModelNode fcModelNodeCopy = (FcModelNode) fcModelNode.copy();
+ try {
+ fcModelNodeCopy.setValueFromMmsDataObj(mmsData);
+ } catch (ServiceError e) {
+ logger.warn("SetDataValues failed because of data missmatch.", e);
+ WriteResponse.CHOICE writeResponseChoice = new WriteResponse.CHOICE();
+ writeResponseChoice.setFailure(new DataAccessError(serviceErrorToMmsError(e)));
+ mmsResponseValues.add(writeResponseChoice);
+ return;
+ }
+
+ if (fcModelNodeCopy.fc == Fc.CO) {
+ // TODO timeactivate operate
+ fcModelNodeCopy = (FcModelNode) fcModelNodeCopy.getChild("ctlVal");
+ // TODO write origin and ctlNum if they exist
+ } else {
+
+ }
+
+ List bdas = fcModelNodeCopy.getBasicDataAttributes();
+ totalBdasToBeWritten.addAll(bdas);
+ numBdas[i] = bdas.size();
+ mmsResponseValues.add(null);
+
+ } else {
+ mmsResponseValues.add(writeResult);
+ }
+ }
+
+ // private WriteResponse.SubChoice operate(FcModelNode modelNode, Data mmsData) {
+ // FcModelNode fcModelNodeCopy = (FcModelNode) modelNode.copy();
+ // try {
+ // fcModelNodeCopy.setValueFromMmsDataObj(mmsData);
+ // } catch (ServiceError e) {
+ // logger.warn("SetDataValues failed because of data missmatch.", e);
+ // return new WriteResponse.SubChoice(new BerInteger(serviceErrorToMmsError(e)), null);
+ // }
+ //
+ // // TODO timeactivate operate
+ //
+ // BasicDataAttribute ctlValBda = (BasicDataAttribute) fcModelNodeCopy.getChild("ctlVal");
+ // List bdas = new ArrayList(1);
+ // bdas.add(ctlValBda);
+ // List serviceErrors;
+ // try {
+ // serviceErrors = serverSap.serverEventListener.write(bdas);
+ // } catch (ServiceError e) {
+ // return new WriteResponse.SubChoice(new BerInteger(serviceErrorToMmsError(e)), null);
+ // }
+ // if (serviceErrors != null && serviceErrors.size() == bdas.size() && serviceErrors.get(1) !=
+ // null) {
+ // return new WriteResponse.SubChoice(new
+ // BerInteger(serviceErrorToMmsError(serviceErrors.get(1))), null);
+ // }
+ //
+ // ctlValBda.mirror.setValueFrom(ctlValBda);
+ // // TODO write origin and ctlNum if they exist
+ //
+ // return writeSuccess;
+ // }
+
+ private WriteResponse.CHOICE getWriteResult(FcModelNode modelNode, Data mmsData) {
+
+ WriteResponse.CHOICE writeResponse = new WriteResponse.CHOICE();
+
+ Fc fc = modelNode.getFc();
+ if (fc == Fc.ST || fc == Fc.MX || fc == Fc.OR || fc == Fc.EX) {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ if (fc == Fc.CO) {
+ String nodeName = modelNode.getName();
+
+ if (nodeName.equals("Oper")) {
+ FcModelNode cdcParent = (FcModelNode) modelNode.getParent();
+ ModelNode ctlModelNode =
+ serverModel.findModelNode(cdcParent.getReference(), Fc.CF).getChild("ctlModel");
+ if (ctlModelNode == null || !(ctlModelNode instanceof BdaInt8)) {
+ logger.warn("Operatring controle DO failed because ctlModel is not set.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ int ctlModel = ((BdaInt8) ctlModelNode).getValue();
+
+ /* Direct control with normal security (direct-operate) */
+ if (ctlModel == 1) {
+ return null;
+ }
+ /* SBO control with normal security (operate-once or operate-many) */
+ else if (ctlModel == 2) {
+ if (cdcParent.isSelectedBy(this)) {
+ return null;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ } else {
+ logger.warn("SetDataValues failed because of unsupported ctlModel: " + ctlModel);
+ // 9 indicates error "object_access_unsupported"
+ writeResponse.setFailure(new DataAccessError(9L));
+ return writeResponse;
+ }
+ } else {
+ logger.warn(
+ "SetDataValues failed because of the operation is not allowed yet: "
+ + modelNode.getName());
+ // 9 indicates error "object_access_unsupported"
+ writeResponse.setFailure(new DataAccessError(9L));
+ return writeResponse;
+ }
+ } else if (fc == Fc.RP) {
+
+ if (modelNode instanceof Rcb) {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ FcModelNode fcModelNodeCopy = (FcModelNode) modelNode.copy();
+
+ try {
+ fcModelNodeCopy.setValueFromMmsDataObj(mmsData);
+ } catch (ServiceError e) {
+ WriteResponse.CHOICE writeResponseChoice = new WriteResponse.CHOICE();
+ writeResponseChoice.setFailure(new DataAccessError(serviceErrorToMmsError(e)));
+ return writeResponseChoice;
+ }
+
+ Urcb urcb = (Urcb) modelNode.getParent();
+
+ String nodeName = modelNode.getName();
+
+ synchronized (urcb) {
+ if (nodeName.equals("RptEna")) {
+ BdaBoolean rptEnaNode = (BdaBoolean) fcModelNodeCopy;
+ if (rptEnaNode.getValue()) {
+ if (urcb.dataSet == null) {
+ logger.info("client tried to enable RCB even though there is no configured data set");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ if (urcb.reserved == null) {
+ urcb.reserved = this;
+ urcb.enable();
+ rsvdURCBs.add(urcb);
+ ((BdaBoolean) modelNode).setValue(true);
+ return writeSuccess;
+ } else if (urcb.reserved == this) {
+ urcb.enable();
+ ((BdaBoolean) modelNode).setValue(true);
+ return writeSuccess;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ } else {
+ // disable reporting
+ if (urcb.reserved == this) {
+ urcb.disable();
+ ((BdaBoolean) modelNode).setValue(false);
+ return writeSuccess;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ }
+ } else if (nodeName.equals("Resv")) {
+ BdaBoolean rptResvNode = (BdaBoolean) fcModelNodeCopy;
+ if (rptResvNode.getValue()) {
+
+ if (urcb.reserved == null) {
+ urcb.reserved = this;
+ urcb.getResv().setValue(true);
+ rsvdURCBs.add(urcb);
+ return writeSuccess;
+ } else if (urcb.reserved == this) {
+ return writeSuccess;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ } else {
+ if (urcb.reserved == this) {
+ urcb.reserved = null;
+ urcb.getResv().setValue(false);
+ rsvdURCBs.remove(urcb);
+ return writeSuccess;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ }
+
+ } else if (nodeName.equals("DatSet")) {
+ if ((urcb.reserved == null || urcb.reserved == this) && !urcb.enabled) {
+ String dataSetRef =
+ ((BdaVisibleString) fcModelNodeCopy).getStringValue().replace('$', '.');
+ if (dataSetRef.isEmpty()) {
+ urcb.dataSet = null;
+ ((BasicDataAttribute) modelNode).setValueFrom((BasicDataAttribute) fcModelNodeCopy);
+ return writeSuccess;
+
+ } else {
+ DataSet dataSet = serverModel.getDataSet(dataSetRef);
+ if (dataSet == null) {
+ dataSet = nonPersistentDataSets.get(dataSetRef);
+ }
+ if (dataSet != null) {
+ urcb.dataSet = dataSet;
+ ((BasicDataAttribute) modelNode).setValueFrom((BasicDataAttribute) fcModelNodeCopy);
+ return writeSuccess;
+ } else {
+ logger.info(
+ "Client tried to set dataSetReference of URCB to non existant data set.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ }
+ } else {
+ logger.info(
+ "Client tried to write RCB parameter even though URCB is reserved by other client or already enabled.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ } else if (nodeName.equals("OptFlds")) {
+ if ((urcb.reserved == null || urcb.reserved == this) && !urcb.enabled) {
+ if (!((BdaOptFlds) modelNode).isBufferOverflow()
+ && !((BdaOptFlds) modelNode).isConfigRevision()
+ && !((BdaOptFlds) modelNode).isDataReference()
+ && !((BdaOptFlds) modelNode).isEntryId()) {
+ ((BasicDataAttribute) modelNode).setValueFrom((BasicDataAttribute) fcModelNodeCopy);
+ return writeSuccess;
+ } else {
+ logger.info("Client tried to write OptFlds with usupported field set to true.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ } else {
+ logger.info(
+ "Client tried to write RCB parameter even though URCB is reserved by other client or already enabled.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ } else if (nodeName.equals("GI")) {
+
+ if ((urcb.reserved == this)
+ && urcb.enabled
+ && ((BdaTriggerConditions) urcb.getChild("TrgOps")).isGeneralInterrogation()) {
+ urcb.generalInterrogation();
+ return writeSuccess;
+ } else {
+ logger.info(
+ "Client tried to initiate a general interrogation even though URCB is not enabled by this client or general interrogation is not enabled in the trigger options.");
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ } else if (nodeName.equals("RptID")
+ || nodeName.equals("BufTm")
+ || nodeName.equals("TrgOps")
+ || nodeName.equals("IntgPd")) {
+ if ((urcb.reserved == null || urcb.reserved == this) && !urcb.enabled) {
+ ((BasicDataAttribute) modelNode).setValueFrom((BasicDataAttribute) fcModelNodeCopy);
+ return writeSuccess;
+ } else {
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+
+ } else {
+ // nodes sqnum, ConfRev, and owner may not be read
+ // 3 indicates error "object_access_denied"
+ writeResponse.setFailure(new DataAccessError(3L));
+ return writeResponse;
+ }
+ }
+
+ } else {
+
+ return null;
+ }
+ }
+
+ private int serviceErrorToMmsError(ServiceError e) {
+
+ switch (e.getErrorCode()) {
+ case ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT:
+ return 1;
+ case ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT:
+ return 2;
+ case ServiceError.ACCESS_VIOLATION:
+ return 3;
+ case ServiceError.TYPE_CONFLICT:
+ return 7;
+ case ServiceError.INSTANCE_NOT_AVAILABLE:
+ return 10;
+ case ServiceError.PARAMETER_VALUE_INCONSISTENT:
+ return 11;
+ default:
+ return 9;
+ }
+ }
+
+ private GetNameListResponse handleGetDataSetNamesRequest(GetNameListRequest getNameListRequest)
+ throws ServiceError {
+
+ BerVisibleString domainSpecific = getNameListRequest.getObjectScope().getDomainSpecific();
+
+ List dsList = null;
+ if (domainSpecific == null) {
+ dsList = new ArrayList<>(nonPersistentDataSets.size());
+ for (String dataSet : nonPersistentDataSets.keySet()) {
+ dsList.add(dataSet);
+ }
+ } else {
+ dsList = serverModel.getDataSetNames(domainSpecific.toString());
+ }
+
+ insertRef = true;
+ if (getNameListRequest.getContinueAfter() != null) {
+ continueAfter = getNameListRequest.getContinueAfter().toString();
+ insertRef = false;
+ }
+
+ ListOfIdentifier listOf = new ListOfIdentifier();
+ List identifiers = listOf.getIdentifier();
+
+ int identifierSize = 0;
+ boolean moreFollows = false;
+
+ if (dsList != null) {
+ for (String dsRef : dsList) {
+ if (insertRef == true) {
+ if (identifierSize > negotiatedMaxPduSize - 200) {
+ moreFollows = true;
+ logger.info("maxMMSPduSize reached");
+ break;
+ }
+ identifiers.add(new Identifier(dsRef.getBytes(UTF_8)));
+ identifierSize += dsRef.length() + 2;
+ } else {
+ if (dsRef.equals(continueAfter)) {
+ insertRef = true;
+ }
+ }
+ }
+ }
+
+ GetNameListResponse getNameListResponse = new GetNameListResponse();
+ getNameListResponse.setListOfIdentifier(listOf);
+ getNameListResponse.setMoreFollows(new BerBoolean(moreFollows));
+
+ return getNameListResponse;
+ }
+
+ private GetNamedVariableListAttributesResponse handleGetDataSetDirectoryRequest(
+ ObjectName mmsGetNamedVarListAttReq) throws ServiceError {
+
+ String dataSetReference = convertToDataSetReference(mmsGetNamedVarListAttReq);
+
+ DataSet dataSet;
+
+ if (dataSetReference.startsWith("@")) {
+ dataSet = nonPersistentDataSets.get(dataSetReference);
+ } else {
+ dataSet = serverModel.getDataSet(dataSetReference);
+ }
+
+ if (dataSet == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE,
+ "DataSet with that reference is does not exist.");
+ }
+
+ VariableDefs variableDefs = new VariableDefs();
+
+ List listOfVariable = variableDefs.getSEQUENCE();
+
+ for (FcModelNode member : dataSet) {
+ listOfVariable.add(member.getMmsVariableDef());
+ }
+
+ GetNamedVariableListAttributesResponse getNamedVariableListAttributesResponse =
+ new GetNamedVariableListAttributesResponse();
+ getNamedVariableListAttributesResponse.setListOfVariable(variableDefs);
+ getNamedVariableListAttributesResponse.setMmsDeletable(new BerBoolean(dataSet.isDeletable()));
+
+ return getNamedVariableListAttributesResponse;
+ }
+
+ private DefineNamedVariableListResponse handleCreateDataSetRequest(
+ DefineNamedVariableListRequest mmsDefineNamedVariableListRequest) throws ServiceError {
+ String dataSetReference =
+ convertToDataSetReference(mmsDefineNamedVariableListRequest.getVariableListName());
+ if (dataSetReference == null) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INCONSISTENT,
+ "handleCreateDataSetRequest: invalid MMS request (No DataSet Name Specified)");
+ }
+
+ List nameList =
+ mmsDefineNamedVariableListRequest.getListOfVariable().getSEQUENCE();
+
+ List dataSetMembers = new ArrayList<>(nameList.size());
+
+ for (VariableDefs.SEQUENCE variableDef : nameList) {
+ dataSetMembers.add(serverModel.getNodeFromVariableDef(variableDef));
+ }
+
+ DataSet dataSet = new DataSet(dataSetReference, dataSetMembers, true);
+
+ if (dataSetReference.startsWith("@")) {
+ if (nonPersistentDataSets.containsKey(dataSetReference)) {
+ throw new ServiceError(
+ ServiceError.PARAMETER_VALUE_INAPPROPRIATE, "data set with that name exists already");
+ }
+ nonPersistentDataSets.put(dataSetReference, dataSet);
+ } else {
+ serverModel.addDataSet(dataSet);
+ }
+
+ return new DefineNamedVariableListResponse();
+ }
+
+ private DeleteNamedVariableListResponse handleDeleteDataSetRequest(
+ DeleteNamedVariableListRequest mmsDelNamVarListReq) throws ServiceError {
+ String dataSetReference =
+ convertToDataSetReference(
+ mmsDelNamVarListReq.getListOfVariableListName().getObjectName().get(0));
+
+ DeleteNamedVariableListResponse deleteNamedVariableListResponse =
+ new DeleteNamedVariableListResponse();
+
+ if (dataSetReference.startsWith("@")) {
+ if (nonPersistentDataSets.remove(dataSetReference) == null) {
+ deleteNamedVariableListResponse.setNumberMatched(new Unsigned32(0));
+ deleteNamedVariableListResponse.setNumberDeleted(new Unsigned32(0));
+ return deleteNamedVariableListResponse;
+ } else {
+ deleteNamedVariableListResponse.setNumberMatched(new Unsigned32(1));
+ deleteNamedVariableListResponse.setNumberDeleted(new Unsigned32(1));
+ return deleteNamedVariableListResponse;
+ }
+ } else {
+ synchronized (serverModel) {
+ if (serverModel.removeDataSet(dataSetReference) == null) {
+ if (serverModel.getDataSet(dataSetReference) == null) {
+ // DataSet with the name does not exist.
+ deleteNamedVariableListResponse.setNumberMatched(new Unsigned32(0));
+ deleteNamedVariableListResponse.setNumberDeleted(new Unsigned32(0));
+ return deleteNamedVariableListResponse;
+ } else {
+ // DataSet exists but is not deletable
+ deleteNamedVariableListResponse.setNumberMatched(new Unsigned32(1));
+ deleteNamedVariableListResponse.setNumberDeleted(new Unsigned32(0));
+ return deleteNamedVariableListResponse;
+ }
+ } else {
+ deleteNamedVariableListResponse.setNumberMatched(new Unsigned32(1));
+ deleteNamedVariableListResponse.setNumberDeleted(new Unsigned32(1));
+ return deleteNamedVariableListResponse;
+ }
+ }
+ }
+ }
+
+ void close() {
+ cleanUpConnection();
+ executor.shutdown();
+ if (acseAssociation != null) {
+ acseAssociation.disconnect();
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServerEventListener.java b/src/main/java/com/beanit/iec61850bean/ServerEventListener.java
new file mode 100644
index 0000000..7cc104d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServerEventListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import java.util.List;
+
+public interface ServerEventListener {
+
+ /**
+ * The write callback function is called if one of more basic data attributes are written using
+ * either the setDataValue, setDataSetValues or control services. If the complete write process
+ * was successful write returns either an empty list or null. If an error occurs writing one or
+ * more attributes then a list shall be returned that is of equal size as the list of basic data
+ * attributes. The returned list's element shall be null if writing the corresponding BDA was
+ * successful and a service error otherwise.
+ *
+ * @param bdas the list of basic data attributes that are to be set.
+ * @return a list of service errors indicating errors writing the corresponding basic data
+ * attributes.
+ */
+ List write(List bdas);
+
+ void serverStoppedListening(ServerSap serverSAP);
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServerModel.java b/src/main/java/com/beanit/iec61850bean/ServerModel.java
new file mode 100644
index 0000000..9a4f59f
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServerModel.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.iec61850bean.internal.mms.asn1.AlternateAccessSelection;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName.DomainSpecific;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableDefs;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class ServerModel extends ModelNode {
+
+ private final Map dataSets = new LinkedHashMap<>();
+
+ private final Map urcbs = new HashMap<>();
+ private final Map brcbs = new HashMap<>();
+
+ public ServerModel(List logicalDevices, Collection dataSets) {
+ children = new LinkedHashMap<>();
+ objectReference = null;
+ for (LogicalDevice logicalDevice : logicalDevices) {
+ children.put(logicalDevice.getReference().getName(), logicalDevice);
+ logicalDevice.setParent(this);
+ }
+
+ if (dataSets != null) {
+ addDataSets(dataSets);
+ }
+
+ for (LogicalDevice ld : logicalDevices) {
+ for (ModelNode ln : ld.getChildren()) {
+ for (Urcb urcb : ((LogicalNode) ln).getUrcbs()) {
+ urcbs.put(urcb.getReference().toString(), urcb);
+ urcb.dataSet = getDataSet(urcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ for (Brcb brcb : ((LogicalNode) ln).getBrcbs()) {
+ brcbs.put(brcb.getReference().toString(), brcb);
+ brcb.dataSet = getDataSet(brcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ }
+ }
+ }
+
+ @Override
+ public ServerModel copy() {
+ List childCopies = new ArrayList<>(children.size());
+ for (ModelNode childNode : children.values()) {
+ childCopies.add((LogicalDevice) childNode.copy());
+ }
+
+ List dataSetCopies = new ArrayList<>(dataSets.size());
+ for (DataSet dataSet : dataSets.values()) {
+ dataSetCopies.add(dataSet);
+ }
+
+ return new ServerModel(childCopies, dataSetCopies);
+ }
+
+ /**
+ * Get the data set with the given reference. Return null if none is found.
+ *
+ * @param reference the reference of the requested data set.
+ * @return the data set with the given reference.
+ */
+ public DataSet getDataSet(String reference) {
+ return dataSets.get(reference);
+ }
+
+ void addDataSet(DataSet dataSet) {
+ dataSets.put(dataSet.getReferenceStr().replace('$', '.'), dataSet);
+ for (ModelNode ld : children.values()) {
+ for (ModelNode ln : ld.getChildren()) {
+ for (Urcb urcb : ((LogicalNode) ln).getUrcbs()) {
+ urcb.dataSet = getDataSet(urcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ for (Brcb brcb : ((LogicalNode) ln).getBrcbs()) {
+ brcb.dataSet = getDataSet(brcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ }
+ }
+ }
+
+ void addDataSets(Collection dataSets) {
+ for (DataSet dataSet : dataSets) {
+ addDataSet(dataSet);
+ }
+ for (ModelNode ld : children.values()) {
+ for (ModelNode ln : ld.getChildren()) {
+ for (Urcb urcb : ((LogicalNode) ln).getUrcbs()) {
+ urcb.dataSet = getDataSet(urcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ for (Brcb brcb : ((LogicalNode) ln).getBrcbs()) {
+ brcb.dataSet = getDataSet(brcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ }
+ }
+ }
+
+ List getDataSetNames(String ldName) {
+ // TODO make thread save
+ List dataSetNames = new ArrayList<>();
+ for (String dataSetRef : dataSets.keySet()) {
+ if (dataSetRef.startsWith(ldName)) {
+ dataSetNames.add(dataSetRef.substring(dataSetRef.indexOf('/') + 1).replace('.', '$'));
+ }
+ }
+ return dataSetNames;
+ }
+
+ /**
+ * Get a collection of all data sets that exist in this model.
+ *
+ * @return a collection of all data sets
+ */
+ public Collection getDataSets() {
+ return dataSets.values();
+ }
+
+ /**
+ * @param dataSetReference the data set reference
+ * @return returns the DataSet that was removed, null if no DataSet with the given reference was
+ * found or the data set is not deletable.
+ */
+ DataSet removeDataSet(String dataSetReference) {
+ DataSet dataSet = dataSets.get(dataSetReference);
+ if (dataSet == null || !dataSet.isDeletable()) {
+ return null;
+ }
+ DataSet removedDataSet = dataSets.remove(dataSetReference);
+ for (ModelNode ld : children.values()) {
+ for (ModelNode ln : ld.getChildren()) {
+ for (Urcb urcb : ((LogicalNode) ln).getUrcbs()) {
+ urcb.dataSet = getDataSet(urcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ for (Brcb brcb : ((LogicalNode) ln).getBrcbs()) {
+ brcb.dataSet = getDataSet(brcb.getDatSet().getStringValue().replace('$', '.'));
+ }
+ }
+ }
+ return removedDataSet;
+ }
+
+ void addUrcb(Urcb urcb) {
+ urcbs.put(urcb.getReference().getName(), urcb);
+ }
+
+ /**
+ * Get the unbuffered report control block (URCB) with the given reference.
+ *
+ * @param reference the reference of the requested URCB.
+ * @return the reference to the requested URCB or null if none with the given reference is found.
+ */
+ public Urcb getUrcb(String reference) {
+ return urcbs.get(reference);
+ }
+
+ /**
+ * Get a collection of all unbuffered report control blocks (URCB) that exist in this model.
+ *
+ * @return a collection of all unbuffered report control blocks (URCB)
+ */
+ public Collection getUrcbs() {
+ return urcbs.values();
+ }
+
+ /**
+ * Get the buffered report control block (BRCB) with the given reference.
+ *
+ * @param reference the reference of the requested BRCB.
+ * @return the reference to the requested BRCB or null if none with the given reference is found.
+ */
+ public Brcb getBrcb(String reference) {
+ return brcbs.get(reference);
+ }
+
+ /**
+ * Get a collection of all buffered report control blocks (BRCB) that exist in this model.
+ *
+ * @return a collection of all buffered report control blocks (BRCB)
+ */
+ public Collection getBrcbs() {
+ return brcbs.values();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (ModelNode logicalDevice : children.values()) {
+ sb.append(logicalDevice.toString());
+ }
+ sb.append("\n\n\n---------------------\nURCBs:");
+ for (Urcb urcb : getUrcbs()) {
+ sb.append("\n\n").append(urcb);
+ }
+
+ sb.append("\n\n\n---------------------\nBRCBs:");
+ for (Brcb brcb : getBrcbs()) {
+ sb.append("\n\n").append(brcb);
+ }
+
+ sb.append("\n\n\n---------------------\nData sets:");
+ for (DataSet dataSet : getDataSets()) {
+ sb.append("\n\n").append(dataSet);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Searches and returns the model node with the given object reference and FC. If searching for
+ * Logical Devices and Logical Nodes the given fc parameter may be null
.
+ *
+ * @param objectReference the object reference of the node that is being searched for. It has a
+ * syntax like "ldname/ln.do....".
+ * @param fc the functional constraint of the requested model node. May be null for Logical Device
+ * and Logical Node references.
+ * @return the model node if it was found or null otherwise
+ */
+ public ModelNode findModelNode(ObjectReference objectReference, Fc fc) {
+
+ ModelNode currentNode = this;
+ Iterator searchedNodeReferenceIterator = objectReference.iterator();
+
+ while (searchedNodeReferenceIterator.hasNext()) {
+ currentNode = currentNode.getChild(searchedNodeReferenceIterator.next(), fc);
+ if (currentNode == null) {
+ return null;
+ }
+ }
+ return currentNode;
+ }
+
+ /**
+ * Searches and returns the model node with the given object reference and FC. If searching for
+ * Logical Devices and Logical Nodes the given fc parameter may be null
.
+ *
+ * @param objectReference the object reference of the node that is being searched for. It has a
+ * syntax like "ldname/ln.do....".
+ * @param fc the functional constraint of the requested model node. May be null for Logical Device
+ * and Logical Node references.
+ * @return the model node if it was found or null otherwise
+ */
+ public ModelNode findModelNode(String objectReference, Fc fc) {
+ return findModelNode(new ObjectReference(objectReference), fc);
+ }
+
+ /**
+ * Returns the subModelNode that is referenced by the given VariableDef. Return null in case the
+ * referenced ModelNode is not found.
+ *
+ * @param variableDef the variableDef
+ * @return the subModelNode that is referenced by the given VariableDef
+ * @throws ServiceError if an error occurs
+ */
+ FcModelNode getNodeFromVariableDef(VariableDefs.SEQUENCE variableDef) throws ServiceError {
+
+ ObjectName objectName = variableDef.getVariableSpecification().getName();
+
+ if (objectName == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "name in objectName is not selected");
+ }
+
+ DomainSpecific domainSpecific = objectName.getDomainSpecific();
+
+ if (domainSpecific == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "domain_specific in name is not selected");
+ }
+
+ ModelNode modelNode = getChild(domainSpecific.getDomainID().toString());
+
+ if (modelNode == null) {
+ return null;
+ }
+
+ String mmsItemId = domainSpecific.getItemID().toString();
+ int index1 = mmsItemId.indexOf('$');
+
+ if (index1 == -1) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "invalid mms item id: " + domainSpecific.getItemID());
+ }
+
+ LogicalNode ln = (LogicalNode) modelNode.getChild(mmsItemId.substring(0, index1));
+
+ if (ln == null) {
+ return null;
+ }
+
+ int index2 = mmsItemId.indexOf('$', index1 + 1);
+
+ if (index2 == -1) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, "invalid mms item id");
+ }
+
+ Fc fc = Fc.fromString(mmsItemId.substring(index1 + 1, index2));
+
+ if (fc == null) {
+ throw new ServiceError(
+ ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
+ "unknown functional constraint: " + mmsItemId.substring(index1 + 1, index2));
+ }
+
+ index1 = index2;
+
+ index2 = mmsItemId.indexOf('$', index1 + 1);
+
+ if (index2 == -1) {
+ if (fc == Fc.RP) {
+ return ln.getUrcb(mmsItemId.substring(index1 + 1));
+ }
+ if (fc == Fc.BR) {
+ return ln.getBrcb(mmsItemId.substring(index1 + 1));
+ }
+ return (FcModelNode) ln.getChild(mmsItemId.substring(index1 + 1), fc);
+ }
+
+ if (fc == Fc.RP) {
+ modelNode = ln.getUrcb(mmsItemId.substring(index1 + 1, index2));
+ } else if (fc == Fc.BR) {
+ modelNode = ln.getBrcb(mmsItemId.substring(index1 + 1, index2));
+ } else {
+ modelNode = ln.getChild(mmsItemId.substring(index1 + 1, index2), fc);
+ }
+
+ index1 = index2;
+ index2 = mmsItemId.indexOf('$', index1 + 1);
+ while (index2 != -1) {
+ modelNode = modelNode.getChild(mmsItemId.substring(index1 + 1, index2));
+ index1 = index2;
+ index2 = mmsItemId.indexOf('$', index1 + 1);
+ }
+
+ modelNode = modelNode.getChild(mmsItemId.substring(index1 + 1));
+
+ if (variableDef.getAlternateAccess() == null) {
+ // no array is in this node path
+ return (FcModelNode) modelNode;
+ }
+
+ AlternateAccessSelection altAccIt =
+ variableDef.getAlternateAccess().getCHOICE().get(0).getUnnamed();
+
+ if (altAccIt.getSelectAlternateAccess() != null) {
+ // path to node below an array element
+ modelNode =
+ ((Array) modelNode)
+ .getChild(
+ altAccIt.getSelectAlternateAccess().getAccessSelection().getIndex().intValue());
+
+ String mmsSubArrayItemId =
+ altAccIt
+ .getSelectAlternateAccess()
+ .getAlternateAccess()
+ .getCHOICE()
+ .get(0)
+ .getUnnamed()
+ .getSelectAccess()
+ .getComponent()
+ .getBasic()
+ .toString();
+
+ index1 = -1;
+ index2 = mmsSubArrayItemId.indexOf('$');
+ while (index2 != -1) {
+ modelNode = modelNode.getChild(mmsSubArrayItemId.substring(index1 + 1, index2));
+ index1 = index2;
+ index2 = mmsItemId.indexOf('$', index1 + 1);
+ }
+
+ return (FcModelNode) modelNode.getChild(mmsSubArrayItemId.substring(index1 + 1));
+ } else {
+ // path to an array element
+ return (FcModelNode)
+ ((Array) modelNode).getChild(altAccIt.getSelectAccess().getIndex().intValue());
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServerSap.java b/src/main/java/com/beanit/iec61850bean/ServerSap.java
new file mode 100644
index 0000000..722de00
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServerSap.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import com.beanit.josistack.AcseAssociation;
+import com.beanit.josistack.ServerAcseSap;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import javax.net.ServerSocketFactory;
+
+/**
+ * The ServerSap
class represents the IEC 61850 service access point for server
+ * applications. It corresponds to the AccessPoint defined in the ICD/SCL file. A server application
+ * that is to listen for client connections should first get an instance of ServerSap
+ * using the static function ServerSap.getSapsFromSclFile(). Next all the necessary configuration
+ * parameters can be set. Finally the startListening
function is called to listen for
+ * client associations. Changing properties of a ServerSap after starting to listen is not
+ * recommended and has unknown effects.
+ */
+public final class ServerSap {
+
+ static final int MINIMUM_MMS_PDU_SIZE = 64;
+ private static final int MAXIMUM_MMS_PDU_SIZE = 65000;
+ final ServerModel serverModel;
+ final List associations = new ArrayList<>();
+ byte[] servicesSupportedCalled =
+ new byte[] {(byte) 0xee, 0x1c, 0, 0, 0x04, 0x08, 0, 0, 0x79, (byte) 0xef, 0x18};
+ byte[] cbbBitString = {(byte) 0xfb, 0x00};
+ ServerEventListener serverEventListener;
+ Timer timer;
+ boolean listening = false;
+ private int proposedMaxMmsPduSize = 65000;
+ private int proposedMaxServOutstandingCalling = 5;
+ private int proposedMaxServOutstandingCalled = 5;
+ private int proposedDataStructureNestingLevel = 10;
+ private int maxAssociations = 100;
+ private ServerAcseSap acseSap;
+ private int port = 102;
+ private int backlog = 0;
+ private InetAddress bindAddr = null;
+ private ServerSocketFactory serverSocketFactory = null;
+
+ /**
+ * Creates a ServerSap.
+ *
+ * @param port local port to listen on for new connections
+ * @param backlog The maximum queue length for incoming connection indications (a request to
+ * connect) is set to the backlog parameter. If a connection indication arrives when the queue
+ * is full, the connection is refused. Set to 0 or less for the default value.
+ * @param bindAddr local IP address to bind to, pass null to bind to all
+ * @param serverModel the server model
+ * @param serverSocketFactory the factory class to generate the ServerSocket. Could be used to
+ * create SSLServerSockets. null = default
+ */
+ public ServerSap(
+ int port,
+ int backlog,
+ InetAddress bindAddr,
+ ServerModel serverModel,
+ ServerSocketFactory serverSocketFactory) {
+ this.port = port;
+ this.backlog = backlog;
+ this.bindAddr = bindAddr;
+ this.serverSocketFactory = serverSocketFactory;
+ this.serverModel = serverModel;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Sets local port to listen on for new connections.
+ *
+ * @param port local port to listen on for new connections
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public int getBacklog() {
+ return backlog;
+ }
+
+ /**
+ * Sets the maximum queue length for incoming connection indications (a request to connect) is set
+ * to the backlog parameter. If a connection indication arrives when the queue is full, the
+ * connection is refused. Set to 0 or less for the default value.
+ *
+ * @param backlog the maximum queue length for incoming connections.
+ */
+ public void setBacklog(int backlog) {
+ this.backlog = backlog;
+ }
+
+ public InetAddress getBindAddress() {
+ return bindAddr;
+ }
+
+ /**
+ * Sets the local IP address to bind to, pass null to bind to all
+ *
+ * @param bindAddr the local IP address to bind to
+ */
+ public void setBindAddress(InetAddress bindAddr) {
+ this.bindAddr = bindAddr;
+ }
+
+ /**
+ * Sets the factory class to generate the ServerSocket. The ServerSocketFactory could be used to
+ * create SSLServerSockets. Set to null
to use ServerSocketFactory.getDefault()
+ *
.
+ *
+ * @param serverSocketFactory the factory class to generate the ServerSocket.
+ */
+ public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) {
+ this.serverSocketFactory = serverSocketFactory;
+ }
+
+ /**
+ * Gets the maximum MMS PDU size.
+ *
+ * @return the maximum MMS PDU size.
+ */
+ public int getMaxMmsPduSize() {
+ return proposedMaxMmsPduSize;
+ }
+
+ /**
+ * Sets the maximum MMS PDU size in bytes that the server will support. If the client requires the
+ * use of a smaller maximum MMS PDU size, then the smaller size will be accepted by the server.
+ * The default size is 65000.
+ *
+ * @param size cannot be less than 64. The upper limit is 65000 so that segmentation at the lower
+ * transport layer is avoided. The Transport Layer's maximum PDU size is 65531.
+ */
+ public void setMaxMmsPduSize(int size) {
+ if (size >= MINIMUM_MMS_PDU_SIZE && size <= MAXIMUM_MMS_PDU_SIZE) {
+ proposedMaxMmsPduSize = size;
+ } else {
+ throw new IllegalArgumentException("maximum size is out of bound");
+ }
+ }
+
+ /**
+ * Set the maximum number of associations that are allowed in parallel by the server.
+ *
+ * @param maxAssociations the number of associations allowed (default is 100)
+ */
+ public void setMaxAssociations(int maxAssociations) {
+ this.maxAssociations = maxAssociations;
+ }
+
+ /**
+ * Sets the message fragment timeout. This is the timeout that the socket timeout is set to after
+ * the first byte of a message has been received. If such a timeout is thrown, the
+ * association/socket is closed.
+ *
+ * @param timeout the message fragment timeout in milliseconds. The default is 60000.
+ */
+ public void setMessageFragmentTimeout(int timeout) {
+ acseSap.serverTSap.setMessageFragmentTimeout(timeout);
+ }
+
+ /**
+ * Gets the ProposedMaxServOutstandingCalling parameter.
+ *
+ * @return the ProposedMaxServOutstandingCalling parameter.
+ */
+ public int getProposedMaxServOutstandingCalling() {
+ return proposedMaxServOutstandingCalling;
+ }
+
+ /**
+ * Sets the ProposedMaxServOutstandingCalling parameter. The given parameter has no affect on the
+ * functionality of this server.
+ *
+ * @param maxCalling the ProposedMaxServOutstandingCalling parameter. The default is 5.
+ */
+ public void setProposedMaxServOutstandingCalling(int maxCalling) {
+ proposedMaxServOutstandingCalling = maxCalling;
+ }
+
+ /**
+ * Gets the ProposedMaxServOutstandingCalled parameter.
+ *
+ * @return the ProposedMaxServOutstandingCalled parameter.
+ */
+ public int getProposedMaxServOutstandingCalled() {
+ return proposedMaxServOutstandingCalled;
+ }
+
+ /**
+ * Sets the ProposedMaxServOutstandingCalled parameter.The given parameter has no affect on the
+ * functionality of this server.
+ *
+ * @param maxCalled the ProposedMaxServOutstandingCalled parameter. The default is 5.
+ */
+ public void setProposedMaxServOutstandingCalled(int maxCalled) {
+ proposedMaxServOutstandingCalled = maxCalled;
+ }
+
+ /**
+ * Gets the ProposedDataStructureNestingLevel parameter.
+ *
+ * @return the ProposedDataStructureNestingLevel parameter.
+ */
+ public int getProposedDataStructureNestingLevel() {
+ return proposedDataStructureNestingLevel;
+ }
+
+ /**
+ * Sets the ProposedDataStructureNestingLevel parameter. The given parameter has no affect on the
+ * functionality of this server.runServer
+ *
+ * @param nestingLevel the ProposedDataStructureNestingLevel parameter. The default is 10.
+ */
+ public void setProposedDataStructureNestingLevel(int nestingLevel) {
+ proposedDataStructureNestingLevel = nestingLevel;
+ }
+
+ /**
+ * Gets the ServicesSupportedCalled parameter.
+ *
+ * @return the ServicesSupportedCalled parameter.
+ */
+ public byte[] getServicesSupportedCalled() {
+ return servicesSupportedCalled;
+ }
+
+ /**
+ * Sets the SevicesSupportedCalled parameter. The given parameter has no affect on the
+ * functionality of this server.
+ *
+ * @param services the ServicesSupportedCalled parameter
+ */
+ public void setServicesSupportedCalled(byte[] services) {
+ if (services.length != 11) {
+ throw new IllegalArgumentException("The services parameter needs to be of lenth 11");
+ }
+ servicesSupportedCalled = services;
+ }
+
+ /**
+ * Creates a server socket waiting on the configured port for incoming association requests.
+ *
+ * @param serverEventListener the listener that is notified of incoming writes and when the server
+ * stopped listening for new connections.
+ * @throws IOException if an error occurs binding to the port.
+ */
+ public void startListening(ServerEventListener serverEventListener) throws IOException {
+ timer = new Timer();
+ if (serverSocketFactory == null) {
+ serverSocketFactory = ServerSocketFactory.getDefault();
+ }
+ acseSap =
+ new ServerAcseSap(port, backlog, bindAddr, new AcseListener(this), serverSocketFactory);
+ acseSap.serverTSap.setMaxConnections(maxAssociations);
+ this.serverEventListener = serverEventListener;
+ listening = true;
+ acseSap.startListening();
+ }
+
+ /** Stops listening for new connections and closes all existing connections/associations. */
+ public void stop() {
+ acseSap.stopListening();
+ synchronized (associations) {
+ listening = false;
+ for (ServerAssociation association : associations) {
+ association.close();
+ }
+ associations.clear();
+ }
+ timer.cancel();
+ timer.purge();
+ }
+
+ void connectionIndication(AcseAssociation acseAssociation, ByteBuffer psdu) {
+
+ ServerAssociation association;
+ synchronized (associations) {
+ if (listening) {
+ association = new ServerAssociation(this);
+ associations.add(association);
+ } else {
+ acseAssociation.close();
+ return;
+ }
+ }
+
+ try {
+ association.handleNewAssociation(acseAssociation, psdu);
+ } catch (Exception e) {
+ // Association closed because of an unexpected exception.
+ }
+
+ association.close();
+ synchronized (associations) {
+ associations.remove(association);
+ }
+ }
+
+ void serverStoppedListeningIndication(IOException e) {
+ if (serverEventListener != null) {
+ serverEventListener.serverStoppedListening(this);
+ }
+ }
+
+ public ServerModel getModelCopy() {
+ return serverModel.copy();
+ }
+
+ public void setValues(List bdas) {
+ synchronized (serverModel) {
+ for (BasicDataAttribute bda : bdas) {
+ // if (bda.getFunctionalConstraint() != FunctionalConstraint.ST) {
+ // logger.debug("fc:" + bda.getFunctionalConstraint());
+ // throw new IllegalArgumentException(
+ // "One can only set values of BDAs with Functional Constraint ST(status)");
+ // }
+
+ BasicDataAttribute bdaMirror = bda.mirror;
+
+ if (bdaMirror.dchg && bdaMirror.chgRcbs.size() != 0 && !bda.equals(bdaMirror)) {
+ bdaMirror.setValueFrom(bda);
+ synchronized (bdaMirror.chgRcbs) {
+ for (Urcb urcb : bdaMirror.chgRcbs) {
+ if (bdaMirror.dupd && urcb.getTrgOps().isDataUpdate()) {
+ urcb.report(bdaMirror, true, false, true);
+ } else {
+ urcb.report(bdaMirror, true, false, false);
+ }
+ }
+ }
+ } else if (bdaMirror.dupd && bdaMirror.dupdRcbs.size() != 0) {
+ bdaMirror.setValueFrom(bda);
+ synchronized (bdaMirror.dupdRcbs) {
+ for (Urcb urcb : bdaMirror.dupdRcbs) {
+ urcb.report(bdaMirror, false, false, true);
+ }
+ }
+ } else if (bdaMirror.qchg && bdaMirror.chgRcbs.size() != 0 && !bda.equals(bdaMirror)) {
+ bdaMirror.setValueFrom(bda);
+ synchronized (bdaMirror.chgRcbs) {
+ for (Urcb urcb : bdaMirror.chgRcbs) {
+ urcb.report(bdaMirror, false, true, false);
+ }
+ }
+ } else {
+ bdaMirror.setValueFrom(bda);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServiceError.java b/src/main/java/com/beanit/iec61850bean/ServiceError.java
new file mode 100644
index 0000000..2f99d7f
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServiceError.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class ServiceError extends Exception {
+
+ public static final int NO_ERROR = 0;
+ public static final int INSTANCE_NOT_AVAILABLE = 1;
+ public static final int INSTANCE_IN_USE = 2;
+ public static final int ACCESS_VIOLATION = 3;
+ public static final int ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = 4;
+ public static final int PARAMETER_VALUE_INAPPROPRIATE = 5;
+ public static final int PARAMETER_VALUE_INCONSISTENT = 6;
+ public static final int CLASS_NOT_SUPPORTED = 7;
+ public static final int INSTANCE_LOCKED_BY_OTHER_CLIENT = 8;
+ public static final int CONTROL_MUST_BE_SELECTED = 9;
+ public static final int TYPE_CONFLICT = 10;
+ public static final int FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = 11;
+ public static final int FAILED_DUE_TO_SERVER_CONSTRAINT = 12;
+ public static final int APPLICATION_UNREACHABLE = 13;
+ public static final int CONNECTION_LOST = 14;
+ public static final int MEMORY_UNAVAILABLE = 15;
+ public static final int PROCESSOR_RESOURCE_UNAVAILABLE = 16;
+ public static final int FILE_NONE_EXISTENT = 17;
+ public static final int FATAL = 20;
+ // added to handle data access errors mentioned in iec61850-8-1
+ // public static final int DATA_ACCESS_ERROR = 21;
+ // added to report timeouts
+ public static final int TIMEOUT = 22;
+ public static final int UNKNOWN = 23;
+
+ private static final long serialVersionUID = 4290107163231828564L;
+ private final int errorCode;
+
+ public ServiceError(int errorCode) {
+ this(errorCode, "", null);
+ }
+
+ public ServiceError(int errorCode, String s) {
+ this(errorCode, s, null);
+ }
+
+ public ServiceError(int errorCode, Throwable cause) {
+ this(errorCode, "", cause);
+ }
+
+ public ServiceError(int errorCode, String s, Throwable cause) {
+ super(
+ "Service error: "
+ + getErrorName(errorCode)
+ + "("
+ + errorCode
+ + ")"
+ + (s.isEmpty() ? "" : (" " + s)),
+ cause);
+ this.errorCode = errorCode;
+ }
+
+ private static String getErrorName(int code) {
+ switch (code) {
+ case NO_ERROR:
+ return "NO_ERROR";
+ case INSTANCE_NOT_AVAILABLE:
+ return "INSTANCE_NOT_AVAILABLE";
+ case INSTANCE_IN_USE:
+ return "INSTANCE_IN_USE";
+ case ACCESS_VIOLATION:
+ return "ACCESS_VIOLATION";
+ case ACCESS_NOT_ALLOWED_IN_CURRENT_STATE:
+ return "ACCESS_NOT_ALLOWED_IN_CURRENT_STATE";
+ case PARAMETER_VALUE_INAPPROPRIATE:
+ return "PARAMETER_VALUE_INAPPROPRIATE";
+ case PARAMETER_VALUE_INCONSISTENT:
+ return "PARAMETER_VALUE_INCONSISTENT";
+ case CLASS_NOT_SUPPORTED:
+ return "CLASS_NOT_SUPPORTED";
+ case INSTANCE_LOCKED_BY_OTHER_CLIENT:
+ return "INSTANCE_LOCKED_BY_OTHER_CLIENT";
+ case CONTROL_MUST_BE_SELECTED:
+ return "CONTROL_MUST_BE_SELECTED";
+ case TYPE_CONFLICT:
+ return "TYPE_CONFLICT";
+ case FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT:
+ return "FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT";
+ case FAILED_DUE_TO_SERVER_CONSTRAINT:
+ return "FAILED_DUE_TO_SERVER_CONSTRAINT";
+ case APPLICATION_UNREACHABLE:
+ return "APPLICATION_UNREACHABLE";
+ case CONNECTION_LOST:
+ return "CONNECTION_LOST";
+ case MEMORY_UNAVAILABLE:
+ return "MEMORY_UNAVAILABLE";
+ case PROCESSOR_RESOURCE_UNAVAILABLE:
+ return "PROCESSOR_RESOURCE_UNAVAILABLE";
+ case FILE_NONE_EXISTENT:
+ return "FILE_NONE_EXISTENT";
+ case FATAL:
+ return "FATAL";
+ case TIMEOUT:
+ return "TIMEOUT_ERROR";
+ case UNKNOWN:
+ return "UNKNOWN";
+ default:
+ return "Unknown ServiceError code";
+ }
+ }
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/ServiceSupport.java b/src/main/java/com/beanit/iec61850bean/ServiceSupport.java
new file mode 100644
index 0000000..54ef47d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/ServiceSupport.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+public final class ServiceSupport {
+
+ public boolean dynAssociation = false;
+ public boolean getDirectory = false;
+ public boolean getDataObjectDefinition = false;
+ public boolean getDataSetValue = false;
+ public boolean dataSetDirectory = false;
+ public boolean readWrite = false;
+ public boolean getCBValues = false;
+ public boolean goose = false;
+ public int gooseMax = 0;
+}
diff --git a/src/main/java/com/beanit/iec61850bean/Urcb.java b/src/main/java/com/beanit/iec61850bean/Urcb.java
new file mode 100644
index 0000000..9d00e4c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/Urcb.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.beanit.asn1bean.ber.types.BerBitString;
+import com.beanit.iec61850bean.internal.mms.asn1.AccessResult;
+import com.beanit.iec61850bean.internal.mms.asn1.Data;
+import com.beanit.iec61850bean.internal.mms.asn1.Identifier;
+import com.beanit.iec61850bean.internal.mms.asn1.InformationReport;
+import com.beanit.iec61850bean.internal.mms.asn1.MMSpdu;
+import com.beanit.iec61850bean.internal.mms.asn1.ObjectName;
+import com.beanit.iec61850bean.internal.mms.asn1.UnconfirmedPDU;
+import com.beanit.iec61850bean.internal.mms.asn1.UnconfirmedService;
+import com.beanit.iec61850bean.internal.mms.asn1.VariableAccessSpecification;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class Urcb extends Rcb {
+
+ final HashMap membersToBeReported = new LinkedHashMap<>();
+ ServerAssociation reserved = null;
+ boolean enabled = false;
+ private Timer integrityTimer;
+ // private ScheduledFuture> integrityFuture = null;
+ private ScheduledFuture> bufTmFuture = null;
+
+ public Urcb(ObjectReference objectReference, List children) {
+ super(objectReference, Fc.RP, children);
+ }
+
+ /**
+ * Reserve URCB - The attribute Resv (if set to TRUE) shall indicate that the URCB is currently
+ * exclusively reserved for the client that has set the value to TRUE. Other clients shall not be
+ * allowed to set any attribute of that URCB.
+ *
+ * @return the Resv child
+ */
+ public BdaBoolean getResv() {
+ return (BdaBoolean) children.get("Resv");
+ }
+
+ void enable() {
+
+ for (FcModelNode dataSetMember : dataSet) {
+ for (BasicDataAttribute bda : dataSetMember.getBasicDataAttributes()) {
+ if (bda.dchg) {
+ if (getTrgOps().isDataChange()) {
+ synchronized (bda.chgRcbs) {
+ bda.chgRcbs.add(this);
+ }
+ }
+ } else if (bda.qchg) {
+ if (getTrgOps().isQualityChange()) {
+ synchronized (bda.chgRcbs) {
+ bda.chgRcbs.add(this);
+ }
+ }
+ }
+ if (bda.dupd) {
+ if (getTrgOps().isDataUpdate()) {
+ synchronized (bda.dupdRcbs) {
+ bda.dupdRcbs.add(this);
+ }
+ }
+ }
+ }
+ }
+
+ if (getTrgOps().isIntegrity() && !(getIntgPd().getValue() < 10l)) {
+ integrityTimer = new Timer();
+
+ integrityTimer.schedule(
+ new TimerTask() {
+ // integrityFuture = reserved.executor.scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (Urcb.this) {
+ if (!enabled) {
+ return;
+ }
+ reserved.sendAnMmsPdu(getMmsReport(true, false));
+ }
+ }
+ // }, getIntgPd().getValue(), getIntgPd().getValue(), TimeUnit.MILLISECONDS);
+ },
+ getIntgPd().getValue(),
+ getIntgPd().getValue());
+ }
+
+ enabled = true;
+ }
+
+ void disable() {
+
+ for (FcModelNode dataSetMember : dataSet) {
+ for (BasicDataAttribute bda : dataSetMember.getBasicDataAttributes()) {
+ if (bda.dchg) {
+ if (getTrgOps().isDataChange()) {
+ synchronized (bda.chgRcbs) {
+ bda.chgRcbs.remove(this);
+ }
+ }
+ } else if (bda.qchg) {
+ if (getTrgOps().isQualityChange()) {
+ synchronized (bda.chgRcbs) {
+ bda.chgRcbs.remove(this);
+ }
+ }
+ }
+ if (bda.dupd) {
+ if (getTrgOps().isDataUpdate()) {
+ synchronized (bda.dupdRcbs) {
+ bda.dupdRcbs.remove(this);
+ }
+ }
+ }
+ }
+ }
+
+ // if (integrityFuture != null) {
+ // integrityFuture.cancel(false);
+ // }
+ if (integrityTimer != null) {
+ integrityTimer.cancel();
+ }
+
+ enabled = false;
+ }
+
+ void generalInterrogation() {
+ reserved.executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (Urcb.this) {
+ if (!enabled) {
+ return;
+ }
+ reserved.sendAnMmsPdu(getMmsReport(false, true));
+ }
+ }
+ });
+ }
+
+ private MMSpdu getMmsReport(boolean integrity, boolean gi) {
+
+ InformationReport.ListOfAccessResult listOfAccessResult =
+ new InformationReport.ListOfAccessResult();
+
+ List accessResults = listOfAccessResult.getAccessResult();
+
+ AccessResult accessResult = new AccessResult();
+ accessResult.setSuccess(getRptId().getMmsDataObj());
+ accessResults.add(accessResult);
+
+ accessResult = new AccessResult();
+ accessResult.setSuccess(getOptFlds().getMmsDataObj());
+ accessResults.add(accessResult);
+
+ if (getOptFlds().isSequenceNumber()) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(getSqNum().getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+ getSqNum().setValue((short) (getSqNum().getValue() + 1));
+
+ if (getOptFlds().isReportTimestamp()) {
+ BdaEntryTime entryTime = new BdaEntryTime(null, null, null, false, false);
+ entryTime.setTimestamp(System.currentTimeMillis());
+
+ accessResult = new AccessResult();
+ accessResult.setSuccess(entryTime.getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+
+ if (getOptFlds().isDataSetName()) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(getDatSet().getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+
+ if (getOptFlds().isConfigRevision()) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(getConfRev().getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+
+ // segmentation not supported
+
+ List dataSetMembers = dataSet.getMembers();
+ int dataSetSize = dataSetMembers.size();
+
+ // inclusion bitstring
+ byte[] inclusionStringArray = new byte[(dataSetSize - 1) / 8 + 1];
+
+ if (integrity || gi) {
+
+ for (int i = 0; i < dataSetSize; i++) {
+ inclusionStringArray[i / 8] = (byte) (inclusionStringArray[i / 8] | 1 << (7 - i % 8));
+ }
+ BerBitString inclusionString = new BerBitString(inclusionStringArray, dataSetSize);
+
+ Data data = new Data();
+ data.setBitString(inclusionString);
+ accessResult = new AccessResult();
+ accessResult.setSuccess(data);
+ accessResults.add(accessResult);
+
+ // data reference sending not supported for now
+
+ for (FcModelNode dataSetMember : dataSetMembers) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(dataSetMember.getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+
+ BdaReasonForInclusion reasonForInclusion = new BdaReasonForInclusion(null);
+ if (integrity) {
+ reasonForInclusion.setIntegrity(true);
+ } else {
+ reasonForInclusion.setGeneralInterrogation(true);
+ }
+
+ if (getOptFlds().isReasonForInclusion()) {
+ for (int i = 0; i < dataSetMembers.size(); i++) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(reasonForInclusion.getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+ }
+
+ } else {
+
+ int index = 0;
+ for (FcModelNode dataSetMember : dataSet) {
+ if (membersToBeReported.get(dataSetMember) != null) {
+ inclusionStringArray[index / 8] =
+ (byte) (inclusionStringArray[index / 8] | 1 << (7 - index % 8));
+ }
+ index++;
+ }
+ BerBitString inclusionString = new BerBitString(inclusionStringArray, dataSetSize);
+
+ Data data = new Data();
+ data.setBitString(inclusionString);
+ accessResult = new AccessResult();
+ accessResult.setSuccess(data);
+ accessResults.add(accessResult);
+
+ // data reference sending not supported for now
+
+ for (FcModelNode dataSetMember : dataSetMembers) {
+ if (membersToBeReported.get(dataSetMember) != null) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(dataSetMember.getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+ }
+
+ if (getOptFlds().isReasonForInclusion()) {
+ for (FcModelNode dataSetMember : dataSetMembers) {
+ BdaReasonForInclusion reasonForInclusion = membersToBeReported.get(dataSetMember);
+ if (reasonForInclusion != null) {
+ accessResult = new AccessResult();
+ accessResult.setSuccess(reasonForInclusion.getMmsDataObj());
+ accessResults.add(accessResult);
+ }
+ }
+ }
+
+ membersToBeReported.clear();
+ bufTmFuture = null;
+ }
+
+ ObjectName objectName = new ObjectName();
+ objectName.setVmdSpecific(new Identifier("RPT".getBytes(UTF_8)));
+
+ VariableAccessSpecification varAccSpec = new VariableAccessSpecification();
+ varAccSpec.setVariableListName(objectName);
+
+ InformationReport infoReport = new InformationReport();
+ infoReport.setVariableAccessSpecification(varAccSpec);
+ infoReport.setListOfAccessResult(listOfAccessResult);
+
+ UnconfirmedService unconfirmedService = new UnconfirmedService();
+ unconfirmedService.setInformationReport(infoReport);
+
+ UnconfirmedPDU unconfirmedPDU = new UnconfirmedPDU();
+ unconfirmedPDU.setService(unconfirmedService);
+
+ MMSpdu mmsPdu = new MMSpdu();
+ mmsPdu.setUnconfirmedPDU(unconfirmedPDU);
+
+ return mmsPdu;
+ }
+
+ @Override
+ public FcDataObject copy() {
+ List childCopies = new ArrayList<>(children.size());
+ for (ModelNode childNode : children.values()) {
+ childCopies.add((FcModelNode) childNode.copy());
+ }
+ Urcb urcb = new Urcb(objectReference, childCopies);
+ urcb.dataSet = dataSet;
+ return urcb;
+ }
+
+ void report(BasicDataAttribute bda, boolean dchg, boolean qchg, boolean dupd) {
+
+ synchronized (this) {
+ if (!enabled) {
+ return;
+ }
+
+ FcModelNode memberFound = null;
+ FcModelNode fcModelNode = bda;
+ while (memberFound == null) {
+ for (FcModelNode member : dataSet) {
+ if (member == fcModelNode) {
+ memberFound = fcModelNode;
+ break;
+ }
+ }
+ if (memberFound != null) {
+ break;
+ }
+ if (!(fcModelNode.parent instanceof FcModelNode)) {
+ // Unable to report Basic Data Attribute because it is not part of the referenced data set
+ return;
+ }
+ fcModelNode = (FcModelNode) fcModelNode.parent;
+ }
+
+ BdaReasonForInclusion reasonForInclusion = membersToBeReported.get(fcModelNode);
+ if (reasonForInclusion == null) {
+ reasonForInclusion = new BdaReasonForInclusion(null);
+ membersToBeReported.put(fcModelNode, reasonForInclusion);
+ }
+
+ if (dchg) {
+ reasonForInclusion.setDataChange(true);
+ }
+ if (dupd) {
+ reasonForInclusion.setDataUpdate(true);
+ } else if (qchg) {
+ reasonForInclusion.setQualityChange(true);
+ }
+
+ // if bufTmFuture is not null then it is already scheduled and will send the combined report
+ if (bufTmFuture == null) {
+ bufTmFuture =
+ reserved.executor.schedule(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (Urcb.this) {
+ if (!enabled) {
+ return;
+ }
+ reserved.sendAnMmsPdu(getMmsReport(false, false));
+ }
+ }
+ },
+ getBufTm().getValue(),
+ TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/app/ConsoleClient.java b/src/main/java/com/beanit/iec61850bean/app/ConsoleClient.java
new file mode 100644
index 0000000..a8cb403
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/app/ConsoleClient.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.app;
+
+import com.beanit.iec61850bean.BdaTriggerConditions;
+import com.beanit.iec61850bean.Brcb;
+import com.beanit.iec61850bean.ClientAssociation;
+import com.beanit.iec61850bean.ClientEventListener;
+import com.beanit.iec61850bean.ClientSap;
+import com.beanit.iec61850bean.DataSet;
+import com.beanit.iec61850bean.Fc;
+import com.beanit.iec61850bean.FcModelNode;
+import com.beanit.iec61850bean.ModelNode;
+import com.beanit.iec61850bean.Report;
+import com.beanit.iec61850bean.SclParseException;
+import com.beanit.iec61850bean.SclParser;
+import com.beanit.iec61850bean.ServerModel;
+import com.beanit.iec61850bean.ServiceError;
+import com.beanit.iec61850bean.Urcb;
+import com.beanit.iec61850bean.internal.cli.Action;
+import com.beanit.iec61850bean.internal.cli.ActionException;
+import com.beanit.iec61850bean.internal.cli.ActionListener;
+import com.beanit.iec61850bean.internal.cli.ActionProcessor;
+import com.beanit.iec61850bean.internal.cli.CliParameter;
+import com.beanit.iec61850bean.internal.cli.CliParameterBuilder;
+import com.beanit.iec61850bean.internal.cli.CliParseException;
+import com.beanit.iec61850bean.internal.cli.CliParser;
+import com.beanit.iec61850bean.internal.cli.IntCliParameter;
+import com.beanit.iec61850bean.internal.cli.StringCliParameter;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConsoleClient {
+
+ private static final String PRINT_MODEL_KEY = "m";
+ private static final String PRINT_MODEL_KEY_DESCRIPTION = "print model";
+ private static final String GET_DATA_VALUES_KEY = "g";
+ private static final String GET_DATA_VALUES_KEY_DESCRIPTION = "send GetDataValues request";
+ private static final String READ_ALL_DATA_KEY = "ga";
+ private static final String READ_ALL_DATA_KEY_DESCRIPTION = "update all data in the model";
+ private static final String CREATE_DATA_SET_KEY = "cds";
+ private static final String CREATE_DATA_SET_KEY_DESCRIPTION = "create data set";
+ private static final String DELETE_DATA_SET_KEY = "dds";
+ private static final String DELETE_DATA_SET_KEY_DESCRIPTION = "delete data set";
+ private static final String REPORTING_KEY = "r";
+ private static final String REPORTING_KEY_DESCRIPTION = "configure reporting";
+
+ private static final StringCliParameter hostParam =
+ new CliParameterBuilder("-h")
+ .setDescription("The IP/domain address of the server you want to access.")
+ .setMandatory()
+ .buildStringParameter("host");
+ private static final IntCliParameter portParam =
+ new CliParameterBuilder("-p")
+ .setDescription("The port to connect to.")
+ .buildIntParameter("port", 102);
+ private static final StringCliParameter modelFileParam =
+ new CliParameterBuilder("-m")
+ .setDescription(
+ "The file name of the SCL file to read the model from. If this parameter is omitted the model will be read from the server device after connection.")
+ .buildStringParameter("model-file");
+ private static final ActionProcessor actionProcessor = new ActionProcessor(new ActionExecutor());
+ private static volatile ClientAssociation association;
+ private static ServerModel serverModel;
+
+ public static void main(String[] args) {
+
+ List cliParameters = new ArrayList<>();
+ cliParameters.add(hostParam);
+ cliParameters.add(portParam);
+ cliParameters.add(modelFileParam);
+
+ CliParser cliParser =
+ new CliParser(
+ "iec61850bean-console-client", "A client application to access IEC 61850 MMS servers.");
+ cliParser.addParameters(cliParameters);
+
+ try {
+ cliParser.parseArguments(args);
+ } catch (CliParseException e1) {
+ System.err.println("Error parsing command line parameters: " + e1.getMessage());
+ System.out.println(cliParser.getUsageString());
+ System.exit(1);
+ }
+
+ InetAddress address;
+ try {
+ address = InetAddress.getByName(hostParam.getValue());
+ } catch (UnknownHostException e) {
+ System.out.println("Unknown host: " + hostParam.getValue());
+ return;
+ }
+
+ ClientSap clientSap = new ClientSap();
+
+ try {
+ association = clientSap.associate(address, portParam.getValue(), null, new EventListener());
+ } catch (IOException e) {
+ System.out.println("Unable to connect to remote host.");
+ return;
+ }
+
+ Runtime.getRuntime()
+ .addShutdownHook(
+ new Thread() {
+ @Override
+ public void run() {
+ association.close();
+ }
+ });
+
+ System.out.println("successfully connected");
+
+ if (modelFileParam.isSelected()) {
+ System.out.println("reading model from file...");
+
+ try {
+ serverModel = SclParser.parse(modelFileParam.getValue()).get(0);
+ } catch (SclParseException e1) {
+ System.out.println("Error parsing SCL file: " + e1.getMessage());
+ return;
+ }
+
+ association.setServerModel(serverModel);
+
+ System.out.println("successfully read model");
+
+ } else {
+ System.out.println("retrieving model...");
+
+ try {
+ serverModel = association.retrieveModel();
+ } catch (ServiceError e) {
+ System.out.println("Service error: " + e.getMessage());
+ return;
+ } catch (IOException e) {
+ System.out.println("Fatal error: " + e.getMessage());
+ return;
+ }
+
+ System.out.println("successfully read model");
+ }
+
+ actionProcessor.addAction(new Action(PRINT_MODEL_KEY, PRINT_MODEL_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(GET_DATA_VALUES_KEY, GET_DATA_VALUES_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(READ_ALL_DATA_KEY, READ_ALL_DATA_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(CREATE_DATA_SET_KEY, CREATE_DATA_SET_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(DELETE_DATA_SET_KEY, DELETE_DATA_SET_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(REPORTING_KEY, REPORTING_KEY_DESCRIPTION));
+
+ actionProcessor.start();
+ }
+
+ private static class EventListener implements ClientEventListener {
+
+ @Override
+ public void newReport(Report report) {
+ System.out.println("\n----------------");
+ System.out.println("Received report: ");
+ System.err.println(report);
+ System.out.println("------------------");
+ }
+
+ @Override
+ public void associationClosed(IOException e) {
+ System.out.print("Received connection closed signal. Reason: ");
+ if (!e.getMessage().isEmpty()) {
+ System.out.println(e.getMessage());
+ } else {
+ System.out.println("unknown");
+ }
+ actionProcessor.close();
+ }
+ }
+
+ private static class ActionExecutor implements ActionListener {
+
+ @Override
+ public void actionCalled(String actionKey) throws ActionException {
+ try {
+ switch (actionKey) {
+ case PRINT_MODEL_KEY:
+ System.out.println(serverModel);
+ break;
+ case READ_ALL_DATA_KEY:
+ System.out.print("Reading all data...");
+ try {
+ association.getAllDataValues();
+ } catch (ServiceError e) {
+ System.err.println("Service error: " + e.getMessage());
+ }
+ System.out.println("done");
+ break;
+ case GET_DATA_VALUES_KEY:
+ {
+ if (serverModel == null) {
+ System.out.println("You have to retrieve the model before reading data.");
+ return;
+ }
+
+ FcModelNode fcModelNode = askForFcModelNode();
+
+ System.out.println("Sending GetDataValues request...");
+
+ try {
+ association.getDataValues(fcModelNode);
+ } catch (ServiceError e) {
+ System.out.println("Service error: " + e.getMessage());
+ return;
+ } catch (IOException e) {
+ System.out.println("Fatal error: " + e.getMessage());
+ return;
+ }
+
+ System.out.println("Successfully read data.");
+ System.out.println(fcModelNode);
+
+ break;
+ }
+ case CREATE_DATA_SET_KEY:
+ {
+ System.out.println(
+ "Enter the reference of the data set to create (e.g. myld/MYLN0.dataset1): ");
+ String reference = actionProcessor.getReader().readLine();
+
+ System.out.println("How many entries shall the data set have: ");
+ String numberOfEntriesString = actionProcessor.getReader().readLine();
+ int numDataSetEntries = Integer.parseInt(numberOfEntriesString);
+
+ List dataSetMembers = new ArrayList<>();
+ for (int i = 0; i < numDataSetEntries; i++) {
+ dataSetMembers.add(askForFcModelNode());
+ }
+
+ DataSet dataSet = new DataSet(reference, dataSetMembers);
+ System.out.print("Creating data set..");
+ association.createDataSet(dataSet);
+ System.out.println("done");
+
+ break;
+ }
+ case DELETE_DATA_SET_KEY:
+ {
+ System.out.println(
+ "Enter the reference of the data set to delete (e.g. myld/MYLN0.dataset1): ");
+ String reference = actionProcessor.getReader().readLine();
+
+ DataSet dataSet = serverModel.getDataSet(reference);
+ if (dataSet == null) {
+ throw new ActionException("Unable to find data set with the given reference.");
+ }
+ System.out.print("Deleting data set..");
+ association.deleteDataSet(dataSet);
+ System.out.println("done");
+
+ break;
+ }
+ case REPORTING_KEY:
+ {
+ System.out.println("Enter the URCB reference: ");
+ String reference = actionProcessor.getReader().readLine();
+ Urcb urcb = serverModel.getUrcb(reference);
+ if (urcb == null) {
+ Brcb brcb = serverModel.getBrcb(reference);
+ if (brcb != null) {
+ throw new ActionException(
+ "Though buffered reporting is supported by the library it is not yet supported by the console application.");
+ }
+ throw new ActionException("Unable to find RCB with the given reference.");
+ }
+
+ while (true) {
+ association.getRcbValues(urcb);
+ System.out.println();
+ System.out.println(urcb);
+ System.out.println();
+ System.out.println("What do you want to configure?");
+ System.out.println("1 - reserve");
+ System.out.println("2 - cancel reservation");
+ System.out.println("3 - enable");
+ System.out.println("4 - disable");
+ System.out.println("5 - set data set");
+ System.out.println("6 - set trigger options");
+ System.out.println("7 - set integrity period");
+ System.out.println("8 - send general interrogation");
+ System.out.println("0 - quit");
+ try {
+ int rcbAction = Integer.parseInt(actionProcessor.getReader().readLine());
+ switch (rcbAction) {
+ case 0:
+ return;
+ case 1:
+ System.out.print("Reserving RCB..");
+ association.reserveUrcb(urcb);
+ System.out.println("done");
+ break;
+ case 2:
+ System.out.print("Canceling RCB reservation..");
+ association.cancelUrcbReservation(urcb);
+ System.out.println("done");
+ break;
+ case 3:
+ System.out.print("Enabling reporting..");
+ association.enableReporting(urcb);
+ System.out.println("done");
+ break;
+ case 4:
+ System.out.print("Disabling reporting..");
+ association.disableReporting(urcb);
+ System.out.println("done");
+ break;
+ case 5:
+ {
+ System.out.print("Set data set reference:");
+ String dataSetReference = actionProcessor.getReader().readLine();
+ urcb.getDatSet().setValue(dataSetReference);
+ List serviceErrors =
+ association.setRcbValues(
+ urcb, false, true, false, false, false, false, false, false);
+ if (serviceErrors.get(0) != null) {
+ throw serviceErrors.get(0);
+ }
+ System.out.println("done");
+ break;
+ }
+ case 6:
+ {
+ System.out.print(
+ "Set the trigger options (data change, data update, quality change, interity, GI):");
+ String triggerOptionsString = actionProcessor.getReader().readLine();
+ String[] triggerOptionsStrings = triggerOptionsString.split(",", -1);
+ BdaTriggerConditions triggerOptions = urcb.getTrgOps();
+ triggerOptions.setDataChange(
+ Boolean.parseBoolean(triggerOptionsStrings[0]));
+ triggerOptions.setDataUpdate(
+ Boolean.parseBoolean(triggerOptionsStrings[1]));
+ triggerOptions.setQualityChange(
+ Boolean.parseBoolean(triggerOptionsStrings[2]));
+ triggerOptions.setIntegrity(Boolean.parseBoolean(triggerOptionsStrings[3]));
+ triggerOptions.setGeneralInterrogation(
+ Boolean.parseBoolean(triggerOptionsStrings[4]));
+ List serviceErrors =
+ association.setRcbValues(
+ urcb, false, false, false, false, true, false, false, false);
+ if (serviceErrors.get(0) != null) {
+ throw serviceErrors.get(0);
+ }
+ System.out.println("done");
+ break;
+ }
+ case 7:
+ {
+ System.out.print("Specify integrity period in ms:");
+ String integrityPeriodString = actionProcessor.getReader().readLine();
+ urcb.getIntgPd().setValue(Long.parseLong(integrityPeriodString));
+ List serviceErrors =
+ association.setRcbValues(
+ urcb, false, false, false, false, false, true, false, false);
+ if (serviceErrors.get(0) != null) {
+ throw serviceErrors.get(0);
+ }
+ System.out.println("done");
+ break;
+ }
+ case 8:
+ System.out.print("Sending GI..");
+ association.startGi(urcb);
+ System.out.println("done");
+ break;
+ default:
+ System.err.println("Unknown option.");
+ break;
+ }
+ } catch (ServiceError e) {
+ System.err.println("Service error: " + e.getMessage());
+ } catch (NumberFormatException e) {
+ System.err.println("Cannot parse number: " + e.getMessage());
+ }
+ }
+ }
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ throw new ActionException(e);
+ }
+ }
+
+ private FcModelNode askForFcModelNode() throws IOException, ActionException {
+ System.out.println("Enter reference (e.g. myld/MYLN0.do.da.bda): ");
+ String reference = actionProcessor.getReader().readLine();
+ System.out.println("Enter functional constraint of referenced node: ");
+ String fcString = actionProcessor.getReader().readLine();
+
+ Fc fc = Fc.fromString(fcString);
+ if (fc == null) {
+ throw new ActionException("Unknown functional constraint.");
+ }
+
+ ModelNode modelNode = serverModel.findModelNode(reference, Fc.fromString(fcString));
+ if (modelNode == null) {
+ throw new ActionException(
+ "A model node with the given reference and functional constraint could not be found.");
+ }
+
+ if (!(modelNode instanceof FcModelNode)) {
+ throw new ActionException(
+ "The given model node is not a functionally constraint model node.");
+ }
+
+ FcModelNode fcModelNode = (FcModelNode) modelNode;
+ return fcModelNode;
+ }
+
+ @Override
+ public void quit() {
+ System.out.println("** Closing connection.");
+ association.close();
+ return;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/app/ConsoleServer.java b/src/main/java/com/beanit/iec61850bean/app/ConsoleServer.java
new file mode 100644
index 0000000..59049c9
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/app/ConsoleServer.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.app;
+
+import com.beanit.iec61850bean.BasicDataAttribute;
+import com.beanit.iec61850bean.BdaBoolean;
+import com.beanit.iec61850bean.BdaFloat32;
+import com.beanit.iec61850bean.BdaFloat64;
+import com.beanit.iec61850bean.BdaInt16;
+import com.beanit.iec61850bean.BdaInt16U;
+import com.beanit.iec61850bean.BdaInt32;
+import com.beanit.iec61850bean.BdaInt32U;
+import com.beanit.iec61850bean.BdaInt64;
+import com.beanit.iec61850bean.BdaInt8;
+import com.beanit.iec61850bean.BdaInt8U;
+import com.beanit.iec61850bean.Fc;
+import com.beanit.iec61850bean.ModelNode;
+import com.beanit.iec61850bean.SclParseException;
+import com.beanit.iec61850bean.SclParser;
+import com.beanit.iec61850bean.ServerEventListener;
+import com.beanit.iec61850bean.ServerModel;
+import com.beanit.iec61850bean.ServerSap;
+import com.beanit.iec61850bean.ServiceError;
+import com.beanit.iec61850bean.internal.cli.Action;
+import com.beanit.iec61850bean.internal.cli.ActionException;
+import com.beanit.iec61850bean.internal.cli.ActionListener;
+import com.beanit.iec61850bean.internal.cli.ActionProcessor;
+import com.beanit.iec61850bean.internal.cli.CliParameter;
+import com.beanit.iec61850bean.internal.cli.CliParameterBuilder;
+import com.beanit.iec61850bean.internal.cli.CliParseException;
+import com.beanit.iec61850bean.internal.cli.CliParser;
+import com.beanit.iec61850bean.internal.cli.IntCliParameter;
+import com.beanit.iec61850bean.internal.cli.StringCliParameter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConsoleServer {
+
+ private static final String WRITE_VALUE_KEY = "w";
+ private static final String WRITE_VALUE_KEY_DESCRIPTION = "write value to model node";
+
+ private static final String PRINT_SERVER_MODEL_KEY = "p";
+ private static final String PRINT_SERVER_MODEL_KEY_DESCRIPTION = "print server's model";
+
+ private static final IntCliParameter portParam =
+ new CliParameterBuilder("-p")
+ .setDescription(
+ "The port to listen on. On unix based systems you need root privilages for ports < 1000.")
+ .buildIntParameter("port", 102);
+
+ private static final StringCliParameter modelFileParam =
+ new CliParameterBuilder("-m")
+ .setDescription("The SCL file that contains the server's information model.")
+ .setMandatory()
+ .buildStringParameter("model-file");
+ private static final ActionProcessor actionProcessor = new ActionProcessor(new ActionExecutor());
+ private static ServerModel serverModel;
+ private static ServerSap serverSap = null;
+
+ public static void main(String[] args) throws IOException {
+
+ List cliParameters = new ArrayList<>();
+ cliParameters.add(modelFileParam);
+ cliParameters.add(portParam);
+
+ CliParser cliParser =
+ new CliParser("iec61850bean-console-server", "An IEC 61850 MMS console server.");
+ cliParser.addParameters(cliParameters);
+
+ try {
+ cliParser.parseArguments(args);
+ } catch (CliParseException e1) {
+ System.err.println("Error parsing command line parameters: " + e1.getMessage());
+ System.out.println(cliParser.getUsageString());
+ System.exit(1);
+ }
+
+ List serverModels = null;
+ try {
+ serverModels = SclParser.parse(modelFileParam.getValue());
+ } catch (SclParseException e) {
+ System.out.println("Error parsing SCL/ICD file: " + e.getMessage());
+ return;
+ }
+
+ serverSap = new ServerSap(102, 0, null, serverModels.get(0), null);
+ serverSap.setPort(portParam.getValue());
+
+ Runtime.getRuntime()
+ .addShutdownHook(
+ new Thread() {
+ @Override
+ public void run() {
+ if (serverSap != null) {
+ serverSap.stop();
+ }
+ System.out.println("Server was stopped.");
+ }
+ });
+
+ serverModel = serverSap.getModelCopy();
+
+ serverSap.startListening(new EventListener());
+
+ actionProcessor.addAction(
+ new Action(PRINT_SERVER_MODEL_KEY, PRINT_SERVER_MODEL_KEY_DESCRIPTION));
+ actionProcessor.addAction(new Action(WRITE_VALUE_KEY, WRITE_VALUE_KEY_DESCRIPTION));
+
+ actionProcessor.start();
+ }
+
+ private static class EventListener implements ServerEventListener {
+
+ @Override
+ public void serverStoppedListening(ServerSap serverSap) {
+ System.out.println("The SAP stopped listening");
+ }
+
+ @Override
+ public List write(List bdas) {
+ for (BasicDataAttribute bda : bdas) {
+ System.out.println("got a write request: " + bda);
+ }
+ return null;
+ }
+ }
+
+ private static class ActionExecutor implements ActionListener {
+
+ @Override
+ public void actionCalled(String actionKey) throws ActionException {
+ try {
+ switch (actionKey) {
+ case PRINT_SERVER_MODEL_KEY:
+ System.out.println("** Printing model.");
+
+ System.out.println(serverModel);
+
+ break;
+ case WRITE_VALUE_KEY:
+ System.out.println("Enter reference to write (e.g. myld/MYLN0.do.da.bda): ");
+ String reference = actionProcessor.getReader().readLine();
+ System.out.println("Enter functional constraint of referenced node: ");
+ String fcString = actionProcessor.getReader().readLine();
+
+ Fc fc = Fc.fromString(fcString);
+ if (fc == null) {
+ System.out.println("Unknown functional constraint.");
+ return;
+ }
+
+ ModelNode modelNode = serverModel.findModelNode(reference, Fc.fromString(fcString));
+ if (modelNode == null) {
+ System.out.println(
+ "A model node with the given reference and functional constraint could not be found.");
+ return;
+ }
+
+ if (!(modelNode instanceof BasicDataAttribute)) {
+ System.out.println("The given model node is not a basic data attribute.");
+ return;
+ }
+
+ BasicDataAttribute bda =
+ (BasicDataAttribute) serverModel.findModelNode(reference, Fc.fromString(fcString));
+
+ System.out.println("Enter value to write: ");
+ String valueString = actionProcessor.getReader().readLine();
+
+ try {
+ setBdaValue(bda, valueString);
+ } catch (Exception e) {
+ System.out.println(
+ "The console server does not support writing this type of basic data attribute.");
+ return;
+ }
+
+ List bdas = new ArrayList<>();
+ bdas.add(bda);
+ serverSap.setValues(bdas);
+
+ System.out.println("Successfully wrote data.");
+ System.out.println(bda);
+
+ break;
+
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ throw new ActionException(e);
+ }
+ }
+
+ private void setBdaValue(BasicDataAttribute bda, String valueString) {
+ if (bda instanceof BdaFloat32) {
+ float value = Float.parseFloat(valueString);
+ ((BdaFloat32) bda).setFloat(value);
+ } else if (bda instanceof BdaFloat64) {
+ double value = Float.parseFloat(valueString);
+ ((BdaFloat64) bda).setDouble(value);
+ } else if (bda instanceof BdaInt8) {
+ byte value = Byte.parseByte(valueString);
+ ((BdaInt8) bda).setValue(value);
+ } else if (bda instanceof BdaInt8U) {
+ short value = Short.parseShort(valueString);
+ ((BdaInt8U) bda).setValue(value);
+ } else if (bda instanceof BdaInt16) {
+ short value = Short.parseShort(valueString);
+ ((BdaInt16) bda).setValue(value);
+ } else if (bda instanceof BdaInt16U) {
+ int value = Integer.parseInt(valueString);
+ ((BdaInt16U) bda).setValue(value);
+ } else if (bda instanceof BdaInt32) {
+ int value = Integer.parseInt(valueString);
+ ((BdaInt32) bda).setValue(value);
+ } else if (bda instanceof BdaInt32U) {
+ long value = Long.parseLong(valueString);
+ ((BdaInt32U) bda).setValue(value);
+ } else if (bda instanceof BdaInt64) {
+ long value = Long.parseLong(valueString);
+ ((BdaInt64) bda).setValue(value);
+ } else if (bda instanceof BdaBoolean) {
+ boolean value = Boolean.parseBoolean(valueString);
+ ((BdaBoolean) bda).setValue(value);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void quit() {
+ System.out.println("** Stopping server.");
+ serverSap.stop();
+ return;
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/BasicDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/BasicDataBind.java
new file mode 100644
index 0000000..e99f24c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/BasicDataBind.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.BasicDataAttribute;
+import com.beanit.iec61850bean.BdaType;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+public abstract class BasicDataBind {
+ protected final E data;
+
+ private JComponent valueField;
+
+ protected BasicDataBind(E data, BdaType type) {
+ if (data.getBasicType() != type) {
+ throw new IllegalArgumentException(data.getName() + " is no " + type);
+ }
+ this.data = data;
+ }
+
+ public JLabel getNameLabel() {
+ return new JLabel(data.getName());
+ }
+
+ public JComponent getValueField() {
+ if (valueField == null) {
+ valueField = init();
+ }
+
+ return valueField;
+ }
+
+ public void reset() {
+ if (valueField == null) {
+ valueField = init();
+ }
+
+ resetImpl();
+ }
+
+ public void write() {
+ if (valueField == null) {
+ valueField = init();
+ }
+
+ writeImpl();
+ }
+
+ protected abstract JComponent init();
+
+ protected abstract void resetImpl();
+
+ protected abstract void writeImpl();
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/ClientGui.java b/src/main/java/com/beanit/iec61850bean/clientgui/ClientGui.java
new file mode 100644
index 0000000..a981eca
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/ClientGui.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.ClientAssociation;
+import com.beanit.iec61850bean.ClientSap;
+import com.beanit.iec61850bean.ServerModel;
+import com.beanit.iec61850bean.ServiceError;
+import com.beanit.iec61850bean.clientgui.util.Counter;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Properties;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTextField;
+import javax.swing.JTree;
+import javax.swing.ToolTipManager;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+public class ClientGui extends JFrame implements ActionListener, TreeSelectionListener {
+
+ private static final String ADDRESS_KEY = "serverAddress";
+ private static final String PORT_KEY = "serverPort";
+ private static final String TSEL_LOCAL_KEY = "tselLocal";
+ private static final String TSEL_REMOTE_KEY = "tselRemote";
+ private static final String LASTCONNECTION_FILE = "lastconnection.properties";
+
+ private static final long serialVersionUID = -1938913902977758367L;
+
+ private final JTextField ipTextField = new JTextField("127.0.0.1");
+ private final JTextField portTextField = new JTextField("10002");
+ private final JTree tree = new JTree(new DefaultMutableTreeNode("No server connected"));
+ private final JPanel detailsPanel = new JPanel();
+ private final GridBagLayout detailsLayout = new GridBagLayout();
+
+ private final SettingsFrame settingsFrame = new SettingsFrame();
+
+ private ClientAssociation association;
+
+ private DataTreeNode selectedNode;
+
+ public ClientGui() {
+ super("IEC61850bean Client GUI");
+
+ Properties lastConnection = new Properties();
+
+ InputStream in = null;
+ try {
+ in = new FileInputStream(LASTCONNECTION_FILE);
+ lastConnection.load(in);
+
+ ipTextField.setText(lastConnection.getProperty(ADDRESS_KEY));
+ portTextField.setText(lastConnection.getProperty(PORT_KEY));
+
+ String[] tselString = lastConnection.getProperty(TSEL_LOCAL_KEY).split(",");
+ byte[] tsel =
+ new byte[] {
+ (byte) Integer.parseInt(tselString[0]), (byte) Integer.parseInt(tselString[1])
+ };
+ settingsFrame.setTselLocal(tsel);
+
+ tselString = lastConnection.getProperty(TSEL_REMOTE_KEY).split(",");
+ tsel =
+ new byte[] {
+ (byte) Integer.parseInt(tselString[0]), (byte) Integer.parseInt(tselString[1])
+ };
+ settingsFrame.setTselRemote(tsel);
+ } catch (Exception ex) {
+ // no lastconnection.properties file found, use default.
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException ignored) {
+ // there is nothing that can be done if closing fails
+ }
+ }
+
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException e) {
+ System.out.println("Class not found: " + e.getMessage());
+ } catch (InstantiationException e) {
+ System.out.println("Object not instantiated: " + e.getMessage());
+ } catch (IllegalAccessException e) {
+ System.out.println("Illegal acces: " + e.getMessage());
+ } catch (UnsupportedLookAndFeelException e) {
+ System.out.println("Unsupported LookAndFeel: " + e.getMessage());
+ }
+
+ GridBagLayout gbl = new GridBagLayout();
+ setLayout(gbl);
+
+ JPanel topPanel = new JPanel();
+ topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
+
+ GridBagConstraints topPanelConstraint = new GridBagConstraints();
+ topPanelConstraint.fill = GridBagConstraints.HORIZONTAL;
+ topPanelConstraint.gridwidth = GridBagConstraints.REMAINDER;
+ topPanelConstraint.gridx = 0;
+ topPanelConstraint.gridy = 0;
+ topPanelConstraint.insets = new Insets(5, 5, 5, 5);
+ topPanelConstraint.anchor = GridBagConstraints.NORTH;
+ gbl.setConstraints(topPanel, topPanelConstraint);
+ add(topPanel);
+
+ JLabel label = new JLabel("IP: ");
+ topPanel.add(label);
+ topPanel.add(ipTextField);
+ topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+ label = new JLabel("Port: ");
+ topPanel.add(label);
+ topPanel.add(portTextField);
+ topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+ JButton newServerButton = new JButton("Connect to Server");
+ newServerButton.addActionListener(this);
+ newServerButton.setActionCommand("Connect");
+ topPanel.add(newServerButton);
+ topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+ JButton settingsButton = new JButton("Settings");
+ settingsButton.addActionListener(this);
+ settingsButton.setActionCommand("Settings");
+ topPanel.add(settingsButton);
+
+ ToolTipManager.sharedInstance().registerComponent(tree);
+
+ tree.setCellRenderer(new DataObjectTreeCellRenderer());
+ tree.setMinimumSize(new Dimension(100, 0));
+ tree.addTreeSelectionListener(this);
+ JScrollPane treeScrollPane = new JScrollPane(tree);
+ treeScrollPane.setMinimumSize(new Dimension(100, 0));
+ treeScrollPane.setVisible(true);
+
+ GridBagConstraints treeScrollPaneConstraint = new GridBagConstraints();
+ treeScrollPaneConstraint.fill = GridBagConstraints.BOTH;
+ treeScrollPaneConstraint.gridx = 0;
+ treeScrollPaneConstraint.gridy = 1;
+ treeScrollPaneConstraint.weightx = 0.2;
+ treeScrollPaneConstraint.weighty = 1;
+ treeScrollPaneConstraint.insets = new Insets(5, 5, 5, 5);
+ gbl.setConstraints(treeScrollPane, treeScrollPaneConstraint);
+ add(treeScrollPane);
+
+ detailsPanel.setLayout(detailsLayout);
+ detailsPanel.setAlignmentY(TOP_ALIGNMENT);
+ JScrollPane detailsScrollPane = new JScrollPane(detailsPanel);
+ detailsPanel.setMaximumSize(detailsScrollPane.getSize());
+ detailsScrollPane.setMinimumSize(new Dimension(0, 0));
+ detailsScrollPane.setPreferredSize(new Dimension(200, 0));
+ detailsScrollPane.setVisible(true);
+ GridBagConstraints detailsScrollPaneConstraint = new GridBagConstraints();
+ detailsScrollPaneConstraint.fill = GridBagConstraints.BOTH;
+ detailsScrollPaneConstraint.gridx = 1;
+ detailsScrollPaneConstraint.gridy = 1;
+ detailsScrollPaneConstraint.weightx = 0.8;
+ detailsScrollPaneConstraint.weighty = 1;
+ detailsScrollPaneConstraint.insets = new Insets(5, 5, 5, 5);
+ gbl.setConstraints(detailsScrollPane, detailsScrollPaneConstraint);
+ add(detailsScrollPane);
+
+ // Display the window.
+ setSize(700, 500);
+ setMinimumSize(new Dimension(420, 0));
+ setVisible(true);
+ }
+
+ public static void main(String[] args) {
+ ClientGui clientGui = new ClientGui();
+ clientGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ clientGui.setVisible(true);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ if ("connect".equalsIgnoreCase(arg0.getActionCommand())) {
+ connect();
+ } else if ("reload".equalsIgnoreCase(arg0.getActionCommand())) {
+ reload();
+ } else if ("write".equalsIgnoreCase(arg0.getActionCommand())) {
+ write();
+ } else if ("settings".equalsIgnoreCase(arg0.getActionCommand())) {
+ settingsFrame.setVisible(true);
+ }
+ }
+
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ detailsPanel.removeAll();
+ detailsPanel.repaint();
+ if (e.getNewLeadSelectionPath() != null) {
+ selectedNode = (DataTreeNode) e.getNewLeadSelectionPath().getLastPathComponent();
+ if (selectedNode.readable()) {
+ showDataDetails(selectedNode, new Counter());
+
+ JPanel filler = new JPanel();
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.gridx = 0;
+ gbc.gridy = GridBagConstraints.RELATIVE;
+ gbc.gridwidth = 3;
+ gbc.gridheight = 1;
+ gbc.weightx = 0;
+ gbc.weighty = 1;
+ detailsLayout.setConstraints(filler, gbc);
+ detailsPanel.add(filler);
+
+ JButton button = new JButton("Reload values");
+ button.addActionListener(this);
+ button.setActionCommand("reload");
+ gbc = new GridBagConstraints();
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridx = 0;
+ gbc.gridy = GridBagConstraints.RELATIVE;
+ gbc.gridwidth = 2;
+ gbc.gridheight = 1;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ gbc.anchor = GridBagConstraints.SOUTHWEST;
+ gbc.insets = new Insets(0, 5, 5, 0);
+ detailsLayout.setConstraints(button, gbc);
+ detailsPanel.add(button);
+
+ if (selectedNode.writable()) {
+ button = new JButton("Write values");
+ button.addActionListener(this);
+ button.setActionCommand("write");
+ gbc = new GridBagConstraints();
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridx = 2;
+ gbc.gridy = GridBagConstraints.RELATIVE;
+ gbc.gridwidth = 1;
+ gbc.gridheight = 1;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ gbc.anchor = GridBagConstraints.SOUTHEAST;
+ gbc.insets = new Insets(0, 0, 5, 5);
+ detailsLayout.setConstraints(button, gbc);
+ detailsPanel.add(button);
+ }
+ }
+ } else {
+ selectedNode = null;
+ }
+
+ validate();
+ }
+
+ private void connect() {
+ ClientSap clientSap = new ClientSap();
+
+ InetAddress address = null;
+ try {
+ address = InetAddress.getByName(ipTextField.getText());
+ } catch (UnknownHostException e1) {
+ e1.printStackTrace();
+ return;
+ }
+
+ int remotePort = 10002;
+ try {
+ remotePort = Integer.parseInt(portTextField.getText());
+ if (remotePort < 1 || remotePort > 0xFFFF) {
+ throw new NumberFormatException("port must be in range [1, 65535]");
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ clientSap.setTSelLocal(settingsFrame.getTselLocal());
+ clientSap.setTSelRemote(settingsFrame.getTselRemote());
+
+ try {
+ association = clientSap.associate(address, remotePort, null, null);
+ } catch (IOException e) {
+ System.out.println("Error connecting to server: " + e.getMessage());
+ return;
+ }
+
+ ServerModel serverModel;
+ try {
+ serverModel = association.retrieveModel();
+ association.getAllDataValues();
+ } catch (ServiceError e) {
+ System.out.println("Service Error requesting model." + e.getMessage());
+ association.close();
+ return;
+ } catch (IOException e) {
+ System.out.println("Fatal IOException requesting model." + e.getMessage());
+ return;
+ }
+
+ ServerModelParser parser = new ServerModelParser(serverModel);
+ tree.setModel(new DefaultTreeModel(parser.getModelTree()));
+
+ Properties lastConnectSettings = new Properties();
+ FileOutputStream out = null;
+ try {
+ lastConnectSettings.setProperty(ADDRESS_KEY, ipTextField.getText());
+ lastConnectSettings.setProperty(PORT_KEY, portTextField.getText());
+ byte[] tsel = settingsFrame.getTselLocal();
+ lastConnectSettings.setProperty(TSEL_LOCAL_KEY, tsel[0] + "," + tsel[1]);
+ tsel = settingsFrame.getTselRemote();
+ lastConnectSettings.setProperty(TSEL_REMOTE_KEY, tsel[0] + "," + tsel[1]);
+
+ out = new FileOutputStream(LASTCONNECTION_FILE);
+ lastConnectSettings.store(out, null);
+ } catch (IOException ex) {
+ System.out.println("Writing properties file failed. Reason: " + ex.getMessage());
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ // nothing meaningful can be done if closing fails
+ }
+ }
+
+ validate();
+ }
+
+ private void reload() {
+ if (selectedNode.readable()) {
+ try {
+ selectedNode.reset(association);
+ } catch (ServiceError e) {
+ System.out.println("ServiceError on reading" + e.getMessage());
+ return;
+ } catch (IOException e) {
+ System.out.println("IOException on reading" + e.getMessage());
+ return;
+ }
+ validate();
+ }
+ }
+
+ private void write() {
+ if (selectedNode.writable()) {
+ try {
+ selectedNode.writeValues(association);
+ } catch (ServiceError e) {
+ System.out.println("ServiceError on writing" + e.getMessage());
+ return;
+ } catch (IOException e) {
+ System.out.println("IOException on writing" + e.getMessage());
+ return;
+ }
+ validate();
+ }
+ }
+
+ private void showDataDetails(DataTreeNode node, Counter y) {
+ if (node.getData() != null) {
+ BasicDataBind> data = node.getData();
+ JLabel nameLabel = data.getNameLabel();
+ nameLabel.setText(nameLabel.getText() + ": ");
+ addDetailsComponent(nameLabel, 0, y.getValue(), 1, 1, 0, 0);
+ addDetailsComponent(data.getValueField(), 1, y.getValue(), 2, 1, 1, 0);
+ y.increment();
+ } else {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ y.increment();
+ DataObjectTreeNode childNode = (DataObjectTreeNode) node.getChildAt(i);
+ showDataDetails(childNode, childNode.toString(), y);
+ }
+ }
+ }
+
+ private void showDataDetails(DataTreeNode node, String pre, Counter y) {
+ if (node.getData() != null) {
+ BasicDataBind> data = node.getData();
+ JLabel nameLabel = data.getNameLabel();
+ nameLabel.setText(pre + ": ");
+ addDetailsComponent(nameLabel, 0, y.getValue(), 1, 1, 0, 0);
+ addDetailsComponent(data.getValueField(), 1, y.getValue(), 2, 1, 1, 0);
+ y.increment();
+ } else {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ y.increment();
+ DataObjectTreeNode childNode = (DataObjectTreeNode) node.getChildAt(i);
+ showDataDetails(childNode, pre + "." + childNode.toString(), y);
+ detailsPanel.add(new JSeparator());
+ addDetailsComponent(new JSeparator(), 0, y.getValue(), 3, 1, 1, 0);
+ }
+ }
+ }
+
+ private void addDetailsComponent(
+ Component c, int x, int y, int width, int height, double weightx, double weighty) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.gridx = x;
+ gbc.gridy = y;
+ gbc.gridwidth = width;
+ gbc.gridheight = height;
+ gbc.weightx = weightx;
+ gbc.weighty = weighty;
+ gbc.anchor = GridBagConstraints.NORTH;
+ gbc.insets = new Insets(3, 3, 3, 3);
+ detailsLayout.setConstraints(c, gbc);
+ detailsPanel.add(c);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeCellRenderer.java b/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeCellRenderer.java
new file mode 100644
index 0000000..808ec07
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeCellRenderer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.BasicDataAttribute;
+import com.beanit.iec61850bean.FcModelNode;
+import java.awt.Component;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+public class DataObjectTreeCellRenderer extends DefaultTreeCellRenderer {
+
+ private static final long serialVersionUID = 1682378972258556129L;
+
+ @Override
+ public Component getTreeCellRendererComponent(
+ JTree tree,
+ Object value,
+ boolean sel,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean hasFocus) {
+ super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+
+ if (value instanceof DataObjectTreeNode) {
+ DataObjectTreeNode treeNode = (DataObjectTreeNode) value;
+ if (!leaf && treeNode.getNode() instanceof FcModelNode) {
+ setIcon(getLeafIcon());
+ }
+
+ if (treeNode.getNode() instanceof BasicDataAttribute) {
+ BasicDataAttribute attribute = (BasicDataAttribute) treeNode.getNode();
+ String tooltip = attribute.getSAddr();
+ setToolTipText(tooltip);
+ }
+ }
+
+ return this;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeNode.java b/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeNode.java
new file mode 100644
index 0000000..ec60968
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/DataObjectTreeNode.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.BasicDataAttribute;
+import com.beanit.iec61850bean.BdaBoolean;
+import com.beanit.iec61850bean.BdaCheck;
+import com.beanit.iec61850bean.BdaDoubleBitPos;
+import com.beanit.iec61850bean.BdaEntryTime;
+import com.beanit.iec61850bean.BdaFloat32;
+import com.beanit.iec61850bean.BdaFloat64;
+import com.beanit.iec61850bean.BdaInt16;
+import com.beanit.iec61850bean.BdaInt16U;
+import com.beanit.iec61850bean.BdaInt32;
+import com.beanit.iec61850bean.BdaInt32U;
+import com.beanit.iec61850bean.BdaInt64;
+import com.beanit.iec61850bean.BdaInt8;
+import com.beanit.iec61850bean.BdaInt8U;
+import com.beanit.iec61850bean.BdaOctetString;
+import com.beanit.iec61850bean.BdaOptFlds;
+import com.beanit.iec61850bean.BdaQuality;
+import com.beanit.iec61850bean.BdaReasonForInclusion;
+import com.beanit.iec61850bean.BdaTapCommand;
+import com.beanit.iec61850bean.BdaTimestamp;
+import com.beanit.iec61850bean.BdaTriggerConditions;
+import com.beanit.iec61850bean.BdaUnicodeString;
+import com.beanit.iec61850bean.BdaVisibleString;
+import com.beanit.iec61850bean.ClientAssociation;
+import com.beanit.iec61850bean.Fc;
+import com.beanit.iec61850bean.FcModelNode;
+import com.beanit.iec61850bean.ModelNode;
+import com.beanit.iec61850bean.ServiceError;
+import com.beanit.iec61850bean.clientgui.databind.BooleanDataBind;
+import com.beanit.iec61850bean.clientgui.databind.CheckDataBind;
+import com.beanit.iec61850bean.clientgui.databind.DoubleBitPosDataBind;
+import com.beanit.iec61850bean.clientgui.databind.EntryTimeDataBind;
+import com.beanit.iec61850bean.clientgui.databind.Float32DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Float64DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int16DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int16UDataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int32DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int32UDataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int64DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int8DataBind;
+import com.beanit.iec61850bean.clientgui.databind.Int8UDataBind;
+import com.beanit.iec61850bean.clientgui.databind.OctetStringDataBind;
+import com.beanit.iec61850bean.clientgui.databind.OptfldsDataBind;
+import com.beanit.iec61850bean.clientgui.databind.QualityDataBind;
+import com.beanit.iec61850bean.clientgui.databind.ReasonForInclusionDataBind;
+import com.beanit.iec61850bean.clientgui.databind.TapCommandDataBind;
+import com.beanit.iec61850bean.clientgui.databind.TimeStampDataBind;
+import com.beanit.iec61850bean.clientgui.databind.TriggerConditionDataBind;
+import com.beanit.iec61850bean.clientgui.databind.UnicodeStringDataBind;
+import com.beanit.iec61850bean.clientgui.databind.VisibleStringDataBind;
+import java.io.IOException;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class DataObjectTreeNode extends DefaultMutableTreeNode implements DataTreeNode {
+
+ private static final long serialVersionUID = -3596243932937737877L;
+
+ private final ModelNode node;
+ private final BasicDataBind> data;
+
+ public DataObjectTreeNode(String name, ModelNode node) {
+ super(name);
+ this.node = node;
+ if (node != null && node.getChildren() == null) {
+ // for (ModelNode child : node.getChildren()) {
+ // if (child instanceof BasicDataAttribute) {
+ // data.add(createDataBind((BasicDataAttribute) child));
+ // }
+ // }
+ data = createDataBind((BasicDataAttribute) node);
+ } else {
+ data = null;
+ }
+ }
+
+ private static BasicDataBind> createDataBind(BasicDataAttribute bda) {
+ switch (bda.getBasicType()) {
+ case BOOLEAN:
+ return new BooleanDataBind((BdaBoolean) bda);
+ case ENTRY_TIME:
+ return new EntryTimeDataBind((BdaEntryTime) bda);
+ case FLOAT32:
+ return new Float32DataBind((BdaFloat32) bda);
+ case FLOAT64:
+ return new Float64DataBind((BdaFloat64) bda);
+ case INT16:
+ return new Int16DataBind((BdaInt16) bda);
+ case INT16U:
+ return new Int16UDataBind((BdaInt16U) bda);
+ case INT32:
+ return new Int32DataBind((BdaInt32) bda);
+ case INT32U:
+ return new Int32UDataBind((BdaInt32U) bda);
+ case INT64:
+ return new Int64DataBind((BdaInt64) bda);
+ case INT8:
+ return new Int8DataBind((BdaInt8) bda);
+ case INT8U:
+ return new Int8UDataBind((BdaInt8U) bda);
+ case OCTET_STRING:
+ return new OctetStringDataBind((BdaOctetString) bda);
+ case TIMESTAMP:
+ return new TimeStampDataBind((BdaTimestamp) bda);
+ case UNICODE_STRING:
+ return new UnicodeStringDataBind((BdaUnicodeString) bda);
+ case VISIBLE_STRING:
+ return new VisibleStringDataBind((BdaVisibleString) bda);
+ case CHECK:
+ return new CheckDataBind((BdaCheck) bda);
+ case DOUBLE_BIT_POS:
+ return new DoubleBitPosDataBind((BdaDoubleBitPos) bda);
+ case OPTFLDS:
+ return new OptfldsDataBind((BdaOptFlds) bda);
+ case QUALITY:
+ return new QualityDataBind((BdaQuality) bda);
+ case REASON_FOR_INCLUSION:
+ return new ReasonForInclusionDataBind((BdaReasonForInclusion) bda);
+ case TAP_COMMAND:
+ return new TapCommandDataBind((BdaTapCommand) bda);
+ case TRIGGER_CONDITIONS:
+ return new TriggerConditionDataBind((BdaTriggerConditions) bda);
+ default:
+ throw new IllegalArgumentException("BasicType " + bda.getBasicType() + " unknown");
+ }
+ }
+
+ public ModelNode getNode() {
+ return node;
+ }
+
+ @Override
+ public BasicDataBind> getData() {
+ return data;
+ }
+
+ @Override
+ public void reset(ClientAssociation association) throws ServiceError, IOException {
+ if (association != null) {
+ association.getDataValues((FcModelNode) node);
+ }
+ if (data != null) {
+ data.reset();
+ } else {
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof DataObjectTreeNode) {
+ DataTreeNode child = (DataTreeNode) getChildAt(i);
+ child.reset(null);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void writeValues(ClientAssociation association) throws ServiceError, IOException {
+ if (data != null) {
+ data.write();
+ } else {
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof DataObjectTreeNode) {
+ DataTreeNode child = (DataTreeNode) getChildAt(i);
+ child.writeValues(null);
+ }
+ }
+ }
+ if (association != null) {
+ association.setDataValues((FcModelNode) node);
+ }
+ }
+
+ @Override
+ public boolean writable() {
+ if (node instanceof FcModelNode) {
+ FcModelNode modelNode = (FcModelNode) node;
+ Fc constraint = modelNode.getFc();
+ return constraint != Fc.ST && constraint != Fc.MX;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean readable() {
+ return node instanceof FcModelNode;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/DataSetTreeNode.java b/src/main/java/com/beanit/iec61850bean/clientgui/DataSetTreeNode.java
new file mode 100644
index 0000000..bbf1a9a
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/DataSetTreeNode.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.ClientAssociation;
+import com.beanit.iec61850bean.DataSet;
+import com.beanit.iec61850bean.ServiceError;
+import java.io.IOException;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class DataSetTreeNode extends DefaultMutableTreeNode implements DataTreeNode {
+
+ private static final long serialVersionUID = 7919716359809465616L;
+
+ private final DataSet node;
+
+ public DataSetTreeNode(String name, DataSet node) {
+ super(name);
+ this.node = node;
+ }
+
+ public DataSet getNode() {
+ return node;
+ }
+
+ @Override
+ public void reset(ClientAssociation association) throws ServiceError, IOException {
+ if (association != null) {
+ association.getDataSetValues(node);
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof DataObjectTreeNode) {
+ DataTreeNode child = (DataTreeNode) getChildAt(i);
+ child.reset(null);
+ }
+ }
+ }
+
+ @Override
+ public void writeValues(ClientAssociation association) throws ServiceError, IOException {
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof DataObjectTreeNode) {
+ DataTreeNode child = (DataTreeNode) getChildAt(i);
+ child.writeValues(null);
+ }
+ }
+ if (association != null) {
+ association.setDataSetValues(node);
+ }
+ }
+
+ @Override
+ public BasicDataBind> getData() {
+ return null;
+ }
+
+ @Override
+ public boolean writable() {
+ return true;
+ }
+
+ @Override
+ public boolean readable() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/DataTreeNode.java b/src/main/java/com/beanit/iec61850bean/clientgui/DataTreeNode.java
new file mode 100644
index 0000000..92e108c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/DataTreeNode.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.ClientAssociation;
+import com.beanit.iec61850bean.ServiceError;
+import java.io.IOException;
+import javax.swing.tree.TreeNode;
+
+public interface DataTreeNode {
+
+ BasicDataBind> getData();
+
+ void reset(ClientAssociation association) throws ServiceError, IOException;
+
+ void writeValues(ClientAssociation association) throws ServiceError, IOException;
+
+ int getChildCount();
+
+ TreeNode getChildAt(int index);
+
+ boolean writable();
+
+ boolean readable();
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/ServerModelParser.java b/src/main/java/com/beanit/iec61850bean/clientgui/ServerModelParser.java
new file mode 100644
index 0000000..e904391
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/ServerModelParser.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import com.beanit.iec61850bean.DataSet;
+import com.beanit.iec61850bean.Fc;
+import com.beanit.iec61850bean.FcModelNode;
+import com.beanit.iec61850bean.LogicalDevice;
+import com.beanit.iec61850bean.LogicalNode;
+import com.beanit.iec61850bean.ModelNode;
+import com.beanit.iec61850bean.ServerModel;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.swing.tree.TreeNode;
+
+public class ServerModelParser {
+
+ private final ServerModel model;
+ private DataObjectTreeNode modelTree;
+
+ public ServerModelParser(ServerModel model) {
+ this.model = model;
+ }
+
+ public TreeNode getModelTree() {
+ if (modelTree == null) {
+ createModelTree();
+ }
+ return modelTree;
+ }
+
+ private synchronized void createModelTree() {
+ if (modelTree == null) {
+ modelTree = new DataObjectTreeNode("server", null);
+ for (ModelNode node : model.getChildren()) {
+ if (node instanceof LogicalDevice == false) {
+ System.out.println(
+ "Node " + node.getName() + " is " + node.getClass() + " (should be LogicalDevice)");
+ continue;
+ }
+ addLogicalDevice(modelTree, (LogicalDevice) node);
+ }
+ for (DataSet dataSet : model.getDataSets()) {
+ addDataSet(modelTree, dataSet);
+ }
+ }
+ }
+
+ private void addLogicalDevice(DataObjectTreeNode root, LogicalDevice node) {
+ DataObjectTreeNode treeLD = new DataObjectTreeNode(node.getName(), node);
+ root.add(treeLD);
+ for (ModelNode subNode : node.getChildren()) {
+ if (subNode instanceof LogicalNode == false) {
+ System.out.println(
+ "Node " + subNode.getName() + " is " + subNode.getClass() + " (should be LogicalNode)");
+ continue;
+ }
+ addLogicalNode(treeLD, (LogicalNode) subNode);
+ }
+ }
+
+ private void addLogicalNode(DataObjectTreeNode parent, LogicalNode node) {
+ DataObjectTreeNode treeLN = new DataObjectTreeNode(node.getName(), node);
+ parent.add(treeLN);
+ Collection children = node.getChildren();
+ Map> childMap = new HashMap<>();
+ for (ModelNode child : children) {
+ if (!childMap.containsKey(child.getName())) {
+ childMap.put(child.getName(), new HashSet());
+ }
+ childMap.get(child.getName()).add(((FcModelNode) child).getFc());
+ }
+ for (Map.Entry> childEntry : childMap.entrySet()) {
+ addFunctionalConstraintObject(treeLN, node, childEntry.getKey(), childEntry.getValue());
+ }
+ }
+
+ private void addDataSet(DataObjectTreeNode parent, DataSet node) {
+ DataSetTreeNode treeDS = new DataSetTreeNode(node.getReferenceStr(), node);
+ parent.add(treeDS);
+ Collection children = node.getMembers();
+ for (ModelNode child : children) {
+ addFunctionalConstraintObject(treeDS, child);
+ }
+ }
+
+ private void addFunctionalConstraintObject(
+ DataObjectTreeNode parent, LogicalNode parentNode, String childName, Set childFcs) {
+ DataObjectTreeNode treeFCDO = new DataObjectTreeNode(childName, null);
+ parent.add(treeFCDO);
+
+ for (Fc constraint : childFcs) {
+ ModelNode subNode = parentNode.getChild(childName, constraint);
+ addDataObject(treeFCDO, "[" + constraint + "]", subNode);
+ }
+ }
+
+ private void addFunctionalConstraintObject(DataSetTreeNode parent, ModelNode node) {
+ DataObjectTreeNode treeFCDO = new DataObjectTreeNode(node.getReference().toString(), node);
+ parent.add(treeFCDO);
+ if (node.getChildren() != null) {
+ for (ModelNode subNode : node.getChildren()) {
+ addDataObject(treeFCDO, subNode.getName(), subNode);
+ }
+ }
+ }
+
+ private void addDataObject(DataObjectTreeNode parent, String name, ModelNode node) {
+ DataObjectTreeNode treeDO = new DataObjectTreeNode(name, node);
+ parent.add(treeDO);
+ if (node.getChildren() != null) {
+ for (ModelNode subNode : node.getChildren()) {
+ addDataObject(treeDO, subNode.getName(), subNode);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/SettingsFrame.java b/src/main/java/com/beanit/iec61850bean/clientgui/SettingsFrame.java
new file mode 100644
index 0000000..52640bb
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/SettingsFrame.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+public class SettingsFrame extends JDialog implements ActionListener {
+
+ private static final long serialVersionUID = -411845634137160667L;
+
+ private int tselLocal1 = 0;
+ private final JTextField tselLocalField1 = new JTextField(Integer.toString(tselLocal1));
+ private int tselLocal2 = 0;
+ private final JTextField tselLocalField2 = new JTextField(Integer.toString(tselLocal2));
+ private int tselRemote1 = 0;
+ private final JTextField tselRemoteField1 = new JTextField(Integer.toString(tselRemote1));
+ private int tselRemote2 = 1;
+ private final JTextField tselRemoteField2 = new JTextField(Integer.toString(tselRemote2));
+
+ public SettingsFrame() {
+ setModalityType(ModalityType.APPLICATION_MODAL);
+
+ final GridBagLayout layout = new GridBagLayout();
+ setLayout(layout);
+
+ this.setSize(200, 120);
+ setLocationRelativeTo(null);
+
+ JLabel label = new JLabel("TSelLocal: ");
+ GridBagConstraints constraint = new GridBagConstraints();
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 0;
+ constraint.gridy = 0;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(label, constraint);
+ add(label);
+
+ constraint = new GridBagConstraints();
+ constraint.fill = GridBagConstraints.HORIZONTAL;
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 1;
+ constraint.gridy = 0;
+ constraint.weightx = 1;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(tselLocalField1, constraint);
+ add(tselLocalField1);
+
+ constraint = new GridBagConstraints();
+ constraint.fill = GridBagConstraints.HORIZONTAL;
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 2;
+ constraint.gridy = 0;
+ constraint.weightx = 1;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(tselLocalField2, constraint);
+ add(tselLocalField2);
+
+ label = new JLabel("TSelRemote: ");
+ constraint = new GridBagConstraints();
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 0;
+ constraint.gridy = 1;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(label, constraint);
+ add(label);
+
+ constraint = new GridBagConstraints();
+ constraint.fill = GridBagConstraints.HORIZONTAL;
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 1;
+ constraint.gridy = 1;
+ constraint.weightx = 1;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(tselRemoteField1, constraint);
+ add(tselRemoteField1);
+
+ constraint = new GridBagConstraints();
+ constraint.fill = GridBagConstraints.HORIZONTAL;
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 2;
+ constraint.gridy = 1;
+ constraint.weightx = 1;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(tselRemoteField2, constraint);
+ add(tselRemoteField2);
+
+ JButton button = new JButton("Cancel");
+ button.setActionCommand("Cancel");
+ button.addActionListener(this);
+ constraint.gridwidth = 1;
+ constraint.gridheight = 1;
+ constraint.gridx = 0;
+ constraint.gridy = 2;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.SOUTHWEST;
+ layout.setConstraints(button, constraint);
+ add(button);
+
+ button = new JButton("OK");
+ button.setActionCommand("Okay");
+ button.addActionListener(this);
+ constraint.gridwidth = 2;
+ constraint.gridheight = 1;
+ constraint.gridx = 1;
+ constraint.gridy = 2;
+ constraint.insets = new Insets(5, 5, 5, 5);
+ constraint.anchor = GridBagConstraints.SOUTHWEST;
+ layout.setConstraints(button, constraint);
+ add(button);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if ("cancel".equalsIgnoreCase(e.getActionCommand())) {
+ tselLocalField1.setText(Integer.toString(tselLocal1 & 0xFF));
+ tselLocalField2.setText(Integer.toString(tselLocal2 & 0xFF));
+ tselRemoteField1.setText(Integer.toString(tselRemote1 & 0xFF));
+ tselRemoteField2.setText(Integer.toString(tselRemote2 & 0xFF));
+ setVisible(false);
+ } else if ("okay".equalsIgnoreCase(e.getActionCommand())) {
+ tselLocal1 = parseTextField(tselLocalField1, tselLocal1);
+ tselLocal2 = parseTextField(tselLocalField2, tselLocal2);
+ tselRemote1 = parseTextField(tselRemoteField1, tselRemote1);
+ tselRemote2 = parseTextField(tselRemoteField2, tselRemote2);
+ setVisible(false);
+ }
+ }
+
+ public byte[] getTselLocal() {
+ return new byte[] {(byte) tselLocal1, (byte) tselLocal2};
+ }
+
+ public void setTselLocal(byte[] tsel) {
+ if (tsel.length != 2) {
+ throw new IllegalArgumentException("TSel must consist of 2 bytes");
+ }
+ tselLocal1 = tsel[0];
+ tselLocal2 = tsel[1];
+
+ tselLocalField1.setText(Integer.toString(tselLocal1 & 0xFF));
+ tselLocalField2.setText(Integer.toString(tselLocal2 & 0xFF));
+ }
+
+ public byte[] getTselRemote() {
+ return new byte[] {(byte) tselRemote1, (byte) tselRemote2};
+ }
+
+ public void setTselRemote(byte[] tsel) {
+ if (tsel.length != 2) {
+ throw new IllegalArgumentException("TSel must consist of 2 bytes");
+ }
+ tselRemote1 = tsel[0];
+ tselRemote2 = tsel[1];
+
+ tselRemoteField1.setText(Integer.toString(tselRemote1 & 0xFF));
+ tselRemoteField2.setText(Integer.toString(tselRemote2 & 0xFF));
+ }
+
+ private int parseTextField(JTextField field, int oldValue) {
+ int value = oldValue;
+ try {
+ int newValue = Integer.parseInt(field.getText());
+ if (newValue >= 0 && newValue <= 255) {
+ value = newValue;
+ }
+ } catch (NumberFormatException e) {
+ return oldValue;
+ }
+ return value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/BooleanDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/BooleanDataBind.java
new file mode 100644
index 0000000..b08b920
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/BooleanDataBind.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaBoolean;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+
+public class BooleanDataBind extends BasicDataBind {
+
+ private JCheckBox checkbox;
+
+ public BooleanDataBind(BdaBoolean data) {
+ super(data, BdaType.BOOLEAN);
+ }
+
+ @Override
+ protected JComponent init() {
+ checkbox = new JCheckBox();
+ checkbox.setBorder(null);
+ checkbox.setSelected(data.getValue());
+ return checkbox;
+ }
+
+ @Override
+ protected void resetImpl() {
+ checkbox.setSelected(data.getValue());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(checkbox.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/CheckDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/CheckDataBind.java
new file mode 100644
index 0000000..687b6af
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/CheckDataBind.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaCheck;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class CheckDataBind extends BasicDataBind {
+
+ private final JCheckBox interlock = new JCheckBox("Interlock");
+ private final JCheckBox synchron = new JCheckBox("Synchron");
+
+ public CheckDataBind(BdaCheck data) {
+ super(data, BdaType.CHECK);
+ }
+
+ @Override
+ protected JComponent init() {
+ interlock.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JPanel valuePanel = new JPanel();
+ valuePanel.setLayout(new BoxLayout(valuePanel, BoxLayout.PAGE_AXIS));
+ valuePanel.add(interlock);
+ valuePanel.add(synchron);
+ return valuePanel;
+ }
+
+ @Override
+ protected void resetImpl() {
+ interlock.setSelected(data.getInterlockCheck());
+ synchron.setSelected(data.getSynchrocheck());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setInterlockCheck(interlock.isSelected());
+ data.setSynchrocheck(synchron.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/DoubleBitPosDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/DoubleBitPosDataBind.java
new file mode 100644
index 0000000..16da758
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/DoubleBitPosDataBind.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaDoubleBitPos;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+
+public class DoubleBitPosDataBind extends BasicDataBind {
+
+ @SuppressWarnings("unchecked")
+ private final JComboBox valueField = new JComboBox(BdaDoubleBitPos.DoubleBitPos.values());
+
+ public DoubleBitPosDataBind(BdaDoubleBitPos data) {
+ super(data, BdaType.DOUBLE_BIT_POS);
+ }
+
+ @Override
+ protected JComponent init() {
+ return valueField;
+ }
+
+ @Override
+ protected void resetImpl() {
+ valueField.setSelectedItem(data.getDoubleBitPos());
+ }
+
+ @Override
+ protected void writeImpl() {
+ // TODO uncomment once data.setTapCommand() is implemented
+ // data.setTapCommand(valueField.getSelectedItem());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/EntryTimeDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/EntryTimeDataBind.java
new file mode 100644
index 0000000..40efefc
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/EntryTimeDataBind.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaEntryTime;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+public class EntryTimeDataBind extends BasicDataBind {
+
+ public EntryTimeDataBind(BdaEntryTime data) {
+ super(data, BdaType.ENTRY_TIME);
+ }
+
+ @Override
+ protected JComponent init() {
+ byte[] value = data.getValue();
+ StringBuilder sb;
+
+ sb = new StringBuilder("EntryTime [");
+ for (int i = 0; i < value.length; i++) {
+ sb.append(Integer.toHexString(value[i] & 0xff));
+ if (i != value.length - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return new JLabel(sb.toString());
+ }
+
+ @Override
+ protected void resetImpl() {
+ // ignore for now
+ }
+
+ @Override
+ protected void writeImpl() {
+ // ignore for now
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float32DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float32DataBind.java
new file mode 100644
index 0000000..cc1785c
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float32DataBind.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaFloat32;
+import com.beanit.iec61850bean.BdaType;
+
+public class Float32DataBind extends TextFieldDataBind {
+
+ private static final FloatFilter FILTER = new FloatFilter();
+
+ public Float32DataBind(BdaFloat32 data) {
+ super(data, BdaType.FLOAT32, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(data.getFloat().toString());
+ }
+
+ @Override
+ protected void writeImpl() {
+ float newFloat = Float.parseFloat(inputField.getText());
+ data.setFloat(newFloat);
+ }
+
+ private static class FloatFilter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Float.parseFloat(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float64DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float64DataBind.java
new file mode 100644
index 0000000..da50178
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Float64DataBind.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaFloat64;
+import com.beanit.iec61850bean.BdaType;
+
+public class Float64DataBind extends TextFieldDataBind {
+
+ private static final DoubleFilter FILTER = new DoubleFilter();
+
+ public Float64DataBind(BdaFloat64 data) {
+ super(data, BdaType.FLOAT64, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(data.getDouble().toString());
+ }
+
+ @Override
+ protected void writeImpl() {
+ double newDouble = Double.parseDouble(inputField.getText());
+ data.setDouble(newDouble);
+ }
+
+ private static class DoubleFilter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Double.parseDouble(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16DataBind.java
new file mode 100644
index 0000000..eb9250e
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16DataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt16;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int16DataBind extends TextFieldDataBind {
+
+ private static final Int16Filter FILTER = new Int16Filter();
+
+ public Int16DataBind(BdaInt16 data) {
+ super(data, BdaType.INT16, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Short.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Short.parseShort(inputField.getText()));
+ }
+
+ private static class Int16Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Short.parseShort(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16UDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16UDataBind.java
new file mode 100644
index 0000000..7196aff
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int16UDataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt16U;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int16UDataBind extends TextFieldDataBind {
+
+ private static final UInt16Filter FILTER = new UInt16Filter();
+
+ public Int16UDataBind(BdaInt16U data) {
+ super(data, BdaType.INT16U, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Integer.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Integer.parseInt(inputField.getText()));
+ }
+
+ private static class UInt16Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ int value = Integer.parseInt(text);
+ return value >= 0 && value <= 0xFFFF;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32DataBind.java
new file mode 100644
index 0000000..2075ab5
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32DataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt32;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int32DataBind extends TextFieldDataBind {
+
+ private static final Int32Filter FILTER = new Int32Filter();
+
+ public Int32DataBind(BdaInt32 data) {
+ super(data, BdaType.INT32, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Integer.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Integer.parseInt(inputField.getText()));
+ }
+
+ private static class Int32Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Integer.parseInt(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32UDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32UDataBind.java
new file mode 100644
index 0000000..1409848
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int32UDataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt32U;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int32UDataBind extends TextFieldDataBind {
+
+ private static final UInt32Filter FILTER = new UInt32Filter();
+
+ public Int32UDataBind(BdaInt32U data) {
+ super(data, BdaType.INT32U, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Long.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Long.parseLong(inputField.getText()));
+ }
+
+ private static class UInt32Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ long value = Long.parseLong(text);
+ return value >= 0 && value <= 0xFFFFFFFFL;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int64DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int64DataBind.java
new file mode 100644
index 0000000..a7b16cb
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int64DataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt64;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int64DataBind extends TextFieldDataBind {
+
+ private static final Int64Filter FILTER = new Int64Filter();
+
+ public Int64DataBind(BdaInt64 data) {
+ super(data, BdaType.INT64, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Long.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Long.parseLong(inputField.getText()));
+ }
+
+ private static class Int64Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Long.parseLong(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8DataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8DataBind.java
new file mode 100644
index 0000000..6acf98e
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8DataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt8;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int8DataBind extends TextFieldDataBind {
+
+ private static final Int8Filter FILTER = new Int8Filter();
+
+ public Int8DataBind(BdaInt8 data) {
+ super(data, BdaType.INT8, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Byte.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Byte.parseByte(inputField.getText()));
+ }
+
+ private static class Int8Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Byte.parseByte(text);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8UDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8UDataBind.java
new file mode 100644
index 0000000..0205554
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/Int8UDataBind.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaInt8U;
+import com.beanit.iec61850bean.BdaType;
+
+public class Int8UDataBind extends TextFieldDataBind {
+
+ private static final UInt8Filter FILTER = new UInt8Filter();
+
+ public Int8UDataBind(BdaInt8U data) {
+ super(data, BdaType.INT8U, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(Short.toString(data.getValue()));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(Short.parseShort(inputField.getText()));
+ }
+
+ private static class UInt8Filter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ short value = Short.parseShort(text);
+ return value >= 0 && value <= 0xFF;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/OctetStringDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/OctetStringDataBind.java
new file mode 100644
index 0000000..a682f11
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/OctetStringDataBind.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaOctetString;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+public class OctetStringDataBind extends BasicDataBind {
+
+ public OctetStringDataBind(BdaOctetString data) {
+ super(data, BdaType.OCTET_STRING);
+ }
+
+ @Override
+ protected JComponent init() {
+ byte[] value = data.getValue();
+ StringBuilder sb;
+
+ sb = new StringBuilder("OctetString [");
+ for (int i = 0; i < value.length; i++) {
+ sb.append(Integer.toHexString(value[i] & 0xff));
+ if (i != value.length - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return new JLabel(sb.toString());
+ }
+
+ @Override
+ protected void resetImpl() {
+ // ignore for now
+ }
+
+ @Override
+ protected void writeImpl() {
+ // ignore for now
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/OptfldsDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/OptfldsDataBind.java
new file mode 100644
index 0000000..73e60c2
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/OptfldsDataBind.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaOptFlds;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class OptfldsDataBind extends BasicDataBind {
+
+ private final JCheckBox bufferOverflow = new JCheckBox("BufferOverflow");
+ private final JCheckBox configRevision = new JCheckBox("ConfigRevision");
+ private final JCheckBox dataReference = new JCheckBox("DataReference");
+ private final JCheckBox dataSetName = new JCheckBox("DataSetName");
+ private final JCheckBox entryId = new JCheckBox("EntryId");
+ private final JCheckBox reasonForInclusion = new JCheckBox("ReasonForInclusion");
+ private final JCheckBox reportTimestamp = new JCheckBox("ReportTimestamp");
+ private final JCheckBox segmentation = new JCheckBox("Segmentation");
+ private final JCheckBox sequenceNumber = new JCheckBox("SequenceNumber");
+
+ public OptfldsDataBind(BdaOptFlds data) {
+ super(data, BdaType.OPTFLDS);
+ }
+
+ @Override
+ protected JComponent init() {
+ bufferOverflow.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JPanel valuePanel = new JPanel();
+ valuePanel.setLayout(new BoxLayout(valuePanel, BoxLayout.PAGE_AXIS));
+ valuePanel.add(bufferOverflow);
+ valuePanel.add(configRevision);
+ valuePanel.add(dataReference);
+ valuePanel.add(dataSetName);
+ valuePanel.add(entryId);
+ valuePanel.add(reasonForInclusion);
+ valuePanel.add(reportTimestamp);
+ valuePanel.add(segmentation);
+ valuePanel.add(sequenceNumber);
+ return valuePanel;
+ }
+
+ @Override
+ protected void resetImpl() {
+ bufferOverflow.setSelected(data.isBufferOverflow());
+ configRevision.setSelected(data.isConfigRevision());
+ dataReference.setSelected(data.isDataReference());
+ dataSetName.setSelected(data.isDataSetName());
+ entryId.setSelected(data.isEntryId());
+ reasonForInclusion.setSelected(data.isReasonForInclusion());
+ reportTimestamp.setSelected(data.isReportTimestamp());
+ segmentation.setSelected(data.isSegmentation());
+ sequenceNumber.setSelected(data.isSequenceNumber());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setBufferOverflow(bufferOverflow.isSelected());
+ data.setConfigRevision(configRevision.isSelected());
+ data.setDataReference(dataReference.isSelected());
+ data.setDataSetName(dataSetName.isSelected());
+ data.setEntryId(entryId.isSelected());
+ data.setReasonForInclusion(reasonForInclusion.isSelected());
+ data.setReportTimestamp(reportTimestamp.isSelected());
+ data.setSegmentation(segmentation.isSelected());
+ data.setSequenceNumber(sequenceNumber.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/QualityDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/QualityDataBind.java
new file mode 100644
index 0000000..5c126d6
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/QualityDataBind.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaQuality;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class QualityDataBind extends BasicDataBind {
+
+ @SuppressWarnings("unchecked")
+ private final JComboBox validity = new JComboBox(BdaQuality.Validity.values());
+
+ private final JCheckBox badReference = new JCheckBox("BadReference");
+ private final JCheckBox failure = new JCheckBox("Failure");
+ private final JCheckBox inaccurate = new JCheckBox("Inaccurate");
+ private final JCheckBox inconsistent = new JCheckBox("Inconsistent");
+ private final JCheckBox oldData = new JCheckBox("OldData");
+ private final JCheckBox operatorBlocked = new JCheckBox("OperatorBlocked");
+ private final JCheckBox oscillatory = new JCheckBox("Oscillatory");
+ private final JCheckBox outOfRange = new JCheckBox("OutOfRange");
+ private final JCheckBox overflow = new JCheckBox("Overflow");
+ private final JCheckBox substituded = new JCheckBox("Substituded");
+ private final JCheckBox test = new JCheckBox("Test");
+
+ public QualityDataBind(BdaQuality data) {
+ super(data, BdaType.QUALITY);
+ }
+
+ @Override
+ protected JComponent init() {
+ validity.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JPanel valuePanel = new JPanel();
+ valuePanel.setLayout(new BoxLayout(valuePanel, BoxLayout.PAGE_AXIS));
+ valuePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ valuePanel.add(validity);
+ valuePanel.add(badReference);
+ valuePanel.add(failure);
+ valuePanel.add(inaccurate);
+ valuePanel.add(inconsistent);
+ valuePanel.add(oldData);
+ valuePanel.add(operatorBlocked);
+ valuePanel.add(oscillatory);
+ valuePanel.add(outOfRange);
+ valuePanel.add(overflow);
+ valuePanel.add(substituded);
+ valuePanel.add(test);
+ return valuePanel;
+ }
+
+ @Override
+ protected void resetImpl() {
+ validity.setSelectedItem(data.getValidity());
+ badReference.setSelected(data.isBadReference());
+ failure.setSelected(data.isFailure());
+ inaccurate.setSelected(data.isInaccurate());
+ inconsistent.setSelected(data.isInconsistent());
+ oldData.setSelected(data.isOldData());
+ operatorBlocked.setSelected(data.isOperatorBlocked());
+ oscillatory.setSelected(data.isOscillatory());
+ outOfRange.setSelected(data.isOutOfRange());
+ overflow.setSelected(data.isOverflow());
+ substituded.setSelected(data.isSubstituted());
+ test.setSelected(data.isTest());
+ }
+
+ @Override
+ protected void writeImpl() {
+ // TODO uncomment once mutators are implemented
+ // data.setValidity(validity.getSelectedItem());
+ // data.setBadReference(badReference.isSelected());
+ // data.setFailure(failure.isSelected());
+ // data.setInaccurate(inaccurate.isSelected());
+ // data.setInconsistent(inconsistent.isSelected());
+ // data.setOldData(oldData.isSelected());
+ // data.setOperatorBlocked(operatorBlocked.isSelected());
+ // data.setOlscillatory(oscillatory.isSelected());
+ // data.setOutOfRange(outOfRange.isSelected());
+ // data.setOverflow(overflow.isSelected());
+ // data.setSubstituded(substituded.isSelected());
+ // data.setTest(test.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/ReasonForInclusionDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/ReasonForInclusionDataBind.java
new file mode 100644
index 0000000..9085073
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/ReasonForInclusionDataBind.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaReasonForInclusion;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class ReasonForInclusionDataBind extends BasicDataBind {
+
+ private final JCheckBox applicationTrigger = new JCheckBox("ApplicationTrigger");
+ private final JCheckBox dataChange = new JCheckBox("DataChange");
+ private final JCheckBox dataUpdate = new JCheckBox("DataUpdate");
+ private final JCheckBox generalInterrogation = new JCheckBox("GeneralInterrogation");
+ private final JCheckBox integrity = new JCheckBox("Integrity");
+ private final JCheckBox qualitychanged = new JCheckBox("QualityChanged");
+
+ public ReasonForInclusionDataBind(BdaReasonForInclusion data) {
+ super(data, BdaType.REASON_FOR_INCLUSION);
+ }
+
+ @Override
+ protected JComponent init() {
+ applicationTrigger.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JPanel valuePanel = new JPanel();
+ valuePanel.setLayout(new BoxLayout(valuePanel, BoxLayout.PAGE_AXIS));
+ valuePanel.add(applicationTrigger);
+ valuePanel.add(dataChange);
+ valuePanel.add(dataUpdate);
+ valuePanel.add(generalInterrogation);
+ valuePanel.add(integrity);
+ valuePanel.add(qualitychanged);
+ return valuePanel;
+ }
+
+ @Override
+ protected void resetImpl() {
+ applicationTrigger.setSelected(data.isApplicationTrigger());
+ dataChange.setSelected(data.isDataChange());
+ dataUpdate.setSelected(data.isDataUpdate());
+ generalInterrogation.setSelected(data.isGeneralInterrogation());
+ integrity.setSelected(data.isIntegrity());
+ qualitychanged.setSelected(data.isQualityChange());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setApplicationTrigger(applicationTrigger.isSelected());
+ data.setDataChange(dataChange.isSelected());
+ data.setDataUpdate(dataUpdate.isSelected());
+ data.setGeneralInterrogation(generalInterrogation.isSelected());
+ data.setIntegrity(integrity.isSelected());
+ data.setQualityChange(qualitychanged.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/TapCommandDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TapCommandDataBind.java
new file mode 100644
index 0000000..1270a0d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TapCommandDataBind.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaTapCommand;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+
+public class TapCommandDataBind extends BasicDataBind {
+
+ @SuppressWarnings("unchecked")
+ private final JComboBox tapCommand = new JComboBox(BdaTapCommand.TapCommand.values());
+
+ public TapCommandDataBind(BdaTapCommand data) {
+ super(data, BdaType.TAP_COMMAND);
+ }
+
+ @Override
+ protected JComponent init() {
+ return tapCommand;
+ }
+
+ @Override
+ protected void resetImpl() {
+ tapCommand.setSelectedItem(data.getTapCommand());
+ }
+
+ @Override
+ protected void writeImpl() {
+ // TODO uncomment once data.setTapCommand is implemented
+ // data.setTapCommand(tapCommand.getSelectedItem());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/TextFieldDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TextFieldDataBind.java
new file mode 100644
index 0000000..4b4e7a3
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TextFieldDataBind.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BasicDataAttribute;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import javax.swing.JComponent;
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.DocumentFilter;
+import javax.swing.text.PlainDocument;
+
+public abstract class TextFieldDataBind extends BasicDataBind {
+
+ private final DocumentFilter filter;
+ protected JTextField inputField;
+
+ TextFieldDataBind(E data, BdaType type, AbstractFilter filter) {
+ super(data, type);
+ this.filter = filter;
+ }
+
+ @Override
+ protected JComponent init() {
+ inputField = new JTextField();
+ PlainDocument doc = (PlainDocument) inputField.getDocument();
+ doc.setDocumentFilter(filter);
+ resetImpl();
+ return inputField;
+ }
+
+ protected abstract static class AbstractFilter extends DocumentFilter {
+ @Override
+ public final void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
+ throws BadLocationException {
+
+ Document doc = fb.getDocument();
+ StringBuilder sb = new StringBuilder();
+ sb.append(doc.getText(0, doc.getLength()));
+ sb.insert(offset, string);
+
+ if (test(sb.toString())) {
+ super.insertString(fb, offset, string, attr);
+ }
+ }
+
+ @Override
+ public final void replace(
+ FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
+ throws BadLocationException {
+
+ Document doc = fb.getDocument();
+ StringBuilder sb = new StringBuilder();
+ sb.append(doc.getText(0, doc.getLength()));
+ sb.replace(offset, offset + length, text);
+
+ if (test(sb.toString())) {
+ super.replace(fb, offset, length, text, attrs);
+ }
+ }
+
+ @Override
+ public final void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
+ Document doc = fb.getDocument();
+ StringBuilder sb = new StringBuilder();
+ sb.append(doc.getText(0, doc.getLength()));
+ sb.delete(offset, offset + length);
+
+ if (test(sb.toString())) {
+ super.remove(fb, offset, length);
+ }
+ }
+
+ protected abstract boolean test(String text);
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/TimeStampDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TimeStampDataBind.java
new file mode 100644
index 0000000..d193463
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TimeStampDataBind.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaTimestamp;
+import com.beanit.iec61850bean.BdaType;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+
+public class TimeStampDataBind extends TextFieldDataBind {
+
+ private static final TimestampFilter FILTER = new TimestampFilter();
+
+ public TimeStampDataBind(BdaTimestamp data) {
+ super(data, BdaType.TIMESTAMP, FILTER);
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(data.getInstant().toString());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setInstant(Instant.parse(inputField.getText()));
+ }
+
+ private static class TimestampFilter extends AbstractFilter {
+ @Override
+ protected boolean test(String text) {
+ try {
+ Instant.parse(text);
+ return true;
+ } catch (DateTimeParseException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/TriggerConditionDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TriggerConditionDataBind.java
new file mode 100644
index 0000000..9ed6168
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/TriggerConditionDataBind.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaTriggerConditions;
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.clientgui.BasicDataBind;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class TriggerConditionDataBind extends BasicDataBind {
+
+ private final JCheckBox dataChange = new JCheckBox("DataChange");
+ private final JCheckBox dataUpdate = new JCheckBox("DataUpdate");
+ private final JCheckBox generalInterrogation = new JCheckBox("GeneralInterrogation");
+ private final JCheckBox integrity = new JCheckBox("Integrity");
+ private final JCheckBox qualityChange = new JCheckBox("QualityChange");
+
+ public TriggerConditionDataBind(BdaTriggerConditions data) {
+ super(data, BdaType.TRIGGER_CONDITIONS);
+ }
+
+ @Override
+ protected JComponent init() {
+ dataChange.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JPanel valuePanel = new JPanel();
+ valuePanel.setLayout(new BoxLayout(valuePanel, BoxLayout.PAGE_AXIS));
+ valuePanel.add(dataChange);
+ valuePanel.add(dataUpdate);
+ valuePanel.add(generalInterrogation);
+ valuePanel.add(integrity);
+ valuePanel.add(qualityChange);
+ return valuePanel;
+ }
+
+ @Override
+ protected void resetImpl() {
+ dataChange.setSelected(data.isDataChange());
+ dataUpdate.setSelected(data.isDataUpdate());
+ generalInterrogation.setSelected(data.isGeneralInterrogation());
+ integrity.setSelected(data.isIntegrity());
+ qualityChange.setSelected(data.isQualityChange());
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setDataChange(dataChange.isSelected());
+ data.setDataUpdate(dataUpdate.isSelected());
+ data.setGeneralInterrogation(generalInterrogation.isSelected());
+ data.setIntegrity(integrity.isSelected());
+ data.setQualityChange(qualityChange.isSelected());
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/UnicodeStringDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/UnicodeStringDataBind.java
new file mode 100644
index 0000000..85d51b9
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/UnicodeStringDataBind.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.BdaUnicodeString;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+
+public class UnicodeStringDataBind extends TextFieldDataBind {
+
+ private static final Charset UTF8 = StandardCharsets.UTF_8;
+
+ public UnicodeStringDataBind(BdaUnicodeString data) {
+ super(data, BdaType.UNICODE_STRING, new Utf8Filter(data.getMaxLength()));
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(new String(data.getValue(), UTF8));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(UTF8.encode(inputField.getText()).array());
+ }
+
+ private static class Utf8Filter extends AbstractFilter {
+ private final CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ private final int maxBytes;
+
+ public Utf8Filter(int maxBytes) {
+ this.maxBytes = maxBytes;
+ }
+
+ @Override
+ protected boolean test(String text) {
+ try {
+ byte[] codedString = encoder.encode(CharBuffer.wrap(text)).array();
+ return codedString.length <= maxBytes;
+ } catch (CharacterCodingException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/databind/VisibleStringDataBind.java b/src/main/java/com/beanit/iec61850bean/clientgui/databind/VisibleStringDataBind.java
new file mode 100644
index 0000000..5acba4e
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/databind/VisibleStringDataBind.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.databind;
+
+import com.beanit.iec61850bean.BdaType;
+import com.beanit.iec61850bean.BdaVisibleString;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+
+public class VisibleStringDataBind extends TextFieldDataBind {
+
+ private static final Charset ASCII = StandardCharsets.US_ASCII;
+
+ public VisibleStringDataBind(BdaVisibleString data) {
+ super(data, BdaType.VISIBLE_STRING, new AsciiFilter(data.getMaxLength()));
+ }
+
+ @Override
+ protected void resetImpl() {
+ inputField.setText(new String(data.getValue(), ASCII));
+ }
+
+ @Override
+ protected void writeImpl() {
+ data.setValue(ASCII.encode(inputField.getText()).array());
+ }
+
+ private static class AsciiFilter extends TextFieldDataBind.AbstractFilter {
+ private final CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder();
+ private final int maxBytes;
+
+ public AsciiFilter(int maxBytes) {
+ this.maxBytes = maxBytes;
+ }
+
+ @Override
+ protected boolean test(String text) {
+ try {
+ byte[] codedString = encoder.encode(CharBuffer.wrap(text)).array();
+ return codedString.length <= maxBytes;
+ } catch (CharacterCodingException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/clientgui/util/Counter.java b/src/main/java/com/beanit/iec61850bean/clientgui/util/Counter.java
new file mode 100644
index 0000000..c29f48f
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/clientgui/util/Counter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 The IEC61850bean Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.clientgui.util;
+
+public class Counter {
+ private int value;
+
+ public Counter(int value) {
+ this.value = value;
+ }
+
+ public Counter() {
+ this(0);
+ }
+
+ public void increment() {
+ value++;
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/internal/BerBoolean.java b/src/main/java/com/beanit/iec61850bean/internal/BerBoolean.java
new file mode 100644
index 0000000..635282d
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/internal/BerBoolean.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 The jASN1 Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.internal;
+
+import com.beanit.asn1bean.ber.BerLength;
+import com.beanit.asn1bean.ber.BerTag;
+import com.beanit.asn1bean.ber.ReverseByteArrayOutputStream;
+import com.beanit.asn1bean.ber.types.BerType;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+public class BerBoolean implements Serializable, BerType {
+
+ public static final BerTag tag =
+ new BerTag(BerTag.UNIVERSAL_CLASS, BerTag.PRIMITIVE, BerTag.BOOLEAN_TAG);
+ private static final long serialVersionUID = 1L;
+ public boolean value;
+ private byte[] code = null;
+
+ public BerBoolean() {}
+
+ public BerBoolean(byte[] code) {
+ this.code = code;
+ }
+
+ public BerBoolean(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public int encode(OutputStream reverseOS) throws IOException {
+ return encode(reverseOS, true);
+ }
+
+ public int encode(OutputStream reverseOS, boolean withTag) throws IOException {
+
+ if (code != null) {
+ reverseOS.write(code);
+ if (withTag) {
+ return tag.encode(reverseOS) + code.length;
+ }
+ return code.length;
+ }
+
+ int codeLength = 1;
+
+ if (value) {
+ reverseOS.write(0x01);
+ } else {
+ reverseOS.write(0);
+ }
+
+ codeLength += BerLength.encodeLength(reverseOS, codeLength);
+
+ if (withTag) {
+ codeLength += tag.encode(reverseOS);
+ }
+
+ return codeLength;
+ }
+
+ @Override
+ public int decode(InputStream is) throws IOException {
+ return decode(is, true);
+ }
+
+ public int decode(InputStream is, boolean withTag) throws IOException {
+
+ int codeLength = 0;
+
+ if (withTag) {
+ codeLength += tag.decodeAndCheck(is);
+ }
+
+ BerLength length = new BerLength();
+ codeLength += length.decode(is);
+
+ if (length.val != 1) {
+ throw new IOException("Decoded length of BerBoolean is not correct");
+ }
+
+ int nextByte = is.read();
+ if (nextByte == -1) {
+ throw new EOFException("Unexpected end of input stream.");
+ }
+
+ codeLength++;
+ value = nextByte != 0;
+
+ return codeLength;
+ }
+
+ public void encodeAndSave(int encodingSizeGuess) throws IOException {
+ ReverseByteArrayOutputStream os = new ReverseByteArrayOutputStream(encodingSizeGuess);
+ encode(os, false);
+ code = os.getArray();
+ }
+
+ @Override
+ public String toString() {
+ return "" + value;
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/internal/HexString.java b/src/main/java/com/beanit/iec61850bean/internal/HexString.java
new file mode 100644
index 0000000..0636370
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/internal/HexString.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2019 beanit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.internal;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+public class HexString {
+
+ private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ /** Don't let anyone instantiate this class. */
+ private HexString() {}
+
+ /**
+ * Returns the byte as a hex string. If b is less than 16 the hex string returned contains a
+ * leading zero.
+ *
+ * @param b the byte to be converted
+ * @return the hex string.
+ */
+ public static String fromByte(byte b) {
+ return fromBytes(new byte[] {b});
+ }
+
+ public static String fromByte(int b) {
+ return fromBytes(new byte[] {(byte) b});
+ }
+
+ /**
+ * Returns the integer value as hex string filled with leading zeros.
+ *
+ * @param i the integer value to be converted
+ * @return the hex string
+ */
+ public static String fromInt(int i) {
+ byte[] bytes = new byte[] {(byte) (i >> 24), (byte) (i >> 16), (byte) (i >> 8), (byte) i};
+ return fromBytes(bytes);
+ }
+
+ /**
+ * Returns the long value as hex string filled with leading zeros.
+ *
+ * @param l the long value to be converted
+ * @return the hex string
+ */
+ public static String fromLong(long l) {
+ byte[] bytes =
+ new byte[] {
+ (byte) (l >> 56),
+ (byte) (l >> 48),
+ (byte) (l >> 40),
+ (byte) (l >> 32),
+ (byte) (l >> 24),
+ (byte) (l >> 16),
+ (byte) (l >> 8),
+ (byte) l
+ };
+ return fromBytes(bytes);
+ }
+
+ public static String fromBytes(byte[] bytes) {
+ return fromBytes(bytes, 0, bytes.length);
+ }
+
+ public static String fromBytesFormatted(byte[] bytes) {
+ return fromBytesFormatted(bytes, 0, bytes.length);
+ }
+
+ public static String fromBytes(byte[] bytes, int offset, int length) {
+ char[] hexChars = new char[length * 2];
+ for (int j = 0; j < length; j++) {
+ int v = bytes[j + offset] & 0xff;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0f];
+ }
+ return new String(hexChars);
+ }
+
+ public static String fromBytes(ByteBuffer buffer) {
+ return fromBytes(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.limit());
+ }
+
+ public static String fromBytesFormatted(byte[] bytes, int offset, int length) {
+ StringBuilder builder = new StringBuilder();
+
+ int l = 1;
+ for (int i = offset; i < (offset + length); i++) {
+ if ((l != 1) && ((l - 1) % 8 == 0)) {
+ builder.append(' ');
+ }
+ if ((l != 1) && ((l - 1) % 16 == 0)) {
+ builder.append('\n');
+ }
+ l++;
+ appendFromByte(bytes[i], builder);
+ if (i != offset + length - 1) {
+ builder.append(' ');
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Converts the given hex string to a byte array.
+ *
+ * @param hexString the hex string
+ * @return the bytes
+ * @throws NumberFormatException if the string is not a valid hex string
+ */
+ public static byte[] toBytes(String hexString) {
+
+ Objects.requireNonNull(hexString);
+ if ((hexString.length() == 0) || ((hexString.length() % 2) != 0)) {
+ throw new NumberFormatException("argument is not a valid hex string");
+ }
+
+ int length = hexString.length();
+
+ byte[] data = new byte[length / 2];
+ for (int i = 0; i < length; i += 2) {
+ int firstCharacter = Character.digit(hexString.charAt(i), 16);
+ int secondCharacter = Character.digit(hexString.charAt(i + 1), 16);
+
+ if (firstCharacter == -1 || secondCharacter == -1) {
+ throw new NumberFormatException("argument is not a valid hex string");
+ }
+
+ data[i / 2] = (byte) ((firstCharacter << 4) + secondCharacter);
+ }
+ return data;
+ }
+
+ public static void appendFromByte(byte b, StringBuilder builder) {
+ builder.append(fromByte(b));
+ }
+
+ public static void appendFromBytes(StringBuilder builder, byte[] bytes, int offset, int length) {
+ builder.append(fromBytes(bytes, offset, length));
+ }
+}
diff --git a/src/main/java/com/beanit/iec61850bean/internal/NamedThreadFactory.java b/src/main/java/com/beanit/iec61850bean/internal/NamedThreadFactory.java
new file mode 100644
index 0000000..2d663ce
--- /dev/null
+++ b/src/main/java/com/beanit/iec61850bean/internal/NamedThreadFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 beanit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.beanit.iec61850bean.internal;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class NamedThreadFactory implements ThreadFactory {
+
+ private final AtomicInteger threadCounter = new AtomicInteger(1);
+ private final String namePrefix;
+
+ /**
+ * Creates a thread factory with the given pool name as a name prefix. Threads created will have
+ * the name {@code -thread-