add soure code core
authorsnickersun <scy@incartech.cn>
Fri, 4 Sep 2020 14:25:39 +0000 (22:25 +0800)
committersnickersun <scy@incartech.cn>
Fri, 4 Sep 2020 14:25:39 +0000 (22:25 +0800)
58 files changed:
core [deleted submodule]
core/.classpath [new file with mode: 0644]
core/.project [new file with mode: 0644]
core/.properties.original [new file with mode: 0644]
core/.travis.yml [new file with mode: 0644]
core/README.md [new file with mode: 0644]
core/build.gradle [new file with mode: 0644]
core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12.bin [new file with mode: 0644]
core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex [new file with mode: 0644]
core/build/.transforms/5e84c834bc7ccd55cb3ab8cb23275e8e.bin [new file with mode: 0644]
core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin [new file with mode: 0644]
core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/MQTTConstants.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/MQTTException.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/MQTTHelper.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/MQTTIdentifierHelper.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/MQTTVersion.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnack.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTConnect.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTDisconnect.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTMessage.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingreq.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPingresp.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPuback.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubcomp.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPublish.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrec.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTPubrel.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSuback.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTSubscribe.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsuback.class [new file with mode: 0644]
core/build/classes/java/main/se/wetcat/qatja/messages/MQTTUnsubscribe.class [new file with mode: 0644]
core/build/libs/core.jar [new file with mode: 0644]
core/build/tmp/jar/MANIFEST.MF [new file with mode: 0644]
core/gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
core/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
core/local.properties [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/MQTTConstants.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/MQTTException.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/MQTTHelper.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/MQTTIdentifierHelper.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/MQTTVersion.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTConnack.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTConnect.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTDisconnect.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTMessage.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPingreq.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPingresp.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPuback.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPubcomp.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPublish.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPubrec.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTPubrel.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTSuback.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTSubscribe.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTUnsuback.java [new file with mode: 0644]
core/src/main/java/se/wetcat/qatja/messages/MQTTUnsubscribe.java [new file with mode: 0644]
core/travis_prebuild.sh [new file with mode: 0644]

diff --git a/core b/core
deleted file mode 160000 (submodule)
index b6b76d5..0000000
--- a/core
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b6b76d5e81e7d87cfca5b1bd72c57d351461640c
diff --git a/core/.classpath b/core/.classpath
new file mode 100644 (file)
index 0000000..45b0c68
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src/main/java"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7/"/>
+       <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/.project b/core/.project
new file mode 100644 (file)
index 0000000..568a4ab
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>core</name>
+       <comment>Project core created by Buildship.</comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/.properties.original b/core/.properties.original
new file mode 100644 (file)
index 0000000..4b7e5a3
--- /dev/null
@@ -0,0 +1 @@
+ARTIFACT_VERSION=@VERSION@
diff --git a/core/.travis.yml b/core/.travis.yml
new file mode 100644 (file)
index 0000000..7dd56bd
--- /dev/null
@@ -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 (file)
index 0000000..542c6a1
--- /dev/null
@@ -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
+<dependency>
+  <groupId>se.wetcat.qatja</groupId>
+  <artifactId>core</artifactId>
+  <version>1.0.1</version>
+  <type>pom</type>
+</dependency>
+```
+
+### Acknowledgements
+
+Based on the work by [Andreas Göransson](https://github.com/agoransson) and [David Cuartielles](https://github.com/dcuartielles) in the book ["Professional Android Open Accessory programming with Arduino"](https://github.com/aoabook) and later [mqtt4processing](https://github.com/agoransson/mqtt4processing) and [mqtt4android](https://github.com/agoransson/mqtt4android).
+
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644 (file)
index 0000000..3a3aa92
--- /dev/null
@@ -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 (file)
index 0000000..ae91532
--- /dev/null
@@ -0,0 +1 @@
+o/core\r
diff --git a/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex b/core/build/.transforms/25ec1d29f49b0007a0a2169d49d41b12/core/classes.dex
new file mode 100644 (file)
index 0000000..9193966
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 (file)
index 0000000..52974b6
--- /dev/null
@@ -0,0 +1 @@
+i/\r
diff --git a/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e.bin
new file mode 100644 (file)
index 0000000..ae91532
--- /dev/null
@@ -0,0 +1 @@
+o/core\r
diff --git a/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex b/core/build/.transforms/6217336ef9697f1a57d60152dcfed48e/core/classes.dex
new file mode 100644 (file)
index 0000000..583a303
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 (file)
index 0000000..c7d9b96
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 (file)
index 0000000..7f266b6
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 (file)
index 0000000..e200d40
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 (file)
index 0000000..2a59092
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 (file)
index 0000000..2a8c00e
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 (file)
index 0000000..89fb105
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 (file)
index 0000000..924bc70
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 (file)
index 0000000..37be148
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 (file)
index 0000000..b978d52
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 (file)
index 0000000..4fb821d
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 (file)
index 0000000..cfb9142
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 (file)
index 0000000..9f5b5e3
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 (file)
index 0000000..1e3d1df
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 (file)
index 0000000..dc07bc3
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 (file)
index 0000000..5b3af63
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 (file)
index 0000000..5657cb1
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 (file)
index 0000000..74b96e4
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 (file)
index 0000000..1035383
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 (file)
index 0000000..20abf50
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 (file)
index 0000000..c2e3fa7
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 (file)
index 0000000..a218237
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 (file)
index 0000000..58630c0
--- /dev/null
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0\r
+\r
diff --git a/core/gradle/wrapper/gradle-wrapper.jar b/core/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..3d0dee6
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 (file)
index 0000000..1a45772
--- /dev/null
@@ -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 (file)
index 0000000..6f44849
--- /dev/null
@@ -0,0 +1,8 @@
+## This file must *NOT* be checked into Version Control Systems,\r
+# as it contains information specific to your local configuration.\r
+#\r
+# Location of the SDK. This is only used by Gradle.\r
+# For customization when using a Version Control System, please read the\r
+# header note.\r
+#Fri Sep 04 17:00:12 CST 2020\r
+sdk.dir=C\:\\Users\\incar\\AppData\\Local\\Android\\Sdk\r
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 (file)
index 0000000..7d903f0
--- /dev/null
@@ -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 (file)
index 0000000..032b9fc
--- /dev/null
@@ -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 (file)
index 0000000..4be998e
--- /dev/null
@@ -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 (file)
index 0000000..f8854cc
--- /dev/null
@@ -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<Integer, MQTTMessage> sentPackages = new ConcurrentHashMap<>();
+  private ConcurrentHashMap<Integer, MQTTMessage> receivedPackages = new ConcurrentHashMap<>();
+
+  private List<Integer> availablePackageIdentifiers = new ArrayList<>();
+
+  private final Object lock = new Object();
+
+  /**
+   * Add MQTTMessage to the list of sent packages, used for QoS
+   * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} and
+   * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}
+   *
+   * @param msg the MQTTMessage
+   */
+  public void addSentPackage(MQTTMessage msg) {
+    synchronized (lock) {
+      sentPackages.put(msg.getPackageIdentifier(), msg);
+    }
+  }
+
+  /**
+   * Remove MQTTMessage from the list of sent packages, used for QoS
+   * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} and
+   * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}
+   *
+   * @param msg the MQTTMessage
+   */
+  public synchronized void removeSentPackage(MQTTMessage msg) {
+    synchronized (lock) {
+      sentPackages.remove(msg.getPackageIdentifier());
+      availablePackageIdentifiers.add(msg.getPackageIdentifier());
+    }
+  }
+
+  public ConcurrentHashMap<Integer, MQTTMessage> getSentPackages() {
+    synchronized (lock) {
+      return sentPackages;
+    }
+  }
+
+  public ConcurrentHashMap<Integer, MQTTMessage> getReceivedPackages() {
+    synchronized (lock) {
+      return receivedPackages;
+    }
+  }
+
+  /**
+   * Add MQTTMessage to the list of received packages, used for QoS
+   * {@link MQTTConstants#AT_MOST_ONCE} and {@link MQTTConstants#EXACTLY_ONCE}
+   *
+   * @param msg the MQTTMessage
+   */
+  public void addReceivedPackage(MQTTMessage msg) {
+    synchronized (lock) {
+      receivedPackages.put(msg.getPackageIdentifier(), msg);
+    }
+  }
+
+  /**
+   * Remove MQTTMessage to the list of received packages, used for QoS
+   * {@link MQTTConstants#AT_MOST_ONCE} and {@link MQTTConstants#EXACTLY_ONCE}
+   *
+   * @param msg the MQTTMessage
+   */
+  public void removeReceivedPackage(MQTTMessage msg) {
+    synchronized (lock) {
+      receivedPackages.remove(msg.getPackageIdentifier());
+    }
+  }
+
+  /**
+   * @return The next available identifier
+   */
+  public synchronized int getIdentifier() {
+    synchronized (lock) {
+      int identifier = 1;
+
+      if (availablePackageIdentifiers.size() > 0) {
+        // Reuse an identifier
+        identifier = availablePackageIdentifiers.remove(0);
+      } else if (sentPackages.size() > 0) {
+        // Generate the next, new, identifier by looping through the map and find the next biggest id
+//        int lastPackage = sentPackages.size() - 1;
+//        identifier = sentPackages.get(lastPackage).getPackageIdentifier() + 1;
+
+//        boolean idFound = true;
+//        do {
+//          for (int id : Collections.list(sentPackages.keys())) {
+//            if (identifier == id) {
+//              idFound = true;
+//            }
+//          }
+//        } while (idFound);
+
+        List<Integer> keys = Collections.list(sentPackages.keys());
+        for (int i = 0; i < keys.size(); i++) {
+          if (identifier == keys.get(i)) {
+            identifier++;
+            i = 0;
+          }
+        }
+      } else {
+        // Generate first identifier
+        identifier = 1;
+      }
+
+
+      return identifier;
+    }
+  }
+}
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 (file)
index 0000000..653f8fd
--- /dev/null
@@ -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 (file)
index 0000000..da567eb
--- /dev/null
@@ -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].
+ * <p>
+ * If the Client does not receive a {@link se.wetcat.qatja.MQTTConstants#CONNACK} Packet from the
+ * Server within a reasonable amount of time, the Client SHOULD close the Network Connection. A
+ * "reasonable" amount of time depends on the type of application and the communications
+ * infrastructure.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTConnack extends MQTTMessage {
+
+  @SuppressWarnings("unused")
+  private byte RESERVED;
+  private byte returnCode;
+
+  public static MQTTConnack fromBuffer(byte[] buffer) {
+    return new MQTTConnack(buffer);
+  }
+
+  private MQTTConnack(byte[] buffer) {
+    int i = 0;
+
+    // Type (just for clarity sake we'll set it...)
+    this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+    // Remaining length
+    int multiplier = 1;
+    int len = 0;
+    byte digit = 0;
+    do {
+      digit = buffer[i++];
+      len += (digit & 127) * multiplier;
+      multiplier *= 128;
+    } while ((digit & 128) != 0);
+    this.setRemainingLength(len);
+
+    // Get length of protocol name
+    len = ((buffer[i] >> 8) & 0xFF) | (buffer[i + 1] & 0xFF);
+
+    // Get variable header (always length 2 in CONNACK)
+    variableHeader = new byte[2];
+    System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+    // Get payload
+    payload = new byte[remainingLength - variableHeader.length];
+    if (payload.length > 0)
+      System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+    RESERVED = variableHeader[0];
+    returnCode = variableHeader[1];
+  }
+
+  /**
+   * @return the returnCode
+   */
+  public byte getReturnCode() {
+    return returnCode;
+  }
+
+  @Override
+  protected byte[] generateFixedHeader() throws MQTTException, IOException {
+    // Client doesn't generate CONNACK
+    return null;
+  }
+
+  @Override
+  protected byte[] generateVariableHeader() throws MQTTException, IOException {
+    // Client doesn't generate CONNACK
+    return null;
+  }
+
+  @Override
+  protected byte[] generatePayload() throws MQTTException, IOException {
+    // Client doesn't generate CONNACK
+    return null;
+  }
+
+}
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 (file)
index 0000000..c582f4e
--- /dev/null
@@ -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].
+ * <p>
+ * A Client can only send the {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet once over a
+ * Network Connection. The Server MUST process a second
+ * {@link se.wetcat.qatja.MQTTConstants#CONNECT} Packet sent from a Client as a protocol violation
+ * and disconnect the Client [MQTT-3.1.0-2].  See section 4.8 for information about handling errors.
+ * <p>
+ * The payload contains one or more encoded fields. They specify a unique Client identifier for the
+ * Client, a Will topic, Will Message, User Name and Password. All but the Client identifier are
+ * optional and their presence is determined based on flags in the variable header.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-06
+ */
+public class MQTTConnect extends MQTTMessage {
+
+  /**
+   * The identifier for the client, MUST be present in all {@link se.wetcat.qatja.MQTTConstants#CONNECT}
+   * messages
+   */
+  private String clientIdentifier;
+
+  protected byte protocolVersion;
+
+  protected String protocolName;
+
+  /**
+   * Position 5 of Connect Flags. If the Will Flag is set to 0, the Will
+   * Retain Flag MUST be set to 0.
+   */
+  private boolean willRetain;
+  private boolean willFlag = true;
+  private String willTopic = "wtopic";
+  private String willMessage = "wmessage";
+  private byte willQoS;
+
+  private String username;
+  private String password;
+
+  private boolean cleanSession;
+  private int keepAlive;
+
+  public static MQTTConnect newInstance() {
+    return new MQTTConnect();
+  }
+
+  public static MQTTConnect newInstance(String clientIdentifier) {
+    return new MQTTConnect(clientIdentifier);
+  }
+
+  public static MQTTConnect newInstance(String clientIdentifier, String username, String password) {
+    return new MQTTConnect(clientIdentifier, username, password);
+  }
+
+  public static MQTTConnect newInstance(String clientIdentifier, String username, String password,
+                                        boolean cleanSession) {
+    return new MQTTConnect(clientIdentifier, username, password, cleanSession);
+  }
+
+  public static MQTTConnect newInstance(String clientIdentifier, String username, String password,
+                                        boolean willRetain, byte willQoS, String willTopic,
+                                        String willMessage, boolean cleanSession, int keepAlive) {
+    return new MQTTConnect(clientIdentifier, username, password, willRetain, willQoS, willTopic, willMessage, cleanSession, keepAlive);
+  }
+
+  public static MQTTConnect fromButton(byte[] buffer) {
+    return new MQTTConnect(buffer);
+  }
+
+  /**
+   * Default MQTTConnect constructor. All flags set to default values, and no
+   * client identifier set (cleanSession set to true). Keep alive set to 10
+   * seconds.
+   */
+  private MQTTConnect() {
+    this("Qatja-" + System.nanoTime());
+  }
+
+  /**
+   * Create a new MQTT Connect message
+   *
+   * @param clientIdentifier The client identifier
+   */
+  private MQTTConnect(String clientIdentifier) {
+    this(clientIdentifier, null, null);
+  }
+
+  /**
+   * Create a new MQTT Connect message
+   *
+   * @param clientIdentifier The client identifier
+   * @param username         The client username
+   * @param password         The client password
+   */
+  private MQTTConnect(String clientIdentifier, String username, String password) {
+    this(clientIdentifier, username, password, false);
+  }
+
+  /**
+   * Create a new MQTT Connect message
+   *
+   * @param clientIdentifier The client identifier
+   * @param username         The client username
+   * @param password         The client password
+   * @param cleanSession     Connect with clear session state
+   */
+  private MQTTConnect(String clientIdentifier, String username, String password, boolean cleanSession) {
+    this(clientIdentifier, username, password, false, AT_MOST_ONCE, null, null, cleanSession, 10);
+  }
+
+  /**
+   * Create a new MQTT Connect message
+   *
+   * @param clientIdentifier The client identifier
+   * @param username         The client username
+   * @param password         The client password
+   * @param willRetain       Set last will and testament
+   * @param willQoS          Last will QoS
+   * @param willTopic        Last will topic
+   * @param willMessage      Last will message
+   * @param cleanSession     Connect with clean session state
+   * @param keepAlive        Time interval in seconds
+   */
+  private MQTTConnect(String clientIdentifier, String username, String password, boolean willRetain, byte willQoS,
+                      String willTopic, String willMessage, boolean cleanSession, int keepAlive) {
+    this.type = CONNECT;
+
+    this.clientIdentifier = clientIdentifier;
+    this.cleanSession = cleanSession;
+    this.keepAlive = keepAlive;
+
+    this.willRetain = willRetain;
+    this.willFlag = (willTopic != null && willMessage != null);
+    this.willTopic = willTopic;
+    this.willMessage = willMessage;
+    this.willQoS = willQoS;
+
+    this.username = username;
+    this.password = password;
+  }
+
+  /**
+   * Create MQTT Connect message from a byte[].
+   *
+   * @param buffer Buffer from which to create message
+   */
+  private MQTTConnect(byte[] buffer) {
+    int i = 0;
+    // Type (just for clarity sake we'll set it...)
+    this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+    // Remaining length
+    int multiplier = 1;
+    int len = 0;
+    byte digit = 0;
+    do {
+      digit = buffer[i++];
+      len += (digit & 127) * multiplier;
+      multiplier *= 128;
+    } while ((digit & 128) != 0);
+    this.setRemainingLength(len);
+
+    // Get length of protocol name
+    len = ((buffer[i] >> 8) & 0xFF) | (buffer[i + 1] & 0xFF);
+
+    // Get variable header (always length 10 in CONNECT)
+    variableHeader = new byte[10];
+    System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+    // Get payload
+    payload = new byte[remainingLength - variableHeader.length];
+    System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+    i = 2;
+    protocolName = new String(variableHeader, i, len);
+    protocolVersion = variableHeader[(i += len)];
+    flags = variableHeader[++i];
+    keepAlive = (variableHeader[++i] >> 8) & 0xFF | (variableHeader[++i] & 0xFF);
+  }
+
+  /**
+   * Set last will and testament
+   *
+   * @param willTopic   Set the last will topic
+   * @param willMessage Set last will message
+   * @param willRetain  Set last will retain flag
+   * @param willQoS     Set last will QoS
+   */
+  public void setWill(String willTopic, String willMessage, boolean willRetain, byte willQoS) {
+    this.willFlag = true;
+    this.willTopic = willTopic;
+    this.willMessage = willMessage;
+    this.willQoS = willQoS;
+  }
+
+  @Override
+  protected byte[] generateFixedHeader() throws MQTTException, IOException {
+    // FIXED HEADER
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+    // Type
+    out.write((byte) (type << 4));
+
+    // Flags (none for CONNECT)
+
+    // Remaining length
+    int length = variableHeader.length + payload.length;
+    this.setRemainingLength(length);
+    do {
+      byte digit = (byte) (length % 128);
+      length /= 128;
+      if (length > 0)
+        digit = (byte) (digit | 0x80);
+      out.write(digit);
+    } while (length > 0);
+
+    return out.toByteArray();
+  }
+
+  @Override
+  protected byte[] generateVariableHeader() throws MQTTException, IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+    // Some packages has this
+    if (protocolName == null)
+      out.write(getProtocol());
+    else {
+      // For some reason I need to test this in the interop testing...
+      out.write((protocolName.getBytes("UTF-8").length >> 8) & 0xFF); // MSB
+      out.write(protocolName.getBytes("UTF-8").length & 0xFF); // LSB
+      out.write(protocolName.getBytes("UTF-8"));
+
+      // Protocol Level (Used by all messages)
+      out.write((byte) VERSION_311);
+    }
+
+    // CONNECT FLAGS
+    byte flags = (byte) (0x00);
+
+    byte username = (byte) ((this.username != null && this.username.length() >= 0) ? 0x01 : 0x00);
+    byte password = (byte) ((this.password != null && this.password.length() >= 0) ? 0x01 : 0x00);
+    byte willRetain = (byte) (this.willRetain ? 0x01 : 0x00);
+    byte willQoS = this.willQoS;
+    byte willFlag = (byte) ((this.willRetain && this.willFlag) ? 0x01 : 0x00);
+    byte cleanSession = (byte) (this.cleanSession ? 0x01 : 0x00);
+    byte RESERVED = (byte) 0x00;
+
+    flags = (byte) ((username << 7) | (password << 6) | (willRetain << 5) | (willQoS << 3) | (willFlag << 2)
+        | (cleanSession << 1) | (RESERVED << 0));
+
+    // Write flags
+    out.write(flags);
+
+    // Keep alive (Use lower two bytes of integer)
+    out.write(MQTTHelper.MSB(keepAlive));
+    out.write(MQTTHelper.LSB(keepAlive));
+
+    return out.toByteArray();
+  }
+
+  @Override
+  protected byte[] generatePayload() throws MQTTException, IOException {
+    // PAYLOAD
+    ByteArrayOutputStream payload = new ByteArrayOutputStream();
+
+    // CLIENT IDENTIFIER
+    // ClientID MUST be present and MUST be the first field in payload
+    if (clientIdentifier == null)
+      throw new MQTTException("Client identifier invalid");
+    // ClientID MUST be UTF-8
+    if (MQTTHelper.isUTF8(clientIdentifier.getBytes("UTF-8")))
+      throw new MQTTException("Invalid identifier encoding");
+    // ClientID MUST be length between 0 and 65535
+    if (clientIdentifier.getBytes("UTF-8").length < MIN_LENGTH
+        || clientIdentifier.getBytes("UTF-8").length > MAX_LENGTH)
+      throw new MQTTException("Client identifier invalid length");
+
+    payload.write(MQTTHelper.MSB(clientIdentifier.getBytes("UTF-8"))); // MSB
+    payload.write(MQTTHelper.LSB(clientIdentifier.getBytes("UTF-8"))); // LSB
+    payload.write(clientIdentifier.getBytes("UTF-8"));
+
+    // WILL
+    if (willFlag) {
+      // Will topic MUST be present
+      if (willTopic == null)
+        throw new MQTTException("Will flag set, will topic MUST be present");
+      // Will topic MUST be UTF-8
+      if (MQTTHelper.isUTF8(willTopic.getBytes("UTF-8")))
+        throw new MQTTException("Invalid will topic encoding");
+      // Write Will topic
+      payload.write(MQTTHelper.MSB(willTopic.getBytes("UTF-8"))); // MSB
+      payload.write(MQTTHelper.LSB(willTopic.getBytes("UTF-8"))); // LSB
+      payload.write(willTopic.getBytes("UTF-8"));
+
+      // Will message MUST be UTF-8
+      if (willMessage == null)
+        throw new MQTTException("Will flag set, will message MUST be present");
+      if (MQTTHelper.isUTF8(willMessage.getBytes("UTF-8")))
+        throw new MQTTException("Invalid will message encoding");
+      // Write Will message
+      payload.write(MQTTHelper.MSB(willMessage.getBytes("UTF-8"))); // MSB
+      payload.write(MQTTHelper.LSB(willMessage.getBytes("UTF-8"))); // LSB
+      payload.write(willMessage.getBytes("UTF-8"));
+    }
+
+    // USERNAME
+    if (username != null && username.length() >= 0) {
+      // Username MUST be UTF-8
+      if (MQTTHelper.isUTF8(username.getBytes("UTF-8")))
+        throw new MQTTException("Invalid username encoding");
+      // Write username
+      payload.write(MQTTHelper.MSB(username.getBytes("UTF-8"))); // MSB
+      payload.write(MQTTHelper.LSB(username.getBytes("UTF-8"))); // LSB
+      payload.write(username.getBytes("UTF-8"));
+    }
+
+    // PASSWORD
+    if (password != null && password.length() >= 0) {
+      // Password MUST be UTF-8
+      if (MQTTHelper.isUTF8(password.getBytes("UTF-8")))
+        throw new MQTTException("Invalid password encoding");
+      // Password contains 0 to 65535 bytes
+      if (password.getBytes("UTF-8").length < MIN_LENGTH || password.getBytes("UTF-8").length > MAX_LENGTH)
+        throw new MQTTException("Password invalid length");
+      // Write password
+      payload.write(MQTTHelper.MSB(password.getBytes("UTF-8"))); // MSB
+      payload.write(MQTTHelper.LSB(password.getBytes("UTF-8"))); // LSB
+      payload.write(password.getBytes("UTF-8"));
+//      Logger.getGlobal().info("mqtt_"+payload.toString());
+    }
+
+    return payload.toByteArray();
+  }
+
+  /**
+   * @return The clientIdentifier
+   */
+  public String getClientIdentifier() {
+    return clientIdentifier;
+  }
+
+  /**
+   * @param clientIdentifier The clientIdentifier to set
+   */
+  public void setClientIdentifier(String clientIdentifier) {
+    this.clientIdentifier = clientIdentifier;
+  }
+
+  /**
+   * @return The protocolVersion
+   */
+  public byte getProtocolVersion() {
+    return protocolVersion;
+  }
+
+  /**
+   * @param protocolVersion The protocolVersion to set
+   */
+  public void setProtocolVersion(byte protocolVersion) {
+    this.protocolVersion = protocolVersion;
+  }
+
+  /**
+   * @return The protocolName
+   */
+  public String getProtocolName() {
+    return protocolName;
+  }
+
+  /**
+   * @param protocolName The protocolName to set
+   */
+  public void setProtocolName(String protocolName) {
+    this.protocolName = protocolName;
+  }
+
+  /**
+   * @return The willRetain
+   */
+  public boolean isWillRetain() {
+    return willRetain;
+  }
+
+  /**
+   * @param willRetain The willRetain to set
+   */
+  public void setWillRetain(boolean willRetain) {
+    this.willRetain = willRetain;
+  }
+
+  /**
+   * @return The willFlag
+   */
+  public boolean isWillFlag() {
+    return willFlag;
+  }
+
+  /**
+   * @param willFlag The willFlag to set
+   */
+  public void setWillFlag(boolean willFlag) {
+    this.willFlag = willFlag;
+  }
+
+  /**
+   * @return The willTopic
+   */
+  public String getWillTopic() {
+    return willTopic;
+  }
+
+  /**
+   * @param willTopic The willTopic to set
+   */
+  public void setWillTopic(String willTopic) {
+    this.willTopic = willTopic;
+  }
+
+  /**
+   * @return The willMessage
+   */
+  public String getWillMessage() {
+    return willMessage;
+  }
+
+  /**
+   * @param willMessage The willMessage to set
+   */
+  public void setWillMessage(String willMessage) {
+    this.willMessage = willMessage;
+  }
+
+  /**
+   * @return The willQoS
+   */
+  public byte getWillQoS() {
+    return willQoS;
+  }
+
+  /**
+   * @param willQoS The willQoS to set
+   */
+  public void setWillQoS(byte willQoS) {
+    this.willQoS = willQoS;
+  }
+
+  /**
+   * @return The username
+   */
+  public String getUsername() {
+    return username;
+  }
+
+  /**
+   * @param username The username to set
+   */
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  /**
+   * @return The password
+   */
+  public String getPassword() {
+    return password;
+  }
+
+  /**
+   * @param password The password to set
+   */
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  /**
+   * @return The cleanSession
+   */
+  public boolean isCleanSession() {
+    return cleanSession;
+  }
+
+  /**
+   * @param cleanSession The cleanSession to set
+   */
+  public void setCleanSession(boolean cleanSession) {
+    this.cleanSession = cleanSession;
+  }
+
+  /**
+   * @return The keepAlive
+   */
+  public int getKeepAlive() {
+    return keepAlive;
+  }
+
+  /**
+   * @param keepAlive The keepAlive to set
+   */
+  public void setKeepAlive(int keepAlive) {
+    this.keepAlive = keepAlive;
+  }
+
+}
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 (file)
index 0000000..ecd502a
--- /dev/null
@@ -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 (file)
index 0000000..31b772a
--- /dev/null
@@ -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 (file)
index 0000000..9010be4
--- /dev/null
@@ -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 (file)
index 0000000..ce43669
--- /dev/null
@@ -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 (file)
index 0000000..71809fd
--- /dev/null
@@ -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 (file)
index 0000000..669bc63
--- /dev/null
@@ -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 (file)
index 0000000..5067f1d
--- /dev/null
@@ -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 (file)
index 0000000..5a42c47
--- /dev/null
@@ -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 (file)
index 0000000..ef53c36
--- /dev/null
@@ -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 (file)
index 0000000..0dcc7e3
--- /dev/null
@@ -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.
+ * <p>
+ * A {@link se.wetcat.qatja.MQTTConstants#SUBACK} Packet contains a list of return codes, that
+ * specify the maximum QoS level that was granted in each Subscription that was requested by the
+ * {@link se.wetcat.qatja.MQTTConstants#SUBSCRIBE}.
+ *
+ * @author Andreas Goransson
+ * @version 1.0.0
+ * @since 2017-05-07
+ */
+public class MQTTSuback extends MQTTMessage {
+
+  public static MQTTSuback fromBuffer(byte[] buffer) {
+    return new MQTTSuback(buffer);
+  }
+
+  /**
+   * Construct a {@link se.wetcat.qatja.MQTTConstants#SUBACK} message from a buffer
+   *
+   * @param buffer The buffer
+   */
+  private MQTTSuback(byte[] buffer) {
+    int i = 0;
+    // Type (just for clarity sake we'll set it...)
+    this.setType((byte) ((buffer[i++] >> 4) & 0x0F));
+
+    // Remaining length
+    int multiplier = 1;
+    int len = 0;
+    byte digit = 0;
+    do {
+      digit = buffer[i++];
+      len += (digit & 127) * multiplier;
+      multiplier *= 128;
+    } while ((digit & 128) != 0);
+    this.setRemainingLength(len);
+
+    // Get variable header (always length 2 in SUBACK)
+    variableHeader = new byte[2];
+    System.arraycopy(buffer, i, variableHeader, 0, variableHeader.length);
+
+    // Get payload
+    payload = new byte[remainingLength - variableHeader.length];
+    if (payload.length > 0)
+      System.arraycopy(buffer, i + variableHeader.length, payload, 0, remainingLength - variableHeader.length);
+
+    // Get package identifier
+    packageIdentifier = (variableHeader[variableHeader.length - 2] >> 8 & 0xFF) | (variableHeader[variableHeader.length - 1] & 0xFF);
+  }
+
+  @Override
+  protected byte[] generateFixedHeader() throws MQTTException, IOException {
+    // Client doesn't create SUBACK
+    return null;
+  }
+
+  @Override
+  protected byte[] generateVariableHeader() throws MQTTException, IOException {
+    // Client doesn't create SUBACK
+    return null;
+  }
+
+  @Override
+  protected byte[] generatePayload() throws MQTTException, IOException {
+    // Client doesn't create SUBACK
+    return null;
+  }
+
+}
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 (file)
index 0000000..554d62b
--- /dev/null
@@ -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 (file)
index 0000000..f0c1dba
--- /dev/null
@@ -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 (file)
index 0000000..7d452a4
--- /dev/null
@@ -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 (file)
index 0000000..bc2e20f
--- /dev/null
@@ -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