All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2009, 2014 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution. 
 *
 * The Eclipse Public License is available at 
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Dave Locke - initial API and implementation and/or initial documentation
 */
package org.eclipse.paho.client.mqttv3.internal.wire;

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 java.io.UnsupportedEncodingException;

import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttPersistable;
import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;


/**
 * An on-the-wire representation of an MQTT message.
 */
public abstract class MqttWireMessage {	
	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;

	protected static final String STRING_ENCODING = "UTF-8";
	
	private static final String PACKET_NAMES[] = { "reserved", "CONNECT", "CONNACK", "PUBLISH",
			"PUBACK", "PUBREC", "PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK",
			"UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT" };

	//The type of the message (e.g. CONNECT, PUBLISH, PUBACK)
	private byte type;
	//The MQTT message ID
	protected int msgId;
	
	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.
	 */
	protected abstract byte getMessageInfo();
	
	/**
	 * Sub-classes should override this method to supply the payload bytes.
	 */
	public byte[] getPayload() throws MqttException {
		return new byte[0];
	}
	
	/**
	 * Returns the type of the message.
	 */
	public byte getType() {
		return type;
	}
	
	/**
	 * Returns the MQTT message ID.
	 */
	public int getMessageId() {
		return msgId;
	}
	
	/**
	 * Sets 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 key a key associated with the message
	 */
	public String getKey() {
		return new Integer(getMessageId()).toString();
	}
	
	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(encodeMBI(remLen));
	        dos.write(varHeader);
	        dos.flush();
	        return baos.toByteArray();
	    } catch(IOException ioe) {
	        throw new MqttException(ioe);
	    }
	}
	
	protected abstract byte[] getVariableHeader() throws MqttException;


	/**
	 * Returns whether or not this message needs to include a message ID.
	 */
	public boolean isMessageIdRequired() {
		return true;
	}
	
	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
		// 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 = readMBI(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);
			}
				
			if (type == MqttWireMessage.MESSAGE_TYPE_CONNECT) {
				result = new MqttConnect(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PUBLISH) {
				result = new MqttPublish(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PUBACK) {
				result = new MqttPubAck(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PUBCOMP) {
				result = new MqttPubComp(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_CONNACK) {
				result = new MqttConnack(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PINGREQ) {
				result = new MqttPingReq(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PINGRESP) {
				result = new MqttPingResp(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE) {
				result = new MqttSubscribe(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_SUBACK) {
				result = new MqttSuback(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE) {
				result = new MqttUnsubscribe(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBACK) {
				result = new MqttUnsubAck(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREL) {
				result = new MqttPubRel(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREC) {
				result = new MqttPubRec(info, data);
			}
			else if (type == MqttWireMessage.MESSAGE_TYPE_DISCONNECT) {
				result = new MqttDisconnect(info, data);
			}
			else {
				throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
			}
			return result;
		} catch(IOException io) {
			throw new MqttException(io);
		}
	}
		
	protected static byte[] encodeMBI( long number) {
		int numBytes = 0;
		long no = number;
		ByteArrayOutputStream bos = 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;
			}
			bos.write(digit);
			numBytes++;
		} while ( (no > 0) && (numBytes<4) );
		
		return bos.toByteArray();
	}
	
	/**
	 * Decodes an MQTT Multi-Byte Integer from the given stream.
	 */
	protected static MultiByteInteger readMBI(DataInputStream in) throws IOException {
		byte digit;
		long msgLength = 0;
		int multiplier = 1;
		int count = 0;
		
		do {
			digit = in.readByte();
			count++;
			msgLength += ((digit & 0x7F) * multiplier);
			multiplier *= 128;
		} while ((digit & 0x80) != 0);
		
		return new MultiByteInteger(msgLength, count);
	}
	
	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 ex) {
			throw new MqttException(ex);
		}
	}
	
	public boolean isRetryable() {
		return false;
	}
	
	public void setDuplicate(boolean duplicate) {
		this.duplicate = duplicate;
	}

	/**
	 * Encodes a String given into UTF-8, before writing this to the DataOutputStream the length of the
	 * encoded string is encoded into two bytes and then written to the DataOutputStream. @link{DataOutputStream#writeUFT(String)}
	 * should be no longer used. @link{DataOutputStream#writeUFT(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
	 */
	protected void encodeUTF8(DataOutputStream dos, String stringToEncode) throws MqttException
	{
		try {

			byte[] encodedString = stringToEncode.getBytes("UTF-8");
			byte byte1 = (byte) ((encodedString.length >>> 8) & 0xFF);
			byte byte2 =  (byte) ((encodedString.length >>> 0) & 0xFF);  
			

			dos.write(byte1);
			dos.write(byte2);
			dos.write(encodedString);
		}
		catch(UnsupportedEncodingException ex)
		{
			throw new MqttException(ex);
		} catch (IOException ex) {
			throw new MqttException(ex);
		}
	}
	
	/**
	 * Decodes a UTF-8 string from the DataInputStream provided. @link(DataInoutStream#readUTF()) should be no longer used, because  @link(DataInoutStream#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 DataInputStream
	 * @throws MqttException thrown when an error occurs with either reading from the stream or
	 * decoding the encoded string.
	 */
	protected String decodeUTF8(DataInputStream input) throws MqttException
	{
		int encodedLength;
		try {
			encodedLength = input.readUnsignedShort();

			byte[] encodedString = new byte[encodedLength];
				input.readFully(encodedString);

			return new String(encodedString, "UTF-8");
		} catch (IOException ex) {
			throw new MqttException(ex);
		}
	}

	public String toString() {
		return PACKET_NAMES[type];
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy