package org.openhab.binding.zwave.internal.protocol.commandclass; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.openhab.binding.zwave.internal.protocol.SerialMessage; import org.openhab.binding.zwave.internal.protocol.ZWaveNode; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass; /** * Used only by {@link ZWaveSecurityCommandClass} * * ZWave security protocol that certain messages be "security encapsulated" * (that is, encrypted and signed). The first step to send a {@link SerialMessage} * securely is break down the payload into one or more security payload frames * that will then be queued up in {@link ZWaveSecurityCommandClass} to await * {@link ZWaveSecurityCommandClass#SECURITY_NONCE_REPORT} messages from the * device. * * @see {@link ZWaveSecurityCommandClass#queueMessageForEncapsulation} * @author Dave Badia * @since TODO */ public class ZWaveSecurityPayloadFrame { // TODO: DB instead of timeout here, tie this back to the sending of the nonce request? private static final long MESSAGE_EXPIRATION_MS = TimeUnit.MINUTES.toMillis(2); /** * The largest amount of payload we can fit into a single * {@link ZWaveSecurityPayloadFrame}. {@link SerialMessage} contents larger than this * must be split into multiple {@link ZWaveSecurityPayloadFrame} */ private static final int SECURITY_PAYLOAD_ONE_PART_SIZE = 28; /** * Sequence byte is zero for messages that fit in a single frame */ static final byte SEQUENCE_BYTE_FOR_SINGLE_FRAME_MESSAGE = 0; /** * Every <b>set</b> of multi frame messages must have unique sequence number. */ private static final AtomicInteger sequenceCounter = new AtomicInteger(0); // metadata fields /** * The time at which this message should be discarded from the encapsulation * queue if no nonce reply has been received */ private final long expirationTime; // data fields private final String logMessage; private final int partNumber; private final int totalParts; private final byte sequenceByte; private final byte[] partBytes; private final SerialMessage originalMessage; public static List<ZWaveSecurityPayloadFrame> convertToSecurityPayload(ZWaveNode node, SerialMessage messageToEncapsulate) { // We need to start with command class byte, so strip off node ID and length from beginning int copyLength = messageToEncapsulate.getMessagePayload().length - 2; byte[] payloadBuffer = new byte[copyLength]; System.arraycopy(messageToEncapsulate.getMessagePayload(), 2, payloadBuffer, 0, copyLength); List<ZWaveSecurityPayloadFrame> list = new ArrayList<ZWaveSecurityPayloadFrame>(); /* * The sequence data in a single byte. The entire byte is zero if the whole * message fit into one frame. If multiple frames are required: * 1st 2 bits: reserved, always 0 * 3rd bit: second frame: 0 for 1st frame, 1 for second frame * 4th bit: sequenced: 0 if the entire message fits in one frame; 1 if more than 1 are required * last 4 bits: sequence counter - used to tell groups of sequenced messages apart. * Must be the same for part 1 and part 2 of a sequenced message */ if (payloadBuffer.length > SECURITY_PAYLOAD_ONE_PART_SIZE) { // Use this byte for both parts, but OR it for each frame byte messageSequnceByte = (byte) sequenceCounter.getAndIncrement(); // Message must be split into two parts byte[] partOneBuffer = new byte[SECURITY_PAYLOAD_ONE_PART_SIZE]; System.arraycopy(payloadBuffer, 0, partOneBuffer, 0, SECURITY_PAYLOAD_ONE_PART_SIZE); byte partOneSequenceByte = (byte) (messageSequnceByte | 0x10); // Sequenced, first frame list.add(new ZWaveSecurityPayloadFrame(node, 1, 2, partOneBuffer, partOneSequenceByte, messageToEncapsulate)); byte partTwoSequenceByte = (byte) (messageSequnceByte | 0x30); // Sequenced, second frame int part2Length = payloadBuffer.length - SECURITY_PAYLOAD_ONE_PART_SIZE; byte[] partTwoBuffer = new byte[part2Length]; System.arraycopy(payloadBuffer, SECURITY_PAYLOAD_ONE_PART_SIZE, partTwoBuffer, 0, part2Length); list.add(new ZWaveSecurityPayloadFrame(node, 2, 2, partTwoBuffer, partTwoSequenceByte, messageToEncapsulate)); } else { // The entire message can be encapsulated as one list.add(new ZWaveSecurityPayloadFrame(node, payloadBuffer, messageToEncapsulate)); } return list; } private ZWaveSecurityPayloadFrame(ZWaveNode node, int partNumber, int totalParts, byte[] partBuffer, byte sequenceByte, SerialMessage originalMessage) { this.originalMessage = originalMessage; this.partNumber = partNumber; this.partBytes = partBuffer; this.totalParts = totalParts; this.sequenceByte = sequenceByte; this.expirationTime = System.currentTimeMillis() + MESSAGE_EXPIRATION_MS; // Replace the original payload bytes with ours String ourSerialMessageString = originalMessage.toString(); int index = ourSerialMessageString.indexOf("payload"); if (index > 0) { ourSerialMessageString = ourSerialMessageString.substring(0, index); ourSerialMessageString = new StringBuilder(ourSerialMessageString).append("payload = ") .append(SerialMessage.bb2hex(partBytes)).toString(); } this.logMessage = String.format("NODE %s: SecurityPayload (part %d of %d) for %s : %s", node.getNodeId(), partNumber, totalParts, CommandClass.getCommandClass(originalMessage.getMessageBuffer()[6] & 0xff), ourSerialMessageString); } private ZWaveSecurityPayloadFrame(ZWaveNode node, byte[] messageBuffer, SerialMessage originalMessage) { this(node, 1, 1, messageBuffer, SEQUENCE_BYTE_FOR_SINGLE_FRAME_MESSAGE, originalMessage); } protected int getTotalParts() { return totalParts; } protected String getLogMessage() { return logMessage; } protected byte[] getMessageBytes() { return partBytes; } protected int getPart() { return partNumber; } protected byte getSequenceByte() { return sequenceByte; } protected int getLength() { return partBytes.length; } @Override public String toString() { return logMessage; } protected long getExpirationTime() { return expirationTime; } protected SerialMessage getOriginalMessage() { return originalMessage; } }