View Javadoc
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-2017 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 }