All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gurux.dlms.GXDLMSServerBase Maven / Gradle / Ivy

There is a newer version: 4.0.72
Show newest version
package gurux.dlms;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import gurux.dlms.enums.AccessMode;
import gurux.dlms.enums.AccessMode3;
import gurux.dlms.enums.AcseServiceProvider;
import gurux.dlms.enums.AssociationResult;
import gurux.dlms.enums.Authentication;
import gurux.dlms.enums.Command;
import gurux.dlms.enums.ConfirmedServiceError;
import gurux.dlms.enums.Conformance;
import gurux.dlms.enums.ConnectionState;
import gurux.dlms.enums.DateTimeSkips;
import gurux.dlms.enums.ErrorCode;
import gurux.dlms.enums.ExceptionServiceError;
import gurux.dlms.enums.ExceptionStateError;
import gurux.dlms.enums.Initiate;
import gurux.dlms.enums.InterfaceType;
import gurux.dlms.enums.MethodAccessMode;
import gurux.dlms.enums.MethodAccessMode3;
import gurux.dlms.enums.ObjectType;
import gurux.dlms.enums.Priority;
import gurux.dlms.enums.RequestTypes;
import gurux.dlms.enums.Security;
import gurux.dlms.enums.Service;
import gurux.dlms.enums.ServiceClass;
import gurux.dlms.enums.SourceDiagnostic;
import gurux.dlms.internal.GXCommon;
import gurux.dlms.manufacturersettings.GXAttributeCollection;
import gurux.dlms.manufacturersettings.GXDLMSAttributeSettings;
import gurux.dlms.objects.GXDLMSAssociationLogicalName;
import gurux.dlms.objects.GXDLMSAssociationShortName;
import gurux.dlms.objects.GXDLMSHdlcSetup;
import gurux.dlms.objects.GXDLMSIECLocalPortSetup;
import gurux.dlms.objects.GXDLMSObject;
import gurux.dlms.objects.GXDLMSObjectCollection;
import gurux.dlms.objects.IGXDLMSBase;
import gurux.dlms.objects.enums.ApplicationContextName;
import gurux.dlms.objects.enums.AssociationStatus;
import gurux.dlms.objects.enums.BaudRate;
import gurux.dlms.secure.GXSecure;

public class GXDLMSServerBase {
    private static final Logger LOGGER = Logger.getLogger(GXDLMSServer.class.getName());

    private final Object owner;
    private GXReplyData info = new GXReplyData();
    /*
     * Received data.
     */
    private GXByteBuffer receivedData = new GXByteBuffer();

    /*
     * Reply data.
     */
    private GXByteBuffer replyData = new GXByteBuffer();

    /*
     * FLAG ID.
     */
    private String flaID;
    private GXDLMSIECLocalPortSetup localPortSetup;

    /*
     * Client system title is optional and it's used when Pre-established
     * Application Associations is used.
     * @return Client system title.
     */
    public byte[] getClientSystemTitle() {
        return settings.getPreEstablishedSystemTitle();
    }

    /*
     * Client system title is optional and it's used when Pre-established
     * Application Associations is used.
     * @param value Client system title.
     */
    public void setClientSystemTitle(final byte[] value) {
        settings.setPreEstablishedSystemTitle(value);
    }

    /*
     * Server is using push client address when sending push messages. Client
     * address is used if PushAddress is zero.
     * @return Push client address.
     */
    public int getPushClientAddress() {
        return settings.getPushClientAddress();
    }

    /*
     * Server is using push client address when sending push messages. Client
     * address is used if PushAddress is zero.
     * @param value Push client address.
     */
    public void setPushClientAddress(final int value) {
        settings.setPushClientAddress(value);
    }

    /*
     * @return FLAG ID.
     */
    public String getFlaID() {
        return flaID;
    }

    /*
     * @param value FLAG ID.
     */
    public void setFlaID(final String value) {
        if (value == null || value.length() != 3) {
            throw new IllegalArgumentException("Invalid FLAG ID.");
        }
        flaID = value;
    }

    /*
     * Local port setup is used when communicating with optical probe.
     * @return Local port setup object.
     */
    public GXDLMSIECLocalPortSetup getLocalPortSetup() {
        return localPortSetup;
    }

    /*
     * Local port setup is used when communicating with optical probe.
     * @param value Local port setup object.
     */
    public void setLocalPortSetup(final GXDLMSIECLocalPortSetup value) {
        localPortSetup = value;
    }

    /*
     * @return Client connection state.
     */
    public byte getConnectionState() {
        return settings.getConnected();
    }

    /*
     * Long get or read transaction information.
     */
    private GXDLMSLongTransaction transaction;

    /*
     * Server settings.
     */
    private final GXDLMSSettings settings;

    /*
     * Is server initialized.
     */
    private boolean initialized = false;

    /*
     * When data was received last time.
     */
    private long dataReceived = 0;

    /*
     * @param value Cipher interface that is used to cipher PDU.
     */
    protected final void setCipher(final GXICipher value) {
        settings.setCipher(value);
    }

    /*
     * This is reserved for internal use only.
     * @param value Transaction.
     */
    final void setTransaction(final GXDLMSLongTransaction value) {
        transaction = value;
    }

    /*
     * This is reserved for internal use only.
     * @return Transaction.
     */
    final GXDLMSLongTransaction getTransaction() {
        return transaction;
    }

    /*
     * @return Client to Server challenge.
     */
    public final byte[] getCtoSChallenge() {
        return settings.getCtoSChallenge();
    }

    /*
     * @return Server to Client challenge.
     */
    public final byte[] getStoCChallenge() {
        return settings.getStoCChallenge();
    }

    /*
     * @return Interface type.
     */
    public final InterfaceType getInterfaceType() {
        return settings.getInterfaceType();
    }

    /*
     * Server to Client custom challenge. This is for debugging purposes. Reset
     * custom challenge settings StoCChallenge to null.
     * @param value Server to Client challenge.
     */
    public final void setStoCChallenge(final byte[] value) {
        settings.setUseCustomChallenge(value != null);
        settings.setStoCChallenge(value);
    }

    /*
     * Set starting packet index. Default is One based, but some meters use Zero
     * based value. Usually this is not used.
     * @param value Zero based starting index.
     */
    public final void setStartingPacketIndex(final int value) {
        settings.setBlockIndex(value);
    }

    /*
     * @return Invoke ID.
     */
    public final int getInvokeID() {
        return settings.getInvokeID();
    }

    /*
     * @param value Invoke ID.
     */
    public final void setInvokeID(final int value) {
        settings.setInvokeID(value);
    }

    /*
     * @return Used service class.
     */
    public final ServiceClass getServiceClass() {
        return settings.getServiceClass();
    }

    /*
     * @param value Used service class.
     */
    public final void setServiceClass(final ServiceClass value) {
        settings.setServiceClass(value);
    }

    /*
     * @return Used priority.
     */
    public final Priority getPriority() {
        return settings.getPriority();
    }

    /*
     * @param value Used priority.
     */
    public final void setPriority(final Priority value) {
        settings.setPriority(value);
    }

    /*
     * @param value Current association of the server.
     */
    public final void setAssignedAssociation(final GXDLMSAssociationLogicalName value) {
        settings.setAssignedAssociation(value);
    }

    /**
     * @return Current server association.
     */
    public final GXDLMSAssociationLogicalName getAssignedAssociation() {
        return settings.getAssignedAssociation();
    }

    /*
     * Constructor.
     * @param forOwner Owner.
     * @param logicalNameReferencing Is logical name referencing used.
     * @param type Interface type.
     */
    public GXDLMSServerBase(final Object forOwner, final boolean logicalNameReferencing, final InterfaceType type) {
        IGXCryptoNotifier notifier1 = this instanceof IGXCryptoNotifier ? (IGXCryptoNotifier) this : null;
        IGXCustomObjectNotifier notifier2 =
                this instanceof IGXCustomObjectNotifier ? (IGXCustomObjectNotifier) this : null;
        settings = new GXDLMSSettings(true, notifier1, notifier2);
        owner = forOwner;
        settings.setUseLogicalNameReferencing(logicalNameReferencing);
        settings.setInterfaceType(type);
        reset();
    }

    /*
     * @return List of objects that meter supports.
     */
    public final GXDLMSObjectCollection getItems() {
        return settings.getObjects();
    }

    /*
     * @return HDLC connection settings.
     * @deprecated use {@link getHdlcSettings} instead.
     */
    public final GXDLMSLimits getLimits() {
        return (GXDLMSLimits) settings.getHdlcSettings();
    }

    /*
     * @return HDLC connection settings.
     */
    public final GXHdlcSettings getHdlcSettings() {
        return settings.getHdlcSettings();
    }

    /*
     * Standard says that Time zone is from normal time to UTC in minutes. If
     * meter is configured to use UTC time (UTC to normal time) set this to
     * true.
     * @return True, if UTC time is used.
     */
    public boolean getUseUtc2NormalTime() {
        return settings.getUseUtc2NormalTime();
    }

    /*
     * Standard says that Time zone is from normal time to UTC in minutes. If
     * meter is configured to use UTC time (UTC to normal time) set this to
     * true.
     * @param value True, if UTC time is used.
     */
    public void setUseUtc2NormalTime(final boolean value) {
        settings.setUseUtc2NormalTime(value);
    }

    /*
     * @return Skipped date time fields. This value can be used if meter can't
     * handle deviation or status.
     */
    public java.util.Set getDateTimeSkips() {
        return settings.getDateTimeSkips();
    }

    /*
     * @param value Skipped date time fields. This value can be used if meter
     * can't handle deviation or status.
     */
    public void setDateTimeSkips(final java.util.Set value) {
        settings.setDateTimeSkips(value);
    }

    /*
     * Retrieves the maximum size of received PDU. PDU size tells maximum size
     * of PDU packet. Value can be from 0 to 0xFFFF. By default the value is
     * 0xFFFF.
     * @return Maximum size of received PDU.
     */
    public final int getMaxReceivePDUSize() {
        return settings.getMaxServerPDUSize();
    }

    /*
     * @param value Maximum size of received PDU.
     */
    public final void setMaxReceivePDUSize(final int value) {
        settings.setMaxServerPDUSize(value);
    }

    /*
     * Determines, whether Logical, or Short name, referencing is used.
     * Referencing depends on the device to communicate with. Normally, a device
     * supports only either Logical or Short name referencing. The referencing
     * is defined by the device manufacturer. If the referencing is wrong, the
     * SNMR message will fail.
     * @see #getMaxReceivePDUSize
     * @return Is logical name referencing used.
     */
    public final boolean getUseLogicalNameReferencing() {
        return settings.getUseLogicalNameReferencing();
    }

    /*
     * @param value Is Logical Name referencing used.
     */
    public final void setUseLogicalNameReferencing(final boolean value) {
        settings.setUseLogicalNameReferencing(value);
    }

    /*
     * @return Get settings.
     */
    public final GXDLMSSettings getSettings() {
        return settings;
    }

    /**
     * Close server.
     * 
     * @throws Exception
     *             Occurred exception.
     */
    public void close() throws Exception {
        for (GXDLMSObject it : settings.getObjects()) {
            it.stop(this);
        }
    }

    /*
     * Initialize server. This must call after server objects are set.
     */
    public final void initialize() {
        GXDLMSObject associationObject = null;
        initialized = true;
        for (int pos = 0; pos != settings.getObjects().size(); ++pos) {
            GXDLMSObject it = settings.getObjects().get(pos);
            if (it.getLogicalName() == null) {
                throw new IllegalArgumentException("Invalid Logical Name.");
            }
            it.start(this);
            if (it instanceof GXDLMSAssociationShortName && !this.getUseLogicalNameReferencing()) {
                if (((GXDLMSAssociationShortName) it).getObjectList().isEmpty()) {
                    ((GXDLMSAssociationShortName) it).getObjectList().addAll(getItems());
                }
                associationObject = it;
            } else if (it instanceof GXDLMSAssociationLogicalName && this.getUseLogicalNameReferencing()) {
                GXDLMSAssociationLogicalName ln = (GXDLMSAssociationLogicalName) it;
                if (ln.getObjectList().isEmpty()) {
                    ln.getObjectList().addAll(getItems());
                }
                associationObject = it;
                if (!settings.getProposedConformance().isEmpty()) {
                    ln.getXDLMSContextInfo().setConformance(settings.getProposedConformance());
                }

            } else if (!(it instanceof IGXDLMSBase)) {
                // Remove unsupported items.
                LOGGER.log(Level.INFO, it.getLogicalName() + " not supported.");
                settings.getObjects().remove(pos);
                --pos;
            }
        }
        if (associationObject == null) {
            if (getUseLogicalNameReferencing()) {
                GXDLMSAssociationLogicalName it = new GXDLMSAssociationLogicalName();
                it.getXDLMSContextInfo().setMaxReceivePduSize(settings.getMaxServerPDUSize());
                it.getXDLMSContextInfo().setMaxSendPduSize(settings.getMaxServerPDUSize());
                getItems().add(it);
                it.getObjectList().addAll(getItems());
            } else {
                GXDLMSAssociationShortName it = new GXDLMSAssociationShortName();
                getItems().add(it);
                it.getObjectList().addAll(getItems());
            }
        }
        // Arrange items by Short Name.
        if (!this.getUseLogicalNameReferencing()) {
            updateShortNames(false);
        }
    }

    /**
     * Update short names.
     * 
     * @param force
     *            Force update.
     */
    final void updateShortNames(final boolean force) {
        int sn = 0xA0;
        int[] offset = new int[1];
        int[] count = new int[1];
        for (GXDLMSObject it : settings.getObjects()) {
            if (!(it instanceof GXDLMSAssociationShortName || it instanceof GXDLMSAssociationLogicalName)) {
                // Generate Short Name if not given.
                if (force || it.getShortName() == 0) {
                    it.setShortName(sn);
                    // Add method index addresses.
                    GXDLMS.getActionInfo(it.getObjectType(), offset, count);
                    if (count[0] != 0) {
                        sn += offset[0] + (8 * count[0]);
                    } else {
                        // If there are no methods.
                        // Add attribute index addresses.
                        sn += 8 * it.getAttributeCount();
                    }
                } else {
                    sn = it.getShortName();
                }
            }
        }
    }

    /*
     * Parse AARQ request that client send and returns AARE request.
     * @return Reply to the client.
     */
    private void handleAarqRequest(final GXByteBuffer data, final GXDLMSConnectionEventArgs connectionInfo)
            throws Exception {
        AssociationResult result = AssociationResult.ACCEPTED;
        GXByteBuffer error = null;
        settings.setCtoSChallenge(null);
        if (settings.getCipher() != null) {
            settings.getCipher().setDedicatedKey(null);
        }
        // Reset settings for wrapper.
        if (settings.getInterfaceType() == InterfaceType.WRAPPER || settings.getInterfaceType() == InterfaceType.PDU) {
            reset(true);
        }
        Object ret;
        int name = 0;
        try {
            ret = GXAPDU.parsePDU(settings, settings.getCipher(), data, null);
            if (ret instanceof ExceptionServiceError) {
                ExceptionServiceError e = (ExceptionServiceError) ret;
                if (GXDLMS.useHdlc(settings.getInterfaceType())) {
                    replyData.set(GXCommon.LLC_REPLY_BYTES);
                }
                replyData.setUInt8(Command.EXCEPTION_RESPONSE);
                replyData.setUInt8(ExceptionStateError.SERVICE_UNKNOWN.getValue());
                replyData.setUInt8(e.getValue());
                if (e == ExceptionServiceError.INVOCATION_COUNTER_ERROR) {
                    replyData.setUInt32(((Number) settings.getInvocationCounter().getValue()).longValue());
                }
                return;
            }
            if (!(ret instanceof AcseServiceProvider)) {
                if (ret instanceof ApplicationContextName) {
                    name = ((ApplicationContextName) ret).ordinal();
                    result = AssociationResult.PERMANENT_REJECTED;
                    ret = SourceDiagnostic.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;

                } else if (settings.getNegotiatedConformance().isEmpty()) {
                    result = AssociationResult.PERMANENT_REJECTED;
                    ret = SourceDiagnostic.NO_REASON_GIVEN;
                    error = new GXByteBuffer();
                    error.setUInt8(0xE);
                    error.setUInt8(ConfirmedServiceError.INITIATE_ERROR.getValue());
                    error.setUInt8(ServiceError.INITIATE.getValue());
                    error.setUInt8(Initiate.INCOMPATIBLE_CONFORMANCE.getValue());
                } else if (settings.getMaxPduSize() < 64) {
                    // If PDU is too low.
                    result = AssociationResult.PERMANENT_REJECTED;
                    ret = SourceDiagnostic.NO_REASON_GIVEN;
                    error = new GXByteBuffer();
                    error.setUInt8(0xE);
                    error.setUInt8(ConfirmedServiceError.INITIATE_ERROR.getValue());
                    error.setUInt8(ServiceError.INITIATE.getValue());
                    error.setUInt8(Initiate.PDU_SIZE_TOO_SHORT.getValue());
                } else if (settings.getDLMSVersion() != 6) {
                    settings.setDLMSVersion(6);
                    result = AssociationResult.PERMANENT_REJECTED;
                    ret = SourceDiagnostic.NO_REASON_GIVEN;
                    error = new GXByteBuffer();
                    error.setUInt8(0xE);
                    error.setUInt8(ConfirmedServiceError.INITIATE_ERROR.getValue());
                    error.setUInt8(ServiceError.INITIATE.getValue());
                    error.setUInt8(Initiate.DLMS_VERSION_TOO_LOW.getValue());
                } else if (SourceDiagnostic.forValue((int) ret) != SourceDiagnostic.NONE) {
                    result = AssociationResult.PERMANENT_REJECTED;
                    ret = SourceDiagnostic.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;
                    notifyInvalidConnection(connectionInfo);
                } else {
                    if (owner instanceof GXDLMSServer) {
                        if (getAssignedAssociation() != null && getAssignedAssociation()
                                .getAuthenticationMechanismName().getMechanismId() != settings.getAuthentication()) {
                            ret = SourceDiagnostic.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;
                        } else {
                            GXDLMSServer b = (GXDLMSServer) owner;
                            ret = b.validateAuthentication(settings.getAuthentication(), settings.getPassword());
                        }
                    } else {
                        if (getAssignedAssociation() != null && getAssignedAssociation()
                                .getAuthenticationMechanismName().getMechanismId() != settings.getAuthentication()) {
                            ret = SourceDiagnostic.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;
                        } else {
                            GXDLMSServer2 b = (GXDLMSServer2) owner;
                            ret = b.onValidateAuthentication(settings.getAuthentication(), settings.getPassword());
                        }
                    }
                    if ((SourceDiagnostic) ret != SourceDiagnostic.NONE) {
                        result = AssociationResult.PERMANENT_REJECTED;
                    } else if (settings.getAuthentication().getValue() > Authentication.LOW.getValue()) {
                        result = AssociationResult.ACCEPTED;
                        ret = SourceDiagnostic.AUTHENTICATION_REQUIRED;
                        if (getUseLogicalNameReferencing()) {
                            GXDLMSAssociationLogicalName ln = getAssignedAssociation();
                            if (ln == null) {
                                ln = (GXDLMSAssociationLogicalName) getItems()
                                        .findByLN(ObjectType.ASSOCIATION_LOGICAL_NAME, "0.0.40.0.0.255");
                                if (ln == null) {
                                    ln = (GXDLMSAssociationLogicalName) notifyFindObject(
                                            ObjectType.ASSOCIATION_LOGICAL_NAME, 0, "0.0.40.0.0.255");
                                }
                            }
                            if (ln != null) {
                                ln.setAssociationStatus(AssociationStatus.ASSOCIATION_PENDING);
                            }
                        }
                    } else {
                        if (getUseLogicalNameReferencing()) {
                            GXDLMSAssociationLogicalName ln = getAssignedAssociation();
                            if (ln == null) {
                                ln = (GXDLMSAssociationLogicalName) getItems()
                                        .findByLN(ObjectType.ASSOCIATION_LOGICAL_NAME, "0.0.40.0.0.255");
                                if (ln == null) {
                                    ln = (GXDLMSAssociationLogicalName) notifyFindObject(
                                            ObjectType.ASSOCIATION_LOGICAL_NAME, 0, "0.0.40.0.0.255");
                                }
                            }
                            if (ln != null) {
                                ln.setAssociationStatus(AssociationStatus.ASSOCIATED);
                            }
                        }
                        settings.setConnected(settings.getConnected() | ConnectionState.DLMS);
                    }
                }
            } else if (result == AssociationResult.ACCEPTED && ((AcseServiceProvider) ret).getValue() != 0) {
                result = AssociationResult.PERMANENT_REJECTED;
            }
        } catch (GXDLMSConfirmedServiceError e) {
            result = AssociationResult.PERMANENT_REJECTED;
            ret = SourceDiagnostic.NO_REASON_GIVEN;
            error = new GXByteBuffer();
            error.setUInt8(0xE);
            error.setUInt8(e.getConfirmedServiceError().getValue());
            error.setUInt8(e.getServiceError().getValue());
            error.setUInt8(e.getServiceErrorValue());
        } catch (IllegalArgumentException e) {
            result = AssociationResult.PERMANENT_REJECTED;
            ret = SourceDiagnostic.NO_REASON_GIVEN;
        } catch (GXDLMSException e) {
            result = e.getResult();
            ret = e.getDiagnostic();
        }
        if (GXDLMS.useHdlc(settings.getInterfaceType())) {
            replyData.set(GXCommon.LLC_REPLY_BYTES);
        }
        // Generate challenge if High authentication is used.
        if (settings.getAuthentication().getValue() > Authentication.LOW.getValue()) {
            settings.setStoCChallenge(
                    GXSecure.generateChallenge(settings.getAuthentication(), settings.getChallengeSize()));
        }
        // Generate AARE packet.
        GXAPDU.generateAARE(name, settings, replyData, result, ret, settings.getCipher(), error, null);
    }

    /**
     * Handles release request.
     * 
     * @param data
     *            Received data.
     * @param connectionInfo
     *            Connection info.
     */
    @SuppressWarnings("squid:S1172")
    private void handleReleaseRequest(final GXByteBuffer data, final GXDLMSConnectionEventArgs connectionInfo)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        // Return error if connection is not established.
        if ((settings.getConnected() & ConnectionState.DLMS) == 0) {
            replyData.set(GXDLMSServerBase.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR,
                    ServiceError.SERVICE, Service.UNSUPPORTED.getValue()));
            return;
        }
        if (GXDLMS.useHdlc(settings.getInterfaceType())) {
            replyData.set(0, GXCommon.LLC_REPLY_BYTES);
        }
        byte[] tmp = GXAPDU.getUserInformation(settings, settings.getCipher());
        replyData.setUInt8(0x63);
        // Len.
        replyData.setUInt8((byte) (tmp.length + 3));
        replyData.setUInt8(0x80);
        replyData.setUInt8(0x01);
        replyData.setUInt8(0x00);
        replyData.setUInt8(0xBE);
        replyData.setUInt8((byte) (tmp.length + 1));
        replyData.setUInt8(4);
        replyData.setUInt8((byte) (tmp.length));
        replyData.set(tmp);
    }

    /*
     * Parse SNRM Request. If server do not accept client empty byte array is
     * returned.
     * @return Returns returned UA packet.
     */
    private void handleSnrmRequest(final GXByteBuffer data) {
        GXDLMS.parseSnrmUaResponse(data, settings);
        reset(true);
        replyData.setUInt8(0x81); // FromatID
        replyData.setUInt8(0x80); // GroupID
        replyData.setUInt8(0); // Length
        if (getHdlc() != null) {
            // If client wants send larger HDLC frames what meter accepts.
            if (settings.getHdlcSettings().getMaxInfoTX() > getHdlc().getMaximumInfoLengthReceive()) {
                settings.getHdlcSettings().setMaxInfoTX(getHdlc().getMaximumInfoLengthReceive());
            }
            // If client wants receive larger HDLC frames what meter accepts.
            if (settings.getHdlcSettings().getMaxInfoRX() > getHdlc().getMaximumInfoLengthTransmit()) {
                settings.getHdlcSettings().setMaxInfoRX(getHdlc().getMaximumInfoLengthTransmit());
            }
            // If client asks higher window size what meter accepts.
            if (settings.getHdlcSettings().getMaxInfoRX() > getHdlc().getMaximumInfoLengthTransmit()) {
                settings.getHdlcSettings().setWindowSizeTX(getHdlc().getWindowSizeReceive());
            }
            // If client asks higher window size what meter accepts.
            if (settings.getHdlcSettings().getMaxInfoRX() > getHdlc().getMaximumInfoLengthTransmit()) {
                settings.getHdlcSettings().setWindowSizeRX(getHdlc().getWindowSizeTransmit());
            }
        }
        replyData.setUInt8(HDLCInfo.MAX_INFO_TX);
        GXDLMS.appendHdlcParameter(replyData, getHdlcSettings().getMaxInfoTX());

        replyData.setUInt8(HDLCInfo.MAX_INFO_RX);
        GXDLMS.appendHdlcParameter(replyData, getHdlcSettings().getMaxInfoRX());

        replyData.setUInt8(HDLCInfo.WINDOW_SIZE_TX);
        replyData.setUInt8(4);
        replyData.setUInt32(getHdlcSettings().getWindowSizeTX());
        replyData.setUInt8(HDLCInfo.WINDOW_SIZE_RX);
        replyData.setUInt8(4);
        replyData.setUInt32(getHdlcSettings().getWindowSizeRX());
        int len = replyData.size() - 3;
        replyData.setUInt8(2, len); // Length
        settings.setConnected(ConnectionState.HDLC);
    }

    /*
     * Generates disconnect request.
     * @return Disconnect request.
     */
    private void generateDisconnectRequest() {
        replyData.setUInt8(0x81); // FromatID
        replyData.setUInt8(0x80); // GroupID
        replyData.setUInt8(0); // Length

        replyData.setUInt8(HDLCInfo.MAX_INFO_TX);
        replyData.setUInt8(1);
        replyData.setUInt8(getHdlcSettings().getMaxInfoTX());

        replyData.setUInt8(HDLCInfo.MAX_INFO_RX);
        replyData.setUInt8(1);
        replyData.setUInt8(getHdlcSettings().getMaxInfoRX());

        replyData.setUInt8(HDLCInfo.WINDOW_SIZE_TX);
        replyData.setUInt8(4);
        replyData.setUInt32(getHdlcSettings().getWindowSizeTX());

        replyData.setUInt8(HDLCInfo.WINDOW_SIZE_RX);
        replyData.setUInt8(4);
        replyData.setUInt32(getHdlcSettings().getWindowSizeRX());

        int len = replyData.size() - 3;
        replyData.setUInt8(2, len); // Length.
    }

    /*
     * Reset after connection is closed.
     * @param connect Is new connection.
     */
    final void reset(final boolean connect) {
        if (!connect) {
            info.clear();
            settings.setServerAddress(0);
            settings.setClientAddress(0);
        }
        settings.setProtocolVersion(null);
        // Reset Ephemeral keys.
        settings.setEphemeralBlockCipherKey(null);
        settings.setEphemeralBroadcastBlockCipherKey(null);
        settings.setEphemeralAuthenticationKey(null);
        settings.setCtoSChallenge(null);
        settings.setStoCChallenge(null);
        receivedData.clear();
        transaction = null;
        settings.setBlockIndex(1);
        settings.setCount(0);
        settings.setIndex(0);
        settings.setConnected(ConnectionState.NONE);
        replyData.clear();
        receivedData.clear();
        settings.setPassword(null);
        if (!connect) {
            info.clear();
            settings.setServerAddress(0);
            settings.setClientAddress(0);
            setAssignedAssociation(null);
        }
        settings.setAuthentication(Authentication.NONE);
        if (settings.getCipher() != null) {
            if (!connect) {
                settings.getCipher().reset();
            } else {
                settings.getCipher().setSecurity(Security.NONE);
            }
        }
        dataReceived = 0;
        settings.setGbtCount(0);

    }

    /*
     * Reset after connection is closed.
     */
    public final void reset() {
        reset(false);
    }

    /**
     * Find IEC frame. Sometimes there are extra bytes or multiple packets on
     * the data so they are removed.
     * 
     * @return
     */
    private boolean GetIecPacket() {
        if (receivedData.size() < 5) {
            return false;
        }
        int eop = -1;
        int bop = -1;
        // Find EOP.
        for (int pos = receivedData.size() - 2; pos != 2; --pos) {
            if (receivedData.getUInt8(pos) == 0x0D && receivedData.getUInt8(pos + 1) == 0x0A) {
                eop = pos;
                break;
            }
        }
        if (eop == -1) {
            return false;
        }
        // Find BOP
        short ch;
        for (int pos = eop - 1; pos != -1; --pos) {
            ch = receivedData.getUInt8(pos);
            if (ch == 6 || (pos + 2 < receivedData.size() && ch == '/' && receivedData.getUInt8(pos + 1) == '?'
                    && receivedData.getUInt8(pos + 2) == '!')) {
                bop = pos;
                break;
            }
        }
        if (bop == -1) {
            return false;
        }
        receivedData.position(bop);
        return true;
    }

    /**
     * Handles client request.
     * 
     * @param sr
     *            Server reply.
     * @throws NoSuchPaddingException
     *             No such padding exception.
     * @throws NoSuchAlgorithmException
     *             No such algorithm exception.
     * @throws InvalidAlgorithmParameterException
     *             Invalid algorithm parameter exception.
     * @throws InvalidKeyException
     *             Invalid key exception.
     * @throws BadPaddingException
     *             Bad padding exception.
     * @throws IllegalBlockSizeException
     *             Illegal block size exception.
     * @throws SignatureException
     *             Signature exception.
     */
    @SuppressWarnings({ "squid:S00112", "squid:S1193", "squid:S1066", "squid:S1141" })
    public final void handleRequest(GXServerReply sr)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, SignatureException {
        sr.setReply(null);
        if (!sr.isStreaming() && (sr.getData() == null || sr.getData().length == 0)) {
            return;
        }
        if (!initialized) {
            throw new RuntimeException("Server not Initialized.");
        }
        try {
            if (!sr.isStreaming()) {
                receivedData.set(sr.getData());
                boolean first = settings.getServerAddress() == 0 && settings.getClientAddress() == 0;
                // If using optical probe.
                if (settings.getInterfaceType() == InterfaceType.HDLC_WITH_MODE_E) {
                    if (settings.getConnected() == ConnectionState.NONE) {
                        // If IEC packet not found.
                        if (!GetIecPacket()) {
                            return;
                        }
                        if (receivedData.getUInt8(receivedData.position()) == 6) {
                            // User changes the baud rate.
                            // Only Mode E is allowed.
                            if (receivedData.getUInt8(receivedData.position() + 1) != 0x32
                                    || receivedData.getUInt8(receivedData.position() + 3) != 0x32) {
                                // Return error.
                            }
                            BaudRate baudrate =
                                    BaudRate.values()[receivedData.getUInt8(receivedData.position() + 2) - '0'];
                            if (baudrate.ordinal() > localPortSetup.getProposedBaudrate().ordinal()) {
                                baudrate = localPortSetup.getProposedBaudrate();
                            }
                            receivedData.clear();
                            // Return used baud rate.
                            settings.setConnected(ConnectionState.IEC);
                            // "2" //(HDLC protocol procedure) (Binary mode)
                            // Set mode E.
                            sr.setReply(new byte[] { 0x06,
                                    // "2" HDLC protocol procedure (Mode E)
                                    (byte) '2',
                                    // Send Baud rate character
                                    (byte) ('0' + baudrate.ordinal()),
                                    // Mode control character
                                    (byte) '2', 13, 10 });
                            // Change the baud rate.
                            sr.setNewBaudRate(300 << baudrate.ordinal());
                            settings.setConnected(ConnectionState.IEC);
                        } else if (receivedData.getUInt8(receivedData.position()) == '/') {
                            String meterAddress = new String(
                                    receivedData.subArray(receivedData.position() + 3, receivedData.available() - 5));
                            // If meter address is wrong.
                            if (meterAddress.length() != 0 && meterAddress != localPortSetup.getDeviceAddress()) {
                                receivedData.clear();
                                return;
                            }
                            receivedData.clear();
                            receivedData.setUInt8((byte) '/');
                            // Add flag ID.
                            receivedData.set(flaID.getBytes());
                            // Add proposed baud rate.
                            receivedData.setUInt8('0' + localPortSetup.getProposedBaudrate().ordinal());
                            // Add device address.
                            receivedData.add(localPortSetup.getDeviceAddress());
                            receivedData.add("\r\n");
                            sr.setReply(receivedData.array());
                            receivedData.clear();
                        }
                        return;
                    }
                }
                try {
                    GXDLMS.getData(settings, receivedData, info, null);
                } catch (GXDLMSExceptionResponse ex) {
                    transaction = null;
                    settings.setCount(0);
                    settings.setIndex(0);
                    // info.clear();
                    receivedData.clear();
                    dataReceived = Calendar.getInstance().getTimeInMillis();
                    sr.setReply(reportError(info.getCommand(), ErrorCode.INCONSISTENT_CLASS));
                    return;
                } catch (Exception ex) {
                    transaction = null;
                    settings.setCount(0);
                    settings.setIndex(0);
                    receivedData.clear();
                    dataReceived = Calendar.getInstance().getTimeInMillis();
                    if ((getSettings().getConnected() & ConnectionState.DLMS) != 0) {
                        sr.setReply(reportError(info.getCommand(), ErrorCode.INCONSISTENT_CLASS));
                    }
                    return;
                }
                // If all data is not received yet.
                if (!info.isComplete()) {
                    return;
                }
                receivedData.clear();
                if (info.getCommand() == Command.DISCONNECT_REQUEST
                        && (settings.getConnected() == ConnectionState.NONE)) {
                    if (owner instanceof GXDLMSServer) {
                        GXDLMSServer b = (GXDLMSServer) owner;
                        // Check is data send to this server.
                        if (!b.isTarget(settings.getServerAddress(), settings.getClientAddress())) {
                            info.clear();
                            return;
                        }
                    } else {
                        GXDLMSServer2 b = (GXDLMSServer2) owner;
                        // Check is data send to this server.
                        if (!b.isTarget(settings.getServerAddress(), settings.getClientAddress())) {
                            info.clear();
                            return;
                        }
                    }
                    sr.setReply(GXDLMS.getHdlcFrame(settings, Command.DISCONNECT_MODE, replyData));
                    info.clear();
                    return;
                }

                if (first || info.getCommand() == Command.SNRM || (settings.getInterfaceType() == InterfaceType.WRAPPER
                        && info.getCommand() == Command.AARQ)) {
                    if (owner instanceof GXDLMSServer) {
                        GXDLMSServer b = (GXDLMSServer) owner;
                        // Check is data send to this server.
                        if (!b.isTarget(settings.getServerAddress(), settings.getClientAddress())) {
                            info.clear();
                            settings.setClientAddress(0);
                            settings.setServerAddress(0);
                            return;
                        }
                    } else {
                        GXDLMSServer2 b = (GXDLMSServer2) owner;
                        // Check is data send to this server.
                        if (!b.isTarget(settings.getServerAddress(), settings.getClientAddress())) {
                            info.clear();
                            settings.setClientAddress(0);
                            settings.setServerAddress(0);
                            return;
                        }
                    }
                }

                // If client want next frame.
                if (info.getMoreData().contains(RequestTypes.FRAME)) {
                    dataReceived = Calendar.getInstance().getTimeInMillis();
                    sr.setReply(GXDLMS.getHdlcFrame(settings, settings.getReceiverReady(), replyData));
                    return;
                }
                // Update command if transaction and next frame is asked.
                if (info.getCommand() == Command.NONE) {
                    if (transaction != null) {
                        info.setCommand(transaction.getCommand());
                    } else if (replyData.size() == 0) {
                        sr.setReply(GXDLMS.getHdlcFrame(settings, settings.getReceiverReady(), replyData));
                        return;
                    }
                }
                // Check inactivity time out.
                if (settings.getHdlc() != null && settings.getHdlc().getInactivityTimeout() != 0) {
                    if (info.getCommand() != Command.SNRM) {
                        int elapsed = (int) (Calendar.getInstance().getTimeInMillis() - dataReceived) / 1000;
                        // If inactivity time out is elapsed.
                        if (elapsed >= settings.getHdlc().getInactivityTimeout()) {
                            reset();
                            UpdateDefaultBaudRate(sr);
                            dataReceived = 0;
                            return;
                        }
                    }
                } else if (settings.getWrapper() != null && settings.getWrapper().getInactivityTimeout() != 0) {
                    if (info.getCommand() != Command.AARQ) {
                        int elapsed = (int) (Calendar.getInstance().getTimeInMillis() - dataReceived) / 1000;
                        // If inactivity time out is elapsed.
                        if (elapsed >= settings.getWrapper().getInactivityTimeout()) {
                            reset();
                            dataReceived = 0;
                            return;
                        }
                    }
                }
            } else {
                info.setCommand(Command.GENERAL_BLOCK_TRANSFER);
            }
            try {
                sr.setReply(handleCommand(info.getCommand(), info.getData(), sr, info.getCipheredCommand()));
            } catch (Exception ex) {
                settings.resetBlockIndex();
                receivedData.size(0);
                sr.setReply(reportError(info.getCommand(), ErrorCode.INCONSISTENT_CLASS));
                return;
            }
            dataReceived = Calendar.getInstance().getTimeInMillis();
            info.clear();
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, e.toString());
            if (e instanceof GXDLMSConfirmedServiceError) {
                sr.setReply(reportConfirmedServiceError((GXDLMSConfirmedServiceError) e));
                transaction = null;
                settings.setCount(0);
                settings.setIndex(0);
                info.clear();
                receivedData.clear();
                return;
            }
            if (info.getCommand() != Command.NONE) {
                sr.setReply(reportError(info.getCommand(), ErrorCode.INCONSISTENT_CLASS));
                transaction = null;
                settings.setCount(0);
                settings.setIndex(0);
                info.clear();
                receivedData.clear();
            } else {
                reset();
                if ((settings.getConnected() & ConnectionState.DLMS) != 0) {
                    settings.setConnected(settings.getConnected() & ~ConnectionState.DLMS);
                    if (owner instanceof GXDLMSServer) {
                        GXDLMSServer b = (GXDLMSServer) owner;
                        b.disconnected(sr.getConnectionInfo());
                    } else {
                        GXDLMSServer2 b = (GXDLMSServer2) owner;
                        try {
                            b.onDisconnected(sr.getConnectionInfo());
                            UpdateDefaultBaudRate(sr);
                        } catch (Exception ex) {
                            // It's OK if this fails.
                        }
                    }
                }
            }
        }
    }

    private void UpdateDefaultBaudRate(GXServerReply sr) {
        if (settings.getInterfaceType() == InterfaceType.HDLC_WITH_MODE_E) {
            sr.setNewBaudRate(300 << (int) localPortSetup.getDefaultBaudrate().ordinal());
        }
    }

    // GXDLMSConfirmedServiceError
    private byte[] reportConfirmedServiceError(final GXDLMSConfirmedServiceError e) {
        replyData.clear();
        if (getSettings().getInterfaceType() == InterfaceType.HDLC) {
            GXDLMS.addLLCBytes(getSettings(), replyData);
        }
        replyData.setUInt8(Command.CONFIRMED_SERVICE_ERROR);
        replyData.setUInt8(e.getConfirmedServiceError().getValue());
        replyData.setUInt8(e.getServiceError().getValue());
        replyData.setUInt8(e.getServiceErrorValue());
        if (settings.getInterfaceType() == InterfaceType.WRAPPER) {
            return GXDLMS.getWrapperFrame(settings, Command.CONFIRMED_SERVICE_ERROR, replyData);
        } else {
            return GXDLMS.getHdlcFrame(settings, (byte) 0, replyData);
        }
    }

    private byte[] reportError(final int command, final ErrorCode error)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, SignatureException {
        short cmd;
        switch (command) {
        case Command.READ_REQUEST:
            cmd = Command.READ_RESPONSE;
            break;
        case Command.WRITE_REQUEST:
            cmd = Command.WRITE_RESPONSE;
            break;
        case Command.GET_REQUEST:
            cmd = Command.GET_RESPONSE;
            break;
        case Command.SET_REQUEST:
            cmd = Command.SET_RESPONSE;
            break;
        case Command.METHOD_REQUEST:
            cmd = Command.METHOD_RESPONSE;
            break;
        case Command.GENERAL_CIPHERING:
            cmd = Command.GENERAL_CIPHERING;
            break;
        default:
            // Return HW error and close connection.
            cmd = Command.NONE;
            break;
        }
        if (settings.getUseLogicalNameReferencing()) {
            GXDLMSLNParameters p = new GXDLMSLNParameters(settings, 0, cmd, 1, null, null, error.getValue(),
                    info.getCipheredCommand());
            GXDLMS.getLNPdu(p, replyData);
        } else {
            GXByteBuffer bb = new GXByteBuffer();
            bb.setUInt8(error.getValue());
            GXDLMSSNParameters p = new GXDLMSSNParameters(settings, cmd, 1, 1, null, bb);
            GXDLMS.getSNPdu(p, replyData);
        }
        if (settings.getInterfaceType() == InterfaceType.WRAPPER) {
            return GXDLMS.getWrapperFrame(settings, cmd, replyData);
        } else {
            return GXDLMS.getHdlcFrame(settings, (byte) 0, replyData);
        }
    }

    /*
     * Handle received command.
     * @param cmd Executed command.
     * @param data Received data from the client.
     * @param connectionInfo Connection info.
     * @return Response for the client.
     */
    private byte[] handleCommand(final int cmd, final GXByteBuffer data, final GXServerReply sr,
            final int cipheredCommand) throws Exception {
        byte frame = 0;
        if (GXDLMS.useHdlc(settings.getInterfaceType()) && replyData.size() != 0) {
            // Get next frame.
            frame = settings.getNextSend(false);
        }
        Boolean invalidCommand = false;
        // Connection established is checked inside of the function because of
        // HLS.
        // If connection is not established.
        if (cmd != Command.AARQ && cmd != Command.SNRM && cmd != Command.WRITE_REQUEST && cmd != Command.METHOD_REQUEST
                && cmd != Command.DISCONNECT_REQUEST && (settings.getConnected() & ConnectionState.DLMS) == 0
                && cipheredCommand == 0) {
            replyData.clear();
            replyData.setUInt8(Command.EXCEPTION_RESPONSE);
            replyData.setUInt8(ExceptionStateError.SERVICE_UNKNOWN.getValue());
            replyData.setUInt8(ExceptionServiceError.SERVICE_NOT_SUPPORTED.getValue());
        } else {
            switch (cmd) {
            case Command.ACCESS_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.ACCESS)) {
                    invalidCommand = true;
                } else {
                    GXDLMSLNCommandHandler.handleAccessRequest(settings, this, data, replyData, null, cipheredCommand);
                }
                break;
            case Command.SET_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.SET)) {
                    invalidCommand = true;
                } else {
                    GXDLMSLNCommandHandler.handleSetRequest(settings, this, data, replyData, null, cipheredCommand);
                }
                break;
            case Command.WRITE_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.WRITE)) {
                    invalidCommand = true;
                } else {
                    GXDLMSSNCommandHandler.handleWriteRequest(settings, this, data, replyData, null, cipheredCommand);
                }
                break;
            case Command.GET_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.GET)) {
                    invalidCommand = true;
                } else {
                    if (data.size() != 0) {
                        GXDLMSLNCommandHandler.handleGetRequest(settings, this, data, replyData, null, cipheredCommand);
                    }
                }
                break;
            case Command.READ_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.READ)) {
                    invalidCommand = true;
                } else {
                    GXDLMSSNCommandHandler.handleReadRequest(settings, this, data, replyData, null, cipheredCommand);
                }
                break;
            case Command.METHOD_REQUEST:
                if (!settings.getNegotiatedConformance().contains(Conformance.ACTION)) {
                    invalidCommand = true;
                } else {
                    GXDLMSLNCommandHandler.handleMethodRequest(settings, this, data, sr.getConnectionInfo(), replyData,
                            null, cipheredCommand);
                }
                break;
            case Command.SNRM:
                handleSnrmRequest(data);
                frame = (byte) Command.UA;
                break;
            case Command.AARQ:
                handleAarqRequest(data, sr.getConnectionInfo());
                settings.updateSecuritySettings(settings.getSourceSystemTitle());
                if ((settings.getConnected() & ConnectionState.DLMS) != 0) {
                    notifyConnected(sr.getConnectionInfo());
                }
                break;
            case Command.RELEASE_REQUEST:
                handleReleaseRequest(data, sr.getConnectionInfo());
                if ((settings.getConnected() & ConnectionState.DLMS) != 0) {
                    settings.setConnected(settings.getConnected() & ~ConnectionState.DLMS);
                    if (owner instanceof GXDLMSServer) {
                        ((GXDLMSServer) owner).disconnected(sr.getConnectionInfo());
                    } else {
                        ((GXDLMSServer2) owner).onDisconnected(sr.getConnectionInfo());
                    }
                }
                break;
            case Command.DISCONNECT_REQUEST:
                replyData.clear();
                generateDisconnectRequest();
                if (settings.getConnected() != ConnectionState.NONE) {
                    if ((settings.getConnected() & ConnectionState.DLMS) != 0) {
                        if (owner instanceof GXDLMSServer) {
                            ((GXDLMSServer) owner).disconnected(sr.getConnectionInfo());
                        } else {
                            ((GXDLMSServer2) owner).onDisconnected(sr.getConnectionInfo());
                        }
                    }
                    settings.setConnected(ConnectionState.NONE);
                    setAssignedAssociation(null);
                    UpdateDefaultBaudRate(sr);
                }
                frame = Command.UA;
                break;
            case Command.GENERAL_BLOCK_TRANSFER:
                if (!handleGeneralBlockTransfer(data, sr, info.getCipheredCommand())) {
                    return null;
                }
                break;
            case Command.DISCOVER_REQUEST:
                settings.getPlc().parseDiscoverRequest(data);
                boolean newMeter = settings.getPlc().getMacSourceAddress() == 0xFFE
                        && settings.getPlc().getMacDestinationAddress() == 0xFFF;
                return settings.getPlc().discoverReport(settings.getPlc().getSystemTitle(), newMeter);
            case Command.REGISTER_REQUEST:
                settings.getPlc().parseRegisterRequest(data);
                return settings.getPlc().discoverReport(settings.getPlc().getSystemTitle(), false);
            case Command.PING_REQUEST:
                break;
            case Command.NONE:
                // Client wants to get next block.
                break;
            default:
                throw new IllegalArgumentException("Invalid command: " + String.valueOf(cmd));
            }
            if (invalidCommand) {
                replyData.clear();
                replyData.setUInt8(Command.EXCEPTION_RESPONSE);
                replyData.setUInt8(ExceptionStateError.SERVICE_UNKNOWN.getValue());
                replyData.setUInt8(ExceptionServiceError.SERVICE_NOT_SUPPORTED.getValue());
            }
        }
        byte[] reply;
        if (settings.getInterfaceType() == InterfaceType.WRAPPER) {
            reply = GXDLMS.getWrapperFrame(settings, cmd, replyData);
        } else {
            reply = GXDLMS.getHdlcFrame(settings, frame, replyData);
        }
        if (cmd == Command.DISCONNECT_REQUEST
                || (settings.getInterfaceType() == InterfaceType.WRAPPER && cmd == Command.RELEASE_REQUEST)) {
            reset();
        }
        return reply;
    }

    private boolean handleGeneralBlockTransfer(final GXByteBuffer data, final GXServerReply sr,
            final int cipheredCommand) throws Exception {
        short bc = 0;
        int blockNumber = 0, blockNumberAck = 0;
        // BlockControl
        if (!sr.isStreaming()) {
            bc = data.getUInt8();
            // Block number.
            blockNumber = data.getUInt16();
            // Block number acknowledged.
            blockNumberAck = data.getUInt16();
            int len = GXCommon.getObjectCount(data);
            if (len > data.size() - data.position()) {
                replyData.set(generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE,
                        Service.UNSUPPORTED.getValue()));
            }
            if (transaction != null) {
                if (transaction.getCommand() == Command.GET_REQUEST
                        || transaction.getCommand() == Command.METHOD_RESPONSE) {
                    // Get request for next data block
                    if (sr.getCount() == 0) {
                        settings.setBlockNumberAck(settings.getBlockNumberAck() + 1);
                        sr.setCount(bc & 0x3F);
                    }
                    if (transaction.getCommand() == Command.GET_REQUEST) {
                        GXDLMSLNCommandHandler.getRequestNextDataBlock(settings, 0, this, data, replyData, null, true,
                                cipheredCommand);
                    } else {
                        GXDLMSLNCommandHandler.methodRequestNextDataBlock(settings, 0, this, data, replyData, null,
                                true, cipheredCommand);
                    }
                    if (sr.getCount() != 0) {
                        sr.setCount(sr.getCount() - 1);
                    }
                    if (this.transaction == null) {
                        sr.setCount(0);
                    }
                    // Save server GBT window size to settings because sr is
                    // lost.
                    if (settings.isServer()) {
                        settings.setCount(sr.getCount());
                    }
                }
            } else {
                transaction.getData().set(data);
                // Send ACK.
                boolean igonoreAck =
                        (bc & 0x40) != 0 && (blockNumberAck * settings.getGbtWindowSize()) + 1 > blockNumber;
                int windowSize = settings.getGbtWindowSize();
                int bn = settings.getBlockIndex();
                if ((bc & 0x80) != 0) {
                    handleCommand(transaction.getCommand(), transaction.getData(), sr, cipheredCommand);
                    transaction = null;
                    igonoreAck = false;
                    windowSize = 1;
                }
                if (igonoreAck) {
                    return false;
                }
                replyData.setUInt8(Command.GENERAL_BLOCK_TRANSFER);
                replyData.setUInt8((byte) (0x80 | windowSize));
                settings.setBlockIndex(settings.getBlockIndex() + 1);
                replyData.setUInt16(bn);
                replyData.setUInt16(blockNumber);
                replyData.setUInt8(0);
            }
        } else {
            transaction = new GXDLMSLongTransaction(null, data.getUInt8(), data);
            replyData.setUInt8(Command.GENERAL_BLOCK_TRANSFER);
            replyData.setUInt8((0x80 | settings.getGbtWindowSize()));
            replyData.setUInt16(blockNumber);
            ++blockNumberAck;
            replyData.setUInt16(blockNumberAck);
            replyData.setUInt8(0);
        }
        return true;
    }

    /*
     * Generate confirmed service error.
     * @param service Confirmed service error.
     * @param type Service error.
     * @param code code
     * @return
     */
    static byte[] generateConfirmedServiceError(final ConfirmedServiceError service, final ServiceError type,
            final int code) {
        return new byte[] { (byte) Command.CONFIRMED_SERVICE_ERROR, (byte) service.getValue(), (byte) type.getValue(),
                (byte) code };
    }

    final GXDLMSObject notifyFindObject(final ObjectType objectType, final int sn, final String ln) throws Exception {
        if (owner instanceof GXDLMSServer) {
            return ((GXDLMSServer) owner).onFindObject(objectType, sn, ln);
        }
        return ((GXDLMSServer2) owner).onFindObject(objectType, sn, ln);
    }

    /*
     * Read selected item(s).
     * @param args Handled read requests.
     */
    final void notifyRead(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer) {
            ((GXDLMSServer) owner).read(args);
        } else {
            ((GXDLMSServer2) owner).onPreRead(args);
        }
    }

    /*
     * Write selected item(s).
     * @param args Handled write requests.
     */
    final void notifyWrite(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer) {
            ((GXDLMSServer) owner).write(args);
        } else {
            ((GXDLMSServer2) owner).onPreWrite(args);
        }
    }

    /*
     * Action is occurred.
     * @param args Handled action requests.
     */
    public final void notifyAction(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer) {
            ((GXDLMSServer) owner).action(args);
        } else {
            ((GXDLMSServer2) owner).onPreAction(args);
        }
    }

    /*
     * Client has try to made invalid connection. Password is incorrect.
     * @param connectionInfo Connection info.
     */
    final void notifyInvalidConnection(final GXDLMSConnectionEventArgs connectionInfo) throws Exception {
        if (owner instanceof GXDLMSServer) {
            ((GXDLMSServer) owner).invalidConnection(connectionInfo);
        } else {
            ((GXDLMSServer2) owner).onInvalidConnection(connectionInfo);
        }
    }

    /*
     * Client has connected.
     * @param connectionInfo Connection info.
     */
    final void notifyConnected(final GXDLMSConnectionEventArgs connectionInfo) throws Exception {
        if ((settings.getConnected() & ConnectionState.DLMS) != 0) {
            if (owner instanceof GXDLMSServer) {
                ((GXDLMSServer) owner).connected(connectionInfo);
            } else {
                ((GXDLMSServer2) owner).onConnected(connectionInfo);
            }
        }
    }

    public final int notifyGetAttributeAccess(final ValueEventArgs arg) throws Exception {
        if (owner instanceof GXDLMSServer) {
            GXAttributeCollection attributes = arg.getTarget().getAttributes();
            GXDLMSAttributeSettings att = attributes.find(arg.getIndex());
            /// If attribute is not set return read only.
            if (att == null) {
                return AccessMode.READ.getValue();
            }
            return att.getAccess().getValue();
        } else if (owner instanceof GXDLMSServer3) {
            if (getSettings().getAssignedAssociation().getVersion() < 3) {
                return ((GXDLMSServer3) owner).onGetAttributeAccess(arg).getValue();
            } else {
                return AccessMode3.toInteger(((GXDLMSServer3) owner).onGetAttributeAccess3(arg));
            }
        }
        if (arg.getIndex() == 1) {
            return AccessMode.READ.getValue();
        }
        return ((GXDLMSServer2) owner).onGetAttributeAccess(arg).getValue();
    }

    public final int notifyGetMethodAccess(final ValueEventArgs arg) throws Exception {
        if (owner instanceof GXDLMSServer) {
            GXAttributeCollection attributes = arg.getTarget().getMethodAttributes();
            GXDLMSAttributeSettings att = attributes.find(arg.getIndex());
            /// If attribute is not set return read only.
            if (att == null) {
                return MethodAccessMode.NO_ACCESS.getValue();
            }
            return att.getMethodAccess().getValue();
        } else if (owner instanceof GXDLMSServer3) {
            if (getSettings().getAssignedAssociation().getVersion() < 3) {
                return ((GXDLMSServer3) owner).onGetMethodAccess(arg).getValue();
            } else {
                return MethodAccessMode3.toInteger(((GXDLMSServer3) owner).onGetMethodAccess3(arg));
            }
        }
        return ((GXDLMSServer2) owner).onGetMethodAccess(arg).getValue();
    }

    /*
     * Read selected item(s).
     * @param args Handled read requests.
     */
    final void notifyPostRead(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer2) {
            ((GXDLMSServer2) owner).onPostRead(args);
        }
    }

    /*
     * Write selected item(s).
     * @param args Handled write requests.
     */
    final void notifyPostWrite(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer2) {
            ((GXDLMSServer2) owner).onPostWrite(args);
        }
    }

    /*
     * Action is occurred.
     * @param args Handled action requests.
     */
    public final void notifyPostAction(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer2) {
            ((GXDLMSServer2) owner).onPostAction(args);
        }
    }

    public final void notifyPreGet(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer2) {
            ((GXDLMSServer2) owner).onPreGet(args);
        } else if (owner instanceof GXDLMSServer) {
            ((GXDLMSServer) owner).read(args);
        }
    }

    public final void notifyPostGet(final ValueEventArgs[] args) throws Exception {
        if (owner instanceof GXDLMSServer2) {
            ((GXDLMSServer2) owner).onPostGet(args);
        }
    }

    /**
     * @return HDLC settings.
     */
    public GXDLMSHdlcSetup getHdlc() {
        return settings.getHdlc();
    }

    /**
     * @param value
     *            HDLC settings.
     */
    public void setHdlc(final GXDLMSHdlcSetup value) {
        settings.setHdlc(value);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy