diff --git a/.travis.yml b/.travis.yml index dff5f3a5d..9e2aefbbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,23 @@ +sudo: true language: java + +matrix: + include: + - os: linux + dist: trusty + sudo: required +# - os: osx + +sudo: required + +before_install: + - ./travis-install.sh + +script: mvn --projects org.eclipse.paho.client.mqttv3,org.eclipse.paho.client.mqttv3.test test -Dtest.server_ssl_port=18885 -Dtest.server_uri=tcp://localhost:1883 -Dtest.server_websocket_uri=ws://localhost:8080 -B + +addons: + apt: + sources: + - sourceline: 'ppa:mosquitto-dev/mosquitto-ppa' + packages: + - mosquitto diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb1acda28..f0ffef9ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,8 +20,6 @@ Please read the [Eclipse Foundation policy on accepting contributions via Git](h ## Contributing a change -## Contributing a change - 1. [Fork the repository on GitHub](https://github.com/eclipse/paho.mqtt.java/fork) 2. Clone the forked repository onto your computer: ``` git clone https://github.com//paho.mqtt.java.git ``` 3. Create a new branch from the latest ```develop``` branch with ```git checkout -b YOUR_BRANCH_NAME origin/develop``` diff --git a/README.md b/README.md index 180e483d2..fc090ddf8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Add the repository definition and the dependency definition shown below to your Replace %REPOURL% with either ``` https://repo.eclipse.org/content/repositories/paho-releases/ ``` for the official releases, or ``` https://repo.eclipse.org/content/repositories/paho-snapshots/ ``` for the nightly snapshots. Replace %VERSION% with the level required . -The latest release version is ```1.1.1``` and the current snapshot version is ```1.1.2-SNAPSHOT```. +The latest release version is ```1.2.0``` and the current snapshot version is ```1.2.1-SNAPSHOT```. ``` diff --git a/org.eclipse.paho.client.eclipse.feature/feature.xml b/org.eclipse.paho.client.eclipse.feature/feature.xml index 4a8ae7c81..e80ef9205 100644 --- a/org.eclipse.paho.client.eclipse.feature/feature.xml +++ b/org.eclipse.paho.client.eclipse.feature/feature.xml @@ -3,8 +3,7 @@ id="org.eclipse.paho.client.eclipse.feature" label="%featureName" provider-name="%providerName" - version="1.1.1"> - + version="1.2.0"> %description diff --git a/org.eclipse.paho.client.eclipse.feature/pom.xml b/org.eclipse.paho.client.eclipse.feature/pom.xml index dadd3704b..1b088ad4b 100644 --- a/org.eclipse.paho.client.eclipse.feature/pom.xml +++ b/org.eclipse.paho.client.eclipse.feature/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.client.eclipse.feature diff --git a/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF b/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF index 4f2b727f8..15d93459b 100644 --- a/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF +++ b/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %bundle.name Bundle-SymbolicName: org.eclipse.paho.client.eclipse.view;singleton:=true -Bundle-Version: 1.1.1 +Bundle-Version: 1.2.0 Bundle-Activator: org.eclipse.paho.client.eclipse.view.Activator Bundle-Vendor: %bundle.provider Bundle-Localization: plugin diff --git a/org.eclipse.paho.client.eclipse.view/pom.xml b/org.eclipse.paho.client.eclipse.view/pom.xml index 38d51e7d8..f91605c37 100644 --- a/org.eclipse.paho.client.eclipse.view/pom.xml +++ b/org.eclipse.paho.client.eclipse.view/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.client.eclipse.view diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java index 5c8a08c49..bf3d7ad74 100644 --- a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java +++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java @@ -1,10 +1,14 @@ -/* +/******************************************************************************* * Copyright (c) 2012 Eurotech Inc. All rights reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Chad Kienle diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java index 0da7f4fdd..c684c578b 100644 --- a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java +++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java @@ -1,10 +1,14 @@ -/* +/******************************************************************************* * Copyright (c) 2012 Eurotech Inc. All rights reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Chad Kienle diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java index c96e27be4..990a4571a 100644 --- a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java +++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java @@ -1,10 +1,14 @@ -/* +/******************************************************************************* * Copyright (c) 2012 Eurotech Inc. All rights reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Chad Kienle diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml b/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml index 8cc6f82e6..7d9c42cd6 100644 --- a/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml +++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml @@ -7,8 +7,8 @@ - - + + diff --git a/org.eclipse.paho.client.mqttv3.repository/category.xml b/org.eclipse.paho.client.mqttv3.repository/category.xml index 19130aac9..459571525 100644 --- a/org.eclipse.paho.client.mqttv3.repository/category.xml +++ b/org.eclipse.paho.client.mqttv3.repository/category.xml @@ -1,9 +1,9 @@ - + - + diff --git a/org.eclipse.paho.client.mqttv3.repository/pom.xml b/org.eclipse.paho.client.mqttv3.repository/pom.xml index 32262b47e..bb5dbb6e4 100644 --- a/org.eclipse.paho.client.mqttv3.repository/pom.xml +++ b/org.eclipse.paho.client.mqttv3.repository/pom.xml @@ -3,7 +3,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.client.mqttv3.repository diff --git a/org.eclipse.paho.client.mqttv3.test/.classpath b/org.eclipse.paho.client.mqttv3.test/.classpath index 6b830f36c..69f1458a4 100644 --- a/org.eclipse.paho.client.mqttv3.test/.classpath +++ b/org.eclipse.paho.client.mqttv3.test/.classpath @@ -1,29 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.paho.client.mqttv3.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.paho.client.mqttv3.test/.settings/org.eclipse.jdt.core.prefs index 107056a36..62492222a 100644 --- a/org.eclipse.paho.client.mqttv3.test/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.paho.client.mqttv3.test/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,12 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.compliance=1.7 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/org.eclipse.paho.client.mqttv3.test/pom.xml b/org.eclipse.paho.client.mqttv3.test/pom.xml index f012e2342..abc1ac64f 100644 --- a/org.eclipse.paho.client.mqttv3.test/pom.xml +++ b/org.eclipse.paho.client.mqttv3.test/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.client.mqttv3.test @@ -47,14 +47,15 @@ maven-surefire-plugin 2.15 - false - 1 ${test.server_uri} + ${test.server_ssl_port} + ${test.server_websocket_uri} ${test.exclude} + alphabetical @@ -64,7 +65,7 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.1.1 + 1.2.0 diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/BasicTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/BasicTest.java index 333bc8216..f5c851b5c 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/BasicTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/BasicTest.java @@ -95,7 +95,7 @@ public static void tearDownAfterClass() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test public void testConnect() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -141,7 +141,7 @@ public void testConnect() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test public void testHAConnect() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -191,7 +191,7 @@ public void testHAConnect() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test public void testPubSub() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -241,7 +241,7 @@ public void testPubSub() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test public void testMsgProperties() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -309,7 +309,7 @@ public void testMsgProperties() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test public void testConnOptDefaults() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/Issue370Test.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/Issue370Test.java new file mode 100644 index 000000000..3e529b439 --- /dev/null +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/Issue370Test.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2017 Red Hat Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Red Hat Inc - initial API and implementation + *******************************************************************************/ +package org.eclipse.paho.client.mqttv3.test; + +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.junit.Test; + +/** + * This is a reproducer for issue #370 + */ +public class Issue370Test { + @Test + public void noOpenClose() throws MqttException { + final MqttAsyncClient client = new MqttAsyncClient("tcp://localhost", "foo-bar"); + client.close(); + } +} diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java index fba9d996e..8f2f65d4b 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java @@ -257,7 +257,7 @@ void repeatedlyPub() { } byte[] payload = ("Message payload " + getClass().getName() + ".publish" + (i++)).getBytes(); MqttTopic mqttTopic = mqttClient.getTopic(FirstSubTopicString); - log.info("Publishing to..." + FirstSubTopicString); + log.fine("Publishing to..." + FirstSubTopicString); mqttTopic.publish(payload, 1, false); } @@ -267,6 +267,7 @@ void repeatedlyPub() { // Its likely the publish rate is too high i.e. inflight window is full } } + log.info("Sent at least " + i + " messages."); } public void run() { diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java index 23cf0660c..d70722068 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java @@ -167,7 +167,7 @@ public static void tearDownAfterClass() throws Exception { /** * @throws Exception */ - @Test(timeout=150000) + //@Test(timeout=150000) public void testRunModel() throws Exception { log.info("Test core operations and parameters by random selection"); log.info("See file: " + logFilename + " for details of selected test sequence"); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/PerSubscriptionMessageHandlerTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/PerSubscriptionMessageHandlerTest.java index 70721a6d3..fe02d8eeb 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/PerSubscriptionMessageHandlerTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/PerSubscriptionMessageHandlerTest.java @@ -136,7 +136,7 @@ public void messageArrived(String topic, MqttMessage message) throws Exception { * * @throws Exception */ - @Test(timeout=10000) + @Test public void testSyncSubs1() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -166,7 +166,7 @@ public void testSyncSubs1() throws Exception { } - @Test(timeout=10000) + @Test public void testAsyncSubs1() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -204,7 +204,7 @@ public void testAsyncSubs1() throws Exception { * Check handlers still exist after reconnection non-cleansession */ - @Test(timeout=10000) + @Test public void testSyncCleanSessionFalse() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -256,7 +256,7 @@ public void testSyncCleanSessionFalse() throws Exception { mqttClient.close(); } - @Test(timeout=10000) + @Test public void testAsyncCleanSessionFalse() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SSLSessionResumptionTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SSLSessionResumptionTest.java index 25a7736b8..b3f9d196b 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SSLSessionResumptionTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SSLSessionResumptionTest.java @@ -1,10 +1,14 @@ /** * Copyright (c) 2011, 2014 Eurotech and/or its affiliates * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Cristiano De Alti - Eurotech (Initial contribution) @@ -59,7 +63,7 @@ public static void setUpBeforeClass() throws Exception { try { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); - serverURI = "ssl://" + TestProperties.getServerURI().getHost(); + serverURI = "ssl://" + TestProperties.getServerURI().getHost() + ":" +TestProperties.getServerSSLPort(); serverHost = TestProperties.getServerURI().getHost(); serverPort = TestProperties.getServerSSLPort(); @@ -80,7 +84,7 @@ public static void setUpBeforeClass() throws Exception { */ @Test(timeout=30000) public void testSSLSessionInvalidated() throws Exception { - // System.setProperty("javax.net.debug", "all"); + //System.setProperty("javax.net.debug", "all"); SSLSocketFactory factory = getSocketFactory(); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java index 50f9ece59..2ec554a2e 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java @@ -95,7 +95,7 @@ public static void tearDownAfterClass() throws Exception { * * @throws Exception */ - @Test(timeout=10000) + @Test public void testConnect() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -142,7 +142,7 @@ public void testConnect() throws Exception { * * @throws Exception */ - @Test(timeout=10000) + @Test public void testRemoteConnect() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -214,7 +214,7 @@ public void testRemoteConnect() throws Exception { /** * Test client pubSub using very large messages */ - @Test(timeout=10000) + @Test public void testLargeMessage() { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -291,7 +291,7 @@ public void testLargeMessage() { /** * Multiple publishers and subscribers. */ - @Test(timeout=20000) + @Test public void testMultipleClients() { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -386,7 +386,7 @@ public void testMultipleClients() { * Test the behaviour of the cleanStart flag, used to clean up before * re-connecting. */ - @Test(timeout=10000) + @Test public void testCleanStart() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -532,7 +532,7 @@ public void testCleanStart() throws Exception { * * Since no other activity (messages from the client to the broker) is generated, the broker disconnects the client. */ - @Test(timeout=300000) + @Test public void testVeryLargeMessageWithShortKeepAlive() { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -601,7 +601,7 @@ public void testVeryLargeMessageWithShortKeepAlive() { * Test the behavior of the connection timeout when connecting to a non MQTT server. * i.e. ssh port 22 */ - @Test(timeout=60000) + @Test public void testConnectTimeout() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java index 354d42036..f14b04ef8 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java @@ -93,7 +93,7 @@ public static void tearDownAfterClass() throws Exception { * service * @throws Exception */ - @Test(timeout=10000) + @Test public void testConnect() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -129,7 +129,7 @@ public void testConnect() throws Exception { * Test connection using a remote host name for the local host. * @throws Exception */ - @Test(timeout=10000) + @Test public void testRemoteConnect() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -184,7 +184,7 @@ public void testRemoteConnect() throws Exception { /** * Test client pubSub using largish messages */ - @Test(timeout=10000) + @Test public void testLargeMessage() { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -245,7 +245,7 @@ public void testLargeMessage() { /** * Test that QOS values are preserved between MQTT publishers and subscribers. */ - @Test(timeout=10000) + @Test public void testQoSPreserved() { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -305,7 +305,7 @@ public void testQoSPreserved() { * Multiple publishers and subscribers. * @throws Exception */ - @Test(timeout=30000) + @Test public void testMultipleClients() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -388,7 +388,7 @@ public void testMultipleClients() throws Exception { * Test the behaviour of the cleanStart flag, used to clean up before re-connecting. * @throws Exception */ - @Test(timeout=10000) + @Test public void testCleanStart() throws Exception { final String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/WebSocketTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/WebSocketTest.java index f0c4e9f27..be3142ab7 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/WebSocketTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/WebSocketTest.java @@ -94,7 +94,7 @@ public static void tearDownAfterClass() throws Exception { /** * @throws Exception */ - @Test(timeout=10000) + @Test() public void testWebSocketConnect() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -182,7 +182,10 @@ public void testWebSocketPubSub() throws Exception { log.info("Disconnecting..."); client.disconnect(); - } + } catch (MqttException exception) { + log.log(Level.SEVERE, "caught exception:", exception); + Assert.fail("Unexpected exception: " + exception); + } finally { if (client != null) { if(client.isConnected()){ @@ -239,9 +242,10 @@ public void largePayloadTest() throws Exception{ log.info("Disconnecting..."); client.disconnect(); log.info("Disconnected..."); - } catch (Exception e){ - e.printStackTrace(); - } finally { + } catch (MqttException exception) { + log.log(Level.SEVERE, "caught exception:", exception); + Assert.fail("Unexpected exception: " + exception); + } finally { if (client != null) { if(client.isConnected()){ client.disconnectForcibly(); @@ -274,9 +278,10 @@ public void testBasicAuth() throws Exception { client = clientFactory.createMqttClient(serverURIWithUserInfo, clientId); client.connect(); client.disconnect(); - } catch (Exception e){ - e.printStackTrace(); - } finally { + }catch (MqttException exception) { + log.log(Level.SEVERE, "caught exception:", exception); + Assert.fail("Unexpected exception: " + exception); + } finally { if (client != null) { if(client.isConnected()){ client.disconnectForcibly(); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/automaticReconnect/OfflineBufferingTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/automaticReconnect/OfflineBufferingTest.java index 11a820f46..803bad0dd 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/automaticReconnect/OfflineBufferingTest.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/automaticReconnect/OfflineBufferingTest.java @@ -50,7 +50,7 @@ public static void setUpBeforeClass() throws Exception { topicPrefix = "OfflineBufferingTest-" + UUID.randomUUID().toString() + "-"; // Use 0 for the first time. - proxy = new ConnectionManipulationProxyServer(serverURI.getHost(), serverURI.getPort(), 0); + proxy = new ConnectionManipulationProxyServer(serverURI.getHost(), serverURI.getPort(), 2883); proxy.startProxy(); while (!proxy.isPortSet()) { Thread.sleep(0); @@ -68,7 +68,7 @@ public static void setUpBeforeClass() throws Exception { * disconnected state and is then delivered once the client has reconnected * automatically. */ - @Test(timeout=60000) + @Test public void testSingleMessageBufferAndDeliver() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -139,7 +139,7 @@ public void testSingleMessageBufferAndDeliver() throws Exception { * disconnected state and that they are all then delivered once the client * has connected automatically. */ - @Test(timeout=60000) + @Test public void testManyMessageBufferAndDeliver() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -231,7 +231,7 @@ public void testManyMessageBufferAndDeliver() throws Exception { * Tests that the buffer correctly handles messages being buffered when the * buffer is full and deleteOldestBufferedMessage is set to true. */ - @Test(timeout=60000) + @Test public void testDeleteOldestBufferedMessages() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -288,7 +288,7 @@ public void testDeleteOldestBufferedMessages() throws Exception { * Tests that A message cannot be buffered when the buffer is full and * deleteOldestBufferedMessage is set to false. */ - @Test(timeout=60000) + @Test public void testNoDeleteOldestBufferedMessages() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -347,7 +347,7 @@ public void testNoDeleteOldestBufferedMessages() throws Exception { * Tests that if enabled, buffered messages are persisted to the persistence * layer */ - @Test(timeout=60000) + @Test public void testPersistBufferedMessages() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); @@ -407,7 +407,7 @@ public void testPersistBufferedMessages() throws Exception { * Tests that persisted buffered messages are published correctly when the * client connects for the first time and are un persisted. */ - @Test(timeout=60000) + @Test public void testUnPersistBufferedMessagesOnNewClient() throws Exception { String methodName = Utility.getMethodName(); LoggingUtilities.banner(log, cclass, methodName); diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java index 60c9acbea..262871d42 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java +++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java @@ -17,6 +17,7 @@ import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttClientPersistence; import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; /** @@ -30,7 +31,7 @@ public class MqttClientPaho extends MqttClient { * @throws MqttException */ public MqttClientPaho(String serverURI, String clientId) throws MqttException { - super(serverURI, clientId); + super(serverURI, clientId, new MemoryPersistence()); } /** diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties b/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties index 833c07635..7688045b5 100644 --- a/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties +++ b/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties @@ -1,12 +1,12 @@ # This is the server URI which will be set in the constructor of an MQTT Client # The default is "tcp://:1883" with expressed in IPV4 dotted decimal notation -SERVER_URI=tcp://iot.eclipse.org:1883 +SERVER_URI=tcp://localhost:1883 CLIENT_KEY_STORE=clientkeystore.jks CLIENT_KEY_STORE_PASSWORD=password CLIENT_TRUST_STORE=clientkeystore.jks SERVER_SSL_PORT=18884 -SERVER_WEBSOCKET_URI=wss://iot.eclipse.org:443 -SERVER_SSL_PORT=8883 +SERVER_WEBSOCKET_URI=ws://localhost:8080 + # The list of server URIs which may be set in the MQTT ConnectOptions for an HA testcase. # There is no default value diff --git a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs index 008a2612d..9c4403fe9 100644 --- a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,13 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.4 +org.eclipse.jdt.core.compiler.compliance=1.7 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning -org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.3 +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF b/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF index ea76130b1..28138578c 100644 --- a/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF +++ b/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF @@ -2,12 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %bundle.name Bundle-SymbolicName: org.eclipse.paho.client.mqttv3 -Bundle-Version: 1.1.1 +Bundle-Version: 1.2.0 Bundle-Localization: bundle -Export-Package: org.eclipse.paho.client.mqttv3;version="1.1.1", - org.eclipse.paho.client.mqttv3.logging;version="1.1.1", - org.eclipse.paho.client.mqttv3.persist;version="1.1.1", - org.eclipse.paho.client.mqttv3.util;version="1.1.1" +Export-Package: org.eclipse.paho.client.mqttv3;version="1.2.0", + org.eclipse.paho.client.mqttv3.logging;version="1.2.0", + org.eclipse.paho.client.mqttv3.persist;version="1.2.0", + org.eclipse.paho.client.mqttv3.util;version="1.2.0" Bundle-Vendor: %bundle.provider Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.4 diff --git a/org.eclipse.paho.client.mqttv3/build.xml b/org.eclipse.paho.client.mqttv3/build.xml index 3c5dd5f04..f13f8b949 100644 --- a/org.eclipse.paho.client.mqttv3/build.xml +++ b/org.eclipse.paho.client.mqttv3/build.xml @@ -16,8 +16,8 @@ - - + + diff --git a/org.eclipse.paho.client.mqttv3/pom.xml b/org.eclipse.paho.client.mqttv3/pom.xml index 6cb734d6e..28fc4824e 100644 --- a/org.eclipse.paho.client.mqttv3/pom.xml +++ b/org.eclipse.paho.client.mqttv3/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.client.mqttv3 diff --git a/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java b/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java index 9c0074edf..542d9df95 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java @@ -22,6 +22,8 @@ import java.util.Enumeration; import java.util.Properties; import java.util.Vector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import org.eclipse.paho.client.mqttv3.BufferedMessage; import org.eclipse.paho.client.mqttv3.IMqttActionListener; @@ -80,22 +82,26 @@ public class ClientComms { private boolean closePending = false; private boolean resting = false; private DisconnectedMessageBuffer disconnectedMessageBuffer; - + + private ExecutorService executorService; + /** * Creates a new ClientComms object, using the specified module to handle * the network calls. * @param client The {@link IMqttAsyncClient} * @param persistence the {@link MqttClientPersistence} layer. * @param pingSender the {@link MqttPingSender} + * @param executorService the {@link ExecutorService} * @throws MqttException if an exception occurs whilst communicating with the server */ - public ClientComms(IMqttAsyncClient client, MqttClientPersistence persistence, MqttPingSender pingSender) throws MqttException { + public ClientComms(IMqttAsyncClient client, MqttClientPersistence persistence, MqttPingSender pingSender, ExecutorService executorService) throws MqttException { this.conState = DISCONNECTED; this.client = client; this.persistence = persistence; this.pingSender = pingSender; this.pingSender.init(this); - + this.executorService = executorService; + this.tokenStore = new CommsTokenStore(getClient().getClientId()); this.callback = new CommsCallback(this); this.clientState = new ClientState(persistence, tokenStore, this.callback, this, pingSender); @@ -108,6 +114,22 @@ CommsReceiver getReceiver() { return receiver; } + private void shutdownExecutorService() { + String methodName = "shutdownExecutorService"; + executorService.shutdown(); + try { + if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { + log.fine(CLASS_NAME, methodName, "executorService did not terminate"); + } + } + } catch (InterruptedException ie) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + /** * Sends a message to the server. Does not check if connected this validation must be done * by invoking routines. @@ -164,8 +186,8 @@ public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttExce } else { this.internalSend(message, token); } - } else if(disconnectedMessageBuffer != null && isResting()){ - //@TRACE 508=Client Resting, Offline Buffer available. Adding message to buffer. message={0} + } else if(disconnectedMessageBuffer != null) { + //@TRACE 508=Offline Buffer available. Adding message to buffer. message={0} log.fine(CLASS_NAME, methodName, "508", new Object[] {message.getKey()}); if(disconnectedMessageBuffer.isPersistBuffer()){ this.clientState.persistBufferedMessage(message); @@ -185,12 +207,12 @@ public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttExce * store which normally survives a disconnect. * @throws MqttException if not disconnected */ - public void close() throws MqttException { + public void close(boolean force) throws MqttException { final String methodName = "close"; synchronized (conLock) { if (!isClosed()) { - // Must be disconnected before close can take place - if (!isDisconnected()) { + // Must be disconnected before close can take place or if we are being forced + if (!isDisconnected() || force) { //@TRACE 224=failed: not disconnected log.fine(CLASS_NAME, methodName, "224"); @@ -205,7 +227,7 @@ public void close() throws MqttException { } conState = CLOSED; - + shutdownExecutorService(); // ShutdownConnection has already cleaned most things clientState.close(); clientState = null; @@ -254,7 +276,7 @@ public void connect(MqttConnectOptions options, MqttToken token) throws MqttExce this.clientState.setMaxInflight(conOptions.getMaxInflight()); tokenStore.open(); - ConnectBG conbg = new ConnectBG(this, token, connect); + ConnectBG conbg = new ConnectBG(this, token, connect, executorService); conbg.start(); } else { @@ -405,7 +427,7 @@ public void shutdownConnection(MqttToken token, MqttException reason) { synchronized(conLock) { if (closePending) { try { - close(); + close(true); } catch (Exception e) { // ignore any errors as closing } } @@ -479,7 +501,7 @@ public void disconnect(MqttDisconnect disconnect, long quiesceTimeout, MqttToken //@TRACE 218=state=DISCONNECTING log.fine(CLASS_NAME,methodName,"218"); conState = DISCONNECTING; - DisconnectBG discbg = new DisconnectBG(disconnect,quiesceTimeout,token); + DisconnectBG discbg = new DisconnectBG(disconnect,quiesceTimeout,token, executorService); discbg.start(); } } @@ -497,7 +519,9 @@ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) thro */ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket) throws MqttException { // Allow current inbound and outbound work to complete - clientState.quiesce(quiesceTimeout); + if (clientState != null) { + clientState.quiesce(quiesceTimeout); + } MqttToken token = new MqttToken(client.getClientId()); try { // Send disconnect packet @@ -636,22 +660,23 @@ public Properties getDebug() { // the socket could take time to create. private class ConnectBG implements Runnable { ClientComms clientComms = null; - Thread cBg = null; MqttToken conToken; MqttConnect conPacket; + private String threadName; - ConnectBG(ClientComms cc, MqttToken cToken, MqttConnect cPacket) { + ConnectBG(ClientComms cc, MqttToken cToken, MqttConnect cPacket, ExecutorService executorService) { clientComms = cc; conToken = cToken; conPacket = cPacket; - cBg = new Thread(this, "MQTT Con: "+getClient().getClientId()); + threadName = "MQTT Con: "+getClient().getClientId(); } void start() { - cBg.start(); + executorService.execute(this); } public void run() { + Thread.currentThread().setName(threadName); final String methodName = "connectBG:run"; MqttException mqttEx = null; //@TRACE 220=> @@ -675,10 +700,10 @@ public void run() { NetworkModule networkModule = networkModules[networkModuleIndex]; networkModule.start(); receiver = new CommsReceiver(clientComms, clientState, tokenStore, networkModule.getInputStream()); - receiver.start("MQTT Rec: "+getClient().getClientId()); + receiver.start("MQTT Rec: "+getClient().getClientId(), executorService); sender = new CommsSender(clientComms, clientState, tokenStore, networkModule.getOutputStream()); - sender.start("MQTT Snd: "+getClient().getClientId()); - callback.start("MQTT Call: "+getClient().getClientId()); + sender.start("MQTT Snd: "+getClient().getClientId(), executorService); + callback.start("MQTT Call: "+getClient().getClientId(), executorService); internalSend(conPacket, conToken); } catch (MqttException ex) { //@TRACE 212=connect failed: unexpected exception @@ -699,23 +724,24 @@ public void run() { // Kick off the disconnect processing in the background so that it does not block. For instance // the quiesce private class DisconnectBG implements Runnable { - Thread dBg = null; MqttDisconnect disconnect; long quiesceTimeout; MqttToken token; + private String threadName; - DisconnectBG(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token ) { + DisconnectBG(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token, ExecutorService executorService) { this.disconnect = disconnect; this.quiesceTimeout = quiesceTimeout; this.token = token; } void start() { - dBg = new Thread(this, "MQTT Disc: "+getClient().getClientId()); - dBg.start(); + threadName = "MQTT Disc: "+getClient().getClientId(); + executorService.execute(this); } - + public void run() { + Thread.currentThread().setName(threadName); final String methodName = "disconnectBG:run"; //@TRACE 221=> log.fine(CLASS_NAME, methodName, "221"); @@ -806,37 +832,50 @@ public void deleteBufferedMessage(int bufferIndex){ /** - * When the client automatically reconnects, we want to send all messages from the + * When the client connects, we want to send all messages from the * buffer first before allowing the user to send any messages */ - public void notifyReconnect() { - final String methodName = "notifyReconnect"; + public void notifyConnect() { + final String methodName = "notifyConnect"; if(disconnectedMessageBuffer != null){ - //@TRACE 509=Client Reconnected, Offline Buffer Available. Sending Buffered Messages. + //@TRACE 509=Client Connected, Offline Buffer Available. Sending Buffered Messages. log.fine(CLASS_NAME, methodName, "509"); - disconnectedMessageBuffer.setPublishCallback(new IDisconnectedBufferCallback() { - - public void publishBufferedMessage(BufferedMessage bufferedMessage) throws MqttException { - if (isConnected()) { - while(clientState.getActualInFlight() >= (clientState.getMaxInFlight()-1)){ - // We need to Yield to the other threads to allow the in flight messages to clear - Thread.yield(); - - } - //@TRACE 510=Publising Buffered message message={0} - log.fine(CLASS_NAME, methodName, "510", new Object[] {bufferedMessage.getMessage().getKey()}); - internalSend(bufferedMessage.getMessage(), bufferedMessage.getToken()); - // Delete from persistence if in there - clientState.unPersistBufferedMessage(bufferedMessage.getMessage()); - } else { - //@TRACE 208=failed: not connected - log.fine(CLASS_NAME, methodName, "208"); - throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED); - } + + disconnectedMessageBuffer.setPublishCallback(new ReconnectDisconnectedBufferCallback(methodName)); + executorService.execute(disconnectedMessageBuffer); + } + } + + class ReconnectDisconnectedBufferCallback implements IDisconnectedBufferCallback{ + + final String methodName; + + ReconnectDisconnectedBufferCallback(String methodName) { + this.methodName = methodName; + } + + public void publishBufferedMessage(BufferedMessage bufferedMessage) throws MqttException { + if (isConnected()) { + while(clientState.getActualInFlight() >= (clientState.getMaxInFlight()-1)){ + // We need to Yield to the other threads to allow the in flight messages to clear + Thread.yield(); + } - }); - new Thread(disconnectedMessageBuffer).start(); + //@TRACE 510=Publising Buffered message message={0} + log.fine(CLASS_NAME, methodName, "510", new Object[] {bufferedMessage.getMessage().getKey()}); + internalSend(bufferedMessage.getMessage(), bufferedMessage.getToken()); + // Delete from persistence if in there + clientState.unPersistBufferedMessage(bufferedMessage.getMessage()); + } else { + //@TRACE 208=failed: not connected + log.fine(CLASS_NAME, methodName, "208"); + throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED); + } } } + public int getActualInFlight() { + return this.clientState.getActualInFlight(); + } + } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/DisconnectedBufferOptions.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/DisconnectedBufferOptions.java index 13db2e944..21ea06e3e 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/DisconnectedBufferOptions.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/DisconnectedBufferOptions.java @@ -51,6 +51,7 @@ public class DisconnectedBufferOptions { * More information about these values can be found in the setter methods. */ public DisconnectedBufferOptions() { + // Do Nothing. } public int getBufferSize() { diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java index 4589f95fa..d19ccde17 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java @@ -1,10 +1,14 @@ -/* +/************************************************************************** * Copyright (c) 2009, 2012 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Dave Locke - initial API and implementation and/or initial documentation diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java index 8f3ed6a30..ae5150b6b 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -21,25 +21,29 @@ package org.eclipse.paho.client.mqttv3; +import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; import java.util.Hashtable; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; + import org.eclipse.paho.client.mqttv3.internal.ClientComms; import org.eclipse.paho.client.mqttv3.internal.ConnectActionListener; import org.eclipse.paho.client.mqttv3.internal.DisconnectedMessageBuffer; import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper; -import org.eclipse.paho.client.mqttv3.internal.LocalNetworkModule; import org.eclipse.paho.client.mqttv3.internal.NetworkModule; import org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule; import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule; import org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory; -import org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule; import org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketNetworkModule; +import org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule; import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect; import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish; import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe; @@ -54,39 +58,49 @@ * Lightweight client for talking to an MQTT server using non-blocking methods * that allow an operation to run in the background. * - *

This class implements the non-blocking {@link IMqttAsyncClient} client interface - * allowing applications to initiate MQTT actions and then carry on working while the - * MQTT action completes on a background thread. - * This implementation is compatible with all Java SE runtimes from 1.4.2 and up. + *

+ * This class implements the non-blocking {@link IMqttAsyncClient} client + * interface allowing applications to initiate MQTT actions and then carry on + * working while the MQTT action completes on a background thread. This + * implementation is compatible with all Java SE runtimes from 1.7 and up. + *

+ *

+ * An application can connect to an MQTT server using: *

- *

An application can connect to an MQTT server using:

*
    *
  • A plain TCP socket *
  • A secure SSL/TLS socket *
- * - *

To enable messages to be delivered even across network and client restarts - * messages need to be safely stored until the message has been delivered at the requested - * quality of service. A pluggable persistence mechanism is provided to store the messages. + * + *

+ * To enable messages to be delivered even across network and client restarts + * messages need to be safely stored until the message has been delivered at the + * requested quality of service. A pluggable persistence mechanism is provided + * to store the messages. *

- *

By default {@link MqttDefaultFilePersistence} is used to store messages to a file. - * If persistence is set to null then messages are stored in memory and hence can be lost - * if the client, Java runtime or device shuts down. + *

+ * By default {@link MqttDefaultFilePersistence} is used to store messages to a + * file. If persistence is set to null then messages are stored in memory and + * hence can be lost if the client, Java runtime or device shuts down. *

- *

If connecting with {@link MqttConnectOptions#setCleanSession(boolean)} set to true it - * is safe to use memory persistence as all state is cleared when a client disconnects. If - * connecting with cleanSession set to false in order to provide reliable message delivery - * then a persistent message store such as the default one should be used. + *

+ * If connecting with {@link MqttConnectOptions#setCleanSession(boolean)} set to + * true it is safe to use memory persistence as all state is cleared when a + * client disconnects. If connecting with cleanSession set to false in order to + * provide reliable message delivery then a persistent message store such as the + * default one should be used. *

- *

The message store interface is pluggable. Different stores can be used by implementing - * the {@link MqttClientPersistence} interface and passing it to the clients constructor. + *

+ * The message store interface is pluggable. Different stores can be used by + * implementing the {@link MqttClientPersistence} interface and passing it to + * the clients constructor. *

* * @see IMqttAsyncClient */ -public class MqttAsyncClient implements IMqttAsyncClient { +public class MqttAsyncClient implements IMqttAsyncClient { private static final String CLASS_NAME = MqttAsyncClient.class.getName(); - private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME); private static final String CLIENT_ID_PREFIX = "paho"; private static final long QUIESCE_TIMEOUT = 30000; // ms @@ -102,266 +116,337 @@ public class MqttAsyncClient implements IMqttAsyncClient { private MqttConnectOptions connOpts; private Object userContext; private Timer reconnectTimer; // Automatic reconnect timer - private static int reconnectDelay = 1000; // Reconnect delay, starts at 1 second + private static int reconnectDelay = 1000; // Reconnect delay, starts at 1 + // second private boolean reconnecting = false; private static Object clientLock = new Object(); // Simple lock - - + private ScheduledExecutorService executorService; /** - * Create an MqttAsyncClient that is used to communicate with an MQTT server. + * Create an MqttAsyncClient that is used to communicate with an MQTT + * server. *

- * The address of a server can be specified on the constructor. Alternatively - * a list containing one or more servers can be specified using the - * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method - * on MqttConnectOptions. + * The address of a server can be specified on the constructor. + * Alternatively a list containing one or more servers can be specified + * using the {@link MqttConnectOptions#setServerURIs(String[]) + * setServerURIs} method on MqttConnectOptions. * - *

The serverURI parameter is typically used with the - * the clientId parameter to form a key. The key - * is used to store and reference messages while they are being delivered. - * Hence the serverURI specified on the constructor must still be specified even if a list - * of servers is specified on an MqttConnectOptions object. - * The serverURI on the constructor must remain the same across - * restarts of the client for delivery of messages to be maintained from a given - * client to a given server or set of servers. + *

+ * The serverURI parameter is typically used with the the + * clientId parameter to form a key. The key is used to store + * and reference messages while they are being delivered. Hence the + * serverURI specified on the constructor must still be specified even if a + * list of servers is specified on an MqttConnectOptions object. The + * serverURI on the constructor must remain the same across restarts of the + * client for delivery of messages to be maintained from a given client to a + * given server or set of servers. * - *

The address of the server to connect to is specified as a URI. Two types of - * connection are supported tcp:// for a TCP connection and - * ssl:// for a TCP connection secured by SSL/TLS. - * For example:

+ *

+ * The address of the server to connect to is specified as a URI. Two types + * of connection are supported tcp:// for a TCP connection and + * ssl:// for a TCP connection secured by SSL/TLS. For example: + *

*
    - *
  • tcp://localhost:1883
  • - *
  • ssl://localhost:8883
  • + *
  • tcp://localhost:1883
  • + *
  • ssl://localhost:8883
  • *
*

- * If the port is not specified, it will - * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs. + * If the port is not specified, it will default to 1883 for + * tcp://" URIs, and 8883 for ssl:// URIs. *

* *

- * A client identifier clientId must be specified and be less that 65535 characters. - * It must be unique across all clients connecting to the same - * server. The clientId is used by the server to store data related to the client, - * hence it is important that the clientId remain the same when connecting to a server - * if durable subscriptions or reliable messaging are required. - *

A convenience method is provided to generate a random client id that - * should satisfy this criteria - {@link #generateClientId()}. As the client identifier - * is used by the server to identify a client when it reconnects, the client must use the - * same identifier between connections if durable subscriptions or reliable - * delivery of messages is required. + * A client identifier clientId must be specified and be less + * that 65535 characters. It must be unique across all clients connecting to + * the same server. The clientId is used by the server to store data related + * to the client, hence it is important that the clientId remain the same + * when connecting to a server if durable subscriptions or reliable + * messaging are required. + *

+ * A convenience method is provided to generate a random client id that + * should satisfy this criteria - {@link #generateClientId()}. As the client + * identifier is used by the server to identify a client when it reconnects, + * the client must use the same identifier between connections if durable + * subscriptions or reliable delivery of messages is required. *

*

* In Java SE, SSL can be configured in one of several ways, which the * client will use in the following order: *

*
    - *
  • Supplying an SSLSocketFactory - applications can - * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply - * a factory with the appropriate SSL settings.
  • - *
  • SSL Properties - applications can supply SSL settings as a - * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • - *
  • Use JVM settings - There are a number of standard - * Java system properties that can be used to configure key and trust stores.
  • + *
  • Supplying an SSLSocketFactory - + * applications can use + * {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply a + * factory with the appropriate SSL settings.
  • + *
  • SSL Properties - applications can supply SSL + * settings as a simple Java Properties using + * {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • + *
  • Use JVM settings - There are a number of standard + * Java system properties that can be used to configure key and trust + * stores.
  • *
* - *

In Java ME, the platform settings are used for SSL connections.

+ *

+ * In Java ME, the platform settings are used for SSL connections. + *

* - *

An instance of the default persistence mechanism {@link MqttDefaultFilePersistence} - * is used by the client. To specify a different persistence mechanism or to turn - * off persistence, use the {@link #MqttAsyncClient(String, String, MqttClientPersistence)} + *

+ * An instance of the default persistence mechanism + * {@link MqttDefaultFilePersistence} is used by the client. To specify a + * different persistence mechanism or to turn off persistence, use the + * {@link #MqttAsyncClient(String, String, MqttClientPersistence)} * constructor. * - * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using - * {@link MqttConnectOptions#setServerURIs(String[])} - * @param clientId a client identifier that is unique on the server being connected to - * @throws IllegalArgumentException if the URI does not start with - * "tcp://", "ssl://" or "local://". - * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length - * @throws MqttException if any other problem was encountered + * @param serverURI + * the address of the server to connect to, specified as a URI. + * Can be overridden using + * {@link MqttConnectOptions#setServerURIs(String[])} + * @param clientId + * a client identifier that is unique on the server being + * connected to + * @throws IllegalArgumentException + * if the URI does not start with "tcp://", "ssl://" or + * "local://". + * @throws IllegalArgumentException + * if the clientId is null or is greater than 65535 characters + * in length + * @throws MqttException + * if any other problem was encountered */ public MqttAsyncClient(String serverURI, String clientId) throws MqttException { - this(serverURI,clientId, new MqttDefaultFilePersistence()); + this(serverURI, clientId, new MqttDefaultFilePersistence()); } - + /** - * Create an MqttAsyncClient that is used to communicate with an MQTT server. + * Create an MqttAsyncClient that is used to communicate with an MQTT + * server. *

- * The address of a server can be specified on the constructor. Alternatively - * a list containing one or more servers can be specified using the - * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method - * on MqttConnectOptions. + * The address of a server can be specified on the constructor. + * Alternatively a list containing one or more servers can be specified + * using the {@link MqttConnectOptions#setServerURIs(String[]) + * setServerURIs} method on MqttConnectOptions. * - *

The serverURI parameter is typically used with the - * the clientId parameter to form a key. The key - * is used to store and reference messages while they are being delivered. - * Hence the serverURI specified on the constructor must still be specified even if a list - * of servers is specified on an MqttConnectOptions object. - * The serverURI on the constructor must remain the same across - * restarts of the client for delivery of messages to be maintained from a given - * client to a given server or set of servers. + *

+ * The serverURI parameter is typically used with the the + * clientId parameter to form a key. The key is used to store + * and reference messages while they are being delivered. Hence the + * serverURI specified on the constructor must still be specified even if a + * list of servers is specified on an MqttConnectOptions object. The + * serverURI on the constructor must remain the same across restarts of the + * client for delivery of messages to be maintained from a given client to a + * given server or set of servers. * - *

The address of the server to connect to is specified as a URI. Two types of - * connection are supported tcp:// for a TCP connection and - * ssl:// for a TCP connection secured by SSL/TLS. - * For example:

+ *

+ * The address of the server to connect to is specified as a URI. Two types + * of connection are supported tcp:// for a TCP connection and + * ssl:// for a TCP connection secured by SSL/TLS. For example: + *

*
    - *
  • tcp://localhost:1883
  • - *
  • ssl://localhost:8883
  • + *
  • tcp://localhost:1883
  • + *
  • ssl://localhost:8883
  • *
*

- * If the port is not specified, it will - * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs. + * If the port is not specified, it will default to 1883 for + * tcp://" URIs, and 8883 for ssl:// URIs. *

* *

- * A client identifier clientId must be specified and be less that 65535 characters. - * It must be unique across all clients connecting to the same - * server. The clientId is used by the server to store data related to the client, - * hence it is important that the clientId remain the same when connecting to a server - * if durable subscriptions or reliable messaging are required. - *

A convenience method is provided to generate a random client id that - * should satisfy this criteria - {@link #generateClientId()}. As the client identifier - * is used by the server to identify a client when it reconnects, the client must use the - * same identifier between connections if durable subscriptions or reliable - * delivery of messages is required. + * A client identifier clientId must be specified and be less + * that 65535 characters. It must be unique across all clients connecting to + * the same server. The clientId is used by the server to store data related + * to the client, hence it is important that the clientId remain the same + * when connecting to a server if durable subscriptions or reliable + * messaging are required. + *

+ * A convenience method is provided to generate a random client id that + * should satisfy this criteria - {@link #generateClientId()}. As the client + * identifier is used by the server to identify a client when it reconnects, + * the client must use the same identifier between connections if durable + * subscriptions or reliable delivery of messages is required. *

*

* In Java SE, SSL can be configured in one of several ways, which the * client will use in the following order: *

*
    - *
  • Supplying an SSLSocketFactory - applications can - * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply - * a factory with the appropriate SSL settings.
  • - *
  • SSL Properties - applications can supply SSL settings as a - * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • - *
  • Use JVM settings - There are a number of standard - * Java system properties that can be used to configure key and trust stores.
  • + *
  • Supplying an SSLSocketFactory - + * applications can use + * {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply a + * factory with the appropriate SSL settings.
  • + *
  • SSL Properties - applications can supply SSL + * settings as a simple Java Properties using + * {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • + *
  • Use JVM settings - There are a number of standard + * Java system properties that can be used to configure key and trust + * stores.
  • *
* - *

In Java ME, the platform settings are used for SSL connections.

*

- * A persistence mechanism is used to enable reliable messaging. - * For messages sent at qualities of service (QoS) 1 or 2 to be reliably delivered, - * messages must be stored (on both the client and server) until the delivery of the message - * is complete. If messages are not safely stored when being delivered then - * a failure in the client or server can result in lost messages. A pluggable - * persistence mechanism is supported via the {@link MqttClientPersistence} - * interface. An implementer of this interface that safely stores messages - * must be specified in order for delivery of messages to be reliable. In - * addition {@link MqttConnectOptions#setCleanSession(boolean)} must be set - * to false. In the event that only QoS 0 messages are sent or received or + * In Java ME, the platform settings are used for SSL connections. + *

+ *

+ * A persistence mechanism is used to enable reliable messaging. For + * messages sent at qualities of service (QoS) 1 or 2 to be reliably + * delivered, messages must be stored (on both the client and server) until + * the delivery of the message is complete. If messages are not safely + * stored when being delivered then a failure in the client or server can + * result in lost messages. A pluggable persistence mechanism is supported + * via the {@link MqttClientPersistence} interface. An implementer of this + * interface that safely stores messages must be specified in order for + * delivery of messages to be reliable. In addition + * {@link MqttConnectOptions#setCleanSession(boolean)} must be set to false. + * In the event that only QoS 0 messages are sent or received or * cleanSession is set to true then a safe store is not needed. *

- *

An implementation of file-based persistence is provided in - * class {@link MqttDefaultFilePersistence} which will work in all Java SE based - * systems. If no persistence is needed, the persistence parameter - * can be explicitly set to null.

+ *

+ * An implementation of file-based persistence is provided in class + * {@link MqttDefaultFilePersistence} which will work in all Java SE based + * systems. If no persistence is needed, the persistence parameter can be + * explicitly set to null. + *

* - * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using - * {@link MqttConnectOptions#setServerURIs(String[])} - * @param clientId a client identifier that is unique on the server being connected to - * @param persistence the persistence class to use to store in-flight message. If null then the - * default persistence mechanism is used - * @throws MqttException if any other problem was encountered + * @param serverURI + * the address of the server to connect to, specified as a URI. + * Can be overridden using + * {@link MqttConnectOptions#setServerURIs(String[])} + * @param clientId + * a client identifier that is unique on the server being + * connected to + * @param persistence + * the persistence class to use to store in-flight message. If + * null then the default persistence mechanism is used + * @throws MqttException + * if any other problem was encountered */ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException { - this(serverURI,clientId, persistence, new TimerPingSender()); + this(serverURI, clientId, persistence, new TimerPingSender()); + } + + public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence, + MqttPingSender pingSender) throws MqttException { + this(serverURI, clientId, persistence, pingSender, null); } /** - * Create an MqttAsyncClient that is used to communicate with an MQTT server. + * Create an MqttAsyncClient that is used to communicate with an MQTT + * server. *

- * The address of a server can be specified on the constructor. Alternatively - * a list containing one or more servers can be specified using the - * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method - * on MqttConnectOptions. + * The address of a server can be specified on the constructor. + * Alternatively a list containing one or more servers can be specified + * using the {@link MqttConnectOptions#setServerURIs(String[]) + * setServerURIs} method on MqttConnectOptions. * - *

The serverURI parameter is typically used with the - * the clientId parameter to form a key. The key - * is used to store and reference messages while they are being delivered. - * Hence the serverURI specified on the constructor must still be specified even if a list - * of servers is specified on an MqttConnectOptions object. - * The serverURI on the constructor must remain the same across - * restarts of the client for delivery of messages to be maintained from a given - * client to a given server or set of servers. + *

+ * The serverURI parameter is typically used with the the + * clientId parameter to form a key. The key is used to store + * and reference messages while they are being delivered. Hence the + * serverURI specified on the constructor must still be specified even if a + * list of servers is specified on an MqttConnectOptions object. The + * serverURI on the constructor must remain the same across restarts of the + * client for delivery of messages to be maintained from a given client to a + * given server or set of servers. * - *

The address of the server to connect to is specified as a URI. Two types of - * connection are supported tcp:// for a TCP connection and - * ssl:// for a TCP connection secured by SSL/TLS. - * For example:

+ *

+ * The address of the server to connect to is specified as a URI. Two types + * of connection are supported tcp:// for a TCP connection and + * ssl:// for a TCP connection secured by SSL/TLS. For example: + *

*
    - *
  • tcp://localhost:1883
  • - *
  • ssl://localhost:8883
  • + *
  • tcp://localhost:1883
  • + *
  • ssl://localhost:8883
  • *
*

- * If the port is not specified, it will - * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs. + * If the port is not specified, it will default to 1883 for + * tcp://" URIs, and 8883 for ssl:// URIs. *

* *

- * A client identifier clientId must be specified and be less that 65535 characters. - * It must be unique across all clients connecting to the same - * server. The clientId is used by the server to store data related to the client, - * hence it is important that the clientId remain the same when connecting to a server - * if durable subscriptions or reliable messaging are required. - *

A convenience method is provided to generate a random client id that - * should satisfy this criteria - {@link #generateClientId()}. As the client identifier - * is used by the server to identify a client when it reconnects, the client must use the - * same identifier between connections if durable subscriptions or reliable - * delivery of messages is required. + * A client identifier clientId must be specified and be less + * that 65535 characters. It must be unique across all clients connecting to + * the same server. The clientId is used by the server to store data related + * to the client, hence it is important that the clientId remain the same + * when connecting to a server if durable subscriptions or reliable + * messaging are required. + *

+ * A convenience method is provided to generate a random client id that + * should satisfy this criteria - {@link #generateClientId()}. As the client + * identifier is used by the server to identify a client when it reconnects, + * the client must use the same identifier between connections if durable + * subscriptions or reliable delivery of messages is required. *

*

* In Java SE, SSL can be configured in one of several ways, which the * client will use in the following order: *

*
    - *
  • Supplying an SSLSocketFactory - applications can - * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply - * a factory with the appropriate SSL settings.
  • - *
  • SSL Properties - applications can supply SSL settings as a - * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • - *
  • Use JVM settings - There are a number of standard - * Java system properties that can be used to configure key and trust stores.
  • + *
  • Supplying an SSLSocketFactory - + * applications can use + * {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply a + * factory with the appropriate SSL settings.
  • + *
  • SSL Properties - applications can supply SSL + * settings as a simple Java Properties using + * {@link MqttConnectOptions#setSSLProperties(Properties)}.
  • + *
  • Use JVM settings - There are a number of standard + * Java system properties that can be used to configure key and trust + * stores.
  • *
* - *

In Java ME, the platform settings are used for SSL connections.

*

- * A persistence mechanism is used to enable reliable messaging. - * For messages sent at qualities of service (QoS) 1 or 2 to be reliably delivered, - * messages must be stored (on both the client and server) until the delivery of the message - * is complete. If messages are not safely stored when being delivered then - * a failure in the client or server can result in lost messages. A pluggable - * persistence mechanism is supported via the {@link MqttClientPersistence} - * interface. An implementer of this interface that safely stores messages - * must be specified in order for delivery of messages to be reliable. In - * addition {@link MqttConnectOptions#setCleanSession(boolean)} must be set - * to false. In the event that only QoS 0 messages are sent or received or + * In Java ME, the platform settings are used for SSL connections. + *

+ *

+ * A persistence mechanism is used to enable reliable messaging. For + * messages sent at qualities of service (QoS) 1 or 2 to be reliably + * delivered, messages must be stored (on both the client and server) until + * the delivery of the message is complete. If messages are not safely + * stored when being delivered then a failure in the client or server can + * result in lost messages. A pluggable persistence mechanism is supported + * via the {@link MqttClientPersistence} interface. An implementer of this + * interface that safely stores messages must be specified in order for + * delivery of messages to be reliable. In addition + * {@link MqttConnectOptions#setCleanSession(boolean)} must be set to false. + * In the event that only QoS 0 messages are sent or received or * cleanSession is set to true then a safe store is not needed. *

- *

An implementation of file-based persistence is provided in - * class {@link MqttDefaultFilePersistence} which will work in all Java SE based - * systems. If no persistence is needed, the persistence parameter - * can be explicitly set to null.

+ *

+ * An implementation of file-based persistence is provided in class + * {@link MqttDefaultFilePersistence} which will work in all Java SE based + * systems. If no persistence is needed, the persistence parameter can be + * explicitly set to null. + *

* - * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using - * {@link MqttConnectOptions#setServerURIs(String[])} - * @param clientId a client identifier that is unique on the server being connected to - * @param persistence the persistence class to use to store in-flight message. If null then the - * default persistence mechanism is used - * @param pingSender Custom {@link MqttPingSender} implementation. - * @throws IllegalArgumentException if the URI does not start with - * "tcp://", "ssl://" or "local://" - * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length - * @throws MqttException if any other problem was encountered + * @param serverURI + * the address of the server to connect to, specified as a URI. + * Can be overridden using + * {@link MqttConnectOptions#setServerURIs(String[])} + * @param clientId + * a client identifier that is unique on the server being + * connected to + * @param persistence + * the persistence class to use to store in-flight message. If + * null then the default persistence mechanism is used + * @param pingSender + * Custom {@link MqttPingSender} implementation. + * @param executorService + * used for managing threads. If null then a newFixedThreadPool + * is used. + * @throws IllegalArgumentException + * if the URI does not start with "tcp://", "ssl://" or + * "local://" + * @throws IllegalArgumentException + * if the clientId is null or is greater than 65535 characters + * in length + * @throws MqttException + * if any other problem was encountered */ - public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence, MqttPingSender pingSender) throws MqttException { + public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence, + MqttPingSender pingSender, ScheduledExecutorService executorService) throws MqttException { final String methodName = "MqttAsyncClient"; log.setResourceName(clientId); - if (clientId == null) { //Support empty client Id, 3.1.1 standard + if (clientId == null) { // Support empty client Id, 3.1.1 standard throw new IllegalArgumentException("Null clientId"); } // Count characters, surrogate pairs count as one character. @@ -371,7 +456,7 @@ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence i++; clientIdLength++; } - if ( clientIdLength > 65535) { + if (clientIdLength > 65535) { throw new IllegalArgumentException("ClientId longer than 65535 characters"); } @@ -385,48 +470,57 @@ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence this.persistence = new MemoryPersistence(); } + this.executorService = executorService; + if (this.executorService == null) { + this.executorService = Executors.newScheduledThreadPool(10); + } + // @TRACE 101= ClientID={0} ServerURI={1} PersistenceType={2} - log.fine(CLASS_NAME,methodName,"101",new Object[]{clientId,serverURI,persistence}); + log.fine(CLASS_NAME, methodName, "101", new Object[] { clientId, serverURI, persistence }); this.persistence.open(clientId, serverURI); - this.comms = new ClientComms(this, this.persistence, pingSender); + this.comms = new ClientComms(this, this.persistence, pingSender, this.executorService); this.persistence.close(); this.topics = new Hashtable(); } - - /** - * @param ch the character to check. + * @param ch + * the character to check. * @return returns 'true' if the character is a high-surrogate code unit */ protected static boolean Character_isHighSurrogate(char ch) { - return(ch >= MIN_HIGH_SURROGATE) && (ch <= MAX_HIGH_SURROGATE); + return (ch >= MIN_HIGH_SURROGATE) && (ch <= MAX_HIGH_SURROGATE); } /** - * Factory method to create an array of network modules, one for - * each of the supplied URIs + * Factory method to create an array of network modules, one for each of the + * supplied URIs * - * @param address the URI for the server. - * @param options the {@link MqttConnectOptions} for the connection. + * @param address + * the URI for the server. + * @param options + * the {@link MqttConnectOptions} for the connection. * @return a network module appropriate to the specified address. - * @throws MqttException if an exception occurs creating the network Modules - * @throws MqttSecurityException if an issue occurs creating an SSL / TLS Socket + * @throws MqttException + * if an exception occurs creating the network Modules + * @throws MqttSecurityException + * if an issue occurs creating an SSL / TLS Socket */ - protected NetworkModule[] createNetworkModules(String address, MqttConnectOptions options) throws MqttException, MqttSecurityException { + protected NetworkModule[] createNetworkModules(String address, MqttConnectOptions options) + throws MqttException, MqttSecurityException { final String methodName = "createNetworkModules"; // @TRACE 116=URI={0} - log.fine(CLASS_NAME, methodName, "116", new Object[]{address}); + log.fine(CLASS_NAME, methodName, "116", new Object[] { address }); NetworkModule[] networkModules = null; String[] serverURIs = options.getServerURIs(); String[] array = null; if (serverURIs == null) { - array = new String[]{address}; + array = new String[] { address }; } else if (serverURIs.length == 0) { - array = new String[]{address}; + array = new String[] { address }; } else { array = serverURIs; } @@ -461,8 +555,22 @@ private NetworkModule createNetworkModule(String address, MqttConnectOptions opt URI uri; try { uri = new URI(address); + // If the returned uri contains no host and the address contains underscores, + // then it's likely that Java did not parse the URI + if(uri.getHost() == null && address.contains("_")){ + try { + final Field hostField = URI.class.getDeclaredField("host"); + hostField.setAccessible(true); + // Get everything after the scheme:// + String shortAddress = address.substring(uri.getScheme().length() + 3); + hostField.set(uri, getHostName(shortAddress)); + + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw ExceptionHelper.createMqttException(e.getCause()); + } + + } } catch (URISyntaxException e) { - // throw new IllegalArgumentException("Malformed URI: " + address, e); // Cannot use for Java 1.4.2 throw new IllegalArgumentException("Malformed URI: " + address + ", " + e.getMessage()); } @@ -507,6 +615,7 @@ else if ((factory instanceof SSLSocketFactory) == false) { // Create the network module... netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId); ((SSLNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout()); + ((SSLNetworkModule)netModule).setSSLHostnameVerifier(options.getSSLHostnameVerifier()); // Ciphers suites need to be set, if they are available if (factoryFactory != null) { String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null); @@ -545,7 +654,7 @@ else if ((factory instanceof SSLSocketFactory) == false) { throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH); } - // Create the network module... + // Create the network module... netModule = new WebSocketSecureNetworkModule((SSLSocketFactory) factory, address, host, port, clientId); ((WebSocketSecureNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout()); // Ciphers suites need to be set, if they are available @@ -556,9 +665,6 @@ else if ((factory instanceof SSLSocketFactory) == false) { } } break; - case MqttConnectOptions.URI_TYPE_LOCAL : - netModule = new LocalNetworkModule(address.substring(8)); - break; default: // This shouldn't happen, as long as validateURI() has been called. log.fine(CLASS_NAME,methodName, "119", new Object[] {address}); @@ -567,30 +673,56 @@ else if ((factory instanceof SSLSocketFactory) == false) { return netModule; } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + private String getHostName(String uri) { + int portIndex = uri.indexOf(':'); + if (portIndex == -1) { + portIndex = uri.indexOf('/'); + } + if (portIndex == -1) { + portIndex = uri.length(); + } + return uri.substring(0, portIndex); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ public IMqttToken connect(Object userContext, IMqttActionListener callback) throws MqttException, MqttSecurityException { return this.connect(new MqttConnectOptions(), userContext, callback); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect() */ public IMqttToken connect() throws MqttException, MqttSecurityException { return this.connect(null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho. + * client.mqttv3.MqttConnectOptions) */ public IMqttToken connect(MqttConnectOptions options) throws MqttException, MqttSecurityException { - return this.connect(options, null,null); + return this.connect(options, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho. + * client.mqttv3.MqttConnectOptions, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttActionListener callback) throws MqttException, MqttSecurityException { @@ -607,56 +739,35 @@ public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttA if (comms.isClosed()) { throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED); } - if(options == null){ + if (options == null) { options = new MqttConnectOptions(); } this.connOpts = options; this.userContext = userContext; final boolean automaticReconnect = options.isAutomaticReconnect(); - // @TRACE 103=cleanSession={0} connectionTimeout={1} TimekeepAlive={2} userName={3} password={4} will={5} userContext={6} callback={7} - log.fine(CLASS_NAME,methodName, "103", - new Object[]{ - Boolean.valueOf(options.isCleanSession()), - new Integer(options.getConnectionTimeout()), - new Integer(options.getKeepAliveInterval()), - options.getUserName(), - ((null == options.getPassword())?"[null]":"[notnull]"), - ((null == options.getWillMessage())?"[null]":"[notnull]"), - userContext, - callback }); + // @TRACE 103=cleanSession={0} connectionTimeout={1} TimekeepAlive={2} + // userName={3} password={4} will={5} userContext={6} callback={7} + log.fine(CLASS_NAME, methodName, "103", + new Object[] { Boolean.valueOf(options.isCleanSession()), new Integer(options.getConnectionTimeout()), + new Integer(options.getKeepAliveInterval()), options.getUserName(), + ((null == options.getPassword()) ? "[null]" : "[notnull]"), + ((null == options.getWillMessage()) ? "[null]" : "[notnull]"), userContext, callback }); comms.setNetworkModules(createNetworkModules(serverURI, options)); - comms.setReconnectCallback(new MqttCallbackExtended() { - - public void messageArrived(String topic, MqttMessage message) throws Exception { - } - public void deliveryComplete(IMqttDeliveryToken token) { - } - public void connectComplete(boolean reconnect, String serverURI) { - } - - public void connectionLost(Throwable cause) { - if(automaticReconnect){ - // Automatic reconnect is set so make sure comms is in resting state - comms.setRestingState(true); - reconnecting = true; - startReconnectCycle(); - } - } - }); - - - + comms.setReconnectCallback(new MqttReconnectCallback(automaticReconnect)); - // Insert our own callback to iterate through the URIs till the connect succeeds + // Insert our own callback to iterate through the URIs till the connect + // succeeds MqttToken userToken = new MqttToken(getClientId()); - ConnectActionListener connectActionListener = new ConnectActionListener(this, persistence, comms, options, userToken, userContext, callback, reconnecting); + ConnectActionListener connectActionListener = new ConnectActionListener(this, persistence, comms, options, + userToken, userContext, callback, reconnecting); userToken.setActionCallback(connectActionListener); userToken.setUserContext(this); - // If we are using the MqttCallbackExtended, set it on the connectActionListener - if(this.mqttCallback instanceof MqttCallbackExtended){ - connectActionListener.setMqttCallbackExtended((MqttCallbackExtended)this.mqttCallback); + // If we are using the MqttCallbackExtended, set it on the + // connectActionListener + if (this.mqttCallback instanceof MqttCallbackExtended) { + connectActionListener.setMqttCallbackExtended((MqttCallbackExtended) this.mqttCallback); } comms.setNetworkModuleIndex(0); @@ -665,34 +776,46 @@ public void connectionLost(Throwable cause) { return userToken; } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(java.lang. + * Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken disconnect( Object userContext, IMqttActionListener callback) throws MqttException { + public IMqttToken disconnect(Object userContext, IMqttActionListener callback) throws MqttException { return this.disconnect(QUIESCE_TIMEOUT, userContext, callback); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect() */ public IMqttToken disconnect() throws MqttException { return this.disconnect(null, null); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long) */ public IMqttToken disconnect(long quiesceTimeout) throws MqttException { return this.disconnect(quiesceTimeout, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, + * java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException { + public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) + throws MqttException { final String methodName = "disconnect"; // @TRACE 104=> quiesceTimeout={0} userContext={1} callback={2} - log.fine(CLASS_NAME,methodName, "104",new Object[]{ new Long(quiesceTimeout), userContext, callback}); + log.fine(CLASS_NAME, methodName, "104", new Object[] { new Long(quiesceTimeout), userContext, callback }); MqttToken token = new MqttToken(getClientId()); token.setActionCallback(callback); @@ -702,18 +825,19 @@ public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActio try { comms.disconnect(disconnect, quiesceTimeout, token); } catch (MqttException ex) { - //@TRACE 105=< exception - log.fine(CLASS_NAME,methodName,"105",null,ex); + // @TRACE 105=< exception + log.fine(CLASS_NAME, methodName, "105", null, ex); throw ex; } - //@TRACE 108=< - log.fine(CLASS_NAME,methodName,"108"); + // @TRACE 108=< + log.fine(CLASS_NAME, methodName, "108"); return token; } - + /* * (non-Javadoc) + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly() */ public void disconnectForcibly() throws MqttException { @@ -722,195 +846,239 @@ public void disconnectForcibly() throws MqttException { /* * (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long) */ public void disconnectForcibly(long disconnectTimeout) throws MqttException { disconnectForcibly(QUIESCE_TIMEOUT, disconnectTimeout); } - + /* * (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, long) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, + * long) */ - public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException{ + public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException { comms.disconnectForcibly(quiesceTimeout, disconnectTimeout); } /** - * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed. + * Disconnects from the server forcibly to reset all the states. Could be + * useful when disconnect attempt failed. *

- * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to - * send the disconnect packet. - * - * @param quiesceTimeout the amount of time in milliseconds to allow for existing work to finish before - * disconnecting. A value of zero or less means the client will not quiesce. - * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server. - * @param sendDisconnectPacket if true, will send the disconnect packet to the server - * @throws MqttException if any unexpected error + * Because the client is able to establish the TCP/IP connection to a none + * MQTT server and it will certainly fail to send the disconnect packet. + * + * @param quiesceTimeout + * the amount of time in milliseconds to allow for existing work + * to finish before disconnecting. A value of zero or less means + * the client will not quiesce. + * @param disconnectTimeout + * the amount of time in milliseconds to allow send disconnect + * packet to server. + * @param sendDisconnectPacket + * if true, will send the disconnect packet to the server + * @throws MqttException + * if any unexpected error */ - public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket) throws MqttException { - comms.disconnectForcibly(quiesceTimeout, disconnectTimeout, sendDisconnectPacket); - } + public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket) + throws MqttException { + comms.disconnectForcibly(quiesceTimeout, disconnectTimeout, sendDisconnectPacket); + } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see IMqttAsyncClient#isConnected() */ public boolean isConnected() { return comms.isConnected(); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see IMqttAsyncClient#getClientId() */ public String getClientId() { return clientId; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see IMqttAsyncClient#getServerURI() */ public String getServerURI() { return serverURI; } - + /** - * Returns the currently connected Server URI - * Implemented due to: https://bugs.eclipse.org/bugs/show_bug.cgi?id=481097 - * + * Returns the currently connected Server URI Implemented due to: + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=481097 + * * Where getServerURI only returns the URI that was provided in * MqttAsyncClient's constructor, getCurrentServerURI returns the URI of the - * Server that the client is currently connected to. This would be different in scenarios - * where multiple server URIs have been provided to the MqttConnectOptions. - * + * Server that the client is currently connected to. This would be different + * in scenarios where multiple server URIs have been provided to the + * MqttConnectOptions. + * * @return the currently connected server URI */ - public String getCurrentServerURI(){ + public String getCurrentServerURI() { return comms.getNetworkModules()[comms.getNetworkModuleIndex()].getServerURI(); } /** * Get a topic object which can be used to publish messages. - *

There are two alternative methods that should be used in preference to this one when publishing a message:

+ *

+ * There are two alternative methods that should be used in preference to + * this one when publishing a message: + *

*
    - *
  • {@link MqttAsyncClient#publish(String, MqttMessage)} to publish a message in a non-blocking manner or
  • - *
  • {@link MqttClient#publish(String, MqttMessage)} to publish a message in a blocking manner
  • + *
  • {@link MqttAsyncClient#publish(String, MqttMessage)} to publish a + * message in a non-blocking manner or
  • + *
  • {@link MqttClient#publish(String, MqttMessage)} to publish a message + * in a blocking manner
  • *
- *

When you build an application, - * the design of the topic tree should take into account the following principles - * of topic name syntax and semantics:

+ *

+ * When you build an application, the design of the topic tree should take + * into account the following principles of topic name syntax and semantics: + *

* *
    - *
  • A topic must be at least one character long.
  • - *
  • Topic names are case sensitive. For example, ACCOUNTS and Accounts are - * two different topics.
  • - *
  • Topic names can include the space character. For example, Accounts - * payable is a valid topic.
  • - *
  • A leading "/" creates a distinct topic. For example, /finance is - * different from finance. /finance matches "+/+" and "/+", but - * not "+".
  • - *
  • Do not include the null character (Unicode \x0000) in - * any topic.
  • + *
  • A topic must be at least one character long.
  • + *
  • Topic names are case sensitive. For example, ACCOUNTS and + * Accounts are two different topics.
  • + *
  • Topic names can include the space character. For example, + * Accounts payable is a valid topic.
  • + *
  • A leading "/" creates a distinct topic. For example, + * /finance is different from finance. /finance + * matches "+/+" and "/+", but not "+".
  • + *
  • Do not include the null character (Unicode \x0000) in any topic.
  • *
* - *

The following principles apply to the construction and content of a topic - * tree:

+ *

+ * The following principles apply to the construction and content of a topic + * tree: + *

* *
    - *
  • The length is limited to 64k but within that there are no limits to the - * number of levels in a topic tree.
  • - *
  • There can be any number of root nodes; that is, there can be any number - * of topic trees.
  • - *
+ *
  • The length is limited to 64k but within that there are no limits to + * the number of levels in a topic tree.
  • + *
  • There can be any number of root nodes; that is, there can be any + * number of topic trees.
  • + * * - * @param topic the topic to use, for example "finance/stock/ibm". - * @return an MqttTopic object, which can be used to publish messages to - * the topic. - * @throws IllegalArgumentException if the topic contains a '+' or '#' - * wildcard character. + * @param topic + * the topic to use, for example "finance/stock/ibm". + * @return an MqttTopic object, which can be used to publish messages to the + * topic. + * @throws IllegalArgumentException + * if the topic contains a '+' or '#' wildcard character. */ protected MqttTopic getTopic(String topic) { - MqttTopic.validate(topic, false/*wildcards NOT allowed*/); + MqttTopic.validate(topic, false/* wildcards NOT allowed */); - MqttTopic result = (MqttTopic)topics.get(topic); + MqttTopic result = (MqttTopic) topics.get(topic); if (result == null) { result = new MqttTopic(topic, comms); - topics.put(topic,result); + topics.put(topic, result); } return result; } - - /* (non-Javadoc) - * Check and send a ping if needed. - *

    By default, client sends PingReq to server to keep the connection to - * server. For some platforms which cannot use this mechanism, such as Android, - * developer needs to handle the ping request manually with this method. - *

    - * - * @throws MqttException for other errors encountered while publishing the message. + + /* + * (non-Javadoc) Check and send a ping if needed.

    By default, client + * sends PingReq to server to keep the connection to server. For some + * platforms which cannot use this mechanism, such as Android, developer + * needs to handle the ping request manually with this method.

    + * + * @throws MqttException for other errors encountered while publishing the + * message. */ - public IMqttToken checkPing(Object userContext, IMqttActionListener callback) throws MqttException{ + public IMqttToken checkPing(Object userContext, IMqttActionListener callback) throws MqttException { final String methodName = "ping"; MqttToken token; - //@TRACE 117=> - log.fine(CLASS_NAME,methodName,"117"); - + // @TRACE 117=> + log.fine(CLASS_NAME, methodName, "117"); + token = comms.checkForActivity(); - //@TRACE 118=< - log.fine(CLASS_NAME,methodName,"118"); - + // @TRACE 118=< + log.fine(CLASS_NAME, methodName, "118"); + return token; } - - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String, int, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback) throws MqttException { - return this.subscribe(new String[] {topicFilter}, new int[] {qos}, userContext, callback); + public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback) + throws MqttException { + return this.subscribe(new String[] { topicFilter }, new int[] { qos }, userContext, callback); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int) + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String, int) */ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException { - return this.subscribe(new String[] {topicFilter}, new int[] {qos}, null, null); + return this.subscribe(new String[] { topicFilter }, new int[] { qos }, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[]) + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String[], int[]) */ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException { return this.subscribe(topicFilters, qos, null, null); } - - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String[], int[], java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback) throws MqttException { + public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback) + throws MqttException { final String methodName = "subscribe"; if (topicFilters.length != qos.length) { throw new IllegalArgumentException(); } - - // remove any message handlers for individual topics + + // remove any message handlers for individual topics for (int i = 0; i < topicFilters.length; ++i) { this.comms.removeMessageListener(topicFilters[i]); } - + // Only Generate Log string if we are logging at FINE level - if(log.isLoggable(Logger.FINE)){ + if (log.isLoggable(Logger.FINE)) { StringBuffer subs = new StringBuffer(); - for (int i=0;i0) { + for (int i = 0; i < topicFilters.length; i++) { + if (i > 0) { subs.append(", "); } subs.append("topic=").append(topicFilters[i]).append(" qos=").append(qos[i]); - - //Check if the topic filter is valid before subscribing - MqttTopic.validate(topicFilters[i], true/*allow wildcards*/); + + // Check if the topic filter is valid before subscribing + MqttTopic.validate(topicFilters[i], true/* allow wildcards */); } - //@TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2} - log.fine(CLASS_NAME,methodName,"106",new Object[]{subs.toString(), userContext, callback}); + // @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2} + log.fine(CLASS_NAME, methodName, "106", new Object[] { subs.toString(), userContext, callback }); } MqttToken token = new MqttToken(getClientId()); @@ -921,101 +1089,135 @@ public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext MqttSubscribe register = new MqttSubscribe(topicFilters, qos); comms.sendNoWait(register, token); - //@TRACE 109=< - log.fine(CLASS_NAME,methodName,"109"); + // @TRACE 109=< + log.fine(CLASS_NAME, methodName, "109"); return token; } - - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String, int, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback, IMqttMessageListener messageListener) throws MqttException { - - return this.subscribe(new String[] {topicFilter}, new int[] {qos}, userContext, callback, new IMqttMessageListener[] {messageListener}); + public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback, + IMqttMessageListener messageListener) throws MqttException { + + return this.subscribe(new String[] { topicFilter }, new int[] { qos }, userContext, callback, + new IMqttMessageListener[] { messageListener }); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int) + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String, int) */ - public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener) throws MqttException { - return this.subscribe(new String[] {topicFilter}, new int[] {qos}, null, null, new IMqttMessageListener[] {messageListener}); + public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener) + throws MqttException { + return this.subscribe(new String[] { topicFilter }, new int[] { qos }, null, null, + new IMqttMessageListener[] { messageListener }); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[]) + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang. + * String[], int[]) */ - public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException { + public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) + throws MqttException { return this.subscribe(topicFilters, qos, null, null, messageListeners); } - - public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback, IMqttMessageListener[] messageListeners) throws MqttException { - + + public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback, + IMqttMessageListener[] messageListeners) throws MqttException { + if ((messageListeners.length != qos.length) || (qos.length != topicFilters.length)) { throw new IllegalArgumentException(); } - + IMqttToken token = this.subscribe(topicFilters, qos, userContext, callback); - + // add message handlers to the list for this client for (int i = 0; i < topicFilters.length; ++i) { this.comms.setMessageListener(topicFilters[i], messageListeners[i]); } - + return token; } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang. + * String, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActionListener callback) throws MqttException { - return unsubscribe(new String[] {topicFilter}, userContext, callback); + public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActionListener callback) + throws MqttException { + return unsubscribe(new String[] { topicFilter }, userContext, callback); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang. + * String) */ public IMqttToken unsubscribe(String topicFilter) throws MqttException { - return unsubscribe(new String[] {topicFilter}, null, null); + return unsubscribe(new String[] { topicFilter }, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[]) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang. + * String[]) */ public IMqttToken unsubscribe(String[] topicFilters) throws MqttException { return unsubscribe(topicFilters, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang. + * String[], java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttActionListener callback) throws MqttException { + public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttActionListener callback) + throws MqttException { final String methodName = "unsubscribe"; - + // Only Generate Log string if we are logging at FINE level - if(log.isLoggable(Logger.FINE)){ + if (log.isLoggable(Logger.FINE)) { String subs = ""; - for (int i=0;i0) { - subs+=", "; + for (int i = 0; i < topicFilters.length; i++) { + if (i > 0) { + subs += ", "; } - subs+=topicFilters[i]; + subs += topicFilters[i]; } - - //@TRACE 107=Unsubscribe topic={0} userContext={1} callback={2} - log.fine(CLASS_NAME, methodName,"107",new Object[]{subs, userContext, callback}); + + // @TRACE 107=Unsubscribe topic={0} userContext={1} callback={2} + log.fine(CLASS_NAME, methodName, "107", new Object[] { subs, userContext, callback }); } - - for (int i=0;iWhen cleanSession is set to false, an application must ensure it uses the - * same client identifier when it reconnects to the server to resume state and maintain - * assured message delivery.

    + * Returns a randomly generated client identifier based on the the fixed + * prefix (paho) and the system time. + *

    + * When cleanSession is set to false, an application must ensure it uses the + * same client identifier when it reconnects to the server to resume state + * and maintain assured message delivery. + *

    + * * @return a generated client identifier * @see MqttConnectOptions#setCleanSession(boolean) */ public static String generateClientId() { - //length of nanoTime = 15, so total length = 19 < 65535(defined in spec) - //return CLIENT_ID_PREFIX + System.nanoTime(); // Cannot use for Java 1.4.2 - return CLIENT_ID_PREFIX + (System.currentTimeMillis() * 1000000); - + // length of nanoTime = 15, so total length = 19 < 65535(defined in + // spec) + return CLIENT_ID_PREFIX + System.nanoTime(); + } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see IMqttAsyncClient#getPendingDeliveryTokens() */ public IMqttDeliveryToken[] getPendingDeliveryTokens() { return comms.getPendingDeliveryTokens(); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, + * byte[], int, boolean, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, - boolean retained, Object userContext, IMqttActionListener callback) throws MqttException, - MqttPersistenceException { + public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean retained, Object userContext, + IMqttActionListener callback) throws MqttException, MqttPersistenceException { MqttMessage message = new MqttMessage(payload); message.setQos(qos); message.setRetained(retained); return this.publish(topic, message, userContext, callback); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean) + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, + * byte[], int, boolean) */ - public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, - boolean retained) throws MqttException, MqttPersistenceException { + public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean retained) + throws MqttException, MqttPersistenceException { return this.publish(topic, payload, qos, retained, null, null); } - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, + * org.eclipse.paho.client.mqttv3.MqttMessage) */ - public IMqttDeliveryToken publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException { + public IMqttDeliveryToken publish(String topic, MqttMessage message) + throws MqttException, MqttPersistenceException { return this.publish(topic, message, null, null); } - - /* (non-Javadoc) - * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) + /* + * (non-Javadoc) + * + * @see + * org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, + * org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object, + * org.eclipse.paho.client.mqttv3.IMqttActionListener) */ - public IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext, IMqttActionListener callback) throws MqttException, - MqttPersistenceException { + public IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext, + IMqttActionListener callback) throws MqttException, MqttPersistenceException { final String methodName = "publish"; - //@TRACE 111=< topic={0} message={1}userContext={1} callback={2} - log.fine(CLASS_NAME,methodName,"111", new Object[] {topic, userContext, callback}); + // @TRACE 111=< topic={0} message={1}userContext={1} callback={2} + log.fine(CLASS_NAME, methodName, "111", new Object[] { topic, userContext, callback }); - //Checks if a topic is valid when publishing a message. - MqttTopic.validate(topic, false/*wildcards NOT allowed*/); + // Checks if a topic is valid when publishing a message. + MqttTopic.validate(topic, false/* wildcards NOT allowed */); MqttDeliveryToken token = new MqttDeliveryToken(getClientId()); token.setActionCallback(callback); token.setUserContext(userContext); token.setMessage(message); - token.internalTok.setTopics(new String[] {topic}); + token.internalTok.setTopics(new String[] { topic }); MqttPublish pubMsg = new MqttPublish(topic, message); comms.sendNoWait(pubMsg, token); - //@TRACE 112=< - log.fine(CLASS_NAME,methodName,"112"); + // @TRACE 112=< + log.fine(CLASS_NAME, methodName, "112"); return token; } /** * User triggered attempt to reconnect - * @throws MqttException if there is an issue with reconnecting + * + * @throws MqttException + * if there is an issue with reconnecting */ public void reconnect() throws MqttException { final String methodName = "reconnect"; - //@Trace 500=Attempting to reconnect client: {0} - log.fine(CLASS_NAME, methodName, "500", new Object[]{this.clientId}); - // Some checks to make sure that we're not attempting to reconnect an already connected client + // @Trace 500=Attempting to reconnect client: {0} + log.fine(CLASS_NAME, methodName, "500", new Object[] { this.clientId }); + // Some checks to make sure that we're not attempting to reconnect an + // already connected client if (comms.isConnected()) { throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED); } @@ -1157,69 +1389,47 @@ public void reconnect() throws MqttException { attemptReconnect(); } - - + /** - * Attempts to reconnect the client to the server. - * If successful it will make sure that there are no further - * reconnects scheduled. However if the connect fails, the delay will double - * up to 128 seconds and will re-schedule the reconnect for after the delay. - * - * Any thrown exceptions are logged but not acted upon as it is assumed that + * Attempts to reconnect the client to the server. If successful it will + * make sure that there are no further reconnects scheduled. However if the + * connect fails, the delay will double up to 128 seconds and will + * re-schedule the reconnect for after the delay. + * + * Any thrown exceptions are logged but not acted upon as it is assumed that * they are being thrown due to the server being offline and so reconnect * attempts will continue. */ - private void attemptReconnect(){ - final String methodName = "attemptReconnect"; - //@Trace 500=Attempting to reconnect client: {0} - log.fine(CLASS_NAME, methodName, "500", new Object[]{this.clientId}); + private void attemptReconnect() { + final String methodName = "attemptReconnect"; + // @Trace 500=Attempting to reconnect client: {0} + log.fine(CLASS_NAME, methodName, "500", new Object[] { this.clientId }); try { - connect(this.connOpts, this.userContext,new IMqttActionListener() { - - public void onSuccess(IMqttToken asyncActionToken) { - //@Trace 501=Automatic Reconnect Successful: {0} - log.fine(CLASS_NAME, methodName, "501", new Object[]{asyncActionToken.getClient().getClientId()}); - comms.setRestingState(false); - stopReconnectCycle(); - } - - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - //@Trace 502=Automatic Reconnect failed, rescheduling: {0} - log.fine(CLASS_NAME, methodName, "502", new Object[]{asyncActionToken.getClient().getClientId()}); - if(reconnectDelay < 128000){ - reconnectDelay = reconnectDelay * 2; - } - rescheduleReconnectCycle(reconnectDelay); - } - }); + connect(this.connOpts, this.userContext, new MqttReconnectActionListener(methodName)); } catch (MqttSecurityException ex) { - //@TRACE 804=exception - log.fine(CLASS_NAME,methodName,"804",null, ex); + // @TRACE 804=exception + log.fine(CLASS_NAME, methodName, "804", null, ex); } catch (MqttException ex) { - //@TRACE 804=exception - log.fine(CLASS_NAME,methodName,"804",null, ex); + // @TRACE 804=exception + log.fine(CLASS_NAME, methodName, "804", null, ex); } } - - - - private void startReconnectCycle(){ + private void startReconnectCycle() { String methodName = "startReconnectCycle"; - //@Trace 503=Start reconnect timer for client: {0}, delay: {1} - log.fine(CLASS_NAME, methodName, "503", new Object[]{this.clientId, new Long(reconnectDelay)}); - //reconnectTimer = new Timer("MQTT Reconnect: " + clientId); // Cannot use for Java 1.4.2 - reconnectTimer = new Timer(); + // @Trace 503=Start reconnect timer for client: {0}, delay: {1} + log.fine(CLASS_NAME, methodName, "503", new Object[] { this.clientId, new Long(reconnectDelay) }); + reconnectTimer = new Timer("MQTT Reconnect: " + clientId); reconnectTimer.schedule(new ReconnectTask(), reconnectDelay); } - - private void stopReconnectCycle(){ + + private void stopReconnectCycle() { String methodName = "stopReconnectCycle"; - //@Trace 504=Stop reconnect timer for client: {0} - log.fine(CLASS_NAME, methodName, "504", new Object[]{this.clientId}); - synchronized(clientLock) { + // @Trace 504=Stop reconnect timer for client: {0} + log.fine(CLASS_NAME, methodName, "504", new Object[] { this.clientId }); + synchronized (clientLock) { if (this.connOpts.isAutomaticReconnect()) { - if(reconnectTimer != null){ + if (reconnectTimer != null) { reconnectTimer.cancel(); reconnectTimer = null; } @@ -1227,90 +1437,174 @@ private void stopReconnectCycle(){ } } } - - private void rescheduleReconnectCycle(int delay){ - String methodName = "rescheduleReconnectCycle"; - //@Trace 505=Rescheduling reconnect timer for client: {0}, delay: {1} - log.fine(CLASS_NAME, methodName, "505", new Object[]{this.clientId, new Long(reconnectDelay)}); - synchronized(clientLock) { - if(this.connOpts.isAutomaticReconnect()) { - if (reconnectTimer != null) { - reconnectTimer.schedule(new ReconnectTask(), delay); - } else { - // The previous reconnect timer was cancelled - reconnectDelay = delay; - startReconnectCycle(); - } - } - } - } - + private class ReconnectTask extends TimerTask { private static final String methodName = "ReconnectTask.run"; + public void run() { - //@Trace 506=Triggering Automatic Reconnect attempt. + // @Trace 506=Triggering Automatic Reconnect attempt. log.fine(CLASS_NAME, methodName, "506"); attemptReconnect(); } } - + + class MqttReconnectCallback implements MqttCallbackExtended { + + final boolean automaticReconnect; + + MqttReconnectCallback(boolean isAutomaticReconnect) { + automaticReconnect = isAutomaticReconnect; + } + + public void connectionLost(Throwable cause) { + if (automaticReconnect) { + // Automatic reconnect is set so make sure comms is in resting + // state + comms.setRestingState(true); + reconnecting = true; + startReconnectCycle(); + } + } + + public void messageArrived(String topic, MqttMessage message) throws Exception { + } + + public void deliveryComplete(IMqttDeliveryToken token) { + } + + public void connectComplete(boolean reconnect, String serverURI) { + } + + } + + class MqttReconnectActionListener implements IMqttActionListener { + + final String methodName; + + MqttReconnectActionListener(String methodName) { + this.methodName = methodName; + } + + public void onSuccess(IMqttToken asyncActionToken) { + // @Trace 501=Automatic Reconnect Successful: {0} + log.fine(CLASS_NAME, methodName, "501", new Object[] { asyncActionToken.getClient().getClientId() }); + comms.setRestingState(false); + stopReconnectCycle(); + } + + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + // @Trace 502=Automatic Reconnect failed, rescheduling: {0} + log.fine(CLASS_NAME, methodName, "502", new Object[] { asyncActionToken.getClient().getClientId() }); + if (reconnectDelay < 128000) { + reconnectDelay = reconnectDelay * 2; + } + rescheduleReconnectCycle(reconnectDelay); + } + + private void rescheduleReconnectCycle(int delay) { + String reschedulemethodName = methodName + ":rescheduleReconnectCycle"; + // @Trace 505=Rescheduling reconnect timer for client: {0}, delay: + // {1} + log.fine(CLASS_NAME, reschedulemethodName, "505", + new Object[] { MqttAsyncClient.this.clientId, String.valueOf(reconnectDelay) }); + synchronized (clientLock) { + if (MqttAsyncClient.this.connOpts.isAutomaticReconnect()) { + if (reconnectTimer != null) { + reconnectTimer.schedule(new ReconnectTask(), delay); + } else { + // The previous reconnect timer was cancelled + reconnectDelay = delay; + startReconnectCycle(); + } + } + } + } + + } + /** * Sets the DisconnectedBufferOptions for this client - * @param bufferOpts the {@link DisconnectedBufferOptions} + * + * @param bufferOpts + * the {@link DisconnectedBufferOptions} */ public void setBufferOpts(DisconnectedBufferOptions bufferOpts) { this.comms.setDisconnectedMessageBuffer(new DisconnectedMessageBuffer(bufferOpts)); } - - + /** * Returns the number of messages in the Disconnected Message Buffer + * * @return Count of messages in the buffer */ - public int getBufferedMessageCount(){ + public int getBufferedMessageCount() { return this.comms.getBufferedMessageCount(); } - - + /** * Returns a message from the Disconnected Message Buffer - * @param bufferIndex the index of the message to be retrieved. + * + * @param bufferIndex + * the index of the message to be retrieved. * @return the message located at the bufferIndex */ - public MqttMessage getBufferedMessage(int bufferIndex){ + public MqttMessage getBufferedMessage(int bufferIndex) { return this.comms.getBufferedMessage(bufferIndex); } - + /** * Deletes a message from the Disconnected Message Buffer - * @param bufferIndex the index of the message to be deleted. + * + * @param bufferIndex + * the index of the message to be deleted. */ - public void deleteBufferedMessage(int bufferIndex){ + public void deleteBufferedMessage(int bufferIndex) { this.comms.deleteBufferedMessage(bufferIndex); } - - + /** + * Returns the current number of outgoing in-flight messages being sent by + * the client. Note that this number cannot be guaranteed to be 100% + * accurate as some messages may have been sent or queued in the time taken + * for this method to return. + * + * @return the current number of in-flight messages. + */ + public int getInFlightMessageCount() { + return this.comms.getActualInFlight(); + } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#close() */ public void close() throws MqttException { + close(false); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#close() + */ + public void close(boolean force) throws MqttException { final String methodName = "close"; - //@TRACE 113=< - log.fine(CLASS_NAME,methodName,"113"); - comms.close(); - //@TRACE 114=> - log.fine(CLASS_NAME,methodName,"114"); + // @TRACE 113=< + log.fine(CLASS_NAME, methodName, "113"); + comms.close(force); + // @TRACE 114=> + log.fine(CLASS_NAME, methodName, "114"); } /** * Return a debug object that can be used to help solve problems. + * * @return the {@link Debug} object */ public Debug getDebug() { - return new Debug(clientId,comms); + return new Debug(clientId, comms); } } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java index 1b5941f94..c87844168 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -19,6 +19,7 @@ package org.eclipse.paho.client.mqttv3; import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; import javax.net.SocketFactory; @@ -31,14 +32,14 @@ * *

    This class implements the blocking {@link IMqttClient} client interface where all * actions block until they have completed (or timed out). - * This implementation is compatible with all Java SE runtimes from 1.4.2 and up. + * This implementation is compatible with all Java SE runtimes from 1.7 and up. *

    *

    An application can connect to an MQTT server using:

    *
      *
    • A plain TCP socket *
    • An secure SSL/TLS socket *
    - * + * *

    To enable messages to be delivered even across network and client restarts * messages need to be safely stored until the message has been delivered at the requested * quality of service. A pluggable persistence mechanism is provided to store the messages. @@ -228,6 +229,96 @@ public MqttClient(String serverURI, String clientId, MqttClientPersistence persi aClient = new MqttAsyncClient(serverURI, clientId, persistence); } + /** + * Create an MqttClient that can be used to communicate with an MQTT server. + *

    + * The address of a server can be specified on the constructor. Alternatively + * a list containing one or more servers can be specified using the + * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method + * on MqttConnectOptions. + * + *

    The serverURI parameter is typically used with the + * the clientId parameter to form a key. The key + * is used to store and reference messages while they are being delivered. + * Hence the serverURI specified on the constructor must still be specified even if a list + * of servers is specified on an MqttConnectOptions object. + * The serverURI on the constructor must remain the same across + * restarts of the client for delivery of messages to be maintained from a given + * client to a given server or set of servers. + * + *

    The address of the server to connect to is specified as a URI. Two types of + * connection are supported tcp:// for a TCP connection and + * ssl:// for a TCP connection secured by SSL/TLS. + * For example: + *

    + *
      + *
    • tcp://localhost:1883
    • + *
    • ssl://localhost:8883
    • + *
    + *

    If the port is not specified, it will + * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs. + *

    + * + *

    + * A client identifier clientId must be specified and be less that 65535 characters. + * It must be unique across all clients connecting to the same + * server. The clientId is used by the server to store data related to the client, + * hence it is important that the clientId remain the same when connecting to a server + * if durable subscriptions or reliable messaging are required. + *

    A convenience method is provided to generate a random client id that + * should satisfy this criteria - {@link #generateClientId()}. As the client identifier + * is used by the server to identify a client when it reconnects, the client must use the + * same identifier between connections if durable subscriptions or reliable + * delivery of messages is required. + *

    + *

    + * In Java SE, SSL can be configured in one of several ways, which the + * client will use in the following order: + *

    + *
      + *
    • Supplying an SSLSocketFactory - applications can + * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply + * a factory with the appropriate SSL settings.
    • + *
    • SSL Properties - applications can supply SSL settings as a + * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
    • + *
    • Use JVM settings - There are a number of standard + * Java system properties that can be used to configure key and trust stores.
    • + *
    + * + *

    In Java ME, the platform settings are used for SSL connections.

    + *

    + * A persistence mechanism is used to enable reliable messaging. + * For messages sent at qualities of service (QoS) 1 or 2 to be reliably delivered, + * messages must be stored (on both the client and server) until the delivery of the message + * is complete. If messages are not safely stored when being delivered then + * a failure in the client or server can result in lost messages. A pluggable + * persistence mechanism is supported via the {@link MqttClientPersistence} + * interface. An implementer of this interface that safely stores messages + * must be specified in order for delivery of messages to be reliable. In + * addition {@link MqttConnectOptions#setCleanSession(boolean)} must be set + * to false. In the event that only QoS 0 messages are sent or received or + * cleanSession is set to true then a safe store is not needed. + *

    + *

    An implementation of file-based persistence is provided in + * class {@link MqttDefaultFilePersistence} which will work in all Java SE based + * systems. If no persistence is needed, the persistence parameter + * can be explicitly set to null.

    + * + * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using + * {@link MqttConnectOptions#setServerURIs(String[])} + * @param clientId a client identifier that is unique on the server being connected to + * @param persistence the persistence class to use to store in-flight message. If null then the + * default persistence mechanism is used + * @param executorService used for managing threads. If null then a newFixedThreadPool is used. + * @throws IllegalArgumentException if the URI does not start with + * "tcp://", "ssl://" or "local://" + * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length + * @throws MqttException if any other problem was encountered + */ + public MqttClient(String serverURI, String clientId, MqttClientPersistence persistence, ScheduledExecutorService executorService) throws MqttException { + aClient = new MqttAsyncClient(serverURI, clientId, persistence, new ScheduledExecutorPingSender(executorService), executorService); + } + /* * @see IMqttClient#connect() */ @@ -241,7 +332,7 @@ public void connect() throws MqttSecurityException, MqttException { public void connect(MqttConnectOptions options) throws MqttSecurityException, MqttException { aClient.connect(options, null, null).waitForCompletion(getTimeToWait()); } - + /* * @see IMqttClient#connect(MqttConnectOptions) */ @@ -267,7 +358,7 @@ public void disconnect(long quiesceTimeout) throws MqttException { /* * (non-Javadoc) - * + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly() */ public void disconnectForcibly() throws MqttException { @@ -276,7 +367,7 @@ public void disconnectForcibly() throws MqttException { /* * (non-Javadoc) - * + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long) */ public void disconnectForcibly(long disconnectTimeout) throws MqttException { @@ -285,7 +376,7 @@ public void disconnectForcibly(long disconnectTimeout) throws MqttException { /* * (non-Javadoc) - * + * * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, long) */ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException { @@ -297,7 +388,7 @@ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) thro *

    * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to * send the disconnect packet. - * + * * @param quiesceTimeout the amount of time in milliseconds to allow for existing work to finish before * disconnecting. A value of zero or less means the client will not quiesce. * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server. @@ -347,14 +438,14 @@ public void subscribe(String[] topicFilters, int[] qos) throws MqttException { throw new MqttException(MqttException.REASON_CODE_SUBSCRIBE_FAILED); } } - + /* (non-Javadoc) * @see org.eclipse.paho.client.mqttv3.IMqttClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) */ public void subscribe(String topicFilter, IMqttMessageListener messageListener) throws MqttException { this.subscribe(new String[] {topicFilter}, new int[] {1}, new IMqttMessageListener[] {messageListener}); } - + /* (non-Javadoc) * @see org.eclipse.paho.client.mqttv3.IMqttClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener) */ @@ -373,23 +464,23 @@ public void subscribe(String topicFilter, int qos, IMqttMessageListener messageL this.subscribe(new String[] {topicFilter}, new int[] {qos}, new IMqttMessageListener[] {messageListener}); } - - public void subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException { + + public void subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException { this.subscribe(topicFilters, qos); - + // add message handlers to the list for this client for (int i = 0; i < topicFilters.length; ++i) { aClient.comms.setMessageListener(topicFilters[i], messageListeners[i]); } } - + /* * @see IMqttClient#subscribeWithResponse(String) */ public IMqttToken subscribeWithResponse(String topicFilter) throws MqttException { return this.subscribeWithResponse(new String[] {topicFilter}, new int[] {1}); } - + /* * @see IMqttClient#subscribeWithResponse(String, IMqttMessageListener) */ @@ -450,7 +541,7 @@ public IMqttToken subscribeWithResponse(String[] topicFilters, int[] qos) throws public IMqttToken subscribeWithResponse(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException { IMqttToken tok = this.subscribeWithResponse(topicFilters, qos); - + // add message handlers to the list for this client for (int i = 0; i < topicFilters.length; ++i) { aClient.comms.setMessageListener(topicFilters[i], messageListeners[i]); @@ -532,8 +623,17 @@ public long getTimeToWait() { * @see org.eclipse.paho.client.mqttv3.IMqttClient#close() */ public void close() throws MqttException { - aClient.close(); + aClient.close(false); + } + + /* (non-Javadoc) + * @see org.eclipse.paho.client.mqttv3.IMqttClient#close() + */ + public void close(boolean force) throws MqttException { + aClient.close(force); } + + /* (non-Javadoc) * @see org.eclipse.paho.client.mqttv3.IMqttClient#getClientId() @@ -555,16 +655,16 @@ public IMqttDeliveryToken[] getPendingDeliveryTokens() { public String getServerURI() { return aClient.getServerURI(); } - + /** * Returns the currently connected Server URI * Implemented due to: https://bugs.eclipse.org/bugs/show_bug.cgi?id=481097 - * + * * Where getServerURI only returns the URI that was provided in * MqttAsyncClient's constructor, getCurrentServerURI returns the URI of the * Server that the client is currently connected to. This would be different in scenarios * where multiple server URIs have been provided to the MqttConnectOptions. - * + * * @return the currently connected server URI */ public String getCurrentServerURI(){ @@ -591,14 +691,14 @@ public boolean isConnected() { public void setCallback(MqttCallback callback) { aClient.setCallback(callback); } - + /* (non-Javadoc) * @see org.eclipse.paho.client.mqttv3.IMqttClient#setCallback(org.eclipse.paho.client.mqttv3.MqttCallback) */ public void setManualAcks(boolean manualAcks) { aClient.setManualAcks(manualAcks); } - + public void messageArrivedComplete(int messageId, int qos) throws MqttException { aClient.messageArrivedComplete(messageId, qos); } @@ -615,7 +715,7 @@ public void messageArrivedComplete(int messageId, int qos) throws MqttException public static String generateClientId() { return MqttAsyncClient.generateClientId(); } - + /** * Will attempt to reconnect to the server after the client has lost connection. * @throws MqttException if an error occurs attempting to reconnect @@ -631,5 +731,5 @@ public void reconnect() throws MqttException { public Debug getDebug() { return (aClient.getDebug()); } - + } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java index 9eb946259..2e16b7a01 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java @@ -20,6 +20,7 @@ import java.util.Properties; import javax.net.SocketFactory; +import javax.net.ssl.HostnameVerifier; import org.eclipse.paho.client.mqttv3.util.Debug; @@ -73,6 +74,7 @@ public class MqttConnectOptions { private char[] password; private SocketFactory socketFactory; private Properties sslClientProps = null; + private HostnameVerifier sslHostnameVerifier = null; private boolean cleanSession = CLEAN_SESSION_DEFAULT; private int connectionTimeout = CONNECTION_TIMEOUT_DEFAULT; private String[] serverURIs = null; @@ -408,6 +410,27 @@ public void setSSLProperties(Properties props) { this.sslClientProps = props; } + /** + * Returns the HostnameVerifier for the SSL connection. + * @return the HostnameVerifier for the SSL connection + */ + public HostnameVerifier getSSLHostnameVerifier() { + return sslHostnameVerifier; + } + + /** + * Sets the HostnameVerifier for the SSL connection. Note that it will be + * used after handshake on a connection and you should do actions by + * yourserlf when hostname is verified error. + *

    + * There is no default HostnameVerifier + *

    + * @param hostnameVerifier the {@link HostnameVerifier} + */ + public void setSSLHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.sslHostnameVerifier = hostnameVerifier; + } + /** * Returns whether the client and server should remember state for the client across reconnects. * @return the clean session flag diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java index febec3bfe..404e9610c 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -25,7 +25,7 @@ * Represents a topic destination, used for publish/subscribe messaging. */ public class MqttTopic { - + /** * The forward slash (/) is used to separate each level within a topic tree * and provide a hierarchical structure to the topic space. The use of the @@ -45,7 +45,7 @@ public class MqttTopic { * matches only one topic level. */ public static final String SINGLE_LEVEL_WILDCARD = "+"; - + /** * Multi-level wildcard pattern(/#) */ @@ -55,15 +55,15 @@ public class MqttTopic { * Topic wildcards (#+) */ public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD; - + //topic name and topic filter length range defined in the spec private static final int MIN_TOPIC_LEN = 1; private static final int MAX_TOPIC_LEN = 65535; private static final char NUL = '\u0000'; - + private ClientComms comms; private String name; - + /** * @param name The Name of the topic * @param comms The {@link ClientComms} @@ -72,12 +72,12 @@ public MqttTopic(String name, ClientComms comms) { this.comms = comms; this.name = name; } - + /** - * Publishes a message on the topic. This is a convenience method, which will + * Publishes a message on the topic. This is a convenience method, which will * create a new {@link MqttMessage} object with a byte array payload and the * specified QoS, and then publish it. All other values in the - * message will be set to the defaults. + * message will be set to the defaults. * @param payload the byte array to use as the payload * @param qos the Quality of Service. Valid values are 0, 1 or 2. @@ -96,15 +96,15 @@ public MqttDeliveryToken publish(byte[] payload, int qos, boolean retained) thro message.setRetained(retained); return this.publish(message); } - + /** - * Publishes the specified message to this topic, but does not wait for delivery + * Publishes the specified message to this topic, but does not wait for delivery * of the message to complete. The returned {@link MqttDeliveryToken token} can be used - * to track the delivery status of the message. Once this method has + * to track the delivery status of the message. Once this method has * returned cleanly, the message has been accepted for publication by the - * client. Message delivery will be completed in the background when a connection + * client. Message delivery will be completed in the background when a connection * is available. - * + * * @param message the message to publish * @return an MqttDeliveryToken for tracking the delivery of the message * @throws MqttException if an error occurs publishing the message @@ -117,23 +117,23 @@ public MqttDeliveryToken publish(MqttMessage message) throws MqttException, Mqtt token.internalTok.waitUntilSent(); return token; } - + /** * Returns the name of the queue or topic. - * + * * @return the name of this destination. */ public String getName() { return name; } - + /** * Create a PUBLISH packet from the specified message. */ private MqttPublish createPublish(MqttMessage message) { return new MqttPublish(this.getName(), message); } - + /** * Returns a string representation of this topic. * @return a string representation of this topic. @@ -141,21 +141,20 @@ private MqttPublish createPublish(MqttMessage message) { public String toString() { return getName(); } - + /** * Validate the topic name or topic filter - * + * * @param topicString topic name or filter * @param wildcardAllowed true if validate topic filter, false otherwise * @throws IllegalArgumentException if the topic is invalid */ - public static void validate(String topicString, boolean wildcardAllowed) + public static void validate(String topicString, boolean wildcardAllowed) throws IllegalArgumentException{ int topicLen = 0; try { topicLen = topicString.getBytes("UTF-8").length; } catch (UnsupportedEncodingException e) { - // throw new IllegalStateException(e); // Cannot use for Java 1.4.2 throw new IllegalStateException(e.getMessage()); } @@ -165,10 +164,8 @@ public static void validate(String topicString, boolean wildcardAllowed) // - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST // NOT encode to more than 65535 bytes if (topicLen < MIN_TOPIC_LEN || topicLen > MAX_TOPIC_LEN) { - //throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!", // Cannot use for Java 1.4.2 - // new Object[] { new Integer(MIN_TOPIC_LEN), new Integer(MAX_TOPIC_LEN) })); - String errorMessage = "Invalid topic length, should be in range["+ MIN_TOPIC_LEN + ", " + MAX_TOPIC_LEN + "]!"; - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!", + new Object[] { new Integer(MIN_TOPIC_LEN), new Integer(MAX_TOPIC_LEN) })); } // ******************************************************************************* @@ -189,8 +186,7 @@ public static void validate(String topicString, boolean wildcardAllowed) // - The multi-level wildcard must be the last character used within // the topic tree if (Strings.countMatches(topicString, MULTI_LEVEL_WILDCARD) > 1 - //|| (topicString.contains(MULTI_LEVEL_WILDCARD) && !topicString// Cannot use for Java 1.4.2 - || ((topicString.indexOf(MULTI_LEVEL_WILDCARD) != -1) && !topicString + || (topicString.contains(MULTI_LEVEL_WILDCARD) && !topicString .endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) { throw new IllegalArgumentException( "Invalid usage of multi-level wildcard in topic string: " @@ -217,7 +213,7 @@ public static void validate(String topicString, boolean wildcardAllowed) "The topic name MUST NOT contain any wildcard characters (#+)"); } } - + private static void validateSingleLevelWildcard(String topicString) { char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0); char topicLevelSeparatorChar = TOPIC_LEVEL_SEPARATOR.charAt(0); @@ -232,43 +228,42 @@ private static void validateSingleLevelWildcard(String topicString) { if (chars[i] == singleLevelWildcardChar) { // prev and next can be only '/' or none if (prev != topicLevelSeparatorChar && prev != NUL || next != topicLevelSeparatorChar && next != NUL) { -// throw new IllegalArgumentException(String.format( // Cannot use for Java 1.4.2 -// "Invalid usage of single-level wildcard in topic string '%s'!", -// new Object[] { topicString })); - String errorMessage = "Invalid usage of single-level wildcard in topic string '" + topicString + "'!"; - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format( + "Invalid usage of single-level wildcard in topic string '%s'!", + new Object[] { topicString })); + } } } } - + /** * Check the supplied topic name and filter match - * + * * @param topicFilter topic filter: wildcards allowed * @param topicName topic name: wildcards not allowed * @return true if the topic matches the filter * @throws IllegalArgumentException if the topic name or filter is invalid */ - public static boolean isMatched(String topicFilter, String topicName) + public static boolean isMatched(String topicFilter, String topicName) throws IllegalArgumentException { int curn = 0, curf = 0; int curn_end = topicName.length(); int curf_end = topicFilter.length(); - + MqttTopic.validate(topicFilter, true); MqttTopic.validate(topicName, false); if (topicFilter.equals(topicName)) { return true; } - + while (curf < curf_end && curn < curn_end) { if (topicName.charAt(curn) == '/' && topicFilter.charAt(curf) != '/') break; - if (topicFilter.charAt(curf) != '+' && topicFilter.charAt(curf) != '#' && + if (topicFilter.charAt(curf) != '+' && topicFilter.charAt(curf) != '#' && topicFilter.charAt(curf) != topicName.charAt(curn)) break; if (topicFilter.charAt(curf) == '+') @@ -285,5 +280,5 @@ else if (topicFilter.charAt(curf) == '#') return (curn == curn_end) && (curf == curf_end); } - + } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/ScheduledExecutorPingSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/ScheduledExecutorPingSender.java new file mode 100644 index 000000000..511909c9e --- /dev/null +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/ScheduledExecutorPingSender.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * Copyright (c) 2017 BMW Car IT GmbH. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ + +package org.eclipse.paho.client.mqttv3; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.paho.client.mqttv3.internal.ClientComms; +import org.eclipse.paho.client.mqttv3.logging.Logger; +import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; + +/** + * Default ping sender implementation + * + *

    This class implements the {@link IMqttPingSender} pinger interface + * allowing applications to send ping packet to server every keep alive interval. + *

    + * + * @see MqttPingSender + */ +public class ScheduledExecutorPingSender implements MqttPingSender { + private static final String CLASS_NAME = ScheduledExecutorPingSender.class.getName(); + private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME); + + private ClientComms comms; + private ScheduledExecutorService executorService; + private ScheduledFuture scheduledFuture; + private String clientid; + + public ScheduledExecutorPingSender(ScheduledExecutorService executorService) { + if (executorService == null) { + throw new IllegalArgumentException("ExecutorService cannot be null."); + } + this.executorService = executorService; + } + + public void init(ClientComms comms) { + if (comms == null) { + throw new IllegalArgumentException("ClientComms cannot be null."); + } + this.comms = comms; + clientid = comms.getClient().getClientId(); + } + + public void start() { + final String methodName = "start"; + + //@Trace 659=start timer for client:{0} + log.fine(CLASS_NAME, methodName, "659", new Object[]{ clientid }); + //Check ping after first keep alive interval. + schedule(comms.getKeepAlive()); + } + + public void stop() { + final String methodName = "stop"; + //@Trace 661=stop + log.fine(CLASS_NAME, methodName, "661", null); + if (scheduledFuture != null) { + scheduledFuture.cancel(true); + } + } + + public void schedule(long delayInMilliseconds) { + scheduledFuture = executorService.schedule(new PingRunnable(), delayInMilliseconds, TimeUnit.MILLISECONDS); + } + + private class PingRunnable implements Runnable { + private static final String methodName = "PingTask.run"; + + public void run() { + String originalThreadName = Thread.currentThread().getName(); + Thread.currentThread().setName("MQTT Ping: " + clientid); + //@Trace 660=Check schedule at {0} + log.fine(CLASS_NAME, methodName, "660", new Object[]{ new Long(System.currentTimeMillis()) }); + comms.checkForActivity(); + Thread.currentThread().setName(originalThreadName); + } + } +} diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java index 2d8e6d2be..4c9321256 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java @@ -50,8 +50,7 @@ public void start() { //@Trace 659=start timer for client:{0} log.fine(CLASS_NAME, methodName, "659", new Object[]{clientid}); - //timer = new Timer("MQTT Ping: " + clientid); // Cannot use for Java 1.4.2 - timer = new Timer(); + timer = new Timer("MQTT Ping: " + clientid); //Check ping after first keep alive interval. timer.schedule(new PingTask(), comms.getKeepAlive()); } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java index d2d1e2078..537693713 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2016 IBM Corp. + * Copyright (c) 2009, 2017 IBM Corp and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -17,6 +17,7 @@ * James Sutton - Ping Callback (bug 473928) * Ian Craggs - fix for NPE bug 470718 * James Sutton - Automatic Reconnect & Offline Buffering + * Jens Reimann - Fix issue #370 */ package org.eclipse.paho.client.mqttv3.internal; @@ -46,6 +47,9 @@ import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubRel; import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish; import org.eclipse.paho.client.mqttv3.internal.wire.MqttSuback; +import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe; +import org.eclipse.paho.client.mqttv3.internal.wire.MqttUnsubAck; +import org.eclipse.paho.client.mqttv3.internal.wire.MqttUnsubscribe; import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage; import org.eclipse.paho.client.mqttv3.logging.Logger; import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; @@ -480,7 +484,18 @@ private void restoreInflightMessages() { public void send(MqttWireMessage message, MqttToken token) throws MqttException { final String methodName = "send"; if (message.isMessageIdRequired() && (message.getMessageId() == 0)) { - message.setMessageId(getNextMessageId()); + if(message instanceof MqttPublish && (((MqttPublish) message).getMessage().getQos() != 0)){ + message.setMessageId(getNextMessageId()); + }else if(message instanceof MqttPubAck || + message instanceof MqttPubRec || + message instanceof MqttPubRel || + message instanceof MqttPubComp || + message instanceof MqttSubscribe || + message instanceof MqttSuback || + message instanceof MqttUnsubscribe || + message instanceof MqttUnsubAck){ + message.setMessageId(getNextMessageId()); + } } if (token != null ) { try { @@ -1340,7 +1355,9 @@ public int getMaxInFlight(){ */ protected void close() { inUseMsgIds.clear(); - pendingMessages.clear(); + if (pendingMessages != null) { + pendingMessages.clear(); + } pendingFlows.clear(); outboundQoS2.clear(); outboundQoS1.clear(); diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java index 94e1f9491..bb6d77c46 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java @@ -21,6 +21,9 @@ import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; import org.eclipse.paho.client.mqttv3.IMqttActionListener; import org.eclipse.paho.client.mqttv3.IMqttMessageListener; @@ -61,6 +64,9 @@ public class CommsCallback implements Runnable { private Object spaceAvailable = new Object(); private ClientState clientState; private boolean manualAcks = false; + private String threadName; + private final Semaphore runningSemaphore = new Semaphore(1); + private Future callbackFuture; CommsCallback(ClientComms clientComms) { this.clientComms = clientComms; @@ -77,8 +83,10 @@ public void setClientState(ClientState clientState) { /** * Starts up the Callback thread. * @param threadName The name of the thread + * @param executorService the {@link ExecutorService} */ - public void start(String threadName) { + public void start(String threadName, ExecutorService executorService) { + this.threadName = threadName; synchronized (lifecycle) { if (!running) { // Preparatory work before starting the background thread. @@ -88,8 +96,7 @@ public void start(String threadName) { running = true; quiescing = false; - callbackThread = new Thread(this, threadName); - callbackThread.start(); + callbackFuture = executorService.submit(this); } } } @@ -101,6 +108,9 @@ public void start(String threadName) { public void stop() { final String methodName = "stop"; synchronized (lifecycle) { + if (callbackFuture != null) { + callbackFuture.cancel(true); + } if (running) { // @TRACE 700=stopping log.fine(CLASS_NAME, methodName, "700"); @@ -114,8 +124,10 @@ public void stop() { workAvailable.notifyAll(); } // Wait for the thread to finish. - callbackThread.join(); + runningSemaphore.acquire(); } catch (InterruptedException ex) { + } finally { + runningSemaphore.release(); } } } @@ -139,6 +151,16 @@ public void setManualAcks(boolean manualAcks) { public void run() { final String methodName = "run"; + callbackThread = Thread.currentThread(); + callbackThread.setName(threadName); + + try { + runningSemaphore.acquire(); + } catch (InterruptedException e) { + running = false; + return; + } + while (running) { try { // If no work is currently available, then wait until there is some... @@ -196,8 +218,8 @@ public void run() { log.fine(CLASS_NAME, methodName, "714", null, ex); running = false; clientComms.shutdownConnection(null, new MqttException(ex)); - } finally { + runningSemaphore.release(); synchronized (spaceAvailable) { // Notify the spaceAvailable lock, to say that there's now // some space on the queue... @@ -481,4 +503,4 @@ protected boolean deliverMessage(String topicName, int messageId, MqttMessage aM return delivered; } -} \ No newline at end of file +} diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java index 25408d877..5bd2db426 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java @@ -17,6 +17,9 @@ import java.io.IOException; import java.io.InputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttToken; @@ -44,6 +47,10 @@ public class CommsReceiver implements Runnable { private CommsTokenStore tokenStore = null; private Thread recThread = null; private volatile boolean receiving; + private final Semaphore runningSemaphore = new Semaphore(1); + private String threadName; + private Future receiverFuture; + public CommsReceiver(ClientComms clientComms, ClientState clientState,CommsTokenStore tokenStore, InputStream in) { this.in = new MqttInputStream(clientState, in); @@ -55,17 +62,18 @@ public CommsReceiver(ClientComms clientComms, ClientState clientState,CommsToken /** * Starts up the Receiver's thread. - * @param threadName The name of the thread + * @param threadName the thread name. + * @param executorService used to execute the thread */ - public void start(String threadName) { + public void start(String threadName, ExecutorService executorService) { + this.threadName = threadName; final String methodName = "start"; //@TRACE 855=starting log.fine(CLASS_NAME,methodName, "855"); synchronized (lifecycle) { if (!running) { running = true; - recThread = new Thread(this, threadName); - recThread.start(); + receiverFuture = executorService.submit(this); } } } @@ -76,6 +84,9 @@ public void start(String threadName) { public void stop() { final String methodName = "stop"; synchronized (lifecycle) { + if (receiverFuture != null) { + receiverFuture.cancel(true); + } //@TRACE 850=stopping log.fine(CLASS_NAME,methodName, "850"); if (running) { @@ -84,14 +95,11 @@ public void stop() { if (!Thread.currentThread().equals(recThread)) { try { // Wait for the thread to finish. - // It is always a good idea to set a timeout. - // WARNING: the timeout must be correlated with the - // socket read timeout. - // Unfortunately we cannot access the socket here to - // get its read timeout. - recThread.join(1500); + runningSemaphore.acquire(); } catch (InterruptedException ex) { + } finally { + runningSemaphore.release(); } } } @@ -105,9 +113,18 @@ public void stop() { * Run loop to receive messages from the server. */ public void run() { + recThread = Thread.currentThread(); + recThread.setName(threadName); final String methodName = "run"; MqttToken token = null; + try { + runningSemaphore.acquire(); + } catch (InterruptedException e) { + running = false; + return; + } + while (running && (in != null)) { try { //@TRACE 852=network read message @@ -166,6 +183,7 @@ public void run() { } finally { receiving = false; + runningSemaphore.release(); } } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java index 6f06254ae..3697a98de 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -17,6 +17,10 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttToken; @@ -40,7 +44,11 @@ public class CommsSender implements Runnable { private ClientComms clientComms = null; private CommsTokenStore tokenStore = null; private Thread sendThread = null; - + + private String threadName; + private final Semaphore runningSemaphore = new Semaphore(1); + private Future senderFuture; + public CommsSender(ClientComms clientComms, ClientState clientState, CommsTokenStore tokenStore, OutputStream out) { this.out = new MqttOutputStream(clientState, out); this.clientComms = clientComms; @@ -48,17 +56,18 @@ public CommsSender(ClientComms clientComms, ClientState clientState, CommsTokenS this.tokenStore = tokenStore; log.setResourceName(clientComms.getClient().getClientId()); } - + /** * Starts up the Sender thread. - * @param threadName The name of the thread + * @param threadName the threadname + * @param executorService used to execute the thread */ - public void start(String threadName) { + public void start(String threadName, ExecutorService executorService) { + this.threadName = threadName; synchronized (lifecycle) { if (!running) { running = true; - sendThread = new Thread(this, threadName); - sendThread.start(); + senderFuture = executorService.submit(this); } } } @@ -68,22 +77,26 @@ public void start(String threadName) { */ public void stop() { final String methodName = "stop"; - + synchronized (lifecycle) { + if (senderFuture != null) { + senderFuture.cancel(true); + } //@TRACE 800=stopping sender log.fine(CLASS_NAME,methodName,"800"); if (running) { running = false; if (!Thread.currentThread().equals(sendThread)) { try { - while(sendThread.isAlive()){ + while (running) { // first notify get routine to finish clientState.notifyQueueLock(); // Wait for the thread to finish. - sendThread.join(100); + runningSemaphore.tryAcquire(100, TimeUnit.MILLISECONDS); } - } - catch (InterruptedException ex) { + } catch (InterruptedException ex) { + } finally { + runningSemaphore.release(); } } } @@ -92,54 +105,69 @@ public void stop() { log.fine(CLASS_NAME,methodName,"801"); } } - + public void run() { + sendThread = Thread.currentThread(); + sendThread.setName(threadName); final String methodName = "run"; MqttWireMessage message = null; - while (running && (out != null)) { - try { - message = clientState.get(); - if (message != null) { - //@TRACE 802=network send key={0} msg={1} - log.fine(CLASS_NAME,methodName,"802", new Object[] {message.getKey(),message}); - - if (message instanceof MqttAck) { - out.write(message); - out.flush(); - } else { - MqttToken token = tokenStore.getToken(message); - // While quiescing the tokenstore can be cleared so need - // to check for null for the case where clear occurs - // while trying to send a message. - if (token != null) { - synchronized (token) { - out.write(message); - try { - out.flush(); - } catch (IOException ex) { - // The flush has been seen to fail on disconnect of a SSL socket - // as disconnect is in progress this should not be treated as an error - if (!(message instanceof MqttDisconnect)) { - throw ex; + + try { + runningSemaphore.acquire(); + } catch (InterruptedException e) { + running = false; + return; + } + + try { + while (running && (out != null)) { + try { + message = clientState.get(); + if (message != null) { + //@TRACE 802=network send key={0} msg={1} + log.fine(CLASS_NAME,methodName,"802", new Object[] {message.getKey(),message}); + + if (message instanceof MqttAck) { + out.write(message); + out.flush(); + } else { + MqttToken token = tokenStore.getToken(message); + // While quiescing the tokenstore can be cleared so need + // to check for null for the case where clear occurs + // while trying to send a message. + if (token != null) { + synchronized (token) { + out.write(message); + try { + out.flush(); + } catch (IOException ex) { + // The flush has been seen to fail on disconnect of a SSL socket + // as disconnect is in progress this should not be treated as an error + if (!(message instanceof MqttDisconnect)) { + throw ex; + } } + clientState.notifySent(message); } - clientState.notifySent(message); } } - } - } else { // null message - //@TRACE 803=get message returned null, stopping} - log.fine(CLASS_NAME,methodName,"803"); + } else { // null message + //@TRACE 803=get message returned null, stopping} + log.fine(CLASS_NAME,methodName,"803"); - running = false; + running = false; + } + } catch (MqttException me) { + handleRunException(message, me); + } catch (Exception ex) { + handleRunException(message, ex); } - } catch (MqttException me) { - handleRunException(message, me); - } catch (Exception ex) { - handleRunException(message, ex); - } - } // end while - + } // end while + } finally { + running = false; + runningSemaphore.release(); + } + //@TRACE 805=< log.fine(CLASS_NAME, methodName,"805"); diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java index 53b199f29..aa6bb8d53 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java @@ -92,9 +92,7 @@ public void onSuccess(IMqttToken token) { userToken.internalTok.notifyComplete(); userToken.internalTok.setClient(this.client); // fix bug 469527 - maybe should be set elsewhere? - if(reconnect){ - comms.notifyReconnect(); - } + comms.notifyConnect(); if (userCallback != null) { userToken.setUserContext(userContext); diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java deleted file mode 100644 index 29c34557b..000000000 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java +++ /dev/null @@ -1,95 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Dave Locke - initial API and implementation and/or initial documentation - */ -package org.eclipse.paho.client.mqttv3.internal; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; - -import org.eclipse.paho.client.mqttv3.MqttException; - - -/** - * Special comms class that allows an MQTT client to use a non TCP / optimised - * mechanism to talk to an MQTT server when running in the same JRE instance as the - * MQTT server. - * - * This class checks for the existence of the optimised comms adatper class i.e. the one - * that provides the optimised communication mechanism. If not available the request - * to connect using the optimised mechanism is rejected. - * - * The only known server that implements this is the microbroker:- an MQTT server that - * ships with a number of IBM products. - */ -public class LocalNetworkModule implements NetworkModule { - private Class localListener; - private String brokerName; - private Object localAdapter; - - public LocalNetworkModule(String brokerName) { - this.brokerName = brokerName; - } - - public void start() throws IOException, MqttException{ - if (!ExceptionHelper.isClassAvailable("com.ibm.mqttdirect.modules.local.bindings.localListener")) { - throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR); - } - try { - localListener = Class.forName("com.ibm.mqttdirect.modules.local.bindings.localListener"); - Method connect_m = localListener.getMethod("connect", new Class[]{ java.lang.String.class }); - localAdapter = connect_m.invoke(null,new Object[]{ brokerName }); - } catch(Exception e) { - } - if(localAdapter == null) { - throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR); - } - } - - public InputStream getInputStream() throws IOException { - InputStream stream = null; - try { - Method m = localListener.getMethod("getClientInputStream",new Class[]{}); - stream = (InputStream)m.invoke(this.localAdapter,new Object[]{}); - } catch(Exception e) { - } - return stream; - } - - public OutputStream getOutputStream() throws IOException { - OutputStream stream = null; - try { - Method m = localListener.getMethod("getClientOutputStream",new Class[]{}); - stream = (OutputStream)m.invoke(this.localAdapter,new Object[]{}); - } catch(Exception e) { - } - return stream; - } - - public void stop() throws IOException { - if (localAdapter != null) { - try { - Method m = localListener.getMethod("close",new Class[]{}); - m.invoke(this.localAdapter,new Object[]{}); - } catch(Exception e) { - } - } - } - - public String getServerURI() { - return "local://" + brokerName; - } -} diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java index f0a78cd17..a20f3c6b3 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java @@ -17,6 +17,8 @@ import java.io.IOException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -33,6 +35,7 @@ public class SSLNetworkModule extends TCPNetworkModule { private String[] enabledCiphers; private int handshakeTimeoutSecs; + private HostnameVerifier hostnameVerifier; private String host; private int port; @@ -88,6 +91,14 @@ public void setSSLhandshakeTimeout(int timeout) { this.handshakeTimeoutSecs = timeout; } + public HostnameVerifier getSSLHostnameVerifier() { + return hostnameVerifier; + } + + public void setSSLHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + public void start() throws IOException, MqttException { super.start(); setEnabledCiphers(enabledCiphers); @@ -95,6 +106,10 @@ public void start() throws IOException, MqttException { // RTC 765: Set a timeout to avoid the SSL handshake being blocked indefinitely socket.setSoTimeout(this.handshakeTimeoutSecs*1000); ((SSLSocket)socket).startHandshake(); + if (hostnameVerifier != null) { + SSLSession session = ((SSLSocket)socket).getSession(); + hostnameVerifier.verify(host, session); + } // reset timeout to default value socket.setSoTimeout(soTimeout); } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java index 9a8e90765..51fb34677 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -25,13 +25,14 @@ import java.net.SocketAddress; import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.logging.Logger; import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; /** - * A network module for connecting over TCP. + * A network module for connecting over TCP. */ public class TCPNetworkModule implements NetworkModule { private static final String CLASS_NAME = TCPNetworkModule.class.getName(); @@ -42,7 +43,7 @@ public class TCPNetworkModule implements NetworkModule { private String host; private int port; private int conTimeout; - + /** * Constructs a new TCPNetworkModule using the specified host and * port. The supplied SocketFactory is used to supply the network @@ -57,7 +58,7 @@ public TCPNetworkModule(SocketFactory factory, String host, int port, String res this.factory = factory; this.host = host; this.port = port; - + } /** @@ -72,15 +73,18 @@ public void start() throws IOException, MqttException { // socket = factory.createSocket(host, port, localAddr, 0); // @TRACE 252=connect to host {0} port {1} timeout {2} log.fine(CLASS_NAME,methodName, "252", new Object[] {host, new Integer(port), new Long(conTimeout*1000)}); - SocketAddress sockaddr = new InetSocketAddress(InetAddress.getByName(host), port); - socket = factory.createSocket(); - // Set a read timeout on the socket. - // If you change the value here you should also change - // the value in CommsReceiver.stop(). - socket.setSoTimeout(1000); - socket.connect(sockaddr, conTimeout*1000); + SocketAddress sockaddr = new InetSocketAddress(host, port); + if (factory instanceof SSLSocketFactory) { + // SNI support + Socket tempsocket = new Socket(); + tempsocket.connect(sockaddr, conTimeout*1000); + socket = ((SSLSocketFactory)factory).createSocket(tempsocket, host, port, true); + } else { + socket = factory.createSocket(); + socket.connect(sockaddr, conTimeout*1000); + } - // SetTcpNoDelay was originally set ot true disabling Nagle's algorithm. + // SetTcpNoDelay was originally set ot true disabling Nagle's algorithm. // This should not be required. // socket.setTcpNoDelay(true); // TCP_NODELAY on, which means we do not use Nagle's algorithm } @@ -94,7 +98,7 @@ public void start() throws IOException, MqttException { public InputStream getInputStream() throws IOException { return socket.getInputStream(); } - + public OutputStream getOutputStream() throws IOException { return socket.getOutputStream(); } @@ -127,7 +131,7 @@ public void stop() throws IOException { socket.close(); } } - + /** * Set the maximum time to wait for a socket to be established * @param timeout The connection timeout diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java new file mode 100644 index 000000000..2caee99e9 --- /dev/null +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java @@ -0,0 +1,49 @@ +package org.eclipse.paho.client.mqttv3.internal.websocket; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule; + +class ExtendedByteArrayOutputStream extends ByteArrayOutputStream { + + final WebSocketNetworkModule webSocketNetworkModule; + final WebSocketSecureNetworkModule webSocketSecureNetworkModule; + + ExtendedByteArrayOutputStream(WebSocketNetworkModule module) { + this.webSocketNetworkModule = module; + this.webSocketSecureNetworkModule = null; + } + + ExtendedByteArrayOutputStream(WebSocketSecureNetworkModule module) { + this.webSocketNetworkModule = null; + this.webSocketSecureNetworkModule = module; + } + + public void flush() throws IOException { + final ByteBuffer byteBuffer; + synchronized (this) { + byteBuffer = ByteBuffer.wrap(toByteArray()); + reset(); + } + WebSocketFrame frame = new WebSocketFrame((byte)0x02, true, byteBuffer.array()); + byte[] rawFrame = frame.encodeFrame(); + getSocketOutputStream().write(rawFrame); + getSocketOutputStream().flush(); + + } + + OutputStream getSocketOutputStream() throws IOException { + + if(webSocketNetworkModule != null ){ + return webSocketNetworkModule.getSocketOutputStream(); + } + if(webSocketSecureNetworkModule != null){ + return webSocketSecureNetworkModule.getSocketOutputStream(); + } + return null; + } + +} \ No newline at end of file diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketHandshake.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketHandshake.java index 1738d45e8..92be90137 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketHandshake.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketHandshake.java @@ -3,11 +3,11 @@ * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. + * and Eclipse Distribution License v1.0 which accompany this distribution. * - * The Eclipse Public License is available at + * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at + * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: @@ -28,11 +28,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.UUID; /** * Helper class to execute a WebSocket Handshake. */ public class WebSocketHandshake { - + // Do not change: https://tools.ietf.org/html/rfc6455#section-1.3 private static final String ACCEPT_SALT = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static final String SHA1_PROTOCOL = "SHA1"; @@ -51,8 +52,8 @@ public class WebSocketHandshake { String uri; String host; int port; - - + + public WebSocketHandshake(InputStream input, OutputStream output, String uri, String host, int port){ this.input = input; this.output = output; @@ -61,19 +62,20 @@ public WebSocketHandshake(InputStream input, OutputStream output, String uri, St this.port = port; } - + /** * Executes a Websocket Handshake. * Will throw an IOException if the handshake fails * @throws IOException thrown if an exception occurs during the handshake */ public void execute() throws IOException { - String key = "mqtt3-" + (System.currentTimeMillis()/1000); - String b64Key = Base64.encode(key); + byte[] key = new byte[16]; + System.arraycopy(UUID.randomUUID().toString().getBytes(), 0, key, 0, 16); + String b64Key = Base64.encodeBytes(key); sendHandshakeRequest(b64Key); receiveHandshakeResponse(b64Key); } - + /** * Builds and sends the HTTP Header GET Request * for the socket. @@ -84,11 +86,9 @@ private void sendHandshakeRequest(String key) throws IOException{ try { String path = "/mqtt"; URI srvUri = new URI(uri); - //if (srvUri.getRawPath() != null && !srvUri.getRawPath().isEmpty()) { // Cannot use for Java 1.4.2 - if (srvUri.getRawPath() != null && srvUri.getRawPath().length() != 0) { + if (srvUri.getRawPath() != null && !srvUri.getRawPath().isEmpty()) { path = srvUri.getRawPath(); - //if (srvUri.getRawQuery() != null && !srvUri.getRawQuery().isEmpty()) { // Cannot use for Java 1.4.2 - if (srvUri.getRawQuery() != null && srvUri.getRawQuery().length() != 0) { + if (srvUri.getRawQuery() != null && !srvUri.getRawQuery().isEmpty()) { path += "?" + srvUri.getRawQuery(); } } @@ -105,7 +105,7 @@ private void sendHandshakeRequest(String key) throws IOException{ pw.print("Upgrade: websocket" + LINE_SEPARATOR); pw.print("Connection: Upgrade" + LINE_SEPARATOR); pw.print("Sec-WebSocket-Key: " + key + LINE_SEPARATOR); - pw.print("Sec-WebSocket-Protocol: mqttv3.1" + LINE_SEPARATOR); + pw.print("Sec-WebSocket-Protocol: mqtt" + LINE_SEPARATOR); pw.print("Sec-WebSocket-Version: 13" + LINE_SEPARATOR); String userInfo = srvUri.getUserInfo(); @@ -116,11 +116,10 @@ private void sendHandshakeRequest(String key) throws IOException{ pw.print(LINE_SEPARATOR); pw.flush(); } catch (URISyntaxException e) { - // throw new IllegalStateException(e.getMessage()); // Cannot use for Java 1.4.2 throw new IllegalStateException(e.getMessage()); } } - + /** * Receives the Handshake response and verifies that it is valid. * @param key Base64 encoded key @@ -145,8 +144,7 @@ private void receiveHandshakeResponse(String key) throws IOException { } String upgradeHeader = (String) headerMap.get(HTTP_HEADER_UPGRADE); - //if(!upgradeHeader.toLowerCase().contains(HTTP_HEADER_UPGRADE_WEBSOCKET)){ // Cannot use for Java 1.4.2 - if(upgradeHeader.toLowerCase().indexOf(HTTP_HEADER_UPGRADE_WEBSOCKET) == -1){ + if(upgradeHeader == null || !upgradeHeader.toLowerCase().contains(HTTP_HEADER_UPGRADE_WEBSOCKET)){ throw new IOException("WebSocket Response header: Incorrect upgrade."); } @@ -158,7 +156,7 @@ private void receiveHandshakeResponse(String key) throws IOException { if(!headerMap.containsKey(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT)){ throw new IOException("WebSocket Response header: Missing Sec-WebSocket-Accept"); } - + try { verifyWebSocketKey(key, (String)headerMap.get(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT)); } catch (NoSuchAlgorithmException e) { @@ -168,7 +166,7 @@ private void receiveHandshakeResponse(String key) throws IOException { } } - + /** * Returns a Hashmap of HTTP headers * @param ArrayList of headers @@ -183,7 +181,7 @@ private Map getHeaders(ArrayList headers){ } return headerMap; } - + /** * Verifies that the Accept key provided is correctly built from the * original key sent. @@ -199,9 +197,9 @@ private void verifyWebSocketKey(String key, String accept) throws NoSuchAlgorith String encodedSha1Bytes = Base64.encodeBytes(sha1Bytes).trim(); if(!encodedSha1Bytes.equals(accept.trim())){ throw new HandshakeFailedException(); - } + } } - + /** * Returns the sha1 byte array of the provided string. * @param input diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketNetworkModule.java index 67fe408c5..5524a3158 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketNetworkModule.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketNetworkModule.java @@ -46,21 +46,7 @@ public class WebSocketNetworkModule extends TCPNetworkModule { * This allows us to encode the MQTT payload into a WebSocket * Frame before passing it through to the real socket. */ - private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(){ - - public void flush() throws IOException { - final ByteBuffer byteBuffer; - synchronized (this) { - byteBuffer = ByteBuffer.wrap(toByteArray()); - reset(); - } - WebSocketFrame frame = new WebSocketFrame((byte)0x02, true, byteBuffer.array()); - byte[] rawFrame = frame.encodeFrame(); - getSocketOutputStream().write(rawFrame); - getSocketOutputStream().flush(); - - } - }; + private ByteArrayOutputStream outputStream = new ExtendedByteArrayOutputStream(this); public WebSocketNetworkModule(SocketFactory factory, String uri, String host, int port, String resourceContext){ super(factory, host, port, resourceContext); @@ -80,11 +66,11 @@ public void start() throws IOException, MqttException { webSocketReceiver.start("webSocketReceiver"); } - private OutputStream getSocketOutputStream() throws IOException { + OutputStream getSocketOutputStream() throws IOException { return super.getOutputStream(); } - private InputStream getSocketInputStream() throws IOException { + InputStream getSocketInputStream() throws IOException { return super.getInputStream(); } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModule.java index f2b9dcbac..8224103cd 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModule.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModule.java @@ -46,21 +46,7 @@ public class WebSocketSecureNetworkModule extends SSLNetworkModule{ * This allows us to encode the MQTT payload into a WebSocket * Frame before passing it through to the real socket. */ - private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(){ - - public void flush() throws IOException { - final ByteBuffer byteBuffer; - synchronized (this) { - byteBuffer = ByteBuffer.wrap(toByteArray()); - reset(); - } - WebSocketFrame frame = new WebSocketFrame((byte)0x02, true, byteBuffer.array()); - byte[] rawFrame = frame.encodeFrame(); - getSocketOutputStream().write(rawFrame); - getSocketOutputStream().flush(); - - } - }; + private ByteArrayOutputStream outputStream = new ExtendedByteArrayOutputStream(this); public WebSocketSecureNetworkModule(SSLSocketFactory factory, String uri, String host, int port, String clientId) { super(factory, host, port, clientId); @@ -80,11 +66,11 @@ public void start() throws IOException, MqttException { } - private OutputStream getSocketOutputStream() throws IOException { + OutputStream getSocketOutputStream() throws IOException { return super.getOutputStream(); } - private InputStream getSocketInputStream() throws IOException { + InputStream getSocketInputStream() throws IOException { return super.getInputStream(); } @@ -112,6 +98,8 @@ public void stop() throws IOException { public String getServerURI() { return "wss://" + host + ":" + port; } + + } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java index 412bf35fe..95b9cea9d 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java @@ -87,8 +87,8 @@ public void write(MqttWireMessage message) throws IOException, MqttException { clientState.notifySentBytes(length); } - // @TRACE 500= sent {0} - log.fine(CLASS_NAME, methodName, "500", new Object[]{message}); + // @TRACE 529= sent {0} + log.fine(CLASS_NAME, methodName, "529", new Object[]{message}); } } diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java index 33e03949c..e86e7e9d4 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java +++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java @@ -298,5 +298,6 @@ public void clear() throws MqttPersistenceException { for (int i=0; i= MIN && ch < (MAX +1); - } + /** * Checks if a CharSequence is empty ("") or null. diff --git a/org.eclipse.paho.client.mqttv3/src/main/resources/org/eclipse/paho/client/mqttv3/internal/nls/logcat.properties b/org.eclipse.paho.client.mqttv3/src/main/resources/org/eclipse/paho/client/mqttv3/internal/nls/logcat.properties index e3e8957ee..b7d07546f 100644 --- a/org.eclipse.paho.client.mqttv3/src/main/resources/org/eclipse/paho/client/mqttv3/internal/nls/logcat.properties +++ b/org.eclipse.paho.client.mqttv3/src/main/resources/org/eclipse/paho/client/mqttv3/internal/nls/logcat.properties @@ -80,6 +80,7 @@ 516=Restoring all buffered messages. 517=Un-Persisting Buffered message key={0} 518=Failed to Un-Persist Buffered message key={0} +529=Sent {0} 600=> 601=key={0} message={1} 602=key={0} exception diff --git a/org.eclipse.paho.jmeclient/org.eclipse.paho.jmeclient.mqttv3/META-INF/MANIFEST.MF b/org.eclipse.paho.jmeclient/org.eclipse.paho.jmeclient.mqttv3/META-INF/MANIFEST.MF index 80420bf77..cb7578b0a 100644 --- a/org.eclipse.paho.jmeclient/org.eclipse.paho.jmeclient.mqttv3/META-INF/MANIFEST.MF +++ b/org.eclipse.paho.jmeclient/org.eclipse.paho.jmeclient.mqttv3/META-INF/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Bundle-Name: MQTTv3_JMEClient Plug-in Bundle-SymbolicName: org.eclipse.paho.jmeclient.mqttv3;singleton:=true -Bundle-Version: 1.1.1 +Bundle-Version: 1.2.0 Bundle-ManifestVersion: 2 Import-Package: javax.net;resolution:=optional, javax.net.ssl;resolution:=optional diff --git a/org.eclipse.paho.sample.utility/pom.xml b/org.eclipse.paho.sample.utility/pom.xml index f9bd9a1f9..2671696a4 100644 --- a/org.eclipse.paho.sample.utility/pom.xml +++ b/org.eclipse.paho.sample.utility/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.mqtt.utility @@ -71,7 +71,7 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.1.1 + 1.2.0 diff --git a/org.eclipse.paho.ui/org.eclipse.paho.ui.app/org.eclipse.paho.ui.app.product b/org.eclipse.paho.ui/org.eclipse.paho.ui.app/org.eclipse.paho.ui.app.product index 1f6de8b13..1e76afd57 100644 --- a/org.eclipse.paho.ui/org.eclipse.paho.ui.app/org.eclipse.paho.ui.app.product +++ b/org.eclipse.paho.ui/org.eclipse.paho.ui.app/org.eclipse.paho.ui.app.product @@ -1,7 +1,7 @@ - + diff --git a/org.eclipse.paho.ui/org.eclipse.paho.ui.app/pom.xml b/org.eclipse.paho.ui/org.eclipse.paho.ui.app/pom.xml index 2522f0bb5..999d41c52 100644 --- a/org.eclipse.paho.ui/org.eclipse.paho.ui.app/pom.xml +++ b/org.eclipse.paho.ui/org.eclipse.paho.ui.app/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho org.eclipse.paho.ui - 1.1.1 + 1.2.0 org.eclipse.paho.ui.app diff --git a/org.eclipse.paho.ui/org.eclipse.paho.ui.core/META-INF/MANIFEST.MF b/org.eclipse.paho.ui/org.eclipse.paho.ui.core/META-INF/MANIFEST.MF index 0cb563ab7..f67d081d9 100644 --- a/org.eclipse.paho.ui/org.eclipse.paho.ui.core/META-INF/MANIFEST.MF +++ b/org.eclipse.paho.ui/org.eclipse.paho.ui.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %bundle.name Bundle-SymbolicName: org.eclipse.paho.ui.core;singleton:=true -Bundle-Version: 1.1.1 +Bundle-Version: 1.2.0 Bundle-Activator: org.eclipse.paho.mqtt.ui.Activator Bundle-Vendor: %bundle.provider Require-Bundle: org.eclipse.ui, diff --git a/org.eclipse.paho.ui/org.eclipse.paho.ui.core/pom.xml b/org.eclipse.paho.ui/org.eclipse.paho.ui.core/pom.xml index 37ebf33b2..44ad19f09 100644 --- a/org.eclipse.paho.ui/org.eclipse.paho.ui.core/pom.xml +++ b/org.eclipse.paho.ui/org.eclipse.paho.ui.core/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho org.eclipse.paho.ui - 1.1.1 + 1.2.0 org.eclipse.paho.ui.core diff --git a/org.eclipse.paho.ui/org.eclipse.paho.ui.plugin/pom.xml b/org.eclipse.paho.ui/org.eclipse.paho.ui.plugin/pom.xml index f72daf294..f63f2382f 100644 --- a/org.eclipse.paho.ui/org.eclipse.paho.ui.plugin/pom.xml +++ b/org.eclipse.paho.ui/org.eclipse.paho.ui.plugin/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho org.eclipse.paho.ui - 1.1.1 + 1.2.0 org.eclipse.paho.ui.plugin @@ -87,7 +87,7 @@ org.eclipse.paho org.eclipse.paho.ui.core - 1.1.1 + 1.2.0 diff --git a/org.eclipse.paho.ui/pom.xml b/org.eclipse.paho.ui/pom.xml index 894b41294..7759d6e92 100644 --- a/org.eclipse.paho.ui/pom.xml +++ b/org.eclipse.paho.ui/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho java-parent - 1.1.1 + 1.2.0 org.eclipse.paho.ui diff --git a/pom.xml b/pom.xml index 1cdb0c176..6916f2111 100644 --- a/pom.xml +++ b/pom.xml @@ -4,13 +4,13 @@ org.eclipse.paho java-parent pom - 1.1.1 + 1.2.0 - 1.4 + 1.7 - 1.6 + 1.7 UTF-8 ${maven.build.timestamp} 0.20.0 @@ -171,6 +171,11 @@ tycho-pack200b-plugin ${tycho.version} + + org.eluder.coveralls + coveralls-maven-plugin + 4.3.0 + diff --git a/test/tls-testing/Dockerfile b/test/tls-testing/Dockerfile new file mode 100644 index 000000000..78270feaf --- /dev/null +++ b/test/tls-testing/Dockerfile @@ -0,0 +1,3 @@ +FROM eclipse-mosquitto:latest +COPY mosquitto-docker.conf /mosquitto/config/mosquitto.conf +COPY keys /mosquitto/config/keys diff --git a/test/tls-testing/keys/all-ca.crt b/test/tls-testing/keys/all-ca.crt new file mode 100644 index 000000000..592d8e588 --- /dev/null +++ b/test/tls-testing/keys/all-ca.crt @@ -0,0 +1,74 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:26:78:40:ae:b2:ad:2f:26:12:0a:d5:b1:18: + 80:16:d8:88:be:0b:42:ce:32:ad:12:d5:f5:78:1b: + 35:28:f2:13:1b:05:09:fb:7e:d7:d9:a1:8a:0d:4a: + fe:95:37:d4:16:75:83:e4:6a:44:34:33:57:2e:49: + ba:bc:b4:cf:d0:c0:87:e0:bc:f0:60:76:14:00:d6: + eb:cb:f6:db:b3:43:f1:c8:4d:4a:0a:bb:e0:37:7c: + 8e:93:1f:a0:87:68:59:fe:0c:25:40:f3:7c:fd:71: + 90:55:ef:de:18:b4:08:86:c9:75:c2:99:2f:ce:12: + bf:c5:5e:cf:5f:f1:06:53:07 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 48:ec:d7:80:8a:8f:82:a6:42:b1:89:2c:b9:4b:6d:0a:37:b8: + 72:19:05:de:75:80:0c:d6:41:97:b2:d7:fe:99:cb:7e:c4:0e: + 77:97:09:a8:9f:87:ff:0b:de:3f:1c:dc:1e:fe:09:36:a7:f5: + 54:9a:85:4e:fb:6f:27:fe:0f:29:45:61:8d:07:c6:0c:da:37: + 3d:a3:69:4b:82:71:e6:24:e0:87:a6:ee:d5:87:61:dd:8f:08: + fe:33:a6:1f:ae:b2:ae:1f:d8:2c:20:c8:a6:fc:33:0e:82:68: + 80:23:61:10:ad:5c:1d:80:d6:b1:5f:e4:af:66:6d:63:10:e4: + 96:e4 +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGAxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBANwmeECusq0vJhIK1bEYgBbYiL4LQs4yrRLV9XgbNSjyExsF +Cft+19mhig1K/pU31BZ1g+RqRDQzVy5Jury0z9DAh+C88GB2FADW68v227ND8chN +Sgq74Dd8jpMfoIdoWf4MJUDzfP1xkFXv3hi0CIbJdcKZL84Sv8Vez1/xBlMHAgMB +AAGjUDBOMB0GA1UdDgQWBBQpTW7H8vdxctonnJyr2gcdR5zYQTAfBgNVHSMEGDAW +gBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBAEjs14CKj4KmQrGJLLlLbQo3uHIZBd51gAzWQZey1/6Zy37EDneXCaif +h/8L3j8c3B7+CTan9VSahU77byf+DylFYY0HxgzaNz2jaUuCceYk4Iem7tWHYd2P +CP4zph+usq4f2CwgyKb8Mw6CaIAjYRCtXB2A1rFf5K9mbWMQ5Jbk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- diff --git a/test/tls-testing/keys/server/server-mitm.crt b/test/tls-testing/keys/server/server-mitm.crt new file mode 100644 index 000000000..7f21fd309 --- /dev/null +++ b/test/tls-testing/keys/server/server-mitm.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 19 14:03:57 2013 GMT + Not After : Aug 18 14:03:57 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=m2m.example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:9f:f2:ee:40:bb:b6:e9:f9:2f:c1:79:ef:9b:a7: + 39:27:15:09:bd:48:44:b6:df:1e:92:eb:dd:1d:4e: + 36:d9:3a:06:80:d3:d1:33:c3:d7:6b:70:01:76:a0: + 53:2b:31:8e:96:6c:72:3a:ae:df:52:19:12:9b:ae: + 55:0b:0a:49:f4:52:b4:34:4c:24:39:45:f6:35:89: + 42:f8:92:86:82:9f:a4:fb:fd:db:b1:19:60:6f:fc: + 1e:22:a7:02:ec:61:07:57:f8:8c:cb:24:64:d7:89: + 39:ee:d0:da:40:04:55:16:8c:a0:78:38:7f:df:59: + 3e:75:6a:53:01:39:f8:3f:f5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 22:EC:63:4C:22:BF:83:04:5D:37:AA:46:49:5D:03:39:AE:50:56:95 + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + b6:b6:65:ec:fe:35:7d:b9:ce:45:2d:be:78:e2:55:79:a0:b0: + 4c:df:07:43:9f:ce:2f:77:61:b8:a0:43:f4:e4:33:51:a1:2d: + f7:d9:cb:1f:b9:ba:a5:33:2e:18:e1:51:29:26:d6:32:93:3d: + f9:19:ac:31:1a:90:1c:1a:56:8b:96:d3:a4:0b:ae:0a:e3:00: + 09:82:81:3f:d7:3a:52:de:9c:84:d7:bb:83:d1:0a:f2:28:be: + 1d:a2:a8:4b:0d:05:7a:e5:ca:cf:f7:23:7d:b3:65:0a:23:cb: + 6a:78:a6:8e:4d:29:2c:b2:5d:98:96:10:b7:cc:fd:04:04:d8: + 7f:7b +-----BEGIN CERTIFICATE----- +MIICzTCCAjagAwIBAgIBAjANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDgxOTE0MDM1 +N1oXDTE4MDgxODE0MDM1N1owfDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRgwFgYDVQQDDA9tMm0uZXhhbXBsZS5vcmcw +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ/y7kC7tun5L8F575unOScVCb1I +RLbfHpLr3R1ONtk6BoDT0TPD12twAXagUysxjpZscjqu31IZEpuuVQsKSfRStDRM +JDlF9jWJQviShoKfpPv927EZYG/8HiKnAuxhB1f4jMskZNeJOe7Q2kAEVRaMoHg4 +f99ZPnVqUwE5+D/1AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQi7GNMIr+D +BF03qkZJXQM5rlBWlTAfBgNVHSMEGDAWgBQpTW7H8vdxctonnJyr2gcdR5zYQTAN +BgkqhkiG9w0BAQUFAAOBgQC2tmXs/jV9uc5FLb544lV5oLBM3wdDn84vd2G4oEP0 +5DNRoS332csfubqlMy4Y4VEpJtYykz35GawxGpAcGlaLltOkC64K4wAJgoE/1zpS +3pyE17uD0QryKL4doqhLDQV65crP9yN9s2UKI8tqeKaOTSkssl2YlhC3zP0EBNh/ +ew== +-----END CERTIFICATE----- diff --git a/test/tls-testing/keys/server/server-mitm.key b/test/tls-testing/keys/server/server-mitm.key new file mode 100644 index 000000000..cbde88546 --- /dev/null +++ b/test/tls-testing/keys/server/server-mitm.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCf8u5Au7bp+S/Bee+bpzknFQm9SES23x6S690dTjbZOgaA09Ez +w9drcAF2oFMrMY6WbHI6rt9SGRKbrlULCkn0UrQ0TCQ5RfY1iUL4koaCn6T7/dux +GWBv/B4ipwLsYQdX+IzLJGTXiTnu0NpABFUWjKB4OH/fWT51alMBOfg/9QIDAQAB +AoGBAJq4AOM5MQxOcHV0n+Qu88fsP8mKuH00gnyYDwEkAUZLLLM9MaYjEKPqal/3 +YbvsagLzz+MX+QmPG0LgdX8CatmV+Y1Ot1aSUykB3y4xJ0K4r5UdgXq3VjQHe0Es +HcFEfd4i+U/TV2XAMi1T70XDE/B1tL7pR8PH1Ut/+jtL4RRBAkEAzwv4zQPcvc5k +oGt6EHhOZCMtdMNhzmlJM6cqOTkzxtAzPo7f18UZ4xVfnL3RVgzDI9ygxQUq2fGn +JP2Y6vd1uQJBAMXEPpjMhb9Z6shK9piEs9trhkuO0rGbCrf1lSnYTTdzojtGM0pV +WiHf61sNq3aB8fvyIBlPkomNhGPjVIdkOh0CQBU1O7n8UEsc7g/Eko9HU3hMZiaU +sNsMy9O/psKPym7t+qHIzsLyTJGHKfELPHsc+Ywd2FX2Doqehylx1I0U5fkCQQC/ +OjT09r5U+pMFPUcYzyhCgMQIh3+/cJV6r6KeJaL0fYFfI1AxDjrrCmO/VYZ4PspH +viW/k6u0Cfz7wTNY/1cZAkBGSivOyQ3Hocj/6aWPWgagMeEwdS9/+7xyeQunigV6 +zf+0RkX0jzHv2uvrpVECt97BLBOmKdQFogKw24XQ9+B4 +-----END RSA PRIVATE KEY----- diff --git a/test/tls-testing/keys/server/server.crt b/test/tls-testing/keys/server/server.crt new file mode 100644 index 000000000..bb02ae1ad --- /dev/null +++ b/test/tls-testing/keys/server/server.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 19 14:03:57 2013 GMT + Not After : Aug 18 14:03:57 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=m2m.eclipse.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a1:17:38:47:00:53:78:7d:b5:28:46:08:76:ed: + df:c0:87:6b:01:7d:5f:40:e6:36:c7:33:bd:e6:c5: + 15:0c:55:60:b8:4d:05:2f:19:0b:29:d6:d6:b9:e7: + 15:a3:2f:1b:e1:61:49:87:23:db:c1:ad:1d:5c:8a: + 4c:2f:1e:60:0b:1f:16:fa:37:a6:e0:76:ff:d2:b2: + 84:dd:b1:be:f5:39:7a:bc:40:0d:1b:fb:08:12:31: + d4:97:89:d0:23:3b:6b:24:35:95:72:d4:bb:f9:f6: + b4:8b:3e:98:f5:76:17:0e:11:27:f3:a3:8a:6f:02: + e8:3e:1d:2a:b0:20:62:ac:17 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 10:9A:A2:BA:80:4A:70:31:1B:EB:92:A0:77:FA:1F:56:A1:7C:E7:C8 + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + 6d:33:28:7c:11:50:1c:c9:10:81:61:e1:bd:ff:5f:ec:d6:90: + 6c:48:7f:a2:30:7c:55:db:f1:11:4e:58:ca:e2:51:b2:91:c3: + da:6a:22:48:e6:6f:5e:6d:39:b6:e5:ab:ec:1a:bb:2a:d3:be: + a0:06:8b:f6:a2:00:a9:5f:22:8c:54:31:b0:99:c2:6b:b4:92: + 9b:f1:08:29:e8:93:0a:1d:e3:e5:7e:0e:9d:ce:b6:cc:fd:29: + 8a:5f:e5:df:54:62:7e:53:d8:65:69:c5:88:a5:26:c2:25:5f: + 73:9a:79:3c:56:a6:23:fc:8d:60:93:bc:3d:76:f2:cc:18:89: + 5a:4d +-----BEGIN CERTIFICATE----- +MIICzTCCAjagAwIBAgIBATANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDgxOTE0MDM1 +N1oXDTE4MDgxODE0MDM1N1owfDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRgwFgYDVQQDDA9tMm0uZWNsaXBzZS5vcmcw +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKEXOEcAU3h9tShGCHbt38CHawF9 +X0DmNsczvebFFQxVYLhNBS8ZCynW1rnnFaMvG+FhSYcj28GtHVyKTC8eYAsfFvo3 +puB2/9KyhN2xvvU5erxADRv7CBIx1JeJ0CM7ayQ1lXLUu/n2tIs+mPV2Fw4RJ/Oj +im8C6D4dKrAgYqwXAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQQmqK6gEpw +MRvrkqB3+h9WoXznyDAfBgNVHSMEGDAWgBQpTW7H8vdxctonnJyr2gcdR5zYQTAN +BgkqhkiG9w0BAQUFAAOBgQBtMyh8EVAcyRCBYeG9/1/s1pBsSH+iMHxV2/ERTljK +4lGykcPaaiJI5m9ebTm25avsGrsq076gBov2ogCpXyKMVDGwmcJrtJKb8Qgp6JMK +HePlfg6dzrbM/SmKX+XfVGJ+U9hlacWIpSbCJV9zmnk8VqYj/I1gk7w9dvLMGIla +TQ== +-----END CERTIFICATE----- diff --git a/test/tls-testing/keys/server/server.key b/test/tls-testing/keys/server/server.key new file mode 100644 index 000000000..86987daae --- /dev/null +++ b/test/tls-testing/keys/server/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQChFzhHAFN4fbUoRgh27d/Ah2sBfV9A5jbHM73mxRUMVWC4TQUv +GQsp1ta55xWjLxvhYUmHI9vBrR1cikwvHmALHxb6N6bgdv/SsoTdsb71OXq8QA0b ++wgSMdSXidAjO2skNZVy1Lv59rSLPpj1dhcOESfzo4pvAug+HSqwIGKsFwIDAQAB +AoGAF+3CD1HuFDzgFw5oHXpyPUAgVgVQi+xmgKqf79KgsMdR6a6PMZLPxLRECzNX +VHkf9Beme2uJLjjP3rowXONvMptVj2U6IQPic4J7H/xIso4q4eqYz2Z3zH6ou95/ +MzqdlGx5Xrq68eZO4Hjjf440DVBURKTcBen6aiH1+LhNBlkCQQDUA3Uwfj2iylP1 +K92rME5eBT3TDQ+nFCN5gidOlzI3tGJLIYmff9vd5FMZ7+Wq94OPEWFSjOCDRYl1 +QptebqvLAkEAwoMhcoPaf8Ho9FgRQ/67ttea8f2aRWfMJ2TbzkXOzqAkIho0/i2B +mP17Y/Ut8ABxzi0OWvc5nLzQyJ38IvgPZQJAFJMhVEtr/9hSvYA8StitQkKP9WNX +GYfCyY/v8JiLrww6XcArcucdlzMJPYMibq0Gf2PYZgJ150dgR5jnKwricQJAcjm7 +bJpNt/TRrpwBF6MB7S/UCAPbkqKP2xp5kjqgZerZDUXClPl4P7Wrvll+7o/0RRVk +drr0FD01hnpwVMDDmQJAYWRBkvXHx/u6N1VHutwW9KtpC5ONxydDwhjv5DS07gjq +xir50wrdoaYKwgPxE7jRRASDjJ75aHUhQSC99Q22EQ== +-----END RSA PRIVATE KEY----- diff --git a/test/tls-testing/keys/test-root-ca.crt b/test/tls-testing/keys/test-root-ca.crt new file mode 100644 index 000000000..92dd58365 --- /dev/null +++ b/test/tls-testing/keys/test-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- diff --git a/test/tls-testing/keys/test-signing-ca.crt b/test/tls-testing/keys/test-signing-ca.crt new file mode 100644 index 000000000..3a10151d6 --- /dev/null +++ b/test/tls-testing/keys/test-signing-ca.crt @@ -0,0 +1,57 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:26:78:40:ae:b2:ad:2f:26:12:0a:d5:b1:18: + 80:16:d8:88:be:0b:42:ce:32:ad:12:d5:f5:78:1b: + 35:28:f2:13:1b:05:09:fb:7e:d7:d9:a1:8a:0d:4a: + fe:95:37:d4:16:75:83:e4:6a:44:34:33:57:2e:49: + ba:bc:b4:cf:d0:c0:87:e0:bc:f0:60:76:14:00:d6: + eb:cb:f6:db:b3:43:f1:c8:4d:4a:0a:bb:e0:37:7c: + 8e:93:1f:a0:87:68:59:fe:0c:25:40:f3:7c:fd:71: + 90:55:ef:de:18:b4:08:86:c9:75:c2:99:2f:ce:12: + bf:c5:5e:cf:5f:f1:06:53:07 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 48:ec:d7:80:8a:8f:82:a6:42:b1:89:2c:b9:4b:6d:0a:37:b8: + 72:19:05:de:75:80:0c:d6:41:97:b2:d7:fe:99:cb:7e:c4:0e: + 77:97:09:a8:9f:87:ff:0b:de:3f:1c:dc:1e:fe:09:36:a7:f5: + 54:9a:85:4e:fb:6f:27:fe:0f:29:45:61:8d:07:c6:0c:da:37: + 3d:a3:69:4b:82:71:e6:24:e0:87:a6:ee:d5:87:61:dd:8f:08: + fe:33:a6:1f:ae:b2:ae:1f:d8:2c:20:c8:a6:fc:33:0e:82:68: + 80:23:61:10:ad:5c:1d:80:d6:b1:5f:e4:af:66:6d:63:10:e4: + 96:e4 +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGAxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBANwmeECusq0vJhIK1bEYgBbYiL4LQs4yrRLV9XgbNSjyExsF +Cft+19mhig1K/pU31BZ1g+RqRDQzVy5Jury0z9DAh+C88GB2FADW68v227ND8chN +Sgq74Dd8jpMfoIdoWf4MJUDzfP1xkFXv3hi0CIbJdcKZL84Sv8Vez1/xBlMHAgMB +AAGjUDBOMB0GA1UdDgQWBBQpTW7H8vdxctonnJyr2gcdR5zYQTAfBgNVHSMEGDAW +gBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBAEjs14CKj4KmQrGJLLlLbQo3uHIZBd51gAzWQZey1/6Zy37EDneXCaif +h/8L3j8c3B7+CTan9VSahU77byf+DylFYY0HxgzaNz2jaUuCceYk4Iem7tWHYd2P +CP4zph+usq4f2CwgyKb8Mw6CaIAjYRCtXB2A1rFf5K9mbWMQ5Jbk +-----END CERTIFICATE----- diff --git a/test/tls-testing/mosquitto-docker.conf b/test/tls-testing/mosquitto-docker.conf new file mode 100644 index 000000000..ebc2a99f9 --- /dev/null +++ b/test/tls-testing/mosquitto-docker.conf @@ -0,0 +1,49 @@ +log_type error +log_type warning +log_type notice +log_type information +#log_type debug + +#log_dest file /var/log/mosquitto/tls-testing.log + +allow_anonymous true + +#message_size_limit 5000000 + +# non-SSL listeners +listener 1883 +listener 18883 + +# listener for mutual authentication +listener 18884 +cafile /mosquitto/config/keys/all-ca.crt +certfile /mosquitto/config/keys/server/server.crt +keyfile /mosquitto/config/keys/server/server.key +require_certificate true +#tls_version tlsv1 + +# server authentication - no client authentication +listener 18885 +cafile /mosquitto/config/keys/all-ca.crt +certfile /mosquitto/config/keys/server/server.crt +keyfile /mosquitto/config/keys/server/server.key +require_certificate false +#tls_version tlsv1 + +listener 18886 +cafile /mosquitto/config/keys/all-ca.crt +certfile /mosquitto/config/keys/server/server.crt +keyfile /mosquitto/config/keys/server/server.key +require_certificate false +#ciphers ADH-DES-CBC-SHA +#tls_version tlsv1 + +# server authentication - no client authentication - uses fake hostname to +# simulate mitm attack. Clients should refuse to connect to this listener. +listener 18887 +#cafile /mosquitto/config/keys/all-ca.crt +cafile /mosquitto/config/keys/server/server.crt +certfile /mosquitto/config/keys/server/server-mitm.crt +keyfile /mosquitto/config/keys/server/server-mitm.key +require_certificate true +#tls_version tlsv1 diff --git a/test/tls-testing/mosquitto.conf b/test/tls-testing/mosquitto.conf new file mode 100644 index 000000000..e0f429404 --- /dev/null +++ b/test/tls-testing/mosquitto.conf @@ -0,0 +1,54 @@ +log_type error +log_type warning +log_type notice +log_type information +#log_type debug + +#log_dest file /var/log/mosquitto/tls-testing.log + +allow_anonymous true +password_file test/tls-testing/mosquitto.pw + +#message_size_limit 5000000 + +# non-SSL listeners +listener 1883 +listener 18883 + +# listener for mutual authentication +listener 18884 +cafile test/tls-testing/keys/all-ca.crt +certfile test/tls-testing/keys/server/server.crt +keyfile test/tls-testing/keys/server/server.key +require_certificate true +use_identity_as_username false +#tls_version tlsv1 + +# server authentication - no client authentication +listener 18885 +cafile test/tls-testing/keys/all-ca.crt +certfile test/tls-testing/keys/server/server.crt +keyfile test/tls-testing/keys/server/server.key +require_certificate false +#tls_version tlsv1 + +listener 18886 +cafile test/tls-testing/keys/all-ca.crt +certfile test/tls-testing/keys/server/server.crt +keyfile test/tls-testing/keys/server/server.key +require_certificate false +#ciphers ADH-DES-CBC-SHA +#tls_version tlsv1 + +# server authentication - no client authentication - uses fake hostname to +# simulate mitm attack. Clients should refuse to connect to this listener. +listener 18887 +#cafile test/tls-testing/keys/all-ca.crt +cafile test/tls-testing/keys/server/server.crt +certfile test/tls-testing/keys/server/server-mitm.crt +keyfile test/tls-testing/keys/server/server-mitm.key +require_certificate true +#tls_version tlsv1 + +listener 8080 +protocol websockets diff --git a/test/tls-testing/mosquitto.pw b/test/tls-testing/mosquitto.pw new file mode 100644 index 000000000..cb50c892e --- /dev/null +++ b/test/tls-testing/mosquitto.pw @@ -0,0 +1,4 @@ +test_user:$6$PY1pf+FdPmONSyhb$gC0yIykOSqj5x2rY7odzhNqLrQJ1S7dpQAgvxK+DXKquw1Yp5whSSgfcv24xTIotm3KCt1D6vcISOq/m3OhGEg== +testuser:$6$VsVwgoeK2yQI5QTo$2fSqD6qUgMvfSOQQxRm7aQH1sA2oqGmdbNFM86GBbjWja7sN5wxOQHIjk/B8PeOqmMOee5UWX2faXy+X9fPZ4w== +Admin:$6$OAKIhJgkrZUTDSbN$AmKUUznotPbGl5XaFbs8wqw4OyWFkjOgxJfJ4C/9vaknsouvR4RmsEozCee2SPwFaXSR52QRype/9+iSvU7y5w== +username:$6$djEZwC47JvyetTxD$8x/J3ClUe38pcxE55hRw/uTpulVOCH6yXwJzJI6SPgmIdSGXAPtoEFLhKeXzoN20P1W30NUpgcIRI716lCYWVw== diff --git a/travis-install.sh b/travis-install.sh new file mode 100755 index 000000000..36ecff689 --- /dev/null +++ b/travis-install.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ "$TRAVIS_OS_NAME" == "linux" ]; then + pwd + sudo service mosquitto stop + mosquitto -h + mosquitto -c test/tls-testing/mosquitto.conf & +fi + +if [ "$TRAVIS_OS_NAME" == "osx" ]; then + pwd + brew update + brew install openssl mosquitto + brew services stop mosquitto + /usr/local/sbin/mosquitto -c test/tls-testing/mosquitto.conf & +fi