Skip to content

Commit

Permalink
Split network and node configuration (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
ogimenez-wirepas authored Sep 3, 2024
1 parent 6328fa1 commit 96722db
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 102 deletions.
73 changes: 41 additions & 32 deletions examples/provisioning_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,61 @@
# takes precedence over the individual components.

# Format is:
#
# UID : (mandatory) Ex: test_node
#
# version: 1
# networks:
# network_name: (mandatory)(string) Ex: test_network
# address: (optional)(uint) Ex: 0x1012EE
# channel: (optional)(uint) Ex: 13
# authentication_key : (mandatory)(16 bytes string) Ex: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
# encryption_key : (mandatory)(16 bytes string) Ex: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
#
# node_name : (mandatory)(string) Ex: test_node
# method : (mandatory)(Unsecured:0, Secured:1, Extended UID:3) Ex: 0
# factory_key : (only for secured method)(32 bytes string, [0:15 Auth key][16:31 Enc Key])
# authentication_key : (mandatory)(16 bytes string) Ex: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
# encryption_key : (mandatory)(16 bytes string) Ex: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
# factory_key : (only for methods 1 and 3)(32 bytes string, [0:15 Auth key][16:31 Enc Key])
# node_uid : (optional)(16 bytes string) Ex: 0x7e 0x71 0xe5 0xd7 0x22 0xef 0x0f 0x4b 0xa8 0x7d 0x44 0xd4 0xe0 0xe5 0xb5 0x7d
# node_uid_type : (optional)(1 byte string) Ex: 0x01
# node_authenticator_uid_type : (optional)(1 byte string) Ex: 0x01
# node_authenticator_uid : (optional)(16 bytes string) Ex: 0xb3 0x43 0x33 0x00 0x93 0x81 0x08 0x4a 0x8d 0xb3 0xaa 0x9e 0x53 0xd2 0x2a 0x1e
# uid : (optional)(34 bytes string) Ex: 0x01 0xb3 0x43 0x33 0x00 0x93 0x81 0x08 0x4a 0x8d 0xb3 0xaa 0x9e 0x53 0xd2 0x2a 0x1e 0x01 0x7e 0x71 0xe5 0xd7 0x22 0xef 0x0f 0x4b 0xa8 0x7d 0x44 0xd4 0xe0 0xe5 0xb5 0x7d
# network_address : (optional)(uint) Ex: 0x1012EE
# network_channel : (optional)(uint) Ex: 13
# network : (mandatory)(string) Ex: test_network
# node_id : (optional)(uint) Ex: 0x11
# node_role : (optional)(1 byte string) Ex: 0x41
# user_specific : (optional) indexes [128:255]
# 128 : 'some_string'
# 129 : 0x34


test_node_extended:
method : 3
node_uid: 0x7e 0x71 0xe5 0xd7 0x22 0xef 0x0f 0x4b 0xa8 0x7d 0x44 0xd4 0xe0 0xe5 0xb5 0x7d
node_uid_type: 0x01
networks:
network_demo:
address: 1053422
authentication_key: '0x0102030405060708090a0b0c0d0e0f10'
channel: 2
encryption_key: '0x0102030405060708090a0b0c0d0e0f10'
network_prod:
address: 2243501
authentication_key: '0x100f0e0d0c0b0a090807060504030201'
channel: 7
encryption_key: '0x100f0e0d0c0b0a090807060504030201'
nodes:
test_node_extended:
authenticator_uid: 0xb3 0x43 0x33 0x00 0x93 0x81 0x08 0x4a 0x8d 0xb3 0xaa 0x9e 0x53 0xd2 0x2a 0x1e
authenticator_uid_type: 0x01
factory_key : 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F
network_address : 0x1012EE
network_channel : 2
node_id : 0x11
authentication_key: 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10
encryption_key: 0x10 0x0F 0x0E 0x0D 0x0C 0x0B 0x0A 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01
test_node_secure:
method: 1
uid: 0x58 0xc8 0x12 0xad 0x37 0xe8 0x36 0x4a 0xa1 0x1f 0x1c 0xbc 0x63 0x3e 0x8e 0x34
authenticator_uid_type: 1
factory_key: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F
network_address: 0x1012EE
network_channel: 2
node_id: 0x11
authentication_key: 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10
encryption_key: 0x10 0x0F 0x0E 0x0D 0x0C 0x0B 0x0A 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01
test_node_insecure:
method: 3
network: network_prod
node_id: 17
node_uid: 0x7e 0x71 0xe5 0xd7 0x22 0xef 0x0f 0x4b 0xa8 0x7d 0x44 0xd4 0xe0 0xe5 0xb5 0x7d
node_uid_type: 1
test_node_insecure:
method: 0
network: network_demo
node_id: 17
uid: 0x41 0xb1 0x85 0x7a 0x0f 0xb6 0xb1 0x48 0xa5 0xe4 0xb9 0xb6 0x03 0x53 0x1b 0x3b
network_address: 0x1012EE
network_channel: 2
node_id: 0x11
authentication_key: 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10
encryption_key: 0x10 0x0F 0x0E 0x0D 0x0C 0x0B 0x0A 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01
test_node_secure:
factory_key: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0X09 0X0A 0X0B 0X0C 0X0D 0X0E 0X0F
method: 1
network: network_prod
node_id: 17
uid: 0x58 0xc8 0x12 0xad 0x37 0xe8 0x36 0x4a 0xa1 0x1f 0x1c 0xbc 0x63 0x3e 0x8e 0x34
version: 1
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
wirepas-mqtt-library==1.2.5
cbor2==5.6.4
PyYAML==6.0.1
pycryptodome==3.20.0
pycryptodome==3.20.0
pydantic==2.8.2
130 changes: 61 additions & 69 deletions wirepas_provisioning_server/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import yaml
import logging

from typing import Optional
from typing import Final, Optional

from wirepas_provisioning_server.helpers import convert_to_bytes, convert_to_int, ProvisioningDataException
from wirepas_provisioning_server.message import ProvisioningMethod
from wirepas_provisioning_server.migrate_config import ConfigFileMigration


def _generate_extended_uid(
Expand All @@ -26,10 +28,10 @@ def _generate_extended_uid(
Generate extended UID bytes
"""

authenticator_uid_type = _convert_to_bytes(authenticator_uid_type_raw)
authenticator_uid = _convert_to_bytes(authenticator_uid_raw)
node_uid_type = _convert_to_bytes(node_uid_type_raw)
node_uid = _convert_to_bytes(node_uid_raw)
authenticator_uid_type = convert_to_bytes(authenticator_uid_type_raw)
authenticator_uid = convert_to_bytes(authenticator_uid_raw)
node_uid_type = convert_to_bytes(node_uid_type_raw)
node_uid = convert_to_bytes(node_uid_raw)

def _any_is_not_bytes(*args: bytes | list[bytes]) -> bool:
return any(not isinstance(arg, bytes) for arg in args)
Expand All @@ -43,116 +45,106 @@ def _any_is_not_bytes(*args: bytes | list[bytes]) -> bool:
return b"".join([authenticator_uid_type, authenticator_uid, node_uid_type, node_uid])


def _convert_to_bytes(param_raw: bytes | int | str) -> bytes:
if isinstance(param_raw, str):
if param_raw.upper().startswith("0X"):
param_raw = param_raw.upper().replace("0X", "")
param = bytes.fromhex(param_raw)
else:
param = bytes(param_raw, "utf-8")
elif isinstance(param_raw, int):
param = param_raw.to_bytes(max(1, (param_raw.bit_length() + 7)) // 8, byteorder="big")
else:
param = param_raw

return param


def _convert_to_int(param: int | str) -> int:
if isinstance(param, str):
param = int(param, 0)

return param


class ProvisioningDataException(Exception):
"""
Wirepas Provisioning data generic Exception
"""


class ProvisioningData(dict):
# flake8: noqa: C901
def __init__(self, config: Optional[str] = None):

super(ProvisioningData, self).__init__()

if config is not None:
migration = ConfigFileMigration(config)
migration.update()
del migration

try:
with open(config, "r") as ymlfile:
cfg = yaml.safe_load(ymlfile)
except yaml.YAMLError:
raise ProvisioningDataException("Invalid data config file.")

for node in cfg:
if cfg.get("version") != 1:
raise ProvisioningDataException("Invalid data config file. Version must be 1")

# Validate network parameters
for name, network in cfg["networks"].items():
try:
for parameter in [
"authentication_key",
"encryption_key",
]:
network[parameter]
except KeyError as e:
raise ProvisioningDataException(f"Invalid data config file. Network {name} must include {str(e)}.")

if "method" not in cfg[node].keys():
raise ProvisioningDataException(f"Invalid data config file. {node} must include method.")
for node_name, node_cfg in cfg["nodes"].items():
if "network" not in node_cfg.keys():
raise ProvisioningDataException(f"Invalid data config file. Node {node_name} must include network.")
network_name = node_cfg["network"]

if "method" not in node_cfg.keys():
raise ProvisioningDataException(f"Invalid data config file. Node {node_name} must include method.")

provision_methods = [e.value for e in ProvisioningMethod]
if cfg[node]["method"] not in provision_methods:
raise ProvisioningDataException(f"Method must be one of {provision_methods}")
if node_cfg["method"] not in provision_methods:
raise ProvisioningDataException(f"Node method must be one of {provision_methods}")

if "uid" in cfg[node].keys():
uid: str | int | bytes = cfg[node]["uid"]
elif cfg[node]["method"] == ProvisioningMethod.EXTENDED:
if "uid" in node_cfg.keys():
uid: str | int | bytes = node_cfg["uid"]
elif node_cfg["method"] == ProvisioningMethod.EXTENDED:
try:
uid = _generate_extended_uid(
cfg[node]["authenticator_uid_type"],
cfg[node]["authenticator_uid"],
cfg[node]["node_uid_type"],
cfg[node]["node_uid"],
node_cfg["authenticator_uid_type"],
node_cfg["authenticator_uid"],
node_cfg["node_uid_type"],
node_cfg["node_uid"],
)

except KeyError:
raise ProvisioningDataException(f"Invalid data config file. {node} must include UID information.")
raise ProvisioningDataException(
f"Invalid data config file. Node {node_name} must include UID information."
)
else:
raise ProvisioningDataException(f"Invalid data config file. {node} must include UID information")
raise ProvisioningDataException(f"Invalid data config file. Node {node_name} must include UID information")

if "network_address" in cfg[node].keys():
network_address = _convert_to_int(cfg[node]["network_address"])
if "network_address" in node_cfg.keys():
network_address = convert_to_int(cfg["networks"][network_name]["address"])
else:
network_address = None

if "network_channel" in cfg[node].keys():
network_channel = _convert_to_int(cfg[node]["network_channel"])
if "network_channel" in node_cfg.keys():
network_channel = convert_to_int(cfg["networks"][network_name]["channel"])
else:
network_channel = None

if "node_id" in cfg[node].keys():
node_id = _convert_to_int(cfg[node]["node_id"])
if "node_id" in node_cfg.keys():
node_id = convert_to_int(node_cfg["node_id"])
else:
node_id = None

if "node_role" in cfg[node].keys():
node_role = _convert_to_bytes(cfg[node]["node_role"])
if "node_role" in node_cfg.keys():
node_role = convert_to_bytes(node_cfg["node_role"])
else:
node_role = None

if "user_specific" in cfg[node].keys():
if "user_specific" in node_cfg.keys():
user_specific = dict()
for k in cfg[node]["user_specific"]:
for k in node_cfg["user_specific"]:
if k < 128 or k > 255:
raise KeyError
user_specific[k] = cfg[node]["user_specific"][k]
user_specific[k] = node_cfg["user_specific"][k]
else:
user_specific = None

if "factory_key" in cfg[node].keys():
factory_key = _convert_to_bytes(cfg[node]["factory_key"])
if "factory_key" in node_cfg.keys():
factory_key = convert_to_bytes(node_cfg["factory_key"])
else:
factory_key = None

if "encryption_key" not in cfg[node].keys():
raise ProvisioningDataException(f"Invalid data config file. {node} must include encryption_key.")
if "authentication_key" not in cfg[node].keys():
raise ProvisioningDataException(f"Invalid data config file. {node} must include authentication_key.")

self.append(
_convert_to_bytes(uid),
cfg[node]["method"],
_convert_to_bytes(cfg[node]["encryption_key"]),
_convert_to_bytes(cfg[node]["authentication_key"]),
convert_to_bytes(uid),
node_cfg["method"],
convert_to_bytes(cfg["networks"][network_name]["encryption_key"]),
convert_to_bytes(cfg["networks"][network_name]["authentication_key"]),
network_address,
network_channel,
node_id=node_id,
Expand Down
36 changes: 36 additions & 0 deletions wirepas_provisioning_server/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Provisioning helpers
===================
.. Copyright:
Copyright 2024 Wirepas Ltd under Apache License, Version 2.0.
See file LICENSE for full license details.
"""


class ProvisioningDataException(Exception):
"""
Wirepas Provisioning data generic Exception
"""


def convert_to_bytes(param_raw: bytes | int | str) -> bytes:
if isinstance(param_raw, str):
if param_raw.upper().startswith("0X"):
param_raw = param_raw.upper().replace("0X", "")
param = bytes.fromhex(param_raw)
else:
param = bytes(param_raw, "utf-8")
elif isinstance(param_raw, int):
param = param_raw.to_bytes(max(1, (param_raw.bit_length() + 7)) // 8, byteorder="big")
else:
param = param_raw

return param


def convert_to_int(param: int | str) -> int:
if isinstance(param, str):
param = int(param, 0)

return param
Loading

0 comments on commit 96722db

Please sign in to comment.