+++ /dev/null
-Subproject commit b6b76d5e81e7d87cfca5b1bd72c57d351461640c
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7/"/>
+ <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>core</name>
+ <comment>Project core created by Buildship.</comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+ARTIFACT_VERSION=@VERSION@
--- /dev/null
+language: java
+
+before_install:
+ - ./travis_prebuild.sh;
+
+deploy:
+ provider: script
+ script: gradle clean bintrayUpload
+ skip_cleanup: true
+ on:
+ tags: true
\ No newline at end of file
--- /dev/null
+## Qatja
+
+[](https://travis-ci.org/Qatja/core)
+
+[](https://bintray.com/wetcat/Qatja/core/_latestVersion)
+
+MQTT Java Library, conforms to MQTT 3.1.1
+
+This library is barebones, it contains no platform specific implementations such as threading or connectionhandling. If you plan on using this for Android projects you should look at [Qatja Android](https://github.com/Qatja/qatja-android) which uses a predefined service.
+
+### Installation
+
+Add the following to your build.gradle file
+
+#### Gradle
+
+```groovy
+dependencies {
+ compile 'se.wetcat.qatja:core:1.0.1'
+}
+```
+
+#### Maven
+
+```xml
+<dependency>
+ <groupId>se.wetcat.qatja</groupId>
+ <artifactId>core</artifactId>
+ <version>1.0.1</version>
+ <type>pom</type>
+</dependency>
+```
+
+### Acknowledgements
+
+Based on the work by [Andreas Göransson](https://github.com/agoransson) and [David Cuartielles](https://github.com/dcuartielles) in the book ["Professional Android Open Accessory programming with Arduino"](https://github.com/aoabook) and later [mqtt4processing](https://github.com/agoransson/mqtt4processing) and [mqtt4android](https://github.com/agoransson/mqtt4android).
+
--- /dev/null
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+
+buildscript {
+ repositories {
+ jcenter()
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
+ }
+ dependencies {
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
+ }
+}
+
+apply plugin: 'com.jfrog.bintray'
+
+repositories {
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+
+ // https://mvnrepository.com/artifact/com.googlecode.juniversalchardet/juniversalchardet
+ compile group: 'com.googlecode.juniversalchardet', name: 'juniversalchardet', version: '1.0.3'
+}
+
+sourceCompatibility = "1.7"
+targetCompatibility = "1.7"
+
+task javadocJar(type: Jar) {
+ baseName = "$project.name"
+ version = getVersion()
+ classifier = 'docs'
+ from javadoc
+}
+
+task sourcesJar(type: Jar) {
+ baseName = "$project.name"
+ version = getVersion()
+ from sourceSets.main.output
+}
+
+publishToMavenLocal.dependsOn build
+bintrayUpload.dependsOn build
+
+publishing {
+ publications {
+ driverPublish(MavenPublication) {
+ groupId 'se.wetcat.qatja'
+ artifactId "$project.name"
+ version getVersion()
+ artifact sourcesJar
+ artifact javadocJar
+ // artifact("$buildDir/libs/$project.name-release.jar")
+ pom.withXml {
+ def dependenciesNode = asNode().appendNode('dependencies')
+ configurations.compile.allDependencies.each {
+ if (it.group != null && it.name != null &&
+ !"unspecified".equals(it.name) && it.version != null &&
+ !"unspecified".equals(it.version)) {
+ def dependencyNode = dependenciesNode.appendNode('dependency')
+ dependencyNode.appendNode('groupId', it.group)
+ dependencyNode.appendNode('artifactId', it.name)
+ dependencyNode.appendNode('version', it.version)
+ }
+ }
+ }
+ }
+ }
+}
+
+bintray {
+ user = System.getenv('BINTRAY_USER')
+ key = System.getenv('BINTRAY_API_KEY')
+ publications = ['driverPublish']
+
+ publish = true
+
+ pkg {
+ repo = 'Qatja'
+ name = "$project.name"
+ userOrg = 'wetcat'
+
+ version {
+ name = getVersion()
+ gpg {
+ sign = true
+ }
+ }
+ }
+}
--- /dev/null
+Manifest-Version: 1.0\r
+\r
--- /dev/null
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
--- /dev/null
+## This file must *NOT* be checked into Version Control Systems,\r
+# as it contains information specific to your local configuration.\r
+#\r
+# Location of the SDK. This is only used by Gradle.\r
+# For customization when using a Version Control System, please read the\r
+# header note.\r
+#Fri Sep 04 17:00:12 CST 2020\r
+sdk.dir=C\:\\Users\\incar\\AppData\\Local\\Android\\Sdk\r
--- /dev/null
+package se.wetcat.qatja;
+
+/*
+ * Copyright (C) 2012 Andreas Goransson, David Cuartielles
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Defines all MQTT constants; including message types, quality of service, etc.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTConstants {
+
+ /**
+ * Client request to connect to a server
+ */
+ public static final byte CONNECT = 0x01;
+
+ /**
+ * Connect Acknowledgement
+ */
+ public static final byte CONNACK = 0x02;
+
+ /**
+ * Publish message
+ */
+ public static final byte PUBLISH = 0x03;
+
+ /**
+ * Publish Acknowledgment
+ */
+ public static final byte PUBACK = 0x04;
+
+ /**
+ * Publish Received
+ */
+ public static final byte PUBREC = 0x05;
+
+ /**
+ * Publish Release
+ */
+ public static final byte PUBREL = 0x06;
+
+ /**
+ * Publish Complete
+ */
+ public static final byte PUBCOMP = 0x07;
+
+ /**
+ * Client Subscription request
+ */
+ public static final byte SUBSCRIBE = 0x08;
+
+ /**
+ * Subscription Acknowledgment
+ */
+ public static final byte SUBACK = 0x09;
+
+ /**
+ * Client Unsubscribe request
+ */
+ public static final byte UNSUBSCRIBE = 0x0a;
+
+ /**
+ * Unsubscribe Acknowledgment
+ */
+ public static final byte UNSUBACK = 0x0b;
+
+ /**
+ * PING Request
+ */
+ public static final byte PINGREQ = 0x0c;
+
+ /**
+ * PING Response
+ */
+ public static final byte PINGRESP = 0x0d;
+
+ /**
+ * Client is Disconnecting
+ */
+ public static final byte DISCONNECT = 0x0e;
+
+ /**
+ * Fire and Forget, QoS:0
+ */
+ public static final byte AT_MOST_ONCE = 0x00;
+
+ /**
+ * Acknowledged deliver, QoS:1
+ */
+ public static final byte AT_LEAST_ONCE = 0x01;
+
+ /**
+ * Assured Delivery, QoS:2
+ */
+ public static final byte EXACTLY_ONCE = 0x02;
+
+ /**
+ * Connection was accepted by server
+ */
+ public static final byte CONNECTION_ACCEPTED = 0x00;
+
+ /**
+ * The Server does not support the level of the MQTT protocol requested by the
+ * Client
+ */
+ public static final byte CONNECTION_REFUSED_VERSION = 0x01;
+
+ /**
+ * The Client identifier is correct UTF-8 but not allowed by the Server
+ */
+ public static final byte CONNECTION_REFUSED_IDENTIFIER = 0x02;
+
+ /**
+ * The Network Connection has been made but the MQTT service is unavailable
+ */
+ public static final byte CONNECTION_REFUSED_SERVER = 0x03;
+
+ /**
+ * The data in the user name or password is malformed
+ */
+ public static final byte CONNECTION_REFUSED_USER = 0x04;
+
+ /**
+ * The Client is not authorized to connect
+ */
+ public static final byte CONNECTION_REFUSED_AUTH = 0x05;
+
+ /*
+ * Subscription return codes
+ */
+ public static final byte SUBSCRIBE_SUCCESS_AT_MOST_ONCE = AT_MOST_ONCE;
+ public static final byte SUBSCRIBE_SUCCESS_AT_LEAST_ONCE = AT_LEAST_ONCE;
+ public static final byte SUBSCRIBE_SUCCESS_EXACTLY_ONCE = EXACTLY_ONCE;
+ public static final byte SUBSCRIBE_FAILURE = (byte) 0x80;
+
+ /**
+ * Minimum length of strings (byte[] length)
+ */
+ public static final int MIN_LENGTH = 0;
+
+ /**
+ * Maximum length of strings (byte[] length)
+ */
+ public static final int MAX_LENGTH = 65535;
+
+}
--- /dev/null
+package se.wetcat.qatja;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * MQTT Exception, defines a specific error according to MQTT rules
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTException extends Exception {
+
+ public MQTTException() {
+ super("MQTT Exception");
+ }
+
+ public MQTTException(String message) {
+ super(message);
+ }
+
+ public MQTTException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.mozilla.universalchardet.UniversalDetector;
+import se.wetcat.qatja.messages.MQTTMessage;
+
+import static se.wetcat.qatja.MQTTConstants.CONNECT;
+import static se.wetcat.qatja.MQTTConstants.CONNACK;
+import static se.wetcat.qatja.MQTTConstants.PUBLISH;
+import static se.wetcat.qatja.MQTTConstants.PUBACK;
+import static se.wetcat.qatja.MQTTConstants.PUBREC;
+import static se.wetcat.qatja.MQTTConstants.PUBREL;
+import static se.wetcat.qatja.MQTTConstants.PUBCOMP;
+import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE;
+import static se.wetcat.qatja.MQTTConstants.SUBACK;
+import static se.wetcat.qatja.MQTTConstants.UNSUBSCRIBE;
+import static se.wetcat.qatja.MQTTConstants.UNSUBACK;
+import static se.wetcat.qatja.MQTTConstants.PINGREQ;
+import static se.wetcat.qatja.MQTTConstants.PINGRESP;
+import static se.wetcat.qatja.MQTTConstants.DISCONNECT;
+
+/**
+ * Helper methods for MQTT
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTHelper {
+
+// private static int lastPackageIdentifier = 1;
+
+ private static final String UTF8 = "UTF-8";
+
+ /**
+ * Detects if string is encoded with UTF-8
+ *
+ * @param buffer String
+ * @return true if UTF-8, false otherwise
+ */
+ public static boolean isUTF8(byte[] buffer) {
+ UniversalDetector detector = new UniversalDetector(null);
+ detector.handleData(buffer, 0, buffer.length);
+ detector.dataEnd();
+
+ String encoding = detector.getDetectedCharset();
+ detector.reset();
+
+ return (UTF8.equals(encoding));
+ }
+
+ public static boolean hasWildcards(String topic) {
+ // TODO, is the $ acceptable?
+ return /*topic.contains("/") ||*/ topic.contains("#") || topic.contains("+");
+ }
+
+// public static int getNewPackageIdentifier() {
+// return (lastPackageIdentifier += 1);
+// }
+
+ /**
+ * Return the MSB of a string length
+ *
+ * @param buffer String
+ * @return MSB
+ */
+ public static byte MSB(byte[] buffer) {
+ return (byte) ((buffer.length) >> 8 & 0xFF);
+ }
+
+ /**
+ * Return the LSB of the string length
+ *
+ * @param buffer String
+ * @return LSB
+ */
+ public static byte LSB(byte[] buffer) {
+ return (byte) (buffer.length & 0xFF);
+ }
+
+ /**
+ * Return most significant byte of integer
+ *
+ * @param val the value
+ * @return the MSB
+ */
+ public static byte MSB(int val) {
+ return (byte) ((val & 0xffff) >> 8 & 0xFF);
+ }
+
+ /**
+ * Return least significant byte of integer
+ *
+ * @param val the value
+ * @return the LSB
+ */
+ public static byte LSB(int val) {
+ return (byte) ((val & 0xffff) & 0xFF);
+ }
+
+ public static void decodeFlags(byte flags) {
+ boolean username = ((flags >> 7) == 1 ? true : false);
+
+ boolean password = ((flags >> 6) == 1 ? true : false);
+ }
+
+ /**
+ * Decode message type
+ *
+ * @param buffer The message buffer to decode
+ * @return The message type
+ */
+ public static byte decodeType(byte[] buffer) {
+ return (byte) ((buffer[0] >> 4) & 0x0F);
+ }
+
+ /**
+ * Get the human readable name of a message type
+ *
+ * @param message the message
+ * @return human readable string of message type
+ */
+ public static String decodePackageName(MQTTMessage message) {
+ return decodePackageName(message.getType());
+ }
+
+ /**
+ * Get the human readable name of a message type
+ *
+ * @param messageType the message type
+ * @return human readable string of message type
+ */
+ public static String decodePackageName(byte messageType) {
+ switch (messageType) {
+ case CONNECT:
+ return "CONNECT";
+ case CONNACK:
+ return "CONNACK";
+ case PUBLISH:
+ return "PUBLISH";
+ case PUBACK:
+ return "PUBACK";
+ case PUBREC:
+ return "PUBREC";
+ case PUBREL:
+ return "PUBREL";
+ case PUBCOMP:
+ return "PUBCOMP";
+ case SUBSCRIBE:
+ return "SUBSCRIBE";
+ case SUBACK:
+ return "SUBACK";
+ case UNSUBSCRIBE:
+ return "UNSUBSCRIBE";
+ case UNSUBACK:
+ return "UNSUBACK";
+ case PINGREQ:
+ return "PINGREQ";
+ case PINGRESP:
+ return "PINGRESP";
+ case DISCONNECT:
+ return "DISCONNECT";
+ default:
+ return "Unknown message type";
+ }
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import se.wetcat.qatja.messages.MQTTMessage;
+
+/**
+ * A helper class for handling package identifiers.
+ *
+ * @author andreasgoransson0@gmail.com
+ */
+public class MQTTIdentifierHelper {
+
+ private ConcurrentHashMap<Integer, MQTTMessage> sentPackages = new ConcurrentHashMap<>();
+ private ConcurrentHashMap<Integer, MQTTMessage> receivedPackages = new ConcurrentHashMap<>();
+
+ private List<Integer> availablePackageIdentifiers = new ArrayList<>();
+
+ private final Object lock = new Object();
+
+ /**
+ * Add MQTTMessage to the list of sent packages, used for QoS
+ * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} and
+ * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}
+ *
+ * @param msg the MQTTMessage
+ */
+ public void addSentPackage(MQTTMessage msg) {
+ synchronized (lock) {
+ sentPackages.put(msg.getPackageIdentifier(), msg);
+ }
+ }
+
+ /**
+ * Remove MQTTMessage from the list of sent packages, used for QoS
+ * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} and
+ * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}
+ *
+ * @param msg the MQTTMessage
+ */
+ public synchronized void removeSentPackage(MQTTMessage msg) {
+ synchronized (lock) {
+ sentPackages.remove(msg.getPackageIdentifier());
+ availablePackageIdentifiers.add(msg.getPackageIdentifier());
+ }
+ }
+
+ public ConcurrentHashMap<Integer, MQTTMessage> getSentPackages() {
+ synchronized (lock) {
+ return sentPackages;
+ }
+ }
+
+ public ConcurrentHashMap<Integer, MQTTMessage> getReceivedPackages() {
+ synchronized (lock) {
+ return receivedPackages;
+ }
+ }
+
+ /**
+ * Add MQTTMessage to the list of received packages, used for QoS
+ * {@link MQTTConstants#AT_MOST_ONCE} and {@link MQTTConstants#EXACTLY_ONCE}
+ *
+ * @param msg the MQTTMessage
+ */
+ public void addReceivedPackage(MQTTMessage msg) {
+ synchronized (lock) {
+ receivedPackages.put(msg.getPackageIdentifier(), msg);
+ }
+ }
+
+ /**
+ * Remove MQTTMessage to the list of received packages, used for QoS
+ * {@link MQTTConstants#AT_MOST_ONCE} and {@link MQTTConstants#EXACTLY_ONCE}
+ *
+ * @param msg the MQTTMessage
+ */
+ public void removeReceivedPackage(MQTTMessage msg) {
+ synchronized (lock) {
+ receivedPackages.remove(msg.getPackageIdentifier());
+ }
+ }
+
+ /**
+ * @return The next available identifier
+ */
+ public synchronized int getIdentifier() {
+ synchronized (lock) {
+ int identifier = 1;
+
+ if (availablePackageIdentifiers.size() > 0) {
+ // Reuse an identifier
+ identifier = availablePackageIdentifiers.remove(0);
+ } else if (sentPackages.size() > 0) {
+ // Generate the next, new, identifier by looping through the map and find the next biggest id
+// int lastPackage = sentPackages.size() - 1;
+// identifier = sentPackages.get(lastPackage).getPackageIdentifier() + 1;
+
+// boolean idFound = true;
+// do {
+// for (int id : Collections.list(sentPackages.keys())) {
+// if (identifier == id) {
+// idFound = true;
+// }
+// }
+// } while (idFound);
+
+ List<Integer> keys = Collections.list(sentPackages.keys());
+ for (int i = 0; i < keys.size(); i++) {
+ if (identifier == keys.get(i)) {
+ identifier++;
+ i = 0;
+ }
+ }
+ } else {
+ // Generate first identifier
+ identifier = 1;
+ }
+
+
+ return identifier;
+ }
+ }
+}
--- /dev/null
+package se.wetcat.qatja;
+
+/*
+ * Copyright (C) 2012 Andreas Goransson, David Cuartielles
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Defines the current MQTT version.
+ *
+ * TODO: Find a better way of defining versions, defining a version should also
+ * affect the other classes of the project - and the specific platform
+ * implementations.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTVersion {
+
+ /**
+ * MQTT 3.1 Version name
+ */
+ public static String NAME_31 = "MQIsdp";
+
+ /**
+ * MQTT 3.1 Version number
+ */
+ public static byte VERSION_31 = (byte) 0x03;
+
+ /**
+ * MQTT 3.1.1 Version name
+ */
+ public static String NAME_311 = "MQTT";
+
+ /**
+ * MQTT 3.1 Version number
+ */
+ public static byte VERSION_311 = (byte) 0x04;
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+/**
+ * The {@link se.wetcat.qatja.MQTTConstants#CONNACK} Packet is the packet sent by the Server in
+ * response to a {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet received from a Client. The
+ * first packet sent from the Server to the Client MUST be a
+ * {@link se.wetcat.qatja.MQTTConstants#CONNACK} Packet [MQTT-3.2.0-1].
+ * <p>
+ * If the Client does not receive a {@link se.wetcat.qatja.MQTTConstants#CONNACK} Packet from the
+ * Server within a reasonable amount of time, the Client SHOULD close the Network Connection. A
+ * "reasonable" amount of time depends on the type of application and the communications
+ * infrastructure.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTConnack extends MQTTMessage {
+
+ @SuppressWarnings("unused")
+ private byte RESERVED;
+ private byte returnCode;
+
+ public static MQTTConnack fromBuffer(byte[] buffer) {
+ return new MQTTConnack(buffer);
+ }
+
+ private MQTTConnack(byte[] buffer) {
+ int i = 0;
+
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get length of protocol name
+ len = ((buffer[i] >> 8) & 0xFF) | (buffer[i + 1] & 0xFF);
+
+ // Get variable header (always length 2 in CONNACK)
+ variableHeader = new byte[2];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ if (payload.length > 0)
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+ RESERVED = variableHeader[0];
+ returnCode = variableHeader[1];
+ }
+
+ /**
+ * @return the returnCode
+ */
+ public byte getReturnCode() {
+ return returnCode;
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // Client doesn't generate CONNACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ // Client doesn't generate CONNACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ // Client doesn't generate CONNACK
+ return null;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.AT_MOST_ONCE;
+import static se.wetcat.qatja.MQTTConstants.CONNECT;
+import static se.wetcat.qatja.MQTTConstants.MAX_LENGTH;
+import static se.wetcat.qatja.MQTTConstants.MIN_LENGTH;
+import static se.wetcat.qatja.MQTTVersion.VERSION_311;
+
+/**
+ * After a Network Connection is established by a Client to a Server, the first Packet sent from the
+ * Client to the Server MUST be a {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet
+ * [MQTT-3.1.0-1].
+ * <p>
+ * A Client can only send the {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet once over a
+ * Network Connection. The Server MUST process a second
+ * {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet sent from a Client as a protocol violation
+ * and disconnect the Client [MQTT-3.1.0-2]. See section 4.8 for information about handling errors.
+ * <p>
+ * The payload contains one or more encoded fields. They specify a unique Client identifier for the
+ * Client, a Will topic, Will Message, User Name and Password. All but the Client identifier are
+ * optional and their presence is determined based on flags in the variable header.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTConnect extends MQTTMessage {
+
+ /**
+ * The identifier for the client, MUST be present in all {@link se.wetcat.qatja.MQTTConstants#CONNECT}
+ * messages
+ */
+ private String clientIdentifier;
+
+ protected byte protocolVersion;
+
+ protected String protocolName;
+
+ /**
+ * Position 5 of Connect Flags. If the Will Flag is set to 0, the Will
+ * Retain Flag MUST be set to 0.
+ */
+ private boolean willRetain;
+ private boolean willFlag = true;
+ private String willTopic = "wtopic";
+ private String willMessage = "wmessage";
+ private byte willQoS;
+
+ private String username;
+ private String password;
+
+ private boolean cleanSession;
+ private int keepAlive;
+
+ public static MQTTConnect newInstance() {
+ return new MQTTConnect();
+ }
+
+ public static MQTTConnect newInstance(String clientIdentifier) {
+ return new MQTTConnect(clientIdentifier);
+ }
+
+ public static MQTTConnect newInstance(String clientIdentifier, String username, String password) {
+ return new MQTTConnect(clientIdentifier, username, password);
+ }
+
+ public static MQTTConnect newInstance(String clientIdentifier, String username, String password,
+ boolean cleanSession) {
+ return new MQTTConnect(clientIdentifier, username, password, cleanSession);
+ }
+
+ public static MQTTConnect newInstance(String clientIdentifier, String username, String password,
+ boolean willRetain, byte willQoS, String willTopic,
+ String willMessage, boolean cleanSession, int keepAlive) {
+ return new MQTTConnect(clientIdentifier, username, password, willRetain, willQoS, willTopic, willMessage, cleanSession, keepAlive);
+ }
+
+ public static MQTTConnect fromButton(byte[] buffer) {
+ return new MQTTConnect(buffer);
+ }
+
+ /**
+ * Default MQTTConnect constructor. All flags set to default values, and no
+ * client identifier set (cleanSession set to true). Keep alive set to 10
+ * seconds.
+ */
+ private MQTTConnect() {
+ this("Qatja-" + System.nanoTime());
+ }
+
+ /**
+ * Create a new MQTT Connect message
+ *
+ * @param clientIdentifier The client identifier
+ */
+ private MQTTConnect(String clientIdentifier) {
+ this(clientIdentifier, null, null);
+ }
+
+ /**
+ * Create a new MQTT Connect message
+ *
+ * @param clientIdentifier The client identifier
+ * @param username The client username
+ * @param password The client password
+ */
+ private MQTTConnect(String clientIdentifier, String username, String password) {
+ this(clientIdentifier, username, password, false);
+ }
+
+ /**
+ * Create a new MQTT Connect message
+ *
+ * @param clientIdentifier The client identifier
+ * @param username The client username
+ * @param password The client password
+ * @param cleanSession Connect with clear session state
+ */
+ private MQTTConnect(String clientIdentifier, String username, String password, boolean cleanSession) {
+ this(clientIdentifier, username, password, false, AT_MOST_ONCE, null, null, cleanSession, 10);
+ }
+
+ /**
+ * Create a new MQTT Connect message
+ *
+ * @param clientIdentifier The client identifier
+ * @param username The client username
+ * @param password The client password
+ * @param willRetain Set last will and testament
+ * @param willQoS Last will QoS
+ * @param willTopic Last will topic
+ * @param willMessage Last will message
+ * @param cleanSession Connect with clean session state
+ * @param keepAlive Time interval in seconds
+ */
+ private MQTTConnect(String clientIdentifier, String username, String password, boolean willRetain, byte willQoS,
+ String willTopic, String willMessage, boolean cleanSession, int keepAlive) {
+ this.type = CONNECT;
+
+ this.clientIdentifier = clientIdentifier;
+ this.cleanSession = cleanSession;
+ this.keepAlive = keepAlive;
+
+ this.willRetain = willRetain;
+ this.willFlag = (willTopic != null && willMessage != null);
+ this.willTopic = willTopic;
+ this.willMessage = willMessage;
+ this.willQoS = willQoS;
+
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Create MQTT Connect message from a byte[].
+ *
+ * @param buffer Buffer from which to create message
+ */
+ private MQTTConnect(byte[] buffer) {
+ int i = 0;
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get length of protocol name
+ len = ((buffer[i] >> 8) & 0xFF) | (buffer[i + 1] & 0xFF);
+
+ // Get variable header (always length 10 in CONNECT)
+ variableHeader = new byte[10];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+ i = 2;
+ protocolName = new String(variableHeader, i, len);
+ protocolVersion = variableHeader[(i += len)];
+ flags = variableHeader[++i];
+ keepAlive = (variableHeader[++i] >> 8) & 0xFF | (variableHeader[++i] & 0xFF);
+ }
+
+ /**
+ * Set last will and testament
+ *
+ * @param willTopic Set the last will topic
+ * @param willMessage Set last will message
+ * @param willRetain Set last will retain flag
+ * @param willQoS Set last will QoS
+ */
+ public void setWill(String willTopic, String willMessage, boolean willRetain, byte willQoS) {
+ this.willFlag = true;
+ this.willTopic = willTopic;
+ this.willMessage = willMessage;
+ this.willQoS = willQoS;
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type
+ out.write((byte) (type << 4));
+
+ // Flags (none for CONNECT)
+
+ // Remaining length
+ int length = variableHeader.length + payload.length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Some packages has this
+ if (protocolName == null)
+ out.write(getProtocol());
+ else {
+ // For some reason I need to test this in the interop testing...
+ out.write((protocolName.getBytes("UTF-8").length >> 8) & 0xFF); // MSB
+ out.write(protocolName.getBytes("UTF-8").length & 0xFF); // LSB
+ out.write(protocolName.getBytes("UTF-8"));
+
+ // Protocol Level (Used by all messages)
+ out.write((byte) VERSION_311);
+ }
+
+ // CONNECT FLAGS
+ byte flags = (byte) (0x00);
+
+ byte username = (byte) ((this.username != null && this.username.length() >= 0) ? 0x01 : 0x00);
+ byte password = (byte) ((this.password != null && this.password.length() >= 0) ? 0x01 : 0x00);
+ byte willRetain = (byte) (this.willRetain ? 0x01 : 0x00);
+ byte willQoS = this.willQoS;
+ byte willFlag = (byte) ((this.willRetain && this.willFlag) ? 0x01 : 0x00);
+ byte cleanSession = (byte) (this.cleanSession ? 0x01 : 0x00);
+ byte RESERVED = (byte) 0x00;
+
+ flags = (byte) ((username << 7) | (password << 6) | (willRetain << 5) | (willQoS << 3) | (willFlag << 2)
+ | (cleanSession << 1) | (RESERVED << 0));
+
+ // Write flags
+ out.write(flags);
+
+ // Keep alive (Use lower two bytes of integer)
+ out.write(MQTTHelper.MSB(keepAlive));
+ out.write(MQTTHelper.LSB(keepAlive));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ // PAYLOAD
+ ByteArrayOutputStream payload = new ByteArrayOutputStream();
+
+ // CLIENT IDENTIFIER
+ // ClientID MUST be present and MUST be the first field in payload
+ if (clientIdentifier == null)
+ throw new MQTTException("Client identifier invalid");
+ // ClientID MUST be UTF-8
+ if (MQTTHelper.isUTF8(clientIdentifier.getBytes("UTF-8")))
+ throw new MQTTException("Invalid identifier encoding");
+ // ClientID MUST be length between 0 and 65535
+ if (clientIdentifier.getBytes("UTF-8").length < MIN_LENGTH
+ || clientIdentifier.getBytes("UTF-8").length > MAX_LENGTH)
+ throw new MQTTException("Client identifier invalid length");
+
+ payload.write(MQTTHelper.MSB(clientIdentifier.getBytes("UTF-8"))); // MSB
+ payload.write(MQTTHelper.LSB(clientIdentifier.getBytes("UTF-8"))); // LSB
+ payload.write(clientIdentifier.getBytes("UTF-8"));
+
+ // WILL
+ if (willFlag) {
+ // Will topic MUST be present
+ if (willTopic == null)
+ throw new MQTTException("Will flag set, will topic MUST be present");
+ // Will topic MUST be UTF-8
+ if (MQTTHelper.isUTF8(willTopic.getBytes("UTF-8")))
+ throw new MQTTException("Invalid will topic encoding");
+ // Write Will topic
+ payload.write(MQTTHelper.MSB(willTopic.getBytes("UTF-8"))); // MSB
+ payload.write(MQTTHelper.LSB(willTopic.getBytes("UTF-8"))); // LSB
+ payload.write(willTopic.getBytes("UTF-8"));
+
+ // Will message MUST be UTF-8
+ if (willMessage == null)
+ throw new MQTTException("Will flag set, will message MUST be present");
+ if (MQTTHelper.isUTF8(willMessage.getBytes("UTF-8")))
+ throw new MQTTException("Invalid will message encoding");
+ // Write Will message
+ payload.write(MQTTHelper.MSB(willMessage.getBytes("UTF-8"))); // MSB
+ payload.write(MQTTHelper.LSB(willMessage.getBytes("UTF-8"))); // LSB
+ payload.write(willMessage.getBytes("UTF-8"));
+ }
+
+ // USERNAME
+ if (username != null && username.length() >= 0) {
+ // Username MUST be UTF-8
+ if (MQTTHelper.isUTF8(username.getBytes("UTF-8")))
+ throw new MQTTException("Invalid username encoding");
+ // Write username
+ payload.write(MQTTHelper.MSB(username.getBytes("UTF-8"))); // MSB
+ payload.write(MQTTHelper.LSB(username.getBytes("UTF-8"))); // LSB
+ payload.write(username.getBytes("UTF-8"));
+ }
+
+ // PASSWORD
+ if (password != null && password.length() >= 0) {
+ // Password MUST be UTF-8
+ if (MQTTHelper.isUTF8(password.getBytes("UTF-8")))
+ throw new MQTTException("Invalid password encoding");
+ // Password contains 0 to 65535 bytes
+ if (password.getBytes("UTF-8").length < MIN_LENGTH || password.getBytes("UTF-8").length > MAX_LENGTH)
+ throw new MQTTException("Password invalid length");
+ // Write password
+ payload.write(MQTTHelper.MSB(password.getBytes("UTF-8"))); // MSB
+ payload.write(MQTTHelper.LSB(password.getBytes("UTF-8"))); // LSB
+ payload.write(password.getBytes("UTF-8"));
+// Logger.getGlobal().info("mqtt_"+payload.toString());
+ }
+
+ return payload.toByteArray();
+ }
+
+ /**
+ * @return The clientIdentifier
+ */
+ public String getClientIdentifier() {
+ return clientIdentifier;
+ }
+
+ /**
+ * @param clientIdentifier The clientIdentifier to set
+ */
+ public void setClientIdentifier(String clientIdentifier) {
+ this.clientIdentifier = clientIdentifier;
+ }
+
+ /**
+ * @return The protocolVersion
+ */
+ public byte getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ /**
+ * @param protocolVersion The protocolVersion to set
+ */
+ public void setProtocolVersion(byte protocolVersion) {
+ this.protocolVersion = protocolVersion;
+ }
+
+ /**
+ * @return The protocolName
+ */
+ public String getProtocolName() {
+ return protocolName;
+ }
+
+ /**
+ * @param protocolName The protocolName to set
+ */
+ public void setProtocolName(String protocolName) {
+ this.protocolName = protocolName;
+ }
+
+ /**
+ * @return The willRetain
+ */
+ public boolean isWillRetain() {
+ return willRetain;
+ }
+
+ /**
+ * @param willRetain The willRetain to set
+ */
+ public void setWillRetain(boolean willRetain) {
+ this.willRetain = willRetain;
+ }
+
+ /**
+ * @return The willFlag
+ */
+ public boolean isWillFlag() {
+ return willFlag;
+ }
+
+ /**
+ * @param willFlag The willFlag to set
+ */
+ public void setWillFlag(boolean willFlag) {
+ this.willFlag = willFlag;
+ }
+
+ /**
+ * @return The willTopic
+ */
+ public String getWillTopic() {
+ return willTopic;
+ }
+
+ /**
+ * @param willTopic The willTopic to set
+ */
+ public void setWillTopic(String willTopic) {
+ this.willTopic = willTopic;
+ }
+
+ /**
+ * @return The willMessage
+ */
+ public String getWillMessage() {
+ return willMessage;
+ }
+
+ /**
+ * @param willMessage The willMessage to set
+ */
+ public void setWillMessage(String willMessage) {
+ this.willMessage = willMessage;
+ }
+
+ /**
+ * @return The willQoS
+ */
+ public byte getWillQoS() {
+ return willQoS;
+ }
+
+ /**
+ * @param willQoS The willQoS to set
+ */
+ public void setWillQoS(byte willQoS) {
+ this.willQoS = willQoS;
+ }
+
+ /**
+ * @return The username
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * @param username The username to set
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ /**
+ * @return The password
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * @param password The password to set
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * @return The cleanSession
+ */
+ public boolean isCleanSession() {
+ return cleanSession;
+ }
+
+ /**
+ * @param cleanSession The cleanSession to set
+ */
+ public void setCleanSession(boolean cleanSession) {
+ this.cleanSession = cleanSession;
+ }
+
+ /**
+ * @return The keepAlive
+ */
+ public int getKeepAlive() {
+ return keepAlive;
+ }
+
+ /**
+ * @param keepAlive The keepAlive to set
+ */
+ public void setKeepAlive(int keepAlive) {
+ this.keepAlive = keepAlive;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+import static se.wetcat.qatja.MQTTConstants.DISCONNECT;
+
+/**
+ * The {@link se.wetcat.qatja.MQTTConstants#DISCONNECT} Packet is the final Control Packet sent from
+ * the Client to the Server. It indicates that the Client is disconnecting cleanly.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTDisconnect extends MQTTMessage {
+
+ public static MQTTDisconnect newInstance() {
+ return new MQTTDisconnect();
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#DISCONNECT} message
+ */
+ private MQTTDisconnect() {
+ this.setType(DISCONNECT);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type
+ out.write((byte) (type << 4));
+
+ // Flags (none for PING)
+
+ // Remaining length
+ int length = variableHeader.length + payload.length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2012 Andreas Goransson, David Cuartielles
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTVersion.NAME_311;
+import static se.wetcat.qatja.MQTTVersion.VERSION_311;
+
+/**
+ * MQTT Message super class, should never be instantiated.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public abstract class MQTTMessage {
+
+ protected byte flags;
+
+ protected int packageIdentifier;
+
+ protected byte[] fixedHeader;
+ protected byte[] variableHeader;
+ protected byte[] payload;
+
+ protected byte type;
+ protected int remainingLength;
+
+ protected abstract byte[] generateFixedHeader() throws MQTTException, IOException;
+
+ protected abstract byte[] generateVariableHeader() throws MQTTException, IOException;
+
+ protected abstract byte[] generatePayload() throws MQTTException, IOException;
+
+ /**
+ * @return The packageIdentifier
+ */
+ public int getPackageIdentifier() {
+ return packageIdentifier;
+ }
+
+ /**
+ * @param packageIdentifier The packageIdentifier to set
+ */
+ public void setPackageIdentifier(int packageIdentifier) {
+ this.packageIdentifier = packageIdentifier;
+ }
+
+ /**
+ * @return The fixedHeader
+ */
+ public byte[] getFixedHeader() {
+ return fixedHeader;
+ }
+
+ /**
+ * @param fixedHeader The fixedHeader to set
+ */
+ public void setFixedHeader(byte[] fixedHeader) {
+ this.fixedHeader = fixedHeader;
+ }
+
+ /**
+ * @return The variableHeader
+ */
+ public byte[] getVariableHeader() {
+ return variableHeader;
+ }
+
+ /**
+ * @param variableHeader The variableHeader to set
+ */
+ public void setVariableHeader(byte[] variableHeader) {
+ this.variableHeader = variableHeader;
+ }
+
+ /**
+ * @return The payload
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ /**
+ * @param payload The payload to set
+ */
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+
+ /**
+ * @return The type
+ */
+ public byte getType() {
+ return type;
+ }
+
+ /**
+ * @param type The type to set
+ */
+ public void setType(byte type) {
+ this.type = type;
+ }
+
+ /**
+ * @return The remainingLength
+ */
+ public int getRemainingLength() {
+ return remainingLength;
+ }
+
+ /**
+ * @param remainingLength The remainingLength to set
+ */
+ public void setRemainingLength(int remainingLength) {
+ this.remainingLength = remainingLength;
+ }
+
+ protected byte[] getProtocol() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Protocol Name (Used by all messages)
+ out.write((NAME_311.getBytes("UTF-8").length >> 8) & 0xFF); // MSB
+ out.write(NAME_311.getBytes("UTF-8").length & 0xFF); // LSB
+ out.write(NAME_311.getBytes("UTF-8"));
+
+ // Protocol Level (Used by all messages)
+ out.write((byte) VERSION_311);
+
+ return out.toByteArray();
+ }
+
+ public byte[] get() throws IOException, MQTTException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ this.payload = generatePayload();
+ this.variableHeader = generateVariableHeader();
+ this.fixedHeader = generateFixedHeader();
+
+ out.write(fixedHeader);
+ out.write(variableHeader);
+ out.write(payload);
+
+ Logger.getGlobal().info("mqtt_ "+out.toString());
+ Logger.getGlobal().info("mqtt_ "+bytesToHexString(out.toByteArray()));
+
+ return out.toByteArray();
+ }
+ public String bytesToHexString(byte[] src) {
+ StringBuilder stringBuilder = new StringBuilder("");
+ if (src == null || src.length <= 0) {
+ return null;
+ }
+ for (int i = 0; i < src.length; i++) {
+ int v = src[i] & 0xFF;
+ String hv = Integer.toHexString(v);
+ if (hv.length() < 2) {
+ stringBuilder.append(0);
+ }
+ stringBuilder.append(hv);
+ }
+ return stringBuilder.toString();
+ }
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("MQTT Package ").append("\n");
+ sb.append(" type: " + MQTTHelper.decodePackageName(this.getType())).append("\n");
+ sb.append(" remaining length: " + this.getRemainingLength()).append("\n");
+ sb.append(" package identifier: " + this.getPackageIdentifier());
+
+ return sb.toString();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+import static se.wetcat.qatja.MQTTConstants.PINGREQ;
+
+/**
+ * The {@link se.wetcat.qatja.MQTTConstants#PINGREQ} Packet is sent from a Client to the Server. It
+ * can be used to:
+ * 1. Indicate to the Server that the Client is alive in the absence of any
+ * other Control Packets being sent from the Client to the Server.
+ * 2. Request that the Server responds to confirm that it is alive.
+ * 3. Exercise the network to indicate that the Network Connection is active.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPingreq extends MQTTMessage {
+
+ public static MQTTPingreq newInstance() {
+ return new MQTTPingreq();
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PINGREQ} message
+ */
+ private MQTTPingreq() {
+ this.setType(PINGREQ);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type
+ byte fixed = (byte) (type << 4);
+ out.write(fixed);
+
+ // Flags (none for PINGREQ)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#PINGRESP} Packet is sent by the Server to the Client in
+ * response to a {@link se.wetcat.qatja.MQTTConstants#PINGREQ} Packet. It indicates that the Server
+ * is alive.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPingresp extends MQTTMessage {
+
+ public static MQTTPingresp fromBuffer(byte[] buffer) {
+ return new MQTTPingresp(buffer);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PINGRESP} message from a buffer
+ *
+ * @param buffer The buffer
+ */
+ private MQTTPingresp(byte[] buffer) {
+
+ int i = 0;
+ // Type (just for clarity sake we'll read it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ this.setRemainingLength(0);
+
+ // No variable header
+ setVariableHeader(null);
+
+ // No payload
+ setPayload(null);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // Client doesn't create the PINGRESP
+ return null;
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ // Client doesn't create the PINGRESP
+ return null;
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ // Client doesn't create the PINGRESP
+ return null;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.PUBACK;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#PUBACK} Packet is the response to a
+ * {@link se.wetcat.qatja.MQTTConstants#PUBLISH} Packet with se.wetcat.qatja.MQTTConstantsQoS level
+ * {@link se.wetcat.qatja.MQTTConstants#AT_LEAST_ONCE}.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPuback extends MQTTMessage {
+
+ public static MQTTPuback newInstance(int packageIdentifier) {
+ return new MQTTPuback(packageIdentifier);
+ }
+
+ public static MQTTPuback fromBuffer(byte[] buffer) {
+ return new MQTTPuback(buffer);
+ }
+
+ private MQTTPuback(int packageIdentifier) {
+ this.setType(PUBACK);
+ this.setPackageIdentifier(packageIdentifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBACK} message from a byte array
+ *
+ * @param buffer The byte array
+ */
+ private MQTTPuback(byte[] buffer) {
+
+ int i = 0;
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get variable header (always length 2 in PUBACK)
+ variableHeader = new byte[2];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ if (payload.length > 0)
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+ // Get package identifier
+ packageIdentifier = (variableHeader[variableHeader.length - 2] >> 8 & 0xFF) | (variableHeader[variableHeader.length - 1] & 0xFF);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and PUBACK flags, reserved bits MUST be [0,0,0,0]
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x00 << 1) | (0x00) << 0);
+ out.write(fixed);
+
+ // Flags (none for PUBACK)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(packageIdentifier));
+ out.write(MQTTHelper.LSB(packageIdentifier));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTConstants;
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+/**
+ * The {@link se.wetcat.qatja.MQTTConstants#PUBCOMP} Packet is the response to a
+ * {@link se.wetcat.qatja.MQTTConstants#PUBREL} Packet. It is the fourth and final packet of the
+ * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE} protocol exchange
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPubcomp extends MQTTMessage {
+
+ public static MQTTPubcomp newInstance(int packageIdentifier) {
+ return new MQTTPubcomp(packageIdentifier);
+ }
+
+ public static MQTTPubcomp fromBuffer(byte[] buffer) {
+ return new MQTTPubcomp(buffer);
+ }
+
+ private MQTTPubcomp(int packageIdentifier) {
+ setType(MQTTConstants.PUBCOMP);
+ setPackageIdentifier(packageIdentifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBCOMP} message from a byte array
+ *
+ * @param buffer The byte array
+ */
+ private MQTTPubcomp(byte[] buffer) {
+
+ // setBuffer(bufferIn, bufferLength);
+
+ int i = 0;
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // No flags for PUBBCOMP
+
+ // Get variable header (always length 2 in PUBACK)
+ variableHeader = new byte[2];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ if (payload.length > 0)
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+ // Get package identifier
+ packageIdentifier = (variableHeader[variableHeader.length - 2] >> 8 & 0xFF) | (variableHeader[variableHeader.length - 1] & 0xFF);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and PUBACK flags, reserved bits MUST be [0,0,0,0]
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x00 << 1) | (0x00) << 0);
+ out.write(fixed);
+
+ // Flags (none for PUBACK)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(packageIdentifier));
+ out.write(MQTTHelper.LSB(packageIdentifier));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.AT_LEAST_ONCE;
+import static se.wetcat.qatja.MQTTConstants.AT_MOST_ONCE;
+import static se.wetcat.qatja.MQTTConstants.EXACTLY_ONCE;
+import static se.wetcat.qatja.MQTTConstants.PUBLISH;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#PUBLISH} Control Packet is sent from a Client to a Server
+ * or from Server to a Client to transport an Application Message.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPublish extends MQTTMessage {
+
+ private boolean dup;
+ private byte QoS;
+ private boolean retain;
+
+ private String topicName;
+
+ public static MQTTPublish fromBuffer(byte[] buffer) {
+ return new MQTTPublish(buffer);
+ }
+
+ public static MQTTPublish newInstance(String topic, byte[] payload, int identifier) {
+ return new MQTTPublish(topic, payload, identifier);
+ }
+
+ public static MQTTPublish newInstance(String topic, byte[] payload, byte QoS, int identifier) {
+ return new MQTTPublish(topic, payload, QoS, identifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBLISH} message
+ *
+ * @param topic The topic to publish to
+ * @param payload The message payload
+ */
+ private MQTTPublish(String topic, byte[] payload, int identifier) {
+ this(topic, payload, AT_MOST_ONCE, identifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBLISH} message
+ *
+ * @param topic the topic to publish to
+ * @param payload the message payload
+ * @param QoS the quality of service for message
+ */
+ private MQTTPublish(String topic, byte[] payload, byte QoS, int identifier) {
+ this.type = PUBLISH;
+
+ this.topicName = topic;
+ this.payload = payload;
+
+ this.QoS = QoS;
+
+ if (this.QoS > AT_MOST_ONCE) {
+ setPackageIdentifier(identifier);
+ }
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBLISH} message from a buffer
+ *
+ * @param buffer The buffer
+ */
+ private MQTTPublish(byte[] buffer) {
+ int i = 0;
+
+ // Type (just for clarity sake we'll set it...)
+ this.retain = (buffer[i] >> 0 & 1) == 0x01 ? true : false;
+ this.QoS = (byte) ((buffer[i] & ((0x00 << 3) | (0x01 << 2) | (0x01 << 1) | (0x00 << 0))) >> 1);
+ this.dup = ((buffer[i] >> 3) & 1) == 1 ? true : false;
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ len = (byte) ((buffer[i] >> 8 & 0xFF) | (buffer[i + 1] & 0xFF));
+
+ // Get variable header (topic length + 2[topic len] + 2[pkg id])
+
+ switch (getQoS()) {
+ case AT_MOST_ONCE:
+ // No packageIdentifier
+ variableHeader = new byte[len + 2];
+ break;
+
+ case AT_LEAST_ONCE:
+ case EXACTLY_ONCE:
+ // 2 byte packageIdentifier
+ variableHeader = new byte[len + 2 + 2];
+ break;
+ }
+
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+ i += variableHeader.length;
+
+ // Get topic
+ topicName = new String(variableHeader, 2, len);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+
+ switch (getQoS()) {
+ case AT_MOST_ONCE:
+ // No packageIdentifier
+ break;
+
+ case AT_LEAST_ONCE:
+ case EXACTLY_ONCE:
+ // 2 byte packageIdentifier
+ packageIdentifier = (variableHeader[variableHeader.length - 1]) | (variableHeader[variableHeader.length - 2]);
+ break;
+ }
+
+ System.arraycopy(buffer, i, payload, 0, payload.length);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ byte type = this.type;
+ byte dup = (byte) (this.dup ? 0x01 : 0x00);
+ byte QoS = this.QoS;
+ byte retain = (byte) (this.retain ? 0x01 : 0x00);
+
+ // Type and PUBLISH flags
+ byte fixed = (byte) ((type << 4) | (dup << 3) | (QoS << 1) | (retain << 0));
+ out.write(fixed);
+
+ // Flags (none for PUBLISH)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // TOPIC
+ // Topic MUST be UTF-8
+ if (MQTTHelper.isUTF8(topicName.getBytes("UTF-8")))
+ throw new MQTTException("Invalid topic encoding");
+ // Topic MUST NOT contain wildcards
+ if (MQTTHelper.hasWildcards(topicName))
+ throw new MQTTException("Invalid topic, may not contain wildcards");
+ out.write(MQTTHelper.MSB(topicName.length()));
+ out.write(MQTTHelper.LSB(topicName.length()));
+ out.write(topicName.getBytes("UTF-8"));
+
+ // Package identifier ONLY exists if QoS is above AT_MOST_ONCE
+ if (QoS > AT_MOST_ONCE) {
+ // Package identifier MUST exist for AT_LEAST_ONCE and EXACTLY_ONCE
+ if (packageIdentifier == 0)
+ throw new MQTTException("Package identifier must not be 0");
+
+ out.write(MQTTHelper.MSB(packageIdentifier));
+ out.write(MQTTHelper.LSB(packageIdentifier));
+ }
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ return payload;
+ }
+
+ /**
+ * Mark this message as duplicate
+ */
+ public void setDup() {
+ this.dup = true;
+ }
+
+ /**
+ * @return The dup
+ */
+ public boolean isDup() {
+ return dup;
+ }
+
+ /**
+ * @return The qoS
+ */
+ public byte getQoS() {
+ return QoS;
+ }
+
+ /**
+ * @return The retain
+ */
+ public boolean isRetain() {
+ return retain;
+ }
+
+ /**
+ * @param retain The retain to set
+ */
+ public void setRetain(boolean retain) {
+ this.retain = retain;
+ }
+
+ /**
+ * @return The topicName
+ */
+ public String getTopicName() {
+ return topicName;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTConstants;
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+/**
+ * MQTT {@link se.wetcat.qatja.MQTTConstants#PUBREC} message is the response to a
+ * {@link se.wetcat.qatja.MQTTConstants#PUBLISH} message. It is the second packet of the
+ * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE} flow
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTPubrec extends MQTTMessage {
+
+ public static MQTTPubrec newInstance(int packageIdentifier) {
+ return new MQTTPubrec(packageIdentifier);
+ }
+
+ public static MQTTPubrec fromBuffer(byte[] buffer) {
+ return new MQTTPubrec(buffer);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBREC} message
+ *
+ * @param packageIdentifier The package identifier
+ */
+ private MQTTPubrec(int packageIdentifier) {
+ setType(MQTTConstants.PUBREC);
+ setPackageIdentifier(packageIdentifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBREC} message from a buffer
+ *
+ * @param buffer The buffer
+ */
+ private MQTTPubrec(byte[] buffer) {
+
+ int i = 0;
+
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get variable header always 2 (just the pkg id) for PUBREC
+ variableHeader = new byte[2];
+
+ // We have to step back two bytes since
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+
+ if (payload.length > 0)
+ System.arraycopy(buffer, i, payload, 0, payload.length);
+
+ // Only get package identifier if the QoS is above AT_MOST_ONCE
+ packageIdentifier = (variableHeader[variableHeader.length - 1]) | (variableHeader[variableHeader.length - 2]);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and PUBREC flags, reserved bits MUST be [0,0,0,0]
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x00 << 1) | (0x00) << 0);
+ out.write(fixed);
+
+ // Flags (none for PUBREC)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(packageIdentifier));
+ out.write(MQTTHelper.LSB(packageIdentifier));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.PUBREL;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#PUBREL} Packet is the response to a
+ * {@link se.wetcat.qatja.MQTTConstants#PUBREC} Packet. It is the third packet of the
+ * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE} protocol exchange.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTPubrel extends MQTTMessage {
+
+ public static MQTTPubrel newInstance(int packageIdentifier) {
+ return new MQTTPubrel(packageIdentifier);
+ }
+
+ public static MQTTPubrel fromBuffer(byte[] buffer) {
+ return new MQTTPubrel(buffer);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#PUBREL} message
+ *
+ * @param packageIdentifier The package identifier
+ */
+ private MQTTPubrel(int packageIdentifier) {
+ setType(PUBREL);
+ setPackageIdentifier(packageIdentifier);
+ }
+
+ private MQTTPubrel(byte[] buffer) {
+
+ int i = 0;
+
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get variable header always 2 (just the pkg id) for PUBREL
+ variableHeader = new byte[2];
+
+ // We have to step back two bytes since
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+
+ if (payload.length > 0)
+ System.arraycopy(buffer, i, payload, 0, payload.length);
+
+ // Only get package identifier if the QoS is above AT_MOST_ONCE
+ packageIdentifier = (variableHeader[variableHeader.length - 1]) | (variableHeader[variableHeader.length - 2]);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and PUBLISH flags (The last four bits MUST be [0 0 1 0])
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x01 << 1) | (0x00 << 0));
+ out.write(fixed);
+
+ // Flags (none for PUBREL)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(getPackageIdentifier()));
+ out.write(MQTTHelper.LSB(getPackageIdentifier()));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ return out.toByteArray();
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#SUBACK} Packet is sent by the Server to the Client to
+ * confirm receipt and processing of a {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE} Packet.
+ * <p>
+ * A {@link se.wetcat.qatja.MQTTConstants#SUBACK} Packet contains a list of return codes, that
+ * specify the maximum QoS level that was granted in each Subscription that was requested by the
+ * {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE}.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTSuback extends MQTTMessage {
+
+ public static MQTTSuback fromBuffer(byte[] buffer) {
+ return new MQTTSuback(buffer);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#SUBACK} message from a buffer
+ *
+ * @param buffer The buffer
+ */
+ private MQTTSuback(byte[] buffer) {
+ int i = 0;
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get variable header (always length 2 in SUBACK)
+ variableHeader = new byte[2];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ if (payload.length > 0)
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+ // Get package identifier
+ packageIdentifier = (variableHeader[variableHeader.length - 2] >> 8 & 0xFF) | (variableHeader[variableHeader.length - 1] & 0xFF);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // Client doesn't create SUBACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ // Client doesn't create SUBACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ // Client doesn't create SUBACK
+ return null;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE;
+
+/**
+ * The {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE} Packet is sent from the Client to the Server
+ * to create one or more Subscriptions. Each Subscription registers a Client’s interest in one or
+ * more Topics. The Server sends {@link se.wetcat.qatja.MQTTConstants#PUBLISH} Packets to the Client
+ * in order to forward Application Messages that were published to Topics that match these
+ * Subscriptions. The {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE} Packet also specifies (for
+ * each Subscription) the maximum QoS with which the Server can send Application Messages to the
+ * Client.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTSubscribe extends MQTTMessage {
+
+ private String[] topicFilters;
+ private byte[] QoSs;
+
+ public static MQTTSubscribe newInstance(String[] topicFilters, byte[] QoSs, int identifier) {
+ return new MQTTSubscribe(topicFilters, QoSs, identifier);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE} message
+ *
+ * @param topicFilters The topics to subscribe to
+ * @param QoSs The QoS for each topic
+ */
+ private MQTTSubscribe(String[] topicFilters, byte[] QoSs, int identifier) {
+ this.setType(SUBSCRIBE);
+ this.topicFilters = topicFilters;
+ this.QoSs = QoSs;
+
+ setPackageIdentifier(identifier);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and reserved bits, MUST be [0 0 1 0]
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x01 << 1) | (0x00 << 0));
+ out.write(fixed);
+
+ // Flags (none for SUBSCRIBE)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(getPackageIdentifier()));
+ out.write(MQTTHelper.LSB(getPackageIdentifier()));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ if (topicFilters.length <= 0 || QoSs.length <= 0)
+ throw new MQTTException("The SUBSCRIBE message must contain at least one topic filter and QoS pair");
+
+ if (topicFilters.length != QoSs.length)
+ throw new MQTTException("The SUBSCRIBE message should have the same number of topic filters and QoS");
+
+ for (int i = 0; i < topicFilters.length; i++) {
+ if (MQTTHelper.isUTF8(topicFilters[i].getBytes("UTF-8")))
+ throw new MQTTException("Invalid topic filter encoding: " + topicFilters[i]);
+
+ out.write(MQTTHelper.MSB(topicFilters[i].length()));
+ out.write(MQTTHelper.LSB(topicFilters[i].length()));
+ out.write(topicFilters[i].getBytes());
+ out.write(QoSs[i]);
+ }
+
+ return out.toByteArray();
+ }
+
+ /**
+ * @return The topicFilters
+ */
+ public String[] getTopicFilters() {
+ return topicFilters;
+ }
+
+ /**
+ * @return The qoSs
+ */
+ public byte[] getQoSs() {
+ return QoSs;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+
+/**
+ * A {@link se.wetcat.qatja.MQTTConstants#UNSUBACK} package is the response to a
+ * {@link se.wetcat.qatja.MQTTConstants#UNSUBSCRIBE} packet
+ *
+ * @author andreas
+ */
+public class MQTTUnsuback extends MQTTMessage {
+
+ public static MQTTUnsuback fromBuffer(byte[] buffer) {
+ return new MQTTUnsuback(buffer);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#UNSUBACK} message from a buffer
+ *
+ * @param buffer the buffer
+ */
+ private MQTTUnsuback(byte[] buffer) {
+
+ // setBuffer(bufferIn, bufferLength);
+
+ int i = 0;
+ // Type (just for clarity sake we'll set it...)
+ this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+ // Remaining length
+ int multiplier = 1;
+ int len = 0;
+ byte digit = 0;
+ do {
+ digit = buffer[i++];
+ len += (digit & 127) * multiplier;
+ multiplier *= 128;
+ } while ((digit & 128) != 0);
+ this.setRemainingLength(len);
+
+ // Get variable header (always length 2 in SUBACK)
+ variableHeader = new byte[2];
+ System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+ // Get payload
+ payload = new byte[remainingLength - variableHeader.length];
+ if (payload.length > 0)
+ System.arraycopy(buffer, i + variableHeader.length, payload, 0,
+ remainingLength - variableHeader.length);
+
+ // Get package identifier
+ packageIdentifier = (variableHeader[variableHeader.length - 2] >> 8 & 0xFF) | (variableHeader[variableHeader.length - 1] & 0xFF);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // Client doesn't create UNSUBACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ // Client doesn't create UNSUBACK
+ return null;
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ // Client doesn't create UNSUBACK
+ return null;
+ }
+
+}
--- /dev/null
+package se.wetcat.qatja.messages;
+
+/*
+ * Copyright (C) 2014 Andreas Goransson
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import se.wetcat.qatja.MQTTException;
+import se.wetcat.qatja.MQTTHelper;
+
+import static se.wetcat.qatja.MQTTConstants.UNSUBSCRIBE;
+
+/**
+ * An {@link se.wetcat.qatja.MQTTConstants#UNSUBSCRIBE} Packet is sent by the Client to the Server,
+ * to unsubscribe from topics.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTUnsubscribe extends MQTTMessage {
+
+ private String[] topicFilters;
+
+ public static MQTTUnsubscribe newInstance(int identifier, String... topicFilters) {
+ return new MQTTUnsubscribe(identifier, topicFilters);
+ }
+
+ /**
+ * Construct a {@link se.wetcat.qatja.MQTTConstants#UNSUBSCRIBE} message
+ *
+ * @param topicFilters Topic filters to unsubscribe to
+ */
+ private MQTTUnsubscribe(int identifier, String... topicFilters) {
+ this.setType(UNSUBSCRIBE);
+ this.topicFilters = topicFilters;
+
+ setPackageIdentifier(identifier);
+ }
+
+ @Override
+ protected byte[] generateFixedHeader() throws MQTTException, IOException {
+ // FIXED HEADER
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // Type and reserved bits, MUST be [0 0 1 0]
+ byte fixed = (byte) ((type << 4) | (0x00 << 3) | (0x00 << 2) | (0x01 << 1) | (0x00 << 0));
+ out.write(fixed);
+
+ // Flags (none for UNSUBSCRIBE)
+
+ // Remaining length
+ int length = getVariableHeader().length + getPayload().length;
+ this.setRemainingLength(length);
+ do {
+ byte digit = (byte) (length % 128);
+ length /= 128;
+ if (length > 0)
+ digit = (byte) (digit | 0x80);
+ out.write(digit);
+ } while (length > 0);
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generateVariableHeader() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(MQTTHelper.MSB(getPackageIdentifier()));
+ out.write(MQTTHelper.LSB(getPackageIdentifier()));
+
+ return out.toByteArray();
+ }
+
+ @Override
+ protected byte[] generatePayload() throws MQTTException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ if (topicFilters.length <= 0)
+ throw new MQTTException("The SUBSCRIBE message must contain at least one topic filter");
+
+ for (int i = 0; i < topicFilters.length; i++) {
+ if (MQTTHelper.isUTF8(topicFilters[i].getBytes("UTF-8")))
+ throw new MQTTException("Invalid topic filter encoding: " + topicFilters[i]);
+
+ out.write(MQTTHelper.MSB(topicFilters[i].length()));
+ out.write(MQTTHelper.LSB(topicFilters[i].length()));
+ out.write(topicFilters[i].getBytes());
+ }
+
+ return out.toByteArray();
+ }
+
+ /**
+ * @return The topicFilters
+ */
+ public String[] getTopicFilters() {
+ return topicFilters;
+ }
+
+}
--- /dev/null
+#!/bin/bash
+
+VERSIONVAL=master
+
+# Use tag as version
+if [ $TRAVIS_TAG ]; then
+ VERSIONVAL=$TRAVIS_TAG
+else
+ VERSIONVAL="dev"
+fi
+
+sed -e s%@VERSION@%${VERSIONVAL}% .properties.original > gradle.properties
\ No newline at end of file