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