broker/ProtocolProcessor: code formatter used

This commit is contained in:
Andreas Mager
2017-02-01 17:13:39 +01:00
parent 6eb635f437
commit db31f77f9b
@@ -13,6 +13,7 @@
* *
* You may elect to redistribute this code under either of these licenses. * You may elect to redistribute this code under either of these licenses.
*/ */
package io.moquette.spi.impl; package io.moquette.spi.impl;
import io.moquette.interception.InterceptHandler; import io.moquette.interception.InterceptHandler;
@@ -34,7 +35,6 @@ import io.netty.handler.codec.mqtt.*;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -43,7 +43,6 @@ import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos; import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
import static io.moquette.spi.impl.Utils.messageId; import static io.moquette.spi.impl.Utils.messageId;
import static io.moquette.spi.impl.Utils.readBytesAndRewind; import static io.moquette.spi.impl.Utils.readBytesAndRewind;
@@ -52,8 +51,8 @@ import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
import static io.netty.handler.codec.mqtt.MqttQoS.*; import static io.netty.handler.codec.mqtt.MqttQoS.*;
/** /**
* Class responsible to handle the logic of MQTT protocol it's the director of * Class responsible to handle the logic of MQTT protocol it's the director of the protocol
* the protocol execution. * execution.
* *
* Used by the front facing class ProtocolProcessorBootstrapper. * Used by the front facing class ProtocolProcessorBootstrapper.
* *
@@ -62,6 +61,7 @@ import static io.netty.handler.codec.mqtt.MqttQoS.*;
public class ProtocolProcessor { public class ProtocolProcessor {
static final class WillMessage { static final class WillMessage {
private final String topic; private final String topic;
private final ByteBuffer payload; private final ByteBuffer payload;
private final boolean retained; private final boolean retained;
@@ -89,7 +89,6 @@ public class ProtocolProcessor {
public MqttQoS getQos() { public MqttQoS getQos() {
return qos; return qos;
} }
} }
private enum SubscriptionState { private enum SubscriptionState {
@@ -97,6 +96,7 @@ public class ProtocolProcessor {
} }
private class RunningSubscription { private class RunningSubscription {
final String clientID; final String clientID;
final long packetId; final long packetId;
@@ -107,12 +107,15 @@ public class ProtocolProcessor {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o)
if (o == null || getClass() != o.getClass()) return false; return true;
if (o == null || getClass() != o.getClass())
return false;
RunningSubscription that = (RunningSubscription) o; RunningSubscription that = (RunningSubscription) o;
return packetId == that.packetId && (clientID != null ? clientID.equals(that.clientID) : that.clientID == null); return packetId == that.packetId
&& (clientID != null ? clientID.equals(that.clientID) : that.clientID == null);
} }
@@ -145,52 +148,80 @@ public class ProtocolProcessor {
private MessagesPublisher messagesPublisher; private MessagesPublisher messagesPublisher;
private InternalRepublisher internalRepublisher; private InternalRepublisher internalRepublisher;
//maps clientID to Will testament, if specified on CONNECT // maps clientID to Will testament, if specified on CONNECT
private ConcurrentMap<String, WillMessage> m_willStore = new ConcurrentHashMap<>(); private ConcurrentMap<String, WillMessage> m_willStore = new ConcurrentHashMap<>();
ProtocolProcessor() {} ProtocolProcessor() {
public void init(SubscriptionsStore subscriptions, IMessagesStore storageService,
ISessionsStore sessionsStore,
IAuthenticator authenticator,
boolean allowAnonymous, IAuthorizator authorizator, BrokerInterceptor interceptor) {
init(subscriptions,storageService,sessionsStore,authenticator,allowAnonymous, false, authorizator,interceptor,null);
} }
public void init(SubscriptionsStore subscriptions, IMessagesStore storageService, public void init(SubscriptionsStore subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
ISessionsStore sessionsStore, IAuthenticator authenticator, boolean allowAnonymous, IAuthorizator authorizator,
IAuthenticator authenticator, BrokerInterceptor interceptor) {
boolean allowAnonymous, init(
boolean allowZeroByteClientId, IAuthorizator authorizator, BrokerInterceptor interceptor) { subscriptions,
init(subscriptions,storageService,sessionsStore,authenticator,allowAnonymous, allowZeroByteClientId, authorizator,interceptor,null); storageService,
sessionsStore,
authenticator,
allowAnonymous,
false,
authorizator,
interceptor,
null);
} }
public void init(SubscriptionsStore subscriptions, IMessagesStore storageService, public void init(SubscriptionsStore subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
ISessionsStore sessionsStore, IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId,
IAuthenticator authenticator, IAuthorizator authorizator, BrokerInterceptor interceptor) {
boolean allowAnonymous, init(
boolean allowZeroByteClientId, IAuthorizator authorizator, BrokerInterceptor interceptor, String serverPort) { subscriptions,
init(new ConnectionDescriptorStore(sessionsStore), subscriptions, storageService, sessionsStore, authenticator, storageService,
allowAnonymous, allowZeroByteClientId, authorizator, interceptor, serverPort); sessionsStore,
} authenticator,
allowAnonymous,
allowZeroByteClientId,
authorizator,
interceptor,
null);
}
public void init(SubscriptionsStore subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId,
IAuthorizator authorizator, BrokerInterceptor interceptor, String serverPort) {
init(
new ConnectionDescriptorStore(sessionsStore),
subscriptions,
storageService,
sessionsStore,
authenticator,
allowAnonymous,
allowZeroByteClientId,
authorizator,
interceptor,
serverPort);
}
/** /**
* @param subscriptions the subscription store where are stored all the existing * @param subscriptions
* clients subscriptions. * the subscription store where are stored all the existing clients subscriptions.
* @param storageService the persistent store to use for save/load of messages * @param storageService
* for QoS1 and QoS2 handling. * the persistent store to use for save/load of messages for QoS1 and QoS2 handling.
* @param sessionsStore the clients sessions store, used to persist subscriptions. * @param sessionsStore
* @param authenticator the authenticator used in connect messages. * the clients sessions store, used to persist subscriptions.
* @param allowAnonymous true connection to clients without credentials. * @param authenticator
* @param allowZeroByteClientId true to allow clients connect without a clientid * the authenticator used in connect messages.
* @param authorizator used to apply ACL policies to publishes and subscriptions. * @param allowAnonymous
* @param interceptor to notify events to an intercept handler * true connection to clients without credentials.
* @param allowZeroByteClientId
* true to allow clients connect without a clientid
* @param authorizator
* used to apply ACL policies to publishes and subscriptions.
* @param interceptor
* to notify events to an intercept handler
*/ */
void init(ConnectionDescriptorStore connectionDescriptors, SubscriptionsStore subscriptions, IMessagesStore storageService, void init(ConnectionDescriptorStore connectionDescriptors, SubscriptionsStore subscriptions,
ISessionsStore sessionsStore, IMessagesStore storageService, ISessionsStore sessionsStore, IAuthenticator authenticator,
IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId, IAuthorizator authorizator,
boolean allowAnonymous, BrokerInterceptor interceptor, String serverPort) {
boolean allowZeroByteClientId, IAuthorizator authorizator, BrokerInterceptor interceptor, String serverPort) {
LOG.info("Initializing MQTT protocol processor..."); LOG.info("Initializing MQTT protocol processor...");
this.connectionDescriptors = connectionDescriptors; this.connectionDescriptors = connectionDescriptors;
this.subscriptionInCourse = new ConcurrentHashMap<>(); this.subscriptionInCourse = new ConcurrentHashMap<>();
@@ -199,9 +230,9 @@ public class ProtocolProcessor {
this.allowAnonymous = allowAnonymous; this.allowAnonymous = allowAnonymous;
this.allowZeroByteClientId = allowZeroByteClientId; this.allowZeroByteClientId = allowZeroByteClientId;
m_authorizator = authorizator; m_authorizator = authorizator;
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Initial subscriptions tree = {}.", subscriptions.dumpTree()); LOG.debug("Initial subscriptions tree = {}.", subscriptions.dumpTree());
} }
m_authenticator = authenticator; m_authenticator = authenticator;
m_messagesStore = storageService; m_messagesStore = storageService;
m_sessionsStore = sessionsStore; m_sessionsStore = sessionsStore;
@@ -209,15 +240,36 @@ public class ProtocolProcessor {
LOG.info("Initializing messages publisher..."); LOG.info("Initializing messages publisher...");
final PersistentQueueMessageSender messageSender = new PersistentQueueMessageSender(this.connectionDescriptors); final PersistentQueueMessageSender messageSender = new PersistentQueueMessageSender(this.connectionDescriptors);
this.messagesPublisher = new MessagesPublisher(connectionDescriptors, sessionsStore, m_messagesStore, messageSender); this.messagesPublisher = new MessagesPublisher(
connectionDescriptors,
sessionsStore,
m_messagesStore,
messageSender);
LOG.info("Initializing QoS publish handlers..."); LOG.info("Initializing QoS publish handlers...");
this.qos0PublishHandler = new Qos0PublishHandler(m_authorizator, subscriptions, m_messagesStore, this.qos0PublishHandler = new Qos0PublishHandler(
m_interceptor, this.messagesPublisher); m_authorizator,
this.qos1PublishHandler = new Qos1PublishHandler(m_authorizator, subscriptions, m_messagesStore, subscriptions,
m_interceptor, this.connectionDescriptors, m_server_port, this.messagesPublisher); m_messagesStore,
this.qos2PublishHandler = new Qos2PublishHandler(m_authorizator, subscriptions, m_messagesStore, m_interceptor,
m_interceptor, this.connectionDescriptors, m_sessionsStore, m_server_port, this.messagesPublisher); this.messagesPublisher);
this.qos1PublishHandler = new Qos1PublishHandler(
m_authorizator,
subscriptions,
m_messagesStore,
m_interceptor,
this.connectionDescriptors,
m_server_port,
this.messagesPublisher);
this.qos2PublishHandler = new Qos2PublishHandler(
m_authorizator,
subscriptions,
m_messagesStore,
m_interceptor,
this.connectionDescriptors,
m_sessionsStore,
m_server_port,
this.messagesPublisher);
LOG.info("Initializing internal republisher..."); LOG.info("Initializing internal republisher...");
this.internalRepublisher = new InternalRepublisher(messageSender); this.internalRepublisher = new InternalRepublisher(messageSender);
@@ -228,8 +280,8 @@ public class ProtocolProcessor {
String clientId = payload.clientIdentifier(); String clientId = payload.clientIdentifier();
LOG.info("Processing CONNECT message. CId = {}, username = {}", clientId, payload.userName()); LOG.info("Processing CONNECT message. CId = {}, username = {}", clientId, payload.userName());
if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel() && if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel()
msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()) { && msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()) {
MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION); MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION);
LOG.error("The MQTT protocol version is not valid. CId = {}", clientId); LOG.error("The MQTT protocol version is not valid. CId = {}", clientId);
@@ -250,8 +302,10 @@ public class ProtocolProcessor {
// Generating client id. // Generating client id.
clientId = UUID.randomUUID().toString().replace("-", ""); clientId = UUID.randomUUID().toString().replace("-", "");
LOG.info("The client has connected with a server generated identifier. CId = {}, username = {}", LOG.info(
clientId, payload.userName()); "The client has connected with a server generated identifier. CId = {}, username = {}",
clientId,
payload.userName());
} }
if (!login(channel, msg, clientId)) { if (!login(channel, msg, clientId)) {
@@ -259,11 +313,13 @@ public class ProtocolProcessor {
return; return;
} }
ConnectionDescriptor descriptor = new ConnectionDescriptor(clientId, channel, msg.variableHeader().isCleanSession()); ConnectionDescriptor descriptor = new ConnectionDescriptor(
clientId,
channel,
msg.variableHeader().isCleanSession());
ConnectionDescriptor existing = this.connectionDescriptors.addConnection(descriptor); ConnectionDescriptor existing = this.connectionDescriptors.addConnection(descriptor);
if (existing != null) { if (existing != null) {
LOG.info("The client ID is being used in an existing connection. It will be closed. CId = {}", LOG.info("The client ID is being used in an existing connection. It will be closed. CId = {}", clientId);
clientId);
existing.abort(); existing.abort();
return; return;
} }
@@ -289,7 +345,8 @@ public class ProtocolProcessor {
channel.close(); channel.close();
return; return;
} }
final boolean success = descriptor.assignState(ConnectionState.MESSAGES_REPUBLISHED, ConnectionState.ESTABLISHED); final boolean success = descriptor
.assignState(ConnectionState.MESSAGES_REPUBLISHED, ConnectionState.ESTABLISHED);
if (!success) { if (!success) {
channel.close(); channel.close();
} }
@@ -306,32 +363,42 @@ public class ProtocolProcessor {
} }
private MqttConnAckMessage connAck(MqttConnectReturnCode returnCode, boolean sessionPresent) { private MqttConnAckMessage connAck(MqttConnectReturnCode returnCode, boolean sessionPresent) {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
MqttMessageType.CONNACK,
false,
MqttQoS.AT_MOST_ONCE,
false,
0);
MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(returnCode, sessionPresent); MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(returnCode, sessionPresent);
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
} }
private boolean login(Channel channel, MqttConnectMessage msg, final String clientId) { private boolean login(Channel channel, MqttConnectMessage msg, final String clientId) {
//handle user authentication // handle user authentication
if (msg.variableHeader().hasUserName()) { if (msg.variableHeader().hasUserName()) {
byte[] pwd = null; byte[] pwd = null;
if (msg.variableHeader().hasPassword()) { if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password().getBytes(); pwd = msg.payload().password().getBytes();
} else if (!this.allowAnonymous) { } else if (!this.allowAnonymous) {
LOG.error("The client didn't supply any password and MQTT anonymous mode is disabled. CId = {}.", LOG.error(
"The client didn't supply any password and MQTT anonymous mode is disabled. CId = {}.",
clientId); clientId);
failedCredentials(channel); failedCredentials(channel);
return false; return false;
} }
if (!m_authenticator.checkValid(clientId, msg.payload().userName(), pwd)) { if (!m_authenticator.checkValid(clientId, msg.payload().userName(), pwd)) {
LOG.error("The authenticator has rejected the MQTT credentials. CId = {}, username = {}, password = {}.", LOG.error(
clientId, msg.payload().userName(), pwd); "The authenticator has rejected the MQTT credentials. CId = {}, username = {}, password = {}.",
clientId,
msg.payload().userName(),
pwd);
failedCredentials(channel); failedCredentials(channel);
return false; return false;
} }
NettyUtils.userName(channel, msg.payload().userName()); NettyUtils.userName(channel, msg.payload().userName());
} else if (!this.allowAnonymous) { } else if (!this.allowAnonymous) {
LOG.error("The client didn't supply any credentials and MQTT anonymous mode is disabled. CId = {}.", LOG.error(
"The client didn't supply any credentials and MQTT anonymous mode is disabled. CId = {}.",
clientId); clientId);
failedCredentials(channel); failedCredentials(channel);
return false; return false;
@@ -340,7 +407,7 @@ public class ProtocolProcessor {
} }
private boolean sendAck(ConnectionDescriptor descriptor, MqttConnectMessage msg, final String clientId) { private boolean sendAck(ConnectionDescriptor descriptor, MqttConnectMessage msg, final String clientId) {
LOG.info("Sending connect ACK. CId = {}.", clientId); LOG.info("Sending connect ACK. CId = {}.", clientId);
final boolean success = descriptor.assignState(ConnectionState.DISCONNECTED, ConnectionState.SENDACK); final boolean success = descriptor.assignState(ConnectionState.DISCONNECTED, ConnectionState.SENDACK);
if (!success) { if (!success) {
return false; return false;
@@ -356,7 +423,7 @@ public class ProtocolProcessor {
} }
if (isSessionAlreadyStored) { if (isSessionAlreadyStored) {
LOG.info("Cleaning session. CId = {}.", clientId); LOG.info("Cleaning session. CId = {}.", clientId);
clientSession.cleanSession(msg.variableHeader().isCleanSession()); clientSession.cleanSession(msg.variableHeader().isCleanSession());
} }
descriptor.writeAndFlush(okResp); descriptor.writeAndFlush(okResp);
@@ -368,35 +435,48 @@ public class ProtocolProcessor {
int keepAlive = msg.variableHeader().keepAliveTimeSeconds(); int keepAlive = msg.variableHeader().keepAliveTimeSeconds();
LOG.info("Configuring connection. CId = {}.", clientId); LOG.info("Configuring connection. CId = {}.", clientId);
NettyUtils.keepAlive(channel, keepAlive); NettyUtils.keepAlive(channel, keepAlive);
//session.attr(NettyUtils.ATTR_KEY_CLEANSESSION).set(msg.variableHeader().isCleanSession()); // session.attr(NettyUtils.ATTR_KEY_CLEANSESSION).set(msg.variableHeader().isCleanSession());
NettyUtils.cleanSession(channel, msg.variableHeader().isCleanSession()); NettyUtils.cleanSession(channel, msg.variableHeader().isCleanSession());
//used to track the client in the subscription and publishing phases. // used to track the client in the subscription and publishing phases.
//session.attr(NettyUtils.ATTR_KEY_CLIENTID).set(msg.getClientID()); // session.attr(NettyUtils.ATTR_KEY_CLIENTID).set(msg.getClientID());
NettyUtils.clientID(channel, clientId); NettyUtils.clientID(channel, clientId);
int idleTime = Math.round(keepAlive * 1.5f); int idleTime = Math.round(keepAlive * 1.5f);
setIdleTime(channel.pipeline(), idleTime); setIdleTime(channel.pipeline(), idleTime);
LOG.info("The connection has been configured. CId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.", LOG.info(
clientId, keepAlive, msg.variableHeader().isCleanSession(), idleTime); "The connection has been configured. CId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.",
clientId,
keepAlive,
msg.variableHeader().isCleanSession(),
idleTime);
} }
private void storeWillMessage(MqttConnectMessage msg, final String clientId) { private void storeWillMessage(MqttConnectMessage msg, final String clientId) {
//Handle will flag // Handle will flag
if (msg.variableHeader().isWillFlag()) { if (msg.variableHeader().isWillFlag()) {
MqttQoS willQos = MqttQoS.valueOf(msg.variableHeader().willQos()); MqttQoS willQos = MqttQoS.valueOf(msg.variableHeader().willQos());
LOG.info("Configuring MQTT last will and testament. CId = {}, willQos = {}, willTopic = {}, willRetain = {}.", LOG.info(
clientId, willQos, msg.payload().willTopic(), msg.variableHeader().isWillRetain()); "Configuring MQTT last will and testament. "
+ "CId = {}, willQos = {}, willTopic = {}, willRetain = {}.",
clientId,
willQos,
msg.payload().willTopic(),
msg.variableHeader().isWillRetain());
byte[] willPayload = msg.payload().willMessage().getBytes(); byte[] willPayload = msg.payload().willMessage().getBytes();
ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip(); ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
//save the will testament in the clientID store // save the will testament in the clientID store
WillMessage will = new WillMessage(msg.payload().willTopic(), bb, msg.variableHeader().isWillRetain(), willQos); WillMessage will = new WillMessage(
msg.payload().willTopic(),
bb,
msg.variableHeader().isWillRetain(),
willQos);
m_willStore.put(clientId, will); m_willStore.put(clientId, will);
LOG.info("MQTT last will and testament has been configured. CId = {}.", clientId); LOG.info("MQTT last will and testament has been configured. CId = {}.", clientId);
} }
} }
private ClientSession createOrLoadClientSession(ConnectionDescriptor descriptor, MqttConnectMessage msg, private ClientSession createOrLoadClientSession(ConnectionDescriptor descriptor, MqttConnectMessage msg,
String clientId) { String clientId) {
final boolean success = descriptor.assignState(ConnectionState.SENDACK, ConnectionState.SESSION_CREATED); final boolean success = descriptor.assignState(ConnectionState.SENDACK, ConnectionState.SESSION_CREATED);
if (!success) { if (!success) {
return null; return null;
@@ -408,23 +488,24 @@ public class ProtocolProcessor {
clientSession = m_sessionsStore.createNewSession(clientId, msg.variableHeader().isCleanSession()); clientSession = m_sessionsStore.createNewSession(clientId, msg.variableHeader().isCleanSession());
} }
if (msg.variableHeader().isCleanSession()) { if (msg.variableHeader().isCleanSession()) {
LOG.info("Cleaning session. CId = {}.", clientId); LOG.info("Cleaning session. CId = {}.", clientId);
clientSession.cleanSession(); clientSession.cleanSession();
} }
return clientSession; return clientSession;
} }
private boolean republish(ConnectionDescriptor descriptor, MqttConnectMessage msg, ClientSession clientSession) { private boolean republish(ConnectionDescriptor descriptor, MqttConnectMessage msg, ClientSession clientSession) {
final boolean success = descriptor.assignState(ConnectionState.SESSION_CREATED, ConnectionState.MESSAGES_REPUBLISHED); final boolean success = descriptor
.assignState(ConnectionState.SESSION_CREATED, ConnectionState.MESSAGES_REPUBLISHED);
if (!success) { if (!success) {
return false; return false;
} }
if (!msg.variableHeader().isCleanSession()) { if (!msg.variableHeader().isCleanSession()) {
//force the republish of stored QoS1 and QoS2 // force the republish of stored QoS1 and QoS2
republishStoredInSession(clientSession); republishStoredInSession(clientSession);
} }
int flushIntervalMs = 500/*(keepAlive * 1000) / 2*/; int flushIntervalMs = 500/* (keepAlive * 1000) / 2 */;
descriptor.setupAutoFlusher(flushIntervalMs); descriptor.setupAutoFlusher(flushIntervalMs);
return true; return true;
} }
@@ -443,7 +524,7 @@ public class ProtocolProcessor {
/** /**
* Republish QoS1 and QoS2 messages stored into the session for the clientID. * Republish QoS1 and QoS2 messages stored into the session for the clientID.
* */ */
private void republishStoredInSession(ClientSession clientSession) { private void republishStoredInSession(ClientSession clientSession) {
LOG.info("Republishing stored publish events. CId = {}.", clientSession.clientID); LOG.info("Republishing stored publish events. CId = {}.", clientSession.clientID);
BlockingQueue<StoredMessage> publishedEvents = clientSession.queue(); BlockingQueue<StoredMessage> publishedEvents = clientSession.queue();
@@ -470,19 +551,24 @@ public class ProtocolProcessor {
} }
public static IMessagesStore.StoredMessage asStoredMessage(MqttPublishMessage msg) { public static IMessagesStore.StoredMessage asStoredMessage(MqttPublishMessage msg) {
//TODO ugly, too much array copy // TODO ugly, too much array copy
ByteBuf payload = msg.payload(); ByteBuf payload = msg.payload();
byte[] payloadContent = readBytesAndRewind(payload); byte[] payloadContent = readBytesAndRewind(payload);
IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(payloadContent, IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(
msg.fixedHeader().qosLevel(), msg.variableHeader().topicName()); payloadContent,
msg.fixedHeader().qosLevel(),
msg.variableHeader().topicName());
stored.setRetained(msg.fixedHeader().isRetain()); stored.setRetained(msg.fixedHeader().isRetain());
stored.setMessageID(msg.variableHeader().messageId()); stored.setMessageID(msg.variableHeader().messageId());
return stored; return stored;
} }
private static IMessagesStore.StoredMessage asStoredMessage(WillMessage will) { private static IMessagesStore.StoredMessage asStoredMessage(WillMessage will) {
IMessagesStore.StoredMessage pub = new IMessagesStore.StoredMessage(will.getPayload().array(), will.getQos(), will.getTopic()); IMessagesStore.StoredMessage pub = new IMessagesStore.StoredMessage(
will.getPayload().array(),
will.getQos(),
will.getTopic());
pub.setRetained(will.isRetained()); pub.setRetained(will.isRetained());
return pub; return pub;
} }
@@ -490,8 +576,12 @@ public class ProtocolProcessor {
public void processPublish(Channel channel, MqttPublishMessage msg) { public void processPublish(Channel channel, MqttPublishMessage msg) {
final MqttQoS qos = msg.fixedHeader().qosLevel(); final MqttQoS qos = msg.fixedHeader().qosLevel();
final String clientId = NettyUtils.clientID(channel); final String clientId = NettyUtils.clientID(channel);
LOG.info("Processing PUBLISH message. CId = {}, topic = {}, messageId = {}, qos = {}.", LOG.info(
clientId, msg.variableHeader().topicName(), msg.variableHeader().messageId(), qos); "Processing PUBLISH message. CId = {}, topic = {}, messageId = {}, qos = {}.",
clientId,
msg.variableHeader().topicName(),
msg.variableHeader().messageId(),
qos);
switch (qos) { switch (qos) {
case AT_MOST_ONCE: case AT_MOST_ONCE:
this.qos0PublishHandler.receivedPublishQos0(channel, msg); this.qos0PublishHandler.receivedPublishQos0(channel, msg);
@@ -503,21 +593,22 @@ public class ProtocolProcessor {
this.qos2PublishHandler.receivedPublishQos2(channel, msg); this.qos2PublishHandler.receivedPublishQos2(channel, msg);
break; break;
default: default:
break; break;
} }
} }
/** /**
* Intended usage is only for embedded versions of the broker, where the hosting application want to use the * Intended usage is only for embedded versions of the broker, where the hosting application
* broker to send a publish message. * want to use the broker to send a publish message. Inspired by {@link #processPublish} but
* Inspired by {@link #processPublish} but with some changes to avoid security check, and the handshake phases * with some changes to avoid security check, and the handshake phases for Qos1 and Qos2. It
* for Qos1 and Qos2. * also doesn't notifyTopicPublished because using internally the owner should already know
* It also doesn't notifyTopicPublished because using internally the owner should already know where * where it's publishing.
* it's publishing.
* *
* @param msg the message to publish. * @param msg
* @param clientId the clientID * the message to publish.
* */ * @param clientId
* the clientID
*/
public void internalPublish(MqttPublishMessage msg, final String clientId) { public void internalPublish(MqttPublishMessage msg, final String clientId) {
final MqttQoS qos = msg.fixedHeader().qosLevel(); final MqttQoS qos = msg.fixedHeader().qosLevel();
final String topic = msg.variableHeader().topicName(); final String topic = msg.variableHeader().topicName();
@@ -531,7 +622,7 @@ public class ProtocolProcessor {
toStoreMsg.setClientID(clientId); toStoreMsg.setClientID(clientId);
} }
toStoreMsg.setMessageID(1); toStoreMsg.setMessageID(1);
if (qos == EXACTLY_ONCE) { //QoS2 if (qos == EXACTLY_ONCE) { // QoS2
guid = m_messagesStore.storePublishForFuture(toStoreMsg); guid = m_messagesStore.storePublishForFuture(toStoreMsg);
} }
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic); List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
@@ -541,12 +632,12 @@ public class ProtocolProcessor {
return; return;
} }
if (qos == AT_MOST_ONCE || msg.payload().readableBytes() == 0) { if (qos == AT_MOST_ONCE || msg.payload().readableBytes() == 0) {
//QoS == 0 && retain => clean old retained // QoS == 0 && retain => clean old retained
m_messagesStore.cleanRetained(topic); m_messagesStore.cleanRetained(topic);
return; return;
} }
if (guid == null) { if (guid == null) {
//before wasn't stored // before wasn't stored
guid = m_messagesStore.storePublishForFuture(toStoreMsg); guid = m_messagesStore.storePublishForFuture(toStoreMsg);
} }
m_messagesStore.storeRetained(topic, guid); m_messagesStore.storeRetained(topic, guid);
@@ -556,8 +647,8 @@ public class ProtocolProcessor {
* Specialized version to publish will testament message. * Specialized version to publish will testament message.
*/ */
private void forwardPublishWill(WillMessage will, String clientID) { private void forwardPublishWill(WillMessage will, String clientID) {
//it has just to publish the message downstream to the subscribers // 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 // NB it's a will publish, it needs a PacketIdentifier for this conn, default to 1
Integer messageId = null; Integer messageId = null;
if (will.getQos() != AT_MOST_ONCE) { if (will.getQos() != AT_MOST_ONCE) {
messageId = m_sessionsStore.nextPacketID(clientID); messageId = m_sessionsStore.nextPacketID(clientID);
@@ -569,7 +660,7 @@ public class ProtocolProcessor {
String topic = tobeStored.getTopic(); String topic = tobeStored.getTopic();
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic); List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
LOG.info("Publishing will message. CId = {}, messageId = {}, topic = {}.", clientID, messageId, topic); LOG.info("Publishing will message. CId = {}, messageId = {}, topic = {}.", clientID, messageId, topic);
this.messagesPublisher.publish2Subscribers(tobeStored, topicMatchingSubscriptions); this.messagesPublisher.publish2Subscribers(tobeStored, topicMatchingSubscriptions);
} }
@@ -581,11 +672,14 @@ public class ProtocolProcessor {
} }
/** /**
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored message and publish * Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored
* to all interested subscribers. * message and publish to all interested subscribers.
* @param channel the channel of the incoming message. *
* @param msg the decoded pubrel message. * @param channel
* */ * the channel of the incoming message.
* @param msg
* the decoded pubrel message.
*/
public void processPubRel(Channel channel, MqttMessage msg) { public void processPubRel(Channel channel, MqttMessage msg) {
this.qos2PublishHandler.processPubRel(channel, msg); this.qos2PublishHandler.processPubRel(channel, msg);
} }
@@ -593,10 +687,10 @@ public class ProtocolProcessor {
public void processPubRec(Channel channel, MqttMessage msg) { public void processPubRec(Channel channel, MqttMessage msg) {
String clientID = NettyUtils.clientID(channel); String clientID = NettyUtils.clientID(channel);
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID); ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
//remove from the inflight and move to the QoS2 second phase queue // remove from the inflight and move to the QoS2 second phase queue
int messageID = messageId(msg); int messageID = messageId(msg);
targetSession.moveInFlightToSecondPhaseAckWaiting(messageID); targetSession.moveInFlightToSecondPhaseAckWaiting(messageID);
//once received a PUBREC reply with a PUBREL(messageID) // once received a PUBREC reply with a PUBREL(messageID)
LOG.debug("Processing PUBREC message. CId = {}, messageId = {}.", clientID, messageID); LOG.debug("Processing PUBREC message. CId = {}, messageId = {}.", clientID, messageID);
MqttFixedHeader pubRelHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0); MqttFixedHeader pubRelHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
@@ -608,7 +702,7 @@ public class ProtocolProcessor {
String clientID = NettyUtils.clientID(channel); String clientID = NettyUtils.clientID(channel);
int messageID = messageId(msg); int messageID = messageId(msg);
LOG.debug("Processing PUBCOMP message. CId = {}, messageId = {}.", clientID, messageID); LOG.debug("Processing PUBCOMP message. CId = {}, messageId = {}.", clientID, messageID);
//once received the PUBCOMP then remove the message from the temp memory // once received the PUBCOMP then remove the message from the temp memory
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID); ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
StoredMessage inflightMsg = targetSession.secondPhaseAcknowledged(messageID); StoredMessage inflightMsg = targetSession.secondPhaseAcknowledged(messageID);
String username = NettyUtils.userName(channel); String username = NettyUtils.userName(channel);
@@ -617,59 +711,59 @@ public class ProtocolProcessor {
} }
public void processDisconnect(Channel channel) throws InterruptedException { public void processDisconnect(Channel channel) throws InterruptedException {
final String clientID = NettyUtils.clientID(channel); final String clientID = NettyUtils.clientID(channel);
LOG.info("Processing DISCONNECT message. CId = {}.", clientID); LOG.info("Processing DISCONNECT message. CId = {}.", clientID);
channel.flush(); channel.flush();
final ConnectionDescriptor existingDescriptor = this.connectionDescriptors.getConnection(clientID); final ConnectionDescriptor existingDescriptor = this.connectionDescriptors.getConnection(clientID);
if (existingDescriptor == null) { if (existingDescriptor == null) {
//another client with same ID removed the descriptor, we must exit // another client with same ID removed the descriptor, we must exit
channel.close(); channel.close();
return; return;
} }
if (existingDescriptor.doesNotUseChannel(channel)) { if (existingDescriptor.doesNotUseChannel(channel)) {
//another client saved it's descriptor, exit // another client saved it's descriptor, exit
LOG.warn("Another client is using the connection descriptor. Closing connection. CId = {}.", LOG.warn("Another client is using the connection descriptor. Closing connection. CId = {}.", clientID);
clientID); existingDescriptor.abort();
existingDescriptor.abort();
return; return;
} }
if (!removeSubscriptions(existingDescriptor, clientID)) { if (!removeSubscriptions(existingDescriptor, clientID)) {
LOG.warn("Unable to remove subscriptions. Closing connection. CId = {}.", clientID); LOG.warn("Unable to remove subscriptions. Closing connection. CId = {}.", clientID);
existingDescriptor.abort(); existingDescriptor.abort();
return; return;
} }
if (!dropStoredMessages(existingDescriptor, clientID)) { if (!dropStoredMessages(existingDescriptor, clientID)) {
LOG.warn("Unable to drop stored messages. Closing connection. CId = {}.", clientID); LOG.warn("Unable to drop stored messages. Closing connection. CId = {}.", clientID);
existingDescriptor.abort(); existingDescriptor.abort();
return; return;
} }
if (!cleanWillMessageAndNotifyInterceptor(existingDescriptor, clientID)) { if (!cleanWillMessageAndNotifyInterceptor(existingDescriptor, clientID)) {
LOG.warn("Unable to drop will message. Closing connection. CId = {}.", clientID); LOG.warn("Unable to drop will message. Closing connection. CId = {}.", clientID);
existingDescriptor.abort(); existingDescriptor.abort();
return; return;
} }
if (!existingDescriptor.close()) { if (!existingDescriptor.close()) {
LOG.info("The connection has been closed. CId = {}.", clientID); LOG.info("The connection has been closed. CId = {}.", clientID);
return; return;
} }
boolean stillPresent = this.connectionDescriptors.removeConnection(existingDescriptor); boolean stillPresent = this.connectionDescriptors.removeConnection(existingDescriptor);
if (!stillPresent) { if (!stillPresent) {
//another descriptor was inserted // another descriptor was inserted
LOG.warn("Another descriptor has been inserted. CId = {}.", clientID); LOG.warn("Another descriptor has been inserted. CId = {}.", clientID);
return; return;
} }
LOG.info("The DISCONNECT message has been processed. CId = {}.", clientID); LOG.info("The DISCONNECT message has been processed. CId = {}.", clientID);
} }
private boolean removeSubscriptions(ConnectionDescriptor descriptor, String clientID) { private boolean removeSubscriptions(ConnectionDescriptor descriptor, String clientID) {
final boolean success = descriptor.assignState(ConnectionState.ESTABLISHED, ConnectionState.SUBSCRIPTIONS_REMOVED); final boolean success = descriptor
.assignState(ConnectionState.ESTABLISHED, ConnectionState.SUBSCRIPTIONS_REMOVED);
if (!success) { if (!success) {
return false; return false;
} }
@@ -683,7 +777,8 @@ public class ProtocolProcessor {
} }
private boolean dropStoredMessages(ConnectionDescriptor descriptor, String clientID) { private boolean dropStoredMessages(ConnectionDescriptor descriptor, String clientID) {
final boolean success = descriptor.assignState(ConnectionState.SUBSCRIPTIONS_REMOVED, ConnectionState.MESSAGES_DROPPED); final boolean success = descriptor
.assignState(ConnectionState.SUBSCRIPTIONS_REMOVED, ConnectionState.MESSAGES_DROPPED);
if (!success) { if (!success) {
return false; return false;
} }
@@ -697,13 +792,14 @@ public class ProtocolProcessor {
} }
private boolean cleanWillMessageAndNotifyInterceptor(ConnectionDescriptor descriptor, String clientID) { private boolean cleanWillMessageAndNotifyInterceptor(ConnectionDescriptor descriptor, String clientID) {
final boolean success = descriptor.assignState(ConnectionState.MESSAGES_DROPPED, ConnectionState.INTERCEPTORS_NOTIFIED); final boolean success = descriptor
.assignState(ConnectionState.MESSAGES_DROPPED, ConnectionState.INTERCEPTORS_NOTIFIED);
if (!success) { if (!success) {
return false; return false;
} }
LOG.info("Removing will message. ClientId = {}.", descriptor.clientID); LOG.info("Removing will message. ClientId = {}.", descriptor.clientID);
//cleanup the will store // cleanup the will store
m_willStore.remove(clientID); m_willStore.remove(clientID);
String username = descriptor.getUsername(); String username = descriptor.getUsername();
m_interceptor.notifyClientDisconnected(clientID, username); m_interceptor.notifyClientDisconnected(clientID, username);
@@ -711,10 +807,10 @@ public class ProtocolProcessor {
} }
public void processConnectionLost(String clientID, Channel channel) { public void processConnectionLost(String clientID, Channel channel) {
LOG.info("Processing connection lost event. CId = {}.", clientID); LOG.info("Processing connection lost event. CId = {}.", clientID);
ConnectionDescriptor oldConnDescr = new ConnectionDescriptor(clientID, channel, true); ConnectionDescriptor oldConnDescr = new ConnectionDescriptor(clientID, channel, true);
connectionDescriptors.removeConnection(oldConnDescr); connectionDescriptors.removeConnection(oldConnDescr);
//publish the Will message (if any) for the clientID // publish the Will message (if any) for the clientID
if (m_willStore.containsKey(clientID)) { if (m_willStore.containsKey(clientID)) {
WillMessage will = m_willStore.get(clientID); WillMessage will = m_willStore.get(clientID);
forwardPublishWill(will, clientID); forwardPublishWill(will, clientID);
@@ -726,25 +822,31 @@ public class ProtocolProcessor {
} }
/** /**
* Remove the clientID from topic subscription, if not previously subscribed, * Remove the clientID from topic subscription, if not previously subscribed, doesn't reply any
* doesn't reply any error. * error.
* @param channel the channel of the incoming message. *
* @param msg the decoded unsubscribe message. * @param channel
* the channel of the incoming message.
* @param msg
* the decoded unsubscribe message.
*/ */
public void processUnsubscribe(Channel channel, MqttUnsubscribeMessage msg) { public void processUnsubscribe(Channel channel, MqttUnsubscribeMessage msg) {
List<String> topics = msg.payload().topics(); List<String> topics = msg.payload().topics();
String clientID = NettyUtils.clientID(channel); String clientID = NettyUtils.clientID(channel);
LOG.info("Processing UNSUBSCRIBE message. CId = {}, topics = {}.", clientID, topics); LOG.info("Processing UNSUBSCRIBE message. CId = {}, topics = {}.", clientID, topics);
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID); ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
for (String topic : topics) { for (String topic : topics) {
boolean validTopic = SubscriptionsStore.validate(topic); boolean validTopic = SubscriptionsStore.validate(topic);
if (!validTopic) { if (!validTopic) {
//close the connection, not valid topicFilter is a protocol violation // close the connection, not valid topicFilter is a protocol violation
channel.close(); channel.close();
LOG.error("The topic filter is not valid. CId = {}, topics = {}, badTopicFilter = {}.", LOG.error(
clientID, topics, topic); "The topic filter is not valid. CId = {}, topics = {}, badTopicFilter = {}.",
clientID,
topics,
topic);
return; return;
} }
@@ -755,57 +857,64 @@ public class ProtocolProcessor {
m_interceptor.notifyTopicUnsubscribed(topic, clientID, username); m_interceptor.notifyTopicUnsubscribed(topic, clientID, username);
} }
//ack the client // ack the client
int messageID = msg.variableHeader().messageId(); int messageID = msg.variableHeader().messageId();
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, AT_LEAST_ONCE, false, 0); MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, AT_LEAST_ONCE, false, 0);
MqttUnsubAckMessage ackMessage = new MqttUnsubAckMessage(fixedHeader, from(messageID)); MqttUnsubAckMessage ackMessage = new MqttUnsubAckMessage(fixedHeader, from(messageID));
LOG.info("Sending UNSUBACK message. CId = {}, topics = {}, messageId = {}.", clientID, topics, messageID); LOG.info("Sending UNSUBACK message. CId = {}, topics = {}, messageId = {}.", clientID, topics, messageID);
channel.writeAndFlush(ackMessage); channel.writeAndFlush(ackMessage);
} }
public void processSubscribe(Channel channel, MqttSubscribeMessage msg) { public void processSubscribe(Channel channel, MqttSubscribeMessage msg) {
String clientID = NettyUtils.clientID(channel); String clientID = NettyUtils.clientID(channel);
int messageID = messageId(msg); int messageID = messageId(msg);
LOG.info("Processing SUBSCRIBE message. CId = {}, messageId = {}.", clientID, messageID); LOG.info("Processing SUBSCRIBE message. CId = {}, messageId = {}.", clientID, messageID);
RunningSubscription executionKey = new RunningSubscription(clientID, messageID); RunningSubscription executionKey = new RunningSubscription(clientID, messageID);
SubscriptionState currentStatus = this.subscriptionInCourse.putIfAbsent(executionKey, SubscriptionState.VERIFIED); SubscriptionState currentStatus = this.subscriptionInCourse
.putIfAbsent(executionKey, SubscriptionState.VERIFIED);
if (currentStatus != null) { if (currentStatus != null) {
LOG.warn("The client sent another SUBSCRIBE message while this one was being processed. " + LOG.warn(
"CId = {}, messageId = {}.", clientID, messageID); "The client sent another SUBSCRIBE message while this one was being processed. "
+ "CId = {}, messageId = {}.",
clientID,
messageID);
return; return;
} }
String username = NettyUtils.userName(channel); String username = NettyUtils.userName(channel);
List<MqttTopicSubscription> ackTopics = doVerify(clientID, username, msg); List<MqttTopicSubscription> ackTopics = doVerify(clientID, username, msg);
MqttSubAckMessage ackMessage = doAckMessageFromValidateFilters(ackTopics, messageID); MqttSubAckMessage ackMessage = doAckMessageFromValidateFilters(ackTopics, messageID);
if (!this.subscriptionInCourse.replace(executionKey, SubscriptionState.VERIFIED, SubscriptionState.STORED)) { if (!this.subscriptionInCourse.replace(executionKey, SubscriptionState.VERIFIED, SubscriptionState.STORED)) {
LOG.warn("The client sent another SUBSCRIBE message while the topic filters were being verified. " + LOG.warn(
"CId = {}, messageId = {}.", clientID, messageID); "The client sent another SUBSCRIBE message while the topic filters were being verified. "
+ "CId = {}, messageId = {}.",
clientID,
messageID);
return; return;
} }
LOG.info("Creating and storing subscriptions CId={}, messageId={}, topics={}", clientID, messageID, ackTopics); LOG.info("Creating and storing subscriptions CId={}, messageId={}, topics={}", clientID, messageID, ackTopics);
List<Subscription> newSubscriptions = doStoreSubscription(ackTopics, clientID); List<Subscription> newSubscriptions = doStoreSubscription(ackTopics, clientID);
//save session, persist subscriptions from session // save session, persist subscriptions from session
for (Subscription subscription : newSubscriptions) { for (Subscription subscription : newSubscriptions) {
subscriptions.add(subscription.asClientTopicCouple()); subscriptions.add(subscription.asClientTopicCouple());
} }
LOG.info("Sending SUBACK response CId={}, messageId={}", clientID, messageID); LOG.info("Sending SUBACK response CId={}, messageId={}", clientID, messageID);
channel.writeAndFlush(ackMessage); channel.writeAndFlush(ackMessage);
//fire the persisted messages in session // fire the persisted messages in session
for (Subscription subscription : newSubscriptions) { for (Subscription subscription : newSubscriptions) {
publishRetainedMessagesInSession(subscription, username); publishRetainedMessagesInSession(subscription, username);
} }
boolean success = this.subscriptionInCourse.remove(executionKey, SubscriptionState.STORED); boolean success = this.subscriptionInCourse.remove(executionKey, SubscriptionState.STORED);
if (!success) { if (!success) {
LOG.warn("Unable to perform the final subscription state update CId={}, messageId={}", clientID, messageID); LOG.warn("Unable to perform the final subscription state update CId={}, messageId={}", clientID, messageID);
} }
} }
@@ -814,7 +923,7 @@ public class ProtocolProcessor {
List<Subscription> newSubscriptions = new ArrayList<>(); List<Subscription> newSubscriptions = new ArrayList<>();
for (MqttTopicSubscription req : ackTopics) { for (MqttTopicSubscription req : ackTopics) {
//TODO this is SUPER UGLY // TODO this is SUPER UGLY
if (req.qualityOfService() == FAILURE) { if (req.qualityOfService() == FAILURE) {
continue; continue;
} }
@@ -826,11 +935,14 @@ public class ProtocolProcessor {
} }
/** /**
* @param clientID the clientID * @param clientID
* @param username the username * the clientID
* @param msg the subscribe message to verify * @param username
* the username
* @param msg
* the subscribe message to verify
* @return the list of verified topics for the given subscribe message. * @return the list of verified topics for the given subscribe message.
* */ */
private List<MqttTopicSubscription> doVerify(String clientID, String username, MqttSubscribeMessage msg) { private List<MqttTopicSubscription> doVerify(String clientID, String username, MqttSubscribeMessage msg) {
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID); ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
List<MqttTopicSubscription> ackTopics = new ArrayList<>(); List<MqttTopicSubscription> ackTopics = new ArrayList<>();
@@ -838,20 +950,33 @@ public class ProtocolProcessor {
final int messageId = messageId(msg); final int messageId = messageId(msg);
for (MqttTopicSubscription req : msg.payload().topicSubscriptions()) { for (MqttTopicSubscription req : msg.payload().topicSubscriptions()) {
if (!m_authorizator.canRead(req.topicName(), username, clientSession.clientID)) { if (!m_authorizator.canRead(req.topicName(), username, clientSession.clientID)) {
//send SUBACK with 0x80, the user hasn't credentials to read the topic // 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. CId = {}, username = {}, messageId = {}, topic = {}.", LOG.error(
clientID, username, messageId, req.topicName()); "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)); ackTopics.add(new MqttTopicSubscription(req.topicName(), FAILURE));
} else { } else {
MqttQoS qos; MqttQoS qos;
if (SubscriptionsStore.validate(req.topicName())) { if (SubscriptionsStore.validate(req.topicName())) {
LOG.info("The client will be subscribed to the topic. CId = {}, username = {}, messageId = {}, topic = {}.", LOG.info(
clientID, username, messageId, req.topicName()); "The client will be subscribed to the topic. "
+ "CId = {}, username = {}, messageId = {}, topic = {}.",
clientID,
username,
messageId,
req.topicName());
qos = req.qualityOfService(); qos = req.qualityOfService();
} else { } else {
LOG.error("The topic filter is not valid. CId = {}, username = {}, messageId = {}, topic = {}.", LOG.error(
clientID, username, messageId, req.topicName()); "The topic filter is not valid. CId = {}, username = {}, messageId = {}, topic = {}.",
qos = FAILURE; clientID,
username,
messageId,
req.topicName());
qos = FAILURE;
} }
ackTopics.add(new MqttTopicSubscription(req.topicName(), qos)); ackTopics.add(new MqttTopicSubscription(req.topicName(), qos));
} }
@@ -861,7 +986,7 @@ public class ProtocolProcessor {
/** /**
* Create the SUBACK response from a list of topicFilters * Create the SUBACK response from a list of topicFilters
* */ */
private MqttSubAckMessage doAckMessageFromValidateFilters(List<MqttTopicSubscription> topicFilters, int messageId) { private MqttSubAckMessage doAckMessageFromValidateFilters(List<MqttTopicSubscription> topicFilters, int messageId) {
List<Integer> grantedQoSLevels = new ArrayList<>(); List<Integer> grantedQoSLevels = new ArrayList<>();
for (MqttTopicSubscription req : topicFilters) { for (MqttTopicSubscription req : topicFilters) {
@@ -874,12 +999,15 @@ public class ProtocolProcessor {
} }
private void publishRetainedMessagesInSession(final Subscription newSubscription, String username) { private void publishRetainedMessagesInSession(final Subscription newSubscription, String username) {
LOG.info("Retrieving retained messages. CId = {}, topics = {}.", newSubscription.getClientId(), LOG.info(
newSubscription.getTopicFilter()); "Retrieving retained messages. CId = {}, topics = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter());
//scans retained messages to be published to the new subscription // scans retained messages to be published to the new subscription
//TODO this is ugly, it does a linear scan on potential big dataset // TODO this is ugly, it does a linear scan on potential big dataset
Collection<IMessagesStore.StoredMessage> messages = m_messagesStore.searchMatching(new IMatchingCondition() { Collection<IMessagesStore.StoredMessage> messages = m_messagesStore.searchMatching(new IMatchingCondition() {
@Override @Override
public boolean match(String key) { public boolean match(String key) {
return SubscriptionsStore.matchTopics(key, newSubscription.getTopicFilter()); return SubscriptionsStore.matchTopics(key, newSubscription.getTopicFilter());
@@ -887,13 +1015,16 @@ public class ProtocolProcessor {
}); });
if (!messages.isEmpty()) { if (!messages.isEmpty()) {
LOG.info("Publishing retained messages. CId = {}, topics = {}, messagesNo = {}.", LOG.info(
newSubscription.getClientId(), newSubscription.getTopicFilter(), messages.size()); "Publishing retained messages. CId = {}, topics = {}, messagesNo = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter(),
messages.size());
} }
ClientSession targetSession = m_sessionsStore.sessionForClient(newSubscription.getClientId()); ClientSession targetSession = m_sessionsStore.sessionForClient(newSubscription.getClientId());
this.internalRepublisher.publishRetained(targetSession, messages); this.internalRepublisher.publishRetained(targetSession, messages);
//notify the Observables // notify the Observables
m_interceptor.notifyTopicSubscribed(newSubscription, username); m_interceptor.notifyTopicSubscribed(newSubscription, username);
} }
@@ -901,14 +1032,19 @@ public class ProtocolProcessor {
String clientID = NettyUtils.clientID(channel); String clientID = NettyUtils.clientID(channel);
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID); ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
boolean emptyQueue = false; boolean emptyQueue = false;
while (channel.isWritable() && !emptyQueue) { while (channel.isWritable() && !emptyQueue) {
StoredMessage msg = clientSession.queue().poll(); StoredMessage msg = clientSession.queue().poll();
if (msg == null) { if (msg == null) {
emptyQueue = true; emptyQueue = true;
} else { } else {
//recreate a publish from stored publish in queue // recreate a publish from stored publish in queue
boolean retained = m_messagesStore.getMessageByGuid(msg.getGuid()) != null; boolean retained = m_messagesStore.getMessageByGuid(msg.getGuid()) != null;
MqttPublishMessage pubMsg = createPublishForQos(msg.getTopic(), msg.getQos(), msg.getMessage(), retained, 0); MqttPublishMessage pubMsg = createPublishForQos(
msg.getTopic(),
msg.getQos(),
msg.getMessage(),
retained,
0);
channel.write(pubMsg); channel.write(pubMsg);
} }
} }