Skip to content

Commit

Permalink
Implement full network backup / restore functionality
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Jackson <[email protected]>
  • Loading branch information
cdjackson committed Sep 28, 2024
1 parent c038c6f commit 5b4e4ff
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -27,6 +29,7 @@
import com.zsmartsystems.zigbee.database.ZclAttributeDao;
import com.zsmartsystems.zigbee.database.ZclClusterDao;
import com.zsmartsystems.zigbee.database.ZigBeeEndpointDao;
import com.zsmartsystems.zigbee.database.ZigBeeNetworkBackupDao;
import com.zsmartsystems.zigbee.database.ZigBeeNetworkDataStore;
import com.zsmartsystems.zigbee.database.ZigBeeNodeDao;
import com.zsmartsystems.zigbee.security.ZigBeeKey;
Expand All @@ -47,17 +50,23 @@ public class ZigBeeDataStore implements ZigBeeNetworkDataStore {
*/
private final static Logger logger = LoggerFactory.getLogger(ZigBeeDataStore.class);

private final static String CHARSET = "UTF-8";
private final static String DATABASE = "database/";
private final static String KEYSTORE = "keystore";
private final static String BACKUP = "backup";

private final String networkId;

public ZigBeeDataStore(String networkId) {
this.networkId = "database/" + networkId + "/";
File file = new File(this.networkId + "/" + KEYSTORE);
if (file.exists()) {
return;
this.networkId = DATABASE + networkId + "/";
File file;

file = new File(this.networkId + "/" + KEYSTORE);
if (!file.exists() && !file.mkdirs()) {
logger.error("Error creating network database folder {}", file);
}
if (!file.mkdirs()) {
file = new File(this.networkId + "/" + BACKUP);
if (!file.exists() && !file.mkdirs()) {
logger.error("Error creating network database folder {}", file);
}
}
Expand Down Expand Up @@ -99,6 +108,10 @@ private File getFile(IeeeAddress address) {
return new File(networkId + address + ".xml");
}

private File getFile(UUID uuid) {
return new File(DATABASE + BACKUP + "/" + uuid + ".xml");
}

private File getFile(String key) {
return new File(networkId + KEYSTORE + "/" + key + ".xml");
}
Expand Down Expand Up @@ -135,7 +148,7 @@ public ZigBeeNodeDao readNode(IeeeAddress address) {
File file = getFile(address);

ZigBeeNodeDao node = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), CHARSET))) {
node = (ZigBeeNodeDao) stream.fromXML(reader);
reader.close();
logger.info("{}: ZigBee reading network state complete.", address);
Expand All @@ -151,7 +164,7 @@ public void writeNode(ZigBeeNodeDao node) {
XStream stream = openStream();
File file = getFile(node.getIeeeAddress());

try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), CHARSET))) {
stream.marshal(node, new PrettyPrintWriter(writer));
writer.close();
logger.info("{}: ZigBee saving network state complete.", node.getIeeeAddress());
Expand All @@ -173,7 +186,7 @@ public void writeObject(String key, Object object) {
XStream stream = openStream();
File file = getFile(key);

try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), CHARSET))) {
stream.marshal(object, new PrettyPrintWriter(writer));
writer.close();
logger.info("{}: ZigBee saving key complete.", key);
Expand All @@ -187,4 +200,41 @@ public Object readObject(String key) {
return null;
}

@Override
public boolean writeBackup(ZigBeeNetworkBackupDao backup) {
XStream stream = openStream();
File file = getFile(backup.getUuid());

try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), CHARSET))) {
stream.marshal(backup, new PrettyPrintWriter(writer));
writer.close();
logger.info("ZigBee saving network backup {} complete.", backup.getUuid());
} catch (Exception e) {
logger.error("Error writing network backup: ", backup.getUuid(), e);
}

return false;
}

@Override
public ZigBeeNetworkBackupDao readBackup(UUID uuid) {
XStream stream = openStream();
File file = getFile(uuid);

ZigBeeNetworkBackupDao backup = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), CHARSET))) {
backup = (ZigBeeNetworkBackupDao) stream.fromXML(reader);
reader.close();
logger.info("ZigBee reading network backup {} complete.", uuid);
} catch (Exception e) {
logger.error("{}: Error reading network backup: ", uuid, e);
}

return backup;
}

@Override
public Set<ZigBeeNetworkBackupDao> listBackups() {
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public String getCommand() {

@Override
public String getDescription() {
return "Sets the link key int the dongle, optionally computing the MMO Hash from the join code";
return "Sets the link key in the dongle, optionally computing the MMO Hash from the join code";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,11 @@
package com.zsmartsystems.zigbee.console;

import java.io.PrintStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import com.zsmartsystems.zigbee.ExtendedPanId;
import com.zsmartsystems.zigbee.ZigBeeChannel;
import com.zsmartsystems.zigbee.ZigBeeNetworkManager;
import com.zsmartsystems.zigbee.security.ZigBeeKey;
import com.zsmartsystems.zigbee.transport.DeviceType;

/**
* Console command to backup the network. This prints the important information required to backup the coordinator to a
* string which can then be parsed and set back into the same, or a different coordinator.
* Key counters are incremented with an assumption of 1 frame per second since the backup.
* Console command to backup the network.
*
* @author Chris Jackson
*
Expand All @@ -36,12 +25,12 @@ public String getCommand() {

@Override
public String getDescription() {
return "Backup or restore the coordinator.";
return "Backup or restore the network.";
}

@Override
public String getSyntax() {
return "[RESTORE STRING]";
return "[BACKUP] [RESTORE UUID] [LIST]";
}

@Override
Expand All @@ -52,153 +41,34 @@ public String getHelp() {
@Override
public void process(ZigBeeNetworkManager networkManager, String[] args, PrintStream out)
throws IllegalArgumentException {
if (args.length > 2) {
if (args.length < 2) {
throw new IllegalArgumentException("Invalid number of arguments");
}

if (args.length == 1) {
out.println(netBackup(networkManager));
return;
switch (args[1].toUpperCase()) {
case "LIST":
listBackups();
break;
case "BACKUP":
createBackup();
break;
case "RESTORE":
restoreBackup();
break;
default:
throw new IllegalArgumentException("Unknown option '" + args[1] + "'");
}

String[] parameters = args[1].split("\\>");
netRestore(networkManager, parameters, out);
}

private String netBackup(ZigBeeNetworkManager networkManager) {
TimeZone timezone = TimeZone.getTimeZone("UTC");
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
formatter.setTimeZone(timezone);
String nowAsIso = formatter.format(new Date());

int pan = networkManager.getZigBeePanId();
ExtendedPanId epan = networkManager.getZigBeeExtendedPanId();
ZigBeeChannel channel = networkManager.getZigBeeChannel();
ZigBeeKey networkKey = networkManager.getZigBeeNetworkKey();
ZigBeeKey linkKey = networkManager.getZigBeeLinkKey();
private void listBackups() {

StringBuilder builder = new StringBuilder();
builder.append(nowAsIso);
builder.append('>');
builder.append(DeviceType.COORDINATOR); // Future proof
builder.append('>');
builder.append(String.format("%04X", pan));
builder.append('>');
builder.append(epan.toString());
builder.append('>');
builder.append(channel.toString());
builder.append('>');
builder.append(networkKey.toString());
builder.append('>');
if (networkKey.hasSequenceNumber()) {
builder.append(String.format("%02X", networkKey.getSequenceNumber()));
}
builder.append('>');
if (networkKey.hasIncomingFrameCounter()) {
builder.append(String.format("%08X", networkKey.getIncomingFrameCounter()));
}
builder.append('>');
if (networkKey.hasOutgoingFrameCounter()) {
builder.append(String.format("%08X", networkKey.getOutgoingFrameCounter()));
}
builder.append('>');
builder.append(linkKey.toString());
builder.append('>');
if (linkKey.hasSequenceNumber()) {
builder.append(String.format("%02X", linkKey.getSequenceNumber()));
}
builder.append('>');
if (linkKey.hasIncomingFrameCounter()) {
builder.append(String.format("%08X", linkKey.getIncomingFrameCounter()));
}
builder.append('>');
if (linkKey.hasOutgoingFrameCounter()) {
builder.append(String.format("%08X", linkKey.getOutgoingFrameCounter()));
}

return builder.toString();
}

private void netRestore(ZigBeeNetworkManager networkManager, String[] parameters, PrintStream out)
throws IllegalArgumentException {
int secondsSinceBackup;
try {
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date backupTime = formatter.parse(parameters[0]);
secondsSinceBackup = (int) ((new Date().getTime() - backupTime.getTime()) / 1000);
} catch (ParseException e) {
throw new IllegalArgumentException("Error parsing backup time");
}

int pan = Integer.parseInt(parameters[2], 16);
ExtendedPanId epan = new ExtendedPanId(parameters[3]);
ZigBeeChannel channel = ZigBeeChannel.valueOf(parameters[4]);
ZigBeeKey networkKey = parseKey(secondsSinceBackup, parameters[5], parameters[6], parameters[7], parameters[8]);
ZigBeeKey linkKey = parseKey(secondsSinceBackup, parameters[9], parameters[10], parameters[11], parameters[12]);
private void createBackup() {

out.println("Restoring network as " + parameters[1] + " after " + getTimeSince(secondsSinceBackup));
out.println("PAN ID :");
out.println("Extended PAN ID :");
out.println("Channel :");
out.println("Network Key :" + networkKey);
if (networkKey.hasSequenceNumber()) {
out.println("Network Key Sequence :" + networkKey.getSequenceNumber());
}
if (networkKey.hasIncomingFrameCounter()) {
out.println("Network Key Incoming Counter :" + networkKey.getIncomingFrameCounter());
}
if (networkKey.hasIncomingFrameCounter()) {
out.println("Network Key Outgoing Counter :" + networkKey.getOutgoingFrameCounter());
}
out.println("Link Key :" + linkKey);
if (linkKey.hasSequenceNumber()) {
out.println("Link Key Sequence :" + linkKey.getSequenceNumber());
}
if (linkKey.hasIncomingFrameCounter()) {
out.println("Link Key Incoming Counter :" + linkKey.getIncomingFrameCounter());
}
if (linkKey.hasIncomingFrameCounter()) {
out.println("Link Key Outgoing Counter :" + linkKey.getOutgoingFrameCounter());
}

networkManager.setZigBeePanId(pan);
networkManager.setZigBeeExtendedPanId(epan);
networkManager.setZigBeeChannel(channel);
networkManager.setZigBeeNetworkKey(networkKey);
networkManager.setZigBeeLinkKey(linkKey);
networkManager.startup(true);
}

private ZigBeeKey parseKey(int secondsSinceBackup, String keyString, String sequence, String incount,
String outcount) {
ZigBeeKey key = new ZigBeeKey(keyString);
try {
if (!sequence.isEmpty()) {
key.setSequenceNumber(Integer.parseInt(sequence, 16));
}
if (!incount.isEmpty()) {
key.setIncomingFrameCounter(Integer.parseInt(incount, 16) + secondsSinceBackup);
}
if (!outcount.isEmpty()) {
key.setOutgoingFrameCounter(Integer.parseInt(outcount, 16) + secondsSinceBackup);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Error parsing key parameters");
}

return key;
}
private void restoreBackup() {

private String getTimeSince(int secondsSince) {
if (secondsSince < 60) {
return secondsSince + " seconds";
}
if (secondsSince < 3600) {
return secondsSince / 60 + " minutes";
}
if (secondsSince < 86400) {
return secondsSince / 3600 + " hours";
}
return secondsSince / 86400 + " days";
}
}
Loading

0 comments on commit 5b4e4ff

Please sign in to comment.