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
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
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-78parses+CMTI: "storage",indexand returns both values.callstack/sms/service.py:151-160assignsstorage, index = parsedbut then callsread_message(index)anddelete_message(index)without selecting or passing the storage.callstack/events/types.py:86-90_RawSMSNotificationhas no storage field, so storage is dropped at the internal event boundary.callstack/events/types.py:93-97_RawDeliveryReportdoes carry storage for+CDSI, butcallstack/sms/service.py:185-218logs it and still callsAT+CMGR=<index>/AT+CMGD=<index>without selecting that storage.callstack/config.py:13definessms_storage, but repository search found noAT+CPMScommand or use ofsms_storageoutside config/tests.Current test coverage only uses
+CMTI: "SM",3and assertsAT+CMGR/AT+CMGDby index; there is no regression for non-default stores.Repository health checks from this scout run:
Duplicate checks:
Expected behavior
When a modem reports
+CMTI: "ME",3or+CDSI: "ME",3, Callstack should read and delete slot 3 fromME, 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>andAT+CMGD=<index>operate against the modem's current selected message storage, which may not match the URC.Suggested fix direction
AT+CPMScommand builder with validation for known storage names (SM,ME,MT, etc.) or a conservative validated string set.SMSService.initialize()usingModemConfig.sms_storageor an explicit SMS service option.read_message()/delete_message()for both+CMTIand+CDSIflows.+CMTI: "ME",3and+CDSI: "ME",3that assert the expectedAT+CPMS/read/delete sequence.Acceptance criteria
ModemConfig.sms_storageis used or removed/replaced with an explicit supported configuration path.+CMTIevents preserve the URC storage through the internal event/service path.SMSServicereads and deletes from the URC-reported storage when it differs from the configured default.+CDSI.SMandMEnotification/report storage without real hardware.Verification gates