package org.openhab.binding.zwave.internal.protocol; import java.util.concurrent.atomic.AtomicBoolean; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecurityCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecurityPayloadFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link SerialMessage} which has been security encapsulated per the * semantics of the zwave spec. * * @author Dave Badia * @since TODO * @see ZWaveSecurityCommandClass */ public class SecurityEncapsulatedSerialMessage extends SerialMessage { private static final Logger logger = LoggerFactory.getLogger(SecurityEncapsulatedSerialMessage.class); private static final byte UNSET = -1; /** * The original message that was encapsulated in {@link ZWaveSecurityCommandClass} encapsulated messages */ private SerialMessage messageBeingEncapsulated = null; /** * The command class that, when received, indicates that this security transaction is complete */ private byte transactionCompleteCommandClass; /** * The command byte that, when received in conjunction with * {@link SecurityEncapsulatedSerialMessage#transactionCompleteCommandClass}, * indicates that this security transaction is complete. This is optional, and be default will be set to * {@link #UNSET} */ private byte transactionCompleteCommand = UNSET; private AtomicBoolean securityTransactionComplete = new AtomicBoolean(false); private long transmittedAt = UNSET; private byte deviceNonceId; /** * The original {@link ZWaveSecurityPayloadFrame} from which this message was formed */ private ZWaveSecurityPayloadFrame securityPayload; // TODO: DB inherit messageClass and messageType too? public SecurityEncapsulatedSerialMessage(SerialMessageClass messageClass, SerialMessageType messageType, SerialMessage messageBeingEncapsulated) { // Inherit most fields super(messageBeingEncapsulated.getMessageNode(), messageClass, messageType, messageBeingEncapsulated.getExpectedReply(), ZWaveSecurityCommandClass.SECURITY_MESSAGE_PRIORITY); this.messageBeingEncapsulated = messageBeingEncapsulated; // Inherit attempts from messageBeingEncapsulated since each retry requires a new // SecurityEncapsulatedSerialMessage object this.attempts = messageBeingEncapsulated.attempts; this.transactionCompleteCommandClass = messageBeingEncapsulated.getMessagePayload()[2]; } public boolean isSecurityTransactionComplete() { boolean result = hasBeenTransmitted(); if (result) { result = securityTransactionComplete.get(); // TODO: DB set isCOmpleted to true on door lock set // boolean isDoorLockSetMessage = bytesAreEqual(securityPayload.getMessageBytes()[0], // ZWaveCommandClass.CommandClass.DOOR_LOCK.getKey()) // && bytesAreEqual(securityPayload.getMessageBytes()[1], ZWaveDoorLockCommandClass.DOORLOCK_SET); } logger.debug("NODE {}: securityTransactionComplete={}, payload=({}), transmitted={}, msSinceTransmitted={}", super.messageNode, result, SerialMessage.bb2hex(messageBeingEncapsulated.getMessagePayload()), hasBeenTransmitted(), hasBeenTransmitted() ? (System.currentTimeMillis() - getTransmittedAt()) : ""); return result; } /** * Checks to see if the given response satisfies the security transaction complete conditions * Call {@link #isSecurityTransactionComplete()} afterwards to see if it did */ public void securityReponseReceived(byte[] payloadBytes) { if (isSecurityTransactionComplete()) { logger.trace("NODE {}: securityReponseReceived is already true, nothing to check", getMessageNode()); return; } // TODO: DB boolean appCommandHandler = ZWaveSecurityCommandClass.bytesAreEqual(payloadBytes[1], // SerialMessageClass.ApplicationCommandHandler.getKey()); boolean result = payloadBytes[1] == transactionCompleteCommandClass; if (result && transactionCompleteCommand != UNSET) { result = ZWaveSecurityCommandClass.bytesAreEqual(transactionCompleteCommand, payloadBytes[3]); } logger.debug("NODE {}: securityReponseReceived={} for {}. Class: want={},got={}; Command: want={},got={}", getMessageNode(), result, SerialMessage.bb2hex(payloadBytes), CommandClass.getCommandClass(transactionCompleteCommandClass & 0xff), CommandClass.getCommandClass(payloadBytes[1] & 0xff), transactionCompleteCommand == UNSET ? "ANY" : SerialMessage.b2hex(transactionCompleteCommand), SerialMessage.b2hex(payloadBytes[2])); if (result) { securityTransactionComplete.set(true); } } /** * @return the original message that was encapsulated, or null if this * {@link SerialMessage} is not a {@link ZWaveSecurityCommandClass} encapsulated message type */ protected SerialMessage getMessageBeingEncapsulated() { return messageBeingEncapsulated; } public long getTransmittedAt() { return transmittedAt; } protected void setTransmittedAt() { this.transmittedAt = System.currentTimeMillis(); } public boolean hasBeenTransmitted() { return this.transmittedAt != UNSET; }; public void setDeviceNonceId(byte nonceId) { this.deviceNonceId = nonceId; } public byte getDeviceNonceId() { return deviceNonceId; } public void setSecurityPayload(ZWaveSecurityPayloadFrame securityPayload) { this.securityPayload = securityPayload; } public ZWaveSecurityPayloadFrame getSecurityPayload() { return securityPayload; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder buf = new StringBuilder(super.toString()); if (getMessageBeingEncapsulated() != null) { buf.append(" encapsulates: ").append(getMessageBeingEncapsulated().toString()); } return buf.toString(); } }