7f63454ea7fd83b164eba6083206d6e7bf563731
[public/netxms.git] / src / java / netxms-base / src / main / java / org / netxms / base / NXCPMessage.java
1 /**
2 * NetXMS - open source network management system
3 * Copyright (C) 2003-2013 Victor Kirhenshtein
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 package org.netxms.base;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.DataOutputStream;
24 import java.io.IOException;
25 import java.net.InetAddress;
26 import java.security.GeneralSecurityException;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.UUID;
31 import java.util.zip.CRC32;
32
33 public class NXCPMessage
34 {
35 public static final int HEADER_SIZE = 16;
36 public static final int ENCRYPTION_HEADER_SIZE = 8;
37
38 // Message flags
39 public static final int MF_BINARY = 0x0001;
40 public static final int MF_END_OF_FILE = 0x0002;
41 public static final int MF_DONT_ENCRYPT = 0x0004;
42 public static final int MF_END_OF_SEQUENCE = 0x0008;
43 public static final int MF_REVERSE_ORDER = 0x0010;
44 public static final int MF_CONTROL = 0x0020;
45
46 private int messageCode;
47 private int messageFlags;
48 private long messageId;
49 private Map<Long, NXCPVariable> variableMap = new HashMap<Long, NXCPVariable>(0);
50 private long timestamp;
51 private byte[] binaryData = null;
52 private long controlData = 0;
53
54 /**
55 * @param msgCode
56 */
57 public NXCPMessage(final int msgCode)
58 {
59 this.messageCode = msgCode;
60 messageId = 0L;
61 messageFlags = 0;
62 }
63
64 /**
65 * @param msgCode
66 * @param msgId
67 */
68 public NXCPMessage(final int msgCode, final long msgId)
69 {
70 this.messageCode = msgCode;
71 this.messageId = msgId;
72 messageFlags = 0;
73 }
74
75 /**
76 * Create NXCPMessage from binary NXCP message
77 *
78 * @param nxcpMessage binary NXCP message
79 * @throws java.io.IOException
80 * @throws GeneralSecurityException
81 * @throws NXCPException
82 */
83 public NXCPMessage(final byte[] nxcpMessage, EncryptionContext ectx) throws IOException, NXCPException
84 {
85 final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(nxcpMessage);
86 final NXCPDataInputStream inputStream = new NXCPDataInputStream(byteArrayInputStream);
87
88 messageCode = inputStream.readUnsignedShort();
89 if (messageCode == NXCPCodes.CMD_ENCRYPTED_MESSAGE)
90 {
91 if (ectx == null)
92 throw new NXCPException(NXCPException.DECRYPTION_ERROR);
93
94 int padding = inputStream.readByte();
95 inputStream.skipBytes(1);
96 int msgLen = inputStream.readInt();
97 byte[] payload;
98 try
99 {
100 payload = ectx.decryptMessage(inputStream, msgLen - padding - ENCRYPTION_HEADER_SIZE);
101 }
102 catch(GeneralSecurityException e)
103 {
104 throw new NXCPException(NXCPException.DECRYPTION_ERROR, e);
105 }
106
107 final ByteArrayInputStream payloadByteArrayInputStream = new ByteArrayInputStream(payload);
108 final NXCPDataInputStream payloadInputStream = new NXCPDataInputStream(payloadByteArrayInputStream);
109
110 CRC32 crc32 = new CRC32();
111 crc32.update(payload, 8, payload.length - 8);
112 if (payloadInputStream.readUnsignedInt() != crc32.getValue())
113 throw new NXCPException(NXCPException.DECRYPTION_ERROR);
114
115 payloadInputStream.skip(4);
116 messageCode = payloadInputStream.readUnsignedShort();
117 createFromStream(payloadInputStream, payloadByteArrayInputStream);
118 }
119 else
120 {
121 createFromStream(inputStream, byteArrayInputStream);
122 }
123 }
124
125 /**
126 * Create NXCPMessage from prepared input byte stream
127 *
128 * @param nxcpMessage binary NXCP message
129 * @throws java.io.IOException
130 */
131 private void createFromStream(NXCPDataInputStream inputStream, ByteArrayInputStream byteArrayInputStream) throws IOException
132 {
133 messageFlags = inputStream.readUnsignedShort();
134 inputStream.skipBytes(4); // Message size
135 messageId = (long)inputStream.readInt();
136
137 if ((messageFlags & MF_BINARY) == MF_BINARY)
138 {
139 final int size = inputStream.readInt();
140 binaryData = new byte[size];
141 inputStream.readFully(binaryData);
142 }
143 else if ((messageFlags & MF_CONTROL) == MF_CONTROL)
144 {
145 controlData = inputStream.readUnsignedInt();
146 }
147 else
148 {
149 final int numVars = inputStream.readInt();
150
151 for(int i = 0; i < numVars; i++)
152 {
153 byteArrayInputStream.mark(byteArrayInputStream.available());
154
155 // Read first 8 bytes - any DF (data field) is at least 8 bytes long
156 byte[] df = new byte[16];
157 inputStream.readFully(df, 0, 8);
158
159 switch(df[4])
160 {
161 case NXCPVariable.TYPE_INT16:
162 break;
163 case NXCPVariable.TYPE_FLOAT: // all these types requires additional 8 bytes
164 case NXCPVariable.TYPE_INTEGER:
165 case NXCPVariable.TYPE_INT64:
166 inputStream.readFully(df, 8, 8);
167 break;
168 case NXCPVariable.TYPE_STRING: // all these types has 4-byte length field followed by actual content
169 case NXCPVariable.TYPE_BINARY:
170 int size = inputStream.readInt();
171 byteArrayInputStream.reset();
172 df = new byte[size + 12];
173 inputStream.readFully(df);
174
175 // Each df aligned to 8-bytes boundary
176 final int rem = (size + 12) % 8;
177 if (rem != 0)
178 {
179 inputStream.skipBytes(8 - rem);
180 }
181 break;
182 }
183
184 final NXCPVariable variable = new NXCPVariable(df);
185 variableMap.put(variable.getVariableId(), variable);
186 }
187 }
188 }
189
190 /**
191 * @return the msgCode
192 */
193 public int getMessageCode()
194 {
195 return messageCode;
196 }
197
198 /**
199 * @param msgCode the msgCode to set
200 */
201 public void setMessageCode(final int msgCode)
202 {
203 this.messageCode = msgCode;
204 }
205
206 /**
207 * @return the msgId
208 */
209 public long getMessageId()
210 {
211 return messageId;
212 }
213
214 /**
215 * @param msgId the msgId to set
216 */
217 public void setMessageId(final long msgId)
218 {
219 this.messageId = msgId;
220 }
221
222 /**
223 * @return the timestamp
224 */
225 public long getTimestamp()
226 {
227 return timestamp;
228 }
229
230 /**
231 * @param timestamp the timestamp to set
232 */
233 public void setTimestamp(final long timestamp)
234 {
235 this.timestamp = timestamp;
236 }
237
238
239 /**
240 * @param varId variable Id to find
241 */
242 public NXCPVariable findVariable(final long varId)
243 {
244 return variableMap.get(varId);
245 }
246
247
248 //
249 // Getters/Setters for variables
250 //
251
252 public void setVariable(final NXCPVariable variable)
253 {
254 variableMap.put(variable.getVariableId(), variable);
255 }
256
257 public void setVariable(final long varId, final byte[] value)
258 {
259 setVariable(new NXCPVariable(varId, value));
260 }
261
262 public void setVariable(final long varId, final long[] value)
263 {
264 setVariable(new NXCPVariable(varId, value));
265 }
266
267 public void setVariable(final long varId, final Long[] value)
268 {
269 setVariable(new NXCPVariable(varId, value));
270 }
271
272 public void setVariable(final long varId, final String value)
273 {
274 setVariable(new NXCPVariable(varId, value));
275 }
276
277 public void setVariable(final long varId, final Double value)
278 {
279 setVariable(new NXCPVariable(varId, value));
280 }
281
282 public void setVariable(final long varId, final InetAddress value)
283 {
284 setVariable(new NXCPVariable(varId, value));
285 }
286
287 public void setVariable(final long varId, final UUID value)
288 {
289 setVariable(new NXCPVariable(varId, value));
290 }
291
292 public void setVariableInt64(final long varId, final long value)
293 {
294 setVariable(new NXCPVariable(varId, NXCPVariable.TYPE_INT64, value));
295 }
296
297 public void setVariableInt32(final long varId, final int value)
298 {
299 setVariable(new NXCPVariable(varId, NXCPVariable.TYPE_INTEGER, (long)value));
300 }
301
302 public void setVariableInt16(final long varId, final int value)
303 {
304 setVariable(new NXCPVariable(varId, NXCPVariable.TYPE_INT16, (long)value));
305 }
306
307 public byte[] getVariableAsBinary(final long varId)
308 {
309 final NXCPVariable var = findVariable(varId);
310 return (var != null) ? var.getAsBinary() : null;
311 }
312
313 public String getVariableAsString(final long varId)
314 {
315 final NXCPVariable var = findVariable(varId);
316 return (var != null) ? var.getAsString() : "";
317 }
318
319 public Double getVariableAsReal(final long varId)
320 {
321 final NXCPVariable var = findVariable(varId);
322 return (var != null) ? var.getAsReal() : 0;
323 }
324
325 public int getVariableAsInteger(final long varId)
326 {
327 final NXCPVariable var = findVariable(varId);
328 return (var != null) ? var.getAsInteger().intValue() : 0;
329 }
330
331 public long getVariableAsInt64(final long varId)
332 {
333 final NXCPVariable var = findVariable(varId);
334 return (var != null) ? var.getAsInteger() : 0;
335 }
336
337 public InetAddress getVariableAsInetAddress(final long varId)
338 {
339 final NXCPVariable var = findVariable(varId);
340 return (var != null) ? var.getAsInetAddress() : null;
341 }
342
343 public UUID getVariableAsUUID(final long varId)
344 {
345 final NXCPVariable var = findVariable(varId);
346 return (var != null) ? var.getAsUUID() : null;
347 }
348
349 public long[] getVariableAsUInt32Array(final long varId)
350 {
351 final NXCPVariable var = findVariable(varId);
352 return (var != null) ? var.getAsUInt32Array() : null;
353 }
354
355 public Long[] getVariableAsUInt32ArrayEx(final long varId)
356 {
357 final NXCPVariable var = findVariable(varId);
358 return (var != null) ? var.getAsUInt32ArrayEx() : null;
359 }
360
361 public boolean getVariableAsBoolean(final long varId)
362 {
363 final NXCPVariable var = findVariable(varId);
364 return (var != null) ? (var.getAsInteger() != 0) : false;
365 }
366
367 public Date getVariableAsDate(final long varId)
368 {
369 final NXCPVariable var = findVariable(varId);
370 return (var != null) ? new Date(var.getAsInteger() * 1000) : null;
371 }
372
373 /**
374 * Create binary NXCP message
375 *
376 * @return byte stream ready to send
377 */
378 public byte[] createNXCPMessage() throws IOException
379 {
380 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
381 DataOutputStream outputStream = new DataOutputStream(byteStream);
382
383 if ((messageFlags & MF_CONTROL) == MF_CONTROL)
384 {
385 outputStream.writeShort(messageCode);
386 outputStream.writeShort(messageFlags);
387 outputStream.writeInt(HEADER_SIZE); // Size
388 outputStream.writeInt((int)messageId);
389 outputStream.writeInt((int)controlData);
390 }
391 else if ((messageFlags & MF_BINARY) == MF_BINARY)
392 {
393 outputStream.writeShort(messageCode); // wCode
394 outputStream.writeShort(messageFlags); // wFlags
395 final int length = binaryData.length;
396 final int padding = (8 - ((length + HEADER_SIZE) % 8)) & 7;
397 final int packetSize = length + HEADER_SIZE + padding;
398 outputStream.writeInt(packetSize); // dwSize (padded to 8 bytes boundaries)
399 outputStream.writeInt((int)messageId); // dwId
400 outputStream.writeInt(length); // dwNumVars, here used for real size of the payload (w/o headers and padding)
401 outputStream.write(binaryData);
402 for (int i = 0; i < padding; i++)
403 outputStream.writeByte(0);
404 }
405 else
406 {
407 // Create byte array with all variables
408 for(final NXCPVariable nxcpVariable: variableMap.values())
409 {
410 final byte[] field = nxcpVariable.createNXCPDataField();
411 outputStream.write(field);
412 }
413 final byte[] payload = byteStream.toByteArray();
414
415 // Create message header in new byte stream and add payload
416 byteStream = new ByteArrayOutputStream();
417 //noinspection IOResourceOpenedButNotSafelyClosed
418 outputStream = new DataOutputStream(byteStream);
419 outputStream.writeShort(messageCode);
420 outputStream.writeShort(messageFlags);
421 outputStream.writeInt(payload.length + HEADER_SIZE); // Size
422 outputStream.writeInt((int)messageId);
423 outputStream.writeInt(variableMap.size());
424 outputStream.write(payload);
425 }
426
427 return byteStream.toByteArray();
428 }
429
430 /**
431 * Get data of raw message. Will return null if message is not a raw message.
432 *
433 * @return Binary data of raw message
434 */
435 public byte[] getBinaryData()
436 {
437 return binaryData;
438 }
439
440 /**
441 * Set data for raw message.
442 *
443 * @param binaryData
444 */
445 public void setBinaryData(final byte[] binaryData)
446 {
447 this.binaryData = binaryData;
448 }
449
450 /**
451 * Return true if message is a raw message
452 * @return raw message flag
453 */
454 public boolean isBinaryMessage()
455 {
456 return (messageFlags & MF_BINARY) == MF_BINARY;
457 }
458
459 /**
460 * Set or clear raw (binary) message flag
461 *
462 * @param isControl
463 * true to set control message flag
464 */
465 public void setBinaryMessage(boolean isRaw)
466 {
467 if (isRaw)
468 {
469 messageFlags |= MF_BINARY;
470 }
471 else
472 {
473 messageFlags &= ~MF_BINARY;
474 }
475 }
476
477 /**
478 * Return true if message is a control message
479 * @return control message flag
480 */
481 public boolean isControlMessage()
482 {
483 return (messageFlags & MF_CONTROL) == MF_CONTROL;
484 }
485
486 /**
487 * Set or clear control message flag
488 *
489 * @param isControl true to set control message flag
490 */
491 public void setControl(boolean isControl)
492 {
493 if (isControl)
494 messageFlags |= MF_CONTROL;
495 else
496 messageFlags &= ~MF_CONTROL;
497 }
498
499 /**
500 * Return true if message has "end of file" flag set
501 * @return "end of file" flag
502 */
503 public boolean isEndOfFile()
504 {
505 return (messageFlags & MF_END_OF_FILE) == MF_END_OF_FILE;
506 }
507
508 /**
509 * Set end of file message flag
510 *
511 * @param isEOF true to set end of file message flag
512 */
513 public void setEndOfFile(boolean isEOF)
514 {
515 if (isEOF)
516 messageFlags |= MF_END_OF_FILE;
517 else
518 messageFlags &= ~MF_END_OF_FILE;
519 }
520
521 /**
522 * Return true if message has "end of sequence" flag set
523 * @return "end of file" flag
524 */
525 public boolean isEndOfSequence()
526 {
527 return (messageFlags & MF_END_OF_SEQUENCE) == MF_END_OF_SEQUENCE;
528 }
529
530 /**
531 * Set end of sequence message flag
532 *
533 * @param isEOS true to set end of sequence message flag
534 */
535 public void setEndOfSequence(boolean isEOS)
536 {
537 if (isEOS)
538 messageFlags |= MF_END_OF_SEQUENCE;
539 else
540 messageFlags &= ~MF_END_OF_SEQUENCE;
541 }
542
543 /**
544 * Return true if message has "don't encrypt" flag set
545 * @return "don't encrypr" flag
546 */
547 public boolean isEncryptionDisabled()
548 {
549 return (messageFlags & MF_DONT_ENCRYPT) == MF_DONT_ENCRYPT;
550 }
551
552 /**
553 * Set "don't encrypt" message flag
554 *
555 * @param disabled true to set "don't encrypt" message flag
556 */
557 public void setEncryptionDisabled(boolean disabled)
558 {
559 if (disabled)
560 messageFlags |= MF_DONT_ENCRYPT;
561 else
562 messageFlags &= ~MF_DONT_ENCRYPT;
563 }
564
565 /**
566 * @return the controlData
567 */
568 public long getControlData()
569 {
570 return controlData;
571 }
572
573 /**
574 * @param controlData the controlData to set
575 */
576 public void setControlData(long controlData)
577 {
578 this.controlData = controlData;
579 }
580 }