1 package org.sim0mq.message; 2 3 import java.io.Serializable; 4 5 import org.djutils.exceptions.Throw; 6 import org.djutils.serialization.EndianUtil; 7 import org.djutils.serialization.SerializationException; 8 import org.djutils.serialization.TypedMessage; 9 import org.sim0mq.Sim0MQException; 10 11 /** 12 * Sim0MQMessage contains the abstract body of the message with the first fields of every Sim0MQ message. The message structure 13 * of a typical typed Sim0MQ simulation message looks as follows: 14 * <ul> 15 * <li>Frame 0. Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 02.</li> 16 * <li>Frame 1. Endianness. A boolean that is True for Big-Endian encoding and false for Little-Endian encosing of the 17 * message.</li> 18 * <li>Frame 2. Federation id. Federation ids can be provided in different types. Examples are a 64-bit long, or a String with a 19 * UUID number, a String with meaningful identification, or a short or an int with a simulation run number. In order to check 20 * whether the right information has been received, the id can be translated to a String and compared with an internal string 21 * representation of the required simulation run id. Typically we use a String to provide maximum freedom. In that case, the run 22 * id can be coded as UTF-8 or UTF-16.</li> 23 * <li>Frame 3. Sender id. Sender ids can be provided in different types. Examples are a 64-bit long, or a String with a UUID 24 * number, a String with meaningful identification, or a short or an int with a sender id number. The sender id can be used to 25 * send back a message to the sender at some later time. Typically we use a String to provide maximum freedom. In that case, the 26 * sender id can be coded as UTF-8 or UTF-16.</li> 27 * <li>Frame 4. Receiver id. Receiver ids can be provided in different types. Examples are a 64-bit long, or a String with a 28 * UUID number, a String with meaningful identification, or a short or an int with a receiver id number. The receiver id can be 29 * used to check whether the message is meant for us, or should be discarded (or an error can be sent if we receive a message 30 * not meant for us). Typically we use a String to provide maximum freedom. In that case, the receiver id can be coded as UTF-8 31 * or UTF-16.</li> 32 * <li>Frame 5. Message type id. Message type ids can be defined per type of simulation, and can be provided in different types. 33 * Examples are a String with a meaningful identification, or a short or an int with a message type number. For interoperability 34 * between different types of simulation, a String id with dot-notation (e.g., DSOL.1 for a simulator start message from DSOL or 35 * OTS.14 for a statistics message from OpenTrafficSim) would be preferred. In that case, the message type id can be coded as 36 * UTF-8 or UTF-16.</li> 37 * <li>Frame 6. Unique message number. The unique message number can have any type, but is typically sent as a long (64 bits), 38 * and is meant to confirm with a callback that the message has been received correctly. The number is unique for the sender, so 39 * not globally within the federation.</li> 40 * <li>Frame 7. Number of fields. The number of fields in the payload is indicated to be able to check the payload and to avoid 41 * reading past the end. The number of fields can be encoded using byte, short, int, or long. A 16-bit positive short (including 42 * zero) is the standard encoding.</li> 43 * <li>Frame 8-n. Payload, where each field has a 1-byte prefix denoting the type of field.</li> 44 * </ul> 45 * <p> 46 * Copyright (c) 2016-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br> 47 * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>. 48 * </p> 49 * initial version Mar 3, 2017 <br> 50 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a> 51 */ 52 public class Sim0MQMessage implements Serializable 53 { 54 /** */ 55 private static final long serialVersionUID = 20191017L; 56 57 /** version of the protocol, magic number. */ 58 protected static final String VERSION = "SIM02"; 59 60 /** 61 * the federation id can be provided in different types. Examples are two 64-bit longs indicating a UUID, or a String with a 62 * UUID number, a String with meaningful identification, or a byte, short or int with a simulation run number. 63 */ 64 private final Object federationId; 65 66 /** 67 * Indicates whether this message using little endian or big endian encoding. Big endian is encoded as true, and little 68 * endian as false. 69 */ 70 private final boolean bigEndian; 71 72 /** The sender id can be used to send back a message to the sender at some later time. */ 73 private final Object senderId; 74 75 /** 76 * The receiver id can be used to check whether the message is meant for us, or should be discarded (or an error can be sent 77 * if we receive a message not meant for us). 78 */ 79 private final Object receiverId; 80 81 /** 82 * Message type ids can be defined per type of simulation, and can be provided in different types. Examples are a String 83 * with a meaningful identification, or a short or an int with a message type number. 84 */ 85 private final Object messageTypeId; 86 87 /** 88 * The unique message id is meant to be used in, e.g., a callback that the message has been received correctly. Within this 89 * implementation, the messageId is unique for the sender, so not globally within the federation. 90 */ 91 private final Object messageId; 92 93 /** Payload as an Object array. */ 94 private final Object[] payload; 95 96 /** 97 * Encode the object array into a message. 98 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 99 * encoded as true, and little endian as false. 100 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 101 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 102 * simulation run number. 103 * @param senderId The sender id can be used to send back a message to the sender at some later time. 104 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 105 * error can be sent if we receive a message not meant for us). 106 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 107 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 108 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 109 * correctly. The number is unique for the sender, so not globally within the federation. 110 * @param payload Object[]; Payload as an Object array 111 * @throws Sim0MQException on unknown data type 112 * @throws NullPointerException when one of the parameters is null 113 */ 114 public Sim0MQMessage(final boolean bigEndian, final Object federationId, final Object senderId, final Object receiverId, 115 final Object messageTypeId, final Object messageId, final Object[] payload) 116 throws Sim0MQException, NullPointerException 117 { 118 Throw.whenNull(federationId, "federationId cannot be null"); 119 Throw.whenNull(senderId, "senderId cannot be null"); 120 Throw.whenNull(receiverId, "receiverId cannot be null"); 121 Throw.whenNull(messageTypeId, "messageTypeId cannot be null"); 122 Throw.whenNull(messageId, "messageId cannot be null"); 123 if (payload != null) 124 { 125 for (int i = 0; i < payload.length; i++) 126 { 127 Throw.whenNull(payload[i], "payload[" + i + "] cannot be null"); 128 } 129 } 130 131 this.bigEndian = bigEndian; 132 this.federationId = federationId; 133 this.senderId = senderId; 134 this.receiverId = receiverId; 135 this.messageTypeId = messageTypeId; 136 this.messageId = messageId; 137 this.payload = payload == null ? new Object[0] : payload; 138 } 139 140 /** 141 * Encode the object array into a message. <br> 142 * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br> 143 * 1 = endianness, boolean indicating the endianness for the message. True is Big Endian, false is Little Endian.<br> 144 * 2 = federation id, could be String, int, Object, ...<br> 145 * 3 = sender id, could be String, int, Object, ...<br> 146 * 4 = receiver id, could be String, int, Object, ...<br> 147 * 5 = message type id, could be String, int, Object, ...<br> 148 * 6 = message id, could be long, Object, String, ....<br> 149 * 7 = number of fields that follow, as a Number (byte, short, int, long).<br> 150 * 8-n = payload, where the number of fields was defined by message[7]. 151 * @param objectArray Object[]; Full message object array 152 * @param expectedNumberOfPayloadFields int; the expected number of fields in the message (field 8 and further) 153 * @param expectedMessageTypeId the expected message type id 154 * @throws Sim0MQException on unknown data type 155 * @throws NullPointerException when one of the parameters is null 156 */ 157 public Sim0MQMessage(final Object[] objectArray, final int expectedNumberOfPayloadFields, 158 final Object expectedMessageTypeId) throws Sim0MQException, NullPointerException 159 { 160 Throw.whenNull(objectArray, "objectArray cannot be null"); 161 Throw.when(objectArray.length != 8 + expectedNumberOfPayloadFields, Sim0MQException.class, 162 "FS1RequestStatusMessage should have " + expectedNumberOfPayloadFields + " fields but has " 163 + (objectArray.length - 8) + " fields"); 164 for (int i = 0; i < 8; i++) 165 { 166 Throw.whenNull(objectArray[i], "objectArray[" + i + "] cannot be null"); 167 } 168 Throw.when(!objectArray[0].equals(VERSION), Sim0MQException.class, "objectArray.version != " + VERSION); 169 Throw.when(!objectArray[5].equals(expectedMessageTypeId), Sim0MQException.class, 170 "objectArray.messageTypeId != " + expectedMessageTypeId); 171 Throw.when(!(objectArray[1] instanceof Boolean), Sim0MQException.class, "objectArray.bigEndian not boolean"); 172 this.bigEndian = ((Boolean) objectArray[1]).booleanValue(); 173 this.federationId = objectArray[2]; 174 this.senderId = objectArray[3]; 175 this.receiverId = objectArray[4]; 176 this.messageTypeId = objectArray[5]; 177 this.messageId = objectArray[6]; 178 Throw.when(!(objectArray[7] instanceof Number), Sim0MQException.class, "objectArray.numberOfFields not a number"); 179 this.payload = new Object[((Number) objectArray[7]).intValue()]; 180 for (int i = 0; i < this.payload.length; i++) 181 { 182 this.payload[i] = objectArray[i + 8]; 183 } 184 } 185 186 /** 187 * @return Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 01. Internally, the magic 188 * number is always coded as a UTF-8 String, so it always starts with a byte equal to 9. 189 */ 190 public final Object getMagicNumber() 191 { 192 return VERSION; 193 } 194 195 /** 196 * @return bigEndian 197 */ 198 public final boolean isBigEndian() 199 { 200 return this.bigEndian; 201 } 202 203 /** 204 * @return federationId 205 */ 206 public final Object getFederationId() 207 { 208 return this.federationId; 209 } 210 211 /** 212 * @return senderId 213 */ 214 public final Object getSenderId() 215 { 216 return this.senderId; 217 } 218 219 /** 220 * @return receiverId 221 */ 222 public final Object getReceiverId() 223 { 224 return this.receiverId; 225 } 226 227 /** 228 * @return messageTypeId 229 */ 230 public final Object getMessageTypeId() 231 { 232 return this.messageTypeId; 233 } 234 235 /** 236 * @return messageId 237 */ 238 public final Object getMessageId() 239 { 240 return this.messageId; 241 } 242 243 /** 244 * Create a Sim0MQ object array of the fields. 245 * @return Object[] a Sim0MQ object array of the fields 246 */ 247 public final Object[] createObjectArray() 248 { 249 Object[] result = new Object[8 + getNumberOfPayloadFields()]; 250 result[0] = Sim0MQMessage.VERSION; 251 result[1] = isBigEndian(); 252 result[2] = getFederationId(); 253 result[3] = getSenderId(); 254 result[4] = getReceiverId(); 255 result[5] = getMessageTypeId(); 256 result[6] = getMessageId(); 257 result[7] = getNumberOfPayloadFields(); 258 for (int i = 0; i < getNumberOfPayloadFields(); i++) 259 { 260 result[8 + i] = this.payload[i]; 261 } 262 return result; 263 } 264 265 /** 266 * Create a byte array of the fields. 267 * @return byte[] a Sim0MQ byte array of the content 268 * @throws Sim0MQException on unknown data type as part of the content 269 * @throws SerializationException when the byte array cannot be created, e.g. because the number of bytes does not match 270 */ 271 public final byte[] createByteArray() throws Sim0MQException, SerializationException 272 { 273 return Sim0MQMessage.encodeUTF8(this.bigEndian, getFederationId(), getSenderId(), getReceiverId(), getMessageTypeId(), 274 getMessageId(), this.payload); 275 } 276 277 /** 278 * Get the number of payload fields in the message. 279 * @return short; the number of payload fields in the message. 280 */ 281 public final short getNumberOfPayloadFields() 282 { 283 return (short) this.payload.length; 284 } 285 286 /** 287 * Check the consistency of a message from an Object[] that was received. 288 * @param fields Object[]; the fields in the message 289 * @param expectedPayloadFields the expected number of payload fields 290 * @param expectedMessageType the expected message type 291 * @param intendedReceiverId id of the intended receiver 292 * @throws Sim0MQException when errors in the message have been detected 293 */ 294 public static void check(final Object[] fields, final int expectedPayloadFields, final String expectedMessageType, 295 final Object intendedReceiverId) throws Sim0MQException 296 { 297 Throw.when(fields.length != expectedPayloadFields + 8, Sim0MQException.class, 298 "Message " + expectedMessageType + " does not contain the right number of fields. " + "Expected: " 299 + (expectedPayloadFields + 8) + ", Actual: " + fields.length); 300 301 for (int i = 0; i < fields.length; i++) 302 { 303 Object field = fields[i]; 304 if (field == null) 305 { 306 throw new Sim0MQException("Message " + expectedMessageType + " field " + i + " equals null"); 307 } 308 } 309 310 Throw.when(!expectedMessageType.equals(fields[5].toString()), Sim0MQException.class, 311 "Message type not right -- should have been " + expectedMessageType); 312 313 Throw.when(!fields[4].equals(intendedReceiverId), Sim0MQException.class, 314 "Receiver in message of type " + expectedMessageType + " not right. Should have been: " + intendedReceiverId); 315 316 Throw.when(!(fields[7] instanceof Number), Sim0MQException.class, 317 "Message " + expectedMessageType + " does not have a Number field[7] for the number of fields"); 318 Throw.when(((Number) fields[7]).longValue() != expectedPayloadFields, Sim0MQException.class, 319 "Message " + expectedMessageType + " does not contain the right number of payload fields in field[7]"); 320 } 321 322 /* ******************************************************************************************************* */ 323 /* ************************************ STATIC METHODS TO BUILD A MESSAGE ******************************** */ 324 /* ******************************************************************************************************* */ 325 326 /** 327 * Encode the object array into a message. Use UTF8 or UTF16 to code Strings. 328 * @param stringEncoding choice to use Use UTF8 or UTF16 to code Strings 329 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 330 * encoded as true, and little endian as false. 331 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 332 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 333 * simulation run number. 334 * @param senderId The sender id can be used to send back a message to the sender at some later time. 335 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 336 * error can be sent if we receive a message not meant for us). 337 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 338 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 339 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 340 * correctly. The number is unique for the sender, so not globally within the federation. 341 * @param content the objects to encode 342 * @return the zeroMQ message to send as a byte array 343 * @throws Sim0MQException on unknown data type 344 * @throws SerializationException on serialization problem 345 */ 346 @SuppressWarnings("checkstyle:parameternumber") 347 private static byte[] encode(final StringEncoding stringEncoding, final boolean bigEndian, final Object federationId, 348 final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId, 349 final Object... content) throws Sim0MQException, SerializationException 350 { 351 Object[] simulationContent = new Object[content.length + 8]; 352 simulationContent[0] = Sim0MQMessage.VERSION; 353 simulationContent[1] = bigEndian; 354 simulationContent[2] = federationId; 355 simulationContent[3] = senderId; 356 simulationContent[4] = receiverId; 357 simulationContent[5] = messageTypeId; 358 simulationContent[6] = messageId; 359 if (content.length < Short.MAX_VALUE) 360 { 361 simulationContent[7] = Short.valueOf((short) content.length); 362 } 363 else 364 { 365 simulationContent[7] = Integer.valueOf(content.length); 366 } 367 for (int i = 0; i < content.length; i++) 368 { 369 simulationContent[i + 8] = content[i]; 370 } 371 return stringEncoding.isUTF8() 372 ? TypedMessage.encodeUTF8(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent) 373 : TypedMessage.encodeUTF16(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent); 374 } 375 376 /** 377 * Encode the object array into a message. Use UTF8 or UTF16 to code Strings. 378 * @param identity the identity of the federate to which this is the reply 379 * @param stringEncoding choice to use Use UTF8 or UTF16 to code Strings 380 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 381 * encoded as true, and little endian as false. 382 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 383 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 384 * simulation run number. 385 * @param senderId The sender id can be used to send back a message to the sender at some later time. 386 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 387 * error can be sent if we receive a message not meant for us). 388 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 389 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 390 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 391 * correctly. The number is unique for the sender, so not globally within the federation. 392 * @param content the objects to encode 393 * @return the zeroMQ message to send as a byte array 394 * @throws Sim0MQException on unknown data type 395 * @throws SerializationException on serialization problem 396 */ 397 @SuppressWarnings("checkstyle:parameternumber") 398 private static byte[] encodeReply(final String identity, final StringEncoding stringEncoding, final boolean bigEndian, 399 final Object federationId, final Object senderId, final Object receiverId, final Object messageTypeId, 400 final Object messageId, final Object... content) throws Sim0MQException, SerializationException 401 { 402 Object[] simulationContent = new Object[content.length + 10]; 403 simulationContent[0] = identity; 404 simulationContent[1] = new byte[] {0}; 405 simulationContent[2] = Sim0MQMessage.VERSION; 406 simulationContent[3] = bigEndian; 407 simulationContent[4] = federationId; 408 simulationContent[5] = senderId; 409 simulationContent[6] = receiverId; 410 simulationContent[7] = messageTypeId; 411 simulationContent[8] = messageId; 412 if (content.length < Short.MAX_VALUE) 413 { 414 simulationContent[9] = Short.valueOf((short) content.length); 415 } 416 else 417 { 418 simulationContent[9] = Integer.valueOf(content.length); 419 } 420 for (int i = 0; i < content.length; i++) 421 { 422 simulationContent[i + 10] = content[i]; 423 } 424 return stringEncoding.isUTF8() 425 ? TypedMessage.encodeUTF8(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent) 426 : TypedMessage.encodeUTF16(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent); 427 } 428 429 /** 430 * Encode the object array into a message. Use UTF8 to code Strings. 431 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 432 * encoded as true, and little endian as false. 433 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 434 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 435 * simulation run number. 436 * @param senderId The sender id can be used to send back a message to the sender at some later time. 437 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 438 * error can be sent if we receive a message not meant for us). 439 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 440 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 441 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 442 * correctly. The number is unique for the sender, so not globally within the federation. 443 * @param content the objects to encode 444 * @return the zeroMQ message to send as a byte array 445 * @throws Sim0MQException on unknown data type 446 * @throws SerializationException on serialization problem 447 */ 448 public static byte[] encodeUTF8(final boolean bigEndian, final Object federationId, final Object senderId, 449 final Object receiverId, final Object messageTypeId, final Object messageId, final Object... content) 450 throws Sim0MQException, SerializationException 451 { 452 return encode(StringEncoding.UTF8, bigEndian, federationId, senderId, receiverId, messageTypeId, messageId, content); 453 } 454 455 /** 456 * Encode the object array into a message. Use UTF16 to code Strings. 457 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 458 * encoded as true, and little endian as false. 459 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 460 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 461 * simulation run number. 462 * @param senderId The sender id can be used to send back a message to the sender at some later time. 463 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 464 * error can be sent if we receive a message not meant for us). 465 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 466 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 467 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 468 * correctly. The number is unique for the sender, so not globally within the federation. 469 * @param content the objects to encode 470 * @return the zeroMQ message to send as a byte array 471 * @throws Sim0MQException on unknown data type 472 * @throws SerializationException on serialization problem 473 */ 474 public static byte[] encodeUTF16(final boolean bigEndian, final Object federationId, final Object senderId, 475 final Object receiverId, final Object messageTypeId, final Object messageId, final Object... content) 476 throws Sim0MQException, SerializationException 477 { 478 return encode(StringEncoding.UTF16, bigEndian, federationId, senderId, receiverId, messageTypeId, messageId, content); 479 } 480 481 /** 482 * Encode the object array into a reply message. Use UTF8 to code Strings. 483 * @param identity the identity of the federate to which this is the reply 484 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 485 * encoded as true, and little endian as false. 486 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 487 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 488 * simulation run number. 489 * @param senderId The sender id can be used to send back a message to the sender at some later time. 490 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 491 * error can be sent if we receive a message not meant for us). 492 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 493 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 494 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 495 * correctly. The number is unique for the sender, so not globally within the federation. 496 * @param content the objects to encode 497 * @return the zeroMQ message to send as a byte array 498 * @throws Sim0MQException on unknown data type 499 * @throws SerializationException on serialization problem 500 */ 501 @SuppressWarnings("checkstyle:parameternumber") 502 public static byte[] encodeReplyUTF8(final String identity, final boolean bigEndian, final Object federationId, 503 final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId, 504 final Object... content) throws Sim0MQException, SerializationException 505 { 506 return encodeReply(identity, StringEncoding.UTF8, bigEndian, federationId, senderId, receiverId, messageTypeId, 507 messageId, content); 508 } 509 510 /** 511 * Encode the object array into a reply message. Use UTF16 to code Strings. 512 * @param identity the identity of the federate to which this is the reply 513 * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is 514 * encoded as true, and little endian as false. 515 * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a 516 * UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a 517 * simulation run number. 518 * @param senderId The sender id can be used to send back a message to the sender at some later time. 519 * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an 520 * error can be sent if we receive a message not meant for us). 521 * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types. 522 * Examples are a String with a meaningful identification, or a short or an int with a message type number. 523 * @param messageId The unique message number is meant to confirm with a callback that the message has been received 524 * correctly. The number is unique for the sender, so not globally within the federation. 525 * @param content the objects to encode 526 * @return the zeroMQ message to send as a byte array 527 * @throws Sim0MQException on unknown data type 528 * @throws SerializationException on serialization problem 529 */ 530 @SuppressWarnings("checkstyle:parameternumber") 531 public static byte[] encodeReplyUTF16(final String identity, final boolean bigEndian, final Object federationId, 532 final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId, 533 final Object... content) throws Sim0MQException, SerializationException 534 { 535 return encodeReply(identity, StringEncoding.UTF16, bigEndian, federationId, senderId, receiverId, messageTypeId, 536 messageId, content); 537 } 538 539 /** 540 * Decode the message into an object array. Note that the message fields are coded as follows:<br> 541 * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br> 542 * 1 = endianness, boolean indicating the endianness for the message. True is Big Endian, false is Little Endian.<br> 543 * 2 = federation id, could be String, int, Object, ...<br> 544 * 3 = sender id, could be String, int, Object, ...<br> 545 * 4 = receiver id, could be String, int, Object, ...<br> 546 * 5 = message type id, could be String, int, Object, ...<br> 547 * 6 = message id, could be long, Object, String, ....<br> 548 * 7 = number of fields that follow, as a Number (byte, short, int, long).<br> 549 * 8-n = payload, where the number of fields was defined by message[7]. 550 * @param bytes the ZeroMQ byte array to decode 551 * @return Sim0MQMessage; a newly created Sim0MQMessage based on the decoded bytes 552 * @throws Sim0MQException on unknown data type 553 * @throws SerializationException when deserialization fails 554 */ 555 public static Sim0MQMessage decode(final byte[] bytes) throws Sim0MQException, SerializationException 556 { 557 Object[] array = decodeToArray(bytes); 558 return new Sim0MQMessage(array, array.length - 8, array[5]); 559 } 560 561 /** 562 * Decode the message into an object array. Note that the message fields are coded as follows:<br> 563 * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br> 564 * 1 = endianness, boolean indicatingthe enfianness for the message. True is Big Endian, false is Little Endian.<br> 565 * 2 = simulation run id, could be String, int, Object, ...<br> 566 * 3 = sender id, could be String, int, Object, ...<br> 567 * 4 = receiver id, could be String, int, Object, ...<br> 568 * 5 = message type id, could be String, int, Object, ...<br> 569 * 6 = message id, as a long.<br> 570 * 7 = number of fields that follow.<br> 571 * 8-n = payload, where the number of fields was defined by message[6]. 572 * @param bytes the ZeroMQ byte array to decode 573 * @return an array of objects of the right type 574 * @throws Sim0MQException on unknown data type 575 * @throws SerializationException when deserialization fails 576 */ 577 public static Object[] decodeToArray(final byte[] bytes) throws Sim0MQException, SerializationException 578 { 579 Throw.whenNull(bytes, "bytes should not be null"); 580 Throw.when(bytes.length < 12, Sim0MQException.class, "number of bytes in message < 12: " + bytes.length); 581 Throw.when(bytes[10] != 6 || bytes[11] < 0 || bytes[11] > 1, Sim0MQException.class, 582 "Bytes 10+11 in the byte array do not contain a boolean"); 583 boolean bigEndian = bytes[11] == 1; 584 Object[] objectArray = 585 TypedMessage.decodeToObjectDataTypes(bytes, bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN); 586 Throw.when(objectArray.length < 8, Sim0MQException.class, "number of message fields < 8: " + objectArray.length); 587 Throw.when(!(objectArray[0] instanceof String) || !(objectArray[0].equals(Sim0MQMessage.VERSION)), 588 Sim0MQException.class, "message[0] does not contain the right version number: " + objectArray[0]); 589 Throw.when(!(objectArray[1] instanceof Boolean), Sim0MQException.class, "message[1] is not a boolean"); 590 Throw.when(!(objectArray[7] instanceof Number), Sim0MQException.class, "message[7] is not a number"); 591 Throw.when(objectArray.length != ((Number) objectArray[7]).intValue() + 8, Sim0MQException.class, 592 "message[7] number of fields not matched by message structure"); 593 return objectArray; 594 } 595 596 /** 597 * Return a printable version of the message, e.g. for debugging purposes. 598 * @param message the message to parse 599 * @return a string representation of the message 600 */ 601 public static String print(final Object[] message) 602 { 603 StringBuffer s = new StringBuffer(); 604 s.append("0. magic number : " + message[0] + "\n"); 605 s.append("1. endianness : " + message[1] + "\n"); 606 s.append("2. simulation run id: " + message[2] + "\n"); 607 s.append("3. sender id : " + message[3] + "\n"); 608 s.append("4. receiver id : " + message[4] + "\n"); 609 s.append("5. message type id : " + message[5] + "\n"); 610 s.append("6. message id : " + message[6] + "\n"); 611 s.append("7. number of fields : " + message[7] + "\n"); 612 int nr = ((Number) message[7]).intValue(); 613 if (message.length != nr + 8) 614 { 615 s.append("Error - number of fields not matched by message structure"); 616 } 617 else 618 { 619 for (int i = 0; i < nr; i++) 620 { 621 s.append((8 + i) + ". message field : " + message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() 622 + ")\n"); 623 } 624 } 625 return s.toString(); 626 } 627 628 /** 629 * Return a printable line with the payload of the message, e.g. for debugging purposes. 630 * @param message the message to parse 631 * @return a string representation of the message 632 */ 633 public static String listPayload(final Object[] message) 634 { 635 StringBuffer s = new StringBuffer(); 636 s.append('|'); 637 int nr = ((Number) message[7]).intValue(); 638 if (message.length != nr + 8) 639 { 640 s.append("Error - number of fields not matched by message structure"); 641 } 642 else 643 { 644 for (int i = 0; i < nr; i++) 645 { 646 s.append(message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() + ") | "); 647 } 648 } 649 return s.toString(); 650 } 651 652 /** 653 * Builder for the Sim0MQMessage. Can string setters together, and call build() at the end to build the actual message. 654 * <p> 655 * Copyright (c) 2016-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. 656 * <br> 657 * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>. 658 * </p> 659 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $, 660 * initial version Apr 22, 2017 <br> 661 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a> 662 * @param <B> the actual inherited builder for the return types. 663 */ 664 public abstract static class Builder<B extends Sim0MQMessage.Builder<B>> 665 { 666 /** 667 * the Simulation run ids can be provided in different types. Examples are two 64-bit longs indicating a UUID, or a 668 * String with a UUID number, a String with meaningful identification, or a short or an int with a simulation run 669 * number. 670 */ 671 @SuppressWarnings("checkstyle:visibilitymodifier") 672 protected Object federationId; 673 674 /** The sender id can be used to send back a message to the sender at some later time. */ 675 @SuppressWarnings("checkstyle:visibilitymodifier") 676 protected Object senderId; 677 678 /** 679 * The receiver id can be used to check whether the message is meant for us, or should be discarded (or an error can be 680 * sent if we receive a message not meant for us). 681 */ 682 @SuppressWarnings("checkstyle:visibilitymodifier") 683 protected Object receiverId; 684 685 /** 686 * Message type ids can be defined per type of simulation, and can be provided in different types. Examples are a String 687 * with a meaningful identification, or a short or an int with a message type number. 688 */ 689 @SuppressWarnings("checkstyle:visibilitymodifier") 690 protected Object messageTypeId; 691 692 /** 693 * The unique message number is meant to confirm with a callback that the message has been received correctly. The 694 * number is unique for the sender, so not globally within the federation. 695 */ 696 @SuppressWarnings("checkstyle:visibilitymodifier") 697 protected Object messageId; 698 699 /** 700 * Empty constructor. 701 */ 702 public Builder() 703 { 704 // nothing to do. 705 } 706 707 /** 708 * @param newFederationId set federationId 709 * @return the original object for chaining 710 */ 711 @SuppressWarnings("unchecked") 712 public final B setSimulationRunId(final Object newFederationId) 713 { 714 this.federationId = newFederationId; 715 return (B) this; 716 } 717 718 /** 719 * @param newSenderId set senderId 720 * @return the original object for chaining 721 */ 722 @SuppressWarnings("unchecked") 723 public final B setSenderId(final Object newSenderId) 724 { 725 this.senderId = newSenderId; 726 return (B) this; 727 } 728 729 /** 730 * @param newReceiverId set receiverId 731 * @return the original object for chaining 732 */ 733 @SuppressWarnings("unchecked") 734 public final B setReceiverId(final Object newReceiverId) 735 { 736 this.receiverId = newReceiverId; 737 return (B) this; 738 } 739 740 /** 741 * @param newMessageTypeId set messageTypeId 742 * @return the original object for chaining 743 */ 744 @SuppressWarnings("unchecked") 745 protected final B setMessageTypeId(final Object newMessageTypeId) 746 { 747 this.messageTypeId = newMessageTypeId; 748 return (B) this; 749 } 750 751 /** 752 * @param newMessageId set messageId 753 * @return the original object for chaining 754 */ 755 @SuppressWarnings("unchecked") 756 public final B setMessageId(final Object newMessageId) 757 { 758 this.messageId = newMessageId; 759 return (B) this; 760 } 761 762 /** 763 * Build the object. 764 * @return the message object from the builder. 765 * @throws Sim0MQException on unknown data type 766 * @throws NullPointerException when one of the parameters is null 767 */ 768 public abstract Sim0MQMessage build() throws Sim0MQException, NullPointerException; 769 770 } 771 }