/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.transport.mqtt;
import static org.fusesource.hawtbuf.UTF8Buffer.utf8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.region.policy.LastImageSubscriptionRecoveryPolicy;
import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.Wait;
import org.fusesource.mqtt.client.BlockingConnection;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.Message;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
import org.fusesource.mqtt.client.Tracer;
import org.fusesource.mqtt.codec.MQTTFrame;
import org.fusesource.mqtt.codec.PUBLISH;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MQTTTest extends MQTTTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(MQTTTest.class);
private static final int NUM_MESSAGES = 200;
@Test(timeout = 60 * 1000)
public void testSendAndReceiveMQTT() throws Exception {
final MQTTClientProvider subscriptionProvider = getMQTTClientProvider();
initializeConnection(subscriptionProvider);
subscriptionProvider.subscribe("foo/bah", AT_MOST_ONCE);
final CountDownLatch latch = new CountDownLatch(NUM_MESSAGES);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < NUM_MESSAGES; i++) {
try {
byte[] payload = subscriptionProvider.receive(10000);
assertNotNull("Should get a message", payload);
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
});
thread.start();
final MQTTClientProvider publishProvider = getMQTTClientProvider();
initializeConnection(publishProvider);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Message " + i;
publishProvider.publish("foo/bah", payload.getBytes(), AT_LEAST_ONCE);
}
latch.await(10, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
subscriptionProvider.disconnect();
publishProvider.disconnect();
}
@Test(timeout = 60 * 1000)
public void testUnsubscribeMQTT() throws Exception {
final MQTTClientProvider subscriptionProvider = getMQTTClientProvider();
initializeConnection(subscriptionProvider);
String topic = "foo/bah";
subscriptionProvider.subscribe(topic, AT_MOST_ONCE);
final CountDownLatch latch = new CountDownLatch(NUM_MESSAGES / 2);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < NUM_MESSAGES; i++) {
try {
byte[] payload = subscriptionProvider.receive(10000);
assertNotNull("Should get a message", payload);
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
});
thread.start();
final MQTTClientProvider publishProvider = getMQTTClientProvider();
initializeConnection(publishProvider);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Message " + i;
if (i == NUM_MESSAGES / 2) {
latch.await(20, TimeUnit.SECONDS);
subscriptionProvider.unsubscribe(topic);
}
publishProvider.publish(topic, payload.getBytes(), AT_LEAST_ONCE);
}
latch.await(20, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
subscriptionProvider.disconnect();
publishProvider.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendAtMostOnceReceiveExactlyOnce() throws Exception {
/**
* Although subscribing with EXACTLY ONCE, the message gets published
* with AT_MOST_ONCE - in MQTT the QoS is always determined by the
* message as published - not the wish of the subscriber
*/
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
provider.subscribe("foo", EXACTLY_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo", payload.getBytes(), AT_MOST_ONCE);
byte[] message = provider.receive(5000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
}
@Test(timeout = 2 * 60 * 1000)
public void testSendAtLeastOnceReceiveExactlyOnce() throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
provider.subscribe("foo", EXACTLY_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo", payload.getBytes(), AT_LEAST_ONCE);
byte[] message = provider.receive(2000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
}
@Test(timeout = 2 * 60 * 1000)
public void testSendAtLeastOnceReceiveAtMostOnce() throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
provider.subscribe("foo", AT_MOST_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo", payload.getBytes(), AT_LEAST_ONCE);
byte[] message = provider.receive(5000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendAndReceiveAtMostOnce() throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
provider.subscribe("foo", AT_MOST_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo", payload.getBytes(), AT_MOST_ONCE);
byte[] message = provider.receive(5000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
}
@Test(timeout = 2 * 60 * 1000)
public void testSendAndReceiveAtLeastOnce() throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
provider.subscribe("foo", AT_LEAST_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo", payload.getBytes(), AT_LEAST_ONCE);
byte[] message = provider.receive(5000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendAndReceiveExactlyOnce() throws Exception {
final MQTTClientProvider publisher = getMQTTClientProvider();
initializeConnection(publisher);
final MQTTClientProvider subscriber = getMQTTClientProvider();
initializeConnection(subscriber);
subscriber.subscribe("foo", EXACTLY_ONCE);
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
publisher.publish("foo", payload.getBytes(), EXACTLY_ONCE);
byte[] message = subscriber.receive(5000);
assertNotNull("Should get a message + [" + i + "]", message);
assertEquals(payload, new String(message));
}
subscriber.disconnect();
publisher.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendAndReceiveLargeMessages() throws Exception {
byte[] payload = new byte[1024 * 32];
for (int i = 0; i < payload.length; i++) {
payload[i] = '2';
}
final MQTTClientProvider publisher = getMQTTClientProvider();
initializeConnection(publisher);
final MQTTClientProvider subscriber = getMQTTClientProvider();
initializeConnection(subscriber);
subscriber.subscribe("foo", AT_LEAST_ONCE);
for (int i = 0; i < 10; i++) {
publisher.publish("foo", payload, AT_LEAST_ONCE);
byte[] message = subscriber.receive(5000);
assertNotNull("Should get a message", message);
assertArrayEquals(payload, message);
}
subscriber.disconnect();
publisher.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendAndReceiveRetainedMessages() throws Exception {
final MQTTClientProvider publisher = getMQTTClientProvider();
initializeConnection(publisher);
final MQTTClientProvider subscriber = getMQTTClientProvider();
initializeConnection(subscriber);
String RETAINED = "retained";
publisher.publish("foo", RETAINED.getBytes(), AT_LEAST_ONCE, true);
List<String> messages = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
messages.add("TEST MESSAGE:" + i);
}
subscriber.subscribe("foo", AT_LEAST_ONCE);
for (int i = 0; i < 10; i++) {
publisher.publish("foo", messages.get(i).getBytes(), AT_LEAST_ONCE);
}
byte[] msg = subscriber.receive(5000);
assertNotNull(msg);
assertEquals(RETAINED, new String(msg));
for (int i = 0; i < 10; i++) {
msg = subscriber.receive(5000);
assertNotNull(msg);
assertEquals(messages.get(i), new String(msg));
}
subscriber.disconnect();
publisher.disconnect();
}
@Test(timeout = 30 * 1000)
public void testValidZeroLengthClientId() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("");
mqtt.setCleanSession(true);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.disconnect();
}
@Test(timeout = 30 * 1000)
public void testConnectWithUserButNoPassword() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("test");
mqtt.setUserName("foo");
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.disconnect();
}
@Test(timeout = 30 * 1000)
public void testConnectWithPasswordButNoUsername() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setVersion("3.1.1"); // The V3.1 spec doesn't make the same assertion
mqtt.setClientId("test");
mqtt.setPassword("bar");
BlockingConnection connection = mqtt.blockingConnection();
try {
connection.connect();
fail("Should not be able to connect in this case.");
} catch (Exception ex) {
LOG.info("Exception expected on connect with password but no username");
}
}
@Test(timeout = 2 * 60 * 1000)
public void testMQTTWildcard() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("");
mqtt.setCleanSession(true);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
Topic[] topics = {new Topic(utf8("a/#"), QoS.values()[AT_MOST_ONCE])};
connection.subscribe(topics);
String payload = "Test Message";
String publishedTopic = "a/b/1.2.3*4>";
connection.publish(publishedTopic, payload.getBytes(), QoS.values()[AT_MOST_ONCE], false);
Message msg = connection.receive(1, TimeUnit.SECONDS);
assertEquals("Topic changed", publishedTopic, msg.getTopic());
}
@Test(timeout = 2 * 60 * 1000)
public void testMQTTCompositeDestinations() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("");
mqtt.setCleanSession(true);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
Topic[] topics = {new Topic(utf8("a/1"), QoS.values()[AT_MOST_ONCE]), new Topic(utf8("a/2"), QoS.values()[AT_MOST_ONCE])};
connection.subscribe(topics);
String payload = "Test Message";
String publishedTopic = "a/1,a/2";
connection.publish(publishedTopic, payload.getBytes(), QoS.values()[AT_MOST_ONCE], false);
Message msg = connection.receive(1, TimeUnit.SECONDS);
assertNotNull(msg);
assertEquals("a/2", msg.getTopic());
msg = connection.receive(1, TimeUnit.SECONDS);
assertNotNull(msg);
assertEquals("a/1", msg.getTopic());
msg = connection.receive(1, TimeUnit.SECONDS);
assertNull(msg);
}
@Test(timeout = 2 * 60 * 1000)
public void testMQTTPathPatterns() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("");
mqtt.setCleanSession(true);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String RETAINED = "RETAINED";
String[] topics = { "TopicA", "/TopicA", "/", "TopicA/", "//" };
for (String topic : topics) {
// test retained message
connection.publish(topic, (RETAINED + topic).getBytes(), QoS.AT_LEAST_ONCE, true);
connection.subscribe(new Topic[] { new Topic(topic, QoS.AT_LEAST_ONCE) });
Message msg = connection.receive(5, TimeUnit.SECONDS);
assertNotNull("No message for " + topic, msg);
assertEquals(RETAINED + topic, new String(msg.getPayload()));
msg.ack();
// test non-retained message
connection.publish(topic, topic.getBytes(), QoS.AT_LEAST_ONCE, false);
msg = connection.receive(1000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(topic, new String(msg.getPayload()));
msg.ack();
connection.unsubscribe(new String[] { topic });
}
connection.disconnect();
// test wildcard patterns with above topics
String[] wildcards = { "#", "+", "+/#", "/+", "+/", "+/+", "+/+/", "+/+/+" };
for (String wildcard : wildcards) {
final Pattern pattern = Pattern.compile(wildcard.replaceAll("/?#", "(/?.*)*").replaceAll("\\+", "[^/]*"));
connection = mqtt.blockingConnection();
connection.connect();
final byte[] qos = connection.subscribe(new Topic[]{new Topic(wildcard, QoS.AT_LEAST_ONCE)});
assertNotEquals("Subscribe failed " + wildcard, (byte)0x80, qos[0]);
// test retained messages
Message msg = connection.receive(2, TimeUnit.SECONDS);
do {
assertNotNull("RETAINED null " + wildcard, msg);
assertTrue("RETAINED prefix " + wildcard, new String(msg.getPayload()).startsWith(RETAINED));
assertTrue("RETAINED matching " + wildcard + " " + msg.getTopic(), pattern.matcher(msg.getTopic()).matches());
msg.ack();
msg = connection.receive(5000, TimeUnit.MILLISECONDS);
} while (msg != null);
// test non-retained message
for (String topic : topics) {
connection.publish(topic, topic.getBytes(), QoS.AT_LEAST_ONCE, false);
}
msg = connection.receive(1000, TimeUnit.MILLISECONDS);
do {
assertNotNull("Non-retained Null " + wildcard, msg);
assertTrue("Non-retained matching " + wildcard + " " + msg.getTopic(), pattern.matcher(msg.getTopic()).matches());
msg.ack();
msg = connection.receive(1000, TimeUnit.MILLISECONDS);
} while (msg != null);
connection.unsubscribe(new String[] { wildcard });
connection.disconnect();
}
}
@Test(timeout = 60 * 1000)
public void testMQTTRetainQoS() throws Exception {
String[] topics = { "AT_MOST_ONCE", "AT_LEAST_ONCE", "EXACTLY_ONCE" };
for (int i = 0; i < topics.length; i++) {
final String topic = topics[i];
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setKeepAlive((short) 2);
final int[] actualQoS = { -1 };
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
// validate the QoS
if (frame.messageType() == PUBLISH.TYPE) {
actualQoS[0] = frame.qos().ordinal();
}
}
});
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.publish(topic, topic.getBytes(), QoS.EXACTLY_ONCE, true);
connection.subscribe(new Topic[]{new Topic(topic, QoS.valueOf(topic))});
final Message msg = connection.receive(5000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(topic, new String(msg.getPayload()));
int waitCount = 0;
while (actualQoS[0] == -1 && waitCount < 10) {
Thread.sleep(1000);
waitCount++;
}
assertEquals(i, actualQoS[0]);
msg.ack();
connection.unsubscribe(new String[]{topic});
connection.disconnect();
}
}
@Test(timeout = 60 * 1000)
public void testDuplicateSubscriptions() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setKeepAlive((short) 2);
final int[] actualQoS = { -1 };
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
// validate the QoS
if (frame.messageType() == PUBLISH.TYPE) {
actualQoS[0] = frame.qos().ordinal();
}
}
});
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String RETAIN = "RETAIN";
connection.publish("TopicA", RETAIN.getBytes(), QoS.EXACTLY_ONCE, true);
QoS[] qoss = { QoS.AT_MOST_ONCE, QoS.AT_MOST_ONCE, QoS.AT_LEAST_ONCE, QoS.EXACTLY_ONCE };
for (QoS qos : qoss) {
connection.subscribe(new Topic[]{new Topic("TopicA", qos)});
final Message msg = connection.receive(5000, TimeUnit.MILLISECONDS);
assertNotNull("No message for " + qos, msg);
assertEquals(RETAIN, new String(msg.getPayload()));
msg.ack();
int waitCount = 0;
while (actualQoS[0] == -1 && waitCount < 10) {
Thread.sleep(1000);
waitCount++;
}
assertEquals(qos.ordinal(), actualQoS[0]);
actualQoS[0] = -1;
}
connection.unsubscribe(new String[] { "TopicA" });
connection.disconnect();
}
@Test(timeout = 120 * 1000)
public void testRetainedMessage() throws Exception {
doTestRetainedMessages("TopicA");
}
@Test(timeout = 120 * 1000)
public void testRetainedMessageOnVirtualTopics() throws Exception {
doTestRetainedMessages("VirtualTopic/TopicA");
}
public void doTestRetainedMessages(String topicName) throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setKeepAlive((short) 60);
final String RETAIN = "RETAIN";
final String TOPICA = topicName;
final String[] clientIds = { null, "foo", "durable" };
for (String clientId : clientIds) {
boolean cleanSession = !"durable".equals(clientId);
LOG.info("Testing now with Client ID: {} clean: {}", clientId, cleanSession);
mqtt.setClientId(clientId);
mqtt.setCleanSession(cleanSession);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
// set retained message and check
connection.publish(TOPICA, RETAIN.getBytes(), QoS.EXACTLY_ONCE, true);
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
Message msg = connection.receive(5000, TimeUnit.MILLISECONDS);
assertNotNull("No retained message for " + clientId, msg);
assertEquals(RETAIN, new String(msg.getPayload()));
msg.ack();
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
// test duplicate subscription
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
msg = connection.receive(15000, TimeUnit.MILLISECONDS);
assertNotNull("No retained message on duplicate subscription for " + clientId, msg);
assertEquals(RETAIN, new String(msg.getPayload()));
msg.ack();
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
connection.unsubscribe(new String[]{TOPICA});
// clear retained message and check that we don't receive it
connection.publish(TOPICA, "".getBytes(), QoS.AT_MOST_ONCE, true);
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
msg = connection.receive(500, TimeUnit.MILLISECONDS);
assertNull("Retained message not cleared for " + clientId, msg);
connection.unsubscribe(new String[]{TOPICA});
// set retained message again and check
connection.publish(TOPICA, RETAIN.getBytes(), QoS.EXACTLY_ONCE, true);
LOG.info("Performing first subscription");
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
msg = connection.receive(5000, TimeUnit.MILLISECONDS);
assertNotNull("No reset retained message for " + clientId, msg);
assertEquals(RETAIN, new String(msg.getPayload()));
msg.ack();
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
// re-connect and check
connection.disconnect();
connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Performing second subscription");
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
msg = connection.receive(5000, TimeUnit.MILLISECONDS);
assertNotNull("No reset retained message for " + clientId, msg);
assertEquals(RETAIN, new String(msg.getPayload()));
msg.ack();
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
LOG.info("Test now unsubscribing from: {} for the last time", TOPICA);
connection.unsubscribe(new String[]{TOPICA});
connection.disconnect();
}
}
@Test(timeout = 60 * 1000)
public void testUniqueMessageIds() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setKeepAlive((short) 2);
mqtt.setCleanSession(true);
final List<PUBLISH> publishList = new ArrayList<PUBLISH>();
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
LOG.debug("Client received:\n" + frame);
if (frame.messageType() == PUBLISH.TYPE) {
PUBLISH publish = new PUBLISH();
try {
publish.decode(frame);
} catch (ProtocolException e) {
fail("Error decoding publish " + e.getMessage());
}
publishList.add(publish);
}
}
@Override
public void onSend(MQTTFrame frame) {
LOG.debug("Client sent:\n" + frame);
}
});
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
// create overlapping subscriptions with different QoSs
QoS[] qoss = { QoS.AT_MOST_ONCE, QoS.AT_LEAST_ONCE, QoS.EXACTLY_ONCE };
final String TOPIC = "TopicA/";
// publish retained message
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, true);
String[] subs = { TOPIC, "TopicA/#", "TopicA/+" };
for (int i = 0; i < qoss.length; i++) {
connection.subscribe(new Topic[] { new Topic(subs[i], qoss[i]) });
}
// publish non-retained message
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
int received = 0;
Message msg = connection.receive(2000, TimeUnit.MILLISECONDS);
do {
assertNotNull(msg);
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
int waitCount = 0;
while (publishList.size() <= received && waitCount < 10) {
Thread.sleep(1000);
waitCount++;
}
msg = connection.receive(2000, TimeUnit.MILLISECONDS);
} while (msg != null && received++ < subs.length * 2);
assertEquals("Unexpected number of messages", subs.length * 2, received + 1);
// make sure we received distinct ids for QoS != AT_MOST_ONCE, and 0 for
// AT_MOST_ONCE
for (int i = 0; i < publishList.size(); i++) {
for (int j = i + 1; j < publishList.size(); j++) {
final PUBLISH publish1 = publishList.get(i);
final PUBLISH publish2 = publishList.get(j);
boolean qos0 = false;
if (publish1.qos() == QoS.AT_MOST_ONCE) {
qos0 = true;
assertEquals(0, publish1.messageId());
}
if (publish2.qos() == QoS.AT_MOST_ONCE) {
qos0 = true;
assertEquals(0, publish2.messageId());
}
if (!qos0) {
assertNotEquals(publish1.messageId(), publish2.messageId());
}
}
}
connection.unsubscribe(subs);
connection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testResendMessageId() throws Exception {
final MQTT mqtt = createMQTTConnection("resend", false);
mqtt.setKeepAlive((short) 5);
final List<PUBLISH> publishList = new ArrayList<PUBLISH>();
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
LOG.debug("Client received:\n" + frame);
if (frame.messageType() == PUBLISH.TYPE) {
PUBLISH publish = new PUBLISH();
try {
publish.decode(frame);
} catch (ProtocolException e) {
fail("Error decoding publish " + e.getMessage());
}
publishList.add(publish);
}
}
@Override
public void onSend(MQTTFrame frame) {
LOG.debug("Client sent:\n" + frame);
}
});
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String TOPIC = "TopicA/";
final String[] topics = new String[] { TOPIC, "TopicA/+" };
connection.subscribe(new Topic[] { new Topic(topics[0], QoS.AT_LEAST_ONCE), new Topic(topics[1], QoS.EXACTLY_ONCE) });
// publish non-retained message
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return publishList.size() == 2;
}
}, TimeUnit.SECONDS.toMillis(5), TimeUnit.MILLISECONDS.toMillis(100));
assertEquals(2, publishList.size());
connection.disconnect();
connection = mqtt.blockingConnection();
connection.connect();
Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return publishList.size() == 4;
}
}, TimeUnit.SECONDS.toMillis(5), TimeUnit.MILLISECONDS.toMillis(100));
assertEquals(4, publishList.size());
// make sure we received duplicate message ids
assertTrue(publishList.get(0).messageId() == publishList.get(2).messageId() || publishList.get(0).messageId() == publishList.get(3).messageId());
assertTrue(publishList.get(1).messageId() == publishList.get(3).messageId() || publishList.get(1).messageId() == publishList.get(2).messageId());
assertTrue(publishList.get(2).dup() && publishList.get(3).dup());
connection.unsubscribe(topics);
connection.disconnect();
}
@Test(timeout = 90 * 1000)
public void testPacketIdGeneratorNonCleanSession() throws Exception {
final MQTT mqtt = createMQTTConnection("nonclean-packetid", false);
mqtt.setKeepAlive((short) 15);
final Map<Short, PUBLISH> publishMap = new ConcurrentHashMap<Short, PUBLISH>();
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
LOG.debug("Client received:\n" + frame);
if (frame.messageType() == PUBLISH.TYPE) {
PUBLISH publish = new PUBLISH();
try {
publish.decode(frame);
LOG.debug("PUBLISH " + publish);
} catch (ProtocolException e) {
fail("Error decoding publish " + e.getMessage());
}
if (publishMap.get(publish.messageId()) != null) {
assertTrue(publish.dup());
}
publishMap.put(publish.messageId(), publish);
}
}
@Override
public void onSend(MQTTFrame frame) {
LOG.debug("Client sent:\n" + frame);
}
});
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String TOPIC = "TopicA/";
connection.subscribe(new Topic[] { new Topic(TOPIC, QoS.EXACTLY_ONCE) });
// publish non-retained messages
final int TOTAL_MESSAGES = 10;
for (int i = 0; i < TOTAL_MESSAGES; i++) {
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
}
// receive half the messages in this session
for (int i = 0; i < TOTAL_MESSAGES / 2; i++) {
final Message msg = connection.receive(1000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
}
connection.disconnect();
// resume session
connection = mqtt.blockingConnection();
connection.connect();
// receive rest of the messages
Message msg = null;
do {
msg = connection.receive(1000, TimeUnit.MILLISECONDS);
if (msg != null) {
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
}
} while (msg != null);
// make sure we received all message ids
for (short id = 1; id <= TOTAL_MESSAGES; id++) {
assertNotNull("No message for id " + id, publishMap.get(id));
}
connection.unsubscribe(new String[] { TOPIC });
connection.disconnect();
}
@Test(timeout = 90 * 1000)
public void testPacketIdGeneratorCleanSession() throws Exception {
final String[] cleanClientIds = new String[] { "", "clean-packetid", null };
final Map<Short, PUBLISH> publishMap = new ConcurrentHashMap<Short, PUBLISH>();
MQTT[] mqtts = new MQTT[cleanClientIds.length];
for (int i = 0; i < cleanClientIds.length; i++) {
mqtts[i] = createMQTTConnection("", true);
mqtts[i].setKeepAlive((short) 15);
mqtts[i].setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
LOG.debug("Client received:\n" + frame);
if (frame.messageType() == PUBLISH.TYPE) {
PUBLISH publish = new PUBLISH();
try {
publish.decode(frame);
LOG.info("PUBLISH " + publish);
} catch (ProtocolException e) {
fail("Error decoding publish " + e.getMessage());
}
if (publishMap.get(publish.messageId()) != null) {
assertTrue(publish.dup());
}
publishMap.put(publish.messageId(), publish);
}
}
@Override
public void onSend(MQTTFrame frame) {
LOG.debug("Client sent:\n" + frame);
}
});
}
final Random random = new Random();
for (short i = 0; i < 10; i++) {
BlockingConnection connection = mqtts[random.nextInt(cleanClientIds.length)].blockingConnection();
connection.connect();
final String TOPIC = "TopicA/";
connection.subscribe(new Topic[] { new Topic(TOPIC, QoS.EXACTLY_ONCE) });
// publish non-retained message
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
Message msg = connection.receive(1000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
assertEquals(1, publishMap.size());
final short id = (short) (i + 1);
assertNotNull("No message for id " + id, publishMap.get(id));
publishMap.clear();
connection.disconnect();
}
}
@Test(timeout = 60 * 1000)
public void testClientConnectionFailure() throws Exception {
MQTT mqtt = createMQTTConnection("reconnect", false);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
});
final String TOPIC = "TopicA";
final byte[] qos = connection.subscribe(new Topic[] { new Topic(TOPIC, QoS.EXACTLY_ONCE) });
assertEquals(QoS.EXACTLY_ONCE.ordinal(), qos[0]);
connection.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
// kill transport
connection.kill();
final BlockingConnection newConnection = mqtt.blockingConnection();
newConnection.connect();
assertTrue(Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return newConnection.isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(100)));
assertEquals(QoS.EXACTLY_ONCE.ordinal(), qos[0]);
Message msg = newConnection.receive(1000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
newConnection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testNoClientId() throws Exception {
final MQTT mqtt = createMQTTConnection("", true);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
assertTrue(Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(100)));
connection.subscribe(new Topic[]{new Topic("TopicA", QoS.AT_LEAST_ONCE)});
connection.publish("TopicA", "test".getBytes(), QoS.AT_LEAST_ONCE, true);
Message message = connection.receive(3, TimeUnit.SECONDS);
assertNotNull(message);
//TODO fix audit problem for retained messages
//Thread.sleep(2000);
//connection.subscribe(new Topic[]{new Topic("TopicA", QoS.AT_LEAST_ONCE)});
//message = connection.receive(3, TimeUnit.SECONDS);
//assertNotNull(message);
}
@Test(timeout = 60 * 1000)
public void testCleanSession() throws Exception {
final String CLIENTID = "cleansession";
final MQTT mqttNotClean = createMQTTConnection(CLIENTID, false);
BlockingConnection notClean = mqttNotClean.blockingConnection();
final String TOPIC = "TopicA";
notClean.connect();
notClean.subscribe(new Topic[] { new Topic(TOPIC, QoS.EXACTLY_ONCE) });
notClean.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
notClean.disconnect();
// MUST receive message from previous not clean session
notClean = mqttNotClean.blockingConnection();
notClean.connect();
Message msg = notClean.receive(10000, TimeUnit.MILLISECONDS);
assertNotNull(msg);
assertEquals(TOPIC, new String(msg.getPayload()));
msg.ack();
notClean.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
notClean.disconnect();
// MUST NOT receive message from previous not clean session
final MQTT mqttClean = createMQTTConnection(CLIENTID, true);
final BlockingConnection clean = mqttClean.blockingConnection();
clean.connect();
msg = clean.receive(2000, TimeUnit.MILLISECONDS);
assertNull(msg);
clean.subscribe(new Topic[] { new Topic(TOPIC, QoS.EXACTLY_ONCE) });
clean.publish(TOPIC, TOPIC.getBytes(), QoS.EXACTLY_ONCE, false);
clean.disconnect();
// MUST NOT receive message from previous clean session
notClean = mqttNotClean.blockingConnection();
notClean.connect();
msg = notClean.receive(1000, TimeUnit.MILLISECONDS);
assertNull(msg);
notClean.disconnect();
}
@Test(timeout = 60 * 1000)
public void testSendMQTTReceiveJMS() throws Exception {
doTestSendMQTTReceiveJMS("foo.*");
}
public void doTestSendMQTTReceiveJMS(String destinationName) throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
// send retained message
final String RETAINED = "RETAINED";
provider.publish("foo/bah", RETAINED.getBytes(), AT_LEAST_ONCE, true);
ActiveMQConnection activeMQConnection = (ActiveMQConnection) cf.createConnection();
// MUST set to true to receive retained messages
activeMQConnection.setUseRetroactiveConsumer(true);
activeMQConnection.start();
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
javax.jms.Topic jmsTopic = s.createTopic(destinationName);
MessageConsumer consumer = s.createConsumer(jmsTopic);
// check whether we received retained message on JMS subscribe
ActiveMQMessage message = (ActiveMQMessage) consumer.receive(5000);
assertNotNull("Should get retained message", message);
ByteSequence bs = message.getContent();
assertEquals(RETAINED, new String(bs.data, bs.offset, bs.length));
assertTrue(message.getBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAINED_PROPERTY));
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish("foo/bah", payload.getBytes(), AT_LEAST_ONCE);
message = (ActiveMQMessage) consumer.receive(5000);
assertNotNull("Should get a message", message);
bs = message.getContent();
assertEquals(payload, new String(bs.data, bs.offset, bs.length));
}
activeMQConnection.close();
provider.disconnect();
}
@Test(timeout = 2 * 60 * 1000)
public void testSendJMSReceiveMQTT() throws Exception {
doTestSendJMSReceiveMQTT("foo.far");
}
public void doTestSendJMSReceiveMQTT(String destinationName) throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
ActiveMQConnection activeMQConnection = (ActiveMQConnection) cf.createConnection();
activeMQConnection.setUseRetroactiveConsumer(true);
activeMQConnection.start();
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
javax.jms.Topic jmsTopic = s.createTopic(destinationName);
MessageProducer producer = s.createProducer(jmsTopic);
// send retained message from JMS
final String RETAINED = "RETAINED";
TextMessage sendMessage = s.createTextMessage(RETAINED);
// mark the message to be retained
sendMessage.setBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAIN_PROPERTY, true);
// MQTT QoS can be set using MQTTProtocolConverter.QOS_PROPERTY_NAME property
sendMessage.setIntProperty(MQTTProtocolConverter.QOS_PROPERTY_NAME, 0);
producer.send(sendMessage);
provider.subscribe("foo/+", AT_MOST_ONCE);
byte[] message = provider.receive(10000);
assertNotNull("Should get retained message", message);
assertEquals(RETAINED, new String(message));
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "This is Test Message: " + i;
sendMessage = s.createTextMessage(payload);
producer.send(sendMessage);
message = provider.receive(5000);
assertNotNull("Should get a message", message);
assertEquals(payload, new String(message));
}
provider.disconnect();
activeMQConnection.close();
}
@Test(timeout = 60 * 1000)
public void testPingKeepsInactivityMonitorAlive() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
assertTrue("KeepAlive didn't work properly", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}));
connection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testTurnOffInactivityMonitor() throws Exception {
stopBroker();
protocolConfig = "transport.useInactivityMonitor=false";
startBroker();
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo3");
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
assertTrue("KeepAlive didn't work properly", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}));
connection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testPublishDollarTopics() throws Exception {
MQTT mqtt = createMQTTConnection();
final String clientId = "publishDollar";
mqtt.setClientId(clientId);
mqtt.setKeepAlive((short) 2);
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String DOLLAR_TOPIC = "$TopicA";
connection.subscribe(new Topic[] { new Topic(DOLLAR_TOPIC, QoS.EXACTLY_ONCE)});
connection.publish(DOLLAR_TOPIC, DOLLAR_TOPIC.getBytes(), QoS.EXACTLY_ONCE, true);
Message message = connection.receive(3, TimeUnit.SECONDS);
assertNull("Publish enabled for $ Topics by default", message);
connection.disconnect();
stopBroker();
protocolConfig = "transport.publishDollarTopics=true";
startBroker();
mqtt = createMQTTConnection();
mqtt.setClientId(clientId);
mqtt.setKeepAlive((short) 2);
connection = mqtt.blockingConnection();
connection.connect();
connection.subscribe(new Topic[] { new Topic(DOLLAR_TOPIC, QoS.EXACTLY_ONCE)});
connection.publish(DOLLAR_TOPIC, DOLLAR_TOPIC.getBytes(), QoS.EXACTLY_ONCE, true);
message = connection.receive(10, TimeUnit.SECONDS);
assertNotNull(message);
message.ack();
assertEquals("Message body", DOLLAR_TOPIC, new String(message.getPayload()));
connection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testPublishWildcard31() throws Exception {
testPublishWildcard("3.1");
}
@Test(timeout = 60 * 1000)
public void testPublishWildcard311() throws Exception {
testPublishWildcard("3.1.1");
}
private void testPublishWildcard(String version) throws Exception {
MQTT mqttPub = createMQTTConnection("MQTTPub-Client", true);
mqttPub.setVersion(version);
BlockingConnection blockingConnection = mqttPub.blockingConnection();
blockingConnection.connect();
String payload = "Test Message";
try {
blockingConnection.publish("foo/#", payload.getBytes(), QoS.AT_LEAST_ONCE, false);
fail("Should not be able to publish with wildcard in topic.");
} catch (Exception ex) {
LOG.info("Exception expected on publish with wildcard in topic name");
}
try {
blockingConnection.publish("foo/+", payload.getBytes(), QoS.AT_LEAST_ONCE, false);
fail("Should not be able to publish with wildcard in topic.");
} catch (Exception ex) {
LOG.info("Exception expected on publish with wildcard in topic name");
}
blockingConnection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testDuplicateClientId() throws Exception {
// test link stealing enabled by default
final String clientId = "duplicateClient";
MQTT mqtt = createMQTTConnection(clientId, false);
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String TOPICA = "TopicA";
connection.publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
MQTT mqtt1 = createMQTTConnection(clientId, false);
mqtt1.setKeepAlive((short) 2);
final BlockingConnection connection1 = mqtt1.blockingConnection();
connection1.connect();
assertTrue("Duplicate client disconnected", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection1.isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(100)));
assertTrue("Old client still connected", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(100)));
connection1.publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
connection1.disconnect();
// disable link stealing
stopBroker();
protocolConfig = "allowLinkStealing=false";
startBroker();
mqtt = createMQTTConnection(clientId, false);
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection2 = mqtt.blockingConnection();
connection2.connect();
connection2.publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
mqtt1 = createMQTTConnection(clientId, false);
mqtt1.setKeepAlive((short) 2);
final BlockingConnection connection3 = mqtt1.blockingConnection();
try {
connection3.connect();
fail("Duplicate client connected");
} catch (Exception e) {
// ignore
}
assertTrue("Old client disconnected", connection2.isConnected());
connection2.publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
connection2.disconnect();
}
@Test(timeout = 60 * 1000)
public void testRepeatedLinkStealing() throws Exception {
final String clientId = "duplicateClient";
final AtomicReference<BlockingConnection> oldConnection = new AtomicReference<BlockingConnection>();
final String TOPICA = "TopicA";
for (int i = 1; i <= 10; ++i) {
LOG.info("Creating MQTT Connection {}", i);
MQTT mqtt = createMQTTConnection(clientId, false);
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
assertTrue("Client connect failed for attempt: " + i, Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(200)));
if (oldConnection.get() != null) {
assertTrue("Old client still connected", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !oldConnection.get().isConnected();
}
}, TimeUnit.SECONDS.toMillis(30), TimeUnit.MILLISECONDS.toMillis(200)));
}
oldConnection.set(connection);
}
oldConnection.get().publish(TOPICA, TOPICA.getBytes(), QoS.EXACTLY_ONCE, true);
oldConnection.get().disconnect();
}
@Test(timeout = 30 * 10000)
public void testJmsMapping() throws Exception {
doTestJmsMapping("test.foo");
}
public void doTestJmsMapping(String destinationName) throws Exception {
// start up jms consumer
Connection jmsConn = cf.createConnection();
Session session = jmsConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination dest = session.createTopic(destinationName);
MessageConsumer consumer = session.createConsumer(dest);
jmsConn.start();
// set up mqtt producer
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo3");
mqtt.setKeepAlive((short) 2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
int messagesToSend = 5;
// publish
for (int i = 0; i < messagesToSend; ++i) {
connection.publish("test/foo", "hello world".getBytes(), QoS.AT_LEAST_ONCE, false);
}
connection.disconnect();
for (int i = 0; i < messagesToSend; i++) {
javax.jms.Message message = consumer.receive(2 * 1000);
assertNotNull(message);
assertTrue(message instanceof BytesMessage);
BytesMessage bytesMessage = (BytesMessage) message;
int length = (int) bytesMessage.getBodyLength();
byte[] buffer = new byte[length];
bytesMessage.readBytes(buffer);
assertEquals("hello world", new String(buffer));
}
jmsConn.close();
}
@Test(timeout = 30 * 10000)
public void testSubscribeWithLargeTopicFilter() throws Exception {
byte[] payload = new byte[1024 * 32];
for (int i = 0; i < payload.length; i++) {
payload[i] = '2';
}
StringBuilder topicBuilder = new StringBuilder("/topic/");
for (int i = 0; i < 32800; ++i) {
topicBuilder.append("a");
}
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
Topic[] topic = { new Topic(topicBuilder.toString(), QoS.EXACTLY_ONCE) };
connection.subscribe(topic);
connection.publish(topic[0].name().toString(), payload, QoS.AT_LEAST_ONCE, false);
Message message = connection.receive();
assertNotNull(message);
message.ack();
}
@Test(timeout = 30 * 10000)
public void testSubscribeWithZeroLengthTopic() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic topic = new Topic("", QoS.EXACTLY_ONCE);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topic.name());
try {
connection.subscribe(new Topic[] { topic });
fail("Should not be able to subscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
@Test(timeout = 30 * 10000)
public void testUnsubscribeWithZeroLengthTopic() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic topic = new Topic("", QoS.EXACTLY_ONCE);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topic.name());
try {
connection.unsubscribe(new String[] { topic.name().toString() });
fail("Should not be able to subscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
@Test(timeout = 30 * 10000)
public void testSubscribeWithInvalidMultiLevelWildcards() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic[] topics = { new Topic("#/Foo", QoS.EXACTLY_ONCE),
new Topic("#/Foo/#", QoS.EXACTLY_ONCE),
new Topic("Foo/#/Level", QoS.EXACTLY_ONCE),
new Topic("Foo/X#", QoS.EXACTLY_ONCE) };
for (int i = 0; i < topics.length; ++i) {
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topics[i].name());
try {
connection.subscribe(new Topic[] { topics[i] });
fail("Should not be able to subscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
}
@Test(timeout = 30 * 10000)
public void testSubscribeWithInvalidSingleLevelWildcards() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic[] topics = { new Topic("Foo+", QoS.EXACTLY_ONCE),
new Topic("+Foo/#", QoS.EXACTLY_ONCE),
new Topic("+#", QoS.EXACTLY_ONCE),
new Topic("Foo/+X/Level", QoS.EXACTLY_ONCE),
new Topic("Foo/+F", QoS.EXACTLY_ONCE) };
for (int i = 0; i < topics.length; ++i) {
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topics[i].name());
try {
connection.subscribe(new Topic[] { topics[i] });
fail("Should not be able to subscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
}
@Test(timeout = 30 * 10000)
public void testUnsubscribeWithInvalidMultiLevelWildcards() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic[] topics = { new Topic("#/Foo", QoS.EXACTLY_ONCE),
new Topic("#/Foo/#", QoS.EXACTLY_ONCE),
new Topic("Foo/#/Level", QoS.EXACTLY_ONCE),
new Topic("Foo/X#", QoS.EXACTLY_ONCE) };
for (int i = 0; i < topics.length; ++i) {
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topics[i].name());
try {
connection.unsubscribe(new String[] { topics[i].name().toString() });
fail("Should not be able to unsubscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
}
@Test(timeout = 30 * 10000)
public void testUnsubscribeWithInvalidSingleLevelWildcards() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
Topic[] topics = { new Topic("Foo+", QoS.EXACTLY_ONCE),
new Topic("+Foo/#", QoS.EXACTLY_ONCE),
new Topic("+#", QoS.EXACTLY_ONCE),
new Topic("Foo/+X/Level", QoS.EXACTLY_ONCE),
new Topic("Foo/+F", QoS.EXACTLY_ONCE) };
for (int i = 0; i < topics.length; ++i) {
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
LOG.info("Trying to subscrobe to topic: {}", topics[i].name());
try {
connection.unsubscribe(new String[] { topics[i].name().toString() });
fail("Should not be able to unsubscribe with invalid Topic");
} catch (Exception ex) {
LOG.info("Caught expected error on subscribe");
}
assertTrue("Should have lost connection", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return !connection.isConnected();
}
}));
}
}
@Test(timeout = 30 * 10000)
public void testSubscribeMultipleTopics() throws Exception {
byte[] payload = new byte[1024 * 32];
for (int i = 0; i < payload.length; i++) {
payload[i] = '2';
}
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("MQTT-Client");
mqtt.setCleanSession(false);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
Topic[] topics = { new Topic("Topic/A", QoS.EXACTLY_ONCE), new Topic("Topic/B", QoS.EXACTLY_ONCE) };
Topic[] wildcardTopic = { new Topic("Topic/#", QoS.AT_LEAST_ONCE) };
connection.subscribe(wildcardTopic);
for (Topic topic : topics) {
connection.publish(topic.name().toString(), payload, QoS.AT_LEAST_ONCE, false);
}
int received = 0;
for (int i = 0; i < topics.length; ++i) {
Message message = connection.receive();
assertNotNull(message);
received++;
payload = message.getPayload();
String messageContent = new String(payload);
LOG.info("Received message from topic: " + message.getTopic() + " Message content: " + messageContent);
message.ack();
}
assertEquals("Should have received " + topics.length + " messages", topics.length, received);
}
@Test(timeout = 60 * 1000)
public void testReceiveMessageSentWhileOffline() throws Exception {
final byte[] payload = new byte[1024 * 32];
for (int i = 0; i < payload.length; i++) {
payload[i] = '2';
}
int numberOfRuns = 100;
int messagesPerRun = 2;
final MQTT mqttPub = createMQTTConnection("MQTT-Pub-Client", true);
final MQTT mqttSub = createMQTTConnection("MQTT-Sub-Client", false);
final BlockingConnection connectionPub = mqttPub.blockingConnection();
connectionPub.connect();
BlockingConnection connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
Topic[] topics = { new Topic("TopicA", QoS.EXACTLY_ONCE) };
connectionSub.subscribe(topics);
for (int i = 0; i < messagesPerRun; ++i) {
connectionPub.publish(topics[0].name().toString(), payload, QoS.AT_LEAST_ONCE, false);
}
int received = 0;
for (int i = 0; i < messagesPerRun; ++i) {
Message message = connectionSub.receive(5, TimeUnit.SECONDS);
assertNotNull(message);
received++;
assertTrue(Arrays.equals(payload, message.getPayload()));
message.ack();
}
connectionSub.disconnect();
try {
for (int j = 0; j < numberOfRuns; j++) {
for (int i = 0; i < messagesPerRun; ++i) {
connectionPub.publish(topics[0].name().toString(), payload, QoS.AT_LEAST_ONCE, false);
}
connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
connectionSub.subscribe(topics);
for (int i = 0; i < messagesPerRun; ++i) {
Message message = connectionSub.receive(5, TimeUnit.SECONDS);
assertNotNull(message);
received++;
assertTrue(Arrays.equals(payload, message.getPayload()));
message.ack();
}
connectionSub.disconnect();
}
} catch (Exception exception) {
LOG.error("unexpected exception", exception);
exception.printStackTrace();
}
assertEquals("Should have received " + (messagesPerRun * (numberOfRuns + 1)) + " messages", (messagesPerRun * (numberOfRuns + 1)), received);
}
@Test(timeout = 60 * 1000)
public void testReceiveMessageSentWhileOfflineAndBrokerRestart() throws Exception {
stopBroker();
this.persistent = true;
startBroker();
final byte[] payload = new byte[1024 * 32];
for (int i = 0; i < payload.length; i++) {
payload[i] = '2';
}
int messagesPerRun = 10;
Topic[] topics = { new Topic("TopicA", QoS.EXACTLY_ONCE) };
{
// Establish a durable subscription.
MQTT mqttSub = createMQTTConnection("MQTT-Sub-Client", false);
BlockingConnection connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
connectionSub.subscribe(topics);
connectionSub.disconnect();
}
MQTT mqttPubLoop = createMQTTConnection("MQTT-Pub-Client", true);
BlockingConnection connectionPub = mqttPubLoop.blockingConnection();
connectionPub.connect();
for (int i = 0; i < messagesPerRun; ++i) {
connectionPub.publish(topics[0].name().toString(), payload, QoS.AT_LEAST_ONCE, false);
}
connectionPub.disconnect();
restartBroker();
MQTT mqttSub = createMQTTConnection("MQTT-Sub-Client", false);
BlockingConnection connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
connectionSub.subscribe(topics);
for (int i = 0; i < messagesPerRun; ++i) {
Message message = connectionSub.receive(5, TimeUnit.SECONDS);
assertNotNull(message);
assertTrue(Arrays.equals(payload, message.getPayload()));
message.ack();
}
connectionSub.disconnect();
}
@Test(timeout = 30 * 1000)
public void testDefaultKeepAliveWhenClientSpecifiesZero() throws Exception {
stopBroker();
protocolConfig = "transport.defaultKeepAlive=2000";
startBroker();
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setKeepAlive((short) 0);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
assertTrue("KeepAlive didn't work properly", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}));
}
@Test(timeout = 60 * 1000)
public void testReuseConnection() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("Test-Client");
{
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.disconnect();
Thread.sleep(500);
}
{
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.disconnect();
Thread.sleep(500);
}
}
@Test(timeout = 60 * 1000)
public void testNoMessageReceivedAfterUnsubscribeMQTT() throws Exception {
Topic[] topics = { new Topic("TopicA", QoS.EXACTLY_ONCE) };
MQTT mqttPub = createMQTTConnection("MQTTPub-Client", true);
// mqttPub.setVersion("3.1.1");
MQTT mqttSub = createMQTTConnection("MQTTSub-Client", false);
// mqttSub.setVersion("3.1.1");
BlockingConnection connectionPub = mqttPub.blockingConnection();
connectionPub.connect();
BlockingConnection connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
connectionSub.subscribe(topics);
connectionSub.disconnect();
for (int i = 0; i < 5; i++) {
String payload = "Message " + i;
connectionPub.publish(topics[0].name().toString(), payload.getBytes(), QoS.EXACTLY_ONCE, false);
}
connectionSub = mqttSub.blockingConnection();
connectionSub.connect();
int received = 0;
for (int i = 0; i < 5; ++i) {
Message message = connectionSub.receive(5, TimeUnit.SECONDS);
assertNotNull("Missing message " + i, message);
LOG.info("Message is " + new String(message.getPayload()));
received++;
message.ack();
}
assertEquals(5, received);
// unsubscribe from topic
connectionSub.unsubscribe(new String[]{"TopicA"});
// send more messages
for (int i = 0; i < 5; i++) {
String payload = "Message " + i;
connectionPub.publish(topics[0].name().toString(), payload.getBytes(), QoS.EXACTLY_ONCE, false);
}
// these should not be received
assertNull(connectionSub.receive(2, TimeUnit.SECONDS));
connectionSub.disconnect();
connectionPub.disconnect();
}
@Test(timeout = 60 * 1000)
public void testMQTT311Connection() throws Exception {
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("foo");
mqtt.setVersion("3.1.1");
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
connection.disconnect();
}
@Test(timeout = 60 * 1000)
public void testActiveMQRecoveryPolicy() throws Exception {
// test with ActiveMQ LastImageSubscriptionRecoveryPolicy
final PolicyMap policyMap = new PolicyMap();
final PolicyEntry policyEntry = new PolicyEntry();
policyEntry.setSubscriptionRecoveryPolicy(new LastImageSubscriptionRecoveryPolicy());
policyMap.put(new ActiveMQTopic(">"), policyEntry);
brokerService.setDestinationPolicy(policyMap);
MQTT mqtt = createMQTTConnection("pub-sub", true);
final int[] retain = new int[1];
final int[] nonretain = new int[1];
mqtt.setTracer(new Tracer() {
@Override
public void onReceive(MQTTFrame frame) {
if (frame.messageType() == PUBLISH.TYPE) {
LOG.info("Received message with retain=" + frame.retain());
if (frame.retain()) {
retain[0]++;
} else {
nonretain[0]++;
}
}
}
});
BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
final String RETAINED = "RETAINED";
connection.publish("one", RETAINED.getBytes(), QoS.AT_LEAST_ONCE, true);
connection.publish("two", RETAINED.getBytes(), QoS.AT_LEAST_ONCE, true);
final String NONRETAINED = "NONRETAINED";
connection.publish("one", NONRETAINED.getBytes(), QoS.AT_LEAST_ONCE, false);
connection.publish("two", NONRETAINED.getBytes(), QoS.AT_LEAST_ONCE, false);
connection.subscribe(new Topic[]{new Topic("#", QoS.AT_LEAST_ONCE)});
for (int i = 0; i < 4; i++) {
final Message message = connection.receive(30, TimeUnit.SECONDS);
assertNotNull("Should receive 4 messages", message);
message.ack();
}
assertEquals("Should receive 2 retained messages", 2, retain[0]);
assertEquals("Should receive 2 non-retained messages", 2, nonretain[0]);
}
@Test(timeout = 60 * 1000)
public void testSendMQTTReceiveJMSVirtualTopic() throws Exception {
final MQTTClientProvider provider = getMQTTClientProvider();
initializeConnection(provider);
final String DESTINATION_NAME = "Consumer.jms.VirtualTopic.TopicA";
// send retained message
final String RETAINED = "RETAINED";
final String MQTT_DESTINATION_NAME = "VirtualTopic/TopicA";
provider.publish(MQTT_DESTINATION_NAME, RETAINED.getBytes(), AT_LEAST_ONCE, true);
ActiveMQConnection activeMQConnection = (ActiveMQConnection) new ActiveMQConnectionFactory(jmsUri).createConnection();
// MUST set to true to receive retained messages
activeMQConnection.setUseRetroactiveConsumer(true);
activeMQConnection.start();
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue jmsQueue = s.createQueue(DESTINATION_NAME);
MessageConsumer consumer = s.createConsumer(jmsQueue);
// check whether we received retained message on JMS subscribe
ActiveMQMessage message = (ActiveMQMessage) consumer.receive(5000);
assertNotNull("Should get retained message", message);
ByteSequence bs = message.getContent();
assertEquals(RETAINED, new String(bs.data, bs.offset, bs.length));
assertTrue(message.getBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAINED_PROPERTY));
for (int i = 0; i < NUM_MESSAGES; i++) {
String payload = "Test Message: " + i;
provider.publish(MQTT_DESTINATION_NAME, payload.getBytes(), AT_LEAST_ONCE);
message = (ActiveMQMessage) consumer.receive(5000);
assertNotNull("Should get a message", message);
bs = message.getContent();
assertEquals(payload, new String(bs.data, bs.offset, bs.length));
}
// re-create consumer and check we received retained message again
consumer.close();
consumer = s.createConsumer(jmsQueue);
message = (ActiveMQMessage) consumer.receive(5000);
assertNotNull("Should get retained message", message);
bs = message.getContent();
assertEquals(RETAINED, new String(bs.data, bs.offset, bs.length));
assertTrue(message.getBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAINED_PROPERTY));
activeMQConnection.close();
provider.disconnect();
}
@Test(timeout = 60 * 1000)
public void testPingOnMQTT() throws Exception {
stopBroker();
protocolConfig = "maxInactivityDuration=-1";
startBroker();
MQTT mqtt = createMQTTConnection();
mqtt.setClientId("test-mqtt");
mqtt.setKeepAlive((short)2);
final BlockingConnection connection = mqtt.blockingConnection();
connection.connect();
assertTrue("KeepAlive didn't work properly", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connection.isConnected();
}
}));
connection.disconnect();
}
@Test
public void testConnectWithLargePassword() throws Exception {
for (String version : Arrays.asList("3.1", "3.1.1")) {
String longString = new String(new char[65535]);
BlockingConnection connection = null;
try {
MQTT mqtt = createMQTTConnection("test-" + version, true);
mqtt.setUserName(longString);
mqtt.setPassword(longString);
mqtt.setConnectAttemptsMax(1);
mqtt.setVersion(version);
connection = mqtt.blockingConnection();
connection.connect();
final BlockingConnection con = connection;
assertTrue(Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return con.isConnected();
}
}));
} finally {
if (connection != null && connection.isConnected()) connection.disconnect();
}
}
}
}