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