org.opendaylight.sxp.core.SxpConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.sxp.core.SxpConnection.java

Source

/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.sxp.core;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableScheduledFuture;
import io.netty.channel.ChannelHandlerContext;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.opendaylight.sxp.core.behavior.Context;
import org.opendaylight.sxp.core.messaging.AttributeList;
import org.opendaylight.sxp.core.messaging.MessageFactory;
import org.opendaylight.sxp.core.service.BindingDispatcher;
import org.opendaylight.sxp.core.service.BindingHandler;
import org.opendaylight.sxp.core.threading.ThreadsWorker;
import org.opendaylight.sxp.util.database.SxpDatabase;
import org.opendaylight.sxp.util.exception.connection.ChannelHandlerContextDiscrepancyException;
import org.opendaylight.sxp.util.exception.connection.ChannelHandlerContextNotFoundException;
import org.opendaylight.sxp.util.exception.connection.IncompatiblePeerModeException;
import org.opendaylight.sxp.util.exception.connection.SocketAddressNotRecognizedException;
import org.opendaylight.sxp.util.exception.message.ErrorMessageException;
import org.opendaylight.sxp.util.exception.message.attribute.AttributeNotFoundException;
import org.opendaylight.sxp.util.exception.unknown.UnknownConnectionModeException;
import org.opendaylight.sxp.util.exception.unknown.UnknownTimerTypeException;
import org.opendaylight.sxp.util.exception.unknown.UnknownVersionException;
import org.opendaylight.sxp.util.filtering.SxpBindingFilter;
import org.opendaylight.sxp.util.inet.NodeIdConv;
import org.opendaylight.sxp.util.inet.Search;
import org.opendaylight.sxp.util.time.SxpTimerTask;
import org.opendaylight.sxp.util.time.TimeConv;
import org.opendaylight.sxp.util.time.connection.DeleteHoldDownTimerTask;
import org.opendaylight.sxp.util.time.connection.HoldTimerTask;
import org.opendaylight.sxp.util.time.connection.KeepAliveTimerTask;
import org.opendaylight.sxp.util.time.connection.ReconcilationTimerTask;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.database.rev160308.SxpBindingFields;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.database.rev160308.sxp.database.fields.binding.database.binding.sources.binding.source.sxp.database.bindings.SxpDatabaseBinding;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.filter.rev150911.FilterSpecific;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.filter.rev150911.FilterType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.filter.rev150911.FilterUpdatePolicy;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.filter.rev150911.SxpFilterFields;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.TimerType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.capabilities.fields.Capabilities;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.sxp.connection.fields.ConnectionTimers;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.sxp.connection.fields.ConnectionTimersBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.sxp.connections.fields.connections.Connection;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.node.rev160308.sxp.connections.fields.connections.ConnectionBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.AttributeType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.CapabilityType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.ConnectionMode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.ConnectionState;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.ErrorCode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.ErrorSubCode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.MessageType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.NodeId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.Version;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.attributes.fields.attribute.attribute.optional.fields.HoldTimeAttribute;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.attributes.fields.attribute.attribute.optional.fields.SxpNodeIdAttribute;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.sxp.messages.OpenMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.sxp.messages.UpdateMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.protocol.rev141002.sxp.messages.UpdateMessageLegacy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * SxpConnection class represent SxpPeer and contains logic for maintaining communication
 */
public class SxpConnection {

    /**
     * ChannelHandlerContextType enum specifies role of ChannelHandlerContext
     */
    public enum ChannelHandlerContextType {
        ListenerContext, None, SpeakerContext
    }

    protected static final Logger LOG = LoggerFactory.getLogger(SxpConnection.class.getName());

    /**
     * Creates SxpConnection using provided values
     *
     * @param owner      SxpNode to be set as owner
     * @param connection Connection that contains settings
     * @param domain     Sxp Domain where Connections contains
     * @return SxpConnection created by specified values
     * @throws UnknownVersionException If version in provided values isn't supported
     */
    public static SxpConnection create(SxpNode owner, Connection connection, String domain)
            throws UnknownVersionException {
        SxpConnection sxpConnection = new SxpConnection(owner, connection, domain);
        sxpConnection.setCapabilities(Configuration.getCapabilities(sxpConnection.getVersion()));
        return sxpConnection;
    }

    protected ConnectionBuilder connectionBuilder;
    private Context context;

    private final List<ChannelHandlerContext> initCtxs = new ArrayList<>(2);
    private final HashMap<ChannelHandlerContextType, ChannelHandlerContext> ctxs = new HashMap<>(2);
    private final List<CapabilityType> remoteCapabilityTypes = new ArrayList<>();

    protected InetSocketAddress localAddress, remoteAddress;

    private final SxpNode owner;
    protected final String domain;
    private final NodeId connectionId;

    protected HashMap<TimerType, ListenableScheduledFuture<?>> timers = new HashMap<>(5);
    private final Map<FilterType, Map<FilterSpecific, SxpBindingFilter<?, ? extends SxpFilterFields>>> bindingFilterMap = new HashMap<>(
            FilterType.values().length);

    /**
     * @param filterType Type of SxpBindingFilter to look for
     * @return Filter with specified type or null if connection doesnt have one
     */
    public SxpBindingFilter<?, ? extends SxpFilterFields> getFilter(FilterType filterType) {
        synchronized (bindingFilterMap) {
            return SxpBindingFilter.mergeFilters(bindingFilterMap.get(filterType).values());
        }
    }

    /**
     * Defines how to setup flags after filter of specific type is set in SxpConnection
     *
     * @param filterType Type of SxpBindingFilter that was set
     */
    private void updateFlagsForDatabase(final FilterType filterType, boolean filterRemoved) {
        if (!isStateOn(ChannelHandlerContextType.SpeakerContext)
                || filterType.equals(FilterType.Outbound) && !isModeSpeaker()
                || (filterType.equals(FilterType.Inbound) || filterType.equals(FilterType.InboundDiscarding))
                        && !isModeListener()) {
            return;
        }
        final SxpDomain sxpDomain = owner.getDomain(getDomainName());
        if (filterType.equals(FilterType.Outbound)) {
            //Sends PurgeAll, All bindings in this order
            owner.getWorker().addListener(BindingDispatcher.sendPurgeAllMessage(this),
                    () -> getOwner().getSvcBindingDispatcher().propagateUpdate(null,
                            sxpDomain.getMasterDatabase().getBindings(), Collections.singletonList(this)));
        } else if (filterRemoved && filterType.equals(FilterType.Inbound)) {
            //Adds all Bindings learned from peer to MasterDB and sends it to All Listeners
            owner.getWorker().executeTaskInSequence(() -> {
                synchronized (sxpDomain) {
                    List<SxpDatabaseBinding> bindingsAdd = sxpDomain.getSxpDatabase().getBindings(getId());
                    if (getFilter(filterType) != null) {
                        bindingsAdd.removeIf(b -> !getFilter(filterType).test(b));
                    }
                    getOwner().getSvcBindingDispatcher().propagateUpdate(null,
                            sxpDomain.getMasterDatabase().addBindings(bindingsAdd),
                            owner.getAllOnSpeakerConnections(getDomainName()));
                    getOwner().getDomain(getDomainName()).pushToSharedMasterDatabases(Collections.emptyList(),
                            bindingsAdd);
                }
                return null;
            }, ThreadsWorker.WorkerType.INBOUND, this);
        } else if (!filterRemoved) {
            //Filters out Bindings from SXP database, removes it from Master and send update to all Listeners
            owner.getWorker().executeTaskInSequence(() -> {
                Map<NodeId, SxpBindingFilter> filterMap = SxpDatabase.getInboundFilters(getOwner(),
                        getDomainName());
                synchronized (sxpDomain) {
                    List<SxpDatabaseBinding> bindingsDelete = SxpDatabase.filterDatabase(sxpDomain.getSxpDatabase(),
                            getId(), getFilter(filterType));

                    if (filterType.equals(FilterType.InboundDiscarding)) {
                        sxpDomain.getSxpDatabase().deleteBindings(getId(), bindingsDelete);
                    }
                    getOwner().getSvcBindingDispatcher().propagateUpdate(
                            sxpDomain.getMasterDatabase().deleteBindings(bindingsDelete),
                            sxpDomain.getMasterDatabase()
                                    .addBindings(SxpDatabase.getReplaceForBindings(bindingsDelete,
                                            sxpDomain.getSxpDatabase(), filterMap)),
                            owner.getAllOnSpeakerConnections(getDomainName()));
                    getOwner().getDomain(getDomainName()).pushToSharedSxpDatabases(getId(), getFilter(filterType),
                            bindingsDelete, Collections.emptyList());
                }
                return null;
            }, ThreadsWorker.WorkerType.INBOUND, this);
        }
    }

    /**
     * Puts SxpBindingFilter into SxpConnection and sets appropriate flags
     *
     * @param filter SxpBindingFilter to be set
     */
    public void putFilter(SxpBindingFilter<?, ? extends SxpFilterFields> filter) {
        if (filter != null) {
            synchronized (bindingFilterMap) {
                LOG.debug("{} put filter {}", this, filter.getSxpFilter());
                FilterType filterType = Preconditions.checkNotNull(filter.getSxpFilter()).getFilterType();
                bindingFilterMap.get(filterType)
                        .put(Preconditions.checkNotNull(filter.getSxpFilter().getFilterSpecific()), filter);
                if (FilterUpdatePolicy.AutoUpdate.equals(filter.getSxpFilter().getFilterPolicy())) {
                    updateFlagsForDatabase(filterType, false);
                }
            }
        }
    }

    /**
     * @param filterType Type of SxpBindingFilter for which looks for its PeerGroup
     * @return PeerGroup name associated with filter of specified type
     */
    public String getGroupName(FilterType filterType) {
        synchronized (bindingFilterMap) {
            SxpBindingFilter filter = getFilter(filterType);
            return filter != null ? filter.getIdentifier() : null;
        }
    }

    /**
     * Removed SxpBindingFilter from SxpConnection and reset appropriate flags
     * if no subtype is specified remove all of specified type
     *
     * @param filterType Type of SxpBindingFilter to be removed
     * @param specific   SubType of SxpBindingFilter to be removed
     * @return Removed SxpBindingFilters
     */
    public List<SxpBindingFilter<?, ? extends SxpFilterFields>> removeFilter(FilterType filterType,
            FilterSpecific specific) {
        List<SxpBindingFilter<?, ? extends SxpFilterFields>> filters = new ArrayList<>();
        synchronized (bindingFilterMap) {
            if (specific == null) {
                filters.addAll(bindingFilterMap.get(Preconditions.checkNotNull(filterType)).values());
                bindingFilterMap.get(Preconditions.checkNotNull(filterType)).clear();
            } else {
                filters.add(bindingFilterMap.get(Preconditions.checkNotNull(filterType)).remove(specific));
            }
            if (!filters.isEmpty() && filters.stream()
                    .allMatch(f -> FilterUpdatePolicy.AutoUpdate.equals(f.getSxpFilter().getFilterPolicy()))) {
                updateFlagsForDatabase(filterType, true);
            }
        }
        return filters;
    }

    /**
     * Default constructor that creates SxpConnection using provided values
     *
     * @param owner      SxpNode to be set as owner
     * @param connection Connection that contains settings
     * @throws UnknownVersionException If version in provided values isn't supported
     */
    protected SxpConnection(SxpNode owner, Connection connection, String domain) throws UnknownVersionException {
        this.owner = Preconditions.checkNotNull(owner);
        this.domain = Preconditions.checkNotNull(domain);
        this.connectionBuilder = new ConnectionBuilder(Preconditions.checkNotNull(connection));
        if (connectionBuilder.getState() == null) {
            this.connectionBuilder.setState(ConnectionState.Off);
        }
        this.remoteAddress = new InetSocketAddress(Search.getAddress(connectionBuilder.getPeerAddress()),
                connectionBuilder.getTcpPort() != null ? connectionBuilder.getTcpPort().getValue()
                        : Configuration.getConstants().getPort());
        this.connectionId = new NodeId(connectionBuilder.getPeerAddress().getIpv4Address());
        for (FilterType filterType : FilterType.values()) {
            bindingFilterMap.put(filterType, new HashMap<>());
        }
        if (!Objects.nonNull(connectionBuilder.getVersion())) {
            connectionBuilder.setVersion(owner.getVersion());
        }
        this.context = new Context(owner, connectionBuilder.getVersion());
    }

    /**
     * @param build Timers to set
     */
    protected void setTimers(ConnectionTimers build) {
        connectionBuilder.setConnectionTimers(Preconditions.checkNotNull(build));
    }

    /**
     * @param state State to be set
     */
    protected void setState(ConnectionState state) {
        connectionBuilder.setState(Preconditions.checkNotNull(state));
    }

    /**
     * @param capabilities Capabilities to be set
     */
    protected void setCapabilities(Capabilities capabilities) {
        connectionBuilder.setCapabilities(Preconditions.checkNotNull(capabilities));
    }

    /**
     * @param version Version to be set
     */
    protected void setVersion(Version version) {
        connectionBuilder.setVersion(Preconditions.checkNotNull(version));
    }

    /**
     * @param connection Connection to be set
     */
    protected void setConnection(Connection connection) {
        connectionBuilder = new ConnectionBuilder(Preconditions.checkNotNull(connection));
    }

    /**
     * Update KeepAlive TimeStamp
     */
    public void setUpdateOrKeepaliveMessageTimestamp() {
        connectionBuilder.setTimestampUpdateOrKeepAliveMessage(TimeConv.toDt(System.currentTimeMillis()));
    }

    /**
     * Sets Id of Peer
     *
     * @param nodeId NodeId to be set
     */
    public void setNodeIdRemote(NodeId nodeId) {
        connectionBuilder.setNodeId(nodeId);
    }

    /**
     * Propagate changes learned from network to SxpDatabase
     *
     * @param message UpdateMessage containing changes
     */
    public void processUpdateMessage(UpdateMessage message) {
        if (getNodeIdRemote() == null) {
            LOG.warn(getOwner() + " Unknown message relevant peer node ID");
            return;
        }
        owner.getWorker().executeTaskInSequence(() -> {
            owner.getSvcBindingHandler().processUpdate(BindingHandler.processMessageDeletion(message),
                    BindingHandler.processMessageAddition(message, getFilter(FilterType.InboundDiscarding)), this);
            return null;
        }, ThreadsWorker.WorkerType.INBOUND, this);
    }

    /**
     * Propagate changes learned from network to SxpDatabase
     *
     * @param message UpdateMessage containing changes
     */
    public void processUpdateMessage(UpdateMessageLegacy message) {
        if (getNodeIdRemote() == null) {
            LOG.warn(getOwner() + " Unknown message relevant peer node ID");
            return;
        }
        owner.getWorker().executeTaskInSequence(() -> {
            owner.getSvcBindingHandler().processUpdate(BindingHandler.processMessageDeletion(message),
                    BindingHandler.processMessageAddition(message, getFilter(FilterType.InboundDiscarding),
                            getNodeIdRemote()),
                    this);
            return null;
        }, ThreadsWorker.WorkerType.INBOUND, this);
    }

    /**
     * @param deleteBindings Bindings to be deleted
     * @param addBindings    Bindings that will be added
     * @param connections    SxpConnections to which change will be propagated
     * @param <T>            Any type extending SxpBindingFields
     */
    public <T extends SxpBindingFields> void propagateUpdate(List<T> deleteBindings, List<T> addBindings,
            List<SxpConnection> connections) {
        owner.getSvcBindingDispatcher().propagateUpdate(deleteBindings, addBindings, connections);
    }

    /**
     * Adds ChannelHandlerContext into init queue
     *
     * @param ctx ChannelHandlerContext to be added
     */
    public void addChannelHandlerContext(ChannelHandlerContext ctx) {
        synchronized (initCtxs) {
            initCtxs.add(ctx);
            LOG.debug(this + " Add init channel context {}/{}", ctx, initCtxs);
        }
    }

    /**
     * Notifies to Delete Bindings with Flag CleanUp learned from this connection
     * and propagate this change to other connections
     */
    public void cleanUpBindings() {
        // Clean-up bindings within reconciliation: If the connection recovers
        // before the delete hold down timer expiration, a reconcile timer is
        // started to clean up old mappings that didnt get informed to be
        // removed because of the loss of connectivity.
        context.getOwner().getBindingSxpDatabase(getDomainName()).reconcileBindings(getId());
    }

    /**
     * Close specified ChannelHandlerContext and remove it from connection
     *
     * @param ctx ChannelHandlerContext to be closed
     */
    public ChannelHandlerContextType closeChannelHandlerContext(ChannelHandlerContext ctx) {
        ChannelHandlerContextType type = ChannelHandlerContextType.None;
        synchronized (initCtxs) {
            initCtxs.remove(ctx);
        }
        synchronized (ctxs) {
            for (Map.Entry<ChannelHandlerContextType, ChannelHandlerContext> e : ctxs.entrySet()) {
                if (e.getValue().equals(ctx)) {
                    type = e.getKey();
                }
            }
            if (type != ChannelHandlerContextType.None) {
                ctxs.remove(type);
            }
        }
        ctx.close();
        return type;
    }

    /**
     * Close all init ChannelHandlerContext and mark specified ChannelHandlerContext
     * according to connection mode
     *
     * @param ctx ChannelHandlerContext ChannelHandlerContext to be marked
     */
    public void closeChannelHandlerContextComplements(ChannelHandlerContext ctx) {
        synchronized (initCtxs) {
            initCtxs.remove(ctx);
            for (ChannelHandlerContext _ctx : initCtxs) {
                _ctx.close();
            }
            initCtxs.clear();
        }
    }

    /**
     * Close and remove all ChannelHandlerContext associated with this connection
     */
    public void closeChannelHandlerContexts() {
        synchronized (initCtxs) {
            for (ChannelHandlerContext _ctx : initCtxs) {
                _ctx.close();
            }
            initCtxs.clear();
        }
        synchronized (ctxs) {
            for (ChannelHandlerContext _ctx : ctxs.values()) {
                _ctx.close();
            }
            ctxs.clear();
        }
    }

    /**
     * Disable KeepAlive mechanism
     *
     * @param log Log message
     */
    private void disableKeepAliveMechanism(String log) {
        if (isModeListener()) {
            setHoldTime(0);
        } else if (isModeSpeaker()) {
            setKeepaliveTime(0);
        }
        LOG.info("{} Connection keep-alive mechanism is disabled | {}", toString(), log);
    }

    /**
     * Disable KeepAlive mechanism and send Peer error message
     *
     * @param holdTimeMin    Negotiated value
     * @param holdTimeMinAcc Negotiated value
     * @param holdTimeMax    Negotiated value
     */
    private void disableKeepAliveMechanismConnectionTermination(int holdTimeMin, int holdTimeMinAcc,
            int holdTimeMax) throws ErrorMessageException {
        disableKeepAliveMechanism("Unacceptable hold time [min=" + holdTimeMin + " acc=" + holdTimeMinAcc + " max="
                + holdTimeMax + "] | Connection termination");
        throw new ErrorMessageException(ErrorCode.OpenMessageError, ErrorSubCode.UnacceptableHoldTime, null);
    }

    /**
     * @return Gets all supported getCapabilities
     */
    public List<CapabilityType> getCapabilities() {
        if (connectionBuilder.getCapabilities() == null
                || connectionBuilder.getCapabilities().getCapability() == null) {
            return new ArrayList<>();
        }
        return connectionBuilder.getCapabilities().getCapability();
    }

    /**
     * @return Gets all supported getCapabilities of Remote Peer
     */
    public List<CapabilityType> getCapabilitiesRemote() {
        synchronized (remoteCapabilityTypes) {
            return Collections.unmodifiableList(remoteCapabilityTypes);
        }
    }

    /**
     * Clears and afterwards sets Capabilities of Remote Peer
     *
     * @param capabilityTypeList List of Capabilities that Remote Peer supports
     */
    public void setCapabilitiesRemote(List<CapabilityType> capabilityTypeList) {
        synchronized (remoteCapabilityTypes) {
            remoteCapabilityTypes.clear();
            remoteCapabilityTypes.addAll(capabilityTypeList);
        }
    }

    /**
     * Gets ChannelHandlerContext according to specified type
     *
     * @param channelHandlerContextType Type of ChannelHandlerContext
     * @return ChannelHandlerContext marked with specified type
     * @throws ChannelHandlerContextNotFoundException    If ChannelHandlerContext isn't present
     * @throws ChannelHandlerContextDiscrepancyException If there are more ChannelHandlerContext,
     *                                                   that it used to be
     */
    public ChannelHandlerContext getChannelHandlerContext(ChannelHandlerContextType channelHandlerContextType)
            throws ChannelHandlerContextNotFoundException, ChannelHandlerContextDiscrepancyException {
        synchronized (ctxs) {
            if ((isModeBoth() && ctxs.size() > 2) || (!isModeBoth() && ctxs.size() > 1)) {
                LOG.warn(this + " Registered contexts: " + ctxs);
                throw new ChannelHandlerContextDiscrepancyException();
            }
            ChannelHandlerContext ctx = ctxs.get(channelHandlerContextType);
            if (ctx == null || ctx.isRemoved()) {
                throw new ChannelHandlerContextNotFoundException();
            }
            return ctx;
        }
    }

    /**
     * @param channelHandlerContextType Type of ChannelHandlerContext
     * @return If ChannelHandlerContext is actively used
     */
    public boolean hasChannelHandlerContext(ChannelHandlerContextType channelHandlerContextType) {
        synchronized (ctxs) {
            ChannelHandlerContext ctx = ctxs.get(channelHandlerContextType);
            if (ctx == null || ctx.isRemoved()) {
                return false;
            }
            return true;
        }
    }

    /**
     * @return Gets Context selecting logic
     */
    public Context getContext() {
        return context;
    }

    /**
     * @return Gets destination address
     */
    public InetSocketAddress getDestination() {
        return remoteAddress;
    }

    /**
     * @return Gets local address
     */
    public InetSocketAddress getLocalAddress() {
        return localAddress;
    }

    /**
     * @return Gets HoldTime value or zero if disabled
     */
    public int getHoldTime() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getHoldTime() == null) {
            return getOwner().getHoldTime();
        }
        return connectionBuilder.getConnectionTimers().getHoldTime();
    }

    /**
     * @return Gets HoldTimeMax value or zero if disabled
     */
    public int getHoldTimeMax() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getHoldTimeMax() == null) {
            return getOwner().getHoldTimeMax();
        }
        return connectionBuilder.getConnectionTimers().getHoldTimeMax();
    }

    /**
     * @return Gets HoldTimeMin value or zero if disabled
     */
    public int getHoldTimeMin() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getHoldTimeMin() == null) {
            return getOwner().getHoldTimeMin();
        }
        return connectionBuilder.getConnectionTimers().getHoldTimeMin();
    }

    /**
     * @return Gets HoldTimeMinAcceptable value or zero if disabled
     */
    public int getHoldTimeMinAcceptable() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getHoldTimeMinAcceptable() == null) {
            return getOwner().getHoldTimeMinAcceptable();
        }
        return connectionBuilder.getConnectionTimers().getHoldTimeMinAcceptable();
    }

    /**
     * @return Gets KeepAlive value or zero if disabled
     */
    public int getKeepaliveTime() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getKeepAliveTime() == null) {
            return getOwner().getKeepAliveTime();
        }
        return connectionBuilder.getConnectionTimers().getKeepAliveTime();
    }

    /**
     * @return Gets Mode of connection
     */
    public ConnectionMode getMode() {
        return connectionBuilder.getMode() != null ? connectionBuilder.getMode() : ConnectionMode.None;
    }

    /**
     * @return Gets Mode of Peer
     */
    public ConnectionMode getModeRemote() {
        return invertMode(getMode());
    }

    /**
     * @param mode Mode that will be logically inverted
     * @return Inverted Mode
     */
    public static ConnectionMode invertMode(ConnectionMode mode) {
        if (ConnectionMode.Listener.equals(mode))
            return ConnectionMode.Speaker;
        else if (ConnectionMode.Speaker.equals(mode))
            return ConnectionMode.Listener;
        return ConnectionMode.Both;
    }

    /**
     * @return Gets NodeId of Peer Node
     */
    public NodeId getNodeIdRemote() {
        return connectionBuilder.getNodeId();
    }

    /**
     * @return Gets Id of assigned for this connection
     */
    public NodeId getId() {
        return connectionId;
    }

    /**
     * @return Gets Node that connections belongs to
     */
    public SxpNode getOwner() {
        return owner;
    }

    /**
     * @return Gets NodeId of Node that connection belongs to
     */
    public NodeId getOwnerId() {
        return owner.getNodeId();
    }

    /**
     * @return Gets password used by connection
     */
    public String getPassword() {
        return connectionBuilder.getPassword() != null && !connectionBuilder.getPassword().isEmpty()
                ? connectionBuilder.getPassword()
                : getOwner().getPassword();
    }

    /**
     * @return Gets Reconciliation timer period or zero if disabled
     */
    public int getReconciliationTime() {
        if (connectionBuilder.getConnectionTimers() == null
                || connectionBuilder.getConnectionTimers().getReconciliationTime() == null) {
            return 0;
        }
        return connectionBuilder.getConnectionTimers().getReconciliationTime();
    }

    /**
     * @return Gets current state of connection
     */
    public ConnectionState getState() {
        return connectionBuilder.getState();
    }

    /**
     * @return Domain to which Connection belongs
     */
    public String getDomainName() {
        return domain;
    }

    /**
     * Gets SxpConnection specific Timer
     *
     * @param timerType Type of Timer
     * @return TimerType or null if not present
     */
    public ListenableScheduledFuture<?> getTimer(TimerType timerType) {
        return timers.get(timerType);
    }

    /**
     * @return Gets KeepAlive timestamp
     */
    public long getTimestampUpdateOrKeepAliveMessage() {
        return TimeConv.toLong(connectionBuilder.getTimestampUpdateOrKeepAliveMessage());
    }

    /**
     * @return Gets connection version
     */
    public Version getVersion() {
        return connectionBuilder.getVersion();
    }

    /**
     * Initialize timers according to specified mode and timer values
     *
     * @param connectionMode ConnectionMode used for setup
     */
    private void initializeTimers(ConnectionMode connectionMode) {
        if (connectionMode.equals(ConnectionMode.Listener)) {
            if (getHoldTime() > 0) {
                setTimer(TimerType.HoldTimer, getHoldTime());
            }
        }
        if (connectionMode.equals(ConnectionMode.Speaker)) {
            if (getKeepaliveTime() > 0) {
                setTimer(TimerType.KeepAliveTimer, getKeepaliveTime());
            }
        }
    }

    /**
     * @return If both ChannelHandlerContext are properly on
     */
    public boolean isBidirectionalBoth() {
        synchronized (ctxs) {
            return ctxs.containsKey(ChannelHandlerContextType.ListenerContext)
                    && !ctxs.get(ChannelHandlerContextType.ListenerContext).isRemoved()
                    && ctxs.containsKey(ChannelHandlerContextType.SpeakerContext)
                    && !ctxs.get(ChannelHandlerContextType.SpeakerContext).isRemoved();
        }
    }

    /**
     * @return If connection is in mode Both
     */
    public boolean isModeBoth() {
        return getMode().equals(ConnectionMode.Both);
    }

    /**
     * @return If connection is in mode Listener
     */
    public boolean isModeListener() {
        return getMode().equals(ConnectionMode.Listener) || isModeBoth();
    }

    /**
     * @return If connection is in mode Speaker
     */
    public boolean isModeSpeaker() {
        return getMode().equals(ConnectionMode.Speaker) || isModeBoth();
    }

    /**
     * @return If State is DeleteHoldDown
     */
    public boolean isStateDeleteHoldDown() {
        return getState().equals(ConnectionState.DeleteHoldDown);
    }

    /**
     * @return If State is Off or in Both mode connections isn't in bidirectional mode
     */
    public boolean isStateOff() {
        return getState().equals(ConnectionState.Off) || (isModeBoth() && !isBidirectionalBoth());
    }

    /**
     * @return If State is On
     */
    public boolean isStateOn() {
        return isModeBoth() ? ConnectionState.On.equals(getState()) && isBidirectionalBoth()
                : ConnectionState.On.equals(getState());
    }

    /**
     * Test if specified function of Connection is On
     *
     * @param type Specifies function (Speaker/Listener)
     * @return if functionality is active on Connection
     */
    public boolean isStateOn(ChannelHandlerContextType type) {
        if (isModeBoth() && !type.equals(ChannelHandlerContextType.None)) {
            synchronized (ctxs) {
                return ctxs.containsKey(type) && !ctxs.get(type).isRemoved();
            }
        }
        return isStateOn();
    }

    /**
     * @return is State is PendingOn
     */
    public boolean isStatePendingOn() {
        return getState().equals(ConnectionState.PendingOn);
    }

    /**
     * @return If connection is version 4
     */
    public boolean isVersion4() {
        return getVersion().equals(Version.Version4);
    }

    /**
     * Mark ChannelHandlerContext with specified Role Speaker or Listener,
     * removes ChannelHandlerContext from init queue
     *
     * @param ctx                       ChannelHandlerContext to be marked
     * @param channelHandlerContextType Type of mark
     */
    public synchronized void markChannelHandlerContext(ChannelHandlerContext ctx,
            ChannelHandlerContextType channelHandlerContextType) {
        synchronized (initCtxs) {
            initCtxs.remove(ctx);
        }
        synchronized (ctxs) {
            ChannelHandlerContext context = ctxs.put(channelHandlerContextType, ctx);
            if (context != null) {
                context.close();
            }
        }
    }

    /**
     * Mark ChannelHandlerContext with Role Speaker or Listener,
     * removes ChannelHandlerContext from init queue
     * Aware that this method is only used for Non Both mode
     *
     * @param ctx ChannelHandlerContext to be marked
     */
    public void markChannelHandlerContext(ChannelHandlerContext ctx) {
        if (isModeBoth()) {
            LOG.warn("{} Cannot automatically mark ChannelHandlerContext {}", this, ctx);
        } else if (isModeListener()) {
            markChannelHandlerContext(ctx, ChannelHandlerContextType.ListenerContext);
        } else if (isModeSpeaker()) {
            markChannelHandlerContext(ctx, ChannelHandlerContextType.SpeakerContext);
        }
    }

    /**
     * Removes all learned Bindings on current connection and shutdown connection
     */
    public void purgeBindings() {
        // Get message relevant peer node ID.
        getOwner().getWorker().addListener(getOwner().getSvcBindingHandler().processPurgeAllMessage(this), () -> {
            try {
                setStateOff(getChannelHandlerContext(ChannelHandlerContextType.ListenerContext));
            } catch (ChannelHandlerContextNotFoundException | ChannelHandlerContextDiscrepancyException e) {
                setStateOff();
            }
        });
    }

    /**
     * Set behaviour context according to specified version
     *
     * @param version Version to be set
     * @throws UnknownVersionException If Version isn't supported
     */
    public void setBehaviorContexts(Version version) throws UnknownVersionException {
        setCapabilities(Configuration.getCapabilities(version));
        setVersion(version);
        context = new Context(owner, version);
    }

    /**
     * Setup Connection by parsing received OpenMessage
     *
     * @param message OpenMessage containing necessary data
     * @throws UnknownConnectionModeException If connection mode isn't compatible
     * @throws ErrorMessageException          If da in OpenMessage are incorrect
     */
    public void setConnection(OpenMessage message) throws ErrorMessageException, UnknownConnectionModeException {
        if (isModeListener() && message.getSxpMode().equals(ConnectionMode.Speaker)) {
            setConnectionListenerPart(message);
        } else if (isModeSpeaker() && message.getSxpMode().equals(ConnectionMode.Listener)) {
            setConnectionSpeakerPart(message);
            try {
                setCapabilitiesRemote(MessageFactory.decodeCapabilities(message));
            } catch (AttributeNotFoundException e) {
                LOG.warn("{} No Capabilities received by remote peer.", this);
            }
        } else {
            throw new UnknownConnectionModeException();
        }
        // Set connection state.
        setStateOn();
    }

    /**
     * Setup Listener mode Connection by parsing received OpenMessage
     *
     * @param message OpenMessage containing necessary data
     * @throws ErrorMessageException If data in OpenMessage are incorrect
     */
    public void setConnectionListenerPart(OpenMessage message) throws ErrorMessageException {
        // Node modes compatibility.
        if (getMode().equals(ConnectionMode.Listener) && !message.getSxpMode().equals(ConnectionMode.Speaker)
                || getMode().equals(ConnectionMode.Speaker)
                        && !message.getSxpMode().equals(ConnectionMode.Listener)) {
            throw new ErrorMessageException(ErrorCode.OpenMessageError,
                    new IncompatiblePeerModeException(getMode(), message.getSxpMode()));
        }
        // Set counter-part NodeID [Purge-All purpose: remove bindings learned
        // from a specific node].
        try {
            SxpNodeIdAttribute attNodeId = (SxpNodeIdAttribute) AttributeList.get(message.getAttribute(),
                    AttributeType.SxpNodeId);
            if (attNodeId != null) {
                setNodeIdRemote(attNodeId.getSxpNodeIdAttributes().getNodeId());
            }
        } catch (AttributeNotFoundException e) {
            if (isVersion4()) {
                throw new ErrorMessageException(ErrorCode.OpenMessageError, ErrorSubCode.OptionalAttributeError, e);
            }
        }

        // Keep-alive and hold-time negotiation.
        HoldTimeAttribute attHoldTime;
        try {
            attHoldTime = (HoldTimeAttribute) AttributeList.get(message.getAttribute(), AttributeType.HoldTime);
        } catch (AttributeNotFoundException e) {
            disableKeepAliveMechanism("Hold time attribute not present in received message");
            return;
        }

        // Peer parameters.
        int holdTimeMinAcc = attHoldTime.getHoldTimeAttributes().getHoldTimeMinValue();
        // Keep-alive mechanism is not used.
        if (holdTimeMinAcc == 0) {
            disableKeepAliveMechanism("Minimum acceptable hold time value is not used in peer");
            return;
        }

        // Local parameters.
        // Per-connection time settings: User (not)defined.
        int holdTimeMin = getHoldTimeMin();
        int holdTimeMax = getHoldTimeMax();
        // Global time settings: Default values have been pulled during node
        // creation TimeSettings.pullDefaults().
        if (holdTimeMin == 0 || holdTimeMax == 0) {
            disableKeepAliveMechanism("Minimum and maximum hold time values are globally disabled");
            return;
        }
        // The negotiation succeeds?
        if (message.getType().equals(MessageType.Open)) {
            if (holdTimeMinAcc < holdTimeMin) {
                setHoldTimeMin(holdTimeMinAcc);
            }
            if (holdTimeMinAcc > holdTimeMax) {
                disableKeepAliveMechanismConnectionTermination(holdTimeMin, holdTimeMinAcc, holdTimeMax);
            } else if (holdTimeMinAcc >= holdTimeMin && holdTimeMinAcc <= holdTimeMax) {
                setHoldTime(holdTimeMinAcc);
                setHoldTimeMin(holdTimeMinAcc);
            }
        } else if (message.getType().equals(MessageType.OpenResp)) {
            if (holdTimeMinAcc <= holdTimeMax) {
                setHoldTime(holdTimeMin);
            } else {
                disableKeepAliveMechanismConnectionTermination(holdTimeMin, holdTimeMinAcc, holdTimeMax);
            }
        }

        initializeTimers(ConnectionMode.Listener);
    }

    /**
     * Setup Speaker mode Connection by parsing received OpenMessage
     *
     * @param message OpenMessage containing necessary data
     * @throws ErrorMessageException If data in OpenMessage are incorrect
     */
    public void setConnectionSpeakerPart(OpenMessage message) throws ErrorMessageException {
        // Node modes compatibility.
        if (getMode().equals(ConnectionMode.Listener) && !message.getSxpMode().equals(ConnectionMode.Speaker)
                || getMode().equals(ConnectionMode.Speaker)
                        && !message.getSxpMode().equals(ConnectionMode.Listener)) {
            throw new ErrorMessageException(ErrorCode.OpenMessageError,
                    new IncompatiblePeerModeException(getMode(), message.getSxpMode()));
        }
        // Set counter-part NodeID [Purge-All purpose: remove bindings learned
        // from a specific node].
        try {
            SxpNodeIdAttribute attNodeId = (SxpNodeIdAttribute) AttributeList.get(message.getAttribute(),
                    AttributeType.SxpNodeId);
            if (attNodeId != null && isVersion4()) {
                throw new ErrorMessageException(ErrorCode.OpenMessageError, ErrorSubCode.OptionalAttributeError,
                        null);
            }
        } catch (AttributeNotFoundException e) {
            //NOP
        }

        // Keep-alive and hold-time negotiation.
        HoldTimeAttribute attHoldTime;
        try {
            attHoldTime = (HoldTimeAttribute) AttributeList.get(message.getAttribute(), AttributeType.HoldTime);
        } catch (AttributeNotFoundException e) {
            disableKeepAliveMechanism("Hold time attribute not present in received message");
            return;
        }

        // Peer parameters.
        int holdTimeMin = attHoldTime.getHoldTimeAttributes().getHoldTimeMinValue();
        int holdTimeMax = attHoldTime.getHoldTimeAttributes().getHoldTimeMaxValue();
        // Keep-alive mechanism is not used.
        if (holdTimeMin == 0) {
            disableKeepAliveMechanism("Minimum hold time value set to ZERO ");
            return;
        }

        // Local parameters.
        // Per-connection time settings: User (not)defined.
        int holdTimeMinAcc = getHoldTimeMinAcceptable();
        // Globally disabled
        if (holdTimeMinAcc == 0) {
            disableKeepAliveMechanism("Minimum acceptable hold time value is globally disabled");
            return;
        }
        // The negotiation succeeds?
        if (message.getType().equals(MessageType.Open)) {
            if (holdTimeMinAcc > holdTimeMax) {
                disableKeepAliveMechanismConnectionTermination(holdTimeMin, holdTimeMinAcc, holdTimeMax);
            } else {
                int holdTimeSelected = Math.max(holdTimeMin, holdTimeMinAcc);
                setKeepaliveTime((int) (1.0 / 3.0 * holdTimeSelected));
                setHoldTimeMinAcceptable(holdTimeSelected);
            }
        } else if (message.getType().equals(MessageType.OpenResp)) {
            setKeepaliveTime((int) (1.0 / 3.0 * holdTimeMin));
        }

        initializeTimers(ConnectionMode.Speaker);
    }

    /**
     * Start DeleteHoldDown timer and if Reconciliation timer is started stop it
     */
    public void setDeleteHoldDownTimer() {
        if (connectionBuilder.getConnectionTimers().getDeleteHoldDownTime() == 0)
            return;
        LOG.info("{} onChannelInactivation/setDeleteHoldDownTimer", this);
        setTimer(TimerType.DeleteHoldDownTimer, connectionBuilder.getConnectionTimers().getDeleteHoldDownTime());
        ListenableScheduledFuture<?> ctReconciliation = getTimer(TimerType.ReconciliationTimer);
        if (ctReconciliation != null && !ctReconciliation.isDone()
                && connectionBuilder.getConnectionTimers().getReconciliationTime() != 0) {
            LOG.info("{} Stopping Reconciliation timer cause | Connection DOWN.", this);
            setTimer(TimerType.ReconciliationTimer, null);
        }
        setStateDeleteHoldDown();
    }

    /**
     * Sets HoldTimer timer period
     *
     * @param value Time to be set
     */
    public void setHoldTime(int value) {
        ConnectionTimersBuilder connectionTimersBuilder = new ConnectionTimersBuilder(
                connectionBuilder.getConnectionTimers());
        connectionTimersBuilder.setHoldTime(value);
        setTimers(connectionTimersBuilder.build());
    }

    /**
     * Sets HoldTimeMin used for HoldTime Negotiation
     *
     * @param value Time to be set
     */
    public void setHoldTimeMin(int value) {
        ConnectionTimersBuilder connectionTimersBuilder = new ConnectionTimersBuilder(
                connectionBuilder.getConnectionTimers());
        connectionTimersBuilder.setHoldTimeMin(value);
        setTimers(connectionTimersBuilder.build());
    }

    /**
     * Sets HoldTimeMinAcceptable used for HoldTime Negotiation
     *
     * @param value Time to be set
     */
    public void setHoldTimeMinAcceptable(int value) {
        ConnectionTimersBuilder connectionTimersBuilder = new ConnectionTimersBuilder(
                connectionBuilder.getConnectionTimers());
        connectionTimersBuilder.setHoldTimeMinAcceptable(value);
        setTimers(connectionTimersBuilder.build());
    }

    /**
     * Sets addresses used to communicate
     *
     * @param localAddress SocketAddress of local connection
     * @throws SocketAddressNotRecognizedException If parameters aren't instance of InetSocketAddress
     */
    public void setInetSocketAddresses(SocketAddress localAddress) throws SocketAddressNotRecognizedException {
        if (!(localAddress instanceof InetSocketAddress)) {
            throw new SocketAddressNotRecognizedException(localAddress);
        }
        this.localAddress = (InetSocketAddress) localAddress;
    }

    /**
     * Sets KeepAlive timer period
     *
     * @param value Time to be set
     */
    public void setKeepaliveTime(int value) {
        ConnectionTimersBuilder connectionTimersBuilder = new ConnectionTimersBuilder(
                connectionBuilder.getConnectionTimers());
        connectionTimersBuilder.setKeepAliveTime(value);
        setTimers(connectionTimersBuilder.build());
    }

    /**
     * Sets Reconciliation timer on current connection
     */
    public void setReconciliationTimer() {
        if (getReconciliationTime() > 0) {
            ListenableScheduledFuture<?> ctDeleteHoldDown = getTimer(TimerType.DeleteHoldDownTimer);
            if (ctDeleteHoldDown != null && !ctDeleteHoldDown.isDone()) {
                LOG.info("{} Stopping Delete Hold Down timer.", this);
                setTimer(TimerType.DeleteHoldDownTimer, null);
            }
            LOG.info("{} Starting Reconciliation timer.", this);
            setTimer(TimerType.ReconciliationTimer, getReconciliationTime());
        }
    }

    /**
     * Set State to DeleteHoldDown and triggers cleanUp of Database
     */
    public void setStateDeleteHoldDown() {
        setState(ConnectionState.DeleteHoldDown);
        owner.getBindingSxpDatabase(getDomainName()).setReconciliation(getId());
    }

    /**
     * Set State to Off resets all flags, stop timers,
     * clear Handling of messages and close ChannelHandlerContexts
     */
    public void setStateOff() {
        stopTimers();
        setState(ConnectionState.Off);
        getOwner().getWorker().cancelTasksInSequence(true, ThreadsWorker.WorkerType.INBOUND, this);
        getOwner().getWorker().cancelTasksInSequence(true, ThreadsWorker.WorkerType.OUTBOUND, this);
        closeChannelHandlerContexts();
    }

    /**
     * Gets type of ChannelHandlerContext in current connection,
     * if ChannelHandlerContext is not used in this connection return None
     *
     * @param ctx ChannelHandlerContext  to be tested
     * @return Type of ChannelHandlerContext in this connection
     */
    public ChannelHandlerContextType getContextType(ChannelHandlerContext ctx) {
        ChannelHandlerContextType type = ChannelHandlerContextType.None;
        synchronized (ctxs) {
            for (Map.Entry<ChannelHandlerContextType, ChannelHandlerContext> e : ctxs.entrySet()) {
                if (e.getValue().equals(ctx)) {
                    type = e.getKey();
                    break;
                }
            }
        }
        return type;
    }

    /**
     * Set State to Off and close ChannelHandlerContext,
     * if Connection is in Both mode disable appropriate
     * functionality based on ChannelHandlerContext type
     *
     * @param ctx ChannelHandlerContext to be closed
     */
    public void setStateOff(ChannelHandlerContext ctx) {
        ChannelHandlerContextType type = closeChannelHandlerContext(ctx);
        if (ctxs.isEmpty()) {
            setStateOff();
        } else {
            switch (type) {
            case ListenerContext:
                setTimer(TimerType.DeleteHoldDownTimer, 0);
                setTimer(TimerType.ReconciliationTimer, 0);
                setTimer(TimerType.HoldTimer, 0);
                getOwner().getWorker().cancelTasksInSequence(true, ThreadsWorker.WorkerType.INBOUND, this);
                break;
            case SpeakerContext:
                setTimer(TimerType.KeepAliveTimer, 0);
                getOwner().getWorker().cancelTasksInSequence(true, ThreadsWorker.WorkerType.OUTBOUND, this);
                break;
            }
        }
    }

    /**
     * Stops all supported timers till version 4
     */
    private void stopTimers() {
        setTimer(TimerType.DeleteHoldDownTimer, 0);
        setTimer(TimerType.ReconciliationTimer, 0);
        setTimer(TimerType.HoldTimer, 0);
        setTimer(TimerType.KeepAliveTimer, 0);
    }

    /**
     * Set State to On and if Connection is mode speaker
     * notifies export of Bindings
     */
    public void setStateOn() {
        setState(ConnectionState.On);
        if (isModeSpeaker() || isModeBoth()) {
            getOwner().getWorker().executeTaskInSequence(() -> {
                synchronized (getOwner().getDomain(getDomainName())) {
                    owner.getSvcBindingDispatcher().propagateUpdate(null,
                            getOwner().getBindingMasterDatabase(getDomainName()).getBindings(),
                            Collections.singletonList(this));
                }
                return null;
            }, ThreadsWorker.WorkerType.OUTBOUND, this);
        }
    }

    /**
     * Set State to PendingOn
     */
    public void setStatePendingOn() {
        setState(ConnectionState.PendingOn);
    }

    /**
     * Sets SxpConnection specific Timer
     *
     * @param timerType Type of Timer that will be set
     * @param period    Time period to wait till execution in Seconds
     * @return ListenableScheduledFuture callback
     * @throws UnknownTimerTypeException If current TimerType isn't supported
     */
    public synchronized ListenableScheduledFuture<?> setTimer(TimerType timerType, int period)
            throws UnknownTimerTypeException {
        SxpTimerTask timer;
        switch (timerType) {
        case DeleteHoldDownTimer:
            timer = new DeleteHoldDownTimerTask(this, period);
            break;
        case HoldTimer:
            timer = new HoldTimerTask(this, period);
            break;
        case KeepAliveTimer:
            timer = new KeepAliveTimerTask(this, period);
            break;
        case ReconciliationTimer:
            timer = new ReconcilationTimerTask(this, period);
            break;
        default:
            throw new UnknownTimerTypeException(timerType);
        }
        ListenableScheduledFuture<?> timer_ = getTimer(timerType);
        if (period > 0 && (timer_ == null || !timer_.isCancelled())) {
            return this.setTimer(timerType, owner.getWorker().scheduleTask(timer, period, TimeUnit.SECONDS));
        } else {
            return this.setTimer(timerType, null);
        }
    }

    /**
     * Sets SxpConnection specific Timer
     *
     * @param timerType Type of Timer that will be set
     * @param timer     Timer logic
     * @return ListenableScheduledFuture callback
     */
    private ListenableScheduledFuture<?> setTimer(TimerType timerType, ListenableScheduledFuture<?> timer) {
        ListenableScheduledFuture<?> t = this.timers.put(timerType, timer);
        if (t != null && !t.isDone()) {
            t.cancel(false);
        }
        return timer;
    }

    /**
     * Shutdown Connection and send PurgeAll if Speaker mode,
     * or purge learned Bindings if Listener mode
     */
    public synchronized void shutdown() {
        if (isModeListener()) {
            try {
                getOwner().getSvcBindingHandler().processPurgeAllMessage(this).get();
                LOG.info("{} PURGE bindings ", this);
            } catch (InterruptedException | ExecutionException e) {
                LOG.warn("{} Error PURGE bindings ", this);
            }
        }
        if (isModeSpeaker() && isStateOn(ChannelHandlerContextType.SpeakerContext)) {
            BindingDispatcher.sendPurgeAllMessageSync(this);
        }
        setStateOff();
    }

    @Override
    public String toString() {
        String localAddress = this.localAddress != null ? this.localAddress.toString() : "";
        if (localAddress.startsWith("/")) {
            localAddress = localAddress.substring(1);
        }
        String remoteAddress = this.remoteAddress != null ? this.remoteAddress.toString() : "";
        if (remoteAddress.startsWith("/")) {
            remoteAddress = remoteAddress.substring(1);
        }
        String result = owner.toString() + "[" + localAddress + "/" + remoteAddress + "]";

        result += "[" + (getState().equals(ConnectionState.Off) ? "X" : getState().toString().charAt(0)) + "|"
                + getMode().toString().charAt(0) + "v" + getVersion().getIntValue();
        if (getModeRemote() != null) {
            result += "/" + getModeRemote().toString().charAt(0);
        }

        if (getNodeIdRemote() != null) {
            result += " " + NodeIdConv.toString(getNodeIdRemote());
        }
        result += "]";
        return result;
    }
}