Rearchitected the broker, switching from ProtocolProcessor and publisher to MQTTConnection, Session and PostOffice
This commit is contained in:
@@ -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;
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+10
-22
@@ -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;
|
||||
|
||||
+328
-326
@@ -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());
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user