Switched from Moquette's MQTT codec to Netty's MQTT codec ones
This commit is contained in:
@@ -15,6 +15,17 @@
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.BrokerConstants;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.interception.Interceptor;
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.netty.handler.codec.mqtt.MqttConnectMessage;
|
||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -23,23 +34,6 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.moquette.BrokerConstants;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.interception.Interceptor;
|
||||
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
|
||||
import io.moquette.interception.messages.InterceptConnectMessage;
|
||||
import io.moquette.interception.messages.InterceptConnectionLostMessage;
|
||||
import io.moquette.interception.messages.InterceptDisconnectMessage;
|
||||
import io.moquette.interception.messages.InterceptPublishMessage;
|
||||
import io.moquette.interception.messages.InterceptSubscribeMessage;
|
||||
import io.moquette.interception.messages.InterceptUnsubscribeMessage;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import static io.moquette.logging.LoggingUtils.getInterceptorIds;
|
||||
|
||||
/**
|
||||
@@ -98,10 +92,10 @@ final class BrokerInterceptor implements Interceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyClientConnected(final ConnectMessage msg) {
|
||||
public void notifyClientConnected(final MqttConnectMessage msg) {
|
||||
for (final InterceptHandler handler : this.handlers.get(InterceptConnectMessage.class)) {
|
||||
LOG.debug("Sending MQTT CONNECT message to interceptor. MqttClientId = {}, interceptorId = {}.",
|
||||
msg.getClientID(), handler.getID());
|
||||
msg.payload().clientIdentifier(), handler.getID());
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -140,10 +134,12 @@ final class BrokerInterceptor implements Interceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicPublished(final PublishMessage msg, final String clientID, final String username) {
|
||||
public void notifyTopicPublished(final MqttPublishMessage msg, final String clientID, final String username) {
|
||||
int messageId = msg.variableHeader().messageId();
|
||||
String topic = msg.variableHeader().topicName();
|
||||
for (final InterceptHandler handler : this.handlers.get(InterceptPublishMessage.class)) {
|
||||
LOG.debug("Notifying MQTT PUBLISH message to interceptor. MqttClientId = {}, messageId = {}, topic = {}, " +
|
||||
"interceptorId = {}.", clientID, msg.getMessageID(), msg.getTopicName(), handler.getID());
|
||||
"interceptorId = {}.", clientID, messageId, topic, handler.getID());
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
@@ -22,11 +24,16 @@ import java.nio.ByteBuffer;
|
||||
* @author andrea
|
||||
*/
|
||||
class DebugUtils {
|
||||
static String payload2Str(ByteBuffer content) {
|
||||
static String payload2Str(ByteBuffer content) {
|
||||
byte[] b = new byte[content.remaining()];
|
||||
content.mark();
|
||||
content.get(b);
|
||||
content.reset();
|
||||
return new String(b);
|
||||
}
|
||||
}
|
||||
|
||||
static String payload2Str(ByteBuf content) {
|
||||
//byte[] b = new byte[content.readableBytes()];
|
||||
return new String(content.copy().array());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.mqtt.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -27,21 +26,23 @@ class InternalRepublisher {
|
||||
void publishRetained(ClientSession targetSession, Collection<IMessagesStore.StoredMessage> messages) {
|
||||
for (IMessagesStore.StoredMessage storedMsg : messages) {
|
||||
//fire as retained the message
|
||||
Integer packetID = storedMsg.getQos() == AbstractMessage.QOSType.MOST_ONE ? null : targetSession.nextPacketId();
|
||||
Integer packetID = storedMsg.getQos() == MqttQoS.AT_MOST_ONCE ? null : targetSession.nextPacketId();
|
||||
if (packetID != null) {
|
||||
LOG.debug("Adding message to inflight zone. MqttClientId = {}, packetId = {}, messageId = {}, topic = {}.",
|
||||
targetSession.clientID, packetID, storedMsg.getMessageID(), storedMsg.getTopic());
|
||||
targetSession.inFlightAckWaiting(storedMsg.getGuid(), packetID);
|
||||
}
|
||||
PublishMessage publishMsg = retainedPublish(storedMsg);
|
||||
MqttPublishMessage publishMsg = retainedPublish(storedMsg);
|
||||
//set the PacketIdentifier only for QoS > 0
|
||||
if (publishMsg.getQos() != AbstractMessage.QOSType.MOST_ONE) {
|
||||
publishMsg.setMessageID(packetID);
|
||||
if (publishMsg.fixedHeader().qosLevel() != MqttQoS.AT_MOST_ONCE) {
|
||||
publishMsg = retainedPublish(storedMsg, packetID);
|
||||
}
|
||||
this.messageSender.sendPublish(targetSession, publishMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void publishStored(ClientSession clientSession, BlockingQueue<IMessagesStore.StoredMessage> publishedEvents) {
|
||||
List<IMessagesStore.StoredMessage> storedPublishes = new ArrayList<>();
|
||||
publishedEvents.drainTo(storedPublishes);
|
||||
@@ -51,29 +52,34 @@ class InternalRepublisher {
|
||||
LOG.debug("Adding message ot inflight zone. MqttClientId = {}, guid = {}, messageId = {}, topic = {}.",
|
||||
clientSession.clientID, pubEvt.getGuid(), pubEvt.getMessageID(), pubEvt.getTopic());
|
||||
clientSession.inFlightAckWaiting(pubEvt.getGuid(), pubEvt.getMessageID());
|
||||
PublishMessage publishMsg = notRetainedPublish(pubEvt);
|
||||
MqttPublishMessage publishMsg = notRetainedPublish(pubEvt);
|
||||
//set the PacketIdentifier only for QoS > 0
|
||||
if (publishMsg.getQos() != AbstractMessage.QOSType.MOST_ONE) {
|
||||
publishMsg.setMessageID(pubEvt.getMessageID());
|
||||
if (publishMsg.fixedHeader().qosLevel() != MqttQoS.AT_MOST_ONCE) {
|
||||
publishMsg = notRetainedPublish(pubEvt, pubEvt.getMessageID());
|
||||
}
|
||||
this.messageSender.sendPublish(clientSession, publishMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private PublishMessage notRetainedPublish(IMessagesStore.StoredMessage storedMessage) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), false);
|
||||
private MqttPublishMessage notRetainedPublish(IMessagesStore.StoredMessage storedMessage, Integer messageID) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), false, messageID);
|
||||
}
|
||||
|
||||
private PublishMessage retainedPublish(IMessagesStore.StoredMessage storedMessage) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), true);
|
||||
private MqttPublishMessage notRetainedPublish(IMessagesStore.StoredMessage storedMessage) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), false, 0);
|
||||
}
|
||||
|
||||
public static PublishMessage createPublishForQos(String topic, AbstractMessage.QOSType qos, ByteBuffer message, boolean retained) {
|
||||
PublishMessage pubMessage = new PublishMessage();
|
||||
pubMessage.setRetainFlag(retained);
|
||||
pubMessage.setTopicName(topic);
|
||||
pubMessage.setQos(qos);
|
||||
pubMessage.setPayload(message);
|
||||
return pubMessage;
|
||||
private MqttPublishMessage retainedPublish(IMessagesStore.StoredMessage storedMessage) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), true, 0);
|
||||
}
|
||||
|
||||
private MqttPublishMessage retainedPublish(IMessagesStore.StoredMessage storedMessage, Integer packetID) {
|
||||
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), true, packetID);
|
||||
}
|
||||
|
||||
public static MqttPublishMessage createPublishForQos(String topic, MqttQoS qos, ByteBuf message, boolean retained, int messageId) {
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0);
|
||||
MqttPublishVariableHeader varHeader = new MqttPublishVariableHeader(topic, messageId);
|
||||
return new MqttPublishMessage(fixedHeader, varHeader, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.server.ConnectionDescriptorStore;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.MessageGUID;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.mqtt.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static io.moquette.spi.impl.ProtocolProcessor.lowerQosToTheSubscriptionDesired;
|
||||
@@ -32,50 +31,51 @@ class MessagesPublisher {
|
||||
this.messageSender = messageSender;
|
||||
}
|
||||
|
||||
static PublishMessage notRetainedPublish(String topic, AbstractMessage.QOSType qos, ByteBuffer message) {
|
||||
PublishMessage pubMessage = new PublishMessage();
|
||||
pubMessage.setRetainFlag(false);
|
||||
pubMessage.setTopicName(topic);
|
||||
pubMessage.setQos(qos);
|
||||
pubMessage.setPayload(message);
|
||||
return pubMessage;
|
||||
static MqttPublishMessage notRetainedPublish(String topic, MqttQoS qos, ByteBuf message) {
|
||||
return notRetainedPublishWithMessageId(topic, qos, message, 0);
|
||||
}
|
||||
|
||||
private static MqttPublishMessage notRetainedPublishWithMessageId(String topic, MqttQoS qos, ByteBuf message, int messageId) {
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0);
|
||||
MqttPublishVariableHeader varHeader = new MqttPublishVariableHeader(topic, messageId);
|
||||
return new MqttPublishMessage(fixedHeader, varHeader, message);
|
||||
}
|
||||
|
||||
void publish2Subscribers(IMessagesStore.StoredMessage pubMsg, List<Subscription> topicMatchingSubscriptions) {
|
||||
final String topic = pubMsg.getTopic();
|
||||
final AbstractMessage.QOSType publishingQos = pubMsg.getQos();
|
||||
final ByteBuffer origMessage = pubMsg.getMessage();
|
||||
final MqttQoS publishingQos = pubMsg.getQos();
|
||||
final ByteBuf origMessage = pubMsg.getMessage();
|
||||
|
||||
//if QoS 1 or 2 store the message
|
||||
//TODO perhaps this block is not needed.
|
||||
MessageGUID guid = null;
|
||||
if (publishingQos != AbstractMessage.QOSType.MOST_ONE) {
|
||||
if (publishingQos != MqttQoS.AT_MOST_ONCE) {
|
||||
guid = m_messagesStore.storePublishForFuture(pubMsg);
|
||||
}
|
||||
|
||||
for (final Subscription sub : topicMatchingSubscriptions) {
|
||||
AbstractMessage.QOSType qos = lowerQosToTheSubscriptionDesired(sub, publishingQos);
|
||||
MqttQoS qos = lowerQosToTheSubscriptionDesired(sub, publishingQos);
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(sub.getClientId());
|
||||
|
||||
boolean targetIsActive = this.connectionDescriptors.isConnected(sub.getClientId());
|
||||
|
||||
ByteBuffer message = origMessage.duplicate();
|
||||
//we need to retain because duplicate only copy r/w indexes and don't retain() causing refCnt = 0
|
||||
ByteBuf message = origMessage.retainedDuplicate();
|
||||
if (targetIsActive) {
|
||||
LOG.debug("Sending PUBLISH message to active subscriber. MessageId = {}, mqttClientId = {}, topicFilter = {}, " +
|
||||
"qos = {}.", pubMsg.getMessageID(), sub.getClientId(), sub.getTopicFilter(), qos);
|
||||
PublishMessage publishMsg = notRetainedPublish(topic, qos, message);
|
||||
if (qos != AbstractMessage.QOSType.MOST_ONE) {
|
||||
LOG.debug("Sending PUBLISH message to active subscriber. MessageId={}, CId={}, topicFilter={}, qos={}",
|
||||
pubMsg.getMessageID(), sub.getClientId(), sub.getTopicFilter(), qos);
|
||||
MqttPublishMessage publishMsg = notRetainedPublish(topic, qos, message);
|
||||
if (qos != MqttQoS.AT_MOST_ONCE) {
|
||||
//QoS 1 or 2
|
||||
int messageId = targetSession.nextPacketId();
|
||||
targetSession.inFlightAckWaiting(guid, messageId);
|
||||
//set the PacketIdentifier only for QoS > 0
|
||||
publishMsg.setMessageID(messageId);
|
||||
publishMsg = notRetainedPublishWithMessageId(topic, qos, message, messageId);
|
||||
}
|
||||
this.messageSender.sendPublish(targetSession, publishMsg);
|
||||
} else {
|
||||
if (!targetSession.isCleanSession()) {
|
||||
LOG.debug("Storing pending PUBLISH inactive message. MessageId = {}, mqttClientId = {}, " +
|
||||
"topicFilter = {}, qos = {}.",
|
||||
LOG.debug("Storing pending PUBLISH inactive message. MessageId={}, CId={}, topicFilter={}, qos={}",
|
||||
pubMsg.getMessageID(), sub.getClientId(), sub.getTopicFilter(), qos);
|
||||
//store the message in targetSession queue to deliver
|
||||
targetSession.enqueue(pubMsg);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.server.ConnectionDescriptorStore;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
|
||||
import io.netty.handler.codec.mqtt.MqttQoS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
import static io.moquette.parser.proto.messages.AbstractMessage.QOSType.MOST_ONE;
|
||||
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
|
||||
|
||||
class PersistentQueueMessageSender {
|
||||
|
||||
@@ -19,28 +19,29 @@ class PersistentQueueMessageSender {
|
||||
this.connectionDescriptorStore = connectionDescriptorStore;
|
||||
}
|
||||
|
||||
void sendPublish(ClientSession clientsession, PublishMessage pubMessage) {
|
||||
void sendPublish(ClientSession clientsession, MqttPublishMessage pubMessage) {
|
||||
String clientId = clientsession.clientID;
|
||||
final int messageId = pubMessage.variableHeader().messageId();
|
||||
final String topicName = pubMessage.variableHeader().topicName();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Sending PUBLISH message. MessageId = {}, mqttClientId = {}, topic = {}, qos = {}, payload = {}.",
|
||||
pubMessage.getMessageID(),
|
||||
clientId, pubMessage.getTopicName(), DebugUtils.payload2Str(pubMessage.getPayload()));
|
||||
LOG.debug("Sending PUBLISH message. MessageId={}, CId={}, topic={}, qos={}, payload={}", messageId,
|
||||
clientId, topicName, DebugUtils.payload2Str(pubMessage.payload()));
|
||||
} else {
|
||||
LOG.info("Sending PUBLISH message. MessageId = {}, mqttClientId = {}, topic = {}.", pubMessage.getMessageID(),
|
||||
clientId, pubMessage.getTopicName());
|
||||
LOG.info("Sending PUBLISH message. MessageId={}, CId={}, topic={}", messageId, clientId, topicName);
|
||||
}
|
||||
|
||||
boolean messageDelivered = connectionDescriptorStore.sendMessage(pubMessage, pubMessage.getMessageID(), clientId);
|
||||
|
||||
if (!messageDelivered && pubMessage.getQos() != MOST_ONE && !clientsession.isCleanSession()) {
|
||||
boolean messageDelivered = connectionDescriptorStore.sendMessage(pubMessage, messageId, clientId);
|
||||
|
||||
MqttQoS qos = pubMessage.fixedHeader().qosLevel();
|
||||
if (!messageDelivered && qos != AT_MOST_ONCE && !clientsession.isCleanSession()) {
|
||||
LOG.warn(
|
||||
"The PUBLISH message could not be delivered. It will be stored. MessageId = {}, mqttClientId = {}, topic = {}, qos = {}, cleanSession = {}.",
|
||||
pubMessage.getMessageID(), clientId, pubMessage.getTopicName(), pubMessage.getQos(), false);
|
||||
"The PUBLISH message could not be delivered. It will be stored. MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
|
||||
messageId, clientId, topicName, qos, false);
|
||||
clientsession.enqueue(asStoredMessage(pubMessage));
|
||||
} else {
|
||||
LOG.warn(
|
||||
"The PUBLISH message could not be delivered. It will be discarded. MessageId = {}, mqttClientId = {}, topic = {}, qos = {}, cleanSession = {}.",
|
||||
pubMessage.getMessageID(), clientId, pubMessage.getTopicName(), pubMessage.getQos(), true);
|
||||
"The PUBLISH message could not be delivered. It will be discarded. MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
|
||||
messageId, clientId, topicName, qos, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,35 +15,42 @@
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.parser.proto.messages.*;
|
||||
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
|
||||
import io.moquette.server.ConnectionDescriptor;
|
||||
import io.moquette.server.ConnectionDescriptor.ConnectionState;
|
||||
import io.moquette.server.ConnectionDescriptorStore;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.*;
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.security.IAuthenticator;
|
||||
import io.moquette.spi.security.IAuthorizator;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
import static io.moquette.parser.netty.Utils.VERSION_3_1;
|
||||
import static io.moquette.parser.netty.Utils.VERSION_3_1_1;
|
||||
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
|
||||
|
||||
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
|
||||
import io.moquette.parser.proto.messages.AbstractMessage.QOSType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.mqtt.*;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
|
||||
import static io.moquette.spi.impl.Utils.messageId;
|
||||
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
|
||||
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
|
||||
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.*;
|
||||
|
||||
/**
|
||||
* Class responsible to handle the logic of MQTT protocol it's the director of
|
||||
* the protocol execution.
|
||||
@@ -58,9 +65,9 @@ public class ProtocolProcessor {
|
||||
private final String topic;
|
||||
private final ByteBuffer payload;
|
||||
private final boolean retained;
|
||||
private final QOSType qos;
|
||||
private final MqttQoS qos;
|
||||
|
||||
public WillMessage(String topic, ByteBuffer payload, boolean retained, QOSType qos) {
|
||||
public WillMessage(String topic, ByteBuffer payload, boolean retained, MqttQoS qos) {
|
||||
this.topic = topic;
|
||||
this.payload = payload;
|
||||
this.retained = retained;
|
||||
@@ -79,7 +86,7 @@ public class ProtocolProcessor {
|
||||
return retained;
|
||||
}
|
||||
|
||||
public QOSType getQos() {
|
||||
public MqttQoS getQos() {
|
||||
return qos;
|
||||
}
|
||||
|
||||
@@ -105,8 +112,7 @@ public class ProtocolProcessor {
|
||||
|
||||
RunningSubscription that = (RunningSubscription) o;
|
||||
|
||||
if (packetId != that.packetId) return false;
|
||||
return clientID != null ? clientID.equals(that.clientID) : that.clientID == null;
|
||||
return packetId == that.packetId && (clientID != null ? clientID.equals(that.clientID) : that.clientID == null);
|
||||
|
||||
}
|
||||
|
||||
@@ -217,65 +223,63 @@ public class ProtocolProcessor {
|
||||
this.internalRepublisher = new InternalRepublisher(messageSender);
|
||||
}
|
||||
|
||||
public void processConnect(Channel channel, ConnectMessage msg) {
|
||||
LOG.info("Processing CONNECT message. MqttClientId = {}, username = {}, password = {}.", msg.getClientID(),
|
||||
msg.getUsername(), msg.getPassword());
|
||||
public void processConnect(Channel channel, MqttConnectMessage msg) {
|
||||
MqttConnectPayload payload = msg.payload();
|
||||
String clientId = payload.clientIdentifier();
|
||||
LOG.info("Processing CONNECT message. CId = {}, username = {}", clientId, payload.userName());
|
||||
|
||||
if (msg.getProtocolVersion() != VERSION_3_1 && msg.getProtocolVersion() != VERSION_3_1_1) {
|
||||
ConnAckMessage badProto = new ConnAckMessage();
|
||||
badProto.setReturnCode(ConnAckMessage.UNNACEPTABLE_PROTOCOL_VERSION);
|
||||
LOG.error("The MQTT protocol version is not valid. MqttClientId = {}, protocolVersion = {}.",
|
||||
msg.getProtocolVersion());
|
||||
if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel() &&
|
||||
msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()) {
|
||||
MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION);
|
||||
|
||||
LOG.error("The MQTT protocol version is not valid. CId = {}", clientId);
|
||||
channel.writeAndFlush(badProto);
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.getClientID() == null || msg.getClientID().length() == 0) {
|
||||
if(!msg.isCleanSession() || !this.allowZeroByteClientId) {
|
||||
ConnAckMessage okResp = new ConnAckMessage();
|
||||
okResp.setReturnCode(ConnAckMessage.IDENTIFIER_REJECTED);
|
||||
channel.writeAndFlush(okResp);
|
||||
if (clientId == null || clientId.length() == 0) {
|
||||
if (!msg.variableHeader().isCleanSession() || !this.allowZeroByteClientId) {
|
||||
MqttConnAckMessage badId = connAck(CONNECTION_REFUSED_IDENTIFIER_REJECTED);
|
||||
|
||||
channel.writeAndFlush(badId);
|
||||
channel.close();
|
||||
LOG.error("The MQTT client ID cannot be empty., Username = {}, password = {}.", msg.getUsername(),
|
||||
msg.getPassword());
|
||||
LOG.error("The MQTT client ID cannot be empty. Username = {}", payload.userName());
|
||||
return;
|
||||
}
|
||||
|
||||
// Generating client id.
|
||||
String randomIdentifier = UUID.randomUUID().toString().replace("-", "");
|
||||
msg.setClientID(randomIdentifier);
|
||||
LOG.info("The client has connected with a server generated identifier. MqttClientId = {}, username = {}, password = {}.",
|
||||
randomIdentifier, msg.getUsername(), msg.getPassword());
|
||||
clientId = UUID.randomUUID().toString().replace("-", "");
|
||||
LOG.info("The client has connected with a server generated identifier. CId = {}, username = {}",
|
||||
clientId, payload.userName());
|
||||
}
|
||||
|
||||
if (!login(channel, msg)) {
|
||||
if (!login(channel, msg, clientId)) {
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
final String clientID = msg.getClientID();
|
||||
ConnectionDescriptor descriptor = new ConnectionDescriptor(clientID, channel, msg.isCleanSession());
|
||||
ConnectionDescriptor descriptor = new ConnectionDescriptor(clientId, channel, msg.variableHeader().isCleanSession());
|
||||
ConnectionDescriptor existing = this.connectionDescriptors.addConnection(descriptor);
|
||||
if (existing != null) {
|
||||
LOG.info("The client ID is being used in an existing connection. It will be closed. MqttClientId = {}.",
|
||||
msg.getClientID());
|
||||
LOG.info("The client ID is being used in an existing connection. It will be closed. CId = {}",
|
||||
clientId);
|
||||
existing.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
initializeKeepAliveTimeout(channel, msg);
|
||||
initializeKeepAliveTimeout(channel, msg, clientId);
|
||||
|
||||
storeWillMessage(msg);
|
||||
storeWillMessage(msg, clientId);
|
||||
|
||||
if (!sendAck(descriptor, msg)) {
|
||||
if (!sendAck(descriptor, msg, clientId)) {
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_interceptor.notifyClientConnected(msg);
|
||||
|
||||
final ClientSession clientSession = createOrLoadClientSession(descriptor, msg);
|
||||
final ClientSession clientSession = createOrLoadClientSession(descriptor, msg, clientId);
|
||||
if (clientSession == null) {
|
||||
channel.close();
|
||||
return;
|
||||
@@ -290,117 +294,133 @@ public class ProtocolProcessor {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
LOG.info("The CONNECT message has been processed. MqttClientId = {}, username = {}, password = {}.",
|
||||
msg.getClientID(), msg.getUsername(), msg.getPassword());
|
||||
LOG.info("The CONNECT message has been processed. CId = {}, username = {}.", clientId, payload.userName());
|
||||
}
|
||||
|
||||
private boolean login(Channel channel, ConnectMessage msg) {
|
||||
private MqttConnAckMessage connAck(MqttConnectReturnCode returnCode) {
|
||||
return connAck(returnCode, false);
|
||||
}
|
||||
|
||||
private MqttConnAckMessage connAckWithSessionPresent(MqttConnectReturnCode returnCode) {
|
||||
return connAck(returnCode, true);
|
||||
}
|
||||
|
||||
private MqttConnAckMessage connAck(MqttConnectReturnCode returnCode, boolean sessionPresent) {
|
||||
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
|
||||
MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(returnCode, sessionPresent);
|
||||
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
|
||||
}
|
||||
|
||||
private boolean login(Channel channel, MqttConnectMessage msg, final String clientId) {
|
||||
//handle user authentication
|
||||
if (msg.isUserFlag()) {
|
||||
if (msg.variableHeader().hasUserName()) {
|
||||
byte[] pwd = null;
|
||||
if (msg.isPasswordFlag()) {
|
||||
pwd = msg.getPassword();
|
||||
if (msg.variableHeader().hasPassword()) {
|
||||
pwd = msg.payload().password().getBytes();
|
||||
} else if (!this.allowAnonymous) {
|
||||
LOG.error("The client didn't supply any password and MQTT anonymous mode is disabled. MqttClientId = {}.",
|
||||
msg.getClientID());
|
||||
LOG.error("The client didn't supply any password and MQTT anonymous mode is disabled. CId = {}.",
|
||||
clientId);
|
||||
failedCredentials(channel);
|
||||
return false;
|
||||
}
|
||||
if (!m_authenticator.checkValid(msg.getClientID(), msg.getUsername(), pwd)) {
|
||||
LOG.error("The authenticator has rejected the MQTT credentials. MqttClientId = {}, username = {}, password = {}.",
|
||||
msg.getClientID(), msg.getUsername(), pwd);
|
||||
if (!m_authenticator.checkValid(clientId, msg.payload().userName(), pwd)) {
|
||||
LOG.error("The authenticator has rejected the MQTT credentials. CId = {}, username = {}, password = {}.",
|
||||
clientId, msg.payload().userName(), pwd);
|
||||
failedCredentials(channel);
|
||||
return false;
|
||||
}
|
||||
NettyUtils.userName(channel, msg.getUsername());
|
||||
NettyUtils.userName(channel, msg.payload().userName());
|
||||
} else if (!this.allowAnonymous) {
|
||||
LOG.error("The client didn't supply any credentials and MQTT anonymous mode is disabled. MqttClientId = {}.",
|
||||
msg.getClientID());
|
||||
LOG.error("The client didn't supply any credentials and MQTT anonymous mode is disabled. CId = {}.",
|
||||
clientId);
|
||||
failedCredentials(channel);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean sendAck(ConnectionDescriptor descriptor, ConnectMessage msg) {
|
||||
LOG.info("Sending connect ACK. MqttClientId = {}.", msg.getClientID());
|
||||
private boolean sendAck(ConnectionDescriptor descriptor, MqttConnectMessage msg, final String clientId) {
|
||||
LOG.info("Sending connect ACK. CId = {}.", clientId);
|
||||
final boolean success = descriptor.assignState(ConnectionState.DISCONNECTED, ConnectionState.SENDACK);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
ConnAckMessage okResp = new ConnAckMessage();
|
||||
okResp.setReturnCode(ConnAckMessage.CONNECTION_ACCEPTED);
|
||||
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(msg.getClientID());
|
||||
MqttConnAckMessage okResp;
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(clientId);
|
||||
boolean isSessionAlreadyStored = clientSession != null;
|
||||
if (!msg.isCleanSession() && isSessionAlreadyStored) {
|
||||
okResp.setSessionPresent(true);
|
||||
if (!msg.variableHeader().isCleanSession() && isSessionAlreadyStored) {
|
||||
okResp = connAckWithSessionPresent(CONNECTION_ACCEPTED);
|
||||
} else {
|
||||
okResp = connAck(CONNECTION_ACCEPTED);
|
||||
}
|
||||
|
||||
if (isSessionAlreadyStored) {
|
||||
LOG.info("Cleaning session. MqttClientId = {}.", msg.getClientID());
|
||||
clientSession.cleanSession(msg.isCleanSession());
|
||||
LOG.info("Cleaning session. CId = {}.", clientId);
|
||||
clientSession.cleanSession(msg.variableHeader().isCleanSession());
|
||||
}
|
||||
descriptor.writeAndFlush(okResp);
|
||||
LOG.info("The connect ACK has been sent. MqttClientId = {}.", msg.getClientID());
|
||||
LOG.info("The connect ACK has been sent. CId = {}.", clientId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initializeKeepAliveTimeout(Channel channel, ConnectMessage msg) {
|
||||
int keepAlive = msg.getKeepAlive();
|
||||
LOG.info("Configuring connection. MqttClientId = {}.", msg.getClientID());
|
||||
private void initializeKeepAliveTimeout(Channel channel, MqttConnectMessage msg, final String clientId) {
|
||||
int keepAlive = msg.variableHeader().keepAliveTimeSeconds();
|
||||
LOG.info("Configuring connection. CId = {}.", clientId);
|
||||
NettyUtils.keepAlive(channel, keepAlive);
|
||||
//session.attr(NettyUtils.ATTR_KEY_CLEANSESSION).set(msg.isCleanSession());
|
||||
NettyUtils.cleanSession(channel, msg.isCleanSession());
|
||||
//session.attr(NettyUtils.ATTR_KEY_CLEANSESSION).set(msg.variableHeader().isCleanSession());
|
||||
NettyUtils.cleanSession(channel, msg.variableHeader().isCleanSession());
|
||||
//used to track the client in the subscription and publishing phases.
|
||||
//session.attr(NettyUtils.ATTR_KEY_CLIENTID).set(msg.getClientID());
|
||||
NettyUtils.clientID(channel, msg.getClientID());
|
||||
NettyUtils.clientID(channel, clientId);
|
||||
int idleTime = Math.round(keepAlive * 1.5f);
|
||||
setIdleTime(channel.pipeline(), idleTime);
|
||||
|
||||
LOG.info("The connection has been configured. MqttClientId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.",
|
||||
msg.getClientID(), keepAlive, msg.isCleanSession(), idleTime);
|
||||
LOG.info("The connection has been configured. CId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.",
|
||||
clientId, keepAlive, msg.variableHeader().isCleanSession(), idleTime);
|
||||
}
|
||||
|
||||
private void storeWillMessage(ConnectMessage msg) {
|
||||
private void storeWillMessage(MqttConnectMessage msg, final String clientId) {
|
||||
//Handle will flag
|
||||
if (msg.isWillFlag()) {
|
||||
AbstractMessage.QOSType willQos = AbstractMessage.QOSType.valueOf(msg.getWillQos());
|
||||
LOG.info("Configuring MQTT last will and testament. MqttClientId = {}, willTopic = {}, willQos = {}, willRetain = {}.",
|
||||
msg.getClientID(), willQos, msg.getWillTopic(), msg.isWillRetain());
|
||||
byte[] willPayload = msg.getWillMessage();
|
||||
if (msg.variableHeader().isWillFlag()) {
|
||||
MqttQoS willQos = MqttQoS.valueOf(msg.variableHeader().willQos());
|
||||
LOG.info("Configuring MQTT last will and testament. CId = {}, willQos = {}, willTopic = {}, willRetain = {}.",
|
||||
clientId, willQos, msg.payload().willTopic(), msg.variableHeader().isWillRetain());
|
||||
byte[] willPayload = msg.payload().willMessage().getBytes();
|
||||
ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
|
||||
//save the will testament in the clientID store
|
||||
WillMessage will = new WillMessage(msg.getWillTopic(), bb, msg.isWillRetain(),willQos);
|
||||
m_willStore.put(msg.getClientID(), will);
|
||||
LOG.info("MQTT last will and testament has been configured. MqttClientId = {}.", msg.getClientID());
|
||||
WillMessage will = new WillMessage(msg.payload().willTopic(), bb, msg.variableHeader().isWillRetain(), willQos);
|
||||
m_willStore.put(clientId, will);
|
||||
LOG.info("MQTT last will and testament has been configured. CId = {}.", clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSession createOrLoadClientSession(ConnectionDescriptor descriptor, ConnectMessage msg) {
|
||||
private ClientSession createOrLoadClientSession(ConnectionDescriptor descriptor, MqttConnectMessage msg,
|
||||
String clientId) {
|
||||
final boolean success = descriptor.assignState(ConnectionState.SENDACK, ConnectionState.SESSION_CREATED);
|
||||
if (!success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(msg.getClientID());
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(clientId);
|
||||
boolean isSessionAlreadyStored = clientSession != null;
|
||||
if (!isSessionAlreadyStored) {
|
||||
clientSession = m_sessionsStore.createNewSession(msg.getClientID(), msg.isCleanSession());
|
||||
clientSession = m_sessionsStore.createNewSession(clientId, msg.variableHeader().isCleanSession());
|
||||
}
|
||||
if (msg.isCleanSession()) {
|
||||
LOG.info("Cleaning session. MqttClientId = {}.", msg.getClientID());
|
||||
if (msg.variableHeader().isCleanSession()) {
|
||||
LOG.info("Cleaning session. CId = {}.", clientId);
|
||||
clientSession.cleanSession();
|
||||
}
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
private boolean republish(ConnectionDescriptor descriptor, ConnectMessage msg, ClientSession clientSession) {
|
||||
private boolean republish(ConnectionDescriptor descriptor, MqttConnectMessage msg, ClientSession clientSession) {
|
||||
final boolean success = descriptor.assignState(ConnectionState.SESSION_CREATED, ConnectionState.MESSAGES_REPUBLISHED);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!msg.isCleanSession()) {
|
||||
if (!msg.variableHeader().isCleanSession()) {
|
||||
//force the republish of stored QoS1 and QoS2
|
||||
republishStoredInSession(clientSession);
|
||||
}
|
||||
@@ -410,9 +430,7 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
private void failedCredentials(Channel session) {
|
||||
ConnAckMessage okResp = new ConnAckMessage();
|
||||
okResp.setReturnCode(ConnAckMessage.BAD_USERNAME_OR_PASSWORD);
|
||||
session.writeAndFlush(okResp);
|
||||
session.writeAndFlush(connAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD));
|
||||
LOG.info("Client {} failed to connect with bad username or password.", session);
|
||||
}
|
||||
|
||||
@@ -427,7 +445,7 @@ public class ProtocolProcessor {
|
||||
* Republish QoS1 and QoS2 messages stored into the session for the clientID.
|
||||
* */
|
||||
private void republishStoredInSession(ClientSession clientSession) {
|
||||
LOG.info("Republishing stored publish events. MqttClientId = {}.", clientSession.clientID);
|
||||
LOG.info("Republishing stored publish events. CId = {}.", clientSession.clientID);
|
||||
BlockingQueue<StoredMessage> publishedEvents = clientSession.queue();
|
||||
if (publishedEvents.isEmpty()) {
|
||||
LOG.info("There are no stored publish events. ClientId = {}.", clientSession.clientID);
|
||||
@@ -437,9 +455,9 @@ public class ProtocolProcessor {
|
||||
this.internalRepublisher.publishStored(clientSession, publishedEvents);
|
||||
}
|
||||
|
||||
public void processPubAck(Channel channel, PubAckMessage msg) {
|
||||
public void processPubAck(Channel channel, MqttPubAckMessage msg) {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
int messageID = msg.getMessageID();
|
||||
int messageID = msg.variableHeader().messageId();
|
||||
String username = NettyUtils.userName(channel);
|
||||
LOG.trace("retrieving inflight for messageID <{}>", messageID);
|
||||
|
||||
@@ -448,18 +466,18 @@ public class ProtocolProcessor {
|
||||
targetSession.inFlightAcknowledged(messageID);
|
||||
|
||||
String topic = inflightMsg.getTopic();
|
||||
|
||||
// MessageGUID guid = inflightMsg.getGuid();
|
||||
//Remove the message from message store
|
||||
// m_messagesStore.decUsageCounter(guid);
|
||||
|
||||
m_interceptor.notifyMessageAcknowledged(new InterceptAcknowledgedMessage(inflightMsg, topic, username));
|
||||
}
|
||||
|
||||
public static IMessagesStore.StoredMessage asStoredMessage(PublishMessage msg) {
|
||||
IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(msg.getPayload().array(), msg.getQos(), msg.getTopicName());
|
||||
stored.setRetained(msg.isRetainFlag());
|
||||
stored.setMessageID(msg.getMessageID());
|
||||
public static IMessagesStore.StoredMessage asStoredMessage(MqttPublishMessage msg) {
|
||||
//TODO ugly, too much array copy
|
||||
ByteBuf payload = msg.payload();
|
||||
byte[] payloadContent = readBytesAndRewind(payload);
|
||||
|
||||
IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(payloadContent,
|
||||
msg.fixedHeader().qosLevel(), msg.variableHeader().topicName());
|
||||
stored.setRetained(msg.fixedHeader().isRetain());
|
||||
stored.setMessageID(msg.variableHeader().messageId());
|
||||
return stored;
|
||||
}
|
||||
|
||||
@@ -469,15 +487,16 @@ public class ProtocolProcessor {
|
||||
return pub;
|
||||
}
|
||||
|
||||
public void processPublish(Channel channel, PublishMessage msg) {
|
||||
LOG.info("Processing PUBLISH message. MqttClientId = {}, topic = {}, messageId = {}, qos = {}.",
|
||||
msg.getClientId(), msg.getTopicName(), msg.getMessageID(), msg.getQos());
|
||||
final AbstractMessage.QOSType qos = msg.getQos();
|
||||
public void processPublish(Channel channel, MqttPublishMessage msg) {
|
||||
final MqttQoS qos = msg.fixedHeader().qosLevel();
|
||||
final String clientId = NettyUtils.clientID(channel);
|
||||
LOG.info("Processing PUBLISH message. CId = {}, topic = {}, messageId = {}, qos = {}.",
|
||||
clientId, msg.variableHeader().topicName(), msg.variableHeader().messageId(), qos);
|
||||
switch (qos) {
|
||||
case MOST_ONE:
|
||||
case AT_MOST_ONCE:
|
||||
this.qos0PublishHandler.receivedPublishQos0(channel, msg);
|
||||
break;
|
||||
case LEAST_ONE:
|
||||
case AT_LEAST_ONCE:
|
||||
this.qos1PublishHandler.receivedPublishQos1(channel, msg);
|
||||
break;
|
||||
case EXACTLY_ONCE:
|
||||
@@ -497,30 +516,31 @@ public class ProtocolProcessor {
|
||||
* it's publishing.
|
||||
*
|
||||
* @param msg the message to publish.
|
||||
* @param clientId the clientID
|
||||
* */
|
||||
public void internalPublish(PublishMessage msg) {
|
||||
final AbstractMessage.QOSType qos = msg.getQos();
|
||||
final String topic = msg.getTopicName();
|
||||
public void internalPublish(MqttPublishMessage msg, final String clientId) {
|
||||
final MqttQoS qos = msg.fixedHeader().qosLevel();
|
||||
final String topic = msg.variableHeader().topicName();
|
||||
LOG.info("Sending PUBLISH message. Topic = {}, qos = {}.", topic, qos);
|
||||
|
||||
MessageGUID guid = null;
|
||||
IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg);
|
||||
if (msg.getClientId() == null || msg.getClientId().isEmpty()) {
|
||||
if (clientId == null || clientId.isEmpty()) {
|
||||
toStoreMsg.setClientID("BROKER_SELF");
|
||||
} else {
|
||||
toStoreMsg.setClientID(msg.getClientId());
|
||||
toStoreMsg.setClientID(clientId);
|
||||
}
|
||||
toStoreMsg.setMessageID(1);
|
||||
if (qos == AbstractMessage.QOSType.EXACTLY_ONCE) { //QoS2
|
||||
if (qos == EXACTLY_ONCE) { //QoS2
|
||||
guid = m_messagesStore.storePublishForFuture(toStoreMsg);
|
||||
}
|
||||
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
|
||||
this.messagesPublisher.publish2Subscribers(toStoreMsg, topicMatchingSubscriptions);
|
||||
|
||||
if (!msg.isRetainFlag()) {
|
||||
if (!msg.fixedHeader().isRetain()) {
|
||||
return;
|
||||
}
|
||||
if (qos == AbstractMessage.QOSType.MOST_ONE || !msg.getPayload().hasRemaining()) {
|
||||
if (qos == AT_MOST_ONCE || msg.payload().readableBytes() == 0) {
|
||||
//QoS == 0 && retain => clean old retained
|
||||
m_messagesStore.cleanRetained(topic);
|
||||
return;
|
||||
@@ -539,7 +559,7 @@ public class ProtocolProcessor {
|
||||
//it has just to publish the message downstream to the subscribers
|
||||
//NB it's a will publish, it needs a PacketIdentifier for this conn, default to 1
|
||||
Integer messageId = null;
|
||||
if (will.getQos() != AbstractMessage.QOSType.MOST_ONE) {
|
||||
if (will.getQos() != AT_MOST_ONCE) {
|
||||
messageId = m_sessionsStore.nextPacketID(clientID);
|
||||
}
|
||||
|
||||
@@ -549,12 +569,12 @@ public class ProtocolProcessor {
|
||||
String topic = tobeStored.getTopic();
|
||||
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
|
||||
|
||||
LOG.info("Publishing will message. MqttClientId = {}, messageId = {}, topic = {}.", clientID, messageId, topic);
|
||||
LOG.info("Publishing will message. CId = {}, messageId = {}, topic = {}.", clientID, messageId, topic);
|
||||
this.messagesPublisher.publish2Subscribers(tobeStored, topicMatchingSubscriptions);
|
||||
}
|
||||
|
||||
static QOSType lowerQosToTheSubscriptionDesired(Subscription sub, QOSType qos) {
|
||||
if (qos.byteValue() > sub.getRequestedQos().byteValue()) {
|
||||
static MqttQoS lowerQosToTheSubscriptionDesired(Subscription sub, MqttQoS qos) {
|
||||
if (qos.value() > sub.getRequestedQos().value()) {
|
||||
qos = sub.getRequestedQos();
|
||||
}
|
||||
return qos;
|
||||
@@ -566,29 +586,28 @@ public class ProtocolProcessor {
|
||||
* @param channel the channel of the incoming message.
|
||||
* @param msg the decoded pubrel message.
|
||||
* */
|
||||
public void processPubRel(Channel channel, PubRelMessage msg) {
|
||||
public void processPubRel(Channel channel, MqttMessage msg) {
|
||||
this.qos2PublishHandler.processPubRel(channel, msg);
|
||||
}
|
||||
|
||||
public void processPubRec(Channel channel, PubRecMessage msg) {
|
||||
public void processPubRec(Channel channel, MqttMessage msg) {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
//remove from the inflight and move to the QoS2 second phase queue
|
||||
int messageID = msg.getMessageID();
|
||||
int messageID = messageId(msg);
|
||||
targetSession.moveInFlightToSecondPhaseAckWaiting(messageID);
|
||||
//once received a PUBREC reply with a PUBREL(messageID)
|
||||
LOG.debug("Processing PUBREC message. MqttClientId = {}, messageId = {}.", clientID, messageID);
|
||||
PubRelMessage pubRelMessage = new PubRelMessage();
|
||||
pubRelMessage.setMessageID(messageID);
|
||||
pubRelMessage.setQos(AbstractMessage.QOSType.LEAST_ONE);
|
||||
LOG.debug("Processing PUBREC message. CId = {}, messageId = {}.", clientID, messageID);
|
||||
|
||||
MqttFixedHeader pubRelHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
|
||||
MqttMessage pubRelMessage = new MqttMessage(pubRelHeader, from(messageID));
|
||||
channel.writeAndFlush(pubRelMessage);
|
||||
}
|
||||
|
||||
public void processPubComp(Channel channel, PubCompMessage msg) {
|
||||
public void processPubComp(Channel channel, MqttMessage msg) {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
int messageID = msg.getMessageID();
|
||||
LOG.debug("Processing PUBCOMP message. MqttClientId = {}, messageId = {}.", clientID, messageID);
|
||||
int messageID = messageId(msg);
|
||||
LOG.debug("Processing PUBCOMP message. CId = {}, messageId = {}.", clientID, messageID);
|
||||
//once received the PUBCOMP then remove the message from the temp memory
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
StoredMessage inflightMsg = targetSession.secondPhaseAcknowledged(messageID);
|
||||
@@ -599,7 +618,7 @@ public class ProtocolProcessor {
|
||||
|
||||
public void processDisconnect(Channel channel) throws InterruptedException {
|
||||
final String clientID = NettyUtils.clientID(channel);
|
||||
LOG.info("Processing DISCONNECT message. MqttClientId = {}.", clientID);
|
||||
LOG.info("Processing DISCONNECT message. CId = {}.", clientID);
|
||||
channel.flush();
|
||||
final ConnectionDescriptor existingDescriptor = this.connectionDescriptors.getConnection(clientID);
|
||||
if (existingDescriptor == null) {
|
||||
@@ -610,43 +629,43 @@ public class ProtocolProcessor {
|
||||
|
||||
if (existingDescriptor.doesNotUseChannel(channel)) {
|
||||
//another client saved it's descriptor, exit
|
||||
LOG.warn("Another client is using the connection descriptor. Closing connection. MqttClientId = {}.",
|
||||
LOG.warn("Another client is using the connection descriptor. Closing connection. CId = {}.",
|
||||
clientID);
|
||||
existingDescriptor.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeSubscriptions(existingDescriptor, clientID)) {
|
||||
LOG.warn("Unable to remove subscriptions. Closing connection. MqttClientId = {}.", clientID);
|
||||
LOG.warn("Unable to remove subscriptions. Closing connection. CId = {}.", clientID);
|
||||
existingDescriptor.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dropStoredMessages(existingDescriptor, clientID)) {
|
||||
LOG.warn("Unable to drop stored messages. Closing connection. MqttClientId = {}.", clientID);
|
||||
LOG.warn("Unable to drop stored messages. Closing connection. CId = {}.", clientID);
|
||||
existingDescriptor.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cleanWillMessageAndNotifyInterceptor(existingDescriptor, clientID)) {
|
||||
LOG.warn("Unable to drop will message. Closing connection. MqttClientId = {}.", clientID);
|
||||
LOG.warn("Unable to drop will message. Closing connection. CId = {}.", clientID);
|
||||
existingDescriptor.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existingDescriptor.close()) {
|
||||
LOG.info("The connection has been closed. MqttClientId = {}.", clientID);
|
||||
LOG.info("The connection has been closed. CId = {}.", clientID);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean stillPresent = this.connectionDescriptors.removeConnection(existingDescriptor);
|
||||
if (!stillPresent) {
|
||||
//another descriptor was inserted
|
||||
LOG.warn("Another descriptor has been inserted. MqttClientId = {}.", clientID);
|
||||
LOG.warn("Another descriptor has been inserted. CId = {}.", clientID);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("The DISCONNECT message has been processed. MqttClientId = {}.", clientID);
|
||||
LOG.info("The DISCONNECT message has been processed. CId = {}.", clientID);
|
||||
}
|
||||
|
||||
private boolean removeSubscriptions(ConnectionDescriptor descriptor, String clientID) {
|
||||
@@ -656,9 +675,9 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
if (descriptor.cleanSession) {
|
||||
LOG.info("Removing saved subscriptions. MqttClientId = {}.", descriptor.clientID);
|
||||
LOG.info("Removing saved subscriptions. CId = {}.", descriptor.clientID);
|
||||
m_sessionsStore.wipeSubscriptions(clientID);
|
||||
LOG.info("The saved subscriptions have been removed. MqttClientId = {}.", descriptor.clientID);
|
||||
LOG.info("The saved subscriptions have been removed. CId = {}.", descriptor.clientID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -670,9 +689,9 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
if (descriptor.cleanSession) {
|
||||
LOG.debug("Removing messages of session. MqttClientId = {}.", descriptor.clientID);
|
||||
LOG.debug("Removing messages of session. CId = {}.", descriptor.clientID);
|
||||
this.m_sessionsStore.dropQueue(clientID);
|
||||
LOG.debug("The messages of the session have been removed. MqttClientId = {}.", descriptor.clientID);
|
||||
LOG.debug("The messages of the session have been removed. CId = {}.", descriptor.clientID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -692,7 +711,7 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
public void processConnectionLost(String clientID, Channel channel) {
|
||||
LOG.info("Processing connection lost event. MqttClientId = {}.", clientID);
|
||||
LOG.info("Processing connection lost event. CId = {}.", clientID);
|
||||
ConnectionDescriptor oldConnDescr = new ConnectionDescriptor(clientID, channel, true);
|
||||
connectionDescriptors.removeConnection(oldConnDescr);
|
||||
//publish the Will message (if any) for the clientID
|
||||
@@ -712,11 +731,11 @@ public class ProtocolProcessor {
|
||||
* @param channel the channel of the incoming message.
|
||||
* @param msg the decoded unsubscribe message.
|
||||
*/
|
||||
public void processUnsubscribe(Channel channel, UnsubscribeMessage msg) {
|
||||
List<String> topics = msg.topicFilters();
|
||||
public void processUnsubscribe(Channel channel, MqttUnsubscribeMessage msg) {
|
||||
List<String> topics = msg.payload().topics();
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
|
||||
LOG.info("Processing UNSUBSCRIBE message. MqttClientId = {}, topics = {}.", clientID, topics);
|
||||
LOG.info("Processing UNSUBSCRIBE message. CId = {}, topics = {}.", clientID, topics);
|
||||
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
|
||||
for (String topic : topics) {
|
||||
@@ -724,12 +743,12 @@ public class ProtocolProcessor {
|
||||
if (!validTopic) {
|
||||
//close the connection, not valid topicFilter is a protocol violation
|
||||
channel.close();
|
||||
LOG.error("The topic filter is not valid. MqttClientId = {}, topics = {}, badTopicFilter = {}.",
|
||||
LOG.error("The topic filter is not valid. CId = {}, topics = {}, badTopicFilter = {}.",
|
||||
clientID, topics, topic);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Removing subscription. MqttClientId = {}, topic = {}.", clientID, topic);
|
||||
LOG.debug("Removing subscription. CId = {}, topic = {}.", clientID, topic);
|
||||
subscriptions.removeSubscription(topic, clientID);
|
||||
clientSession.unsubscribeFrom(topic);
|
||||
String username = NettyUtils.userName(channel);
|
||||
@@ -737,40 +756,37 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
//ack the client
|
||||
int messageID = msg.getMessageID();
|
||||
UnsubAckMessage ackMessage = new UnsubAckMessage();
|
||||
ackMessage.setMessageID(messageID);
|
||||
int messageID = msg.variableHeader().messageId();
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, AT_LEAST_ONCE, false, 0);
|
||||
MqttUnsubAckMessage ackMessage = new MqttUnsubAckMessage(fixedHeader, from(messageID));
|
||||
|
||||
LOG.info("Sending UNSUBACK message. MqttClientId = {}, topics = {}, messageId = {}.", clientID, topics,
|
||||
messageID);
|
||||
LOG.info("Sending UNSUBACK message. CId = {}, topics = {}, messageId = {}.", clientID, topics, messageID);
|
||||
channel.writeAndFlush(ackMessage);
|
||||
}
|
||||
|
||||
public void processSubscribe(Channel channel, SubscribeMessage msg) {
|
||||
public void processSubscribe(Channel channel, MqttSubscribeMessage msg) {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
int messageID = msg.getMessageID();
|
||||
LOG.info("Processing SUBSCRIBE message. MqttClientId = {}, messageId = {}.", clientID, msg.getMessageID());
|
||||
int messageID = messageId(msg);
|
||||
LOG.info("Processing SUBSCRIBE message. CId = {}, messageId = {}.", clientID, messageID);
|
||||
|
||||
RunningSubscription executionKey = new RunningSubscription(clientID, messageID);
|
||||
SubscriptionState currentStatus = this.subscriptionInCourse.putIfAbsent(executionKey, SubscriptionState.VERIFIED);
|
||||
if (currentStatus != null) {
|
||||
LOG.warn("The client sent another SUBSCRIBE message while this one was being processed. MqttClientId = {}, messageId = {}.",
|
||||
clientID, msg.getMessageID());
|
||||
LOG.warn("The client sent another SUBSCRIBE message while this one was being processed. " +
|
||||
"CId = {}, messageId = {}.", clientID, messageID);
|
||||
return;
|
||||
}
|
||||
String username = NettyUtils.userName(channel);
|
||||
List<SubscribeMessage.Couple> ackTopics = doVerify(clientID, username, msg);
|
||||
SubAckMessage ackMessage = doAckMessageFromValidateFilters(ackTopics);
|
||||
List<MqttTopicSubscription> ackTopics = doVerify(clientID, username, msg);
|
||||
MqttSubAckMessage ackMessage = doAckMessageFromValidateFilters(ackTopics, messageID);
|
||||
if (!this.subscriptionInCourse.replace(executionKey, SubscriptionState.VERIFIED, SubscriptionState.STORED)) {
|
||||
LOG.warn("The client sent another SUBSCRIBE message while the topic filters were being verified. MqttClientId = {}, messageId = {}.",
|
||||
clientID, msg.getMessageID());
|
||||
LOG.warn("The client sent another SUBSCRIBE message while the topic filters were being verified. " +
|
||||
"CId = {}, messageId = {}.", clientID, messageID);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Creating and storing subscriptions. MqttClientId = {}, messageId = {}, topics = {}.", clientID,
|
||||
msg.getMessageID(), ackTopics);
|
||||
LOG.info("Creating and storing subscriptions CId={}, messageId={}, topics={}", clientID, messageID, ackTopics);
|
||||
|
||||
ackMessage.setMessageID(messageID);
|
||||
List<Subscription> newSubscriptions = doStoreSubscription(ackTopics, clientID);
|
||||
|
||||
//save session, persist subscriptions from session
|
||||
@@ -779,7 +795,7 @@ public class ProtocolProcessor {
|
||||
subscriptions.add(subscription.asClientTopicCouple());
|
||||
}
|
||||
|
||||
LOG.info("Sending SUBACK response. MqttClientId = {}, messageId = {}.", clientID, msg.getMessageID());
|
||||
LOG.info("Sending SUBACK response CId={}, messageId={}", clientID, messageID);
|
||||
channel.writeAndFlush(ackMessage);
|
||||
|
||||
//fire the persisted messages in session
|
||||
@@ -789,22 +805,20 @@ public class ProtocolProcessor {
|
||||
|
||||
boolean success = this.subscriptionInCourse.remove(executionKey, SubscriptionState.STORED);
|
||||
if (!success) {
|
||||
LOG.warn("Unable to perform the final subscription state update. MqttClientId = {}, messageId = {}.",
|
||||
clientID, msg.getMessageID());
|
||||
LOG.warn("Unable to perform the final subscription state update CId={}, messageId={}", clientID, messageID);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Subscription> doStoreSubscription(List<SubscribeMessage.Couple> ackTopics, String clientID) {
|
||||
private List<Subscription> doStoreSubscription(List<MqttTopicSubscription> ackTopics, String clientID) {
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
|
||||
|
||||
List<Subscription> newSubscriptions = new ArrayList<>();
|
||||
for (SubscribeMessage.Couple req : ackTopics) {
|
||||
for (MqttTopicSubscription req : ackTopics) {
|
||||
//TODO this is SUPER UGLY
|
||||
if (req.qos == AbstractMessage.QOSType.FAILURE.byteValue()) {
|
||||
if (req.qualityOfService() == FAILURE) {
|
||||
continue;
|
||||
}
|
||||
AbstractMessage.QOSType qos = AbstractMessage.QOSType.valueOf(req.qos);
|
||||
Subscription newSubscription = new Subscription(clientID, req.topicFilter, qos);
|
||||
Subscription newSubscription = new Subscription(clientID, req.topicName(), req.qualityOfService());
|
||||
clientSession.subscribe(newSubscription);
|
||||
newSubscriptions.add(newSubscription);
|
||||
}
|
||||
@@ -812,32 +826,34 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of verified topics
|
||||
* @param clientID
|
||||
* @param username
|
||||
* @param clientID the clientID
|
||||
* @param username the username
|
||||
* @param msg the subscribe message to verify
|
||||
* @return the list of verified topics for the given subscribe message.
|
||||
* */
|
||||
private List<SubscribeMessage.Couple> doVerify(String clientID, String username, SubscribeMessage msg) {
|
||||
private List<MqttTopicSubscription> doVerify(String clientID, String username, MqttSubscribeMessage msg) {
|
||||
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
|
||||
List<SubscribeMessage.Couple> ackTopics = new ArrayList<>();
|
||||
List<MqttTopicSubscription> ackTopics = new ArrayList<>();
|
||||
|
||||
for (SubscribeMessage.Couple req : msg.subscriptions()) {
|
||||
if (!m_authorizator.canRead(req.topicFilter, username, clientSession.clientID)) {
|
||||
final int messageId = messageId(msg);
|
||||
for (MqttTopicSubscription req : msg.payload().topicSubscriptions()) {
|
||||
if (!m_authorizator.canRead(req.topicName(), username, clientSession.clientID)) {
|
||||
//send SUBACK with 0x80, the user hasn't credentials to read the topic
|
||||
LOG.error("The client does not have read permissions on the topic. MqttClientId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, msg.getMessageID(), req.topicFilter);
|
||||
ackTopics.add(new SubscribeMessage.Couple(AbstractMessage.QOSType.FAILURE.byteValue(), req.topicFilter));
|
||||
LOG.error("The client does not have read permissions on the topic. CId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, messageId, req.topicName());
|
||||
ackTopics.add(new MqttTopicSubscription(req.topicName(), FAILURE));
|
||||
} else {
|
||||
AbstractMessage.QOSType qos = null;
|
||||
if (SubscriptionsStore.validate(req.topicFilter)) {
|
||||
qos = AbstractMessage.QOSType.valueOf(req.qos);
|
||||
LOG.info("The client will be subscribed to the topic. MqttClientId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, msg.getMessageID(), req.topicFilter);
|
||||
MqttQoS qos;
|
||||
if (SubscriptionsStore.validate(req.topicName())) {
|
||||
LOG.info("The client will be subscribed to the topic. CId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, messageId, req.topicName());
|
||||
qos = req.qualityOfService();
|
||||
} else {
|
||||
LOG.error("The topic filter is not valid. MqttClientId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, msg.getMessageID(), req.topicFilter);
|
||||
qos = AbstractMessage.QOSType.FAILURE;
|
||||
LOG.error("The topic filter is not valid. CId = {}, username = {}, messageId = {}, topic = {}.",
|
||||
clientID, username, messageId, req.topicName());
|
||||
qos = FAILURE;
|
||||
}
|
||||
ackTopics.add(new SubscribeMessage.Couple(qos.byteValue(), req.topicFilter));
|
||||
ackTopics.add(new MqttTopicSubscription(req.topicName(), qos));
|
||||
}
|
||||
}
|
||||
return ackTopics;
|
||||
@@ -846,17 +862,19 @@ public class ProtocolProcessor {
|
||||
/**
|
||||
* Create the SUBACK response from a list of topicFilters
|
||||
* */
|
||||
private SubAckMessage doAckMessageFromValidateFilters(List<SubscribeMessage.Couple> topicFilters) {
|
||||
//ack the client
|
||||
SubAckMessage ackMessage = new SubAckMessage();
|
||||
for (SubscribeMessage.Couple req : topicFilters) {
|
||||
ackMessage.addType(AbstractMessage.QOSType.valueOf(req.qos));
|
||||
private MqttSubAckMessage doAckMessageFromValidateFilters(List<MqttTopicSubscription> topicFilters, int messageId) {
|
||||
List<Integer> grantedQoSLevels = new ArrayList<>();
|
||||
for (MqttTopicSubscription req : topicFilters) {
|
||||
grantedQoSLevels.add(req.qualityOfService().value());
|
||||
}
|
||||
return ackMessage;
|
||||
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK, false, AT_LEAST_ONCE, false, 0);
|
||||
MqttSubAckPayload payload = new MqttSubAckPayload(grantedQoSLevels);
|
||||
return new MqttSubAckMessage(fixedHeader, from(messageId), payload);
|
||||
}
|
||||
|
||||
private void publishRetainedMessagesInSession(final Subscription newSubscription, String username) {
|
||||
LOG.info("Retrieving retained messages. MqttClientId = {}, topics = {}.", newSubscription.getClientId(),
|
||||
LOG.info("Retrieving retained messages. CId = {}, topics = {}.", newSubscription.getClientId(),
|
||||
newSubscription.getTopicFilter());
|
||||
|
||||
//scans retained messages to be published to the new subscription
|
||||
@@ -869,7 +887,7 @@ public class ProtocolProcessor {
|
||||
});
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
LOG.info("Publishing retained messages. MqttClientId = {}, topics = {}, messagesNo = {}.",
|
||||
LOG.info("Publishing retained messages. CId = {}, topics = {}, messagesNo = {}.",
|
||||
newSubscription.getClientId(), newSubscription.getTopicFilter(), messages.size());
|
||||
}
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(newSubscription.getClientId());
|
||||
@@ -890,7 +908,7 @@ public class ProtocolProcessor {
|
||||
} else {
|
||||
//recreate a publish from stored publish in queue
|
||||
boolean retained = m_messagesStore.getMessageByGuid(msg.getGuid()) != null;
|
||||
PublishMessage pubMsg = createPublishForQos(msg.getTopic(), msg.getQos(), msg.getMessage(), retained);
|
||||
MqttPublishMessage pubMsg = createPublishForQos(msg.getTopic(), msg.getQos(), msg.getMessage(), retained, 0);
|
||||
channel.write(pubMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.security.IAuthorizator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -33,9 +33,9 @@ class Qos0PublishHandler extends QosPublishHandler {
|
||||
this.publisher = messagesPublisher;
|
||||
}
|
||||
|
||||
void receivedPublishQos0(Channel channel, PublishMessage msg) {
|
||||
void receivedPublishQos0(Channel channel, MqttPublishMessage msg) {
|
||||
//verify if topic can be write
|
||||
final String topic = msg.getTopicName();
|
||||
final String topic = msg.variableHeader().topicName();
|
||||
if (checkWriteOnTopic(topic, channel)) {
|
||||
return;
|
||||
}
|
||||
@@ -45,19 +45,21 @@ class Qos0PublishHandler extends QosPublishHandler {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
toStoreMsg.setClientID(clientID);
|
||||
|
||||
final int messageID = msg.variableHeader().messageId();
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
|
||||
clientID, topic, msg.getMessageID(), DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
subscriptions.dumpTree());
|
||||
} else {
|
||||
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
|
||||
topic, msg.getMessageID());
|
||||
topic, messageID);
|
||||
}
|
||||
|
||||
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
|
||||
this.publisher.publish2Subscribers(toStoreMsg, topicMatchingSubscriptions);
|
||||
|
||||
if (msg.isRetainFlag()) {
|
||||
if (msg.fixedHeader().isRetain()) {
|
||||
//QoS == 0 && retain => clean old retained
|
||||
m_messagesStore.cleanRetained(topic);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.parser.proto.messages.PubAckMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.server.ConnectionDescriptorStore;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
@@ -10,12 +8,18 @@ import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.security.IAuthorizator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.mqtt.MqttFixedHeader;
|
||||
import io.netty.handler.codec.mqtt.MqttMessageType;
|
||||
import io.netty.handler.codec.mqtt.MqttPubAckMessage;
|
||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
|
||||
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
|
||||
|
||||
class Qos1PublishHandler extends QosPublishHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Qos1PublishHandler.class);
|
||||
@@ -38,13 +42,15 @@ class Qos1PublishHandler extends QosPublishHandler {
|
||||
this.publisher = messagesPublisher;
|
||||
}
|
||||
|
||||
void receivedPublishQos1(Channel channel, PublishMessage msg) {
|
||||
void receivedPublishQos1(Channel channel, MqttPublishMessage msg) {
|
||||
//verify if topic can be write
|
||||
final String topic = msg.getTopicName();
|
||||
final String topic = msg.variableHeader().topicName();
|
||||
if (checkWriteOnTopic(topic, channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int messageID = msg.variableHeader().messageId();
|
||||
|
||||
//route message to subscribers
|
||||
IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg);
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
@@ -52,24 +58,23 @@ class Qos1PublishHandler extends QosPublishHandler {
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
|
||||
clientID, topic, msg.getMessageID(), DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
subscriptions.dumpTree());
|
||||
} else {
|
||||
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
|
||||
topic, msg.getMessageID());
|
||||
topic, messageID);
|
||||
}
|
||||
|
||||
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
|
||||
this.publisher.publish2Subscribers(toStoreMsg, topicMatchingSubscriptions);
|
||||
|
||||
//send PUBACK
|
||||
final Integer messageID = msg.getMessageID();
|
||||
if (msg.isLocal()) {
|
||||
//TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
|
||||
sendPubAck(clientID, messageID);
|
||||
}
|
||||
// }
|
||||
|
||||
if (msg.isRetainFlag()) {
|
||||
if (!msg.getPayload().hasRemaining()) {
|
||||
if (msg.fixedHeader().isRetain()) {
|
||||
if (!msg.payload().isReadable()) {
|
||||
m_messagesStore.cleanRetained(topic);
|
||||
} else {
|
||||
//before wasn't stored
|
||||
@@ -84,8 +89,8 @@ class Qos1PublishHandler extends QosPublishHandler {
|
||||
|
||||
private void sendPubAck(String clientId, int messageID) {
|
||||
LOG.trace("sendPubAck invoked");
|
||||
PubAckMessage pubAckMessage = new PubAckMessage();
|
||||
pubAckMessage.setMessageID(messageID);
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, AT_MOST_ONCE, false, 0);
|
||||
MqttPubAckMessage pubAckMessage = new MqttPubAckMessage(fixedHeader, from(messageID));
|
||||
|
||||
try {
|
||||
if (connectionDescriptors == null) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.parser.proto.messages.*;
|
||||
import io.moquette.server.ConnectionDescriptorStore;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.ClientSession;
|
||||
@@ -11,13 +10,16 @@ import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.security.IAuthorizator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.mqtt.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
|
||||
|
||||
import java.util.List;
|
||||
import static io.moquette.spi.impl.Utils.messageId;
|
||||
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
|
||||
|
||||
class Qos2PublishHandler extends QosPublishHandler {
|
||||
|
||||
@@ -43,37 +45,38 @@ class Qos2PublishHandler extends QosPublishHandler {
|
||||
this.publisher = messagesPublisher;
|
||||
}
|
||||
|
||||
void receivedPublishQos2(Channel channel, PublishMessage msg) {
|
||||
final String topic = msg.getTopicName();
|
||||
void receivedPublishQos2(Channel channel, MqttPublishMessage msg) {
|
||||
final String topic = msg.variableHeader().topicName();
|
||||
//check if the topic can be wrote
|
||||
if (checkWriteOnTopic(topic, channel)) {
|
||||
return;
|
||||
}
|
||||
final Integer messageID = msg.getMessageID();
|
||||
final int messageID = msg.variableHeader().messageId();
|
||||
|
||||
IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg);
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
toStoreMsg.setClientID(clientID);
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
|
||||
clientID, topic, msg.getMessageID(), DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, " +
|
||||
"payload = {}, subscriptionTree = {}.",
|
||||
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
|
||||
subscriptions.dumpTree());
|
||||
} else {
|
||||
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
|
||||
topic, msg.getMessageID());
|
||||
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
|
||||
clientID, topic, messageID);
|
||||
}
|
||||
|
||||
//QoS2
|
||||
MessageGUID guid = m_messagesStore.storePublishForFuture(toStoreMsg);
|
||||
if (msg.isLocal()) {
|
||||
//TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
|
||||
sendPubRec(clientID, messageID);
|
||||
}
|
||||
//}
|
||||
//Next the client will send us a pub rel
|
||||
//NB publish to subscribers for QoS 2 happen upon PUBREL from publisher
|
||||
|
||||
if (msg.isRetainFlag()) {
|
||||
if (!msg.getPayload().hasRemaining()) {
|
||||
if (msg.fixedHeader().isRetain()) {
|
||||
if (msg.payload().readableBytes() == 0) {
|
||||
m_messagesStore.cleanRetained(topic);
|
||||
} else {
|
||||
m_messagesStore.storeRetained(topic, guid);
|
||||
@@ -87,9 +90,9 @@ class Qos2PublishHandler extends QosPublishHandler {
|
||||
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored message and publish
|
||||
* to all interested subscribers.
|
||||
* */
|
||||
void processPubRel(Channel channel, PubRelMessage msg) {
|
||||
void processPubRel(Channel channel, MqttMessage msg) {
|
||||
String clientID = NettyUtils.clientID(channel);
|
||||
int messageID = msg.getMessageID();
|
||||
int messageID = messageId(msg);
|
||||
LOG.info("Processing PUBREL message. MqttClientId = {}, messageId = {}.", clientID, messageID);
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
IMessagesStore.StoredMessage evt = targetSession.storedMessage(messageID);
|
||||
@@ -98,16 +101,16 @@ class Qos2PublishHandler extends QosPublishHandler {
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
|
||||
clientID, topic, msg.getMessageID(), DebugUtils.payload2Str(evt.getMessage()),
|
||||
clientID, topic, messageID, DebugUtils.payload2Str(evt.getMessage()),
|
||||
subscriptions.dumpTree());
|
||||
} else {
|
||||
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
|
||||
topic, msg.getMessageID());
|
||||
topic, messageID);
|
||||
}
|
||||
this.publisher.publish2Subscribers(evt, topicMatchingSubscriptions);
|
||||
|
||||
if (evt.isRetained()) {
|
||||
if (!evt.getMessage().hasRemaining()) {
|
||||
if (evt.getMessage().readableBytes() == 0) {
|
||||
m_messagesStore.cleanRetained(topic);
|
||||
} else {
|
||||
m_messagesStore.storeRetained(topic, evt.getGuid());
|
||||
@@ -120,15 +123,15 @@ class Qos2PublishHandler extends QosPublishHandler {
|
||||
|
||||
private void sendPubRec(String clientID, int messageID) {
|
||||
LOG.debug("Sending PUBREC message. MqttClientId = {}, messageId = {}.", clientID, messageID);
|
||||
PubRecMessage pubRecMessage = new PubRecMessage();
|
||||
pubRecMessage.setMessageID(messageID);
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, AT_MOST_ONCE, false, 0);
|
||||
MqttMessage pubRecMessage = new MqttMessage(fixedHeader, from(messageID));
|
||||
connectionDescriptors.sendMessage(pubRecMessage,messageID, clientID);
|
||||
}
|
||||
|
||||
private void sendPubComp(String clientID, int messageID) {
|
||||
LOG.debug("Sending PUBCOMP message. MqttClientId = {}, messageId = {}.", clientID, messageID);
|
||||
PubCompMessage pubCompMessage = new PubCompMessage();
|
||||
pubCompMessage.setMessageID(messageID);
|
||||
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, AT_MOST_ONCE, false, 0);
|
||||
MqttMessage pubCompMessage = new MqttMessage(fixedHeader, from(messageID));
|
||||
connectionDescriptors.sendMessage(pubCompMessage, messageID, clientID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.mqtt.MqttMessage;
|
||||
import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility static methods, like Map get with default value, or elvis operator.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
public static <T, K> T defaultGet(Map<K, T> map, K key, T defaultValue) {
|
||||
T value = map.get(key);
|
||||
if (value != null) {
|
||||
@@ -13,4 +18,16 @@ public class Utils {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int messageId(MqttMessage msg) {
|
||||
return ((MqttMessageIdVariableHeader) msg.variableHeader()).messageId();
|
||||
}
|
||||
|
||||
public static byte[] readBytesAndRewind(ByteBuf payload) {
|
||||
byte[] payloadContent = new byte[payload.readableBytes()];
|
||||
int mark = payload.readerIndex();
|
||||
payload.readBytes(payloadContent);
|
||||
payload.readerIndex(mark);
|
||||
return payloadContent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import java.io.Serializable;
|
||||
import io.moquette.parser.proto.messages.AbstractMessage.QOSType;
|
||||
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
||||
import io.netty.handler.codec.mqtt.MqttQoS;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Maintain the information about which Topic a certain ClientID is subscribed
|
||||
@@ -29,12 +30,12 @@ import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
||||
public final class Subscription implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -3383457629635732794L;
|
||||
final QOSType requestedQos; //max QoS acceptable
|
||||
final MqttQoS requestedQos; //max QoS acceptable
|
||||
final String clientId;
|
||||
final String topicFilter;
|
||||
final boolean active;
|
||||
|
||||
public Subscription(String clientId, String topicFilter, QOSType requestedQos) {
|
||||
public Subscription(String clientId, String topicFilter, MqttQoS requestedQos) {
|
||||
this.requestedQos = requestedQos;
|
||||
this.clientId = clientId;
|
||||
this.topicFilter = topicFilter;
|
||||
@@ -52,7 +53,7 @@ public final class Subscription implements Serializable {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public QOSType getRequestedQos() {
|
||||
public MqttQoS getRequestedQos() {
|
||||
return requestedQos;
|
||||
}
|
||||
|
||||
@@ -85,7 +86,8 @@ public final class Subscription implements Serializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[filter:%s, cliID: %s, qos: %s, active: %s]", this.topicFilter, this.clientId, this.requestedQos, this.active);
|
||||
return String.format("[filter:%s, cliID: %s, qos: %s, active: %s]", this.topicFilter, this.clientId,
|
||||
this.requestedQos, this.active);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Represents a tree of topics subscriptions.
|
||||
*
|
||||
@@ -235,7 +235,7 @@ public class SubscriptionsStore {
|
||||
continue;
|
||||
}
|
||||
//update the selected subscriptions if not present or if has a greater qos
|
||||
if (existingSub == null || existingSub.getRequestedQos().byteValue() < sub.getRequestedQos().byteValue()) {
|
||||
if (existingSub == null || existingSub.getRequestedQos().value() < sub.getRequestedQos().value()) {
|
||||
subsForClient.put(matchingCouple.clientID, sub);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user