Merge pull request #265 from AndreasMager/fromating-broker

Minor Checkstyle adjustement & Fromating broker folder
This commit is contained in:
Andrea Selva
2017-02-09 13:34:45 +01:00
committed by GitHub
126 changed files with 3224 additions and 2583 deletions
+25 -25
View File
@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<relativePath>../</relativePath>
@@ -23,37 +23,37 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
@@ -71,13 +71,13 @@
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<!--version>1.1.0-SNAPSHOT</version-->
<!--<version>2.0.0-SNAPSHOT</version>-->
<!--<version>2.0-alpha3</version>-->
<!--version>1.1.0-SNAPSHOT</version -->
<!--<version>2.0.0-SNAPSHOT</version> -->
<!--<version>2.0-alpha3</version> -->
<version>1.0.8</version>
</dependency>
@@ -111,11 +111,11 @@
<!-- needs extra dependencies: objenesis & hamcrest -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.0</version>
<artifactId>mockito-core</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fusesource.mqtt-client</groupId>
<artifactId>mqtt-client</artifactId>
@@ -129,7 +129,7 @@
<version>1.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
@@ -152,7 +152,7 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
@@ -165,32 +165,32 @@
</configuration>
</plugin>
<!-- Includes to generate OSGi headers -->
<plugin>
<!-- Includes to generate OSGi headers -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goals>
<goal>manifest</goal>
</goals>
</goals>
</execution>
</executions>
</plugin>
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette;
import java.io.File;
@@ -21,7 +22,8 @@ import java.io.File;
* Created by andrea on 08/12/15.
*/
public class BrokerConstants {
public static final String INTERCEPT_HANDLER_PROPERTY_NAME ="intercept.handler";
public static final String INTERCEPT_HANDLER_PROPERTY_NAME = "intercept.handler";
public static final String BROKER_INTERCEPTOR_THREAD_POOL_SIZE = "intercept.thread_pool.size";
public static final String PERSISTENT_STORE_PROPERTY_NAME = "persistent_store";
public static final String AUTOSAVE_INTERVAL_PROPERTY_NAME = "autosave_interval";
@@ -29,7 +31,8 @@ public class BrokerConstants {
public static final String PORT_PROPERTY_NAME = "port";
public static final String HOST_PROPERTY_NAME = "host";
public static final String DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME = "moquette_store.mapdb";
public static final String DEFAULT_PERSISTENT_PATH = System.getProperty("user.dir") + File.separator + DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME;
public static final String DEFAULT_PERSISTENT_PATH = System.getProperty("user.dir") + File.separator
+ DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME;
public static final String WEB_SOCKET_PORT_PROPERTY_NAME = "websocket_port";
public static final String WSS_PORT_PROPERTY_NAME = "secure_websocket_port";
public static final String SSL_PORT_PROPERTY_NAME = "ssl_port";
@@ -1,58 +1,60 @@
package io.moquette.connections;
import java.util.Collection;
/**
* This interface will be used by an external codebase to retrieve and close
* physical connections.
*
* This interface will be used by an external codebase to retrieve and close physical connections.
*
* @author lbarrios
*
*/
public interface IConnectionsManager {
/**
* Returns the number of physical connections
*
* @return
*/
public int getActiveConnectionsNo();
/**
* Determines wether a MQTT client is connected to the broker.
*
* @param clientID
* @return
*/
public boolean isConnected(String clientID);
/**
* Returns the number of physical connections
*
* @return
*/
public int getActiveConnectionsNo();
/**
* Returns the identifiers of the MQTT clients that are connected to the
* broker.
*
* @return
*/
public Collection<String> getConnectedClientIds();
/**
* Determines wether a MQTT client is connected to the broker.
*
* @param clientID
* @return
*/
public boolean isConnected(String clientID);
/**
* Closes a physical connection.
*
* @param clientID
* @param closeImmediately
* If false, the connection will be flushed before it is closed.
* @return
*/
public boolean closeConnection(String clientID, boolean closeImmediately);
/**
* Returns the state of the session of a given client.
* @param clientID
* @return
*/
public MqttSession getSessionStatus(String clientID);
/**
* Returns the state of all the sessions
* @return
*/
public Collection<MqttSession> getSessions();
/**
* Returns the identifiers of the MQTT clients that are connected to the broker.
*
* @return
*/
public Collection<String> getConnectedClientIds();
/**
* Closes a physical connection.
*
* @param clientID
* @param closeImmediately
* If false, the connection will be flushed before it is closed.
* @return
*/
public boolean closeConnection(String clientID, boolean closeImmediately);
/**
* Returns the state of the session of a given client.
*
* @param clientID
* @return
*/
public MqttSession getSessionStatus(String clientID);
/**
* Returns the state of all the sessions
*
* @return
*/
public Collection<MqttSession> getSessions();
}
@@ -1,12 +1,14 @@
package io.moquette.connections;
/**
* A class that represents the metrics of a given MQTT connection.
*
*
* @author lbarrios
*
*/
public class MqttConnectionMetrics {
private long readKb;
private long writtenKb;
private long readMessages;
@@ -1,56 +1,56 @@
package io.moquette.connections;
import java.util.Collection;
/**
* A class that represents the overall connection status of a MQTT session. Its
* instances will be used by an external codebase when the broker is configured
* in embedded mode.
*
* A class that represents the overall connection status of a MQTT session. Its instances will be
* used by an external codebase when the broker is configured in embedded mode.
*
* @author lbarrios
*
*/
public class MqttSession {
private boolean connectionEstablished;
private boolean cleanSession;
private int inflightMessages;
private int pendingPublishMessagesNo;
private int secondPhaseAckPendingMessages;
private Collection<MqttSubscription> activeSubscriptions;
private MqttConnectionMetrics connectionMetrics;
private boolean connectionEstablished;
private boolean cleanSession;
private int inflightMessages;
private int pendingPublishMessagesNo;
private int secondPhaseAckPendingMessages;
private Collection<MqttSubscription> activeSubscriptions;
private MqttConnectionMetrics connectionMetrics;
public boolean isConnectionEstablished() {
return connectionEstablished;
}
public boolean isConnectionEstablished() {
return connectionEstablished;
}
public void setConnectionEstablished(boolean connectionEstablished) {
this.connectionEstablished = connectionEstablished;
}
public void setConnectionEstablished(boolean connectionEstablished) {
this.connectionEstablished = connectionEstablished;
}
public boolean isCleanSession() {
return cleanSession;
}
public boolean isCleanSession() {
return cleanSession;
}
public void setCleanSession(boolean cleanSession) {
this.cleanSession = cleanSession;
}
public int getPendingPublishMessagesNo() {
public void setCleanSession(boolean cleanSession) {
this.cleanSession = cleanSession;
}
public int getPendingPublishMessagesNo() {
return pendingPublishMessagesNo;
}
public void setPendingPublishMessagesNo(int pendingPublishMessagesNo) {
public void setPendingPublishMessagesNo(int pendingPublishMessagesNo) {
this.pendingPublishMessagesNo = pendingPublishMessagesNo;
}
public int getInflightMessages() {
return inflightMessages;
}
public int getInflightMessages() {
return inflightMessages;
}
public void setInflightMessages(int inflightMessages) {
this.inflightMessages = inflightMessages;
}
public void setInflightMessages(int inflightMessages) {
this.inflightMessages = inflightMessages;
}
public int getSecondPhaseAckPendingMessages() {
return secondPhaseAckPendingMessages;
@@ -1,12 +1,14 @@
package io.moquette.connections;
/**
* A class that represents a MQTT subscription.
*
* A class that represents a MQTT subscription.
*
* @author lbarrios
*
*/
public class MqttSubscription {
private final String requestedQos;
private final String clientId;
private final String topicFilter;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.interception;
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
@@ -29,30 +30,37 @@ import io.moquette.interception.messages.InterceptUnsubscribeMessage;
* Created by andrea on 08/12/15.
*/
public abstract class AbstractInterceptHandler implements InterceptHandler {
@Override
public Class<?>[] getInterceptedMessageTypes() {
return InterceptHandler.ALL_MESSAGE_TYPES;
}
@Override
public void onConnect(InterceptConnectMessage msg) {}
@Override
public void onDisconnect(InterceptDisconnectMessage msg) {}
public Class<?>[] getInterceptedMessageTypes() {
return InterceptHandler.ALL_MESSAGE_TYPES;
}
@Override
public void onConnectionLost(InterceptConnectionLostMessage msg) {}
public void onConnect(InterceptConnectMessage msg) {
}
@Override
public void onPublish(InterceptPublishMessage msg) {}
public void onDisconnect(InterceptDisconnectMessage msg) {
}
@Override
public void onSubscribe(InterceptSubscribeMessage msg) {}
public void onConnectionLost(InterceptConnectionLostMessage msg) {
}
@Override
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {}
public void onPublish(InterceptPublishMessage msg) {
}
@Override
public void onMessageAcknowledged(InterceptAcknowledgedMessage msg) {}
public void onSubscribe(InterceptSubscribeMessage msg) {
}
@Override
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {
}
@Override
public void onMessageAcknowledged(InterceptAcknowledgedMessage msg) {
}
}
@@ -1,3 +1,4 @@
package io.moquette.interception;
import com.hazelcast.core.HazelcastInstance;
@@ -7,7 +8,6 @@ import io.moquette.server.Server;
import io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
/**
@@ -18,18 +18,18 @@ public class HazelcastInterceptHandler extends AbstractInterceptHandler {
private static final Logger LOG = LoggerFactory.getLogger(HazelcastInterceptHandler.class);
private final HazelcastInstance hz;
public HazelcastInterceptHandler(Server server){
public HazelcastInterceptHandler(Server server) {
this.hz = server.getHazelcastInstance();
}
@Override
public String getID() {
return HazelcastInterceptHandler.class.getName() + "@" + hz.getName();
return HazelcastInterceptHandler.class.getName() + "@" + hz.getName();
}
@Override
public void onPublish(InterceptPublishMessage msg) {
//TODO ugly, too much array copy
// TODO ugly, too much array copy
ByteBuf payload = msg.getPayload();
byte[] payloadContent = readBytesAndRewind(payload);
@@ -1,9 +1,8 @@
package io.moquette.interception;
import io.moquette.interception.messages.InterceptPublishMessage;
import java.io.Serializable;
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
/**
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.interception;
import io.moquette.interception.messages.*;
@@ -24,32 +25,33 @@ import io.netty.handler.codec.mqtt.MqttMessage;
* <p>
* The events can act only as observers.
* <p>
* Almost every method receives a subclass of {@link MqttMessage}, except
* <code>onDisconnect</code> that receives the client id string and
* <code>onSubscribe</code> and <code>onUnsubscribe</code> that receive a
* {@link Subscription} object.
* Almost every method receives a subclass of {@link MqttMessage}, except <code>onDisconnect</code>
* that receives the client id string and <code>onSubscribe</code> and <code>onUnsubscribe</code>
* that receive a {@link Subscription} object.
*
* @author Wagner Macedo
*/
public interface InterceptHandler {
public static final Class<?> ALL_MESSAGE_TYPES[] = { InterceptConnectMessage.class,
InterceptDisconnectMessage.class, InterceptConnectionLostMessage.class, InterceptPublishMessage.class,
InterceptSubscribeMessage.class, InterceptUnsubscribeMessage.class, InterceptAcknowledgedMessage.class };
/**
* Returns the identifier of this intercept handler.
* @return
*/
String getID();
/**
* Returns the InterceptMessage subtypes that this handler can process. If the result is null or equal to ALL_MESSAGE_TYPES,
* all the message types will be processed.
* @return
*/
Class<?>[] getInterceptedMessageTypes();
Class<?>[] ALL_MESSAGE_TYPES = {InterceptConnectMessage.class, InterceptDisconnectMessage.class,
InterceptConnectionLostMessage.class, InterceptPublishMessage.class, InterceptSubscribeMessage.class,
InterceptUnsubscribeMessage.class, InterceptAcknowledgedMessage.class};
/**
* Returns the identifier of this intercept handler.
*
* @return
*/
String getID();
/**
* Returns the InterceptMessage subtypes that this handler can process. If the result is null or
* equal to ALL_MESSAGE_TYPES, all the message types will be processed.
*
* @return
*/
Class<?>[] getInterceptedMessageTypes();
void onConnect(InterceptConnectMessage msg);
void onDisconnect(InterceptDisconnectMessage msg);
@@ -61,6 +63,6 @@ public interface InterceptHandler {
void onSubscribe(InterceptSubscribeMessage msg);
void onUnsubscribe(InterceptUnsubscribeMessage msg);
void onMessageAcknowledged(InterceptAcknowledgedMessage msg);
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.interception;
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
@@ -23,12 +24,10 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
/**
* This interface is to be used internally by the broker components.
* <p>
* An interface is used instead of a class to allow more flexibility in changing
* an implementation.
* An interface is used instead of a class to allow more flexibility in changing an implementation.
* <p>
* Interceptor implementations forward notifications to a <code>InterceptHandler</code>,
* that is normally a field. So, the implementations should act as a proxy to a custom
* intercept handler.
* Interceptor implementations forward notifications to a <code>InterceptHandler</code>, that is
* normally a field. So, the implementations should act as a proxy to a custom intercept handler.
*
* @see InterceptHandler
* @author Wagner Macedo
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.interception.messages;
import io.netty.handler.codec.mqtt.MqttMessage;
@@ -22,6 +23,7 @@ import io.netty.handler.codec.mqtt.MqttQoS;
* @author Wagner Macedo
*/
public abstract class InterceptAbstractMessage implements InterceptMessage {
private final MqttMessage msg;
InterceptAbstractMessage(MqttMessage msg) {
@@ -1,27 +1,29 @@
package io.moquette.interception.messages;
import static io.moquette.spi.IMessagesStore.StoredMessage;
public class InterceptAcknowledgedMessage implements InterceptMessage {
final private StoredMessage msg;
private final String username;
private final String topic;
public InterceptAcknowledgedMessage( final StoredMessage msg, final String topic, final String username) {
this.msg = msg;
this.username = username;
this.topic = topic;
}
public StoredMessage getMsg() {
return msg;
}
public String getUsername() {
return username;
}
final private StoredMessage msg;
private final String username;
private final String topic;
public String getTopic() {
return topic;
}
public InterceptAcknowledgedMessage(final StoredMessage msg, final String topic, final String username) {
this.msg = msg;
this.username = username;
this.topic = topic;
}
public StoredMessage getMsg() {
return msg;
}
public String getUsername() {
return username;
}
public String getTopic() {
return topic;
}
}
@@ -1,3 +1,4 @@
package io.moquette.interception.messages;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
@@ -6,6 +7,7 @@ import io.netty.handler.codec.mqtt.MqttConnectMessage;
* @author Wagner Macedo
*/
public class InterceptConnectMessage extends InterceptAbstractMessage {
private final MqttConnectMessage msg;
public InterceptConnectMessage(MqttConnectMessage msg) {
@@ -1,9 +1,11 @@
package io.moquette.interception.messages;
/**
* @author Wagner Macedo
*/
public class InterceptConnectionLostMessage implements InterceptMessage {
private final String clientID;
private final String username;
@@ -16,7 +18,7 @@ public class InterceptConnectionLostMessage implements InterceptMessage {
return clientID;
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
}
@@ -1,9 +1,11 @@
package io.moquette.interception.messages;
/**
* @author Wagner Macedo
*/
public class InterceptDisconnectMessage implements InterceptMessage {
private final String clientID;
private final String username;
@@ -16,7 +18,7 @@ public class InterceptDisconnectMessage implements InterceptMessage {
return clientID;
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
}
@@ -1,7 +1,9 @@
package io.moquette.interception.messages;
/**
* An interface that sets the root of the interceptor messages type hierarchy.
*
* @author lbarrios
*
*/
@@ -1,3 +1,4 @@
package io.moquette.interception.messages;
import io.netty.buffer.ByteBuf;
@@ -7,10 +8,11 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
* @author Wagner Macedo
*/
public class InterceptPublishMessage extends InterceptAbstractMessage {
private final MqttPublishMessage msg;
private final String clientID;
private final String username;
public InterceptPublishMessage(MqttPublishMessage msg, String clientID, String username) {
super(msg);
this.msg = msg;
@@ -30,7 +32,7 @@ public class InterceptPublishMessage extends InterceptAbstractMessage {
return clientID;
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
}
@@ -1,3 +1,4 @@
package io.moquette.interception.messages;
import io.moquette.spi.impl.subscriptions.Subscription;
@@ -7,6 +8,7 @@ import io.netty.handler.codec.mqtt.MqttQoS;
* @author Wagner Macedo
*/
public class InterceptSubscribeMessage implements InterceptMessage {
private final Subscription subscription;
private final String username;
@@ -27,7 +29,7 @@ public class InterceptSubscribeMessage implements InterceptMessage {
return subscription.getTopicFilter();
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
}
@@ -1,9 +1,11 @@
package io.moquette.interception.messages;
/**
* @author Wagner Macedo
*/
public class InterceptUnsubscribeMessage implements InterceptMessage {
private final String topicFilter;
private final String clientID;
private final String username;
@@ -22,7 +24,7 @@ public class InterceptUnsubscribeMessage implements InterceptMessage {
return clientID;
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
}
@@ -1,16 +1,17 @@
package io.moquette.logging;
import java.util.ArrayList;
import java.util.Collection;
import io.moquette.interception.InterceptHandler;
public class LoggingUtils {
public static <T extends InterceptHandler> Collection<String> getInterceptorIds(Collection<T> handlers) {
Collection<String> result = new ArrayList<>(handlers.size());
for (T handler : handlers) {
result.add(handler.getID());
}
return result;
}
public static <T extends InterceptHandler> Collection<String> getInterceptorIds(Collection<T> handlers) {
Collection<String> result = new ArrayList<>(handlers.size());
for (T handler : handlers) {
result.add(handler.getID());
}
return result;
}
}
@@ -13,15 +13,14 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.moquette.server.netty.AutoFlushHandler;
import io.moquette.server.netty.NettyUtils;
import io.moquette.server.netty.metrics.BytesMetrics;
@@ -30,10 +29,9 @@ import io.moquette.server.netty.metrics.MessageMetrics;
import io.moquette.server.netty.metrics.MessageMetricsHandler;
import io.netty.channel.Channel;
/**
* Value object to maintain the information of single connection, like ClientID, Channel,
* and clean session flag.
* Value object to maintain the information of single connection, like ClientID, Channel, and clean
* session flag.
*
*
* @author andrea
@@ -43,10 +41,16 @@ public class ConnectionDescriptor {
private static final Logger LOG = LoggerFactory.getLogger(ConnectionDescriptor.class);
public enum ConnectionState {
//Connection states
DISCONNECTED, SENDACK, SESSION_CREATED, MESSAGES_REPUBLISHED, ESTABLISHED,
//Disconnection states
SUBSCRIPTIONS_REMOVED, MESSAGES_DROPPED, INTERCEPTORS_NOTIFIED;
// Connection states
DISCONNECTED,
SENDACK,
SESSION_CREATED,
MESSAGES_REPUBLISHED,
ESTABLISHED,
// Disconnection states
SUBSCRIPTIONS_REMOVED,
MESSAGES_DROPPED,
INTERCEPTORS_NOTIFIED;
}
public final String clientID;
@@ -59,78 +63,88 @@ public class ConnectionDescriptor {
this.channel = session;
this.cleanSession = cleanSession;
}
public void writeAndFlush(Object payload) {
this.channel.writeAndFlush(payload);
this.channel.writeAndFlush(payload);
}
public void setupAutoFlusher(int flushIntervalMs) {
try {
this.channel.pipeline().addAfter("idleEventHandler", "autoFlusher", new AutoFlushHandler(flushIntervalMs, TimeUnit.MILLISECONDS));
this.channel.pipeline().addAfter(
"idleEventHandler",
"autoFlusher",
new AutoFlushHandler(flushIntervalMs, TimeUnit.MILLISECONDS));
} catch (NoSuchElementException nseex) {
//the idleEventHandler is not present on the pipeline
this.channel.pipeline().addFirst("autoFlusher", new AutoFlushHandler(flushIntervalMs, TimeUnit.MILLISECONDS));
// the idleEventHandler is not present on the pipeline
this.channel.pipeline()
.addFirst("autoFlusher", new AutoFlushHandler(flushIntervalMs, TimeUnit.MILLISECONDS));
}
}
public boolean doesNotUseChannel(Channel channel) {
return !(this.channel.equals(channel));
return !(this.channel.equals(channel));
}
public boolean close() {
LOG.info("Closing connection descriptor. MqttClientId = {}.", clientID);
final boolean success = assignState(ConnectionState.INTERCEPTORS_NOTIFIED, ConnectionState.DISCONNECTED);
if (!success) {
return false;
}
this.channel.close();
return true;
}
public boolean close() {
LOG.info("Closing connection descriptor. MqttClientId = {}.", clientID);
final boolean success = assignState(ConnectionState.INTERCEPTORS_NOTIFIED, ConnectionState.DISCONNECTED);
if (!success) {
return false;
}
this.channel.close();
return true;
}
public String getUsername() {
return NettyUtils.userName(this.channel);
return NettyUtils.userName(this.channel);
}
public void abort() {
LOG.info("Closing connection descriptor. MqttClientId = {}.", clientID);
// try {
//this.channel.disconnect().sync();
this.channel.close();//.sync();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
LOG.info("Closing connection descriptor. MqttClientId = {}.", clientID);
// try {
// this.channel.disconnect().sync();
this.channel.close(); // .sync();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
public boolean assignState(ConnectionState expected, ConnectionState newState) {
LOG.debug("Updating state of connection descriptor. MqttClientId = {}, expectedState = {}, newState = {}.",
clientID, expected, newState);
boolean retval = channelState.compareAndSet(expected, newState);
if (!retval) {
LOG.error(
"Unable to update state of connection descriptor. MqttclientId = {}, expectedState = {}, newState = {}.",
clientID, expected, newState);
}
return retval;
}
LOG.debug(
"Updating state of connection descriptor. MqttClientId = {}, expectedState = {}, newState = {}.",
clientID,
expected,
newState);
boolean retval = channelState.compareAndSet(expected, newState);
if (!retval) {
LOG.error(
"Unable to update state of connection descriptor."
+ " MqttclientId = {}, expectedState = {}, newState = {}.",
clientID,
expected,
newState);
}
return retval;
}
@Override
public String toString() {
return "ConnectionDescriptor{" + "clientID=" + clientID +
", cleanSession=" + cleanSession +
", state=" + channelState.get() +
'}';
return "ConnectionDescriptor{" + "clientID=" + clientID + ", cleanSession=" + cleanSession + ", state="
+ channelState.get() + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ConnectionDescriptor that = (ConnectionDescriptor) o;
if (clientID != null ? !clientID.equals(that.clientID) : that.clientID != null) return false;
if (clientID != null ? !clientID.equals(that.clientID) : that.clientID != null)
return false;
return !(channel != null ? !channel.equals(that.channel) : that.channel != null);
}
public BytesMetrics getBytesMetrics() {
@@ -1,3 +1,4 @@
package io.moquette.server;
import io.moquette.connections.IConnectionsManager;
@@ -13,7 +14,6 @@ import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
@@ -21,64 +21,69 @@ import java.util.concurrent.ConcurrentMap;
public class ConnectionDescriptorStore implements IConnectionsManager {
private static final Logger LOG = LoggerFactory.getLogger(ConnectionDescriptorStore.class);
private static final Logger LOG = LoggerFactory.getLogger(ConnectionDescriptorStore.class);
private final ConcurrentMap<String, ConnectionDescriptor> connectionDescriptors;
private final ISessionsStore sessionsStore;
private final ConcurrentMap<String, ConnectionDescriptor> connectionDescriptors;
private final ISessionsStore sessionsStore;
public ConnectionDescriptorStore(ISessionsStore sessionsStore) {
this.connectionDescriptors = new ConcurrentHashMap<>();
this.sessionsStore = sessionsStore;
}
public ConnectionDescriptorStore(ISessionsStore sessionsStore) {
this.connectionDescriptors = new ConcurrentHashMap<>();
this.sessionsStore = sessionsStore;
}
public boolean sendMessage(MqttMessage message, Integer messageID, String clientID) {
public boolean sendMessage(MqttMessage message, Integer messageID, String clientID) {
final MqttMessageType messageType = message.fixedHeader().messageType();
try {
if (messageID != null) {
LOG.info("Sending {} message CId=<{}>, messageId={}", messageType, clientID, messageID);
} else {
LOG.debug("Sending {} message CId=<{}>", messageType, clientID);
}
try {
if (messageID != null) {
LOG.info("Sending {} message CId=<{}>, messageId={}", messageType, clientID, messageID);
} else {
LOG.debug("Sending {} message CId=<{}>", messageType, clientID);
}
ConnectionDescriptor descriptor = connectionDescriptors.get(clientID);
if (descriptor == null) {
if (messageID != null) {
LOG.error("Client has just disconnected. The {} message could not be sent. CId=<{}>, messageId={}",
messageType, clientID, messageID);
} else {
LOG.error("Client has just disconnected. The {} could not be sent. CId=<{}>", messageType, clientID);
}
/*
* If the client has just disconnected, its connection
* descriptor will be null. We don't have to make the broker
* crash: we'll just discard the PUBACK message.
*/
return false;
}
descriptor.writeAndFlush(message);
return true;
} catch (Throwable e) {
ConnectionDescriptor descriptor = connectionDescriptors.get(clientID);
if (descriptor == null) {
if (messageID != null) {
LOG.error(
"Client has just disconnected. The {} message could not be sent. CId=<{}>, messageId={}",
messageType,
clientID,
messageID);
} else {
LOG.error(
"Client has just disconnected. The {} could not be sent. CId=<{}>",
messageType,
clientID);
}
/*
* If the client has just disconnected, its connection descriptor will be null. We
* don't have to make the broker crash: we'll just discard the PUBACK message.
*/
return false;
}
descriptor.writeAndFlush(message);
return true;
} catch (Throwable e) {
String errorMsg = "Unable to send " + messageType + " message. CId=<" + clientID + ">";
if (messageID != null) {
if (messageID != null) {
errorMsg += ", messageId=" + messageID;
}
}
LOG.error(errorMsg, e);
return false;
}
}
return false;
}
}
public ConnectionDescriptor addConnection(ConnectionDescriptor descriptor) {
return connectionDescriptors.putIfAbsent(descriptor.clientID, descriptor);
}
public ConnectionDescriptor addConnection(ConnectionDescriptor descriptor) {
return connectionDescriptors.putIfAbsent(descriptor.clientID, descriptor);
}
public boolean removeConnection(ConnectionDescriptor descriptor) {
return connectionDescriptors.remove(descriptor.clientID, descriptor);
}
public boolean removeConnection(ConnectionDescriptor descriptor) {
return connectionDescriptors.remove(descriptor.clientID, descriptor);
}
public ConnectionDescriptor getConnection(String clientID) {
return connectionDescriptors.get(clientID);
}
public ConnectionDescriptor getConnection(String clientID) {
return connectionDescriptors.get(clientID);
}
@Override
public boolean isConnected(String clientID) {
return connectionDescriptors.containsKey(clientID);
@@ -99,8 +104,10 @@ public class ConnectionDescriptorStore implements IConnectionsManager {
ConnectionDescriptor descriptor = connectionDescriptors.get(clientID);
if (descriptor == null) {
LOG.error(
"The connection descriptor doesn't exist. The MQTT connection cannot be closed. CId=<{}>, closeImmediately = {}.",
clientID, closeImmediately);
"The connection descriptor doesn't exist. "
+ "The MQTT connection cannot be closed. CId=<{}>, closeImmediately = {}.",
clientID,
closeImmediately);
return false;
}
if (closeImmediately) {
@@ -125,7 +132,7 @@ public class ConnectionDescriptorStore implements IConnectionsManager {
@Override
public Collection<MqttSession> getSessions() {
LOG.info("Retrieving status of all sessions.");
Collection<MqttSession> result = new ArrayList<MqttSession>();
Collection<MqttSession> result = new ArrayList<>();
for (ClientSession session : sessionsStore.getAllSessions()) {
result.add(buildMqttSession(session));
}
@@ -136,8 +143,12 @@ public class ConnectionDescriptorStore implements IConnectionsManager {
MqttSession result = new MqttSession();
Collection<MqttSubscription> mqttSubscriptions = new ArrayList<>();
for (Subscription subscription : session.getSubscriptions()) {
mqttSubscriptions.add(new MqttSubscription(subscription.getRequestedQos().toString(),
subscription.getClientId(), subscription.getTopicFilter(), subscription.isActive()));
mqttSubscriptions.add(
new MqttSubscription(
subscription.getRequestedQos().toString(),
subscription.getClientId(),
subscription.getTopicFilter(),
subscription.isActive()));
}
result.setActiveSubscriptions(mqttSubscriptions);
result.setCleanSession(session.isCleanSession());
@@ -146,8 +157,12 @@ public class ConnectionDescriptorStore implements IConnectionsManager {
result.setConnectionEstablished(true);
BytesMetrics bytesMetrics = descriptor.getBytesMetrics();
MessageMetrics messageMetrics = descriptor.getMessageMetrics();
result.setConnectionMetrics(new MqttConnectionMetrics(bytesMetrics.readBytes(), bytesMetrics.wroteBytes(),
messageMetrics.messagesRead(), messageMetrics.messagesWrote()));
result.setConnectionMetrics(
new MqttConnectionMetrics(
bytesMetrics.readBytes(),
bytesMetrics.wroteBytes(),
messageMetrics.messagesRead(),
messageMetrics.messagesWrote()));
} else {
result.setConnectionEstablished(false);
}
@@ -13,14 +13,16 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
/**
* Server constants keeper
*/
public class Constants {
public static final String ATTR_CLIENTID = "ClientID";
public static final String CLEAN_SESSION = "cleanSession";
public static final String KEEP_ALIVE = "keepAlive";
public static final int MAX_MESSAGE_QUEUE = 1024; //number of messages
public static final int MAX_MESSAGE_QUEUE = 1024; // number of messages
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.BrokerConstants;
@@ -20,20 +21,18 @@ import io.moquette.server.config.IConfig;
import io.moquette.spi.security.ISslContextCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateException;
/**
* Moquette server implementation to load SSL certificate from local filesystem path
* configured in config file.
* Moquette server implementation to load SSL certificate from local filesystem path configured in
* config file.
*
* Created by andrea on 13/12/15.
*/
@@ -49,34 +48,34 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator {
@Override
public SSLContext initSSLContext() {
LOG.info("Checking SSL configuration properties...");
LOG.info("Checking SSL configuration properties...");
final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME);
LOG.info("Initializing SSL context. KeystorePath = {}.", jksPath);
if (jksPath == null || jksPath.isEmpty()) {
//key_store_password or key_manager_password are empty
// key_store_password or key_manager_password are empty
LOG.warn("The keystore path is null or empty. The SSL context won't be initialized.");
return null;
}
//if we have the port also the jks then keyStorePassword and keyManagerPassword
//has to be defined
// if we have the port also the jks then keyStorePassword and keyManagerPassword
// has to be defined
final String keyStorePassword = props.getProperty(BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME);
final String keyManagerPassword = props.getProperty(BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME);
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
//key_store_password or key_manager_password are empty
// key_store_password or key_manager_password are empty
LOG.warn("The keystore password is null or empty. The SSL context won't be initialized.");
return null;
}
if (keyManagerPassword == null || keyManagerPassword.isEmpty()) {
//key_manager_password or key_manager_password are empty
// key_manager_password or key_manager_password are empty
LOG.warn("The key manager password is null or empty. The SSL context won't be initialized.");
return null;
}
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
try {
LOG.info("Loading keystore. KeystorePath = {}.", jksPath);
@@ -87,25 +86,30 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator {
LOG.info("Initializing key manager...");
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
TrustManager[] trustManagers = null;
if (needsClientAuth) {
LOG.warn(
"Client authentication is enabled. The keystore will be used as a truststore. KeystorePath = {}.",
jksPath);
// use keystore as truststore, as server needs to trust certificates signed by the server certificates
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
trustManagers = tmf.getTrustManagers();
}
// init sslContext
LOG.info("Initializing SSL context...");
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
LOG.info("The SSL context has been initialized successfully.");
TrustManager[] trustManagers = null;
if (needsClientAuth) {
LOG.warn(
"Client authentication is enabled. "
+ "The keystore will be used as a truststore. KeystorePath = {}.",
jksPath);
// use keystore as truststore, as server needs to trust certificates signed by the
// server certificates
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
trustManagers = tmf.getTrustManagers();
}
// init sslContext
LOG.info("Initializing SSL context...");
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
LOG.info("The SSL context has been initialized successfully.");
return serverContext;
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
| KeyManagementException | IOException ex) {
LOG.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(), ex.getMessage());
LOG.error(
"Unable to initialize SSL context. Cause = {}, errorMessage = {}.",
ex.getCause(),
ex.getMessage());
return null;
}
}
@@ -1,3 +1,4 @@
package io.moquette.server;
import com.hazelcast.core.Message;
@@ -13,11 +14,12 @@ import org.slf4j.LoggerFactory;
* Created by mackristof on 28/05/2016.
*/
public class HazelcastListener implements MessageListener<HazelcastMsg> {
private static final Logger LOG = LoggerFactory.getLogger(HazelcastListener.class);
private final Server server;
public HazelcastListener(Server server){
public HazelcastListener(Server server) {
this.server = server;
}
@@ -26,9 +28,12 @@ public class HazelcastListener implements MessageListener<HazelcastMsg> {
try {
if (!msg.getPublishingMember().equals(server.getHazelcastInstance().getCluster().getLocalMember())) {
HazelcastMsg hzMsg = msg.getMessageObject();
LOG.info("{} received from hazelcast for topic {} message: {}", hzMsg.getClientId(),
hzMsg.getTopic(), hzMsg.getPayload());
//TODO pass forward this information in somehow publishMessage.setLocal(false);
LOG.info(
"{} received from hazelcast for topic {} message: {}",
hzMsg.getClientId(),
hzMsg.getTopic(),
hzMsg.getPayload());
// TODO pass forward this information in somehow publishMessage.setLocal(false);
MqttQoS qos = MqttQoS.valueOf(hzMsg.getQos());
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0);
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import com.hazelcast.config.ClasspathXmlConfig;
@@ -38,26 +39,25 @@ import io.moquette.spi.security.ISslContextCreator;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import static io.moquette.logging.LoggingUtils.getInterceptorIds;
/**
* Launch a configured version of the server.
* Launch a configured version of the server.
*
* @author andrea
*/
public class Server {
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private static final String HZ_INTERCEPT_HANDLER = HazelcastInterceptHandler.class.getCanonicalName();
private ServerAcceptor m_acceptor;
private volatile boolean m_initialized;
@@ -72,22 +72,24 @@ public class Server {
final Server server = new Server();
server.startServer();
System.out.println("Server started, version 0.10-SNAPSHOT");
//Bind a shutdown hook
// Bind a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
server.stopServer();
}
});
}
/**
* Starts Moquette bringing the configuration from the file
* located at m_config/moquette.conf
* @throws IOException in case of any IO error.
* Starts Moquette bringing the configuration from the file located at m_config/moquette.conf
*
* @throws IOException
* in case of any IO error.
*/
public void startServer() throws IOException {
File defaultConfigurationFile = defaultConfigFile();
File defaultConfigurationFile = defaultConfigFile();
LOG.info("Starting Moquette server. Configuration file path = {}.", defaultConfigurationFile.getAbsolutePath());
IResourceLoader filesystemLoader = new FileResourceLoader(defaultConfigurationFile);
final IConfig config = new ResourceLoaderConfig(filesystemLoader);
@@ -101,8 +103,11 @@ public class Server {
/**
* Starts Moquette bringing the configuration from the given file
* @param configFile text file that contains the configuration.
* @throws IOException in case of any IO Error.
*
* @param configFile
* text file that contains the configuration.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(File configFile) throws IOException {
LOG.info("Starting Moquette server. Configuration file path = {}.", configFile.getAbsolutePath());
@@ -110,50 +115,58 @@ public class Server {
final IConfig config = new ResourceLoaderConfig(filesystemLoader);
startServer(config);
}
/**
* Starts the server with the given properties.
*
*
* Its suggested to at least have the following properties:
* <ul>
* <li>port</li>
* <li>password_file</li>
* <li>port</li>
* <li>password_file</li>
* </ul>
*
* @param configProps the properties map to use as configuration.
* @throws IOException in case of any IO Error.
* @param configProps
* the properties map to use as configuration.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(Properties configProps) throws IOException {
LOG.info("Starting Moquette server using properties object...");
LOG.info("Starting Moquette server using properties object...");
final IConfig config = new MemoryConfig(configProps);
startServer(config);
}
/**
* Starts Moquette bringing the configuration files from the given Config implementation.
* @param config the configuration to use to start the broker.
* @throws IOException in case of any IO Error.
*
* @param config
* the configuration to use to start the broker.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(IConfig config) throws IOException {
LOG.info("Starting Moquette server using IConfig instance...");
LOG.info("Starting Moquette server using IConfig instance...");
startServer(config, null);
}
/**
* Starts Moquette with config provided by an implementation of IConfig class and with the
* set of InterceptHandler.
* @param config the configuration to use to start the broker.
* @param handlers the handlers to install in the broker.
* @throws IOException in case of any IO Error.
* */
* Starts Moquette with config provided by an implementation of IConfig class and with the set
* of InterceptHandler.
*
* @param config
* the configuration to use to start the broker.
* @param handlers
* the handlers to install in the broker.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(IConfig config, List<? extends InterceptHandler> handlers) throws IOException {
LOG.info("Starting moquette server using IConfig instance and intercept handlers...");
LOG.info("Starting moquette server using IConfig instance and intercept handlers...");
startServer(config, handlers, null, null, null);
}
public void startServer(IConfig config, List<? extends InterceptHandler> handlers,
ISslContextCreator sslCtxCreator, IAuthenticator authenticator,
IAuthorizator authorizator) throws IOException {
public void startServer(IConfig config, List<? extends InterceptHandler> handlers, ISslContextCreator sslCtxCreator,
IAuthenticator authenticator, IAuthorizator authorizator) throws IOException {
if (handlers == null) {
handlers = Collections.emptyList();
}
@@ -164,10 +177,13 @@ public class Server {
config.setProperty(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME, handlerProp);
}
configureCluster(config);
LOG.info("Configuring Using persistent store file. Path = {}.", config.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME));
LOG.info(
"Configuring Using persistent store file. Path = {}.",
config.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME));
m_processorBootstrapper = new ProtocolProcessorBootstrapper();
LOG.info("Initializing MQTT protocol processor...");
final ProtocolProcessor processor = m_processorBootstrapper.init(config, handlers, authenticator, authorizator, this);
final ProtocolProcessor processor = m_processorBootstrapper
.init(config, handlers, authenticator, authorizator, this);
if (sslCtxCreator == null) {
LOG.warn("Using default SSL context creator...");
@@ -184,19 +200,19 @@ public class Server {
}
private void configureCluster(IConfig config) throws FileNotFoundException {
LOG.info("Configuring embedded Hazelcast instance...");
LOG.info("Configuring embedded Hazelcast instance...");
String interceptHandlerClassname = config.getProperty(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME);
if (interceptHandlerClassname == null || !HZ_INTERCEPT_HANDLER.equals(interceptHandlerClassname)) {
LOG.info("There are no Hazelcast intercept handlers. The server won't start a Hazelcast instance.");
LOG.info("There are no Hazelcast intercept handlers. The server won't start a Hazelcast instance.");
return;
}
String hzConfigPath = config.getProperty(BrokerConstants.HAZELCAST_CONFIGURATION);
if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath ?
new ClasspathXmlConfig(hzConfigPath) :
new FileSystemXmlConfig(hzConfigPath);
LOG.info("Starting Hazelcast instance. ConfigurationFile = {}.",hzconfig);
Config hzconfig = isHzConfigOnClasspath
? new ClasspathXmlConfig(hzConfigPath)
: new FileSystemXmlConfig(hzConfigPath);
LOG.info("Starting Hazelcast instance. ConfigurationFile = {}.", hzconfig);
hazelcastInstance = Hazelcast.newHazelcastInstance(hzconfig);
} else {
LOG.info("Starting Hazelcast instance with default configuration.");
@@ -206,43 +222,48 @@ public class Server {
}
private void listenOnHazelCastMsg() {
LOG.info("Subscribing to Hazelcast topic. TopicName = {}.", "moquette");
LOG.info("Subscribing to Hazelcast topic. TopicName = {}.", "moquette");
HazelcastInstance hz = getHazelcastInstance();
ITopic<HazelcastMsg> topic = hz.getTopic("moquette");
topic.addMessageListener(new HazelcastListener(this));
}
public HazelcastInstance getHazelcastInstance(){
public HazelcastInstance getHazelcastInstance() {
return hazelcastInstance;
}
/**
* Use the broker to publish a message. It's intended for embedding applications.
* It can be used only after the server is correctly started with startServer.
* Use the broker to publish a message. It's intended for embedding applications. It can be used
* only after the server is correctly started with startServer.
*
* @param msg the message to forward.
* @param clientId the id of the sending server.
* @throws IllegalStateException if the server is not yet started
* */
* @param msg
* the message to forward.
* @param clientId
* the id of the sending server.
* @throws IllegalStateException
* if the server is not yet started
*/
public void internalPublish(MqttPublishMessage msg, final String clientId) {
final int messageID = msg.variableHeader().messageId();
if (!m_initialized) {
LOG.error("The server is not started. The message cannot be published. MqttClientId = {}, messageId = {}",
clientId, messageID);
LOG.error(
"The server is not started. The message cannot be published. MqttClientId = {}, messageId = {}",
clientId,
messageID);
throw new IllegalStateException("Can't publish on a server is not yet started");
}
LOG.debug("Publishing message. MqttClientId = {}, messageId = {}", clientId, messageID);
m_processor.internalPublish(msg, clientId);
}
public void stopServer() {
LOG.info("Unbinding server from the configured ports...");
LOG.info("Unbinding server from the configured ports...");
m_acceptor.close();
LOG.info("Stopping MQTT protocol processor...");
m_processorBootstrapper.shutdown();
m_initialized = false;
if (hazelcastInstance != null) {
LOG.info("Stopping embedded Hazelcast instance...");
LOG.info("Stopping embedded Hazelcast instance...");
try {
hazelcastInstance.shutdown();
} catch (HazelcastInstanceNotActiveException e) {
@@ -253,8 +274,8 @@ public class Server {
}
/**
* SPI method used by Broker embedded applications to get list of subscribers.
* Returns null if the broker is not started.
* SPI method used by Broker embedded applications to get list of subscribers. Returns null if
* the broker is not started.
*
* @return list of subscriptions.
*/
@@ -267,14 +288,16 @@ public class Server {
/**
* SPI method used by Broker embedded applications to add intercept handlers.
* @param interceptHandler the handler to add.
* */
*
* @param interceptHandler
* the handler to add.
*/
public void addInterceptHandler(InterceptHandler interceptHandler) {
if (!m_initialized) {
LOG.error("The server is not started. The MQTT message interceptor cannot be added. InterceptorId = {}.",
interceptHandler.getID());
LOG.error(
"The server is not started. The MQTT message interceptor cannot be added. InterceptorId = {}.",
interceptHandler.getID());
throw new IllegalStateException("Can't register interceptors on a server that is not yet started");
}
LOG.info("Adding MQTT message interceptor. InterceptorId = {}.", interceptHandler.getID());
m_processor.addInterceptHandler(interceptHandler);
@@ -282,12 +305,15 @@ public class Server {
/**
* SPI method used by Broker embedded applications to remove intercept handlers.
* @param interceptHandler the handler to remove.
* */
*
* @param interceptHandler
* the handler to remove.
*/
public void removeInterceptHandler(InterceptHandler interceptHandler) {
if (!m_initialized) {
LOG.error("The server is not started. The MQTT message interceptor cannot be removed. InterceptorId = {}.",
interceptHandler.getID());
LOG.error(
"The server is not started. The MQTT message interceptor cannot be removed. InterceptorId = {}.",
interceptHandler.getID());
throw new IllegalStateException("Can't deregister interceptors from a server that is not yet started");
}
LOG.info("Removing MQTT message interceptor. InterceptorId = {}.", interceptHandler.getID());
@@ -296,7 +322,7 @@ public class Server {
/**
* Returns the connections manager of this broker.
*
*
* @return IConnectionsManager the instance used bt the broker.
*/
public IConnectionsManager getConnectionsManager() {
@@ -13,10 +13,10 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import java.io.IOException;
import io.moquette.server.config.IConfig;
import io.moquette.spi.impl.ProtocolProcessor;
import io.moquette.spi.security.ISslContextCreator;
@@ -26,8 +26,8 @@ import io.moquette.spi.security.ISslContextCreator;
* @author andrea
*/
public interface ServerAcceptor {
void initialize(ProtocolProcessor processor, IConfig props, ISslContextCreator sslCtxCreator) throws IOException;
void close();
}
@@ -1,16 +1,16 @@
package io.moquette.server.config;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClasspathResourceLoader implements IResourceLoader {
private static final Logger LOG = LoggerFactory.getLogger(ClasspathResourceLoader.class);
private static final Logger LOG = LoggerFactory.getLogger(ClasspathResourceLoader.class);
private final String defaultResource;
private final ClassLoader classLoader;
@@ -34,7 +34,7 @@ public class ClasspathResourceLoader implements IResourceLoader {
@Override
public Reader loadResource(String relativePath) {
LOG.info("Loading resource. RelativePath = {}.", relativePath);
LOG.info("Loading resource. RelativePath = {}.", relativePath);
InputStream is = this.classLoader.getResourceAsStream(relativePath);
return is != null ? new InputStreamReader(is) : null;
}
@@ -13,11 +13,11 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
@@ -29,18 +29,18 @@ import java.util.Properties;
/**
* Mosquitto configuration parser.
*
* A line that at the very first has # is a comment
* Each line has key value format, where the separator used it the space.
*
*
* A line that at the very first has # is a comment Each line has key value format, where the
* separator used it the space.
*
* @author andrea
*/
class ConfigurationParser {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationParser.class);
private Properties m_properties = new Properties();
/**
* Parse the configuration from file.
*/
@@ -50,30 +50,38 @@ class ConfigurationParser {
return;
}
if (!file.exists()) {
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()));
LOG.warn(
String.format(
"parsing not existing file %s, so fallback on default configuration!",
file.getAbsolutePath()));
return;
}
try {
FileReader reader = new FileReader(file);
parse(reader);
} catch (FileNotFoundException fex) {
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()), fex);
LOG.warn(
String.format(
"parsing not existing file %s, so fallback on default configuration!",
file.getAbsolutePath()),
fex);
return;
}
}
/**
* Parse the configuration
*
* @throws ParseException if the format is not compliant.
* Parse the configuration
*
* @throws ParseException
* if the format is not compliant.
*/
void parse(Reader reader) throws ParseException {
if (reader == null) {
//just log and return default properties
// just log and return default properties
LOG.warn("parsing NULL reader, so fallback on default configuration!");
return;
}
BufferedReader br = new BufferedReader(reader);
String line;
try {
@@ -81,39 +89,37 @@ class ConfigurationParser {
int commentMarker = line.indexOf('#');
if (commentMarker != -1) {
if (commentMarker == 0) {
//skip its a comment
// skip its a comment
continue;
} else {
//it's a malformed comment
// it's a malformed comment
throw new ParseException(line, commentMarker);
}
} else {
if (line.isEmpty() || line.matches("^\\s*$")) {
//skip it's a black line
// skip it's a black line
continue;
}
//split till the first space
// split till the first space
int delimiterIdx = line.indexOf(' ');
String key = line.substring(0, delimiterIdx).trim();
String value = line.substring(delimiterIdx).trim();
m_properties.put(key, value);
}
}
} catch (IOException ex) {
throw new ParseException("Failed to read", 1);
}
finally {
} finally {
try {
reader.close();
}
catch (IOException e) {
} catch (IOException e) {
// ignore
}
}
}
Properties getProperties() {
return m_properties;
}
@@ -1,17 +1,17 @@
package io.moquette.server.config;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FileResourceLoader implements IResourceLoader {
private static final Logger LOG = LoggerFactory.getLogger(FileResourceLoader.class);
private static final Logger LOG = LoggerFactory.getLogger(FileResourceLoader.class);
private final File defaultFile;
private final String parentPath;
@@ -47,15 +47,15 @@ public class FileResourceLoader implements IResourceLoader {
}
public Reader loadResource(File f) {
LOG.info("Loading file. Path = {}.", f.getAbsolutePath());
LOG.info("Loading file. Path = {}.", f.getAbsolutePath());
if (f.isDirectory()) {
LOG.error("The given file is a directory. Path = {}.", f.getAbsolutePath());
LOG.error("The given file is a directory. Path = {}.", f.getAbsolutePath());
throw new ResourceIsDirectoryException("File \"" + f + "\" is a directory!");
}
try {
return new FileReader(f);
} catch (FileNotFoundException e) {
LOG.error("The file does not exist. Path = {}.", f.getAbsolutePath());
LOG.error("The file does not exist. Path = {}.", f.getAbsolutePath());
return null;
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.config;
import io.moquette.BrokerConstants;
@@ -23,6 +24,7 @@ import io.moquette.BrokerConstants;
* @author andrea
*/
public abstract class IConfig {
public static final String DEFAULT_CONFIG = "config/moquette.conf";
public abstract void setProperty(String name, String value);
@@ -34,9 +36,11 @@ public abstract class IConfig {
void assignDefaults() {
setProperty(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
setProperty(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
//setProperty(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
// setProperty(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME,
// Integer.toString(BrokerConstants.WEBSOCKET_PORT));
setProperty(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, "");
//setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, BrokerConstants.DEFAULT_PERSISTENT_PATH);
// setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME,
// BrokerConstants.DEFAULT_PERSISTENT_PATH);
setProperty(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, Boolean.TRUE.toString());
setProperty(BrokerConstants.AUTHENTICATOR_CLASS_NAME, "");
setProperty(BrokerConstants.AUTHORIZATOR_CLASS_NAME, "");
@@ -1,3 +1,4 @@
package io.moquette.server.config;
import java.io.Reader;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.config;
import java.util.Map;
@@ -34,16 +35,18 @@ public class MemoryConfig extends IConfig {
}
}
// private void createDefaults() {
// m_properties.put(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
// m_properties.put(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
// m_properties.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
// m_properties.put(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, "");
// m_properties.put(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, BrokerConstants.DEFAULT_PERSISTENT_PATH);
// m_properties.put(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, true);
// m_properties.put(BrokerConstants.AUTHENTICATOR_CLASS_NAME, "");
// m_properties.put(BrokerConstants.AUTHORIZATOR_CLASS_NAME, "");
// }
// private void createDefaults() {
// m_properties.put(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
// m_properties.put(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
// m_properties.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME,
// Integer.toString(BrokerConstants.WEBSOCKET_PORT));
// m_properties.put(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, "");
// m_properties.put(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME,
// BrokerConstants.DEFAULT_PERSISTENT_PATH);
// m_properties.put(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, true);
// m_properties.put(BrokerConstants.AUTHENTICATOR_CLASS_NAME, "");
// m_properties.put(BrokerConstants.AUTHORIZATOR_CLASS_NAME, "");
// }
@Override
public void setProperty(String name, String value) {
@@ -13,11 +13,11 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Reader;
import java.text.ParseException;
import java.util.Properties;
@@ -39,38 +39,45 @@ public class ResourceLoaderConfig extends IConfig {
public ResourceLoaderConfig(IResourceLoader resourceLoader, String configName) {
LOG.info("Loading configuration. ResourceLoader = {}, configName = {}.", resourceLoader.getName(), configName);
this.resourceLoader = resourceLoader;
/*
* If we use a conditional operator, the loadResource() and the
* loadDefaultResource() methods will be always called. This makes the
* log traces confusing.
*/
* If we use a conditional operator, the loadResource() and the loadDefaultResource()
* methods will be always called. This makes the log traces confusing.
*/
Reader configReader;
if (configName != null) {
configReader = resourceLoader.loadResource(configName);
configReader = resourceLoader.loadResource(configName);
} else {
configReader = resourceLoader.loadDefaultResource();
configReader = resourceLoader.loadDefaultResource();
}
if (configReader == null) {
LOG.error("The resource loader returned no configuration reader. ResourceLoader = {}, configName = {}.",
resourceLoader.getName(), configName);
throw new IllegalArgumentException("Can't locate " + resourceLoader.getName() + " \"" + configName + "\"");
LOG.error(
"The resource loader returned no configuration reader. ResourceLoader = {}, configName = {}.",
resourceLoader.getName(),
configName);
throw new IllegalArgumentException("Can't locate " + resourceLoader.getName() + " \"" + configName + "\"");
}
LOG.info("Parsing configuration properties. ResourceLoader = {}, configName = {}.", resourceLoader.getName(),
configName);
LOG.info(
"Parsing configuration properties. ResourceLoader = {}, configName = {}.",
resourceLoader.getName(),
configName);
ConfigurationParser confParser = new ConfigurationParser();
m_properties = confParser.getProperties();
assignDefaults();
try {
confParser.parse(configReader);
} catch (ParseException pex) {
LOG.warn(
"Unable to parse configuration properties. Using default configuration. ResourceLoader = {}, configName = {}, cause = {}, errorMessage = {}.",
resourceLoader.getName(), configName, pex.getCause(), pex.getMessage());
}
try {
confParser.parse(configReader);
} catch (ParseException pex) {
LOG.warn(
"Unable to parse configuration properties. Using default configuration. "
+ "ResourceLoader = {}, configName = {}, cause = {}, errorMessage = {}.",
resourceLoader.getName(),
configName,
pex.getCause(),
pex.getMessage());
}
}
@Override
@@ -13,33 +13,31 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.EventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Auto-flush data on channel after a read timeout.
* It's inspired by IdleStateHandler but it's specialized version, just flushing data after
* no read is done on the channel after a period. It's used to avoid aggressively flushing from
* the ProtocolProcessor.
* Auto-flush data on channel after a read timeout. It's inspired by IdleStateHandler but it's
* specialized version, just flushing data after no read is done on the channel after a period. It's
* used to avoid aggressively flushing from the ProtocolProcessor.
*/
public class AutoFlushHandler extends ChannelDuplexHandler {
private static final Logger LOG = LoggerFactory.getLogger(AutoFlushHandler.class);
private static final Logger LOG = LoggerFactory.getLogger(AutoFlushHandler.class);
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
private final long writerIdleTimeNanos;
volatile ScheduledFuture<?> writerIdleTimeout;
volatile long lastWriteTime;
// private boolean firstWriterIdleEvent = true;
// private boolean firstWriterIdleEvent = true;
private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed
@@ -57,7 +55,7 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
// not be invoked. We have to initialize here instead.
initialize(ctx);
} else {
// channelActive() event has not been fired yet. this.channelActive() will be invoked
// channelActive() event has not been fired yet. this.channelActive() will be invoked
// and initialization will occur there.
}
}
@@ -79,7 +77,7 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// This method will be invoked only if this handler was added
// before channelActive() event is fired. If a user adds this handler
// before channelActive() event is fired. If a user adds this handler
// after the channelActive() event, initialize() will be called by beforeAdd().
initialize(ctx);
super.channelActive(ctx);
@@ -91,30 +89,30 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
super.channelInactive(ctx);
}
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// if (writerrIdleTimeNanos > 0) {
// reading = true;
// firstReaderIdleEvent = true;
// }
// ctx.fireChannelRead(msg);
// }
//
// @Override
// public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// if (writerrIdleTimeNanos > 0) {
// lastReadTime = System.nanoTime();
// reading = false;
// }
// ctx.fireChannelReadComplete();
// }
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// if (writerrIdleTimeNanos > 0) {
// reading = true;
// firstReaderIdleEvent = true;
// }
// ctx.fireChannelRead(msg);
// }
//
// @Override
// public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// if (writerrIdleTimeNanos > 0) {
// lastReadTime = System.nanoTime();
// reading = false;
// }
// ctx.fireChannelReadComplete();
// }
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
if (LOG.isDebugEnabled()) {
LOG.debug("Initializing autoflush handler. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
}
if (LOG.isDebugEnabled()) {
LOG.debug("Initializing autoflush handler. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
}
switch (state) {
case 1:
case 2:
@@ -126,9 +124,7 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
EventExecutor loop = ctx.executor();
lastWriteTime = System.nanoTime();
writerIdleTimeout = loop.schedule(
new WriterIdleTimeoutTask(ctx),
writerIdleTimeNanos, TimeUnit.NANOSECONDS);
writerIdleTimeout = loop.schedule(new WriterIdleTimeoutTask(ctx), writerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
private void destroy() {
@@ -143,14 +139,16 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
/**
* Is called when the write timeout expire.
*
* @param ctx the channel context.
* @throws Exception in case of any IO error.
* @param ctx
* the channel context.
* @throws Exception
* in case of any IO error.
*/
protected void channelIdle(ChannelHandlerContext ctx/*, IdleStateEvent evt*/) throws Exception {
// ctx.fireUserEventTriggered(evt);
if (LOG.isDebugEnabled()) {
LOG.debug("Flushing idle Netty channel. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
}
protected void channelIdle(ChannelHandlerContext ctx/* , IdleStateEvent evt */) throws Exception {
// ctx.fireUserEventTriggered(evt);
if (LOG.isDebugEnabled()) {
LOG.debug("Flushing idle Netty channel. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
}
ctx.channel().flush();
}
@@ -168,22 +166,19 @@ public class AutoFlushHandler extends ChannelDuplexHandler {
return;
}
// long lastWriteTime = IdleStateHandler.this.lastWriteTime;
// long lastWriteTime = lastWriteTime;
// long lastWriteTime = IdleStateHandler.this.lastWriteTime;
// long lastWriteTime = lastWriteTime;
long nextDelay = writerIdleTimeNanos - (System.nanoTime() - lastWriteTime);
if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback.
writerIdleTimeout = ctx.executor().schedule(
this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
writerIdleTimeout = ctx.executor().schedule(this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
try {
/*IdleStateEvent event;
if (firstWriterIdleEvent) {
firstWriterIdleEvent = false;
event = IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT;
} else {
event = IdleStateEvent.WRITER_IDLE_STATE_EVENT;
}*/
channelIdle(ctx/*, event*/);
/*
* IdleStateEvent event; if (firstWriterIdleEvent) { firstWriterIdleEvent =
* false; event = IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT; } else { event =
* IdleStateEvent.WRITER_IDLE_STATE_EVENT; }
*/
channelIdle(ctx/* , event */);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
@@ -13,17 +13,18 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.*;
import java.util.ArrayList;
import java.util.List;
public class MessageBuilder {
public static class PublishBuilder {
private String topic = null;
private boolean retained = false;
private MqttQoS qos;
@@ -138,19 +139,28 @@ public class MessageBuilder {
}
public MqttConnectMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnectVariableHeader mqttConnectVariableHeader =
new MqttConnectVariableHeader(
version.protocolName(),
version.protocolLevel(),
hasUser,
hasPassword,
false,
willQos.value(),
willFlag,
cleanSession,
keepAliveSecs);
MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(clientId, willTopic, willMessage, username, password);
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
MqttMessageType.CONNECT,
false,
MqttQoS.AT_MOST_ONCE,
false,
0);
MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
version.protocolName(),
version.protocolLevel(),
hasUser,
hasPassword,
false,
willQos.value(),
willFlag,
cleanSession,
keepAliveSecs);
MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(
clientId,
willTopic,
willMessage,
username,
password);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
}
}
@@ -171,7 +181,12 @@ public class MessageBuilder {
}
public MqttSubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
MqttMessageType.SUBSCRIBE,
false,
MqttQoS.AT_LEAST_ONCE,
false,
0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId);
MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions);
return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
@@ -194,8 +209,12 @@ public class MessageBuilder {
}
public MqttUnsubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false,
MqttQoS.AT_LEAST_ONCE, false, 0);
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
MqttMessageType.UNSUBSCRIBE,
false,
MqttQoS.AT_LEAST_ONCE,
false,
0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId);
MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters);
return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
@@ -13,11 +13,11 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
@@ -26,24 +26,26 @@ import io.netty.handler.timeout.IdleStateEvent;
@Sharable
class MoquetteIdleTimeoutHandler extends ChannelDuplexHandler {
private static final Logger LOG = LoggerFactory.getLogger(MoquetteIdleTimeoutHandler.class);
private static final Logger LOG = LoggerFactory.getLogger(MoquetteIdleTimeoutHandler.class);
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState e = ((IdleStateEvent) evt).state();
if (e == IdleState.ALL_IDLE) {
LOG.info("Firing channel inactive event. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
//fire a channelInactive to trigger publish of Will
LOG.info("Firing channel inactive event. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
// fire a channelInactive to trigger publish of Will
ctx.fireChannelInactive();
ctx.close();
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Firing Netty event. MqttClientId = {}, eventClass = {}.", NettyUtils.clientID(ctx.channel()),
evt.getClass().getName());
}
if (LOG.isDebugEnabled()) {
LOG.debug(
"Firing Netty event. MqttClientId = {}, eventClass = {}.",
NettyUtils.clientID(ctx.channel()),
evt.getClass().getName());
}
super.userEventTriggered(ctx, evt);
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import io.moquette.BrokerConstants;
@@ -42,13 +43,11 @@ import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.moquette.BrokerConstants.*;
/**
@@ -56,28 +55,31 @@ import static io.moquette.BrokerConstants.*;
* @author andrea
*/
public class NettyAcceptor implements ServerAcceptor {
private static final String MQTT_SUBPROTOCOL_CSV_LIST = "mqtt, mqttv3.1, mqttv3.1.1";
static class WebSocketFrameToByteBufDecoder extends MessageToMessageDecoder<BinaryWebSocketFrame> {
@Override
protected void decode(ChannelHandlerContext chc, BinaryWebSocketFrame frame, List<Object> out) throws Exception {
//convert the frame to a ByteBuf
protected void decode(ChannelHandlerContext chc, BinaryWebSocketFrame frame, List<Object> out)
throws Exception {
// convert the frame to a ByteBuf
ByteBuf bb = frame.content();
//System.out.println("WebSocketFrameToByteBufDecoder decode - " + ByteBufUtil.hexDump(bb));
// System.out.println("WebSocketFrameToByteBufDecoder decode - " +
// ByteBufUtil.hexDump(bb));
bb.retain();
out.add(bb);
}
}
static class ByteBufToWebSocketFrameEncoder extends MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext chc, ByteBuf bb, List<Object> out) throws Exception {
//convert the ByteBuf to a WebSocketFrame
// convert the ByteBuf to a WebSocketFrame
BinaryWebSocketFrame result = new BinaryWebSocketFrame();
//System.out.println("ByteBufToWebSocketFrameEncoder encode - " + ByteBufUtil.hexDump(bb));
// System.out.println("ByteBufToWebSocketFrameEncoder encode - " +
// ByteBufUtil.hexDump(bb));
result.content().writeBytes(bb);
out.add(result);
}
@@ -89,7 +91,7 @@ public class NettyAcceptor implements ServerAcceptor {
}
private static final Logger LOG = LoggerFactory.getLogger(NettyAcceptor.class);
EventLoopGroup m_bossGroup;
EventLoopGroup m_workerGroup;
BytesMetricsCollector m_bytesMetricsCollector = new BytesMetricsCollector();
@@ -102,20 +104,24 @@ public class NettyAcceptor implements ServerAcceptor {
private int nettyChannelTimeoutSeconds;
@Override
public void initialize(ProtocolProcessor processor, IConfig props, ISslContextCreator sslCtxCreator) throws IOException {
public void initialize(ProtocolProcessor processor, IConfig props, ISslContextCreator sslCtxCreator)
throws IOException {
LOG.info("Initializing Netty acceptor...");
nettySoBacklog = Integer.parseInt(props.getProperty(BrokerConstants.NETTY_SO_BACKLOG_PROPERTY_NAME, "128"));
nettySoReuseaddr = Boolean.parseBoolean(props.getProperty(BrokerConstants.NETTY_SO_REUSEADDR_PROPERTY_NAME, "true"));
nettyTcpNodelay = Boolean.parseBoolean(props.getProperty(BrokerConstants.NETTY_TCP_NODELAY_PROPERTY_NAME, "true"));
nettySoKeepalive = Boolean.parseBoolean(props.getProperty(BrokerConstants.NETTY_SO_KEEPALIVE_PROPERTY_NAME, "true"));
nettySoReuseaddr = Boolean
.parseBoolean(props.getProperty(BrokerConstants.NETTY_SO_REUSEADDR_PROPERTY_NAME, "true"));
nettyTcpNodelay = Boolean
.parseBoolean(props.getProperty(BrokerConstants.NETTY_TCP_NODELAY_PROPERTY_NAME, "true"));
nettySoKeepalive = Boolean
.parseBoolean(props.getProperty(BrokerConstants.NETTY_SO_KEEPALIVE_PROPERTY_NAME, "true"));
nettyChannelTimeoutSeconds = Integer
.parseInt(props.getProperty(BrokerConstants.NETTY_CHANNEL_TIMEOUT_SECONDS_PROPERTY_NAME, "10"));
m_bossGroup = new NioEventLoopGroup();
m_workerGroup = new NioEventLoopGroup();
final NettyMQTTHandler handler = new NettyMQTTHandler(processor);
initializePlainTCPTransport(handler, props);
initializeWebSocketTransport(handler, props);
String sslTcpPortProp = props.getProperty(BrokerConstants.SSL_PORT_PROPERTY_NAME);
@@ -132,11 +138,11 @@ public class NettyAcceptor implements ServerAcceptor {
}
private void initFactory(String host, int port, String protocol, final PipelineInitializer pipeliner) {
LOG.info("Initializing server. Protocol = {}.", protocol);
LOG.info("Initializing server. Protocol = {}.", protocol);
ServerBootstrap b = new ServerBootstrap();
b.group(m_bossGroup, m_workerGroup)
.channel(NioServerSocketChannel.class)
b.group(m_bossGroup, m_workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
@@ -147,13 +153,11 @@ public class NettyAcceptor implements ServerAcceptor {
throw th;
}
}
})
.option(ChannelOption.SO_BACKLOG, nettySoBacklog)
.option(ChannelOption.SO_REUSEADDR, nettySoReuseaddr)
}).option(ChannelOption.SO_BACKLOG, nettySoBacklog).option(ChannelOption.SO_REUSEADDR, nettySoReuseaddr)
.option(ChannelOption.TCP_NODELAY, nettyTcpNodelay)
.childOption(ChannelOption.SO_KEEPALIVE, nettySoKeepalive);
try {
LOG.info("Binding server. Protocol = {}, host = {}, port = {}.", host, port);
LOG.info("Binding server. Protocol = {}, host = {}, port = {}.", host, port);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(host, port);
LOG.info("The server has been binded. Protocol = {}, host = {}, port = {}", host, port);
@@ -162,23 +166,27 @@ public class NettyAcceptor implements ServerAcceptor {
LOG.error("An interruptedException was caught while initializing server. Protocol = {}.", protocol, ex);
}
}
private void initializePlainTCPTransport(final NettyMQTTHandler handler, IConfig props) throws IOException {
LOG.info("Configuring TCP MQTT transport...");
LOG.info("Configuring TCP MQTT transport...");
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
String tcpPortProp = props.getProperty(PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(tcpPortProp)) {
LOG.info("The property {} has been setted to {}. TCP MQTT will be disabled.", BrokerConstants.PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
LOG.info(
"The property {} has been setted to {}. TCP MQTT will be disabled.",
BrokerConstants.PORT_PROPERTY_NAME,
DISABLED_PORT_BIND);
return;
}
int port = Integer.parseInt(tcpPortProp);
initFactory(host, port, "TCP MQTT", new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) {
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, nettyChannelTimeoutSeconds));
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
// pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
// pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
pipeline.addLast("decoder", new MqttDecoder());
pipeline.addLast("encoder", MqttEncoder.INSTANCE);
@@ -188,27 +196,32 @@ public class NettyAcceptor implements ServerAcceptor {
}
});
}
private void initializeWebSocketTransport(final NettyMQTTHandler handler, IConfig props) throws IOException {
LOG.info("Configuring Websocket MQTT transport...");
LOG.info("Configuring Websocket MQTT transport...");
String webSocketPortProp = props.getProperty(WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(webSocketPortProp)) {
//Do nothing no WebSocket configured
LOG.info("The property {} has been setted to {}. Websocket MQTT will be disabled.",
BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
// Do nothing no WebSocket configured
LOG.info(
"The property {} has been setted to {}. Websocket MQTT will be disabled.",
BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME,
DISABLED_PORT_BIND);
return;
}
int port = Integer.parseInt(webSocketPortProp);
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
initFactory(host, port, "Websocket MQTT", new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) {
pipeline.addLast(new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("webSocketHandler", new WebSocketServerProtocolHandler("/mqtt", MQTT_SUBPROTOCOL_CSV_LIST));
pipeline.addLast(
"webSocketHandler",
new WebSocketServerProtocolHandler("/mqtt", MQTT_SUBPROTOCOL_CSV_LIST));
pipeline.addLast("ws2bytebufDecoder", new WebSocketFrameToByteBufDecoder());
pipeline.addLast("bytebuf2wsEncoder", new ByteBufToWebSocketFrameEncoder());
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, nettyChannelTimeoutSeconds));
@@ -222,14 +235,17 @@ public class NettyAcceptor implements ServerAcceptor {
}
});
}
private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext) throws IOException {
LOG.info("Configuring SSL MQTT transport...");
String sslPortProp = props.getProperty(SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext)
throws IOException {
LOG.info("Configuring SSL MQTT transport...");
String sslPortProp = props.getProperty(SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(sslPortProp)) {
//Do nothing no SSL configured
LOG.info("The property {} has been setted to {}. SSL MQTT will be disabled.",
BrokerConstants.SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
// Do nothing no SSL configured
LOG.info(
"The property {} has been setted to {}. SSL MQTT will be disabled.",
BrokerConstants.SSL_PORT_PROPERTY_NAME,
DISABLED_PORT_BIND);
return;
}
@@ -239,14 +255,15 @@ public class NettyAcceptor implements ServerAcceptor {
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
initFactory(host, sslPort, "SSL MQTT", new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) throws Exception {
pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth));
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, nettyChannelTimeoutSeconds));
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
//pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
// pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
pipeline.addLast("decoder", new MqttDecoder());
pipeline.addLast("encoder", MqttEncoder.INSTANCE);
@@ -257,28 +274,34 @@ public class NettyAcceptor implements ServerAcceptor {
});
}
private void initializeWSSTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext) throws IOException {
LOG.info("Configuring secure websocket MQTT transport...");
String sslPortProp = props.getProperty(WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
private void initializeWSSTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext)
throws IOException {
LOG.info("Configuring secure websocket MQTT transport...");
String sslPortProp = props.getProperty(WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(sslPortProp)) {
//Do nothing no SSL configured
LOG.info("The property {} has been setted to {}. Secure websocket MQTT will be disabled.",
BrokerConstants.WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
// Do nothing no SSL configured
LOG.info(
"The property {} has been setted to {}. Secure websocket MQTT will be disabled.",
BrokerConstants.WSS_PORT_PROPERTY_NAME,
DISABLED_PORT_BIND);
return;
}
int sslPort = Integer.parseInt(sslPortProp);
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
initFactory(host, sslPort, "Secure websocket", new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) throws Exception {
pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth));
pipeline.addLast("httpEncoder", new HttpResponseEncoder());
pipeline.addLast("httpDecoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("webSocketHandler", new WebSocketServerProtocolHandler("/mqtt", MQTT_SUBPROTOCOL_CSV_LIST));
pipeline.addLast(
"webSocketHandler",
new WebSocketServerProtocolHandler("/mqtt", MQTT_SUBPROTOCOL_CSV_LIST));
pipeline.addLast("ws2bytebufDecoder", new WebSocketFrameToByteBufDecoder());
pipeline.addLast("bytebuf2wsEncoder", new ByteBufToWebSocketFrameEncoder());
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, nettyChannelTimeoutSeconds));
@@ -294,52 +317,56 @@ public class NettyAcceptor implements ServerAcceptor {
}
public void close() {
LOG.info("Closing Netty acceptor...");
if (m_workerGroup == null || m_bossGroup == null) {
LOG.error("The Netty acceptor is not initialized.");
throw new IllegalStateException("Invoked close on an Acceptor that wasn't initialized");
}
LOG.info("Closing Netty acceptor...");
if (m_workerGroup == null || m_bossGroup == null) {
LOG.error("The Netty acceptor is not initialized.");
throw new IllegalStateException("Invoked close on an Acceptor that wasn't initialized");
}
Future<?> workerWaiter = m_workerGroup.shutdownGracefully();
Future<?> bossWaiter = m_bossGroup.shutdownGracefully();
/*
* We shouldn't raise an IllegalStateException if we are interrupted. If
* we did so, the broker is not shut down properly.
*/
/*
* We shouldn't raise an IllegalStateException if we are interrupted. If we did so, the
* broker is not shut down properly.
*/
LOG.info("Waiting for worker and boss event loop groups to terminate...");
try {
workerWaiter.await(10, TimeUnit.SECONDS);
bossWaiter.await(10, TimeUnit.SECONDS);
} catch (InterruptedException iex) {
LOG.warn("An InterruptedException was caught while waiting for event loops to terminate...");
LOG.warn("An InterruptedException was caught while waiting for event loops to terminate...");
}
if (!m_workerGroup.isTerminated()) {
LOG.warn("Forcing shutdown of worker event loop...");
m_workerGroup.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS);
LOG.warn("Forcing shutdown of worker event loop...");
m_workerGroup.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS);
}
if (!m_bossGroup.isTerminated()) {
LOG.warn("Forcing shutdown of boss event loop...");
m_bossGroup.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS);
LOG.warn("Forcing shutdown of boss event loop...");
m_bossGroup.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS);
}
LOG.info("Collecting message metrics...");
MessageMetrics metrics = m_metricsCollector.computeMetrics();
LOG.info("The metrics have been collected. Read messages = {}, written messages = {}.",
metrics.messagesRead(), metrics.messagesWrote());
LOG.info(
"The metrics have been collected. Read messages = {}, written messages = {}.",
metrics.messagesRead(),
metrics.messagesWrote());
LOG.info("Collecting bytes metrics...");
BytesMetrics bytesMetrics = m_bytesMetricsCollector.computeMetrics();
LOG.info("The bytes metrics have been collected. Read bytes = {}, written bytes = {}.", bytesMetrics.readBytes(),
bytesMetrics.wroteBytes());
LOG.info(
"The bytes metrics have been collected. Read bytes = {}, written bytes = {}.",
bytesMetrics.readBytes(),
bytesMetrics.wroteBytes());
}
private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
if(needsClientAuth) {
sslEngine.setNeedClientAuth(true);
if (needsClientAuth) {
sslEngine.setNeedClientAuth(true);
}
return new SslHandler(sslEngine);
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import io.moquette.spi.impl.ProtocolProcessor;
@@ -22,17 +23,15 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
/**
*
* @author andrea
*/
@Sharable
public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(NettyMQTTHandler.class);
private final ProtocolProcessor m_processor;
@@ -75,22 +74,27 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
m_processor.processPubAck(ctx.channel(), (MqttPubAckMessage) msg);
break;
case PINGREQ:
MqttFixedHeader pingHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, AT_MOST_ONCE, false, 0);
MqttFixedHeader pingHeader = new MqttFixedHeader(
MqttMessageType.PINGRESP,
false,
AT_MOST_ONCE,
false,
0);
MqttMessage pingResp = new MqttMessage(pingHeader);
ctx.writeAndFlush(pingResp);
break;
}
} catch (Throwable ex) {
LOG.error("Exception was caught while processing MQTT message, " + ex.getCause(), ex);
ctx.fireExceptionCaught(ex);
LOG.error("Exception was caught while processing MQTT message, " + ex.getCause(), ex);
ctx.fireExceptionCaught(ex);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String clientID = NettyUtils.clientID(ctx.channel());
if (clientID != null && !clientID.isEmpty()) {
LOG.info("Notifying connection lost event. MqttClientId = {}.", clientID);
LOG.info("Notifying connection lost event. MqttClientId = {}.", clientID);
m_processor.processConnectionLost(clientID, ctx.channel());
}
ctx.close();
@@ -98,9 +102,12 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOG.error(
"An unexpected exception was caught while processing MQTT message. Closing Netty channel. MqttClientId = {}, cause = {}, errorMessage = {}.",
NettyUtils.clientID(ctx.channel()), cause.getCause(), cause.getMessage());
LOG.error(
"An unexpected exception was caught while processing MQTT message. "
+ "Closing Netty channel. MqttClientId = {}, cause = {}, errorMessage = {}.",
NettyUtils.clientID(ctx.channel()),
cause.getCause(),
cause.getMessage());
ctx.close();
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty;
import io.moquette.server.Constants;
@@ -13,9 +13,11 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
public class BytesMetrics {
private long m_readBytes = 0;
private long m_wroteBytes = 0;
@@ -34,4 +36,4 @@ public class BytesMetrics {
public long wroteBytes() {
return m_wroteBytes;
}
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import java.util.concurrent.atomic.AtomicLong;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import io.netty.buffer.ByteBuf;
@@ -30,7 +31,7 @@ public class BytesMetricsHandler extends ChannelDuplexHandler {
private BytesMetricsCollector m_collector;
public BytesMetricsHandler(BytesMetricsCollector collector) {
m_collector = collector;
m_collector = collector;
}
@Override
@@ -44,21 +45,19 @@ public class BytesMetricsHandler extends ChannelDuplexHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
metrics.incrementRead(((ByteBuf)msg).readableBytes());
metrics.incrementRead(((ByteBuf) msg).readableBytes());
ctx.fireChannelRead(msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
metrics.incrementWrote(((ByteBuf)msg).writableBytes());
metrics.incrementWrote(((ByteBuf) msg).writableBytes());
ctx.write(msg, promise);
}
@Override
public void close(ChannelHandlerContext ctx,
ChannelPromise promise) throws Exception {
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
m_collector.sumReadBytes(metrics.readBytes());
m_collector.sumWroteBytes(metrics.wroteBytes());
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import io.moquette.server.netty.NettyUtils;
@@ -23,7 +24,6 @@ import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.moquette.spi.impl.Utils.messageId;
/**
@@ -54,7 +54,11 @@ public class MQTTMessageLogger extends ChannelDuplexHandler {
break;
case SUBSCRIBE:
MqttSubscribeMessage subscribe = (MqttSubscribeMessage) msg;
LOG.info("{} SUBSCRIBE <{}> to topics {}", direction, clientID, subscribe.payload().topicSubscriptions());
LOG.info(
"{} SUBSCRIBE <{}> to topics {}",
direction,
clientID,
subscribe.payload().topicSubscriptions());
break;
case UNSUBSCRIBE:
MqttUnsubscribeMessage unsubscribe = (MqttUnsubscribeMessage) msg;
@@ -13,9 +13,11 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
public class MessageMetrics {
private long m_messagesRead = 0;
private long m_messageWrote = 0;
@@ -34,4 +36,4 @@ public class MessageMetrics {
public long messagesWrote() {
return m_messageWrote;
}
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import java.util.concurrent.atomic.AtomicLong;
@@ -21,6 +22,7 @@ import java.util.concurrent.atomic.AtomicLong;
* Collects all the metrics from the various pipeline.
*/
public class MessageMetricsCollector {
private AtomicLong readMsgs = new AtomicLong();
private AtomicLong wroteMsgs = new AtomicLong();
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import io.netty.channel.Channel;
@@ -29,7 +30,7 @@ public class MessageMetricsHandler extends ChannelDuplexHandler {
private MessageMetricsCollector m_collector;
public MessageMetricsHandler(MessageMetricsCollector collector) {
m_collector = collector;
m_collector = collector;
}
@Override
@@ -54,10 +55,8 @@ public class MessageMetricsHandler extends ChannelDuplexHandler {
ctx.write(msg, promise);
}
@Override
public void close(ChannelHandlerContext ctx,
ChannelPromise promise) throws Exception {
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
MessageMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
m_collector.sumReadMessages(metrics.messagesRead());
m_collector.sumWroteMessages(metrics.messagesWrote());
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
@@ -20,7 +21,6 @@ import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
@@ -55,10 +55,11 @@ public class ClientSession {
private volatile boolean cleanSession;
// private BlockingQueue<AbstractMessage> m_queueToPublish = new ArrayBlockingQueue<>(Constants.MAX_MESSAGE_QUEUE);
// private BlockingQueue<AbstractMessage> m_queueToPublish = new
// ArrayBlockingQueue<>(Constants.MAX_MESSAGE_QUEUE);
public ClientSession(String clientID, IMessagesStore messagesStore, ISessionsStore sessionsStore,
boolean cleanSession) {
boolean cleanSession) {
this.clientID = clientID;
this.messagesStore = messagesStore;
this.m_sessionsStore = sessionsStore;
@@ -66,12 +67,12 @@ public class ClientSession {
}
/**
* Return the list of persisted publishes for the given clientID.
* For QoS1 and QoS2 with clean session flag, this method return the list of
* missed publish events while the client was disconnected.
* Return the list of persisted publishes for the given clientID. For QoS1 and QoS2 with clean
* session flag, this method return the list of missed publish events while the client was
* disconnected.
*
* @return the list of messages to be delivered for client related to the session.
* */
*/
public BlockingQueue<IMessagesStore.StoredMessage> queue() {
LOG.info("Retrieving stored messages. MqttClientId = {}.", clientID);
return this.m_sessionsStore.queue(clientID);
@@ -79,26 +80,35 @@ public class ClientSession {
@Override
public String toString() {
return "ClientSession{clientID='" + clientID + '\'' +"}";
return "ClientSession{clientID='" + clientID + '\'' + "}";
}
public boolean subscribe(Subscription newSubscription) {
LOG.info("Adding new subscription. MqttClientId = {}, topics = {}, qos = {}.", newSubscription.getClientId(),
newSubscription.getTopicFilter(), newSubscription.getRequestedQos());
LOG.info(
"Adding new subscription. MqttClientId = {}, topics = {}, qos = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter(),
newSubscription.getRequestedQos());
boolean validTopic = SubscriptionsStore.validate(newSubscription.getTopicFilter());
if (!validTopic) {
LOG.warn("The topic filter is not valid. MqttClientId = {}, topics = {}.", newSubscription.getClientId(),
newSubscription.getTopicFilter());
//send SUBACK with 0x80 for this topic filter
LOG.warn(
"The topic filter is not valid. MqttClientId = {}, topics = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter());
// send SUBACK with 0x80 for this topic filter
return false;
}
ClientTopicCouple matchingCouple = new ClientTopicCouple(this.clientID, newSubscription.getTopicFilter());
Subscription existingSub = m_sessionsStore.getSubscription(matchingCouple);
//update the selected subscriptions if not present or if has a greater qos
// update the selected subscriptions if not present or if has a greater qos
if (existingSub == null || existingSub.getRequestedQos().value() < newSubscription.getRequestedQos().value()) {
if (existingSub != null) {
LOG.info("The subscription already existed with a lower QoS value. It will be updated. MqttClientId = {}, topics = {}, existingQos = {}, newQos = {}.",
newSubscription.getClientId(), newSubscription.getTopicFilter(), existingSub.getRequestedQos(),
LOG.info(
"The subscription already existed with a lower QoS value. It will be updated. "
+ "MqttClientId = {}, topics = {}, existingQos = {}, newQos = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter(),
existingSub.getRequestedQos(),
newSubscription.getRequestedQos());
subscriptions.remove(newSubscription);
}
@@ -123,17 +133,16 @@ public class ClientSession {
public void disconnect() {
if (this.cleanSession) {
LOG.info("The client has disconnected. Removing its subscriptions. MqttClientId = {}.", clientID);
//cleanup topic subscriptions
// cleanup topic subscriptions
cleanSession();
}
}
public void cleanSession() {
LOG.info("Wiping existing subscriptions. MqttClientId = {}.", this.clientID);
m_sessionsStore.wipeSubscriptions(this.clientID);
//remove also the messages stored of type QoS1/2
// remove also the messages stored of type QoS1/2
LOG.info("Removing stored messages with QoS 1 and 2. MqttClientId = {}.", this.clientID);
messagesStore.dropMessagesInSession(this.clientID);
}
@@ -152,7 +161,7 @@ public class ClientSession {
}
public void inFlightAcknowledged(int messageID) {
if (LOG.isTraceEnabled())
if (LOG.isTraceEnabled())
LOG.trace("Acknowledging inflight, clientID <{}> messageID {}", this.clientID, messageID);
m_sessionsStore.inFlightAck(this.clientID, messageID);
}
@@ -170,8 +179,10 @@ public class ClientSession {
/**
* Enqueue a message to be sent to the client.
* @param message the message to enqueue.
* */
*
* @param message
* the message to enqueue.
*/
public void enqueue(IMessagesStore.StoredMessage message) {
this.m_sessionsStore.queue(this.clientID).add(message);
}
@@ -205,4 +216,4 @@ public class ClientSession {
return m_sessionsStore.getInflightMessagesNo(clientID);
}
}
}
@@ -13,11 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
/**
*/
public interface IMatchingCondition {
boolean match(String key);
}
@@ -13,12 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.MqttQoS;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Collection;
@@ -29,13 +29,14 @@ import java.util.Collection;
public interface IMessagesStore {
class StoredMessage implements Serializable {
private static final long serialVersionUID = 1755296138639817304L;
final MqttQoS m_qos;
final byte[] m_payload;
final String m_topic;
private boolean m_retained;
private String m_clientID;
//Optional attribute, available only fo QoS 1 and 2
// Optional attribute, available only fo QoS 1 and 2
private Integer m_msgID;
private MessageGUID m_guid;
@@ -95,41 +96,42 @@ public interface IMessagesStore {
@Override
public String toString() {
return "PublishEvent{" +
"m_msgID=" + m_msgID +
", clientID='" + m_clientID + '\'' +
", m_retain=" + m_retained +
", m_qos=" + m_qos +
", m_topic='" + m_topic + '\'' +
'}';
return "PublishEvent{" + "m_msgID=" + m_msgID + ", clientID='" + m_clientID + '\'' + ", m_retain="
+ m_retained + ", m_qos=" + m_qos + ", m_topic='" + m_topic + '\'' + '}';
}
}
/**
* Used to initialize all persistent store structures
* */
*/
void initStore();
/**
* Persist the message.
* If the message is empty then the topic is cleaned, else it's stored.
* @param topic for the retained.
* @param guid of the message to mark as retained.
* Persist the message. If the message is empty then the topic is cleaned, else it's stored.
*
* @param topic
* for the retained.
* @param guid
* of the message to mark as retained.
*/
void storeRetained(String topic, MessageGUID guid);
/**
* Return a list of retained messages that satisfy the condition.
* @param condition the condition to match during the search.
*
* @param condition
* the condition to match during the search.
* @return the collection of matching messages.
*/
Collection<StoredMessage> searchMatching(IMatchingCondition condition);
/**
* Persist the message.
* @param storedMessage the message to store for future usage.
*
* @param storedMessage
* the message to store for future usage.
* @return the unique id in the storage (guid).
* */
*/
MessageGUID storePublishForFuture(StoredMessage storedMessage);
void dropMessagesInSession(String clientID);
@@ -13,14 +13,14 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
/**
* Callback interface used to be notified of some events from the input event queue.
*
* It's the abstraction of the messaging stuff attached in after the front protocol
* parsing stuff.
*
*
* It's the abstraction of the messaging stuff attached in after the front protocol parsing stuff.
*
* @author andrea
*/
public interface IMessaging {
@@ -13,12 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.impl.subscriptions.Subscription;
@@ -30,6 +30,7 @@ import io.moquette.spi.impl.subscriptions.Subscription;
public interface ISessionsStore {
class ClientTopicCouple {
public final String topicFilter;
public final String clientID;
@@ -40,12 +41,15 @@ public interface ISessionsStore {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ClientTopicCouple that = (ClientTopicCouple) o;
if (topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null) return false;
if (topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null)
return false;
return !(clientID != null ? !clientID.equals(that.clientID) : that.clientID != null);
}
@@ -58,10 +62,7 @@ public interface ISessionsStore {
@Override
public String toString() {
return "ClientTopicCouple{" +
"topicFilter='" + topicFilter + '\'' +
", clientID='" + clientID + '\'' +
'}';
return "ClientTopicCouple{" + "topicFilter='" + topicFilter + '\'' + ", clientID='" + clientID + '\'' + '}';
}
}
@@ -72,41 +73,50 @@ public interface ISessionsStore {
/**
* Add a new subscription to the session
* @param newSubscription the subscription to add.
*
* @param newSubscription
* the subscription to add.
*/
void addNewSubscription(Subscription newSubscription);
/**
* Removed a specific subscription
* @param topic the topic of the subscription.
* @param clientID the session client.
* */
*
* @param topic
* the topic of the subscription.
* @param clientID
* the session client.
*/
void removeSubscription(String topic, String clientID);
/**
* Remove all the subscriptions of the session
* @param sessionID the client ID
*
* @param sessionID
* the client ID
*/
void wipeSubscriptions(String sessionID);
/**
* Return all topic filters to recreate the subscription tree.
* */
*/
List<ClientTopicCouple> listAllSubscriptions();
/**
* @param couple the subscription descriptor.
* @param couple
* the subscription descriptor.
* @return the subscription stored by clientID and topicFilter, if any else null;
* */
*/
Subscription getSubscription(ClientTopicCouple couple);
/*
* @return all subscriptions stored.
*/
List<Subscription> getSubscriptions();
/**
* @param clientID the session client ID.
* @param clientID
* the session client ID.
* @return true iff there are subscriptions persisted with clientID
*/
boolean contains(String clientID);
@@ -114,13 +124,15 @@ public interface ISessionsStore {
ClientSession createNewSession(String clientID, boolean cleanSession);
/**
* @param clientID the client owning the session.
* @param clientID
* the client owning the session.
* @return the session for the given clientID, null if not found.
* */
*/
ClientSession sessionForClient(String clientID);
/**
* Returns all the sessions
*
* @return
*/
Collection<ClientSession> getAllSessions();
@@ -129,24 +141,32 @@ public interface ISessionsStore {
/**
* Save the binding messageID, clientID - guid
* @param clientID the client ID
* @param messageID the message ID
* @param guid the uuid of the message to mark as inflight.
* */
*
* @param clientID
* the client ID
* @param messageID
* the message ID
* @param guid
* the uuid of the message to mark as inflight.
*/
void inFlight(String clientID, int messageID, MessageGUID guid);
/**
* Return the next valid packetIdentifier for the given client session.
* @param clientID the clientID requesting next packet id.
*
* @param clientID
* the clientID requesting next packet id.
* @return the next valid id.
* */
*/
int nextPacketID(String clientID);
/**
* List the guids for retained messages for the session
* @param clientID the client ID owning the queue.
*
* @param clientID
* the client ID owning the queue.
* @return the list of queue message UUIDs.
* */
*/
BlockingQueue<StoredMessage> queue(String clientID);
void dropQueue(String clientID);
@@ -154,19 +174,21 @@ public interface ISessionsStore {
void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID);
/**
* @param clientID the client ID accessing the second phase.
* @param messageID the message ID that reached the second phase.
* @param clientID
* the client ID accessing the second phase.
* @param messageID
* the message ID that reached the second phase.
* @return the guid of message just acked.
* */
*/
MessageGUID secondPhaseAcknowledged(String clientID, int messageID);
MessageGUID mapToGuid(String clientID, int messageID);
StoredMessage getInflightMessage(String clientID, int messageID);
/**
* Returns the number of inflight messages for the given client ID
*
*
* @param clientID
* @return
*/
@@ -174,16 +196,15 @@ public interface ISessionsStore {
/**
* Returns the size of the session queue for the given client ID
*
*
* @param clientID
* @return
*/
int getPendingPublishMessagesNo(String clientID);
/**
* Returns the number of second-phase ACK pending messages for the given
* client ID
*
* Returns the number of second-phase ACK pending messages for the given client ID
*
* @param clientID
* @return
*/
@@ -13,16 +13,18 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi;
import java.io.Serializable;
/**
* Value object for GUIDs of messages.
* */
*/
public class MessageGUID implements Serializable {
private static final long serialVersionUID = 4315161987111542406L;
private final String guid;
private final String guid;
public MessageGUID(String guid) {
this.guid = guid;
@@ -30,13 +32,14 @@ public class MessageGUID implements Serializable {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MessageGUID that = (MessageGUID) o;
return guid != null ? guid.equals(that.guid) : that.guid == null;
}
@Override
@@ -46,8 +49,6 @@ public class MessageGUID implements Serializable {
@Override
public String toString() {
return "MessageGUID{" +
"guid='" + guid + '\'' +
'}';
return "MessageGUID{" + "guid='" + guid + '\'' + '}';
}
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl;
import io.moquette.BrokerConstants;
@@ -25,7 +26,6 @@ import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,7 +33,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static io.moquette.logging.LoggingUtils.getInterceptorIds;
/**
@@ -47,56 +46,60 @@ final class BrokerInterceptor implements Interceptor {
private final Map<Class<?>, List<InterceptHandler>> handlers;
private final ExecutorService executor;
private BrokerInterceptor(int poolSize, List<InterceptHandler> handlers) {
LOG.info("Initializing broker interceptor. InterceptorIds = {}.", getInterceptorIds(handlers));
this.handlers = new HashMap<>();
for (Class<?> messageType : InterceptHandler.ALL_MESSAGE_TYPES) {
this.handlers.put(messageType, new CopyOnWriteArrayList<InterceptHandler>());
}
for (InterceptHandler handler : handlers) {
this.addInterceptHandler(handler);
}
executor = Executors.newFixedThreadPool(poolSize);
}
/**
* Configures a broker interceptor, with a thread pool of one thread.
* @param handlers
*/
public BrokerInterceptor(List<InterceptHandler> handlers) {
this(1, handlers);
}
/**
* Configures a broker interceptor using the pool size specified in the
* IConfig argument.
*/
public BrokerInterceptor(IConfig props, List<InterceptHandler> handlers) {
this(Integer.parseInt(props.getProperty(BrokerConstants.BROKER_INTERCEPTOR_THREAD_POOL_SIZE, "1")), handlers);
}
private BrokerInterceptor(int poolSize, List<InterceptHandler> handlers) {
LOG.info("Initializing broker interceptor. InterceptorIds = {}.", getInterceptorIds(handlers));
this.handlers = new HashMap<>();
for (Class<?> messageType : InterceptHandler.ALL_MESSAGE_TYPES) {
this.handlers.put(messageType, new CopyOnWriteArrayList<InterceptHandler>());
}
for (InterceptHandler handler : handlers) {
this.addInterceptHandler(handler);
}
executor = Executors.newFixedThreadPool(poolSize);
}
/**
* Configures a broker interceptor, with a thread pool of one thread.
*
* @param handlers
*/
public BrokerInterceptor(List<InterceptHandler> handlers) {
this(1, handlers);
}
/**
* Configures a broker interceptor using the pool size specified in the IConfig argument.
*/
public BrokerInterceptor(IConfig props, List<InterceptHandler> handlers) {
this(Integer.parseInt(props.getProperty(BrokerConstants.BROKER_INTERCEPTOR_THREAD_POOL_SIZE, "1")), handlers);
}
/**
* Shutdown graciously the executor service
*/
void stop() {
LOG.info("Shutting down interceptor thread pool...");
executor.shutdown();
try {
LOG.info("Waiting for thread pool tasks to terminate...");
executor.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException e) {}
if (!executor.isTerminated()) {
LOG.warn("Forcing shutdown of interceptor thread pool...");
executor.shutdownNow();
}
}
void stop() {
LOG.info("Shutting down interceptor thread pool...");
executor.shutdown();
try {
LOG.info("Waiting for thread pool tasks to terminate...");
executor.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
if (!executor.isTerminated()) {
LOG.warn("Forcing shutdown of interceptor thread pool...");
executor.shutdownNow();
}
}
@Override
public void notifyClientConnected(final MqttConnectMessage msg) {
for (final InterceptHandler handler : this.handlers.get(InterceptConnectMessage.class)) {
LOG.debug("Sending MQTT CONNECT message to interceptor. MqttClientId = {}, interceptorId = {}.",
msg.payload().clientIdentifier(), handler.getID());
LOG.debug(
"Sending MQTT CONNECT message to interceptor. MqttClientId = {}, interceptorId = {}.",
msg.payload().clientIdentifier(),
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onConnect(new InterceptConnectMessage(msg));
@@ -106,11 +109,16 @@ final class BrokerInterceptor implements Interceptor {
}
@Override
public void notifyClientDisconnected(final String clientID, final String username ) {
public void notifyClientDisconnected(final String clientID, final String username) {
for (final InterceptHandler handler : this.handlers.get(InterceptDisconnectMessage.class)) {
LOG.debug("Notifying MQTT client disconnection to interceptor. MqttClientId = {}, username = {}, " +
"interceptorId = {}.", clientID, username, handler.getID());
LOG.debug(
"Notifying MQTT client disconnection to interceptor. MqttClientId = {}, username = {}, "
+ "interceptorId = {}.",
clientID,
username,
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onDisconnect(new InterceptDisconnectMessage(clientID, username));
@@ -122,9 +130,14 @@ final class BrokerInterceptor implements Interceptor {
@Override
public void notifyClientConnectionLost(final String clientID, final String username) {
for (final InterceptHandler handler : this.handlers.get(InterceptConnectionLostMessage.class)) {
LOG.debug("Notifying unexpected MQTT client disconnection to interceptor. MqttClientId = {}, " +
"username = {}, interceptorId = {}.", clientID, username, handler.getID());
LOG.debug(
"Notifying unexpected MQTT client disconnection to interceptor. MqttClientId = {}, "
+ "username = {}, interceptorId = {}.",
clientID,
username,
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onConnectionLost(new InterceptConnectionLostMessage(clientID, username));
@@ -138,9 +151,15 @@ final class BrokerInterceptor implements Interceptor {
int messageId = msg.variableHeader().messageId();
String topic = msg.variableHeader().topicName();
for (final InterceptHandler handler : this.handlers.get(InterceptPublishMessage.class)) {
LOG.debug("Notifying MQTT PUBLISH message to interceptor. MqttClientId = {}, messageId = {}, topic = {}, " +
"interceptorId = {}.", clientID, messageId, topic, handler.getID());
LOG.debug(
"Notifying MQTT PUBLISH message to interceptor. MqttClientId = {}, messageId = {}, topic = {}, "
+ "interceptorId = {}.",
clientID,
messageId,
topic,
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onPublish(new InterceptPublishMessage(msg, clientID, username));
@@ -152,9 +171,14 @@ final class BrokerInterceptor implements Interceptor {
@Override
public void notifyTopicSubscribed(final Subscription sub, final String username) {
for (final InterceptHandler handler : this.handlers.get(InterceptSubscribeMessage.class)) {
LOG.debug("Notifying MQTT SUBSCRIBE message to interceptor. MqttClientId = {}, topicFilter = {}, " +
"interceptorId = {}.", sub.getClientId(), sub.getTopicFilter(), handler.getID());
LOG.debug(
"Notifying MQTT SUBSCRIBE message to interceptor. MqttClientId = {}, topicFilter = {}, "
+ "interceptorId = {}.",
sub.getClientId(),
sub.getTopicFilter(),
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onSubscribe(new InterceptSubscribeMessage(sub, username));
@@ -166,9 +190,14 @@ final class BrokerInterceptor implements Interceptor {
@Override
public void notifyTopicUnsubscribed(final String topic, final String clientID, final String username) {
for (final InterceptHandler handler : this.handlers.get(InterceptUnsubscribeMessage.class)) {
LOG.debug("Notifying MQTT UNSUBSCRIBE message to interceptor. MqttClientId = {}, topic = {}, " +
"interceptorId = {}.", clientID, topic, handler.getID());
LOG.debug(
"Notifying MQTT UNSUBSCRIBE message to interceptor. MqttClientId = {}, topic = {}, "
+ "interceptorId = {}.",
clientID,
topic,
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onUnsubscribe(new InterceptUnsubscribeMessage(topic, clientID, username));
@@ -177,13 +206,18 @@ final class BrokerInterceptor implements Interceptor {
}
}
@Override
public void notifyMessageAcknowledged( final InterceptAcknowledgedMessage msg ) {
@Override
public void notifyMessageAcknowledged(final InterceptAcknowledgedMessage msg) {
for (final InterceptHandler handler : this.handlers.get(InterceptAcknowledgedMessage.class)) {
LOG.debug("Notifying MQTT ACK message to interceptor. MqttClientId = {}, messageId = {}, topic = {}, " +
"interceptorId = {}.",
msg.getMsg().getClientID(), msg.getMsg().getMessageID(), msg.getTopic(), handler.getID());
LOG.debug(
"Notifying MQTT ACK message to interceptor. MqttClientId = {}, messageId = {}, topic = {}, "
+ "interceptorId = {}.",
msg.getMsg().getClientID(),
msg.getMsg().getMessageID(),
msg.getTopic(),
handler.getID());
executor.execute(new Runnable() {
@Override
public void run() {
handler.onMessageAcknowledged(msg);
@@ -192,31 +226,35 @@ final class BrokerInterceptor implements Interceptor {
}
}
@Override
public void addInterceptHandler(InterceptHandler interceptHandler) {
Class<?>[] interceptedMessageTypes = getInterceptedMessageTypes(interceptHandler);
LOG.info("Adding MQTT message interceptor. InterceptorId = {}, handledMessageTypes = {}.",
interceptHandler.getID(), interceptedMessageTypes);
for (Class<?> interceptMessageType : interceptedMessageTypes) {
this.handlers.get(interceptMessageType).add(interceptHandler);
}
}
@Override
public void removeInterceptHandler(InterceptHandler interceptHandler) {
Class<?>[] interceptedMessageTypes = getInterceptedMessageTypes(interceptHandler);
LOG.info("Removing MQTT message interceptor. InterceptorId = {}, handledMessageTypes = {}.",
interceptHandler.getID(), interceptedMessageTypes);
for (Class<?> interceptMessageType : interceptedMessageTypes) {
this.handlers.get(interceptMessageType).remove(interceptHandler);
}
@Override
public void addInterceptHandler(InterceptHandler interceptHandler) {
Class<?>[] interceptedMessageTypes = getInterceptedMessageTypes(interceptHandler);
LOG.info(
"Adding MQTT message interceptor. InterceptorId = {}, handledMessageTypes = {}.",
interceptHandler.getID(),
interceptedMessageTypes);
for (Class<?> interceptMessageType : interceptedMessageTypes) {
this.handlers.get(interceptMessageType).add(interceptHandler);
}
}
private static Class<?>[] getInterceptedMessageTypes(InterceptHandler interceptHandler) {
Class<?> interceptedMessageTypes[] = interceptHandler.getInterceptedMessageTypes();
if (interceptedMessageTypes == null) {
return InterceptHandler.ALL_MESSAGE_TYPES;
}
return interceptedMessageTypes;
}
@Override
public void removeInterceptHandler(InterceptHandler interceptHandler) {
Class<?>[] interceptedMessageTypes = getInterceptedMessageTypes(interceptHandler);
LOG.info(
"Removing MQTT message interceptor. InterceptorId = {}, handledMessageTypes = {}.",
interceptHandler.getID(),
interceptedMessageTypes);
for (Class<?> interceptMessageType : interceptedMessageTypes) {
this.handlers.get(interceptMessageType).remove(interceptHandler);
}
}
private static Class<?>[] getInterceptedMessageTypes(InterceptHandler interceptHandler) {
Class<?>[] interceptedMessageTypes = interceptHandler.getInterceptedMessageTypes();
if (interceptedMessageTypes == null) {
return InterceptHandler.ALL_MESSAGE_TYPES;
}
return interceptedMessageTypes;
}
}
@@ -13,10 +13,10 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl;
import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
/**
@@ -24,6 +24,7 @@ import java.nio.ByteBuffer;
* @author andrea
*/
class DebugUtils {
static String payload2Str(ByteBuffer content) {
byte[] b = new byte[content.remaining()];
content.mark();
@@ -33,7 +34,7 @@ class DebugUtils {
}
static String payload2Str(ByteBuf content) {
//byte[] b = new byte[content.readableBytes()];
// byte[] b = new byte[content.readableBytes()];
return new String(content.copy().array());
}
}
@@ -1,5 +1,5 @@
package io.moquette.spi.impl;
package io.moquette.spi.impl;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
@@ -7,7 +7,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -25,15 +24,20 @@ class InternalRepublisher {
void publishRetained(ClientSession targetSession, Collection<IMessagesStore.StoredMessage> messages) {
for (IMessagesStore.StoredMessage storedMsg : messages) {
//fire as retained the message
// fire as retained the message
Integer packetID = storedMsg.getQos() == MqttQoS.AT_MOST_ONCE ? null : targetSession.nextPacketId();
if (packetID != null) {
LOG.debug("Adding message to inflight zone. MqttClientId = {}, packetId = {}, messageId = {}, topic = {}.",
targetSession.clientID, packetID, storedMsg.getMessageID(), storedMsg.getTopic());
LOG.debug(
"Adding message to inflight zone. "
+ "MqttClientId = {}, packetId = {}, messageId = {}, topic = {}.",
targetSession.clientID,
packetID,
storedMsg.getMessageID(),
storedMsg.getTopic());
targetSession.inFlightAckWaiting(storedMsg.getGuid(), packetID);
}
MqttPublishMessage publishMsg = retainedPublish(storedMsg);
//set the PacketIdentifier only for QoS > 0
// set the PacketIdentifier only for QoS > 0
if (publishMsg.fixedHeader().qosLevel() != MqttQoS.AT_MOST_ONCE) {
publishMsg = retainedPublish(storedMsg, packetID);
}
@@ -41,19 +45,21 @@ class InternalRepublisher {
}
}
void publishStored(ClientSession clientSession, BlockingQueue<IMessagesStore.StoredMessage> publishedEvents) {
List<IMessagesStore.StoredMessage> storedPublishes = new ArrayList<>();
publishedEvents.drainTo(storedPublishes);
for (IMessagesStore.StoredMessage pubEvt : storedPublishes) {
//put in flight zone
LOG.debug("Adding message ot inflight zone. MqttClientId = {}, guid = {}, messageId = {}, topic = {}.",
clientSession.clientID, pubEvt.getGuid(), pubEvt.getMessageID(), pubEvt.getTopic());
// put in flight zone
LOG.debug(
"Adding message ot inflight zone. MqttClientId = {}, guid = {}, messageId = {}, topic = {}.",
clientSession.clientID,
pubEvt.getGuid(),
pubEvt.getMessageID(),
pubEvt.getTopic());
clientSession.inFlightAckWaiting(pubEvt.getGuid(), pubEvt.getMessageID());
MqttPublishMessage publishMsg = notRetainedPublish(pubEvt);
//set the PacketIdentifier only for QoS > 0
// set the PacketIdentifier only for QoS > 0
if (publishMsg.fixedHeader().qosLevel() != MqttQoS.AT_MOST_ONCE) {
publishMsg = notRetainedPublish(pubEvt, pubEvt.getMessageID());
}
@@ -62,22 +68,43 @@ class InternalRepublisher {
}
private MqttPublishMessage notRetainedPublish(IMessagesStore.StoredMessage storedMessage, Integer messageID) {
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), false, messageID);
return createPublishForQos(
storedMessage.getTopic(),
storedMessage.getQos(),
storedMessage.getMessage(),
false,
messageID);
}
private MqttPublishMessage notRetainedPublish(IMessagesStore.StoredMessage storedMessage) {
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), false, 0);
return createPublishForQos(
storedMessage.getTopic(),
storedMessage.getQos(),
storedMessage.getMessage(),
false,
0);
}
private MqttPublishMessage retainedPublish(IMessagesStore.StoredMessage storedMessage) {
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), true, 0);
return createPublishForQos(
storedMessage.getTopic(),
storedMessage.getQos(),
storedMessage.getMessage(),
true,
0);
}
private MqttPublishMessage retainedPublish(IMessagesStore.StoredMessage storedMessage, Integer packetID) {
return createPublishForQos(storedMessage.getTopic(), storedMessage.getQos(), storedMessage.getMessage(), true, packetID);
return createPublishForQos(
storedMessage.getTopic(),
storedMessage.getQos(),
storedMessage.getMessage(),
true,
packetID);
}
public static MqttPublishMessage createPublishForQos(String topic, MqttQoS qos, ByteBuf message, boolean retained, int messageId) {
public static MqttPublishMessage createPublishForQos(String topic, MqttQoS qos, ByteBuf message, boolean retained,
int messageId) {
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0);
MqttPublishVariableHeader varHeader = new MqttPublishVariableHeader(topic, messageId);
return new MqttPublishMessage(fixedHeader, varHeader, message);
@@ -1,3 +1,4 @@
package io.moquette.spi.impl;
import io.moquette.server.ConnectionDescriptorStore;
@@ -10,9 +11,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static io.moquette.spi.impl.ProtocolProcessor.lowerQosToTheSubscriptionDesired;
class MessagesPublisher {
@@ -24,7 +23,7 @@ class MessagesPublisher {
private final PersistentQueueMessageSender messageSender;
public MessagesPublisher(ConnectionDescriptorStore connectionDescriptors, ISessionsStore sessionsStore,
IMessagesStore messagesStore, PersistentQueueMessageSender messageSender) {
IMessagesStore messagesStore, PersistentQueueMessageSender messageSender) {
this.connectionDescriptors = connectionDescriptors;
this.m_sessionsStore = sessionsStore;
this.m_messagesStore = messagesStore;
@@ -35,7 +34,8 @@ class MessagesPublisher {
return notRetainedPublishWithMessageId(topic, qos, message, 0);
}
private static MqttPublishMessage notRetainedPublishWithMessageId(String topic, MqttQoS qos, ByteBuf message, int messageId) {
private static MqttPublishMessage notRetainedPublishWithMessageId(String topic, MqttQoS qos, ByteBuf message,
int messageId) {
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0);
MqttPublishVariableHeader varHeader = new MqttPublishVariableHeader(topic, messageId);
return new MqttPublishMessage(fixedHeader, varHeader, message);
@@ -46,38 +46,47 @@ class MessagesPublisher {
final MqttQoS publishingQos = pubMsg.getQos();
final ByteBuf origMessage = pubMsg.getMessage();
//if QoS 1 or 2 store the message
//TODO perhaps this block is not needed.
// if QoS 1 or 2 store the message
// TODO perhaps this block is not needed.
MessageGUID guid = null;
if (publishingQos != MqttQoS.AT_MOST_ONCE) {
guid = m_messagesStore.storePublishForFuture(pubMsg);
}
for (final Subscription sub : topicMatchingSubscriptions) {
MqttQoS qos = lowerQosToTheSubscriptionDesired(sub, publishingQos);
ClientSession targetSession = m_sessionsStore.sessionForClient(sub.getClientId());
boolean targetIsActive = this.connectionDescriptors.isConnected(sub.getClientId());
//we need to retain because duplicate only copy r/w indexes and don't retain() causing refCnt = 0
// we need to retain because duplicate only copy r/w indexes and don't retain() causing
// refCnt = 0
ByteBuf message = origMessage.retainedDuplicate();
if (targetIsActive) {
LOG.debug("Sending PUBLISH message to active subscriber. MessageId={}, CId={}, topicFilter={}, qos={}",
pubMsg.getMessageID(), sub.getClientId(), sub.getTopicFilter(), qos);
LOG.debug(
"Sending PUBLISH message to active subscriber. MessageId={}, CId={}, topicFilter={}, qos={}",
pubMsg.getMessageID(),
sub.getClientId(),
sub.getTopicFilter(),
qos);
MqttPublishMessage publishMsg = notRetainedPublish(topic, qos, message);
if (qos != MqttQoS.AT_MOST_ONCE) {
//QoS 1 or 2
// QoS 1 or 2
int messageId = targetSession.nextPacketId();
targetSession.inFlightAckWaiting(guid, messageId);
//set the PacketIdentifier only for QoS > 0
// set the PacketIdentifier only for QoS > 0
publishMsg = notRetainedPublishWithMessageId(topic, qos, message, messageId);
}
this.messageSender.sendPublish(targetSession, publishMsg);
} else {
if (!targetSession.isCleanSession()) {
LOG.debug("Storing pending PUBLISH inactive message. MessageId={}, CId={}, topicFilter={}, qos={}",
pubMsg.getMessageID(), sub.getClientId(), sub.getTopicFilter(), qos);
//store the message in targetSession queue to deliver
LOG.debug(
"Storing pending PUBLISH inactive message. MessageId={}, CId={}, topicFilter={}, qos={}",
pubMsg.getMessageID(),
sub.getClientId(),
sub.getTopicFilter(),
qos);
// store the message in targetSession queue to deliver
targetSession.enqueue(pubMsg);
}
}
@@ -1,3 +1,4 @@
package io.moquette.spi.impl;
import io.moquette.server.ConnectionDescriptorStore;
@@ -6,7 +7,6 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
@@ -21,27 +21,41 @@ class PersistentQueueMessageSender {
void sendPublish(ClientSession clientsession, MqttPublishMessage pubMessage) {
String clientId = clientsession.clientID;
final int messageId = pubMessage.variableHeader().messageId();
final String topicName = pubMessage.variableHeader().topicName();
final int messageId = pubMessage.variableHeader().messageId();
final String topicName = pubMessage.variableHeader().topicName();
if (LOG.isDebugEnabled()) {
LOG.debug("Sending PUBLISH message. MessageId={}, CId={}, topic={}, qos={}, payload={}", messageId,
clientId, topicName, DebugUtils.payload2Str(pubMessage.payload()));
LOG.debug(
"Sending PUBLISH message. MessageId={}, CId={}, topic={}, qos={}, payload={}",
messageId,
clientId,
topicName,
DebugUtils.payload2Str(pubMessage.payload()));
} else {
LOG.info("Sending PUBLISH message. MessageId={}, CId={}, topic={}", messageId, clientId, topicName);
LOG.info("Sending PUBLISH message. MessageId={}, CId={}, topic={}", messageId, clientId, topicName);
}
boolean messageDelivered = connectionDescriptorStore.sendMessage(pubMessage, messageId, clientId);
boolean messageDelivered = connectionDescriptorStore.sendMessage(pubMessage, messageId, clientId);
MqttQoS qos = pubMessage.fixedHeader().qosLevel();
if (!messageDelivered && qos != AT_MOST_ONCE && !clientsession.isCleanSession()) {
LOG.warn(
"The PUBLISH message could not be delivered. It will be stored. MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
messageId, clientId, topicName, qos, false);
clientsession.enqueue(asStoredMessage(pubMessage));
} else {
LOG.warn(
"The PUBLISH message could not be delivered. It will be discarded. MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
messageId, clientId, topicName, qos, true);
}
MqttQoS qos = pubMessage.fixedHeader().qosLevel();
if (!messageDelivered && qos != AT_MOST_ONCE && !clientsession.isCleanSession()) {
LOG.warn(
"The PUBLISH message could not be delivered. It will be stored. "
+ "MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
messageId,
clientId,
topicName,
qos,
false);
clientsession.enqueue(asStoredMessage(pubMessage));
} else {
LOG.warn(
"The PUBLISH message could not be delivered. It will be discarded. "
+ "MessageId = {}, CId={}, topic = {}, qos = {}, cleanSession = {}.",
messageId,
clientId,
topicName,
qos,
true);
}
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl;
import io.moquette.interception.InterceptHandler;
@@ -34,7 +35,6 @@ import io.netty.handler.codec.mqtt.*;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
@@ -43,7 +43,6 @@ import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
import static io.moquette.spi.impl.Utils.messageId;
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
@@ -52,8 +51,8 @@ import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
import static io.netty.handler.codec.mqtt.MqttQoS.*;
/**
* Class responsible to handle the logic of MQTT protocol it's the director of
* the protocol execution.
* Class responsible to handle the logic of MQTT protocol it's the director of the protocol
* execution.
*
* Used by the front facing class ProtocolProcessorBootstrapper.
*
@@ -62,6 +61,7 @@ import static io.netty.handler.codec.mqtt.MqttQoS.*;
public class ProtocolProcessor {
static final class WillMessage {
private final String topic;
private final ByteBuffer payload;
private final boolean retained;
@@ -89,7 +89,6 @@ public class ProtocolProcessor {
public MqttQoS getQos() {
return qos;
}
}
private enum SubscriptionState {
@@ -97,6 +96,7 @@ public class ProtocolProcessor {
}
private class RunningSubscription {
final String clientID;
final long packetId;
@@ -107,13 +107,15 @@ public class ProtocolProcessor {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
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);
}
@Override
@@ -145,52 +147,80 @@ public class ProtocolProcessor {
private MessagesPublisher messagesPublisher;
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<>();
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);
ProtocolProcessor() {
}
public void init(SubscriptionsStore subscriptions, IMessagesStore storageService,
ISessionsStore sessionsStore,
IAuthenticator authenticator,
boolean allowAnonymous,
boolean allowZeroByteClientId, IAuthorizator authorizator, BrokerInterceptor interceptor) {
init(subscriptions,storageService,sessionsStore,authenticator,allowAnonymous, allowZeroByteClientId, authorizator,interceptor,null);
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, ISessionsStore sessionsStore,
IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId,
IAuthorizator authorizator, BrokerInterceptor interceptor) {
init(
subscriptions,
storageService,
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);
}
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
* clients subscriptions.
* @param storageService 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 authenticator the authenticator used in connect messages.
* @param allowAnonymous 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
* @param subscriptions
* the subscription store where are stored all the existing clients subscriptions.
* @param storageService
* 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 authenticator
* the authenticator used in connect messages.
* @param allowAnonymous
* 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,
ISessionsStore sessionsStore,
IAuthenticator authenticator,
boolean allowAnonymous,
boolean allowZeroByteClientId, IAuthorizator authorizator, BrokerInterceptor interceptor, String serverPort) {
void init(ConnectionDescriptorStore connectionDescriptors, SubscriptionsStore subscriptions,
IMessagesStore storageService, ISessionsStore sessionsStore, IAuthenticator authenticator,
boolean allowAnonymous, boolean allowZeroByteClientId, IAuthorizator authorizator,
BrokerInterceptor interceptor, String serverPort) {
LOG.info("Initializing MQTT protocol processor...");
this.connectionDescriptors = connectionDescriptors;
this.subscriptionInCourse = new ConcurrentHashMap<>();
@@ -199,9 +229,9 @@ public class ProtocolProcessor {
this.allowAnonymous = allowAnonymous;
this.allowZeroByteClientId = allowZeroByteClientId;
m_authorizator = authorizator;
if (LOG.isDebugEnabled()) {
LOG.debug("Initial subscriptions tree = {}.", subscriptions.dumpTree());
}
if (LOG.isDebugEnabled()) {
LOG.debug("Initial subscriptions tree = {}.", subscriptions.dumpTree());
}
m_authenticator = authenticator;
m_messagesStore = storageService;
m_sessionsStore = sessionsStore;
@@ -209,15 +239,36 @@ public class ProtocolProcessor {
LOG.info("Initializing messages publisher...");
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...");
this.qos0PublishHandler = new Qos0PublishHandler(m_authorizator, subscriptions, m_messagesStore,
m_interceptor, 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);
this.qos0PublishHandler = new Qos0PublishHandler(
m_authorizator,
subscriptions,
m_messagesStore,
m_interceptor,
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...");
this.internalRepublisher = new InternalRepublisher(messageSender);
@@ -228,8 +279,8 @@ public class ProtocolProcessor {
String clientId = payload.clientIdentifier();
LOG.info("Processing CONNECT message. CId = {}, username = {}", clientId, payload.userName());
if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel() &&
msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()) {
if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel()
&& msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()) {
MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION);
LOG.error("The MQTT protocol version is not valid. CId = {}", clientId);
@@ -250,8 +301,10 @@ public class ProtocolProcessor {
// Generating client id.
clientId = UUID.randomUUID().toString().replace("-", "");
LOG.info("The client has connected with a server generated identifier. CId = {}, username = {}",
clientId, payload.userName());
LOG.info(
"The client has connected with a server generated identifier. CId = {}, username = {}",
clientId,
payload.userName());
}
if (!login(channel, msg, clientId)) {
@@ -259,11 +312,13 @@ public class ProtocolProcessor {
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);
if (existing != null) {
LOG.info("The client ID is being used in an existing connection. It will be closed. CId = {}",
clientId);
LOG.info("The client ID is being used in an existing connection. It will be closed. CId = {}", clientId);
existing.abort();
return;
}
@@ -289,7 +344,8 @@ public class ProtocolProcessor {
channel.close();
return;
}
final boolean success = descriptor.assignState(ConnectionState.MESSAGES_REPUBLISHED, ConnectionState.ESTABLISHED);
final boolean success = descriptor
.assignState(ConnectionState.MESSAGES_REPUBLISHED, ConnectionState.ESTABLISHED);
if (!success) {
channel.close();
}
@@ -306,32 +362,42 @@ public class ProtocolProcessor {
}
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);
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
}
private boolean login(Channel channel, MqttConnectMessage msg, final String clientId) {
//handle user authentication
// handle user authentication
if (msg.variableHeader().hasUserName()) {
byte[] pwd = null;
if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password().getBytes();
} else if (!this.allowAnonymous) {
LOG.error("The client didn't supply any password and MQTT anonymous mode is disabled. CId = {}.",
LOG.error(
"The client didn't supply any password and MQTT anonymous mode is disabled. CId = {}.",
clientId);
failedCredentials(channel);
return false;
}
if (!m_authenticator.checkValid(clientId, msg.payload().userName(), pwd)) {
LOG.error("The authenticator has rejected the MQTT credentials. CId = {}, username = {}, password = {}.",
clientId, msg.payload().userName(), pwd);
LOG.error(
"The authenticator has rejected the MQTT credentials. CId = {}, username = {}, password = {}.",
clientId,
msg.payload().userName(),
pwd);
failedCredentials(channel);
return false;
}
NettyUtils.userName(channel, msg.payload().userName());
} 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);
failedCredentials(channel);
return false;
@@ -340,7 +406,7 @@ public class ProtocolProcessor {
}
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);
if (!success) {
return false;
@@ -356,7 +422,7 @@ public class ProtocolProcessor {
}
if (isSessionAlreadyStored) {
LOG.info("Cleaning session. CId = {}.", clientId);
LOG.info("Cleaning session. CId = {}.", clientId);
clientSession.cleanSession(msg.variableHeader().isCleanSession());
}
descriptor.writeAndFlush(okResp);
@@ -368,35 +434,48 @@ public class ProtocolProcessor {
int keepAlive = msg.variableHeader().keepAliveTimeSeconds();
LOG.info("Configuring connection. CId = {}.", clientId);
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());
//used to track the client in the subscription and publishing phases.
//session.attr(NettyUtils.ATTR_KEY_CLIENTID).set(msg.getClientID());
// used to track the client in the subscription and publishing phases.
// session.attr(NettyUtils.ATTR_KEY_CLIENTID).set(msg.getClientID());
NettyUtils.clientID(channel, clientId);
int idleTime = Math.round(keepAlive * 1.5f);
setIdleTime(channel.pipeline(), idleTime);
LOG.info("The connection has been configured. CId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.",
clientId, keepAlive, msg.variableHeader().isCleanSession(), idleTime);
LOG.info(
"The connection has been configured. CId = {}, keepAlive = {}, cleanSession = {}, idleTime = {}.",
clientId,
keepAlive,
msg.variableHeader().isCleanSession(),
idleTime);
}
private void storeWillMessage(MqttConnectMessage msg, final String clientId) {
//Handle will flag
// Handle will flag
if (msg.variableHeader().isWillFlag()) {
MqttQoS willQos = MqttQoS.valueOf(msg.variableHeader().willQos());
LOG.info("Configuring MQTT last will and testament. CId = {}, willQos = {}, willTopic = {}, willRetain = {}.",
clientId, willQos, msg.payload().willTopic(), msg.variableHeader().isWillRetain());
LOG.info(
"Configuring MQTT last will and testament. "
+ "CId = {}, willQos = {}, willTopic = {}, willRetain = {}.",
clientId,
willQos,
msg.payload().willTopic(),
msg.variableHeader().isWillRetain());
byte[] willPayload = msg.payload().willMessage().getBytes();
ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
//save the will testament in the clientID store
WillMessage will = new WillMessage(msg.payload().willTopic(), bb, msg.variableHeader().isWillRetain(), willQos);
// save the will testament in the clientID store
WillMessage will = new WillMessage(
msg.payload().willTopic(),
bb,
msg.variableHeader().isWillRetain(),
willQos);
m_willStore.put(clientId, will);
LOG.info("MQTT last will and testament has been configured. CId = {}.", clientId);
}
}
private ClientSession createOrLoadClientSession(ConnectionDescriptor descriptor, MqttConnectMessage msg,
String clientId) {
String clientId) {
final boolean success = descriptor.assignState(ConnectionState.SENDACK, ConnectionState.SESSION_CREATED);
if (!success) {
return null;
@@ -408,23 +487,24 @@ public class ProtocolProcessor {
clientSession = m_sessionsStore.createNewSession(clientId, msg.variableHeader().isCleanSession());
}
if (msg.variableHeader().isCleanSession()) {
LOG.info("Cleaning session. CId = {}.", clientId);
LOG.info("Cleaning session. CId = {}.", clientId);
clientSession.cleanSession();
}
return 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) {
return false;
}
if (!msg.variableHeader().isCleanSession()) {
//force the republish of stored QoS1 and QoS2
// force the republish of stored QoS1 and QoS2
republishStoredInSession(clientSession);
}
int flushIntervalMs = 500/*(keepAlive * 1000) / 2*/;
int flushIntervalMs = 500/* (keepAlive * 1000) / 2 */;
descriptor.setupAutoFlusher(flushIntervalMs);
return true;
}
@@ -443,7 +523,7 @@ public class ProtocolProcessor {
/**
* Republish QoS1 and QoS2 messages stored into the session for the clientID.
* */
*/
private void republishStoredInSession(ClientSession clientSession) {
LOG.info("Republishing stored publish events. CId = {}.", clientSession.clientID);
BlockingQueue<StoredMessage> publishedEvents = clientSession.queue();
@@ -470,19 +550,24 @@ public class ProtocolProcessor {
}
public static IMessagesStore.StoredMessage asStoredMessage(MqttPublishMessage msg) {
//TODO ugly, too much array copy
// TODO ugly, too much array copy
ByteBuf payload = msg.payload();
byte[] payloadContent = readBytesAndRewind(payload);
IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(payloadContent,
msg.fixedHeader().qosLevel(), msg.variableHeader().topicName());
IMessagesStore.StoredMessage stored = new IMessagesStore.StoredMessage(
payloadContent,
msg.fixedHeader().qosLevel(),
msg.variableHeader().topicName());
stored.setRetained(msg.fixedHeader().isRetain());
stored.setMessageID(msg.variableHeader().messageId());
return stored;
}
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());
return pub;
}
@@ -490,8 +575,12 @@ public class ProtocolProcessor {
public void processPublish(Channel channel, MqttPublishMessage msg) {
final MqttQoS qos = msg.fixedHeader().qosLevel();
final String clientId = NettyUtils.clientID(channel);
LOG.info("Processing PUBLISH message. CId = {}, topic = {}, messageId = {}, qos = {}.",
clientId, msg.variableHeader().topicName(), msg.variableHeader().messageId(), qos);
LOG.info(
"Processing PUBLISH message. CId = {}, topic = {}, messageId = {}, qos = {}.",
clientId,
msg.variableHeader().topicName(),
msg.variableHeader().messageId(),
qos);
switch (qos) {
case AT_MOST_ONCE:
this.qos0PublishHandler.receivedPublishQos0(channel, msg);
@@ -503,21 +592,22 @@ public class ProtocolProcessor {
this.qos2PublishHandler.receivedPublishQos2(channel, msg);
break;
default:
break;
break;
}
}
/**
* Intended usage is only for embedded versions of the broker, where the hosting application want to use the
* broker to send a publish message.
* Inspired by {@link #processPublish} but with some changes to avoid security check, and the handshake phases
* for Qos1 and Qos2.
* It also doesn't notifyTopicPublished because using internally the owner should already know where
* it's publishing.
* Intended usage is only for embedded versions of the broker, where the hosting application
* want to use the broker to send a publish message. Inspired by {@link #processPublish} but
* with some changes to avoid security check, and the handshake phases for Qos1 and Qos2. It
* also doesn't notifyTopicPublished because using internally the owner should already know
* where it's publishing.
*
* @param msg the message to publish.
* @param clientId the clientID
* */
* @param msg
* the message to publish.
* @param clientId
* the clientID
*/
public void internalPublish(MqttPublishMessage msg, final String clientId) {
final MqttQoS qos = msg.fixedHeader().qosLevel();
final String topic = msg.variableHeader().topicName();
@@ -531,7 +621,7 @@ public class ProtocolProcessor {
toStoreMsg.setClientID(clientId);
}
toStoreMsg.setMessageID(1);
if (qos == EXACTLY_ONCE) { //QoS2
if (qos == EXACTLY_ONCE) { // QoS2
guid = m_messagesStore.storePublishForFuture(toStoreMsg);
}
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
@@ -541,12 +631,12 @@ public class ProtocolProcessor {
return;
}
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);
return;
}
if (guid == null) {
//before wasn't stored
// before wasn't stored
guid = m_messagesStore.storePublishForFuture(toStoreMsg);
}
m_messagesStore.storeRetained(topic, guid);
@@ -556,8 +646,8 @@ public class ProtocolProcessor {
* Specialized version to publish will testament message.
*/
private void forwardPublishWill(WillMessage will, String clientID) {
//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
// it has just to publish the message downstream to the subscribers
// NB it's a will publish, it needs a PacketIdentifier for this conn, default to 1
Integer messageId = null;
if (will.getQos() != AT_MOST_ONCE) {
messageId = m_sessionsStore.nextPacketID(clientID);
@@ -569,7 +659,7 @@ public class ProtocolProcessor {
String topic = tobeStored.getTopic();
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);
}
@@ -581,11 +671,14 @@ public class ProtocolProcessor {
}
/**
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored message and publish
* to all interested subscribers.
* @param channel the channel of the incoming message.
* @param msg the decoded pubrel message.
* */
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored
* message and publish to all interested subscribers.
*
* @param channel
* the channel of the incoming message.
* @param msg
* the decoded pubrel message.
*/
public void processPubRel(Channel channel, MqttMessage msg) {
this.qos2PublishHandler.processPubRel(channel, msg);
}
@@ -593,10 +686,10 @@ public class ProtocolProcessor {
public void processPubRec(Channel channel, MqttMessage msg) {
String clientID = NettyUtils.clientID(channel);
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
//remove from the inflight and move to the QoS2 second phase queue
// remove from the inflight and move to the QoS2 second phase queue
int messageID = messageId(msg);
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);
MqttFixedHeader pubRelHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
@@ -608,7 +701,7 @@ public class ProtocolProcessor {
String clientID = NettyUtils.clientID(channel);
int messageID = messageId(msg);
LOG.debug("Processing PUBCOMP message. CId = {}, messageId = {}.", clientID, messageID);
//once received the PUBCOMP then remove the message from the temp memory
// once received the PUBCOMP then remove the message from the temp memory
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
StoredMessage inflightMsg = targetSession.secondPhaseAcknowledged(messageID);
String username = NettyUtils.userName(channel);
@@ -617,59 +710,59 @@ public class ProtocolProcessor {
}
public void processDisconnect(Channel channel) throws InterruptedException {
final String clientID = NettyUtils.clientID(channel);
LOG.info("Processing DISCONNECT message. CId = {}.", clientID);
channel.flush();
final String clientID = NettyUtils.clientID(channel);
LOG.info("Processing DISCONNECT message. CId = {}.", clientID);
channel.flush();
final ConnectionDescriptor existingDescriptor = this.connectionDescriptors.getConnection(clientID);
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();
return;
}
if (existingDescriptor.doesNotUseChannel(channel)) {
//another client saved it's descriptor, exit
LOG.warn("Another client is using the connection descriptor. Closing connection. CId = {}.",
clientID);
existingDescriptor.abort();
// another client saved it's descriptor, exit
LOG.warn("Another client is using the connection descriptor. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
return;
}
if (!removeSubscriptions(existingDescriptor, clientID)) {
LOG.warn("Unable to remove subscriptions. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
LOG.warn("Unable to remove subscriptions. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
return;
}
if (!dropStoredMessages(existingDescriptor, clientID)) {
LOG.warn("Unable to drop stored messages. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
LOG.warn("Unable to drop stored messages. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
return;
}
if (!cleanWillMessageAndNotifyInterceptor(existingDescriptor, clientID)) {
LOG.warn("Unable to drop will message. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
LOG.warn("Unable to drop will message. Closing connection. CId = {}.", clientID);
existingDescriptor.abort();
return;
}
if (!existingDescriptor.close()) {
LOG.info("The connection has been closed. CId = {}.", clientID);
LOG.info("The connection has been closed. CId = {}.", clientID);
return;
}
boolean stillPresent = this.connectionDescriptors.removeConnection(existingDescriptor);
if (!stillPresent) {
//another descriptor was inserted
LOG.warn("Another descriptor has been inserted. CId = {}.", clientID);
return;
// another descriptor was inserted
LOG.warn("Another descriptor has been inserted. CId = {}.", clientID);
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) {
final boolean success = descriptor.assignState(ConnectionState.ESTABLISHED, ConnectionState.SUBSCRIPTIONS_REMOVED);
final boolean success = descriptor
.assignState(ConnectionState.ESTABLISHED, ConnectionState.SUBSCRIPTIONS_REMOVED);
if (!success) {
return false;
}
@@ -683,7 +776,8 @@ public class ProtocolProcessor {
}
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) {
return false;
}
@@ -697,13 +791,14 @@ public class ProtocolProcessor {
}
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) {
return false;
}
LOG.info("Removing will message. ClientId = {}.", descriptor.clientID);
//cleanup the will store
// cleanup the will store
m_willStore.remove(clientID);
String username = descriptor.getUsername();
m_interceptor.notifyClientDisconnected(clientID, username);
@@ -711,10 +806,10 @@ public class ProtocolProcessor {
}
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);
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)) {
WillMessage will = m_willStore.get(clientID);
forwardPublishWill(will, clientID);
@@ -726,25 +821,31 @@ public class ProtocolProcessor {
}
/**
* Remove the clientID from topic subscription, if not previously subscribed,
* doesn't reply any error.
* @param channel the channel of the incoming message.
* @param msg the decoded unsubscribe message.
* Remove the clientID from topic subscription, if not previously subscribed, doesn't reply any
* error.
*
* @param channel
* the channel of the incoming message.
* @param msg
* the decoded unsubscribe message.
*/
public void processUnsubscribe(Channel channel, MqttUnsubscribeMessage msg) {
List<String> topics = msg.payload().topics();
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);
for (String topic : topics) {
boolean validTopic = SubscriptionsStore.validate(topic);
if (!validTopic) {
//close the connection, not valid topicFilter is a protocol violation
// close the connection, not valid topicFilter is a protocol violation
channel.close();
LOG.error("The topic filter is not valid. CId = {}, topics = {}, badTopicFilter = {}.",
clientID, topics, topic);
LOG.error(
"The topic filter is not valid. CId = {}, topics = {}, badTopicFilter = {}.",
clientID,
topics,
topic);
return;
}
@@ -755,57 +856,64 @@ public class ProtocolProcessor {
m_interceptor.notifyTopicUnsubscribed(topic, clientID, username);
}
//ack the client
// ack the client
int messageID = msg.variableHeader().messageId();
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, AT_LEAST_ONCE, false, 0);
MqttUnsubAckMessage ackMessage = new MqttUnsubAckMessage(fixedHeader, from(messageID));
LOG.info("Sending UNSUBACK message. CId = {}, topics = {}, messageId = {}.", clientID, topics, messageID);
LOG.info("Sending UNSUBACK message. CId = {}, topics = {}, messageId = {}.", clientID, topics, messageID);
channel.writeAndFlush(ackMessage);
}
public void processSubscribe(Channel channel, MqttSubscribeMessage msg) {
String clientID = NettyUtils.clientID(channel);
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);
SubscriptionState currentStatus = this.subscriptionInCourse.putIfAbsent(executionKey, SubscriptionState.VERIFIED);
SubscriptionState currentStatus = this.subscriptionInCourse
.putIfAbsent(executionKey, SubscriptionState.VERIFIED);
if (currentStatus != null) {
LOG.warn("The client sent another SUBSCRIBE message while this one was being processed. " +
"CId = {}, messageId = {}.", clientID, messageID);
LOG.warn(
"The client sent another SUBSCRIBE message while this one was being processed. "
+ "CId = {}, messageId = {}.",
clientID,
messageID);
return;
}
String username = NettyUtils.userName(channel);
List<MqttTopicSubscription> ackTopics = doVerify(clientID, username, msg);
MqttSubAckMessage ackMessage = doAckMessageFromValidateFilters(ackTopics, messageID);
if (!this.subscriptionInCourse.replace(executionKey, SubscriptionState.VERIFIED, SubscriptionState.STORED)) {
LOG.warn("The client sent another SUBSCRIBE message while the topic filters were being verified. " +
"CId = {}, messageId = {}.", clientID, messageID);
LOG.warn(
"The client sent another SUBSCRIBE message while the topic filters were being verified. "
+ "CId = {}, messageId = {}.",
clientID,
messageID);
return;
}
LOG.info("Creating and storing subscriptions CId={}, messageId={}, topics={}", clientID, messageID, ackTopics);
LOG.info("Creating and storing subscriptions CId={}, messageId={}, topics={}", clientID, messageID, ackTopics);
List<Subscription> newSubscriptions = doStoreSubscription(ackTopics, clientID);
//save session, persist subscriptions from session
// save session, persist subscriptions from session
for (Subscription subscription : newSubscriptions) {
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);
//fire the persisted messages in session
// fire the persisted messages in session
for (Subscription subscription : newSubscriptions) {
publishRetainedMessagesInSession(subscription, username);
}
boolean success = this.subscriptionInCourse.remove(executionKey, SubscriptionState.STORED);
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 +922,7 @@ public class ProtocolProcessor {
List<Subscription> newSubscriptions = new ArrayList<>();
for (MqttTopicSubscription req : ackTopics) {
//TODO this is SUPER UGLY
// TODO this is SUPER UGLY
if (req.qualityOfService() == FAILURE) {
continue;
}
@@ -826,11 +934,14 @@ public class ProtocolProcessor {
}
/**
* @param clientID the clientID
* @param username the username
* @param msg the subscribe message to verify
* @param clientID
* the clientID
* @param username
* the username
* @param msg
* the subscribe message to verify
* @return the list of verified topics for the given subscribe message.
* */
*/
private List<MqttTopicSubscription> doVerify(String clientID, String username, MqttSubscribeMessage msg) {
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
List<MqttTopicSubscription> ackTopics = new ArrayList<>();
@@ -838,20 +949,34 @@ public class ProtocolProcessor {
final int messageId = messageId(msg);
for (MqttTopicSubscription req : msg.payload().topicSubscriptions()) {
if (!m_authorizator.canRead(req.topicName(), username, clientSession.clientID)) {
//send SUBACK with 0x80, the user hasn't credentials to read the topic
LOG.error("The client does not have read permissions on the topic. CId = {}, username = {}, messageId = {}, topic = {}.",
clientID, username, messageId, req.topicName());
// 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 = {}.",
clientID,
username,
messageId,
req.topicName());
ackTopics.add(new MqttTopicSubscription(req.topicName(), FAILURE));
} else {
MqttQoS qos;
if (SubscriptionsStore.validate(req.topicName())) {
LOG.info("The client will be subscribed to the topic. CId = {}, username = {}, messageId = {}, topic = {}.",
clientID, username, messageId, req.topicName());
LOG.info(
"The client will be subscribed to the topic. "
+ "CId = {}, username = {}, messageId = {}, topic = {}.",
clientID,
username,
messageId,
req.topicName());
qos = req.qualityOfService();
} else {
LOG.error("The topic filter is not valid. CId = {}, username = {}, messageId = {}, topic = {}.",
clientID, username, messageId, req.topicName());
qos = FAILURE;
LOG.error(
"The topic filter is not valid. CId = {}, username = {}, messageId = {}, topic = {}.",
clientID,
username,
messageId,
req.topicName());
qos = FAILURE;
}
ackTopics.add(new MqttTopicSubscription(req.topicName(), qos));
}
@@ -861,7 +986,7 @@ public class ProtocolProcessor {
/**
* Create the SUBACK response from a list of topicFilters
* */
*/
private MqttSubAckMessage doAckMessageFromValidateFilters(List<MqttTopicSubscription> topicFilters, int messageId) {
List<Integer> grantedQoSLevels = new ArrayList<>();
for (MqttTopicSubscription req : topicFilters) {
@@ -874,12 +999,15 @@ public class ProtocolProcessor {
}
private void publishRetainedMessagesInSession(final Subscription newSubscription, String username) {
LOG.info("Retrieving retained messages. CId = {}, topics = {}.", newSubscription.getClientId(),
newSubscription.getTopicFilter());
LOG.info(
"Retrieving retained messages. CId = {}, topics = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter());
//scans retained messages to be published to the new subscription
//TODO this is ugly, it does a linear scan on potential big dataset
// scans retained messages to be published to the new subscription
// TODO this is ugly, it does a linear scan on potential big dataset
Collection<IMessagesStore.StoredMessage> messages = m_messagesStore.searchMatching(new IMatchingCondition() {
@Override
public boolean match(String key) {
return SubscriptionsStore.matchTopics(key, newSubscription.getTopicFilter());
@@ -887,13 +1015,16 @@ public class ProtocolProcessor {
});
if (!messages.isEmpty()) {
LOG.info("Publishing retained messages. CId = {}, topics = {}, messagesNo = {}.",
newSubscription.getClientId(), newSubscription.getTopicFilter(), messages.size());
LOG.info(
"Publishing retained messages. CId = {}, topics = {}, messagesNo = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter(),
messages.size());
}
ClientSession targetSession = m_sessionsStore.sessionForClient(newSubscription.getClientId());
this.internalRepublisher.publishRetained(targetSession, messages);
//notify the Observables
// notify the Observables
m_interceptor.notifyTopicSubscribed(newSubscription, username);
}
@@ -901,14 +1032,19 @@ public class ProtocolProcessor {
String clientID = NettyUtils.clientID(channel);
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
boolean emptyQueue = false;
while (channel.isWritable() && !emptyQueue) {
while (channel.isWritable() && !emptyQueue) {
StoredMessage msg = clientSession.queue().poll();
if (msg == null) {
emptyQueue = true;
} 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;
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);
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl;
import io.moquette.BrokerConstants;
@@ -35,7 +36,6 @@ import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
@@ -54,13 +54,13 @@ public class ProtocolProcessorBootstrapper {
private SubscriptionsStore subscriptions;
private MapDBPersistentStore m_mapStorage;
private ISessionsStore m_sessionsStore;
private BrokerInterceptor m_interceptor;
private final ProtocolProcessor m_processor = new ProtocolProcessor();
private ConnectionDescriptorStore connectionDescriptors;
public ProtocolProcessorBootstrapper() {
@@ -68,19 +68,25 @@ public class ProtocolProcessorBootstrapper {
/**
* Initialize the processing part of the broker.
* @param props the properties carrier where some props like port end host could be loaded.
* For the full list check of configurable properties check moquette.conf file.
* @param embeddedObservers a list of callbacks to be notified of certain events inside the broker.
* Could be empty list of null.
* @param authenticator an implementation of the authenticator to be used, if null load that specified in config
* and fallback on the default one (permit all).
* @param authorizator an implementation of the authorizator to be used, if null load that specified in config
* and fallback on the default one (permit all).
* @param server the serber to init.
*
* @param props
* the properties carrier where some props like port end host could be loaded. For
* the full list check of configurable properties check moquette.conf file.
* @param embeddedObservers
* a list of callbacks to be notified of certain events inside the broker. Could be
* empty list of null.
* @param authenticator
* an implementation of the authenticator to be used, if null load that specified in
* config and fallback on the default one (permit all).
* @param authorizator
* an implementation of the authorizator to be used, if null load that specified in
* config and fallback on the default one (permit all).
* @param server
* the serber to init.
* @return the processor created for the broker.
* */
*/
public ProtocolProcessor init(IConfig props, List<? extends InterceptHandler> embeddedObservers,
IAuthenticator authenticator, IAuthorizator authorizator, Server server) {
IAuthenticator authenticator, IAuthorizator authorizator, Server server) {
subscriptions = new SubscriptionsStore();
LOG.info("Initializing messages and sessions stores...");
@@ -94,10 +100,10 @@ public class ProtocolProcessorBootstrapper {
List<InterceptHandler> observers = new ArrayList<>(embeddedObservers);
String interceptorClassName = props.getProperty(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME);
if (interceptorClassName != null && !interceptorClassName.isEmpty()) {
InterceptHandler handler = loadClass(interceptorClassName, InterceptHandler.class, Server.class, server);
if (handler != null) {
observers.add(handler);
}
InterceptHandler handler = loadClass(interceptorClassName, InterceptHandler.class, Server.class, server);
if (handler != null) {
observers.add(handler);
}
}
m_interceptor = new BrokerInterceptor(props, observers);
@@ -136,82 +142,121 @@ public class ProtocolProcessorBootstrapper {
LOG.info("Parsing ACL file. Path = {}.", aclFilePath);
authorizator = ACLFileParser.parse(resourceLoader.loadResource(aclFilePath));
} catch (ParseException pex) {
LOG.error("Unable to parse ACL file. Path = {}, cause = {}, errorMessage = {}.", aclFilePath,
pex.getCause(), pex.getMessage());
LOG.error(
"Unable to parse ACL file. Path = {}, cause = {}, errorMessage = {}.",
aclFilePath,
pex.getCause(),
pex.getMessage());
}
} else {
authorizator = new PermitAllAuthorizator();
}
LOG.info("An {} authorizator instance will be used.", authorizator.getClass().getName());
}
LOG.info("Initializing connection descriptor store...");
connectionDescriptors = new ConnectionDescriptorStore(m_sessionsStore);
LOG.info("Initializing MQTT protocol processor...");
boolean allowAnonymous = Boolean.parseBoolean(props.getProperty(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, "true"));
boolean allowZeroByteClientId = Boolean.parseBoolean(props.getProperty(BrokerConstants.ALLOW_ZERO_BYTE_CLIENT_ID_PROPERTY_NAME, "false"));
m_processor.init(connectionDescriptors, subscriptions, messagesStore, m_sessionsStore, authenticator, allowAnonymous, allowZeroByteClientId, authorizator, m_interceptor, props.getProperty(BrokerConstants.PORT_PROPERTY_NAME));
boolean allowAnonymous = Boolean
.parseBoolean(props.getProperty(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, "true"));
boolean allowZeroByteClientId = Boolean
.parseBoolean(props.getProperty(BrokerConstants.ALLOW_ZERO_BYTE_CLIENT_ID_PROPERTY_NAME, "false"));
m_processor.init(
connectionDescriptors,
subscriptions,
messagesStore,
m_sessionsStore,
authenticator,
allowAnonymous,
allowZeroByteClientId,
authorizator,
m_interceptor,
props.getProperty(BrokerConstants.PORT_PROPERTY_NAME));
return m_processor;
}
@SuppressWarnings("unchecked")
private <T, U> T loadClass(String className, Class<T> iface, Class<U> constructorArgClass, U props) {
T instance = null;
try {
LOG.info("Loading class. ClassName = {}, interfaceName = {}.", className, iface.getName());
Class<?> clazz = Class.forName(className);
// check if method getInstance exists
Method method = clazz.getMethod("getInstance", new Class[] {});
try {
LOG.info("Invoking getInstance() method. ClassName = {}, interfaceName = {}.", className,
iface.getName());
instance = (T) method.invoke(null, new Object[] {});
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException ex) {
LOG.error(
"Unable to invoke getInstance() method. ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className, iface.getName(), ex.getCause(), ex.getMessage());
return null;
}
} catch (NoSuchMethodException nsmex) {
try {
// check if constructor with constructor arg class parameter
// exists
LOG.info("Invoking constructor with {} argument. ClassName = {}, interfaceName = {}.",
constructorArgClass.getName(), className, iface.getName());
instance = this.getClass().getClassLoader().loadClass(className).asSubclass(iface)
.getConstructor(constructorArgClass).newInstance(props);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
LOG.warn(
"Unable to invoke constructor with {} argument. ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
constructorArgClass.getName(), className, iface.getName(), ex.getCause(), ex.getMessage());
return null;
} catch (NoSuchMethodException | InvocationTargetException e) {
try {
LOG.info("Invoking default constructor. ClassName = {}, interfaceName = {}.", className,
iface.getName());
// fallback to default constructor
instance = this.getClass().getClassLoader().loadClass(className).asSubclass(iface).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
LOG.error(
"Unable to invoke default constructor. ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className, iface.getName(), ex.getCause(), ex.getMessage());
return null;
}
}
} catch (ClassNotFoundException ex) {
LOG.error("The class does not exist. ClassName = {}, interfaceName = {}.", className, iface.getName());
return null;
} catch (SecurityException ex) {
LOG.error(
"Unable to load class due to a security violation. ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className, iface.getName(), ex.getCause(), ex.getMessage());
return null;
}
@SuppressWarnings("unchecked")
private <T, U> T loadClass(String className, Class<T> iface, Class<U> constructorArgClass, U props) {
T instance = null;
try {
LOG.info("Loading class. ClassName = {}, interfaceName = {}.", className, iface.getName());
Class<?> clazz = Class.forName(className);
return instance;
}
// check if method getInstance exists
Method method = clazz.getMethod("getInstance", new Class[]{});
try {
LOG.info(
"Invoking getInstance() method. ClassName = {}, interfaceName = {}.",
className,
iface.getName());
instance = (T) method.invoke(null, new Object[]{});
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException ex) {
LOG.error(
"Unable to invoke getInstance() method. "
+ "ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className,
iface.getName(),
ex.getCause(),
ex.getMessage());
return null;
}
} catch (NoSuchMethodException nsmex) {
try {
// check if constructor with constructor arg class parameter
// exists
LOG.info(
"Invoking constructor with {} argument. ClassName = {}, interfaceName = {}.",
constructorArgClass.getName(),
className,
iface.getName());
instance = this.getClass().getClassLoader().loadClass(className).asSubclass(iface)
.getConstructor(constructorArgClass).newInstance(props);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
LOG.warn(
"Unable to invoke constructor with {} argument. "
+ "ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
constructorArgClass.getName(),
className,
iface.getName(),
ex.getCause(),
ex.getMessage());
return null;
} catch (NoSuchMethodException | InvocationTargetException e) {
try {
LOG.info(
"Invoking default constructor. ClassName = {}, interfaceName = {}.",
className,
iface.getName());
// fallback to default constructor
instance = this.getClass().getClassLoader().loadClass(className).asSubclass(iface).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
LOG.error(
"Unable to invoke default constructor. "
+ "ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className,
iface.getName(),
ex.getCause(),
ex.getMessage());
return null;
}
}
} catch (ClassNotFoundException ex) {
LOG.error("The class does not exist. ClassName = {}, interfaceName = {}.", className, iface.getName());
return null;
} catch (SecurityException ex) {
LOG.error(
"Unable to load class due to a security violation. "
+ "ClassName = {}, interfaceName = {}, cause = {}, errorMessage = {}.",
className,
iface.getName(),
ex.getCause(),
ex.getMessage());
return null;
}
return instance;
}
public List<Subscription> getSubscriptions() {
return m_sessionsStore.getSubscriptions();
@@ -1,3 +1,4 @@
package io.moquette.spi.impl;
import io.moquette.server.netty.NettyUtils;
@@ -9,9 +10,7 @@ import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
class Qos0PublishHandler extends QosPublishHandler {
@@ -24,9 +23,8 @@ class Qos0PublishHandler extends QosPublishHandler {
private final MessagesPublisher publisher;
public Qos0PublishHandler(IAuthorizator authorizator, SubscriptionsStore subscriptions,
IMessagesStore messagesStore, BrokerInterceptor interceptor,
MessagesPublisher messagesPublisher) {
super(authorizator);
IMessagesStore messagesStore, BrokerInterceptor interceptor, MessagesPublisher messagesPublisher) {
super(authorizator);
this.subscriptions = subscriptions;
this.m_messagesStore = messagesStore;
this.m_interceptor = interceptor;
@@ -34,33 +32,41 @@ class Qos0PublishHandler extends QosPublishHandler {
}
void receivedPublishQos0(Channel channel, MqttPublishMessage msg) {
//verify if topic can be write
// verify if topic can be write
final String topic = msg.variableHeader().topicName();
if (checkWriteOnTopic(topic, channel)) {
return;
}
//route message to subscribers
// route message to subscribers
IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg);
String clientID = NettyUtils.clientID(channel);
toStoreMsg.setClientID(clientID);
final int messageID = msg.variableHeader().messageId();
if (LOG.isTraceEnabled()) {
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
topic, messageID);
}
if (LOG.isTraceEnabled()) {
LOG.trace(
"Sending publish message to subscribers. "
+ "MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID,
topic,
messageID,
DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info(
"Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
clientID,
topic,
messageID);
}
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
this.publisher.publish2Subscribers(toStoreMsg, topicMatchingSubscriptions);
if (msg.fixedHeader().isRetain()) {
//QoS == 0 && retain => clean old retained
// QoS == 0 && retain => clean old retained
m_messagesStore.cleanRetained(topic);
}
@@ -1,3 +1,4 @@
package io.moquette.spi.impl;
import io.moquette.server.ConnectionDescriptorStore;
@@ -14,14 +15,13 @@ import io.netty.handler.codec.mqtt.MqttPubAckMessage;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
class Qos1PublishHandler extends QosPublishHandler {
private static final Logger LOG = LoggerFactory.getLogger(Qos1PublishHandler.class);
private final SubscriptionsStore subscriptions;
@@ -31,10 +31,9 @@ class Qos1PublishHandler extends QosPublishHandler {
private final MessagesPublisher publisher;
public Qos1PublishHandler(IAuthorizator authorizator, SubscriptionsStore subscriptions,
IMessagesStore messagesStore, BrokerInterceptor interceptor,
ConnectionDescriptorStore connectionDescriptors,
String brokerPort, MessagesPublisher messagesPublisher) {
super(authorizator);
IMessagesStore messagesStore, BrokerInterceptor interceptor,
ConnectionDescriptorStore connectionDescriptors, String brokerPort, MessagesPublisher messagesPublisher) {
super(authorizator);
this.subscriptions = subscriptions;
this.m_messagesStore = messagesStore;
this.m_interceptor = interceptor;
@@ -43,7 +42,7 @@ class Qos1PublishHandler extends QosPublishHandler {
}
void receivedPublishQos1(Channel channel, MqttPublishMessage msg) {
//verify if topic can be write
// verify if topic can be write
final String topic = msg.variableHeader().topicName();
if (checkWriteOnTopic(topic, channel)) {
return;
@@ -51,33 +50,41 @@ class Qos1PublishHandler extends QosPublishHandler {
final int messageID = msg.variableHeader().messageId();
//route message to subscribers
// route message to subscribers
IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg);
String clientID = NettyUtils.clientID(channel);
toStoreMsg.setClientID(clientID);
if (LOG.isTraceEnabled()) {
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
topic, messageID);
}
if (LOG.isTraceEnabled()) {
LOG.trace(
"Sending publish message to subscribers. "
+ "MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID,
topic,
messageID,
DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info(
"Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
clientID,
topic,
messageID);
}
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
this.publisher.publish2Subscribers(toStoreMsg, topicMatchingSubscriptions);
//send PUBACK
//TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
sendPubAck(clientID, messageID);
// }
// send PUBACK
// TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
sendPubAck(clientID, messageID);
// }
if (msg.fixedHeader().isRetain()) {
if (!msg.payload().isReadable()) {
m_messagesStore.cleanRetained(topic);
} else {
//before wasn't stored
// before wasn't stored
MessageGUID guid = m_messagesStore.storePublishForFuture(toStoreMsg);
m_messagesStore.storeRetained(topic, guid);
}
@@ -94,14 +101,20 @@ class Qos1PublishHandler extends QosPublishHandler {
try {
if (connectionDescriptors == null) {
throw new RuntimeException("Internal bad error, found connectionDescriptors to null while it should be initialized, somewhere it's overwritten!!");
throw new RuntimeException(
"Internal bad error, found connectionDescriptors to null while it should be initialized, "
+ "somewhere it's overwritten!!");
}
LOG.debug("clientIDs are {}", connectionDescriptors);
if (!connectionDescriptors.isConnected(clientId)) {
throw new RuntimeException(String.format("Can't find a ConnectionDescriptor for client %s in cache %s", clientId, connectionDescriptors));
throw new RuntimeException(
String.format(
"Can't find a ConnectionDescriptor for client %s in cache %s",
clientId,
connectionDescriptors));
}
connectionDescriptors.sendMessage(pubAckMessage, messageID, clientId);
} catch(Throwable t) {
} catch (Throwable t) {
LOG.error(null, t);
}
}
@@ -1,3 +1,4 @@
package io.moquette.spi.impl;
import io.moquette.server.ConnectionDescriptorStore;
@@ -13,15 +14,13 @@ import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static io.moquette.spi.impl.ProtocolProcessor.asStoredMessage;
import static io.moquette.spi.impl.Utils.messageId;
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
class Qos2PublishHandler extends QosPublishHandler {
class Qos2PublishHandler extends QosPublishHandler {
private static final Logger LOG = LoggerFactory.getLogger(Qos1PublishHandler.class);
@@ -33,10 +32,10 @@ class Qos2PublishHandler extends QosPublishHandler {
private final MessagesPublisher publisher;
public Qos2PublishHandler(IAuthorizator authorizator, SubscriptionsStore subscriptions,
IMessagesStore messagesStore, BrokerInterceptor interceptor,
ConnectionDescriptorStore connectionDescriptors,
ISessionsStore sessionsStore, String brokerPort, MessagesPublisher messagesPublisher) {
super(authorizator);
IMessagesStore messagesStore, BrokerInterceptor interceptor,
ConnectionDescriptorStore connectionDescriptors, ISessionsStore sessionsStore, String brokerPort,
MessagesPublisher messagesPublisher) {
super(authorizator);
this.subscriptions = subscriptions;
this.m_messagesStore = messagesStore;
this.m_interceptor = interceptor;
@@ -47,7 +46,7 @@ class Qos2PublishHandler extends QosPublishHandler {
void receivedPublishQos2(Channel channel, MqttPublishMessage msg) {
final String topic = msg.variableHeader().topicName();
//check if the topic can be wrote
// check if the topic can be wrote
if (checkWriteOnTopic(topic, channel)) {
return;
}
@@ -57,23 +56,30 @@ class Qos2PublishHandler extends QosPublishHandler {
String clientID = NettyUtils.clientID(channel);
toStoreMsg.setClientID(clientID);
if (LOG.isTraceEnabled()) {
LOG.trace("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, " +
"payload = {}, subscriptionTree = {}.",
clientID, topic, messageID, DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
clientID, topic, messageID);
}
if (LOG.isTraceEnabled()) {
LOG.trace(
"Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, "
+ "payload = {}, subscriptionTree = {}.",
clientID,
topic,
messageID,
DebugUtils.payload2Str(toStoreMsg.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info(
"Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
clientID,
topic,
messageID);
}
//QoS2
// QoS2
MessageGUID guid = m_messagesStore.storePublishForFuture(toStoreMsg);
//TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
sendPubRec(clientID, messageID);
//}
//Next the client will send us a pub rel
//NB publish to subscribers for QoS 2 happen upon PUBREL from publisher
// TODO Don't send PUBREC for Hz publish notification, if (msg.isLocal()) {
sendPubRec(clientID, messageID);
// }
// Next the client will send us a pub rel
// NB publish to subscribers for QoS 2 happen upon PUBREL from publisher
if (msg.fixedHeader().isRetain()) {
if (msg.payload().readableBytes() == 0) {
@@ -87,9 +93,9 @@ class Qos2PublishHandler extends QosPublishHandler {
}
/**
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored message and publish
* to all interested subscribers.
* */
* Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored
* message and publish to all interested subscribers.
*/
void processPubRel(Channel channel, MqttMessage msg) {
String clientID = NettyUtils.clientID(channel);
int messageID = messageId(msg);
@@ -99,14 +105,22 @@ class Qos2PublishHandler extends QosPublishHandler {
final String topic = evt.getTopic();
List<Subscription> topicMatchingSubscriptions = subscriptions.matches(topic);
if (LOG.isDebugEnabled()) {
LOG.debug("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID, topic, messageID, DebugUtils.payload2Str(evt.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info("Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.", clientID,
topic, messageID);
}
if (LOG.isDebugEnabled()) {
LOG.debug(
"Sending publish message to subscribers. "
+ "MqttClientId = {}, topic = {}, messageId = {}, payload = {}, subscriptionTree = {}.",
clientID,
topic,
messageID,
DebugUtils.payload2Str(evt.getMessage()),
subscriptions.dumpTree());
} else {
LOG.info(
"Sending publish message to subscribers. MqttClientId = {}, topic = {}, messageId = {}.",
clientID,
topic,
messageID);
}
this.publisher.publish2Subscribers(evt, topicMatchingSubscriptions);
if (evt.isRetained()) {
@@ -120,12 +134,11 @@ class Qos2PublishHandler extends QosPublishHandler {
sendPubComp(clientID, messageID);
}
private void sendPubRec(String clientID, int messageID) {
LOG.debug("Sending PUBREC message. MqttClientId = {}, messageId = {}.", clientID, messageID);
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, AT_MOST_ONCE, false, 0);
MqttMessage pubRecMessage = new MqttMessage(fixedHeader, from(messageID));
connectionDescriptors.sendMessage(pubRecMessage,messageID, clientID);
connectionDescriptors.sendMessage(pubRecMessage, messageID, clientID);
}
private void sendPubComp(String clientID, int messageID) {
@@ -1,30 +1,32 @@
package io.moquette.spi.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.security.IAuthorizator;
import io.netty.channel.Channel;
abstract class QosPublishHandler {
private static final Logger LOG = LoggerFactory.getLogger(QosPublishHandler.class);
private final IAuthorizator m_authorizator;
protected QosPublishHandler(IAuthorizator m_authorizator) {
this.m_authorizator = m_authorizator;
}
public boolean checkWriteOnTopic(String topic, Channel channel) {
String clientID = NettyUtils.clientID(channel);
String username = NettyUtils.userName(channel);
if (!m_authorizator.canWrite(topic, username, clientID)) {
LOG.error("The MQTT client is not authorized to publish on topic. MqttClientId = {}, topic = {}.", clientID,
topic);
return true;
}
return false;
}
private static final Logger LOG = LoggerFactory.getLogger(QosPublishHandler.class);
private final IAuthorizator m_authorizator;
protected QosPublishHandler(IAuthorizator m_authorizator) {
this.m_authorizator = m_authorizator;
}
public boolean checkWriteOnTopic(String topic, Channel channel) {
String clientID = NettyUtils.clientID(channel);
String username = NettyUtils.userName(channel);
if (!m_authorizator.canWrite(topic, username, clientID)) {
LOG.error(
"The MQTT client is not authorized to publish on topic. MqttClientId = {}, topic = {}.",
clientID,
topic);
return true;
}
return false;
}
}
@@ -1,9 +1,9 @@
package io.moquette.spi.impl;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
import java.util.Map;
/**
@@ -13,29 +13,32 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.text.ParseException;
/**
* Parses the acl configuration file.
* If a line starts with # it's comment.
* Blank lines are skipped. The format is "topic [read|write|readwrite] {topic name}"
* Parses the acl configuration file. If a line starts with # it's comment. Blank lines are skipped.
* The format is "topic [read|write|readwrite] {topic name}"
*
* @author andrea
* */
*/
public class ACLFileParser {
private static final Logger LOG = LoggerFactory.getLogger(ACLFileParser.class);
/**
* Parse the configuration from file.
* @param file to parse
*
* @param file
* to parse
* @return the collector of authorizations form reader passed into.
* @throws ParseException if the format is not compliant.
* @throws ParseException
* if the format is not compliant.
*/
public static AuthorizationsCollector parse(File file) throws ParseException {
if (file == null) {
@@ -43,27 +46,37 @@ public class ACLFileParser {
return AuthorizationsCollector.emptyImmutableCollector();
}
if (!file.exists()) {
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()));
LOG.warn(
String.format(
"parsing not existing file %s, so fallback on default configuration!",
file.getAbsolutePath()));
return AuthorizationsCollector.emptyImmutableCollector();
}
try {
FileReader reader = new FileReader(file);
return parse(reader);
} catch (FileNotFoundException fex) {
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()), fex);
LOG.warn(
String.format(
"parsing not existing file %s, so fallback on default configuration!",
file.getAbsolutePath()),
fex);
return AuthorizationsCollector.emptyImmutableCollector();
}
}
/**
* Parse the ACL configuration file
* @param reader to parse
*
* @param reader
* to parse
* @return the collector of authorizations form reader passed into.
* @throws ParseException if the format is not compliant.
* @throws ParseException
* if the format is not compliant.
*/
public static AuthorizationsCollector parse(Reader reader) throws ParseException {
if (reader == null) {
//just log and return default properties
// just log and return default properties
LOG.warn("parsing NULL reader, so fallback on default configuration!");
return AuthorizationsCollector.emptyImmutableCollector();
}
@@ -77,15 +90,15 @@ public class ACLFileParser {
int commentMarker = line.indexOf('#');
if (commentMarker != -1) {
if (commentMarker == 0) {
//skip its a comment
// skip its a comment
continue;
} else {
//it's a malformed comment
// it's a malformed comment
throw new ParseException(line, commentMarker);
}
} else {
if (line.isEmpty() || line.matches("^\\s*$")) {
//skip it's a black line
// skip it's a black line
continue;
}
@@ -1,3 +1,4 @@
package io.moquette.spi.impl.security;
import io.moquette.spi.security.IAuthenticator;
@@ -6,6 +7,7 @@ import io.moquette.spi.security.IAuthenticator;
* Created by andrea on 8/23/14.
*/
public class AcceptAllAuthenticator implements IAuthenticator {
public boolean checkValid(String clientId, String username, byte[] password) {
return true;
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import static io.moquette.spi.impl.security.Authorization.Permission.READWRITE;
@@ -23,12 +24,13 @@ import static io.moquette.spi.impl.security.Authorization.Permission.READWRITE;
* @author andrea
*/
public class Authorization {
protected final String topic;
protected final Permission permission;
/**
* Access rights
* */
*/
enum Permission {
READ, WRITE, READWRITE
}
@@ -48,13 +50,17 @@ public class Authorization {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Authorization that = (Authorization) o;
if (permission != that.permission) return false;
if (!topic.equals(that.topic)) return false;
if (permission != that.permission)
return false;
if (!topic.equals(that.topic))
return false;
return true;
}
@@ -13,19 +13,19 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
import io.moquette.spi.security.IAuthorizator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.*;
/**
* Used by the ACLFileParser to push all authorizations it finds.
* ACLAuthorizator uses it in read mode to check it topics matches the ACLs.
* Used by the ACLFileParser to push all authorizations it finds. ACLAuthorizator uses it in read
* mode to check it topics matches the ACLs.
*
* Not thread safe.
*
@@ -53,11 +53,12 @@ class AuthorizationsCollector implements IAuthorizator {
void parse(String line) throws ParseException {
Authorization acl = parseAuthLine(line);
if (acl == null) {
//skip it's a user
// skip it's a user
return;
}
if (m_parsingUsersSpecificSection) {
//TODO in java 8 switch to m_userAuthorizations.putIfAbsent(m_currentUser, new ArrayList());
// TODO in java 8 switch to m_userAuthorizations.putIfAbsent(m_currentUser, new
// ArrayList());
if (!m_userAuthorizations.containsKey(m_currentUser)) {
m_userAuthorizations.put(m_currentUser, new ArrayList<Authorization>());
}
@@ -93,10 +94,10 @@ class AuthorizationsCollector implements IAuthorizator {
private Authorization createAuthorization(String line, String[] tokens) throws ParseException {
if (tokens.length > 2) {
//if the tokenized lines has 3 token the second must be the permission
// if the tokenized lines has 3 token the second must be the permission
try {
Authorization.Permission permission = Authorization.Permission.valueOf(tokens[1].toUpperCase());
//bring topic with all original spacing
// bring topic with all original spacing
String topic = line.substring(line.indexOf(tokens[2]));
return new Authorization(topic, permission);
@@ -119,7 +120,7 @@ class AuthorizationsCollector implements IAuthorizator {
}
private boolean canDoOperation(String topic, Authorization.Permission permission, String username, String client) {
if (matchACL(m_globalAuthorizations, topic, permission)) {
if (matchACL(m_globalAuthorizations, topic, permission)) {
return true;
}
@@ -137,7 +138,7 @@ class AuthorizationsCollector implements IAuthorizator {
if (isNotEmpty(username)) {
if (m_userAuthorizations.containsKey(username)) {
List<Authorization> auths = m_userAuthorizations.get(username);
if (matchACL(auths, topic, permission)) {
if (matchACL(auths, topic, permission)) {
return true;
}
}
@@ -1,3 +1,4 @@
package io.moquette.spi.impl.security;
import io.moquette.BrokerConstants;
@@ -6,14 +7,12 @@ import io.moquette.spi.security.IAuthenticator;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
/**
* Load user credentials from a SQL database.
* sql driver must be provided at runtime
* Load user credentials from a SQL database. sql driver must be provided at runtime
*
* @author mackristof
*/
@@ -25,30 +24,34 @@ public class DBAuthenticator implements IAuthenticator {
private final MessageDigest messageDigest;
private final PreparedStatement preparedStatement;
public DBAuthenticator(IConfig conf){
this(conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_DRIVER, ""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_URL,""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_QUERY, ""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_DIGEST,""));
public DBAuthenticator(IConfig conf) {
this(
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_DRIVER, ""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_URL, ""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_QUERY, ""),
conf.getProperty(BrokerConstants.DB_AUTHENTICATOR_DIGEST, ""));
}
/** provide authenticator from SQL database
* @param driver : jdbc driver class like : "org.postgresql.Driver"
* @param jdbcUrl : jdbc url like : "jdbc:postgresql://host:port/dbname"
* @param sqlQuery : sql query like : "SELECT PASSWORD FROM USER WHERE LOGIN=?"
* @param digestMethod : password encoding algorithm : "MD5", "SHA-1", "SHA-256"
/**
* provide authenticator from SQL database
*
* @param driver
* : jdbc driver class like : "org.postgresql.Driver"
* @param jdbcUrl
* : jdbc url like : "jdbc:postgresql://host:port/dbname"
* @param sqlQuery
* : sql query like : "SELECT PASSWORD FROM USER WHERE LOGIN=?"
* @param digestMethod
* : password encoding algorithm : "MD5", "SHA-1", "SHA-256"
*/
public DBAuthenticator(String driver, String jdbcUrl, String sqlQuery, String digestMethod){
public DBAuthenticator(String driver, String jdbcUrl, String sqlQuery, String digestMethod) {
try {
Class.forName(driver);
final Connection connection = DriverManager.getConnection(jdbcUrl);
this.messageDigest = MessageDigest.getInstance(digestMethod);
this.messageDigest = MessageDigest.getInstance(digestMethod);
this.preparedStatement = connection.prepareStatement(sqlQuery);
} catch (ClassNotFoundException cnfe){
} catch (ClassNotFoundException cnfe) {
LOG.error(String.format("Can't find driver %s", driver), cnfe);
throw new RuntimeException(cnfe);
} catch (SQLException sqle) {
@@ -67,11 +70,11 @@ public class DBAuthenticator implements IAuthenticator {
LOG.info("username or password was null");
return false;
}
ResultSet r= null;
ResultSet r = null;
try {
this.preparedStatement.setString(1,username);
this.preparedStatement.setString(1, username);
r = this.preparedStatement.executeQuery();
if(r.next()) {
if (r.next()) {
final String foundPwq = r.getString(1);
messageDigest.update(password);
byte[] digest = messageDigest.digest();
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import io.moquette.spi.security.IAuthorizator;
@@ -21,6 +22,7 @@ import io.moquette.spi.security.IAuthorizator;
* @author andrea
*/
public class DenyAllAuthorizator implements IAuthorizator {
@Override
public boolean canWrite(String topic, String user, String client) {
return true;
@@ -30,4 +32,4 @@ public class DenyAllAuthorizator implements IAuthorizator {
public boolean canRead(String topic, String user, String client) {
return true;
}
}
}
@@ -13,19 +13,23 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import io.moquette.server.config.FileResourceLoader;
/**
* Load user credentials from a text file.
* Each line of the file is formatted as "[username]:[sha256(password)]". The username mustn't contains : char.
* Load user credentials from a text file. Each line of the file is formatted as
* "[username]:[sha256(password)]". The username mustn't contains : char.
*
* To encode your password from command line on Linux systems, you could use:
*
* <pre>
* echo -n "yourpassword" | sha256sum
* </pre>
* NB -n is important because echo append a newline by default at the of string. -n avoid this behaviour.
*
* NB -n is important because echo append a newline by default at the of string. -n avoid this
* behaviour.
*
* @author andrea
* @deprecated user {@link ResourceAuthenticator} instead
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import io.moquette.spi.security.IAuthorizator;
@@ -21,6 +22,7 @@ import io.moquette.spi.security.IAuthorizator;
* @author andrea
*/
public class PermitAllAuthorizator implements IAuthorizator {
@Override
public boolean canWrite(String topic, String user, String client) {
return true;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.security;
import io.moquette.server.config.IResourceLoader;
@@ -20,7 +21,6 @@ import io.moquette.spi.security.IAuthenticator;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
@@ -31,14 +31,17 @@ import java.util.HashMap;
import java.util.Map;
/**
* Load user credentials from a text resource.
* Each line of the file is formatted as "[username]:[sha256(password)]". The username mustn't contains : char.
* Load user credentials from a text resource. Each line of the file is formatted as
* "[username]:[sha256(password)]". The username mustn't contains : char.
*
* To encode your password from command line on Linux systems, you could use:
*
* <pre>
* echo -n "yourpassword" | sha256sum
* </pre>
* NB -n is important because echo append a newline by default at the of string. -n avoid this behaviour.
*
* NB -n is important because echo append a newline by default at the of string. -n avoid this
* behaviour.
*
* @author andrea
*/
@@ -63,24 +66,23 @@ public class ResourceAuthenticator implements IAuthenticator {
reader = resourceLoader.loadResource(resourceName);
if (reader == null) {
LOG.warn(String.format("Parsing not existing %s %s", resourceLoader.getName(), resourceName));
}
else {
} else {
parse(reader);
}
}
catch (IResourceLoader.ResourceIsDirectoryException e) {
} catch (IResourceLoader.ResourceIsDirectoryException e) {
LOG.warn(String.format("Trying to parse directory %s", resourceName));
}
catch (ParseException pex) {
LOG.warn(String.format("Format error in parsing password %s %s", resourceLoader.getName(), resourceName), pex);
} catch (ParseException pex) {
LOG.warn(
String.format("Format error in parsing password %s %s", resourceLoader.getName(), resourceName),
pex);
}
}
private void parse(Reader reader) throws ParseException {
if (reader == null) {
return;
}
BufferedReader br = new BufferedReader(reader);
String line;
try {
@@ -88,23 +90,23 @@ public class ResourceAuthenticator implements IAuthenticator {
int commentMarker = line.indexOf('#');
if (commentMarker != -1) {
if (commentMarker == 0) {
//skip its a comment
// skip its a comment
continue;
} else {
//it's a malformed comment
// it's a malformed comment
throw new ParseException(line, commentMarker);
}
} else {
if (line.isEmpty() || line.matches("^\\s*$")) {
//skip it's a black line
// skip it's a black line
continue;
}
//split till the first space
// split till the first space
int delimiterIdx = line.indexOf(':');
String username = line.substring(0, delimiterIdx).trim();
String password = line.substring(delimiterIdx + 1).trim();
m_identities.put(username, password);
}
}
@@ -126,5 +128,5 @@ public class ResourceAuthenticator implements IAuthenticator {
String encodedPasswd = DigestUtils.sha256Hex(password);
return foundPwq.equals(encodedPasswd);
}
}
@@ -13,28 +13,27 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.subscriptions;
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
import io.netty.handler.codec.mqtt.MqttQoS;
import java.io.Serializable;
/**
* Maintain the information about which Topic a certain ClientID is subscribed
* and at which QoS
*
*
* Maintain the information about which Topic a certain ClientID is subscribed and at which QoS
*
*
* @author andrea
*/
public final class Subscription implements Serializable {
private static final long serialVersionUID = -3383457629635732794L;
final MqttQoS requestedQos; //max QoS acceptable
final MqttQoS requestedQos; // max QoS acceptable
final String clientId;
final String topicFilter;
final boolean active;
public Subscription(String clientId, String topicFilter, MqttQoS requestedQos) {
this.requestedQos = requestedQos;
this.clientId = clientId;
@@ -67,14 +66,16 @@ public final class Subscription implements Serializable {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Subscription that = (Subscription) o;
if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false;
if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null)
return false;
return !(topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null);
}
@Override
@@ -86,8 +87,12 @@ public final class Subscription implements Serializable {
@Override
public String toString() {
return String.format("[filter:%s, cliID: %s, qos: %s, active: %s]", this.topicFilter, this.clientId,
this.requestedQos, this.active);
return String.format(
"[filter:%s, cliID: %s, qos: %s, active: %s]",
this.topicFilter,
this.clientId,
this.requestedQos,
this.active);
}
@Override
@@ -13,13 +13,13 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.subscriptions;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
@@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class SubscriptionsStore {
public static class NodeCouple {
final TreeNode root;
final TreeNode createdNode;
@@ -44,28 +45,34 @@ public class SubscriptionsStore {
/**
* Check if the topic filter of the subscription is well formed
* @param topicFilter the filter to validate
*
* @param topicFilter
* the filter to validate
* @return true if it's correct.
* */
*/
public static boolean validate(String topicFilter) {
try {
parseTopic(topicFilter);
return true;
} catch (ParseException pex) {
LOG.warn("The topic filter is malformed. TopicFilter = {}, cause = {}, errorMessage = {}.", topicFilter,
pex.getCause(), pex.getMessage());
return false;
}
} catch (ParseException pex) {
LOG.warn(
"The topic filter is malformed. TopicFilter = {}, cause = {}, errorMessage = {}.",
topicFilter,
pex.getCause(),
pex.getMessage());
return false;
}
}
public interface IVisitor<T> {
void visit(TreeNode node, int deep);
T getResult();
}
private class DumpTreeVisitor implements IVisitor<String> {
String s = "";
@Override
@@ -76,14 +83,15 @@ public class SubscriptionsStore {
subScriptionsStr += indentTabs + couple.toString() + "\n";
}
s += node.getToken() == null ? "" : node.getToken().toString();
s += "\n" + (node.m_subscriptions.isEmpty() ? indentTabs : "") + subScriptionsStr /*+ "\n"*/;
s += "\n" + (node.m_subscriptions.isEmpty() ? indentTabs : "")
+ subScriptionsStr /* + "\n" */;
}
private String indentTabs(int deep) {
String s = "";
for (int i=0; i < deep; i++) {
for (int i = 0; i < deep; i++) {
s += "\t";
// s += "--";
// s += "--";
}
return s;
}
@@ -93,27 +101,32 @@ public class SubscriptionsStore {
return s;
}
}
private AtomicReference<TreeNode> subscriptions = new AtomicReference<>(new TreeNode());
private static final Logger LOG = LoggerFactory.getLogger(SubscriptionsStore.class);
private volatile ISessionsStore m_sessionsStore;
/**
* Initialize the subscription tree with the list of subscriptions.
* Maintained for compatibility reasons.
* @param sessionsStore to be used as backing store from the subscription store.
* Initialize the subscription tree with the list of subscriptions. Maintained for compatibility
* reasons.
*
* @param sessionsStore
* to be used as backing store from the subscription store.
*/
public void init(ISessionsStore sessionsStore) {
LOG.info("Initializing subscriptions store...");
m_sessionsStore = sessionsStore;
List<ClientTopicCouple> subscriptions = sessionsStore.listAllSubscriptions();
//reload any subscriptions persisted
// reload any subscriptions persisted
if (LOG.isTraceEnabled()) {
LOG.trace("Reloading all stored subscriptions. SubscriptionTree = {}.", dumpTree());
}
for (ClientTopicCouple clientTopic : subscriptions) {
LOG.info("Re-subscribing client to topic. ClientId = {}, topicFilter = {}.", clientTopic.clientID, clientTopic.topicFilter);
LOG.info(
"Re-subscribing client to topic. ClientId = {}, topicFilter = {}.",
clientTopic.clientID,
clientTopic.topicFilter);
add(clientTopic);
}
if (LOG.isTraceEnabled()) {
@@ -122,30 +135,33 @@ public class SubscriptionsStore {
}
public void add(ClientTopicCouple newSubscription) {
/*
* The topic filters have already been validated at the ProtocolProcessor. We can assume they are valid.
*/
/*
* The topic filters have already been validated at the ProtocolProcessor. We can assume
* they are valid.
*/
TreeNode oldRoot;
NodeCouple couple;
do {
oldRoot = subscriptions.get();
couple = recreatePath(newSubscription.topicFilter, oldRoot);
couple.createdNode.addSubscription(newSubscription); //createdNode could be null?
//spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while(!subscriptions.compareAndSet(oldRoot, couple.root));
couple.createdNode.addSubscription(newSubscription); // createdNode could be null?
// spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while (!subscriptions.compareAndSet(oldRoot, couple.root));
LOG.debug("A subscription has been added. Root = {}, oldRoot = {}.", couple.root, oldRoot);
}
protected NodeCouple recreatePath(String topic, final TreeNode oldRoot) {
List<Token> tokens;
try {
tokens = parseTopic(topic);
} catch (ParseException ex) {
//TODO handle the parse exception
LOG.error("The topic is malformed. Topic = {}, cause = {}, errorMessage = {}.", topic, ex.getCause(),
ex.getMessage());
throw new IllegalArgumentException(ex.getMessage());
// TODO handle the parse exception
LOG.error(
"The topic is malformed. Topic = {}, cause = {}, errorMessage = {}.",
topic,
ex.getCause(),
ex.getMessage());
throw new IllegalArgumentException(ex.getMessage());
}
final TreeNode newRoot = oldRoot.copy();
@@ -154,15 +170,15 @@ public class SubscriptionsStore {
for (Token token : tokens) {
TreeNode matchingChildren;
//check if a children with the same token already exists
// check if a children with the same token already exists
if ((matchingChildren = current.childWithToken(token)) != null) {
//copy the traversed node
// copy the traversed node
current = matchingChildren.copy();
//update the child just added in the children list
// update the child just added in the children list
parent.updateChild(matchingChildren, current);
parent = current;
} else {
//create a new node for the newly inserted token
// create a new node for the newly inserted token
matchingChildren = new TreeNode();
matchingChildren.setToken(token);
current.addChild(matchingChildren);
@@ -173,9 +189,10 @@ public class SubscriptionsStore {
}
public void removeSubscription(String topic, String clientID) {
/*
* The topic filters have already been validated at the ProtocolProcessor. We can assume they are valid.
*/
/*
* The topic filters have already been validated at the ProtocolProcessor. We can assume
* they are valid.
*/
TreeNode oldRoot;
NodeCouple couple;
do {
@@ -183,14 +200,16 @@ public class SubscriptionsStore {
couple = recreatePath(topic, oldRoot);
couple.createdNode.remove(new ClientTopicCouple(clientID, topic));
//spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while(!subscriptions.compareAndSet(oldRoot, couple.root));
// spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while (!subscriptions.compareAndSet(oldRoot, couple.root));
}
/**
* Visit the topics tree to remove matching subscriptions with clientID.
* It's a mutating structure operation so create a new subscription tree (partial or total).
* @param clientID the client ID to remove.
* Visit the topics tree to remove matching subscriptions with clientID. It's a mutating
* structure operation so create a new subscription tree (partial or total).
*
* @param clientID
* the client ID to remove.
*/
public void removeForClient(String clientID) {
TreeNode oldRoot;
@@ -198,17 +217,17 @@ public class SubscriptionsStore {
do {
oldRoot = subscriptions.get();
newRoot = oldRoot.removeClientSubscriptions(clientID);
//spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while(!subscriptions.compareAndSet(oldRoot, newRoot));
// spin lock repeating till we can, swap root, if can't swap just re-do the operation
} while (!subscriptions.compareAndSet(oldRoot, newRoot));
}
/**
* Given a topic string return the clients subscriptions that matches it.
* Topic string can't contain character # and + because they are reserved to
* listeners subscriptions, and not topic publishing.
* Given a topic string return the clients subscriptions that matches it. Topic string can't
* contain character # and + because they are reserved to listeners subscriptions, and not topic
* publishing.
*
* @param topic to use fo searching matching subscriptions.
* @param topic
* to use fo searching matching subscriptions.
* @return the list of matching subscriptions, or empty if not matching.
*/
public List<Subscription> matches(String topic) {
@@ -216,8 +235,12 @@ public class SubscriptionsStore {
try {
tokens = parseTopic(topic);
} catch (ParseException ex) {
//TODO handle the parse exception
LOG.warn("The topic is malformed. Topic = {}, cause = {}, errorMessage = {}.", topic, ex.getCause(), ex.getMessage());
// TODO handle the parse exception
LOG.warn(
"The topic is malformed. Topic = {}, cause = {}, errorMessage = {}.",
topic,
ex.getCause(),
ex.getMessage());
return Collections.emptyList();
}
@@ -225,16 +248,16 @@ public class SubscriptionsStore {
List<ClientTopicCouple> matchingSubs = new ArrayList<>();
subscriptions.get().matches(tokenQueue, matchingSubs);
//remove the overlapping subscriptions, selecting ones with greatest qos
// remove the overlapping subscriptions, selecting ones with greatest qos
Map<String, Subscription> subsForClient = new HashMap<>();
for (ClientTopicCouple matchingCouple : matchingSubs) {
Subscription existingSub = subsForClient.get(matchingCouple.clientID);
Subscription sub = m_sessionsStore.getSubscription(matchingCouple);
if (sub == null) {
//if the m_sessionStore hasn't the sub because the client disconnected
// if the m_sessionStore hasn't the sub because the client disconnected
continue;
}
//update the selected subscriptions if not present or if has a greater qos
// update the selected subscriptions if not present or if has a greater qos
if (existingSub == null || existingSub.getRequestedQos().value() < sub.getRequestedQos().value()) {
subsForClient.put(matchingCouple.clientID, sub);
}
@@ -249,13 +272,13 @@ public class SubscriptionsStore {
public int size() {
return subscriptions.get().size();
}
public String dumpTree() {
DumpTreeVisitor visitor = new DumpTreeVisitor();
bfsVisit(subscriptions.get(), visitor, 0);
return visitor.getResult();
}
private void bfsVisit(TreeNode node, IVisitor<?> visitor, int deep) {
if (node == null) {
return;
@@ -265,20 +288,23 @@ public class SubscriptionsStore {
bfsVisit(child, visitor, ++deep);
}
}
/**
* Verify if the 2 topics matching respecting the rules of MQTT Appendix A
* @param msgTopic the topic to match from the message
* @param subscriptionTopic the topic filter of the subscription
*
* @param msgTopic
* the topic to match from the message
* @param subscriptionTopic
* the topic filter of the subscription
* @return true if the two topics match.
*/
//TODO reimplement with iterators or with queues
// TODO reimplement with iterators or with queues
public static boolean matchTopics(String msgTopic, String subscriptionTopic) {
try {
List<Token> msgTokens = SubscriptionsStore.parseTopic(msgTopic);
List<Token> subscriptionTokens = SubscriptionsStore.parseTopic(subscriptionTopic);
int i = 0;
for (; i< subscriptionTokens.size(); i++) {
for (; i < subscriptionTokens.size(); i++) {
Token subToken = subscriptionTokens.get(i);
if (subToken != Token.MULTI && subToken != Token.SINGLE) {
if (i >= msgTokens.size()) {
@@ -293,23 +319,27 @@ public class SubscriptionsStore {
return true;
}
if (subToken == Token.SINGLE) {
//skip a step forward
// skip a step forward
}
}
}
//if last token was a SINGLE then treat it as an empty
// if (subToken == Token.SINGLE && (i - msgTokens.size() == 1)) {
// i--;
// }
// if last token was a SINGLE then treat it as an empty
// if (subToken == Token.SINGLE && (i - msgTokens.size() == 1)) {
// i--;
// }
return i == msgTokens.size();
} catch (ParseException ex) {
LOG.error(
"The message topic, the subscription topic or both are malformed. MsgTopic = {}, subscriptionTopic = {}, cause = {}, errorMessage = {}.",
msgTopic, subscriptionTopic, ex.getCause(), ex.getMessage());
throw new IllegalStateException(ex.getMessage());
LOG.error(
"The message topic, the subscription topic or both are malformed. "
+ "MsgTopic = {}, subscriptionTopic = {}, cause = {}, errorMessage = {}.",
msgTopic,
subscriptionTopic,
ex.getCause(),
ex.getMessage());
throw new IllegalStateException(ex.getMessage());
}
}
protected static List<Token> parseTopic(String topic) throws ParseException {
List<Token> res = new ArrayList<>();
String[] splitted = topic.split("/");
@@ -317,26 +347,29 @@ public class SubscriptionsStore {
if (splitted.length == 0) {
res.add(Token.EMPTY);
}
if (topic.endsWith("/")) {
//Add a fictious space
// Add a fictious space
String[] newSplitted = new String[splitted.length + 1];
System.arraycopy(splitted, 0, newSplitted, 0, splitted.length);
System.arraycopy(splitted, 0, newSplitted, 0, splitted.length);
newSplitted[splitted.length] = "";
splitted = newSplitted;
}
for (int i = 0; i < splitted.length; i++) {
String s = splitted[i];
if (s.isEmpty()) {
// if (i != 0) {
// throw new ParseException("Bad format of topic, expetec topic name between separators", i);
// }
// if (i != 0) {
// throw new ParseException("Bad format of topic, expetec topic name between
// separators", i);
// }
res.add(Token.EMPTY);
} else if (s.equals("#")) {
//check that multi is the last symbol
// check that multi is the last symbol
if (i != splitted.length - 1) {
throw new ParseException("Bad format of topic, the multi symbol (#) has to be the last one after a separator", i);
throw new ParseException(
"Bad format of topic, the multi symbol (#) has to be the last one after a separator",
i);
}
res.add(Token.MULTI);
} else if (s.contains("#")) {
@@ -13,11 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.subscriptions;
/**
* Internal use only class.
* */
*/
public class Token {
static final Token EMPTY = new Token("");
@@ -71,4 +72,4 @@ public class Token {
public String toString() {
return name;
}
}
}
@@ -13,10 +13,10 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.impl.subscriptions;
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
@@ -24,7 +24,7 @@ class TreeNode {
Token m_token;
List<TreeNode> m_children = new ArrayList<>();
//TODO move to set of ClientIDthe set of clientIDs that has subscriptions to this topic
// TODO move to set of ClientIDthe set of clientIDs that has subscriptions to this topic
Set<ClientTopicCouple> m_subscriptions = new HashSet<>();
TreeNode() {
@@ -47,9 +47,8 @@ class TreeNode {
}
/**
* Creates a shallow copy of the current node.
* Copy the token and the children.
* */
* Creates a shallow copy of the current node. Copy the token and the children.
*/
TreeNode copy() {
final TreeNode copy = new TreeNode();
copy.m_children = new ArrayList<>(m_children);
@@ -59,8 +58,7 @@ class TreeNode {
}
/**
* Search for children that has the specified token, if not found return
* null;
* Search for children that has the specified token, if not found return null;
*/
TreeNode childWithToken(Token token) {
for (TreeNode child : m_children) {
@@ -85,14 +83,14 @@ class TreeNode {
m_subscriptions.remove(clientTopicCouple);
}
//TODO smell a query method that return the result modifing the parameter (matchingSubs)
// TODO smell a query method that return the result modifing the parameter (matchingSubs)
void matches(Queue<Token> tokens, List<ClientTopicCouple> matchingSubs) {
Token t = tokens.poll();
//check if t is null <=> tokens finished
// check if t is null <=> tokens finished
if (t == null) {
matchingSubs.addAll(m_subscriptions);
//check if it has got a MULTI child and add its subscriptions
// check if it has got a MULTI child and add its subscriptions
for (TreeNode n : m_children) {
if (n.getToken() == Token.MULTI || n.getToken() == Token.SINGLE) {
matchingSubs.addAll(n.subscriptions());
@@ -102,7 +100,7 @@ class TreeNode {
return;
}
//we are on MULTI, than add subscriptions and return
// we are on MULTI, than add subscriptions and return
if (m_token == Token.MULTI) {
matchingSubs.addAll(m_subscriptions);
return;
@@ -110,10 +108,10 @@ class TreeNode {
for (TreeNode n : m_children) {
if (n.getToken().match(t)) {
//Create a copy of token, else if navigate 2 sibling it
//consumes 2 elements on the queue instead of one
// Create a copy of token, else if navigate 2 sibling it
// consumes 2 elements on the queue instead of one
n.matches(new LinkedBlockingQueue<>(tokens), matchingSubs);
//TODO don't create a copy n.matches(tokens, matchingSubs);
// TODO don't create a copy n.matches(tokens, matchingSubs);
}
}
}
@@ -131,9 +129,9 @@ class TreeNode {
/**
* Create a copied subtree rooted on this node but purged of clientID's subscriptions.
* */
*/
TreeNode removeClientSubscriptions(String clientID) {
//collect what to delete and then delete to avoid ConcurrentModification
// collect what to delete and then delete to avoid ConcurrentModification
TreeNode newSubRoot = this.copy();
List<ClientTopicCouple> subsToRemove = new ArrayList<>();
for (ClientTopicCouple s : newSubRoot.m_subscriptions) {
@@ -146,7 +144,7 @@ class TreeNode {
newSubRoot.m_subscriptions.remove(s);
}
//go deep
// go deep
List<TreeNode> newChildren = new ArrayList<>(newSubRoot.m_children.size());
for (TreeNode child : newSubRoot.m_children) {
newChildren.add(child.removeClientSubscriptions(clientID));
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.persistence;
import io.moquette.spi.IMatchingCondition;
@@ -21,7 +22,6 @@ import io.moquette.spi.MessageGUID;
import org.mapdb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
@@ -36,12 +36,11 @@ class MapDBMessagesStore implements IMessagesStore {
private DB m_db;
//maps clientID -> guid
// maps clientID -> guid
private ConcurrentMap<String, MessageGUID> m_retainedStore;
//maps guid to message, it's message store
// maps guid to message, it's message store
private ConcurrentMap<MessageGUID, IMessagesStore.StoredMessage> m_persistentMessageStore;
MapDBMessagesStore(DB db) {
m_db = db;
}
@@ -83,10 +82,15 @@ class MapDBMessagesStore implements IMessagesStore {
assert storedMessage.getClientID() != null : "The message to be persisted must have a valid client ID";
MessageGUID guid = new MessageGUID(UUID.randomUUID().toString());
storedMessage.setGuid(guid);
LOG.debug("Storing publish event. MqttClientId = {}, messageId = {}, guid = {}, topic = {}.",
storedMessage.getClientID(), storedMessage.getMessageID(), guid, storedMessage.getTopic());
LOG.debug(
"Storing publish event. MqttClientId = {}, messageId = {}, guid = {}, topic = {}.",
storedMessage.getClientID(),
storedMessage.getMessageID(),
guid,
storedMessage.getTopic());
m_persistentMessageStore.put(guid, storedMessage);
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db.getHashMap(MapDBSessionsStore.messageId2GuidsMapName(storedMessage.getClientID()));
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db
.getHashMap(MapDBSessionsStore.messageId2GuidsMapName(storedMessage.getClientID()));
messageIdToGuid.put(storedMessage.getMessageID(), guid);
return guid;
}
@@ -94,7 +98,8 @@ class MapDBMessagesStore implements IMessagesStore {
@Override
public void dropMessagesInSession(String clientID) {
LOG.debug("Dropping stored messages. ClientId = {}.", clientID);
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db.getHashMap(MapDBSessionsStore.messageId2GuidsMapName(clientID));
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db
.getHashMap(MapDBSessionsStore.messageId2GuidsMapName(clientID));
for (MessageGUID guid : messageIdToGuid.values()) {
removeStoredMessage(guid);
}
@@ -102,11 +107,15 @@ class MapDBMessagesStore implements IMessagesStore {
}
void removeStoredMessage(MessageGUID guid) {
//remove only the not retained and no more referenced
// remove only the not retained and no more referenced
StoredMessage storedMessage = m_persistentMessageStore.get(guid);
if (!storedMessage.isRetained()) {
LOG.debug("Dropping stored message. ClientId = {}, messageId = {}, guid = {}, topic = {}.",
storedMessage.getClientID(), storedMessage.getMessageID(), guid, storedMessage.getTopic());
LOG.debug(
"Dropping stored message. ClientId = {}, messageId = {}, guid = {}, topic = {}.",
storedMessage.getClientID(),
storedMessage.getMessageID(),
guid,
storedMessage.getTopic());
m_persistentMessageStore.remove(guid);
}
}
@@ -23,14 +23,12 @@ import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static io.moquette.BrokerConstants.AUTOSAVE_INTERVAL_PROPERTY_NAME;
import static io.moquette.BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME;
@@ -40,10 +38,11 @@ import static io.moquette.BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME;
public class MapDBPersistentStore {
/**
* This is a DTO used to persist minimal status (clean session and activation status) of
* a session.
* */
* This is a DTO used to persist minimal status (clean session and activation status) of a
* session.
*/
public static class PersistentSession implements Serializable {
private static final long serialVersionUID = 5052054783220481854L;
public final boolean cleanSession;
@@ -69,8 +68,9 @@ public class MapDBPersistentStore {
/**
* Factory method to create message store backed by MapDB
*
* @return the message store instance.
* */
*/
public IMessagesStore messagesStore() {
return m_messageStore;
}
@@ -78,7 +78,7 @@ public class MapDBPersistentStore {
public ISessionsStore sessionsStore() {
return m_sessionsStore;
}
public void initStore() {
LOG.info("Initializing MapDB store...");
if (m_storePath == null || m_storePath.isEmpty()) {
@@ -92,13 +92,20 @@ public class MapDBPersistentStore {
boolean fileNewlyCreated = tmpFile.createNewFile();
LOG.warn("Using {} MapDB store file. Path = {}.", fileNewlyCreated ? "fresh" : "existing", m_storePath);
} catch (IOException ex) {
LOG.error("Unable to open MapDB store file. Path = {}, cause = {}, errorMessage = {}.", m_storePath, ex.getCause(), ex.getMessage());
throw new RuntimeException("Can't create temp file for subscriptions storage [" + m_storePath + "]", ex);
LOG.error(
"Unable to open MapDB store file. Path = {}, cause = {}, errorMessage = {}.",
m_storePath,
ex.getCause(),
ex.getMessage());
throw new RuntimeException(
"Can't create temp file for subscriptions storage [" + m_storePath + "]",
ex);
}
m_db = DBMaker.newFileDB(tmpFile).make();
}
LOG.info("Scheduling MapDB commit task...");
m_scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
LOG.debug("Committing to MapDB...");
@@ -106,7 +113,7 @@ public class MapDBPersistentStore {
}
}, this.m_autosaveInterval, this.m_autosaveInterval, TimeUnit.SECONDS);
//TODO check m_db is valid and
// TODO check m_db is valid and
m_messageStore = new MapDBMessagesStore(m_db);
m_messageStore.initStore();
@@ -127,7 +134,8 @@ public class MapDBPersistentStore {
this.m_scheduler.shutdown();
try {
m_scheduler.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException e) {}
} catch (InterruptedException e) {
}
if (!m_scheduler.isTerminated()) {
LOG.warn("Forcing shutdown of MapDB commit tasks...");
m_scheduler.shutdown();
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.persistence;
import io.moquette.spi.ClientSession;
@@ -25,7 +26,6 @@ import io.moquette.spi.persistence.MapDBPersistentStore.PersistentSession;
import org.mapdb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
@@ -40,12 +40,12 @@ class MapDBSessionsStore implements ISessionsStore {
private static final Logger LOG = LoggerFactory.getLogger(MapDBSessionsStore.class);
//maps clientID->[MessageId -> guid]
// maps clientID->[MessageId -> guid]
private ConcurrentMap<String, ConcurrentMap<Integer, MessageGUID>> m_inflightStore;
//map clientID <-> set of currently in flight packet identifiers
// map clientID <-> set of currently in flight packet identifiers
private Map<String, Set<Integer>> m_inFlightIds;
private ConcurrentMap<String, PersistentSession> m_persistentSessions;
//maps clientID->[MessageId -> guid]
// maps clientID->[MessageId -> guid]
private ConcurrentMap<String, Map<Integer, MessageGUID>> m_secondPhaseStore;
private final DB m_db;
@@ -66,14 +66,18 @@ class MapDBSessionsStore implements ISessionsStore {
@Override
public void addNewSubscription(Subscription newSubscription) {
LOG.info("Adding new subscription. CId= {}, topics = {}.", newSubscription.getClientId(),
LOG.info(
"Adding new subscription. CId= {}, topics = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter());
final String clientID = newSubscription.getClientId();
m_db.getHashMap("subscriptions_" + clientID).put(newSubscription.getTopicFilter(), newSubscription);
if (LOG.isTraceEnabled()) {
LOG.trace("The subscription has been added. CId= {}, topics = {}, clientSubscriptions = {}.",
newSubscription.getClientId(), newSubscription.getTopicFilter(),
LOG.trace(
"The subscription has been added. CId= {}, topics = {}, clientSubscriptions = {}.",
newSubscription.getClientId(),
newSubscription.getTopicFilter(),
m_db.getHashMap("subscriptions_" + clientID));
}
}
@@ -86,8 +90,11 @@ class MapDBSessionsStore implements ISessionsStore {
}
m_db.getHashMap("subscriptions_" + clientID).remove(topicFilter);
if (LOG.isDebugEnabled()) {
LOG.debug("The subscription has been removed. CId= {}, topics = {}, clientSubscriptions = {}.",
clientID, topicFilter, m_db.getHashMap("subscriptions_" + clientID));
LOG.debug(
"The subscription has been removed. CId= {}, topics = {}, clientSubscriptions = {}.",
clientID,
topicFilter,
m_db.getHashMap("subscriptions_" + clientID));
}
}
@@ -96,7 +103,9 @@ class MapDBSessionsStore implements ISessionsStore {
LOG.info("Wiping subscriptions. CId= {}.", clientID);
m_db.delete("subscriptions_" + clientID);
if (LOG.isDebugEnabled()) {
LOG.debug("The subscriptions have been removed. CId= {}, clientSubscriptions = {}.", clientID,
LOG.debug(
"The subscriptions have been removed. CId= {}, clientSubscriptions = {}.",
clientID,
m_db.getHashMap("subscriptions_" + clientID));
}
}
@@ -120,8 +129,7 @@ class MapDBSessionsStore implements ISessionsStore {
@Override
public Subscription getSubscription(ClientTopicCouple couple) {
ConcurrentMap<String, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + couple.clientID);
LOG.info("Retrieving subscriptions. CId= {}, subscriptions = {}.", couple.clientID,
clientSubscriptions);
LOG.info("Retrieving subscriptions. CId= {}, subscriptions = {}.", couple.clientID, clientSubscriptions);
return clientSubscriptions.get(couple.topicFilter);
}
@@ -147,7 +155,8 @@ class MapDBSessionsStore implements ISessionsStore {
if (m_persistentSessions.containsKey(clientID)) {
LOG.error(
"Unable to create a new session: the client ID is already in use. CId= {}, cleanSession = {}.",
clientID, cleanSession);
clientID,
cleanSession);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
LOG.info("Creating new session. CId= {}, cleanSession = {}.", clientID, cleanSession);
@@ -166,10 +175,10 @@ class MapDBSessionsStore implements ISessionsStore {
PersistentSession storedSession = m_persistentSessions.get(clientID);
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
}
@Override
public Collection<ClientSession> getAllSessions() {
Collection<ClientSession> result = new ArrayList<ClientSession>();
Collection<ClientSession> result = new ArrayList<>();
for (Map.Entry<String, PersistentSession> entry : m_persistentSessions.entrySet()) {
result.add(new ClientSession(entry.getKey(), m_messagesStore, this, entry.getValue().cleanSession));
}
@@ -184,7 +193,7 @@ class MapDBSessionsStore implements ISessionsStore {
/**
* Return the next valid packetIdentifier for the given client session.
* */
*/
@Override
public int nextPacketID(String clientID) {
if (LOG.isDebugEnabled()) {
@@ -219,7 +228,7 @@ class MapDBSessionsStore implements ISessionsStore {
}
m.remove(messageID);
//remove from the ids store
// remove from the ids store
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
if (inFlightForClient != null) {
inFlightForClient.remove(messageID);
@@ -262,13 +271,14 @@ class MapDBSessionsStore implements ISessionsStore {
}
MessageGUID guid = m.remove(messageID);
//remove from the ids store
// remove from the ids store
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
if (inFlightForClient != null) {
inFlightForClient.remove(messageID);
}
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
Map<Integer, MessageGUID> messageIDs = Utils
.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
messageIDs.put(messageID, guid);
m_secondPhaseStore.put(clientID, messageIDs);
}
@@ -278,7 +288,8 @@ class MapDBSessionsStore implements ISessionsStore {
if (LOG.isDebugEnabled()) {
LOG.debug("Processing second phase ACK CId={}, messageId={}", clientID, messageID);
}
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
Map<Integer, MessageGUID> messageIDs = Utils
.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
MessageGUID guid = messageIDs.remove(messageID);
m_secondPhaseStore.put(clientID, messageIDs);
return guid;
@@ -297,22 +308,21 @@ class MapDBSessionsStore implements ISessionsStore {
return "guidsMapping_" + clientID;
}
@Override
public StoredMessage getInflightMessage(String clientID, int messageID) {
@Override
public StoredMessage getInflightMessage(String clientID, int messageID) {
LOG.info("Retrieving inflight message CId={}, messageId={}", clientID, messageID);
Map<Integer, MessageGUID> clientEntries = m_inflightStore.get(clientID);
Map<Integer, MessageGUID> clientEntries = m_inflightStore.get(clientID);
if (clientEntries == null) {
LOG.warn("The client has no inflight messages CId={}, messageId={}", clientID, messageID);
return null;
}
MessageGUID guid = clientEntries.get(messageID);
if (guid == null) {
LOG.warn("The message ID does not have an associated GUID. CId= {}, messageId = {}.", clientID,
messageID);
LOG.warn("The message ID does not have an associated GUID. CId= {}, messageId = {}.", clientID, messageID);
return null;
}
return m_messagesStore.getMessageByGuid(guid);
}
return m_messagesStore.getMessageByGuid(guid);
}
@Override
public int getInflightMessagesNo(String clientID) {
@@ -13,11 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.security;
/**
* username and password checker
*
*
* @author andrea
*/
public interface IAuthenticator {
@@ -13,14 +13,14 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.security;
/**
* ACL checker.
*
* Create an authorizator that matches topic names with same grammar of subscriptions.
* The # is always a terminator and its the multilevel matcher.
* The + sign is the single level matcher.
* Create an authorizator that matches topic names with same grammar of subscriptions. The # is
* always a terminator and its the multilevel matcher. The + sign is the single level matcher.
*
* @author andrea
*/
@@ -28,11 +28,15 @@ public interface IAuthorizator {
/**
* Ask the implementation of the authorizator if the topic can be used in a publish.
* @param topic the topic to write to.
* @param user the user
* @param client the client
*
* @param topic
* the topic to write to.
* @param user
* the user
* @param client
* the client
* @return true if the user from client can publish data on topic.
* */
*/
boolean canWrite(String topic, String user, String client);
boolean canRead(String topic, String user, String client);
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.spi.security;
import javax.net.ssl.SSLContext;
+52 -50
View File
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette;
import io.netty.buffer.ByteBuf;
@@ -23,7 +24,6 @@ import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.AttributeMap;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
/**
@@ -31,44 +31,43 @@ import java.io.UnsupportedEncodingException;
* @author andrea
*/
public class Utils {
public static final int MAX_LENGTH_LIMIT = 268435455;
public static final byte VERSION_3_1 = 3;
public static final byte VERSION_3_1_1 = 4;
static byte readMessageType(ByteBuf in) {
byte h1 = in.readByte();
byte messageType = (byte) ((h1 & 0x00F0) >> 4);
return messageType;
}
static boolean checkHeaderAvailability(ByteBuf in) {
if (in.readableBytes() < 1) {
return false;
}
//byte h1 = in.get();
//byte messageType = (byte) ((h1 & 0x00F0) >> 4);
in.skipBytes(1); //skip the messageType byte
// byte h1 = in.get();
// byte messageType = (byte) ((h1 & 0x00F0) >> 4);
in.skipBytes(1); // skip the messageType byte
int remainingLength = Utils.decodeRemainingLenght(in);
if (remainingLength == -1) {
return false;
}
//check remaining length
// check remaining length
if (in.readableBytes() < remainingLength) {
return false;
}
//return messageType == type ? MessageDecoderResult.OK : MessageDecoderResult.NOT_OK;
// return messageType == type ? MessageDecoderResult.OK : MessageDecoderResult.NOT_OK;
return true;
}
/**
* Decode the variable remaining length as defined in MQTT v3.1 specification
* (section 2.1).
*
* Decode the variable remaining length as defined in MQTT v3.1 specification (section 2.1).
*
* @return the decoded length or -1 if needed more data to decode the length field.
*/
static int decodeRemainingLenght(ByteBuf in) {
@@ -85,13 +84,12 @@ public class Utils {
} while ((digit & 0x80) != 0);
return value;
}
/**
* Encode the value in the format defined in specification as variable length
* array.
*
* @throws IllegalArgumentException if the value is not in the specification bounds
* [0..268435455].
* Encode the value in the format defined in specification as variable length array.
*
* @throws IllegalArgumentException
* if the value is not in the specification bounds [0..268435455].
*/
static ByteBuf encodeRemainingLength(int value) throws CorruptedFrameException {
if (value > MAX_LENGTH_LIMIT || value < 0) {
@@ -111,11 +109,11 @@ public class Utils {
} while (value > 0);
return encoded;
}
/**
* Load a string from the given buffer, reading first the two bytes of len
* and then the UTF-8 bytes of the string.
*
* Load a string from the given buffer, reading first the two bytes of len and then the UTF-8
* bytes of the string.
*
* @return the decoded string or null if NEED_DATA
*/
static String decodeString(ByteBuf in) throws UnsupportedEncodingException {
@@ -123,8 +121,9 @@ public class Utils {
}
/**
* Read a byte array from the buffer, use two bytes as length information followed by length bytes.
* */
* Read a byte array from the buffer, use two bytes as length information followed by length
* bytes.
*/
static byte[] readFixedLengthContent(ByteBuf in) throws UnsupportedEncodingException {
if (in.readableBytes() < 2) {
return null;
@@ -140,15 +139,14 @@ public class Utils {
}
/**
* Return the IoBuffer with string encoded as MSB, LSB and UTF-8 encoded
* string content.
* Return the IoBuffer with string encoded as MSB, LSB and UTF-8 encoded string content.
*/
public static ByteBuf encodeString(String str) {
byte[] raw;
try {
raw = str.getBytes("UTF-8");
//NB every Java platform has got UTF-8 encoding by default, so this
//exception are never raised.
// NB every Java platform has got UTF-8 encoding by default, so this
// exception are never raised.
} catch (UnsupportedEncodingException ex) {
LoggerFactory.getLogger(Utils.class).error(null, ex);
return null;
@@ -170,13 +168,17 @@ public class Utils {
* Return the number of bytes to encode the given remaining length value
*/
static int numBytesToEncode(int len) {
if (0 <= len && len <= 127) return 1;
if (128 <= len && len <= 16383) return 2;
if (16384 <= len && len <= 2097151) return 3;
if (2097152 <= len && len <= 268435455) return 4;
if (0 <= len && len <= 127)
return 1;
if (128 <= len && len <= 16383)
return 2;
if (16384 <= len && len <= 2097151)
return 3;
if (2097152 <= len && len <= 268435455)
return 4;
throw new IllegalArgumentException("value shoul be in the range [0..268435455]");
}
static byte encodeFlags(MqttMessage message) {
byte flags = 0;
if (message.fixedHeader().isDup()) {
@@ -185,20 +187,20 @@ public class Utils {
if (message.fixedHeader().isRetain()) {
flags |= 0x01;
}
flags |= ((message.fixedHeader().qosLevel().value() & 0x03) << 1);
return flags;
}
//3 = 3.1, 4 = 3.1.1
// static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf("version");
//
// static boolean isMQTT3_1_1(AttributeMap attrsMap) {
// Attribute<Integer> versionAttr = attrsMap.attr(PROTOCOL_VERSION);
// Integer protocolVersion = versionAttr.get();
// if (protocolVersion == null) {
// return true;
// }
// return protocolVersion == VERSION_3_1_1;
// }
// 3 = 3.1, 4 = 3.1.1
// static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf("version");
//
// static boolean isMQTT3_1_1(AttributeMap attrsMap) {
// Attribute<Integer> versionAttr = attrsMap.attr(PROTOCOL_VERSION);
// Integer protocolVersion = versionAttr.get();
// if (protocolVersion == null) {
// return true;
// }
// return protocolVersion == VERSION_3_1_1;
// }
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.BrokerConstants;
@@ -22,33 +23,31 @@ import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.*;
/**
*
* @author luca <[email protected]>
* @author luca <[email protected]>
*/
public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizator {
Server m_server;
IConfig m_config;
protected void startServer(Properties props) throws IOException {
m_server = new Server();
m_config = new MemoryConfig(props);
m_server.startServer(m_config);
}
@After
public void tearDown() throws Exception {
m_server.stopServer();
}
@Test
public void loadAuthenticator() throws Exception {
Properties props = new Properties(IntegrationUtils.prepareTestProperties());
@@ -56,7 +55,7 @@ public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizat
startServer(props);
assertTrue(true);
}
@Test
public void loadAuthorizator() throws Exception {
Properties props = new Properties(IntegrationUtils.prepareTestProperties());
@@ -79,5 +78,5 @@ public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizat
public boolean canRead(String topic, String user, String client) {
return true;
}
}
@@ -13,13 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.server.config.IConfig;
import java.io.File;
import java.util.Properties;
import static io.moquette.BrokerConstants.DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME;
import static io.moquette.BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME;
import static io.moquette.BrokerConstants.PORT_PROPERTY_NAME;
@@ -31,6 +30,7 @@ import static org.junit.Assert.assertFalse;
* Created by andrea on 4/7/15.
*/
public class IntegrationUtils {
static String localMapDBPath() {
String currentDir = System.getProperty("user.dir");
return currentDir + File.separator + DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import java.io.IOException;
@@ -35,62 +36,61 @@ import org.slf4j.LoggerFactory;
*/
@WebSocket
public class MQTTWebSocket {
private static final Logger LOG = LoggerFactory.getLogger(MQTTWebSocket.class);
private Session session;
private final CountDownLatch connectSentLatch;
public MQTTWebSocket() {
this.connectSentLatch = new CountDownLatch(1);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
System.out.printf("Connection closed: %d - %s%n", statusCode, reason);
this.session = null;
}
@OnWebSocketConnect
public void onConnect(Session session) throws IOException {
System.out.printf("Got connect: %s%n", session);
LOG.info("Got connect: %s%n", session);
this.session = session;
//Trivial MQTT Connect message
// assertEquals(12, out.readByte()); //remaining length
// verifyString("MQIsdp", out);
// assertEquals(0x03, out.readByte()); //protocol version
// assertEquals(0x32, out.readByte()); //flags
// assertEquals(2, out.readByte()); //keepAliveTimer msb
// assertEquals(0, out.readByte()); //keepAliveTimer lsb
ByteBuffer msg = ByteBuffer.wrap(new byte[] { 0x10, //message type
0x0C, //remaining lenght 12 bytes
'M', 'Q','I','s','d','p',
0x03, //protocol version
0x32, //flags
0x02, 0x00// keepAlive Timer, 2 seconds
// Trivial MQTT Connect message
// assertEquals(12, out.readByte()); //remaining length
// verifyString("MQIsdp", out);
// assertEquals(0x03, out.readByte()); //protocol version
// assertEquals(0x32, out.readByte()); //flags
// assertEquals(2, out.readByte()); //keepAliveTimer msb
// assertEquals(0, out.readByte()); //keepAliveTimer lsb
ByteBuffer msg = ByteBuffer.wrap(new byte[]{0x10, // message type
0x0C, // remaining lenght 12 bytes
'M', 'Q', 'I', 's', 'd', 'p', 0x03, // protocol version
0x32, // flags
0x02, 0x00// keepAlive Timer, 2 seconds
});
// try {
session.getRemote().sendBytes(msg);
this.connectSentLatch.countDown();
session.close(StatusCode.NORMAL, "I'm done");
// } catch (IOException t) {
// LOG.error(null, t);
// }
// try {
session.getRemote().sendBytes(msg);
this.connectSentLatch.countDown();
session.close(StatusCode.NORMAL, "I'm done");
// } catch (IOException t) {
// LOG.error(null, t);
// }
}
public boolean awaitConnected(int duration, TimeUnit unit) throws InterruptedException {
return this.connectSentLatch.await(duration, unit);
}
@OnWebSocketMessage
public void onMessage(Session session, byte[] buf, int offset, int length) {
System.out.printf("Got msg: %s%n", buf);
}
@OnWebSocketError
public void onError(Session session, Throwable cause) {
System.out.printf("Got exception: %s%n", cause);
@@ -13,12 +13,12 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.util.concurrent.*;
/**
@@ -27,6 +27,7 @@ import java.util.concurrent.*;
public class MessageCollector implements MqttCallback {
private class ReceivedMessage {
private final MqttMessage message;
private final String topic;
@@ -41,7 +42,7 @@ public class MessageCollector implements MqttCallback {
/**
* Return the message from the queue if not empty, else return null with wait period.
* */
*/
public MqttMessage getMessageImmediate() {
if (m_messages.isEmpty()) {
return null;
@@ -94,13 +95,13 @@ public class MessageCollector implements MqttCallback {
/**
* Invoked when the message sent to a server is ACKED (PUBACK or PUBCOMP by the server)
* */
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// try {
// m_messages.offer(new ReceivedMessage(token.waitMessage(), token.getTopics()[0]));
// } catch (MqttException e) {
// e.printStackTrace();
// }
// try {
// m_messages.offer(new ReceivedMessage(token.waitMessage(), token.getTopics()[0]));
// } catch (MqttException e) {
// e.printStackTrace();
// }
}
}
@@ -1,3 +1,4 @@
package io.moquette.server;
import io.moquette.BrokerConstants;
@@ -10,19 +11,18 @@ import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Properties;
import static org.junit.Assert.assertTrue;
/**
* Created by mackristof on 13/05/2016.
*/
public class ServerIntegrationDBAuthenticatorTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationDBAuthenticatorTest.class);
static MqttClientPersistence s_dataStore;
@@ -35,8 +35,6 @@ public class ServerIntegrationDBAuthenticatorTest {
MessageCollector m_messagesCollector;
IConfig m_config;
@BeforeClass
public static void beforeTests() throws NoSuchAlgorithmException, SQLException, ClassNotFoundException {
String tmpDir = System.getProperty("java.io.tmpdir");
@@ -53,7 +51,6 @@ public class ServerIntegrationDBAuthenticatorTest {
m_server.startServer(m_config);
}
private void stopServer() {
m_server.stopServer();
IntegrationUtils.cleanPersistenceFile(m_config);
@@ -96,7 +93,7 @@ public class ServerIntegrationDBAuthenticatorTest {
}
@AfterClass
public static void shutdown(){
public static void shutdown() {
dbAuthenticatorTest.teardown();
}
@@ -111,9 +108,8 @@ public class ServerIntegrationDBAuthenticatorTest {
assertTrue(true);
}
@Test
public void connectWithWrongCredentials() {
public void connectWithWrongCredentials() {
LOG.info("*** connectWithWrongCredentials ***");
try {
m_client = new MqttClient("tcp://localhost:1883", "Publisher", s_pubDataStore);
@@ -121,18 +117,16 @@ public class ServerIntegrationDBAuthenticatorTest {
options.setUserName("dbuser");
options.setPassword("wrongPassword".toCharArray());
m_client.connect(options);
} catch (MqttException e){
} catch (MqttException e) {
if (e instanceof MqttSecurityException) {
assertTrue(true);
return;
} else {
assertTrue(e.getMessage(),false);
assertTrue(e.getMessage(), false);
return;
}
}
assertTrue("must not be connected. cause : wrong password given to client",false);
assertTrue("must not be connected. cause : wrong password given to client", false);
}
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.server.netty.MessageBuilder;
@@ -28,11 +29,9 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.*;
public class ServerIntegrationEmbeddedPublishTest {
@@ -89,12 +88,8 @@ public class ServerIntegrationEmbeddedPublishTest {
}
private void internalPublishToWithQosAndRetained(String topic, MqttQoS qos, boolean retained) {
MqttPublishMessage message = MessageBuilder.publish()
.topicName(topic)
.retained(retained)
.qos(qos)
.payload("Hello world MQTT!!".getBytes())
.build();
MqttPublishMessage message = MessageBuilder.publish().topicName(topic).retained(retained).qos(qos)
.payload("Hello world MQTT!!".getBytes()).build();
m_server.internalPublish(message, "INTRPUBL");
}
@@ -115,7 +110,7 @@ public class ServerIntegrationEmbeddedPublishTest {
}
private void verifyMessageIsReceivedSuccessfully() throws Exception {
//check in 2 seconds
// check in 2 seconds
MqttMessage msg = m_callback.waitMessage(2);
assertNotNull(msg);
assertEquals("Hello world MQTT!!", new String(msg.getPayload()));
@@ -125,11 +120,11 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterNotRetainedQoS0IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterNotRetainedQoS0IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_MOST_ONCE, false);
subscribeToWithQos("/topic", 0);
//Verify
// Verify
verifyNoMessageIsReceived();
}
@@ -139,10 +134,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 0);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_MOST_ONCE, false);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -152,10 +147,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 0);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_MOST_ONCE, true);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -163,12 +158,12 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterRetainedQoS0IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterRetainedQoS0IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_MOST_ONCE, true);
//LOG.info("** post internalPublish **");
// LOG.info("** post internalPublish **");
subscribeToWithQos("/topic", 0);
//Verify
// Verify
verifyNoMessageIsReceived();
}
@@ -178,10 +173,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 1);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_LEAST_ONCE, false);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -189,11 +184,11 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterNotRetainedQoS1IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterNotRetainedQoS1IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_LEAST_ONCE, false);
subscribeToWithQos("/topic", 1);
//Verify
// Verify
verifyNoMessageIsReceived();
}
@@ -203,10 +198,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 1);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_LEAST_ONCE, true);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -214,12 +209,12 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterRetainedQoS1IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterRetainedQoS0IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.AT_LEAST_ONCE, true);
subscribeToWithQos("/topic", MqttQoS.AT_LEAST_ONCE.value());
LOG.info("** After subscribe **");
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
LOG.info("** Post verify **");
}
@@ -230,10 +225,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 2);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.EXACTLY_ONCE, false);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -241,11 +236,11 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterNotRetainedQoS2IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterNotRetainedQoS2IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.EXACTLY_ONCE, false);
subscribeToWithQos("/topic", 2);
//Verify
// Verify
verifyNoMessageIsReceived();
}
@@ -255,10 +250,10 @@ public class ServerIntegrationEmbeddedPublishTest {
subscribeToWithQos("/topic", 2);
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.EXACTLY_ONCE, true);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -266,11 +261,11 @@ public class ServerIntegrationEmbeddedPublishTest {
public void testClientSubscribeAfterRetainedQoS2IsSent() throws Exception {
LOG.info("*** testClientSubscribeAfterRetainedQoS2IsSent ***");
//Exercise
// Exercise
internalPublishToWithQosAndRetained("/topic", MqttQoS.EXACTLY_ONCE, true);
subscribeToWithQos("/topic", 2);
//Verify
// Verify
verifyMessageIsReceivedSuccessfully();
}
@@ -283,8 +278,8 @@ public class ServerIntegrationEmbeddedPublishTest {
internalPublishToWithQosAndRetained("foo", MqttQoS.AT_MOST_ONCE, false);
//verifyMessageIsReceivedSuccessfully();
//m_subscriber.receive(2, TimeUnit.MILLISECONDS);
// verifyMessageIsReceivedSuccessfully();
// m_subscriber.receive(2, TimeUnit.MILLISECONDS);
MqttMessage message = m_callback.waitMessage(1);
assertTrue(message == null);
}
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.server.config.IConfig;
@@ -23,11 +24,9 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
/**
@@ -35,6 +34,7 @@ import static org.junit.Assert.*;
* @author andrea
*/
public class ServerIntegrationFuseTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationPahoTest.class);
Server m_server;
@@ -42,7 +42,7 @@ public class ServerIntegrationFuseTest {
BlockingConnection m_subscriber;
BlockingConnection m_publisher;
IConfig m_config;
protected void startServer() throws IOException {
m_server = new Server();
final Properties configProps = IntegrationUtils.prepareTestProperties();
@@ -63,7 +63,7 @@ public class ServerIntegrationFuseTest {
if (m_subscriber != null) {
m_subscriber.disconnect();
}
if (m_publisher != null) {
m_publisher.disconnect();
}
@@ -75,31 +75,31 @@ public class ServerIntegrationFuseTest {
@Test
public void checkWillTestamentIsPublishedOnConnectionKill_noRetain() throws Exception {
LOG.info("checkWillTestamentIsPublishedOnConnectionKill_noRetain");
String willTestamentTopic = "/will/test";
String willTestamentMsg = "Bye bye";
MQTT mqtt = new MQTT();
mqtt.setHost("localhost", 1883);
mqtt.setHost("localhost", 1883);
mqtt.setClientId("WillTestamentPublisher");
mqtt.setWillRetain(false);
mqtt.setWillMessage(willTestamentMsg);
mqtt.setWillTopic(willTestamentTopic);
m_publisher = mqtt.blockingConnection();
m_publisher.connect();
m_mqtt.setHost("localhost", 1883);
m_mqtt.setHost("localhost", 1883);
m_mqtt.setCleanSession(false);
m_mqtt.setClientId("Subscriber");
m_subscriber = m_mqtt.blockingConnection();
m_subscriber.connect();
Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_MOST_ONCE)};
m_subscriber.subscribe(topics);
//Exercise, kill the publisher connection
// Exercise, kill the publisher connection
m_publisher.kill();
//Verify, that the testament is fired
// Verify, that the testament is fired
Message msg = m_subscriber.receive(500, TimeUnit.MILLISECONDS);
assertNotNull("We should get notified with 'Will' message", msg);
msg.ack();
@@ -1,3 +1,4 @@
package io.moquette.server;
import io.moquette.BrokerConstants;
@@ -12,16 +13,15 @@ import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
public class ServerIntegrationHazelcastHandlerInterceptorTest{
public class ServerIntegrationHazelcastHandlerInterceptorTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationHazelcastHandlerInterceptorTest.class);
static MqttClientPersistence s_dataStore;
@@ -55,7 +55,9 @@ public class ServerIntegrationHazelcastHandlerInterceptorTest{
private Properties addHazelCastConf(Properties properties, int port, String hazelcastConfigurationFile) {
properties.put(BrokerConstants.PORT_PROPERTY_NAME, port);
properties.put(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME, HazelcastInterceptHandler.class.getCanonicalName());
properties.put(
BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME,
HazelcastInterceptHandler.class.getCanonicalName());
properties.put(BrokerConstants.HAZELCAST_CONFIGURATION, hazelcastConfigurationFile);
return properties;
}
@@ -65,14 +67,18 @@ public class ServerIntegrationHazelcastHandlerInterceptorTest{
String dbPath = IntegrationUtils.localMapDBPath();
IntegrationUtils.cleanPersistenceFile(dbPath);
final Properties configProps = addHazelCastConf(IntegrationUtils.prepareTestClusterProperties(1883),
1883, "config/hazelcast.xml");
final Properties configProps = addHazelCastConf(
IntegrationUtils.prepareTestClusterProperties(1883),
1883,
"config/hazelcast.xml");
m_config_1883 = new MemoryConfig(configProps);
server1883 = startServer(1883, m_config_1883);
final Properties configProps1884 = addHazelCastConf(IntegrationUtils.prepareTestClusterProperties(1884),
1883, "config/hazelcast.xml");
final Properties configProps1884 = addHazelCastConf(
IntegrationUtils.prepareTestClusterProperties(1884),
1883,
"config/hazelcast.xml");
m_config_1884 = new MemoryConfig(configProps1884);
server1884 = startServer(1884, m_config_1884);
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.server.config.IConfig;
@@ -22,11 +23,9 @@ import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.*;
public class ServerIntegrationPahoTest {
@@ -35,7 +34,7 @@ public class ServerIntegrationPahoTest {
static MqttClientPersistence s_dataStore;
static MqttClientPersistence s_pubDataStore;
Server m_server;
IMqttClient m_client;
IMqttClient m_publisher;
@@ -113,7 +112,6 @@ public class ServerIntegrationPahoTest {
assertEquals("/topic", m_messagesCollector.getTopic());
}
@Test
public void testCleanSession_maintainClientSubscriptions() throws Exception {
@@ -124,7 +122,7 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 0);
m_client.disconnect();
//reconnect and publish
// reconnect and publish
MqttClient anotherClient = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
m_messagesCollector = new MessageCollector();
anotherClient.setCallback(m_messagesCollector);
@@ -134,7 +132,6 @@ public class ServerIntegrationPahoTest {
assertEquals("/topic", m_messagesCollector.getTopic());
}
@Test
public void testCleanSession_maintainClientSubscriptions_againstClientDestruction() throws Exception {
LOG.info("*** testCleanSession_maintainClientSubscriptions_againstClientDestruction ***");
@@ -144,7 +141,7 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 0);
m_client.disconnect();
//reconnect and publish
// reconnect and publish
m_client.connect(options);
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
@@ -152,9 +149,9 @@ public class ServerIntegrationPahoTest {
}
/**
* Check that after a client has connected with clean session false, subscribed
* to some topic and exited, if it reconnects with clean session true, the m_server
* correctly cleanup every previous subscription
* Check that after a client has connected with clean session false, subscribed to some topic
* and exited, if it reconnects with clean session true, the m_server correctly cleanup every
* previous subscription
*/
@Test
public void testCleanSession_correctlyClientSubscriptions() throws Exception {
@@ -165,17 +162,17 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 0);
m_client.disconnect();
//reconnect and publish
//options.setCleanSession(false);
// reconnect and publish
// options.setCleanSession(false);
m_client = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
MessageCollector subListener = new MessageCollector();
m_client.setCallback(subListener);
m_client.connect(options);
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
//Ugly, we need to get self notified after the publish
// Ugly, we need to get self notified after the publish
assertEquals("Test my payload", new String(subListener.waitMessage(1).getPayload()));
//close
// close
m_client.disconnect();
}
@@ -192,7 +189,7 @@ public class ServerIntegrationPahoTest {
m_server.startServer(IntegrationUtils.prepareTestProperties());
//reconnect and publish
// reconnect and publish
m_client.connect(options);
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
@@ -206,7 +203,7 @@ public class ServerIntegrationPahoTest {
m_client.publish("/topic", "Test my payload".getBytes(), 1, true);
m_client.disconnect();
//reconnect and publish
// reconnect and publish
m_client.connect();
m_client.subscribe("/topic", 0);
@@ -221,7 +218,7 @@ public class ServerIntegrationPahoTest {
m_client.connect(options);
m_client.subscribe("/topic", 0);
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
//m_client.disconnect();
// m_client.disconnect();
assertEquals("/topic", m_messagesCollector.getTopic());
m_client.unsubscribe("/topic");
@@ -239,7 +236,7 @@ public class ServerIntegrationPahoTest {
m_client.connect(options);
m_client.subscribe("/topic", 0);
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
//m_client.disconnect();
// m_client.disconnect();
assertEquals("/topic", m_messagesCollector.getTopic());
m_client.unsubscribe("/topic");
@@ -260,7 +257,7 @@ public class ServerIntegrationPahoTest {
m_client.publish("/topic", "Hello MQTT".getBytes(), 1, false);
m_client.disconnect();
//reconnect and publish
// reconnect and publish
MqttMessage message = m_messagesCollector.waitMessage(1);
assertEquals("Hello MQTT", message.toString());
assertEquals(1, message.getQos());
@@ -275,7 +272,7 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 1);
m_client.disconnect();
//publish a QoS 1 message another client publish a message on the topic
// publish a QoS 1 message another client publish a message on the topic
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 1);
m_client.connect(options);
@@ -283,7 +280,6 @@ public class ServerIntegrationPahoTest {
assertEquals("Hello MQTT", m_messagesCollector.waitMessage(1).toString());
}
@Test
public void checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession() throws Exception {
LOG.info("*** checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession ***");
@@ -296,10 +292,10 @@ public class ServerIntegrationPahoTest {
m_client.connect(options);
m_client.subscribe("/topic", 1);
//publish a QoS 1 message another client publish a message on the topic
// publish a QoS 1 message another client publish a message on the topic
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 1);
//Verify that after a reconnection the client receive the message
// Verify that after a reconnection the client receive the message
MqttMessage message = m_messagesCollector.waitMessage(1);
assertNotNull(message);
assertEquals("Hello MQTT", message.toString());
@@ -321,7 +317,7 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 2);
m_client.disconnect();
//publish a QoS 1 message another client publish a message on the topic
// publish a QoS 1 message another client publish a message on the topic
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 2);
m_messagesCollector.reinit();
m_client.connect(options);
@@ -340,7 +336,7 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 2);
m_client.disconnect();
//publish a QoS 2 message another client publish a message on the topic
// publish a QoS 2 message another client publish a message on the topic
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 2);
m_messagesCollector.reinit();
m_client.connect(options);
@@ -370,10 +366,10 @@ public class ServerIntegrationPahoTest {
assertEquals("Hello MQTT 1", message.toString());
m_client.disconnect();
//publish other message
// publish other message
publishFromAnotherClient("/topic", "Hello MQTT 2".getBytes(), 1);
//reconnect the second time
// reconnect the second time
m_messagesCollector.reinit();
m_client.connect(options);
assertNotNull(m_messagesCollector);
@@ -383,11 +379,9 @@ public class ServerIntegrationPahoTest {
}
/**
* subscriber A connect and subscribe on "a/b" QoS 1
* subscriber B connect and subscribe on "a/+" BUT with QoS 2
* publisher connects and send a message "hello" on "a/b"
* subscriber A must receive a notification with QoS1
* subscriber B must receive a notification with QoS2
* subscriber A connect and subscribe on "a/b" QoS 1 subscriber B connect and subscribe on "a/+"
* BUT with QoS 2 publisher connects and send a message "hello" on "a/b" subscriber A must
* receive a notification with QoS1 subscriber B must receive a notification with QoS2
*/
@Test
public void checkSubscribersGetCorrectQosNotifications() throws Exception {
@@ -410,7 +404,6 @@ public class ServerIntegrationPahoTest {
subscriberB.connect();
subscriberB.subscribe("a/+", 2);
m_client.connect();
m_client.publish("a/b", "Hello world MQTT!!".getBytes(), 2, false);
@@ -429,8 +422,9 @@ public class ServerIntegrationPahoTest {
public void testSubcriptionDoesntStayActiveAfterARestart() throws Exception {
LOG.info("*** testSubcriptionDoesntStayActiveAfterARestart ***");
String tmpDir = System.getProperty("java.io.tmpdir");
//clientForSubscribe1 connect and subscribe to /topic QoS2
MqttClientPersistence dsSubscriberA = new MqttDefaultFilePersistence(tmpDir + File.separator + "clientForSubscribe1");
// clientForSubscribe1 connect and subscribe to /topic QoS2
MqttClientPersistence dsSubscriberA = new MqttDefaultFilePersistence(
tmpDir + File.separator + "clientForSubscribe1");
MqttClient clientForSubscribe1 = new MqttClient("tcp://localhost:1883", "clientForSubscribe1", dsSubscriberA);
MessageCollector cbSubscriber1 = new MessageCollector();
@@ -438,27 +432,29 @@ public class ServerIntegrationPahoTest {
clientForSubscribe1.connect();
clientForSubscribe1.subscribe("topic", 0);
//server stop
// server stop
m_server.stopServer();
System.out.println("\n\n SEVER REBOOTING \n\n");
//server start
// server start
startServer();
//clientForSubscribe2 connect and subscribe to /topic QoS2
MqttClientPersistence dsSubscriberB = new MqttDefaultFilePersistence(tmpDir + File.separator + "clientForSubscribe2");
// clientForSubscribe2 connect and subscribe to /topic QoS2
MqttClientPersistence dsSubscriberB = new MqttDefaultFilePersistence(
tmpDir + File.separator + "clientForSubscribe2");
MqttClient clientForSubscribe2 = new MqttClient("tcp://localhost:1883", "clientForSubscribe2", dsSubscriberB);
MessageCollector cbSubscriber2 = new MessageCollector();
clientForSubscribe2.setCallback(cbSubscriber2);
clientForSubscribe2.connect();
clientForSubscribe2.subscribe("topic", 0);
//clientForPublish publish on /topic with QoS2 a message
MqttClientPersistence dsSubscriberPUB = new MqttDefaultFilePersistence(tmpDir + File.separator + "clientForPublish");
// clientForPublish publish on /topic with QoS2 a message
MqttClientPersistence dsSubscriberPUB = new MqttDefaultFilePersistence(
tmpDir + File.separator + "clientForPublish");
MqttClient clientForPublish = new MqttClient("tcp://localhost:1883", "clientForPublish", dsSubscriberPUB);
clientForPublish.connect();
clientForPublish.publish("topic", "Hello".getBytes(), 2, true);
//verify clientForSubscribe1 doesn't receive a notification but clientForSubscribe2 yes
// verify clientForSubscribe1 doesn't receive a notification but clientForSubscribe2 yes
LOG.info("Before waiting to receive 1 sec from " + clientForSubscribe1.getClientId());
assertFalse(clientForSubscribe1.isConnected());
assertTrue(clientForSubscribe2.isConnected());
@@ -490,7 +486,7 @@ public class ServerIntegrationPahoTest {
MqttClient clientYB = createClient("publisher", "Y");
clientYB.publish("topic", "Hello 2".getBytes(), 2, true);
//Verify that the second subscriber client get notified and not the first.
// Verify that the second subscriber client get notified and not the first.
assertTrue(cbSubscriber1.connectionLost());
assertEquals("Hello 2", new String(cbSubscriber2.waitMessage(1).getPayload()));
}
@@ -505,11 +501,11 @@ public class ServerIntegrationPahoTest {
m_client.publish("/topic", "Hello".getBytes(), 0, true);
m_client.disconnect();
//second loop
// second loop
m_client.connect(options);
m_client.subscribe("/topic", 0);
m_client.publish("/topic", "Hello".getBytes(), 0, true);
m_client.disconnect(); //this should give timeout
m_client.disconnect(); // this should give timeout
assertFalse("after a disconnect the client should be disconnected", m_client.isConnected());
}
@@ -517,8 +513,8 @@ public class ServerIntegrationPahoTest {
@Test
public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() throws Exception {
LOG.info("*** checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1 ***");
// MessageCollector m_messagesCollector = new MessageCollector();
// m_client.setCallback(m_messagesCollector);
// MessageCollector m_messagesCollector = new MessageCollector();
// m_client.setCallback(m_messagesCollector);
m_publisher.connect();
MqttConnectOptions options = new MqttConnectOptions();
@@ -526,10 +522,10 @@ public class ServerIntegrationPahoTest {
m_client.connect(options);
m_client.subscribe("/topic", 1);
//force the publisher to send
// force the publisher to send
m_publisher.publish("/topic", "Hello world MQTT!!-1".getBytes(), 1, false);
//read the first message and drop the connection
// read the first message and drop the connection
MqttMessage msg = m_messagesCollector.waitMessage(1);
assertEquals("Hello world MQTT!!-1", new String(msg.getPayload()));
m_client.disconnect();
@@ -537,7 +533,7 @@ public class ServerIntegrationPahoTest {
m_publisher.publish("/topic", "Hello world MQTT!!-2".getBytes(), 1, false);
m_publisher.publish("/topic", "Hello world MQTT!!-3".getBytes(), 1, false);
//reconnect and expect to receive the hello 2 message
// reconnect and expect to receive the hello 2 message
m_client.connect(options);
msg = m_messagesCollector.waitMessage(1);
assertEquals("Hello world MQTT!!-2", new String(msg.getPayload()));
@@ -560,10 +556,10 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("/topic", 1);
m_client.disconnect();
//force the publisher to send
// force the publisher to send
m_publisher.publish("/topic", "Hello world MQTT!!-1".getBytes(), 1, false);
//reconnect and expect to receive the hello 2 message
// reconnect and expect to receive the hello 2 message
m_client.connect(options);
m_client.subscribe("/topic", 1);
MqttMessage msg = messagesCollector.waitMessage(1);
@@ -572,10 +568,9 @@ public class ServerIntegrationPahoTest {
}
/**
* subscriber connect and subscribe on "topic"
* subscriber disconnects
* publisher connects and send two message "hello1" "hello2" to "topic"
* subscriber connects again and receive "hello1" "hello2"
* subscriber connect and subscribe on "topic" subscriber disconnects publisher connects and
* send two message "hello1" "hello2" to "topic" subscriber connects again and receive "hello1"
* "hello2"
*/
@Test
public void checkQoS2SubscriberDisconnectReceivePersistedPublishes() throws Exception {
@@ -593,9 +588,9 @@ public class ServerIntegrationPahoTest {
m_publisher.publish("topic", "Hello2".getBytes(), 2, true);
m_publisher.disconnect();
//subscriber reconnects
// subscriber reconnects
m_client.connect(options);
//subscriber should receive the 2 messages missed
// subscriber should receive the 2 messages missed
MqttMessage msg = messagesCollector.waitMessage(1);
assertNotNull(msg);
assertEquals("Hello1", new String(msg.getPayload()));
@@ -605,9 +600,8 @@ public class ServerIntegrationPahoTest {
}
/**
* subscriber connect and subscribe on "a/b" QoS 1 and "a/+" QoS 2
* publisher connects and send a message "hello" on "a/b"
* subscriber must receive only a single message not twice
* subscriber connect and subscribe on "a/b" QoS 1 and "a/+" QoS 2 publisher connects and send a
* message "hello" on "a/b" subscriber must receive only a single message not twice
*/
@Test
public void checkSinglePublishOnOverlappingSubscriptions() throws Exception {
@@ -622,27 +616,27 @@ public class ServerIntegrationPahoTest {
m_client.subscribe("a/+", 2);
m_client.subscribe("a/b", 1);
//force the publisher to send
// force the publisher to send
m_publisher.publish("a/b", "Hello world MQTT!!".getBytes(), 1, false);
//reconnect and expect to receive the hello 2 message
// reconnect and expect to receive the hello 2 message
MqttMessage msg = messagesCollector.waitMessage(1);
assertEquals("Hello world MQTT!!", new String(msg.getPayload()));
//try to listen a second publish
// try to listen a second publish
msg = messagesCollector.waitMessage(1);
assertNull(msg);
}
protected MqttClient createClient(String clientName, String storeSuffix) throws MqttException {
return createClient(clientName, storeSuffix, null);
}
protected MqttClient createClient(String clientName, String storeSuffix, MessageCollector cb) throws MqttException {
String tmpDir = System.getProperty("java.io.tmpdir");
//clientX connect and subscribe to /topic QoS2
MqttClientPersistence dsClient = new MqttDefaultFilePersistence(tmpDir + File.separator + storeSuffix + clientName);
// clientX connect and subscribe to /topic QoS2
MqttClientPersistence dsClient = new MqttDefaultFilePersistence(
tmpDir + File.separator + storeSuffix + clientName);
MqttClient client = new MqttClient("tcp://localhost:1883", clientName, dsClient);
if (cb != null) {
client.setCallback(cb);
@@ -13,8 +13,8 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
package io.moquette.server;
import io.moquette.server.config.IConfig;
import io.moquette.server.config.MemoryConfig;
@@ -29,11 +29,9 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
/**
@@ -41,13 +39,13 @@ import static org.junit.Assert.assertEquals;
* @author andrea
*/
public class ServerIntegrationQoSValidationTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationPahoTest.class);
Server m_server;
static MqttClientPersistence s_subDataStore;
static MqttClientPersistence s_pubDataStore;
IMqttClient m_subscriber;
IMqttClient m_publisher;
MessageCollector m_callback;
@@ -75,7 +73,7 @@ public class ServerIntegrationQoSValidationTest {
m_callback = new MessageCollector();
m_subscriber.setCallback(m_callback);
m_subscriber.connect();
m_publisher = new MqttClient("tcp://localhost:1883", "Publisher", s_pubDataStore);
m_publisher.connect();
}
@@ -85,7 +83,7 @@ public class ServerIntegrationQoSValidationTest {
if (m_publisher.isConnected()) {
m_publisher.disconnect();
}
if (m_subscriber.isConnected()) {
m_subscriber.disconnect();
}
@@ -93,104 +91,104 @@ public class ServerIntegrationQoSValidationTest {
m_server.stopServer();
IntegrationUtils.cleanPersistenceFile(m_config);
}
@Test
public void checkSubscriberQoS0ReceiveQoS0publishes() throws Exception {
LOG.info("*** checkSubscriberQoS0ReceiveQoS0publishes ***");
m_subscriber.subscribe("/topic", 0);
m_publisher.publish("/topic", "Hello world MQTT QoS0".getBytes(), 0, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS0", message.toString());
assertEquals(0, message.getQos());
}
@Test
public void checkSubscriberQoS0ReceiveQoS1publishes_downgrade() throws Exception {
LOG.info("*** checkSubscriberQoS0ReceiveQoS1publishes_downgrade ***");
m_subscriber.subscribe("/topic", 0);
m_publisher.publish("/topic", "Hello world MQTT QoS1".getBytes(), 1, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS1", message.toString());
assertEquals(0, message.getQos());
}
@Test
public void checkSubscriberQoS0ReceiveQoS2publishes_downgrade() throws Exception {
LOG.info("*** checkSubscriberQoS0ReceiveQoS2publishes_downgrade ***");
m_subscriber.subscribe("/topic", 0);
m_publisher.publish("/topic", "Hello world MQTT QoS2".getBytes(), 2, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS2", message.toString());
assertEquals(0, message.getQos());
}
@Test
public void checkSubscriberQoS1ReceiveQoS0publishes() throws Exception {
LOG.info("*** checkSubscriberQoS1ReceiveQoS0publishes ***");
m_subscriber.subscribe("/topic", 1);
m_publisher.publish("/topic", "Hello world MQTT QoS0".getBytes(), 0, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS0", message.toString());
assertEquals(0, message.getQos());
}
@Test
public void checkSubscriberQoS1ReceiveQoS1publishes() throws Exception {
LOG.info("*** checkSubscriberQoS1ReceiveQoS1publishes ***");
m_subscriber.subscribe("/topic", 1);
m_publisher.publish("/topic", "Hello world MQTT QoS1".getBytes(), 1, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS1", message.toString());
assertEquals(1, message.getQos());
}
@Test
public void checkSubscriberQoS1ReceiveQoS2publishes_downgrade() throws Exception {
LOG.info("*** checkSubscriberQoS1ReceiveQoS2publishes_downgrade ***");
m_subscriber.subscribe("/topic", 1);
m_publisher.publish("/topic", "Hello world MQTT QoS2".getBytes(), 2, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS2", message.toString());
assertEquals(1, message.getQos());
}
@Test
public void checkSubscriberQoS2ReceiveQoS0publishes() throws Exception {
LOG.info("*** checkSubscriberQoS2ReceiveQoS0publishes ***");
m_subscriber.subscribe("/topic", 2);
m_publisher.publish("/topic", "Hello world MQTT QoS2".getBytes(), 0, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS2", message.toString());
assertEquals(0, message.getQos());
}
@Test
public void checkSubscriberQoS2ReceiveQoS1publishes() throws Exception {
LOG.info("*** checkSubscriberQoS2ReceiveQoS1publishes ***");
m_subscriber.subscribe("/topic", 2);
m_publisher.publish("/topic", "Hello world MQTT QoS2".getBytes(), 1, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS2", message.toString());
assertEquals(1, message.getQos());
}
@Test
public void checkSubscriberQoS2ReceiveQoS2publishes() throws Exception {
LOG.info("*** checkSubscriberQoS2ReceiveQoS2publishes ***");
m_subscriber.subscribe("/topic", 2);
m_publisher.publish("/topic", "Hello world MQTT QoS2".getBytes(), 2, false);
MqttMessage message = m_callback.waitMessage(1);
assertEquals("Hello world MQTT QoS2", message.toString());
assertEquals(2, message.getQos());
}
}
@@ -13,10 +13,10 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import static org.junit.Assert.assertFalse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -27,13 +27,11 @@ import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Properties;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import io.moquette.BrokerConstants;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -50,46 +48,49 @@ import org.slf4j.LoggerFactory;
/**
* Check that Moquette could also handle SSL with client authentication.
*
*
* <p>
* Create certificates needed for client authentication
*
* <pre>
* # generate server certificate chain (valid for 10000 days)
* keytool -genkeypair -alias signedtestserver -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000
* keytool -genkeypair -alias signedtestserver -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000
* keytool -genkeypair -alias signedtestserver_sub -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv
*
* # sign server subcertificate with server certificate (valid for 10000 days)
*
* # sign server subcertificate with server certificate (valid for 10000 days)
* keytool -certreq -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
* keytool -gencert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
* keytool -importcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv
*
* keytool -gencert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
* keytool -importcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv
*
* # generate client keypair
* keytool -genkeypair -alias signedtestclient -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
*
* # create signed client certificate with server subcertificate and import to client keystore (valid for 10000 days)
*
* # create signed client certificate with server subcertificate and import to client keystore (valid for 10000 days)
* keytool -certreq -alias signedtestclient -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd | \
* keytool -gencert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
* keytool -importcert -alias signedtestclient -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd -noprompt
*
* keytool -gencert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
* keytool -importcert -alias signedtestclient -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd -noprompt
*
* # import server certificates into signed truststore
* keytool -exportcert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* keytool -exportcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
*
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
*
* # create unsigned client certificate (valid for 10000 days)
* keytool -genkeypair -alias unsignedtestclient -dname cn=moquette.eclipse.org -validity 10000 -keyalg RSA -keysize 2048 -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
*
*
* # import server certificates into unsigned truststore
* keytool -exportcert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* keytool -exportcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
* </pre>
* </p>
*
* @author andrea / konradm
*/
public class ServerIntegrationSSLClientAuthTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationSSLClientAuthTest.class);
Server m_server;
@@ -128,8 +129,8 @@ public class ServerIntegrationSSLClientAuthTest {
startServer();
m_client = new MqttClient("ssl://localhost:8883", "TestClient", s_dataStore);
// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
m_callback = new MessageCollector();
m_client.setCallback(m_callback);
}
@@ -150,12 +151,12 @@ public class ServerIntegrationSSLClientAuthTest {
}
assertFalse(dbFile.exists());
}
@Test
public void checkClientAuthentication() throws Exception {
LOG.info("*** checkClientAuthentication ***");
SSLSocketFactory ssf = configureSSLSocketFactory("signedclientkeystore.jks");
MqttConnectOptions options = new MqttConnectOptions();
options.setSocketFactory(ssf);
m_client.connect(options);
@@ -163,24 +164,24 @@ public class ServerIntegrationSSLClientAuthTest {
m_client.disconnect();
}
@Test (expected = MqttException.class)
@Test(expected = MqttException.class)
public void checkClientAuthenticationFail() throws Exception {
LOG.info("*** checkClientAuthenticationFail ***");
SSLSocketFactory ssf = configureSSLSocketFactory("unsignedclientkeystore.jks");
MqttConnectOptions options = new MqttConnectOptions();
options.setSocketFactory(ssf);
// actual a "Broken pipe" is thrown, this is not very specific.
try {
m_client.connect(options);
}catch(MqttException e)
{
e.printStackTrace();
throw e;
m_client.connect(options);
} catch (MqttException e) {
e.printStackTrace();
throw e;
}
}
private SSLSocketFactory configureSSLSocketFactory(String keystore) throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, IOException, CertificateException, KeyStoreException {
private SSLSocketFactory configureSSLSocketFactory(String keystore) throws KeyManagementException,
NoSuchAlgorithmException, UnrecoverableKeyException, IOException, CertificateException, KeyStoreException {
KeyStore ks = KeyStore.getInstance("JKS");
InputStream jksInputStream = getClass().getClassLoader().getResourceAsStream(keystore);
ks.load(jksInputStream, "passw0rd".toCharArray());
@@ -191,9 +192,9 @@ public class ServerIntegrationSSLClientAuthTest {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = tmf.getTrustManagers();
sc.init(kmf.getKeyManagers(), trustManagers, null);
SSLContext sc = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = tmf.getTrustManagers();
sc.init(kmf.getKeyManagers(), trustManagers, null);
SSLSocketFactory ssf = sc.getSocketFactory();
return ssf;
@@ -13,10 +13,10 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import static org.junit.Assert.assertFalse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -27,13 +27,11 @@ import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Properties;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import io.moquette.BrokerConstants;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -51,10 +49,11 @@ import org.slf4j.LoggerFactory;
/**
* Check that Moquette could also handle SSL.
*
*
* @author andrea
*/
public class ServerIntegrationSSLTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationSSLTest.class);
Server m_server;
@@ -92,8 +91,8 @@ public class ServerIntegrationSSLTest {
startServer();
m_client = new MqttClient("ssl://localhost:8883", "TestClient", s_dataStore);
// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
m_callback = new MessageCollector();
m_client.setCallback(m_callback);
}
@@ -114,12 +113,12 @@ public class ServerIntegrationSSLTest {
}
assertFalse(dbFile.exists());
}
@Test
public void checkSupportSSL() throws Exception {
LOG.info("*** checkSupportSSL ***");
SSLSocketFactory ssf = configureSSLSocketFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setSocketFactory(ssf);
m_client.connect(options);
@@ -131,38 +130,38 @@ public class ServerIntegrationSSLTest {
public void checkSupportSSLForMultipleClient() throws Exception {
LOG.info("*** checkSupportSSLForMultipleClient ***");
SSLSocketFactory ssf = configureSSLSocketFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setSocketFactory(ssf);
m_client.connect(options);
m_client.subscribe("/topic", 0);
MqttClient secondClient = new MqttClient("ssl://localhost:8883", "secondTestClient", new MemoryPersistence());
MqttConnectOptions secondClientOptions = new MqttConnectOptions();
secondClientOptions.setSocketFactory(ssf);
secondClient.connect(secondClientOptions);
secondClient.publish("/topic", new MqttMessage("message".getBytes()));
secondClient.disconnect();
m_client.disconnect();
}
/**
* keystore generated into test/resources with command:
*
* keytool -keystore clientkeystore.jks -alias testclient -genkey -keyalg RSA
* -> mandatory to put the name surname
* -> password is passw0rd
* -> type yes at the end
*
* to generate the crt file from the keystore
* -- keytool -certreq -alias testclient -keystore clientkeystore.jks -file testclient.csr
*
*
* keytool -keystore clientkeystore.jks -alias testclient -genkey -keyalg RSA -> mandatory to
* put the name surname -> password is passw0rd -> type yes at the end
*
* to generate the crt file from the keystore -- keytool -certreq -alias testclient -keystore
* clientkeystore.jks -file testclient.csr
*
* keytool -export -alias testclient -keystore clientkeystore.jks -file testclient.crt
*
* to import an existing certificate:
* keytool -keystore clientkeystore.jks -import -alias testclient -file testclient.crt -trustcacerts
*
* to import an existing certificate: keytool -keystore clientkeystore.jks -import -alias
* testclient -file testclient.crt -trustcacerts
*/
private SSLSocketFactory configureSSLSocketFactory() throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, IOException, CertificateException, KeyStoreException {
private SSLSocketFactory configureSSLSocketFactory() throws KeyManagementException, NoSuchAlgorithmException,
UnrecoverableKeyException, IOException, CertificateException, KeyStoreException {
KeyStore ks = KeyStore.getInstance("JKS");
InputStream jksInputStream = getClass().getClassLoader().getResourceAsStream("clientkeystore.jks");
ks.load(jksInputStream, "passw0rd".toCharArray());
@@ -173,9 +172,9 @@ public class ServerIntegrationSSLTest {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = tmf.getTrustManagers();
sc.init(kmf.getKeyManagers(), trustManagers, null);
SSLContext sc = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = tmf.getTrustManagers();
sc.init(kmf.getKeyManagers(), trustManagers, null);
SSLSocketFactory ssf = sc.getSocketFactory();
return ssf;
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.BrokerConstants;
@@ -25,21 +26,19 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertTrue;
/**
* Integration test to check the function of Moquette with a WebSocket channel.
*
*
* @author andrea
*/
public class ServerIntegrationWebSocketTest {
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationWebSocketTest.class);
Server m_server;
@@ -49,7 +48,8 @@ public class ServerIntegrationWebSocketTest {
protected void startServer() throws IOException {
m_server = new Server();
final Properties configProps = IntegrationUtils.prepareTestProperties();
configProps.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
configProps
.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
m_config = new MemoryConfig(configProps);
m_server.startServer(m_config);
}
@@ -63,16 +63,16 @@ public class ServerIntegrationWebSocketTest {
@After
public void tearDown() throws Exception {
client.stop();
m_server.stopServer();
IntegrationUtils.cleanPersistenceFile(m_config);
}
@Test
public void checkPlainConnect() throws Exception {
LOG.info("*** checkPlainConnect ***");
String destUri = "ws://localhost:" + BrokerConstants.WEBSOCKET_PORT + "/mqtt";
MQTTWebSocket socket = new MQTTWebSocket();
client.start();
URI echoUri = new URI(destUri);
@@ -13,6 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import io.moquette.server.config.IConfig;
@@ -26,7 +27,6 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Properties;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
@@ -37,6 +37,7 @@ import static org.junit.Assert.*;
* @author andrea
*/
public class ServerLowlevelMessagesIntegrationTests {
private static final Logger LOG = LoggerFactory.getLogger(ServerLowlevelMessagesIntegrationTests.class);
static MqttClientPersistence s_dataStore;
Server m_server;
@@ -66,7 +67,7 @@ public class ServerLowlevelMessagesIntegrationTests {
public void tearDown() throws Exception {
m_client.close();
LOG.debug("After raw client close");
Thread.sleep(300); //to let the close event pass before server stop event
Thread.sleep(300); // to let the close event pass before server stop event
m_server.stopServer();
LOG.debug("After asked server to stop");
IntegrationUtils.cleanPersistenceFile(m_config);
@@ -74,62 +75,67 @@ public class ServerLowlevelMessagesIntegrationTests {
@Test
public void elapseKeepAliveTime() throws InterruptedException {
int keepAlive = 2; //secs
int keepAlive = 2; // secs
MqttConnectMessage connectMessage = createConnectMessage("FAKECLNT", keepAlive);
/*ConnectMessage connectMessage = new ConnectMessage();
connectMessage.setProtocolVersion((byte) 3);
connectMessage.setClientID("FAKECLNT");
connectMessage.setKeepAlive(keepAlive);*/
/*
* ConnectMessage connectMessage = new ConnectMessage();
* connectMessage.setProtocolVersion((byte) 3); connectMessage.setClientID("FAKECLNT");
* connectMessage.setKeepAlive(keepAlive);
*/
m_client.sendMessage(connectMessage);
//wait 2 times the keepAlive
// wait 2 times the keepAlive
Thread.sleep(keepAlive * 2 * 1000);
assertTrue(m_client.isConnectionLost());
}
private static MqttConnectMessage createConnectMessage(String clientID, int keepAlive) {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnectVariableHeader mqttConnectVariableHeader =
new MqttConnectVariableHeader(
MqttVersion.MQTT_3_1.protocolName(),
MqttVersion.MQTT_3_1.protocolLevel(),
false,
false,
false,
1,
false,
true,
keepAlive);
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
MqttMessageType.CONNECT,
false,
MqttQoS.AT_MOST_ONCE,
false,
0);
MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
MqttVersion.MQTT_3_1.protocolName(),
MqttVersion.MQTT_3_1.protocolLevel(),
false,
false,
false,
1,
false,
true,
keepAlive);
MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(clientID, null, null, null, null);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
}
@Test
public void testWillMessageIsWiredOnClientKeepAliveExpiry() throws Exception {
LOG.info("*** testWillMessageIsWiredOnClientKeepAliveExpiry ***");
String willTestamentTopic = "/will/test";
String willTestamentMsg = "Bye bye";
m_willSubscriber.connect();
m_willSubscriber.subscribe(willTestamentTopic, 0);
m_client.clientId("FAKECLNT").connect(willTestamentTopic, willTestamentMsg);
long connectTime = System.currentTimeMillis();
//but after the 2 KEEP ALIVE timeout expires it gets fired,
//NB it's 1,5 * KEEP_ALIVE so 3 secs and some millis to propagate the message
// but after the 2 KEEP ALIVE timeout expires it gets fired,
// NB it's 1,5 * KEEP_ALIVE so 3 secs and some millis to propagate the message
org.eclipse.paho.client.mqttv3.MqttMessage msg = m_messageCollector.waitMessage(5);
assertNotNull("the will message should be fired after keep alive!", msg);
//the will message hasn't to be received before the elapsing of Keep Alive timeout
assertTrue(System.currentTimeMillis() - connectTime > 3000);
// the will message hasn't to be received before the elapsing of Keep Alive timeout
assertTrue(System.currentTimeMillis() - connectTime > 3000);
assertEquals(willTestamentMsg, new String(msg.getPayload()));
m_willSubscriber.disconnect();
}
@Test
public void testRejectConnectWithEmptyClientID() throws InterruptedException {
LOG.info("*** testRejectConnectWithEmptyClientID ***");
@@ -151,10 +157,10 @@ public class ServerLowlevelMessagesIntegrationTests {
m_willSubscriber.subscribe(willTestamentTopic, 0);
m_client.clientId("FAKECLNT").connect(willTestamentTopic, willTestamentMsg);
//kill will publisher
// kill will publisher
m_client.close();
//Verify will testament is published
// Verify will testament is published
org.eclipse.paho.client.mqttv3.MqttMessage receivedTestament = m_messageCollector.waitMessage(1);
assertEquals(willTestamentMsg, new String(receivedTestament.getPayload()));
m_willSubscriber.disconnect();

Some files were not shown because too many files have changed in this diff Show More