From: snickersun Date: Fri, 4 Sep 2020 14:25:39 +0000 (+0800) Subject: add soure code core X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=89d0e7356aef0ef8c5d186322e93e0cc7e6f230a;p=protocol_srv.git add soure code core --- diff --git a/core b/core deleted file mode 160000 index b6b76d5e..00000000 --- a/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6b76d5e81e7d87cfca5b1bd72c57d351461640c diff --git a/core/.classpath b/core/.classpath new file mode 100644 index 00000000..45b0c68c --- /dev/null +++ b/core/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/.project b/core/.project new file mode 100644 index 00000000..568a4ab0 --- /dev/null +++ b/core/.project @@ -0,0 +1,23 @@ + + + core + Project core created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + org.eclipse.jdt.core.javanature + + diff --git a/core/.properties.original b/core/.properties.original new file mode 100644 index 00000000..4b7e5a3d --- /dev/null +++ b/core/.properties.original @@ -0,0 +1 @@ +ARTIFACT_VERSION=@VERSION@ diff --git a/core/.travis.yml b/core/.travis.yml new file mode 100644 index 00000000..7dd56bd1 --- /dev/null +++ b/core/.travis.yml @@ -0,0 +1,11 @@ +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 diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000..542c6a15 --- /dev/null +++ b/core/README.md @@ -0,0 +1,37 @@ +## Qatja + +[![Build Status](https://travis-ci.org/Qatja/core.svg?branch=master)](https://travis-ci.org/Qatja/core) + +[![Download](https://api.bintray.com/packages/wetcat/Qatja/core/images/download.svg)](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 + + se.wetcat.qatja + core + 1.0.1 + pom + +``` + +### 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). + diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 00000000..3a3aa924 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,99 @@ +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 + } + } + } +} diff --git a/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12.bin b/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12.bin new file mode 100644 index 00000000..ae91532f --- /dev/null +++ b/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12.bin @@ -0,0 +1 @@ +o/core diff --git a/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex b/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex new file mode 100644 index 00000000..91939669 Binary files /dev/null and b/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex differ diff --git a/core/build/.transforms/5e84c834bc7ccd55cb3ab8cb23275e8e.bin b/core/build/.transforms/5e84c834bc7ccd55cb3ab8cb23275e8e.bin new file mode 100644 index 00000000..52974b66 --- /dev/null +++ b/core/build/.transforms/5e84c834bc7ccd55cb3ab8cb23275e8e.bin @@ -0,0 +1 @@ +i/ diff --git a/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin new file mode 100644 index 00000000..ae91532f --- /dev/null +++ b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin @@ -0,0 +1 @@ +o/core diff --git a/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex new file mode 100644 index 00000000..583a3033 Binary files /dev/null and b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/MQTTConstants.class b/core/build/classes/java/main/se/wetcat/qatja/MQTTConstants.class new file mode 100644 index 00000000..c7d9b96c Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/MQTTConstants.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/MQTTException.class b/core/build/classes/java/main/se/wetcat/qatja/MQTTException.class new file mode 100644 index 00000000..7f266b6e Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/MQTTException.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/MQTTHelper.class b/core/build/classes/java/main/se/wetcat/qatja/MQTTHelper.class new file mode 100644 index 00000000..e200d401 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/MQTTHelper.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/MQTTIdentifierHelper.class b/core/build/classes/java/main/se/wetcat/qatja/MQTTIdentifierHelper.class new file mode 100644 index 00000000..2a590923 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/MQTTIdentifierHelper.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/MQTTVersion.class b/core/build/classes/java/main/se/wetcat/qatja/MQTTVersion.class new file mode 100644 index 00000000..2a8c00e4 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/MQTTVersion.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnack.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnack.class new file mode 100644 index 00000000..89fb105d Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnack.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnect.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnect.class new file mode 100644 index 00000000..924bc70e Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnect.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTDisconnect.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTDisconnect.class new file mode 100644 index 00000000..37be148b Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTDisconnect.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTMessage.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTMessage.class new file mode 100644 index 00000000..b978d52a Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTMessage.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingreq.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingreq.class new file mode 100644 index 00000000..4fb821d2 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingreq.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingresp.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingresp.class new file mode 100644 index 00000000..cfb9142c Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingresp.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPuback.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPuback.class new file mode 100644 index 00000000..9f5b5e33 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPuback.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubcomp.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubcomp.class new file mode 100644 index 00000000..1e3d1df5 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubcomp.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPublish.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPublish.class new file mode 100644 index 00000000..dc07bc34 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPublish.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrec.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrec.class new file mode 100644 index 00000000..5b3af636 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrec.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrel.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrel.class new file mode 100644 index 00000000..5657cb1c Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrel.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSuback.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSuback.class new file mode 100644 index 00000000..74b96e47 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSuback.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSubscribe.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSubscribe.class new file mode 100644 index 00000000..1035383c Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSubscribe.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsuback.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsuback.class new file mode 100644 index 00000000..20abf509 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsuback.class differ diff --git a/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsubscribe.class b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsubscribe.class new file mode 100644 index 00000000..c2e3fa78 Binary files /dev/null and b/core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsubscribe.class differ diff --git a/core/build/libs/core.jar b/core/build/libs/core.jar new file mode 100644 index 00000000..a218237b Binary files /dev/null and b/core/build/libs/core.jar differ diff --git a/core/build/tmp/jar/MANIFEST.MF b/core/build/tmp/jar/MANIFEST.MF new file mode 100644 index 00000000..58630c02 --- /dev/null +++ b/core/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/core/gradle/wrapper/gradle-wrapper.jar b/core/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..3d0dee6e Binary files /dev/null and b/core/gradle/wrapper/gradle-wrapper.jar differ diff --git a/core/gradle/wrapper/gradle-wrapper.properties b/core/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1a45772f --- /dev/null +++ b/core/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +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 diff --git a/core/local.properties b/core/local.properties new file mode 100644 index 00000000..6f448491 --- /dev/null +++ b/core/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Fri Sep 04 17:00:12 CST 2020 +sdk.dir=C\:\\Users\\incar\\AppData\\Local\\Android\\Sdk diff --git a/core/src/main/java/se/wetcat/qatja/MQTTConstants.java b/core/src/main/java/se/wetcat/qatja/MQTTConstants.java new file mode 100644 index 00000000..7d903f04 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/MQTTConstants.java @@ -0,0 +1,162 @@ +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; + +} diff --git a/core/src/main/java/se/wetcat/qatja/MQTTException.java b/core/src/main/java/se/wetcat/qatja/MQTTException.java new file mode 100644 index 00000000..032b9fc8 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/MQTTException.java @@ -0,0 +1,40 @@ +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); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/MQTTHelper.java b/core/src/main/java/se/wetcat/qatja/MQTTHelper.java new file mode 100644 index 00000000..4be998ef --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/MQTTHelper.java @@ -0,0 +1,183 @@ +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"; + } + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/MQTTIdentifierHelper.java b/core/src/main/java/se/wetcat/qatja/MQTTIdentifierHelper.java new file mode 100644 index 00000000..f8854ccb --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/MQTTIdentifierHelper.java @@ -0,0 +1,143 @@ +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 sentPackages = new ConcurrentHashMap<>(); + private ConcurrentHashMap receivedPackages = new ConcurrentHashMap<>(); + + private List 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 getSentPackages() { + synchronized (lock) { + return sentPackages; + } + } + + public ConcurrentHashMap 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 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; + } + } +} diff --git a/core/src/main/java/se/wetcat/qatja/MQTTVersion.java b/core/src/main/java/se/wetcat/qatja/MQTTVersion.java new file mode 100644 index 00000000..653f8fd7 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/MQTTVersion.java @@ -0,0 +1,52 @@ +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; + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTConnack.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTConnack.java new file mode 100644 index 00000000..da567ebe --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTConnack.java @@ -0,0 +1,106 @@ +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]. + *

+ * 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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTConnect.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTConnect.java new file mode 100644 index 00000000..c582f4e3 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTConnect.java @@ -0,0 +1,538 @@ +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]. + *

+ * 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. + *

+ * 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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTDisconnect.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTDisconnect.java new file mode 100644 index 00000000..ecd502a7 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTDisconnect.java @@ -0,0 +1,83 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTMessage.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTMessage.java new file mode 100644 index 00000000..31b772a5 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTMessage.java @@ -0,0 +1,196 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPingreq.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPingreq.java new file mode 100644 index 00000000..9010be47 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPingreq.java @@ -0,0 +1,88 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPingresp.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPingresp.java new file mode 100644 index 00000000..ce43669f --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPingresp.java @@ -0,0 +1,77 @@ +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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPuback.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPuback.java new file mode 100644 index 00000000..71809fd3 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPuback.java @@ -0,0 +1,127 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPubcomp.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubcomp.java new file mode 100644 index 00000000..669bc636 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubcomp.java @@ -0,0 +1,130 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPublish.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPublish.java new file mode 100644 index 00000000..5067f1de --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPublish.java @@ -0,0 +1,259 @@ +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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrec.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrec.java new file mode 100644 index 00000000..5a42c478 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrec.java @@ -0,0 +1,135 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrel.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrel.java new file mode 100644 index 00000000..ef53c364 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTPubrel.java @@ -0,0 +1,131 @@ +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(); + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTSuback.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTSuback.java new file mode 100644 index 00000000..0dcc7e39 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTSuback.java @@ -0,0 +1,93 @@ +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. + *

+ * 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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTSubscribe.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTSubscribe.java new file mode 100644 index 00000000..554d62b6 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTSubscribe.java @@ -0,0 +1,135 @@ +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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsuback.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsuback.java new file mode 100644 index 00000000..f0c1dba2 --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsuback.java @@ -0,0 +1,91 @@ +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; + } + +} diff --git a/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsubscribe.java b/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsubscribe.java new file mode 100644 index 00000000..7d452a4c --- /dev/null +++ b/core/src/main/java/se/wetcat/qatja/messages/MQTTUnsubscribe.java @@ -0,0 +1,116 @@ +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; + } + +} diff --git a/core/travis_prebuild.sh b/core/travis_prebuild.sh new file mode 100644 index 00000000..bc2e20fa --- /dev/null +++ b/core/travis_prebuild.sh @@ -0,0 +1,12 @@ +#!/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