package org.openhab.binding.zwave.internal.protocol.commandclass;
import java.util.Arrays;
import java.util.List;
import org.openhab.binding.zwave.internal.protocol.SerialMessage;
import org.openhab.binding.zwave.internal.protocol.ZWaveNode;
import org.openhab.binding.zwave.internal.protocol.initialization.ZWaveNodeStageAdvancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used only by {@link ZWaveSecurityCommandClassWithInitialization} during device inclusion.
*
* During device inclusion, the security registration process between us and the node
* has multiple stages in order to share our network key with the device. This class
* is used to track our current state in that process and determine next steps.
*
* The specific commands to be exchanged are:
* {@value #INIT_COMMAND_ORDER_LIST}
*
* @author Dave Badia
* @since TODO
*/
class ZWaveSecureInclusionStateTracker {
private static final Logger logger = LoggerFactory.getLogger(ZWaveSecureInclusionStateTracker.class);
/**
* During node inclusion <b>only</b>, this is the order in which commands should be sent and received.
* Commands absent from this list (for example {@link #SECURITY_MESSAGE_ENCAP}) can be sent/received at any time
*/
private final List<Byte> INIT_COMMAND_ORDER_LIST =
Arrays.asList(new Byte[]{
ZWaveSecurityCommandClass.SECURITY_SCHEME_GET,
ZWaveSecurityCommandClass.SECURITY_SCHEME_REPORT,
ZWaveSecurityCommandClass.SECURITY_NETWORK_KEY_SET,
ZWaveSecurityCommandClass.SECURITY_NETWORK_KEY_VERIFY,
ZWaveSecurityCommandClass.SECURITY_COMMANDS_SUPPORTED_GET,
ZWaveSecurityCommandClass.SECURITY_COMMANDS_SUPPORTED_REPORT,
});
private static final boolean HALT_ON_IMPROPER_ORDER = true;
private byte currentStep = INIT_COMMAND_ORDER_LIST.get(0);
/**
* After we send a non-nonce security message, we can wait up to 10 seconds
* for a reply. Then we must exit the inclusion process
*/
private static final int WAIT_TIME_MILLIS = 10000;
/**
* The next {@link SerialMessage} that will be given to {@link ZWaveNodeStageAdvancer}
* when it calls {@link ZWaveSecurityCommandClass#initialize(boolean)}
*/
private SerialMessage nextRequestMessage = null;
/**
* Lock object that will be used for synchronization
*/
private final Object nextMessageLock = new Object();
private String errorState = null;
private long waitForReplyTimeout = 0;
private final ZWaveNode node;
ZWaveSecureInclusionStateTracker(ZWaveNode node) {
this.node = node;
}
/**
* Since these operations are security sensitive we must ensure they are
* executing in the proper sequence
* @param newStep the state we are about to enter
* @return true if the new command was in an acceptable order, false
* if it was not. if false is returned, the response should <b>not</b>
* be sent.
*/
synchronized boolean verifyAndAdvanceState(Byte newStep) {
logger.debug("NODE {}: ZWaveSecurityCommandClass in verifyAndAdvanceState with newstep={}, currentstep={}",
node.getNodeId(), ZWaveSecurityCommandClass.commandToString(newStep), ZWaveSecurityCommandClass.commandToString(currentStep));
if(!INIT_COMMAND_ORDER_LIST.contains(newStep)) {
// Commands absent from EXPECTED_COMMAND_ORDER_LIST are always ok
return true;
}
// Going back to the first step (zero index) is always OK // TODO: DB is it really?
if(INIT_COMMAND_ORDER_LIST.indexOf(newStep) > 0) {
// We have to verify where we are at
int currentIndex = INIT_COMMAND_ORDER_LIST.indexOf(currentStep);
int newIndex = INIT_COMMAND_ORDER_LIST.indexOf(newStep);
// Accept one message back or the same message(device resending last reply) in addition to the normal one message ahead
if(newIndex != currentIndex && newIndex - currentIndex > 1) {
if(HALT_ON_IMPROPER_ORDER) {
setErrorState(String.format("NODE %s: Commands received out of order, aborting current=%s, new=%s",
node.getNodeId(), ZWaveSecurityCommandClass.commandToString(currentStep), ZWaveSecurityCommandClass.commandToString(newStep)));
return false;
} else {
logger.warn("NODE {}: Commands received out of order (warning only, continuing) current={}, new={}",
node.getNodeId(), ZWaveSecurityCommandClass.commandToString(currentStep), ZWaveSecurityCommandClass.commandToString(newStep));
// fall through below
}
}
}
currentStep = newStep;
return true;
}
public void setErrorState(String errorState) {
this.errorState = errorState;
}
public void resetWaitForReplyTimeout() {
waitForReplyTimeout = System.currentTimeMillis() + WAIT_TIME_MILLIS;
}
void setNextRequest(SerialMessage message) {
logger.debug("NODE {}: in InclusionStateTracker.setNextRequest() (current={}) with {}", node.getNodeId(), (nextRequestMessage != null), message);
if(nextRequestMessage != null) {
logger.warn("NODE {}: in InclusionStateTracker.setNextRequest() overriding old message which was never sent of {}", node.getNodeId(), message);
}
verifyAndAdvanceState((byte) (message.getMessagePayloadByte(3) & 0xff));
synchronized(nextMessageLock) {
nextRequestMessage = message;
nextMessageLock.notify();
}
}
/**
* Gets the next message to be sent during the inclusion flow.
* Each message can only get retrieved once
* @return the next message or null if there was none
*/
SerialMessage getNextRequest() {
synchronized(nextMessageLock) {
logger.debug("NODE {}: in InclusionStateTracker.getNextRequest() time left for reply: {}ms, returning {}", node.getNodeId(),
(System.currentTimeMillis() - waitForReplyTimeout), nextRequestMessage);
if(System.currentTimeMillis() > waitForReplyTimeout) {
// waited too long for a reply, secure inclusion failed
setErrorState(WAIT_TIME_MILLIS+"ms passed since last request was sent, secure inclusion failed.");
return null;
}
if(nextRequestMessage != null) {
SerialMessage message = nextRequestMessage;
resetWaitForReplyTimeout();
nextRequestMessage = null;
return message;
}
return null;
}
}
public byte getCurrentStep() {
return currentStep;
}
public String getErrorState() {
return errorState;
}
}