1 package org.sim0mq.message;
2
3 import java.io.Serializable;
4
5 import org.djutils.exceptions.Throw;
6 import org.djutils.serialization.Endianness;
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., 03.</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 = "SIM03";
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., 03. 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 ? Endianness.BIG_ENDIAN : Endianness.LITTLE_ENDIAN, simulationContent)
373 : TypedMessage.encodeUTF16(bigEndian ? Endianness.BIG_ENDIAN : Endianness.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 ? Endianness.BIG_ENDIAN : Endianness.LITTLE_ENDIAN, simulationContent)
426 : TypedMessage.encodeUTF16(bigEndian ? Endianness.BIG_ENDIAN : Endianness.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 Throw.when(bytes[0] != 9, Sim0MQException.class, "Byte 0 of message is not equal to 9");
584 int magicLength = Endianness.BIG_ENDIAN.decodeInt(bytes, 1);
585 Throw.when(bytes[5 + magicLength] != 6, Sim0MQException.class, "Byte 1 frame 1 of message is not equal to 6");
586 var endianness = bytes[6 + magicLength] == 1 ? Endianness.BIG_ENDIAN : Endianness.LITTLE_ENDIAN;
587 Object[] objectArray = TypedMessage.decodeToObjectDataTypes(endianness, bytes);
588 Throw.when(objectArray.length < 8, Sim0MQException.class, "number of message fields < 8: " + objectArray.length);
589 Throw.when(!(objectArray[0] instanceof String) || !(objectArray[0].equals(Sim0MQMessage.VERSION)),
590 Sim0MQException.class, "message[0] does not contain the right version number: " + objectArray[0]);
591 Throw.when(!(objectArray[1] instanceof Boolean), Sim0MQException.class, "message[1] is not a boolean");
592 Throw.when(!(objectArray[7] instanceof Number), Sim0MQException.class, "message[7] is not a number");
593 Throw.when(objectArray.length != ((Number) objectArray[7]).intValue() + 8, Sim0MQException.class,
594 "message[7] number of fields not matched by message structure");
595 return objectArray;
596 }
597
598 /**
599 * Return a printable version of the message, e.g. for debugging purposes.
600 * @param message the message to parse
601 * @return a string representation of the message
602 */
603 public static String print(final Object[] message)
604 {
605 StringBuffer s = new StringBuffer();
606 s.append("0. magic number : " + message[0] + "\n");
607 s.append("1. endianness : " + message[1] + "\n");
608 s.append("2. simulation run id: " + message[2] + "\n");
609 s.append("3. sender id : " + message[3] + "\n");
610 s.append("4. receiver id : " + message[4] + "\n");
611 s.append("5. message type id : " + message[5] + "\n");
612 s.append("6. message id : " + message[6] + "\n");
613 s.append("7. number of fields : " + message[7] + "\n");
614 int nr = ((Number) message[7]).intValue();
615 if (message.length != nr + 8)
616 {
617 s.append("Error - number of fields not matched by message structure");
618 }
619 else
620 {
621 for (int i = 0; i < nr; i++)
622 {
623 s.append((8 + i) + ". message field : " + message[8 + i] + " (" + message[8 + i].getClass().getSimpleName()
624 + ")\n");
625 }
626 }
627 return s.toString();
628 }
629
630 /**
631 * Return a printable line with the payload of the message, e.g. for debugging purposes.
632 * @param message the message to parse
633 * @return a string representation of the message
634 */
635 public static String listPayload(final Object[] message)
636 {
637 StringBuffer s = new StringBuffer();
638 s.append('|');
639 int nr = ((Number) message[7]).intValue();
640 if (message.length != nr + 8)
641 {
642 s.append("Error - number of fields not matched by message structure");
643 }
644 else
645 {
646 for (int i = 0; i < nr; i++)
647 {
648 s.append(message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() + ") | ");
649 }
650 }
651 return s.toString();
652 }
653
654 /**
655 * Builder for the Sim0MQMessage. Can string setters together, and call build() at the end to build the actual message.
656 * <p>
657 * Copyright (c) 2016-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
658 * <br>
659 * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>.
660 * </p>
661 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
662 * initial version Apr 22, 2017 <br>
663 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
664 * @param <B> the actual inherited builder for the return types.
665 */
666 public abstract static class Builder<B extends Sim0MQMessage.Builder<B>>
667 {
668 /**
669 * the Simulation run ids can be provided in different types. Examples are two 64-bit longs indicating a UUID, or a
670 * String with a UUID number, a String with meaningful identification, or a short or an int with a simulation run
671 * number.
672 */
673 @SuppressWarnings("checkstyle:visibilitymodifier")
674 protected Object federationId;
675
676 /** The sender id can be used to send back a message to the sender at some later time. */
677 @SuppressWarnings("checkstyle:visibilitymodifier")
678 protected Object senderId;
679
680 /**
681 * The receiver id can be used to check whether the message is meant for us, or should be discarded (or an error can be
682 * sent if we receive a message not meant for us).
683 */
684 @SuppressWarnings("checkstyle:visibilitymodifier")
685 protected Object receiverId;
686
687 /**
688 * Message type ids can be defined per type of simulation, and can be provided in different types. Examples are a String
689 * with a meaningful identification, or a short or an int with a message type number.
690 */
691 @SuppressWarnings("checkstyle:visibilitymodifier")
692 protected Object messageTypeId;
693
694 /**
695 * The unique message number is meant to confirm with a callback that the message has been received correctly. The
696 * number is unique for the sender, so not globally within the federation.
697 */
698 @SuppressWarnings("checkstyle:visibilitymodifier")
699 protected Object messageId;
700
701 /**
702 * Empty constructor.
703 */
704 public Builder()
705 {
706 // nothing to do.
707 }
708
709 /**
710 * @param newFederationId set federationId
711 * @return the original object for chaining
712 */
713 @SuppressWarnings("unchecked")
714 public final B setSimulationRunId(final Object newFederationId)
715 {
716 this.federationId = newFederationId;
717 return (B) this;
718 }
719
720 /**
721 * @param newSenderId set senderId
722 * @return the original object for chaining
723 */
724 @SuppressWarnings("unchecked")
725 public final B setSenderId(final Object newSenderId)
726 {
727 this.senderId = newSenderId;
728 return (B) this;
729 }
730
731 /**
732 * @param newReceiverId set receiverId
733 * @return the original object for chaining
734 */
735 @SuppressWarnings("unchecked")
736 public final B setReceiverId(final Object newReceiverId)
737 {
738 this.receiverId = newReceiverId;
739 return (B) this;
740 }
741
742 /**
743 * @param newMessageTypeId set messageTypeId
744 * @return the original object for chaining
745 */
746 @SuppressWarnings("unchecked")
747 protected final B setMessageTypeId(final Object newMessageTypeId)
748 {
749 this.messageTypeId = newMessageTypeId;
750 return (B) this;
751 }
752
753 /**
754 * @param newMessageId set messageId
755 * @return the original object for chaining
756 */
757 @SuppressWarnings("unchecked")
758 public final B setMessageId(final Object newMessageId)
759 {
760 this.messageId = newMessageId;
761 return (B) this;
762 }
763
764 /**
765 * Build the object.
766 * @return the message object from the builder.
767 * @throws Sim0MQException on unknown data type
768 * @throws NullPointerException when one of the parameters is null
769 */
770 public abstract Sim0MQMessage build() throws Sim0MQException, NullPointerException;
771
772 }
773 }