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

eu.dariolucia.ccsds.sle.utl.si.ServiceInstance Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2018-2019 Dario Lucia (https://www.dariolucia.eu)
 *
 *  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 eu.dariolucia.ccsds.sle.utl.si;

import com.beanit.jasn1.ber.types.BerNull;
import com.beanit.jasn1.ber.types.BerType;
import eu.dariolucia.ccsds.sle.generated.ccsds.sle.transfer.service.bind.types.*;
import eu.dariolucia.ccsds.sle.generated.ccsds.sle.transfer.service.common.types.Credentials;
import eu.dariolucia.ccsds.sle.utl.config.PeerConfiguration;
import eu.dariolucia.ccsds.sle.utl.config.ServiceInstanceConfiguration;
import eu.dariolucia.ccsds.sle.utl.config.network.PortMapping;
import eu.dariolucia.ccsds.sle.utl.config.network.RemotePeer;
import eu.dariolucia.ccsds.sle.utl.network.tml.ITmlChannelObserver;
import eu.dariolucia.ccsds.sle.utl.network.tml.TmlChannel;
import eu.dariolucia.ccsds.sle.utl.network.tml.TmlChannelException;
import eu.dariolucia.ccsds.sle.utl.network.tml.TmlDisconnectionReasonEnum;
import eu.dariolucia.ccsds.sle.utl.pdu.PduFactoryUtil;
import eu.dariolucia.ccsds.sle.utl.pdu.PduStringUtil;
import eu.dariolucia.ccsds.sle.utl.util.DataRateCalculator;
import eu.dariolucia.ccsds.sle.utl.util.DataRateSample;

import java.io.IOException;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

import static eu.dariolucia.ccsds.sle.utl.si.SleOperationNames.*;

/**
 * This class is the core class of the SLE User Test Library. In an SLE association it represents the user peer: it
 * can act as initiator (User Initiated Bind) or responder (Provider Initiated Bind). It is the entry point to send
 * and receive SLE operations from the SLE provider, further specialised by service-type specific classes.
 * 

* This class is thread-safe and uses two threads for its operations: *

    *
  • A dispatcher thread, which implements a thread confinement managing the internals of the object state
  • *
  • A notifier thread, which is responsible to notify state changes and SLE operations to the subscribers
  • *
* Operations requested on this object by the user code or by the TML channel threads are queued in a queue and processed * by a single thread (the dispatcher thread). When a change in state must be notified to subscribers, the state object * is created and the notification is delegated to a separate thread. Therefore operations performed by instances of * this class are fully asynchronous and lock-free. */ public abstract class ServiceInstance implements ITmlChannelObserver { private static final int BIND_INTERNAL_INVOKE_ID = -1; private static final int UNBIND_INTERNAL_INVOKE_ID = -2; private static final int SI_INIT_MODE_NOT_SELECTED = 0; private static final int SI_INIT_MODE_UIB = 1; private static final int SI_INIT_MODE_PIB = 2; private static final int MAX_DISPATCHER_QUEUE = 10; private static final int MAX_NOTIFIER_QUEUE = 10; private static final Logger LOG = Logger.getLogger(ServiceInstance.class.getName()); private final ExecutorService dispatcherService; private final ExecutorService notifierService; private final BlockingQueue notificationQueue = new ArrayBlockingQueue<>(MAX_NOTIFIER_QUEUE); private final PriorityBlockingQueue dispatchQueue = new PriorityBlockingQueue<>(MAX_DISPATCHER_QUEUE); private final AtomicLong sleTaskSequencer = new AtomicLong(0); // The SLE API configuration private final PeerConfiguration peerConfiguration; // The service instance configuration protected final ServiceInstanceConfiguration serviceInstanceConfiguration; // The listeners to this service instance private final List listeners = new CopyOnWriteArrayList<>(); private volatile int initMode = SI_INIT_MODE_NOT_SELECTED; // Service instance state protected volatile ServiceInstanceBindingStateEnum currentState = ServiceInstanceBindingStateEnum.UNBOUND; // NOSONAR enumeration is immutable private final Object currentStateMonitor = new Object(); private Integer sleVersion = null; // Set by the bind request private TmlChannel tmlChannel = null; // The TML channel private boolean expectConnectionToBeClosed = false; // To be set to true when it is clear that the channel will be // closed (peer abort, unbind return) // The sequencer for operations protected final AtomicInteger invokeIdSequencer = new AtomicInteger(0); // Set of timer tasks and timer to handle return timeouts private final Map invokeId2timeout = new HashMap<>(); private final Timer returnTimeoutTimer = new Timer(true); // Handler map: one handler registered for each return operation private final Map, Consumer> handlers = new HashMap<>(); private String lastErrorMessage = null; // Set in case of protocol abort or peer abort or other problem private Exception lastErrorException = null; // Set in case of protocol abort or peer abort or other problem private boolean statusReportScheduled = false; // If the status report was scheduled private byte[] lastPduSent = null; private byte[] lastPduReceived = null; private final DataRateCalculator statsCounter = new DataRateCalculator(); private boolean sendPositiveBindReturn = true; private boolean sendPositiveUnbindReturn = true; private BindDiagnosticsEnum negativeBindReturnDiagnostics = BindDiagnosticsEnum.OTHER_REASON; private boolean configured = false; private boolean disposing = false; protected ServiceInstance(PeerConfiguration peerConfiguration, ServiceInstanceConfiguration serviceInstanceConfiguration) { this.peerConfiguration = peerConfiguration; this.serviceInstanceConfiguration = serviceInstanceConfiguration; this.dispatcherService = Executors.newFixedThreadPool(1, r -> { Thread t = new Thread(r); t.setDaemon(true); t.setName(ServiceInstance.this.serviceInstanceConfiguration.getServiceInstanceIdentifier() + " - SI Dispatcher"); return t; }); this.dispatcherService.execute(this::dispatcherMainThread); this.notifierService = Executors.newFixedThreadPool(1, r -> { Thread t = new Thread(r); t.setDaemon(true); t.setName(ServiceInstance.this.serviceInstanceConfiguration.getServiceInstanceIdentifier() + " - SI Notifier"); return t; }); this.notifierService.execute(this::notifierMainThread); // register handlers for unbind return and bind return operations registerPduReceptionHandler(SleBindInvocation.class, this::handleSleBindInvocation); registerPduReceptionHandler(SleUnbindInvocation.class, this::handleSleUnbindInvocation); registerPduReceptionHandler(SleBindReturn.class, this::handleSleBindReturn); registerPduReceptionHandler(SleUnbindReturn.class, this::handleSleUnbindReturn); // Custom setup setup(); } private void notifierMainThread() { while(!this.notifierService.isShutdown()) { Runnable r; try { r = this.notificationQueue.take(); } catch (InterruptedException e) { // NOSONAR not ignored // This thread can be interrupted on dispose if (!disposing && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, String.format("%s: Interruption when waiting for element in the notification queue", getServiceInstanceIdentifier()), e); } return; } if(this.notifierService.isShutdown()) { return; } try { r.run(); } catch (Exception e) { if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, String.format("%s: Exception when notifying information", getServiceInstanceIdentifier()), e); } } } } private void dispatcherMainThread() { while(!this.dispatcherService.isShutdown()) { SleTask t; synchronized (dispatchQueue) { while(dispatchQueue.isEmpty() && !this.dispatcherService.isShutdown()) { try { dispatchQueue.wait(); } catch (InterruptedException e) { // NOSONAR // This thread can be interrupted on dispose if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, String.format("%s: Interruption when waiting for element in the dispatch queue", getServiceInstanceIdentifier()), e); } return; } } if(dispatcherService.isShutdown()) { return; } t = this.dispatchQueue.poll(); dispatchQueue.notifyAll(); } if(t != null) { try { t.run(); } catch (Exception e) { if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, String.format("%s: Exception when dispatching task", getServiceInstanceIdentifier()), e); } } } } } protected final void registerPduReceptionHandler(Class clazz, Consumer handler) { this.handlers.put(clazz, handler); } /** * This method returns the configuration of the service instance. This is a pointer to the internal configuration * (not a copy) and shall not be changed by the calling object. The service-type specific subclass is returned. * * @return the service instance configuration */ public ServiceInstanceConfiguration getServiceInstanceConfiguration() { return serviceInstanceConfiguration; } @SuppressWarnings("unchecked") private Consumer getHandler(Class clazz) { return (Consumer) this.handlers.get(clazz); } private void setError(String message, Exception e) { LOG.log(Level.SEVERE, String.format("%s: %s", getServiceInstanceIdentifier(), message), e); this.lastErrorMessage = message; this.lastErrorException = e; } protected final void setError(String message) { setError(message, null); } protected final void clearError() { this.lastErrorException = null; this.lastErrorMessage = null; } protected final byte[] getLastPduReceived() { return this.lastPduReceived; } protected final byte[] getLastPduSent() { return this.lastPduSent; } private Exception getErrorException() { return this.lastErrorException; } private String getErrorMessage() { return this.lastErrorMessage; } protected final Integer getSleVersion() { return this.sleVersion; } /** * This method can be overridden by subclasses to indicate that they implement a provider role. Currently, * all the production-code of this library purely implement a user role. Provider-role implementations are * available in the test location. * * @return true if the service instance is a user-side peer, false if it is a provider-side peer */ protected boolean isUserSide() { return true; } protected final void dispatchFromUser(Runnable r) { if (!this.configured) { throw new IllegalStateException("Service instance not configured: call configure() before invoking any method"); } if (this.dispatcherService.isShutdown()) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": Invocation not dispatched, dispatcher was shut down"); } return; } SleTask t = new SleTask(SleTask.FROM_USER_TYPE, r); addToDispatchQueue(t); } protected final void dispatchFromProvider(Runnable r) { if (!this.configured) { throw new IllegalStateException("Service instance not configured: call configure() before invoking any method"); } if (this.dispatcherService.isShutdown()) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": Invocation not dispatched, dispatcher was shut down"); } return; } SleTask t = new SleTask(SleTask.FROM_PROVIDER_TYPE, r); addToDispatchQueue(t); } private void addToDispatchQueue(SleTask t) { synchronized (dispatchQueue) { while (dispatchQueue.size() > MAX_DISPATCHER_QUEUE) { try { dispatchQueue.wait(); } catch (InterruptedException e) { // NOSONAR // Do nothing but return if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": Task not added to dispatch queue due to interruption"); } return; } } this.dispatchQueue.offer(t); // NOSONAR this method never returns false this.dispatchQueue.notifyAll(); } } private void notify(Runnable r) { if (this.notifierService.isShutdown()) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": Information not notified, notifier was shut down"); } return; } try { this.notificationQueue.put(r); } catch (InterruptedException e) { // NOSONAR // Do nothing but return if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": Information not added to notification queue due to interruption"); } } } protected final void notifyStateUpdate() { final ServiceInstanceState state = buildCurrentState(); if (state != null) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { l.onStateUpdated(this, state); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (state update) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } } protected final void notifyPduSent(Object pdu, String name, byte[] encodedOperation) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { l.onPduSent(this, pdu, name, encodedOperation); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (PDU sent) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } protected final void notifyPduSentError(Object pdu, String name, byte[] encodedOperation) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { l.onPduSentError(this, pdu, name, encodedOperation, getErrorMessage(), getErrorException()); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (PDU sent error) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } protected final void notifyPduReceived(Object pdu, String name, byte[] encodedOperation) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { if(LOG.isLoggable(Level.FINEST)) { LOG.finer(String.format("%s: Notify PDU %s to listener %s", getServiceInstanceIdentifier(), name, l)); } l.onPduReceived(this, pdu, name, encodedOperation); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (PDU received) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } private void notifyPduDecodingError(byte[] encodedOperation) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { l.onPduDecodingError(this, encodedOperation); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (PDU decoding error) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } private void notifyPduHandlingError(Object pdu, byte[] encodedOperation) { notify(() -> { for (IServiceInstanceListener l : this.listeners) { try { l.onPduHandlingError(this, pdu, encodedOperation); } catch (Exception e) { LOG.log(Level.SEVERE, String.format("%s: Service instance cannot notify (PDU handling error) listener %s", getServiceInstanceIdentifier(), l), e); } } }); } /** * This method registers a listener to this service instance. * * @param l the listener to be registered */ public final void register(final IServiceInstanceListener l) { dispatchFromUser(() -> doRegister(l)); } private void doRegister(final IServiceInstanceListener l) { final ServiceInstanceState state = buildCurrentState(); this.listeners.add(l); notify(() -> l.onStateUpdated(this, state)); } /** * This method deregisters a listener from this service instance. * * @param l the listener to be deregistered */ public final void deregister(final IServiceInstanceListener l) { dispatchFromUser(() -> doDeregister(l)); } private void doDeregister(IServiceInstanceListener l) { this.listeners.remove(l); } /** * This method sets the BIND response behaviour of the service instance if configured for Provider Initiated Bind. * This method can be called at any time and the change in configuration will be applied immediately, i.e. they will * overwrite the configuration applied when calling a prior waitForBind(...). * * @param sendPositiveBindReturn if true (default), the user will respond with a positive BIND return, otherwise it will send back a negative response * @param negativeBindReturnDiagnostics if sendPositiveBindReturn is false, the negative response will deliver this diagnostics */ public final void setBindReturnBehaviour(boolean sendPositiveBindReturn, BindDiagnosticsEnum negativeBindReturnDiagnostics) { dispatchFromUser(() -> { this.sendPositiveBindReturn = sendPositiveBindReturn; this.negativeBindReturnDiagnostics = negativeBindReturnDiagnostics; LOG.log(Level.CONFIG, String.format("%s: BIND-RETURN behaviour updated: send positive BIND-RETURN=%s, negative diagnostics=%s", getServiceInstanceIdentifier(), sendPositiveBindReturn, negativeBindReturnDiagnostics)); }); } /** * This method sets the UNBIND response behaviour of the service instance if configured for the Provided Initiated Bind. * This method can be called at any time and the change in configuration will be applied immediately. * * @param sendPositiveUnbindReturn if true (default), the user will responde with a positive BIND return, otherwise it will send back a negative response */ public final void setUnbindReturnBehaviour(boolean sendPositiveUnbindReturn) { dispatchFromUser(() -> { this.sendPositiveUnbindReturn = sendPositiveUnbindReturn; LOG.log(Level.CONFIG, String.format("%s: UNBIND-RETURN behaviour updated: send positive BIND-RETURN=%s", getServiceInstanceIdentifier(), sendPositiveUnbindReturn)); }); } /** * This method requests the service instance to start waiting for a connection plus BIND coming from the provider. * This method does not block and returns immediately. The provided arguments will be applied immediately, i.e. they will * overwrite the configuration applied when calling a prior setBindReturnBehaviour(...). * * @param sendPositiveBindReturn if true, the user will respond with a positive BIND return, otherwise it will send back a negative response * @param negativeBindReturnDiagnostics if sendPositiveBindReturn is false, the negative response will deliver this diagnostics */ public final void waitForBind(boolean sendPositiveBindReturn, BindDiagnosticsEnum negativeBindReturnDiagnostics) { dispatchFromUser(() -> doWaitForBind(sendPositiveBindReturn, negativeBindReturnDiagnostics)); } private void doWaitForBind(boolean sendPositiveBindReturn, BindDiagnosticsEnum negativeBindReturnDiagnostics) { clearError(); // if (this.initMode != SI_INIT_MODE_NOT_SELECTED) { notifyInternalError("Wait for bind requested, but service instance is not in clean init mode"); return; } if (this.serviceInstanceConfiguration.getInitiator() == InitiatorRoleEnum.USER && isUserSide()) { notifyInternalError("Wait for bind requested, but service instance initiator is set to USER"); return; } if (this.serviceInstanceConfiguration.getInitiator() == InitiatorRoleEnum.PROVIDER && !isUserSide()) { notifyInternalError("Wait for bind requested, but service instance initiator is set to PROVIDER"); return; } // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.UNBOUND) { notifyInternalError("Wait for bind requested, but service instance is in state " + this.currentState); return; } // Set the invokeId sequencer to 0 this.invokeIdSequencer.set(0); // Establish connection, just open the port // From the responder port, go to API configuration and check the // foreign local port and the related IP address. Optional port = this.peerConfiguration.getPortMappings().stream() .filter(flp -> flp.getPortName().equals(getResponderPortIdentifier())).findFirst(); if (port.isPresent()) { // Create a new TML channel this.tmlChannel = TmlChannel.createServerTmlChannel(port.get().getRemotePort(), this, port.get().getTcpTxBufferSize(), port.get().getTcpRxBufferSize()); } else { setError(String.format("Foreign local port %s not found in the SLE configuration file for service instance %s", getResponderPortIdentifier(), getServiceInstanceIdentifier())); notifyStateUpdate(); return; } // At this point in time, the TML is in PIB mode this.initMode = SI_INIT_MODE_PIB; // Initialise predefined behaviour this.sendPositiveBindReturn = sendPositiveBindReturn; this.negativeBindReturnDiagnostics = negativeBindReturnDiagnostics; // Go for connection try { setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBOUND_WAIT); this.tmlChannel.connect(); if(LOG.isLoggable(Level.INFO)) { LOG.log(Level.INFO, String.format("%s: Waiting for incoming connections", getServiceInstanceIdentifier())); } } catch (TmlChannelException e) { LOG.log(Level.SEVERE, String.format("%s: error when waiting for connection", getServiceInstanceIdentifier()), e); setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBOUND); disconnect("Cannot wait for connection", e, null); } // Generate state and notify update notifyStateUpdate(); } /** * This method requests the user to initiate the TML connection and send a BIND operation to the provider, with the * specification of the provided SLE version. This method does not block and returns immediately. * * @param version the SLE version to be used */ public final void bind(int version) { dispatchFromUser(() -> doBind(version)); } private void doBind(int version) { clearError(); // if (this.initMode != SI_INIT_MODE_NOT_SELECTED) { notifyInternalError("Bind requested, but service instance is not in clean init mode"); return; } if (this.serviceInstanceConfiguration.getInitiator() == InitiatorRoleEnum.PROVIDER && isUserSide()) { notifyInternalError("Bind requested, but service instance initiator is set to PROVIDER"); return; } if (this.serviceInstanceConfiguration.getInitiator() == InitiatorRoleEnum.USER && !isUserSide()) { notifyInternalError("Bind requested, but service instance initiator is set to USER"); return; } // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.UNBOUND) { notifyInternalError("Bind requested, but service instance is in state " + this.currentState); return; } // Set the invokeId sequencer to 0 this.invokeIdSequencer.set(0); // Set the version to be used this.sleVersion = version; // Create operation SleBindInvocation pdu = new SleBindInvocation(); pdu.setVersionNumber(new VersionNumber(version)); pdu.setInitiatorIdentifier(new AuthorityIdentifier(getInitiatorIdentifier().getBytes())); pdu.setResponderPortIdentifier(new PortId(getResponderPortIdentifier().getBytes())); pdu.setServiceType(new ApplicationIdentifier(getApplicationIdentifier().getCode())); pdu.setServiceInstanceIdentifier(PduFactoryUtil.buildServiceInstanceIdentifier(getServiceInstanceIdentifier(), getApplicationIdentifier())); // Add credentials // From the API configuration (remote peers) and SI configuration (responder // id), check remote peer and check if authentication must be used. Credentials creds = generateCredentials(getResponderIdentifier(), AuthenticationModeEnum.ALL, AuthenticationModeEnum.BIND); if (creds == null) { // Error while generating credentials, set by generateCredentials() pduTransmissionError(pdu, BIND_NAME, null); return; } else { pdu.setInvokerCredentials(creds); } updateHandlersForVersion(this.sleVersion); // Establish connection // From the responder port of the bind, go to API configuration and check the // foreign local port and the related IP address. Optional port = this.peerConfiguration.getPortMappings().stream() .filter(flp -> flp.getPortName().equals(getResponderPortIdentifier())).findFirst(); if (port.isPresent()) { // Create a new TML channel this.tmlChannel = TmlChannel.createClientTmlChannel(port.get().getRemoteHost(), port.get().getRemotePort(), port.get().getHeartbeatInterval(), port.get().getDeadFactor(), this, port.get().getTcpTxBufferSize(), port.get().getTcpRxBufferSize()); } else { setError("Foreign local port " + getResponderPortIdentifier() + " not found in the SLE configuration file for service instance " + getServiceInstanceIdentifier()); pduTransmissionError(pdu, BIND_NAME, null); return; } // Go for connection try { this.tmlChannel.connect(); } catch (TmlChannelException e) { disconnect("Cannot connect", e, null); pduTransmissionError(pdu, BIND_NAME, null); return; } boolean resultOk = encodeAndSend(BIND_INTERNAL_INVOKE_ID, pdu, BIND_NAME); if (resultOk) { // If all fine, transition to new state: BIND_PENDING and notify PDU sent setServiceInstanceState(ServiceInstanceBindingStateEnum.BIND_PENDING); // Init mode to be set this.initMode = SI_INIT_MODE_UIB; // Notify pduTransmissionOk(pdu, BIND_NAME); } } private void startReturnTimeout(final long opInvokeId, Object pdu) { if (LOG.isLoggable(Level.FINER)) { LOG.finer(getServiceInstanceIdentifier() + ": Return timeout started for invokeId=" + opInvokeId + ", operation: " + pdu); } TimerTask tt = new TimerTask() { @Override public void run() { try { dispatchFromUser(() -> returnTimeoutExpired(opInvokeId)); } catch (Exception e) { LOG.log(Level.WARNING, String.format("%s: Cannot run return timeout expire task for operation with invokeId=%d", getServiceInstanceIdentifier(), opInvokeId), e); } } }; this.invokeId2timeout.put(opInvokeId, tt); this.returnTimeoutTimer.schedule(tt, this.serviceInstanceConfiguration.getReturnTimeoutPeriod() * 1000L); } private void returnTimeoutExpired(long opInvokeId) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning(String.format("%s: Return timeout expired for invokeId=%d", getServiceInstanceIdentifier(), opInvokeId)); } TimerTask tt = this.invokeId2timeout.remove(opInvokeId); if (tt != null) { disconnect("Return timeout expired for operation with invokeId=" + opInvokeId); // Generate state and notify update notifyStateUpdate(); } } protected void cancelReturnTimeout(long opInvokeId) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(String.format("%s: Return timeout cancelled for invokeId=%d", getServiceInstanceIdentifier(), opInvokeId)); } TimerTask tt = this.invokeId2timeout.remove(opInvokeId); if (tt != null) { tt.cancel(); } } protected boolean encodeAndSend(Integer invokeId, BerType pdu, String name) { // Encode the pdu // Use a map class 2 function to convert the PDU into a byte array. See encdec // package. byte[] encodedPdu; try { encodedPdu = encodePdu(pdu); String invokeStr = invokeId != null && invokeId >= 0 ? "(" + invokeId + ") " : "() "; if (LOG.isLoggable(Level.FINE)) { LOG.fine(String.format("%s: PDU %s%s encoded: %s", getServiceInstanceIdentifier(), invokeStr, name, PduStringUtil.toHexDump(encodedPdu))); } } catch (IOException e1) { disconnect("Cannot encode PDU", e1, null); pduTransmissionError(pdu, name, null); return false; } // Send to TML try { this.tmlChannel.sendPdu(encodedPdu); } catch (TmlChannelException e) { disconnect("Cannot send PDU", e, null); pduTransmissionError(pdu, name, encodedPdu); return false; } // Start return timeout // Start a timer task for this confirmed operation and add it in a map. // This is done only if the invoke ID is set. if (invokeId != null) { startReturnTimeout(invokeId, pdu); } this.statsCounter.addOut(1); // Save in the last pdu sent this.lastPduSent = encodedPdu; return true; } protected final Credentials generateCredentials(String responderIdentifier, AuthenticationModeEnum... requiredAuthModes) { Optional remotePeer = this.peerConfiguration.getRemotePeers().stream() .filter(rp -> rp.getId().equals(responderIdentifier)).findFirst(); if (remotePeer.isPresent()) { Credentials credentials; // If so, build and add credentials. if (Arrays.asList(requiredAuthModes).contains(remotePeer.get().getAuthenticationMode())) { credentials = PduFactoryUtil.buildCredentials(true, this.peerConfiguration.getLocalId(), this.peerConfiguration.getLocalPassword(), remotePeer.get().getAuthenticationHash()); } else { credentials = PduFactoryUtil.buildEmptyCredentials(); } return credentials; } else { setError("Remote peer " + responderIdentifier + " not found in the SLE configuration file for service instance " + getServiceInstanceIdentifier()); return null; } } protected void setServiceInstanceState(ServiceInstanceBindingStateEnum newState) { if (newState != this.currentState) { if (LOG.isLoggable(Level.INFO)) { LOG.log(Level.INFO, String.format("%s: State transition from %s to %s", getServiceInstanceIdentifier(), this.currentState, newState)); } this.currentState = newState; // currentState is volatile and it is written only by the owning thread, so no writing race conditions. Also code reading this // variable in derived classes is safe, because the code is read in methods executed only by the owning thread. // There is no need to put this under lock as its assignment is atomic, being a volatile field. synchronized (currentStateMonitor) { currentStateMonitor.notifyAll(); } } } /** * This method waits until the service instance reaches the specified state, or the timeout expires. * * @param expectedState the expected state to reach * @param timeoutMillis the timeout in milliseconds * @return true if the expected state was reached, false if the timeout expires * @throws InterruptedException if the thread is interrupted */ public final boolean waitForState(ServiceInstanceBindingStateEnum expectedState, int timeoutMillis) throws InterruptedException { long maxWait = System.currentTimeMillis() + timeoutMillis; synchronized (currentStateMonitor) { long now = System.currentTimeMillis(); ServiceInstanceBindingStateEnum theState = this.currentState; while(theState != expectedState && now < maxWait) { currentStateMonitor.wait(maxWait - now); now = System.currentTimeMillis(); theState = this.currentState; } return theState == expectedState; } } /** * This method requests to send an UNBIND operation with the provided reason. This method does not block and returns immediately. * * @param reason the unbind reason */ public final void unbind(UnbindReasonEnum reason) { dispatchFromUser(() -> doUnbind(reason)); } private void doUnbind(UnbindReasonEnum reason) { clearError(); // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.READY) { notifyInternalError("Unbind requested, but service instance is in state " + this.currentState); return; } // Create operation SleUnbindInvocation pdu = new SleUnbindInvocation(); pdu.setUnbindReason(new UnbindReason(reason.getCode())); // Check end/suspend // Add credentials // From the API configuration (remote peers) and SI configuration (responder // id), check remote peer and check if authentication must be used. Credentials creds = generateCredentials(getResponderIdentifier(), AuthenticationModeEnum.ALL, AuthenticationModeEnum.BIND); if (creds == null) { // Error while generating credentials, set by generateCredentials() pduTransmissionError(pdu, UNBIND_NAME, null); return; } else { pdu.setInvokerCredentials(creds); } boolean resultOk = encodeAndSend(UNBIND_INTERNAL_INVOKE_ID, pdu, UNBIND_NAME); if (resultOk) { // About to close the TML channel, do not be surprised this.tmlChannel.aboutToDisconnect(); // If all fine, transition to new state: UNBIND_PENDING and notify PDU sent setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBIND_PENDING); pduTransmissionOk(pdu, UNBIND_NAME); } } /** * This method requests a local PEER-ABORT to be propagated to the remote peer. This method does not block and returns immediately. * * @param reason the reason of the peer abort */ public final void peerAbort(PeerAbortReasonEnum reason) { dispatchFromUser(() -> doPeerAbort(reason)); } private void doPeerAbort(PeerAbortReasonEnum reason) { clearError(); // Validate state if (this.currentState == ServiceInstanceBindingStateEnum.UNBOUND) { notifyInternalError("Peer abort requested, but service instance is in state " + this.currentState); return; } disconnect("Peer abort requested", null, reason); notifyStateUpdate(); } protected final String getRemotePeer() { return getInitiatorIdentifier().equals(this.peerConfiguration.getLocalId()) ? getResponderIdentifier() : getInitiatorIdentifier(); } private void handleSleUnbindReturn(SleUnbindReturn pdu) { clearError(); // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.UNBIND_PENDING) { pduReceptionProcessingError("Unbind return received, but service instance is in state " + this.currentState, pdu, UNBIND_RETURN_NAME); return; } // Validate credentials // From the API configuration (remote peers) and SI configuration (remote peer), // check remote peer and check if authentication must be used. // If so, verify credentials. if (!authenticate(pdu.getResponderCredentials(), AuthenticationModeEnum.ALL)) { pduReceptionProcessingError("Unbind return received, but wrong credentials", pdu, UNBIND_RETURN_NAME); return; } // Cancel timer task for unbind operation cancelReturnTimeout(UNBIND_INTERNAL_INVOKE_ID); // If all fine (result positive), transition to new state: UNBOUND and notify // PDU received if (pdu.getResult().getPositive() != null) { setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBOUND); disconnect(null); } else { // If problems (result negative), UNBOUND, disconnect, cleanup disconnect("Unbind return received, null result"); } // Notify PDU pduReceptionOk(pdu, UNBIND_RETURN_NAME); } protected final boolean authenticate(Credentials remoteCredentials, AuthenticationModeEnum... authModeEnums) { String remoteId = getRemotePeer(); int authDelay = this.peerConfiguration.getAuthenticationDelay(); Optional remotePeer = this.peerConfiguration.getRemotePeers().stream() .filter(a -> a.getId().equals(remoteId)).findFirst(); if (remotePeer.isPresent()) { AuthenticationModeEnum requiredAuthMode = remotePeer.get().getAuthenticationMode(); boolean credentialsRequired = requiredAuthMode == AuthenticationModeEnum.ALL || (requiredAuthMode == AuthenticationModeEnum.BIND && Arrays.asList(authModeEnums).contains(AuthenticationModeEnum.BIND)); if (credentialsRequired && remoteCredentials.getUnused() != null) { // Credential required, but they are set as unused LOG.severe(() -> String.format("%s: Credential required but the received operation does not provide them", getServiceInstanceIdentifier())); return false; } else if (!credentialsRequired && remoteCredentials.getUsed() != null) { // No credentials required, but they are present LOG.severe(() -> String.format("%s: Credential not required but the received operation provides them", getServiceInstanceIdentifier())); return false; } else if (!credentialsRequired) { // No credentials required if(LOG.isLoggable(Level.FINEST)) { LOG.finest(() -> String.format("%s: Credential not required, operation credentials empty, ok", getServiceInstanceIdentifier())); } return true; } else { byte[] encodedCredentials = remoteCredentials.getUsed().value; boolean result = PduFactoryUtil.performAuthentication(remotePeer.get(), encodedCredentials, authDelay); if (!result) { LOG.severe(() -> String.format("%s: Credential check failed on provided credentials", getServiceInstanceIdentifier())); } return result; } } else { LOG.severe(() -> String.format("%s: Credentials for remote peer %s not present", getServiceInstanceIdentifier(), remoteId)); return false; } } private void handleSleBindInvocation(SleBindInvocation pdu) { clearError(); // Notify the reception on the PDU notifyPduReceived(pdu, BIND_NAME, getLastPduReceived()); // Validate state (this implies that the service instance is in PIB mode) if (this.currentState != ServiceInstanceBindingStateEnum.UNBOUND_WAIT) { // Quite odd, to be honest, disconnect disconnect("Bind invocation received, but service instance is in state " + this.currentState); notifyStateUpdate(); return; } // At this point, let's prepare the return operation SleBindReturn resp = new SleBindReturn(); resp.setResult(new SleBindReturn.Result()); resp.setResponderIdentifier( new AuthorityIdentifier(this.serviceInstanceConfiguration.getResponderIdentifier().getBytes())); // Add credentials // From the API configuration (remote peers) and SI configuration (responder // id), check remote peer and check if authentication must be used. Credentials creds = generateCredentials(getInitiatorIdentifier(), AuthenticationModeEnum.ALL, AuthenticationModeEnum.BIND); if (creds == null) { // Error while generating credentials, set by generateCredentials() pduTransmissionError(pdu, BIND_RETURN_NAME, null); return; } else { resp.setPerformerCredentials(creds); } // Validate operation attributes if (pdu.getInitiatorIdentifier() == null || pdu.getInitiatorIdentifier().value == null) { // Send response resp.getResult() .setNegative(new BindDiagnostic(BindDiagnosticsEnum.SI_NOT_ACCESSIBLE_TO_THIS_INITIATOR.getCode())); encodeAndSend(null, resp, BIND_RETURN_NAME); // Then disconnect disconnect("Bind invocation received, but no initiator identifier set"); notifyStateUpdate(); return; } String initiatorId = pdu.getInitiatorIdentifier().toString(); if (!initiatorId.equals(this.serviceInstanceConfiguration.getInitiatorIdentifier())) { // Send response resp.getResult() .setNegative(new BindDiagnostic(BindDiagnosticsEnum.SI_NOT_ACCESSIBLE_TO_THIS_INITIATOR.getCode())); encodeAndSend(null, resp, BIND_RETURN_NAME); // Then disconnect disconnect("Bind invocation received, but initiator identifier does not match: expected " + this.serviceInstanceConfiguration.getInitiatorIdentifier() + ", got " + initiatorId); notifyStateUpdate(); return; } // Validate credentials // From the API configuration (remote peers) and SI configuration (remote peer), // check remote peer and check if authentication must be used. // If so, verify credentials. if (!authenticate(pdu.getInvokerCredentials(), AuthenticationModeEnum.ALL, AuthenticationModeEnum.BIND)) { // Send response resp.getResult().setNegative(new BindDiagnostic(BindDiagnosticsEnum.ACCESS_DENIED.getCode())); encodeAndSend(null, resp, BIND_RETURN_NAME); // Then disconnect disconnect("Bind invocation received, but wrong credentials for initiator " + initiatorId); notifyStateUpdate(); return; } // All fine, check what to do with the bind if (this.sendPositiveBindReturn) { // Update version to use this.sleVersion = pdu.getVersionNumber().intValue(); updateHandlersForVersion(this.sleVersion); // Send response resp.getResult().setPositive(new VersionNumber(pdu.getVersionNumber().intValue())); encodeAndSend(null, resp, BIND_RETURN_NAME); // Update state setServiceInstanceState(ServiceInstanceBindingStateEnum.READY); notifyPduSent(resp, BIND_RETURN_NAME, getLastPduSent()); } else { // Send response resp.getResult().setNegative(new BindDiagnostic(this.negativeBindReturnDiagnostics.getCode())); encodeAndSend(null, resp, BIND_RETURN_NAME); // Then disconnect disconnect("Bind invocation received, but rejected by user configuration with diagnostics " + this.negativeBindReturnDiagnostics); } // Generate state and notify update notifyStateUpdate(); } private void handleSleUnbindInvocation(SleUnbindInvocation pdu) { clearError(); // Notify the reception on the PDU notifyPduReceived(pdu, UNBIND_NAME, getLastPduReceived()); // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.READY) { // Peer abort, according to specs doPeerAbort(PeerAbortReasonEnum.PROTOCOL_ERROR); return; } // At this point, let's prepare the return operation SleUnbindReturn resp = new SleUnbindReturn(); resp.setResult(new SleUnbindReturn.Result()); // Add credentials // From the API configuration (remote peers) and SI configuration (responder // id), check remote peer and check if authentication must be used. Credentials creds = generateCredentials(getInitiatorIdentifier(), AuthenticationModeEnum.ALL); if (creds == null) { // Error while generating credentials, set by generateCredentials() pduTransmissionError(pdu, UNBIND_RETURN_NAME, null); return; } else { resp.setResponderCredentials(creds); } // Validate credentials // From the API configuration (remote peers) and SI configuration (remote peer), // check remote peer and check if authentication must be used. // If so, verify credentials. if (!authenticate(pdu.getInvokerCredentials(), AuthenticationModeEnum.ALL)) { doPeerAbort(PeerAbortReasonEnum.ACCESS_DENIED); return; } // All fine, check what to do with the bind if (this.sendPositiveUnbindReturn) { // Inform TML that this will be the last one, disconnection will follow soon this.tmlChannel.aboutToDisconnect(); // Send response resp.getResult().setPositive(new BerNull()); encodeAndSend(null, resp, UNBIND_RETURN_NAME); // Update state setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBOUND); notifyPduSent(resp, UNBIND_RETURN_NAME, getLastPduSent()); // We disconnect and wait again for bind if the reason was SUSPEND disconnect(null); if (pdu.getUnbindReason().intValue() == UnbindReasonEnum.SUSPEND.getCode()) { waitForBind(this.sendPositiveBindReturn, this.negativeBindReturnDiagnostics); } } else { // Do nothing if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, String.format("%s: Received UNBIND but service instance not configured to respond", getServiceInstanceIdentifier())); } } // Generate state and notify update notifyStateUpdate(); } private void handleSleBindReturn(SleBindReturn pdu) { clearError(); // Validate state if (this.currentState != ServiceInstanceBindingStateEnum.BIND_PENDING) { pduReceptionProcessingError("Bind return received, but service instance is in state " + this.currentState, pdu, BIND_RETURN_NAME); return; } // Validate operation attributes if (pdu.getResponderIdentifier() == null || pdu.getResponderIdentifier().value == null) { pduReceptionProcessingError("Bind return received, but no responder identifier set", pdu, BIND_RETURN_NAME); return; } String responderId = pdu.getResponderIdentifier().toString(); if (!responderId.equals(this.serviceInstanceConfiguration.getResponderIdentifier())) { pduReceptionProcessingError("Bind return received, but responder identifier does not match: expected " + this.serviceInstanceConfiguration.getResponderIdentifier() + ", got " + responderId, pdu, BIND_RETURN_NAME); return; } // Validate credentials // From the API configuration (remote peers) and SI configuration (remote peer), // check remote peer and check if authentication must be used. // If so, verify credentials. if (!authenticate(pdu.getPerformerCredentials(), AuthenticationModeEnum.ALL, AuthenticationModeEnum.BIND)) { pduReceptionProcessingError("Bind return received, but wrong credentials", pdu, BIND_RETURN_NAME); return; } // Cancel timer task for bind operation cancelReturnTimeout(BIND_INTERNAL_INVOKE_ID); // If all fine (result positive), transition to new state: BOUND and notify PDU // received if (pdu.getResult().getPositive() != null) { setServiceInstanceState(ServiceInstanceBindingStateEnum.READY); } else { // If problems (result negative), UNBOUND, disconnect, cleanup disconnect("Bind return received, negative result: " + BindDiagnosticsEnum.getBindDiagnostics(pdu.getResult().getNegative().intValue())); } // Notify PDU notifyPduReceived(pdu, BIND_RETURN_NAME, getLastPduReceived()); // Generate state and notify update notifyStateUpdate(); } /** * This method disconnects or abort the TML channel, frees up the resources and shutdowns the internal dispatcher * thread, hence making this instance not usable anymore. */ public void dispose() { dispatchFromUser(this::doDispose); try { this.dispatcherService.awaitTermination(5000L, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // NOSONAR rule to be disabled Thread.interrupted(); LOG.log(Level.WARNING, String.format("%s: problem while waiting for full disposal", getServiceInstanceIdentifier()), e); } } private void doDispose() { this.disposing = true; if (LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, String.format("%s: Dispose requested", getServiceInstanceIdentifier())); } // Disconnect if UNBOUND/UNBOUND_WAIT, PEER-ABORT if !UNBOUND/UNBOUND_WAIT if (getCurrentBindingState() == ServiceInstanceBindingStateEnum.UNBOUND || getCurrentBindingState() == ServiceInstanceBindingStateEnum.UNBOUND_WAIT) { disconnect(null); } else { doPeerAbort(PeerAbortReasonEnum.OPERATIONAL_REQUIREMENTS); } this.dispatcherService.shutdownNow(); this.notifierService.shutdownNow(); if (LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, String.format("%s: Dispose completed", getServiceInstanceIdentifier())); } } protected void pduReceptionProcessingError(String reason, BerType pdu, String pduName) { disconnect(reason); notifyPduReceived(pdu, pduName, getLastPduReceived()); notifyStateUpdate(); } protected void pduReceptionOk(BerType pdu, String pduName) { notifyPduReceived(pdu, pduName, getLastPduReceived()); notifyStateUpdate(); } protected void pduTransmissionOk(BerType pdu, String pduName) { notifyPduSent(pdu, pduName, getLastPduSent()); notifyStateUpdate(); } protected void pduTransmissionError(BerType pdu, String pduName, byte[] encodedOperation) { notifyPduSentError(pdu, pduName, encodedOperation); notifyStateUpdate(); } protected void notifyInternalError(String reason) { setError(reason); notifyStateUpdate(); } protected void disconnect(String reason) { disconnect(reason, null, null); } private void disconnect(String reason, Exception e, PeerAbortReasonEnum peerAbortReason) { if (reason != null && LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, String.format("%s: Disconnection with reason detected: %s", getServiceInstanceIdentifier(), reason), e); } setServiceInstanceState(ServiceInstanceBindingStateEnum.UNBOUND); this.expectConnectionToBeClosed = true; if (this.tmlChannel != null) { if (reason != null) { setError(reason, e); this.tmlChannel.abort(Objects.requireNonNullElse(peerAbortReason, PeerAbortReasonEnum.OTHER_REASON).getCode()); } else { this.tmlChannel.aboutToDisconnect(); this.tmlChannel.disconnect(); } } this.sleVersion = null; this.tmlChannel = null; this.invokeId2timeout.values().forEach(TimerTask::cancel); this.invokeId2timeout.clear(); this.statusReportScheduled = false; // Init mode reset this.initMode = SI_INIT_MODE_NOT_SELECTED; // Allow child classes to clean up resetState(); } @Override public void onChannelConnected(TmlChannel channel) { if (LOG.isLoggable(Level.INFO)) { LOG.info(String.format("%s: TML channel %s connected", getServiceInstanceIdentifier(), channel)); } } @Override public void onChannelDisconnected(TmlChannel channel, TmlDisconnectionReasonEnum reason, PeerAbortReasonEnum peerAbortReason) { dispatchFromUser(() -> { if (channel != tmlChannel) { // Old event, to be ignored if (LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, String.format("%s: Ignoring disconnection of TML channel %s, not current channel", getServiceInstanceIdentifier(), channel)); } return; } clearError(); // If disconnection is expected, close the channel and cleanup. if (this.expectConnectionToBeClosed) { disconnect(null); } else { // Otherwise, set the error disconnect("Unexpected disconnection detected: " + reason.name() + (Objects.isNull(peerAbortReason) ? "" : ", reason " + peerAbortReason)); } // Generate state and notify update notifyStateUpdate(); }); } @Override public void onPduReceived(TmlChannel channel, final byte[] pdu) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(getServiceInstanceIdentifier() + ": PDU received: " + PduStringUtil.toHexDump(pdu)); } dispatchFromProvider(() -> { clearError(); // See package encdec Object op; try { op = decodePdu(pdu); } catch (IOException e) { disconnect("Exception while decoding PDU " + Arrays.toString(pdu) + " from channel " + channel, e, null); // notifyPduDecodingError(pdu); // Generate state and notify update notifyStateUpdate(); return; } // At this stage, op cannot be null if (LOG.isLoggable(Level.FINER)) { LOG.finer(getServiceInstanceIdentifier() + ": PDU decoded as " + op.getClass().getSimpleName()); } // Use a map to process the PDU accordingly to its type. This // must be in this class, and handlers must be registered by children classes in the setup. @SuppressWarnings("unchecked") Consumer c = (Consumer) getHandler(op.getClass()); if (c != null) { this.statsCounter.addIn(1); this.lastPduReceived = pdu; c.accept(op); } else { disconnect("No handler to handle received PDU " + op.getClass().getSimpleName() + " from channel " + channel); // notifyPduHandlingError(op, pdu); // Generate state and notify update notifyStateUpdate(); } }); } /** * This method returns the service instance identifier. * * @return the service instance identifier */ public final String getServiceInstanceIdentifier() { return this.serviceInstanceConfiguration.getServiceInstanceIdentifier(); } /** * This method returns the responder port identifier. * * @return the responder port identifier */ public final String getResponderPortIdentifier() { return this.serviceInstanceConfiguration.getResponderPortIdentifier(); } /** * This method returns the initiator identifier. * * @return the initiator identifier */ public final String getInitiatorIdentifier() { return this.serviceInstanceConfiguration.getInitiatorIdentifier(); } /** * This method returns the responder identifier. * * @return the responder identifier */ public final String getResponderIdentifier() { return this.serviceInstanceConfiguration.getResponderIdentifier(); } /** * This method returns the return timeout period. * * @return the return timeout period */ public final int getReturnTimeoutPeriod() { return this.serviceInstanceConfiguration.getReturnTimeoutPeriod(); } protected void copyCommonState(ServiceInstanceState state) { state.setInitiatorIdentifier(getInitiatorIdentifier()); state.setResponderIdentifier(getResponderIdentifier()); state.setResponderPortIdentifier(getResponderPortIdentifier()); state.setServiceInstanceIdentifier(getServiceInstanceIdentifier()); state.setSleVersion(getSleVersion()); state.setReturnTimeoutPeriod(getReturnTimeoutPeriod()); state.setState(currentState); state.setStatusReportScheduled(statusReportScheduled); state.setLastError(lastErrorMessage); state.setLastException(lastErrorException); } /** * This method is used to compute the current rate of the service instance. It is important to understand that * invocations of this method drive the sample rate. If this method is invoked very fast (i.e. two immediate sub * sequent calls), the second call might receive a misleading result (possibly 0 units/set). * * @return the rate sample (related to SLE PDUs and bytes) */ public RateSample getCurrentRate() { Instant instant = Instant.now(); DataRateSample pdus = this.statsCounter.sample(); DataRateSample datas; if (this.tmlChannel != null) { datas = this.tmlChannel.getDataRate(); } else { datas = new DataRateSample(new Date(), 0, 0, 0, 0); } return new RateSample(instant, pdus, datas); } /** * This method returns the current service instance state. * * @return the service instance state */ public ServiceInstanceBindingStateEnum getCurrentBindingState() { return this.currentState; } /** * Configure this service instance to accept operations. */ public void configure() { if (this.configured) { throw new IllegalStateException("Service instance already configured"); } this.configured = true; resetState(); } protected abstract void setup(); protected abstract ServiceInstanceState buildCurrentState(); protected abstract Object decodePdu(byte[] pdu) throws IOException; protected abstract byte[] encodePdu(BerType pdu) throws IOException; protected abstract void updateHandlersForVersion(int version); protected abstract void resetState(); /** * This method returns the service type. * * @return the service type */ public abstract ApplicationIdentifierEnum getApplicationIdentifier(); /** * This internal task (a {@link FutureTask} extension that returns Void) is used to schedule an operation to be * executed by the dispatcher thread. The dispatcher uses a priority blocking queue, and the priority is provided * by the following hierarchy: *
    *
  • the request type: a user request always has precedence compared to a remote peer request (PDU reception). * This design decision allows to remove backlog processing on the dispatcher queue (e.g. due to high data rate) * and to process immediately a user request (e.g. a STOP request)
  • *
  • the creation time: if two requests have the same type, then the one scheduled before will be executed * before
  • *
  • the sequencer value: to prevent that two requests with the same type and the same creation time can be * considered equal from the point of view of the priority blocking queue comparator, and to keep the order in case * of equal time (e.g. high rate TM)
  • *
* * Public class for testability reasons */ public class SleTask extends FutureTask implements Comparable { private static final byte FROM_USER_TYPE = 0x00; private static final byte FROM_PROVIDER_TYPE = 0x01; private final long creation; private final byte type; private final long sequencerValue; public SleTask(byte type, Runnable task) { super(task, null); this.type = type; this.creation = Instant.now().toEpochMilli(); this.sequencerValue = sleTaskSequencer.incrementAndGet(); } @Override protected void setException(Throwable t) { // Exception detected LOG.log(Level.SEVERE, String.format("Exception caught when running SleTask: %s", t.getMessage()), t); super.setException(t); } @Override public int compareTo(SleTask o) { if (this.type != o.type) { return this.type - o.type; } else if (this.creation != o.creation) { return (int) (this.creation - o.creation); } else { return (int) (this.sequencerValue - o.sequencerValue); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SleTask sleTask = (SleTask) o; return creation == sleTask.creation && type == sleTask.type && sequencerValue == sleTask.sequencerValue; } @Override public int hashCode() { return Objects.hash(creation, type, sequencerValue); } } }