Rearchitected the broker, switching from ProtocolProcessor and publisher to MQTTConnection, Session and PostOffice

This commit is contained in:
andsel
2018-11-02 12:33:59 +01:00
parent 0ad6ffdd3c
commit a2266e5bbe
60 changed files with 5225 additions and 1025 deletions
@@ -15,14 +15,12 @@
*/
package io.moquette.spi.impl;
import io.moquette.interception.InterceptHandler;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.security.PermitAllAuthorizatorPolicy;
import io.moquette.spi.impl.subscriptions.*;
import io.moquette.spi.security.IAuthorizator;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
@@ -81,7 +79,7 @@ abstract class AbstractProtocolProcessorCommonUtils {
subscriptions.init(sessionsRepository);
m_processor = new ProtocolProcessor();
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR, sessionsRepository, false);
new PermitAllAuthorizatorPolicy(), NO_OBSERVERS_INTERCEPTOR, sessionsRepository, false);
}
void verifyNoPublishIsReceived() {
@@ -126,14 +124,14 @@ abstract class AbstractProtocolProcessorCommonUtils {
assertEquals(expectedSubscription, subscription);
}
protected MqttSubAckMessage subscribeWithoutVerify(String topic, MqttQoS desiredQos) {
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
this.m_processor.processSubscribe(m_channel, subscribe);
return m_channel.readOutbound();
}
// protected MqttSubAckMessage subscribeWithoutVerify(String topic, MqttQoS desiredQos) {
// MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
// .addSubscription(desiredQos, topic)
// .messageId(1)
// .build();
// this.m_processor.processSubscribe(m_channel, subscribe);
// return m_channel.readOutbound();
// }
protected void subscribeAndNotReadResponse(String topic, MqttQoS desiredQos) {
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
@@ -143,16 +141,6 @@ abstract class AbstractProtocolProcessorCommonUtils {
this.m_processor.processSubscribe(m_channel, subscribe);
}
protected void unsubscribe(String topic) {
final int messageId = 1;
MqttUnsubscribeMessage msg = MqttMessageBuilders.unsubscribe()
.addTopicFilter(topic)
.messageId(messageId)
.build();
m_processor.processUnsubscribe(m_channel, msg);
}
protected void unsubscribeAndVerifyAck(String topic) {
final int messageId = 1;
MqttUnsubscribeMessage msg = MqttMessageBuilders.unsubscribe()
@@ -1,315 +0,0 @@
package io.moquette.spi.impl;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.ConnectionDescriptorStore;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttMessageBuilders;
import io.netty.handler.codec.mqtt.MqttVersion;
import org.junit.Before;
import org.junit.Test;
import static io.moquette.spi.impl.NettyChannelAssertions.assertEqualsConnAck;
import static io.moquette.spi.impl.ProtocolProcessorTest.*;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ConnectHandlerTest {
EmbeddedChannel session;
MqttMessageBuilders.ConnectBuilder connMsg;
IMessagesStore m_messagesStore;
ISessionsStore m_sessionStore;
ISubscriptionsDirectory subscriptions;
MockAuthenticator m_mockAuthenticator;
private ConnectHandler sut;
private DisconnectHandler disconnectHandler;
private SessionsRepository sessionsRepository;
@Before
public void setUp() {
connMsg = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1).cleanSession(true);
session = new EmbeddedChannel();
MemoryStorageService memStorage = new MemoryStorageService(null, null);
m_messagesStore = memStorage.messagesStore();
m_sessionStore = memStorage.sessionsStore();
m_mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
subscriptions = new CTrieSubscriptionDirectory();
sessionsRepository = new SessionsRepository(m_sessionStore, null);
subscriptions.init(sessionsRepository);
final ConnectionDescriptorStore connectionsRegistry = new ConnectionDescriptorStore();
sut = new ConnectHandler(connectionsRegistry, NO_OBSERVERS_INTERCEPTOR, sessionsRepository, m_sessionStore, m_mockAuthenticator,
new PermitAllAuthorizator(), subscriptions, true, true, false, null);
disconnectHandler = new DisconnectHandler(connectionsRegistry, sessionsRepository,
subscriptions, NO_OBSERVERS_INTERCEPTOR);
}
@Test
public void testZeroByteClientIdWithCleanSession() {
// Allow zero byte client ids
sut = new ConnectHandler(new ConnectionDescriptorStore(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore, null), m_sessionStore, m_mockAuthenticator,
new PermitAllAuthorizator(), subscriptions, true, true, false, null);
// Connect message with clean session set to true and client id is null.
MqttConnectMessage msg = connMsg.clientId(null)
.protocolVersion(MqttVersion.MQTT_3_1_1)
.cleanSession(true)
.build();
sut.processConnect(session, msg);
assertEqualsConnAck("Connection MUST be accepted, unique clientid MUST be generated and clean session to true",
CONNECTION_ACCEPTED, session.readOutbound());
assertTrue("Connection should be valid and open,", session.isOpen());
}
@Test
public void invalidAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER + "_fake")
.password(TEST_PWD)
.build();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, session.readOutbound());
assertFalse("Connection should be closed by the broker.", session.isOpen());
}
@Test
public void testConnect_badClientID() {
connMsg.clientId("extremely_long_clientID_greater_than_23").build();
// Exercise
sut.processConnect(session, connMsg.clientId("extremely_long_clientID_greater_than_23").build());
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
}
@Test
public void testWill() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).willFlag(true)
.willTopic("topic").willMessage("Topic message").build();
// Exercise
// m_handler.setMessaging(mockedMessaging);
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
assertTrue("Connection is accepted and therefore should remain open.", session.isOpen());
// 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 connectWithSameClientIDBadCredentialsDoesntDropExistingClient() {
// Connect a client1
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER)
.password(TEST_PWD)
.build();
sut.processConnect(session, msg);
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
// create another connect same clientID but with bad credentials
MqttConnectMessage evilClientConnMsg = MqttMessageBuilders.connect()
.protocolVersion(MqttVersion.MQTT_3_1)
.clientId(FAKE_CLIENT_ID)
.username(ProtocolProcessorTest.EVIL_TEST_USER)
.password(ProtocolProcessorTest.EVIL_TEST_PWD)
.build();
EmbeddedChannel evilSession = new EmbeddedChannel();
// Exercise
sut.processConnect(evilSession, evilClientConnMsg);
// Verify
// the evil client gets a not auth notification
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, evilSession.readOutbound());
// the good client remains connected
assertTrue(session.isOpen());
assertFalse(evilSession.isOpen());
}
@Test
public void acceptAnonymousClient() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).build();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
assertTrue("Connection is accepted and therefore should remain open.", session.isOpen());
}
@Test
public void validAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER).password(TEST_PWD).build();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
assertTrue("Connection is accepted and therefore should remain open.", session.isOpen());
}
@Test
public void noPasswdAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER)
.build();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, session.readOutbound());
assertFalse("Connection should be closed by the broker.", session.isOpen());
}
@Test
public void prohibitAnonymousClient() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).build();
reinitProcessorProhibitingAnonymousClients();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, session.readOutbound());
assertFalse("Connection should be closed by the broker.", session.isOpen());
}
protected void reinitProcessorProhibitingAnonymousClients() {
sut = new ConnectHandler(new ConnectionDescriptorStore(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore, null), m_sessionStore, m_mockAuthenticator,
new PermitAllAuthorizator(), subscriptions, false, true, false, null);
}
@Test
public void prohibitAnonymousClient_providingUsername() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER + "_fake")
.build();
reinitProcessorProhibitingAnonymousClients();
// Exercise
sut.processConnect(session, msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, session.readOutbound());
assertFalse("Connection should be closed by the broker.", session.isOpen());
}
@Test
public void testZeroByteClientIdNotAllowed() {
sut = new ConnectHandler(new ConnectionDescriptorStore(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore, null), m_sessionStore, m_mockAuthenticator,
new PermitAllAuthorizator(), subscriptions, false, false, false, null);
// Connect message with clean session set to true and client id is null.
MqttConnectMessage msg = connMsg.clientId(null)
.protocolVersion(MqttVersion.MQTT_3_1_1)
.cleanSession(true)
.build();
sut.processConnect(session, msg);
assertEqualsConnAck("Zero byte client identifiers are not allowed",
CONNECTION_REFUSED_IDENTIFIER_REJECTED, session.readOutbound());
assertFalse("Connection should closed.", session.isOpen());
}
@Test
public void testZeroByteClientIdWithoutCleanSession() {
// Allow zero byte client ids
reinitProtocolProcessorWithZeroLengthClientIdAndAnonymousClients();
// Connect message without clean session set to true but client id is still null
MqttConnectMessage msg = MqttMessageBuilders.connect().clientId(null).protocolVersion(MqttVersion.MQTT_3_1_1)
.build();
sut.processConnect(session, msg);
assertEqualsConnAck(
"Identifier should be rejected due to having clean session set to false.",
CONNECTION_REFUSED_IDENTIFIER_REJECTED,
session.readOutbound());
assertFalse("Connection should be closed by the broker.", session.isOpen());
}
protected void reinitProtocolProcessorWithZeroLengthClientIdAndAnonymousClients() {
sut = new ConnectHandler(new ConnectionDescriptorStore(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore, null), m_sessionStore, m_mockAuthenticator,
new PermitAllAuthorizator(), subscriptions, true, true, false, null);
}
@Test
public void testConnAckContainsSessionPresentFlag() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.protocolVersion(MqttVersion.MQTT_3_1_1).build();
NettyUtils.clientID(session, FAKE_CLIENT_ID);
NettyUtils.cleanSession(session, false);
// Connect a first time
sut.processConnect(session, msg);
// disconnect
disconnectHandler.processDisconnect(session, FAKE_CLIENT_ID);
// Exercise, reconnect
EmbeddedChannel firstReceiverSession = new EmbeddedChannel();
sut.processConnect(firstReceiverSession, msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, firstReceiverSession.readOutbound());
assertTrue("Connection is accepted and therefore should remain open.", firstReceiverSession.isOpen());
}
@Test
public void connectWithCleanSessionUpdateClientSession() {
// first connect with clean session true
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).cleanSession(true).build();
sut.processConnect(session, msg);
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
disconnectHandler.processDisconnect(session, FAKE_CLIENT_ID);
assertFalse(session.isOpen());
// second connect with clean session false
session = new EmbeddedChannel();
MqttConnectMessage secondConnMsg = MqttMessageBuilders.connect().clientId(FAKE_CLIENT_ID)
.protocolVersion(MqttVersion.MQTT_3_1).build();
sut.processConnect(session, secondConnMsg);
assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
// Verify client session is clean false
ClientSession session = sessionsRepository.sessionForClient(FAKE_CLIENT_ID);
assertFalse(session.isCleanSession());
// Verify
// assertEqualsConnAck(CONNECTION_ACCEPTED, session.readOutbound());
}
}
@@ -25,12 +25,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Test utility to implements authenticator instance.
*/
class MockAuthenticator implements IAuthenticator {
public class MockAuthenticator implements IAuthenticator {
private Set<String> m_clientIds;
private Map<String, String> m_userPwds;
MockAuthenticator(Set<String> clientIds, Map<String, String> userPwds) {
public MockAuthenticator(Set<String> clientIds, Map<String, String> userPwds) {
m_clientIds = clientIds;
m_userPwds = userPwds;
}
@@ -27,13 +27,13 @@ import static org.junit.Assert.assertTrue;
/**
* Some useful assertions used by Netty's EmbeddedChannel in tests.
*/
final class NettyChannelAssertions {
public final class NettyChannelAssertions {
static void assertEqualsConnAck(MqttConnectReturnCode expectedCode, Object connAck) {
public static void assertEqualsConnAck(MqttConnectReturnCode expectedCode, Object connAck) {
assertEqualsConnAck(null, expectedCode, connAck);
}
static void assertEqualsConnAck(String msg, MqttConnectReturnCode expectedCode, Object connAck) {
public static void assertEqualsConnAck(String msg, MqttConnectReturnCode expectedCode, Object connAck) {
assertTrue("connAck is not an instance of ConnAckMessage", connAck instanceof MqttConnAckMessage);
MqttConnAckMessage connAckMsg = (MqttConnAckMessage) connAck;
@@ -43,12 +43,12 @@ final class NettyChannelAssertions {
assertEquals(msg, expectedCode, connAckMsg.variableHeader().connectReturnCode());
}
static void assertConnAckAccepted(EmbeddedChannel channel) {
public static void assertConnAckAccepted(EmbeddedChannel channel) {
channel.flush();
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
}
static void assertEqualsSubAck(/* byte expectedCode, */ Object subAck) {
public static void assertEqualsSubAck(/* byte expectedCode, */ Object subAck) {
assertTrue(subAck instanceof MqttSubAckMessage);
// SubAckMessage connAckMsg = (SubAckMessage) connAck;
// assertEquals(expectedCode, connAckMsg.getReturnCode());
@@ -16,17 +16,18 @@
package io.moquette.spi.impl;
import io.moquette.broker.PostOfficePublishTest;
import io.moquette.interception.InterceptHandler;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.security.PermitAllAuthorizatorPolicy;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
@@ -69,201 +70,138 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
initializeProcessorAndSubsystems();
}
@Test
public void testPublishToItself() {
final Subscription subscription = new Subscription(FAKE_CLIENT_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
//same test moved into PostOfficePublishTest
// @Test
// public void testPublishToItself() {
// final Subscription subscription = new Subscription(FAKE_CLIENT_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
//
// // subscriptions.matches(topic) redefine the method to return true
// ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
//
// @Override
// public List<Subscription> matches(Topic topic) {
// if (topic.toString().equals(NEWS_TOPIC)) {
// return Collections.singletonList(subscription);
// } else {
// throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
// }
// }
// };
//
// // simulate a connect that register a clientID to an IoSession
// MemoryStorageService storageService = new MemoryStorageService(null, null);
// SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
// subs.init(sessionsRepository);
// m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
// NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
//
// connect_v3_1();
//
// // Exercise
// publishToAs("FakeCLI", NEWS_TOPIC, AT_MOST_ONCE, false);
//
// // Verify
// verifyPublishIsReceived();
// }
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
if (topic.toString().equals(NEWS_TOPIC)) {
return Collections.singletonList(subscription);
} else {
throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
}
}
};
// simulate a connect that register a clientID to an IoSession
MemoryStorageService storageService = new MemoryStorageService(null, null);
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
subs.init(sessionsRepository);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
connect_v3_1();
// Exercise
publishToAs("FakeCLI", NEWS_TOPIC, AT_MOST_ONCE, false);
// Verify
verifyPublishIsReceived();
}
@Test
public void testPublishToMultipleSubscribers() {
final Subscription subscription = new Subscription(FAKE_CLIENT_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
final Subscription subscriptionClient2 = new Subscription(
FAKE_CLIENT_ID2,
new Topic(NEWS_TOPIC),
AT_MOST_ONCE);
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
if (topic.toString().equals(NEWS_TOPIC)) {
return Arrays.asList(subscription, subscriptionClient2);
} else {
throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
}
}
};
// simulate a connect that register a clientID to an IoSession
MemoryStorageService storageService = new MemoryStorageService(null, null);
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
subs.init(sessionsRepository);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
EmbeddedChannel firstReceiverChannel = new EmbeddedChannel();
MqttConnectMessage connectMessage = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1)
.clientId(FAKE_CLIENT_ID).cleanSession(true).build();
m_processor.processConnect(firstReceiverChannel, connectMessage);
assertConnAckAccepted(firstReceiverChannel);
// connect the second fake subscriber
EmbeddedChannel secondReceiverChannel = new EmbeddedChannel();
MqttConnectMessage connectMessage2 = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1)
.clientId(FAKE_CLIENT_ID2).cleanSession(true).build();
m_processor.processConnect(secondReceiverChannel, connectMessage2);
assertConnAckAccepted(secondReceiverChannel);
// Exercise
MqttPublishMessage msg = MqttMessageBuilders.publish().topicName(NEWS_TOPIC).qos(AT_MOST_ONCE)
.retained(false).payload(Unpooled.copiedBuffer("Hello".getBytes(UTF_8))).build();
NettyUtils.userName(m_channel, "FakeCLI");
m_processor.processPublish(m_channel, msg);
// Verify
firstReceiverChannel.flush();
MqttPublishMessage pub2FirstSubscriber = firstReceiverChannel.readOutbound();
assertNotNull(pub2FirstSubscriber);
String firstMessageContent = DebugUtils.payload2Str(pub2FirstSubscriber.payload());
assertEquals("Hello", firstMessageContent);
secondReceiverChannel.flush();
MqttPublishMessage pub2SecondSubscriber = secondReceiverChannel.readOutbound();
assertNotNull(pub2SecondSubscriber);
String secondMessageContent = DebugUtils.payload2Str(pub2SecondSubscriber.payload());
assertEquals("Hello", secondMessageContent);
}
@Test
public void testSubscribe() {
connect();
// Exercise & verify
subscribe(NEWS_TOPIC, AT_MOST_ONCE);
}
@Test
public void testSubscribedToNotAuthorizedTopic() {
final String fakeUserName = "UnAuthUser";
NettyUtils.userName(m_channel, fakeUserName);
IAuthorizator mockAuthorizator = mock(IAuthorizator.class);
when(mockAuthorizator.canRead(eq(new Topic(NEWS_TOPIC)), eq(fakeUserName), eq(FAKE_CLIENT_ID)))
.thenReturn(false);
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true, mockAuthorizator,
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
connect();
//Exercise
MqttSubAckMessage subAckMsg = subscribeWithoutVerify(NEWS_TOPIC, AT_MOST_ONCE);
verifyFailureQos(subAckMsg);
}
private void verifyFailureQos(MqttSubAckMessage subAckMsg) {
List<Integer> grantedQoSes = subAckMsg.payload().grantedQoSLevels();
assertEquals(1, grantedQoSes.size());
assertTrue(grantedQoSes.contains(MqttQoS.FAILURE.value()));
}
@Test
public void testDoubleSubscribe() {
connect();
assertEquals(0, subscriptions.size());
subscribe(NEWS_TOPIC, AT_MOST_ONCE);
assertEquals(1, subscriptions.size());
//Exercise & verify
subscribe(NEWS_TOPIC, AT_MOST_ONCE);
}
@Test
public void testSubscribeWithBadFormattedTopic() {
connect();
assertEquals(0, subscriptions.size());
//Exercise
MqttSubAckMessage subAckMsg = subscribeWithoutVerify(BAD_FORMATTED_TOPIC, AT_MOST_ONCE);
assertEquals(0, subscriptions.size());
verifyFailureQos(subAckMsg);
}
@Test
public void testUnsubscribeWithBadFormattedTopic() {
// Exercise
unsubscribe(BAD_FORMATTED_TOPIC);
// Verify
assertFalse("If client unsubscribe with bad topic than channel must be closed, (issue 68)", m_channel.isOpen());
}
@Test
public void testPublishOfRetainedMessage_afterNewSubscription() throws Exception {
// simulate a connect that register a clientID to an IoSession
final Subscription subscription =
new Subscription(PUBLISHER_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
if (topic.toString().equals(NEWS_TOPIC)) {
return Collections.singletonList(subscription);
} else {
throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
}
}
};
MemoryStorageService storageService = new MemoryStorageService(null, null);
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
subs.init(sessionsRepository);
// 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, this.sessionsRepository, false);
connect_v3_1_asClient(PUBLISHER_ID);
publishToAs(PUBLISHER_ID, NEWS_TOPIC, AT_MOST_ONCE, true);
NettyUtils.cleanSession(m_channel, false);
// Exercise
subscribeAndNotReadResponse("#", AT_MOST_ONCE);
// Verify
verifyPublishIsReceived();
}
//same test moved into PostOfficePublishTest
// @Test
// public void testPublishToMultipleSubscribers() {
// final Subscription subscription = new Subscription(FAKE_CLIENT_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
// final Subscription subscriptionClient2 = new Subscription(
// FAKE_CLIENT_ID2,
// new Topic(NEWS_TOPIC),
// AT_MOST_ONCE);
//
// // subscriptions.matches(topic) redefine the method to return true
// ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
//
// @Override
// public List<Subscription> matches(Topic topic) {
// if (topic.toString().equals(NEWS_TOPIC)) {
// return Arrays.asList(subscription, subscriptionClient2);
// } else {
// throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
// }
// }
// };
//
// // simulate a connect that register a clientID to an IoSession
// MemoryStorageService storageService = new MemoryStorageService(null, null);
// SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
// subs.init(sessionsRepository);
// m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
// NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
//
// EmbeddedChannel firstReceiverChannel = new EmbeddedChannel();
// MqttConnectMessage connectMessage = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1)
// .clientId(FAKE_CLIENT_ID).cleanSession(true).build();
// m_processor.processConnect(firstReceiverChannel, connectMessage);
// assertConnAckAccepted(firstReceiverChannel);
//
// // connect the second fake subscriber
// EmbeddedChannel secondReceiverChannel = new EmbeddedChannel();
// MqttConnectMessage connectMessage2 = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1)
// .clientId(FAKE_CLIENT_ID2).cleanSession(true).build();
// m_processor.processConnect(secondReceiverChannel, connectMessage2);
// assertConnAckAccepted(secondReceiverChannel);
//
// // Exercise
// MqttPublishMessage msg = MqttMessageBuilders.publish().topicName(NEWS_TOPIC).qos(AT_MOST_ONCE)
// .retained(false).payload(Unpooled.copiedBuffer("Hello".getBytes(UTF_8))).build();
// NettyUtils.userName(m_channel, "FakeCLI");
// m_processor.processPublish(m_channel, msg);
//
// // Verify
// firstReceiverChannel.flush();
// MqttPublishMessage pub2FirstSubscriber = firstReceiverChannel.readOutbound();
// assertNotNull(pub2FirstSubscriber);
// String firstMessageContent = DebugUtils.payload2Str(pub2FirstSubscriber.payload());
// assertEquals("Hello", firstMessageContent);
//
// secondReceiverChannel.flush();
// MqttPublishMessage pub2SecondSubscriber = secondReceiverChannel.readOutbound();
// assertNotNull(pub2SecondSubscriber);
// String secondMessageContent = DebugUtils.payload2Str(pub2SecondSubscriber.payload());
// assertEquals("Hello", secondMessageContent);
// }
//
// //TODO move this test on PostOfficeSubscribeTest
// @Test
// public void testPublishOfRetainedMessage_afterNewSubscription() throws Exception {
// // simulate a connect that register a clientID to an IoSession
// final Subscription subscription =
// new Subscription(PUBLISHER_ID, new Topic(NEWS_TOPIC), AT_MOST_ONCE);
//
// // subscriptions.matches(topic) redefine the method to return true
// ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
//
// @Override
// public List<Subscription> matches(Topic topic) {
// if (topic.toString().equals(NEWS_TOPIC)) {
// return Collections.singletonList(subscription);
// } else {
// throw new IllegalArgumentException("Expected " + NEWS_TOPIC + " buf found " + topic);
// }
// }
// };
// MemoryStorageService storageService = new MemoryStorageService(null, null);
// SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
// subs.init(sessionsRepository);
//
// // simulate a connect that register a clientID to an IoSession
// m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
// NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
// connect_v3_1_asClient(PUBLISHER_ID);
// publishToAs(PUBLISHER_ID, NEWS_TOPIC, AT_MOST_ONCE, true);
// NettyUtils.cleanSession(m_channel, false);
//
// // Exercise
// subscribeAndNotReadResponse("#", AT_MOST_ONCE);
//
// // Verify
// verifyPublishIsReceived();
// }
@Test
public void testRepublishAndConsumePersistedMessages_onReconnect() {
@@ -276,7 +214,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
retainedMessage.setClientID(PUBLISHER_ID);
m_messagesStore.storeRetained(new Topic("/topic"), retainedMessage);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
connect_v3_1_asClient(PUBLISHER_ID);
@@ -285,84 +223,85 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
assertTrue(this.sessionsRepository.sessionForClient(PUBLISHER_ID).isEmptyQueue());
}
@Test
public void publishNoPublishToInactiveSession() {
// create an inactive session for Subscriber
ISubscriptionsDirectory mockedSubscriptions = mock(ISubscriptionsDirectory.class);
Subscription inactiveSub = new Subscription("Subscriber", new Topic("/topic"), MqttQoS.AT_LEAST_ONCE);
List<Subscription> inactiveSubscriptions = Collections.singletonList(inactiveSub);
when(mockedSubscriptions.matches(eq(new Topic("/topic")))).thenReturn(inactiveSubscriptions);
m_processor = new ProtocolProcessor();
m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
m_processor.sessionsRepository.createNewSession("Subscriber", false);
// Exercise
connectAsClient("Publisher");
publishToAs("Publisher", "/topic", AT_MOST_ONCE, true);
verifyNoPublishIsReceived();
}
/**
* 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
connect_v3_1_asClient("Publisher");
// prepare and existing retained store
publishToAs("Publisher", NEWS_TOPIC, AT_LEAST_ONCE, 100, true);
Collection<IMessagesStore.StoredMessage> messages = m_messagesStore
.searchMatching(key -> key.match(new Topic(NEWS_TOPIC)));
assertFalse(messages.isEmpty());
// Exercise
// send a message that clean the previous retained publish
publishToAs("Publisher", NEWS_TOPIC, AT_MOST_ONCE, true);
// Verify
messages = m_messagesStore.searchMatching(key -> key.match(new Topic(NEWS_TOPIC)));
assertTrue(messages.isEmpty());
}
@Test
public void testLowerTheQosToTheRequestedBySubscription() {
Subscription subQos1 = new Subscription("Sub A", new Topic("a/b"), MqttQoS.AT_LEAST_ONCE);
assertEquals(MqttQoS.AT_LEAST_ONCE, lowerQosToTheSubscriptionDesired(subQos1, EXACTLY_ONCE));
Subscription subQos2 = new Subscription("Sub B", new Topic("a/+"), EXACTLY_ONCE);
assertEquals(EXACTLY_ONCE, lowerQosToTheSubscriptionDesired(subQos2, EXACTLY_ONCE));
}
@Test
public void testSubscribeReceivePublishedMessageAtSubscriberQoSAndNotPublisherQoS() {
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory();
MemoryStorageService storageService = new MemoryStorageService(null, null);
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
subs.init(sessionsRepository);
// 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, this.sessionsRepository, false);
connect_v3_1_asClient(PUBLISHER_ID);
publishQoS2ToAs(this.m_channel, PUBLISHER_ID, NEWS_TOPIC, 1, true);
disconnect();
// connect from the subscriber
EmbeddedChannel subscriberChannel = new EmbeddedChannel();
NettyUtils.clientID(subscriberChannel, SUBSCRIBER_ID);
NettyUtils.cleanSession(subscriberChannel, false);
connectAsClient(subscriberChannel, SUBSCRIBER_ID);
// subscribe to the topic news with granted QoS0
subscribe(subscriberChannel, NEWS_TOPIC, MqttQoS.AT_MOST_ONCE);
// Verify the retained message arrives with QoS0 instead of QoS2
verifyPublishIsReceived(subscriberChannel, HELLO_WORLD_MQTT, MqttQoS.AT_MOST_ONCE);
}
// Moved to PostOfficePublishTest
// @Test
// public void publishNoPublishToInactiveSession() {
// // create an inactive session for Subscriber
// ISubscriptionsDirectory mockedSubscriptions = mock(ISubscriptionsDirectory.class);
// Subscription inactiveSub = new Subscription("Subscriber", new Topic("/topic"), MqttQoS.AT_LEAST_ONCE);
// List<Subscription> inactiveSubscriptions = Collections.singletonList(inactiveSub);
// when(mockedSubscriptions.matches(eq(new Topic("/topic")))).thenReturn(inactiveSubscriptions);
// m_processor = new ProtocolProcessor();
// m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
// NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
//
// m_processor.sessionsRepository.createNewSession("Subscriber", false);
//
// // Exercise
// connectAsClient("Publisher");
// publishToAs("Publisher", "/topic", AT_MOST_ONCE, true);
//
// verifyNoPublishIsReceived();
// }
//
// /**
// * 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
// connect_v3_1_asClient("Publisher");
//
// // prepare and existing retained store
// publishToAs("Publisher", NEWS_TOPIC, AT_LEAST_ONCE, 100, true);
//
// Collection<IMessagesStore.StoredMessage> messages = m_messagesStore
// .searchMatching(key -> key.match(new Topic(NEWS_TOPIC)));
// assertFalse(messages.isEmpty());
//
// // Exercise
// // send a message that clean the previous retained publish
// publishToAs("Publisher", NEWS_TOPIC, AT_MOST_ONCE, true);
//
// // Verify
// messages = m_messagesStore.searchMatching(key -> key.match(new Topic(NEWS_TOPIC)));
// assertTrue(messages.isEmpty());
// }
//
// @Test
// public void testLowerTheQosToTheRequestedBySubscription() {
// Subscription subQos1 = new Subscription("Sub A", new Topic("a/b"), MqttQoS.AT_LEAST_ONCE);
// assertEquals(MqttQoS.AT_LEAST_ONCE, lowerQosToTheSubscriptionDesired(subQos1, EXACTLY_ONCE));
//
// Subscription subQos2 = new Subscription("Sub B", new Topic("a/+"), EXACTLY_ONCE);
// assertEquals(EXACTLY_ONCE, lowerQosToTheSubscriptionDesired(subQos2, EXACTLY_ONCE));
// }
//
// @Test
// public void testSubscribeReceivePublishedMessageAtSubscriberQoSAndNotPublisherQoS() {
// ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory();
// MemoryStorageService storageService = new MemoryStorageService(null, null);
// SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore(), null);
// subs.init(sessionsRepository);
//
// // simulate a connect that register a clientID to an IoSession
// m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizatorPolicy(),
// NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository, false);
// connect_v3_1_asClient(PUBLISHER_ID);
// publishQoS2ToAs(this.m_channel, PUBLISHER_ID, NEWS_TOPIC, 1, true);
// disconnect();
//
// // connect from the subscriber
// EmbeddedChannel subscriberChannel = new EmbeddedChannel();
// NettyUtils.clientID(subscriberChannel, SUBSCRIBER_ID);
// NettyUtils.cleanSession(subscriberChannel, false);
// connectAsClient(subscriberChannel, SUBSCRIBER_ID);
//
// // subscribe to the topic news with granted QoS0
// subscribe(subscriberChannel, NEWS_TOPIC, MqttQoS.AT_MOST_ONCE);
//
// // Verify the retained message arrives with QoS0 instead of QoS2
// verifyPublishIsReceived(subscriberChannel, HELLO_WORLD_MQTT, MqttQoS.AT_MOST_ONCE);
// }
}
@@ -20,7 +20,7 @@ import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.security.PermitAllAuthorizatorPolicy;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
@@ -60,7 +60,7 @@ public class ProtocolProcessor_CONNECT_Test {
subscriptions.init(sessionsRepository);
processor = new ProtocolProcessor();
processor.init(subscriptions, m_messagesStore, sessionStore, mockAuthenticator, true,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR,
new PermitAllAuthorizatorPolicy(), NO_OBSERVERS_INTERCEPTOR,
new SessionsRepository(this.sessionStore, null), false);
}
@@ -16,9 +16,6 @@
package io.moquette.spi.impl;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.junit.Before;
@@ -31,331 +31,333 @@ public class ProtocolProcessor_VariousUseCases_Test extends AbstractProtocolProc
initializeProcessorAndSubsystems();
}
@Test
public void testCleanSession_maintainClientSubscriptions() {
connectAsClient(this.m_channel, "TestClient");
subscribe("/topic", AT_MOST_ONCE);
disconnect();
// reconnect and publish
EmbeddedChannel anotherChannel = new EmbeddedChannel();
connectAsClient(anotherChannel, "TestClient");
publishToAs(anotherChannel, "TestClient", "/topic", AT_MOST_ONCE, false);
verifyPublishIsReceived(anotherChannel);
}
@Ignore("At the end it's equal to testCleanSession_maintainClientSubscriptions")
@Test
public void testCleanSession_maintainClientSubscriptions_againstClientDestruction() {
connectAsClient("TestClient");
subscribe("/topic", AT_MOST_ONCE);
disconnect();
this.m_channel = new EmbeddedChannel();
// reconnect and publish
connectAsClient("TestClient");
publishToAs("TestClient", "/topic", AT_MOST_ONCE, false);
verifyPublishIsReceived();
}
/**
* Check that after a client has connected with clean session false, subscribed to some topic
* and exited, if it reconnects with clean session true, the broker correctly cleanup every
* previous subscription
*/
@Test
public void testCleanSession_correctlyClientSubscriptions() {
connectAsClient("TestClient");
subscribe("/topic", AT_MOST_ONCE);
disconnect();
this.m_channel = new EmbeddedChannel();
// reconnect and publish
connectWithCleanSession("TestClient");
publishToAs("TestClient", "/topic", AT_MOST_ONCE, false);
verifyNoPublishIsReceived();
}
@Test
public void testRetain_maintainMessage_againstClientDestruction() {
connect();
publishToAs(FAKE_CLIENT_ID, "/topic", AT_LEAST_ONCE, 66, true);
disconnect();
// reconnect and publish
this.m_channel = new EmbeddedChannel();
connect();
subscribe("/topic", AT_MOST_ONCE);
verifyPublishIsReceived();
}
@Test
public void testUnsubscribe_do_not_notify_anymore_same_session() {
connect();
subscribe("/topic", AT_MOST_ONCE);
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
verifyPublishIsReceived();
unsubscribeAndVerifyAck("/topic");
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
verifyNoPublishIsReceived();
}
@Test
public void testUnsubscribe_do_not_notify_anymore_new_session() {
connect();
subscribe("/topic", AT_MOST_ONCE);
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
verifyPublishIsReceived();
unsubscribeAndVerifyAck("/topic");
disconnect();
this.m_channel = new EmbeddedChannel();
connect();
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
verifyNoPublishIsReceived();
}
@Test
public void testPublishWithQoS1() {
connect();
subscribe("/topic", AT_LEAST_ONCE);
publishToAs(FAKE_CLIENT_ID, "/topic", AT_LEAST_ONCE, 66, false);
disconnect();
verifyPublishIsReceived(AT_LEAST_ONCE);
}
@Test
public void testPublishWithQoS1_notCleanSession() {
connect();
subscribe("/topic", AT_LEAST_ONCE);
disconnect();
// publish a QoS 1 message another client publish a message on the topic
publishFromAnotherClient("/topic", AT_LEAST_ONCE);
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived();
}
private void publishFromAnotherClient(String topic, MqttQoS qos) {
EmbeddedChannel anotherChannel = new EmbeddedChannel();
connectAsClient(anotherChannel, "AnotherClient");
publishToAs(anotherChannel, "AnotherClient", topic, qos, 67, false);
disconnect(anotherChannel);
}
private void publishFromAnotherClient(String topic, String payload, MqttQoS qos) {
EmbeddedChannel anotherChannel = new EmbeddedChannel();
connectAsClient(anotherChannel, "AnotherClient");
publishToAs(anotherChannel, "AnotherClient", topic, payload, qos, 68, false);
disconnect(anotherChannel);
}
private void publishQoS2FromAnotherClient(String topic) {
EmbeddedChannel anotherChannel = new EmbeddedChannel();
connectAsClient(anotherChannel, "AnotherClient");
publishQoS2ToAs(anotherChannel, "AnotherClient", topic, 67, false);
disconnect(anotherChannel);
}
@Test
public void checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession() {
connect();
subscribe("/topic", AT_LEAST_ONCE);
disconnect();
this.m_channel = new EmbeddedChannel();
connect();
subscribe("/topic", AT_LEAST_ONCE);
// publish a QoS 1 message another client publish a message on the topic
publishFromAnotherClient("/topic", AT_LEAST_ONCE);
// Verify that after a reconnection the client receive the message
verifyPublishIsReceived();
}
@Test
public void testPublishWithQoS2() {
connect();
subscribe("/topic", EXACTLY_ONCE);
disconnect();
publishQoS2FromAnotherClient("/topic");
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived(EXACTLY_ONCE);
}
@Test
public void avoidMultipleNotificationsAfterMultipleReconnection_cleanSessionFalseQoS1() {
connect();
subscribe("/topic", AT_LEAST_ONCE);
disconnect();
publishFromAnotherClient("/topic", "Hello MQTT 1", AT_LEAST_ONCE);
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived("Hello MQTT 1", AT_LEAST_ONCE);
disconnect();
// publish other message
publishFromAnotherClient("/topic", "Hello MQTT 2", AT_LEAST_ONCE);
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived("Hello MQTT 2", AT_LEAST_ONCE);
}
@Test
public void testConnectSubPub_cycle_getTimeout_on_second_disconnect_issue142() {
connect();
subscribe("/topic", AT_MOST_ONCE);
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, true);
disconnect();
// second loop
this.m_channel = new EmbeddedChannel();
connect();
subscribe("/topic", AT_MOST_ONCE);
publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, true);
disconnect();
assertFalse("after a disconnect the client should be disconnected", this.m_channel.isOpen());
}
@Test
public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() {
EmbeddedChannel publisher = new EmbeddedChannel();
connectAsClient(publisher, "Publisher");
connect();
subscribe("/topic", AT_LEAST_ONCE);
publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-1", AT_LEAST_ONCE, 99, false);
verifyPublishIsReceived("Hello world MQTT!!-1", AT_LEAST_ONCE);
disconnect();
publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-2", AT_LEAST_ONCE, 100, false);
publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-3", AT_LEAST_ONCE, 101, false);
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived("Hello world MQTT!!-2", AT_LEAST_ONCE);
verifyPublishIsReceived("Hello world MQTT!!-3", AT_LEAST_ONCE);
}
@Test
public void checkReplayStoredPublish_forNoCleanSession_qos1() {
EmbeddedChannel publisher = new EmbeddedChannel();
connectAsClient(publisher, "Publisher");
connect();
subscribe("/topic", AT_LEAST_ONCE);
disconnect();
publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-1", AT_LEAST_ONCE, 99, false);
this.m_channel = new EmbeddedChannel();
connect();
subscribeAndNotReadResponse("/topic", AT_LEAST_ONCE);
verifyPublishIsReceived("Hello world MQTT!!-1", AT_LEAST_ONCE);
}
/**
* 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() {
connect();
subscribe("topic", EXACTLY_ONCE);
disconnect();
EmbeddedChannel publisher = new EmbeddedChannel();
connectAsClient(publisher, "Publisher");
publishQoS2ToAs(publisher, "Publisher", "topic", "Hello1", 101, true);
publishQoS2ToAs(publisher, "Publisher", "topic", "Hello2", 102, true);
disconnect(publisher);
this.m_channel = new EmbeddedChannel();
connect();
verifyPublishIsReceived("Hello1", EXACTLY_ONCE);
verifyPublishIsReceived("Hello2", EXACTLY_ONCE);
}
/**
* 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() {
EmbeddedChannel publisher = new EmbeddedChannel();
connectAsClient(publisher, "Publisher");
connect();
subscribe("a/+", EXACTLY_ONCE);
subscribe("a/b", AT_LEAST_ONCE);
// force the publisher to send
publishToAs(publisher, "Publisher", "a/b", "Hello world MQTT!!", AT_LEAST_ONCE, 60, false);
verifyPublishIsReceived("Hello world MQTT!!", AT_LEAST_ONCE);
// try to listen a second publish
verifyNoPublishIsReceived();
}
@Test
public void testForceClientDisconnection_issue116() {
EmbeddedChannel clientXA = new EmbeddedChannel();
connectAsClient(clientXA, "subscriber");
subscribe(clientXA, "topic", AT_MOST_ONCE);
EmbeddedChannel clientXB = new EmbeddedChannel();
connectAsClient(clientXB, "publisher");
publishQoS2ToAs(clientXB, "publisher", "topic", "Hello", 20, false);
EmbeddedChannel clientYA = new EmbeddedChannel();
connectAsClient(clientYA, "subscriber");
subscribe(clientYA, "topic", AT_MOST_ONCE);
EmbeddedChannel clientYB = new EmbeddedChannel();
connectAsClient(clientYB, "publisher");
publishQoS2ToAs(clientYB, "publisher", "topic", "Hello 2", 20, true);
assertFalse("First 'subscriber' channel MUST be closed by the broker", clientXA.isOpen());
verifyPublishIsReceived(clientYA, "Hello 2", AT_MOST_ONCE);
}
@Test
public void testPublishToEmptyTopic() {
EmbeddedChannel subscriber = new EmbeddedChannel();
connectAsClient(subscriber, "Subscriber");
subscribe(subscriber, "#", AT_LEAST_ONCE);
// Exercise
connect();
publishToAs(FAKE_CLIENT_ID, "", AT_LEAST_ONCE, 1, false);
// Verify
verifyNoPublishIsReceived(subscriber);
assertFalse("On empty topic subscription the channel MUST be closed", m_channel.isOpen());
}
// @Test
// public void testCleanSession_maintainClientSubscriptions() {
// connectAsClient(this.m_channel, "TestClient");
// subscribe("/topic", AT_MOST_ONCE);
// disconnect();
//
// // reconnect and publish
// EmbeddedChannel anotherChannel = new EmbeddedChannel();
// connectAsClient(anotherChannel, "TestClient");
//
// publishToAs(anotherChannel, "TestClient", "/topic", AT_MOST_ONCE, false);
//
// verifyPublishIsReceived(anotherChannel);
// }
//
// @Ignore("At the end it's equal to testCleanSession_maintainClientSubscriptions")
// @Test
// public void testCleanSession_maintainClientSubscriptions_againstClientDestruction() {
// connectAsClient("TestClient");
// subscribe("/topic", AT_MOST_ONCE);
// disconnect();
//
// this.m_channel = new EmbeddedChannel();
// // reconnect and publish
// connectAsClient("TestClient");
//
// publishToAs("TestClient", "/topic", AT_MOST_ONCE, false);
//
// verifyPublishIsReceived();
// }
// /**
// * Check that after a client has connected with clean session false, subscribed to some topic
// * and exited, if it reconnects with clean session true, the broker correctly cleanup every
// * previous subscription
// */
// @Test
// public void testCleanSession_correctlyClientSubscriptions() {
// connectAsClient("TestClient");
// subscribe("/topic", AT_MOST_ONCE);
// disconnect();
//
// this.m_channel = new EmbeddedChannel();
// // reconnect and publish
// connectWithCleanSession("TestClient");
//
// publishToAs("TestClient", "/topic", AT_MOST_ONCE, false);
//
// verifyNoPublishIsReceived();
// }
// Duplicated, just verify retain works correctly
// @Test
// public void testRetain_maintainMessage_againstClientDestruction() {
// connect();
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_LEAST_ONCE, 66, true);
// disconnect();
//
// // reconnect and publish
// this.m_channel = new EmbeddedChannel();
// connect();
// subscribe("/topic", AT_MOST_ONCE);
//
// verifyPublishIsReceived();
// }
// @Test
// public void testUnsubscribe_do_not_notify_anymore_same_session() {
// connect();
// subscribe("/topic", AT_MOST_ONCE);
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
// verifyPublishIsReceived();
//
// unsubscribeAndVerifyAck("/topic");
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
// verifyNoPublishIsReceived();
// }
// @Test
// public void testUnsubscribe_do_not_notify_anymore_new_session() {
// connect();
// subscribe("/topic", AT_MOST_ONCE);
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
// verifyPublishIsReceived();
//
// unsubscribeAndVerifyAck("/topic");
// disconnect();
//
// this.m_channel = new EmbeddedChannel();
// connect();
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, false);
// verifyNoPublishIsReceived();
// }
// Moved to PostOfficePublishTest
// @Test
// public void testPublishWithQoS1() {
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_LEAST_ONCE, 66, false);
// disconnect();
//
// verifyPublishIsReceived(AT_LEAST_ONCE);
// }
// Moved to PostOfficePublishTest.forwardQoS1PublishesWhenNotCleanSessionReconnects
// @Test
// public void testPublishWithQoS1_notCleanSession() {
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
// disconnect();
//
// // publish a QoS 1 message another client publish a message on the topic
// publishFromAnotherClient("/topic", AT_LEAST_ONCE);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// verifyPublishIsReceived();
// }
// private void publishFromAnotherClient(String topic, MqttQoS qos) {
// EmbeddedChannel anotherChannel = new EmbeddedChannel();
// connectAsClient(anotherChannel, "AnotherClient");
// publishToAs(anotherChannel, "AnotherClient", topic, qos, 67, false);
// disconnect(anotherChannel);
// }
// private void publishFromAnotherClient(String topic, String payload, MqttQoS qos) {
// EmbeddedChannel anotherChannel = new EmbeddedChannel();
// connectAsClient(anotherChannel, "AnotherClient");
// publishToAs(anotherChannel, "AnotherClient", topic, payload, qos, 68, false);
// disconnect(anotherChannel);
// }
// private void publishQoS2FromAnotherClient(String topic) {
// EmbeddedChannel anotherChannel = new EmbeddedChannel();
// connectAsClient(anotherChannel, "AnotherClient");
// publishQoS2ToAs(anotherChannel, "AnotherClient", topic, 67, false);
// disconnect(anotherChannel);
// }
// @Test
// public void checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession() {
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
// disconnect();
//
// this.m_channel = new EmbeddedChannel();
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
//
// // publish a QoS 1 message another client publish a message on the topic
// publishFromAnotherClient("/topic", AT_LEAST_ONCE);
//
// // Verify that after a reconnection the client receive the message
// verifyPublishIsReceived();
// }
// @Test
// public void testPublishWithQoS2() {
// connect();
// subscribe("/topic", EXACTLY_ONCE);
// disconnect();
//
// publishQoS2FromAnotherClient("/topic");
//
// this.m_channel = new EmbeddedChannel();
// connect();
//
// verifyPublishIsReceived(EXACTLY_ONCE);
// }
//
// @Test
// public void avoidMultipleNotificationsAfterMultipleReconnection_cleanSessionFalseQoS1() {
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
// disconnect();
//
// publishFromAnotherClient("/topic", "Hello MQTT 1", AT_LEAST_ONCE);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// verifyPublishIsReceived("Hello MQTT 1", AT_LEAST_ONCE);
// disconnect();
//
// // publish other message
// publishFromAnotherClient("/topic", "Hello MQTT 2", AT_LEAST_ONCE);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// verifyPublishIsReceived("Hello MQTT 2", AT_LEAST_ONCE);
// }
// @Test
// public void testConnectSubPub_cycle_getTimeout_on_second_disconnect_issue142() {
// connect();
// subscribe("/topic", AT_MOST_ONCE);
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, true);
// disconnect();
//
// // second loop
// this.m_channel = new EmbeddedChannel();
// connect();
// subscribe("/topic", AT_MOST_ONCE);
// publishToAs(FAKE_CLIENT_ID, "/topic", AT_MOST_ONCE, true);
// disconnect();
//
// assertFalse("after a disconnect the client should be disconnected", this.m_channel.isOpen());
// }
//
// @Test
// public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() {
// EmbeddedChannel publisher = new EmbeddedChannel();
// connectAsClient(publisher, "Publisher");
//
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
//
// publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-1", AT_LEAST_ONCE, 99, false);
// verifyPublishIsReceived("Hello world MQTT!!-1", AT_LEAST_ONCE);
// disconnect();
//
// publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-2", AT_LEAST_ONCE, 100, false);
// publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-3", AT_LEAST_ONCE, 101, false);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// verifyPublishIsReceived("Hello world MQTT!!-2", AT_LEAST_ONCE);
// verifyPublishIsReceived("Hello world MQTT!!-3", AT_LEAST_ONCE);
// }
//
// @Test
// public void checkReplayStoredPublish_forNoCleanSession_qos1() {
// EmbeddedChannel publisher = new EmbeddedChannel();
// connectAsClient(publisher, "Publisher");
//
// connect();
// subscribe("/topic", AT_LEAST_ONCE);
// disconnect();
//
// publishToAs(publisher, "Publisher", "/topic", "Hello world MQTT!!-1", AT_LEAST_ONCE, 99, false);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// subscribeAndNotReadResponse("/topic", AT_LEAST_ONCE);
// verifyPublishIsReceived("Hello world MQTT!!-1", AT_LEAST_ONCE);
// }
//
// /**
// * 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() {
// connect();
// subscribe("topic", EXACTLY_ONCE);
// disconnect();
//
// EmbeddedChannel publisher = new EmbeddedChannel();
// connectAsClient(publisher, "Publisher");
// publishQoS2ToAs(publisher, "Publisher", "topic", "Hello1", 101, true);
// publishQoS2ToAs(publisher, "Publisher", "topic", "Hello2", 102, true);
// disconnect(publisher);
//
// this.m_channel = new EmbeddedChannel();
// connect();
// verifyPublishIsReceived("Hello1", EXACTLY_ONCE);
// verifyPublishIsReceived("Hello2", EXACTLY_ONCE);
// }
// /**
// * 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() {
// EmbeddedChannel publisher = new EmbeddedChannel();
// connectAsClient(publisher, "Publisher");
//
// connect();
// subscribe("a/+", EXACTLY_ONCE);
// subscribe("a/b", AT_LEAST_ONCE);
//
// // force the publisher to send
// publishToAs(publisher, "Publisher", "a/b", "Hello world MQTT!!", AT_LEAST_ONCE, 60, false);
//
// verifyPublishIsReceived("Hello world MQTT!!", AT_LEAST_ONCE);
// // try to listen a second publish
// verifyNoPublishIsReceived();
// }
// @Test
// public void testForceClientDisconnection_issue116() {
// EmbeddedChannel clientXA = new EmbeddedChannel();
// connectAsClient(clientXA, "subscriber");
// subscribe(clientXA, "topic", AT_MOST_ONCE);
//
// EmbeddedChannel clientXB = new EmbeddedChannel();
// connectAsClient(clientXB, "publisher");
// publishQoS2ToAs(clientXB, "publisher", "topic", "Hello", 20, false);
//
// EmbeddedChannel clientYA = new EmbeddedChannel();
// connectAsClient(clientYA, "subscriber");
// subscribe(clientYA, "topic", AT_MOST_ONCE);
//
// EmbeddedChannel clientYB = new EmbeddedChannel();
// connectAsClient(clientYB, "publisher");
// publishQoS2ToAs(clientYB, "publisher", "topic", "Hello 2", 20, true);
//
// assertFalse("First 'subscriber' channel MUST be closed by the broker", clientXA.isOpen());
// verifyPublishIsReceived(clientYA, "Hello 2", AT_MOST_ONCE);
// }
// @Test
// public void testPublishToEmptyTopic() {
// EmbeddedChannel subscriber = new EmbeddedChannel();
// connectAsClient(subscriber, "Subscriber");
//
// subscribe(subscriber, "#", AT_LEAST_ONCE);
//
// // Exercise
// connect();
// publishToAs(FAKE_CLIENT_ID, "", AT_LEAST_ONCE, 1, false);
//
// // Verify
// verifyNoPublishIsReceived(subscriber);
// assertFalse("On empty topic subscription the channel MUST be closed", m_channel.isOpen());
// }
}