
org.openmuc.openiec61850.ClientAssociation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openiec61850 Show documentation
Show all versions of openiec61850 Show documentation
OpenIEC61850 is a library implementing the IEC 61850 MMS communication standard (client and server).
/*
* Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors
*
* 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 org.openmuc.openiec61850;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.openmuc.jasn1.ber.BerByteArrayOutputStream;
import org.openmuc.jasn1.ber.types.BerBoolean;
import org.openmuc.jasn1.ber.types.BerInteger;
import org.openmuc.jasn1.ber.types.BerNull;
import org.openmuc.jasn1.ber.types.string.BerVisibleString;
import org.openmuc.josistack.AcseAssociation;
import org.openmuc.josistack.ByteBufferInputStream;
import org.openmuc.josistack.ClientAcseSap;
import org.openmuc.josistack.DecodingException;
import org.openmuc.openiec61850.internal.mms.asn1.AccessResult;
import org.openmuc.openiec61850.internal.mms.asn1.ConfirmedRequestPDU;
import org.openmuc.openiec61850.internal.mms.asn1.ConfirmedResponsePDU;
import org.openmuc.openiec61850.internal.mms.asn1.ConfirmedServiceRequest;
import org.openmuc.openiec61850.internal.mms.asn1.ConfirmedServiceResponse;
import org.openmuc.openiec61850.internal.mms.asn1.Data;
import org.openmuc.openiec61850.internal.mms.asn1.DefineNamedVariableListRequest;
import org.openmuc.openiec61850.internal.mms.asn1.DeleteNamedVariableListRequest;
import org.openmuc.openiec61850.internal.mms.asn1.DeleteNamedVariableListRequest.ListOfVariableListName;
import org.openmuc.openiec61850.internal.mms.asn1.DeleteNamedVariableListResponse;
import org.openmuc.openiec61850.internal.mms.asn1.GetNameListRequest;
import org.openmuc.openiec61850.internal.mms.asn1.GetNameListRequest.ObjectScope;
import org.openmuc.openiec61850.internal.mms.asn1.GetNameListResponse;
import org.openmuc.openiec61850.internal.mms.asn1.GetNamedVariableListAttributesRequest;
import org.openmuc.openiec61850.internal.mms.asn1.GetNamedVariableListAttributesResponse;
import org.openmuc.openiec61850.internal.mms.asn1.GetVariableAccessAttributesRequest;
import org.openmuc.openiec61850.internal.mms.asn1.Identifier;
import org.openmuc.openiec61850.internal.mms.asn1.InitiateRequestPDU;
import org.openmuc.openiec61850.internal.mms.asn1.InitiateResponsePDU;
import org.openmuc.openiec61850.internal.mms.asn1.Integer16;
import org.openmuc.openiec61850.internal.mms.asn1.Integer32;
import org.openmuc.openiec61850.internal.mms.asn1.Integer8;
import org.openmuc.openiec61850.internal.mms.asn1.MMSpdu;
import org.openmuc.openiec61850.internal.mms.asn1.ObjectClass;
import org.openmuc.openiec61850.internal.mms.asn1.ObjectName;
import org.openmuc.openiec61850.internal.mms.asn1.ParameterSupportOptions;
import org.openmuc.openiec61850.internal.mms.asn1.ReadRequest;
import org.openmuc.openiec61850.internal.mms.asn1.ReadResponse;
import org.openmuc.openiec61850.internal.mms.asn1.RejectPDU.RejectReason;
import org.openmuc.openiec61850.internal.mms.asn1.ServiceError.ErrorClass;
import org.openmuc.openiec61850.internal.mms.asn1.ServiceSupportOptions;
import org.openmuc.openiec61850.internal.mms.asn1.UnconfirmedPDU;
import org.openmuc.openiec61850.internal.mms.asn1.UnconfirmedService;
import org.openmuc.openiec61850.internal.mms.asn1.Unsigned32;
import org.openmuc.openiec61850.internal.mms.asn1.VariableAccessSpecification;
import org.openmuc.openiec61850.internal.mms.asn1.VariableDefs;
import org.openmuc.openiec61850.internal.mms.asn1.WriteRequest;
import org.openmuc.openiec61850.internal.mms.asn1.WriteRequest.ListOfData;
import org.openmuc.openiec61850.internal.mms.asn1.WriteResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 Logger logger = LoggerFactory.getLogger(ClientAssociation.class);
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 AcseAssociation acseAssociation = null;
private final ClientReceiver clientReceiver;
private final BlockingQueue incomingResponses = new LinkedBlockingQueue<>();
private final BerByteArrayOutputStream berOStream = new BerByteArrayOutputStream(500, true);
ServerModel serverModel;
private int responseTimeout;
private int invokeId = 0;
private int negotiatedMaxPduSize;
private ClientEventListener reportListener = null;
private boolean closed = false;
final class ClientReceiver extends Thread {
private Integer expectedResponseId;
private final ByteBuffer pduBuffer;
private IOException lastIOException = null;
public ClientReceiver(int maxMmsPduSize) {
pduBuffer = ByteBuffer.allocate(maxMmsPduSize + 400);
}
@Override
public void run() {
try {
while (true) {
pduBuffer.clear();
try {
acseAssociation.receive(pduBuffer);
} catch (TimeoutException e) {
logger.error("Illegal state: A timeout exception was thrown.", e);
throw new IllegalStateException();
} catch (DecodingException e) {
logger.warn("Error decoding the OSI headers of the received packet", e);
continue;
}
MMSpdu decodedResponsePdu = new MMSpdu();
try {
decodedResponsePdu.decode(new ByteBufferInputStream(pduBuffer), null);
} catch (IOException e) {
logger.warn("Error decoding the received MMS PDU", e);
continue;
}
if (decodedResponsePdu.getUnconfirmedPDU() != null) {
if (decodedResponsePdu.getUnconfirmedPDU()
.getService()
.getInformationReport()
.getVariableAccessSpecification()
.getListOfVariable() != null) {
logger.debug("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 {
logger.debug("discarding report because no ReportListener was registered.");
}
}
}
else if (decodedResponsePdu.getRejectPDU() != null) {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
logger.warn("Discarding Reject MMS PDU because no listener for request was found.");
continue;
}
else if (decodedResponsePdu.getRejectPDU().getOriginalInvokeID().value
.intValue() != expectedResponseId) {
logger.warn(
"Discarding Reject MMS PDU because no listener with fitting invokeID was found.");
continue;
}
else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
}
else if (decodedResponsePdu.getConfirmedErrorPDU() != null) {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
logger.warn(
"Discarding ConfirmedError MMS PDU because no listener for request was found.");
continue;
}
else if (decodedResponsePdu.getConfirmedErrorPDU().getInvokeID().value
.intValue() != expectedResponseId) {
logger.warn(
"Discarding ConfirmedError MMS PDU because no listener with fitting invokeID was found.");
continue;
}
else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
}
else {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
logger.warn(
"Discarding ConfirmedResponse MMS PDU because no listener for request was found.");
continue;
}
else if (decodedResponsePdu.getConfirmedResponsePDU().getInvokeID().value
.intValue() != expectedResponseId) {
logger.warn(
"Discarding ConfirmedResponse MMS PDU because no listener with fitting invokeID was found.");
continue;
}
else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
}
}
} 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) {
}
}
}
}
private void close(IOException e) {
synchronized (this) {
if (closed == false) {
closed = true;
acseAssociation.close();
lastIOException = e;
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) {
}
}
}
}
IOException getLastIOException() {
return lastIOException;
}
MMSpdu removeExpectedResponse() {
synchronized (incomingResponses) {
expectedResponseId = null;
return incomingResponses.poll();
}
}
}
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();
}
/**
* 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;
}
/**
* 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;
}
private int getInvokeId() {
invokeId = (invokeId + 1) % 2147483647;
return invokeId;
}
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\"");
}
}
}
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 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);
berOStream.reset();
try {
requestPdu.encode(berOStream);
} catch (Exception e) {
IOException e2 = new IOException("Error encoding MmsPdu.", e);
clientReceiver.close(e2);
throw e2;
}
clientReceiver.setResponseExpected(currentInvokeId);
try {
acseAssociation.send(berOStream.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) {
}
if (decodedResponsePdu == null) {
decodedResponsePdu = clientReceiver.removeExpectedResponse();
if (decodedResponsePdu == null) {
throw new ServiceError(ServiceError.TIMEOUT);
}
}
if (decodedResponsePdu.getConfirmedRequestPDU() != null) {
incomingResponses.add(decodedResponsePdu);
throw 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);
BerByteArrayOutputStream berOStream = new BerByteArrayOutputStream(500, true);
initiateRequestMMSpdu.encode(berOStream);
try {
acseAssociation = acseSap.associate(address, port, localAddr, localPort, authenticationParameter,
berOStream.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 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;
}
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.");
}
byte[] servicesSupported = initiateResponsePdu.getInitResponseDetail().getServicesSupportedCalled().value;
if ((servicesSupported[0] & 0x40) != 0x40) {
throw new IOException("Obligatory services are not supported by the server.");
}
}
/**
* Parses the given SCL File and returns the server model that is described by it. This function can be used instead
* of retrieveModel
in order to get the server model that is needed to call the other ACSI services.
*
* @param sclFilePath
* the path to the SCL file that is to be parsed.
* @return The ServerNode that is the root node of the complete server model.
* @throws SclParseException
* if any kind of fatal error occurs in the parsing process.
*/
public ServerModel getModelFromSclFile(String sclFilePath) throws SclParseException {
List serverSaps = ServerSap.getSapsFromSclFile(sclFilePath);
if (serverSaps == null || serverSaps.size() == 0) {
throw new SclParseException("No AccessPoint found in SCL file.");
}
serverModel = serverSaps.get(0).serverModel;
return serverModel;
}
/**
* 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 LinkedList<>();
String continueAfterRef = "";
do {
ConfirmedServiceRequest serviceRequest = constructGetDirectoryRequest(ld, continueAfterRef, true);
ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
continueAfterRef = decodeGetDirectoryResponse(confirmedServiceResponse, lns);
} while (continueAfterRef != "");
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 = null;
ObjectScope objectScopeChoiceType = new ObjectScope();
objectScopeChoiceType.setDomainSpecific(new Identifier(ldRef.getBytes()));
getNameListRequest = new GetNameListRequest();
getNameListRequest.setObjectClass(objectClass);
getNameListRequest.setObjectScope(objectScopeChoiceType);
if (continueAfter != "") {
getNameListRequest.setContinueAfter(new Identifier(continueAfter.getBytes()));
}
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()));
domainSpec.setItemID(new Identifier(lnRef.get(1).getBytes()));
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);
}
/**
* 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) {
getDataValues(fcdo);
}
}
}
}
}
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 were returned by the retrieveModel() or getModelFromSclFile() functions. 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()));
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.toString());
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
* reporting for this report control block is not enabled and if it is not reserved by another client. The
* parameters PurgeBuf, EntryId are only applicable if the given rcb is of type BRCB.
*
* @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) {
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;
BdaOptFlds optFlds;
Integer sqNum = null;
Integer subSqNum = null;
boolean moreSegmentsFollow = false;
String dataSetRef = null;
boolean bufOvfl = false;
Long confRev = null;
BdaEntryTime timeOfEntry = null;
BdaOctetString entryId = null;
byte[] inclusionBitString;
DataSet dataSet = null;
rptId = listRes.get(index++).getSuccess().getVisibleString().toString();
if (listRes.get(index).getSuccess().getBitString() == null) {
throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
"processReport: report does not contain OptFlds");
}
optFlds = new BdaOptFlds(new ObjectReference("none"));
optFlds.setValue(listRes.get(index++).getSuccess().getBitString().value);
if (optFlds.isSequenceNumber()) {
sqNum = listRes.get(index++).getSuccess().getUnsigned().intValue();
}
if (optFlds.isReportTimestamp()) {
timeOfEntry = new BdaEntryTime(new ObjectReference("none"), null, "", false, false);
timeOfEntry.setValueFromMmsDataObj(listRes.get(index++).getSuccess());
}
if (optFlds.isDataSetName()) {
dataSetRef = (listRes.get(index++).getSuccess().getVisibleString().toString());
}
if (optFlds.isBufferOverflow()) {
bufOvfl = (listRes.get(index++).getSuccess().getBool().value);
}
if (optFlds.isEntryId()) {
entryId = new BdaOctetString(new ObjectReference("none"), null, "", 8, false, false);
entryId.setValue(listRes.get(index++).getSuccess().getOctetString().value);
}
if (optFlds.isConfigRevision()) {
confRev = listRes.get(index++).getSuccess().getUnsigned().longValue();
}
if (optFlds.isSegmentation()) {
subSqNum = listRes.get(index++).getSuccess().getUnsigned().intValue();
moreSegmentsFollow = listRes.get(index++).getSuccess().getBool().value;
}
inclusionBitString = listRes.get(index++).getSuccess().getBitString().value;
if (optFlds.isDataReference()) {
// this is just to move the index to the right place
// The next part will process the changes to the values
// without the dataRefs
for (int i = 0; i < inclusionBitString.length * 8; i++) {
if ((inclusionBitString[i / 8] & (1 << (7 - i % 8))) == (1 << (7 - i % 8))) {
index++;
}
}
}
if (dataSetRef == null) {
for (Urcb urcb : serverModel.getUrcbs()) {
if ((urcb.getRptId() != null && urcb.getRptId().getStringValue().equals(rptId))
|| urcb.getReference().toString().equals(rptId)) {
dataSetRef = urcb.getDatSet().getStringValue();
break;
}
}
}
if (dataSetRef == null) {
for (Brcb brcb : serverModel.getBrcbs()) {
if ((brcb.getRptId() != null && brcb.getRptId().getStringValue().equals(rptId))
|| brcb.getReference().toString().equals(rptId)) {
dataSetRef = brcb.getDatSet().getStringValue();
break;
}
}
}
if (dataSetRef == null) {
throw new ServiceError(ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
"unable to find URCB that matches the given RptID in the report.");
}
// updating of data set copy - original stays the same
dataSet = serverModel.getDataSet(dataSetRef.replace('$', '.')).copy();
int shiftNum = 0;
for (ModelNode child : dataSet.getMembers()) {
if ((inclusionBitString[shiftNum / 8] & (1 << (7 - shiftNum % 8))) == (1 << (7 - shiftNum % 8))) {
AccessResult accessRes = listRes.get(index++);
child.setValueFromMmsDataObj(accessRes.getSuccess());
}
shiftNum++;
}
List reasonCodes = null;
if (optFlds.isReasonForInclusion()) {
reasonCodes = new ArrayList<>(dataSet.getMembers().size());
for (int i = 0; i < dataSet.getMembers().size(); i++) {
if ((inclusionBitString[i / 8] & (1 << (7 - i % 8))) == (1 << (7 - i % 8))) {
BdaReasonForInclusion reasonForInclusion = new BdaReasonForInclusion(null);
reasonCodes.add(reasonForInclusion);
byte[] reason = listRes.get(index++).getSuccess().getBitString().value;
reasonForInclusion.setValue(reason);
}
}
}
return new Report(rptId, optFlds, sqNum, subSqNum, moreSegmentsFollow, dataSetRef, bufOvfl, confRev,
timeOfEntry, entryId, inclusionBitString, reasonCodes, dataSet);
}
/**
* 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);
if (sbo.getValue().length == 0) {
return false;
}
return true;
}
/**
* 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")).setDate(new Date(System.currentTimeMillis()));
setDataValues(oper);
}
/**
* 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();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy