1 package iotcode.DlinkAlarm;
3 // Standard Java Packages
6 import java.util.concurrent.Semaphore;
7 import java.util.concurrent.atomic.AtomicBoolean;
8 import java.security.InvalidParameterException;
10 import java.util.Iterator;
11 import java.nio.charset.StandardCharsets;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.ArrayList;
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;
25 import iotruntime.IoTTCP;
26 import iotruntime.slave.IoTDeviceAddress;
27 import iotruntime.slave.IoTSet;
28 import iotcode.interfaces.ZoneState;
29 import iotcode.interfaces.Alarm;
31 //import iotchecker.qual.*;
32 import iotcode.annotation.*;
34 /** Class DlinkAlarm for the D-Link Alarm.
36 * @author Rahmadi Trimananda <rtrimana @ uci.edu>
41 public class DlinkAlarm implements Alarm {
43 /*******************************************************************************************************************************************
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 = 0;
51 private static final int INT_ALARM_MAX_DUR = 86400;
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;
65 /*******************************************************************************************************************************************
67 ** IoT Sets and Relations
69 *******************************************************************************************************************************************/
71 // IoTSet of Device Addresses.
72 // Will be filled with only 1 address.
73 @config private IoTSet<IoTDeviceAddress> alm_Addresses;
75 public DlinkAlarm(IoTSet<IoTDeviceAddress> _alm_addr, String _devicePin) {
76 alm_Addresses = _alm_addr;
77 devicePin = _devicePin;
80 public DlinkAlarm(String _devicePin) {
81 devicePin = _devicePin;
85 /*******************************************************************************************************************************************
89 *******************************************************************************************************************************************/
91 /** Method to set the state of a specified zone. Interface implementation.
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.
97 * @return [void] None.
99 public void setZone(int _zone, boolean _onOff, int _onDurationSeconds) {
101 // We don't use zone at this point (for this alarm there is only 1 zone and 1 alarm)
105 if ((_onDurationSeconds == -1) || (_onDurationSeconds > 86400)) {
106 // Set alarm on to max duration
107 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, INT_ALARM_MAX_DUR);
109 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, _onDurationSeconds);
111 } else { // Else turn off
117 /** Method to get the current state of all the zones. Interface implementation.
121 * @return [List<ZoneState>] list of the states for the zones.
123 public List<ZoneState> getZoneStates() {
129 /** Method to get the number of zones this alarm can control. Interface implementation.
133 * @return [int] number of zones that can be controlled.
135 public int getNumberOfZones() {
140 /** Method to get whether or not this alarm can control durations. Interface implementation.
144 * @return [boolean] boolean if this sprinkler can do durations.
146 public boolean doesHaveZoneTimers() {
151 /** Method to initialize the alarm. Interface implementation.
155 * @return [void] None.
160 Iterator itr = alm_Addresses.iterator();
161 deviceAddress = (IoTDeviceAddress)itr.next();
162 host = deviceAddress.getHostAddress();
163 System.out.println("Address: " + host);
166 head = "<?xml version=\"1.0\" encoding=\"utf-8" +
167 "\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance" +
168 "\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=" +
169 "\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>";
171 end = "</soap:Body></soap:Envelope>\r\n";
172 // Send login request first
174 // Send login info (challenge and HMAC encrypted message)
176 } catch (Exception e) {
181 /*******************************************************************************************************************************************
185 *******************************************************************************************************************************************/
187 private void sendLoginRequest() {
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";
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);
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()));
214 tcpOut.print(postMessage);
218 while (!tcpIn.ready()) {
221 // Wait a bit longer for data
225 while (tcpIn.ready()) {
226 response = tcpIn.readLine();
227 System.out.println("Response to Login Request: " + response);
231 } catch (Exception e) {
236 private void sendLoginInfo() {
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;
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;
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";
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);
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()));
280 tcpOut.print(postMessage);
284 while (!tcpIn.ready()) {
287 // Wait a bit longer for data
291 while (tcpIn.ready()) {
292 response = tcpIn.readLine();
293 System.out.println("Response to Login Info: " + response);
297 } catch (Exception e) {
302 private void setSoundPlay(int soundType, int volume, int duration) {
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";
327 System.out.println(postMessage);
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);
335 // Get in and out communication
336 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
337 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
339 tcpOut.print(postMessage);
343 while (!tcpIn.ready()) {
346 // Wait a bit longer for data
350 while (tcpIn.ready()) {
351 response = tcpIn.readLine();
352 System.out.println("Response to SetSoundPlay: " + response);
356 } catch (Exception e) {
361 private void setAlarmDismissed() {
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";
384 System.out.println(postMessage);
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);
392 // Get in and out communication
393 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
394 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
396 tcpOut.print(postMessage);
400 while (!tcpIn.ready()) {
403 // Wait a bit longer for data
407 while (tcpIn.ready()) {
408 response = tcpIn.readLine();
409 System.out.println("Response to SetSoundPlay: " + response);
413 } catch (Exception e) {
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) {
421 return xml.split("<"+tagName+">")[1].split("</"+tagName+">")[0];
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;
428 SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algorithm);
429 Mac mac = Mac.getInstance(algorithm);
432 byte[] bytes = mac.doFinal(message.getBytes("ASCII"));
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) {
443 // We need to convert the String to upper case
444 digest = hash.toString().toUpperCase();
445 } catch ( UnsupportedEncodingException |
446 InvalidKeyException |
447 NoSuchAlgorithmException e ) {
453 private long getUnixEpochSeconds() {
454 // Return time since January 1, 1970 00:00:00 UTC in seconds
455 return System.currentTimeMillis()/1000;
458 /*public static void main(String[] args) throws Exception {
460 String ipAddress = "192.168.1.183";
461 String devicePin = "215530";
462 IoTDeviceAddress iotAddress = new IoTDeviceAddress(ipAddress, 12345, 80, false, false);
463 Set<IoTDeviceAddress> setAddress = new HashSet<IoTDeviceAddress>();
464 setAddress.add(iotAddress);
465 IoTSet<IoTDeviceAddress> iotSetAddress = new IoTSet<IoTDeviceAddress>(setAddress);
467 DlinkAlarm alarm = new DlinkAlarm(iotSetAddress, devicePin);
470 alarm.setZone(0, true, -1);
472 alarm.setZone(0, false, 0);
474 alarm.setZone(0, true, -1);
476 alarm.setZone(0, false, 0);