Skip to content

Fail SMS send responses that omit the +CMGS submit reference #125

Description

@Justinabox

Summary

SMSService.send() reports an SMS as sent when the modem response contains only OK and no +CMGS: <mr> submit reference. The returned SMS is saved with reference=0, which is indistinguishable from a legitimate TP-MR value of zero and breaks delivery-report correlation/debugging.

Evidence

Affected code:

  • callstack/sms/service.py:155-161 treats send_data(..., expect=["+CMGS:", "OK"]) as successful when the final OK is seen.
  • callstack/sms/service.py:163-169 initializes reference = 0 and leaves it at zero if no +CMGS: line is present.
  • callstack/sms/service.py:171-180 then saves/emits a sent SMS with reference=0.
  • callstack/protocol/executor.py:302-312 uses exact success matching, so +CMGS: 42 is not actually the success terminator; the later OK is what makes the send successful.

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.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()
    transport = MockTransport()
    service = SMSService(ATCommandExecutor(transport, URCDispatcher(bus)), bus)
    transport.feed('> ')
    transport.feed('OK')
    sms = await service.send('5551234', 'Hello')
    print('sms_reference:', sms.reference)
    print('sms_status:', sms.status)
    print('written_commands:', [w.strip() for w in transport.all_written])

asyncio.run(main())
PY

Actual output:

sms_reference: 0
sms_status: sent
written_commands: ['AT+CMGS="5551234"', 'Hello\x1a']

Expected behavior

For text-mode AT+CMGS, Callstack should require an explicit parsed +CMGS: <mr> line before reporting a successful sent SMS. Absence of the submit reference should be treated as a send/protocol error, not silently normalized to 0.

Important nuance: +CMGS: 0 can be a legitimate modem reference, so the fix should use a sentinel such as reference: int | None while parsing rather than using 0 as both "missing" and "real zero".

Suggested fix direction

  • Parse +CMGS: into reference: int | None = None.
  • After a successful final OK, raise SMSSendError if reference is None.
  • Preserve valid +CMGS: 0 as a real successful reference.
  • Add focused tests for both OK without +CMGS and explicit +CMGS: 0.

Acceptance criteria

  • send() raises SMSSendError when the submit response lacks +CMGS: <mr> even if it ends in OK.
  • send() still accepts and returns reference=0 when the modem explicitly returns +CMGS: 0.
  • Sent-message persistence and SMSSentEvent emission happen only after an explicit reference is parsed.
  • Existing SMS prompt/body timeout and GSM 03.38 encoding tests remain green.

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 -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 CMGS missing reference, SMS send reference 0, and OK no +CMGS. Existing issue #13 covered exact final-result matching for SMS body truncation, not submit-reference absence. I did not find an existing open issue or PR for this behavior.

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