broker/ProtocolProcessor: code formatter used
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user