Changing the alarm/siren driver - make it always authenticate when it is to make...
[iot2.git] / benchmarks / drivers / Java / DlinkAlarm / DlinkAlarm.java
1 package iotcode.DlinkAlarm;
2
3 // Standard Java Packages
4 import java.io.*;
5 import java.net.*;
6 import java.util.concurrent.Semaphore;
7 import java.util.concurrent.atomic.AtomicBoolean;
8 import java.security.InvalidParameterException;
9 import java.util.Date;
10 import java.util.Iterator;
11 import java.nio.charset.StandardCharsets;
12 import java.util.Set;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.ArrayList;
16
17 // HMAC digest packages
18 import javax.crypto.Mac;
19 import javax.crypto.spec.SecretKeySpec;
20 import java.io.UnsupportedEncodingException;
21 import java.security.InvalidKeyException;
22 import java.security.NoSuchAlgorithmException;
23
24 // IoT Packages
25 import iotruntime.IoTTCP;
26 import iotruntime.slave.IoTDeviceAddress;
27 import iotruntime.slave.IoTSet;
28 import iotcode.interfaces.ZoneState;
29 import iotcode.interfaces.Alarm;
30
31 //import iotchecker.qual.*;
32 import iotcode.annotation.*;
33
34 /** Class DlinkAlarm for the D-Link Alarm.
35  *
36  * @author      Rahmadi Trimananda <rtrimana @ uci.edu>
37  * @version     1.0
38  * @since       2017-11-28
39  */
40
41 public class DlinkAlarm implements Alarm {
42
43         /*******************************************************************************************************************************************
44         **
45         **  Variables
46         **
47         *******************************************************************************************************************************************/
48     private static final String STR_HMAC_ALGO = "HmacMD5";
49     private static final int INT_ALARM_SOUND_TYPE = 6;
50     private static final int INT_ALARM_VOLUME = 1;
51     private static final int INT_ALARM_MAX_DUR = 86400;
52
53     private String host = null;
54     private String head = null;
55     private String end = null;
56     private IoTDeviceAddress deviceAddress = null;
57     private String devicePin = null;
58     private String response = null;
59     // The following 3 are needed to send commands to the alarm/siren
60     private String cookie = null;
61     private String timeStamp = null;
62     private String privateKey = null;
63       
64
65         /*******************************************************************************************************************************************
66         **
67         **  IoT Sets and Relations
68         **
69         *******************************************************************************************************************************************/
70
71         // IoTSet of Device Addresses.
72         // Will be filled with only 1 address.
73         @config private IoTSet<IoTDeviceAddress> alm_Addresses;
74
75         public DlinkAlarm(IoTSet<IoTDeviceAddress> _alm_addr, String _devicePin) {
76                 alm_Addresses = _alm_addr;
77                 devicePin = _devicePin;
78         }
79
80         public DlinkAlarm(String _devicePin) {
81                 devicePin = _devicePin;
82         }
83
84
85         /*******************************************************************************************************************************************
86         **
87         **  Interface Methods
88         **
89         *******************************************************************************************************************************************/
90
91         /** Method to set the state of a specified zone. Interface implementation.
92          *
93          *   @param _zone [int]             : zone number to set.
94          *   @param _onOff [boolean]        : the state to set the zone to, on or off.
95          *   @param _onDurationSeconds [int]: the duration to set the state on to, if -1 then infinite.
96          *
97          *   @return [void] None.
98          */
99         public void setZone(int _zone, boolean _onOff, int _onDurationSeconds) {
100         
101         // We don't use zone at this point (for this alarm there is only 1 zone and 1 alarm)
102
103         // Send login request first
104         sendLoginRequest();
105         // Send login info (challenge and HMAC encrypted message)
106         sendLoginInfo();
107         // True means on
108         if (_onOff) {
109             if ((_onDurationSeconds == -1) || (_onDurationSeconds > 86400)) {
110                 // Set alarm on to max duration
111                 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, INT_ALARM_MAX_DUR);
112             } else {
113                 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, _onDurationSeconds);
114             }
115         } else { // Else turn off
116             setAlarmDismissed();
117         }
118     }
119
120
121         /** Method to get the current state of all the zones. Interface implementation.
122          *
123          *   @param None.
124          *
125          *   @return [List<ZoneState>] list of the states for the zones.
126          */
127         public List<ZoneState> getZoneStates() {
128
129         return null;
130         }
131
132
133         /** Method to get the number of zones this alarm can control. Interface implementation.
134          *
135          *   @param None.
136          *
137          *   @return [int] number of zones that can be controlled.
138          */
139         public int getNumberOfZones() {
140                 return 1;
141         }
142
143
144         /** Method to get whether or not this alarm can control durations. Interface implementation.
145          *
146          *   @param None.
147          *
148          *   @return [boolean] boolean if this sprinkler can do durations.
149          */
150         public boolean doesHaveZoneTimers() {
151                 return true;
152         }
153
154
155         /** Method to initialize the alarm. Interface implementation.
156          *
157          *   @param None.
158          *
159          *   @return [void] None.
160          */
161         public void init() {
162
163                 try {
164                         Iterator itr = alm_Addresses.iterator();
165                         deviceAddress = (IoTDeviceAddress)itr.next();
166                         host = deviceAddress.getHostAddress();
167                         System.out.println("Address: " + host);
168                         
169             // Add the head ...
170             head = "<?xml version=\"1.0\" encoding=\"utf-8" +
171                           "\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance" +
172                           "\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=" + 
173                           "\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>";
174             // End!
175             end = "</soap:Body></soap:Envelope>\r\n";
176                 } catch (Exception e) {
177                         e.printStackTrace();
178                 }
179     }
180
181         /*******************************************************************************************************************************************
182         **
183         **  Private Handlers
184         **
185         *******************************************************************************************************************************************/
186
187     private void sendLoginRequest() {
188
189         try {
190             // Message
191             String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
192                           "\"><Action>request</Action><Username>admin</Username><LoginPassword></LoginPassword><Captcha/></Login>" + end;
193             String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
194             postMessage += "User-Agent: Java/1.8.0_144\r\n";
195             postMessage += "Host: " + host + "\r\n";
196             postMessage += "Accept: */*\r\n";
197             postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
198             postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
199             postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
200             postMessage += "\r\n";
201             postMessage += body;
202
203                         // Create the communication channel
204                         //IoTTCP connection = new IoTTCP(deviceAddress);
205                         IoTTCP connection = new IoTTCP();
206                         connection.setReuseAddress(true);
207                         connection.bindAndConnect(deviceAddress, false);
208
209             // Get in and out communication
210             System.out.println(postMessage);          
211             PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
212             BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
213
214             tcpOut.print(postMessage);
215             tcpOut.flush();
216
217             // wait for data
218             while (!tcpIn.ready()) {
219             }
220
221             // Wait a bit longer for data
222             Thread.sleep(10);
223
224             // get the response
225             while (tcpIn.ready()) {
226                 response = tcpIn.readLine();
227                 System.out.println("Response to Login Request: " + response);
228             }
229             connection.close();
230             
231         } catch (Exception e) {
232             e.printStackTrace();
233         }
234     }
235     
236     private void sendLoginInfo() {
237
238         // Extract challenge, public key, and cookie
239         String challenge = getTagValue(response, "Challenge");
240         cookie = "Cookie: uid=" + getTagValue(response, "Cookie");
241         String publicKey = getTagValue(response, "PublicKey") + devicePin;
242         
243         // Generate private key and password
244         privateKey = hmacDigest(challenge, publicKey, STR_HMAC_ALGO);
245         String password = hmacDigest(challenge, privateKey, STR_HMAC_ALGO);
246         timeStamp = Long.toString(getUnixEpochSeconds());
247         // HNAP authentication
248         String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/Login\"";
249         String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
250         String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
251
252         try {
253             // Message
254             String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
255                           "\"><Action>login</Action><Username>admin</Username><LoginPassword>" + 
256                           password + "</LoginPassword><Captcha/></Login>" + end;
257             String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
258             postMessage += "User-Agent: Java/1.8.0_144\r\n";
259             postMessage += "Host: " + host + "\r\n";
260             postMessage += "Accept: */*\r\n";
261             postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
262             postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
263             postMessage += hnap_auth + "\r\n";
264             postMessage += cookie + "\r\n";
265             postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
266             postMessage += "\r\n";
267             postMessage += body;
268
269                         // Create the communication channel
270                         //IoTTCP connection = new IoTTCP(deviceAddress);
271                         IoTTCP connection = new IoTTCP();
272                         connection.setReuseAddress(true);
273                         connection.bindAndConnect(deviceAddress, false);
274
275             // Get in and out communication
276             System.out.println(postMessage);          
277             PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
278             BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
279
280             tcpOut.print(postMessage);
281             tcpOut.flush();
282
283             // wait for data
284             while (!tcpIn.ready()) {
285             }
286
287             // Wait a bit longer for data
288             //Thread.sleep(10);
289
290             // get the response
291             while (tcpIn.ready()) {
292                 response = tcpIn.readLine();
293                 System.out.println("Response to Login Info: " + response);
294             }
295             connection.close();
296                        
297         } catch (Exception e) {
298             e.printStackTrace();
299         }
300     }
301     
302     private void setSoundPlay(int soundType, int volume, int duration) {
303
304         try {
305             // Message
306             String method = "SetSoundPlay";
307             String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" + 
308                         "<ModuleID>1</ModuleID><Controller>1</Controller><SoundType>" + Integer.toString(soundType) + 
309                         "</SoundType><Volume>" + Integer.toString(volume) + "</Volume><Duration>" + Integer.toString(duration) +
310                         "</Duration></" + method + ">" + end;
311             String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
312             postMessage += "User-Agent: Java/1.8.0_144\r\n";
313             postMessage += "Host: " + host + "\r\n";
314             postMessage += "Accept: */*\r\n";
315             postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
316             postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
317             // HNAP authentication
318             String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
319             String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
320             String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;            
321             postMessage += hnap_auth + "\r\n";
322             postMessage += cookie + "\r\n";
323             postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
324             postMessage += "\r\n";
325             postMessage += body;
326
327             System.out.println(postMessage);
328
329                         // Create the communication channel
330                         //IoTTCP connection = new IoTTCP(deviceAddress);
331                         IoTTCP connection = new IoTTCP();
332                         connection.setReuseAddress(true);
333                         connection.bindAndConnect(deviceAddress, false);
334
335             // Get in and out communication
336             PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
337             BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
338
339             tcpOut.print(postMessage);
340             tcpOut.flush();
341
342             // wait for data
343             while (!tcpIn.ready()) {
344             }
345
346             // Wait a bit longer for data
347             //Thread.sleep(10);
348             
349             // get the response
350             while (tcpIn.ready()) {
351                 response = tcpIn.readLine();
352                 System.out.println("Response to SetSoundPlay: " + response);
353             }
354             connection.close();
355            
356         } catch (Exception e) {
357             e.printStackTrace();
358         }
359     }
360     
361     private void setAlarmDismissed() {
362
363         try {
364             // Message
365             String method = "setAlarmDismissed";
366             String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" + 
367                         "<ModuleID>1</ModuleID><Controller>1</Controller></" + method + ">" + end;
368             String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
369             postMessage += "User-Agent: Java/1.8.0_144\r\n";
370             postMessage += "Host: " + host + "\r\n";
371             postMessage += "Accept: */*\r\n";
372             postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
373             postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
374             // HNAP authentication
375             String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
376             String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
377             String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;            
378             postMessage += hnap_auth + "\r\n";
379             postMessage += cookie + "\r\n";
380             postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
381             postMessage += "\r\n";
382             postMessage += body;
383
384             System.out.println(postMessage);
385
386                         // Create the communication channel
387                         //IoTTCP connection = new IoTTCP(deviceAddress);
388                         IoTTCP connection = new IoTTCP();
389                         connection.setReuseAddress(true);
390                         connection.bindAndConnect(deviceAddress, false);
391
392             // Get in and out communication
393             PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
394             BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
395
396             tcpOut.print(postMessage);
397             tcpOut.flush();
398
399             // wait for data
400             while (!tcpIn.ready()) {
401             }
402
403             // Wait a bit longer for data
404             //Thread.sleep(10);
405             
406             // get the response
407             while (tcpIn.ready()) {
408                 response = tcpIn.readLine();
409                 System.out.println("Response to SetSoundPlay: " + response);
410             }
411             connection.close();
412            
413         } catch (Exception e) {
414             e.printStackTrace();
415         }
416     }
417     
418     // Adapted from https://stackoverflow.com/questions/4076910/how-to-retrieve-element-value-of-xml-using-java
419     private String getTagValue(String xml, String tagName) {
420
421         return xml.split("<"+tagName+">")[1].split("</"+tagName+">")[0];
422     }
423     
424     // Adapted from http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
425     private String hmacDigest(String message, String keyString, String algorithm) {
426         String digest = null;
427         try {
428             SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algorithm);
429             Mac mac = Mac.getInstance(algorithm);
430             mac.init(key);
431
432             byte[] bytes = mac.doFinal(message.getBytes("ASCII"));
433
434             // Create a StringBuffer and convert the bytes into a String
435             StringBuffer hash = new StringBuffer();
436             for (int i = 0; i < bytes.length; i++) {
437                 String hex = Integer.toHexString(0xFF & bytes[i]);
438                 if (hex.length() == 1) {
439                     hash.append('0');
440                 }
441                 hash.append(hex);
442             }
443             // We need to convert the String to upper case
444             digest = hash.toString().toUpperCase();
445         } catch (   UnsupportedEncodingException |
446                     InvalidKeyException          |
447                     NoSuchAlgorithmException e ) {
448             e.printStackTrace();
449         }
450         return digest;
451     }
452     
453     private long getUnixEpochSeconds() {
454         // Return time since January 1, 1970 00:00:00 UTC in seconds
455         return System.currentTimeMillis()/1000;
456     }
457 }
458
459