This commit is contained in:
candi
2025-09-04 04:43:31 +08:00
parent 2723b0ddd3
commit 212c19d40a
136 changed files with 96 additions and 766 deletions

View File

@@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common;
/**
* Utility class to help create exceptions of the correct type.
*/
public class ExceptionHelper {
public static MqttException createMqttException(int reasonCode) {
return new MqttException(reasonCode);
}
public static MqttException createMqttException(Throwable cause) {
return new MqttException(cause);
}
/**
* Returns whether or not the specified class is available to the current
* class loader. This is used to protect the code against using Java SE
* APIs on Java ME.
* @param className The ClassName
* @return if the class is available
*/
public static boolean isClassAvailable(String className) {
boolean result = false;
try {
Class.forName(className);
result = true;
}
catch (ClassNotFoundException ex) {
}
return result;
}
// Utility classes should not have a public or default constructor.
private ExceptionHelper() {
}
}

View File

@@ -0,0 +1,158 @@
package org.eclipse.paho.mqttv5.common;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
public class MqttException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Client encountered an exception. Use the {@link #getCause()} method to get
* the underlying reason.
*/
public static final short REASON_CODE_CLIENT_EXCEPTION = 0x00;
// New MQTTv5 Packet Errors
public static final int REASON_CODE_INVALID_IDENTIFIER = 50000; // Invalid Identifier in the IV fields
public static final int REASON_CODE_INVALID_RETURN_CODE = 50001; // Invalid Return code
public static final int REASON_CODE_MALFORMED_PACKET = 50002; // Packet was somehow malformed and did not comply to the MQTTv5 specification
public static final int REASON_CODE_UNSUPPORTED_PROTOCOL_VERSION = 50003; // The CONNECT packet did not contain the correct protocol name or version
/**
* The Server sent a publish message with an invalid topic alias.
*/
public static final int REASON_CODE_INVALID_TOPIC_ALAS = 50004;
/**
* The client attempted to decode a property that had already been decoded, and can only be included once.
*/
public static final int REASON_CODE_DUPLICATE_PROPERTY = 50005;
private int reasonCode;
private Throwable cause;
private String disconnectReasonString;
private int disconnectReasonCode = 0;
/**
* Constructs a new <code>MqttException</code> with the specified code as the
* underlying reason.
*
* @param reasonCode
* the reason code for the exception.
*/
public MqttException(int reasonCode) {
super();
this.reasonCode = reasonCode;
}
/**
* Constructs a new <code>MqttException</code> with the specified code as the
* underlying reason, with the disconnect reason if available. This is only
* meant as a hint for the developer, as the
* <code>MqttCallback.disconnected</code> callback is the intended disconnect
* notification mechanism.
*
* @param reasonCode the reason code for the exception.
* @param disconnect diconnect flag
*/
public MqttException(int reasonCode, MqttDisconnect disconnect) {
super();
this.reasonCode = reasonCode;
if (disconnect != null) {
this.disconnectReasonCode = disconnect.getReturnCode();
if (disconnect.getProperties() != null) {
this.disconnectReasonString = disconnect.getProperties().getReasonString();
}
}
}
/**
* Constructs a new <code>MqttException</code> with the specified
* <code>Throwable</code> as the underlying reason.
*
* @param cause
* the underlying cause of the exception.
*/
public MqttException(Throwable cause) {
super();
this.reasonCode = REASON_CODE_CLIENT_EXCEPTION;
this.cause = cause;
}
/**
* Constructs a new <code>MqttException</code> with the specified
* <code>Throwable</code> as the underlying reason.
*
* @param reason
* the reason code for the exception.
* @param cause
* the underlying cause of the exception.
*/
public MqttException(int reason, Throwable cause) {
super();
this.reasonCode = reason;
this.cause = cause;
}
/**
* Returns the reason code for this exception.
*
* @return the code representing the reason for this exception.
*/
public int getReasonCode() {
return reasonCode;
}
/**
* Returns the underlying cause of this exception, if available.
*
* @return the Throwable that was the root cause of this exception, which may be
* <code>null</code>.
*/
@Override
public Throwable getCause() {
return cause;
}
/**
* Returns the detail message for this exception.
*
* @return the detail message, which may be <code>null</code>.
*/
@Override
public String getMessage() {
ResourceBundle bundle = ResourceBundle.getBundle("org.eclipse.paho.mqttv5.common.nls.messages");
String message;
try {
message = bundle.getString(Integer.toString(reasonCode));
} catch (MissingResourceException mre) {
message = "Untranslated MqttException - RC: " + reasonCode;
}
if(this.disconnectReasonCode != 0) {
message += " Disconnect RC: " + disconnectReasonCode;
}
if(this.disconnectReasonString != null) {
message += " Disconnect Reason: " + disconnectReasonString;
}
return message;
}
/**
* Returns a <code>String</code> representation of this exception.
*
* @return a <code>String</code> representation of this exception.
*/
@Override
public String toString() {
String result = getMessage() + " (" + reasonCode + ")";
if (cause != null) {
result = result + " - " + cause.toString();
}
return result;
}
}

View File

@@ -0,0 +1,298 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
/**
* An MQTT message holds the application payload and options specifying how the
* message is to be delivered The message includes a "payload" (the body of the
* message) represented as a byte[].
*/
public class MqttMessage {
private boolean mutable = true;
private byte[] payload;
private int qos = 1;
private boolean retained = false;
private boolean dup = false;
private int messageId;
private MqttProperties properties;
/**
* Utility method to validate the supplied QoS value.
*
* @param qos
* The Quality Of Service Level to validate
* @throws IllegalArgumentException
* if value of QoS is not 0, 1 or 2.
*
*/
public static void validateQos(int qos) {
if ((qos < 0) || (qos > 2)) {
throw new IllegalArgumentException();
}
}
/**
* Constructs a message with an empty payload, and all other values set to
* defaults.
*
* The defaults are:
* <ul>
* <li>Message QoS set to 1</li>
* <li>Message will not be "retained" by the server</li>
* </ul>
*/
public MqttMessage() {
setPayload(new byte[] {});
}
/**
* Constructs a message with the specified byte array as a payload, and all
* other values set to defaults.
*
* @param payload
* the payload
*/
public MqttMessage(byte[] payload) {
setPayload(payload);
}
/**
* Contructs an message with the specified payload, qos and retained flag.
*
* @param payload
* The Message Payload.
* @param qos
* The Message QoS.
* @param retained
* If the message is retained.
* @param properties
* The Message {@link MqttProperties}
*/
public MqttMessage(byte[] payload, int qos, boolean retained, MqttProperties properties) {
setPayload(payload);
setQos(qos);
setRetained(retained);
setProperties(properties);
}
/**
* Returns the payload as a byte array.
*
* @return the payload as a byte array.
*/
public byte[] getPayload() {
return payload;
}
/**
* Clears the payload, resetting it to be empty.
*
* @throws IllegalStateException
* if this message cannot be edited
*/
public void clearPayload() {
checkMutable();
this.payload = new byte[] {};
}
/**
* Sets the payload of this message to be the specified byte array.
*
* @param payload
* the payload for this message.
* @throws IllegalStateException
* if this message cannot be edited
* @throws NullPointerException
* if no payload is provided
*/
public void setPayload(byte[] payload) {
checkMutable();
if (payload == null) {
throw new NullPointerException();
}
this.payload = payload;
}
/**
* Returns whether or not this message should be/was retained by the server. For
* messages received from the server, this method returns whether or not the
* message was from a current publisher, or was "retained" by the server as the
* last message published on the topic.
*
* @return <code>true</code> if the message should be, or was, retained by the
* server.
* @see #setRetained(boolean)
*/
public boolean isRetained() {
return retained;
}
/**
* Whether or not the publish message should be retained by the messaging
* engine. Sending a message with retained set to <code>true</code> and with an
* empty byte array as the payload e.g. <code>new byte[0]</code> will clear the
* retained message from the server. The default value is <code>false</code>
*
* @param retained
* whether or not the messaging engine should retain the message.
* @throws IllegalStateException
* if this message cannot be edited
*/
public void setRetained(boolean retained) {
checkMutable();
this.retained = retained;
}
/**
* Returns the quality of service for this message.
*
* @return the quality of service to use, either 0, 1, or 2.
* @see #setQos(int)
*/
public int getQos() {
return qos;
}
/**
* Sets the quality of service for this message.
* <ul>
* <li>Quality of Service 0 - indicates that a message should be delivered at
* most once (zero or one times). The message will not be persisted to disk, and
* will not be acknowledged across the network. This QoS is the fastest, but
* should only be used for messages which are not valuable - note that if the
* server cannot process the message (for example, there is an authorization
* problem). Also known as "fire and forget".</li>
*
* <li>Quality of Service 1 - indicates that a message should be delivered at
* least once (one or more times). The message can only be delivered safely if
* it can be persisted, so the application must supply a means of persistence
* using <code>MqttConnectOptions</code>. If a persistence mechanism is not
* specified, the message will not be delivered in the event of a client
* failure. The message will be acknowledged across the network. This is the
* default QoS.</li>
*
* <li>Quality of Service 2 - indicates that a message should be delivered once.
* The message will be persisted to disk, and will be subject to a two-phase
* acknowledgement across the network. The message can only be delivered safely
* if it can be persisted, so the application must supply a means of persistence
* using <code>MqttConnectOptions</code>. If a persistence mechanism is not
* specified, the message will not be delivered in the event of a client
* failure.</li>
* </ul>
*
* If persistence is not configured, QoS 1 and 2 messages will still be
* delivered in the event of a network or server problem as the client will hold
* state in memory. If the MQTT client is shutdown or fails and persistence is
* not configured then delivery of QoS 1 and 2 messages can not be maintained as
* client-side state will be lost.
*
* @param qos
* the "quality of service" to use. Set to 0, 1, 2.
* @throws IllegalArgumentException
* if value of QoS is not 0, 1 or 2.
* @throws IllegalStateException
* if this message cannot be edited
*/
public void setQos(int qos) {
checkMutable();
validateQos(qos);
this.qos = qos;
}
/**
* Sets the mutability of this object (whether or not its values can be changed.
*
* @param mutable
* <code>true</code> if the values can be changed, <code>false</code>
* to prevent them from being changed.
*/
public void setMutable(boolean mutable) {
this.mutable = mutable;
}
protected void checkMutable() throws IllegalStateException {
if (!mutable) {
throw new IllegalStateException();
}
}
public void setDuplicate(boolean dup) {
this.dup = dup;
}
/**
* Returns whether or not this message might be a duplicate of one which has
* already been received. This will only be set on messages received from the
* server.
*
* @return <code>true</code> if the message might be a duplicate.
*/
public boolean isDuplicate() {
return this.dup;
}
/**
* This is only to be used internally to provide the MQTT id of a message
* received from the server. Has no effect when publishing messages.
*
* @param messageId
* the Message Identifier
*/
public void setId(int messageId) {
this.messageId = messageId;
}
/**
* Returns the MQTT id of the message. This is only applicable to messages
* received from the server.
*
* @return the MQTT id of the message
*/
public int getId() {
return this.messageId;
}
public MqttProperties getProperties() {
return properties;
}
public void setProperties(MqttProperties properties) {
this.properties = properties;
}
/**
* Returns a string representation of this message's payload. Makes an attempt
* to return the payload as a string. As the MQTT client has no control over the
* content of the payload it may fail.
*
* @return a string representation of this message.
*/
public String toString() {
return new String(payload);
}
public String toDebugString() {
return "MqttMessage [mutable=" + mutable + ", payload=" + new String(payload) + ", qos=" + qos + ", retained="
+ retained + ", dup=" + dup + ", messageId=" + messageId + "]";
}
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.mqttv5.common;
/**
* Represents an object used to pass data to be persisted across the
* <code>MqttClientPersistence</code> interface.
* <p>
* When data is passed across the interface the header and payload are
* separated, so that unnecessary message copies may be avoided. For example, if
* a 10 MB payload was published it would be inefficient to create a byte array
* a few bytes larger than 10 MB and copy the MQTT message header and payload
* into a contiguous byte array.
* </p>
* <p>
* When the request to persist data is made a separate byte array and offset is
* passed for the header and payload. Only the data between offset and length
* need be persisted. So for example, a message to be persisted consists of a
* header byte array starting at offset 1 and length 4, plus a payload byte
* array starting at offset 30 and length 40000. There are three ways in which
* the persistence implementation may return data to the client on recovery:
* </p>
* <ul>
* <li>It could return the data as it was passed in originally, with the same
* byte arrays and offsets.</li>
* <li>It could safely just persist and return the bytes from the offset for the
* specified length. For example, return a header byte array with offset 0 and
* length 4, plus a payload byte array with offset 0 and length 40000</li>
* <li>It could return the header and payload as a contiguous byte array with
* the header bytes preceeding the payload. The contiguous byte array should be
* set as the header byte array, with the payload byte array being null. For
* example, return a single byte array with offset 0 and length 40004. This is
* useful when recovering from a file where the header and payload could be
* written as a contiguous stream of bytes.</li>
* </ul>
*/
public interface MqttPersistable {
/**
* Returns the header bytes in an array. The bytes start at
* {@link #getHeaderOffset()} and continue for {@link #getHeaderLength()}.
*
* @return the header bytes.
* @throws MqttPersistenceException
* if an error occurs getting the Header Bytes
*/
byte[] getHeaderBytes() throws MqttPersistenceException;
/**
* Returns the length of the header.
*
* @return the header length
* @throws MqttPersistenceException
* if an error occurs getting the Header length
*/
int getHeaderLength() throws MqttPersistenceException;
/**
* Returns the offset of the header within the byte array returned by
* {@link #getHeaderBytes()}.
*
* @return the header offset.
* @throws MqttPersistenceException
* if an error occurs getting the Header offset
*
*/
int getHeaderOffset() throws MqttPersistenceException;
/**
* Returns the payload bytes in an array. The bytes start at
* {@link #getPayloadOffset()} and continue for {@link #getPayloadLength()}.
*
* @return the payload bytes.
* @throws MqttPersistenceException
* if an error occurs getting the Payload Bytes
*/
byte[] getPayloadBytes() throws MqttPersistenceException;
/**
* Returns the length of the payload.
*
* @return the payload length.
* @throws MqttPersistenceException
* if an error occurs getting the Payload length
*/
int getPayloadLength() throws MqttPersistenceException;
/**
* Returns the offset of the payload within the byte array returned by
* {@link #getPayloadBytes()}.
*
* @return the payload offset.
* @throws MqttPersistenceException
* if an error occurs getting the Payload Offset
*
*/
int getPayloadOffset() throws MqttPersistenceException;
}

View File

@@ -0,0 +1,45 @@
package org.eclipse.paho.mqttv5.common;
/**
* This exception is thrown by the implementor of the persistence
* interface if there is a problem reading or writing persistent data.
*/
public class MqttPersistenceException extends MqttException {
private static final long serialVersionUID = 300L;
/** Persistence is already being used by another client. */
public static final short REASON_CODE_PERSISTENCE_IN_USE = 32200;
/**
* Constructs a new <code>MqttPersistenceException</code>
*/
public MqttPersistenceException() {
super(REASON_CODE_CLIENT_EXCEPTION);
}
/**
* Constructs a new <code>MqttPersistenceException</code> with the specified code
* as the underlying reason.
* @param reasonCode the reason code for the exception.
*/
public MqttPersistenceException(int reasonCode) {
super(reasonCode);
}
/**
* Constructs a new <code>MqttPersistenceException</code> with the specified
* <code>Throwable</code> as the underlying reason.
* @param cause the underlying cause of the exception.
*/
public MqttPersistenceException(Throwable cause) {
super(cause);
}
/**
* Constructs a new <code>MqttPersistenceException</code> with the specified
* <code>Throwable</code> as the underlying reason.
* @param reason the reason code for the exception.
* @param cause the underlying cause of the exception.
*/
public MqttPersistenceException(int reason, Throwable cause) {
super(reason, cause);
}
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common;
/**
* Thrown when a client is not authorized to perform an operation, or
* if there is a problem with the security configuration.
*/
public class MqttSecurityException extends MqttException {
private static final long serialVersionUID = 300L;
/**
* Constructs a new <code>MqttSecurityException</code> with the specified code
* as the underlying reason.
* @param reasonCode the reason code for the exception.
*/
public MqttSecurityException(int reasonCode) {
super(reasonCode);
}
/**
* Constructs a new <code>MqttSecurityException</code> with the specified
* <code>Throwable</code> as the underlying reason.
* @param cause the underlying cause of the exception.
*/
public MqttSecurityException(Throwable cause) {
super(cause);
}
/**
* Constructs a new <code>MqttSecurityException</code> with the specified
* code and <code>Throwable</code> as the underlying reason.
* @param reasonCode the reason code for the exception.
* @param cause the underlying cause of the exception.
*/
public MqttSecurityException(int reasonCode, Throwable cause) {
super(reasonCode, cause);
}
}

View File

@@ -0,0 +1,166 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common;
public class MqttSubscription {
private boolean mutable = true;
private String topic;
private int qos = 1;
private boolean noLocal = false;
private boolean retainAsPublished = false;
private int retainHandling = 0;
private int messageId;
/**
* Constructs a subscription with the specified topic with
* all other values set to defaults.
*
* The defaults are:
* <ul>
* <li>Subscription QoS is set to 1</li>
* <li>Messages published to this topic by the same client will also be received.</li>
* <li>Messages received by this subscription will keep the retain flag (if it is set).</li>
* <li>Retained messages on this topic will be delivered once the subscription has been made.</li>
* </ul>
* @param topic The Topic
*/
public MqttSubscription(String topic){
setTopic(topic);
}
public MqttSubscription(String topic, int qos) {
setTopic(topic);
setQos(qos);
}
/**
* Utility method to validate the supplied QoS value.
* @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
* @param qos The QoS level to validate.
*/
public static void validateQos(int qos) {
if ((qos < 0) || (qos > 2)) {
throw new IllegalArgumentException();
}
}
/**
* Utility method to validate the supplied Retain handling value.
* @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
* @param retainHandling the retain value to validate.
*/
public static void validateRetainHandling(int retainHandling) {
if ((retainHandling < 0) || (retainHandling > 2)) {
throw new IllegalArgumentException();
}
}
/**
* Sets the mutability of this object (whether or not its values can be
* changed.
* @param mutable <code>true</code> if the values can be changed,
* <code>false</code> to prevent them from being changed.
*/
protected void setMutable(boolean mutable) {
this.mutable = mutable;
}
protected void checkMutable() throws IllegalStateException {
if (!mutable) {
throw new IllegalStateException();
}
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
checkMutable();
if(topic == null){
throw new NullPointerException();
}
this.topic = topic;
}
public int getQos() {
return qos;
}
public void setQos(int qos) {
checkMutable();
validateQos(qos);
this.qos = qos;
}
public boolean isNoLocal() {
return noLocal;
}
public void setNoLocal(boolean noLocal) {
checkMutable();
this.noLocal = noLocal;
}
public boolean isRetainAsPublished() {
return retainAsPublished;
}
public void setRetainAsPublished(boolean retainAsPublished) {
checkMutable();
this.retainAsPublished = retainAsPublished;
}
public int getRetainHandling() {
return retainHandling;
}
public void setRetainHandling(int retainHandling) {
checkMutable();
validateRetainHandling(retainHandling);
this.retainHandling = retainHandling;
}
@Override
public String toString() {
return "MqttSubscription [mutable=" + mutable + ", topic=" + topic + ", qos=" + qos + ", noLocal=" + noLocal
+ ", retainAsPublished=" + retainAsPublished + ", retainHandling=" + retainHandling + "]";
}
/**
* This is only to be used internally to provide the MQTT id of a message
* received from the server. Has no effect when publishing messages.
* @param messageId The Message Identifier
*/
public void setId(int messageId) {
this.messageId = messageId;
}
/**
* Returns the MQTT id of the message. This is only applicable to messages
* received from the server.
* @return the MQTT id of the message
*/
public int getId() {
return this.messageId;
}
}

View File

@@ -0,0 +1,28 @@
package org.eclipse.paho.mqttv5.common;
public class Validators {
public boolean validateClientId(String clientId) {
// Count characters, surrogate pairs count as one character.
int clientIdLength = 0;
for (int i = 0; i < clientId.length() - 1; i++) {
if (isCharacterHighSurrogate(clientId.charAt(i))) {
i++;
}
clientIdLength++;
}
return clientIdLength < 65535;
}
/**
* @param ch
* the character to check.
* @return returns 'true' if the character is a high-surrogate code unit
*/
protected static boolean isCharacterHighSurrogate(char ch) {
final char minHighSurrogate = '\uD800';
final char maxHighSurrogate = '\uDBFF';
return (ch >= minHighSurrogate) && (ch <= maxHighSurrogate);
}
}

View File

@@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2016, 2019 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
/**
* Abstract super-class of all acknowledgement messages.
*/
public abstract class MqttAck extends MqttPersistableWireMessage {
public MqttAck(byte type) {
super(type);
}
@Override
protected byte getMessageInfo() {
return 0;
}
}

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
/**
* An on-the-wire representation of an MQTT AUTH message. MQTTv5 - 3.15
*/
public class MqttAuth extends MqttWireMessage {
// Return codes
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_CONTINUE_AUTHENTICATION, MqttReturnCode.RETURN_CODE_RE_AUTHENTICATE };
private static final Byte[] validProperties = { MqttProperties.AUTH_METHOD_IDENTIFIER,
MqttProperties.AUTH_DATA_IDENTIFIER, MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
/**
* Constructs an Auth message from a raw byte array
*
* @param data
* - The variable header and payload bytes.
* @throws IOException
* - if an exception occurs when decoding an input stream
* @throws MqttException
* - If an exception occurs decoding this packet
*/
public MqttAuth(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_AUTH);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream inputStream = new DataInputStream(bais);
reasonCode = inputStream.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
this.properties.decodeProperties(inputStream);
inputStream.close();
}
/**
* Constructs an Auth message from the return code
*
* @param returnCode
* - The Auth Return Code
* @param properties
* - The {@link MqttProperties} for the packet.
* @throws MqttException
* - If an exception occurs encoding this packet
*/
public MqttAuth(int returnCode, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_AUTH);
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
validateReturnCode(returnCode, validReturnCodes);
this.reasonCode = returnCode;
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Return Code
outputStream.writeByte(reasonCode);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
protected byte getMessageInfo() {
return (byte) (1);
}
public int getReturnCode() {
return reasonCode;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttAuth [returnCode=" + reasonCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
/**
* An on-the-wire representation of an MQTT CONNACK.
*/
public class MqttConnAck extends MqttAck {
public static final String KEY = "Con";
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR, MqttReturnCode.RETURN_CODE_MALFORMED_CONTROL_PACKET,
MqttReturnCode.RETURN_CODE_PROTOCOL_ERROR, MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR,
MqttReturnCode.RETURN_CODE_UNSUPPORTED_PROTOCOL_VERSION, MqttReturnCode.RETURN_CODE_IDENTIFIER_NOT_VALID,
MqttReturnCode.RETURN_CODE_BAD_USERNAME_OR_PASSWORD, MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED,
MqttReturnCode.RETURN_CODE_SERVER_UNAVAILABLE, MqttReturnCode.RETURN_CODE_SERVER_BUSY,
MqttReturnCode.RETURN_CODE_BANNED, MqttReturnCode.RETURN_CODE_BAD_AUTHENTICATION,
MqttReturnCode.RETURN_CODE_TOPIC_NAME_INVALID, MqttReturnCode.RETURN_CODE_PACKET_TOO_LARGE,
MqttReturnCode.RETURN_CODE_QUOTA_EXCEEDED, MqttReturnCode.RETURN_CODE_RETAIN_NOT_SUPPORTED,
MqttReturnCode.RETURN_CODE_USE_ANOTHER_SERVER, MqttReturnCode.RETURN_CODE_SERVER_MOVED,
MqttReturnCode.RETURN_CODE_CONNECTION_RATE_EXCEEDED };
private static final Byte[] validProperties = { MqttProperties.SESSION_EXPIRY_INTERVAL_IDENTIFIER,
MqttProperties.RECEIVE_MAXIMUM_IDENTIFIER, MqttProperties.MAXIMUM_QOS_IDENTIFIER,
MqttProperties.RETAIN_AVAILABLE_IDENTIFIER, MqttProperties.MAXIMUM_PACKET_SIZE_IDENTIFIER,
MqttProperties.ASSIGNED_CLIENT_IDENTIFIER_IDENTIFIER, MqttProperties.TOPIC_ALIAS_MAXIMUM_IDENTIFIER,
MqttProperties.WILDCARD_SUB_AVAILABLE_IDENTIFIER, MqttProperties.SUBSCRIPTION_AVAILABLE_IDENTIFIER,
MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_IDENTIFIER, MqttProperties.SERVER_KEEP_ALIVE_IDENTIFIER,
MqttProperties.RESPONSE_INFO_IDENTIFIER, MqttProperties.SERVER_REFERENCE_IDENTIFIER,
MqttProperties.AUTH_METHOD_IDENTIFIER, MqttProperties.AUTH_DATA_IDENTIFIER,
MqttProperties.REASON_STRING_IDENTIFIER, MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
private boolean sessionPresent;
private MqttProperties properties;
public MqttConnAck(byte[] variableHeader) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_CONNACK);
this.properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(variableHeader);
DataInputStream dis = new DataInputStream(bais);
sessionPresent = (dis.readUnsignedByte() & 0x01) == 0x01;
reasonCode = dis.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
this.properties.decodeProperties(dis);
dis.close();
}
public MqttConnAck(boolean sessionPresent, int returnCode, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_CONNACK);
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
this.sessionPresent = sessionPresent;
validateReturnCode(returnCode, validReturnCodes);
this.reasonCode = returnCode;
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// Encode the Session Present Flag
byte connectAchnowledgeFlag = 0;
if (sessionPresent) {
connectAchnowledgeFlag |= 0x01;
}
dos.write(connectAchnowledgeFlag);
// Encode the Connect Return Code
dos.write((byte) reasonCode);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
dos.write(identifierValueFieldsByteArray);
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
/**
* Returns whether or not this message needs to include a message ID.
*/
@Override
public boolean isMessageIdRequired() {
return false;
}
@Override
public String getKey() {
return KEY;
}
public boolean getSessionPresent() {
return sessionPresent;
}
public void setSessionPresent(boolean sessionPresent) {
this.sessionPresent = sessionPresent;
}
public int getReturnCode() {
return reasonCode;
}
public void setReturnCode(int returnCode) {
this.reasonCode = returnCode;
}
@Override
public MqttProperties getProperties() {
return properties;
}
public static int[] getValidreturncodes() {
return validReturnCodes;
}
@Override
public String toString() {
return "MqttConnAck [returnCode=" + reasonCode + ", sessionPresent=" + sessionPresent + ", properties="
+ properties + "]";
}
}

View File

@@ -0,0 +1,337 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
public class MqttConnect extends MqttWireMessage {
public static final String KEY = "Con";
private MqttProperties properties;
private MqttProperties willProperties;
// Fields
private byte info;
private String clientId;
private boolean reservedByte;
private boolean cleanStart;
private MqttMessage willMessage;
private String userName;
private byte[] password;
private int keepAliveInterval;
private String willDestination;
private int mqttVersion = DEFAULT_PROTOCOL_VERSION;
private static final Byte[] validProperties = { MqttProperties.SESSION_EXPIRY_INTERVAL_IDENTIFIER,
MqttProperties.WILL_DELAY_INTERVAL_IDENTIFIER, MqttProperties.RECEIVE_MAXIMUM_IDENTIFIER,
MqttProperties.MAXIMUM_PACKET_SIZE_IDENTIFIER, MqttProperties.TOPIC_ALIAS_MAXIMUM_IDENTIFIER,
MqttProperties.REQUEST_RESPONSE_INFO_IDENTIFIER, MqttProperties.REQUEST_PROBLEM_INFO_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER, MqttProperties.AUTH_METHOD_IDENTIFIER,
MqttProperties.AUTH_DATA_IDENTIFIER };
private static final Byte[] validWillProperties = { MqttProperties.WILL_DELAY_INTERVAL_IDENTIFIER,
MqttProperties.PAYLOAD_FORMAT_INDICATOR_IDENTIFIER, MqttProperties.MESSAGE_EXPIRY_INTERVAL_IDENTIFIER,
MqttProperties.RESPONSE_TOPIC_IDENTIFIER, MqttProperties.CORRELATION_DATA_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER, MqttProperties.CONTENT_TYPE_IDENTIFIER };
/**
* Constructor for an on the wire MQTT Connect message
*
* @param info
* - Info Byte
* @param data
* - The variable header and payload bytes.
* @throws IOException
* - if an exception occurs when decoding an input stream
* @throws MqttException
* - If an exception occurs decoding this packet
*/
public MqttConnect(byte info, byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_CONNECT);
this.info = info;
this.properties = new MqttProperties(validProperties);
this.willProperties = new MqttProperties(validWillProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
// Verify the Protocol name and version
String protocolName = MqttDataTypes.decodeUTF8(dis);
if (!protocolName.equalsIgnoreCase(DEFAULT_PROTOCOL_NAME)) {
throw new MqttPacketException(MqttPacketException.PACKET_CONNECT_ERROR_UNSUPPORTED_PROTOCOL_NAME);
}
mqttVersion = dis.readByte();
if (mqttVersion != DEFAULT_PROTOCOL_VERSION) {
throw new MqttPacketException(MqttPacketException.PACKET_CONNECT_ERROR_UNSUPPORTED_PROTOCOL_VERSION);
}
byte connectFlags = dis.readByte();
reservedByte = (connectFlags & 0x01) != 0;
cleanStart = (connectFlags & 0x02) != 0;
boolean willFlag = (connectFlags & 0x04) != 0;
int willQoS = (connectFlags >> 3) & 0x03;
boolean willRetain = (connectFlags & 0x20) != 0;
boolean passwordFlag = (connectFlags & 0x40) != 0;
boolean usernameFlag = (connectFlags & 0x80) != 0;
if (reservedByte) {
throw new MqttPacketException(MqttPacketException.PACKET_CONNECT_ERROR_INVALID_RESERVE_FLAG);
}
keepAliveInterval = dis.readUnsignedShort();
properties.decodeProperties(dis);
clientId = MqttDataTypes.decodeUTF8(dis);
if (willFlag) {
willProperties.decodeProperties(dis);
if (willQoS == 3) {
throw new MqttPacketException(MqttPacketException.PACKET_CONNECT_ERROR_INVALID_WILL_QOS);
}
willDestination = MqttDataTypes.decodeUTF8(dis);
int willMessageLength = dis.readShort();
byte[] willMessageBytes = new byte[willMessageLength];
dis.read(willMessageBytes, 0, willMessageLength);
willMessage = new MqttMessage(willMessageBytes);
willMessage.setQos(willQoS);
willMessage.setRetained(willRetain);
}
if (usernameFlag) {
userName = MqttDataTypes.decodeUTF8(dis);
}
if (passwordFlag) {
int passwordLength = dis.readShort();
password = new byte[passwordLength];
dis.read(password, 0, passwordLength);
}
dis.close();
}
/**
* Constructor for a new MQTT Connect Message
*
* @param clientId
* - The Client Identifier
* @param mqttVersion
* - The MQTT Protocol version
* @param cleanStart
* - The Clean Session Identifier
* @param keepAliveInterval
* - The Keep Alive Interval
* @param properties
* - The {@link MqttProperties} for the packet.
* @param willProperties
* - The {@link MqttProperties} for the will message.
*
*/
public MqttConnect(String clientId, int mqttVersion, boolean cleanStart, int keepAliveInterval,
MqttProperties properties, MqttProperties willProperties) {
super(MqttWireMessage.MESSAGE_TYPE_CONNECT);
this.clientId = clientId;
this.mqttVersion = mqttVersion;
this.cleanStart = cleanStart;
this.keepAliveInterval = keepAliveInterval;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
this.willProperties = willProperties;
this.willProperties.setValidProperties(validWillProperties);
}
@Override
protected byte getMessageInfo() {
return (byte) 0;
}
public boolean isCleanStart() {
return cleanStart;
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// Encode the Protocol Name
MqttDataTypes.encodeUTF8(dos, "MQTT");
// Encode the MQTT Version
dos.write(mqttVersion);
byte connectFlags = 0;
if (cleanStart) {
connectFlags |= 0x02;
}
if (willMessage != null) {
connectFlags |= 0x04;
connectFlags |= (willMessage.getQos() << 3);
if (willMessage.isRetained()) {
connectFlags |= 0x20;
}
}
if (userName != null) {
connectFlags |= 0x80;
}
if (password != null) {
connectFlags |= 0x40;
}
dos.write(connectFlags);
dos.writeShort(keepAliveInterval);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
dos.write(identifierValueFieldsByteArray);
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public byte[] getPayload() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
MqttDataTypes.encodeUTF8(dos, clientId);
if (willMessage != null) {
// Encode Will properties here
byte[] willIdentifierValueFieldsByteArray = willProperties.encodeProperties();
dos.write(willIdentifierValueFieldsByteArray);
MqttDataTypes.encodeUTF8(dos, willDestination);
dos.writeShort(willMessage.getPayload().length);
dos.write(willMessage.getPayload());
}
if (userName != null) {
MqttDataTypes.encodeUTF8(dos, userName);
}
if (password != null) {
dos.writeShort(password.length);
dos.write(password);
}
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
/**
* Returns whether or not this message needs to include a message ID.
*/
@Override
public boolean isMessageIdRequired() {
return false;
}
@Override
public String getKey() {
return KEY;
}
public void setWillMessage(MqttMessage willMessage) {
this.willMessage = willMessage;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setPassword(byte[] password) {
this.password = password;
}
public void setWillDestination(String willDestination) {
this.willDestination = willDestination;
}
public byte getInfo() {
return info;
}
public String getClientId() {
return clientId;
}
public MqttMessage getWillMessage() {
return willMessage;
}
public String getUserName() {
return userName;
}
public byte[] getPassword() {
return password;
}
public int getKeepAliveInterval() {
return keepAliveInterval;
}
public String getWillDestination() {
return willDestination;
}
public int getMqttVersion() {
return mqttVersion;
}
@Override
public MqttProperties getProperties() {
return properties;
}
public MqttProperties getWillProperties() {
return willProperties;
}
@Override
public String toString() {
return "MqttConnect [properties=" + properties + ", willProperties=" + willProperties + ", info=" + info
+ ", clientId=" + clientId + ", reservedByte=" + reservedByte + ", cleanStart=" + cleanStart
+ ", willMessage=" + willMessage + ", userName=" + userName + ", password=" + Arrays.toString(password)
+ ", keepAliveInterval=" + keepAliveInterval + ", willDestination=" + willDestination + ", mqttVersion="
+ mqttVersion + "]";
}
}

View File

@@ -0,0 +1,253 @@
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.VariableByteInteger;
public class MqttDataTypes {
private static final int TWO_BYTE_INT_MAX = 65535;
private static final long FOUR_BYTE_INT_MAX = 4294967295L;
public static final int VARIABLE_BYTE_INT_MAX = 268435455;
public MqttDataTypes() throws IllegalAccessException {
throw new IllegalAccessException("Utility Class");
}
public static void validateTwoByteInt(Integer value) throws IllegalArgumentException {
if (value == null) {
return;
}
if (value >= 0 && value <= TWO_BYTE_INT_MAX) {
return;
} else {
throw new IllegalArgumentException("This property must be a number between 0 and " + TWO_BYTE_INT_MAX);
}
}
public static void validateFourByteInt(Long value) throws IllegalArgumentException {
if (value == null) {
return;
}
if (value >= 0 && value <= FOUR_BYTE_INT_MAX) {
return;
} else {
throw new IllegalArgumentException("This property must be a number between 0 and " + FOUR_BYTE_INT_MAX);
}
}
public static void validateVariableByteInt(int value) throws IllegalArgumentException {
if (value >= 0 && value <= VARIABLE_BYTE_INT_MAX) {
return;
} else {
throw new IllegalArgumentException("This property must be a number between 0 and " + VARIABLE_BYTE_INT_MAX);
}
}
public static void writeUnsignedFourByteInt(long value, DataOutputStream stream) throws IOException {
stream.writeByte((byte) (value >>> 24));
stream.writeByte((byte) (value >>> 16));
stream.writeByte((byte) (value >>> 8));
stream.writeByte((byte) (value >>> 0));
}
/**
* Reads a Four Byte Integer, then converts it to a float. This is because Java
* doesn't have Unsigned Integers.
*
* @param inputStream
* The input stream to read from.
* @return a {@link Long} containing the value of the Four Byte int (Between 0
* and 4294967295)
* @throws IOException
* if an exception occurs whilst reading from the Input Stream
*/
public static Long readUnsignedFourByteInt(DataInputStream inputStream) throws IOException {
byte[] readBuffer = {0, 0, 0, 0, 0, 0, 0, 0};
inputStream.readFully(readBuffer, 4, 4);
return (((long) readBuffer[0] << 56) + ((long) (readBuffer[1] & 255) << 48)
+ ((long) (readBuffer[2] & 255) << 40) + ((long) (readBuffer[3] & 255) << 32)
+ ((long) (readBuffer[4] & 255) << 24) + ((readBuffer[5] & 255) << 16) + ((readBuffer[6] & 255) << 8)
+ ((readBuffer[7] & 255) << 0));
}
/**
* Reads a Two Byte Integer, this is because Java does not have unsigned
* integers.
*
* @param inputStream
* The input stream to read from.
* @return a {@link int} containing the value of the Two Byte int (Between 0 and
* 65535)
* @throws IOException
* if an exception occurs whilst reading from the Input Stream
*
*/
public static int readUnsignedTwoByteInt(DataInputStream inputStream) throws IOException {
// byte readBuffer[] = {0,0}
int ch1 = inputStream.read();
int ch2 = inputStream.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (int) ((ch1 << 8) + (ch2 << 0));
}
/**
* Encodes a String given into UTF-8, before writing this to the
* {@link DataOutputStream} the length of the encoded string is encoded into two
* bytes and then written to the {@link DataOutputStream}.
* {@link DataOutputStream#writeUTF(String)} should be no longer used.
* {@link DataOutputStream#writeUTF(String)} does not correctly encode UTF-16
* surrogate characters.
*
* @param dos
* The stream to write the encoded UTF-8 string to.
* @param stringToEncode
* The string to be encoded
* @throws MqttException
* Thrown when an error occurs with either the encoding or writing
* the data to the stream.
*/
public static void encodeUTF8(DataOutputStream dos, String stringToEncode) throws MqttException {
validateUTF8String(stringToEncode);
try {
byte[] encodedString = stringToEncode.getBytes(STRING_ENCODING);
byte byte1 = (byte) ((encodedString.length >>> 8) & 0xFF);
byte byte2 = (byte) ((encodedString.length >>> 0) & 0xFF);
dos.write(byte1);
dos.write(byte2);
dos.write(encodedString);
} catch (IOException ex) {
throw new MqttException(ex);
}
}
protected static final Charset STRING_ENCODING = StandardCharsets.UTF_8;
/**
* Decodes a UTF-8 string from the {@link DataInputStream} provided.
* {@link DataInputStream#readUTF()} should be no longer used, because
* {@link DataInputStream#readUTF()} does not decode UTF-16 surrogate characters
* correctly.
*
* @param input
* The input stream from which to read the encoded string.
* @return a decoded String from the {@link DataInputStream}.
* @throws MqttException
* thrown when an error occurs with either reading from the stream
* or decoding the encoding string.
*/
public static String decodeUTF8(DataInputStream input) throws MqttException {
int encodedLength;
try {
encodedLength = input.readUnsignedShort();
byte[] encodedString = new byte[encodedLength];
input.readFully(encodedString);
String output = new String(encodedString, STRING_ENCODING);
validateUTF8String(output);
return output;
} catch (IOException ioe) {
throw new MqttException(MqttException.REASON_CODE_MALFORMED_PACKET, ioe);
}
}
/**
* Validate a UTF-8 String for suitability for MQTT.
*
* @param input
* - The Input String
* @throws IllegalArgumentException
*/
private static void validateUTF8String(String input) throws IllegalArgumentException {
for (int i = 0; i < input.length(); i++) {
boolean isBad = false;
char c = input.charAt(i);
/* Check for mismatched surrogates */
if (Character.isHighSurrogate(c)) {
if (++i == input.length()) {
isBad = true; /* Trailing high surrogate */
} else {
char c2 = input.charAt(i);
if (!Character.isLowSurrogate(c2)) {
isBad = true; /* No low surrogate */
} else {
int ch = ((((int) c) & 0x3ff) << 10) | (c2 & 0x3ff);
if ((ch & 0xffff) == 0xffff || (ch & 0xffff) == 0xfffe) {
isBad = true; /* Noncharacter in base plane */
}
}
}
} else {
if (Character.isISOControl(c) || Character.isLowSurrogate(c)) {
isBad = true; /* Control character or no high surrogate */
} else if (c >= 0xfdd0 && (c <= 0xfddf || c >= 0xfffe)) {
isBad = true; /* Noncharacter in other nonbase plane */
}
}
if (isBad) {
throw new IllegalArgumentException(String.format("Invalid UTF-8 char: [%04x]", (int) c));
}
}
}
/**
* Decodes an MQTT Multi-Byte Integer from the given stream
*
* @param in
* the DataInputStream to decode a Variable Byte Integer From
* @return a new VariableByteInteger
* @throws IOException
* if an error occurred whilst decoding the VBI
*/
public static VariableByteInteger readVariableByteInteger(DataInputStream in) throws IOException {
byte digit;
int value = 0;
int multiplier = 1;
int count = 0;
do {
digit = in.readByte();
count++;
value += ((digit & 0x7F) * multiplier);
multiplier *= 128;
} while ((digit & 0x80) != 0);
if (value < 0 || value > VARIABLE_BYTE_INT_MAX) {
throw new IOException("This property must be a number between 0 and " + VARIABLE_BYTE_INT_MAX
+ ". Read value was: " + value);
}
return new VariableByteInteger(value, count);
}
public static byte[] encodeVariableByteInteger(int number) throws IllegalArgumentException {
validateVariableByteInt(number);
int numBytes = 0;
long no = number;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Encode the remaining length fields in the four bytes
do {
byte digit = (byte) (no % 128);
no = no / 128;
if (no > 0) {
digit |= 0x80;
}
baos.write(digit);
numBytes++;
} while ((no > 0) && (numBytes < 4));
return baos.toByteArray();
}
}

View File

@@ -0,0 +1,129 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttDisconnect extends MqttWireMessage {
public static final String KEY = "Disc";
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_DISCONNECT_WITH_WILL_MESSAGE, MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR,
MqttReturnCode.RETURN_CODE_MALFORMED_CONTROL_PACKET, MqttReturnCode.RETURN_CODE_PROTOCOL_ERROR,
MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR, MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED,
MqttReturnCode.RETURN_CODE_SERVER_BUSY, MqttReturnCode.RETURN_CODE_SERVER_SHUTTING_DOWN,
MqttReturnCode.RETURN_CODE_KEEP_ALIVE_TIMEOUT, MqttReturnCode.RETURN_CODE_SESSION_TAKEN_OVER,
MqttReturnCode.RETURN_CODE_TOPIC_FILTER_NOT_VALID, MqttReturnCode.RETURN_CODE_TOPIC_NAME_INVALID,
MqttReturnCode.RETURN_CODE_RECEIVE_MAXIMUM_EXCEEDED, MqttReturnCode.RETURN_CODE_TOPIC_ALIAS_NOT_ACCEPTED,
MqttReturnCode.RETURN_CODE_PACKET_TOO_LARGE, MqttReturnCode.RETURN_CODE_MESSAGE_RATE_TOO_HIGH,
MqttReturnCode.RETURN_CODE_QUOTA_EXCEEDED, MqttReturnCode.RETURN_CODE_ADMINISTRITIVE_ACTION,
MqttReturnCode.RETURN_CODE_PAYLOAD_FORMAT_INVALID, MqttReturnCode.RETURN_CODE_RETAIN_NOT_SUPPORTED,
MqttReturnCode.RETURN_CODE_QOS_NOT_SUPPORTED, MqttReturnCode.RETURN_CODE_USE_ANOTHER_SERVER,
MqttReturnCode.RETURN_CODE_SERVER_MOVED, MqttReturnCode.RETURN_CODE_SHARED_SUB_NOT_SUPPORTED,
MqttReturnCode.RETURN_CODE_CONNECTION_RATE_EXCEEDED, MqttReturnCode.RETURN_CODE_MAXIMUM_CONNECT_TIME,
MqttReturnCode.RETURN_CODE_SUB_IDENTIFIERS_NOT_SUPPORTED,
MqttReturnCode.RETURN_CODE_WILDCARD_SUB_NOT_SUPPORTED };
// Fields
private int returnCode = MqttReturnCode.RETURN_CODE_SUCCESS;
private static final Byte[] validProperties = { MqttProperties.SESSION_EXPIRY_INTERVAL_IDENTIFIER,
MqttProperties.SERVER_REFERENCE_IDENTIFIER, MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
private MqttProperties properties;
public MqttDisconnect(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_DISCONNECT);
this.properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream inputStream = new DataInputStream(counter);
if(data.length - counter.getCounter() >= 1) {
returnCode = inputStream.readUnsignedByte();
validateReturnCode(returnCode, validReturnCodes);
}
long remainder = (long) data.length - counter.getCounter();
if (remainder >= 2) {
this.properties.decodeProperties(inputStream);
}
inputStream.close();
}
public MqttDisconnect(int returnCode, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_DISCONNECT);
validateReturnCode(returnCode, validReturnCodes);
this.returnCode = returnCode;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Return Code
outputStream.writeByte(returnCode);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
if (identifierValueFieldsByteArray.length != 0) {
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
}
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int getReturnCode() {
return returnCode;
}
@Override
protected byte getMessageInfo() {
return (byte) 0;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttDisconnect [returnCode=" + returnCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import org.eclipse.paho.mqttv5.common.MqttException;
/**
* This is a Class containing all of the low level packet exceptions that may be useful in identifying the cause of protocol errors.
* @author jamessutton
*/
public class MqttPacketException extends MqttException {
private static final long serialVersionUID = 1L;
/**
* Protocol Exceptions for the CONNECT Packet
*/
public static final int PACKET_CONNECT_ERROR_UNSUPPORTED_PROTOCOL_NAME = 51000; // 3.1.2.1 - If Protocol name is not 'MQTT', return Unsupported Protocol Version Error.
public static final int PACKET_CONNECT_ERROR_UNSUPPORTED_PROTOCOL_VERSION = 51001; // 3.1.2.2 - If Protocol version is not 5, return Unsupported Protocol Version Error.
public static final int PACKET_CONNECT_ERROR_INVALID_RESERVE_FLAG = 51002; // 3.1.2.3 - If Reserved flag is not 0, return Malformed Packet Error.
public static final int PACKET_CONNECT_ERROR_INVALID_WILL_QOS = 51003; // 3.1.2.6 - If Will QoS is 0x03, return Malformed Packet Error.
public MqttPacketException(int reasonCode) {
super(reasonCode);
}
}

View File

@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.mqttv5.common.packet;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttPersistable;
import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
public abstract class MqttPersistableWireMessage extends MqttWireMessage
implements MqttPersistable {
public MqttPersistableWireMessage(byte type) {
super(type);
}
public byte[] getHeaderBytes() throws MqttPersistenceException {
byte[] headerBytes = null;
try {
if(this.getClass() == MqttPublish.class && this.getProperties().getTopicAlias() != null) {
// Remove the Topic Alias temporarily.
MqttProperties props = this.getProperties();
Integer topicAlias = props.getTopicAlias();
props.setTopicAlias(null);
headerBytes = getHeader();
// Re Set Topic Alias
props.setTopicAlias(topicAlias);
this.properties = props;
} else {
headerBytes = getHeader();
}
return headerBytes;
}
catch (MqttException ex) {
throw new MqttPersistenceException(ex.getCause());
}
}
public int getHeaderLength() throws MqttPersistenceException {
return getHeaderBytes().length;
}
public int getHeaderOffset() throws MqttPersistenceException{
return 0;
}
public byte[] getPayloadBytes() throws MqttPersistenceException {
try {
return getPayload();
}
catch (MqttException ex) {
throw new MqttPersistenceException(ex.getCause());
}
}
public int getPayloadLength() throws MqttPersistenceException {
return 0;
}
public int getPayloadOffset() throws MqttPersistenceException {
return 0;
}
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import org.eclipse.paho.mqttv5.common.MqttException;
public class MqttPingReq extends MqttWireMessage{
public static final String KEY = "Ping";
public MqttPingReq(){
super(MqttWireMessage.MESSAGE_TYPE_PINGREQ);
}
/**
* Returns <code>false</code> as message IDs are not required for MQTT
* PINGREQ messages.
*/
@Override
public boolean isMessageIdRequired() {
return false;
}
@Override
protected byte[] getVariableHeader() throws MqttException {
return new byte[0];
}
@Override
protected byte getMessageInfo() {
return 0;
}
@Override
public String getKey() {
return KEY;
}
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import org.eclipse.paho.mqttv5.common.MqttException;
public class MqttPingResp extends MqttAck {
public static final String KEY = "Ping";
public MqttPingResp() {
super(MqttWireMessage.MESSAGE_TYPE_PINGRESP);
}
/**
* Returns <code>false</code> as message IDs are not required for MQTT PINGREQ
* messages.
*/
@Override
public boolean isMessageIdRequired() {
return false;
}
@Override
protected byte[] getVariableHeader() throws MqttException {
return new byte[0];
}
@Override
protected byte getMessageInfo() {
return 0;
}
@Override
public String getKey() {
return KEY;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttPubAck extends MqttAck {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_NO_MATCHING_SUBSCRIBERS, MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR,
MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR, MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED,
MqttReturnCode.RETURN_CODE_TOPIC_NAME_INVALID, MqttReturnCode.RETURN_CODE_QUOTA_EXCEEDED,
MqttReturnCode.RETURN_CODE_PAYLOAD_FORMAT_INVALID };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
private MqttProperties properties;
public MqttPubAck(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream dis = new DataInputStream(counter);
msgId = dis.readUnsignedShort();
long remainder = (long) data.length - counter.getCounter();
if (remainder >= 1) {
reasonCode = dis.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
} else {
reasonCode = 0;
}
if (remainder >= 4) {
this.properties.decodeProperties(dis);
}
dis.close();
}
public MqttPubAck(int returnCode, int msgId, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
this.reasonCode = returnCode;
this.msgId = msgId;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
validateReturnCode(returnCode, validReturnCodes);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS && identifierValueFieldsByteArray.length == 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
} else if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS || identifierValueFieldsByteArray.length > 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int getReturnCode() {
return reasonCode;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttPubAck [returnCode=" + reasonCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttPubComp extends MqttAck {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_PACKET_ID_NOT_FOUND };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
public MqttPubComp(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream dis = new DataInputStream(counter);
msgId = dis.readUnsignedShort();
long remainder = (long) data.length - counter.getCounter();
if (remainder >= 1) {
reasonCode = dis.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
} else {
reasonCode = 0;
}
if (remainder >= 4) {
this.properties.decodeProperties(dis);
}
dis.close();
}
public MqttPubComp(int returnCode, int msgId, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
validateReturnCode(returnCode, validReturnCodes);
this.reasonCode = returnCode;
this.msgId = msgId;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS && identifierValueFieldsByteArray.length == 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
} else if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS || identifierValueFieldsByteArray.length > 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int getReturnCode() {
return reasonCode;
}
public void setReturnCode(int returnCode) {
this.reasonCode = returnCode;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttPubComp [returnCode=" + reasonCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttPubRec extends MqttAck {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_NO_MATCHING_SUBSCRIBERS, MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR,
MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR, MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED,
MqttReturnCode.RETURN_CODE_TOPIC_NAME_INVALID, MqttReturnCode.RETURN_CODE_PACKET_ID_IN_USE,
MqttReturnCode.RETURN_CODE_QUOTA_EXCEEDED, MqttReturnCode.RETURN_CODE_PAYLOAD_FORMAT_INVALID };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
public MqttPubRec(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream dis = new DataInputStream(counter);
msgId = dis.readUnsignedShort();
long remainder = (long) data.length - counter.getCounter();
if (remainder >= 1) {
reasonCode = dis.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
} else {
reasonCode = 0;
}
if (remainder >= 4) {
this.properties.decodeProperties(dis);
}
dis.close();
}
public MqttPubRec(int returnCode, int msgId, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
validateReturnCode(returnCode, validReturnCodes);
this.reasonCode = returnCode;
this.msgId = msgId;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS && identifierValueFieldsByteArray.length == 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
} else if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS || identifierValueFieldsByteArray.length > 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int getReturnCode() {
return reasonCode;
}
public void setReturnCode(int returnCode) {
this.reasonCode = returnCode;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttPubRec [returnCode=" + reasonCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttPubRel extends MqttPersistableWireMessage {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_PACKET_ID_NOT_FOUND };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
public MqttPubRel(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream dis = new DataInputStream(counter);
msgId = dis.readUnsignedShort();
long remainder = (long) data.length - counter.getCounter();
if (remainder >= 1) {
reasonCode = dis.readUnsignedByte();
validateReturnCode(reasonCode, validReturnCodes);
} else {
reasonCode = 0;
}
if (remainder >= 4) {
this.properties.decodeProperties(dis);
}
dis.close();
}
public MqttPubRel(int returnCode, int msgId, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
validateReturnCode(returnCode, validReturnCodes);
this.reasonCode = returnCode;
this.msgId = msgId;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS && identifierValueFieldsByteArray.length == 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
} else if (reasonCode != MqttReturnCode.RETURN_CODE_SUCCESS || identifierValueFieldsByteArray.length > 1) {
// Encode the Return Code
outputStream.write((byte) reasonCode);
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
protected byte getMessageInfo() {
return (byte) (2 | (this.duplicate ? 8 : 0));
}
public int getReturnCode() {
return reasonCode;
}
public void setReturnCode(int returnCode) {
this.reasonCode = returnCode;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttPubRel [returnCode=" + reasonCode + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,242 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
/**
* An on-the-wire representation of an MQTT Publish message.
*/
public class MqttPublish extends MqttPersistableWireMessage {
private static final Byte[] validProperties = { MqttProperties.PAYLOAD_FORMAT_INDICATOR_IDENTIFIER,
MqttProperties.MESSAGE_EXPIRY_INTERVAL_IDENTIFIER, MqttProperties.TOPIC_ALIAS_IDENTIFIER,
MqttProperties.RESPONSE_TOPIC_IDENTIFIER, MqttProperties.CORRELATION_DATA_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER, MqttProperties.CONTENT_TYPE_IDENTIFIER,
MqttProperties.SUBSCRIPTION_IDENTIFIER_MULTI, MqttProperties.SUBSCRIPTION_IDENTIFIER };
private MqttProperties properties;
// Fields
private byte[] payload;
private int qos = 1;
private boolean retained = false;
private boolean dup = false;
private String topicName;
/**
* Constructs a new MqttPublish message
*
* @param topic
* - The Destination Topic.
* @param message
* - The Message being sent.
* @param properties
* - The {@link MqttProperties} for the packet.
*/
public MqttPublish(String topic, MqttMessage message, MqttProperties properties) {
super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
this.topicName = topic;
this.payload = message.getPayload();
this.qos = message.getQos();
this.dup = message.isDuplicate();
this.retained = message.isRetained();
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
/**
* Constructs a new MqttPublish message from a byte array
*
* @param info
* - Info Byte
* @param data
* - The variable header and payload bytes.
* @throws IOException
* - if an exception occurs when decoding an input stream
* @throws MqttException
* - If an exception occurs decoding this packet
*/
public MqttPublish(byte info, byte[] data) throws MqttException, IOException {
super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
this.properties = new MqttProperties(validProperties);
this.qos = (info >> 1) & 0x03;
if ((info & 0x01) == 0x01) {
this.retained = true;
}
if ((info & 0x08) == 0x08) {
this.dup = true;
}
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream dis = new DataInputStream(counter);
topicName = MqttDataTypes.decodeUTF8(dis);
if (this.qos > 0) {
msgId = dis.readUnsignedShort();
}
this.properties.decodeProperties(dis);
this.payload = new byte[data.length - counter.getCounter()];
dis.readFully(this.payload);
dis.close();
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// If we are using a Topic Alias, then the topic should be empty
if (topicName != null) {
MqttDataTypes.encodeUTF8(dos, topicName);
} else {
MqttDataTypes.encodeUTF8(dos, "");
}
if (this.qos > 0) {
dos.writeShort(msgId);
}
// Write Identifier / Value Fields
byte[] identifierValueFieldsArray = this.properties.encodeProperties();
dos.write(identifierValueFieldsArray);
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
protected byte getMessageInfo() {
byte info = (byte) (this.qos << 1);
if (this.retained) {
info |= 0x01;
}
if (this.dup || duplicate) {
info |= 0x08;
}
return info;
}
@Override
public byte[] getPayload() {
return this.payload;
}
@Override
public int getPayloadLength() {
if (this.payload != null) {
return this.payload.length;
} else {
return 0;
}
}
@Override
public boolean isMessageIdRequired() {
// all publishes require a message ID as it's used as the key to the
// token store
return true;
}
public MqttMessage getMessage() {
MqttMessage message = new MqttMessage(payload, qos, retained, properties);
return message;
}
public void setMessage(MqttMessage message) {
this.payload = message.getPayload();
this.qos = message.getQos();
this.dup = message.isDuplicate();
this.retained = message.isRetained();
}
public String getTopicName() {
return topicName;
}
public int getQoS() {
return qos;
}
public void setTopicName(String topicName) {
this.topicName = topicName;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
// Convert the first few bytes of the payload into a hex string
StringBuilder hex = new StringBuilder();
int limit = Math.min(payload.length, 20);
for (int i = 0; i < limit; i++) {
byte b = payload[i];
String ch = Integer.toHexString(b);
if (ch.length() == 1) {
ch = "0" + ch;
}
hex.append(ch);
}
// It will not always be possible to convert the binary payload into
// characters, but never-the-less we attempt to do this as it is often
// useful.
String string = null;
try {
string = new String(payload, 0, limit, "UTF-8");
} catch (UnsupportedEncodingException uee) {
string = "?";
}
StringBuilder sb = new StringBuilder();
sb.append("MqttPublish [");
sb.append(", qos=").append(this.qos);
if (this.qos > 0) {
sb.append(", messageId=").append(msgId);
}
sb.append(", retained=").append(this.retained);
sb.append(", duplicate=").append(duplicate);
sb.append(", topic=").append(topicName);
sb.append(", payload=[hex=").append(hex);
sb.append(", utf8=").append(string);
sb.append(", length=").append(payload.length).append("]");
sb.append(", properties=").append(this.properties.toString());
return sb.toString();
}
}

View File

@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import org.eclipse.paho.mqttv5.common.MqttMessage;
public class MqttReceivedMessage extends MqttMessage {
public void setMessageId(int messageId){
super.setId(messageId);
}
public int getMessageId(){
return super.getId();
}
// This method exists here to get around the protected visibility of the
// super class method.
@Override
public void setDuplicate(boolean value) {
super.setDuplicate(value);
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
/**
* Reference of MQTT v5 Return Codes - 2.4
*/
public class MqttReturnCode {
/** Success and general Return Codes **/
public static final int RETURN_CODE_SUCCESS = 0x00; // 0
public static final int RETURN_CODE_MAX_QOS_0 = 0x00; // 0
public static final int RETURN_CODE_MAX_QOS_1 = 0x01; // 1
public static final int RETURN_CODE_MAX_QOS_2 = 0x02; // 2
public static final int RETURN_CODE_DISCONNECT_WITH_WILL_MESSAGE = 0x04; // 4
public static final int RETURN_CODE_NO_MATCHING_SUBSCRIBERS = 0x10; // 16
public static final int RETURN_CODE_NO_SUBSCRIPTION_EXISTED = 0x11; // 17
public static final int RETURN_CODE_CONTINUE_AUTHENTICATION = 0x18; // 24
public static final int RETURN_CODE_RE_AUTHENTICATE = 0x19; // 25
/** Error Return Codes **/
public static final int RETURN_CODE_UNSPECIFIED_ERROR = 0x80; // 128
public static final int RETURN_CODE_MALFORMED_CONTROL_PACKET = 0x81; // 129
public static final int RETURN_CODE_PROTOCOL_ERROR = 0x82; // 130
public static final int RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR = 0x83; // 131
public static final int RETURN_CODE_UNSUPPORTED_PROTOCOL_VERSION = 0x84; // 132
public static final int RETURN_CODE_IDENTIFIER_NOT_VALID = 0x85; // 133
public static final int RETURN_CODE_BAD_USERNAME_OR_PASSWORD = 0x86; // 134
public static final int RETURN_CODE_NOT_AUTHORIZED = 0x87; // 135
public static final int RETURN_CODE_SERVER_UNAVAILABLE = 0x88; // 136
public static final int RETURN_CODE_SERVER_BUSY = 0x89; // 137
public static final int RETURN_CODE_BANNED = 0x8A; // 138
public static final int RETURN_CODE_SERVER_SHUTTING_DOWN = 0x8B; // 139
public static final int RETURN_CODE_BAD_AUTHENTICATION = 0x8C; // 140
public static final int RETURN_CODE_KEEP_ALIVE_TIMEOUT = 0x8D; // 141
public static final int RETURN_CODE_SESSION_TAKEN_OVER = 0x8E; // 142
public static final int RETURN_CODE_TOPIC_FILTER_NOT_VALID = 0x8F; // 143
public static final int RETURN_CODE_TOPIC_NAME_INVALID = 0x90; // 144
public static final int RETURN_CODE_PACKET_ID_IN_USE = 0x91; // 145
public static final int RETURN_CODE_PACKET_ID_NOT_FOUND = 0x92; // 146
public static final int RETURN_CODE_RECEIVE_MAXIMUM_EXCEEDED = 0x93; // 147
public static final int RETURN_CODE_TOPIC_ALIAS_NOT_ACCEPTED = 0x94; // 148
public static final int RETURN_CODE_PACKET_TOO_LARGE = 0x95; // 149
public static final int RETURN_CODE_MESSAGE_RATE_TOO_HIGH = 0x96; // 150
public static final int RETURN_CODE_QUOTA_EXCEEDED = 0x97; // 151
public static final int RETURN_CODE_ADMINISTRITIVE_ACTION = 0x98; // 152
public static final int RETURN_CODE_PAYLOAD_FORMAT_INVALID = 0x99; // 153
public static final int RETURN_CODE_RETAIN_NOT_SUPPORTED = 0x9A; // 154
public static final int RETURN_CODE_QOS_NOT_SUPPORTED = 0x9B; // 155
public static final int RETURN_CODE_USE_ANOTHER_SERVER = 0x9C; // 156
public static final int RETURN_CODE_SERVER_MOVED = 0x9D; // 157
public static final int RETURN_CODE_SHARED_SUB_NOT_SUPPORTED = 0x9E; // 158
public static final int RETURN_CODE_CONNECTION_RATE_EXCEEDED = 0x9F; // 159
public static final int RETURN_CODE_MAXIMUM_CONNECT_TIME = 0xA0; // 160
public static final int RETURN_CODE_SUB_IDENTIFIERS_NOT_SUPPORTED = 0xA1; // 161
public static final int RETURN_CODE_WILDCARD_SUB_NOT_SUPPORTED = 0xA2; // 162
private MqttReturnCode() {
throw new IllegalAccessError("Utility class");
}
}

View File

@@ -0,0 +1,134 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttSubAck extends MqttAck {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_MAX_QOS_0,
MqttReturnCode.RETURN_CODE_MAX_QOS_1, MqttReturnCode.RETURN_CODE_MAX_QOS_2,
MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR, MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR,
MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED, MqttReturnCode.RETURN_CODE_TOPIC_FILTER_NOT_VALID,
MqttReturnCode.RETURN_CODE_PACKET_ID_IN_USE, MqttReturnCode.RETURN_CODE_SHARED_SUB_NOT_SUPPORTED };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
public MqttSubAck(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_SUBACK);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream inputStream = new DataInputStream(counter);
msgId = inputStream.readUnsignedShort();
this.properties.decodeProperties(inputStream);
int remainingLength = data.length - counter.getCounter();
reasonCodes = new int[remainingLength];
for (int i = 0; i < remainingLength; i++) {
int returnCode = inputStream.readUnsignedByte();
validateReturnCode(returnCode, validReturnCodes);
reasonCodes[i] = returnCode;
}
inputStream.close();
}
public MqttSubAck(int[] returnCodes, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_SUBACK);
for (int returnCode : returnCodes) {
validateReturnCode(returnCode, validReturnCodes);
}
this.reasonCodes = returnCodes;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public byte[] getPayload() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
for (int returnCode : reasonCodes) {
outputStream.writeByte(returnCode);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int[] getReturnCodes() {
return reasonCodes;
}
public void setReturnCodes(int[] returnCodes) {
this.reasonCodes = returnCodes;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttSubAck [returnCodes=" + Arrays.toString(reasonCodes) + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,217 @@
/*******************************************************************************
* Copyright (c) 2016, 2019 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttSubscription;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttSubscribe extends MqttPersistableWireMessage {
private static final Byte[] validProperties = { MqttProperties.SUBSCRIPTION_IDENTIFIER,
MqttProperties.SUBSCRIPTION_IDENTIFIER_SINGLE,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
private MqttSubscription[] subscriptions;
/**
* Constructor for an on the Wire MQTT Subscribe message
*
* @param data
* - The variable header and payload bytes.
* @throws IOException
* - if an exception occurs when decoding an input stream
* @throws MqttException
* - If an exception occurs decoding this packet
*/
public MqttSubscribe(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE);
this.properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream inputStream = new DataInputStream(counter);
msgId = inputStream.readUnsignedShort();
this.properties.decodeProperties(inputStream);
ArrayList<MqttSubscription> subscriptionList = new ArrayList<>();
// Whilst we are reading data
while (counter.getCounter() < data.length) {
String topic = MqttDataTypes.decodeUTF8(inputStream);
byte subscriptionOptions = inputStream.readByte();
subscriptionList.add(decodeSubscription(topic, subscriptionOptions));
}
subscriptions = subscriptionList.toArray(new MqttSubscription[subscriptionList.size()]);
inputStream.close();
}
/**
* Constructor for an on the Wire MQTT Subscribe message
*
* @param subscriptions
* - An Array of {@link MqttSubscription} subscriptions.
* @param properties
* - The {@link MqttProperties} for the packet.
*/
public MqttSubscribe(MqttSubscription[] subscriptions, MqttProperties properties) {
super(MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE);
this.subscriptions = subscriptions;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
/**
* Constructor for an on the Wire MQTT Subscribe message
*
* @param subscription
* - An {@link MqttSubscription}
* @param properties
* - The {@link MqttProperties} for the packet.
*/
public MqttSubscribe(MqttSubscription subscription, MqttProperties properties) {
super(MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE);
this.subscriptions = new MqttSubscription[] { subscription };
this.properties = properties;
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public byte[] getPayload() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
for (MqttSubscription subscription : subscriptions) {
outputStream.write(encodeSubscription(subscription));
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public boolean isRetryable() {
return true;
}
/**
* Encodes an {@link MqttSubscription} into it's on-the-wire representation.
* Assumes that the Subscription topic is valid.
*
* @param subscription
* - The {@link MqttSubscription} to encode.
* @return A byte array containing the encoded subscription.
* @throws MqttException
*/
private byte[] encodeSubscription(MqttSubscription subscription) throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
MqttDataTypes.encodeUTF8(outputStream, subscription.getTopic());
// Encode Subscription QoS
byte subscriptionOptions = (byte) subscription.getQos();
// Encode NoLocal Option
if (subscription.isNoLocal()) {
subscriptionOptions |= 0x04;
}
// Encode Retain As Published Option
if (subscription.isRetainAsPublished()) {
subscriptionOptions |= 0x08;
}
// Encode Retain Handling Level
subscriptionOptions |= (subscription.getRetainHandling() << 4);
outputStream.write(subscriptionOptions);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
private MqttSubscription decodeSubscription(String topic, byte subscriptionOptions) {
MqttSubscription subscription = new MqttSubscription(topic);
subscription.setQos(subscriptionOptions & 0x03);
subscription.setNoLocal((subscriptionOptions & 0x04) != 0);
subscription.setRetainAsPublished((subscriptionOptions & 0x08) != 0);
subscription.setRetainHandling((subscriptionOptions >> 4) & 0x03);
return subscription;
}
@Override
protected byte getMessageInfo() {
return (byte) (2 | (duplicate ? 8 : 0));
}
public MqttSubscription[] getSubscriptions() {
return subscriptions;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttSubscribe [properties=" + properties + ", subscriptions=" + Arrays.toString(subscriptions) + "]";
}
}

View File

@@ -0,0 +1,130 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttUnsubAck extends MqttAck {
private static final int[] validReturnCodes = { MqttReturnCode.RETURN_CODE_SUCCESS,
MqttReturnCode.RETURN_CODE_NO_SUBSCRIPTION_EXISTED, MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR,
MqttReturnCode.RETURN_CODE_IMPLEMENTATION_SPECIFIC_ERROR, MqttReturnCode.RETURN_CODE_NOT_AUTHORIZED,
MqttReturnCode.RETURN_CODE_TOPIC_FILTER_NOT_VALID, MqttReturnCode.RETURN_CODE_PACKET_ID_IN_USE };
private static final Byte[] validProperties = { MqttProperties.REASON_STRING_IDENTIFIER,
MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private MqttProperties properties;
public MqttUnsubAck(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_UNSUBACK);
properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream inputStream = new DataInputStream(counter);
msgId = inputStream.readUnsignedShort();
this.properties.decodeProperties(inputStream);
int remainingLengh = data.length - counter.getCounter();
reasonCodes = new int[remainingLengh];
for (int i = 0; i < remainingLengh; i++) {
reasonCodes[i] = inputStream.readUnsignedByte();
validateReturnCode(reasonCodes[i], validReturnCodes);
}
inputStream.close();
}
public MqttUnsubAck(int[] returnCodes, MqttProperties properties) throws MqttException {
super(MqttWireMessage.MESSAGE_TYPE_UNSUBACK);
for (int returnCode : returnCodes) {
validateReturnCode(returnCode, validReturnCodes);
}
this.reasonCodes = returnCodes;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the msgId
outputStream.writeShort(msgId);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
// Write Identifier / Value Fields
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public byte[] getPayload() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
for (int returnCode : reasonCodes) {
outputStream.writeByte(returnCode);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public int[] getReturnCodes() {
return reasonCodes;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttUnsubAck [returnCodes=" + Arrays.toString(reasonCodes) + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,138 @@
/*******************************************************************************
* Copyright (c) 2016, 2019 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
public class MqttUnsubscribe extends MqttPersistableWireMessage{
private static final Byte[] validProperties = { MqttProperties.USER_DEFINED_PAIR_IDENTIFIER };
// Fields
private String[] topics;
private MqttProperties properties;
public MqttUnsubscribe(byte[] data) throws IOException, MqttException {
super(MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE);
this.properties = new MqttProperties(validProperties);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
CountingInputStream counter = new CountingInputStream(bais);
DataInputStream inputStream = new DataInputStream(counter);
msgId = inputStream.readUnsignedShort();
this.properties.decodeProperties(inputStream);
ArrayList<String> topicList = new ArrayList<>();
// Whilst we are reading data
while(counter.getCounter() < data.length){
topicList.add( MqttDataTypes.decodeUTF8(inputStream));
}
topics = topicList.toArray(new String[topicList.size()]);
inputStream.close();
}
public MqttUnsubscribe(String[] topics, MqttProperties properties){
super(MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE);
this.topics = topics;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new MqttProperties();
}
this.properties.setValidProperties(validProperties);
}
@Override
protected byte[] getVariableHeader() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
// Encode the Message ID
outputStream.writeShort(msgId);
// Write Identifier / Value Fields
byte[] identifierValueFieldsByteArray = this.properties.encodeProperties();
outputStream.write(identifierValueFieldsByteArray);
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
@Override
public byte[] getPayload() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(baos);
for(String topic : topics){
MqttDataTypes.encodeUTF8(outputStream, topic);
}
outputStream.flush();
return baos.toByteArray();
} catch (IOException ioe){
throw new MqttException(ioe);
}
}
@Override
protected byte getMessageInfo() {
return (byte)( 2 | (this.duplicate ? 8 : 0));
}
public String[] getTopics() {
return topics;
}
public void setTopics(String[] topics) {
this.topics = topics;
}
@Override
public MqttProperties getProperties() {
return this.properties;
}
@Override
public String toString() {
return "MqttUnsubscribe [topics=" + Arrays.toString(topics) + ", properties=" + properties + "]";
}
}

View File

@@ -0,0 +1,417 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.eclipse.paho.mqttv5.common.ExceptionHelper;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttPersistable;
import org.eclipse.paho.mqttv5.common.packet.util.CountingInputStream;
import org.eclipse.paho.mqttv5.common.packet.util.MultiByteArrayInputStream;
/**
* An on-the wire representation of an MQTTv5 Message
*/
public abstract class MqttWireMessage {
public static final byte MESSAGE_TYPE_RESERVED = 0;
public static final byte MESSAGE_TYPE_CONNECT = 1;
public static final byte MESSAGE_TYPE_CONNACK = 2;
public static final byte MESSAGE_TYPE_PUBLISH = 3;
public static final byte MESSAGE_TYPE_PUBACK = 4;
public static final byte MESSAGE_TYPE_PUBREC = 5;
public static final byte MESSAGE_TYPE_PUBREL = 6;
public static final byte MESSAGE_TYPE_PUBCOMP = 7;
public static final byte MESSAGE_TYPE_SUBSCRIBE = 8;
public static final byte MESSAGE_TYPE_SUBACK = 9;
public static final byte MESSAGE_TYPE_UNSUBSCRIBE = 10;
public static final byte MESSAGE_TYPE_UNSUBACK = 11;
public static final byte MESSAGE_TYPE_PINGREQ = 12;
public static final byte MESSAGE_TYPE_PINGRESP = 13;
public static final byte MESSAGE_TYPE_DISCONNECT = 14;
public static final byte MESSAGE_TYPE_AUTH = 15;
protected static final String STRING_ENCODING = "UTF-8";
protected static final String DEFAULT_PROTOCOL_NAME = "MQTT";
protected static final int DEFAULT_PROTOCOL_VERSION = 5;
private static final String[] PACKET_NAMES = { "reserved", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC",
"PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT",
"AUTH" };
private static final byte[] PACKET_RESERVED_MASKS = { 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0 };
// The type of the message (e.g CONNECT, PUBLISH, SUBSCRIBE)
private byte type;
MqttProperties properties = new MqttProperties();
// The MQTT Message ID
protected int msgId;
protected int[] reasonCodes = null; // Multiple Reason Codes (SUBACK, UNSUBACK)
protected int reasonCode = -1; // Single Reason Code, init with -1 as that's an invalid RC
protected boolean duplicate = false;
public MqttWireMessage(byte type) {
this.type = type;
// Use zero as the default message ID. Can't use -1, as that is serialized
// as 65535, which would be a valid ID.
this.msgId = 0;
}
/**
* Sub-classes should override this to encode the message info. Only the
* least-significant four bits will be used.
*
* @return The Message information byte.
*/
protected abstract byte getMessageInfo();
/**
* Sub-classes should override this method to supply the payload bytes.
*
* @return The payload byte array
* @throws MqttException
* if an exception occurs whilst getting the payload.
*/
public byte[] getPayload() throws MqttException {
return new byte[0];
}
/**
* @return the type of the message
*/
public byte getType() {
return type;
}
/**
* @return the MQTT message ID
*/
public int getMessageId() {
return msgId;
}
/**
* Sets the MQTT message ID.
*
* @param msgId
* the MQTT message ID
*/
public void setMessageId(int msgId) {
this.msgId = msgId;
}
/**
* Returns a key associated with the message. For most message types this will
* be unique. For connect, disconnect and ping only one message of this type is
* allowed so a fixed key will be returned.
*
* @return The key associated with the message
*/
public String getKey() {
return Integer.toString(getMessageId());
}
/**
* Returns a byte array containing the MQTT header for the message.
*
* @return The MQTT Message Header
* @throws MqttException
* if there was an issue encoding the header
*/
public byte[] getHeader() throws MqttException {
try {
int first = ((getType() & 0x0f) << 4) ^ (getMessageInfo() & 0x0f);
byte[] varHeader = getVariableHeader();
int remLen = varHeader.length + getPayload().length;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeByte(first);
dos.write(encodeVariableByteInteger(remLen));
dos.write(varHeader);
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
protected abstract byte[] getVariableHeader() throws MqttException;
/**
* @return whether or not this message needs to include a message ID.
*/
public boolean isMessageIdRequired() {
return true;
}
/**
* Create an MQTT Wire Message
*
* @throws MqttException
* if an error occurred whilst creating the WireMessage
* @param data
* the MqttPersistable to create the message from
* @return MqttWireMessage the de-persisted message
*/
public static MqttWireMessage createWireMessage(MqttPersistable data) throws MqttException {
byte[] payload = data.getPayloadBytes();
// The persistable interface allows a message to be restored entirely in the
// header array.
// We need to treat these two arrays as a single array of bytes and use the
// decoding
// logic to identify the true header / payload split.
if (payload == null) {
payload = new byte[0];
}
MultiByteArrayInputStream mbais = new MultiByteArrayInputStream(data.getHeaderBytes(), data.getHeaderOffset(),
data.getHeaderLength(), payload, data.getPayloadOffset(), data.getPayloadLength());
return createWireMessage(mbais);
}
public static MqttWireMessage createWireMessage(byte[] bytes) throws MqttException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return createWireMessage(bais);
}
private static MqttWireMessage createWireMessage(InputStream inputStream) throws MqttException {
try {
CountingInputStream counter = new CountingInputStream(inputStream);
DataInputStream in = new DataInputStream(counter);
int first = in.readUnsignedByte();
byte type = (byte) (first >> 4);
byte info = (byte) (first &= 0x0f);
long remLen = MqttDataTypes.readVariableByteInteger(in).getValue();
long totalToRead = counter.getCounter() + remLen;
MqttWireMessage result;
long remainder = totalToRead - counter.getCounter();
byte[] data = new byte[0];
// The remaining bytes must be the payload
if (remainder > 0) {
data = new byte[(int) remainder];
in.readFully(data, 0, data.length);
}
switch (type) {
case MqttWireMessage.MESSAGE_TYPE_CONNECT:
result = new MqttConnect(info, data);
break;
case MqttWireMessage.MESSAGE_TYPE_CONNACK:
result = new MqttConnAck(data);
break;
case MqttWireMessage.MESSAGE_TYPE_PUBLISH:
result = new MqttPublish(info, data);
break;
case MqttWireMessage.MESSAGE_TYPE_PUBACK:
result = new MqttPubAck(data);
break;
case MqttWireMessage.MESSAGE_TYPE_PUBREC:
result = new MqttPubRec(data);
break;
case MqttWireMessage.MESSAGE_TYPE_PUBREL:
result = new MqttPubRel(data);
break;
case MqttWireMessage.MESSAGE_TYPE_PUBCOMP:
result = new MqttPubComp(data);
break;
case MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE:
result = new MqttSubscribe(data);
break;
case MqttWireMessage.MESSAGE_TYPE_SUBACK:
result = new MqttSubAck(data);
break;
case MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE:
result = new MqttUnsubscribe(data);
break;
case MqttWireMessage.MESSAGE_TYPE_UNSUBACK:
result = new MqttUnsubAck(data);
break;
case MqttWireMessage.MESSAGE_TYPE_PINGREQ:
result = new MqttPingReq();
break;
case MqttWireMessage.MESSAGE_TYPE_PINGRESP:
result = new MqttPingResp();
break;
case MqttWireMessage.MESSAGE_TYPE_DISCONNECT:
result = new MqttDisconnect(data);
break;
case MqttWireMessage.MESSAGE_TYPE_AUTH:
result = new MqttAuth(data);
break;
default:
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_MALFORMED_PACKET);
}
return result;
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public static byte[] encodeVariableByteInteger(int number) {
int numBytes = 0;
long no = number;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Encode the remaining length fields in the four bytes
do {
byte digit = (byte) (no % 128);
no = no / 128;
if (no > 0) {
digit |= 0x80;
}
baos.write(digit);
numBytes++;
} while ((no > 0) && (numBytes < 4));
return baos.toByteArray();
}
/**
* Validates that the reserved bits set on an MQTT packet conform to the MQTT
* specification.
*
* @param type
* - The Message Type
* @param reserved
* - The Reserved Bits
* @throws MqttException
* If the set reserved bits do not match the specification.
* @throws IllegalArgumentException
* If the message type does not exist.
*/
public static void validateReservedBits(byte type, byte reserved) throws MqttException, IllegalArgumentException {
if (type == MESSAGE_TYPE_PUBLISH) {
// Publish can vary, but will be parsed separately.
return;
}
if (type > MESSAGE_TYPE_AUTH) {
throw new IllegalArgumentException("Unrecognised Message Type.");
}
if (reserved != PACKET_RESERVED_MASKS[type]) {
throw new MqttException(MqttException.REASON_CODE_MALFORMED_PACKET);
}
}
protected byte[] encodeMessageId() throws MqttException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeShort(msgId);
dos.flush();
return baos.toByteArray();
} catch (IOException ioe) {
throw new MqttException(ioe);
}
}
public boolean isRetryable() {
return false;
}
public void setDuplicate(boolean duplicate) {
this.duplicate = duplicate;
}
public boolean isDuplicate() {
return this.duplicate;
}
public MqttProperties getProperties() {
return properties;
}
public void setProperties(MqttProperties properties) {
this.properties = properties;
}
@Override
public String toString() {
return PACKET_NAMES[type];
}
/**
* Validates that a return code is valid for this Packet
*
* @param returnCode
* - The return code to validate
* @param validReturnCodes
* - The list of valid return codes
* @throws MqttException
* - Thrown if the return code is not valid
*/
protected void validateReturnCode(int returnCode, int[] validReturnCodes) throws MqttException {
for (int validReturnCode : validReturnCodes) {
if (returnCode == validReturnCode) {
return;
}
}
throw new MqttException(MqttException.REASON_CODE_INVALID_RETURN_CODE);
}
/**
*
* Returns the reason codes from the MqttWireMessage. These will be present if
* the messages is of the following types:
* <ul>
* <li>CONNACK - 1 Reason Code Max.</li>
* <li>PUBACK - 1 Reason Code Max.</li>
* <li>PUBREC - 1 Reason Code Max.</li>
* <li>PUBCOMP - 1 Reason Code Max.</li>
* <li>PUBREL - 1 Reason Code Max.</li>
* <li>SUBACK - 1 or more Reason Codes.</li>
* <li>UNSUBACK - 1 or more Reason Codes.</li>
* <li>AUTH - 1 Reason Code Max.</li>
* </ul>
*
* Warning: This method may be removed in favour of Token.getReasonCodes()
*
* May be null if this message does not contain any Reason Codes.
*
* @return An array of return codes, or null.
*/
public int[] getReasonCodes() {
if (this.reasonCodes != null) {
return this.reasonCodes;
} else if (this.reasonCode != -1) {
return new int[] { this.reasonCode };
} else {
return null;
}
}
public byte[] serialize() throws MqttException {
byte[] a = getHeader();
byte[] b = getPayload();
byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}

View File

@@ -0,0 +1,40 @@
package org.eclipse.paho.mqttv5.common.packet;
public class UserProperty {
private final String key;
private final String value;
public UserProperty(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
@Override
public int hashCode() {
return key.hashCode() ^ value.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof UserProperty) {
UserProperty property = (UserProperty) o;
return this.key.equals(property.getKey()) && this.value.equals(property.getValue());
} else {
return false;
}
}
@Override
public String toString() {
return "UserProperty [key=" + key + ", value=" + value + "]";
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet.util;
import java.io.IOException;
import java.io.InputStream;
public class CountingInputStream extends InputStream{
private InputStream inputStream;
private int counter;
/**Constructs a new <code>CountingInputStream</code> wrapping the supplied
* input stream.
* @param inputStream The inputStream to count and provide
*/
public CountingInputStream(InputStream inputStream){
this.inputStream = inputStream;
this.counter = 0;
}
public int read() throws IOException {
int i = inputStream.read();
if (i != -1){
counter++;
}
return i;
}
/**
* Returns the number of bytes read since last reset
* @return the counter
*/
public int getCounter() {
return counter;
}
/**
* Resets the counter to zero
*/
public void resetCounter() {
counter = 0;
}
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet.util;
import java.io.IOException;
import java.io.InputStream;
public class MultiByteArrayInputStream extends InputStream {
private byte[] bytesA;
private int offsetA;
private int lengthA;
private byte[] bytesB;
private int offsetB;
private int lengthB;
private int pos = 0;
public MultiByteArrayInputStream(byte[] bytesA, int offsetA, int lengthA, byte[] bytesB, int offsetB, int lengthB) {
this.bytesA = bytesA;
this.bytesB = bytesB;
this.offsetA = offsetA;
this.offsetB = offsetB;
this.lengthA = lengthA;
this.lengthB = lengthB;
}
public int read() throws IOException {
int result = -1;
if (pos<lengthA) {
result = bytesA[offsetA+pos];
} else if (pos<lengthA+lengthB) {
result = bytesB[offsetB+pos-lengthA];
} else {
return -1;
}
if (result < 0) {
result += 256;
}
pos++;
return result;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2016 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - Original MQTTv3 implementation
* James Sutton - Initial MQTTv5 implementation
*/
package org.eclipse.paho.mqttv5.common.packet.util;
/**
* Represents a Variable Byte Integer (VBI), as defined by the MQTT v5 (1.5.5)
* specification.
*/
public class VariableByteInteger {
private int value;
private int length;
public VariableByteInteger(int value) {
this(value, -1);
}
public VariableByteInteger(int value, int length) {
this.value = value;
this.length = length;
}
/**
* Returns the number of bytes read when decoding this MBI
*
* @return The Encoded Length of the VBI.
*/
public int getEncodedLength() {
return length;
}
/**
* Returns the value of this MBI.
*
* @return The value of the VBI.
*/
public int getValue() {
return value;
}
}

View File

@@ -0,0 +1,225 @@
package org.eclipse.paho.mqttv5.common.util;
import java.io.UnsupportedEncodingException;
public class MqttTopicValidator {
/**
* The forward slash (/) is used to separate each level within a topic tree and provide a hierarchical structure to
* the topic space. The use of the topic level separator is significant when the two wildcard characters are
* encountered in topics specified by subscribers.
*/
public static final String TOPIC_LEVEL_SEPARATOR = "/";
/**
* Multi-level wildcard The number sign (#) is a wildcard character that matches any number of levels within a topic.
*/
public static final String MULTI_LEVEL_WILDCARD = "#";
/**
* Single-level wildcard The plus sign (+) is a wildcard character that matches only one topic level.
*/
public static final String SINGLE_LEVEL_WILDCARD = "+";
/**
* Multi-level wildcard pattern(/#)
*/
public static final String MULTI_LEVEL_WILDCARD_PATTERN = TOPIC_LEVEL_SEPARATOR + MULTI_LEVEL_WILDCARD;
/**
* Topic wildcards (#+)
*/
public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD;
// topic name and topic filter length range defined in the spec
private static final int MIN_TOPIC_LEN = 1;
private static final int MAX_TOPIC_LEN = 65535;
private static final char NUL = '\u0000';
/**
* Validate the topic name or topic filter
*
* @param topicString
* topic name or filter
* @param wildcardAllowed
* true if validate topic filter, false otherwise
* @param sharedSubAllowed
* true if shared subscription is allowed, false otherwise
* @throws IllegalArgumentException
* if the topic is invalid
*/
public static void validate(String topicString, boolean wildcardAllowed, boolean sharedSubAllowed)
throws IllegalArgumentException {
int topicLen = 0;
try {
topicLen = topicString.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e.getMessage());
}
// Spec: length check
// - All Topic Names and Topic Filters MUST be at least one character
// long
// - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST
// NOT encode to more than 65535 bytes
if (topicLen < MIN_TOPIC_LEN || topicLen > MAX_TOPIC_LEN) {
throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!",
new Object[] { Integer.valueOf(MIN_TOPIC_LEN), Integer.valueOf(MAX_TOPIC_LEN) }));
}
// *******************************************************************************
// 1) This is a topic filter string that can contain wildcard characters
// *******************************************************************************
if (wildcardAllowed) {
// Only # or +
if (Strings.equalsAny(topicString, new String[] { MULTI_LEVEL_WILDCARD, SINGLE_LEVEL_WILDCARD })) {
return;
}
// 1) Check multi-level wildcard
// Rule:
// The multi-level wildcard can be specified only on its own or next
// to the topic level separator character.
// - Can only contains one multi-level wildcard character
// - The multi-level wildcard must be the last character used within
// the topic tree
if (Strings.countMatches(topicString, MULTI_LEVEL_WILDCARD) > 1
|| (topicString.contains(MULTI_LEVEL_WILDCARD) && !topicString.endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) {
throw new IllegalArgumentException("Invalid usage of multi-level wildcard in topic string: " + topicString);
}
// 2) Check single-level wildcard
// Rule:
// The single-level wildcard can be used at any level in the topic
// tree, and in conjunction with the
// multilevel wildcard. It must be used next to the topic level
// separator, except when it is specified on
// its own.
validateSingleLevelWildcard(topicString);
return;
}
// Validate Shared Subscriptions
if (!sharedSubAllowed && topicString.startsWith("$share/")) {
throw new IllegalArgumentException("Shared Subscriptions are not allowed.");
}
// *******************************************************************************
// 2) This is a topic name string that MUST NOT contains any wildcard characters
// *******************************************************************************
if (Strings.containsAny(topicString, TOPIC_WILDCARDS)) {
throw new IllegalArgumentException("The topic name MUST NOT contain any wildcard characters (#+)");
}
}
private static void validateSingleLevelWildcard(String topicString) {
char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0);
char topicLevelSeparatorChar = TOPIC_LEVEL_SEPARATOR.charAt(0);
char[] chars = topicString.toCharArray();
int length = chars.length;
char prev = NUL, next = NUL;
for (int i = 0; i < length; i++) {
prev = (i - 1 >= 0) ? chars[i - 1] : NUL;
next = (i + 1 < length) ? chars[i + 1] : NUL;
if (chars[i] == singleLevelWildcardChar) {
// prev and next can be only '/' or none
if (prev != topicLevelSeparatorChar && prev != NUL || next != topicLevelSeparatorChar && next != NUL) {
throw new IllegalArgumentException(String
.format("Invalid usage of single-level wildcard in topic string '%s'!", new Object[] { topicString }));
}
}
}
}
/**
* Check the supplied topic name and filter match
*
* @param topicFilter
* topic filter: wildcards allowed
* @param topicName
* topic name: wildcards not allowed
* @return true if the topic matches the filter
* @throws IllegalArgumentException
* if the topic name or filter is invalid
*/
public static boolean isMatched(String topicFilter, String topicName) throws IllegalArgumentException {
int topicPos = 0;
int filterPos = 0;
int topicLen = topicName.length();
int filterLen = topicFilter.length();
MqttTopicValidator.validate(topicFilter, true, true);
MqttTopicValidator.validate(topicName, false, true);
if (topicFilter.equals(topicName)) {
return true;
}
while (filterPos < filterLen && topicPos < topicLen) {
if (topicFilter.charAt(filterPos) == '#') {
/*
* next 'if' will break when topicFilter = topic/# and topicName topic/A/, but they are matched
*/
topicPos = topicLen;
filterPos = filterLen;
break;
}
if (topicName.charAt(topicPos) == '/' && topicFilter.charAt(filterPos) != '/')
break;
if (topicFilter.charAt(filterPos) != '+' && topicFilter.charAt(filterPos) != '#'
&& topicFilter.charAt(filterPos) != topicName.charAt(topicPos))
break;
if (topicFilter.charAt(filterPos) == '+') { // skip until we meet the next separator, or end of string
int nextpos = topicPos + 1;
while (nextpos < topicLen && topicName.charAt(nextpos) != '/')
nextpos = ++topicPos + 1;
} else if (topicFilter.charAt(filterPos) == '#')
topicPos = topicLen - 1; // skip until end of string
filterPos++;
topicPos++;
}
if ((topicPos == topicLen) && (filterPos == filterLen)) {
return true;
} else {
/*
* https://github.com/eclipse/paho.mqtt.java/issues/418 Covers edge case to match sport/# to sport
*/
if ((topicFilter.length() - filterPos > 0) && (topicPos == topicLen)) {
if (topicName.charAt(topicPos - 1) == '/' && topicFilter.charAt(filterPos) == '#')
return true;
if (topicFilter.length() - filterPos > 1 && topicFilter.substring(filterPos, filterPos + 2).equals("/#")) {
if ((topicFilter.length() - topicName.length()) == 2
&& topicFilter.substring(topicFilter.length() - 2, topicFilter.length()).equals("/#")) {
return true;
}
}
}
/*
* https://github.com/eclipse/paho.mqtt.java/issues/918
* covers cases that include more then one wildcard
* sport/+/tennis/#
*/
String[] topicFilterParts = topicFilter.split(TOPIC_LEVEL_SEPARATOR);
String[] topicParts = topicName.split(TOPIC_LEVEL_SEPARATOR);
if(topicFilterParts.length -1 == topicParts.length &&
topicFilterParts[topicFilterParts.length-1].equals( MULTI_LEVEL_WILDCARD)) {
for (int i = 0; i<topicParts.length;i++) {
if(!topicParts[i].equals(topicFilterParts[i]) && !topicFilterParts[i].equals(SINGLE_LEVEL_WILDCARD)) {
return false;
}
}
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,173 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Bin Zhang - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.mqttv5.common.util;
/**
* String helper
*/
public final class Strings {
// Represents a failed index search.
private static final int INDEX_NOT_FOUND = -1;
/**
* Checks if the CharSequence equals any character in the given set of characters.
*
* @param cs the CharSequence to check
* @param strs the set of characters to check against
* @return true if equals any
*/
public static boolean equalsAny(CharSequence cs, CharSequence[] strs) {
boolean eq = false;
if (cs == null) {
eq = strs == null;
}
if (strs != null) {
for (CharSequence str : strs) {
eq = eq || str.equals(cs);
}
}
return eq;
}
/**
* Checks if the CharSequence contains any character in the given set of characters.
*
* @param cs the CharSequence to check, may be null
* @param searchChars the chars to search for, may be null
* @return the {@code true} if any of the chars are found, {@code false} if no match or null input
*/
public static boolean containsAny(CharSequence cs, CharSequence searchChars) {
if (searchChars == null) {
return false;
}
return containsAny(cs, toCharArray(searchChars));
}
/**
* Checks if the CharSequence contains any character in the given set of characters.
*
* @param cs the CharSequence to check, may be null
* @param searchChars the chars to search for, may be null
* @return the {@code true} if any of the chars are found, {@code false} if no match or null input
*/
public static boolean containsAny(CharSequence cs, char[] searchChars) {
if (isEmpty(cs) || isEmpty(searchChars)) {
return false;
}
int csLength = cs.length();
int searchLength = searchChars.length;
int csLast = csLength - 1;
int searchLast = searchLength - 1;
for (int i = 0; i < csLength; i++) {
char ch = cs.charAt(i);
for (int j = 0; j < searchLength; j++) {
if (searchChars[j] == ch) {
if (Character.isHighSurrogate(ch)) {
if (j == searchLast) {
// missing low surrogate, fine, like String.indexOf(String)
return true;
}
if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
return true;
}
}
else {
// ch is in the Basic Multilingual Plane
return true;
}
}
}
}
return false;
}
/**
* Checks if a CharSequence is empty ("") or null.
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is empty or null
*/
public static boolean isEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
}
/**
* @param array
*/
private static boolean isEmpty(char[] array) {
return array == null || array.length == 0;
}
/**
* Green implementation of toCharArray.
*
* @param cs the {@code CharSequence} to be processed
* @return the resulting char array
*/
private static char[] toCharArray(CharSequence cs) {
if (cs instanceof String) {
return ((String) cs).toCharArray();
}
else {
int sz = cs.length();
char[] array = new char[cs.length()];
for (int i = 0; i < sz; i++) {
array[i] = cs.charAt(i);
}
return array;
}
}
/**
* Counts how many times the substring appears in the larger string.
*
* @param str the CharSequence to check, may be null
* @param sub the substring to count, may be null
* @return the number of occurrences, 0 if either CharSequence is {@code null}
*/
public static int countMatches(CharSequence str, CharSequence sub) {
if (isEmpty(str) || isEmpty(sub)) {
return 0;
}
int count = 0;
int idx = 0;
while ((idx = indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
count++;
idx += sub.length();
}
return count;
}
/**
* Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
*
* @param cs the {@code CharSequence} to be processed
* @param searchChar the {@code CharSequence} to be searched for
* @param start the start index
* @return the index where the search sequence was found
*/
private static int indexOf(CharSequence cs, CharSequence searchChar, int start) {
return cs.toString().indexOf(searchChar.toString(), start);
}
private Strings() {
// prevented from constructing objects
}
}