Compare commits
2 Commits
8342904488
...
56fc58b843
| Author | SHA1 | Date | |
|---|---|---|---|
| 56fc58b843 | |||
| 641ab29277 |
@@ -11,7 +11,7 @@ import sqlite3
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, model_validator
|
||||||
|
|
||||||
|
|
||||||
class CameraRecord(BaseModel):
|
class CameraRecord(BaseModel):
|
||||||
@@ -20,6 +20,12 @@ class CameraRecord(BaseModel):
|
|||||||
entries: int = Field(ge=0)
|
entries: int = Field(ge=0)
|
||||||
exits: int = Field(ge=0)
|
exits: int = Field(ge=0)
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def _period_order(self):
|
||||||
|
if self.period_end <= self.period_start:
|
||||||
|
raise ValueError("period_end must be strictly greater than period_start")
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class CameraEventsRequest(BaseModel):
|
class CameraEventsRequest(BaseModel):
|
||||||
location_id: str
|
location_id: str
|
||||||
|
|||||||
@@ -98,3 +98,15 @@ def test_negative_counts_rejected():
|
|||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
CameraRecord(period_start=1712000000, period_end=1712003600,
|
CameraRecord(period_start=1712000000, period_end=1712003600,
|
||||||
entries=-1, exits=0)
|
entries=-1, exits=0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inverted_period_rejected():
|
||||||
|
"""Pydantic should reject period_end <= period_start."""
|
||||||
|
from pydantic import ValidationError
|
||||||
|
from server.camera_endpoint import CameraRecord
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
CameraRecord(period_start=1712003600, period_end=1712003600,
|
||||||
|
entries=0, exits=0)
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
CameraRecord(period_start=1712003600, period_end=1712000000,
|
||||||
|
entries=0, exits=0)
|
||||||
|
|||||||
0
tools/__init__.py
Normal file
0
tools/__init__.py
Normal file
@@ -28,6 +28,25 @@ NVS_NAMESPACE = "doorcounter"
|
|||||||
NVS_PARTITION_OFFSET = "0x9000"
|
NVS_PARTITION_OFFSET = "0x9000"
|
||||||
NVS_PARTITION_SIZE = "0x5000" # matches firmware partition table (20KB)
|
NVS_PARTITION_SIZE = "0x5000" # matches firmware partition table (20KB)
|
||||||
|
|
||||||
|
# Characters that would change the field/row structure of the NVS-CSV format
|
||||||
|
# (key,type,encoding,value). A value containing any of these would either
|
||||||
|
# split into more fields or add rows, silently provisioning the wrong keys.
|
||||||
|
_CSV_FORBIDDEN = (",", '"', "\n", "\r")
|
||||||
|
|
||||||
|
|
||||||
|
def _reject_csv_metacharacters(name, value):
|
||||||
|
"""Exit with an error if value contains a character that would corrupt
|
||||||
|
the NVS CSV. Used for operator-supplied strings (device id, location id,
|
||||||
|
WiFi credentials)."""
|
||||||
|
for c in _CSV_FORBIDDEN:
|
||||||
|
if c in value:
|
||||||
|
print(
|
||||||
|
f"Error: --{name} contains forbidden character {c!r}; "
|
||||||
|
f"this would corrupt the NVS partition CSV.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def build_nvs_csv(device_id, location_id, hmac_secret,
|
def build_nvs_csv(device_id, location_id, hmac_secret,
|
||||||
wifi_ssid=None, wifi_pass=None, line_offset=50):
|
wifi_ssid=None, wifi_pass=None, line_offset=50):
|
||||||
@@ -78,6 +97,13 @@ def main():
|
|||||||
print("Error: --line-offset must be 0-100", file=sys.stderr)
|
print("Error: --line-offset must be 0-100", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
_reject_csv_metacharacters("device-id", args.device_id)
|
||||||
|
_reject_csv_metacharacters("location-id", args.location_id)
|
||||||
|
if args.wifi_ssid is not None:
|
||||||
|
_reject_csv_metacharacters("wifi-ssid", args.wifi_ssid)
|
||||||
|
if args.wifi_password is not None:
|
||||||
|
_reject_csv_metacharacters("wifi-password", args.wifi_password)
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
csv_path = os.path.join(tmp, "nvs.csv")
|
csv_path = os.path.join(tmp, "nvs.csv")
|
||||||
bin_path = os.path.join(tmp, "nvs.bin")
|
bin_path = os.path.join(tmp, "nvs.bin")
|
||||||
|
|||||||
17
tools/test_flash_device.py
Normal file
17
tools/test_flash_device.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from tools.flash_device import _reject_csv_metacharacters
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_value_accepted():
|
||||||
|
"""A value with no metacharacters should pass without exiting."""
|
||||||
|
_reject_csv_metacharacters("device-id", "dc-0042")
|
||||||
|
_reject_csv_metacharacters("location-id", "retailer-123")
|
||||||
|
_reject_csv_metacharacters("wifi-ssid", "StoreWiFi-2.4GHz")
|
||||||
|
_reject_csv_metacharacters("wifi-password", "p@ssw0rd!~#$%^&*()_+-=:;<>?/")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bad", ["Home,Network", 'pa"ss', "ssid\nfoo", "name\rbar"])
|
||||||
|
def test_metacharacter_rejected(bad):
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
_reject_csv_metacharacters("wifi-ssid", bad)
|
||||||
Reference in New Issue
Block a user