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:
parent
6e77823933
commit
f2a404855f
8 changed files with 365 additions and 42 deletions
|
|
@ -41,7 +41,7 @@ import Imsd;
|
|||
namespace {
|
||||
|
||||
// ---- static config (env-overridable, same knobs as imsd.py) ---------------
|
||||
constexpr const char* Version = "0.2.7";
|
||||
constexpr const char* Version = "0.3.0";
|
||||
constexpr const char* BusName = "net.catcrafts.IMS1";
|
||||
constexpr const char* ObjPath = "/net/catcrafts/IMS1";
|
||||
constexpr const char* Iface = "net.catcrafts.IMS1";
|
||||
|
|
@ -571,6 +571,18 @@ public:
|
|||
precond_ = EnvOr("PRECOND", "0") == "1";
|
||||
ealgOffer_ = EnvOr("EALG", "aes-cbc");
|
||||
mediaBin_ = ResolveMediaBin();
|
||||
// EMERGENCY_NUMBERS: comma-separated additions to the builtin
|
||||
// 112/911 (a lab core's advertised short code, carrier extras).
|
||||
std::string extra = EnvOr("EMERGENCY_NUMBERS", "");
|
||||
for (std::size_t pos = 0; pos <= extra.size();) {
|
||||
std::size_t comma = extra.find(',', pos);
|
||||
std::size_t end = comma == std::string::npos ? extra.size() : comma;
|
||||
std::string n = extra.substr(pos, end - pos);
|
||||
std::erase(n, ' ');
|
||||
if (!n.empty()) emergencyExtra_.push_back(std::move(n));
|
||||
if (comma == std::string::npos) break;
|
||||
pos = comma + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void SetLocal(std::string l) { local_ = std::move(l); }
|
||||
|
|
@ -672,6 +684,7 @@ private:
|
|||
|
||||
// config
|
||||
std::string pcscf_, dev_, outDir_, local_, ealgOffer_, mediaBin_;
|
||||
std::vector<std::string> emergencyExtra_;
|
||||
int pcscfPort_ = 5060;
|
||||
int rtpPort_ = 50004;
|
||||
bool precond_ = false;
|
||||
|
|
@ -1098,20 +1111,34 @@ private:
|
|||
void HandleCmd(const Cmd& c) {
|
||||
if (c.kind == Cmd::Kind::Stop) { quit_.store(true); return; }
|
||||
if (c.kind == Cmd::Kind::Dial) {
|
||||
bool emergency = imsd::msg::IsEmergency(c.number, emergencyExtra_);
|
||||
if (call_) {
|
||||
Log(std::format("dial({}) refused: call in progress", c.number));
|
||||
CallInfo bad{c.uni, c.number, "terminated", "error", NowEpoch(), 0};
|
||||
PostAdded(bad);
|
||||
PostState(c.uni, "terminated", "error");
|
||||
PostDeleted(c.uni);
|
||||
return;
|
||||
if (!emergency) {
|
||||
Log(std::format("dial({}) refused: call in progress", c.number));
|
||||
CallInfo bad{c.uni, c.number, "terminated", "error", NowEpoch(), 0};
|
||||
PostAdded(bad);
|
||||
PostState(c.uni, "terminated", "error");
|
||||
PostDeleted(c.uni);
|
||||
return;
|
||||
}
|
||||
// an emergency dial preempts whatever exists (TS 22.101):
|
||||
// release it; if it lingers awaiting a CANCEL's 487, abandon
|
||||
// it — the late final is ignored by Call-ID mismatch
|
||||
Log(std::format("emergency dial: preempting call {}", call_->Uni()));
|
||||
Execute(call_->OnHangup());
|
||||
MaybeDropCall();
|
||||
if (call_) { Execute(call_->Abandon("preempted by emergency dial")); MaybeDropCall(); }
|
||||
}
|
||||
// a dead client flow fails the INVITE instantly — give the
|
||||
// reconnect path one immediate shot first (user action beats
|
||||
// the backoff timer)
|
||||
if (!sip_.Alive()) { nextReconnect_ = 0; MaybeReconnect(); }
|
||||
RefreshPani();
|
||||
call_.emplace(ctx_, rng_, c.uni, c.number, rtpPort_, precond_);
|
||||
if (emergency)
|
||||
Log(std::format("EMERGENCY dial ({}): urn:service:sos over the live registration, "
|
||||
"fallback plain INVITE — stage 1, no emergency registration/PDN; "
|
||||
"carrier-side behavior UNVERIFIED", c.number));
|
||||
call_.emplace(ctx_, rng_, c.uni, c.number, rtpPort_, precond_, emergency);
|
||||
CallInfo info{c.uni, c.number, "dialing", "outgoing", NowEpoch(), 0};
|
||||
PostAdded(info);
|
||||
bool ok = Execute(call_->Start());
|
||||
|
|
|
|||
Loading…
Reference in a new issue