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