org.kaaproject.kaa.server.transports.tcp.transport.TcpHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.kaaproject.kaa.server.transports.tcp.transport.TcpHandler.java

Source

/*
 * Copyright 2014-2016 CyberVision, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.kaaproject.kaa.server.transports.tcp.transport;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.ConnAck;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.ConnAck.ReturnCode;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Connect;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Disconnect;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Disconnect.DisconnectReason;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.KaaSync;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.KaaSyncMessageType;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.MessageType;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.MqttFrame;
import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.SyncRequest;
import org.kaaproject.kaa.server.common.server.NettyChannelContext;
import org.kaaproject.kaa.server.transport.EndpointVerificationException;
import org.kaaproject.kaa.server.transport.InvalidSdkTokenException;
import org.kaaproject.kaa.server.transport.channel.ChannelType;
import org.kaaproject.kaa.server.transport.message.ErrorBuilder;
import org.kaaproject.kaa.server.transport.message.MessageBuilder;
import org.kaaproject.kaa.server.transport.message.MessageHandler;
import org.kaaproject.kaa.server.transport.session.SessionCreateListener;
import org.kaaproject.kaa.server.transport.session.SessionInfo;
import org.kaaproject.kaa.server.transports.tcp.transport.messages.NettyTcpConnectMessage;
import org.kaaproject.kaa.server.transports.tcp.transport.messages.NettyTcpDisconnectMessage;
import org.kaaproject.kaa.server.transports.tcp.transport.messages.NettyTcpPingMessage;
import org.kaaproject.kaa.server.transports.tcp.transport.messages.NettyTcpSyncMessage;
import org.kaaproject.kaa.server.transports.tcp.transport.netty.AbstractKaaTcpCommandProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.UUID;

public class TcpHandler extends SimpleChannelInboundHandler<AbstractKaaTcpCommandProcessor>
        implements SessionCreateListener {

    private static final Logger LOG = LoggerFactory.getLogger(TcpHandler.class);

    private static final boolean NOT_ZIPPED = false;
    private static final ErrorBuilder connectErrorConverter = new ErrorBuilder() { //NOSONAR
        @Override
        public Object[] build(Exception exception) {
            Object[] responses = new Object[1];
            if (exception instanceof GeneralSecurityException || exception instanceof IOException
                    || exception instanceof IllegalArgumentException
                    || exception instanceof InvalidSdkTokenException) {
                responses[0] = new ConnAck(ReturnCode.REFUSE_BAD_CREDENTIALS);
            } else if (exception instanceof EndpointVerificationException) {
                responses[0] = new ConnAck(ReturnCode.REFUSE_VERIFICATION_FAILED);
            } else {
                responses[0] = new ConnAck(ReturnCode.REFUSE_SERVER_UNAVAILABLE);
            }
            return responses;
        }
    };

    private static final MessageBuilder syncResponseConverter = new MessageBuilder() { //NOSONAR
        @Override
        public Object[] build(byte[] encriptedResponseData, byte[] encriptedResponseSignature,
                boolean isEncrypted) {
            Object[] responses = new Object[1];
            responses[0] = new org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.SyncResponse(
                    encriptedResponseData, NOT_ZIPPED, isEncrypted);

            LOG.debug("Sending {} response objects", responses.length);
            return responses;
        }

        @Override
        public Object[] build(byte[] messageData, boolean isEncrypted) {
            return build(messageData, null, isEncrypted);
        }
    };

    private static final ErrorBuilder syncErrorConverter = new ErrorBuilder() { //NOSONAR
        @Override
        public Object[] build(Exception exception) {
            Object[] responses = new Object[1];
            if (exception instanceof GeneralSecurityException || exception instanceof IOException
                    || exception instanceof IllegalArgumentException
                    || exception instanceof InvalidSdkTokenException) {
                responses[0] = new Disconnect(DisconnectReason.BAD_REQUEST);
            } else if (exception instanceof EndpointVerificationException) {
                responses[0] = new Disconnect(DisconnectReason.CREDENTIALS_REVOKED);
            } else {
                responses[0] = new Disconnect(DisconnectReason.INTERNAL_ERROR);
            }
            return responses;
        }
    };

    private final UUID uuid;
    private final MessageHandler handler;
    private volatile SessionInfo session;
    private volatile boolean sessionDisconnected;
    private MessageBuilder connectResponseConverter;

    /**
     * Create new instance of <code>TcpHandler</code>.
     *
     * @param uuid immutable universally unique identifier
     * @param akkaService handler of messages with the transport layer connection info
     */
    public TcpHandler(UUID uuid, MessageHandler akkaService) {
        this.uuid = uuid;
        this.handler = akkaService;
        this.connectResponseConverter = new MessageBuilder() {
            volatile boolean connAckSent = false;

            @Override
            public Object[] build(byte[] encriptedResponseData, byte[] encriptedResponseSignature,
                    boolean isEncrypted) {
                if (!connAckSent) {
                    synchronized (this) {
                        if (!connAckSent) {
                            connAckSent = true;
                            Object[] responses = new Object[2];
                            responses[0] = new ConnAck(ReturnCode.ACCEPTED);
                            responses[1] = new org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.SyncResponse(
                                    encriptedResponseData, NOT_ZIPPED, isEncrypted);
                            LOG.debug("Sending {} response objects", responses.length);
                            return responses;
                        }
                    }
                }
                return syncResponseConverter.build(encriptedResponseData, encriptedResponseSignature, isEncrypted);
            }

            @Override
            public Object[] build(byte[] messageData, boolean isEncrypted) {
                return build(messageData, null, isEncrypted);
            }
        };
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, AbstractKaaTcpCommandProcessor msg) throws Exception {
        MqttFrame frame = msg.getRequest();
        LOG.trace("[{}] Processing {}", uuid, frame);
        if (frame.getMessageType() == MessageType.CONNECT) {
            ChannelFuture closeFuture = ctx.channel().closeFuture();
            closeFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    if (!sessionDisconnected) {
                        if (session != null) {
                            handler.process(new NettyTcpDisconnectMessage(session));
                            LOG.trace("[{}] Channel is closed - sending disconnect", uuid);
                        } else {
                            LOG.trace("[{}] Session is not yet established. Skip sending disconnect", uuid);
                        }
                        sessionDisconnected = true;
                    } else {
                        LOG.trace("[{}] Channel is closed and disconnect is already sent", uuid);
                    }
                }
            });

            if (session == null) {
                handler.process(new NettyTcpConnectMessage(uuid, new NettyChannelContext(ctx), (Connect) frame,
                        ChannelType.ASYNC, this, connectResponseConverter, connectErrorConverter));
            } else {
                LOG.info("[{}] Ignoring duplicate {} message ", uuid, MessageType.CONNECT);
            }
        } else {
            if (session != null) {
                switch (frame.getMessageType()) {
                case KAASYNC:
                    if (((KaaSync) frame).getKaaSyncMessageType() == KaaSyncMessageType.SYNC) {
                        handler.process(new NettyTcpSyncMessage((SyncRequest) frame, session, syncResponseConverter,
                                syncErrorConverter));
                    }
                    break;
                case PINGREQ:
                    handler.process(new NettyTcpPingMessage(session));
                    break;
                case DISCONNECT:
                    sessionDisconnected = true;
                    handler.process(new NettyTcpDisconnectMessage(session));
                    break;
                default:
                    break;
                }
            } else {
                LOG.info("[{}] Ignoring {} message due to incomplete CONNECT sequence", uuid,
                        frame.getMessageType());
                ctx.writeAndFlush(new ConnAck(ReturnCode.REFUSE_BAD_PROTOCOL));
            }
        }
    }

    @Override
    public void onSessionCreated(SessionInfo session) {
        LOG.trace("[{}] Session info is set to {}", uuid, session);
        this.session = session;
    }

}