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

com.hivemq.client.internal.mqtt.handler.connect.MqttConnectHandler Maven / Gradle / Ivy

Go to download

HiveMQ MQTT Client is an MQTT 5.0 and MQTT 3.1.1 compatible and feature-rich high-performance Java client library with different API flavours and backpressure support

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright 2018 dc-square and the HiveMQ MQTT Client Project
 *
 * 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 com.hivemq.client.internal.mqtt.handler.connect;

import com.hivemq.client.internal.logging.InternalLogger;
import com.hivemq.client.internal.logging.InternalLoggerFactory;
import com.hivemq.client.internal.mqtt.MqttClientConfig;
import com.hivemq.client.internal.mqtt.MqttClientConnectionConfig;
import com.hivemq.client.internal.mqtt.codec.decoder.MqttDecoder;
import com.hivemq.client.internal.mqtt.codec.encoder.MqttEncoder;
import com.hivemq.client.internal.mqtt.datatypes.MqttClientIdentifierImpl;
import com.hivemq.client.internal.mqtt.handler.MqttSession;
import com.hivemq.client.internal.mqtt.handler.disconnect.MqttDisconnectEvent;
import com.hivemq.client.internal.mqtt.handler.disconnect.MqttDisconnectUtil;
import com.hivemq.client.internal.mqtt.handler.ping.MqttPingHandler;
import com.hivemq.client.internal.mqtt.handler.util.MqttTimeoutInboundHandler;
import com.hivemq.client.internal.mqtt.ioc.ConnectionScope;
import com.hivemq.client.internal.mqtt.message.MqttMessage;
import com.hivemq.client.internal.mqtt.message.connect.MqttConnect;
import com.hivemq.client.internal.mqtt.message.connect.MqttConnectRestrictions;
import com.hivemq.client.internal.mqtt.message.connect.connack.MqttConnAck;
import com.hivemq.client.internal.mqtt.message.connect.connack.MqttConnAckRestrictions;
import com.hivemq.client.mqtt.MqttClientState;
import com.hivemq.client.mqtt.MqttVersion;
import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException;
import com.hivemq.client.mqtt.mqtt5.message.disconnect.Mqtt5DisconnectReasonCode;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.NotNull;

import javax.inject.Inject;

/**
 * Handles the connection to a MQTT Server.
 * 
    *
  • Writes the CONNECT message.
  • *
  • Handles the CONNACK message.
  • *
  • Disconnects or closes the channel on receiving other messages before CONNACK.
  • *
  • Disconnects or closes the channel if the CONNACK message is not received in the timeout.
  • *
* * @author Silvio Giebl */ @ConnectionScope public class MqttConnectHandler extends MqttTimeoutInboundHandler { public static final @NotNull String NAME = "connect"; private static final @NotNull InternalLogger LOGGER = InternalLoggerFactory.getLogger(MqttConnectHandler.class); private static final int CONNACK_TIMEOUT = 60; // TODO configurable private final @NotNull MqttConnect connect; private final @NotNull MqttConnAckFlow connAckFlow; private final @NotNull MqttClientConfig clientConfig; private final @NotNull MqttSession session; private final @NotNull MqttDecoder decoder; private final @NotNull MqttDisconnectOnConnAckHandler disconnectOnConnAckHandler; private boolean connectCalled = false; @Inject MqttConnectHandler( final @NotNull MqttConnect connect, final @NotNull MqttConnAckFlow connAckFlow, final @NotNull MqttClientConfig clientConfig, final @NotNull MqttSession session, final @NotNull MqttDecoder decoder, final @NotNull MqttDisconnectOnConnAckHandler disconnectOnConnAckHandler) { this.connect = connect; this.connAckFlow = connAckFlow; this.clientConfig = clientConfig; this.session = session; this.decoder = decoder; this.disconnectOnConnAckHandler = disconnectOnConnAckHandler; } @Override public void channelActive(final @NotNull ChannelHandlerContext ctx) { if (!connectCalled) { connectCalled = true; writeConnect(ctx); } ctx.fireChannelActive(); } @Override public void handlerAdded(final @NotNull ChannelHandlerContext ctx) { super.handlerAdded(ctx); if (!connectCalled && ctx.channel().isActive()) { connectCalled = true; writeConnect(ctx); } } /** * Writes the Connect message. *

* The MQTT message Decoder is added after the write succeeded as the server is not allowed to send messages before * the CONNECT is sent. *

* If the write fails, the channel is closed. * * @param ctx the channel handler context. */ private void writeConnect(final @NotNull ChannelHandlerContext ctx) { ctx.writeAndFlush((connect.getRawEnhancedAuthMechanism() == null) ? connect.createStateful(clientConfig.getRawClientIdentifier(), null) : connect).addListener(this); } @Override public void operationComplete(final @NotNull ChannelFuture future) { if (ctx == null) { return; } if (future.isSuccess()) { if (connect.getRawEnhancedAuthMechanism() == null) { scheduleTimeout(ctx.channel()); } ctx.pipeline().addAfter(MqttEncoder.NAME, MqttDecoder.NAME, decoder); } } @Override public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) { cancelTimeout(); if (msg instanceof MqttConnAck) { readConnAck((MqttConnAck) msg, ctx.channel()); } else { readOtherThanConnAck(msg, ctx.channel()); } } /** * Handles the given CONNACK message. *

* If it contains an Error Code, the channel is closed. *

* Otherwise it is validated. Then this handler is removed from the pipeline and the {@link MqttPingHandler} and * {@link MqttDisconnectOnConnAckHandler} are added to the pipeline. * * @param connAck the CONNACK message. * @param channel the channel. */ private void readConnAck(final @NotNull MqttConnAck connAck, final @NotNull Channel channel) { if (connAck.getReasonCode().isError()) { MqttDisconnectUtil.close(channel, new Mqtt5ConnAckException( connAck, "CONNECT failed as CONNACK contained an Error Code: " + connAck.getReasonCode() + ".")); } else if (validateClientIdentifier(connAck, channel)) { final MqttClientConnectionConfig connectionConfig = addConnectionConfig(connAck, channel); channel.pipeline().remove(this).addLast(MqttDisconnectOnConnAckHandler.NAME, disconnectOnConnAckHandler); ((MqttEncoder) channel.pipeline().get(MqttEncoder.NAME)).onConnected(connectionConfig); session.startOrResume(connAck, channel.pipeline(), connectionConfig); final int keepAlive = connectionConfig.getKeepAlive(); if (keepAlive > 0) { channel.pipeline().addAfter(MqttDecoder.NAME, MqttPingHandler.NAME, new MqttPingHandler(keepAlive)); } clientConfig.getRawState().set(MqttClientState.CONNECTED); connAckFlow.onSuccess(connAck); } } /** * The server must not send other messages before CONNACK. *

* If a MQTT message other than CONNACK is received after the CONNECT message was sent, a DISCONNECT message is sent * and the channel is closed. *

* If a message is received before the CONNECT message was sent, the channel is closed. * * @param msg the received message other than CONNACK. * @param channel the channel. */ private void readOtherThanConnAck(final @NotNull Object msg, final @NotNull Channel channel) { if (msg instanceof MqttMessage) { MqttDisconnectUtil.disconnect(channel, Mqtt5DisconnectReasonCode.PROTOCOL_ERROR, ((MqttMessage) msg).getType() + " message must not be received before CONNACK"); } else { MqttDisconnectUtil.close(channel, "No data must be received before CONNECT is sent"); } } /** * Validates the given CONNACK message. *

* If validation fails, disconnection and closing of the channel is already handled. * * @param connAck the CONNACK message. * @param channel the channel. * @return true if the CONNACK message is valid, otherwise false. */ private boolean validateClientIdentifier(final @NotNull MqttConnAck connAck, final @NotNull Channel channel) { final MqttClientIdentifierImpl clientIdentifier = clientConfig.getRawClientIdentifier(); final MqttClientIdentifierImpl assignedClientIdentifier = connAck.getRawAssignedClientIdentifier(); if (clientIdentifier == MqttClientIdentifierImpl.REQUEST_CLIENT_IDENTIFIER_FROM_SERVER) { if ((clientConfig.getMqttVersion() == MqttVersion.MQTT_5_0) && (assignedClientIdentifier == null)) { MqttDisconnectUtil.disconnect(channel, Mqtt5DisconnectReasonCode.PROTOCOL_ERROR, new Mqtt5ConnAckException(connAck, "Server did not assign a Client Identifier")); return false; } } else { if (assignedClientIdentifier != null) { LOGGER.warn("Server overwrote the Client Identifier {} with {}", clientIdentifier, assignedClientIdentifier); } } if (assignedClientIdentifier != null) { clientConfig.setClientIdentifier(assignedClientIdentifier); } return true; } private @NotNull MqttClientConnectionConfig addConnectionConfig( final @NotNull MqttConnAck connAck, final @NotNull Channel channel) { int keepAlive = connAck.getRawServerKeepAlive(); if (keepAlive == MqttConnAck.KEEP_ALIVE_FROM_CONNECT) { keepAlive = connect.getKeepAlive(); } long sessionExpiryInterval = connAck.getRawSessionExpiryInterval(); if (sessionExpiryInterval == MqttConnAck.SESSION_EXPIRY_INTERVAL_FROM_CONNECT) { sessionExpiryInterval = connect.getSessionExpiryInterval(); } final MqttConnectRestrictions restrictions = connect.getRestrictions(); final MqttConnAckRestrictions connAckRestrictions = connAck.getRestrictions(); // @formatter:off final MqttClientConnectionConfig connectionConfig = new MqttClientConnectionConfig( keepAlive, sessionExpiryInterval, connect.getRawWillPublish() != null, connect.getRawEnhancedAuthMechanism(), restrictions.getReceiveMaximum(), restrictions.getMaximumPacketSize(), restrictions.getTopicAliasMaximum(), restrictions.isRequestProblemInformation(), restrictions.isRequestResponseInformation(), Math.min(restrictions.getSendMaximum(), connAckRestrictions.getReceiveMaximum()), Math.min(restrictions.getSendMaximumPacketSize(), connAckRestrictions.getMaximumPacketSize()), Math.min(restrictions.getSendTopicAliasMaximum(), connAckRestrictions.getTopicAliasMaximum()), connAckRestrictions.getMaximumQos(), connAckRestrictions.isRetainAvailable(), connAckRestrictions.isWildcardSubscriptionAvailable(), connAckRestrictions.isSharedSubscriptionAvailable(), connAckRestrictions.areSubscriptionIdentifiersAvailable(), channel); // @formatter:on clientConfig.setConnectionConfig(connectionConfig); return connectionConfig; } @Override protected void onDisconnectEvent(final @NotNull MqttDisconnectEvent disconnectEvent) { super.onDisconnectEvent(disconnectEvent); connAckFlow.onError(disconnectEvent.getCause()); } @Override protected long getTimeout() { return CONNACK_TIMEOUT; } @Override protected @NotNull Mqtt5DisconnectReasonCode getTimeoutReasonCode() { return Mqtt5DisconnectReasonCode.PROTOCOL_ERROR; } @Override protected @NotNull String getTimeoutReasonString() { return "Timeout while waiting for CONNACK"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy