use of org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode in project openremote by openremote.
the class BaseMeshNetwork method deleteNode.
/**
* Deletes a mesh node from the list of provisioned nodes
*
* <p>
* Note that deleting a node manually will not reset the node, but only be deleted from the stored list of provisioned nodes.
* However you may still be able to connect to the same node, if it was not reset since the network may still exist. This
* would be useful to in case if a node was physically reset and needs to be removed from the mesh network/db
* </p>
*
* @param meshNode node to be deleted
* @return true if deleted and false otherwise
*/
public synchronized boolean deleteNode(ProvisionedMeshNode meshNode) {
// Let's go through the nodes and delete if a node exists
boolean nodeDeleted = false;
for (ProvisionedMeshNode node : nodes) {
if (node.getUuid().equalsIgnoreCase(meshNode.getUuid())) {
excludeNode(node);
nodes.remove(node);
notifyNodeDeleted(node);
nodeDeleted = true;
break;
}
}
// We must also check if there is a provisioner based on the node we deleted
if (nodeDeleted) {
for (Provisioner provisioner : provisioners) {
if (provisioner.getProvisionerUuid().equalsIgnoreCase(meshNode.getUuid())) {
provisioners.remove(provisioner);
notifyProvisionerDeleted(provisioner);
break;
}
}
}
return nodeDeleted;
}
use of org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode in project openremote by openremote.
the class MeshNetwork method nextAvailableUnicastAddress.
/**
* Returns the next unicast address for a node based on the number of elements
* and the range allocated to the provisioner.
* P.S. When setting up a new network don't forget to assign an address to the provisioner.
* When importing a network make sure to create a new provisioner with a different address
* which is the recommended approach. However you can also use the same provisioner
* with a different address.
*
* @param elementCount Element count
* @param provisioner provisioner
* @return Allocated unicast address or -1 if none
* @throws IllegalArgumentException if there is no allocated unicast range to the provisioner
*/
public synchronized int nextAvailableUnicastAddress(final int elementCount, final Provisioner provisioner) throws IllegalArgumentException {
if (provisioner.getAllocatedUnicastRanges().isEmpty()) {
throw new IllegalArgumentException("Please allocate a unicast address range to the provisioner");
}
// Populate all addresses that are currently in use
final ArrayList<Integer> usedAddresses = new ArrayList<>();
for (ProvisionedMeshNode node : nodes) {
usedAddresses.addAll(node.getElements().keySet());
}
// Excluded addresses with the current IvIndex and current IvIndex - 1 must be considered as addresses in use.
if (networkExclusions.get(ivIndex.getIvIndex()) != null)
usedAddresses.addAll(networkExclusions.get(ivIndex.getIvIndex()));
if (networkExclusions.get(ivIndex.getIvIndex() - 1) != null)
usedAddresses.addAll(networkExclusions.get(ivIndex.getIvIndex() - 1));
Collections.sort(usedAddresses);
// Iterate through all nodes just once, while iterating over ranges.
int index = 0;
for (AllocatedUnicastRange range : provisioner.getAllocatedUnicastRanges()) {
// Start from the beginning of the current range.
int address = range.getLowAddress();
// Iterate through nodes that weren't checked yet in essence the used addresses which include the excluded adresses
for (int usedAddress : usedAddresses) {
// Skip nodes with addresses below the range.
if (address > usedAddress) {
continue;
}
// If we found a space before the current node, return the address.
if (usedAddress > (address + (elementCount - 1))) {
return address;
}
// Else, move the address to the next available address.
address = usedAddress + 1;
// If the new address is outside of the range, go to the next one.
if (range.highAddress < address + (elementCount - 1)) {
break;
}
}
if (range.getHighAddress() >= address + (elementCount - 1)) {
return address;
}
}
// No address was found :(
return -1;
}
use of org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode in project openremote by openremote.
the class MeshProvisioningHandler method parseProvisioningState.
private void parseProvisioningState(final UnprovisionedMeshNode unprovisionedMeshNode, final byte[] data) {
isProvisioningPublicKeySent = false;
isProvisioneePublicKeyReceived = false;
if (data[1] == ProvisioningState.State.PROVISIONING_COMPLETE.getState()) {
provisioningState = new ProvisioningCompleteState(unprovisionedMeshNode);
// Generate the network id and store it in the mesh node, this is needed to reconnect to the device at a later stage.
final ProvisionedMeshNode provisionedMeshNode = new ProvisionedMeshNode(unprovisionedMeshNode);
mInternalMeshManagerCallbacks.onNodeProvisioned(provisionedMeshNode);
mStatusCallbacks.onProvisioningCompleted(provisionedMeshNode, ProvisioningState.States.PROVISIONING_COMPLETE, data);
} else {
final ProvisioningFailedState provisioningFailedState = new ProvisioningFailedState();
provisioningState = provisioningFailedState;
if (provisioningFailedState.parseData(data)) {
mStatusCallbacks.onProvisioningFailed(unprovisionedMeshNode, ProvisioningState.States.PROVISIONING_FAILED, data);
}
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode in project openremote by openremote.
the class MeshManagerApi method parseNotifications.
/**
* Parses notifications received by the client.
*
* @param unsegmentedPdu pdu received by the client.
*/
private void parseNotifications(final byte[] unsegmentedPdu) {
try {
switch(unsegmentedPdu[0]) {
case PDU_TYPE_NETWORK:
// MeshNetwork PDU
LOG.info("Received network pdu: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshMessageHandler.parseMeshPduNotifications(unsegmentedPdu, mMeshNetwork);
break;
case PDU_TYPE_MESH_BEACON:
// Validate SNBs against all network keys
NetworkKey networkKey;
for (int i = 0; i < mMeshNetwork.getNetKeys().size(); i++) {
networkKey = mMeshNetwork.getNetKeys().get(i);
final byte[] receivedBeaconData = new byte[unsegmentedPdu.length - 1];
System.arraycopy(unsegmentedPdu, 1, receivedBeaconData, 0, receivedBeaconData.length);
final SecureNetworkBeacon receivedBeacon = new SecureNetworkBeacon(receivedBeaconData);
final byte[] n = networkKey.getTxNetworkKey();
final int flags = receivedBeacon.getFlags();
final byte[] networkId = SecureUtils.calculateK3(n);
final int ivIndex = receivedBeacon.getIvIndex().getIvIndex();
LOG.info("Received mesh beacon: " + receivedBeacon.toString());
final SecureNetworkBeacon localSecureNetworkBeacon = SecureUtils.createSecureNetworkBeacon(n, flags, networkId, ivIndex);
// Check the the beacon received is a valid by matching the authentication values
if (Arrays.equals(receivedBeacon.getAuthenticationValue(), localSecureNetworkBeacon.getAuthenticationValue())) {
LOG.info("Secure Network Beacon beacon authenticated.");
// beacon on a secondary subnet, it will disregard it.
if (mMeshNetwork.getPrimaryNetworkKey() != null && networkKey.keyIndex != 0) {
LOG.info("Discarding beacon for secondary subnet with network key index: " + networkKey.keyIndex);
return;
}
// Get the last IV Index.
// / The last used IV Index for this mesh network.
final IvIndex lastIvIndex = mMeshNetwork.getIvIndex();
LOG.info("Last IV Index: " + lastIvIndex.getIvIndex());
// / The date of the last change of IV Index or IV Update Flag.
final Calendar lastTransitionDate = lastIvIndex.getTransitionDate();
// / A flag whether the IV has recently been updated using IV Recovery procedure.
// / The at-least-96h requirement for the duration of the current state will not apply.
// / The node shall not execute more than one IV Index Recovery within a period of 192 hours.
final boolean isIvRecoveryActive = lastIvIndex.getIvRecoveryFlag();
// / The test mode disables the 96h rule, leaving all other behavior unchanged.
final boolean isIvTestModeActive = ivUpdateTestModeActive;
final boolean flag = allowIvIndexRecoveryOver42;
if (!receivedBeacon.canOverwrite(lastIvIndex, lastTransitionDate, isIvRecoveryActive, isIvTestModeActive, flag)) {
String numberOfHoursSinceDate = ((Calendar.getInstance().getTimeInMillis() - lastTransitionDate.getTimeInMillis()) / (3600 * 1000)) + "h";
LOG.info("Discarding beacon " + receivedBeacon.getIvIndex() + ", last " + lastIvIndex.getIvIndex() + ", changed: " + numberOfHoursSinceDate + "ago, test mode: " + ivUpdateTestModeActive);
return;
}
final IvIndex receivedIvIndex = receivedBeacon.getIvIndex();
mMeshNetwork.ivIndex = new IvIndex(receivedIvIndex.getIvIndex(), receivedIvIndex.isIvUpdateActive(), lastTransitionDate);
if (mMeshNetwork.ivIndex.getIvIndex() > lastIvIndex.getIvIndex()) {
LOG.info("Applying: " + mMeshNetwork.ivIndex.getIvIndex());
}
// the Node shall reset the sequence number to 0x000000.
if (mMeshNetwork.ivIndex.getTransmitIvIndex() > lastIvIndex.getTransmitIvIndex()) {
LOG.info("Resetting local sequence numbers to 0");
final Provisioner provisioner = mMeshNetwork.getSelectedProvisioner();
final ProvisionedMeshNode node = mMeshNetwork.getNode(provisioner.getProvisionerUuid());
node.setSequenceNumber(0);
}
// Updating the iv recovery flag
if (lastIvIndex != mMeshNetwork.ivIndex) {
final boolean ivRecovery = mMeshNetwork.getIvIndex().getIvIndex() > lastIvIndex.getIvIndex() + 1 && !receivedBeacon.getIvIndex().isIvUpdateActive();
mMeshNetwork.getIvIndex().setIvRecoveryFlag(ivRecovery);
}
if (!mMeshNetwork.ivIndex.getIvRecoveryFlag()) {
final Iterator<Map.Entry<Integer, ArrayList<Integer>>> iterator = mMeshNetwork.networkExclusions.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<Integer, ArrayList<Integer>> exclusions = iterator.next();
final int expectedIncrement = exclusions.getKey() + 2;
if (mMeshNetwork.ivIndex.getIvIndex() >= expectedIncrement) {
// Clear the last known sequence number of addresses that are to be removed from the exclusion list.
// Decided to retain the last known sequence number as the IV Indexes increment the sequence number
// will be greater than the last known anyways
// for (Integer address : mMeshNetwork.networkExclusions.get(expectedIncrement)) {
// mMeshNetwork.sequenceNumbers.removeAt(address);
// }
iterator.remove();
}
}
}
}
}
break;
case PDU_TYPE_PROXY_CONFIGURATION:
// Proxy configuration
LOG.info("Received proxy configuration message: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshMessageHandler.parseMeshPduNotifications(unsegmentedPdu, mMeshNetwork);
break;
case PDU_TYPE_PROVISIONING:
// Provisioning PDU
LOG.info("Received provisioning message: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshProvisioningHandler.parseProvisioningNotifications(unsegmentedPdu);
break;
}
} catch (ExtendedInvalidCipherTextException ex) {
// TODO handle decryption failure
} catch (IllegalArgumentException ex) {
LOG.severe("Parsing notification failed: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true) + " - " + ex.getMessage());
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode in project openremote by openremote.
the class MeshManagerApi method generateMeshNetwork.
private MeshNetwork generateMeshNetwork(int provisionerAddress) {
final String meshUuid = UUID.randomUUID().toString().toUpperCase(Locale.US);
final MeshNetwork network = new MeshNetwork(meshUuid);
// network.netKeys = generateNetKeys(meshUuid);
// network.appKeys = generateAppKeys(meshUuid);
// final AllocatedUnicastRange unicastRange = new AllocatedUnicastRange(0x0001, 0x199A);
final AllocatedUnicastRange unicastRange = new AllocatedUnicastRange(provisionerAddress, provisionerAddress);
final AllocatedGroupRange groupRange = new AllocatedGroupRange(0xC000, 0xCC9A);
final AllocatedSceneRange sceneRange = new AllocatedSceneRange(0x0001, 0x3333);
final Provisioner provisioner = network.createProvisioner("nRF Mesh Provisioner", unicastRange, groupRange, sceneRange);
final int unicast = provisioner.getAllocatedUnicastRanges().get(0).getLowAddress();
provisioner.assignProvisionerAddress(unicast);
network.selectProvisioner(provisioner);
network.addProvisioner(provisioner);
final ProvisionedMeshNode node = network.getNode(unicast);
if (node != null) {
network.unicastAddress = node.getUnicastAddress() + (node.getNumberOfElements() - 1);
} else {
network.unicastAddress = 1;
}
network.lastSelected = true;
// Clear the sequence numbers first
network.sequenceNumbers.clear();
network.loadSequenceNumbers();
ivUpdateTestModeActive = false;
allowIvIndexRecoveryOver42 = false;
return network;
}
Aggregations