Emergency calling, stage 1: urn:service:sos with a digits fallback

Classify 112/911 (plus EMERGENCY_NUMBERS) at Dial. A classified call
INVITEs urn:service:sos over the existing registration and, on any
non-2xx final not caused by the user hanging up (including the
setup-timeout CANCEL paths), retries once as a plain INVITE of the
dialled digits — the pre-0.3.0 behavior, so classification can never
place a call worse than the status quo. The reverse edge: a 380
Alternative Service whose body carries the emergency indication
upgrades an unclassified call to the sos URN; a bare 380 stays an
error, since promoting an arbitrary redirect would put a
non-emergency call through to a PSAP. An emergency dial preempts an
in-progress call, and an answer racing a deadline-initiated CANCEL is
taken instead of BYE'd (user-initiated CANCEL races still BYE).

No emergency registration, no emergency PDN, no CS fallback, no
SIM-less calling, no AML — and no carrier has confirmed the sos path
end-to-end. The README warning states exactly that.

Assisted-by: Claude:claude-fable-5
This commit is contained in:
Jorijn van der Graaf 2026-08-01 22:29:12 +02:00
commit f2a404855f
8 changed files with 365 additions and 42 deletions

View file

@ -133,6 +133,27 @@ export namespace imsd::msg {
return s;
}
// The service URN an emergency INVITE targets (RFC 5031); Request-URI and
// To both carry it (TS 24.229 5.1.6). No sos subtype: a dialled string
// does not say police/ambulance/fire.
inline constexpr std::string_view SosUrn = "urn:service:sos";
// Emergency-number classification. 112 and 911 are emergency numbers on
// every network (TS 22.101 §10.1.1), so they are compiled in; `extra`
// adds carrier- or lab-specific numbers (the EMERGENCY_NUMBERS list —
// e.g. a private test core's advertised short code). Exact match after
// stripping "1 1 2"-style separators; prefixed/suffixed strings ("0112",
// "1120") are ordinary numbers. SIM EF_ECC is not read yet.
inline bool IsEmergency(std::string_view target, std::span<const std::string> extra = {}) {
std::string t;
for (char ch : target)
if (ch != ' ' && ch != '-' && ch != '.') t.push_back(ch);
if (t == "112" || t == "911") return true;
for (const std::string& e : extra)
if (!e.empty() && t == e) return true;
return false;
}
// Map a dialled string to a Request-URI (imscall.ruri_for): E.164/national
// numbers become user=phone SIP URIs at the home domain; a bare short code
// gets phone-context.