Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/infuse_iot/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def _load_tools(self, parser: argparse.ArgumentParser):
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module)
except Exception as _:
except Exception as e:
print(f"Failed to import '{name}': {str(e)}")
continue
if hasattr(module, "SubCommand"):
self._load_from_module(tools_parser, module)
Expand Down
11 changes: 10 additions & 1 deletion src/infuse_iot/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import keyring
import yaml

DEFAULT_NETWORK_KEY = (
b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
)


def set_api_key(api_key: str) -> None:
"""
Expand Down Expand Up @@ -36,10 +41,14 @@ def save_network(network_id: int, network_info: str) -> None:
keyring.set_password("infuse-iot", username, network_info)


def load_network(network_id: int):
def load_network(network_id: int) -> dict:
"""
Retrieve an Infuse-IoT network key from the keyring module
"""
if network_id == 0x000000:
# Default network
return {"id": 0, "key": DEFAULT_NETWORK_KEY}

username = f"network-{network_id:06x}"
key = keyring.get_password("infuse-iot", username)
if key is None:
Expand Down
5 changes: 1 addition & 4 deletions src/infuse_iot/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ class DeviceKeyChangedError(KeyError):
class DeviceDatabase:
"""Database of current device state"""

_network_keys = {
0x000000: b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
}
_network_keys: dict[int, bytes] = {}
_derived_keys: dict[tuple[int, bytes, int], bytes] = {}

class DeviceState:
Expand Down
22 changes: 21 additions & 1 deletion src/infuse_iot/generated/tdf_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,7 @@ class pcm_16bit_chan_left(TdfReadingBase):
}

class pcm_16bit_chan_right(TdfReadingBase):
"""Duration an Infuse-IoT application state was asserted for"""
"""16bit PCM (Audio) data for the right channel"""

ID = 59
NAME = "PCM_16BIT_CHAN_RIGHT"
Expand All @@ -1618,6 +1618,25 @@ class pcm_16bit_chan_right(TdfReadingBase):
"val": "{}",
}

class pcm_16bit_chan_dual(TdfReadingBase):
"""16bit PCM (Audio) data for both the left and right channels"""

ID = 60
NAME = "PCM_16BIT_CHAN_DUAL"
_fields_ = [
("left", ctypes.c_int16),
("right", ctypes.c_int16),
]
_pack_ = 1
_postfix_ = {
"left": "",
"right": "",
}
_display_fmt_ = {
"left": "{}",
"right": "{}",
}


id_type_mapping: dict[int, type[TdfReadingBase]] = {
readings.announce.ID: readings.announce,
Expand Down Expand Up @@ -1676,6 +1695,7 @@ class pcm_16bit_chan_right(TdfReadingBase):
readings.state_duration.ID: readings.state_duration,
readings.pcm_16bit_chan_left.ID: readings.pcm_16bit_chan_left,
readings.pcm_16bit_chan_right.ID: readings.pcm_16bit_chan_right,
readings.pcm_16bit_chan_dual.ID: readings.pcm_16bit_chan_dual,
}

__all__ = [
Expand Down
19 changes: 19 additions & 0 deletions src/infuse_iot/tools/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
ClientNotificationConnectionDropped,
ClientNotificationConnectionFailed,
ClientNotificationEpacketReceived,
ClientNotificationObservedDevices,
GatewayRequestConnectionRelease,
GatewayRequestConnectionRequest,
GatewayRequestEpacketSend,
GatewayRequestObservedDevices,
LocalServer,
default_multicast_address,
)
Expand Down Expand Up @@ -388,6 +390,21 @@ def _handle_conn_release(self, req: GatewayRequestConnectionRelease):
Console.log_tx(cmd.ptype, len(encrypted))
self._common.port.write(encrypted)

def _handle_observed_devices(self):
if self._common.server is None:
raise RuntimeError
observed_devices = {}
for device, state in self._common.ddb.devices.items():
info = {}
if state.network_id is not None:
info["network_id"] = state.network_id
if state.device_id is not None:
info["device_id"] = state.device_id
if self._common.ddb.gateway == device:
info["gateway"] = True
observed_devices[device] = info
self._common.server.broadcast(ClientNotificationObservedDevices(observed_devices))

def _iter(self) -> None:
if self._common.server is None:
time.sleep(1.0)
Expand All @@ -401,6 +418,8 @@ def _iter(self) -> None:
self._handle_conn_request(req)
elif isinstance(req, GatewayRequestConnectionRelease):
self._handle_conn_release(req)
elif isinstance(req, GatewayRequestObservedDevices):
self._handle_observed_devices()
else:
Console.log_error(f"Unhandled request {type(req)}")

Expand Down
6 changes: 3 additions & 3 deletions src/infuse_iot/util/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cryptography.hazmat.primitives.kdf.hkdf import HKDF


def hkdf_derive(input_key, salt, info):
def hkdf_derive(input_key: bytes, salt: bytes, info: bytes) -> bytes:
"""Derive a cryptographic key using HKDF-SHA256"""
hkdf = HKDF(
algorithm=hashes.SHA256(),
Expand All @@ -16,13 +16,13 @@ def hkdf_derive(input_key, salt, info):
return hkdf.derive(input_key)


def chachapoly_encrypt(key: bytes, associated_data: bytes, nonce: bytes, payload: bytes) -> bytes:
def chachapoly_encrypt(key: bytes, associated_data: bytes | None, nonce: bytes, payload: bytes) -> bytes:
"""Encrypt a payload using ChaCha20-Poly1305"""
cipher = ChaCha20Poly1305(key)
return cipher.encrypt(nonce, payload, associated_data)


def chachapoly_decrypt(key: bytes, associated_data: bytes, nonce: bytes, payload: bytes) -> bytes:
def chachapoly_decrypt(key: bytes, associated_data: bytes | None, nonce: bytes, payload: bytes) -> bytes:
"""Decrypt a payload using ChaCha20-Poly1305"""
cipher = ChaCha20Poly1305(key)
return cipher.decrypt(nonce, payload, associated_data)