Merge pull request #10734 from imbeacon/improvement/mqtt-reason-codes
Added MQTT Disconnect messages from server with reason codes
This commit is contained in:
		
						commit
						181f117c64
					
				@ -90,6 +90,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCre
 | 
				
			|||||||
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
 | 
					import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
 | 
				
			||||||
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
 | 
					import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
 | 
				
			||||||
import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg;
 | 
					import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg;
 | 
				
			||||||
 | 
					import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseReason;
 | 
				
			||||||
import org.thingsboard.server.service.rpc.RpcSubmitStrategy;
 | 
					import org.thingsboard.server.service.rpc.RpcSubmitStrategy;
 | 
				
			||||||
import org.thingsboard.server.service.state.DefaultDeviceStateService;
 | 
					import org.thingsboard.server.service.state.DefaultDeviceStateService;
 | 
				
			||||||
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
 | 
					import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
 | 
				
			||||||
@ -845,7 +846,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
 | 
				
			|||||||
                notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
 | 
					                notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!"));
 | 
					            sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!", SessionCloseReason.CREDENTIALS_UPDATED));
 | 
				
			||||||
            attributeSubscriptions.clear();
 | 
					            attributeSubscriptions.clear();
 | 
				
			||||||
            rpcSubscriptions.clear();
 | 
					            rpcSubscriptions.clear();
 | 
				
			||||||
            dumpSessions();
 | 
					            dumpSessions();
 | 
				
			||||||
@ -855,13 +856,15 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
 | 
					    private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
 | 
				
			||||||
        log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId: [{}] sessionMd: [{}]", sessionId, sessionMd);
 | 
					        log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId: [{}] sessionMd: [{}]", sessionId, sessionMd);
 | 
				
			||||||
        notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
 | 
					        notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!", SessionCloseReason.MAX_CONCURRENT_SESSIONS_LIMIT_REACHED);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
 | 
					    private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message, SessionCloseReason reason) {
 | 
				
			||||||
        SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
 | 
					        SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
 | 
				
			||||||
                .newBuilder()
 | 
					                .newBuilder()
 | 
				
			||||||
                .setMessage(message).build();
 | 
					                .setMessage(message)
 | 
				
			||||||
 | 
					                .setReason(reason)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
        ToTransportMsg msg = ToTransportMsg.newBuilder()
 | 
					        ToTransportMsg msg = ToTransportMsg.newBuilder()
 | 
				
			||||||
                .setSessionIdMSB(sessionId.getMostSignificantBits())
 | 
					                .setSessionIdMSB(sessionId.getMostSignificantBits())
 | 
				
			||||||
                .setSessionIdLSB(sessionId.getLeastSignificantBits())
 | 
					                .setSessionIdLSB(sessionId.getLeastSignificantBits())
 | 
				
			||||||
@ -1044,7 +1047,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
 | 
				
			|||||||
                attributeSubscriptions.remove(id);
 | 
					                attributeSubscriptions.remove(id);
 | 
				
			||||||
                if (session != null) {
 | 
					                if (session != null) {
 | 
				
			||||||
                    removed++;
 | 
					                    removed++;
 | 
				
			||||||
                    notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE);
 | 
					                    notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE, SessionCloseReason.SESSION_TIMEOUT);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (removed != 0) {
 | 
					            if (removed != 0) {
 | 
				
			||||||
 | 
				
			|||||||
@ -972,6 +972,8 @@ transport:
 | 
				
			|||||||
    proxy_enabled: "${MQTT_PROXY_PROTOCOL_ENABLED:false}"
 | 
					    proxy_enabled: "${MQTT_PROXY_PROTOCOL_ENABLED:false}"
 | 
				
			||||||
    # MQTT processing timeout in milliseconds
 | 
					    # MQTT processing timeout in milliseconds
 | 
				
			||||||
    timeout: "${MQTT_TIMEOUT:10000}"
 | 
					    timeout: "${MQTT_TIMEOUT:10000}"
 | 
				
			||||||
 | 
					    # MQTT disconnect timeout in milliseconds. The time to wait for the client to disconnect after the server sends a disconnect message.
 | 
				
			||||||
 | 
					    disconnect_timeout: "${MQTT_DISCONNECT_TIMEOUT:1000}"
 | 
				
			||||||
    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before the device connected state. This limit works on the low level before TenantProfileLimits mechanism
 | 
					    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before the device connected state. This limit works on the low level before TenantProfileLimits mechanism
 | 
				
			||||||
    netty:
 | 
					    netty:
 | 
				
			||||||
      # Netty leak detector level
 | 
					      # Netty leak detector level
 | 
				
			||||||
 | 
				
			|||||||
@ -466,6 +466,13 @@ public class JsonConverter {
 | 
				
			|||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static JsonObject toGatewayDeviceDisconnectJson(String deviceName, int reasonCode) {
 | 
				
			||||||
 | 
					        JsonObject result = new JsonObject();
 | 
				
			||||||
 | 
					        result.addProperty(DEVICE_PROPERTY, deviceName);
 | 
				
			||||||
 | 
					        result.addProperty("reason", reasonCode);
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static JsonElement toErrorJson(String errorMsg) {
 | 
					    public static JsonElement toErrorJson(String errorMsg) {
 | 
				
			||||||
        JsonObject error = new JsonObject();
 | 
					        JsonObject error = new JsonObject();
 | 
				
			||||||
        error.addProperty("error", errorMsg);
 | 
					        error.addProperty("error", errorMsg);
 | 
				
			||||||
 | 
				
			|||||||
@ -397,6 +397,11 @@ message GetOrCreateDeviceFromGatewayResponseMsg {
 | 
				
			|||||||
  TransportApiRequestErrorCode error = 3;
 | 
					  TransportApiRequestErrorCode error = 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message GatewayDisconnectDeviceMsg {
 | 
				
			||||||
 | 
					  string deviceName = 1;
 | 
				
			||||||
 | 
					  int32 reasonCode = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum TransportApiRequestErrorCode {
 | 
					enum TransportApiRequestErrorCode {
 | 
				
			||||||
  UNKNOWN_TRANSPORT_API_ERROR  = 0;
 | 
					  UNKNOWN_TRANSPORT_API_ERROR  = 0;
 | 
				
			||||||
  ENTITY_LIMIT = 1;
 | 
					  ENTITY_LIMIT = 1;
 | 
				
			||||||
@ -579,8 +584,16 @@ message ResourceDeleteMsg {
 | 
				
			|||||||
  string resourceKey = 4;
 | 
					  string resourceKey = 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum SessionCloseReason {
 | 
				
			||||||
 | 
					  UNKNOWN_REASON = 0;
 | 
				
			||||||
 | 
					  CREDENTIALS_UPDATED = 1;
 | 
				
			||||||
 | 
					  MAX_CONCURRENT_SESSIONS_LIMIT_REACHED = 2;
 | 
				
			||||||
 | 
					  SESSION_TIMEOUT = 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message SessionCloseNotificationProto {
 | 
					message SessionCloseNotificationProto {
 | 
				
			||||||
  string message = 1;
 | 
					  string message = 1;
 | 
				
			||||||
 | 
					  SessionCloseReason reason = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message SubscribeToAttributeUpdatesMsg {
 | 
					message SubscribeToAttributeUpdatesMsg {
 | 
				
			||||||
 | 
				
			|||||||
@ -71,6 +71,10 @@ public class MqttTransportContext extends TransportContext {
 | 
				
			|||||||
    @Value("${transport.mqtt.timeout:10000}")
 | 
					    @Value("${transport.mqtt.timeout:10000}")
 | 
				
			||||||
    private long timeout;
 | 
					    private long timeout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
 | 
					    @Value("${transport.mqtt.disconnect_timeout:1000}")
 | 
				
			||||||
 | 
					    private long disconnectTimeout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Getter
 | 
					    @Getter
 | 
				
			||||||
    @Value("${transport.mqtt.proxy_enabled:false}")
 | 
					    @Value("${transport.mqtt.proxy_enabled:false}")
 | 
				
			||||||
    private boolean proxyEnabled;
 | 
					    private boolean proxyEnabled;
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
 | 
				
			|||||||
import io.netty.handler.codec.mqtt.MqttPubAckMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttPubAckMessage;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttQoS;
 | 
					import io.netty.handler.codec.mqtt.MqttQoS;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttReasonCodes;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttSubAckMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttSubAckMessage;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttSubAckPayload;
 | 
					import io.netty.handler.codec.mqtt.MqttSubAckPayload;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
 | 
				
			||||||
@ -68,7 +69,6 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
 | 
				
			|||||||
import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
 | 
					import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
 | 
				
			||||||
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
 | 
					import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
 | 
				
			||||||
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
 | 
					import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
 | 
				
			||||||
import org.thingsboard.server.common.transport.service.DefaultTransportService;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.transport.service.SessionMetaData;
 | 
					import org.thingsboard.server.common.transport.service.SessionMetaData;
 | 
				
			||||||
import org.thingsboard.server.common.transport.util.SslUtil;
 | 
					import org.thingsboard.server.common.transport.util.SslUtil;
 | 
				
			||||||
import org.thingsboard.server.gen.transport.TransportProtos;
 | 
					import org.thingsboard.server.gen.transport.TransportProtos;
 | 
				
			||||||
@ -82,7 +82,6 @@ import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
 | 
				
			|||||||
import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
 | 
					import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
 | 
					import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.session.SparkplugNodeSessionHandler;
 | 
					import org.thingsboard.server.transport.mqtt.session.SparkplugNodeSessionHandler;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.ReturnCode;
 | 
					 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver;
 | 
					import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType;
 | 
					import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugRpcRequestHeader;
 | 
					import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugRpcRequestHeader;
 | 
				
			||||||
@ -196,23 +195,53 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                    processMqttMsg(ctx, message);
 | 
					                    processMqttMsg(ctx, message);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    log.error("[{}] Message decoding failed: {}", sessionId, message.decoderResult().cause().getMessage());
 | 
					                    log.error("[{}] Message decoding failed: {}", sessionId, message.decoderResult().cause().getMessage());
 | 
				
			||||||
                    closeCtx(ctx);
 | 
					                    closeCtx(ctx, MqttReasonCodes.Disconnect.MALFORMED_PACKET);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                log.debug("[{}] Received non mqtt message: {}", sessionId, msg.getClass().getSimpleName());
 | 
					                log.debug("[{}] Received non mqtt message: {}", sessionId, msg.getClass().getSimpleName());
 | 
				
			||||||
                closeCtx(ctx);
 | 
					                closeCtx(ctx, (MqttMessage) null);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
            ReferenceCountUtil.safeRelease(msg);
 | 
					            ReferenceCountUtil.safeRelease(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void closeCtx(ChannelHandlerContext ctx) {
 | 
					    private void closeCtx(ChannelHandlerContext ctx, MqttReasonCodes.Disconnect returnCode) {
 | 
				
			||||||
 | 
					        closeCtx(ctx, returnCode.byteValue());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void closeCtx(ChannelHandlerContext ctx, MqttConnectReturnCode returnCode) {
 | 
				
			||||||
 | 
					        closeCtx(ctx, ReturnCodeResolver.getConnectionReturnCode(deviceSessionCtx.getMqttVersion(), returnCode).byteValue());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void closeCtx(ChannelHandlerContext ctx, byte returnCode) {
 | 
				
			||||||
 | 
					        closeCtx(ctx, createMqttDisconnectMsg(deviceSessionCtx, returnCode));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void closeCtx(ChannelHandlerContext ctx, MqttMessage msg) {
 | 
				
			||||||
        if (!rpcAwaitingAck.isEmpty()) {
 | 
					        if (!rpcAwaitingAck.isEmpty()) {
 | 
				
			||||||
            log.debug("[{}] Cleanup RPC awaiting ack map due to session close!", sessionId);
 | 
					            log.debug("[{}] Cleanup RPC awaiting ack map due to session close!", sessionId);
 | 
				
			||||||
            rpcAwaitingAck.clear();
 | 
					            rpcAwaitingAck.clear();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ctx.close();
 | 
					
 | 
				
			||||||
 | 
					        if (ctx.channel() == null) {
 | 
				
			||||||
 | 
					            log.debug("[{}] Channel is null, closing ctx...", sessionId);
 | 
				
			||||||
 | 
					            ctx.close();
 | 
				
			||||||
 | 
					        } else if (ctx.channel().isOpen()) {
 | 
				
			||||||
 | 
					            if (msg != null && MqttVersion.MQTT_5 == deviceSessionCtx.getMqttVersion()) {
 | 
				
			||||||
 | 
					                ChannelFuture channelFuture = ctx.writeAndFlush(msg).addListener(future -> ctx.close());
 | 
				
			||||||
 | 
					                scheduler.schedule(() -> {
 | 
				
			||||||
 | 
					                    if (!channelFuture.isDone()) {
 | 
				
			||||||
 | 
					                        log.debug("[{}] Closing channel due to timeout!", sessionId);
 | 
				
			||||||
 | 
					                        ctx.close();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }, context.getDisconnectTimeout(), TimeUnit.MILLISECONDS);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                ctx.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            log.debug("[{}] Channel is already closed!", sessionId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    InetSocketAddress getAddress(ChannelHandlerContext ctx) {
 | 
					    InetSocketAddress getAddress(ChannelHandlerContext ctx) {
 | 
				
			||||||
@ -231,7 +260,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
    void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) {
 | 
					    void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) {
 | 
				
			||||||
        if (msg.fixedHeader() == null) {
 | 
					        if (msg.fixedHeader() == null) {
 | 
				
			||||||
            log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
 | 
					            log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.PROTOCOL_ERROR);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        deviceSessionCtx.setChannel(ctx);
 | 
					        deviceSessionCtx.setChannel(ctx);
 | 
				
			||||||
@ -268,21 +297,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
 | 
					                        log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
 | 
				
			||||||
                        closeCtx(ctx);
 | 
					                        ack(ctx, msgId, MqttReasonCodes.PubAck.TOPIC_NAME_INVALID);
 | 
				
			||||||
 | 
					                        closeCtx(ctx, MqttReasonCodes.Disconnect.TOPIC_NAME_INVALID);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } catch (RuntimeException e) {
 | 
					                } catch (RuntimeException e) {
 | 
				
			||||||
                    log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					                    log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
                    closeCtx(ctx);
 | 
					                    ack(ctx, msgId, MqttReasonCodes.PubAck.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
 | 
					                    closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
                } catch (AdaptorException e) {
 | 
					                } catch (AdaptorException e) {
 | 
				
			||||||
                    log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					                    log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
                    closeCtx(ctx);
 | 
					                    sendResponseForAdaptorErrorOrCloseContext(ctx, topicName, msgId);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case PINGREQ:
 | 
					            case PINGREQ:
 | 
				
			||||||
                ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
 | 
					                ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case DISCONNECT:
 | 
					            case DISCONNECT:
 | 
				
			||||||
                closeCtx(ctx);
 | 
					                closeCtx(ctx, MqttReasonCodes.Disconnect.NORMAL_DISCONNECT);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -292,7 +323,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) {
 | 
					        if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) {
 | 
				
			||||||
            log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}",
 | 
					            log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}",
 | 
				
			||||||
                    deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize());
 | 
					                    deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize());
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.QUOTA_EXCEEDED);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -329,7 +360,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case DISCONNECT:
 | 
					            case DISCONNECT:
 | 
				
			||||||
                closeCtx(ctx);
 | 
					                closeCtx(ctx, MqttReasonCodes.Disconnect.NORMAL_DISCONNECT);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case PUBACK:
 | 
					            case PUBACK:
 | 
				
			||||||
                int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId();
 | 
					                int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId();
 | 
				
			||||||
@ -389,15 +420,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                    gatewaySessionHandler.onDeviceDisconnect(mqttMsg);
 | 
					                    gatewaySessionHandler.onDeviceDisconnect(mqttMsg);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
                    ack(ctx, msgId, ReturnCode.TOPIC_NAME_INVALID);
 | 
					                    ack(ctx, msgId, MqttReasonCodes.PubAck.TOPIC_NAME_INVALID);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (RuntimeException e) {
 | 
					        } catch (RuntimeException e) {
 | 
				
			||||||
            log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					            log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
            ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC);
 | 
					            ack(ctx, msgId, MqttReasonCodes.PubAck.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
        } catch (AdaptorException e) {
 | 
					        } catch (AdaptorException e) {
 | 
				
			||||||
            log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					            log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
            sendAckOrCloseSession(ctx, topicName, msgId);
 | 
					            sendResponseForAdaptorErrorOrCloseContext(ctx, topicName, msgId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -433,11 +464,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (RuntimeException e) {
 | 
					        } catch (RuntimeException e) {
 | 
				
			||||||
            log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					            log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
            ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC);
 | 
					            ack(ctx, msgId, MqttReasonCodes.PubAck.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
        } catch (AdaptorException | ThingsboardException | InvalidProtocolBufferException e) {
 | 
					        } catch (AdaptorException | ThingsboardException | InvalidProtocolBufferException e) {
 | 
				
			||||||
            log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					            log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
            sendAckOrCloseSession(ctx, topicName, msgId);
 | 
					            sendResponseForAdaptorErrorOrCloseContext(ctx, topicName, msgId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -530,11 +561,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                attrReqTopicType = TopicType.V2;
 | 
					                attrReqTopicType = TopicType.V2;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                transportService.recordActivity(deviceSessionCtx.getSessionInfo());
 | 
					                transportService.recordActivity(deviceSessionCtx.getSessionInfo());
 | 
				
			||||||
                ack(ctx, msgId, ReturnCode.TOPIC_NAME_INVALID);
 | 
					                ack(ctx, msgId, MqttReasonCodes.PubAck.TOPIC_NAME_INVALID);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (AdaptorException e) {
 | 
					        } catch (AdaptorException e) {
 | 
				
			||||||
            log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
					            log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
 | 
				
			||||||
            sendAckOrCloseSession(ctx, topicName, msgId);
 | 
					            sendResponseForAdaptorErrorOrCloseContext(ctx, topicName, msgId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -548,13 +579,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void sendAckOrCloseSession(ChannelHandlerContext ctx, String topicName, int msgId) {
 | 
					    private void sendResponseForAdaptorErrorOrCloseContext(ChannelHandlerContext ctx, String topicName, int msgId) {
 | 
				
			||||||
        if ((deviceSessionCtx.isSendAckOnValidationException() || MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) && msgId > 0) {
 | 
					        if ((deviceSessionCtx.isSendAckOnValidationException() || MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) && msgId > 0) {
 | 
				
			||||||
            log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId);
 | 
					            log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId);
 | 
				
			||||||
            ctx.writeAndFlush(createMqttPubAckMsg(deviceSessionCtx, msgId, ReturnCode.PAYLOAD_FORMAT_INVALID));
 | 
					            ctx.writeAndFlush(createMqttPubAckMsg(deviceSessionCtx, msgId, MqttReasonCodes.PubAck.PAYLOAD_FORMAT_INVALID.byteValue()));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
 | 
					            log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.PAYLOAD_FORMAT_INVALID);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -593,7 +624,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void ack(ChannelHandlerContext ctx, int msgId, ReturnCode returnCode) {
 | 
					    private void ack(ChannelHandlerContext ctx, int msgId, MqttReasonCodes.PubAck returnCode) {
 | 
				
			||||||
 | 
					        ack(ctx, msgId, returnCode.byteValue());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void ack(ChannelHandlerContext ctx, int msgId, byte returnCode) {
 | 
				
			||||||
        if (msgId > 0) {
 | 
					        if (msgId > 0) {
 | 
				
			||||||
            ctx.writeAndFlush(createMqttPubAckMsg(deviceSessionCtx, msgId, returnCode));
 | 
					            ctx.writeAndFlush(createMqttPubAckMsg(deviceSessionCtx, msgId, returnCode));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -604,13 +639,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
            public void onSuccess(Void dummy) {
 | 
					            public void onSuccess(Void dummy) {
 | 
				
			||||||
                log.trace("[{}] Published msg: {}", sessionId, msg);
 | 
					                log.trace("[{}] Published msg: {}", sessionId, msg);
 | 
				
			||||||
                ack(ctx, msgId, ReturnCode.SUCCESS);
 | 
					                ack(ctx, msgId, MqttReasonCodes.PubAck.SUCCESS);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
            public void onError(Throwable e) {
 | 
					            public void onError(Throwable e) {
 | 
				
			||||||
                log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
 | 
					                log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
 | 
				
			||||||
                closeCtx(ctx);
 | 
					                closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -629,7 +664,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
 | 
					        public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
 | 
				
			||||||
            log.trace("[{}] Published msg: {}", sessionId, msg);
 | 
					            log.trace("[{}] Published msg: {}", sessionId, msg);
 | 
				
			||||||
            ack(ctx, msgId, ReturnCode.SUCCESS);
 | 
					            ack(ctx, msgId, MqttReasonCodes.PubAck.SUCCESS);
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) {
 | 
					                if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) {
 | 
				
			||||||
                    deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
 | 
					                    deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
 | 
				
			||||||
@ -645,8 +680,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onError(Throwable e) {
 | 
					        public void onError(Throwable e) {
 | 
				
			||||||
            log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
 | 
					            log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
 | 
				
			||||||
            ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC);
 | 
					            ack(ctx, msgId, MqttReasonCodes.PubAck.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -681,13 +716,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onError(Throwable e) {
 | 
					        public void onError(Throwable e) {
 | 
				
			||||||
            log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
 | 
					            log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) {
 | 
					    private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) {
 | 
				
			||||||
        log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
 | 
					        log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
 | 
				
			||||||
        ack(ctx, msgId, ReturnCode.SUCCESS);
 | 
					        ack(ctx, msgId, MqttReasonCodes.PubAck.SUCCESS);
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk);
 | 
					            byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk);
 | 
				
			||||||
            deviceSessionCtx.getPayloadAdaptor()
 | 
					            deviceSessionCtx.getPayloadAdaptor()
 | 
				
			||||||
@ -703,13 +738,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
 | 
					        deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
 | 
				
			||||||
                .getPayloadAdaptor()
 | 
					                .getPayloadAdaptor()
 | 
				
			||||||
                .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
 | 
					                .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
 | 
				
			||||||
        closeCtx(ctx);
 | 
					        closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
 | 
					    private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
 | 
				
			||||||
        if (!checkConnected(ctx, mqttMsg)) {
 | 
					        if (!checkConnected(ctx, mqttMsg)) {
 | 
				
			||||||
            int returnCode = ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), ReturnCode.NOT_AUTHORIZED_5);
 | 
					            ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), Collections.singletonList(MqttReasonCodes.SubAck.NOT_AUTHORIZED.byteValue() & 0xFF)));
 | 
				
			||||||
            ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), Collections.singletonList(returnCode)));
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
 | 
					        log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
 | 
				
			||||||
@ -718,7 +752,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
 | 
					        for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
 | 
				
			||||||
            String topic = subscription.topicName();
 | 
					            String topic = subscription.topicName();
 | 
				
			||||||
            MqttQoS reqQoS = subscription.qualityOfService();
 | 
					            MqttQoS reqQoS = subscription.qualityOfService();
 | 
				
			||||||
            if (deviceSessionCtx.isDeviceSubscriptionAttributesTopic(topic)){
 | 
					            if (deviceSessionCtx.isDeviceSubscriptionAttributesTopic(topic)) {
 | 
				
			||||||
                processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
 | 
					                processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
 | 
				
			||||||
                activityReported = true;
 | 
					                activityReported = true;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
@ -789,13 +823,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        default:
 | 
					                        default:
 | 
				
			||||||
                            log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
 | 
					                            log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
 | 
				
			||||||
                            grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), ReturnCode.TOPIC_FILTER_INVALID));
 | 
					                            grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), MqttReasonCodes.SubAck.TOPIC_FILTER_INVALID));
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
 | 
					                log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
 | 
				
			||||||
                grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), ReturnCode.IMPLEMENTATION_SPECIFIC));
 | 
					                grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), MqttReasonCodes.SubAck.IMPLEMENTATION_SPECIFIC_ERROR));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!activityReported) {
 | 
					        if (!activityReported) {
 | 
				
			||||||
@ -832,7 +866,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
 | 
					    private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
 | 
				
			||||||
        if (!checkConnected(ctx, mqttMsg)) {
 | 
					        if (!checkConnected(ctx, mqttMsg)) {
 | 
				
			||||||
            ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId(), Collections.singletonList(ReturnCode.NOT_AUTHORIZED_5.shortValue())));
 | 
					            ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId(),
 | 
				
			||||||
 | 
					                    Collections.singletonList((short) MqttReasonCodes.UnsubAck.NOT_AUTHORIZED.byteValue())));
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        boolean activityReported = false;
 | 
					        boolean activityReported = false;
 | 
				
			||||||
@ -843,7 +878,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            if (mqttQoSMap.containsKey(matcher)) {
 | 
					            if (mqttQoSMap.containsKey(matcher)) {
 | 
				
			||||||
                mqttQoSMap.remove(matcher);
 | 
					                mqttQoSMap.remove(matcher);
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    short resultValue = ReturnCode.SUCCESS.shortValue();
 | 
					                    short resultValue = MqttReasonCodes.UnsubAck.SUCCESS.byteValue();
 | 
				
			||||||
                    switch (topicName) {
 | 
					                    switch (topicName) {
 | 
				
			||||||
                        case MqttTopics.DEVICE_ATTRIBUTES_TOPIC:
 | 
					                        case MqttTopics.DEVICE_ATTRIBUTES_TOPIC:
 | 
				
			||||||
                        case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC:
 | 
					                        case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC:
 | 
				
			||||||
@ -884,16 +919,16 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        default:
 | 
					                        default:
 | 
				
			||||||
                            log.trace("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
					                            log.trace("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
				
			||||||
                            resultValue = ReturnCode.TOPIC_FILTER_INVALID.shortValue();
 | 
					                            resultValue = MqttReasonCodes.UnsubAck.TOPIC_FILTER_INVALID.byteValue();
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    unSubResults.add(resultValue);
 | 
					                    unSubResults.add(resultValue);
 | 
				
			||||||
                } catch (Exception e) {
 | 
					                } catch (Exception e) {
 | 
				
			||||||
                    log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
					                    log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
				
			||||||
                    unSubResults.add(ReturnCode.IMPLEMENTATION_SPECIFIC.shortValue());
 | 
					                    unSubResults.add((short) MqttReasonCodes.UnsubAck.IMPLEMENTATION_SPECIFIC_ERROR.byteValue());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                log.debug("[{}] Failed to process unsubscription [{}] to [{}] - Subscription not found", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
					                log.debug("[{}] Failed to process unsubscription [{}] to [{}] - Subscription not found", sessionId, mqttMsg.variableHeader().messageId(), topicName);
 | 
				
			||||||
                unSubResults.add(ReturnCode.NO_SUBSCRIPTION_EXISTED.shortValue());
 | 
					                unSubResults.add((short)MqttReasonCodes.UnsubAck.NO_SUBSCRIPTION_EXISTED.byteValue());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!activityReported) {
 | 
					        if (!activityReported) {
 | 
				
			||||||
@ -918,7 +953,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        deviceSessionCtx.setMqttVersion(getMqttVersion(msg.variableHeader().version()));
 | 
					        deviceSessionCtx.setMqttVersion(getMqttVersion(msg.variableHeader().version()));
 | 
				
			||||||
        if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) {
 | 
					        if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) {
 | 
				
			||||||
            deviceSessionCtx.setProvisionOnly(true);
 | 
					            deviceSessionCtx.setProvisionOnly(true);
 | 
				
			||||||
            ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg));
 | 
					            ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_ACCEPTED, msg));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            X509Certificate cert;
 | 
					            X509Certificate cert;
 | 
				
			||||||
            if (sslHandler != null && (cert = getX509Certificate()) != null) {
 | 
					            if (sslHandler != null && (cert = getX509Certificate()) != null) {
 | 
				
			||||||
@ -952,8 +987,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                    @Override
 | 
					                    @Override
 | 
				
			||||||
                    public void onError(Throwable e) {
 | 
					                    public void onError(Throwable e) {
 | 
				
			||||||
                        log.trace("[{}] Failed to process credentials: {}", address, userName, e);
 | 
					                        log.trace("[{}] Failed to process credentials: {}", address, userName, e);
 | 
				
			||||||
                        ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
 | 
					                        ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE_5, connectMessage));
 | 
				
			||||||
                        closeCtx(ctx);
 | 
					                        closeCtx(ctx, MqttReasonCodes.Disconnect.SERVER_BUSY);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -975,15 +1010,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                        @Override
 | 
					                        @Override
 | 
				
			||||||
                        public void onError(Throwable e) {
 | 
					                        public void onError(Throwable e) {
 | 
				
			||||||
                            log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
 | 
					                            log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
 | 
				
			||||||
                            ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
 | 
					                            ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE_5, connectMessage));
 | 
				
			||||||
                            closeCtx(ctx);
 | 
					                            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            context.onAuthFailure(address);
 | 
					            context.onAuthFailure(address);
 | 
				
			||||||
            ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage));
 | 
					            ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED_5, connectMessage));
 | 
				
			||||||
            log.trace("[{}] X509 auth failure: {}", sessionId, address, e);
 | 
					            log.trace("[{}] X509 auth failure: {}", sessionId, address, e);
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.NOT_AUTHORIZED);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1000,7 +1035,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) {
 | 
					    private MqttConnAckMessage createMqttConnAckMsg(MqttConnectReturnCode returnCode, MqttConnectMessage msg) {
 | 
				
			||||||
        MqttMessageBuilders.ConnAckBuilder connAckBuilder = MqttMessageBuilders.connAck();
 | 
					        MqttMessageBuilders.ConnAckBuilder connAckBuilder = MqttMessageBuilders.connAck();
 | 
				
			||||||
        connAckBuilder.sessionPresent(!msg.variableHeader().isCleanSession());
 | 
					        connAckBuilder.sessionPresent(!msg.variableHeader().isCleanSession());
 | 
				
			||||||
        MqttConnectReturnCode finalReturnCode = ReturnCodeResolver.getConnectionReturnCode(deviceSessionCtx.getMqttVersion(), returnCode);
 | 
					        MqttConnectReturnCode finalReturnCode = ReturnCodeResolver.getConnectionReturnCode(deviceSessionCtx.getMqttVersion(), returnCode);
 | 
				
			||||||
@ -1031,18 +1066,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            log.error("[{}] Unexpected Exception", sessionId, cause);
 | 
					            log.error("[{}] Unexpected Exception", sessionId, cause);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        closeCtx(ctx);
 | 
					        closeCtx(ctx, MqttReasonCodes.Disconnect.SERVER_SHUTTING_DOWN);
 | 
				
			||||||
        if (cause instanceof OutOfMemoryError) {
 | 
					        if (cause instanceof OutOfMemoryError) {
 | 
				
			||||||
            log.error("Received critical error. Going to shutdown the service.");
 | 
					            log.error("Received critical error. Going to shutdown the service.");
 | 
				
			||||||
            System.exit(1);
 | 
					            System.exit(1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
 | 
					    private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> reasonCodes) {
 | 
				
			||||||
        MqttFixedHeader mqttFixedHeader =
 | 
					        MqttFixedHeader mqttFixedHeader =
 | 
				
			||||||
                new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0);
 | 
					                new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0);
 | 
				
			||||||
        MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
 | 
					        MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
 | 
				
			||||||
        MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList);
 | 
					        MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(reasonCodes);
 | 
				
			||||||
        return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload);
 | 
					        return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1061,14 +1096,22 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static MqttMessage createMqttPubAckMsg(DeviceSessionCtx deviceSessionCtx, int requestId, ReturnCode returnCode) {
 | 
					    public static MqttMessage createMqttPubAckMsg(DeviceSessionCtx deviceSessionCtx, int requestId, byte returnCode) {
 | 
				
			||||||
        MqttMessageBuilders.PubAckBuilder pubAckMsgBuilder = MqttMessageBuilders.pubAck().packetId(requestId);
 | 
					        MqttMessageBuilders.PubAckBuilder pubAckMsgBuilder = MqttMessageBuilders.pubAck().packetId(requestId);
 | 
				
			||||||
        if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
					        if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
				
			||||||
            pubAckMsgBuilder.reasonCode(returnCode.byteValue());
 | 
					            pubAckMsgBuilder.reasonCode(returnCode);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return pubAckMsgBuilder.build();
 | 
					        return pubAckMsgBuilder.build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static MqttMessage createMqttDisconnectMsg(DeviceSessionCtx deviceSessionCtx, byte returnCode) {
 | 
				
			||||||
 | 
					        MqttMessageBuilders.DisconnectBuilder disconnectBuilder = MqttMessageBuilders.disconnect();
 | 
				
			||||||
 | 
					        if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
				
			||||||
 | 
					            disconnectBuilder.reasonCode(returnCode);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return disconnectBuilder.build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean checkConnected(ChannelHandlerContext ctx, MqttMessage msg) {
 | 
					    private boolean checkConnected(ChannelHandlerContext ctx, MqttMessage msg) {
 | 
				
			||||||
        if (deviceSessionCtx.isConnected()) {
 | 
					        if (deviceSessionCtx.isConnected()) {
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
@ -1115,8 +1158,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            log.trace("[{}][{}] Failed to fetch sparkplugDevice connect, sparkplugTopicName", sessionId, deviceSessionCtx.getDeviceInfo().getDeviceName(), e);
 | 
					            log.trace("[{}][{}] Failed to fetch sparkplugDevice connect, sparkplugTopicName", sessionId, deviceSessionCtx.getDeviceInfo().getDeviceName(), e);
 | 
				
			||||||
            ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
 | 
					            ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE_5, connectMessage));
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1160,20 +1203,20 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
    private void onValidateDeviceResponse(ValidateDeviceCredentialsResponse msg, ChannelHandlerContext ctx, MqttConnectMessage connectMessage) {
 | 
					    private void onValidateDeviceResponse(ValidateDeviceCredentialsResponse msg, ChannelHandlerContext ctx, MqttConnectMessage connectMessage) {
 | 
				
			||||||
        if (!msg.hasDeviceInfo()) {
 | 
					        if (!msg.hasDeviceInfo()) {
 | 
				
			||||||
            context.onAuthFailure(address);
 | 
					            context.onAuthFailure(address);
 | 
				
			||||||
            ReturnCode returnCode = ReturnCode.NOT_AUTHORIZED_5;
 | 
					            MqttConnectReturnCode returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED_5;
 | 
				
			||||||
            if (sslHandler == null || getX509Certificate() == null) {
 | 
					            if (sslHandler == null || getX509Certificate() == null) {
 | 
				
			||||||
                String username = connectMessage.payload().userName();
 | 
					                String username = connectMessage.payload().userName();
 | 
				
			||||||
                byte[] passwordBytes = connectMessage.payload().passwordInBytes();
 | 
					                byte[] passwordBytes = connectMessage.payload().passwordInBytes();
 | 
				
			||||||
                String clientId = connectMessage.payload().clientIdentifier();
 | 
					                String clientId = connectMessage.payload().clientIdentifier();
 | 
				
			||||||
                if ((username != null && passwordBytes != null && clientId != null)
 | 
					                if ((username != null && passwordBytes != null && clientId != null)
 | 
				
			||||||
                        || (username == null ^ passwordBytes == null)) {
 | 
					                        || (username == null ^ passwordBytes == null)) {
 | 
				
			||||||
                    returnCode = ReturnCode.BAD_USERNAME_OR_PASSWORD;
 | 
					                    returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD;
 | 
				
			||||||
                } else if (!StringUtils.isBlank(clientId)) {
 | 
					                } else if (!StringUtils.isBlank(clientId)) {
 | 
				
			||||||
                    returnCode = ReturnCode.CLIENT_IDENTIFIER_NOT_VALID;
 | 
					                    returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ctx.writeAndFlush(createMqttConnAckMsg(returnCode, connectMessage));
 | 
					            ctx.writeAndFlush(createMqttConnAckMsg(returnCode, connectMessage));
 | 
				
			||||||
            closeCtx(ctx);
 | 
					            closeCtx(ctx, returnCode);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            context.onAuthSuccess(address);
 | 
					            context.onAuthSuccess(address);
 | 
				
			||||||
            deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo());
 | 
					            deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo());
 | 
				
			||||||
@ -1188,7 +1231,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        checkGatewaySession(sessionMetaData);
 | 
					                        checkGatewaySession(sessionMetaData);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, connectMessage));
 | 
					                    ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_ACCEPTED, connectMessage));
 | 
				
			||||||
                    deviceSessionCtx.setConnected(true);
 | 
					                    deviceSessionCtx.setConnected(true);
 | 
				
			||||||
                    log.debug("[{}] Client connected!", sessionId);
 | 
					                    log.debug("[{}] Client connected!", sessionId);
 | 
				
			||||||
                    transportService.getCallbackExecutor().execute(() -> processMsgQueue(ctx)); //this callback will execute in Producer worker thread and hard or blocking work have to be submitted to the separate thread.
 | 
					                    transportService.getCallbackExecutor().execute(() -> processMsgQueue(ctx)); //this callback will execute in Producer worker thread and hard or blocking work have to be submitted to the separate thread.
 | 
				
			||||||
@ -1198,11 +1241,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                public void onError(Throwable e) {
 | 
					                public void onError(Throwable e) {
 | 
				
			||||||
                    if (e instanceof TbRateLimitsException) {
 | 
					                    if (e instanceof TbRateLimitsException) {
 | 
				
			||||||
                        log.trace("[{}] Failed to submit session event: {}", sessionId, e.getMessage());
 | 
					                        log.trace("[{}] Failed to submit session event: {}", sessionId, e.getMessage());
 | 
				
			||||||
 | 
					                        ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_CONNECTION_RATE_EXCEEDED, connectMessage));
 | 
				
			||||||
 | 
					                        closeCtx(ctx, MqttReasonCodes.Disconnect.MESSAGE_RATE_TOO_HIGH);
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        log.warn("[{}] Failed to submit session event", sessionId, e);
 | 
					                        log.warn("[{}] Failed to submit session event", sessionId, e);
 | 
				
			||||||
 | 
					                        ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE_5, connectMessage));
 | 
				
			||||||
 | 
					                        closeCtx(ctx, MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
 | 
					 | 
				
			||||||
                    closeCtx(ctx);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -1231,8 +1276,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
                        SparkplugTopic sparkplugTopic = new SparkplugTopic(sparkplugSessionHandler.getSparkplugTopicNode(),
 | 
					                        SparkplugTopic sparkplugTopic = new SparkplugTopic(sparkplugSessionHandler.getSparkplugTopicNode(),
 | 
				
			||||||
                                SparkplugMessageType.NCMD);
 | 
					                                SparkplugMessageType.NCMD);
 | 
				
			||||||
                        sparkplugSessionHandler.createSparkplugMqttPublishMsg(tsKvProto,
 | 
					                        sparkplugSessionHandler.createSparkplugMqttPublishMsg(tsKvProto,
 | 
				
			||||||
                                sparkplugTopic.toString(),
 | 
					                                        sparkplugTopic.toString(),
 | 
				
			||||||
                                sparkplugSessionHandler.getNodeBirthMetrics().get(tsKvProto.getKv().getKey()))
 | 
					                                        sparkplugSessionHandler.getNodeBirthMetrics().get(tsKvProto.getKv().getKey()))
 | 
				
			||||||
                                .ifPresent(sparkplugSessionHandler::writeAndFlush);
 | 
					                                .ifPresent(sparkplugSessionHandler::writeAndFlush);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
@ -1250,7 +1295,19 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
    public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
 | 
					    public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
 | 
				
			||||||
        log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
 | 
					        log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
 | 
				
			||||||
        transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
 | 
					        transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
 | 
				
			||||||
        closeCtx(deviceSessionCtx.getChannel());
 | 
					        MqttReasonCodes.Disconnect returnCode = MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR;
 | 
				
			||||||
 | 
					        switch (sessionCloseNotification.getReason()) {
 | 
				
			||||||
 | 
					            case CREDENTIALS_UPDATED:
 | 
				
			||||||
 | 
					                returnCode = MqttReasonCodes.Disconnect.ADMINISTRATIVE_ACTION;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case MAX_CONCURRENT_SESSIONS_LIMIT_REACHED:
 | 
				
			||||||
 | 
					                returnCode = MqttReasonCodes.Disconnect.SESSION_TAKEN_OVER;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case SESSION_TIMEOUT:
 | 
				
			||||||
 | 
					                returnCode = MqttReasonCodes.Disconnect.MAXIMUM_CONNECT_TIME;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        closeCtx(deviceSessionCtx.getChannel(), returnCode);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -1287,8 +1344,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
            SparkplugTopic sparkplugTopic = new SparkplugTopic(sparkplugSessionHandler.getSparkplugTopicNode(),
 | 
					            SparkplugTopic sparkplugTopic = new SparkplugTopic(sparkplugSessionHandler.getSparkplugTopicNode(),
 | 
				
			||||||
                    messageType);
 | 
					                    messageType);
 | 
				
			||||||
            sparkplugSessionHandler.createSparkplugMqttPublishMsg(tsKvProto,
 | 
					            sparkplugSessionHandler.createSparkplugMqttPublishMsg(tsKvProto,
 | 
				
			||||||
                    sparkplugTopic.toString(),
 | 
					                            sparkplugTopic.toString(),
 | 
				
			||||||
                    sparkplugSessionHandler.getNodeBirthMetrics().get(tsKvProto.getKv().getKey()))
 | 
					                            sparkplugSessionHandler.getNodeBirthMetrics().get(tsKvProto.getKv().getKey()))
 | 
				
			||||||
                    .ifPresent(payload -> sendToDeviceRpcRequest(payload, rpcRequest, deviceSessionCtx.getSessionInfo()));
 | 
					                    .ifPresent(payload -> sendToDeviceRpcRequest(payload, rpcRequest, deviceSessionCtx.getSessionInfo()));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            sendErrorRpcResponse(deviceSessionCtx.getSessionInfo(), rpcRequest.getRequestId(),
 | 
					            sendErrorRpcResponse(deviceSessionCtx.getSessionInfo(), rpcRequest.getRequestId(),
 | 
				
			||||||
@ -1369,7 +1426,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
				
			|||||||
    public void onDeviceDeleted(DeviceId deviceId) {
 | 
					    public void onDeviceDeleted(DeviceId deviceId) {
 | 
				
			||||||
        context.onAuthFailure(address);
 | 
					        context.onAuthFailure(address);
 | 
				
			||||||
        ChannelHandlerContext ctx = deviceSessionCtx.getChannel();
 | 
					        ChannelHandlerContext ctx = deviceSessionCtx.getChannel();
 | 
				
			||||||
        closeCtx(ctx);
 | 
					        closeCtx(ctx, MqttReasonCodes.Disconnect.ADMINISTRATIVE_ACTION);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, ThingsboardErrorCode result, String errorMsg) {
 | 
					    public void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, ThingsboardErrorCode result, String errorMsg) {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,8 +20,8 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
				
			|||||||
import lombok.AllArgsConstructor;
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.thingsboard.server.common.data.ota.OtaPackageType;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.adaptor.AdaptorException;
 | 
					import org.thingsboard.server.common.adaptor.AdaptorException;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.ota.OtaPackageType;
 | 
				
			||||||
import org.thingsboard.server.gen.transport.TransportProtos;
 | 
					import org.thingsboard.server.gen.transport.TransportProtos;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
 | 
					import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -146,6 +146,12 @@ public class BackwardCompatibilityAdaptor implements MqttTransportAdaptor {
 | 
				
			|||||||
        return Optional.empty();
 | 
					        return Optional.empty();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Optional<MqttMessage> convertToGatewayDeviceDisconnectPublish(MqttDeviceAwareSessionContext ctx, String deviceName, int reasonCode) throws AdaptorException {
 | 
				
			||||||
 | 
					        log.warn("[{}] invoked not implemented adaptor method! Device name: {} ReasonCode: {}", ctx.getSessionId(), deviceName, reasonCode);
 | 
				
			||||||
 | 
					        return Optional.empty();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
 | 
					    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
 | 
				
			||||||
        return protoAdaptor.convertToPublish(ctx, firmwareChunk, requestId, chunk, firmwareType);
 | 
					        return protoAdaptor.convertToPublish(ctx, firmwareChunk, requestId, chunk, firmwareType);
 | 
				
			||||||
 | 
				
			|||||||
@ -154,6 +154,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
				
			|||||||
        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
 | 
					        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Optional<MqttMessage> convertToGatewayDeviceDisconnectPublish(MqttDeviceAwareSessionContext ctx, String deviceName, int reasonCode) {
 | 
				
			||||||
 | 
					        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_DISCONNECT_TOPIC, JsonConverter.toGatewayDeviceDisconnectJson(deviceName, reasonCode)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) {
 | 
					    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) {
 | 
				
			||||||
        return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
 | 
					        return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,8 @@ public interface MqttTransportAdaptor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException;
 | 
					    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Optional<MqttMessage> convertToGatewayDeviceDisconnectPublish(MqttDeviceAwareSessionContext ctx, String deviceName, int reasonCode) throws AdaptorException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) {
 | 
					    default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) {
 | 
				
			||||||
        MqttFixedHeader mqttFixedHeader =
 | 
					        MqttFixedHeader mqttFixedHeader =
 | 
				
			||||||
                new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
 | 
					                new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
 | 
				
			||||||
 | 
				
			|||||||
@ -173,6 +173,15 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
 | 
				
			|||||||
        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
 | 
					        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Optional<MqttMessage> convertToGatewayDeviceDisconnectPublish(MqttDeviceAwareSessionContext ctx, String deviceName, int reasonCode) {
 | 
				
			||||||
 | 
					        TransportProtos.GatewayDisconnectDeviceMsg gatewayDeviceDisconnectMsg = TransportProtos.GatewayDisconnectDeviceMsg.newBuilder()
 | 
				
			||||||
 | 
					                .setDeviceName(deviceName)
 | 
				
			||||||
 | 
					                .setReasonCode(reasonCode)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_DISCONNECT_TOPIC, gatewayDeviceDisconnectMsg.toByteArray()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
 | 
					    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
 | 
				
			||||||
        return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
 | 
					        return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ import io.netty.channel.ChannelFuture;
 | 
				
			|||||||
import io.netty.channel.ChannelHandlerContext;
 | 
					import io.netty.channel.ChannelHandlerContext;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttMessage;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
					import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttReasonCodes;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttVersion;
 | 
					import io.netty.handler.codec.mqtt.MqttVersion;
 | 
				
			||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
import lombok.Setter;
 | 
					import lombok.Setter;
 | 
				
			||||||
@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.Device;
 | 
				
			|||||||
import org.thingsboard.server.common.data.DeviceProfile;
 | 
					import org.thingsboard.server.common.data.DeviceProfile;
 | 
				
			||||||
import org.thingsboard.server.common.data.StringUtils;
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
					import org.thingsboard.server.common.data.id.DeviceId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
 | 
				
			||||||
import org.thingsboard.server.common.transport.TransportService;
 | 
					import org.thingsboard.server.common.transport.TransportService;
 | 
				
			||||||
import org.thingsboard.server.common.transport.TransportServiceCallback;
 | 
					import org.thingsboard.server.common.transport.TransportServiceCallback;
 | 
				
			||||||
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
 | 
					import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
 | 
				
			||||||
@ -60,7 +62,6 @@ import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
 | 
				
			|||||||
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
 | 
					import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
 | 
					import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor;
 | 
					import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor;
 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.ReturnCode;
 | 
					 | 
				
			||||||
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState;
 | 
					import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
@ -84,7 +85,6 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe
 | 
				
			|||||||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
 | 
					import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
 | 
				
			||||||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG;
 | 
					import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG;
 | 
				
			||||||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG;
 | 
					import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG;
 | 
				
			||||||
import static org.thingsboard.server.transport.mqtt.util.ReturnCode.PAYLOAD_FORMAT_INVALID;
 | 
					 | 
				
			||||||
import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState.OFFLINE;
 | 
					import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState.OFFLINE;
 | 
				
			||||||
import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType.STATE;
 | 
					import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType.STATE;
 | 
				
			||||||
import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType.messageName;
 | 
					import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType.messageName;
 | 
				
			||||||
@ -230,7 +230,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
        log.trace("[{}][{}][{}] onDeviceConnect: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName);
 | 
					        log.trace("[{}][{}][{}] onDeviceConnect: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName);
 | 
				
			||||||
        process(onDeviceConnect(deviceName, deviceType),
 | 
					        process(onDeviceConnect(deviceName, deviceType),
 | 
				
			||||||
                result -> {
 | 
					                result -> {
 | 
				
			||||||
                    ack(msg, ReturnCode.SUCCESS);
 | 
					                    ack(msg, MqttReasonCodes.PubAck.SUCCESS);
 | 
				
			||||||
                    log.trace("[{}][{}][{}] onDeviceConnectOk: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName);
 | 
					                    log.trace("[{}][{}][{}] onDeviceConnectOk: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName);
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                t -> logDeviceCreationError(t, deviceName));
 | 
					                t -> logDeviceCreationError(t, deviceName));
 | 
				
			||||||
@ -361,7 +361,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void processOnDisconnect(MqttPublishMessage msg, String deviceName) {
 | 
					    void processOnDisconnect(MqttPublishMessage msg, String deviceName) {
 | 
				
			||||||
        deregisterSession(deviceName);
 | 
					        deregisterSession(deviceName);
 | 
				
			||||||
        ack(msg, ReturnCode.SUCCESS);
 | 
					        ack(msg, MqttReasonCodes.PubAck.SUCCESS);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void onDeviceTelemetryJson(int msgId, ByteBuf payload) throws AdaptorException {
 | 
					    protected void onDeviceTelemetryJson(int msgId, ByteBuf payload) throws AdaptorException {
 | 
				
			||||||
@ -618,7 +618,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
        process(deviceName, deviceCtx -> processGetAttributeRequestMessage(deviceCtx, requestMsg, deviceName, msgId),
 | 
					        process(deviceName, deviceCtx -> processGetAttributeRequestMessage(deviceCtx, requestMsg, deviceName, msgId),
 | 
				
			||||||
                t -> {
 | 
					                t -> {
 | 
				
			||||||
                    failedToProcessLog(deviceName, ATTRIBUTES_REQUEST, t);
 | 
					                    failedToProcessLog(deviceName, ATTRIBUTES_REQUEST, t);
 | 
				
			||||||
                    ack(msgId, ReturnCode.IMPLEMENTATION_SPECIFIC);
 | 
					                    ack(mqttMsg, MqttReasonCodes.PubAck.IMPLEMENTATION_SPECIFIC_ERROR);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -663,20 +663,20 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
        return ProtoMqttAdaptor.toBytes(payload);
 | 
					        return ProtoMqttAdaptor.toBytes(payload);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void ack(MqttPublishMessage msg, ReturnCode returnCode) {
 | 
					    protected void ack(MqttPublishMessage msg, MqttReasonCodes.PubAck returnCode) {
 | 
				
			||||||
        int msgId = getMsgId(msg);
 | 
					        int msgId = getMsgId(msg);
 | 
				
			||||||
        ack(msgId, returnCode);
 | 
					        ack(msgId, returnCode);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void ack(int msgId, ReturnCode returnCode) {
 | 
					    protected void ack(int msgId, MqttReasonCodes.PubAck returnCode) {
 | 
				
			||||||
        if (msgId > 0) {
 | 
					        if (msgId > 0) {
 | 
				
			||||||
            writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(deviceSessionCtx, msgId, returnCode));
 | 
					            writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(deviceSessionCtx, msgId, returnCode.byteValue()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void ackOrClose(int msgId) {
 | 
					    protected void ackOrClose(int msgId) {
 | 
				
			||||||
        if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
					        if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
				
			||||||
            ack(msgId, PAYLOAD_FORMAT_INVALID);
 | 
					            ack(msgId, MqttReasonCodes.PubAck.PAYLOAD_FORMAT_INVALID);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            channel.close();
 | 
					            channel.close();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -707,13 +707,22 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
            public void onSuccess(Void dummy) {
 | 
					            public void onSuccess(Void dummy) {
 | 
				
			||||||
                log.trace("[{}][{}][{}][{}] Published msg: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, msg);
 | 
					                log.trace("[{}][{}][{}][{}] Published msg: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, msg);
 | 
				
			||||||
                if (msgId > 0) {
 | 
					                if (msgId > 0) {
 | 
				
			||||||
                    ctx.writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(deviceSessionCtx, msgId, ReturnCode.SUCCESS));
 | 
					                    ctx.writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(deviceSessionCtx, msgId, MqttReasonCodes.PubAck.SUCCESS.byteValue()));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    log.trace("[{}][{}][{}] Wrong msg id: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, msg);
 | 
				
			||||||
 | 
					                    ctx.writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(deviceSessionCtx, msgId, MqttReasonCodes.PubAck.UNSPECIFIED_ERROR.byteValue()));
 | 
				
			||||||
 | 
					                    closeDeviceSession(deviceName, MqttReasonCodes.Disconnect.MALFORMED_PACKET);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
            public void onError(Throwable e) {
 | 
					            public void onError(Throwable e) {
 | 
				
			||||||
                log.trace("[{}][{}][{}] Failed to publish msg: [{}] for device: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, msg, deviceName, e);
 | 
					                log.trace("[{}][{}][{}] Failed to publish msg: [{}] for device: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, msg, deviceName, e);
 | 
				
			||||||
 | 
					                if (e instanceof TbRateLimitsException) {
 | 
				
			||||||
 | 
					                    closeDeviceSession(deviceName, MqttReasonCodes.Disconnect.MESSAGE_RATE_TOO_HIGH);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    closeDeviceSession(deviceName, MqttReasonCodes.Disconnect.UNSPECIFIED_ERROR);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                ctx.close();
 | 
					                ctx.close();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
@ -737,4 +746,17 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
				
			|||||||
        log.debug("[{}][{}][{}] Failed to process device {} command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, msgType, deviceName, t);
 | 
					        log.debug("[{}][{}][{}] Failed to process device {} command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, msgType, deviceName, t);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void closeDeviceSession(String deviceName, MqttReasonCodes.Disconnect returnCode) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) {
 | 
				
			||||||
 | 
					                MqttTransportAdaptor adaptor = deviceSessionCtx.getPayloadAdaptor();
 | 
				
			||||||
 | 
					                int returnCodeValue = returnCode.byteValue() & 0xFF;
 | 
				
			||||||
 | 
					                Optional<MqttMessage> deviceDisconnectPublishMsg = adaptor.convertToGatewayDeviceDisconnectPublish(deviceSessionCtx, deviceName, returnCodeValue);
 | 
				
			||||||
 | 
					                deviceDisconnectPublishMsg.ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            log.trace("Failed to send device disconnect to gateway session", e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,104 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * Copyright © 2016-2024 The Thingsboard Authors
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * 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.thingsboard.server.transport.mqtt.util;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public enum ReturnCode {
 | 
					 | 
				
			||||||
    SUCCESS((byte) 0x00),
 | 
					 | 
				
			||||||
    //MQTT 3 codes
 | 
					 | 
				
			||||||
    UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01),
 | 
					 | 
				
			||||||
    IDENTIFIER_REJECTED((byte) 0x02),
 | 
					 | 
				
			||||||
    SERVER_UNAVAILABLE((byte) 0x03),
 | 
					 | 
				
			||||||
    BAD_USER_NAME_OR_PASSWORD((byte) 0x04),
 | 
					 | 
				
			||||||
    NOT_AUTHORIZED((byte) 0x05),
 | 
					 | 
				
			||||||
    //MQTT 5 codes
 | 
					 | 
				
			||||||
    NO_MATCHING_SUBSCRIBERS((byte) 0x10),
 | 
					 | 
				
			||||||
    NO_SUBSCRIPTION_EXISTED((byte) 0x11),
 | 
					 | 
				
			||||||
    CONTINUE_AUTHENTICATION((byte) 0x18),
 | 
					 | 
				
			||||||
    REAUTHENTICATE((byte) 0x19),
 | 
					 | 
				
			||||||
    UNSPECIFIED_ERROR((byte) 0x80),
 | 
					 | 
				
			||||||
    MALFORMED_PACKET((byte) 0x81),
 | 
					 | 
				
			||||||
    PROTOCOL_ERROR((byte) 0x82),
 | 
					 | 
				
			||||||
    IMPLEMENTATION_SPECIFIC((byte) 0x83),
 | 
					 | 
				
			||||||
    UNSUPPORTED_PROTOCOL_VERSION((byte) 0x84),
 | 
					 | 
				
			||||||
    CLIENT_IDENTIFIER_NOT_VALID((byte) 0x85),
 | 
					 | 
				
			||||||
    BAD_USERNAME_OR_PASSWORD((byte) 0x86),
 | 
					 | 
				
			||||||
    NOT_AUTHORIZED_5((byte) 0x87),
 | 
					 | 
				
			||||||
    SERVER_UNAVAILABLE_5((byte) 0x88),
 | 
					 | 
				
			||||||
    SERVER_BUSY((byte) 0x89),
 | 
					 | 
				
			||||||
    BANNED((byte) 0x8A),
 | 
					 | 
				
			||||||
    SERVER_SHUTTING_DOWN((byte) 0x8B),
 | 
					 | 
				
			||||||
    BAD_AUTHENTICATION_METHOD((byte) 0x8C),
 | 
					 | 
				
			||||||
    KEEP_ALIVE_TIMEOUT((byte) 0x8D),
 | 
					 | 
				
			||||||
    SESSION_TAKEN_OVER((byte) 0x8E),
 | 
					 | 
				
			||||||
    TOPIC_FILTER_INVALID((byte) 0x8F),
 | 
					 | 
				
			||||||
    TOPIC_NAME_INVALID((byte) 0x90),
 | 
					 | 
				
			||||||
    PACKET_IDENTIFIER_IN_USE((byte) 0x91),
 | 
					 | 
				
			||||||
    PACKET_IDENTIFIER_NOT_FOUND((byte) 0x92),
 | 
					 | 
				
			||||||
    RECEIVE_MAXIMUM_EXCEEDED((byte) 0x93),
 | 
					 | 
				
			||||||
    TOPIC_ALIAS_INVALID((byte) 0x94),
 | 
					 | 
				
			||||||
    PACKET_TOO_LARGE((byte) 0x95),
 | 
					 | 
				
			||||||
    MESSAGE_RATE_TOO_HIGH((byte) 0x96),
 | 
					 | 
				
			||||||
    QUOTA_EXCEEDED((byte) 0x97),
 | 
					 | 
				
			||||||
    ADMINISTRATIVE_ACTION((byte) 0x98),
 | 
					 | 
				
			||||||
    PAYLOAD_FORMAT_INVALID((byte) 0x99),
 | 
					 | 
				
			||||||
    RETAIN_NOT_SUPPORTED((byte) 0x9A),
 | 
					 | 
				
			||||||
    QOS_NOT_SUPPORTED((byte) 0x9B),
 | 
					 | 
				
			||||||
    USE_ANOTHER_SERVER((byte) 0x9C),
 | 
					 | 
				
			||||||
    SERVER_MOVED((byte) 0x9D),
 | 
					 | 
				
			||||||
    SHARED_SUBSCRIPTION_NOT_SUPPORTED((byte) 0x9E),
 | 
					 | 
				
			||||||
    CONNECTION_RATE_EXCEEDED((byte) 0x9F),
 | 
					 | 
				
			||||||
    MAXIMUM_CONNECT_TIME((byte) 0xA0),
 | 
					 | 
				
			||||||
    SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1),
 | 
					 | 
				
			||||||
    WILDCARD_SUBSCRIPTION_NOT_SUPPORTED((byte) 0xA2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final ReturnCode[] VALUES;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static {
 | 
					 | 
				
			||||||
        ReturnCode[] values = values();
 | 
					 | 
				
			||||||
        VALUES = new ReturnCode[163];
 | 
					 | 
				
			||||||
        for (ReturnCode code : values) {
 | 
					 | 
				
			||||||
            final int unsignedByte = code.byteValue & 0xFF;
 | 
					 | 
				
			||||||
            // Suppress a warning about out of bounds access since the enum contains only correct values
 | 
					 | 
				
			||||||
            VALUES[unsignedByte] = code;    // lgtm [java/index-out-of-bounds]
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final byte byteValue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ReturnCode(byte byteValue) {
 | 
					 | 
				
			||||||
        this.byteValue = byteValue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public byte byteValue() {
 | 
					 | 
				
			||||||
        return byteValue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public short shortValue(){return byteValue;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static ReturnCode valueOf(byte b) {
 | 
					 | 
				
			||||||
        final int unsignedByte = b & 0xFF;
 | 
					 | 
				
			||||||
        ReturnCode mqttConnectReturnCode = null;
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            mqttConnectReturnCode = VALUES[unsignedByte];
 | 
					 | 
				
			||||||
        } catch (ArrayIndexOutOfBoundsException ignored) {
 | 
					 | 
				
			||||||
            // no op
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (mqttConnectReturnCode == null) {
 | 
					 | 
				
			||||||
            throw new IllegalArgumentException("unknown connect return code: " + unsignedByte);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return mqttConnectReturnCode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -17,22 +17,24 @@ package org.thingsboard.server.transport.mqtt.util;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
 | 
					import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttQoS;
 | 
					import io.netty.handler.codec.mqtt.MqttQoS;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttReasonCodes;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttVersion;
 | 
					import io.netty.handler.codec.mqtt.MqttVersion;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class ReturnCodeResolver {
 | 
					public class ReturnCodeResolver {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static MqttConnectReturnCode getConnectionReturnCode(MqttVersion mqttVersion, ReturnCode returnCode) {
 | 
					    public static MqttConnectReturnCode getConnectionReturnCode(MqttVersion mqttVersion, MqttConnectReturnCode returnCode) {
 | 
				
			||||||
        if (!MqttVersion.MQTT_5.equals(mqttVersion) && !ReturnCode.SUCCESS.equals(returnCode)) {
 | 
					        if (!MqttVersion.MQTT_5.equals(mqttVersion) && !MqttConnectReturnCode.CONNECTION_ACCEPTED.equals(returnCode)) {
 | 
				
			||||||
            switch (returnCode) {
 | 
					            switch (returnCode) {
 | 
				
			||||||
                case BAD_USERNAME_OR_PASSWORD:
 | 
					                case CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD:
 | 
				
			||||||
                case NOT_AUTHORIZED_5:
 | 
					                case CONNECTION_REFUSED_NOT_AUTHORIZED_5:
 | 
				
			||||||
                    return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
 | 
					                    return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
 | 
				
			||||||
                case SERVER_UNAVAILABLE_5:
 | 
					                case CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID:
 | 
				
			||||||
                    return MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE;
 | 
					 | 
				
			||||||
                case CLIENT_IDENTIFIER_NOT_VALID:
 | 
					 | 
				
			||||||
                    return MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED;
 | 
					                    return MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED;
 | 
				
			||||||
 | 
					                case CONNECTION_REFUSED_SERVER_UNAVAILABLE_5:
 | 
				
			||||||
 | 
					                case CONNECTION_REFUSED_CONNECTION_RATE_EXCEEDED:
 | 
				
			||||||
 | 
					                    return MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE;
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
                    log.warn("Unknown return code for conversion: {}", returnCode.name());
 | 
					                    log.warn("Unknown return code for conversion: {}", returnCode.name());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -40,18 +42,20 @@ public class ReturnCodeResolver {
 | 
				
			|||||||
        return MqttConnectReturnCode.valueOf(returnCode.byteValue());
 | 
					        return MqttConnectReturnCode.valueOf(returnCode.byteValue());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static int getSubscriptionReturnCode(MqttVersion mqttVersion, ReturnCode returnCode) {
 | 
					    public static int getSubscriptionReturnCode(MqttVersion mqttVersion, MqttReasonCodes.SubAck returnCode) {
 | 
				
			||||||
        if (!MqttVersion.MQTT_5.equals(mqttVersion) && !ReturnCode.SUCCESS.equals(returnCode)) {
 | 
					        if (!MqttVersion.MQTT_5.equals(mqttVersion) && !(MqttReasonCodes.SubAck.GRANTED_QOS_0.equals(returnCode) ||
 | 
				
			||||||
 | 
					                MqttReasonCodes.SubAck.GRANTED_QOS_1.equals(returnCode) ||
 | 
				
			||||||
 | 
					                MqttReasonCodes.SubAck.GRANTED_QOS_2.equals(returnCode))) {
 | 
				
			||||||
            switch (returnCode) {
 | 
					            switch (returnCode) {
 | 
				
			||||||
                case UNSPECIFIED_ERROR:
 | 
					                case UNSPECIFIED_ERROR:
 | 
				
			||||||
                case TOPIC_FILTER_INVALID:
 | 
					                case TOPIC_FILTER_INVALID:
 | 
				
			||||||
                case IMPLEMENTATION_SPECIFIC:
 | 
					                case IMPLEMENTATION_SPECIFIC_ERROR:
 | 
				
			||||||
                case NOT_AUTHORIZED_5:
 | 
					                case NOT_AUTHORIZED:
 | 
				
			||||||
                case PACKET_IDENTIFIER_IN_USE:
 | 
					                case PACKET_IDENTIFIER_IN_USE:
 | 
				
			||||||
                case QUOTA_EXCEEDED:
 | 
					                case QUOTA_EXCEEDED:
 | 
				
			||||||
                case SHARED_SUBSCRIPTION_NOT_SUPPORTED:
 | 
					                case SHARED_SUBSCRIPTIONS_NOT_SUPPORTED:
 | 
				
			||||||
                case SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED:
 | 
					                case SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED:
 | 
				
			||||||
                case WILDCARD_SUBSCRIPTION_NOT_SUPPORTED:
 | 
					                case WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED:
 | 
				
			||||||
                    return MqttQoS.FAILURE.value();
 | 
					                    return MqttQoS.FAILURE.value();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,8 @@ import java.util.Random;
 | 
				
			|||||||
@Listeners(TestListener.class)
 | 
					@Listeners(TestListener.class)
 | 
				
			||||||
public abstract class AbstractContainerTest {
 | 
					public abstract class AbstractContainerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static final int TIMEOUT = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected final static String TEST_PROVISION_DEVICE_KEY = "test_provision_key";
 | 
					    protected final static String TEST_PROVISION_DEVICE_KEY = "test_provision_key";
 | 
				
			||||||
    protected final static String TEST_PROVISION_DEVICE_SECRET = "test_provision_secret";
 | 
					    protected final static String TEST_PROVISION_DEVICE_SECRET = "test_provision_secret";
 | 
				
			||||||
    protected static long timeoutMultiplier = 1;
 | 
					    protected static long timeoutMultiplier = 1;
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,12 @@ import com.google.common.util.concurrent.MoreExecutors;
 | 
				
			|||||||
import com.google.gson.JsonObject;
 | 
					import com.google.gson.JsonObject;
 | 
				
			||||||
import io.netty.buffer.ByteBuf;
 | 
					import io.netty.buffer.ByteBuf;
 | 
				
			||||||
import io.netty.buffer.Unpooled;
 | 
					import io.netty.buffer.Unpooled;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttMessage;
 | 
				
			||||||
import io.netty.handler.codec.mqtt.MqttQoS;
 | 
					import io.netty.handler.codec.mqtt.MqttQoS;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttReasonCodeAndPropertiesVariableHeader;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttReasonCodes;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttVersion;
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.awaitility.Awaitility;
 | 
					import org.awaitility.Awaitility;
 | 
				
			||||||
@ -33,6 +38,7 @@ import org.testng.annotations.Test;
 | 
				
			|||||||
import org.thingsboard.common.util.AbstractListeningExecutor;
 | 
					import org.thingsboard.common.util.AbstractListeningExecutor;
 | 
				
			||||||
import org.thingsboard.common.util.ThingsBoardThreadFactory;
 | 
					import org.thingsboard.common.util.ThingsBoardThreadFactory;
 | 
				
			||||||
import org.thingsboard.mqtt.MqttClient;
 | 
					import org.thingsboard.mqtt.MqttClient;
 | 
				
			||||||
 | 
					import org.thingsboard.mqtt.MqttClientCallback;
 | 
				
			||||||
import org.thingsboard.mqtt.MqttClientConfig;
 | 
					import org.thingsboard.mqtt.MqttClientConfig;
 | 
				
			||||||
import org.thingsboard.mqtt.MqttHandler;
 | 
					import org.thingsboard.mqtt.MqttHandler;
 | 
				
			||||||
import org.thingsboard.server.common.data.DataConstants;
 | 
					import org.thingsboard.server.common.data.DataConstants;
 | 
				
			||||||
@ -56,7 +62,9 @@ import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.nio.charset.StandardCharsets;
 | 
					import java.nio.charset.StandardCharsets;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
@ -65,6 +73,7 @@ import java.util.concurrent.BlockingQueue;
 | 
				
			|||||||
import java.util.concurrent.ExecutionException;
 | 
					import java.util.concurrent.ExecutionException;
 | 
				
			||||||
import java.util.concurrent.Executors;
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeoutException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
import static org.testng.Assert.fail;
 | 
					import static org.testng.Assert.fail;
 | 
				
			||||||
@ -76,6 +85,9 @@ import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevi
 | 
				
			|||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class MqttClientTest extends AbstractContainerTest {
 | 
					public class MqttClientTest extends AbstractContainerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String TRANSPORT_HOST = "localhost";
 | 
				
			||||||
 | 
					    private static final int TRANSPORT_PORT = 1883;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private Device device;
 | 
					    private Device device;
 | 
				
			||||||
    AbstractListeningExecutor handlerExecutor;
 | 
					    AbstractListeningExecutor handlerExecutor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -100,6 +112,7 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
            handlerExecutor.destroy();
 | 
					            handlerExecutor.destroy();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void telemetryUpload() throws Exception {
 | 
					    public void telemetryUpload() throws Exception {
 | 
				
			||||||
        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
					        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
				
			||||||
@ -194,7 +207,7 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
        String sharedAttributeValue = StringUtils.randomAlphanumeric(8);
 | 
					        String sharedAttributeValue = StringUtils.randomAlphanumeric(8);
 | 
				
			||||||
        sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
 | 
					        sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
 | 
				
			||||||
        JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString());
 | 
					        JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString());
 | 
				
			||||||
        testRestClient.postTelemetryAttribute(DataConstants.DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute);
 | 
					        testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Subscribe to attributes response
 | 
					        // Subscribe to attributes response
 | 
				
			||||||
        mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get();
 | 
					        mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get();
 | 
				
			||||||
@ -317,8 +330,8 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
        mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())).get();
 | 
					        mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())).get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check the response from the server
 | 
					        // Check the response from the server
 | 
				
			||||||
        TimeUnit.SECONDS.sleep(1 * timeoutMultiplier);
 | 
					        TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
 | 
				
			||||||
        MqttEvent responseFromServer = listener.getEvents().poll(1 * timeoutMultiplier, TimeUnit.SECONDS);
 | 
					        MqttEvent responseFromServer = listener.getEvents().poll(3 * timeoutMultiplier, TimeUnit.SECONDS);
 | 
				
			||||||
        Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length()));
 | 
					        Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length()));
 | 
				
			||||||
        assertThat(responseId).isEqualTo(requestId);
 | 
					        assertThat(responseId).isEqualTo(requestId);
 | 
				
			||||||
        assertThat(mapper.readTree(responseFromServer.getMessage()).get("response").asText()).isEqualTo("requestReceived");
 | 
					        assertThat(mapper.readTree(responseFromServer.getMessage()).get("response").asText()).isEqualTo("requestReceived");
 | 
				
			||||||
@ -444,6 +457,84 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
        assertThat(provisionResponse.get("status").asText()).isEqualTo("NOT_FOUND");
 | 
					        assertThat(provisionResponse.get("status").asText()).isEqualTo("NOT_FOUND");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void regularDisconnect() throws Exception {
 | 
				
			||||||
 | 
					        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MqttMessageListener listener = new MqttMessageListener();
 | 
				
			||||||
 | 
					        MqttClient mqttClient = getMqttClient(deviceCredentials, listener, MqttVersion.MQTT_5);
 | 
				
			||||||
 | 
					        final List<Byte> returnCodeByteValue = new ArrayList<>();
 | 
				
			||||||
 | 
					        MqttClientCallback callbackForDisconnectWithReturnCode = getCallbackWrapperForDisconnectWithReturnCode(returnCodeByteValue);
 | 
				
			||||||
 | 
					        mqttClient.setCallback(callbackForDisconnectWithReturnCode);
 | 
				
			||||||
 | 
					        mqttClient.disconnect();
 | 
				
			||||||
 | 
					        Thread.sleep(1000);
 | 
				
			||||||
 | 
					        assertThat(returnCodeByteValue.size()).isEqualTo(1);
 | 
				
			||||||
 | 
					        MqttReasonCodes.Disconnect returnCode = MqttReasonCodes.Disconnect.valueOf(returnCodeByteValue.get(0));
 | 
				
			||||||
 | 
					        assertThat(returnCode).isEqualTo(MqttReasonCodes.Disconnect.NORMAL_DISCONNECT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void clientSessionTakenOverDisconnect() throws Exception {
 | 
				
			||||||
 | 
					        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MqttMessageListener listener = new MqttMessageListener();
 | 
				
			||||||
 | 
					        MqttClient mqttClient = getMqttClient(deviceCredentials, listener, MqttVersion.MQTT_5);
 | 
				
			||||||
 | 
					        final List<Byte> returnCodeByteValue = new ArrayList<>();
 | 
				
			||||||
 | 
					        MqttClientCallback callbackForDisconnectWithReturnCode = getCallbackWrapperForDisconnectWithReturnCode(returnCodeByteValue);
 | 
				
			||||||
 | 
					        mqttClient.setCallback(callbackForDisconnectWithReturnCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Thread.sleep(1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MqttMessageListener dummyListener = new MqttMessageListener();
 | 
				
			||||||
 | 
					        MqttClient dummyMqttClient = getMqttClient(deviceCredentials, dummyListener, MqttVersion.MQTT_5);
 | 
				
			||||||
 | 
					        final List<Byte> returnCodeByteValueSecondClient = new ArrayList<>();
 | 
				
			||||||
 | 
					        MqttClientCallback callbackForDisconnectWithReturnCodeDummy = getCallbackWrapperForDisconnectWithReturnCode(returnCodeByteValueSecondClient);
 | 
				
			||||||
 | 
					        dummyMqttClient.setCallback(callbackForDisconnectWithReturnCodeDummy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Awaitility
 | 
				
			||||||
 | 
					                .await()
 | 
				
			||||||
 | 
					                .alias("Check device disconnect.")
 | 
				
			||||||
 | 
					                .atMost(TIMEOUT*timeoutMultiplier, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					                .until(() -> returnCodeByteValue.size() > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(returnCodeByteValueSecondClient).isEmpty();
 | 
				
			||||||
 | 
					        assertThat(returnCodeByteValue).isNotEmpty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MqttReasonCodes.Disconnect returnCode = MqttReasonCodes.Disconnect.valueOf(returnCodeByteValue.get(0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dummyMqttClient.disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(returnCode).isEqualTo(MqttReasonCodes.Disconnect.SESSION_TAKEN_OVER);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void clientPublishForRegularTopicByProvisionClient() throws Exception {
 | 
				
			||||||
 | 
					        MqttClient mqttClient = getMqttClient("provision", new MqttMessageListener(), MqttVersion.MQTT_5);
 | 
				
			||||||
 | 
					        final List<Byte> returnCodeByteValue = new ArrayList<>();
 | 
				
			||||||
 | 
					        MqttClientCallback callbackForDisconnectWithReturnCode = getCallbackWrapperForDisconnectWithReturnCode(returnCodeByteValue);
 | 
				
			||||||
 | 
					        mqttClient.setCallback(callbackForDisconnectWithReturnCode);
 | 
				
			||||||
 | 
					        mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer("test".getBytes()), MqttQoS.AT_LEAST_ONCE).get();
 | 
				
			||||||
 | 
					        Thread.sleep(1000);
 | 
				
			||||||
 | 
					        assertThat(returnCodeByteValue).isNotEmpty();
 | 
				
			||||||
 | 
					        MqttReasonCodes.Disconnect returnCode = MqttReasonCodes.Disconnect.valueOf(returnCodeByteValue.get(0));
 | 
				
			||||||
 | 
					        assertThat(returnCode).isEqualTo(MqttReasonCodes.Disconnect.TOPIC_NAME_INVALID);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void clientConnectWithBadCredentials() throws Exception {
 | 
				
			||||||
 | 
					        MqttClient mqttClient = getMqttClient("unknownAccessToken", new MqttMessageListener(), MqttVersion.MQTT_5, false);
 | 
				
			||||||
 | 
					        final List<Byte> returnCodeByteValue = new ArrayList<>();
 | 
				
			||||||
 | 
					        MqttClientCallback callbackForDisconnectWithReturnCode = getCallbackWrapperForDisconnectWithReturnCode(returnCodeByteValue);
 | 
				
			||||||
 | 
					        mqttClient.setCallback(callbackForDisconnectWithReturnCode);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            mqttClient.connect(TRANSPORT_HOST, TRANSPORT_PORT).get(1, TimeUnit.SECONDS);
 | 
				
			||||||
 | 
					        } catch (TimeoutException ignored) {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        assertThat(returnCodeByteValue).isNotEmpty();
 | 
				
			||||||
 | 
					        MqttConnectReturnCode returnCode = MqttConnectReturnCode.valueOf(returnCodeByteValue.get(0));
 | 
				
			||||||
 | 
					        assertThat(returnCode).isIn(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private RuleChainId createRootRuleChainForRpcResponse() throws Exception {
 | 
					    private RuleChainId createRootRuleChainForRpcResponse() throws Exception {
 | 
				
			||||||
        RuleChain newRuleChain = new RuleChain();
 | 
					        RuleChain newRuleChain = new RuleChain();
 | 
				
			||||||
        newRuleChain.setName("testRuleChain");
 | 
					        newRuleChain.setName("testRuleChain");
 | 
				
			||||||
@ -477,8 +568,34 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
        return defaultRuleChain.get().getId();
 | 
					        return defaultRuleChain.get().getId();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MqttClientCallback getCallbackWrapperForDisconnectWithReturnCode(List<Byte> returnCodeByteValueWrapper) {
 | 
				
			||||||
 | 
					        return new MqttClientCallback() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void connectionLost(Throwable cause) {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void onSuccessfulReconnect() {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void onDisconnect(MqttMessage mqttDisconnectMessage) {
 | 
				
			||||||
 | 
					                log.info("Disconnected with reason: {}", mqttDisconnectMessage);
 | 
				
			||||||
 | 
					                returnCodeByteValueWrapper.add(((MqttReasonCodeAndPropertiesVariableHeader) mqttDisconnectMessage.variableHeader()).reasonCode());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException {
 | 
					    private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException {
 | 
				
			||||||
        return getMqttClient(deviceCredentials.getCredentialsId(), listener);
 | 
					        return getMqttClient(deviceCredentials.getCredentialsId(), listener, MqttVersion.MQTT_3_1_1, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener, MqttVersion mqttVersion) throws InterruptedException, ExecutionException {
 | 
				
			||||||
 | 
					        return getMqttClient(deviceCredentials.getCredentialsId(), listener, mqttVersion, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener, MqttVersion mqttVersion, boolean connect) throws InterruptedException, ExecutionException {
 | 
				
			||||||
 | 
					        return getMqttClient(deviceCredentials.getCredentialsId(), listener, mqttVersion, connect);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getOwnerId() {
 | 
					    private String getOwnerId() {
 | 
				
			||||||
@ -486,12 +603,23 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private MqttClient getMqttClient(String username, MqttMessageListener listener) throws InterruptedException, ExecutionException {
 | 
					    private MqttClient getMqttClient(String username, MqttMessageListener listener) throws InterruptedException, ExecutionException {
 | 
				
			||||||
 | 
					        return getMqttClient(username, listener, MqttVersion.MQTT_3_1_1, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MqttClient getMqttClient(String username, MqttMessageListener listener, MqttVersion mqttVersion) throws InterruptedException, ExecutionException {
 | 
				
			||||||
 | 
					        return getMqttClient(username, listener, mqttVersion, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MqttClient getMqttClient(String username, MqttMessageListener listener, MqttVersion mqttVersion, boolean connect) throws InterruptedException, ExecutionException {
 | 
				
			||||||
        MqttClientConfig clientConfig = new MqttClientConfig();
 | 
					        MqttClientConfig clientConfig = new MqttClientConfig();
 | 
				
			||||||
        clientConfig.setOwnerId(getOwnerId());
 | 
					        clientConfig.setOwnerId(getOwnerId());
 | 
				
			||||||
        clientConfig.setClientId("MQTT client from test");
 | 
					        clientConfig.setClientId("MQTT client from test");
 | 
				
			||||||
        clientConfig.setUsername(username);
 | 
					        clientConfig.setUsername(username);
 | 
				
			||||||
 | 
					        clientConfig.setProtocolVersion(mqttVersion);
 | 
				
			||||||
        MqttClient mqttClient = MqttClient.create(clientConfig, listener, handlerExecutor);
 | 
					        MqttClient mqttClient = MqttClient.create(clientConfig, listener, handlerExecutor);
 | 
				
			||||||
        mqttClient.connect("localhost", 1883).get();
 | 
					        if (connect) {
 | 
				
			||||||
 | 
					            mqttClient.connect(TRANSPORT_HOST, TRANSPORT_PORT).get();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return mqttClient;
 | 
					        return mqttClient;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -84,6 +84,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
                case PUBCOMP:
 | 
					                case PUBCOMP:
 | 
				
			||||||
                    handlePubcomp(msg);
 | 
					                    handlePubcomp(msg);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case DISCONNECT:
 | 
				
			||||||
 | 
					                    handleDisconnect(msg);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            log.error("[{}] Message decoding failed: {}", client.getClientConfig().getClientId(), msg.decoderResult().cause().getMessage());
 | 
					            log.error("[{}] Message decoding failed: {}", client.getClientConfig().getClientId(), msg.decoderResult().cause().getMessage());
 | 
				
			||||||
@ -204,6 +207,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
                // Don't start reconnect logic here
 | 
					                // Don't start reconnect logic here
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.client.getCallback() != null) {
 | 
				
			||||||
 | 
					            this.client.getCallback().onConnAck(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handleSubAck(MqttSubAckMessage message) {
 | 
					    private void handleSubAck(MqttSubAckMessage message) {
 | 
				
			||||||
@ -224,6 +230,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
        if (!pendingSubscription.getFuture().isDone()) {
 | 
					        if (!pendingSubscription.getFuture().isDone()) {
 | 
				
			||||||
            pendingSubscription.getFuture().setSuccess(null);
 | 
					            pendingSubscription.getFuture().setSuccess(null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.client.getCallback() != null) {
 | 
				
			||||||
 | 
					            this.client.getCallback().onSubAck(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handlePublish(Channel channel, MqttPublishMessage message) {
 | 
					    private void handlePublish(Channel channel, MqttPublishMessage message) {
 | 
				
			||||||
@ -267,6 +276,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
        this.client.getServerSubscriptions().remove(unsubscription.getTopic());
 | 
					        this.client.getServerSubscriptions().remove(unsubscription.getTopic());
 | 
				
			||||||
        unsubscription.getFuture().setSuccess(null);
 | 
					        unsubscription.getFuture().setSuccess(null);
 | 
				
			||||||
        this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId());
 | 
					        this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId());
 | 
				
			||||||
 | 
					        if (this.client.getCallback() != null) {
 | 
				
			||||||
 | 
					            this.client.getCallback().onUnsubAck(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handlePuback(MqttPubAckMessage message) {
 | 
					    private void handlePuback(MqttPubAckMessage message) {
 | 
				
			||||||
@ -278,6 +290,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
        pendingPublish.onPubackReceived();
 | 
					        pendingPublish.onPubackReceived();
 | 
				
			||||||
        this.client.getPendingPublishes().remove(message.variableHeader().messageId());
 | 
					        this.client.getPendingPublishes().remove(message.variableHeader().messageId());
 | 
				
			||||||
        pendingPublish.getPayload().release();
 | 
					        pendingPublish.getPayload().release();
 | 
				
			||||||
 | 
					        if (this.client.getCallback() != null) {
 | 
				
			||||||
 | 
					            this.client.getCallback().onPubAck(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handlePubrec(Channel channel, MqttMessage message) {
 | 
					    private void handlePubrec(Channel channel, MqttMessage message) {
 | 
				
			||||||
@ -301,7 +316,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
            future = Futures.transform(future, x -> {
 | 
					            future = Futures.transform(future, x -> {
 | 
				
			||||||
                this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId());
 | 
					                this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId());
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
                }, MoreExecutors.directExecutor());
 | 
					            }, MoreExecutors.directExecutor());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        future.addListener(() -> {
 | 
					        future.addListener(() -> {
 | 
				
			||||||
            MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0);
 | 
					            MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0);
 | 
				
			||||||
@ -319,6 +334,12 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
 | 
				
			|||||||
        pendingPublish.onPubcompReceived();
 | 
					        pendingPublish.onPubcompReceived();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void handleDisconnect(MqttMessage message) {
 | 
				
			||||||
 | 
					        if (this.client.getCallback() != null) {
 | 
				
			||||||
 | 
					            this.client.getCallback().onDisconnect(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
 | 
					    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,12 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.mqtt;
 | 
					package org.thingsboard.mqtt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttConnAckMessage;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttMessage;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttPubAckMessage;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttSubAckMessage;
 | 
				
			||||||
 | 
					import io.netty.handler.codec.mqtt.MqttUnsubAckMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Created by Valerii Sosliuk on 12/30/2017.
 | 
					 * Created by Valerii Sosliuk on 12/30/2017.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -32,4 +38,19 @@ public interface MqttClientCallback {
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void onSuccessfulReconnect();
 | 
					    void onSuccessfulReconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void onConnAck(MqttConnAckMessage connAckMessage) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void onPubAck(MqttPubAckMessage pubAckMessage) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void onSubAck(MqttSubAckMessage pubAckMessage) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void onUnsubAck(MqttUnsubAckMessage unsubAckMessage) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void onDisconnect(MqttMessage mqttDisconnectMessage) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,7 @@ import io.netty.handler.timeout.IdleStateHandler;
 | 
				
			|||||||
import io.netty.util.concurrent.DefaultPromise;
 | 
					import io.netty.util.concurrent.DefaultPromise;
 | 
				
			||||||
import io.netty.util.concurrent.Future;
 | 
					import io.netty.util.concurrent.Future;
 | 
				
			||||||
import io.netty.util.concurrent.Promise;
 | 
					import io.netty.util.concurrent.Promise;
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.thingsboard.common.util.ListeningExecutor;
 | 
					import org.thingsboard.common.util.ListeningExecutor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,6 +88,7 @@ final class MqttClientImpl implements MqttClient {
 | 
				
			|||||||
    private volatile boolean reconnect = false;
 | 
					    private volatile boolean reconnect = false;
 | 
				
			||||||
    private String host;
 | 
					    private String host;
 | 
				
			||||||
    private int port;
 | 
					    private int port;
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
    private MqttClientCallback callback;
 | 
					    private MqttClientCallback callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ListeningExecutor handlerExecutor;
 | 
					    private final ListeningExecutor handlerExecutor;
 | 
				
			||||||
@ -426,7 +428,12 @@ final class MqttClientImpl implements MqttClient {
 | 
				
			|||||||
        disconnected = true;
 | 
					        disconnected = true;
 | 
				
			||||||
        if (this.channel != null) {
 | 
					        if (this.channel != null) {
 | 
				
			||||||
            MqttMessage message = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0));
 | 
					            MqttMessage message = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0));
 | 
				
			||||||
            this.sendAndFlushPacket(message).addListener(future1 -> channel.close());
 | 
					            ChannelFuture channelFuture = this.sendAndFlushPacket(message);
 | 
				
			||||||
 | 
					            eventLoop.schedule(() -> {
 | 
				
			||||||
 | 
					                if (!channelFuture.isDone()) {
 | 
				
			||||||
 | 
					                    this.channel.close();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }, 500, TimeUnit.MILLISECONDS);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -143,6 +143,8 @@ transport:
 | 
				
			|||||||
    proxy_enabled: "${MQTT_PROXY_PROTOCOL_ENABLED:false}"
 | 
					    proxy_enabled: "${MQTT_PROXY_PROTOCOL_ENABLED:false}"
 | 
				
			||||||
    # MQTT processing timeout in milliseconds
 | 
					    # MQTT processing timeout in milliseconds
 | 
				
			||||||
    timeout: "${MQTT_TIMEOUT:10000}"
 | 
					    timeout: "${MQTT_TIMEOUT:10000}"
 | 
				
			||||||
 | 
					    # MQTT disconnect timeout in milliseconds. The time to wait for the client to disconnect after the server sends a disconnect message.
 | 
				
			||||||
 | 
					    disconnect_timeout: "${MQTT_DISCONNECT_TIMEOUT:1000}"
 | 
				
			||||||
    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
 | 
					    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
 | 
				
			||||||
    netty:
 | 
					    netty:
 | 
				
			||||||
      # Netty leak detector level
 | 
					      # Netty leak detector level
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user