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