From cd783b4d8dc7e5685d146821dff2573ebcd7e072 Mon Sep 17 00:00:00 2001
From: Jordan Yates <jordan@embeint.com>
Date: Thu, 16 Apr 2026 16:39:31 +1000
Subject: [PATCH 1/4] util: argparse: reject directories from `ValidFile`

Update `ValidFile` to reject directories.

Signed-off-by: Jordan Yates <jordan@embeint.com>
---
 src/infuse_iot/util/argparse.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/infuse_iot/util/argparse.py b/src/infuse_iot/util/argparse.py
index aefbeea..9eb7794 100644
--- a/src/infuse_iot/util/argparse.py
+++ b/src/infuse_iot/util/argparse.py
@@ -12,12 +12,15 @@
 
 
 class ValidFile:
-    """Filesystem path that exists"""
+    """Filesystem file that exists"""
 
     def __new__(cls, string) -> pathlib.Path:  # type: ignore
         p = pathlib.Path(string)
         if p.exists():
-            return p
+            if p.is_dir():
+                raise argparse.ArgumentTypeError(f"{string} is a directory")
+            else:
+                return p
         else:
             raise argparse.ArgumentTypeError(f"{string} does not exist")
 

From de64f4b279e15792e34ad03c13bdf08b93ac5023 Mon Sep 17 00:00:00 2001
From: Jordan Yates <jordan@embeint.com>
Date: Thu, 16 Apr 2026 17:21:18 +1000
Subject: [PATCH 2/4] tests: util: argparse: added

Add tests for the argparse utilities.

Signed-off-by: Jordan Yates <jordan@embeint.com>
---
 tests/util/test_argparse.py | 46 +++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 tests/util/test_argparse.py

diff --git a/tests/util/test_argparse.py b/tests/util/test_argparse.py
new file mode 100644
index 0000000..060bebf
--- /dev/null
+++ b/tests/util/test_argparse.py
@@ -0,0 +1,46 @@
+import argparse
+import os
+import pathlib
+
+import pytest
+
+from infuse_iot.util.argparse import BtLeAddress, ValidDir, ValidFile
+
+assert "TOXTEMPDIR" in os.environ, "you must run these tests using tox"
+
+
+def test_valid_file():
+    test_file = __file__
+    test_dir = str(pathlib.Path(test_file).parent)
+
+    with pytest.raises(argparse.ArgumentTypeError):
+        ValidFile("random_file_doesnt_exist.txt")
+    with pytest.raises(argparse.ArgumentTypeError):
+        ValidFile(test_dir)
+    parsed = ValidFile(test_file)
+    assert isinstance(parsed, pathlib.Path)
+
+
+def test_valid_directory():
+    test_file = __file__
+    test_dir = str(pathlib.Path(test_file).parent)
+
+    with pytest.raises(argparse.ArgumentTypeError):
+        ValidDir("random_folder_doesnt_exist")
+    with pytest.raises(argparse.ArgumentTypeError):
+        ValidDir(test_file)
+    parsed = ValidDir(test_dir)
+    assert isinstance(parsed, pathlib.Path)
+
+
+def test_bt_le_address():
+    with pytest.raises(argparse.ArgumentTypeError):
+        BtLeAddress("NotAnAddress")
+    with pytest.raises(argparse.ArgumentTypeError):
+        BtLeAddress("XX:XX:XX:XX:XX:XX")
+    with pytest.raises(argparse.ArgumentTypeError):
+        BtLeAddress("12:34:56:aa:FF")
+    addr = BtLeAddress("12:34:56:aa:FF:4A")
+    assert isinstance(addr, int)
+    addr = BtLeAddress("123456aaFF4A")
+    assert isinstance(addr, int)

From a47467e3438406635a70252388d28ad4486ce6ce Mon Sep 17 00:00:00 2001
From: Jordan Yates <jordan@embeint.com>
Date: Thu, 16 Apr 2026 17:21:52 +1000
Subject: [PATCH 3/4] util: argparse: remove `-` string replace

Addresses with `-` won't pass the regex, so no need to handle.

Signed-off-by: Jordan Yates <jordan@embeint.com>
---
 src/infuse_iot/util/argparse.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/infuse_iot/util/argparse.py b/src/infuse_iot/util/argparse.py
index 9eb7794..662e035 100644
--- a/src/infuse_iot/util/argparse.py
+++ b/src/infuse_iot/util/argparse.py
@@ -58,7 +58,7 @@ def __new__(cls, string) -> int:  # type: ignore
         pattern = r"((([0-9a-fA-F]{2}):){5})([0-9a-fA-F]{2})"
 
         if re.match(pattern, string):
-            mac_cleaned = string.replace(":", "").replace("-", "")
+            mac_cleaned = string.replace(":", "")
             addr = int(mac_cleaned, 16)
         else:
             try:

From b4fa00f25ee3a6e9e6a66dcffc743b601f1d11ec Mon Sep 17 00:00:00 2001
From: Jordan Yates <jordan@embeint.com>
Date: Thu, 16 Apr 2026 17:23:59 +1000
Subject: [PATCH 4/4] commands: add alternate return type to `request_struct`

Command implementations can validly return a `bytes` object from
`request_struct`.

Signed-off-by: Jordan Yates <jordan@embeint.com>
---
 src/infuse_iot/commands.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/infuse_iot/commands.py b/src/infuse_iot/commands.py
index f82c828..c41557f 100644
--- a/src/infuse_iot/commands.py
+++ b/src/infuse_iot/commands.py
@@ -60,7 +60,7 @@ def auth_level(self) -> Auth:
         """Authentication level to run command with"""
         return Auth.DEVICE
 
-    def request_struct(self) -> ctypes.LittleEndianStructure:
+    def request_struct(self) -> ctypes.LittleEndianStructure | bytes:
         """RPC_CMD request structure"""
         raise NotImplementedError
 
