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

org.bidib.jbidibc.netbidib.server.NetBidibServerHandler Maven / Gradle / Ivy

The newest version!
package org.bidib.jbidibc.netbidib.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.BidibMessagePublisher;
import org.bidib.jbidibc.messages.ConnectionListener;
import org.bidib.jbidibc.messages.HostAdapter;
import org.bidib.jbidibc.messages.Node;
import org.bidib.jbidibc.messages.StringData;
import org.bidib.jbidibc.messages.enums.PairingResult;
import org.bidib.jbidibc.messages.exception.InvalidConfigurationException;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.exception.ProtocolInvalidContentException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.message.BidibCommand;
import org.bidib.jbidibc.messages.message.BidibMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.BidibResponseFactory;
import org.bidib.jbidibc.messages.message.LocalBidibUpResponse;
import org.bidib.jbidibc.messages.message.LocalLogoffMessage;
import org.bidib.jbidibc.messages.message.LocalLogonAckMessage;
import org.bidib.jbidibc.messages.message.LocalLogonMessage;
import org.bidib.jbidibc.messages.message.StringResponse;
import org.bidib.jbidibc.messages.message.SysPVersionResponse;
import org.bidib.jbidibc.messages.message.SysUniqueIdResponse;
import org.bidib.jbidibc.messages.message.netbidib.LocalLinkMessage;
import org.bidib.jbidibc.messages.message.netbidib.LocalProtocolSignatureMessage;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibCommandMessage;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData.LogonStatus;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData.PairingStatus;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.MessageUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.jbidibc.netbidib.ConnectionUpdateEvent;
import org.bidib.jbidibc.netbidib.NetBidibContextKeys;
import org.bidib.jbidibc.netbidib.exception.AccessDeniedException;
import org.bidib.jbidibc.netbidib.exception.PairingDeniedException;
import org.bidib.jbidibc.netbidib.pairingstore.LocalPairingStore.PairingLookupResult;
import org.bidib.jbidibc.netbidib.pairingstore.PairingStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;

public abstract class NetBidibServerHandler extends SimpleChannelInboundHandler
    implements BidibMessagePublisher {

    private static final Logger LOGGER = LoggerFactory.getLogger(NetBidibServerHandler.class);

    private BidibRequestFactory bidibRequestFactory;

    protected BidibResponseFactory responseFactory;

    private final HostAdapter hostAdapter;

    private final String backendPortName;

    protected final Object pairedPartnerLock = new Object();

    /**
     * The link data of the paired remote partner.
     */
    protected NetBidibLinkData pairedPartner;

    /**
     * The link data of the server partner (ourself).
     */
    protected final NetBidibLinkData serverLinkData;

    private PairingStore pairingStore;

    /**
     * The channel group to pool all TCP connections. It is used to guaranty that no more than one connection is opened.
     */
    private ChannelGroup channelGroup;

    protected ChannelHandlerContext ctx;

    private Set remoteConnectionListeners = new HashSet<>();

    private final Consumer> lazyInitializationCallback;

    private final Function messageContentSupplier;

    private RoleTypeEnum roleType;

    private final org.bidib.jbidibc.messages.logger.Logger splitMessageLogger;

    private final AtomicBoolean isFirstPacket = new AtomicBoolean(true);

    protected final ScheduledExecutorService localNodeDataWorkers;

    /**
     * Constructor
     *
     * @param channelGroup
     *            the channel group to pool all TCP connections.
     * @param hostAdapter
     *            the host adapter
     * @param backendPortName
     *            the port identifier of the backend, e.g. COM8
     * @param serverLinkData
     *            the server link data instance to use
     */
    public NetBidibServerHandler(final ChannelGroup channelGroup, final HostAdapter hostAdapter,
        final String backendPortName, final NetBidibLinkData serverLinkData,
        final Consumer> lazyInitializationCallback,
        Function messageContentSupplier, final RoleTypeEnum roleType,
        final NetBidibLinkData pairedPartner) {

        LOGGER.info("Create new NetBidibServerHandler instance. Provided roleType: {}", roleType);

        this.channelGroup = channelGroup;
        this.hostAdapter = hostAdapter;
        this.backendPortName = backendPortName;

        this.serverLinkData = serverLinkData;
        this.lazyInitializationCallback = lazyInitializationCallback;
        this.messageContentSupplier = messageContentSupplier;

        this.roleType = roleType;
        this.pairedPartner = pairedPartner;

        splitMessageLogger = new org.bidib.jbidibc.messages.logger.Logger() {

            @Override
            public void debug(String format, Object... arguments) {
                LOGGER.debug(format, arguments);
            }

            @Override
            public void info(String format, Object... arguments) {
                LOGGER.info(format, arguments);
            }

            @Override
            public void warn(String format, Object... arguments) {
                LOGGER.warn(format, arguments);
            }

            @Override
            public void error(String format, Object... arguments) {
                LOGGER.error(format, arguments);
            }
        };

        this.localNodeDataWorkers =
            Executors
                .newScheduledThreadPool(1,
                    new ThreadFactoryBuilder().setNameFormat("localNodeDataWorkers-thread-%d").build());
    }

    protected boolean handleLocalBidibUpResponse() {
        return this.roleType == RoleTypeEnum.INTERFACE;
    }

    public void addRemoteConnectionListener(final ConnectionListener remoteConnectionListener) {
        synchronized (this.remoteConnectionListeners) {

            this.remoteConnectionListeners.add(remoteConnectionListener);
        }
    }

    public void removeRemoteConnectionListener(final ConnectionListener remoteConnectionListener) {
        synchronized (this.remoteConnectionListeners) {

            this.remoteConnectionListeners.remove(remoteConnectionListener);
        }
    }

    public void setPairingStore(final PairingStore pairingStore) {
        this.pairingStore = pairingStore;
    }

    protected void performLazyInitialization() {
        LOGGER.info("Perform the lazy initialization.");

        if (this.bidibRequestFactory == null) {
            this.bidibRequestFactory = new BidibRequestFactory();
            this.bidibRequestFactory.setEscapeMagic(false);
            this.bidibRequestFactory.initialize();
        }

        if (this.responseFactory == null) {
            this.responseFactory = new BidibResponseFactory();
        }

        if (this.lazyInitializationCallback != null) {
            LOGGER.info("Call the lazy initialization callback.");
            // this will set the toHostPublisher
            this.lazyInitializationCallback.accept(this);
        }
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {

        final SocketAddress remoteAddress = ctx.channel().remoteAddress();
        LOGGER.info("Session created. IP: {}, channel: {}", remoteAddress, ctx.channel());

        // prevent multiple simultaneous connections
        if (hasActiveConnection()) {
            LOGGER.warn("More than one session: reject!");

            // send 'SHUTDOWN' message, then close the Channel
            ctx.channel().writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            return;
        }

        LOGGER.info("Connected");

        performLazyInitialization();

        // add to channel group and set the state
        channelGroup.add(ctx.channel());
        ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
        if (remoteAddress != null) {
            ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).set(remoteAddress.toString());
        }
        else {
            final SocketAddress localAddress = ctx.channel().localAddress();
            LOGGER.info("No remoteAddress available. Use localAddress: {}", localAddress);
            // ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).set(localAddress.toString());
        }
        super.channelRegistered(ctx);

        LOGGER.info("Store the channelHandlerContext: {}", ctx);
        this.ctx = ctx;

        synchronized (this.remoteConnectionListeners) {
            for (ConnectionListener connectionListener : this.remoteConnectionListeners) {
                try {
                    connectionListener
                        .opened(ctx.channel().remoteAddress() != null ? ctx.channel().remoteAddress().toString()
                            : "unknown");
                }
                catch (Exception ex) {
                    LOGGER.warn("Notify that the client connection was closed failed.", ex);
                }
            }
        }

    }

    private void sendInitialLocalProtocolSignatureMessage() throws ProtocolException {

        String requestorName = this.serverLinkData.getRequestorName();

        LOGGER
            .info("Send the initial LocalProtocolSignatureMessage to the client. Current requestorName: {}",
                requestorName);

        if (StringUtils.isBlank(requestorName)
            || !requestorName.startsWith(LocalProtocolSignatureMessage.EMITTER_PREFIX_BIDIB)) {
            LOGGER.warn("Invalid requestor name provided: {}", requestorName);

            throw new IllegalArgumentException("Invalid requestor name provided.");
        }

        // use BiDiB emitter
        NetBidibCommandMessage message =
            bidibRequestFactory
                .createLocalProtocolSignature(requestorName /* LocalProtocolSignatureMessage.EMITTER_PREFIX_BIDIB */);
        publishMessage(ctx, message);

        final Context context = new DefaultContext();
        context.register(NetBidibContextKeys.KEY_PORT, backendPortName);

        final ConnectionListener connectionListener = new ConnectionListener() {

            @Override
            public void opened(String port) {
                LOGGER.info("The port was opened: {}", port);

            }

            @Override
            public void closed(String port) {

            }

            @Override
            public void status(String messageKey, final Context context) {
                LOGGER.info("Received status event, messageKey: {}, context: {}", messageKey, context);
            }

            @Override
            public void stall(boolean stall) {
                // TODO Auto-generated method stub
            }

            @Override
            public void pairingFinished(PairingResult pairingResult) {
                LOGGER.info("The pairing process has finished. Current pairingResult: {}", pairingResult);

                // TODO Auto-generated method stub

            }
        };
        context.register(NetBidibContextKeys.KEY_CONNECTION_LISTENER, connectionListener);

        try {
            LOGGER.info("Initialize the host adapter.");

            hostAdapter.initialize(context);

            LOGGER.info("Connect to the backend. Provided context: {}", context);
            // this call will connect to the backend
            hostAdapter.signalConnectionOpened(context);

            // // TODO wait until the root node is available
            //
            // // disable spontaneous events from backend
            // LOGGER.info("Disable all spontaneous events from backend.");
            // BidibCommand sysDisableMessage = bidibRequestFactory.createSysDisable();
            // sysDisableMessage.setAddr(Node.ROOTNODE_ADDR);
            // hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(sysDisableMessage));

            this.localNodeDataWorkers.schedule(() -> fetchLocalBidibNodeData(), 20, TimeUnit.MILLISECONDS);
            // fetchLocalBidibNodeData();
        }
        catch (Exception ex) {
            LOGGER.warn("Connect to backend failed.", ex);

            throw new InvalidConfigurationException("Connect to backend failed.");
        }
    }

    private void fetchLocalBidibNodeData() {

        // ask the backend for the names and uniqueId
        LOGGER.info("Get the productname, username, uniqueId and protocol version from the root node.");

        BidibCommand stringGetMessage =
            bidibRequestFactory.createStringGet(StringData.NAMESPACE_NODE, StringData.INDEX_PRODUCTNAME);
        stringGetMessage.setAddr(Node.ROOTNODE_ADDR);
        sendLocalBidibDownMessage(stringGetMessage);

        stringGetMessage = bidibRequestFactory.createStringGet(StringData.NAMESPACE_NODE, StringData.INDEX_USERNAME);
        stringGetMessage.setAddr(Node.ROOTNODE_ADDR);
        sendLocalBidibDownMessage(stringGetMessage);

        // get the unique id from the backend
        BidibCommand getUniqueIdMessage = bidibRequestFactory.createSysGetUniqueId();
        getUniqueIdMessage.setAddr(Node.ROOTNODE_ADDR);
        sendLocalBidibDownMessage(getUniqueIdMessage);

        // get the product version from the backend
        BidibCommand getPVersionMessage = bidibRequestFactory.createSysGetPVersion();
        getUniqueIdMessage.setAddr(Node.ROOTNODE_ADDR);
        sendLocalBidibDownMessage(getPVersionMessage);
    }

    private void sendLocalBidibDownMessage(final BidibCommand bidibCommand) {

        BidibCommand localBidibDownMessage = bidibRequestFactory.createLocalBidibDown(bidibCommand);
        localBidibDownMessage.setAddr(Node.ROOTNODE_ADDR);

        LOGGER.info("Prepared the localBidibDownMessage to forward to backend: {}", localBidibDownMessage);

        hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(localBidibDownMessage));
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        SocketAddress remoteAddress = ctx.channel().remoteAddress();
        LOGGER.info("Session closed. IP: {}", remoteAddress);

        if (remoteAddress == null) {
            final SocketAddress localAddress = ctx.channel().localAddress();
            LOGGER.info("No remoteAddress available. Use localAddress: {}", localAddress);

            remoteAddress = localAddress;
        }

        String connectedRemoteAddress = ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).get();
        LOGGER.info("The connected remote address: {}", connectedRemoteAddress);

        // only close for primary connection
        if (hasActiveConnection() && !Objects.equals(remoteAddress.toString(), connectedRemoteAddress)) {
            LOGGER.info("The primary connection is still established.");
            return;
        }

        // set the state and remove from channel group (by super call)
        ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
        super.channelUnregistered(ctx);

        final Context context = new DefaultContext();
        if (pairedPartner.getUniqueId() != null) {
            context.register(Context.UNIQUE_ID, pairedPartner.getUniqueId());
        }

        LOGGER.info("Signal that the connection to the guest was closed.");
        try {
            hostAdapter.signalConnectionClosed(context);
        }
        catch (Exception ex) {
            LOGGER.warn("Signal that the connection to the guest was closed failed.", ex);
        }

        try {
            LOGGER.info("Clear the saved serverLinkData.");
            serverLinkData.clear(false);
        }
        catch (Exception ex) {
            LOGGER.warn("Remove uniqueid from serverLinkData failed.", ex);
        }

        // release the pairedPartner
        synchronized (pairedPartnerLock) {
            LOGGER.info("Clear the paired partner link data: {}", pairedPartner);
            pairedPartner.clear(true);
        }

        LOGGER.info("Release the stored channelHandlerContext: {}", ctx);
        this.ctx = null;

        // only trigger the "no connection" event when no active connection exists
        if (!hasActiveConnection()) {
            ctx.fireUserEventTriggered(new ConnectionUpdateEvent(ConnectionState.NOT_CONNECTED));
        }

        synchronized (this.remoteConnectionListeners) {
            for (ConnectionListener connectionListener : this.remoteConnectionListeners) {
                try {
                    connectionListener
                        .closed(ctx.channel().remoteAddress() != null ? ctx.channel().remoteAddress().toString()
                            : "unknown");
                }
                catch (Exception ex) {
                    LOGGER.warn("Notify that the client connection was closed failed.", ex);
                }
            }
        }

        LOGGER.info("Disconnected");
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {

        // process the data that is received from the remote partner over netBidib

        byte[] messageContent = new byte[in.readableBytes()];
        in.readBytes(messageContent);

        LOGGER.info("< processReceivedMessage(messageArray), isFirstPacket);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create messages from provided data failed.", ex);
        }
    }

    private void processReceivedMessage(byte[] messageArray) throws ProtocolException {

        BidibMessage message = null;

        try {
            // let the request factory create the commands
            List commands = bidibRequestFactory.create(messageArray);
            LOGGER.info("Received commands: {}", commands);

            for (BidibMessageInterface bidibCommand : commands) {

                switch (ByteUtils.getInt(bidibCommand.getType())) {
                    case BidibLibrary.MSG_LOCAL_PROTOCOL_SIGNATURE:
                        // respond the protocol signature
                        final LocalProtocolSignatureMessage localProtocolSignatureMessage =
                            (LocalProtocolSignatureMessage) bidibCommand;

                        processMsgLocalProtocolSignature(ctx, localProtocolSignatureMessage);
                        break;
                    case BidibLibrary.MSG_LOCAL_LINK:
                        LocalLinkMessage localLinkMessage = (LocalLinkMessage) bidibCommand;
                        processMsgLocalLink(ctx, localLinkMessage);
                        break;

                    case BidibLibrary.MSG_LOCAL_LOGON:
                        // forward the command to the backend
                        if (hostAdapter != null) {
                            // the bidibCommand encodes the raw message
                            hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(bidibCommand));
                        }
                        else {
                            LOGGER.warn("No hostAdapter assigned.");
                        }
                        break;

                    case BidibLibrary.MSG_LOCAL_LOGON_ACK:
                        // if MSG_LOCAL_LOGON_ACK is received here we are connected to the remote partner
                        // without bidib-distributed
                        LocalLogonAckMessage localLogonAckMessage = (LocalLogonAckMessage) bidibCommand;
                        processMsgLocalLogonAck(ctx, localLogonAckMessage);
                        break;

                    case BidibLibrary.MSG_LOCAL_LOGON_REJECTED:
                        LOGGER.warn("Process the MSG_LOCAL_LOGON_REJECTED !!!");
                        break;

                    default:
                        LOGGER.debug("Processing BiDiB node related command: {}", bidibCommand);

                        final LogonStatus logonStatus = this.pairedPartner.getLogonStatus();

                        if (LogonStatus.LOGGED_ON == logonStatus) {
                            // forward the command to the backend
                            if (hostAdapter != null) {
                                // the bidibCommand encodes the raw message
                                hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(bidibCommand));
                            }
                            else {
                                LOGGER.warn("No hostAdapter assigned.");
                            }
                        }
                        else {
                            LOGGER.warn("The logon status for the paired partner is not LOGGED_ON.");

                            // TODO init the disconnect
                            throw new IllegalArgumentException("The paired partner is not LOGGED_ON.");
                        }
                        break;
                }
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Process received messages failed: {}", ByteUtils.bytesToHex(messageArray), ex);

            StringBuilder sb = new StringBuilder("< {
                    Boolean paired = Boolean.FALSE;

                    if (pairingCallback != null) {
                        LOGGER.info("Use the pairing callback to get the paired result.");
                        synchronized (pairedPartnerLock) {
                            paired = pairingCallback.apply(pairedPartner, pairingTimeout);
                        }
                    }
                    else {
                        LOGGER.warn("No pairingCallback available. Accept every client.");
                        paired = Boolean.TRUE;
                    }

                    LOGGER
                        .info("After pairing callback. Pairing success: {}, pairedPartner: {}", paired, pairedPartner);
                    try {
                        if (Boolean.TRUE.equals(paired)) {
                            publishPairedStatus(ctx, BidibLibrary.BIDIB_LINK_STATUS_PAIRED);
                        }
                        else {
                            publishPairedStatus(ctx, BidibLibrary.BIDIB_LINK_STATUS_UNPAIRED);
                        }
                    }
                    catch (ProtocolException ex) {
                        LOGGER.warn("Publish paired status failed.", ex);
                    }
                    catch (PairingDeniedException ex) {
                        LOGGER.warn("Pairing was denied. We must close the connection.", ex);
                        // TODO: handle exception

                        channelGroup.close();
                    }
                }, 5, TimeUnit.MILLISECONDS);

                break;
            default:
                LOGGER.warn("Unhandled message: {}", localLinkMessage);
                break;
        }
    }

    private final ScheduledExecutorService pairingWorker =
        Executors
            .newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("pairingWorkers-thread-%d").build());

    private BiFunction pairingCallback;

    public void setPairingCallback(final BiFunction pairingCallback) {
        LOGGER.info("Set the pairing callback: {}", pairingCallback);
        this.pairingCallback = pairingCallback;
    }

    /**
     * Process the MSG_LOCAL_LOGON_ACK netBidib message that was received from the remote partner.
     * 
     * @param ctx
     *            the netty context
     * @param localLogonAckMessage
     *            the message
     */
    private void processMsgLocalLogonAck(
        final ChannelHandlerContext ctx, final LocalLogonAckMessage localLogonAckMessage) {
        LOGGER.info("Received MSG_LOCAL_LOGON_ACK: {}", localLogonAckMessage);

        int nodeAddress = localLogonAckMessage.getNodeAddress();
        long uniqueId = localLogonAckMessage.getSenderUniqueId();

        LOGGER.info("Current nodeAddress: {}, uniqueId: {}", nodeAddress, ByteUtils.formatHexUniqueId(uniqueId));

        // TODO use the provided nodeAddress as the local bidib address

        if (serverLinkData.getUniqueId() == uniqueId) {
            synchronized (pairedPartnerLock) {
                pairedPartner.setLogonStatus(LogonStatus.LOGGED_ON);

                LOGGER.info("Current pairedPartner: {}", pairedPartner);
            }
        }
        else {
            LOGGER
                .warn("The provided uniqueId does not match, provided: {}, expected: {}",
                    ByteUtils.formatHexUniqueId(uniqueId), ByteUtils.formatHexUniqueId(serverLinkData.getUniqueId()));
        }
    }

    protected void publishPairedStatus(final ChannelHandlerContext ctx, int descriptor) throws ProtocolException {

        LOGGER.info("Publish the paired status: {}", descriptor);
        if (BidibLibrary.BIDIB_LINK_STATUS_PAIRED == descriptor) {
            LOGGER.info("The BIDIB_LINK_STATUS_PAIRED is published.");

            // set the state to paired
            ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.PAIRED);

            LOGGER.info("Send the status pairing status PAIRED message to the client.");

            NetBidibCommandMessage statusPaired =
                bidibRequestFactory
                    .createLocalLinkStatusPaired(serverLinkData.getUniqueId(), pairedPartner.getUniqueId());
            publishMessage(ctx, statusPaired);

            serverLinkData.setPairingStatus(PairingStatus.PAIRED);

            LOGGER.info("Current pairedPartner: {}, serverLinkData: {}", pairedPartner, serverLinkData);

            if (PairingStatus.PAIRED == pairedPartner.getPairingStatus()
                && PairingStatus.PAIRED == serverLinkData.getPairingStatus()
                && LogonStatus.LOGGED_OFF == pairedPartner.getLogonStatus()) {
                LOGGER.info("The pairedPartner accepted the pairing already. Send the LOGON.");

                LocalLogonMessage localLogonMessage =
                    new LocalLogonMessage(new byte[] { 0 }, 0, serverLinkData.getUniqueId());
                publishMessage(ctx, localLogonMessage);

                pairedPartner.setLogonStatus(LogonStatus.LOGGED_ON);
            }
            else {
                LOGGER.info("LOGON was not sent.");
            }

        }
        else {
            LOGGER.info("The BIDIB_LINK_STATUS_UNPAIRED is published.");

            // set the state to unpaired
            ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.UNPAIRED);

            NetBidibCommandMessage statusUnpaired =
                bidibRequestFactory
                    .createLocalLinkStatusUnpaired(serverLinkData.getUniqueId(), pairedPartner.getUniqueId());

            publishMessage(ctx, statusUnpaired);

            serverLinkData.setPairingStatus(PairingStatus.UNPAIRED);

            LOGGER
                .info("Throw PairingDeniedException to signal the connection is not paired. Current uniqueId: {}",
                    ByteUtils.formatHexUniqueId(pairedPartner.getUniqueId()));

            throw new PairingDeniedException(
                "Pairing denied for uniqueId: " + ByteUtils.formatHexUniqueId(pairedPartner.getUniqueId()));
        }
    }

    /**
     * Process the {@code LocalBidibUpResponse} and publish {@code LocalLinkMessage} messages to the connected partner.
     * 
     * @param localBidibUpResponse
     *            the localBidibUpResponse message received from the backend
     * @return {@code true}: send the pairing status to the connected partner, {@code false}: don't send the pairing
     *         status to the connected partner
     * @throws ProtocolException
     *             thrown if illegal message content is detected or processing of message failed.
     */
    protected boolean processLocalBidibUpResponseFromBackend(final LocalBidibUpResponse localBidibUpResponse)
        throws ProtocolException {

        boolean sendPairingStatusIfRequired = false;

        try {
            // add missing header info to the wrapped message

            byte[] wrapped = localBidibUpResponse.getWrappedMessage();

            byte[] raw = ByteUtils.concat(new byte[] { ByteUtils.getLowByte(wrapped.length + 2), 0, 0 }, wrapped);

            BidibMessageInterface message = responseFactory.create(raw);
            LOGGER.info("Received wrapped message: {}", message);

            boolean publish = false;
            switch (ByteUtils.getInt(message.getType())) {
                case BidibLibrary.MSG_SYS_UNIQUE_ID:
                    SysUniqueIdResponse response = (SysUniqueIdResponse) message;

                    synchronized (pairedPartnerLock) {
                        publish = serverLinkData.getUniqueId() == null;
                        serverLinkData.setUniqueId(NodeUtils.getUniqueId(response.getUniqueId()));

                        if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
                            LOGGER.info("Publish the uniqueId: {}", serverLinkData.getUniqueId());
                            LocalLinkMessage myUID =
                                new LocalLinkMessage(new byte[] { 0 }, 0, serverLinkData.getUniqueId());
                            publishMessage(ctx, myUID);
                        }
                    }
                    break;
                case BidibLibrary.MSG_STRING:
                    StringResponse stringResponse = (StringResponse) message;
                    StringData stringData = stringResponse.getStringData();

                    switch (stringData.getIndex()) {
                        case StringData.INDEX_PRODUCTNAME:
                            synchronized (pairedPartnerLock) {
                                publish = serverLinkData.getProdString() == null;

                                serverLinkData.setProdString(stringData.getValue());

                                // publish if the paired partner is available
                                if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
                                    LocalLinkMessage myProd =
                                        new LocalLinkMessage(new byte[] { 0 }, 0,
                                            BidibLibrary.BIDIB_LINK_DESCRIPTOR_PROD_STRING,
                                            serverLinkData.getProdString());
                                    LOGGER.info("Publish the product string: {}", myProd);
                                    publishMessage(ctx, myProd);
                                }
                            }
                            break;
                        case StringData.INDEX_USERNAME:
                            synchronized (pairedPartnerLock) {
                                publish = serverLinkData.getUserString() == null;

                                serverLinkData.setUserString(stringData.getValue());

                                if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
                                    LocalLinkMessage myUser =
                                        new LocalLinkMessage(new byte[] { 0 }, 0,
                                            BidibLibrary.BIDIB_LINK_DESCRIPTOR_USER_STRING,
                                            serverLinkData.getUserString());
                                    LOGGER.info("Publish the user string: {}", myUser);
                                    publishMessage(ctx, myUser);
                                }
                            }
                            break;
                        default:
                            break;
                    }

                    break;
                case BidibLibrary.MSG_SYS_P_VERSION:
                    SysPVersionResponse pVersionResponse = (SysPVersionResponse) message;

                    synchronized (pairedPartnerLock) {
                        publish = serverLinkData.getProtocolVersion() == null;
                        serverLinkData.setProtocolVersion(pVersionResponse.getVersion());

                        if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
                            LocalLinkMessage myProtocolVersion =
                                new LocalLinkMessage(new byte[] { 0 }, 0, BidibLibrary.BIDIB_LINK_DESCRIPTOR_P_VERSION,
                                    serverLinkData.getProtocolVersion());
                            LOGGER.info("Publish the protocol version: {}", myProtocolVersion);
                            publishMessage(ctx, myProtocolVersion);
                        }

                        sendPairingStatusIfRequired = true;
                    }
                    break;
                default:
                    break;
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create message from wrapped message data failed: {}", ex.getMessage());
            throw new ProtocolInvalidContentException("Create message from wrapped message data failed.", ex);
        }
        catch (Exception ex) {
            LOGGER.warn("Process the LocalBidibUpResponse message failed.", ex);
            throw new ProtocolException("Process the LocalBidibUpResponse message failed.");
        }

        return sendPairingStatusIfRequired;
    }

    /**
     * Publish the message to the connected host.
     * 
     * @param ctx
     *            the context
     * @param message
     *            the message
     */
    private void publishMessage(final ChannelHandlerContext ctx, final BidibMessageInterface message) {
        LOGGER.info("Publish the message to channel: {}", message);

        byte[] msg = message.getContent();

        // this.socketChannel.writeAndFlush(Unpooled.copiedBuffer(msg));
        channelGroup.iterator().next().writeAndFlush(Unpooled.copiedBuffer(msg));

        LOGGER.info("Write message to socketChannel has finished, msg: {}", ByteUtils.bytesToHex(msg));
        // ctx.channel().writeAndFlush(Unpooled.copiedBuffer(msg));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        LOGGER.debug("channelReadComplete.");

        // ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LOGGER.warn("Exception in handler. Close the channelHandlerContext.", cause);
        if (cause instanceof IOException) {
            LOGGER.warn("IOException: {}", cause.getMessage());
        }
        else {
            LOGGER.warn("Exception caught in server handler. Will close connection.", cause);
        }
        ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
        ctx.close();

        LOGGER.info("Clear the info of the paired partner.");
        synchronized (pairedPartnerLock) {
            pairedPartner.clear(true);
        }
    }

    /**
     * Returns true if currently an active connection exists.
     *
     * @return channel group has an active connection
     */
    public boolean hasActiveConnection() {
        return !channelGroup.isEmpty();
    }

    // public void setPairedPartner(NetBidibLinkData pairedPartner) {
    // LOGGER.info("Set the new paired partner: {}", pairedPartner);
    //
    // synchronized (pairedPartnerLock) {
    // this.pairedPartner = pairedPartner;
    // }
    //
    // }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy