/** * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.artemis.tests.integration.mqtt.imported; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import java.io.EOFException; import java.lang.reflect.Field; import java.net.ProtocolException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.protocol.mqtt.MQTTConnectionManager; import org.apache.activemq.artemis.core.protocol.mqtt.MQTTSession; import org.apache.activemq.artemis.core.protocol.mqtt.MQTTUtil; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.impl.AddressInfo; import org.apache.activemq.artemis.tests.util.Wait; import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; import org.fusesource.mqtt.client.BlockingConnection; import org.fusesource.mqtt.client.MQTT; import org.fusesource.mqtt.client.MQTTException; 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.Before; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * QT * MQTT Test imported from ActiveMQ MQTT component. */ public class MQTTTest extends MQTTTestSupport { private static final Logger LOG = LoggerFactory.getLogger(MQTTTest.class); @Override @Before public void setUp() throws Exception { Field sessions = MQTTSession.class.getDeclaredField("SESSIONS"); sessions.setAccessible(true); sessions.set(null, new ConcurrentHashMap<>()); Field connectedClients = MQTTConnectionManager.class.getDeclaredField("CONNECTED_CLIENTS"); connectedClients.setAccessible(true); connectedClients.set(null, new ConcurrentHashSet<>()); super.setUp(); } @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(); assertTrue(connection.isConnected()); } finally { if (connection != null && connection.isConnected()) connection.disconnect(); } } } @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) { 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 testManagementQueueMessagesAreAckd() throws Exception { String clientId = "test.client.id"; final MQTTClientProvider provider = getMQTTClientProvider(); provider.setClientId(clientId); 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(), EXACTLY_ONCE); byte[] message = provider.receive(5000); assertNotNull("Should get a message", message); assertEquals(payload, new String(message)); } final Queue queue = server.locateQueue(new SimpleString(MQTTUtil.MANAGEMENT_QUEUE_PREFIX + clientId)); Wait.waitFor(() -> queue.getMessageCount() == 0, 1000, 100); assertEquals(0, queue.getMessageCount()); 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(5000); 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 testSendAndReceiveExactlyOnceWithInterceptors() throws Exception { MQTTIncomingInterceptor.clear(); MQTTOutoingInterceptor.clear(); 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(); assertEquals(NUM_MESSAGES, MQTTIncomingInterceptor.getMessageCount()); assertEquals(NUM_MESSAGES, MQTTOutoingInterceptor.getMessageCount()); } @Ignore @Test(timeout = 600 * 1000) public void testSendMoreThanUniqueId() throws Exception { int messages = (Short.MAX_VALUE * 2) + 1; final MQTTClientProvider publisher = getMQTTClientProvider(); initializeConnection(publisher); final MQTTClientProvider subscriber = getMQTTClientProvider(); initializeConnection(subscriber); int count = 0; subscriber.subscribe("foo", EXACTLY_ONCE); for (int i = 0; i < 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)); count++; } assertEquals(messages, count); 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<>(); 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 = 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(5, TimeUnit.SECONDS); do { assertNotNull("RETAINED null " + wildcard, msg); String msgPayload = new String(msg.getPayload()); assertTrue("RETAINED prefix " + wildcard + " msg " + msgPayload, msgPayload.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) 20); 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 { MQTT mqtt = createMQTTConnection(); mqtt.setKeepAlive((short) 60); final String RETAIN = "RETAIN"; final String TOPICA = "TopicA"; final String[] clientIds = {null, "foo", "durable"}; for (String clientId : clientIds) { LOG.info("Testing now with Client ID: {}", clientId); mqtt.setClientId(clientId); mqtt.setCleanSession(!"durable".equals(clientId)); 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); 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(); 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)); connection.unsubscribe(new String[]{TOPICA}); connection.disconnect(); } } @Ignore @Test(timeout = 120 * 1000) public void testRetainedMessageOnVirtualTopics() throws Exception { MQTT mqtt = createMQTTConnection(); mqtt.setKeepAlive((short) 60); final String RETAIN = "RETAIN"; final String TOPICA = "VirtualTopic/TopicA"; final String[] clientIds = {null, "foo", "durable"}; for (String clientId : clientIds) { LOG.info("Testing now with Client ID: {}", clientId); mqtt.setClientId(clientId); mqtt.setCleanSession(!"durable".equals(clientId)); 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); 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(); 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<>(); mqtt.setTracer(new Tracer() { @Override public void onReceive(MQTTFrame frame) { LOG.info("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.info("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(5000, 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(5000, 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<>(); mqtt.setTracer(new Tracer() { @Override public void onReceive(MQTTFrame frame) { LOG.info("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.info("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 isSatisfied() throws Exception { return publishList.size() == 2; } }, 5000); assertEquals(2, publishList.size()); connection.disconnect(); connection = mqtt.blockingConnection(); connection.connect(); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() throws Exception { return publishList.size() == 4; } }, 5000); assertEquals(4, publishList.size()); // TODO Investigate if receiving the same ID for overlapping subscriptions is actually spec compliant. // In Artemis we send a new ID for every copy of the message. // 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<>(); mqtt.setTracer(new Tracer() { @Override public void onReceive(MQTTFrame frame) { LOG.info("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.info("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(); } @Ignore @Test(timeout = 90 * 1000) // TODO ActiveMQ 5.x does not reset the message id generator even after a clean session. In Artemis we always reset. // If there is a good reason for this we should follow ActiveMQ. public void testPacketIdGeneratorCleanSession() throws Exception { final String[] cleanClientIds = new String[]{"", "clean-packetid", null}; final Map<Short, PUBLISH> publishMap = new ConcurrentHashMap<>(); 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.info("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.info("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); mqtt.setKeepAlive((short) 1); final BlockingConnection connection = mqtt.blockingConnection(); connection.connect(); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() 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(); // FIXME Wait for the previous connection to timeout. This is not required in ActiveMQ. Needs investigating. Thread.sleep(10000); final BlockingConnection newConnection = mqtt.blockingConnection(); newConnection.connect(); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() throws Exception { return newConnection.isConnected(); } }); 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 testClientConnectionFailureSendsWillMessage() throws Exception { getServer().createQueue(SimpleString.toSimpleString("will"), RoutingType.MULTICAST, SimpleString.toSimpleString("will"), null, true, false); MQTT mqtt = createMQTTConnection("1", false); mqtt.setKeepAlive((short) 1); mqtt.setWillMessage("test message"); mqtt.setWillTopic("will"); mqtt.setWillQos(QoS.AT_LEAST_ONCE); final BlockingConnection connection = mqtt.blockingConnection(); connection.connect(); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() throws Exception { return connection.isConnected(); } }); MQTT mqtt2 = createMQTTConnection("2", false); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(new Topic[]{new Topic("will", QoS.AT_LEAST_ONCE)}); // kill transport connection.kill(); // FIXME Wait for the previous connection to timeout. This is not required in ActiveMQ. Needs investigating. Thread.sleep(10000); Message m = connection2.receive(1000, TimeUnit.MILLISECONDS); assertEquals("test message", new String(m.getPayload())); } @Test(timeout = 60 * 1000) public void testWillMessageIsRetained() throws Exception { getServer().createQueue(SimpleString.toSimpleString("will"), RoutingType.MULTICAST, SimpleString.toSimpleString("will"), null, true, false); MQTT mqtt = createMQTTConnection("1", false); mqtt.setKeepAlive((short) 1); mqtt.setWillMessage("test message"); mqtt.setWillTopic("will"); mqtt.setWillQos(QoS.AT_LEAST_ONCE); mqtt.setWillRetain(true); final BlockingConnection connection = mqtt.blockingConnection(); connection.connect(); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() throws Exception { return connection.isConnected(); } }); // kill transport connection.kill(); Thread.sleep(10000); MQTT mqtt2 = createMQTTConnection("2", false); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(new Topic[]{new Topic("will", QoS.AT_LEAST_ONCE)}); Message m = connection2.receive(1000, TimeUnit.MILLISECONDS); assertNotNull(m); m.ack(); assertEquals("test message", new String(m.getPayload())); } @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(10000, 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.*", "foo/bar"); } public void doTestSendMQTTReceiveJMS(String jmsTopicAddress, String mqttAddress) throws Exception { final MQTTClientProvider provider = getMQTTClientProvider(); initializeConnection(provider); // send retained message final String address = mqttAddress; final String RETAINED = "RETAINED"; final byte[] payload = RETAINED.getBytes(); Connection connection = cf.createConnection(); // MUST set to true to receive retained messages connection.start(); Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); javax.jms.Queue jmsQueue = s.createQueue(jmsTopicAddress); MessageConsumer consumer = s.createConsumer(jmsQueue); provider.publish(address, RETAINED.getBytes(), AT_LEAST_ONCE, true); // check whether we received retained message on JMS subscribe BytesMessage message = (BytesMessage) consumer.receive(5000); assertNotNull("Should get retained message", message); byte[] b = new byte[8]; message.readBytes(b); assertArrayEquals(payload, b); for (int i = 0; i < NUM_MESSAGES; i++) { String p = "Test Message: " + i; provider.publish(address, p.getBytes(), AT_LEAST_ONCE); message = (BytesMessage) consumer.receive(5000); assertNotNull("Should get a message", message); byte[] bytePayload = new byte[p.getBytes().length]; message.readBytes(bytePayload); assertArrayEquals(payload, b); } connection.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); provider.subscribe("foo/+", AT_MOST_ONCE); Connection connection = cf.createConnection(); connection.start(); Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); javax.jms.Topic topic = s.createTopic(destinationName); MessageProducer producer = s.createProducer(topic); // send retained message from JMS final byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; BytesMessage bytesMessage = s.createBytesMessage(); bytesMessage.writeBytes(bytes); producer.send(bytesMessage); byte[] message = provider.receive(10000); assertNotNull("Should get retained message", message); assertArrayEquals(bytes, message); provider.disconnect(); connection.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 isSatisfied() 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 isSatisfied() throws Exception { return connection.isConnected(); } })); connection.disconnect(); } @Ignore @Test(timeout = 60 * 1000) // TODO Make dollar topics configurable in code base. 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(10, 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(); } @Ignore @Test(timeout = 60 * 1000) // TODO We currently do not support link stealing. This needs to be enabled for this test to pass. 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 isSatisfied() throws Exception { return connection1.isConnected(); } })); assertTrue("Old client still connected", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisfied() throws Exception { return !connection.isConnected(); } })); 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 = 30 * 10000) public void testJmsMapping() throws Exception { doTestJmsMapping("test.foo"); } // TODO As with other tests, this should be enabled as part of the cross protocol support with MQTT. 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.createQueue(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 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(); 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(); } assertEquals("Should have received " + (messagesPerRun * (numberOfRuns + 1)) + " messages", (messagesPerRun * (numberOfRuns + 1)), received); } @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 isSatisfied() 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(1000); } { BlockingConnection connection = mqtt.blockingConnection(); connection.connect(); connection.disconnect(); Thread.sleep(1000); } } @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(5, 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(); } // TODO This should be reworked to align with Artemis recovery. // @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]); // } // TODO As with other tests, this should be enabled as part of the cross protocol support with MQTT. // @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 isSatisfied() throws Exception { return connection.isConnected(); } })); connection.disconnect(); } @Test(timeout = 60 * 1000) public void testClientDisconnectedOnMaxConsumerLimitReached() throws Exception { Exception peerDisconnectedException = null; try { String clientId = "test.client"; SimpleString coreAddress = new SimpleString("foo.bar"); Topic[] mqttSubscription = new Topic[]{new Topic("foo/bar", QoS.AT_LEAST_ONCE)}; getServer().createQueue(coreAddress, RoutingType.MULTICAST, new SimpleString(clientId + "." + coreAddress), null, false, true, 0, false, true); MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); mqtt.setKeepAlive((short) 2); final BlockingConnection connection = mqtt.blockingConnection(); connection.connect(); connection.subscribe(mqttSubscription); } catch (EOFException e) { peerDisconnectedException = e; } assertNotNull(peerDisconnectedException); assertTrue(peerDisconnectedException.getMessage().contains("Peer disconnected")); } @Test(timeout = 60 * 1000) public void testAnycastPrefixWorksWithMQTT() throws Exception { String clientId = "testMqtt"; String anycastAddress = "anycast:foo/bar"; String sendAddress = "foo/bar"; Topic[] mqttSubscription = new Topic[]{new Topic(anycastAddress, QoS.AT_LEAST_ONCE)}; MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); BlockingConnection connection1 = mqtt.blockingConnection(); connection1.connect(); connection1.subscribe(mqttSubscription); MQTT mqtt2 = createMQTTConnection(); mqtt2.setClientId(clientId + "2"); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(mqttSubscription); String message1 = "TestMessage1"; String message2 = "TestMessage2"; connection1.publish(sendAddress, message1.getBytes(), QoS.AT_LEAST_ONCE, false); connection2.publish(sendAddress, message2.getBytes(), QoS.AT_LEAST_ONCE, false); assertNotNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNotNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); assertNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); } @Test(timeout = 60 * 1000) public void testAnycastAddressWorksWithMQTT() throws Exception { String anycastAddress = "foo/bar"; getServer().addAddressInfo(new AddressInfo(SimpleString.toSimpleString("foo.bar"), RoutingType.ANYCAST)); String clientId = "testMqtt"; Topic[] mqttSubscription = new Topic[]{new Topic(anycastAddress, QoS.AT_LEAST_ONCE)}; MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); BlockingConnection connection1 = mqtt.blockingConnection(); connection1.connect(); connection1.subscribe(mqttSubscription); MQTT mqtt2 = createMQTTConnection(); mqtt2.setClientId(clientId + "2"); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(mqttSubscription); String message1 = "TestMessage1"; String message2 = "TestMessage2"; connection1.publish(anycastAddress, message1.getBytes(), QoS.AT_LEAST_ONCE, false); connection2.publish(anycastAddress, message2.getBytes(), QoS.AT_LEAST_ONCE, false); assertNotNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNotNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); assertNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); } @Test(timeout = 60 * 1000) public void testAmbiguousRoutingWithMQTT() throws Exception { String anycastAddress = "foo/bar"; Set<RoutingType> routingTypeSet = new HashSet<>(); routingTypeSet.add(RoutingType.ANYCAST); routingTypeSet.add(RoutingType.MULTICAST); getServer().addAddressInfo(new AddressInfo(SimpleString.toSimpleString("foo.bar"), routingTypeSet)); String clientId = "testMqtt"; Topic[] mqttSubscription = new Topic[]{new Topic(anycastAddress, QoS.AT_LEAST_ONCE)}; MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); BlockingConnection connection1 = mqtt.blockingConnection(); connection1.connect(); connection1.subscribe(mqttSubscription); MQTT mqtt2 = createMQTTConnection(); mqtt2.setClientId(clientId + "2"); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(mqttSubscription); String message1 = "TestMessage1"; String message2 = "TestMessage2"; connection1.publish(anycastAddress, message1.getBytes(), QoS.AT_LEAST_ONCE, false); connection2.publish(anycastAddress, message2.getBytes(), QoS.AT_LEAST_ONCE, false); assertNotNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNotNull(connection1.receive(1000, TimeUnit.MILLISECONDS)); assertNotNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); assertNotNull(connection2.receive(1000, TimeUnit.MILLISECONDS)); } @Test public void testRetainedMessagesAreCorrectlyFormedAfterRestart() throws Exception { String clientId = "testMqtt"; String address = "testAddress"; String payload = "This is a test message"; // Create address getServer().addAddressInfo(new AddressInfo(SimpleString.toSimpleString(address), RoutingType.MULTICAST)); // Send MQTT Retain Message Topic[] mqttTopic = new Topic[]{new Topic(address, QoS.AT_LEAST_ONCE)}; MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); BlockingConnection connection1 = mqtt.blockingConnection(); connection1.connect(); connection1.publish(address, payload.getBytes(), QoS.AT_LEAST_ONCE, true); getServer().stop(false); getServer().start(); waitForServerToStart(getServer()); MQTT mqtt2 = createMQTTConnection(); mqtt2.setClientId(clientId + "2"); BlockingConnection connection2 = mqtt2.blockingConnection(); connection2.connect(); connection2.subscribe(mqttTopic); Message message = connection2.receive(5000, TimeUnit.MILLISECONDS); assertEquals(payload, new String(message.getPayload())); } @Test public void testDuplicateIDReturnsError() throws Exception { String clientId = "clientId"; MQTT mqtt = createMQTTConnection(); mqtt.setClientId(clientId); mqtt.blockingConnection().connect(); MQTTException e = null; try { MQTT mqtt2 = createMQTTConnection(); mqtt2.setClientId(clientId); mqtt2.blockingConnection().connect(); } catch (MQTTException mqttE) { e = mqttE; } assertTrue(e.getMessage().contains("CONNECTION_REFUSED_IDENTIFIER_REJECTED")); } }