Skip to content

Honor CMTI/CDSI SMS storage before reading or deleting slots #14

Description

@Justinabox

Summary

Incoming SMS and delivery-report URCs include a storage name (SM, ME, etc.), but the receive paths ignore that storage before reading and deleting the reported index. If the modem's current preferred storage differs from the URC storage, Callstack can read or delete the wrong slot.

This is a robustness hazard for unattended Raspberry Pi deployments and for broader modem support, where SIMCOM/Quectel/Huawei devices may report notifications from different stores.

Evidence

Affected code:

  • callstack/protocol/parser.py:72-78 parses +CMTI: "storage",index and returns both values.
  • callstack/sms/service.py:151-160 assigns storage, index = parsed but then calls read_message(index) and delete_message(index) without selecting or passing the storage.
  • callstack/events/types.py:86-90 _RawSMSNotification has no storage field, so storage is dropped at the internal event boundary.
  • callstack/events/types.py:93-97 _RawDeliveryReport does carry storage for +CDSI, but callstack/sms/service.py:185-218 logs it and still calls AT+CMGR=<index> / AT+CMGD=<index> without selecting that storage.
  • callstack/config.py:13 defines sms_storage, but repository search found no AT+CPMS command or use of sms_storage outside config/tests.

Current test coverage only uses +CMTI: "SM",3 and asserts AT+CMGR/AT+CMGD by index; there is no regression for non-default stores.

Repository health checks from this scout run:

git diff --check
# exit 0

PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
# 291 passed in 3.94s

Duplicate checks:

gh issue list --repo Justinabox/Callstack --state all --search 'CPMS CMTI storage read delete SMS storage in:title,body'
# no matches

gh issue list --repo Justinabox/Callstack --state all --search 'sms_storage ModemConfig unused in:title,body'
# no matches

Expected behavior

When a modem reports +CMTI: "ME",3 or +CDSI: "ME",3, Callstack should read and delete slot 3 from ME, not from whichever storage happened to be selected previously. The configured/default storage should also be applied deliberately during SMS initialization.

Actual behavior

The reported storage is ignored. AT+CMGR=<index> and AT+CMGD=<index> operate against the modem's current selected message storage, which may not match the URC.

Suggested fix direction

  • Add an AT+CPMS command builder with validation for known storage names (SM, ME, MT, etc.) or a conservative validated string set.
  • Configure preferred storage during SMSService.initialize() using ModemConfig.sms_storage or an explicit SMS service option.
  • Preserve storage on internal raw SMS notifications, and select the URC-reported storage before read_message() / delete_message() for both +CMTI and +CDSI flows.
  • Keep storage switching serialized with other AT commands through the existing executor lock.
  • Add tests with +CMTI: "ME",3 and +CDSI: "ME",3 that assert the expected AT+CPMS/read/delete sequence.

Acceptance criteria

  • ModemConfig.sms_storage is used or removed/replaced with an explicit supported configuration path.
  • Incoming +CMTI events preserve the URC storage through the internal event/service path.
  • SMSService reads and deletes from the URC-reported storage when it differs from the configured default.
  • Delivery report handling does the same for +CDSI.
  • Tests cover at least SM and ME notification/report storage without real hardware.
  • No phone numbers, message bodies, IMSI/ICCID/SIM identifiers, or credentials are logged while adding diagnostics.

Verification gates

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/test_sms_service.py tests/test_delivery_reports.py tests/test_config.py -q
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions