Moved project package from org.eclipse.moquette to io.moquette
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import io.moquette.spi.impl.security.IAuthenticator;
|
||||
import io.moquette.spi.impl.security.IAuthorizator;
|
||||
import org.junit.After;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author luca <[email protected]>
|
||||
*/
|
||||
public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationClassLoaderTest.class);
|
||||
|
||||
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();
|
||||
File dbFile = new File(m_config.getProperty(io.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME));
|
||||
if (dbFile.exists()) {
|
||||
dbFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthenticator() throws Exception {
|
||||
Properties props = new Properties(IntegrationUtils.prepareTestPropeties());
|
||||
props.setProperty(Constants.AUTHENTICATOR_CLASS_NAME, "io.moquette.server.ConfigurationClassLoaderTest");
|
||||
startServer(props);
|
||||
assertTrue(true);
|
||||
m_server.stopServer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizator() throws Exception {
|
||||
Properties props = new Properties(IntegrationUtils.prepareTestPropeties());
|
||||
props.setProperty(Constants.AUTHORIZATOR_CLASS_NAME, "io.moquette.server.ConfigurationClassLoaderTest");
|
||||
startServer(props);
|
||||
assertTrue(true);
|
||||
m_server.stopServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkValid(String username, byte[] password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(String topic, String user, String client) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(String topic, String user, String client) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* 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.commons.Constants.DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME;
|
||||
import static io.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
/**
|
||||
* Used to carry integration configurations.
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
|
||||
public static Properties prepareTestPropeties() {
|
||||
Properties testProperties = new Properties();
|
||||
testProperties.put(PERSISTENT_STORE_PROPERTY_NAME, IntegrationUtils.localMapDBPath());
|
||||
return testProperties;
|
||||
}
|
||||
|
||||
public static void cleanPersistenceFile(IConfig config) {
|
||||
String fileName = config.getProperty(PERSISTENT_STORE_PROPERTY_NAME);
|
||||
cleanPersistenceFile(fileName);
|
||||
}
|
||||
|
||||
public static void cleanPersistenceFile(String fileName) {
|
||||
File dbFile = new File(fileName);
|
||||
if (dbFile.exists()) {
|
||||
dbFile.delete();
|
||||
new File(fileName + ".p").delete();
|
||||
new File(fileName + ".t").delete();
|
||||
}
|
||||
assertFalse(dbFile.exists());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
@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
|
||||
});
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import org.fusesource.mqtt.client.*;
|
||||
import org.junit.After;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ServerIntegrationFuseTest {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationPahoTest.class);
|
||||
|
||||
Server m_server;
|
||||
MQTT m_mqtt;
|
||||
BlockingConnection m_subscriber;
|
||||
BlockingConnection m_publisher;
|
||||
IConfig m_config;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
|
||||
m_mqtt = new MQTT();
|
||||
m_mqtt.setHost("localhost", 1883);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (m_subscriber != null) {
|
||||
m_subscriber.disconnect();
|
||||
}
|
||||
|
||||
if (m_publisher != null) {
|
||||
m_publisher.disconnect();
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectWithCredentials() throws Exception {
|
||||
LOG.info("*** connectWithCredetials ***");
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
mqtt.setUserName("testuser");
|
||||
mqtt.setPassword("passwd");
|
||||
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWillTestamentIsPublishedOnConnectionKill_noRetain() throws Exception {
|
||||
LOG.info("checkWillTestamentIsPublishedOnConnectionKill");
|
||||
|
||||
String willTestamentTopic = "/will/test";
|
||||
String willTestamentMsg = "Bye bye";
|
||||
|
||||
MQTT mqtt = new MQTT();
|
||||
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.setCleanSession(false);
|
||||
m_mqtt.setClientId("Subscriber");
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
// Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_LEAST_ONCE)};
|
||||
Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_MOST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
|
||||
//Exercise, kill the publisher connection
|
||||
m_publisher.kill();
|
||||
|
||||
//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();
|
||||
assertEquals(willTestamentMsg, new String(msg.getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() throws Exception {
|
||||
LOG.info("*** checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1 ***");
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
|
||||
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("/topic", QoS.AT_LEAST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
|
||||
//force the publisher to send
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!-1".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//read the first message and drop the connection
|
||||
Message msg = m_subscriber.receive();
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!-1", new String(msg.getPayload()));
|
||||
m_subscriber.disconnect();
|
||||
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!-2".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!-3".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//reconnect and expect to receive the hello 2 message
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
//topics = new Topic[]{new Topic("/topic", QoS.AT_LEAST_ONCE)};
|
||||
//m_subscriber.subscribe(topics);
|
||||
msg = m_subscriber.receive();
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!-2", new String(msg.getPayload()));
|
||||
|
||||
msg = m_subscriber.receive();
|
||||
assertEquals("Hello world MQTT!!-3", new String(msg.getPayload()));
|
||||
msg.ack();
|
||||
m_subscriber.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkReplayStoredPublish_forNoCleanSession_qos1() throws Exception {
|
||||
LOG.info("*** checkReplayStoredPublish_forNoCleanSession_qos1 ***");
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
|
||||
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("/topic", QoS.AT_LEAST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
m_subscriber.disconnect();
|
||||
|
||||
//force the publisher to send
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!-1".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//reconnect and expect to receive the hello 2 message
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
topics = new Topic[]{new Topic("/topic", QoS.AT_LEAST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
Message msg = m_subscriber.receive(1, TimeUnit.SECONDS);
|
||||
assertNotNull(msg);
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!-1", new String(msg.getPayload()));
|
||||
// m_subscriber.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
LOG.info("*** checkQoS2SubscriberDisconnectReceivePersistedPublishes ***");
|
||||
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("topic", QoS.EXACTLY_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
m_subscriber.disconnect();
|
||||
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
m_publisher.publish("topic", "Hello1".getBytes(), QoS.EXACTLY_ONCE, true);
|
||||
m_publisher.publish("topic", "Hello2".getBytes(), QoS.EXACTLY_ONCE, true);
|
||||
m_publisher.disconnect();
|
||||
|
||||
//subscriber reconnects
|
||||
m_mqtt = new MQTT();
|
||||
m_mqtt.setHost("localhost", 1883);
|
||||
m_mqtt.setClientId("Subscriber");
|
||||
m_mqtt.setCleanSession(false);
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
//subscriber should receive the 2 messages missed
|
||||
Message msg = m_subscriber.receive(1, TimeUnit.SECONDS);
|
||||
assertNotNull(msg);
|
||||
msg.ack();
|
||||
assertEquals("Hello1", new String(msg.getPayload()));
|
||||
msg = m_subscriber.receive(1, TimeUnit.SECONDS);
|
||||
assertNotNull(msg);
|
||||
msg.ack();
|
||||
assertEquals("Hello2", new String(msg.getPayload()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
LOG.info("*** checkSinglePublishOnOverlappingSubscriptions ***");
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
|
||||
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("a/+", QoS.EXACTLY_ONCE),
|
||||
new Topic("a/b", QoS.AT_LEAST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
|
||||
//force the publisher to send
|
||||
m_publisher.publish("a/b", "Hello world MQTT!!".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//reconnect and expect to receive the hello 2 message
|
||||
Message msg = m_subscriber.receive(1, TimeUnit.SECONDS);
|
||||
assertNotNull(msg);
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!", new String(msg.getPayload()));
|
||||
//try to listen a second publish
|
||||
msg = m_subscriber.receive(1, TimeUnit.SECONDS);
|
||||
assertNull(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
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 ServerIntegrationPahoTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationPahoTest.class);
|
||||
|
||||
static MqttClientPersistence s_dataStore;
|
||||
static MqttClientPersistence s_pubDataStore;
|
||||
|
||||
Server m_server;
|
||||
IMqttClient m_client;
|
||||
TestCallback m_callback;
|
||||
IConfig m_config;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeTests() {
|
||||
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
s_dataStore = new MqttDefaultFilePersistence(tmpDir);
|
||||
s_pubDataStore = new MqttDefaultFilePersistence(tmpDir + File.separator + "publisher");
|
||||
}
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
// File dbFile = new File(dbPath);
|
||||
// assertFalse(String.format("The DB storagefile %s already exists", dbPath), dbFile.exists());
|
||||
|
||||
startServer();
|
||||
|
||||
m_client = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
|
||||
m_callback = new TestCallback();
|
||||
m_client.setCallback(m_callback);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (m_client.isConnected()) {
|
||||
m_client.disconnect();
|
||||
}
|
||||
|
||||
stopServer();
|
||||
}
|
||||
|
||||
private void stopServer() {
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribe() throws Exception {
|
||||
LOG.info("*** testSubscribe ***");
|
||||
m_client.connect();
|
||||
m_client.subscribe("/topic", 0);
|
||||
|
||||
MqttMessage message = new MqttMessage("Hello world!!".getBytes());
|
||||
message.setQos(0);
|
||||
message.setRetained(false);
|
||||
m_client.publish("/topic", message);
|
||||
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCleanSession_maintainClientSubscriptions() throws Exception {
|
||||
LOG.info("*** testCleanSession_maintainClientSubscriptions ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.disconnect();
|
||||
|
||||
//reconnect and publish
|
||||
MqttClient anotherClient = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
|
||||
m_callback = new TestCallback();
|
||||
anotherClient.setCallback(m_callback);
|
||||
anotherClient.connect(options);
|
||||
anotherClient.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCleanSession_maintainClientSubscriptions_againstClientDestruction() throws Exception {
|
||||
LOG.info("*** testCleanSession_maintainClientSubscriptions_againstClientDestruction ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.disconnect();
|
||||
|
||||
//reconnect and publish
|
||||
m_client.connect(options);
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that after a client has connected with clean session false, subscribed
|
||||
* to some topic and exited, if it reconnect with clean session true, the m_server
|
||||
* correctly cleanup every previous subscription
|
||||
*/
|
||||
@Test
|
||||
public void testCleanSession_correctlyClientSubscriptions() throws Exception {
|
||||
LOG.info("*** testCleanSession_correctlyClientSubscriptions ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.disconnect();
|
||||
|
||||
//reconnect and publish
|
||||
m_client.connect();
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertEquals("Test my payload", new String(m_callback.getMessage(false).getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanSession_maintainClientSubscriptions_withServerRestart() throws Exception {
|
||||
LOG.info("*** testCleanSession_maintainClientSubscriptions_withServerRestart ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.disconnect();
|
||||
|
||||
m_server.stopServer();
|
||||
|
||||
m_server.startServer(IntegrationUtils.prepareTestPropeties());
|
||||
|
||||
//reconnect and publish
|
||||
m_client.connect(options);
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetain_maintainMessage_againstClientDestruction() throws Exception {
|
||||
LOG.info("*** testRetain_maintainMessage_againstClientDestruction ***");
|
||||
m_client.connect();
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 1, true);
|
||||
m_client.disconnect();
|
||||
|
||||
//reconnect and publish
|
||||
m_client.connect();
|
||||
m_client.subscribe("/topic", 0);
|
||||
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_do_not_notify_anymore_same_session() throws Exception {
|
||||
LOG.info("*** testUnsubscribe_do_not_notify_anymore_same_session ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
//m_client.disconnect();
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
|
||||
m_client.unsubscribe("/topic");
|
||||
m_callback.reinit();
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertNull(m_callback.getMessage(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_do_not_notify_anymore_new_session() throws Exception {
|
||||
LOG.info("*** testUnsubscribe_do_not_notify_anymore_new_session ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
//m_client.disconnect();
|
||||
assertEquals("/topic", m_callback.getTopic());
|
||||
|
||||
m_client.unsubscribe("/topic");
|
||||
m_client.disconnect();
|
||||
|
||||
m_callback.reinit();
|
||||
m_client.connect(options);
|
||||
m_client.publish("/topic", "Test my payload".getBytes(), 0, false);
|
||||
|
||||
assertNull(m_callback.getMessage(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublishWithQoS1() throws Exception {
|
||||
LOG.info("*** testPublishWithQoS1 ***");
|
||||
m_client.connect();
|
||||
m_client.subscribe("/topic", 1);
|
||||
m_client.publish("/topic", "Hello MQTT".getBytes(), 1, false);
|
||||
m_client.disconnect();
|
||||
|
||||
//reconnect and publish
|
||||
MqttMessage message = m_callback.getMessage(true);
|
||||
assertEquals("Hello MQTT", message.toString());
|
||||
assertEquals(1, message.getQos());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublishWithQoS1_notCleanSession() throws Exception {
|
||||
LOG.info("*** testPublishWithQoS1_notCleanSession ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 1);
|
||||
m_client.disconnect();
|
||||
|
||||
//publish a QoS 1 message another client publish a message on the topic
|
||||
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 1);
|
||||
|
||||
m_client.connect(options);
|
||||
|
||||
assertEquals("Hello MQTT", m_callback.getMessage(true).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession() throws Exception {
|
||||
LOG.info("*** checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 1);
|
||||
m_client.disconnect();
|
||||
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 1);
|
||||
|
||||
//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
|
||||
MqttMessage message = m_callback.getMessage(true);
|
||||
assertNotNull(message);
|
||||
assertEquals("Hello MQTT", message.toString());
|
||||
}
|
||||
|
||||
private void publishFromAnotherClient(String topic, byte[] payload, int qos) throws Exception {
|
||||
IMqttClient anotherClient = new MqttClient("tcp://localhost:1883", "TestClientPUB", s_pubDataStore);
|
||||
anotherClient.connect();
|
||||
anotherClient.publish(topic, payload, qos, false);
|
||||
anotherClient.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublishWithQoS2() throws Exception {
|
||||
LOG.info("*** testPublishWithQoS2 ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 2);
|
||||
m_client.disconnect();
|
||||
|
||||
//publish a QoS 1 message another client publish a message on the topic
|
||||
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 2);
|
||||
m_callback.reinit();
|
||||
m_client.connect(options);
|
||||
|
||||
MqttMessage message = m_callback.getMessage(true);
|
||||
assertEquals("Hello MQTT", message.toString());
|
||||
assertEquals(2, message.getQos());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublishReceiveWithQoS2() throws Exception {
|
||||
LOG.info("*** testPublishReceiveWithQoS2 ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 2);
|
||||
m_client.disconnect();
|
||||
|
||||
//publish a QoS 2 message another client publish a message on the topic
|
||||
publishFromAnotherClient("/topic", "Hello MQTT".getBytes(), 2);
|
||||
m_callback.reinit();
|
||||
m_client.connect(options);
|
||||
|
||||
assertNotNull(m_callback);
|
||||
MqttMessage message = m_callback.getMessage(true);
|
||||
assertNotNull(message);
|
||||
assertEquals("Hello MQTT", message.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void avoidMultipleNotificationsAfterMultipleReconnection_cleanSessionFalseQoS1() throws Exception {
|
||||
LOG.info("*** avoidMultipleNotificationsAfterMultipleReconnection_cleanSessionFalseQoS1, issue #16 ***");
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setCleanSession(false);
|
||||
m_client.connect(options);
|
||||
m_client.subscribe("/topic", 1);
|
||||
m_client.disconnect();
|
||||
|
||||
publishFromAnotherClient("/topic", "Hello MQTT 1".getBytes(), 1);
|
||||
m_callback.reinit();
|
||||
m_client.connect(options);
|
||||
|
||||
assertNotNull(m_callback);
|
||||
MqttMessage message = m_callback.getMessage(true);
|
||||
assertNotNull(message);
|
||||
assertEquals("Hello MQTT 1", message.toString());
|
||||
m_client.disconnect();
|
||||
|
||||
//publish other message
|
||||
publishFromAnotherClient("/topic", "Hello MQTT 2".getBytes(), 1);
|
||||
|
||||
//reconnect the second time
|
||||
m_callback.reinit();
|
||||
m_client.connect(options);
|
||||
assertNotNull(m_callback);
|
||||
message = m_callback.getMessage(true);
|
||||
assertNotNull(message);
|
||||
assertEquals("Hello MQTT 2", message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
LOG.info("*** checkSubscribersGetCorrectQosNotifications ***");
|
||||
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
|
||||
MqttClientPersistence dsSubscriberA = new MqttDefaultFilePersistence(tmpDir + File.separator + "subscriberA");
|
||||
|
||||
MqttClient subscriberA = new MqttClient("tcp://localhost:1883", "SubscriberA", dsSubscriberA);
|
||||
TestCallback cbSubscriberA = new TestCallback();
|
||||
subscriberA.setCallback(cbSubscriberA);
|
||||
subscriberA.connect();
|
||||
subscriberA.subscribe("a/b", 1);
|
||||
|
||||
MqttClientPersistence dsSubscriberB = new MqttDefaultFilePersistence(tmpDir + File.separator + "subscriberB");
|
||||
|
||||
MqttClient subscriberB = new MqttClient("tcp://localhost:1883", "SubscriberB", dsSubscriberB);
|
||||
TestCallback cbSubscriberB = new TestCallback();
|
||||
subscriberB.setCallback(cbSubscriberB);
|
||||
subscriberB.connect();
|
||||
subscriberB.subscribe("a/+", 2);
|
||||
|
||||
|
||||
m_client.connect();
|
||||
m_client.publish("a/b", "Hello world MQTT!!".getBytes(), 2, false);
|
||||
|
||||
MqttMessage messageOnA = cbSubscriberA.getMessage(true);
|
||||
assertEquals("Hello world MQTT!!", new String(messageOnA.getPayload()));
|
||||
assertEquals(1, messageOnA.getQos());
|
||||
subscriberA.disconnect();
|
||||
|
||||
MqttMessage messageOnB = cbSubscriberB.getMessage(true);
|
||||
assertEquals("Hello world MQTT!!", new String(messageOnB.getPayload()));
|
||||
assertEquals(2, messageOnB.getQos());
|
||||
subscriberB.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
TestCallback m_callback;
|
||||
IConfig m_config;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeTests() {
|
||||
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
s_subDataStore = new MqttDefaultFilePersistence(tmpDir + File.separator + "subscriber");
|
||||
s_pubDataStore = new MqttDefaultFilePersistence(tmpDir + File.separator + "publisher");
|
||||
}
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
|
||||
m_subscriber = new MqttClient("tcp://localhost:1883", "Subscriber", s_subDataStore);
|
||||
m_callback = new TestCallback();
|
||||
m_subscriber.setCallback(m_callback);
|
||||
m_subscriber.connect();
|
||||
|
||||
m_publisher = new MqttClient("tcp://localhost:1883", "Publisher", s_pubDataStore);
|
||||
m_publisher.connect();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (m_publisher.isConnected()) {
|
||||
m_publisher.disconnect();
|
||||
}
|
||||
|
||||
if (m_subscriber.isConnected()) {
|
||||
m_subscriber.disconnect();
|
||||
}
|
||||
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
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.getMessage(true);
|
||||
assertEquals("Hello world MQTT QoS2", message.toString());
|
||||
assertEquals(2, message.getQos());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* 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;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
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.commons.Constants;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
|
||||
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
static MqttClientPersistence s_dataStore;
|
||||
|
||||
IMqttClient m_client;
|
||||
TestCallback m_callback;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeTests() {
|
||||
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
s_dataStore = new MqttDefaultFilePersistence(tmpDir);
|
||||
}
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
String file = getClass().getResource("/").getPath();
|
||||
System.setProperty("moquette.path", file);
|
||||
m_server = new Server();
|
||||
|
||||
Properties sslProps = new Properties();
|
||||
sslProps.put(io.moquette.commons.Constants.SSL_PORT_PROPERTY_NAME, "8883");
|
||||
sslProps.put(Constants.JKS_PATH_PROPERTY_NAME, "serverkeystore.jks");
|
||||
sslProps.put(Constants.KEY_STORE_PASSWORD_PROPERTY_NAME, "passw0rdsrv");
|
||||
sslProps.put(Constants.KEY_MANAGER_PASSWORD_PROPERTY_NAME, "passw0rdsrv");
|
||||
sslProps.put(Constants.PERSISTENT_STORE_PROPERTY_NAME, IntegrationUtils.localMapDBPath());
|
||||
m_server.startServer(sslProps);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
File dbFile = new File(dbPath);
|
||||
assertFalse(String.format("The DB storagefile %s already exists", dbPath), dbFile.exists());
|
||||
|
||||
startServer();
|
||||
|
||||
m_client = new MqttClient("ssl://localhost:8883", "TestClient", s_dataStore);
|
||||
// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
|
||||
|
||||
m_callback = new TestCallback();
|
||||
m_client.setCallback(m_callback);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (m_client != null && m_client.isConnected()) {
|
||||
m_client.disconnect();
|
||||
}
|
||||
|
||||
if (m_server != null) {
|
||||
m_server.stopServer();
|
||||
}
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
File dbFile = new File(dbPath);
|
||||
if (dbFile.exists()) {
|
||||
dbFile.delete();
|
||||
}
|
||||
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);
|
||||
m_client.subscribe("/topic", 0);
|
||||
m_client.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
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 -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
|
||||
*/
|
||||
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());
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, "passw0rd".toCharArray());
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
sc.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
|
||||
SSLSocketFactory ssf = sc.getSocketFactory();
|
||||
return ssf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import org.junit.After;
|
||||
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;
|
||||
WebSocketClient client;
|
||||
IConfig m_config;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
client = new WebSocketClient();
|
||||
}
|
||||
|
||||
@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:" + io.moquette.commons.Constants.WEBSOCKET_PORT;
|
||||
|
||||
MQTTWebSocket socket = new MQTTWebSocket();
|
||||
client.start();
|
||||
URI echoUri = new URI(destUri);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
client.connect(socket, echoUri, request);
|
||||
LOG.info("Connecting to : %s", echoUri);
|
||||
boolean connected = socket.awaitConnected(4, TimeUnit.SECONDS);
|
||||
|
||||
assertTrue(connected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.proto.messages.ConnectMessage;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
import io.moquette.proto.messages.ConnAckMessage;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import io.moquette.testclient.Client;
|
||||
import org.fusesource.mqtt.client.*;
|
||||
import org.junit.After;
|
||||
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.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ServerLowlevelMessagesIntegrationTests {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerLowlevelMessagesIntegrationTests.class);
|
||||
|
||||
Server m_server;
|
||||
Client m_client;
|
||||
MQTT m_subscriberDef;
|
||||
IConfig m_config;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
m_client = new Client("localhost");
|
||||
m_subscriberDef = new MQTT();
|
||||
m_subscriberDef.setHost("localhost", 1883);
|
||||
m_subscriberDef.setClientId("Subscriber");
|
||||
}
|
||||
|
||||
@After
|
||||
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
|
||||
m_server.stopServer();
|
||||
LOG.debug("After asked server to stop");
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void elapseKeepAliveTime() throws InterruptedException {
|
||||
int keepAlive = 2; //secs
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setClientID("FAKECLNT");
|
||||
connectMessage.setKeepAlive(keepAlive);
|
||||
m_client.sendMessage(connectMessage);
|
||||
|
||||
//wait 2 times the keepAlive
|
||||
Thread.sleep(keepAlive * 2 * 1000);
|
||||
|
||||
assertTrue(m_client.isConnectionLost());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWillMessageIsWiredOnClientKeepAliveExpiry() throws Exception {
|
||||
LOG.info("*** checkWillMessageIsWiredOnClientKeepAliveExpiry ***");
|
||||
String willTestamentTopic = "/will/test";
|
||||
String willTestamentMsg = "Bye bye";
|
||||
|
||||
BlockingConnection willSubscriber = m_subscriberDef.blockingConnection();
|
||||
willSubscriber.connect();
|
||||
Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_MOST_ONCE)};
|
||||
willSubscriber.subscribe(topics);
|
||||
|
||||
int keepAlive = 2; //secs
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setClientID("FAKECLNT");
|
||||
connectMessage.setKeepAlive(keepAlive);
|
||||
connectMessage.setWillFlag(true);
|
||||
connectMessage.setWillMessage(willTestamentMsg.getBytes());
|
||||
connectMessage.setWillTopic(willTestamentTopic);
|
||||
connectMessage.setWillQos(QOSType.MOST_ONE.byteValue());
|
||||
|
||||
//Execute
|
||||
m_client.sendMessage(connectMessage);
|
||||
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
|
||||
Message msg = willSubscriber.receive(3300, TimeUnit.MILLISECONDS);
|
||||
long willMessageReceiveTime = System.currentTimeMillis();
|
||||
if (msg == null) {
|
||||
LOG.warn("testament message is null");
|
||||
}
|
||||
assertNotNull("the will message should be fired after keep alive!", msg);
|
||||
msg.ack();
|
||||
//the will message hasn't to be received before the elapsing of Keep Alive timeout
|
||||
assertTrue(willMessageReceiveTime - connectTime > 3000);
|
||||
|
||||
assertEquals(willTestamentMsg, new String(msg.getPayload()));
|
||||
willSubscriber.disconnect();
|
||||
}
|
||||
|
||||
AbstractMessage receivedMsg;
|
||||
|
||||
@Test
|
||||
public void checkRejectConnectWithEmptyClientID() throws InterruptedException {
|
||||
LOG.info("*** checkRejectConnectWithEmptyClientID ***");
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
m_client.setCallback(new Client.ICallback() {
|
||||
|
||||
public void call(AbstractMessage msg) {
|
||||
receivedMsg = msg;
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
int keepAlive = 2; //secs
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProtocolVersion((byte) 4);
|
||||
connectMessage.setClientID("");
|
||||
connectMessage.setKeepAlive(keepAlive);
|
||||
connectMessage.setWillFlag(false);
|
||||
connectMessage.setWillQos(QOSType.MOST_ONE.byteValue());
|
||||
|
||||
//Execute
|
||||
m_client.sendMessage(connectMessage);
|
||||
|
||||
latch.await(200, TimeUnit.MILLISECONDS);
|
||||
|
||||
assertTrue(receivedMsg instanceof ConnAckMessage);
|
||||
ConnAckMessage connAck = (ConnAckMessage) receivedMsg;
|
||||
assertEquals(ConnAckMessage.IDENTIFIER_REJECTED, connAck.getReturnCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import org.fusesource.mqtt.client.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ServerRestartIntegrationTest {
|
||||
|
||||
Server m_server;
|
||||
MQTT m_mqtt;
|
||||
BlockingConnection m_subscriber;
|
||||
BlockingConnection m_publisher;
|
||||
IConfig m_config;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
final Properties configProps = IntegrationUtils.prepareTestPropeties();
|
||||
m_config = new MemoryConfig(configProps);
|
||||
m_server.startServer(m_config);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
|
||||
m_mqtt = new MQTT();
|
||||
|
||||
m_mqtt.setHost("localhost", 1883);
|
||||
m_mqtt.setCleanSession(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (m_subscriber != null) {
|
||||
m_subscriber.disconnect();
|
||||
}
|
||||
|
||||
if (m_publisher != null) {
|
||||
m_publisher.disconnect();
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void checkRestartCleanSubscriptionTree() throws Exception {
|
||||
m_mqtt.setClientId("Subscriber");
|
||||
//subscribe to /topic
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
Topic[] topics = new Topic[]{new Topic("/topic", QoS.AT_LEAST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
m_subscriber.disconnect();
|
||||
|
||||
//shutdown the server
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
|
||||
//restart the server
|
||||
m_server.startServer(IntegrationUtils.prepareTestPropeties());
|
||||
|
||||
//reconnect the Subscriber subscribing to the same /topic but different QoS
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
topics = new Topic[]{new Topic("/topic", QoS.EXACTLY_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
|
||||
//should be just one registration so a publisher receive one notification
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//read the messages
|
||||
Message msg = m_subscriber.receive();
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!", new String(msg.getPayload()));
|
||||
//no more messages on the same topic will be received
|
||||
assertNull(m_subscriber.receive(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void checkDontPublishInactiveClientsAfterServerRestart() throws Exception {
|
||||
m_mqtt.setClientId("SubPub");
|
||||
BlockingConnection conn = subscribeAndPublish("/topic");
|
||||
conn.disconnect();
|
||||
|
||||
//shutdown the server
|
||||
m_server.stopServer();
|
||||
|
||||
//restart the server
|
||||
m_server.startServer(IntegrationUtils.prepareTestPropeties());
|
||||
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!".getBytes(), QoS.AT_MOST_ONCE, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientDoesntRemainSubscribedAfterASubscriptionAndServerRestart() throws Exception {
|
||||
m_mqtt.setClientId("Subscriber");
|
||||
//subscribe to /topic
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
Topic[] topics = new Topic[]{new Topic("/topic", QoS.AT_MOST_ONCE)};
|
||||
//subscribe /topic
|
||||
m_subscriber.subscribe(topics);
|
||||
//unsubscribe from /topic
|
||||
m_subscriber.unsubscribe(new String[]{"/topic"});
|
||||
m_subscriber.disconnect();
|
||||
|
||||
//shutdown the server
|
||||
m_server.stopServer();
|
||||
|
||||
//restart the server
|
||||
m_server.startServer(IntegrationUtils.prepareTestPropeties());
|
||||
//subscriber reconnects
|
||||
MQTT mqttSub = new MQTT();
|
||||
mqttSub.setHost("localhost", 1883);
|
||||
mqttSub.setClientId("Subscriber");
|
||||
m_subscriber = mqttSub.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
|
||||
//publisher publishes on /topic
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("Publisher");
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
m_publisher.publish("/topic", "Hello world MQTT!!".getBytes(), QoS.AT_LEAST_ONCE, false);
|
||||
|
||||
//Expected
|
||||
//the subscriber doesn't get notified (it's fully unsubscribed)
|
||||
assertNull(m_subscriber.receive(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect subscribe to topic and publish on the same topic
|
||||
* */
|
||||
private BlockingConnection subscribeAndPublish(String topic) throws Exception {
|
||||
BlockingConnection conn = m_mqtt.blockingConnection();
|
||||
conn.connect();
|
||||
Topic[] topics = new Topic[]{new Topic(topic, QoS.AT_MOST_ONCE)};
|
||||
conn.subscribe(topics);
|
||||
conn.publish(topic, "Hello world MQTT!!".getBytes(), QoS.AT_MOST_ONCE, false);
|
||||
//read the message
|
||||
Message msg = conn.receive();
|
||||
msg.ack();
|
||||
assertEquals("Hello world MQTT!!", new String(msg.getPayload()));
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
|
||||
import org.eclipse.paho.client.mqttv3.MqttCallback;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
class TestCallback implements MqttCallback {
|
||||
|
||||
private MqttMessage m_message;
|
||||
private String m_topic;
|
||||
private CountDownLatch m_latch = new CountDownLatch(1);
|
||||
|
||||
public MqttMessage getMessage(boolean checkElapsed) {
|
||||
try {
|
||||
boolean elapsed = !m_latch.await(1, TimeUnit.SECONDS);
|
||||
if (elapsed && checkElapsed) {
|
||||
throw new IllegalStateException("Elapsed the timeout to get the result");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return m_message;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
try {
|
||||
m_latch.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return m_topic;
|
||||
}
|
||||
|
||||
void reinit() {
|
||||
m_latch = new CountDownLatch(1);
|
||||
m_message = null;
|
||||
m_topic = null;
|
||||
}
|
||||
|
||||
public void connectionLost(Throwable cause) {
|
||||
}
|
||||
|
||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||
m_message = message;
|
||||
m_topic = topic;
|
||||
m_latch.countDown();
|
||||
}
|
||||
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.server.config;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ConfigurationParserTest {
|
||||
|
||||
ConfigurationParser m_parser;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
m_parser = new ConfigurationParser();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDefaultOptions() {
|
||||
Properties props = m_parser.getProperties();
|
||||
|
||||
verifyDefaults(props);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseEmpty() throws ParseException {
|
||||
Reader conf = new StringReader(" ");
|
||||
m_parser.parse(conf);
|
||||
|
||||
//Verify
|
||||
verifyDefaults(m_parser.getProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseValidComment() throws ParseException {
|
||||
Reader conf = new StringReader("#simple comment");
|
||||
m_parser.parse(conf);
|
||||
|
||||
//Verify
|
||||
verifyDefaults(m_parser.getProperties());
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void parseInvalidComment() throws ParseException {
|
||||
Reader conf = new StringReader(" #simple comment");
|
||||
m_parser.parse(conf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSingleVariable() throws ParseException {
|
||||
Reader conf = new StringReader("port 1234");
|
||||
m_parser.parse(conf);
|
||||
|
||||
//Verify
|
||||
assertEquals("1234", m_parser.getProperties().getProperty("port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCompleteFile() throws ParseException {
|
||||
String content = "# This is initial m_config format \r\n"
|
||||
+ " \r\n"
|
||||
+ "port 1234 \r\n"
|
||||
+ "host localhost \r\n"
|
||||
+ "fake multi word string property\r\n";
|
||||
Reader conf = new StringReader(content);
|
||||
m_parser.parse(conf);
|
||||
|
||||
//Verify
|
||||
Properties props = m_parser.getProperties();
|
||||
assertEquals("1234", props.getProperty("port"));
|
||||
assertEquals("localhost", props.getProperty("host"));
|
||||
assertEquals("multi word string property", props.getProperty("fake"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to verify default options.
|
||||
*/
|
||||
private void verifyDefaults(Properties props) {
|
||||
Assert.assertEquals(Constants.PORT, Integer.parseInt(props.getProperty("port")));
|
||||
Assert.assertEquals(Constants.HOST, props.getProperty("host"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spec.v3_1_1.connection;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import io.moquette.server.Server;
|
||||
import io.moquette.testclient.RawClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author pkhanal
|
||||
*
|
||||
*/
|
||||
public class ConnectionIT {
|
||||
|
||||
Server m_server;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
m_server.startServer();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
m_server.stopServer();
|
||||
|
||||
File dbFile = new File(Constants.DEFAULT_PERSISTENT_PATH);
|
||||
if (dbFile.exists()) {
|
||||
assertTrue("Error deleting the moquette db file " + Constants.DEFAULT_PERSISTENT_PATH, dbFile.delete());
|
||||
}
|
||||
assertFalse(dbFile.exists());
|
||||
}
|
||||
|
||||
@Test(timeout = 3000)
|
||||
public void testConnectThenClose() throws Exception {
|
||||
RawClient.connect("127.0.0.1", 1883).isConnected()
|
||||
//CONNECT
|
||||
.write(0x10) //MQTT Control Packet type(1)
|
||||
.write(0x13) // Remaining Length
|
||||
.write(0x00, 0x04) // Protocol Name Length
|
||||
.write("MQTT") // Protocol Name
|
||||
.write(0x04) // The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04)
|
||||
|
||||
//Connect Flags
|
||||
//User Name Flag(0)
|
||||
//Password Flag(0)
|
||||
//Will Retain(0)
|
||||
//Will QoS(00)
|
||||
//Will Flag(0)
|
||||
//Clean Session(1)
|
||||
.write(0x02) // Reserved(0)
|
||||
.write(0x00, 0x00) // Keep Alive
|
||||
|
||||
// Payload
|
||||
.write(0x00, 0x07) // Client Identifier Length
|
||||
.write("client1") // Client Identifier
|
||||
.flush()
|
||||
|
||||
//CONNACK
|
||||
.read(0x20) // MQTT Control Packet type(2)
|
||||
.read(0x02) // Remaining Length
|
||||
|
||||
//Connect Acknowledge Flags
|
||||
.read(0x00) // Session Present Flag(0)
|
||||
|
||||
//Connect Return code
|
||||
.read(0x00) // Connection Accepted
|
||||
|
||||
//DISCONNECT
|
||||
.write(0xE0) // MQTT Control Packet type(14)
|
||||
.write(0x00) // Remaining Length
|
||||
.closed(1000);
|
||||
}
|
||||
|
||||
@Test(timeout = 3000)
|
||||
public void testConnectWithInvalidWillQoS() throws Exception {
|
||||
RawClient.connect("127.0.0.1", 1883).isConnected()
|
||||
//CONNECT
|
||||
.write(0x10) //MQTT Control Packet type(1)
|
||||
.write(0x13) // Remaining Length
|
||||
.write(0x00, 0x04) // Protocol Name Length
|
||||
.write("MQTT") // Protocol Name
|
||||
.write(0x04) // The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04)
|
||||
|
||||
//Connect Flags
|
||||
//User Name Flag(0)
|
||||
//Password Flag(0)
|
||||
//Will Retain(0)
|
||||
//Will QoS(11) - It MUST NOT be 3 (0x03). Server should close the connection.
|
||||
//Will Flag(1)
|
||||
//Clean Session(1)
|
||||
.write(0x1E) // Reserved(0)
|
||||
.write(0x00, 0x00) // Keep Alive
|
||||
|
||||
// Payload
|
||||
.write(0x00, 0x07) // Client Identifier Length
|
||||
.write("client1") // Client Identifier
|
||||
.flush()
|
||||
|
||||
.closed(1000);
|
||||
}
|
||||
|
||||
@Ignore("Need to validate the test case.")
|
||||
@Test(timeout = 15000)
|
||||
public void testConnectWithWillFlagSetToZeroButWillQoSFlagSetToNonZero() throws Exception {
|
||||
RawClient.connect("127.0.0.1", 1883).isConnected()
|
||||
// CONNECT
|
||||
.write(0x10) // MQTT Control Packet type(1)
|
||||
.write(0x12) // Remaining Length
|
||||
.write(0x00, 0x04) // Protocol Name Length
|
||||
.write("MQTT") // Protocol Name
|
||||
.write(0x04) // The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04)
|
||||
|
||||
// Connect Flags
|
||||
// User Name Flag(0)
|
||||
// Password Flag(0)
|
||||
// Will Retain(0)
|
||||
// Will QoS(01) - If the Will Flag is set to 0, then the Will QoS MUST be set to 0
|
||||
// Will Flag(0)
|
||||
//Clean Session(1)
|
||||
.write(0x0A) // Reserved(0)
|
||||
|
||||
.write(0x00, 0x00) // Keep Alive
|
||||
|
||||
// Payload
|
||||
.write(0x00, 0x07) // Client Identifier Length
|
||||
.write("client1") //Client Identifier
|
||||
|
||||
//Server MUST close the Network Connection due to invalid Will QoS flag
|
||||
.closed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.proto.messages.ConnectMessage;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public class BrokerInterceptorTest {
|
||||
// value to check for changes after every notification
|
||||
private static final AtomicInteger n = new AtomicInteger(0);
|
||||
|
||||
// Interceptor loaded with a custom InterceptHandler special for the tests
|
||||
private static final class MockObserver implements InterceptHandler {
|
||||
@Override
|
||||
public void onConnect(InterceptConnectMessage msg) {
|
||||
n.set(40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(InterceptDisconnectMessage msg) {
|
||||
n.set(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(InterceptPublishMessage msg) {
|
||||
n.set(60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(InterceptSubscribeMessage msg) {
|
||||
n.set(70);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {
|
||||
n.set(80);
|
||||
}
|
||||
}
|
||||
|
||||
private static final BrokerInterceptor interceptor = new BrokerInterceptor(Arrays.<InterceptHandler>asList(new MockObserver()));
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAllTests() {
|
||||
// check if any of the handler methods was called before notifications
|
||||
assertEquals(0, n.get());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterAllTests() {
|
||||
interceptor.stop();
|
||||
}
|
||||
|
||||
/* Used to wait handler notification by the interceptor internal thread */
|
||||
private static void interval() throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientConnected() throws Exception {
|
||||
interceptor.notifyClientConnected(new ConnectMessage());
|
||||
interval();
|
||||
assertEquals(40, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientDisconnected() throws Exception {
|
||||
interceptor.notifyClientDisconnected("cli1234");
|
||||
interval();
|
||||
assertEquals(50, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicPublished() throws Exception {
|
||||
interceptor.notifyTopicPublished(new PublishMessage(), "cli1234");
|
||||
interval();
|
||||
assertEquals(60, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicSubscribed() throws Exception {
|
||||
interceptor.notifyTopicSubscribed(new Subscription("cli1", "o2", AbstractMessage.QOSType.MOST_ONE, true));
|
||||
interval();
|
||||
assertEquals(70, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicUnsubscribed() throws Exception {
|
||||
interceptor.notifyTopicUnsubscribed("o2", "cli1234");
|
||||
interval();
|
||||
assertEquals(80, n.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.server.ServerChannel;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.proto.messages.ConnAckMessage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class DummyChannel implements ServerChannel {
|
||||
|
||||
AbstractMessage m_receivedMessage;
|
||||
|
||||
byte m_returnCode;
|
||||
|
||||
private boolean m_channelClosed = false;
|
||||
|
||||
private Map<Object, Object> m_attributes = new HashMap<>();
|
||||
|
||||
public Object getAttribute(AttributeKey<Object> key) {
|
||||
return m_attributes.get(key);
|
||||
}
|
||||
|
||||
public void setAttribute(AttributeKey<Object> key, Object value) {
|
||||
m_attributes.put(key, value);
|
||||
}
|
||||
|
||||
public void setIdleTime(int idleTime) {
|
||||
}
|
||||
|
||||
public void close(boolean immediately) {
|
||||
m_channelClosed = true;
|
||||
}
|
||||
|
||||
boolean isClosed() {
|
||||
return this.m_channelClosed;
|
||||
}
|
||||
|
||||
public void write(Object value) {
|
||||
try {
|
||||
m_receivedMessage = (AbstractMessage) value;
|
||||
if (m_receivedMessage instanceof ConnAckMessage) {
|
||||
ConnAckMessage buf = (ConnAckMessage) m_receivedMessage;
|
||||
m_returnCode = buf.getReturnCode();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new AssertionError("Wrong return code");
|
||||
}
|
||||
}
|
||||
|
||||
public byte getReturnCode() {
|
||||
return m_returnCode;
|
||||
}
|
||||
|
||||
public AbstractMessage getReceivedMessage() {
|
||||
return m_receivedMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
/**
|
||||
* Created by andrea on 6/7/15.
|
||||
*/
|
||||
public class DummyChannelHandlerContext {
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMatchingCondition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.moquette.spi.impl.Utils.defaultGet;
|
||||
|
||||
/**
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemoryMessagesStore implements IMessagesStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemoryMessagesStore.class);
|
||||
|
||||
private Map<String, String> m_retainedStore = new HashMap<>();
|
||||
private Map<String, StoredMessage> m_persistentMessageStore = new HashMap<>();
|
||||
private Map<String, Map<Integer, String>> m_messageToGuids;
|
||||
private Map<String, Set<Integer>> m_inflightIDs = new HashMap<>();
|
||||
|
||||
MemoryMessagesStore(Map<String, Map<Integer, String>> messageToGuids) {
|
||||
m_messageToGuids = messageToGuids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeRetained(String topic, String guid) {
|
||||
m_retainedStore.put(topic, guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<StoredMessage> searchMatching(IMatchingCondition condition) {
|
||||
LOG.debug("searchMatching scanning all retained messages, presents are {}", m_retainedStore.size());
|
||||
|
||||
List<StoredMessage> results = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, String> entry : m_retainedStore.entrySet()) {
|
||||
final String guid = entry.getValue();
|
||||
StoredMessage storedMsg = m_persistentMessageStore.get(guid);
|
||||
if (condition.match(entry.getKey())) {
|
||||
results.add(storedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String storePublishForFuture(StoredMessage evt) {
|
||||
LOG.debug("storePublishForFuture store evt {}", evt);
|
||||
String guid = UUID.randomUUID().toString();
|
||||
evt.setGuid(guid);
|
||||
m_persistentMessageStore.put(guid, evt);
|
||||
HashMap<Integer, String> guids = (HashMap<Integer, String>) defaultGet(m_messageToGuids,
|
||||
evt.getClientID(), new HashMap<Integer, String>());
|
||||
guids.put(evt.getMessageID(), guid);
|
||||
return guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StoredMessage> listMessagesInSession(Collection<String> guids) {
|
||||
List<StoredMessage> ret = new ArrayList<>();
|
||||
for (String guid : guids) {
|
||||
ret.add(m_persistentMessageStore.get(guid));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropMessagesInSession(String clientID) {
|
||||
m_persistentMessageStore.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage getMessageByGuid(String guid) {
|
||||
return m_persistentMessageStore.get(guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Set<Integer> inFlightForClient = m_inflightIDs.get(clientID);
|
||||
if (inFlightForClient == null) {
|
||||
int nextPacketId = 1;
|
||||
inFlightForClient = new HashSet<>();
|
||||
inFlightForClient.add(nextPacketId);
|
||||
m_inflightIDs.put(clientID, inFlightForClient);
|
||||
return nextPacketId;
|
||||
}
|
||||
int maxId = Collections.max(inFlightForClient);
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
inFlightForClient.add(nextPacketId);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanRetained(String topic) {
|
||||
m_retainedStore.remove(topic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.persistence.MemorySessionStore;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class MemoryStorageService {
|
||||
|
||||
private MemorySessionStore m_sessionsStore;
|
||||
private MemoryMessagesStore m_messagesStore;
|
||||
|
||||
public void initStore() {
|
||||
Map<String, Map<Integer, String>> messageToGuids = new HashMap<>();
|
||||
m_messagesStore = new MemoryMessagesStore(messageToGuids);
|
||||
m_sessionsStore = new MemorySessionStore(m_messagesStore, messageToGuids);
|
||||
}
|
||||
|
||||
public IMessagesStore messagesStore() {
|
||||
return m_messagesStore;
|
||||
}
|
||||
|
||||
public ISessionsStore sessionsStore() {
|
||||
return m_sessionsStore;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.moquette.spi.impl.security.IAuthenticator;
|
||||
|
||||
/**
|
||||
* Test utility to implements authenticator instance.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
class MockAuthenticator implements IAuthenticator {
|
||||
|
||||
private Map<String, byte[]> m_userPwds;
|
||||
|
||||
MockAuthenticator(Map<String, byte[]> userPwds) {
|
||||
m_userPwds = userPwds;
|
||||
}
|
||||
|
||||
public boolean checkValid(String username, byte[] password) {
|
||||
if (!m_userPwds.containsKey(username)) {
|
||||
return false;
|
||||
}
|
||||
return m_userPwds.get(username).equals(password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/package io.moquette.spi.impl;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.server.ServerChannel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This a synchronous channel that avoid output ring buffer from Processor
|
||||
*/
|
||||
class MockReceiverChannel implements ServerChannel {
|
||||
// byte m_returnCode;
|
||||
AbstractMessage m_receivedMessage;
|
||||
private Map<Object, Object> m_attributes = new HashMap<>();
|
||||
|
||||
public Object getAttribute(AttributeKey<Object> key) {
|
||||
return m_attributes.get(key);
|
||||
}
|
||||
|
||||
public void setAttribute(AttributeKey<Object> key, Object value) {
|
||||
m_attributes.put(key, value);
|
||||
}
|
||||
|
||||
public void setIdleTime(int idleTime) {
|
||||
}
|
||||
|
||||
public void close(boolean immediately) {
|
||||
}
|
||||
|
||||
public AbstractMessage getMessage() {
|
||||
return this.m_receivedMessage;
|
||||
}
|
||||
|
||||
// public byte getReturnCode() {
|
||||
// return this.m_returnCode;
|
||||
// }
|
||||
|
||||
public void write(Object value) {
|
||||
try {
|
||||
this.m_receivedMessage = (AbstractMessage) value;
|
||||
// if (this.m_receivedMessage instanceof PublishMessage) {
|
||||
// T buf = (T) this.m_receivedMessage;
|
||||
// }
|
||||
} catch (Exception ex) {
|
||||
throw new AssertionError("Wrong return code");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.proto.messages.*;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
import io.moquette.server.netty.NettyChannel;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMatchingCondition;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.security.PermitAllAuthorizator;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ProtocolProcessorTest {
|
||||
final static String FAKE_CLIENT_ID = "FAKE_123";
|
||||
final static String FAKE_CLIENT_ID2 = "FAKE_456";
|
||||
final static String FAKE_PUBLISHER_ID = "Publisher";
|
||||
final static String FAKE_TOPIC = "/news";
|
||||
final static String BAD_FORMATTED_TOPIC = "#MQTTClient";
|
||||
|
||||
final static String TEST_USER = "fakeuser";
|
||||
final static byte[] TEST_PWD = "fakepwd".getBytes();
|
||||
final static String EVIL_TEST_USER = "eviluser";
|
||||
final static byte[] EVIL_TEST_PWD = "unsecret".getBytes();
|
||||
|
||||
final static List<InterceptHandler> EMPTY_OBSERVERS = Collections.emptyList();
|
||||
final static BrokerInterceptor NO_OBSERVERS_INTERCEPTOR = new BrokerInterceptor(EMPTY_OBSERVERS);
|
||||
|
||||
DummyChannel m_session;
|
||||
ConnectMessage connMsg;
|
||||
ProtocolProcessor m_processor;
|
||||
|
||||
IMessagesStore m_messagesStore;
|
||||
ISessionsStore m_sessionStore;
|
||||
SubscriptionsStore subscriptions;
|
||||
MockAuthenticator m_mockAuthenticator;
|
||||
|
||||
@Before
|
||||
public void setUp() throws InterruptedException {
|
||||
connMsg = new ConnectMessage();
|
||||
connMsg.setProtocolVersion((byte) 0x03);
|
||||
|
||||
m_session = new DummyChannel();
|
||||
|
||||
//sleep to let the messaging batch processor to process the initEvent
|
||||
Thread.sleep(300);
|
||||
MemoryStorageService memStorage = new MemoryStorageService();
|
||||
memStorage.initStore();
|
||||
m_messagesStore = memStorage.messagesStore();
|
||||
m_sessionStore = memStorage.sessionsStore();
|
||||
//m_messagesStore.initStore();
|
||||
|
||||
Map<String, byte[]> users = new HashMap<>();
|
||||
users.put(TEST_USER, TEST_PWD);
|
||||
m_mockAuthenticator = new MockAuthenticator(users);
|
||||
|
||||
subscriptions = new SubscriptionsStore();
|
||||
subscriptions.init(memStorage.sessionsStore());
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
|
||||
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPublish() throws InterruptedException {
|
||||
final Subscription subscription = new Subscription(FAKE_CLIENT_ID,
|
||||
FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, true);
|
||||
|
||||
//subscriptions.matches(topic) redefine the method to return true
|
||||
SubscriptionsStore subs = new SubscriptionsStore() {
|
||||
@Override
|
||||
public List<Subscription> matches(String topic) {
|
||||
if (topic.equals(FAKE_TOPIC)) {
|
||||
return Arrays.asList(subscription);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Expected " + FAKE_TOPIC + " buf found " + topic);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//simulate a connect that register a clientID to an IoSession
|
||||
MemoryStorageService storageService = new MemoryStorageService();
|
||||
storageService.initStore();
|
||||
subs.init(storageService.sessionsStore());
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setClientID(FAKE_CLIENT_ID);
|
||||
m_sessionStore.createNewSession(FAKE_CLIENT_ID, false);
|
||||
connectMessage.setCleanSession(subscription.isCleanSession());
|
||||
m_processor.processConnect(m_session, connectMessage);
|
||||
|
||||
|
||||
//Exercise
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
PublishMessage msg = new PublishMessage();
|
||||
msg.setTopicName(FAKE_TOPIC);
|
||||
msg.setQos(QOSType.MOST_ONE);
|
||||
msg.setPayload(buffer);
|
||||
msg.setRetainFlag(false);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "FakeCLI");
|
||||
m_processor.processPublish(m_session, msg);
|
||||
|
||||
//Verify
|
||||
assertNotNull(m_session.getReceivedMessage());
|
||||
//TODO check received message attributes
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublishToMultipleSubscribers() throws InterruptedException {
|
||||
final Subscription subscription = new Subscription(FAKE_CLIENT_ID,
|
||||
FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, true);
|
||||
final Subscription subscriptionClient2 = new Subscription(FAKE_CLIENT_ID2,
|
||||
FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, true);
|
||||
|
||||
//subscriptions.matches(topic) redefine the method to return true
|
||||
SubscriptionsStore subs = new SubscriptionsStore() {
|
||||
@Override
|
||||
public List<Subscription> matches(String topic) {
|
||||
if (topic.equals(FAKE_TOPIC)) {
|
||||
return Arrays.asList(subscription, subscriptionClient2);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Expected " + FAKE_TOPIC + " buf found " + topic);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//simulate a connect that register a clientID to an IoSession
|
||||
MemoryStorageService storageService = new MemoryStorageService();
|
||||
storageService.initStore();
|
||||
subs.init(storageService.sessionsStore());
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
MockReceiverChannel firstReceiverSession = new MockReceiverChannel();
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setClientID(FAKE_CLIENT_ID);
|
||||
connectMessage.setCleanSession(subscription.isCleanSession());
|
||||
m_processor.processConnect(firstReceiverSession, connectMessage);
|
||||
|
||||
//connect the second fake subscriber
|
||||
MockReceiverChannel secondReceiverSession = new MockReceiverChannel();
|
||||
ConnectMessage connectMessage2 = new ConnectMessage();
|
||||
connectMessage2.setProtocolVersion((byte) 3);
|
||||
connectMessage2.setClientID(FAKE_CLIENT_ID2);
|
||||
connectMessage2.setCleanSession(subscription.isCleanSession());
|
||||
m_processor.processConnect(secondReceiverSession, connectMessage2);
|
||||
|
||||
//Exercise
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
buffer.rewind();
|
||||
PublishMessage msg = new PublishMessage();
|
||||
msg.setTopicName(FAKE_TOPIC);
|
||||
msg.setQos(QOSType.MOST_ONE);
|
||||
msg.setPayload(buffer);
|
||||
msg.setRetainFlag(false);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "FakeCLI");
|
||||
m_processor.processPublish(m_session, msg);
|
||||
|
||||
//Verify
|
||||
Thread.sleep(100); //ugly but we depend on the asynch that pull data from back disruptor
|
||||
PublishMessage pub2FirstSubscriber = (PublishMessage) firstReceiverSession.getMessage();
|
||||
assertNotNull(pub2FirstSubscriber);
|
||||
String firstMessageContent = DebugUtils.payload2Str(pub2FirstSubscriber.getPayload());
|
||||
assertEquals("Hello", firstMessageContent);
|
||||
|
||||
PublishMessage pub2SecondSubscriber = (PublishMessage) secondReceiverSession.getMessage();
|
||||
assertNotNull(pub2SecondSubscriber);
|
||||
String secondMessageContent = DebugUtils.payload2Str(pub2SecondSubscriber.getPayload());
|
||||
assertEquals("Hello", secondMessageContent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribe() {
|
||||
//Exercise
|
||||
SubscribeMessage msg = new SubscribeMessage();
|
||||
msg.addSubscription(new SubscribeMessage.Couple(AbstractMessage.QOSType.MOST_ONE.byteValue(), FAKE_TOPIC));
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, FAKE_CLIENT_ID);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
m_sessionStore.createNewSession(FAKE_CLIENT_ID, false);
|
||||
m_processor.processSubscribe(m_session, msg/*, FAKE_CLIENT_ID, false*/);
|
||||
|
||||
//Verify
|
||||
assertTrue(m_session.getReceivedMessage() instanceof SubAckMessage);
|
||||
Subscription expectedSubscription = new Subscription(FAKE_CLIENT_ID, FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, false);
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleSubscribe() {
|
||||
SubscribeMessage msg = new SubscribeMessage();
|
||||
msg.addSubscription(new SubscribeMessage.Couple(AbstractMessage.QOSType.MOST_ONE.byteValue(), FAKE_TOPIC));
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, FAKE_CLIENT_ID);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
m_sessionStore.createNewSession(FAKE_CLIENT_ID, false);
|
||||
assertEquals(0, subscriptions.size());
|
||||
|
||||
m_processor.processSubscribe(m_session, msg);
|
||||
|
||||
//Exercise
|
||||
m_processor.processSubscribe(m_session, msg);
|
||||
|
||||
//Verify
|
||||
assertEquals(1, subscriptions.size());
|
||||
Subscription expectedSubscription = new Subscription(FAKE_CLIENT_ID, FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, false);
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSubscribeWithBadFormattedTopic() {
|
||||
SubscribeMessage msg = new SubscribeMessage();
|
||||
msg.addSubscription(new SubscribeMessage.Couple(AbstractMessage.QOSType.MOST_ONE.byteValue(), BAD_FORMATTED_TOPIC));
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, FAKE_CLIENT_ID);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
m_sessionStore.createNewSession(FAKE_CLIENT_ID, false);
|
||||
assertEquals(0, subscriptions.size());
|
||||
|
||||
//Exercise
|
||||
m_processor.processSubscribe(m_session, msg);
|
||||
|
||||
//Verify
|
||||
assertEquals(0, subscriptions.size());
|
||||
assertTrue(m_session.getReceivedMessage() instanceof SubAckMessage);
|
||||
List<QOSType> qosSubAcked = ((SubAckMessage) m_session.getReceivedMessage()).types();
|
||||
assertEquals(1, qosSubAcked.size());
|
||||
assertEquals(QOSType.FAILURE, qosSubAcked.get(0));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check topicFilter is a valid MQTT topic filter (issue 68)
|
||||
* */
|
||||
@Test
|
||||
public void testUnsubscribeWithBadFormattedTopic() {
|
||||
UnsubscribeMessage msg = new UnsubscribeMessage();
|
||||
msg.setMessageID(1);
|
||||
msg.addTopicFilter(BAD_FORMATTED_TOPIC);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, FAKE_CLIENT_ID);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
|
||||
//Exercise
|
||||
m_processor.processUnsubscribe(m_session, msg);
|
||||
|
||||
//Verify
|
||||
assertTrue(m_session.isClosed());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPublishOfRetainedMessage_afterNewSubscription() throws Exception {
|
||||
final CountDownLatch publishRecvSignal = new CountDownLatch(1);
|
||||
m_session = new DummyChannel() {
|
||||
@Override
|
||||
public void write(Object value) {
|
||||
try {
|
||||
System.out.println("filterReceived class " + value.getClass().getName());
|
||||
if (value instanceof PublishMessage) {
|
||||
m_receivedMessage = (AbstractMessage) value;
|
||||
publishRecvSignal.countDown();
|
||||
}
|
||||
|
||||
if (m_receivedMessage instanceof ConnAckMessage) {
|
||||
ConnAckMessage buf = (ConnAckMessage) m_receivedMessage;
|
||||
m_returnCode = buf.getReturnCode();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new AssertionError("Wrong return code");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//simulate a connect that register a clientID to an IoSession
|
||||
final Subscription subscription = new Subscription(FAKE_PUBLISHER_ID,
|
||||
FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, true);
|
||||
|
||||
//subscriptions.matches(topic) redefine the method to return true
|
||||
SubscriptionsStore subs = new SubscriptionsStore() {
|
||||
@Override
|
||||
public List<Subscription> matches(String topic) {
|
||||
if (topic.equals(FAKE_TOPIC)) {
|
||||
return Arrays.asList(subscription);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Expected " + FAKE_TOPIC + " buf found " + topic);
|
||||
}
|
||||
}
|
||||
};
|
||||
MemoryStorageService storageService = new MemoryStorageService();
|
||||
storageService.initStore();
|
||||
subs.init(storageService.sessionsStore());
|
||||
|
||||
//simulate a connect that register a clientID to an IoSession
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setClientID(FAKE_PUBLISHER_ID);
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setCleanSession(subscription.isCleanSession());
|
||||
m_processor.processConnect(m_session, connectMessage);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
PublishMessage pubmsg = new PublishMessage();
|
||||
pubmsg.setTopicName(FAKE_TOPIC);
|
||||
pubmsg.setQos(QOSType.MOST_ONE);
|
||||
pubmsg.setPayload(buffer);
|
||||
pubmsg.setRetainFlag(true);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, FAKE_PUBLISHER_ID);
|
||||
m_processor.processPublish(m_session, pubmsg);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
|
||||
//Exercise
|
||||
SubscribeMessage msg = new SubscribeMessage();
|
||||
msg.addSubscription(new SubscribeMessage.Couple(QOSType.MOST_ONE.byteValue(), "#"));
|
||||
m_processor.processSubscribe(m_session, msg/*, FAKE_PUBLISHER_ID, false*/);
|
||||
|
||||
//Verify
|
||||
//wait the latch
|
||||
assertTrue(publishRecvSignal.await(1, TimeUnit.SECONDS)); //no timeout
|
||||
assertNotNull(m_session.getReceivedMessage());
|
||||
assertTrue(m_session.getReceivedMessage() instanceof PublishMessage);
|
||||
PublishMessage pubMessage = (PublishMessage) m_session.getReceivedMessage();
|
||||
assertEquals(FAKE_TOPIC, pubMessage.getTopicName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepublishAndConsumePersistedMessages_onReconnect() {
|
||||
SubscriptionsStore subs = mock(SubscriptionsStore.class);
|
||||
List<Subscription> emptySubs = Collections.emptyList();
|
||||
when(subs.matches(anyString())).thenReturn(emptySubs);
|
||||
|
||||
StoredMessage retainedMessage = new StoredMessage("Hello".getBytes(), QOSType.EXACTLY_ONCE, "/topic");
|
||||
retainedMessage.setRetained(true);
|
||||
retainedMessage.setMessageID(120);
|
||||
retainedMessage.setClientID(FAKE_PUBLISHER_ID);
|
||||
m_messagesStore.storePublishForFuture(retainedMessage);
|
||||
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setClientID(FAKE_PUBLISHER_ID);
|
||||
connectMessage.setProtocolVersion((byte) 3);
|
||||
connectMessage.setCleanSession(false);
|
||||
m_processor.processConnect(m_session, connectMessage);
|
||||
|
||||
//Verify no messages are still stored
|
||||
Collection<String> guids = m_sessionStore.enqueued(FAKE_PUBLISHER_ID);
|
||||
assertTrue(m_messagesStore.listMessagesInSession(guids).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishNoPublishToInactiveSession() {
|
||||
//create an inactive session for Subscriber
|
||||
m_sessionStore.createNewSession("Subscriber", false).deactivate();
|
||||
|
||||
SubscriptionsStore mockedSubscriptions = mock(SubscriptionsStore.class);
|
||||
Subscription inactiveSub = new Subscription("Subscriber", "/topic", QOSType.LEAST_ONE, false);
|
||||
List<Subscription> inactiveSubscriptions = Arrays.asList(inactiveSub);
|
||||
when(mockedSubscriptions.matches(eq("/topic"))).thenReturn(inactiveSubscriptions);
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
PublishMessage msg = new PublishMessage();
|
||||
msg.setTopicName("/topic");
|
||||
msg.setQos(QOSType.MOST_ONE);
|
||||
msg.setPayload(buffer);
|
||||
msg.setRetainFlag(true);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "Publisher");
|
||||
m_processor.processPublish(m_session, msg);
|
||||
|
||||
//Verify no message is received
|
||||
assertNull(m_session.getReceivedMessage());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void publishToAnInactiveSubscriptionsCleanSession() {
|
||||
//create an inactive session for Subscriber
|
||||
m_sessionStore.createNewSession("Subscriber", false).deactivate();
|
||||
SubscriptionsStore mockedSubscriptions = mock(SubscriptionsStore.class);
|
||||
Subscription inactiveSub = new Subscription("Subscriber", "/topic", QOSType.LEAST_ONE, true);
|
||||
List<Subscription> inactiveSubscriptions = Arrays.asList(inactiveSub);
|
||||
when(mockedSubscriptions.matches(eq("/topic"))).thenReturn(inactiveSubscriptions);
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
PublishMessage msg = new PublishMessage();
|
||||
msg.setTopicName("/topic");
|
||||
msg.setQos(QOSType.MOST_ONE);
|
||||
msg.setPayload(buffer);
|
||||
msg.setRetainFlag(true);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "Publisher");
|
||||
m_processor.processPublish(m_session, msg);
|
||||
|
||||
//Verify no message is received
|
||||
assertNull(m_session.getReceivedMessage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that receiving a publish with retained message and with Q0S = 0
|
||||
* clean the existing retained messages for that topic.
|
||||
*/
|
||||
@Test
|
||||
public void testCleanRetainedStoreAfterAQoS0AndRetainedTrue() {
|
||||
//force a connect
|
||||
connMsg.setClientID("Publisher");
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
//prepare and existing retained store
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "Publisher");
|
||||
ByteBuffer payload = ByteBuffer.allocate(5).put("Hello".getBytes());
|
||||
PublishMessage msg = new PublishMessage();
|
||||
msg.setTopicName(FAKE_TOPIC);
|
||||
msg.setQos(QOSType.LEAST_ONE);
|
||||
msg.setPayload(payload);
|
||||
msg.setRetainFlag(true);
|
||||
msg.setMessageID(100);
|
||||
m_processor.processPublish(m_session, msg);
|
||||
|
||||
//Exercise
|
||||
PublishMessage cleanPubMsg = new PublishMessage();
|
||||
cleanPubMsg.setTopicName(FAKE_TOPIC);
|
||||
cleanPubMsg.setPayload(payload);
|
||||
cleanPubMsg.setQos(QOSType.MOST_ONE);
|
||||
cleanPubMsg.setRetainFlag(true);
|
||||
m_processor.processPublish(m_session, cleanPubMsg);
|
||||
|
||||
//Verify
|
||||
Collection<IMessagesStore.StoredMessage> messages = m_messagesStore.searchMatching(new IMatchingCondition() {
|
||||
public boolean match(String key) {
|
||||
return SubscriptionsStore.matchTopics(key, FAKE_TOPIC);
|
||||
}
|
||||
});
|
||||
assertTrue(messages.isEmpty());
|
||||
}
|
||||
|
||||
List<StoredMessage> publishedForwarded = new ArrayList<>();
|
||||
|
||||
@Test
|
||||
public void testForwardPublishWithCorrectQos() {
|
||||
StoredMessage forwardPublish = new StoredMessage("Hello world MQTT!!".getBytes(), QOSType.EXACTLY_ONCE, "a/b");
|
||||
forwardPublish.setRetained(true);
|
||||
forwardPublish.setMessageID(1);
|
||||
|
||||
MemoryStorageService memStore = new MemoryStorageService();
|
||||
memStore.initStore();
|
||||
IMessagesStore memoryMessageStore = memStore.messagesStore();
|
||||
ISessionsStore sessionsStore = memStore.sessionsStore();
|
||||
sessionsStore.createNewSession("Sub A", false).activate();
|
||||
sessionsStore.createNewSession("Sub B", false).activate();
|
||||
|
||||
Subscription subQos1 = new Subscription("Sub A", "a/b", QOSType.LEAST_ONE, false);
|
||||
Subscription subQos2 = new Subscription("Sub B", "a/+", QOSType.EXACTLY_ONCE, false);
|
||||
SubscriptionsStore subscriptions = new SubscriptionsStore();
|
||||
subscriptions.init(sessionsStore);
|
||||
subscriptions.add(subQos1);
|
||||
subscriptions.add(subQos2);
|
||||
|
||||
|
||||
ProtocolProcessor processor = new ProtocolProcessor() {
|
||||
@Override
|
||||
protected void directSend(ClientSession session, String topic, AbstractMessage.QOSType qos, ByteBuffer message,
|
||||
boolean retained, Integer messageID) {
|
||||
StoredMessage msgToStore = new StoredMessage(message.array(), qos, topic);
|
||||
msgToStore.setRetained(retained);
|
||||
msgToStore.setMessageID(messageID);
|
||||
msgToStore.setClientID(session.clientID);
|
||||
publishedForwarded.add(msgToStore);
|
||||
}
|
||||
};
|
||||
processor.init(subscriptions, memoryMessageStore, sessionsStore, null, true, null, NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
processor.forward2Subscribers(forwardPublish);
|
||||
|
||||
//Verify
|
||||
assertEquals(2, publishedForwarded.size());
|
||||
assertEquals(subQos1.getClientId(), publishedForwarded.get(0).getClientID());
|
||||
assertEquals(subQos1.getRequestedQos(), publishedForwarded.get(0).getQos());
|
||||
assertEquals(subQos2.getClientId(), publishedForwarded.get(1).getClientID());
|
||||
assertEquals(subQos2.getRequestedQos(), publishedForwarded.get(1).getQos());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.proto.messages.ConnAckMessage;
|
||||
import io.moquette.proto.messages.ConnectMessage;
|
||||
import io.moquette.proto.messages.SubscribeMessage;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.server.netty.NettyChannel;
|
||||
import io.moquette.spi.impl.security.PermitAllAuthorizator;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.moquette.parser.netty.Utils.VERSION_3_1_1;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ProtocolProcessor_CONNECT_Test {
|
||||
|
||||
DummyChannel m_session;
|
||||
ConnectMessage connMsg;
|
||||
ProtocolProcessor m_processor;
|
||||
|
||||
IMessagesStore m_messagesStore;
|
||||
ISessionsStore m_sessionStore;
|
||||
SubscriptionsStore subscriptions;
|
||||
MockAuthenticator m_mockAuthenticator;
|
||||
|
||||
@Before
|
||||
public void setUp() throws InterruptedException {
|
||||
connMsg = new ConnectMessage();
|
||||
connMsg.setProtocolVersion((byte) 0x03);
|
||||
|
||||
m_session = new DummyChannel();
|
||||
|
||||
//sleep to let the messaging batch processor to process the initEvent
|
||||
Thread.sleep(300);
|
||||
MemoryStorageService memStorage = new MemoryStorageService();
|
||||
memStorage.initStore();
|
||||
m_messagesStore = memStorage.messagesStore();
|
||||
m_sessionStore = memStorage.sessionsStore();
|
||||
//m_messagesStore.initStore();
|
||||
|
||||
Map<String, byte[]> users = new HashMap<>();
|
||||
users.put(ProtocolProcessorTest.TEST_USER, ProtocolProcessorTest.TEST_PWD);
|
||||
m_mockAuthenticator = new MockAuthenticator(users);
|
||||
|
||||
subscriptions = new SubscriptionsStore();
|
||||
subscriptions.init(m_sessionStore);
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
|
||||
new PermitAllAuthorizator(), ProtocolProcessorTest.NO_OBSERVERS_INTERCEPTOR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleConnect_BadProtocol() {
|
||||
connMsg.setProtocolVersion((byte) 0x02);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.UNNACEPTABLE_PROTOCOL_VERSION, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnect_badClientID() {
|
||||
connMsg.setClientID("extremely_long_clientID_greater_than_23");
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWill() {
|
||||
connMsg.setClientID("123");
|
||||
connMsg.setWillFlag(true);
|
||||
connMsg.setWillTopic("topic");
|
||||
connMsg.setWillMessage("Topic message".getBytes());
|
||||
|
||||
|
||||
//Exercise
|
||||
//m_handler.setMessaging(mockedMessaging);
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
//TODO verify the call
|
||||
/*verify(mockedMessaging).publish(eq("topic"), eq("Topic message".getBytes()),
|
||||
any(AbstractMessage.QOSType.class), anyBoolean(), eq("123"), any(IoSession.class));*/
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validAuthentication() {
|
||||
connMsg.setClientID("123");
|
||||
connMsg.setUserFlag(true);
|
||||
connMsg.setPasswordFlag(true);
|
||||
connMsg.setUsername(ProtocolProcessorTest.TEST_USER);
|
||||
connMsg.setPassword(ProtocolProcessorTest.TEST_PWD);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noPasswdAuthentication() {
|
||||
connMsg.setClientID("123");
|
||||
connMsg.setUserFlag(true);
|
||||
connMsg.setPasswordFlag(false);
|
||||
connMsg.setUsername(ProtocolProcessorTest.TEST_USER);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidAuthentication() {
|
||||
connMsg.setClientID("123");
|
||||
connMsg.setUserFlag(true);
|
||||
connMsg.setPasswordFlag(true);
|
||||
connMsg.setUsername(ProtocolProcessorTest.TEST_USER + "_fake");
|
||||
connMsg.setPassword(ProtocolProcessorTest.TEST_PWD);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prohibitAnonymousClient() {
|
||||
connMsg.setClientID("123");
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, false, new PermitAllAuthorizator(), ProtocolProcessorTest.NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prohibitAnonymousClient_providingUsername() {
|
||||
connMsg.setClientID("123");
|
||||
connMsg.setUserFlag(true);
|
||||
connMsg.setUsername(ProtocolProcessorTest.TEST_USER + "_fake");
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, false, new PermitAllAuthorizator(), ProtocolProcessorTest.NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptAnonymousClient() {
|
||||
connMsg.setClientID("123");
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true, new PermitAllAuthorizator(), ProtocolProcessorTest.NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
|
||||
//Verify
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectWithSameClientIDBadCredentialsDoesntDropExistingClient() {
|
||||
//Connect a client1
|
||||
connMsg.setClientID("Client1");
|
||||
connMsg.setUserFlag(true);
|
||||
connMsg.setPasswordFlag(true);
|
||||
connMsg.setUsername(ProtocolProcessorTest.TEST_USER);
|
||||
connMsg.setPassword(ProtocolProcessorTest.TEST_PWD);
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
|
||||
//create another connect same clientID but with bad credentials
|
||||
ConnectMessage evilClientConnMsg = new ConnectMessage();
|
||||
evilClientConnMsg.setProtocolVersion((byte) 0x03);
|
||||
evilClientConnMsg.setClientID("Client1");
|
||||
evilClientConnMsg.setUserFlag(true);
|
||||
evilClientConnMsg.setPasswordFlag(true);
|
||||
evilClientConnMsg.setUsername(ProtocolProcessorTest.EVIL_TEST_USER);
|
||||
evilClientConnMsg.setPassword(ProtocolProcessorTest.EVIL_TEST_PWD);
|
||||
DummyChannel evilSession = new DummyChannel();
|
||||
|
||||
//Exercise
|
||||
m_processor.processConnect(evilSession, evilClientConnMsg);
|
||||
|
||||
//Verify
|
||||
//the evil client gets a not auth notification
|
||||
assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, evilSession.getReturnCode());
|
||||
//the good client remains connected
|
||||
assertFalse(m_session.isClosed());
|
||||
assertTrue(evilSession.isClosed());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConnAckContainsSessionPresentFlag() throws InterruptedException {
|
||||
connMsg = new ConnectMessage();
|
||||
connMsg.setProtocolVersion(VERSION_3_1_1);
|
||||
connMsg.setClientID("CliID");
|
||||
connMsg.setCleanSession(false);
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "CliID");
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
|
||||
//Connect a first time
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
//disconnect
|
||||
m_processor.processDisconnect(m_session);
|
||||
|
||||
//Exercise, reconnect
|
||||
MockReceiverChannel firstReceiverSession = new MockReceiverChannel();
|
||||
m_processor.processConnect(firstReceiverSession, connMsg);
|
||||
|
||||
//Verify
|
||||
AbstractMessage recvMsg = firstReceiverSession.getMessage();
|
||||
assertTrue(recvMsg instanceof ConnAckMessage);
|
||||
ConnAckMessage connAckMsg = (ConnAckMessage) recvMsg;
|
||||
assertTrue(connAckMsg.isSessionPresent());
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, connAckMsg.getReturnCode());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleReconnection() throws InterruptedException {
|
||||
//connect with clean a false and subscribe to a topic
|
||||
connMsg = new ConnectMessage();
|
||||
connMsg.setProtocolVersion(VERSION_3_1_1);
|
||||
connMsg.setClientID("CliID");
|
||||
connMsg.setCleanSession(false);
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
|
||||
//subscribe
|
||||
SubscribeMessage subscribeMsg = new SubscribeMessage();
|
||||
subscribeMsg.addSubscription(new SubscribeMessage.Couple((byte) AbstractMessage.QOSType.MOST_ONE.ordinal(), ProtocolProcessorTest.FAKE_TOPIC));
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, "CliID");
|
||||
m_session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, false);
|
||||
m_processor.processSubscribe(m_session, subscribeMsg);
|
||||
Subscription expectedSubscription = new Subscription("CliID", ProtocolProcessorTest.FAKE_TOPIC, AbstractMessage.QOSType.MOST_ONE, false);
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
|
||||
//disconnect
|
||||
m_processor.processDisconnect(m_session);
|
||||
|
||||
//reconnect clean session a false
|
||||
m_processor.processConnect(m_session, connMsg);
|
||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_session.getReturnCode());
|
||||
|
||||
//verify that the first subscription is still preserved
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.security;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.text.ParseException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author andrea
|
||||
* */
|
||||
public class ACLFileParserTest {
|
||||
|
||||
@Test
|
||||
public void testParseEmpty() throws ParseException {
|
||||
Reader conf = new StringReader(" ");
|
||||
AuthorizationsCollector authorizations = ACLFileParser.parse(conf);
|
||||
|
||||
//Verify
|
||||
assertTrue(authorizations.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseValidComment() throws ParseException {
|
||||
Reader conf = new StringReader("#simple comment");
|
||||
AuthorizationsCollector authorizations = ACLFileParser.parse(conf);
|
||||
|
||||
//Verify
|
||||
assertTrue(authorizations.isEmpty());
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseInvalidComment() throws ParseException {
|
||||
Reader conf = new StringReader(" #simple comment");
|
||||
ACLFileParser.parse(conf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSingleLineACL() throws ParseException {
|
||||
Reader conf = new StringReader("topic /weather/italy/anemometer");
|
||||
AuthorizationsCollector authorizations = ACLFileParser.parse(conf);
|
||||
|
||||
//Verify
|
||||
assertTrue(authorizations.canRead("/weather/italy/anemometer", "", ""));
|
||||
assertTrue(authorizations.canWrite("/weather/italy/anemometer", "", ""));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.security;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author andrea
|
||||
*/
|
||||
public class AuthorizationsCollectorTest {
|
||||
|
||||
private static final Authorization RW_ANEMOMETER = new Authorization("/weather/italy/anemometer");
|
||||
private static final Authorization R_ANEMOMETER = new Authorization("/weather/italy/anemometer", Authorization.Permission.READ);
|
||||
private static final Authorization W_ANEMOMETER = new Authorization("/weather/italy/anemometer", Authorization.Permission.WRITE);
|
||||
|
||||
private AuthorizationsCollector authorizator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
authorizator = new AuthorizationsCollector();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAuthLineValid() throws ParseException {
|
||||
Authorization authorization = authorizator.parseAuthLine("topic /weather/italy/anemometer");
|
||||
|
||||
//Verify
|
||||
assertEquals(RW_ANEMOMETER, authorization);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAuthLineValid_read() throws ParseException {
|
||||
Authorization authorization = authorizator.parseAuthLine("topic read /weather/italy/anemometer");
|
||||
|
||||
//Verify
|
||||
assertEquals(R_ANEMOMETER, authorization);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAuthLineValid_write() throws ParseException {
|
||||
Authorization authorization = authorizator.parseAuthLine("topic write /weather/italy/anemometer");
|
||||
|
||||
//Verify
|
||||
assertEquals(W_ANEMOMETER, authorization);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAuthLineValid_readwrite() throws ParseException {
|
||||
Authorization authorization = authorizator.parseAuthLine("topic readwrite /weather/italy/anemometer");
|
||||
|
||||
//Verify
|
||||
assertEquals(RW_ANEMOMETER, authorization);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testParseAuthLineValid_topic_with_space() throws ParseException {
|
||||
Authorization expected = new Authorization("/weather/eastern italy/anemometer");
|
||||
Authorization authorization = authorizator.parseAuthLine("topic readwrite /weather/eastern italy/anemometer");
|
||||
|
||||
//Verify
|
||||
assertEquals(expected, authorization);
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseAuthLineValid_invalid() throws ParseException {
|
||||
authorizator.parseAuthLine("topic faker /weather/italy/anemometer");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanWriteSimpleTopic() throws ParseException {
|
||||
authorizator.parse("topic write /sensors");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canWrite("/sensors", "", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanReadSimpleTopic() throws ParseException {
|
||||
authorizator.parse("topic read /sensors");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canRead("/sensors", "", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanReadWriteMixedSimpleTopic() throws ParseException {
|
||||
authorizator.parse("topic write /sensors");
|
||||
authorizator.parse("topic read /sensors/anemometer");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canWrite("/sensors", "", ""));
|
||||
assertFalse(authorizator.canRead("/sensors", "", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanWriteMultiMatherTopic() throws ParseException {
|
||||
authorizator.parse("topic write /sensors/#");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canWrite("/sensors/anemometer/wind", "", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanWriteSingleMatherTopic() throws ParseException {
|
||||
authorizator.parse("topic write /sensors/+");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canWrite("/sensors/anemometer", "", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanWriteUserTopic() throws ParseException {
|
||||
authorizator.parse("user john");
|
||||
authorizator.parse("topic write /sensors");
|
||||
|
||||
//verify
|
||||
assertTrue(authorizator.canWrite("/sensors", "john", ""));
|
||||
assertFalse(authorizator.canWrite("/sensors", "jack", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatternClientLineACL() throws ParseException {
|
||||
authorizator.parse("pattern read /weather/italy/%c");
|
||||
|
||||
//Verify
|
||||
assertTrue(authorizator.canRead("/weather/italy/anemometer1", "", "anemometer1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatternClientAndUserLineACL() throws ParseException {
|
||||
authorizator.parse("pattern read /weather/%u/%c");
|
||||
|
||||
//Verify
|
||||
assertTrue(authorizator.canRead("/weather/italy/anemometer1", "italy", "anemometer1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.security;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class FileAuthenticatorTest {
|
||||
|
||||
@Test
|
||||
public void loadPasswordFile_verifyValid() {
|
||||
String file = getClass().getResource("/password_file.conf").getPath();
|
||||
IAuthenticator auth = new FileAuthenticator(null, file);
|
||||
|
||||
assertTrue(auth.checkValid("testuser", "passwd".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPasswordFile_verifyInvalid() {
|
||||
String file = getClass().getResource("/password_file.conf").getPath();
|
||||
IAuthenticator auth = new FileAuthenticator(null, file);
|
||||
|
||||
assertFalse(auth.checkValid("testuser2", "passwd".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPasswordFile_verifyDirectoryRef() {
|
||||
IAuthenticator auth = new FileAuthenticator("", "");
|
||||
|
||||
assertFalse(auth.checkValid("testuser2", "passwd".getBytes()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.MemoryStorageService;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class SubscriptionsStoreTest {
|
||||
|
||||
private SubscriptionsStore store;
|
||||
|
||||
public SubscriptionsStoreTest() {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
store = new SubscriptionsStore();
|
||||
MemoryStorageService storageService = new MemoryStorageService();
|
||||
storageService.initStore();
|
||||
store.init(storageService.sessionsStore());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseTopic() throws ParseException {
|
||||
List<Token> tokens = store.parseTopic("finance/stock/ibm");
|
||||
assertEqualsSeq(asArray("finance", "stock", "ibm"), tokens);
|
||||
|
||||
tokens = store.parseTopic("/finance/stock/ibm");
|
||||
assertEqualsSeq(asArray(Token.EMPTY, "finance", "stock", "ibm"), tokens);
|
||||
|
||||
tokens = store.parseTopic("/");
|
||||
assertEqualsSeq(asArray(Token.EMPTY, Token.EMPTY), tokens);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseTopicMultiValid() throws ParseException {
|
||||
List<Token> tokens = store.parseTopic("finance/stock/#");
|
||||
assertEqualsSeq(asArray("finance", "stock", Token.MULTI), tokens);
|
||||
|
||||
tokens = store.parseTopic("#");
|
||||
assertEqualsSeq(asArray(Token.MULTI), tokens);
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseTopicMultiInTheMiddleNotValid() throws ParseException {
|
||||
store.parseTopic("finance/#/closingprice");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseTopicMultiNotAfterSeparatorNotValid() throws ParseException {
|
||||
store.parseTopic("finance#");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseTopicMultiNotAlone() throws ParseException {
|
||||
store.parseTopic("/finance/#closingprice");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseTopicSingleValid() throws ParseException {
|
||||
List<Token> tokens = store.parseTopic("finance/stock/+");
|
||||
assertEqualsSeq(asArray("finance", "stock", Token.SINGLE), tokens);
|
||||
|
||||
tokens = store.parseTopic("+");
|
||||
assertEqualsSeq(asArray(Token.SINGLE), tokens);
|
||||
|
||||
tokens = store.parseTopic("finance/+/ibm");
|
||||
assertEqualsSeq(asArray("finance", Token.SINGLE, "ibm"), tokens);
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseTopicSingleNotAferSeparatorNotValid() throws ParseException {
|
||||
store.parseTopic("finance+");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchSimple() {
|
||||
Subscription slashSub = new Subscription("FAKE_CLI_ID_1", "/", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(slashSub);
|
||||
assertTrue(store.matches("finance").isEmpty());
|
||||
|
||||
Subscription slashFinanceSub = new Subscription("FAKE_CLI_ID_1", "/finance", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(slashFinanceSub);
|
||||
assertTrue(store.matches("finance").isEmpty());
|
||||
|
||||
assertTrue(store.matches("/finance").contains(slashFinanceSub));
|
||||
assertTrue(store.matches("/").contains(slashSub));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchSimpleMulti() {
|
||||
Subscription anySub = new Subscription("FAKE_CLI_ID_1", "#", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(anySub);
|
||||
assertTrue(store.matches("finance").contains(anySub));
|
||||
|
||||
Subscription financeAnySub = new Subscription("FAKE_CLI_ID_2", "finance/#", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(financeAnySub);
|
||||
assertTrue(store.matches("finance").containsAll(Arrays.asList(financeAnySub, anySub)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingDeepMulti_one_layer() {
|
||||
Subscription anySub = new Subscription("FAKE_CLI_ID_1", "#", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
Subscription financeAnySub = new Subscription("FAKE_CLI_ID_2", "finance/#", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(anySub);
|
||||
store.add(financeAnySub);
|
||||
|
||||
//Verify
|
||||
assertTrue(store.matches("finance/stock").containsAll(Arrays.asList(financeAnySub, anySub)));
|
||||
assertTrue(store.matches("finance/stock/ibm").containsAll(Arrays.asList(financeAnySub, anySub)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchingDeepMulti_two_layer() {
|
||||
Subscription financeAnySub = new Subscription("FAKE_CLI_ID_1", "finance/stock/#", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(financeAnySub);
|
||||
|
||||
//Verify
|
||||
assertTrue(store.matches("finance/stock/ibm").contains(financeAnySub));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchSimpleSingle() {
|
||||
Subscription anySub = new Subscription("FAKE_CLI_ID_1", "+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(anySub);
|
||||
assertTrue(store.matches("finance").contains(anySub));
|
||||
|
||||
Subscription financeOne = new Subscription("FAKE_CLI_ID_1", "finance/+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(financeOne);
|
||||
assertTrue(store.matches("finance/stock").contains(financeOne));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchManySingle() {
|
||||
Subscription manySub = new Subscription("FAKE_CLI_ID_1", "+/+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(manySub);
|
||||
|
||||
//verify
|
||||
assertTrue(store.matches("/finance").contains(manySub));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchSlashSingle() {
|
||||
Subscription slashPlusSub = new Subscription("FAKE_CLI_ID_1", "/+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(slashPlusSub);
|
||||
Subscription anySub = new Subscription("FAKE_CLI_ID_1", "+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(anySub);
|
||||
|
||||
//Verify
|
||||
assertEquals(1, store.matches("/finance").size());
|
||||
assertTrue(store.matches("/finance").contains(slashPlusSub));
|
||||
assertFalse(store.matches("/finance").contains(anySub));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchManyDeepSingle() {
|
||||
Subscription slashPlusSub = new Subscription("FAKE_CLI_ID_1", "/finance/+/ibm", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(slashPlusSub);
|
||||
|
||||
Subscription slashPlusDeepSub = new Subscription("FAKE_CLI_ID_2", "/+/stock/+", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(slashPlusDeepSub);
|
||||
|
||||
//Verify
|
||||
assertTrue(store.matches("/finance/stock/ibm").containsAll(Arrays.asList(slashPlusSub, slashPlusDeepSub)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchSimpleMulti_allTheTree() {
|
||||
store.add(new Subscription("FAKE_CLI_ID_1", "#", AbstractMessage.QOSType.MOST_ONE, false));
|
||||
assertFalse(store.matches("finance").isEmpty());
|
||||
assertFalse(store.matches("finance/ibm").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchSimpleMulti_zeroLevel() {
|
||||
//check MULTI in case of zero level match
|
||||
store.add(new Subscription("FAKE_CLI_ID_1", "finance/#", AbstractMessage.QOSType.MOST_ONE, false));
|
||||
assertFalse(store.matches("finance").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rogerLightTopicMatches() {
|
||||
assertMatch("foo/bar", "foo/bar");
|
||||
assertMatch("foo/bar", "foo/bar");
|
||||
assertMatch("foo/+", "foo/bar");
|
||||
assertMatch("foo/+/baz", "foo/bar/baz");
|
||||
assertMatch("foo/+/#", "foo/bar/baz");
|
||||
assertMatch("#", "foo/bar/baz");
|
||||
|
||||
assertNotMatch("foo/bar", "foo");
|
||||
assertNotMatch("foo/+", "foo/bar/baz");
|
||||
assertNotMatch("foo/+/baz", "foo/bar/bar");
|
||||
assertNotMatch("foo/+/#", "fo2/bar/baz");
|
||||
|
||||
assertMatch("#", "/foo/bar");
|
||||
assertMatch("/#", "/foo/bar");
|
||||
assertNotMatch("/#", "foo/bar");
|
||||
|
||||
assertMatch("foo//bar", "foo//bar");
|
||||
assertMatch("foo//+", "foo//bar");
|
||||
assertMatch("foo/+/+/baz", "foo///baz");
|
||||
assertMatch("foo/bar/+", "foo/bar/");
|
||||
}
|
||||
|
||||
private void assertMatch(String subscription, String topic) {
|
||||
store = new SubscriptionsStore();
|
||||
MemoryStorageService memStore = new MemoryStorageService();
|
||||
memStore.initStore();
|
||||
store.init(memStore.sessionsStore());
|
||||
Subscription sub = new Subscription("FAKE_CLI_ID_1", subscription, AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(sub);
|
||||
assertFalse(store.matches(topic).isEmpty());
|
||||
}
|
||||
|
||||
private void assertNotMatch(String subscription, String topic) {
|
||||
store = new SubscriptionsStore();
|
||||
MemoryStorageService memStore = new MemoryStorageService();
|
||||
memStore.initStore();
|
||||
store.init(memStore.sessionsStore());
|
||||
Subscription sub = new Subscription("FAKE_CLI_ID_1", subscription, AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(sub);
|
||||
assertTrue(store.matches(topic).isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveClientSubscriptions_existingClientID() {
|
||||
String cliendID = "FAKE_CLID_1";
|
||||
store.add(new Subscription(cliendID, "finance/#", AbstractMessage.QOSType.MOST_ONE, false));
|
||||
|
||||
//Exercise
|
||||
store.removeForClient(cliendID);
|
||||
|
||||
//Verify
|
||||
assertEquals(0, store.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveClientSubscriptions_notexistingClientID() {
|
||||
String cliendID = "FAKE_CLID_1";
|
||||
store.add(new Subscription(cliendID, "finance/#", AbstractMessage.QOSType.MOST_ONE, false));
|
||||
|
||||
//Exercise
|
||||
store.removeForClient("FAKE_CLID_2");
|
||||
|
||||
//Verify
|
||||
assertEquals(1, store.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlappingSubscriptions() {
|
||||
Subscription genericSub = new Subscription("FAKE_CLI_ID_1", "a/+", AbstractMessage.QOSType.EXACTLY_ONCE, false);
|
||||
store.add(genericSub);
|
||||
Subscription specificSub = new Subscription("FAKE_CLI_ID_1", "a/b", AbstractMessage.QOSType.LEAST_ONE, false);
|
||||
store.add(specificSub);
|
||||
|
||||
//Verify
|
||||
assertEquals(1, store.matches("a/b").size());
|
||||
//assertTrue(store.matches("/finance").contains(slashPlusSub));
|
||||
//assertFalse(store.matches("/finance").contains(anySub));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchTopics_simple() {
|
||||
assertTrue(SubscriptionsStore.matchTopics("/", "/"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/finance", "/finance"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchTopics_multi() {
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance", "#"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance", "finance/#"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance/stock", "finance/#"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance/stock/ibm", "finance/#"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchTopics_single() {
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance", "+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("finance/stock", "finance/+"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("finance", "finance/+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/finance", "/+"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("/finance", "+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/finance", "+/+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/finance/stock/ibm", "/finance/+/ibm"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/", "+/+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("sport/", "sport/+"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("/finance/stock", "+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rogerLightMatchTopics() {
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar", "foo/bar"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar", "foo/+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar/baz", "foo/+/baz"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar/baz", "foo/+/#"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar/baz", "#"));
|
||||
|
||||
assertFalse(SubscriptionsStore.matchTopics("foo", "foo/bar"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("foo/bar/baz", "foo/+"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("foo/bar/bar", "foo/+/baz"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("fo2/bar/baz", "foo/+/#"));
|
||||
|
||||
assertTrue(SubscriptionsStore.matchTopics("/foo/bar", "#"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("/foo/bar", "/#"));
|
||||
assertFalse(SubscriptionsStore.matchTopics("foo/bar", "/#"));
|
||||
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo//bar", "foo//bar"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo//bar", "foo//+"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo///baz", "foo/+/+/baz"));
|
||||
assertTrue(SubscriptionsStore.matchTopics("foo/bar/", "foo/bar/+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeSubscription_withDifferentClients_subscribedSameTopic() {
|
||||
SubscriptionsStore aStore = new SubscriptionsStore();
|
||||
MemoryStorageService memStore = new MemoryStorageService();
|
||||
memStore.initStore();
|
||||
ISessionsStore sessionsStore = memStore.sessionsStore();
|
||||
aStore.init(sessionsStore);
|
||||
//subscribe a not active clientID1 to /topic
|
||||
Subscription slashSub = new Subscription("FAKE_CLI_ID_1", "/topic", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
aStore.add(slashSub);
|
||||
sessionsStore.createNewSession("FAKE_CLI_ID_1", true).deactivate();
|
||||
|
||||
//subscribe an active clientID2 to /topic
|
||||
Subscription slashSub2 = new Subscription("FAKE_CLI_ID_2", "/topic", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
aStore.add(slashSub2);
|
||||
|
||||
//Exercise
|
||||
aStore.removeSubscription("/topic", slashSub2.getClientId());
|
||||
|
||||
//Verify
|
||||
Subscription remainedSubscription = aStore.matches("/topic").get(0);
|
||||
assertEquals(slashSub.getClientId(), remainedSubscription.getClientId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overridingSubscriptions() {
|
||||
Subscription oldSubscription = new Subscription("FAKE_CLI_ID_1", "/topic", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
store.add(oldSubscription);
|
||||
Subscription overrindingSubscription = new Subscription("FAKE_CLI_ID_1", "/topic", AbstractMessage.QOSType.EXACTLY_ONCE, false);
|
||||
store.add(overrindingSubscription);
|
||||
|
||||
//Verify
|
||||
List<Subscription> subscriptions = store.matches("/topic");
|
||||
assertEquals(1, subscriptions.size());
|
||||
Subscription sub = subscriptions.get(0);
|
||||
assertEquals(overrindingSubscription.getRequestedQos(), sub.getRequestedQos());
|
||||
}
|
||||
|
||||
/*
|
||||
Test for Issue #49
|
||||
* */
|
||||
@Test
|
||||
public void duplicatedSubscriptionsWithDifferentQos() {
|
||||
Subscription client2Sub = new Subscription("client2", "client/test/b", AbstractMessage.QOSType.MOST_ONE, true);
|
||||
store.add(client2Sub);
|
||||
Subscription client1SubQoS0 = new Subscription("client1", "client/test/b", AbstractMessage.QOSType.MOST_ONE, true);
|
||||
store.add(client1SubQoS0);
|
||||
|
||||
Subscription client1SubQoS2 = new Subscription("client1", "client/test/b", AbstractMessage.QOSType.EXACTLY_ONCE, true);
|
||||
store.add(client1SubQoS2);
|
||||
|
||||
System.out.println(store.dumpTree());
|
||||
|
||||
//Verify
|
||||
List<Subscription> subscriptions = store.matches("client/test/b");
|
||||
assertTrue(subscriptions.contains(client1SubQoS2));
|
||||
assertTrue(subscriptions.contains(client2Sub));
|
||||
assertFalse(subscriptions.contains(client1SubQoS0)); //client1SubQoS2 should override client1SubQoS0
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecreatePath_emptyRoot() {
|
||||
TreeNode oldRoot = new TreeNode(null);
|
||||
final SubscriptionsStore.NodeCouple resp = store.recreatePath("/finance", oldRoot);
|
||||
|
||||
//Verify
|
||||
assertNotNull(resp.root);
|
||||
assertNull(resp.root.m_token);
|
||||
assertEquals(1, resp.root.m_children.size());
|
||||
assertEquals(resp.createdNode, resp.root.m_children.get(0).m_children.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecreatePath_1layer_tree() {
|
||||
TreeNode oldRoot = new TreeNode(null);
|
||||
final SubscriptionsStore.NodeCouple respFinance = store.recreatePath("/finance", oldRoot);
|
||||
final SubscriptionsStore.NodeCouple respPlus = store.recreatePath("/+", respFinance.root);
|
||||
|
||||
//Verify
|
||||
assertNotNull(respPlus.root);
|
||||
assertNull(respPlus.root.m_token);
|
||||
assertEquals(1, respPlus.root.m_children.size());
|
||||
assertTrue(respPlus.root.m_children.get(0).m_children.contains(respPlus.createdNode));
|
||||
assertTrue(respPlus.root.m_children.get(0).m_children.contains(respFinance.createdNode));
|
||||
}
|
||||
|
||||
private static Token[] asArray(Object... l) {
|
||||
Token[] tokens = new Token[l.length];
|
||||
for (int i = 0; i < l.length; i++) {
|
||||
Object o = l[i];
|
||||
if (o instanceof Token) {
|
||||
tokens[i] = (Token) o;
|
||||
} else {
|
||||
tokens[i] = new Token(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private void assertEqualsSeq(Token[] exptected, List<Token> result) {
|
||||
List<Token> expectedList = Arrays.asList(exptected);
|
||||
assertEquals(expectedList, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import static io.moquette.proto.messages.AbstractMessage.QOSType.*;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class TreeNodeTest {
|
||||
|
||||
public static final String CLIENT_ID = "FAKE_CLIENT_ID";
|
||||
|
||||
@Test
|
||||
public void testAddSubscriptionOverwriteAnExistingWithDifferentCleanSessionFlag() throws Exception {
|
||||
TreeNode root = new TreeNode(null);
|
||||
Subscription existingSub = new Subscription(CLIENT_ID, "/topic", LEAST_ONE, false);
|
||||
root.addSubscription(existingSub);
|
||||
Set<Subscription> subs = root.findAllByClientID(CLIENT_ID);
|
||||
assertEquals(1, subs.size());
|
||||
|
||||
//Exercise
|
||||
Subscription overwritingSub = new Subscription(CLIENT_ID, "/topic", LEAST_ONE, true);
|
||||
root.addSubscription(overwritingSub);
|
||||
|
||||
//Verify
|
||||
//the second subscription has overwritten the first one
|
||||
subs = root.findAllByClientID(CLIENT_ID);
|
||||
assertEquals(1, subs.size());
|
||||
Subscription storedSub = subs.iterator().next();
|
||||
assertEquals(overwritingSub.isCleanSession(), storedSub.isCleanSession());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.persistence;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import io.moquette.server.IntegrationUtils;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MapDBPersistentStoreTest {
|
||||
|
||||
MapDBPersistentStore m_storageService;
|
||||
ISessionsStore m_sessionsStore;
|
||||
IMessagesStore m_messagesStore;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
IntegrationUtils.cleanPersistenceFile(Constants.DEFAULT_PERSISTENT_PATH);
|
||||
m_storageService = new MapDBPersistentStore(Constants.DEFAULT_PERSISTENT_PATH);
|
||||
m_storageService.initStore();
|
||||
m_messagesStore = m_storageService.messagesStore();
|
||||
m_sessionsStore = m_storageService.sessionsStore(m_messagesStore);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (m_storageService != null) {
|
||||
m_storageService.close();
|
||||
}
|
||||
|
||||
IntegrationUtils.cleanPersistenceFile(Constants.DEFAULT_PERSISTENT_PATH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overridingSubscriptions() {
|
||||
Subscription oldSubscription = new Subscription("FAKE_CLI_ID_1", "/topic", AbstractMessage.QOSType.MOST_ONE, false);
|
||||
m_sessionsStore.addNewSubscription(oldSubscription);
|
||||
Subscription overrindingSubscription = new Subscription("FAKE_CLI_ID_1", "/topic", AbstractMessage.QOSType.EXACTLY_ONCE, false);
|
||||
m_sessionsStore.addNewSubscription(overrindingSubscription);
|
||||
|
||||
//Verify
|
||||
List<Subscription> subscriptions = m_sessionsStore.listAllSubscriptions();
|
||||
assertEquals(1, subscriptions.size());
|
||||
Subscription sub = subscriptions.get(0);
|
||||
assertEquals(overrindingSubscription.getRequestedQos(), sub.getRequestedQos());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID_notExistingClientSession() {
|
||||
int packetId = m_messagesStore.nextPacketID("NOT_EXISTING_CLI");
|
||||
assertEquals(1, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID_existingClientSession() {
|
||||
//Force creation of inflight map for the CLIENT session
|
||||
int packetId = m_messagesStore.nextPacketID("CLIENT");
|
||||
assertEquals(1, packetId);
|
||||
|
||||
//request a second packetID
|
||||
packetId = m_messagesStore.nextPacketID("CLIENT");
|
||||
assertEquals(2, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID() {
|
||||
//request a first ID
|
||||
|
||||
int packetId = m_messagesStore.nextPacketID("CLIENT");
|
||||
m_sessionsStore.inFlight("CLIENT", packetId, "ABCDE"); //simulate an inflight
|
||||
assertEquals(1, packetId);
|
||||
|
||||
//release the ID
|
||||
m_sessionsStore.inFlightAck("CLIENT", packetId);
|
||||
|
||||
//request a second packetID, counter restarts from 0
|
||||
packetId = m_messagesStore.nextPacketID("CLIENT");
|
||||
assertEquals(1, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseShutdownCommitTask() throws InterruptedException {
|
||||
m_storageService.close();
|
||||
|
||||
//verify the executor is shutdown
|
||||
assertTrue("Storage service scheduler can't be stopped in 3 seconds",
|
||||
m_storageService.m_scheduler.awaitTermination(3, TimeUnit.SECONDS));
|
||||
assertTrue(m_storageService.m_scheduler.isTerminated());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.persistence;
|
||||
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemorySessionStore implements ISessionsStore {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
|
||||
|
||||
private Map<String, Set<Subscription>> m_persistentSubscriptions = new HashMap<>();
|
||||
|
||||
private Map<String, MapDBPersistentStore.PersistentSession> m_persistentSessions = new HashMap<>();
|
||||
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private Map<String, Map<Integer, String>> m_inflightStore = new HashMap<>();
|
||||
//maps clientID->[guid*]
|
||||
private Map<String, Set<String>> m_enqueuedStore = new HashMap<>();
|
||||
//maps clientID->[messageID*]
|
||||
private Map<String, Set<Integer>> m_secondPhaseStore = new HashMap<>();
|
||||
|
||||
private Map<String, Map<Integer, String>> m_messageToGuids;
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
public MemorySessionStore(IMessagesStore messagesStore, Map<String, Map<Integer, String>> messageToGuids) {
|
||||
m_messageToGuids = messageToGuids;
|
||||
this.m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(String topic, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topic, clientID);
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
return;
|
||||
}
|
||||
Set<Subscription> clientSubscriptions = m_persistentSubscriptions.get(clientID);
|
||||
//search for the subscription to remove
|
||||
Subscription toBeRemoved = null;
|
||||
for (Subscription sub : clientSubscriptions) {
|
||||
if (sub.getTopicFilter().equals(topic)) {
|
||||
toBeRemoved = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeRemoved != null) {
|
||||
clientSubscriptions.remove(toBeRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
final String clientID = newSubscription.getClientId();
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
}
|
||||
|
||||
Set<Subscription> subs = m_persistentSubscriptions.get(clientID);
|
||||
if (!subs.contains(newSubscription)) {
|
||||
subs.add(newSubscription);
|
||||
m_persistentSubscriptions.put(clientID, subs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
m_persistentSubscriptions.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSubscriptions(String clientID, Set<Subscription> subscriptions) {
|
||||
m_persistentSubscriptions.put(clientID, subscriptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_persistentSubscriptions.containsKey(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}>", clientID);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession, false));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MapDBPersistentStore.PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
ClientSession clientSession = new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
if (storedSession.active) {
|
||||
clientSession.activate();
|
||||
}
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Subscription> listAllSubscriptions() {
|
||||
List<Subscription> allSubscriptions = new ArrayList<>();
|
||||
for (Map.Entry<String, Set<Subscription>> entry : m_persistentSubscriptions.entrySet()) {
|
||||
allSubscriptions.addAll(entry.getValue());
|
||||
}
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(String clientID) {
|
||||
activationHelper(clientID, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate(String clientID) {
|
||||
activationHelper(clientID, false);
|
||||
}
|
||||
|
||||
private void activationHelper(String clientID, boolean activation) {
|
||||
MapDBPersistentStore.PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
if (storedSession == null) {
|
||||
throw new IllegalStateException((activation ? "activating" : "deactivating") + " a session never stored/created, clientID <"+ clientID + ">", null);
|
||||
}
|
||||
MapDBPersistentStore.PersistentSession newStoredSession = new MapDBPersistentStore.PersistentSession(storedSession.cleanSession, activation);
|
||||
m_persistentSessions.put(clientID, newStoredSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlightAck(String clientID, int messageID) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
LOG.error("Can't find the inFlight record for client <{}>", clientID);
|
||||
return;
|
||||
}
|
||||
m.remove(messageID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlight(String clientID, int messageID, String guid) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
}
|
||||
m.put(messageID, guid);
|
||||
this.m_inflightStore.put(clientID, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindToDeliver(String guid, String clientID) {
|
||||
Set<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
Set<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.remove(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.remove(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAckWaiting(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapToGuid(String clientID, int messageID) {
|
||||
HashMap<Integer, String> guids = (HashMap<Integer, String>) Utils.defaultGet(m_messageToGuids,
|
||||
clientID, new HashMap<Integer, String>());
|
||||
return guids.get(messageID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.testclient;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import io.moquette.parser.netty.MQTTDecoder;
|
||||
import io.moquette.parser.netty.MQTTEncoder;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class used just to send and receive MQTT messages without any protocol login
|
||||
* in action, just use the encoder/decoder part.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class Client {
|
||||
|
||||
public static interface ICallback {
|
||||
void call(AbstractMessage msg);
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
|
||||
final ClientNettyMQTTHandler handler = new ClientNettyMQTTHandler();
|
||||
EventLoopGroup workerGroup;
|
||||
Channel m_channel;
|
||||
private boolean m_connectionLost = false;
|
||||
|
||||
public Client(String host) {
|
||||
this(host, Constants.PORT);
|
||||
}
|
||||
|
||||
public Client(String host, int port) {
|
||||
|
||||
handler.setClient(this);
|
||||
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(workerGroup);
|
||||
b.channel(NioSocketChannel.class);
|
||||
b.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
b.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast("decoder", new MQTTDecoder());
|
||||
pipeline.addLast("encoder", new MQTTEncoder());
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
|
||||
// Start the client.
|
||||
m_channel = b.connect(host, port).sync().channel();
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Error received in client setup", ex);
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
ICallback callback;
|
||||
public void setCallback(ICallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void sendMessage(AbstractMessage msg) {
|
||||
m_channel.writeAndFlush(msg);
|
||||
}
|
||||
|
||||
void messageReceived(AbstractMessage msg) {
|
||||
LOG.info("Received message " + msg);
|
||||
if (this.callback != null) {
|
||||
this.callback.call(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void setConnectionLost(boolean status) {
|
||||
m_connectionLost = status;
|
||||
}
|
||||
|
||||
public boolean isConnectionLost() {
|
||||
return m_connectionLost;
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
// Wait until the connection is closed.
|
||||
m_channel.closeFuture().sync();
|
||||
if (workerGroup == null) {
|
||||
throw new IllegalStateException("Invoked close on an Acceptor that wasn't initialized");
|
||||
}
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.testclient;
|
||||
|
||||
import io.moquette.proto.Utils;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.moquette.proto.messages.AbstractMessage;
|
||||
import io.moquette.server.netty.NettyMQTTHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
class ClientNettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NettyMQTTHandler.class);
|
||||
private Client m_client;
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object message) {
|
||||
AbstractMessage msg = (AbstractMessage) message;
|
||||
LOG.info("Received a message of type {}", Utils.msgType2String(msg.getMessageType()));
|
||||
m_client.messageReceived(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx)throws Exception {
|
||||
m_client.setConnectionLost(true);
|
||||
ctx.close(/*false*/);
|
||||
}
|
||||
|
||||
void setClient(Client client) {
|
||||
m_client = client;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package io.moquette.testclient;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.moquette.parser.netty.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class is used to have a fluent interface to interact with a server.
|
||||
* Inspired by org.kaazing.robot
|
||||
*/
|
||||
public class RawClient {
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
class RawMessageHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object message) {
|
||||
LOG.info("Received a message {}", message);
|
||||
try {
|
||||
ByteBuf in = (ByteBuf) message;
|
||||
int readBytes = in.readableBytes();
|
||||
heapBuffer.writeBytes(in);
|
||||
readableBytesSem.release(readBytes);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
//m_client.setConnectionLost(true);
|
||||
disconnectLatch.countDown();
|
||||
ctx.close(/*false*/);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RawClient.class);
|
||||
final RawMessageHandler handler;
|
||||
EventLoopGroup workerGroup;
|
||||
Channel m_channel;
|
||||
|
||||
private boolean connected = false;
|
||||
private ByteBuf heapBuffer;
|
||||
private CountDownLatch disconnectLatch;
|
||||
private final Semaphore readableBytesSem;
|
||||
|
||||
private RawClient(String host, int port) {
|
||||
handler = new RawMessageHandler();
|
||||
heapBuffer = Unpooled.buffer(128);
|
||||
disconnectLatch = new CountDownLatch(1);
|
||||
readableBytesSem = new Semaphore(0, true);
|
||||
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(workerGroup);
|
||||
b.channel(NioSocketChannel.class);
|
||||
b.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
b.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
|
||||
// Start the client.
|
||||
m_channel = b.connect(host, port).sync().channel();
|
||||
this.connected = true;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Error received in client setup", ex);
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
public static RawClient connect(String host, int port) {
|
||||
return new RawClient(host, port);
|
||||
}
|
||||
|
||||
public RawClient isConnected() {
|
||||
if (!this.connected) {
|
||||
throw new IllegalStateException("Can't connect the client");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public RawClient write(int... bytes) {
|
||||
ByteBuf buff = Unpooled.buffer(bytes.length);
|
||||
buff.clear();
|
||||
for (int b : bytes) {
|
||||
buff.writeByte((byte) b);
|
||||
}
|
||||
m_channel.write(buff);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RawClient writeWithSize(String str) {
|
||||
ByteBuf buff = Unpooled.buffer(str.length() + 2);
|
||||
buff.writeBytes(Utils.encodeString(str));
|
||||
m_channel.write(buff);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write just String bytes not length
|
||||
* */
|
||||
public RawClient write(String str) {
|
||||
ByteBuf out = Unpooled.buffer(str.length());
|
||||
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.
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
out.writeBytes(raw);
|
||||
m_channel.write(out);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RawClient flush() {
|
||||
m_channel.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
public RawClient read(int expectedByte) {
|
||||
try {
|
||||
readableBytesSem.acquire(1);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalStateException("Interrupted while waiting data");
|
||||
}
|
||||
byte b = heapBuffer.readByte();
|
||||
if (b != expectedByte) {
|
||||
throw new IllegalStateException(String.format("Expected byte 0x%02X but found 0x%02X", b, expectedByte));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect the closing of the underling channel, with timeout
|
||||
* */
|
||||
public void closed(long timeout) throws InterruptedException {
|
||||
disconnectLatch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect closing the connect with unbound time
|
||||
* */
|
||||
public void closed() throws InterruptedException {
|
||||
disconnectLatch.await();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user