c6edd62696da84dcc9a2a87e51d95ac56f2d81c3
[public/netxms.git] / src / java / client / mobile-agent / src / main / java / org / netxms / mobile / agent / Session.java
1 /**
2 * NetXMS - open source network management system
3 * Copyright (C) 2003-2012 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.mobile.agent;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.net.InetAddress;
25 import java.net.Socket;
26 import java.net.UnknownHostException;
27 import java.security.GeneralSecurityException;
28 import java.util.concurrent.atomic.AtomicLong;
29 import org.netxms.base.EncryptionContext;
30 import org.netxms.base.GeoLocation;
31 import org.netxms.base.Logger;
32 import org.netxms.base.NXCPCodes;
33 import org.netxms.base.NXCPException;
34 import org.netxms.base.NXCPMessage;
35 import org.netxms.base.NXCPMessageReceiver;
36 import org.netxms.base.NXCPMsgWaitQueue;
37 import org.netxms.base.NXCommon;
38 import org.netxms.mobile.agent.constants.RCC;
39
40 /**
41 * Communication session with NetXMS server.
42 */
43 public class Session
44 {
45 // Various public constants
46 public static final int DEFAULT_CONN_PORT = 4747;
47 public static final int PROTOCOL_VERSION = 1;
48
49 // Connection-related attributes
50 private String connAddress;
51 private int connPort;
52 private String connDeviceId;
53 private String connLoginName;
54 private String connPassword;
55 private boolean connUseEncryption;
56
57 // Internal communication data
58 private Socket connSocket = null;
59 private NXCPMsgWaitQueue msgWaitQueue = null;
60 private ReceiverThread recvThread = null;
61 private AtomicLong requestId = new AtomicLong(1);
62 private boolean isConnected = false;
63 private EncryptionContext encryptionContext = null;
64
65 // Communication parameters
66 private int defaultRecvBufferSize = 65536; // Default is 64KB
67 private int maxRecvBufferSize = 262144; // Max is 256KB
68 private int commandTimeout = 30000; // Default is 30 sec
69
70 /**
71 * Setup encryption
72 *
73 * @param msg CMD_REQUEST_SESSION_KEY message
74 * @throws IOException
75 * @throws MobileAgentException
76 */
77 private void setupEncryption(NXCPMessage msg) throws IOException, MobileAgentException
78 {
79 final NXCPMessage response = new NXCPMessage(NXCPCodes.CMD_SESSION_KEY, msg.getMessageId());
80 response.setEncryptionDisabled(true);
81
82 try
83 {
84 encryptionContext = EncryptionContext.createInstance(msg);
85 response.setField(NXCPCodes.VID_SESSION_KEY, encryptionContext.getEncryptedSessionKey(msg));
86 response.setField(NXCPCodes.VID_SESSION_IV, encryptionContext.getEncryptedIv(msg));
87 response.setFieldInt16(NXCPCodes.VID_CIPHER, encryptionContext.getCipher());
88 response.setFieldInt16(NXCPCodes.VID_KEY_LENGTH, encryptionContext.getKeyLength());
89 response.setFieldInt16(NXCPCodes.VID_IV_LENGTH, encryptionContext.getIvLength());
90 response.setFieldInt32(NXCPCodes.VID_RCC, RCC.SUCCESS);
91 Logger.debug("Session.setupEncryption", "Cipher selected: " + EncryptionContext.getCipherName(encryptionContext.getCipher()));
92 }
93 catch(Exception e)
94 {
95 response.setFieldInt32(NXCPCodes.VID_RCC, RCC.NO_CIPHERS);
96 }
97
98 sendMessage(response);
99 }
100
101 /**
102 * Receiver thread for NXCSession
103 */
104 private class ReceiverThread extends Thread
105 {
106 ReceiverThread()
107 {
108 setDaemon(true);
109 start();
110 }
111
112 @Override
113 public void run()
114 {
115 final NXCPMessageReceiver receiver = new NXCPMessageReceiver(defaultRecvBufferSize, maxRecvBufferSize);
116 InputStream in;
117
118 try
119 {
120 in = connSocket.getInputStream();
121 }
122 catch(IOException e)
123 {
124 return; // Stop receiver thread if input stream cannot be obtained
125 }
126
127 while(connSocket.isConnected())
128 {
129 try
130 {
131 final NXCPMessage msg = receiver.receiveMessage(in, encryptionContext);
132 switch(msg.getMessageCode())
133 {
134 case NXCPCodes.CMD_REQUEST_SESSION_KEY:
135 setupEncryption(msg);
136 break;
137 default:
138 msgWaitQueue.putMessage(msg);
139 break;
140 }
141 }
142 catch(IOException e)
143 {
144 break; // Stop on read errors
145 }
146 catch(NXCPException e)
147 {
148 if (e.getErrorCode() == NXCPException.SESSION_CLOSED)
149 break;
150 }
151 catch(MobileAgentException e)
152 {
153 if (e.getErrorCode() == RCC.ENCRYPTION_ERROR)
154 break;
155 }
156 }
157 }
158 }
159
160 /**
161 * @param address
162 * @param deviceId
163 * @param loginName
164 * @param password
165 */
166 public Session(String address, String deviceId, String loginName, String password)
167 {
168 this.connAddress = address;
169 this.connPort = DEFAULT_CONN_PORT;
170 this.connDeviceId = deviceId;
171 this.connLoginName = loginName;
172 this.connPassword = password;
173 this.connUseEncryption = false;
174 }
175
176 /**
177 * @param address
178 * @param port
179 * @param deviceId
180 * @param loginName
181 * @param password
182 */
183 public Session(String address, int port, String deviceId, String loginName, String password)
184 {
185 this.connAddress = address;
186 this.connPort = port;
187 this.connDeviceId = deviceId;
188 this.connLoginName = loginName;
189 this.connPassword = password;
190 this.connUseEncryption = false;
191 }
192
193 /**
194 * @param address
195 * @param port
196 * @param deviceId
197 * @param loginName
198 * @param password
199 * @param useEncryption
200 */
201 public Session(String address, int port, String deviceId, String loginName, String password, boolean useEncryption)
202 {
203 this.connAddress = address;
204 this.connPort = port;
205 this.connDeviceId = deviceId;
206 this.connLoginName = loginName;
207 this.connPassword = password;
208 this.connUseEncryption = useEncryption;
209 }
210
211 /* (non-Javadoc)
212 * @see java.lang.Object#finalize()
213 */
214 @Override
215 protected void finalize()
216 {
217 disconnect();
218 }
219
220 /**
221 * Send message to server
222 *
223 * @param msg
224 * Message to sent
225 * @throws IOException in case of socket communication failure
226 * @throws MobileAgentException in case of encryption error
227 */
228 public synchronized void sendMessage(final NXCPMessage msg) throws IOException, MobileAgentException
229 {
230 if (connSocket == null)
231 {
232 throw new IllegalStateException("Not connected to the server. Did you forgot to call connect() first?");
233 }
234 final OutputStream outputStream = connSocket.getOutputStream();
235 byte[] message;
236 if ((encryptionContext != null) && !msg.isEncryptionDisabled())
237 {
238 try
239 {
240 message = encryptionContext.encryptMessage(msg);
241 }
242 catch(GeneralSecurityException e)
243 {
244 throw new MobileAgentException(RCC.ENCRYPTION_ERROR);
245 }
246 }
247 else
248 {
249 message = msg.createNXCPMessage();
250 }
251 outputStream.write(message);
252 }
253
254 /**
255 * Wait for message with specific code and id.
256 *
257 * @param code
258 * @param id
259 * @param timeout
260 * @return Message object
261 * @throws MobileAgentException
262 */
263 public NXCPMessage waitForMessage(final int code, final long id, final int timeout) throws MobileAgentException
264 {
265 final NXCPMessage msg = msgWaitQueue.waitForMessage(code, id, timeout);
266 if (msg == null)
267 throw new MobileAgentException(RCC.TIMEOUT);
268 return msg;
269 }
270
271 /**
272 * Wait for message with specific code and id.
273 *
274 * @param code
275 * @param id
276 * @return Message object
277 * @throws MobileAgentException
278 */
279 public NXCPMessage waitForMessage(final int code, final long id) throws MobileAgentException
280 {
281 final NXCPMessage msg = msgWaitQueue.waitForMessage(code, id);
282 if (msg == null)
283 throw new MobileAgentException(RCC.TIMEOUT);
284 return msg;
285 }
286
287 /**
288 * Wait for CMD_REQUEST_COMPLETED message with given id using default timeout
289 *
290 * @param id
291 * @return
292 * @throws MobileAgentException
293 */
294 public NXCPMessage waitForRCC(final long id) throws MobileAgentException
295 {
296 return waitForRCC(id, msgWaitQueue.getDefaultTimeout());
297 }
298
299 /**
300 * @param id
301 * @param timeout
302 * @return
303 * @throws NXCException
304 */
305 public NXCPMessage waitForRCC(final long id, final int timeout) throws MobileAgentException
306 {
307 final NXCPMessage msg = waitForMessage(NXCPCodes.CMD_REQUEST_COMPLETED, id, timeout);
308 final int rcc = msg.getFieldAsInt32(NXCPCodes.VID_RCC);
309 if (rcc != RCC.SUCCESS)
310 {
311 throw new MobileAgentException(rcc);
312 }
313 return msg;
314 }
315
316 /**
317 * Create new NXCP message with unique id
318 *
319 * @param code message code
320 * @return new message object
321 */
322 public final NXCPMessage newMessage(int code)
323 {
324 return new NXCPMessage(code, requestId.getAndIncrement());
325 }
326
327 /**
328 * Execute simple commands (without arguments and only returning RCC)
329 *
330 * @param command
331 * Command code
332 * @throws IOException
333 * @throws MobileAgentException
334 */
335 protected void executeSimpleCommand(int command) throws IOException, MobileAgentException
336 {
337 final NXCPMessage msg = newMessage(command);
338 sendMessage(msg);
339 waitForRCC(msg.getMessageId());
340 }
341
342 /**
343 * Connect to server.
344 *
345 * @throws IOException
346 * @throws UnknownHostException
347 * @throws MobileAgentException
348 */
349 public void connect() throws IOException, UnknownHostException, MobileAgentException
350 {
351 Logger.info("Session.connect", "Connecting to " + connAddress + ":" + connPort);
352 try
353 {
354 connSocket = new Socket(connAddress, connPort);
355 msgWaitQueue = new NXCPMsgWaitQueue(commandTimeout);
356 recvThread = new ReceiverThread();
357
358 // get server information
359 Logger.debug("Session.connect", "connection established, retrieving server info");
360 NXCPMessage request = newMessage(NXCPCodes.CMD_GET_SERVER_INFO);
361 sendMessage(request);
362 NXCPMessage response = waitForMessage(NXCPCodes.CMD_REQUEST_COMPLETED, request.getMessageId());
363
364 if (response.getFieldAsInt32(NXCPCodes.VID_PROTOCOL_VERSION) != PROTOCOL_VERSION)
365 {
366 Logger.warning("Session.connect", "connection failed, server protocol version is " + response.getFieldAsInt32(NXCPCodes.VID_PROTOCOL_VERSION));
367 throw new MobileAgentException(RCC.BAD_PROTOCOL);
368 }
369
370 String serverVersion = response.getFieldAsString(NXCPCodes.VID_SERVER_VERSION);
371
372 // Setup encryption if required
373 if (connUseEncryption)
374 {
375 request = newMessage(NXCPCodes.CMD_REQUEST_ENCRYPTION);
376 request.setFieldInt16(NXCPCodes.VID_USE_X509_KEY_FORMAT, 1);
377 sendMessage(request);
378 waitForRCC(request.getMessageId());
379 }
380
381 // Login to server
382 Logger.debug("Session.connect", "Connected to server version " + serverVersion + ", trying to login");
383 request = newMessage(NXCPCodes.CMD_LOGIN);
384 request.setField(NXCPCodes.VID_DEVICE_ID, connDeviceId);
385 request.setField(NXCPCodes.VID_LOGIN_NAME, connLoginName);
386 request.setField(NXCPCodes.VID_PASSWORD, connPassword);
387 request.setField(NXCPCodes.VID_LIBNXCL_VERSION, NXCommon.VERSION);
388 request.setField(NXCPCodes.VID_OS_INFO, System.getProperty("os.name") + " " + System.getProperty("os.version"));
389 sendMessage(request);
390 response = waitForMessage(NXCPCodes.CMD_LOGIN_RESP, request.getMessageId());
391 int rcc = response.getFieldAsInt32(NXCPCodes.VID_RCC);
392 Logger.debug("Session.connect", "CMD_LOGIN_RESP received, RCC=" + rcc);
393 if (rcc != RCC.SUCCESS)
394 {
395 Logger.warning("NXCSession.connect", "Login failed, RCC=" + rcc);
396 throw new MobileAgentException(rcc);
397 }
398
399 Logger.info("Session.connect", "succesfully connected and logged in");
400 isConnected = true;
401 }
402 finally
403 {
404 if (!isConnected)
405 disconnect();
406 }
407 }
408
409 /**
410 * Disconnect from server
411 */
412 public void disconnect()
413 {
414 if (connSocket != null)
415 {
416 try
417 {
418 connSocket.shutdownInput();
419 connSocket.shutdownOutput();
420 }
421 catch(IOException e)
422 {
423 }
424
425 try
426 {
427 connSocket.close();
428 }
429 catch(IOException e)
430 {
431 }
432 }
433
434 if (recvThread != null)
435 {
436 while(recvThread.isAlive())
437 {
438 try
439 {
440 recvThread.join();
441 }
442 catch(InterruptedException e)
443 {
444 }
445 }
446 recvThread = null;
447 }
448
449 connSocket = null;
450
451 if (msgWaitQueue != null)
452 {
453 msgWaitQueue.shutdown();
454 msgWaitQueue = null;
455 }
456
457 isConnected = false;
458 }
459
460 /**
461 * Set command execution timeout in milliseconds.
462 *
463 * @param commandTimeout
464 */
465 public void setCommandTimeout(final int commandTimeout)
466 {
467 this.commandTimeout = commandTimeout;
468 }
469
470 /**
471 * Push data to server.
472 *
473 * @param data push data
474 * @throws IOException if socket I/O error occurs
475 * @throws MobileAgentException if NetXMS server returns an error or operation was timed out
476 */
477 public void pushDciData(DciPushData[] data) throws IOException, MobileAgentException
478 {
479 NXCPMessage msg = newMessage(NXCPCodes.CMD_PUSH_DCI_DATA);
480 msg.setFieldInt32(NXCPCodes.VID_NUM_ITEMS, data.length);
481 long varId = NXCPCodes.VID_PUSH_DCI_DATA_BASE;
482 for(DciPushData d : data)
483 {
484 msg.setFieldInt32(varId++, (int)d.nodeId);
485 if (d.nodeId == 0)
486 msg.setField(varId++, d.nodeName);
487 msg.setFieldInt32(varId++, (int)d.dciId);
488 if (d.dciId == 0)
489 msg.setField(varId++, d.dciName);
490 msg.setField(varId++, d.value);
491 }
492
493 sendMessage(msg);
494 waitForRCC(msg.getMessageId());
495 }
496
497 /**
498 * Push value for single DCI.
499 *
500 * @param nodeId node ID
501 * @param dciId DCI ID
502 * @param value value to push
503 * @throws IOException if socket I/O error occurs
504 * @throws MobileAgentException if NetXMS server returns an error or operation was timed out
505 */
506 public void pushDciData(long nodeId, long dciId, String value) throws IOException, MobileAgentException
507 {
508 pushDciData(new DciPushData[] { new DciPushData(nodeId, dciId, value) });
509 }
510
511 /**
512 * Push value for single DCI.
513 *
514 * @param nodeName node name
515 * @param dciName DCI name
516 * @param value value to push
517 * @throws IOException if socket I/O error occurs
518 * @throws MobileAgentException if NetXMS server returns an error or operation was timed out
519 */
520 public void pushDciData(String nodeName, String dciName, String value) throws IOException, MobileAgentException
521 {
522 pushDciData(new DciPushData[] { new DciPushData(nodeName, dciName, value) });
523 }
524
525 /**
526 * Report basic system information about mobile device. Additional information may be reported via push DCIs.
527 *
528 * @param vendor vendor name (like "HTC")
529 * @param model device model (like "Desire A8181")
530 * @param osName operating system name (like "Android")
531 * @param osVersion operating system version (like "2.2")
532 * @param serialNumber device serial number
533 * @param userId user id, if available (can be null)
534 * @throws IOException if socket I/O error occurs
535 * @throws MobileAgentException if NetXMS server returns an error or operation was timed out
536 */
537 public void reportDeviceSystemInfo(String vendor, String model, String osName, String osVersion, String serialNumber, String userId) throws IOException, MobileAgentException
538 {
539 NXCPMessage msg = newMessage(NXCPCodes.CMD_REPORT_DEVICE_INFO);
540 msg.setField(NXCPCodes.VID_VENDOR, vendor);
541 msg.setField(NXCPCodes.VID_MODEL, model);
542 msg.setField(NXCPCodes.VID_OS_NAME, osName);
543 msg.setField(NXCPCodes.VID_OS_VERSION, osVersion);
544 msg.setField(NXCPCodes.VID_SERIAL_NUMBER, serialNumber);
545 if (userId != null)
546 msg.setField(NXCPCodes.VID_USER_NAME, userId);
547 sendMessage(msg);
548 waitForRCC(msg.getMessageId());
549 }
550
551 /**
552 * Report basic current status of the device. Additional information may be reported via push DCIs.
553 *
554 * @param address current IP address of the device (may be null if not known)
555 * @param location current device location (may be null if not known)
556 * @param flags
557 * @param batteryLevel current battery level, -1 if not known or not applicable
558 * @throws IOException if socket I/O error occurs
559 * @throws MobileAgentException if NetXMS server returns an error or operation was timed out
560 */
561 public void reportDeviceStatus(InetAddress address, GeoLocation location, int flags, int batteryLevel) throws IOException, MobileAgentException
562 {
563 NXCPMessage msg = newMessage(NXCPCodes.CMD_REPORT_DEVICE_STATUS);
564
565 if (address != null)
566 msg.setField(NXCPCodes.VID_IP_ADDRESS, address);
567 else
568 msg.setFieldInt32(NXCPCodes.VID_IP_ADDRESS, 0);
569
570 if (location != null)
571 {
572 msg.setField(NXCPCodes.VID_LATITUDE, location.getLatitude());
573 msg.setField(NXCPCodes.VID_LONGITUDE, location.getLongitude());
574 msg.setFieldInt16(NXCPCodes.VID_GEOLOCATION_TYPE, location.getType());
575 msg.setFieldInt16(NXCPCodes.VID_ACCURACY, location.getAccuracy());
576 msg.setFieldInt64(NXCPCodes.VID_GEOLOCATION_TIMESTAMP, location.getTimestamp() != null ? location.getTimestamp().getTime() / 1000 : 0);
577 }
578 else
579 {
580 msg.setFieldInt16(NXCPCodes.VID_GEOLOCATION_TYPE, GeoLocation.UNSET);
581 }
582
583 msg.setFieldInt32(NXCPCodes.VID_FLAGS, flags);
584 msg.setFieldInt32(NXCPCodes.VID_BATTERY_LEVEL, batteryLevel);
585 sendMessage(msg);
586 waitForRCC(msg.getMessageId());
587 }
588 }