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