1 package org.sim0mq.message; 2 3 import org.djutils.exceptions.Throw; 4 import org.djutils.serialization.EndianUtil; 5 import org.djutils.serialization.SerializationException; 6 import org.djutils.serialization.TypedMessage; 7 import org.sim0mq.Sim0MQException; 8 9 /** 10 * The message structure of a typical typed Sim0MQ simulation message looks as follows:<br> 11 * Frame 0. Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 01.<br> 12 * Frame 1. Simulation run id. Simulation run ids can be provided in different types. Examples are two 64-bit longs indicating a 13 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a simulation run 14 * number. In order to check whether the right information has been received, the id can be translated to a String and compared 15 * with an internal string representation of the required id.<br> 16 * Frame 2. Message type id. Message type ids can be defined per type of simulation, and can be provided in different types. 17 * Examples are a String with a meaningful identification, or a short or an int with a message type number. For interoperability 18 * between different types of simulation, a String id with dot-notation (e.g., DSOL.1 for a simulator start message from DSOL or 19 * OTS.14 for a statistics message from OpenTrafficSim) would be preferred.<br> 20 * Frame 3. Message status id. Messages can be about something new (containing a definition that can be quite long), an update 21 * (which is often just an id followed by a single number), and a deletion (which is often just an id).<br> 22 * Frame 4. Number of fields. The number of fields in the payload is indicated to be able to check the payload and to avoid 23 * reading past the end. The number of fields can be encoded using any length type (byte, short, int, long).<br> 24 * Frame 5-n. Payload, where each field has a 1-byte prefix denoting the type of field. 25 * <p> 26 * Copyright (c) 2016-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br> 27 * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>. 28 * </p> 29 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $, 30 * initial version Mar 3, 2017 <br> 31 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a> 32 */ 33 public final class SimulationMessage 34 { 35 /** 36 * Do not instantiate this utility class. 37 */ 38 private SimulationMessage() 39 { 40 // Utility class; do not instantiate. 41 } 42 43 /** 44 * Encode the object array into a message. Use UTF8 or UTF16 to code Strings. 45 * @param utf8 choice to use Use UTF8 or UTF16 to code Strings 46 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 47 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 48 * int with a simulation run number. 49 * @param senderId The sender id can be used to send back a message to the sender at some later time. 50 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 51 * error can be sent if we receive a message not meant for us). 52 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 53 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 54 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 55 * correctly. The number is unique for the sender, so not globally within the federation. 56 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 57 * is coded as a byte. 58 * @param content the objects to encode 59 * @return the zeroMQ message to send as a byte array 60 * @throws Sim0MQException on unknown data type 61 * @throws SerializationException on serialization problem 62 */ 63 @SuppressWarnings("checkstyle:parameternumber") 64 private static byte[] encode(final Encoding utf8, final Object simulationRunId, final Object senderId, 65 final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus, 66 final Object... content) throws Sim0MQException, SerializationException 67 { 68 Object[] simulationContent = new Object[content.length + 8]; 69 simulationContent[0] = Sim0MQMessage.VERSION; 70 simulationContent[1] = simulationRunId; 71 simulationContent[2] = senderId; 72 simulationContent[3] = receiverId; 73 simulationContent[4] = messageTypeId; 74 simulationContent[5] = messageId; 75 simulationContent[6] = new Byte(messageStatus.getStatus()); 76 simulationContent[7] = new Short((short) content.length); 77 for (int i = 0; i < content.length; i++) 78 { 79 simulationContent[i + 8] = content[i]; 80 } 81 return TypedMessage.encodeUTF8(EndianUtil.BIG_ENDIAN, simulationContent); 82 } 83 84 /** 85 * Encode the object array into a message. Use UTF8 or UTF16 to code Strings. 86 * @param utf8 choice to use Use UTF8 or UTF16 to code Strings 87 * @param identity the identity of the federate to which this is the reply 88 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 89 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 90 * int with a simulation run number. 91 * @param senderId The sender id can be used to send back a message to the sender at some later time. 92 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 93 * error can be sent if we receive a message not meant for us). 94 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 95 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 96 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 97 * correctly. The number is unique for the sender, so not globally within the federation. 98 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 99 * is coded as a byte. 100 * @param content the objects to encode 101 * @return the zeroMQ message to send as a byte array 102 * @throws Sim0MQException on unknown data type 103 * @throws SerializationException on serialization problem 104 */ 105 @SuppressWarnings("checkstyle:parameternumber") 106 private static byte[] encodeReply(final Encoding utf8, final String identity, final Object simulationRunId, 107 final Object senderId, final Object receiverId, final Object messageTypeId, final long messageId, 108 final MessageStatus messageStatus, final Object... content) throws Sim0MQException, SerializationException 109 { 110 Object[] simulationContent = new Object[content.length + 10]; 111 simulationContent[0] = identity; 112 simulationContent[1] = new byte[] { 0 }; 113 simulationContent[2] = Sim0MQMessage.VERSION; 114 simulationContent[3] = simulationRunId; 115 simulationContent[4] = senderId; 116 simulationContent[5] = receiverId; 117 simulationContent[6] = messageTypeId; 118 simulationContent[7] = messageId; 119 simulationContent[8] = new Byte(messageStatus.getStatus()); 120 simulationContent[9] = new Short((short) content.length); 121 for (int i = 0; i < content.length; i++) 122 { 123 simulationContent[i + 10] = content[i]; 124 } 125 return TypedMessage.encodeUTF8(EndianUtil.BIG_ENDIAN, simulationContent); 126 } 127 128 /** 129 * Encode the object array into a message. Use UTF8 to code Strings. 130 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 131 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 132 * int with a simulation run number. 133 * @param senderId The sender id can be used to send back a message to the sender at some later time. 134 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 135 * error can be sent if we receive a message not meant for us). 136 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 137 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 138 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 139 * correctly. The number is unique for the sender, so not globally within the federation. 140 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 141 * is coded as a byte. 142 * @param content the objects to encode 143 * @return the zeroMQ message to send as a byte array 144 * @throws Sim0MQException on unknown data type 145 * @throws SerializationException on serialization problem 146 */ 147 public static byte[] encodeUTF8(final Object simulationRunId, final Object senderId, final Object receiverId, 148 final Object messageTypeId, final long messageId, final MessageStatus messageStatus, final Object... content) 149 throws Sim0MQException, SerializationException 150 { 151 return encode(Encoding.UTF8, simulationRunId, senderId, receiverId, messageTypeId, messageId, messageStatus, content); 152 } 153 154 /** 155 * Encode the object array into a message. Use UTF16 to code Strings. 156 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 157 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 158 * int with a simulation run number. 159 * @param senderId The sender id can be used to send back a message to the sender at some later time. 160 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 161 * error can be sent if we receive a message not meant for us). 162 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 163 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 164 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 165 * correctly. The number is unique for the sender, so not globally within the federation. 166 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 167 * is coded as a byte. 168 * @param content the objects to encode 169 * @return the zeroMQ message to send as a byte array 170 * @throws Sim0MQException on unknown data type 171 * @throws SerializationException on serialization problem 172 */ 173 public static byte[] encodeUTF16(final Object simulationRunId, final Object senderId, final Object receiverId, 174 final Object messageTypeId, final long messageId, final MessageStatus messageStatus, final Object... content) 175 throws Sim0MQException, SerializationException 176 { 177 return encode(Encoding.UTF16, simulationRunId, senderId, receiverId, messageTypeId, messageId, messageStatus, content); 178 } 179 180 /** 181 * Encode the object array into a reply message. Use UTF8 to code Strings. 182 * @param identity the identity of the federate to which this is the reply 183 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 184 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 185 * int with a simulation run number. 186 * @param senderId The sender id can be used to send back a message to the sender at some later time. 187 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 188 * error can be sent if we receive a message not meant for us). 189 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 190 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 191 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 192 * correctly. The number is unique for the sender, so not globally within the federation. 193 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 194 * is coded as a byte. 195 * @param content the objects to encode 196 * @return the zeroMQ message to send as a byte array 197 * @throws Sim0MQException on unknown data type 198 * @throws SerializationException on serialization problem 199 */ 200 @SuppressWarnings("checkstyle:parameternumber") 201 public static byte[] encodeReplyUTF8(final String identity, final Object simulationRunId, final Object senderId, 202 final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus, 203 final Object... content) throws Sim0MQException, SerializationException 204 { 205 return encodeReply(Encoding.UTF8, identity, simulationRunId, senderId, receiverId, messageTypeId, messageId, 206 messageStatus, content); 207 } 208 209 /** 210 * Encode the object array into a reply message. Use UTF16 to code Strings. 211 * @param identity the identity of the federate to which this is the reply 212 * @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs 213 * indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an 214 * int with a simulation run number. 215 * @param senderId The sender id can be used to send back a message to the sender at some later time. 216 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 217 * error can be sent if we receive a message not meant for us). 218 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 219 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 220 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 221 * correctly. The number is unique for the sender, so not globally within the federation. 222 * @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field 223 * is coded as a byte. 224 * @param content the objects to encode 225 * @return the zeroMQ message to send as a byte array 226 * @throws Sim0MQException on unknown data type 227 * @throws SerializationException on serialization problem 228 */ 229 @SuppressWarnings("checkstyle:parameternumber") 230 public static byte[] encodeReplyUTF16(final String identity, final Object simulationRunId, final Object senderId, 231 final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus, 232 final Object... content) throws Sim0MQException, SerializationException 233 { 234 return encodeReply(Encoding.UTF16, identity, simulationRunId, senderId, receiverId, messageTypeId, messageId, 235 messageStatus, content); 236 } 237 238 /** 239 * Decode the message into an object array. Note that the message fields are coded as follows:<br> 240 * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br> 241 * 1 = simulation run id, could be String, int, Object, ...<br> 242 * 2 = sender id, could be String, int, Object, ...<br> 243 * 3 = receiver id, could be String, int, Object, ...<br> 244 * 4 = message type id, could be String, int, Object, ...<br> 245 * 5 = message id, as a long.<br> 246 * 6 = message status, 1=NEW, 2=CHANGE, 3=DELETE.<br> 247 * 7 = number of fields that follow.<br> 248 * 8-n = payload, where the number of fields was defined by message[7]. 249 * @param bytes the ZeroMQ byte array to decode 250 * @return an array of objects of the right type 251 * @throws Sim0MQException on unknown data type 252 * @throws SerializationException when deserialization fails 253 */ 254 public static Object[] decode(final byte[] bytes) throws Sim0MQException, SerializationException 255 { 256 Object[] message = TypedMessage.decodeToObjectDataTypes(bytes, EndianUtil.BIG_ENDIAN); 257 Throw.when(!(message[7] instanceof Number), Sim0MQException.class, "message[7] is not a number"); 258 Throw.when(message.length != ((Number) message[7]).intValue() + 8, Sim0MQException.class, 259 "message[7] number of fields not matched by message structure"); 260 return message; 261 } 262 263 /** 264 * Return a printable version of the message, e.g. for debugging purposes. 265 * @param message the message to parse 266 * @return a string representation of the message 267 */ 268 public static String print(final Object[] message) 269 { 270 StringBuffer s = new StringBuffer(); 271 s.append("0. magic number : " + message[0] + "\n"); 272 s.append("1. simulation run id: " + message[1] + "\n"); 273 s.append("2. sender id : " + message[2] + "\n"); 274 s.append("3. receiver id : " + message[3] + "\n"); 275 s.append("4. message type id : " + message[4] + "\n"); 276 s.append("5. message id : " + message[5] + "\n"); 277 s.append("6. message status : " + MessageStatus.getTypes().get((int) (byte) message[6]) + "\n"); 278 s.append("7. number of fields : " + message[7] + "\n"); 279 int nr = ((Number) message[7]).intValue(); 280 if (message.length != nr + 8) 281 { 282 s.append("Error - number of fields not matched by message structure"); 283 } 284 else 285 { 286 for (int i = 0; i < nr; i++) 287 { 288 s.append((8 + i) + ". message field : " + message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() 289 + ")\n"); 290 } 291 } 292 return s.toString(); 293 } 294 295 /** 296 * Return a printable line with the payload of the message, e.g. for debugging purposes. 297 * @param message the message to parse 298 * @return a string representation of the message 299 */ 300 public static String listPayload(final Object[] message) 301 { 302 StringBuffer s = new StringBuffer(); 303 s.append('|'); 304 int nr = ((Number) message[7]).intValue(); 305 if (message.length != nr + 8) 306 { 307 s.append("Error - number of fields not matched by message structure"); 308 } 309 else 310 { 311 for (int i = 0; i < nr; i++) 312 { 313 s.append(message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() + ") | "); 314 } 315 } 316 return s.toString(); 317 } 318 319 }