+package iotcode.DlinkAlarm;
+
+// Standard Java Packages
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.security.InvalidParameterException;
+import java.util.Date;
+import java.util.Iterator;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+
+// HMAC digest packages
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+// IoT Packages
+import iotruntime.IoTTCP;
+import iotruntime.slave.IoTDeviceAddress;
+import iotruntime.slave.IoTSet;
+import iotcode.interfaces.ZoneState;
+import iotcode.interfaces.Alarm;
+
+//import iotchecker.qual.*;
+import iotcode.annotation.*;
+
+/** Class DlinkAlarm for the D-Link Alarm.
+ *
+ * @author Rahmadi Trimananda <rtrimana @ uci.edu>
+ * @version 1.0
+ * @since 2017-11-28
+ */
+
+public class DlinkAlarm implements Alarm {
+
+ /*******************************************************************************************************************************************
+ **
+ ** Variables
+ **
+ *******************************************************************************************************************************************/
+ private static final String STR_HMAC_ALGO = "HmacMD5";
+ private static final int INT_ALARM_SOUND_TYPE = 6;
+ private static final int INT_ALARM_VOLUME = 0;
+ private static final int INT_ALARM_MAX_DUR = 86400;
+
+ private String host = null;
+ private String head = null;
+ private String end = null;
+ private IoTDeviceAddress deviceAddress = null;
+ private String devicePin = null;
+ private String response = null;
+ // The following 3 are needed to send commands to the alarm/siren
+ private String cookie = null;
+ private String timeStamp = null;
+ private String privateKey = null;
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** IoT Sets and Relations
+ **
+ *******************************************************************************************************************************************/
+
+ // IoTSet of Device Addresses.
+ // Will be filled with only 1 address.
+ @config private IoTSet<IoTDeviceAddress> alm_Addresses;
+
+ public DlinkAlarm(IoTSet<IoTDeviceAddress> _alm_addr, String _devicePin) {
+ alm_Addresses = _alm_addr;
+ devicePin = _devicePin;
+ }
+
+ public DlinkAlarm(String _devicePin) {
+ devicePin = _devicePin;
+ }
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** Interface Methods
+ **
+ *******************************************************************************************************************************************/
+
+ /** Method to set the state of a specified zone. Interface implementation.
+ *
+ * @param _zone [int] : zone number to set.
+ * @param _onOff [boolean] : the state to set the zone to, on or off.
+ * @param _onDurationSeconds [int]: the duration to set the state on to, if -1 then infinite.
+ *
+ * @return [void] None.
+ */
+ public void setZone(int _zone, boolean _onOff, int _onDurationSeconds) {
+
+ // We don't use zone at this point (for this alarm there is only 1 zone and 1 alarm)
+
+ // True means on
+ if (_onOff) {
+ if ((_onDurationSeconds == -1) || (_onDurationSeconds > 86400)) {
+ // Set alarm on to max duration
+ setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, INT_ALARM_MAX_DUR);
+ } else {
+ setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, _onDurationSeconds);
+ }
+ } else { // Else turn off
+ setAlarmDismissed();
+ }
+ }
+
+
+ /** Method to get the current state of all the zones. Interface implementation.
+ *
+ * @param None.
+ *
+ * @return [List<ZoneState>] list of the states for the zones.
+ */
+ public List<ZoneState> getZoneStates() {
+
+ return null;
+ }
+
+
+ /** Method to get the number of zones this alarm can control. Interface implementation.
+ *
+ * @param None.
+ *
+ * @return [int] number of zones that can be controlled.
+ */
+ public int getNumberOfZones() {
+ return 1;
+ }
+
+
+ /** Method to get whether or not this alarm can control durations. Interface implementation.
+ *
+ * @param None.
+ *
+ * @return [boolean] boolean if this sprinkler can do durations.
+ */
+ public boolean doesHaveZoneTimers() {
+ return true;
+ }
+
+
+ /** Method to initialize the alarm. Interface implementation.
+ *
+ * @param None.
+ *
+ * @return [void] None.
+ */
+ public void init() {
+
+ try {
+ Iterator itr = alm_Addresses.iterator();
+ deviceAddress = (IoTDeviceAddress)itr.next();
+ host = deviceAddress.getHostAddress();
+ System.out.println("Address: " + host);
+
+ // Add the head ...
+ head = "<?xml version=\"1.0\" encoding=\"utf-8" +
+ "\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance" +
+ "\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=" +
+ "\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>";
+ // End!
+ end = "</soap:Body></soap:Envelope>\r\n";
+ // Send login request first
+ sendLoginRequest();
+ // Send login info (challenge and HMAC encrypted message)
+ sendLoginInfo();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /*******************************************************************************************************************************************
+ **
+ ** Private Handlers
+ **
+ *******************************************************************************************************************************************/
+
+ private void sendLoginRequest() {
+
+ try {
+ // Message
+ String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
+ "\"><Action>request</Action><Username>admin</Username><LoginPassword></LoginPassword><Captcha/></Login>" + end;
+ String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
+ postMessage += "User-Agent: Java/1.8.0_144\r\n";
+ postMessage += "Host: " + host + "\r\n";
+ postMessage += "Accept: */*\r\n";
+ postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
+ postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
+ postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
+ postMessage += "\r\n";
+ postMessage += body;
+
+ // Create the communication channel
+ //IoTTCP connection = new IoTTCP(deviceAddress);
+ IoTTCP connection = new IoTTCP();
+ connection.setReuseAddress(true);
+ connection.bindAndConnect(deviceAddress, false);
+
+ // Get in and out communication
+ System.out.println(postMessage);
+ PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
+ BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+
+ tcpOut.print(postMessage);
+ tcpOut.flush();
+
+ // wait for data
+ while (!tcpIn.ready()) {
+ }
+
+ // Wait a bit longer for data
+ Thread.sleep(10);
+
+ // get the response
+ while (tcpIn.ready()) {
+ response = tcpIn.readLine();
+ System.out.println("Response to Login Request: " + response);
+ }
+ connection.close();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void sendLoginInfo() {
+
+ // Extract challenge, public key, and cookie
+ String challenge = getTagValue(response, "Challenge");
+ cookie = "Cookie: uid=" + getTagValue(response, "Cookie");
+ String publicKey = getTagValue(response, "PublicKey") + devicePin;
+
+ // Generate private key and password
+ privateKey = hmacDigest(challenge, publicKey, STR_HMAC_ALGO);
+ String password = hmacDigest(challenge, privateKey, STR_HMAC_ALGO);
+ timeStamp = Long.toString(getUnixEpochSeconds());
+ // HNAP authentication
+ String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/Login\"";
+ String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
+ String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
+
+ try {
+ // Message
+ String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
+ "\"><Action>login</Action><Username>admin</Username><LoginPassword>" +
+ password + "</LoginPassword><Captcha/></Login>" + end;
+ String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
+ postMessage += "User-Agent: Java/1.8.0_144\r\n";
+ postMessage += "Host: " + host + "\r\n";
+ postMessage += "Accept: */*\r\n";
+ postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
+ postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
+ postMessage += hnap_auth + "\r\n";
+ postMessage += cookie + "\r\n";
+ postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
+ postMessage += "\r\n";
+ postMessage += body;
+
+ // Create the communication channel
+ //IoTTCP connection = new IoTTCP(deviceAddress);
+ IoTTCP connection = new IoTTCP();
+ connection.setReuseAddress(true);
+ connection.bindAndConnect(deviceAddress, false);
+
+ // Get in and out communication
+ System.out.println(postMessage);
+ PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
+ BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+
+ tcpOut.print(postMessage);
+ tcpOut.flush();
+
+ // wait for data
+ while (!tcpIn.ready()) {
+ }
+
+ // Wait a bit longer for data
+ //Thread.sleep(10);
+
+ // get the response
+ while (tcpIn.ready()) {
+ response = tcpIn.readLine();
+ System.out.println("Response to Login Info: " + response);
+ }
+ connection.close();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setSoundPlay(int soundType, int volume, int duration) {
+
+ try {
+ // Message
+ String method = "SetSoundPlay";
+ String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" +
+ "<ModuleID>1</ModuleID><Controller>1</Controller><SoundType>" + Integer.toString(soundType) +
+ "</SoundType><Volume>" + Integer.toString(volume) + "</Volume><Duration>" + Integer.toString(duration) +
+ "</Duration></" + method + ">" + end;
+ String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
+ postMessage += "User-Agent: Java/1.8.0_144\r\n";
+ postMessage += "Host: " + host + "\r\n";
+ postMessage += "Accept: */*\r\n";
+ postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
+ postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
+ // HNAP authentication
+ String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
+ String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
+ String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
+ postMessage += hnap_auth + "\r\n";
+ postMessage += cookie + "\r\n";
+ postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
+ postMessage += "\r\n";
+ postMessage += body;
+
+ System.out.println(postMessage);
+
+ // Create the communication channel
+ //IoTTCP connection = new IoTTCP(deviceAddress);
+ IoTTCP connection = new IoTTCP();
+ connection.setReuseAddress(true);
+ connection.bindAndConnect(deviceAddress, false);
+
+ // Get in and out communication
+ PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
+ BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+
+ tcpOut.print(postMessage);
+ tcpOut.flush();
+
+ // wait for data
+ while (!tcpIn.ready()) {
+ }
+
+ // Wait a bit longer for data
+ //Thread.sleep(10);
+
+ // get the response
+ while (tcpIn.ready()) {
+ response = tcpIn.readLine();
+ System.out.println("Response to SetSoundPlay: " + response);
+ }
+ connection.close();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setAlarmDismissed() {
+
+ try {
+ // Message
+ String method = "setAlarmDismissed";
+ String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" +
+ "<ModuleID>1</ModuleID><Controller>1</Controller></" + method + ">" + end;
+ String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
+ postMessage += "User-Agent: Java/1.8.0_144\r\n";
+ postMessage += "Host: " + host + "\r\n";
+ postMessage += "Accept: */*\r\n";
+ postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
+ postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
+ // HNAP authentication
+ String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
+ String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
+ String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
+ postMessage += hnap_auth + "\r\n";
+ postMessage += cookie + "\r\n";
+ postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
+ postMessage += "\r\n";
+ postMessage += body;
+
+ System.out.println(postMessage);
+
+ // Create the communication channel
+ //IoTTCP connection = new IoTTCP(deviceAddress);
+ IoTTCP connection = new IoTTCP();
+ connection.setReuseAddress(true);
+ connection.bindAndConnect(deviceAddress, false);
+
+ // Get in and out communication
+ PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
+ BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+
+ tcpOut.print(postMessage);
+ tcpOut.flush();
+
+ // wait for data
+ while (!tcpIn.ready()) {
+ }
+
+ // Wait a bit longer for data
+ //Thread.sleep(10);
+
+ // get the response
+ while (tcpIn.ready()) {
+ response = tcpIn.readLine();
+ System.out.println("Response to SetSoundPlay: " + response);
+ }
+ connection.close();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Adapted from https://stackoverflow.com/questions/4076910/how-to-retrieve-element-value-of-xml-using-java
+ private String getTagValue(String xml, String tagName) {
+
+ return xml.split("<"+tagName+">")[1].split("</"+tagName+">")[0];
+ }
+
+ // Adapted from http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
+ private String hmacDigest(String message, String keyString, String algorithm) {
+ String digest = null;
+ try {
+ SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algorithm);
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(key);
+
+ byte[] bytes = mac.doFinal(message.getBytes("ASCII"));
+
+ // Create a StringBuffer and convert the bytes into a String
+ StringBuffer hash = new StringBuffer();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(0xFF & bytes[i]);
+ if (hex.length() == 1) {
+ hash.append('0');
+ }
+ hash.append(hex);
+ }
+ // We need to convert the String to upper case
+ digest = hash.toString().toUpperCase();
+ } catch ( UnsupportedEncodingException |
+ InvalidKeyException |
+ NoSuchAlgorithmException e ) {
+ e.printStackTrace();
+ }
+ return digest;
+ }
+
+ private long getUnixEpochSeconds() {
+ // Return time since January 1, 1970 00:00:00 UTC in seconds
+ return System.currentTimeMillis()/1000;
+ }
+
+ /*public static void main(String[] args) throws Exception {
+
+ String ipAddress = "192.168.1.183";
+ String devicePin = "215530";
+ IoTDeviceAddress iotAddress = new IoTDeviceAddress(ipAddress, 12345, 80, false, false);
+ Set<IoTDeviceAddress> setAddress = new HashSet<IoTDeviceAddress>();
+ setAddress.add(iotAddress);
+ IoTSet<IoTDeviceAddress> iotSetAddress = new IoTSet<IoTDeviceAddress>(setAddress);
+
+ DlinkAlarm alarm = new DlinkAlarm(iotSetAddress, devicePin);
+ alarm.init();
+ // Set alarm
+ alarm.setZone(0, true, -1);
+ // Set alarm
+ alarm.setZone(0, false, 0);
+ // Set alarm
+ alarm.setZone(0, true, -1);
+ // Set alarm
+ alarm.setZone(0, false, 0);
+ }*/
+}
+
+