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