PageRenderTime 25ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttWireMessage.java

https://gitlab.com/jforge/paho.mqtt.java
Java | 352 lines | 236 code | 43 blank | 73 comment | 50 complexity | 878d6b0e91b111a4102a06a8faa0a8e6 MD5 | raw file
  1. /*******************************************************************************
  2. * Copyright (c) 2009, 2014 IBM Corp.
  3. *
  4. * All rights reserved. This program and the accompanying materials
  5. * are made available under the terms of the Eclipse Public License v1.0
  6. * and Eclipse Distribution License v1.0 which accompany this distribution.
  7. *
  8. * The Eclipse Public License is available at
  9. * http://www.eclipse.org/legal/epl-v10.html
  10. * and the Eclipse Distribution License is available at
  11. * http://www.eclipse.org/org/documents/edl-v10.php.
  12. *
  13. * Contributors:
  14. * Dave Locke - initial API and implementation and/or initial documentation
  15. */
  16. package org.eclipse.paho.client.mqttv3.internal.wire;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.DataInputStream;
  20. import java.io.DataOutputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.UnsupportedEncodingException;
  24. import org.eclipse.paho.client.mqttv3.MqttException;
  25. import org.eclipse.paho.client.mqttv3.MqttPersistable;
  26. import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
  27. /**
  28. * An on-the-wire representation of an MQTT message.
  29. */
  30. public abstract class MqttWireMessage {
  31. public static final byte MESSAGE_TYPE_CONNECT = 1;
  32. public static final byte MESSAGE_TYPE_CONNACK = 2;
  33. public static final byte MESSAGE_TYPE_PUBLISH = 3;
  34. public static final byte MESSAGE_TYPE_PUBACK = 4;
  35. public static final byte MESSAGE_TYPE_PUBREC = 5;
  36. public static final byte MESSAGE_TYPE_PUBREL = 6;
  37. public static final byte MESSAGE_TYPE_PUBCOMP = 7;
  38. public static final byte MESSAGE_TYPE_SUBSCRIBE = 8;
  39. public static final byte MESSAGE_TYPE_SUBACK = 9;
  40. public static final byte MESSAGE_TYPE_UNSUBSCRIBE = 10;
  41. public static final byte MESSAGE_TYPE_UNSUBACK = 11;
  42. public static final byte MESSAGE_TYPE_PINGREQ = 12;
  43. public static final byte MESSAGE_TYPE_PINGRESP = 13;
  44. public static final byte MESSAGE_TYPE_DISCONNECT = 14;
  45. protected static final String STRING_ENCODING = "UTF-8";
  46. private static final String PACKET_NAMES[] = { "reserved", "CONNECT", "CONNACK", "PUBLISH",
  47. "PUBACK", "PUBREC", "PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK",
  48. "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT" };
  49. //The type of the message (e.g. CONNECT, PUBLISH, PUBACK)
  50. private byte type;
  51. //The MQTT message ID
  52. protected int msgId;
  53. protected boolean duplicate = false;
  54. public MqttWireMessage(byte type) {
  55. this.type = type;
  56. // Use zero as the default message ID. Can't use -1, as that is serialized
  57. // as 65535, which would be a valid ID.
  58. this.msgId = 0;
  59. }
  60. /**
  61. * Sub-classes should override this to encode the message info.
  62. * Only the least-significant four bits will be used.
  63. */
  64. protected abstract byte getMessageInfo();
  65. /**
  66. * Sub-classes should override this method to supply the payload bytes.
  67. */
  68. public byte[] getPayload() throws MqttException {
  69. return new byte[0];
  70. }
  71. /**
  72. * Returns the type of the message.
  73. */
  74. public byte getType() {
  75. return type;
  76. }
  77. /**
  78. * Returns the MQTT message ID.
  79. */
  80. public int getMessageId() {
  81. return msgId;
  82. }
  83. /**
  84. * Sets the MQTT message ID.
  85. */
  86. public void setMessageId(int msgId) {
  87. this.msgId = msgId;
  88. }
  89. /**
  90. * Returns a key associated with the message. For most message types
  91. * this will be unique. For connect, disconnect and ping only one
  92. * message of this type is allowed so a fixed key will be returned
  93. * @return key a key associated with the message
  94. */
  95. public String getKey() {
  96. return new Integer(getMessageId()).toString();
  97. }
  98. public byte[] getHeader() throws MqttException {
  99. try {
  100. int first = ((getType() & 0x0f) << 4) ^ (getMessageInfo() & 0x0f);
  101. byte[] varHeader = getVariableHeader();
  102. int remLen = varHeader.length + getPayload().length;
  103. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  104. DataOutputStream dos = new DataOutputStream(baos);
  105. dos.writeByte(first);
  106. dos.write(encodeMBI(remLen));
  107. dos.write(varHeader);
  108. dos.flush();
  109. return baos.toByteArray();
  110. } catch(IOException ioe) {
  111. throw new MqttException(ioe);
  112. }
  113. }
  114. protected abstract byte[] getVariableHeader() throws MqttException;
  115. /**
  116. * Returns whether or not this message needs to include a message ID.
  117. */
  118. public boolean isMessageIdRequired() {
  119. return true;
  120. }
  121. public static MqttWireMessage createWireMessage(MqttPersistable data) throws MqttException {
  122. byte[] payload = data.getPayloadBytes();
  123. // The persistable interface allows a message to be restored entirely in the header array
  124. // Need to treat these two arrays as a single array of bytes and use the decoding
  125. // logic to identify the true header/payload split
  126. if (payload == null) {
  127. payload = new byte[0];
  128. }
  129. MultiByteArrayInputStream mbais = new MultiByteArrayInputStream(
  130. data.getHeaderBytes(),
  131. data.getHeaderOffset(),
  132. data.getHeaderLength(),
  133. payload,
  134. data.getPayloadOffset(),
  135. data.getPayloadLength());
  136. return createWireMessage(mbais);
  137. }
  138. public static MqttWireMessage createWireMessage(byte[] bytes) throws MqttException {
  139. ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
  140. return createWireMessage(bais);
  141. }
  142. private static MqttWireMessage createWireMessage(InputStream inputStream) throws MqttException {
  143. try {
  144. CountingInputStream counter = new CountingInputStream(inputStream);
  145. DataInputStream in = new DataInputStream(counter);
  146. int first = in.readUnsignedByte();
  147. byte type = (byte) (first >> 4);
  148. byte info = (byte) (first &= 0x0f);
  149. long remLen = readMBI(in).getValue();
  150. long totalToRead = counter.getCounter() + remLen;
  151. MqttWireMessage result;
  152. long remainder = totalToRead - counter.getCounter();
  153. byte[] data = new byte[0];
  154. // The remaining bytes must be the payload...
  155. if (remainder > 0) {
  156. data = new byte[(int) remainder];
  157. in.readFully(data, 0, data.length);
  158. }
  159. if (type == MqttWireMessage.MESSAGE_TYPE_CONNECT) {
  160. result = new MqttConnect(info, data);
  161. }
  162. else if (type == MqttWireMessage.MESSAGE_TYPE_PUBLISH) {
  163. result = new MqttPublish(info, data);
  164. }
  165. else if (type == MqttWireMessage.MESSAGE_TYPE_PUBACK) {
  166. result = new MqttPubAck(info, data);
  167. }
  168. else if (type == MqttWireMessage.MESSAGE_TYPE_PUBCOMP) {
  169. result = new MqttPubComp(info, data);
  170. }
  171. else if (type == MqttWireMessage.MESSAGE_TYPE_CONNACK) {
  172. result = new MqttConnack(info, data);
  173. }
  174. else if (type == MqttWireMessage.MESSAGE_TYPE_PINGREQ) {
  175. result = new MqttPingReq(info, data);
  176. }
  177. else if (type == MqttWireMessage.MESSAGE_TYPE_PINGRESP) {
  178. result = new MqttPingResp(info, data);
  179. }
  180. else if (type == MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE) {
  181. result = new MqttSubscribe(info, data);
  182. }
  183. else if (type == MqttWireMessage.MESSAGE_TYPE_SUBACK) {
  184. result = new MqttSuback(info, data);
  185. }
  186. else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE) {
  187. result = new MqttUnsubscribe(info, data);
  188. }
  189. else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBACK) {
  190. result = new MqttUnsubAck(info, data);
  191. }
  192. else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREL) {
  193. result = new MqttPubRel(info, data);
  194. }
  195. else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREC) {
  196. result = new MqttPubRec(info, data);
  197. }
  198. else if (type == MqttWireMessage.MESSAGE_TYPE_DISCONNECT) {
  199. result = new MqttDisconnect(info, data);
  200. }
  201. else {
  202. throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
  203. }
  204. return result;
  205. } catch(IOException io) {
  206. throw new MqttException(io);
  207. }
  208. }
  209. protected static byte[] encodeMBI( long number) {
  210. int numBytes = 0;
  211. long no = number;
  212. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  213. // Encode the remaining length fields in the four bytes
  214. do {
  215. byte digit = (byte)(no % 128);
  216. no = no / 128;
  217. if (no > 0) {
  218. digit |= 0x80;
  219. }
  220. bos.write(digit);
  221. numBytes++;
  222. } while ( (no > 0) && (numBytes<4) );
  223. return bos.toByteArray();
  224. }
  225. /**
  226. * Decodes an MQTT Multi-Byte Integer from the given stream.
  227. */
  228. protected static MultiByteInteger readMBI(DataInputStream in) throws IOException {
  229. byte digit;
  230. long msgLength = 0;
  231. int multiplier = 1;
  232. int count = 0;
  233. do {
  234. digit = in.readByte();
  235. count++;
  236. msgLength += ((digit & 0x7F) * multiplier);
  237. multiplier *= 128;
  238. } while ((digit & 0x80) != 0);
  239. return new MultiByteInteger(msgLength, count);
  240. }
  241. protected byte[] encodeMessageId() throws MqttException {
  242. try {
  243. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  244. DataOutputStream dos = new DataOutputStream(baos);
  245. dos.writeShort(msgId);
  246. dos.flush();
  247. return baos.toByteArray();
  248. }
  249. catch (IOException ex) {
  250. throw new MqttException(ex);
  251. }
  252. }
  253. public boolean isRetryable() {
  254. return false;
  255. }
  256. public void setDuplicate(boolean duplicate) {
  257. this.duplicate = duplicate;
  258. }
  259. /**
  260. * Encodes a String given into UTF-8, before writing this to the DataOutputStream the length of the
  261. * encoded string is encoded into two bytes and then written to the DataOutputStream. @link{DataOutputStream#writeUFT(String)}
  262. * should be no longer used. @link{DataOutputStream#writeUFT(String)} does not correctly encode UTF-16 surrogate characters.
  263. *
  264. * @param dos The stream to write the encoded UTF-8 String to.
  265. * @param stringToEncode The String to be encoded
  266. * @throws MqttException Thrown when an error occurs with either the encoding or writing the data to the stream
  267. */
  268. protected void encodeUTF8(DataOutputStream dos, String stringToEncode) throws MqttException
  269. {
  270. try {
  271. byte[] encodedString = stringToEncode.getBytes("UTF-8");
  272. byte byte1 = (byte) ((encodedString.length >>> 8) & 0xFF);
  273. byte byte2 = (byte) ((encodedString.length >>> 0) & 0xFF);
  274. dos.write(byte1);
  275. dos.write(byte2);
  276. dos.write(encodedString);
  277. }
  278. catch(UnsupportedEncodingException ex)
  279. {
  280. throw new MqttException(ex);
  281. } catch (IOException ex) {
  282. throw new MqttException(ex);
  283. }
  284. }
  285. /**
  286. * Decodes a UTF-8 string from the DataInputStream provided. @link(DataInoutStream#readUTF()) should be no longer used, because @link(DataInoutStream#readUTF())
  287. * does not decode UTF-16 surrogate characters correctly.
  288. *
  289. * @param input The input stream from which to read the encoded string
  290. * @return a decoded String from the DataInputStream
  291. * @throws MqttException thrown when an error occurs with either reading from the stream or
  292. * decoding the encoded string.
  293. */
  294. protected String decodeUTF8(DataInputStream input) throws MqttException
  295. {
  296. int encodedLength;
  297. try {
  298. encodedLength = input.readUnsignedShort();
  299. byte[] encodedString = new byte[encodedLength];
  300. input.readFully(encodedString);
  301. return new String(encodedString, "UTF-8");
  302. } catch (IOException ex) {
  303. throw new MqttException(ex);
  304. }
  305. }
  306. public String toString() {
  307. return PACKET_NAMES[type];
  308. }
  309. }