Skip to content

Do not delete malformed delivery reports before parsing succeeds #124

Description

@Justinabox

Summary

SMSService._on_delivery_report() deletes the modem/SIM delivery-report slot even when the +CMGR status-report header cannot be parsed. That turns an unparseable or vendor-variant delivery report into permanent data loss: no SMSDeliveryReportEvent is emitted, but AT+CMGD=<index> has already been sent.

Evidence

Affected code:

  • callstack/sms/service.py:230-247 reads the +CDSI slot and attempts to parse +CMGR status-report lines.
  • callstack/sms/service.py:249-253 deletes the SMS slot before checking whether status was parsed.
  • callstack/sms/service.py:255-259 then logs Could not parse delivery report and returns without emitting an event.
  • Existing coverage in tests/test_delivery_reports.py:106-115 asserts that malformed reports do not emit false success, but it does not assert that the SIM slot is preserved.

No-hardware reproduction from main (6926b369cdba):

PYTHONPATH=. uv run --no-project --with pyserial-asyncio python - <<'PY'
import asyncio
from callstack.events.bus import EventBus
from callstack.events.types import SMSDeliveryReportEvent, _RawDeliveryReport
from callstack.protocol.executor import ATCommandExecutor
from callstack.protocol.urc import URCDispatcher
from callstack.sms.service import SMSService
from callstack.transport.mock import MockTransport

async def main():
    bus = EventBus()
    urc = URCDispatcher(bus)
    transport = MockTransport()
    service = SMSService(ATCommandExecutor(transport, urc), bus)
    emitted = []
    bus.subscribe(SMSDeliveryReportEvent, lambda e: emitted.append(e))
    transport.feed('+CMGR: "REC READ",not-a-reference,"+120****0999"', 'OK')
    transport.feed('OK')
    await service._on_delivery_report(_RawDeliveryReport(storage='SM', index=7))
    await asyncio.sleep(0.02)
    print('written_commands:', [w.strip() for w in transport.all_written])
    print('delivery_events:', len(emitted))

asyncio.run(main())
PY

Actual output:

Could not parse delivery report at index 7
written_commands: ['AT+CMGR=7', 'AT+CMGD=7']
delivery_events: 0

Expected behavior

If a delivery report cannot be parsed, Callstack should not silently delete it. It should either leave the slot intact for a later parser fix/manual inspection, or move it through an explicit failure/quarantine path that preserves enough bounded, PII-safe metadata for debugging.

Suggested fix direction

  • Parse the +CMGR status-report header first.
  • Send AT+CMGD=<index> only after a report has been parsed and emitted/stored successfully.
  • Add a regression test around malformed +CMGR status reports that asserts AT+CMGD is not written when no delivery status can be parsed.
  • Keep logs redacted; do not dump raw recipient numbers or full status-report lines by default.

Acceptance criteria

  • Malformed/unrecognized delivery-report responses do not emit a false success event.
  • Malformed/unrecognized delivery-report responses do not delete the SIM/modem slot.
  • Valid delivery reports still emit SMSDeliveryReportEvent and are deleted after successful processing.
  • Regression coverage uses MockTransport and requires no modem hardware.

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_delivery_reports.py tests/test_sms_service.py -q
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Duplicate check

Searched existing issues/PRs for delivery report malformed delete CMGD, CDSI malformed status report deletes SIM slot, and broader delivery-report parse/delete terms. I did not find an existing issue or open PR covering delete-before-parse data loss.

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