/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.zwave.internal.protocol.serialmessage; import org.openhab.binding.zwave.internal.protocol.SerialMessage; import org.openhab.binding.zwave.internal.protocol.ZWaveController; import org.openhab.binding.zwave.internal.protocol.ZWaveNode; import org.openhab.binding.zwave.internal.protocol.ZWaveNodeState; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecurityCommandClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class processes a serial message from the zwave controller * * @author Chris Jackson * @since 1.5.0 */ public class ApplicationCommandMessageClass extends ZWaveCommandProcessor { private static final Logger logger = LoggerFactory.getLogger(ApplicationCommandMessageClass.class); @Override public boolean handleRequest(ZWaveController zController, SerialMessage lastSentMessage, SerialMessage incomingMessage) { logger.trace("Handle Message Application Command Request"); int nodeId = incomingMessage.getMessagePayloadByte(1); ZWaveNode node = zController.getNode(nodeId); if (node == null) { logger.warn("NODE {}: Not initialized yet, ignoring message.", nodeId); return false; } logger.debug("NODE {}: Application Command Request ({}:{})", nodeId, node.getNodeState().toString(), node.getNodeInitializationStage().toString()); // If the node is DEAD, but we've just received a message from it, then it's not dead! if (node.isDead()) { node.setNodeState(ZWaveNodeState.ALIVE); } node.resetResendCount(); node.incrementReceiveCount(); int commandClassCode = incomingMessage.getMessagePayloadByte(3); ZWaveCommandClass zwaveCommandClass = resolveZWaveCommandClass(node, commandClassCode, zController); if (zwaveCommandClass == null) { return false; // Error message was logged in resolveZWaveCommandClass } final int commandByte = incomingMessage.getMessagePayloadByte(4); if (zwaveCommandClass instanceof ZWaveSecurityCommandClass && (ZWaveSecurityCommandClass .bytesAreEqual(ZWaveSecurityCommandClass.SECURITY_MESSAGE_ENCAP, commandByte) || ZWaveSecurityCommandClass.bytesAreEqual(ZWaveSecurityCommandClass.SECURITY_MESSAGE_ENCAP_NONCE_GET, commandByte))) { boolean isEncapNonceGet = ZWaveSecurityCommandClass .bytesAreEqual(ZWaveSecurityCommandClass.SECURITY_MESSAGE_ENCAP_NONCE_GET, commandByte); // Intercept security encapsulated messages here and decrypt them. ZWaveSecurityCommandClass zwaveSecurityCommandClass = (ZWaveSecurityCommandClass) zwaveCommandClass; logger.trace("NODE {}: Preparing to decrypt security encapsulated message, messagePayload={}", nodeId, SerialMessage.bb2hex(incomingMessage.getMessagePayload())); int toDecryptLength = incomingMessage.getMessageBuffer().length - 9; byte[] toDecrypt = new byte[toDecryptLength]; System.arraycopy(incomingMessage.getMessageBuffer(), 8, toDecrypt, 0, toDecryptLength); byte[] decryptedBytes = zwaveSecurityCommandClass.decryptMessage(toDecrypt, 0); if (decryptedBytes == null) { logger.error("NODE {}: Failed to decrypt message out of {} .", nodeId, incomingMessage); } else { // call handleApplicationCommandRequest with the decrypted message. Note that we do NOT set // incomingMessage as that needs to be processed below with the original security encapsulated message final SerialMessage decryptedMessage = new SerialMessage(incomingMessage.getMessageClass(), incomingMessage.getMessageType(), incomingMessage.getExpectedReply(), incomingMessage.getPriority()); decryptedMessage.setMessagePayload(decryptedBytes); // Get the new command class with the decrypted contents zwaveCommandClass = resolveZWaveCommandClass(node, decryptedBytes[1], zController); boolean failed = false; // Use a flag bc we need to handle isEncapNonceGet either way if (zwaveCommandClass == null) { failed = true; // Error message was logged in resolveZWaveCommandClass } else { // Note that we do not call node.doesMessageRequireSecurityEncapsulation since it was encapsulated. // Messages that are not required to be are allowed to be, just not the other way around logger.debug( "NODE {}: After decrypt, found Command Class {}, passing to handleApplicationCommandRequest", nodeId, zwaveCommandClass.getCommandClass().getLabel()); zwaveCommandClass.handleApplicationCommandRequest(decryptedMessage, 2, 0); } if (isEncapNonceGet) { // the device also needs another nonce; send it regardless of the success/failure of decryption zwaveSecurityCommandClass.sendNonceReport(); } if (failed) { return false; } } } else { // Message does not require decryption if (node.doesMessageRequireSecurityEncapsulation(incomingMessage)) { // Should have been security encapsulation but wasn't! logger.error( "NODE {}: Command Class {} {} was required to be security encapsulation but it wasn't! Dropping message.", nodeId, zwaveCommandClass.getCommandClass().getKey(), zwaveCommandClass.getCommandClass().getLabel()); // do not call zwaveCommandClass.handleApplicationCommandRequest(); } else { logger.trace("NODE {}: Found Command Class {}, passing to handleApplicationCommandRequest", nodeId, zwaveCommandClass.getCommandClass().getLabel()); zwaveCommandClass.handleApplicationCommandRequest(incomingMessage, 4, 0); } } if (node.getNodeId() == lastSentMessage.getMessageNode()) { checkTransactionComplete(lastSentMessage, incomingMessage); } else { logger.debug("NODE {}: Transaction not completed: node address inconsistent. lastSent={}, incoming={}", lastSentMessage.getMessageNode(), lastSentMessage.getMessageNode(), incomingMessage.getMessageNode()); } return true; } /** * Takes the given commandClassCode and tries to instantiate the corresponding {@link ZWaveCommandClass} * for the given node * * @return the zwave command class for this node or null if it is not possible */ private ZWaveCommandClass resolveZWaveCommandClass(ZWaveNode node, int commandClassCode, ZWaveController zController) { CommandClass commandClass = CommandClass.getCommandClass(commandClassCode & 0xff); if (commandClass == null) { logger.error( String.format("NODE %d: Unsupported command class 0x%02x", node.getNodeId(), commandClassCode)); return null; } logger.debug(String.format("NODE %d: Incoming command class %s (0x%02x)", node.getNodeId(), commandClass.getLabel(), commandClass.getKey())); ZWaveCommandClass zwaveCommandClass = node.getCommandClass(commandClass); // Apparently, this node supports a command class that we did not get (yet) during initialization. // Let's add it now then to support handling this message. if (zwaveCommandClass == null) { logger.debug(String.format("NODE %d: Command class %s (0x%02x) not found, trying to add it.", node.getNodeId(), commandClass.getLabel(), commandClass.getKey())); zwaveCommandClass = ZWaveCommandClass.getInstance(commandClass.getKey(), node, zController); if (zwaveCommandClass == null) { // We got an unsupported command class, leave zwaveCommandClass as null logger.error(String.format("NODE %d: Unsupported zwave command class %s (0x%02x)", node.getNodeId(), commandClass.getLabel(), commandClassCode)); } else { logger.debug(String.format("NODE %d: Adding command class %s (0x%02x)", node.getNodeId(), commandClass.getLabel(), commandClass.getKey())); node.addCommandClass(zwaveCommandClass); } } return zwaveCommandClass; } }