io.vertx.mqtt.impl.MqttServerConnection.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.mqtt.impl.MqttServerConnection.java

Source

/*
 * Copyright 2016 Red Hat 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 io.vertx.mqtt.impl;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.vertx.core.Handler;
import io.vertx.core.VertxException;
import io.vertx.core.impl.NetSocketInternal;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.mqtt.MqttAuth;
import io.vertx.mqtt.MqttEndpoint;
import io.vertx.mqtt.MqttServerOptions;
import io.vertx.mqtt.MqttWill;
import io.vertx.mqtt.messages.MqttPublishMessage;
import io.vertx.mqtt.messages.MqttSubscribeMessage;
import io.vertx.mqtt.messages.MqttUnsubscribeMessage;

import java.util.UUID;

/**
 * Represents an MQTT connection with a remote client
 */
public class MqttServerConnection {

    private static final Logger log = LoggerFactory.getLogger(MqttServerConnection.class);

    // handler to call when a remote MQTT client connects and establishes a connection
    private Handler<MqttEndpoint> endpointHandler;

    // handler to call when an connection is rejected
    private Handler<Throwable> exceptionHandler;

    private final NetSocketInternal so;

    // endpoint for handling point-to-point communication with the remote MQTT client
    private MqttEndpointImpl endpoint;
    private final ChannelHandlerContext chctx;
    private final MqttServerOptions options;

    void init(Handler<MqttEndpoint> endpointHandler, Handler<Throwable> rejectHandler) {
        this.endpointHandler = endpointHandler;
        this.exceptionHandler = rejectHandler;
    }

    public MqttServerConnection(NetSocketInternal so, MqttServerOptions options) {
        this.so = so;
        this.chctx = so.channelHandlerContext();
        this.options = options;
    }

    /**
     * Handle the MQTT message received by the remote MQTT client
     *
     * @param msg message to handle
     */
    synchronized void handleMessage(Object msg) {

        // handling directly native Netty MQTT messages, some of them are translated
        // to the related Vert.x ones for polyglotization
        if (msg instanceof io.netty.handler.codec.mqtt.MqttMessage) {

            io.netty.handler.codec.mqtt.MqttMessage mqttMessage = (io.netty.handler.codec.mqtt.MqttMessage) msg;

            DecoderResult result = mqttMessage.decoderResult();
            if (result.isFailure()) {
                chctx.pipeline().fireExceptionCaught(result.cause());
                return;
            }
            if (!result.isFinished()) {
                chctx.pipeline().fireExceptionCaught(new Exception("Unfinished message"));
                return;
            }

            switch (mqttMessage.fixedHeader().messageType()) {

            case CONNECT:
                handleConnect((MqttConnectMessage) msg);
                break;

            case SUBSCRIBE:

                io.netty.handler.codec.mqtt.MqttSubscribeMessage subscribe = (io.netty.handler.codec.mqtt.MqttSubscribeMessage) mqttMessage;

                MqttSubscribeMessage mqttSubscribeMessage = MqttSubscribeMessage
                        .create(subscribe.variableHeader().messageId(), subscribe.payload().topicSubscriptions());
                this.handleSubscribe(mqttSubscribeMessage);
                break;

            case UNSUBSCRIBE:

                io.netty.handler.codec.mqtt.MqttUnsubscribeMessage unsubscribe = (io.netty.handler.codec.mqtt.MqttUnsubscribeMessage) mqttMessage;

                MqttUnsubscribeMessage mqttUnsubscribeMessage = MqttUnsubscribeMessage
                        .create(unsubscribe.variableHeader().messageId(), unsubscribe.payload().topics());
                this.handleUnsubscribe(mqttUnsubscribeMessage);
                break;

            case PUBLISH:

                io.netty.handler.codec.mqtt.MqttPublishMessage publish = (io.netty.handler.codec.mqtt.MqttPublishMessage) mqttMessage;
                ByteBuf newBuf = VertxHandler.safeBuffer(publish.payload(), this.chctx.alloc());

                MqttPublishMessage mqttPublishMessage = MqttPublishMessage.create(
                        publish.variableHeader().messageId(), publish.fixedHeader().qosLevel(),
                        publish.fixedHeader().isDup(), publish.fixedHeader().isRetain(),
                        publish.variableHeader().topicName(), newBuf);
                this.handlePublish(mqttPublishMessage);
                break;

            case PUBACK:

                io.netty.handler.codec.mqtt.MqttPubAckMessage mqttPubackMessage = (io.netty.handler.codec.mqtt.MqttPubAckMessage) mqttMessage;
                this.handlePuback(mqttPubackMessage.variableHeader().messageId());
                break;

            case PUBREC:

                int pubrecMessageId = ((io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader) mqttMessage
                        .variableHeader()).messageId();
                this.handlePubrec(pubrecMessageId);
                break;

            case PUBREL:

                int pubrelMessageId = ((io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader) mqttMessage
                        .variableHeader()).messageId();
                this.handlePubrel(pubrelMessageId);
                break;

            case PUBCOMP:

                int pubcompMessageId = ((io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader) mqttMessage
                        .variableHeader()).messageId();
                this.handlePubcomp(pubcompMessageId);
                break;

            case PINGREQ:

                this.handlePingreq();
                break;

            case DISCONNECT:

                this.handleDisconnect();
                break;

            default:

                this.chctx.fireExceptionCaught(new Exception("Wrong message type " + msg.getClass().getName()));
                break;

            }

        } else {

            this.chctx.fireExceptionCaught(new Exception("Wrong message type"));
        }
    }

    /**
     * Used for calling the endpoint handler when a connection is established with a remote MQTT client
     */
    private void handleConnect(MqttConnectMessage msg) {

        // if client sent one more CONNECT packet
        if (endpoint != null) {
            //we should treat it as a protocol violation and disconnect the client
            endpoint.close();
            return;
        }

        // if client sent one more CONNECT packet
        if (endpoint != null) {
            //we should treat it as a protocol violation and disconnect the client
            endpoint.close();
            return;
        }

        // retrieve will information from CONNECT message
        MqttWill will = new MqttWill(msg.variableHeader().isWillFlag(), msg.payload().willTopic(),
                msg.payload().willMessage(), msg.variableHeader().willQos(), msg.variableHeader().isWillRetain());

        // retrieve authorization information from CONNECT message
        MqttAuth auth = (msg.variableHeader().hasUserName() && msg.variableHeader().hasPassword())
                ? new MqttAuth(msg.payload().userName(), msg.payload().password())
                : null;

        // check if remote MQTT client didn't specify a client-id
        boolean isZeroBytes = (msg.payload().clientIdentifier() == null)
                || msg.payload().clientIdentifier().isEmpty();

        String clientIdentifier = null;

        // client-id got from payload or auto-generated (according to options)
        if (!isZeroBytes) {
            clientIdentifier = msg.payload().clientIdentifier();
        } else if (this.options.isAutoClientId()) {
            clientIdentifier = UUID.randomUUID().toString();
        }

        // create the MQTT endpoint provided to the application handler
        this.endpoint = new MqttEndpointImpl(so, clientIdentifier, auth, will,
                msg.variableHeader().isCleanSession(), msg.variableHeader().version(), msg.variableHeader().name(),
                msg.variableHeader().keepAliveTimeSeconds());

        // remove the idle state handler for timeout on CONNECT
        chctx.pipeline().remove("idle");
        chctx.pipeline().remove("timeoutOnConnect");

        // keep alive == 0 means NO keep alive, no timeout to handle
        if (msg.variableHeader().keepAliveTimeSeconds() != 0) {

            // the server waits for one and a half times the keep alive time period (MQTT spec)
            int timeout = msg.variableHeader().keepAliveTimeSeconds()
                    + msg.variableHeader().keepAliveTimeSeconds() / 2;

            // modifying the channel pipeline for adding the idle state handler with previous timeout
            chctx.pipeline().addBefore("handler", "idle", new IdleStateHandler(timeout, 0, 0));
            chctx.pipeline().addBefore("handler", "keepAliveHandler", new ChannelDuplexHandler() {

                @Override
                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

                    if (evt instanceof IdleStateEvent) {
                        IdleStateEvent e = (IdleStateEvent) evt;
                        if (e.state() == IdleState.READER_IDLE) {
                            endpoint.close();
                        }
                    }
                }
            });
        }

        // MQTT spec 3.1.1 : if client-id is "zero-bytes", clean session MUST be true
        if (isZeroBytes && !msg.variableHeader().isCleanSession()) {
            if (this.exceptionHandler != null) {
                this.exceptionHandler
                        .handle(new VertxException("With zero-length client-id, clean session MUST be true"));
            }
            this.endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED);
        } else {

            // an exception at connection level is propagated to the endpoint
            this.so.exceptionHandler(t -> {
                this.endpoint.handleException(t);
            });

            // Used for calling the close handler when the remote MQTT client closes the connection
            this.so.closeHandler(v -> this.endpoint.handleClosed());

            this.endpointHandler.handle(this.endpoint);
        }
    }

    /**
     * Used for calling the subscribe handler when the remote MQTT client subscribes to topics
     *
     * @param msg message with subscribe information
     */
    synchronized void handleSubscribe(MqttSubscribeMessage msg) {

        if (this.checkConnected()) {
            this.endpoint.handleSubscribe(msg);
        }
    }

    /**
     * Used for calling the unsubscribe handler when the remote MQTT client unsubscribe to topics
     *
     * @param msg message with unsubscribe information
     */
    synchronized void handleUnsubscribe(MqttUnsubscribeMessage msg) {

        if (this.checkConnected()) {
            this.endpoint.handleUnsubscribe(msg);
        }
    }

    /**
     * Used for calling the publish handler when the remote MQTT client publishes a message
     *
     * @param msg published message
     */
    synchronized void handlePublish(MqttPublishMessage msg) {

        if (this.checkConnected()) {
            this.endpoint.handlePublish(msg);
        }
    }

    /**
     * Used for calling the puback handler when the remote MQTT client acknowledge a QoS 1 message with puback
     *
     * @param pubackMessageId identifier of the message acknowledged by the remote MQTT client
     */
    synchronized void handlePuback(int pubackMessageId) {

        if (this.checkConnected()) {
            this.endpoint.handlePuback(pubackMessageId);
        }
    }

    /**
     * Used for calling the pubrec handler when the remote MQTT client acknowledge a QoS 2 message with pubrec
     *
     * @param pubrecMessageId identifier of the message acknowledged by the remote MQTT client
     */
    synchronized void handlePubrec(int pubrecMessageId) {

        if (this.checkConnected()) {
            this.endpoint.handlePubrec(pubrecMessageId);
        }
    }

    /**
     * Used for calling the pubrel handler when the remote MQTT client acknowledge a QoS 2 message with pubrel
     *
     * @param pubrelMessageId identifier of the message acknowledged by the remote MQTT client
     */
    synchronized void handlePubrel(int pubrelMessageId) {

        if (this.checkConnected()) {
            this.endpoint.handlePubrel(pubrelMessageId);
        }
    }

    /**
     * Used for calling the pubcomp handler when the remote MQTT client acknowledge a QoS 2 message with pubcomp
     *
     * @param pubcompMessageId identifier of the message acknowledged by the remote MQTT client
     */
    synchronized void handlePubcomp(int pubcompMessageId) {

        if (this.checkConnected()) {
            this.endpoint.handlePubcomp(pubcompMessageId);
        }
    }

    /**
     * Used internally for handling the pinreq from the remote MQTT client
     */
    synchronized void handlePingreq() {

        if (this.checkConnected()) {
            this.endpoint.handlePingreq();
        }
    }

    /**
     * Used for calling the disconnect handler when the remote MQTT client disconnects
     */
    synchronized void handleDisconnect() {

        if (this.checkConnected()) {
            this.endpoint.handleDisconnect();
        }
    }

    /**
     * Check if the endpoint was created and is connected
     *
     * @return  status of the endpoint (connected or not)
     */
    private boolean checkConnected() {

        if ((this.endpoint != null) && (this.endpoint.isConnected())) {
            return true;
        } else {
            so.close();
            throw new IllegalStateException(
                    "Received an MQTT packet from a not connected client (CONNECT not sent yet)");
        }
    }
}