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
@@ -0,0 +1,68 @@
package io.moquette.broker;
import io.moquette.spi.impl.DebugUtils;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
import static org.junit.Assert.*;
class ConnectionTestUtils {
static void assertConnectAccepted(EmbeddedChannel channel) {
MqttConnAckMessage connAck = channel.readOutbound();
final MqttConnectReturnCode connAckReturnCode = connAck.variableHeader().connectReturnCode();
assertEquals("Connect must be accepted", CONNECTION_ACCEPTED, connAckReturnCode);
}
static void verifyReceivePublish(EmbeddedChannel embeddedChannel, String expectedTopic, String expectedContent) {
MqttPublishMessage receivedPublish = embeddedChannel.flushOutbound().readOutbound();
assertPublishIsCorrect(expectedTopic, expectedContent, receivedPublish);
}
private static void assertPublishIsCorrect(String expectedTopic, String expectedContent, MqttPublishMessage receivedPublish) {
assertNotNull("Expecting a PUBLISH message", receivedPublish);
final String decodedPayload = DebugUtils.payload2Str(receivedPublish.payload());
assertEquals(expectedContent, decodedPayload);
assertEquals(expectedTopic, receivedPublish.variableHeader().topicName());
}
static void verifyReceiveRetainedPublish(EmbeddedChannel embeddedChannel, String expectedTopic, String expectedContent) {
MqttPublishMessage receivedPublish = embeddedChannel.readOutbound();
assertPublishIsCorrect(expectedTopic, expectedContent, receivedPublish);
assertTrue("MUST be retained publish", receivedPublish.fixedHeader().isRetain());
}
static void verifyReceiveRetainedPublish(EmbeddedChannel embeddedChannel, String expectedTopic,
String expectedContent, MqttQoS expectedQos) {
MqttPublishMessage receivedPublish = embeddedChannel.flushOutbound().readOutbound();
assertEquals(receivedPublish.fixedHeader().qosLevel(), expectedQos);
assertPublishIsCorrect(expectedTopic, expectedContent, receivedPublish);
assertTrue("MUST be retained publish", receivedPublish.fixedHeader().isRetain());
}
static void verifyPublishIsReceived(EmbeddedChannel embCh, MqttQoS expectedQos, String expectedPayload) {
final MqttPublishMessage publishReceived = embCh.flushOutbound().readOutbound();
final String payloadMessage = DebugUtils.payload2Str(publishReceived.payload());
assertEquals("Sent and received payload must be identical", expectedPayload, payloadMessage);
assertEquals("Expected QoS don't match", expectedQos, publishReceived.fixedHeader().qosLevel());
}
static void verifyNoPublishIsReceived(EmbeddedChannel channel) {
final Object messageReceived = channel.readOutbound();
assertNull("Received an out message from processor while not expected", messageReceived);
}
static MqttConnectMessage buildConnect(String clientId) {
return MqttMessageBuilders.connect()
.clientId(clientId)
.build();
}
static MqttConnectMessage buildConnectNotClean(String clientId) {
return MqttMessageBuilders.connect()
.clientId(clientId)
.cleanSession(false)
.build();
}
}
@@ -0,0 +1,307 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.security.IAuthenticator;
import io.netty.channel.Channel;
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.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MQTTConnectionConnectTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
private static final String EVIL_TEST_USER = "eviluser";
private static final String EVIL_TEST_PWD = "unsecret";
private MQTTConnection sut;
private EmbeddedChannel channel;
private SessionRegistry sessionRegistry;
private MqttMessageBuilders.ConnectBuilder connMsg;
private static final BrokerConfiguration CONFIG = new BrokerConfiguration(true, true, false);
private IAuthenticator mockAuthenticator;
private PostOffice postOffice;
@Before
public void setUp() {
connMsg = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1).cleanSession(true);
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
ISubscriptionsDirectory subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
sessionRegistry = new SessionRegistry(subscriptions);
postOffice = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(), new MemoryRetainedRepository(), sessionRegistry);
sut = createMQTTConnection(CONFIG);
channel = (EmbeddedChannel) sut.channel;
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config) {
EmbeddedChannel channel = new EmbeddedChannel();
return createMQTTConnection(config, channel, postOffice);
}
private MQTTConnection createMQTTConnectionWithPostOffice(BrokerConfiguration config, PostOffice postOffice) {
EmbeddedChannel channel = new EmbeddedChannel();
return createMQTTConnection(config, channel, postOffice);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel, PostOffice postOffice) {
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, postOffice);
}
@Test
public void testZeroByteClientIdWithCleanSession() {
// Connect message with clean session set to true and client id is null.
MqttConnectMessage msg = MqttMessageBuilders.connect()
.protocolVersion(MqttVersion.MQTT_3_1_1)
.clientId(null)
.cleanSession(true)
.build();
sut.processConnect(msg);
assertEqualsConnAck("Connection must be accepted", CONNECTION_ACCEPTED, channel.readOutbound());
assertNotNull("unique clientid must be generated", sut.getClientId());
assertTrue("clean session flag must be true", sessionRegistry.retrieve(sut.getClientId()).isClean());
assertTrue("Connection must be open", channel.isOpen());
}
@Test
public void invalidAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER + "_fake")
.password(TEST_PWD)
.build();
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, channel.readOutbound());
assertFalse("Connection should be closed by the broker.", channel.isOpen());
}
@Test
public void testConnect_badClientID() {
connMsg.clientId("extremely_long_clientID_greater_than_23").build();
// Exercise
sut.processConnect(connMsg.clientId("extremely_long_clientID_greater_than_23").build());
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
}
@Test
public void testWillIsAccepted() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).willFlag(true)
.willTopic("topic").willMessage("Topic message").build();
// Exercise
// m_handler.setMessaging(mockedMessaging);
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
assertTrue("Connection is accepted and therefore should remain open", channel.isOpen());
}
@Test
public void testWillIsFired() {
final PostOffice postOfficeMock = mock(PostOffice.class);
sut = createMQTTConnectionWithPostOffice(CONFIG, postOfficeMock);
channel = (EmbeddedChannel) sut.channel;
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).willFlag(true)
.willTopic("topic").willMessage("Topic message").build();
sut.processConnect(msg);
// Exercise
sut.handleConnectionLost();
// Verify
verify(postOfficeMock).fireWill(any(Session.Will.class));
assertFalse("Connection is accepted and therefore should remain open", channel.isOpen());
}
@Test
public void acceptAnonymousClient() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).build();
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
assertTrue("Connection is accepted and therefore must remain open", channel.isOpen());
}
@Test
public void validAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER).password(TEST_PWD).build();
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
assertTrue("Connection is accepted and therefore must remain open", channel.isOpen());
}
@Test
public void noPasswdAuthentication() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER)
.build();
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, channel.readOutbound());
assertFalse("Connection must be closed by the broker", channel.isOpen());
}
@Test
public void prohibitAnonymousClient() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).build();
BrokerConfiguration config = new BrokerConfiguration(false, true, false);
sut = createMQTTConnection(config);
channel = (EmbeddedChannel) sut.channel;
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, channel.readOutbound());
assertFalse("Connection must be closed by the broker", channel.isOpen());
}
@Test
public void prohibitAnonymousClient_providingUsername() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER + "_fake")
.build();
BrokerConfiguration config = new BrokerConfiguration(false, true, false);
createMQTTConnection(config);
// Exercise
sut.processConnect(msg);
// Verify
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, channel.readOutbound());
assertFalse("Connection should be closed by the broker.", channel.isOpen());
}
@Test
public void testZeroByteClientIdNotAllowed() {
BrokerConfiguration config = new BrokerConfiguration(false, false, false);
sut = createMQTTConnection(config);
channel = (EmbeddedChannel) sut.channel;
// 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(msg);
assertEqualsConnAck("Zero byte client identifiers are not allowed",
CONNECTION_REFUSED_IDENTIFIER_REJECTED, channel.readOutbound());
assertFalse("Connection must closed", channel.isOpen());
}
@Test
public void testZeroByteClientIdWithoutCleanSession() {
// Allow zero byte client ids
// 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(msg);
assertEqualsConnAck("Identifier must be rejected due to having clean session set to false",
CONNECTION_REFUSED_IDENTIFIER_REJECTED, channel.readOutbound());
assertFalse("Connection must be closed by the broker", channel.isOpen());
}
@Test
public void testBindWithSameClientIDBadCredentialsDoesntDropExistingClient() {
// Connect a client1
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER)
.password(TEST_PWD)
.build();
sut.processConnect(msg);
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
// create another connect same clientID but with bad credentials
MqttConnectMessage evilClientConnMsg = MqttMessageBuilders.connect()
.protocolVersion(MqttVersion.MQTT_3_1)
.clientId(FAKE_CLIENT_ID)
.username(EVIL_TEST_USER)
.password(EVIL_TEST_PWD)
.build();
EmbeddedChannel evilChannel = new EmbeddedChannel();
// Exercise
BrokerConfiguration config = new BrokerConfiguration(true, true, false);
final MQTTConnection evilConnection = createMQTTConnection(config, evilChannel, postOffice);
evilConnection.processConnect(evilClientConnMsg);
// Verify
// the evil client gets a not auth notification
assertEqualsConnAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, evilChannel.readOutbound());
// the good client remains connected
assertTrue("Original connected client must remain connected", channel.isOpen());
assertFalse("Channel trying to connect with bad credentials must be closed", evilChannel.isOpen());
}
@Test
public void testForceClientDisconnection_issue116() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.username(TEST_USER)
.password(TEST_PWD)
.build();
sut.processConnect(msg);
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
// now create another connection and check the new one closes the older
MQTTConnection anotherConnection = createMQTTConnection(CONFIG);
anotherConnection.processConnect(msg);
EmbeddedChannel anotherChannel = (EmbeddedChannel) anotherConnection.channel;
assertEqualsConnAck(CONNECTION_ACCEPTED, anotherChannel.readOutbound());
// Verify
assertFalse("First 'FAKE_CLIENT_ID' channel MUST be closed by the broker", channel.isOpen());
assertTrue("Second 'FAKE_CLIENT_ID' channel MUST be still open", anotherChannel.isOpen());
}
}
@@ -0,0 +1,81 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.security.IAuthenticator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.MqttMessageBuilders;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.netty.handler.codec.mqtt.MqttVersion;
import org.junit.Before;
import org.junit.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertFalse;
public class MQTTConnectionPublishTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
private MQTTConnection sut;
private EmbeddedChannel channel;
private SessionRegistry sessionRegistry;
private MqttMessageBuilders.ConnectBuilder connMsg;
@Before
public void setUp() {
connMsg = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1).cleanSession(true);
BrokerConfiguration config = new BrokerConfiguration(true, true, false);
createMQTTConnection(config);
}
private void createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
sut = createMQTTConnection(config, channel);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
IAuthenticator mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
ISubscriptionsDirectory subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
sessionRegistry = new SessionRegistry(subscriptions);
final PostOffice postOffice = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(),
new MemoryRetainedRepository(), sessionRegistry);
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, postOffice);
}
@Test
public void dropConnectionOnPublishWithInvalidTopicFormat() {
// Connect message with clean session set to true and client id is null.
MqttPublishMessage publish = MqttMessageBuilders.publish()
.topicName("")
.retained(false)
.qos(MqttQoS.AT_MOST_ONCE)
.payload(Unpooled.copiedBuffer("Hello MQTT world!".getBytes(UTF_8))).build();
sut.processPublish(publish);
// Verify
assertFalse("Connection should be closed by the broker", channel.isOpen());
}
}
@@ -0,0 +1,298 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
import org.junit.Test;
import java.util.Set;
import static io.moquette.broker.PostOfficeUnsubscribeTest.CONFIG;
import static io.netty.handler.codec.mqtt.MqttQoS.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.*;
public class PostOfficeInternalPublishTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
private static final String PAYLOAD = "Hello MQTT World";
private MQTTConnection connection;
private EmbeddedChannel channel;
private PostOffice sut;
private ISubscriptionsDirectory subscriptions;
private MqttConnectMessage connectMessage;
private SessionRegistry sessionRegistry;
private MockAuthenticator mockAuthenticator;
private static final BrokerConfiguration ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID =
new BrokerConfiguration(true, true, false);
private MemoryRetainedRepository retainedRepository;
@Before
public void setUp() {
sessionRegistry = initPostOfficeAndSubsystems();
mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
connection = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID);
connectMessage = ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID);
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
return createMQTTConnection(config, channel);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, sut);
}
private SessionRegistry initPostOfficeAndSubsystems() {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
retainedRepository = new MemoryRetainedRepository();
SessionRegistry sessionRegistry = new SessionRegistry(subscriptions);
sut = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(), retainedRepository, sessionRegistry);
return sessionRegistry;
}
private void internalPublishNotRetainedTo(String topic) {
internalPublishTo(topic, AT_MOST_ONCE, false);
}
private void internalPublishRetainedTo(String topic) {
internalPublishTo(topic, AT_MOST_ONCE, true);
}
private void internalPublishTo(String topic, MqttQoS qos, boolean retained) {
MqttPublishMessage publish = MqttMessageBuilders.publish()
.topicName(topic)
.retained(retained)
.qos(qos)
.payload(Unpooled.copiedBuffer(PAYLOAD.getBytes(UTF_8))).build();
sut.internalPublish(publish, "INTRPUBL");
}
@Test
public void testClientSubscribeAfterNotRetainedQoS0IsSent() {
// connection.processConnect(connectMessage);
// ConnectionTestUtils.assertConnectAccepted(channel);
// Exercise
final String topic = "/topic";
internalPublishNotRetainedTo(topic);
subscribe(AT_MOST_ONCE, topic, connection);
// Verify
verifyNoPublishIsReceived(channel);
}
@Test
public void testClientSubscribeAfterRetainedQoS0IsSent() {
// Exercise
final String topic = "/topic";
internalPublishRetainedTo(topic);
subscribe(AT_MOST_ONCE, topic, connection);
// Verify
verifyNoPublishIsReceived(channel);
}
@Test
public void testClientSubscribeBeforeNotRetainedQoS0IsSent() {
subscribe(AT_MOST_ONCE, "/topic", connection);
// Exercise
internalPublishNotRetainedTo("/topic");
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_MOST_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeBeforeRetainedQoS0IsSent() {
subscribe(AT_MOST_ONCE, "/topic", connection);
// Exercise
internalPublishRetainedTo("/topic");
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_MOST_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeBeforeNotRetainedQoS1IsSent() {
subscribe(AT_LEAST_ONCE, "/topic", connection);
// Exercise
internalPublishTo("/topic", AT_LEAST_ONCE, false);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeAfterNotRetainedQoS1IsSent() {
// Exercise
internalPublishTo("/topic", AT_LEAST_ONCE, false);
subscribe(AT_LEAST_ONCE, "/topic", connection);
// Verify
verifyNoPublishIsReceived(channel);
}
@Test
public void testClientSubscribeBeforeRetainedQoS1IsSent() {
subscribe(AT_LEAST_ONCE, "/topic", connection);
// Exercise
internalPublishTo("/topic", AT_LEAST_ONCE, true);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeAfterRetainedQoS1IsSent() {
// Exercise
internalPublishTo("/topic", AT_LEAST_ONCE, true);
subscribe(AT_LEAST_ONCE, "/topic", connection);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeBeforeNotRetainedQoS2IsSent() {
subscribe(EXACTLY_ONCE, "/topic", connection);
// Exercise
internalPublishTo("/topic", EXACTLY_ONCE, false);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeAfterNotRetainedQoS2IsSent() {
// Exercise
internalPublishTo("/topic", EXACTLY_ONCE, false);
subscribe(EXACTLY_ONCE, "/topic", connection);
// Verify
verifyNoPublishIsReceived(channel);
}
@Test
public void testClientSubscribeBeforeRetainedQoS2IsSent() {
subscribe(EXACTLY_ONCE, "/topic", connection);
// Exercise
internalPublishTo("/topic", EXACTLY_ONCE, true);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeAfterRetainedQoS2IsSent() {
// Exercise
internalPublishTo("/topic", EXACTLY_ONCE, true);
subscribe(EXACTLY_ONCE, "/topic", connection);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, PAYLOAD);
}
@Test
public void testClientSubscribeAfterDisconnected() {
subscribe(AT_MOST_ONCE, "foo", connection);
connection.processDisconnect(null);
internalPublishTo("foo", AT_MOST_ONCE, false);
verifyNoPublishIsReceived(channel);
}
@Test
public void testClientSubscribeWithoutCleanSession() {
subscribe(AT_MOST_ONCE, "foo", connection);
connection.processDisconnect(null);
assertEquals(1, subscriptions.size());
MQTTConnection anotherConn = createMQTTConnection(CONFIG);
MqttConnectMessage connectMessage = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.cleanSession(false)
.build();
anotherConn.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted((EmbeddedChannel) anotherConn.channel);
assertEquals(1, subscriptions.size());
internalPublishTo("foo", MqttQoS.AT_MOST_ONCE, false);
ConnectionTestUtils.verifyPublishIsReceived((EmbeddedChannel) anotherConn.channel, AT_MOST_ONCE, PAYLOAD);
}
private void subscribe(MqttQoS topic, String newsTopic, MQTTConnection connection) {
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(topic, newsTopic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, this.connection);
MqttSubAckMessage subAck = ((EmbeddedChannel) this.connection.channel).readOutbound();
assertEquals(topic.value(), (int) subAck.payload().grantedQoSLevels().get(0));
}
protected void subscribe(MQTTConnection connection, String topic, MqttQoS desiredQos) {
EmbeddedChannel channel = (EmbeddedChannel) connection.channel;
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, connection);
MqttSubAckMessage subAck = channel.readOutbound();
assertEquals(desiredQos.value(), (int) subAck.payload().grantedQoSLevels().get(0));
final String clientId = connection.getClientId();
Subscription expectedSubscription = new Subscription(clientId, new Topic(topic), desiredQos);
final Set<Subscription> matchedSubscriptions = subscriptions.matchWithoutQosSharpening(new Topic(topic));
assertEquals(1, matchedSubscriptions.size());
final Subscription onlyMatchedSubscription = matchedSubscriptions.iterator().next();
assertEquals(expectedSubscription, onlyMatchedSubscription);
}
private void verifyNoPublishIsReceived(EmbeddedChannel channel) {
final Object messageReceived = channel.readOutbound();
assertNull("Received an out message from processor while not expected", messageReceived);
}
}
@@ -0,0 +1,381 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
import org.junit.Test;
import java.nio.charset.Charset;
import java.util.*;
import static io.moquette.broker.PostOfficeUnsubscribeTest.CONFIG;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
import static io.netty.handler.codec.mqtt.MqttQoS.EXACTLY_ONCE;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.*;
public class PostOfficePublishTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String FAKE_CLIENT_ID2 = "FAKE_456";
static final String SUBSCRIBER_ID = "Subscriber";
static final String PUBLISHER_ID = "Publisher";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
private static final String NEWS_TOPIC = "/news";
private static final String BAD_FORMATTED_TOPIC = "#MQTTClient";
private MQTTConnection connection;
private EmbeddedChannel channel;
private PostOffice sut;
private ISubscriptionsDirectory subscriptions;
public static final String FAKE_USER_NAME = "UnAuthUser";
private MqttConnectMessage connectMessage;
private SessionRegistry sessionRegistry;
private MockAuthenticator mockAuthenticator;
static final BrokerConfiguration ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID =
new BrokerConfiguration(true, true, false);
private MemoryRetainedRepository retainedRepository;
@Before
public void setUp() {
sessionRegistry = initPostOfficeAndSubsystems();
mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
connection = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID);
connectMessage = ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
return createMQTTConnection(config, channel);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, sut);
}
private SessionRegistry initPostOfficeAndSubsystems() {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
retainedRepository = new MemoryRetainedRepository();
SessionRegistry sessionRegistry = new SessionRegistry(subscriptions);
sut = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(), retainedRepository, sessionRegistry);
return sessionRegistry;
}
@Test
public void testPublishQoS0ToItself() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
// subscribe
subscribe(AT_MOST_ONCE, NEWS_TOPIC, connection);
// Exercise
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, FAKE_CLIENT_ID, payload, false);
// Verify
ConnectionTestUtils.verifyReceivePublish(channel, NEWS_TOPIC, "Hello world!");
}
@Test
public void testForceClientDisconnection_issue116() {
final MQTTConnection clientXA = connectAs("subscriber");
subscribe(clientXA, NEWS_TOPIC, AT_MOST_ONCE);
final MQTTConnection clientXB = connectAs("publisher");
final ByteBuf anyPayload = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
sut.receivedPublishQos2(clientXB, MqttMessageBuilders.publish()
.payload(anyPayload)
.qos(MqttQoS.EXACTLY_ONCE)
.retained(false)
.topicName(NEWS_TOPIC).build());
final MQTTConnection clientYA = connectAs("subscriber");
subscribe(clientYA, NEWS_TOPIC, AT_MOST_ONCE);
final MQTTConnection clientYB = connectAs("publisher");
final ByteBuf anyPayload2 = Unpooled.copiedBuffer("Hello 2", Charset.defaultCharset());
sut.receivedPublishQos2(clientYB, MqttMessageBuilders.publish()
.payload(anyPayload2)
.qos(MqttQoS.EXACTLY_ONCE)
.retained(true)
.topicName(NEWS_TOPIC).build());
// Verify
assertFalse("First 'subscriber' channel MUST be closed by the broker", clientXA.channel.isOpen());
ConnectionTestUtils.verifyPublishIsReceived((EmbeddedChannel) clientYA.channel, AT_MOST_ONCE, "Hello 2");
}
private MQTTConnection connectAs(String clientId) {
EmbeddedChannel channel = new EmbeddedChannel();
MQTTConnection connection = createMQTTConnection(CONFIG, channel);
connection.processConnect(ConnectionTestUtils.buildConnect(clientId));
ConnectionTestUtils.assertConnectAccepted(channel);
return connection;
}
private void subscribe(MqttQoS topic, String newsTopic, MQTTConnection connection) {
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(topic, newsTopic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, this.connection);
MqttSubAckMessage subAck = ((EmbeddedChannel) this.connection.channel).readOutbound();
assertEquals(topic.value(), (int) subAck.payload().grantedQoSLevels().get(0));
}
protected void subscribe(MQTTConnection connection, String topic, MqttQoS desiredQos) {
EmbeddedChannel channel = (EmbeddedChannel) connection.channel;
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, connection);
MqttSubAckMessage subAck = channel.readOutbound();
assertEquals(desiredQos.value(), (int) subAck.payload().grantedQoSLevels().get(0));
final String clientId = connection.getClientId();
Subscription expectedSubscription = new Subscription(clientId, new Topic(topic), desiredQos);
final Set<Subscription> matchedSubscriptions = subscriptions.matchWithoutQosSharpening(new Topic(topic));
assertEquals(1, matchedSubscriptions.size());
final Subscription onlyMatchedSubscription = matchedSubscriptions.iterator().next();
assertEquals(expectedSubscription, onlyMatchedSubscription);
}
@Test
public void testPublishToMultipleSubscribers() {
final Set<String> clientIds = new HashSet<>(Arrays.asList(FAKE_CLIENT_ID, FAKE_CLIENT_ID2));
mockAuthenticator = new MockAuthenticator(clientIds, singletonMap(TEST_USER, TEST_PWD));
EmbeddedChannel channel1 = new EmbeddedChannel();
MQTTConnection connection1 = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, channel1);
connection1.processConnect(ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID));
ConnectionTestUtils.assertConnectAccepted(channel1);
EmbeddedChannel channel2 = new EmbeddedChannel();
MQTTConnection connection2 = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, channel2);
connection2.processConnect(ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID2));
ConnectionTestUtils.assertConnectAccepted(channel2);
// subscribe
final MqttQoS qos = AT_MOST_ONCE;
final String newsTopic = NEWS_TOPIC;
subscribe(qos, newsTopic, connection1);
subscribe(qos, newsTopic, connection2);
// Exercise
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, FAKE_CLIENT_ID, payload, false);
// Verify
ConnectionTestUtils.verifyReceivePublish(channel1, NEWS_TOPIC, "Hello world!");
ConnectionTestUtils.verifyReceivePublish(channel2, NEWS_TOPIC, "Hello world!");
}
@Test
public void testPublishWithEmptyPayloadClearRetainedStore() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
this.retainedRepository.retain(new Topic(NEWS_TOPIC), MqttMessageBuilders.publish()
.payload(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "Hello world!"))
.qos(AT_LEAST_ONCE)
.build());
// Exercise
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, FAKE_CLIENT_ID, anyPayload, true);
// Verify
assertTrue("QoS0 MUST clean retained message for topic", retainedRepository.isEmtpy());
}
@Test
public void testPublishWithQoS1() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
// Exercise
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos1(connection, new Topic(NEWS_TOPIC), TEST_USER, anyPayload, 1,true,
MqttMessageBuilders.publish()
.payload(Unpooled.copiedBuffer("Any payload", Charset.defaultCharset()))
.qos(MqttQoS.AT_LEAST_ONCE)
.retained(true)
.topicName(NEWS_TOPIC).build());
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Any payload");
}
@Test
public void testPublishWithQoS2() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
subscribe(connection, NEWS_TOPIC, EXACTLY_ONCE);
// Exercise
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos2(connection, MqttMessageBuilders.publish()
.payload(anyPayload)
.qos(MqttQoS.EXACTLY_ONCE)
.retained(true)
.topicName(NEWS_TOPIC).build());
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, "Any payload");
}
// aka testPublishWithQoS1_notCleanSession
@Test
public void forwardQoS1PublishesWhenNotCleanSessionReconnects() {
connection.processConnect(ConnectionTestUtils.buildConnectNotClean(FAKE_CLIENT_ID));
ConnectionTestUtils.assertConnectAccepted(channel);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
connection.processDisconnect(null);
// publish a QoS 1 message from another client publish a message on the topic
EmbeddedChannel pubChannel = new EmbeddedChannel();
MQTTConnection pubConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, pubChannel);
pubConn.processConnect(ConnectionTestUtils.buildConnect(PUBLISHER_ID));
ConnectionTestUtils.assertConnectAccepted(pubChannel);
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos1(pubConn, new Topic(NEWS_TOPIC), TEST_USER, anyPayload, 1,true,
MqttMessageBuilders.publish()
.payload(anyPayload.retainedDuplicate())
.qos(MqttQoS.AT_LEAST_ONCE)
.topicName(NEWS_TOPIC).build());
// simulate a reconnection from the other client
connection = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID);
connectMessage = ConnectionTestUtils.buildConnectNotClean(FAKE_CLIENT_ID);
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
// Verify
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Any payload");
}
@Test
public void checkReceivePublishedMessage_after_a_reconnect_with_notCleanSession() {
// first connect - subscribe -disconnect
connection.processConnect(ConnectionTestUtils.buildConnectNotClean(FAKE_CLIENT_ID));
ConnectionTestUtils.assertConnectAccepted(channel);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
connection.processDisconnect(null);
// connect - subscribe from another connection but with same ClientID
EmbeddedChannel secondChannel = new EmbeddedChannel();
MQTTConnection secondConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, secondChannel);
secondConn.processConnect(ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID));
ConnectionTestUtils.assertConnectAccepted(secondChannel);
subscribe(secondConn, NEWS_TOPIC, AT_LEAST_ONCE);
// publish a QoS 1 message another client publish a message on the topic
EmbeddedChannel pubChannel = new EmbeddedChannel();
MQTTConnection pubConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, pubChannel);
pubConn.processConnect(ConnectionTestUtils.buildConnect(PUBLISHER_ID));
ConnectionTestUtils.assertConnectAccepted(pubChannel);
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos1(pubConn, new Topic(NEWS_TOPIC), TEST_USER, anyPayload, 1,true,
MqttMessageBuilders.publish()
.payload(anyPayload.retainedDuplicate())
.qos(MqttQoS.AT_LEAST_ONCE)
.topicName(NEWS_TOPIC).build());
// Verify that after a reconnection the client receive the message
ConnectionTestUtils.verifyPublishIsReceived(secondChannel, AT_LEAST_ONCE, "Any payload");
}
@Test
public void noPublishToInactiveSession() {
// create an inactive session for Subscriber
connection.processConnect(ConnectionTestUtils.buildConnectNotClean(SUBSCRIBER_ID));
ConnectionTestUtils.assertConnectAccepted(channel);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
connection.processDisconnect(null);
// Exercise
EmbeddedChannel pubChannel = new EmbeddedChannel();
MQTTConnection pubConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, pubChannel);
pubConn.processConnect(ConnectionTestUtils.buildConnect(PUBLISHER_ID));
ConnectionTestUtils.assertConnectAccepted(pubChannel);
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
sut.receivedPublishQos1(pubConn, new Topic(NEWS_TOPIC), TEST_USER, anyPayload, 1,true,
MqttMessageBuilders.publish()
.payload(anyPayload)
.qos(MqttQoS.AT_LEAST_ONCE)
.retained(true)
.topicName(NEWS_TOPIC).build());
verifyNoPublishIsReceived(channel);
}
private void verifyNoPublishIsReceived(EmbeddedChannel channel) {
final Object messageReceived = channel.readOutbound();
assertNull("Received an out message from processor while not expected", messageReceived);
}
@Test
public void cleanRetainedMessageStoreWhenPublishWithRetainedQos0IsReceived() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
// publish a QoS1 retained message
final ByteBuf anyPayload = Unpooled.copiedBuffer("Any payload", Charset.defaultCharset());
final MqttPublishMessage publishMsg = MqttMessageBuilders.publish()
.payload(Unpooled.copiedBuffer("Any payload", Charset.defaultCharset()))
.qos(MqttQoS.AT_LEAST_ONCE)
.retained(true)
.topicName(NEWS_TOPIC)
.build();
sut.receivedPublishQos1(connection, new Topic(NEWS_TOPIC), TEST_USER, anyPayload, 1,true,
publishMsg);
assertMessageIsRetained(NEWS_TOPIC);
// publish a QoS0 retained message
// Exercise
final ByteBuf qos0Payload = Unpooled.copiedBuffer("QoS0 payload", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, connection.getClientId(), qos0Payload, true);
// Verify
assertTrue("Retained message for topic /news must be cleared", retainedRepository.isEmtpy());
}
private void assertMessageIsRetained(String expectedTopicName) {
MqttPublishMessage msg = retainedRepository.retainedOnTopic(expectedTopicName);
assertEquals(expectedTopicName, msg.variableHeader().topicName());
}
}
@@ -0,0 +1,308 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.IAuthenticator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
import org.junit.Test;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Set;
import static io.moquette.broker.PostOfficePublishTest.ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID;
import static io.moquette.broker.PostOfficePublishTest.SUBSCRIBER_ID;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
import static io.netty.handler.codec.mqtt.MqttQoS.EXACTLY_ONCE;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class PostOfficeSubscribeTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
private static final String NEWS_TOPIC = "/news";
private static final String BAD_FORMATTED_TOPIC = "#MQTTClient";
private MQTTConnection connection;
private EmbeddedChannel channel;
private PostOffice sut;
private ISubscriptionsDirectory subscriptions;
public static final String FAKE_USER_NAME = "UnAuthUser";
private MqttConnectMessage connectMessage;
private IAuthenticator mockAuthenticator;
private SessionRegistry sessionRegistry;
public static final BrokerConfiguration CONFIG = new BrokerConfiguration(true, true, false);
@Before
public void setUp() {
connectMessage = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.build();
prepareSUT();
createMQTTConnection(CONFIG);
}
private void createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
connection = createMQTTConnection(config, channel);
}
private void prepareSUT() {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
sessionRegistry = new SessionRegistry(subscriptions);
sut = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(), new MemoryRetainedRepository(),
sessionRegistry);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, sut);
}
protected void connect() {
MqttConnectMessage connectMessage = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.build();
connection.processConnect(connectMessage);
MqttConnAckMessage connAck = channel.readOutbound();
assertEquals("Connect must be accepted", CONNECTION_ACCEPTED, connAck.variableHeader().connectReturnCode());
}
@Test
public void testSubscribe() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
// Exercise & verify
subscribe(channel, NEWS_TOPIC, AT_MOST_ONCE);
}
protected void subscribe(EmbeddedChannel channel, String topic, MqttQoS desiredQos) {
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, FAKE_CLIENT_ID, null, connection);
MqttSubAckMessage subAck = channel.readOutbound();
assertEquals(desiredQos.value(), (int) subAck.payload().grantedQoSLevels().get(0));
final String clientId = NettyUtils.clientID(channel);
Subscription expectedSubscription = new Subscription(clientId, new Topic(topic), desiredQos);
final Set<Subscription> matchedSubscriptions = subscriptions.matchWithoutQosSharpening(new Topic(topic));
assertEquals(1, matchedSubscriptions.size());
final Subscription onlyMatchedSubscription = matchedSubscriptions.iterator().next();
assertEquals(expectedSubscription, onlyMatchedSubscription);
}
protected void subscribe(MQTTConnection connection, String topic, MqttQoS desiredQos) {
EmbeddedChannel channel = (EmbeddedChannel) connection.channel;
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, connection);
MqttSubAckMessage subAck = channel.readOutbound();
assertEquals(desiredQos.value(), (int) subAck.payload().grantedQoSLevels().get(0));
final String clientId = connection.getClientId();
Subscription expectedSubscription = new Subscription(clientId, new Topic(topic), desiredQos);
final Set<Subscription> matchedSubscriptions = subscriptions.matchWithoutQosSharpening(new Topic(topic));
assertEquals(1, matchedSubscriptions.size());
final Subscription onlyMatchedSubscription = matchedSubscriptions.iterator().next();
assertEquals(expectedSubscription, onlyMatchedSubscription);
}
@Test
public void testSubscribedToNotAuthorizedTopic() {
NettyUtils.userName(channel, FAKE_USER_NAME);
IAuthorizatorPolicy prohibitReadOnNewsTopic = mock(IAuthorizatorPolicy.class);
when(prohibitReadOnNewsTopic.canRead(eq(new Topic(NEWS_TOPIC)), eq(FAKE_USER_NAME), eq(FAKE_CLIENT_ID)))
.thenReturn(false);
sut = new PostOffice(subscriptions, prohibitReadOnNewsTopic, new MemoryRetainedRepository(), sessionRegistry);
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
//Exercise
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(AT_MOST_ONCE, NEWS_TOPIC)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, FAKE_CLIENT_ID, FAKE_USER_NAME, connection);
// Verify
MqttSubAckMessage subAckMsg = channel.readOutbound();
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() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
assertEquals("After CONNECT subscription MUST be empty", 0, subscriptions.size());
subscribe(channel, NEWS_TOPIC, AT_MOST_ONCE);
assertEquals("After /news subscribe, subscription MUST contain it",1, subscriptions.size());
//Exercise & verify
subscribe(channel, NEWS_TOPIC, AT_MOST_ONCE);
}
@Test
public void testSubscribeWithBadFormattedTopic() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
assertEquals("After CONNECT subscription MUST be empty", 0, subscriptions.size());
//Exercise
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(AT_MOST_ONCE, BAD_FORMATTED_TOPIC)
.messageId(1)
.build();
this.sut.subscribeClientToTopics(subscribe, FAKE_CLIENT_ID, FAKE_USER_NAME, connection);
MqttSubAckMessage subAckMsg = channel.readOutbound();
assertEquals("Bad topic CAN'T add any subscription",0, subscriptions.size());
verifyFailureQos(subAckMsg);
}
@Test
public void testCleanSession_maintainClientSubscriptions() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
assertEquals("After CONNECT subscription MUST be empty", 0, subscriptions.size());
subscribe(channel, NEWS_TOPIC, AT_MOST_ONCE);
assertEquals("Subscribe MUST contain one subscription", 1, subscriptions.size());
connection.processDisconnect(null);
assertEquals("Disconnection MUSTN'T clear subscriptions", 1, subscriptions.size());
EmbeddedChannel anotherChannel = new EmbeddedChannel();
MQTTConnection anotherConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, anotherChannel);
anotherConn.processConnect(ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID));
ConnectionTestUtils.assertConnectAccepted(anotherChannel);
assertEquals("After a reconnect, subscription MUST be still present", 1, subscriptions.size());
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload, false);
ConnectionTestUtils.verifyPublishIsReceived(anotherChannel, AT_MOST_ONCE, "Hello world!");
}
/**
* 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() {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
assertEquals("After CONNECT subscription MUST be empty", 0, subscriptions.size());
//subscribe(channel, NEWS_TOPIC, AT_MOST_ONCE);
final MqttSubscribeMessage subscribeMsg = MqttMessageBuilders
.subscribe()
.addSubscription(AT_MOST_ONCE, NEWS_TOPIC)
.messageId(1)
.build();
connection.processSubscribe(subscribeMsg);
assertEquals("Subscribe MUST contain one subscription", 1, subscriptions.size());
connection.processDisconnect(null);
assertEquals("Disconnection MUSTN'T clear subscriptions", 1, subscriptions.size());
connectMessage = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.cleanSession(true)
.build();
channel = new EmbeddedChannel();
connection = createMQTTConnection(CONFIG, channel);
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
assertEquals("After CONNECT with clean, subscription MUST be empty", 0, subscriptions.size());
// publish on /news
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload, false);
// verify no publish is fired
ConnectionTestUtils.verifyNoPublishIsReceived(channel);
}
@Test
public void testReceiveRetainedPublishRespectingSubscriptionQoSAndNotPublisher() {
// publisher publish a retained message on topic /news
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(channel);
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
final MqttPublishMessage retainedPubQoS1Msg = MqttMessageBuilders.publish()
.payload(payload.retainedDuplicate())
.qos(MqttQoS.AT_LEAST_ONCE)
.topicName(NEWS_TOPIC).build();
sut.receivedPublishQos1(connection, new Topic(NEWS_TOPIC), TEST_USER, payload, 1, true,
retainedPubQoS1Msg);
// subscriber connects subscribe to topic /news and receive the last retained message
EmbeddedChannel subChannel = new EmbeddedChannel();
MQTTConnection subConn = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZERO_BYTES_CLID, subChannel);
subConn.processConnect(ConnectionTestUtils.buildConnect(SUBSCRIBER_ID));
ConnectionTestUtils.assertConnectAccepted(subChannel);
subscribe(subConn, NEWS_TOPIC, MqttQoS.AT_MOST_ONCE);
// Verify publish is received
ConnectionTestUtils.verifyReceiveRetainedPublish(subChannel, NEWS_TOPIC, "Hello world!", MqttQoS.AT_MOST_ONCE);
}
@Test
public void testLowerTheQosToTheRequestedBySubscription() {
Subscription subQos1 = new Subscription("Sub A", new Topic("a/b"), MqttQoS.AT_LEAST_ONCE);
assertEquals(MqttQoS.AT_LEAST_ONCE, PostOffice.lowerQosToTheSubscriptionDesired(subQos1, EXACTLY_ONCE));
Subscription subQos2 = new Subscription("Sub B", new Topic("a/+"), EXACTLY_ONCE);
assertEquals(EXACTLY_ONCE, PostOffice.lowerQosToTheSubscriptionDesired(subQos2, EXACTLY_ONCE));
}
}
@@ -0,0 +1,352 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.IAuthenticator;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
import org.junit.Test;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Set;
import static io.moquette.broker.PostOfficePublishTest.PUBLISHER_ID;
import static io.netty.handler.codec.mqtt.MqttQoS.*;
import static java.util.Collections.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class PostOfficeUnsubscribeTest {
private static final String FAKE_CLIENT_ID = "FAKE_123";
private static final String TEST_USER = "fakeuser";
private static final String TEST_PWD = "fakepwd";
static final String NEWS_TOPIC = "/news";
private static final String BAD_FORMATTED_TOPIC = "#MQTTClient";
private MQTTConnection connection;
private EmbeddedChannel channel;
private PostOffice sut;
private ISubscriptionsDirectory subscriptions;
private MqttConnectMessage connectMessage;
private IAuthenticator mockAuthenticator;
private SessionRegistry sessionRegistry;
public static final BrokerConfiguration CONFIG = new BrokerConfiguration(true, true, false);
@Before
public void setUp() {
connectMessage = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.build();
prepareSUT();
createMQTTConnection(CONFIG);
}
private void createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
connection = createMQTTConnection(config, channel);
}
private void prepareSUT() {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
sessionRegistry = new SessionRegistry(subscriptions);
sut = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(), new MemoryRetainedRepository(), sessionRegistry);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
return new MQTTConnection(channel, config, mockAuthenticator, sessionRegistry, sut);
}
protected void connect(MQTTConnection connection, String clientId) {
MqttConnectMessage connectMessage = ConnectionTestUtils.buildConnect(clientId);
connect(connection, connectMessage);
}
protected void connect(MQTTConnection connection, MqttConnectMessage connectMessage) {
connection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted((EmbeddedChannel) connection.channel);
}
protected void subscribe(MQTTConnection connection, String topic, MqttQoS desiredQos) {
EmbeddedChannel channel = (EmbeddedChannel) connection.channel;
MqttSubscribeMessage subscribe = MqttMessageBuilders.subscribe()
.addSubscription(desiredQos, topic)
.messageId(1)
.build();
sut.subscribeClientToTopics(subscribe, connection.getClientId(), null, connection);
MqttSubAckMessage subAck = channel.readOutbound();
assertEquals(desiredQos.value(), (int) subAck.payload().grantedQoSLevels().get(0));
final String clientId = connection.getClientId();
Subscription expectedSubscription = new Subscription(clientId, new Topic(topic), desiredQos);
final Set<Subscription> matchedSubscriptions = subscriptions.matchQosSharpening(new Topic(topic));
assertEquals(1, matchedSubscriptions.size());
//assertTrue(matchedSubscriptions.size() >=1);
final Subscription onlyMatchedSubscription = matchedSubscriptions.iterator().next();
assertEquals(expectedSubscription, onlyMatchedSubscription);
// assertTrue(matchedSubscriptions.contains(expectedSubscription));
}
@Test
public void testUnsubscribeWithBadFormattedTopic() {
connect(this.connection, FAKE_CLIENT_ID);
// Exercise
sut.unsubscribe(singletonList(BAD_FORMATTED_TOPIC), connection, 1);
// Verify
assertFalse("Unsubscribe with bad topic MUST close drop the connection, (issue 68)", channel.isOpen());
}
@Test
public void testDontNotifyClientSubscribedToTopicAfterDisconnectedAndReconnectOnSameChannel() {
connect(this.connection, FAKE_CLIENT_ID);
subscribe(connection, NEWS_TOPIC, AT_MOST_ONCE);
// publish on /news
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload, false);
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_MOST_ONCE, "Hello world!");
unsubscribeAndVerifyAck(NEWS_TOPIC);
// publish on /news
final ByteBuf payload2 = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload2, false);
ConnectionTestUtils.verifyNoPublishIsReceived(channel);
}
protected void unsubscribeAndVerifyAck(String topic) {
final int messageId = 1;
sut.unsubscribe(Collections.singletonList(topic), connection, messageId);
MqttUnsubAckMessage unsubAckMessageAck = channel.readOutbound();
assertEquals("Unsubscribe must be accepted", messageId, unsubAckMessageAck.variableHeader().messageId());
}
@Test
public void testDontNotifyClientSubscribedToTopicAfterDisconnectedAndReconnectOnNewChannel() {
connect(this.connection, FAKE_CLIENT_ID);
subscribe(connection, NEWS_TOPIC, AT_MOST_ONCE);
// publish on /news
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload, false);
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_MOST_ONCE, "Hello world!");
unsubscribeAndVerifyAck(NEWS_TOPIC);
connection.processDisconnect(null);
// connect on another channel
EmbeddedChannel anotherChannel = new EmbeddedChannel();
MQTTConnection anotherConnection = createMQTTConnection(CONFIG, anotherChannel);
anotherConnection.processConnect(connectMessage);
ConnectionTestUtils.assertConnectAccepted(anotherChannel);
// publish on /news
final ByteBuf payload2 = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload2, false);
ConnectionTestUtils.verifyNoPublishIsReceived(anotherChannel);
}
@Test
public void avoidMultipleNotificationsAfterMultipleReconnection_cleanSessionFalseQoS1() {
final MqttConnectMessage notCleanConnect = ConnectionTestUtils.buildConnectNotClean(FAKE_CLIENT_ID);
connect(connection, notCleanConnect);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
connection.processDisconnect(null);
// connect on another channel
final String firstPayload = "Hello MQTT 1";
connectPublishDisconnectFromAnotherClient(firstPayload, NEWS_TOPIC);
// reconnect FAKE_CLIENT on another channel
EmbeddedChannel anotherChannel2 = new EmbeddedChannel();
MQTTConnection anotherConnection2 = createMQTTConnection(CONFIG, anotherChannel2);
anotherConnection2.processConnect(notCleanConnect);
ConnectionTestUtils.assertConnectAccepted(anotherChannel2);
ConnectionTestUtils.verifyPublishIsReceived(anotherChannel2, MqttQoS.AT_LEAST_ONCE, firstPayload);
anotherConnection2.processDisconnect(null);
final String secondPayload = "Hello MQTT 2";
connectPublishDisconnectFromAnotherClient(secondPayload, NEWS_TOPIC);
EmbeddedChannel anotherChannel3 = new EmbeddedChannel();
MQTTConnection anotherConnection3 = createMQTTConnection(CONFIG, anotherChannel3);
anotherConnection3.processConnect(notCleanConnect);
ConnectionTestUtils.assertConnectAccepted(anotherChannel3);
ConnectionTestUtils.verifyPublishIsReceived(anotherChannel3, MqttQoS.AT_LEAST_ONCE, secondPayload);
}
private void connectPublishDisconnectFromAnotherClient(String firstPayload, String topic) {
MQTTConnection anotherConnection = connectNotCleanAs(PUBLISHER_ID);
// publish from another channel
final ByteBuf anyPayload = Unpooled.copiedBuffer(firstPayload, Charset.defaultCharset());
sut.receivedPublishQos1(anotherConnection, new Topic(topic), TEST_USER, anyPayload, 1,false,
MqttMessageBuilders.publish()
.payload(Unpooled.copiedBuffer(firstPayload, Charset.defaultCharset()))
.qos(MqttQoS.AT_LEAST_ONCE)
.retained(false)
.topicName(topic).build());
// disconnect the other channel
anotherConnection.processDisconnect(null);
}
private MQTTConnection connectNotCleanAs(String clientId) {
EmbeddedChannel channel = new EmbeddedChannel();
MQTTConnection connection = createMQTTConnection(CONFIG, channel);
connection.processConnect(ConnectionTestUtils.buildConnectNotClean(clientId));
ConnectionTestUtils.assertConnectAccepted(channel);
return connection;
}
private MQTTConnection connectAs(String clientId) {
EmbeddedChannel channel = new EmbeddedChannel();
MQTTConnection connection = createMQTTConnection(CONFIG, channel);
connection.processConnect(ConnectionTestUtils.buildConnect(clientId));
ConnectionTestUtils.assertConnectAccepted(channel);
return connection;
}
@Test
public void testConnectSubPub_cycle_getTimeout_on_second_disconnect_issue142() {
connect(connection, FAKE_CLIENT_ID);
subscribe(connection, NEWS_TOPIC, AT_MOST_ONCE);
// publish on /news
final ByteBuf payload = Unpooled.copiedBuffer("Hello world!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload, false);
ConnectionTestUtils.verifyPublishIsReceived((EmbeddedChannel) connection.channel, AT_MOST_ONCE, "Hello world!");
connection.processDisconnect(null);
final MqttConnectMessage notCleanConnect = ConnectionTestUtils.buildConnect(FAKE_CLIENT_ID);
EmbeddedChannel subscriberChannel = new EmbeddedChannel();
MQTTConnection subscriberConnection = createMQTTConnection(CONFIG, subscriberChannel);
subscriberConnection.processConnect(notCleanConnect);
ConnectionTestUtils.assertConnectAccepted(subscriberChannel);
subscribe(subscriberConnection, NEWS_TOPIC, AT_MOST_ONCE);
// publish on /news
final ByteBuf payload2 = Unpooled.copiedBuffer("Hello world2!", Charset.defaultCharset());
sut.receivedPublishQos0(new Topic(NEWS_TOPIC), TEST_USER, TEST_PWD, payload2, false);
ConnectionTestUtils.verifyPublishIsReceived(subscriberChannel, AT_MOST_ONCE, "Hello world2!");
subscriberConnection.processDisconnect(null);
assertFalse("after a disconnect the client should be disconnected", subscriberChannel.isOpen());
}
@Test
public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() {
final MQTTConnection publisher = connectAs("Publisher");
connect(this.connection, FAKE_CLIENT_ID);
subscribe(connection, NEWS_TOPIC, AT_LEAST_ONCE);
// publish from another channel
publishQos1(publisher, NEWS_TOPIC, "Hello world MQTT!!-1", 99);
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Hello world MQTT!!-1");
connection.processDisconnect(null);
publishQos1(publisher, NEWS_TOPIC, "Hello world MQTT!!-2", 100);
publishQos1(publisher, NEWS_TOPIC, "Hello world MQTT!!-3", 101);
createMQTTConnection(CONFIG);
connect(this.connection, FAKE_CLIENT_ID);
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Hello world MQTT!!-2");
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Hello world MQTT!!-3");
}
private void publishQos1(MQTTConnection publisher, String topic, String payload, int messageID) {
final ByteBuf bytePayload = Unpooled.copiedBuffer(payload, Charset.defaultCharset());
sut.receivedPublishQos1(publisher, new Topic(topic), TEST_USER, bytePayload, messageID,false,
MqttMessageBuilders.publish()
.payload(Unpooled.copiedBuffer(payload, Charset.defaultCharset()))
.qos(MqttQoS.AT_LEAST_ONCE)
.retained(false)
.topicName(NEWS_TOPIC).build());
}
private void publishQos2(MQTTConnection connection, String topic, String payload) {
final ByteBuf bytePayload = Unpooled.copiedBuffer(payload, Charset.defaultCharset());
sut.receivedPublishQos2(connection, MqttMessageBuilders.publish()
.payload(bytePayload)
.qos(MqttQoS.EXACTLY_ONCE)
.retained(true)
.topicName(NEWS_TOPIC).build());
}
/**
* 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(this.connection, FAKE_CLIENT_ID);
subscribe(connection, NEWS_TOPIC, EXACTLY_ONCE);
connection.processDisconnect(null);
final MQTTConnection publisher = connectAs("Publisher");
publishQos2(publisher, NEWS_TOPIC, "Hello world MQTT!!-1");
publishQos2(publisher, NEWS_TOPIC, "Hello world MQTT!!-2");
createMQTTConnection(CONFIG);
connect(this.connection, FAKE_CLIENT_ID);
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, "Hello world MQTT!!-1");
ConnectionTestUtils.verifyPublishIsReceived(channel, EXACTLY_ONCE, "Hello world MQTT!!-2");
}
/**
* 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() {
final MQTTConnection publisher = connectAs("Publisher");
connect(this.connection, FAKE_CLIENT_ID);
subscribe(connection, "a/b", AT_LEAST_ONCE);
subscribe(connection, "a/+", EXACTLY_ONCE);
// force the publisher to send
publishQos1(publisher, "a/b", "Hello world MQTT!!", 60);
ConnectionTestUtils.verifyPublishIsReceived(channel, AT_LEAST_ONCE, "Hello world MQTT!!");
ConnectionTestUtils.verifyNoPublishIsReceived(channel);
}
}
@@ -0,0 +1,126 @@
package io.moquette.broker;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.MockAuthenticator;
import io.moquette.spi.impl.SessionsRepository;
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.security.IAuthenticator;
import io.netty.channel.Channel;
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.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class SessionRegistryTest {
static final String FAKE_CLIENT_ID = "FAKE_123";
static final String FAKE_CLIENT_ID2 = "FAKE_456";
static final String PUBLISHER_ID = "Publisher";
private static final String SUBSCRIBER_ID = "Subscriber";
static final String NEWS_TOPIC = "/news";
static final String BAD_FORMATTED_TOPIC = "#MQTTClient";
static final String TEST_USER = "fakeuser";
static final String TEST_PWD = "fakepwd";
static final String EVIL_TEST_USER = "eviluser";
static final String EVIL_TEST_PWD = "unsecret";
private MQTTConnection connection;
private EmbeddedChannel channel;
private SessionRegistry sut;
private MqttMessageBuilders.ConnectBuilder connMsg;
private static final BrokerConfiguration ALLOW_ANONYMOUS_AND_ZEROBYTE_CLIENT_ID =
new BrokerConfiguration(true, true, false);
@Before
public void setUp() {
connMsg = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1).cleanSession(true);
createMQTTConnection(ALLOW_ANONYMOUS_AND_ZEROBYTE_CLIENT_ID);
}
private void createMQTTConnection(BrokerConfiguration config) {
channel = new EmbeddedChannel();
connection = createMQTTConnection(config, channel);
}
private MQTTConnection createMQTTConnection(BrokerConfiguration config, Channel channel) {
MemoryStorageService memStorage = new MemoryStorageService(null, null);
ISessionsStore sessionStore = memStorage.sessionsStore();
IAuthenticator mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
ISubscriptionsDirectory subscriptions = new CTrieSubscriptionDirectory();
SessionsRepository sessionsRepository = new SessionsRepository(sessionStore, null);
subscriptions.init(sessionsRepository);
sut = new SessionRegistry(subscriptions);
final PostOffice postOffice = new PostOffice(subscriptions, new PermitAllAuthorizatorPolicy(),
new MemoryRetainedRepository(), sut);
return new MQTTConnection(channel, config, mockAuthenticator, sut, postOffice);
}
@Test
public void testConnAckContainsSessionPresentFlag() {
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID)
.protocolVersion(MqttVersion.MQTT_3_1_1)
.build();
NettyUtils.clientID(channel, FAKE_CLIENT_ID);
NettyUtils.cleanSession(channel, false);
// Connect a first time
sut.bindToSession(connection, msg, FAKE_CLIENT_ID);
// disconnect
sut.disconnect(FAKE_CLIENT_ID);
// Exercise, reconnect
EmbeddedChannel anotherChannel = new EmbeddedChannel();
MQTTConnection anotherConnection = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZEROBYTE_CLIENT_ID, anotherChannel);
sut.bindToSession(anotherConnection, msg, FAKE_CLIENT_ID);
// Verify
assertEqualsConnAck(CONNECTION_ACCEPTED, anotherChannel.readOutbound());
assertTrue("Connection is accepted and therefore should remain open", anotherChannel.isOpen());
}
@Test
public void connectWithCleanSessionUpdateClientSession() {
// first connect with clean session true
MqttConnectMessage msg = connMsg.clientId(FAKE_CLIENT_ID).cleanSession(true).build();
connection.processConnect(msg);
assertEqualsConnAck(CONNECTION_ACCEPTED, channel.readOutbound());
connection.processDisconnect(null);
assertFalse(channel.isOpen());
// second connect with clean session false
EmbeddedChannel anotherChannel = new EmbeddedChannel();
MQTTConnection anotherConnection = createMQTTConnection(ALLOW_ANONYMOUS_AND_ZEROBYTE_CLIENT_ID,
anotherChannel);
MqttConnectMessage secondConnMsg = MqttMessageBuilders.connect()
.clientId(FAKE_CLIENT_ID)
.protocolVersion(MqttVersion.MQTT_3_1)
.build();
anotherConnection.processConnect(secondConnMsg);
assertEqualsConnAck(CONNECTION_ACCEPTED, anotherChannel.readOutbound());
// Verify client session is clean false
Session session = sut.retrieve(FAKE_CLIENT_ID);
assertFalse(session.isClean());
}
}
@@ -24,14 +24,14 @@ import io.moquette.persistence.MemoryMessagesStore;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import org.junit.After;
import org.junit.Test;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.*;
public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizator {
public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizatorPolicy {
Server m_server;
IConfig m_config;
@@ -21,7 +21,7 @@ import io.moquette.server.config.IConfig;
import io.moquette.server.config.MemoryConfig;
import io.moquette.spi.impl.security.AcceptAllAuthenticator;
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.handler.codec.mqtt.MqttMessageBuilders;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
@@ -71,7 +71,7 @@ public class ServerIntegrationPahoCanPublishOnReadBlockedTopicTest {
m_config = new MemoryConfig(configProps);
canRead = true;
final IAuthorizator switchingAuthorizator = new IAuthorizator() {
final IAuthorizatorPolicy switchingAuthorizator = new IAuthorizatorPolicy() {
// int callCount = 0;
@Override
public boolean canWrite(Topic topic, String user, String client) {
@@ -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());
// }
}