Moved project package from org.eclipse.moquette to io.moquette

This commit is contained in:
andsel
2015-11-20 21:30:59 +01:00
parent f438ef7f49
commit 371c740321
175 changed files with 629 additions and 669 deletions
@@ -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());
}
}