You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iec61850bean/src/main/java/com/beanit/openiec61850/ClientAssociation.java

2128 lines
81 KiB
Java

/*
* Copyright 2011 The OpenIEC61850 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.openiec61850;
import com.beanit.jasn1.ber.ReverseByteArrayOutputStream;
import com.beanit.jasn1.ber.types.BerBoolean;
import com.beanit.jasn1.ber.types.BerInteger;
import com.beanit.jasn1.ber.types.BerNull;
import com.beanit.jasn1.ber.types.string.BerGraphicString;
import com.beanit.jasn1.ber.types.string.BerVisibleString;
import com.beanit.josistack.AcseAssociation;
import com.beanit.josistack.ByteBufferInputStream;
import com.beanit.josistack.ClientAcseSap;
import com.beanit.josistack.DecodingException;
import com.beanit.openiec61850.internal.mms.asn1.AccessResult;
import com.beanit.openiec61850.internal.mms.asn1.ConfirmedRequestPDU;
import com.beanit.openiec61850.internal.mms.asn1.ConfirmedResponsePDU;
import com.beanit.openiec61850.internal.mms.asn1.ConfirmedServiceRequest;
import com.beanit.openiec61850.internal.mms.asn1.ConfirmedServiceResponse;
import com.beanit.openiec61850.internal.mms.asn1.Data;
import com.beanit.openiec61850.internal.mms.asn1.DefineNamedVariableListRequest;
import com.beanit.openiec61850.internal.mms.asn1.DeleteNamedVariableListRequest;
import com.beanit.openiec61850.internal.mms.asn1.DeleteNamedVariableListRequest.ListOfVariableListName;
import com.beanit.openiec61850.internal.mms.asn1.DeleteNamedVariableListResponse;
import com.beanit.openiec61850.internal.mms.asn1.DirectoryEntry;
import com.beanit.openiec61850.internal.mms.asn1.FileCloseRequest;
import com.beanit.openiec61850.internal.mms.asn1.FileDeleteRequest;
import com.beanit.openiec61850.internal.mms.asn1.FileDirectoryRequest;
import com.beanit.openiec61850.internal.mms.asn1.FileDirectoryResponse;
import com.beanit.openiec61850.internal.mms.asn1.FileName;
import com.beanit.openiec61850.internal.mms.asn1.FileOpenRequest;
import com.beanit.openiec61850.internal.mms.asn1.FileReadRequest;
import com.beanit.openiec61850.internal.mms.asn1.GetNameListRequest;
import com.beanit.openiec61850.internal.mms.asn1.GetNameListRequest.ObjectScope;
import com.beanit.openiec61850.internal.mms.asn1.GetNameListResponse;
import com.beanit.openiec61850.internal.mms.asn1.GetNamedVariableListAttributesRequest;
import com.beanit.openiec61850.internal.mms.asn1.GetNamedVariableListAttributesResponse;
import com.beanit.openiec61850.internal.mms.asn1.GetVariableAccessAttributesRequest;
import com.beanit.openiec61850.internal.mms.asn1.Identifier;
import com.beanit.openiec61850.internal.mms.asn1.InitiateRequestPDU;
import com.beanit.openiec61850.internal.mms.asn1.InitiateResponsePDU;
import com.beanit.openiec61850.internal.mms.asn1.Integer16;
import com.beanit.openiec61850.internal.mms.asn1.Integer32;
import com.beanit.openiec61850.internal.mms.asn1.Integer8;
import com.beanit.openiec61850.internal.mms.asn1.MMSpdu;
import com.beanit.openiec61850.internal.mms.asn1.ObjectClass;
import com.beanit.openiec61850.internal.mms.asn1.ObjectName;
import com.beanit.openiec61850.internal.mms.asn1.ParameterSupportOptions;
import com.beanit.openiec61850.internal.mms.asn1.ReadRequest;
import com.beanit.openiec61850.internal.mms.asn1.ReadResponse;
import com.beanit.openiec61850.internal.mms.asn1.RejectPDU.RejectReason;
import com.beanit.openiec61850.internal.mms.asn1.ServiceError.ErrorClass;
import com.beanit.openiec61850.internal.mms.asn1.ServiceSupportOptions;
import com.beanit.openiec61850.internal.mms.asn1.UnconfirmedPDU;
import com.beanit.openiec61850.internal.mms.asn1.UnconfirmedService;
import com.beanit.openiec61850.internal.mms.asn1.Unsigned32;
import com.beanit.openiec61850.internal.mms.asn1.VariableAccessSpecification;
import com.beanit.openiec61850.internal.mms.asn1.VariableDefs;
import com.beanit.openiec61850.internal.mms.asn1.WriteRequest;
import com.beanit.openiec61850.internal.mms.asn1.WriteRequest.ListOfData;
import com.beanit.openiec61850.internal.mms.asn1.WriteResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
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;
/**
* Represents an association/connection to an IEC 61850 MMS server. An instance of <code>
* ClientAssociation</code> is obtained using <code>ClientSap</code>. 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
* <code>retrieveModel()</code>. 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<MMSpdu> 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 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;
}
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) {
}
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.");
}
byte[] 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<String> ldNames = retrieveLogicalDevices();
List<List<String>> lnNames = new ArrayList<>(ldNames.size());
for (int i = 0; i < ldNames.size(); i++) {
lnNames.add(retrieveLogicalNodeNames(ldNames.get(i)));
}
List<LogicalDevice> lds = new ArrayList<>();
for (int i = 0; i < ldNames.size(); i++) {
List<LogicalNode> 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<String> 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<String> 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<Identifier> identifiers =
confirmedServiceResponse.getGetNameList().getListOfIdentifier().getIdentifier();
ArrayList<String> objectRefs = new ArrayList<>(); // ObjectReference[identifiers.size()];
for (BerVisibleString identifier : identifiers) {
objectRefs.add(identifier.toString());
}
return objectRefs;
}
private List<String> retrieveLogicalNodeNames(String ld) throws ServiceError, IOException {
List<String> 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<String> 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<Identifier> identifiers = getNameListResponse.getListOfIdentifier().getIdentifier();
if (identifiers.size() == 0) {
throw new ServiceError(
ServiceError.INSTANCE_NOT_AVAILABLE,
"decodeGetLDDirectoryResponse: Instance not available");
}
BerVisibleString identifier = null;
Iterator<Identifier> 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 <code>ServiceError</code> 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<FileInformation> files)
throws ServiceError {
if (confirmedServiceResponse.getFileDirectory() == null) {
throw new ServiceError(
ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,
"Error decoding GetFileDirectoryResponsePdu");
}
FileDirectoryResponse fileDirectoryRes = confirmedServiceResponse.getFileDirectory();
List<DirectoryEntry> entries = fileDirectoryRes.getListOfDirectoryEntry().getDirectoryEntry();
for (DirectoryEntry entry : entries) {
List<BerGraphicString> 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<FileInformation> getFileDirectory(String directoryName)
throws ServiceError, IOException {
List<FileInformation> files = new LinkedList<>();
boolean moreFollows = true;
String continueAfter = null;
while (moreFollows) {
FileDirectoryRequest fileDirectoryRequest = new FileDirectoryRequest();
BerGraphicString berGraphicString = new BerGraphicString(directoryName.getBytes());
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()));
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()));
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()));
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<AccessResult> 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
* <code>ServiceError</code> 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<Data> 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<VariableDefs.SEQUENCE> 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<ModelNode> 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<Identifier> 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<VariableDefs.SEQUENCE> variables =
getNamedVariableListAttResponse.getListOfVariable().getSEQUENCE();
if (variables.size() == 0) {
throw new ServiceError(
ServiceError.INSTANCE_NOT_AVAILABLE,
"decodeGetDataSetDirectoryResponse: Instance not available");
}
List<FcModelNode> 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.SEQUENCE> 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<ObjectName> 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 <code>
* null</code> 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<ServiceError> getDataSetValues(DataSet dataSet) throws IOException {
ConfirmedServiceResponse confirmedServiceResponse;
try {
ConfirmedServiceRequest serviceRequest = constructGetDataSetValuesRequest(dataSet);
confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
} catch (ServiceError e) {
int dataSetSize = dataSet.getMembers().size();
List<ServiceError> 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<ServiceError> decodeGetDataSetValuesResponse(
ConfirmedServiceResponse confirmedServiceResponse, DataSet ds) {
int dataSetSize = ds.getMembers().size();
List<ServiceError> 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<AccessResult> 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<AccessResult> 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<ServiceError> 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<Data> 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<ServiceError> 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<WriteResponse.CHOICE> writeResChoiceType = writeResponse.getCHOICE();
List<ServiceError> 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.
*
* <p>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.
*
* <p>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<ServiceError> setRcbValues(
Rcb rcb,
boolean setRptId,
boolean setDatSet,
boolean setOptFlds,
boolean setBufTm,
boolean setTrgOps,
boolean setIntgPd,
boolean setPurgeBuf,
boolean setEntryId)
throws IOException {
List<FcModelNode> 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<ServiceError> 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<AccessResult> 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<FcModelNode> 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<BdaReasonForInclusion> 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).
*
* <p>The selection is canceled in one of the following events:
*
* <ul>
* <li>The "Cancel" ACSI service is issued.
* <li>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.
* <li>The connection to the server is closed.
* <li>An operate service failed because of some error
* <li>The sboClass is set to "operate-once" then the selection is also canceled after a
* successful operate service.
* </ul>
*
* @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):
*
* <ul>
* <li>Oper.ctlVal - has to be set to actual control value that is to be written using the
* operate service.
* <li>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.
* <li>Oper.check (type: BdaCheck) is used to tell the server whether to perform the
* synchrocheck and interlockcheck. By default they are turned off.
* <li>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).
* <li>Oper.Test (BdaBoolean) - if true this operate command is sent for test purposes only.
* Default is false.
* </ul>
*
* 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);
}
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) {
}
}
}
} 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) {
}
}
}
} 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) {
}
}
}
}
}
} 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();
}
}
}
}