/*
* 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 javax.net.SocketFactory;
import org.openmuc.josistack.ClientAcseSap;
/**
* The ClientSap
class represents the IEC 61850 service access point for client applications. A client
* application that wants to connect to a server should first create an instance of ClientSap
. Next all the
* necessary configuration parameters can be set. Finally the associate
function is called to connect to
* the server. An instance of ClientSap
can be used to create an unlimited number of associations. Changing
* the parameters of a ClientSap has no affect on associations that have already been created.
*/
public final class ClientSap {
static final int MINIMUM_MMS_PDU_SIZE = 64;
private static final int MAXIMUM_MMS_PDU_SIZE = 65000;
private static final byte[] DEFAULT_TSEL_LOCAL = new byte[] { 0, 0 };
private static final byte[] DEFAULT_TSEL_REMOTE = new byte[] { 0, 1 };
private static final int DEFAUTL_TPDU_SIZE_PARAMETER = 10; // size = 1024
private int proposedMaxMmsPduSize = 65000;
private final int proposedMaxServOutstandingCalling = 5;
private final int proposedMaxServOutstandingCalled = 5;
private final int proposedDataStructureNestingLevel = 10;
private byte[] servicesSupportedCalling = new byte[] { (byte) 0xee, 0x1c, 0, 0, 0x04, 0x08, 0, 0, 0x79, (byte) 0xef,
0x18 };
private int messageFragmentTimeout = 20000;
private int responseTimeout = 50000;
private final ClientAcseSap acseSap;
/**
* Use this constructor to create a default client SAP.
*/
public ClientSap() {
acseSap = new ClientAcseSap();
acseSap.tSap.tSelLocal = DEFAULT_TSEL_LOCAL;
acseSap.tSap.tSelRemote = DEFAULT_TSEL_REMOTE;
acseSap.tSap.setMaxTPDUSizeParam(DEFAUTL_TPDU_SIZE_PARAMETER);
}
/**
* Use this constructor to create a client SAP that uses the given SocketFactory
to connect to servers.
* You could pass an SSLSocketFactory to enable SSL.
*
* @param socketFactory
* the socket factory to construct the socket
*/
public ClientSap(SocketFactory socketFactory) {
acseSap = new ClientAcseSap(socketFactory);
acseSap.tSap.tSelLocal = DEFAULT_TSEL_LOCAL;
acseSap.tSap.tSelRemote = DEFAULT_TSEL_REMOTE;
acseSap.tSap.setMaxTPDUSizeParam(DEFAUTL_TPDU_SIZE_PARAMETER);
}
/**
* Sets the maximum MMS PDU size in bytes that the client association will support. The client proposes this value
* to the server during association. If the server requires the use of a smaller maximum MMS PDU size, then the
* smaller size will be accepted by the client. The default size is 65000.
*
* @param size
* cannot be less than 64. The upper limit is 65000 so that segmentation at the lower transport layer is
* avoided. The Transport Layer's maximum PDU size is 65531.
*/
public void setMaxMmsPduSize(int size) {
if (size >= MINIMUM_MMS_PDU_SIZE && size <= MAXIMUM_MMS_PDU_SIZE) {
proposedMaxMmsPduSize = size;
}
else {
throw new IllegalArgumentException("maximum size is out of bound");
}
}
/**
* Gets the maximum MMS PDU size.
*
* @return the maximum MMS PDU size.
*/
public int getMaxMmsPduSize() {
return proposedMaxMmsPduSize;
}
/**
* Sets the SevicesSupportedCalling parameter. The given parameter is sent to the server but has no affect on the
* functionality of this client.
*
* @param services
* the ServicesSupportedCalling parameter
*/
public void setServicesSupportedCalling(byte[] services) {
if (services.length != 11) {
throw new IllegalArgumentException("The services parameter needs to be of lenth 11");
}
servicesSupportedCalling = services;
}
/**
* Gets the ServicesSupportedCalling parameter.
*
* @return the ServicesSupportedCalling parameter.
*/
public byte[] getServicesSupportedCalling() {
return servicesSupportedCalling;
}
/**
* Sets the remote/called Session-Selector (S-SEL). The default remote S-SEL is byte[] { 0, 1 }.
*
* @param sSelRemote
* the remote/called S-SEL.
*/
public void setSSelRemote(byte[] sSelRemote) {
acseSap.sSelRemote = sSelRemote;
}
/**
* Sets the local/calling Session-Selector (S-SEL). The default local S-SEL is byte[] { 0, 1 }.
*
* @param sSelLocal
* the local/calling S-SEL.
*/
public void setSSelLocal(byte[] sSelLocal) {
acseSap.sSelLocal = sSelLocal;
}
/**
* Sets the remote/called Presentation-Selector (P-SEL). The default remote P-SEL is byte[] { 0, 0, 0, 1 }.
*
* @param pSelRemote
* the remote/called P-SEL.
*/
public void setPSelRemote(byte[] pSelRemote) {
acseSap.pSelRemote = pSelRemote;
}
/**
* Sets the local/calling Presentation-Selector (P-SEL). The default local P-SEL is byte[] { 0, 0, 0, 1 }.
*
* @param pSelLocal
* the local/calling P-SEL.
*/
public void setPSelLocal(byte[] pSelLocal) {
acseSap.pSelLocal = pSelLocal;
}
/**
* Sets the remote/called Transport-Selector (T-SEL). It is optionally transmitted in the OSI Transport Layer
* connection request (CR). The default remote T-SEL is byte[] { 0, 1 }.
*
* @param tSelRemote
* the remote/called T-SEL. If null the T-SEL will be omitted. No maximum size is defined by the
* standard.
*/
public void setTSelRemote(byte[] tSelRemote) {
acseSap.tSap.tSelRemote = tSelRemote;
}
/**
* Sets the local/calling Transport-Selector (T-SEL). It is optionally transmitted in the OSI Transport Layer
* connection request (CR). The default local T-SEL byte[] { 0, 0 }.
*
* @param tSelLocal
* the local/calling T-SEL. If null the T-SEL will be omitted. No maximum size is defined by the
* standard.
*/
public void setTSelLocal(byte[] tSelLocal) {
acseSap.tSap.tSelLocal = tSelLocal;
}
/**
* Set the maxTPDUSize. The default maxTPduSize is 65531 (see RFC 1006).
*
* @param maxTPduSizeParam
* The maximum length is equal to 2^(maxTPduSizeParam) octets. Note that the actual TSDU size that can be
* transfered is equal to TPduSize-3. Default is 65531 octets (see RFC 1006), 7 <= maxTPduSizeParam
* <= 16, needs to be set before listening or connecting
*/
public void setMaxTPduSizeParameter(int maxTPduSizeParam) {
acseSap.tSap.setMaxTPDUSizeParam(maxTPduSizeParam);
}
/**
* Sets the remote/called Application Process Title. The default value is int[] { 1, 1, 999, 1, 1 }
*
* @param title
* the remote/called AP title.
*/
public void setApTitleCalled(int[] title) {
acseSap.setApTitleCalled(title);
}
/**
* Sets the local/calling Application Process Title. The default value is int[] { 1, 1, 999, 1 }
*
* @param title
* the local/calling AP title.
*/
public void setApTitleCalling(int[] title) {
acseSap.setApTitleCalling(title);
}
/**
* Sets the remote/called Application Entity Qualifier. The default value is 12.
*
* @param qualifier
* the remote/called AE Qualifier
*/
public void setAeQualifierCalled(int qualifier) {
acseSap.setAeQualifierCalled(qualifier);
}
/**
* Sets the local/calling Application Entity Qualifier. The default value is 12.
*
* @param qualifier
* the local/calling AE Qualifier
*/
public void setAeQualifierCalling(int qualifier) {
acseSap.setAeQualifierCalling(qualifier);
}
/**
* Sets the default response timeout of the ClientAssociation
that is created using this ClientSap.
*
* @param timeout
* the response timeout in milliseconds. The default is 20000.
*/
public void setResponseTimeout(int timeout) {
responseTimeout = timeout;
}
/**
* Sets the message fragment timeout. This is the timeout that the socket timeout is set to after the first byte of
* a message has been received. A request function (e.g. setDataValues()) will throw an IOException if the socket
* throws this timeout because the association/connection cannot recover from this kind of error.
*
* @param timeout
* the timeout in milliseconds. The default is 10000.
*/
public void setMessageFragmentTimeout(int timeout) {
messageFragmentTimeout = timeout;
}
/**
* Connects to the IEC 61850 MMS server at the given address and port and returns the resulting association object.
*
* @param address
* the address to connect to.
* @param port
* the port to connect to. Usually the MMS port is 102.
* @param authenticationParameter
* an optional authentication parameters that is transmitted. It will be omitted if equal to null.
* @param reportListener
* the listener to be notified of incoming reports
* @return the association object
* @throws IOException
* if any kind of error occurs trying build up the association
*/
public ClientAssociation associate(InetAddress address, int port, String authenticationParameter,
ClientEventListener reportListener) throws IOException {
return associate(address, port, authenticationParameter, null, -1, reportListener);
}
/**
* Connects to the IEC 61850 MMS server at the given address and port and returns the resulting association object.
*
* @param address
* the address to connect to
* @param port
* the port to connect to. Usually the MMS port is 102.
* @param authenticationParameter
* an optional authentication parameters that is transmitted. It will be omitted if equal to null.
* @param localAddr
* the local address to use
* @param localPort
* the local port to use
* @param reportListener
* the listener to be notified of incoming reports
* @return the association object.
* @throws IOException
* if any kind of error occurs trying build up the association
*/
public ClientAssociation associate(InetAddress address, int port, String authenticationParameter,
InetAddress localAddr, int localPort, ClientEventListener reportListener) throws IOException {
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("invalid port");
}
if (address == null) {
throw new IllegalArgumentException("address may not be null");
}
if (acseSap.sSelRemote == null) {
throw new IllegalArgumentException("sSelRemote may not be null");
}
if (acseSap.sSelRemote.length != 2) {
throw new IllegalArgumentException("sSelRemote lenght must be two");
}
if (acseSap.sSelLocal == null) {
throw new IllegalArgumentException("sSelLocal may not be null");
}
if (acseSap.sSelLocal.length != 2) {
throw new IllegalArgumentException("sSelLocal lenght must be two");
}
ClientAssociation clientAssociation = new ClientAssociation(address, port, localAddr, localPort,
authenticationParameter, acseSap, proposedMaxMmsPduSize, proposedMaxServOutstandingCalling,
proposedMaxServOutstandingCalled, proposedDataStructureNestingLevel, servicesSupportedCalling,
responseTimeout, messageFragmentTimeout, reportListener);
return clientAssociation;
}
}