Compare commits
7 changed files with 20 additions and 135 deletions
|
|
@ -164,8 +164,7 @@ PCSCF=2001:db8::105
|
|||
| `EMERGENCY_NUMBERS` | *(empty)* | comma-separated additions to the builtin 112/911 emergency numbers (e.g. a private test core's short code). SIM `EF_ECC` is not read yet |
|
||||
| `RTP_PORT` | `50004` | local RTP port advertised in SDP |
|
||||
| `PRECOND` | `0` | `1` offers SDP QoS preconditions |
|
||||
| `CODECS` | *(empty — defaults)* | comma-separated codec preference list over `AMR-WB`, `AMR` (or `AMR-NB`), `PCMA`, `PCMU`: restricts and orders both the codecs offered on an outgoing call and those accepted from an inbound offer (default: offer AMR-WB + AMR, accept all four in that order). A bench knob — a network whose gateway transcodes every caller up to AMR-WB otherwise never lets the narrowband path run |
|
||||
| `DUMP_SIP` | `0` | `1` writes raw REGISTER-200/SUBSCRIBE-200 dumps, the last inbound INVITE (`imsd-invite-in.raw`) and the last outgoing INVITE (`imsd-invite-out.raw`), mode 0600, for debugging — they contain your IMSI/MSISDN and addresses |
|
||||
| `DUMP_SIP` | `0` | `1` writes raw REGISTER-200/SUBSCRIBE-200/INVITE dumps (mode 0600) for debugging — they contain your IMSI/MSISDN and addresses |
|
||||
| `DUMP_DIR`, `OUT_DIR` | state dir | where dumps / per-call media stats land |
|
||||
| `IMSD_MEDIA` | *(auto)* | path to `imsd-media` (default: next to `imsd`, else `/usr/libexec/imsd-media`) |
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import Imsd;
|
|||
namespace {
|
||||
|
||||
// ---- static config (env-overridable, same knobs as imsd.py) ---------------
|
||||
constexpr const char* Version = "0.3.4";
|
||||
constexpr const char* Version = "0.3.3";
|
||||
constexpr const char* BusName = "net.catcrafts.IMS1";
|
||||
constexpr const char* ObjPath = "/net/catcrafts/IMS1";
|
||||
constexpr const char* Iface = "net.catcrafts.IMS1";
|
||||
|
|
@ -411,12 +411,12 @@ std::string StateDir() {
|
|||
// tiny JSON reader/writer for the persisted registration context.
|
||||
std::string StatePath() { return EnvOr("STATE_FILE", std::format("{}/imsreg.state", StateDir())); }
|
||||
|
||||
void PersistState(const RegState& r, const std::string& route, const std::string& ppi, const std::string& aor) {
|
||||
void PersistState(const RegState& r, const std::string& route, const std::string& ppi) {
|
||||
std::string j = std::format(
|
||||
"{{\"callid\": \"{}\", \"ftag\": \"{}\", \"cseq\": {}, \"spi_uc\": {}, "
|
||||
"\"spi_us\": {}, \"expiry\": {}, \"route\": \"{}\", \"ppi\": \"{}\", "
|
||||
"\"aor\": \"{}\", \"contact_user\": \"{}\"}}",
|
||||
r.callid, r.ftag, r.cseq, r.spiUc, r.spiUs, r.expiry, route, ppi, aor,
|
||||
"\"contact_user\": \"{}\"}}",
|
||||
r.callid, r.ftag, r.cseq, r.spiUc, r.spiUs, r.expiry, route, ppi,
|
||||
r.contactUser);
|
||||
std::string path = StatePath();
|
||||
std::error_code ec;
|
||||
|
|
@ -441,7 +441,7 @@ void DumpRaw(std::string_view name, std::string_view msg) {
|
|||
}
|
||||
|
||||
struct PersistedState {
|
||||
std::optional<std::string> callid, ftag, route, ppi, aor, contactUser;
|
||||
std::optional<std::string> callid, ftag, route, ppi, contactUser;
|
||||
std::optional<long> cseq, spiUc, spiUs, expiry;
|
||||
};
|
||||
std::optional<PersistedState> LoadState() {
|
||||
|
|
@ -456,7 +456,6 @@ std::optional<PersistedState> LoadState() {
|
|||
ps.ftag = QuotedAfter(s, "\"ftag\": \"");
|
||||
ps.route = QuotedAfter(s, "\"route\": \"");
|
||||
ps.ppi = QuotedAfter(s, "\"ppi\": \"");
|
||||
ps.aor = QuotedAfter(s, "\"aor\": \"");
|
||||
ps.contactUser = QuotedAfter(s, "\"contact_user\": \"");
|
||||
ps.cseq = IntAfter(s, "\"cseq\": ");
|
||||
ps.spiUc = IntAfter(s, "\"spi_uc\": ");
|
||||
|
|
@ -810,10 +809,6 @@ private:
|
|||
// is re-learned from the next 200's P-Associated-URI.
|
||||
ppi_ = ps->ppi.value_or("");
|
||||
ss_ = sa->securityServer;
|
||||
// The public identity our requests are signed with (From) — restored
|
||||
// here so a call placed before the refresh 200 re-learns it does not
|
||||
// fall back to the barred temporary IMPU.
|
||||
ctx_.aor = ps->aor.value_or("");
|
||||
ctx_.route = route_; ctx_.ppi = ppi_; ctx_.securityServer = ss_;
|
||||
ctx_.portUc = PortUc; ctx_.portUs = PortUs;
|
||||
Log(std::format("RESUME via existing SA (cseq {})", ps->cseq.value_or(1)));
|
||||
|
|
@ -839,7 +834,7 @@ private:
|
|||
if (auto e = imsd::sip::GrantedExpires(okMsg)) reg_.expiry = *e;
|
||||
UpdateRouteAndPpi(okMsg);
|
||||
LogBindings(okMsg);
|
||||
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||
PersistState(reg_, route_, ppi_);
|
||||
Log("REGISTERED (resumed + true-refreshed)");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -938,7 +933,7 @@ private:
|
|||
LogBindings(ok);
|
||||
if (route_.empty()) route_ = std::format("<sip:{};lr>", imsd::util::HostPort(pcscf_, portPs));
|
||||
ctx_.route = route_; ctx_.ppi = ppi_;
|
||||
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||
PersistState(reg_, route_, ppi_);
|
||||
Log("REGISTERED (fresh)");
|
||||
}
|
||||
|
||||
|
|
@ -1056,7 +1051,7 @@ private:
|
|||
auto [msg, used] = Reregister(reg_.callid, reg_.ftag, reg_.cseq + 1, reg_.spiUc, reg_.spiUs);
|
||||
reg_.cseq = used;
|
||||
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
||||
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||
PersistState(reg_, route_, ppi_);
|
||||
Log(std::format("keepalive re-REGISTER ok (cseq {})", used));
|
||||
SubscribeRegEvent();
|
||||
if (!registered_) { registered_ = true; EmitStatus(true); }
|
||||
|
|
@ -1092,7 +1087,7 @@ private:
|
|||
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
||||
UpdateRouteAndPpi(msg);
|
||||
LogBindings(msg);
|
||||
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||
PersistState(reg_, route_, ppi_);
|
||||
Log("reconnected + re-registered");
|
||||
SubscribeRegEvent();
|
||||
if (!registered_) { registered_ = true; EmitStatus(true); }
|
||||
|
|
@ -1247,9 +1242,6 @@ private:
|
|||
for (const auto& a : actions) {
|
||||
switch (a.type) {
|
||||
case T::SendClient:
|
||||
// the outgoing INVITE is the one request a field log could
|
||||
// never show (DUMP_SIP dumped inbound messages only)
|
||||
if (a.text.starts_with("INVITE ")) DumpRaw("imsd-invite-out.raw", a.text);
|
||||
if (!sip_.Send(a.text)) { sendOk = false; Log("client send failed"); }
|
||||
break;
|
||||
case T::SendResponse:
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ export namespace imsd::engine {
|
|||
d_.itag = rng_.Token(10); // the To tag we mint = our dialog tag
|
||||
d_.invBranch = rng_.Token(20); // unused as UAS; kept initialized
|
||||
if (auto from = imsd::sip::Header(invite_, "From")) d_.dialogTo = std::string(*from);
|
||||
if (auto to = imsd::sip::Header(invite_, "To")) d_.localUri = AngleUri(*to).value_or("");
|
||||
if (auto ct = imsd::sip::Header(invite_, "Contact")) d_.remoteTarget = AngleUri(*ct);
|
||||
d_.ruri = d_.remoteTarget.value_or("");
|
||||
// The UAS route set is the Record-Route in RECEIVED order
|
||||
|
|
|
|||
|
|
@ -290,28 +290,8 @@ export namespace imsd::msg {
|
|||
std::optional<std::string> dialogTo; // remote To/From incl. tag, once known
|
||||
std::vector<std::string> recordRoute; // route set, UAC order (see DialogRoute)
|
||||
std::optional<std::string> remoteTarget; // remote Contact URI
|
||||
std::string localUri; // our URI in this dialog: the UAS takes the
|
||||
// INVITE's To URI; empty = CallerId(c) (UAC)
|
||||
};
|
||||
|
||||
// The identity we sign our own requests with: the registered public
|
||||
// identity (P-Associated-URI sip: entry), else the tel: one, and the
|
||||
// IMSI-derived temporary IMPU only before either is learned. 3GPP allows
|
||||
// the temporary IMPU in REGISTER alone; a P-CSCF that does not overwrite
|
||||
// From shows it to the callee (Telia, 2026-09-01: the reporter's IMSI on
|
||||
// the far phone) and a strict one may reject the request.
|
||||
inline std::string_view CallerId(const Context& c) {
|
||||
if (!c.aor.empty()) return c.aor;
|
||||
if (!c.ppi.empty()) return c.ppi;
|
||||
return c.id.impu;
|
||||
}
|
||||
|
||||
// From URI of a request inside a dialog (RFC 3261 12.2.1.1: the dialog's
|
||||
// local URI — as UAS that is the INVITE's To, as UAC our caller identity).
|
||||
inline std::string_view FromUri(const Context& c, const Dialog& d) {
|
||||
return d.localUri.empty() ? CallerId(c) : std::string_view(d.localUri);
|
||||
}
|
||||
|
||||
// Dialog route set: the reversed Record-Route, or the registration Route
|
||||
// when none was recorded (imsd._dialog_route).
|
||||
inline std::string DialogRoute(const Context& c, const Dialog& d) {
|
||||
|
|
@ -334,7 +314,7 @@ export namespace imsd::msg {
|
|||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||
"Max-Forwards: 70",
|
||||
std::format("Route: {}", c.route),
|
||||
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
||||
std::format("To: <{}>", d.ruri),
|
||||
std::format("Call-ID: {}", d.callid),
|
||||
"CSeq: 1 INVITE",
|
||||
|
|
@ -367,7 +347,7 @@ export namespace imsd::msg {
|
|||
ViaLine(c, "TCP", c.portUs, viaBranch),
|
||||
"Max-Forwards: 70",
|
||||
std::format("Route: {}", DialogRoute(c, d)),
|
||||
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
||||
std::format("To: {}", to),
|
||||
std::format("Call-ID: {}", d.callid),
|
||||
std::format("CSeq: {} {}", cseq, method),
|
||||
|
|
@ -394,7 +374,7 @@ export namespace imsd::msg {
|
|||
ViaLine(c, "TCP", c.portUs, viaBranch),
|
||||
"Max-Forwards: 70",
|
||||
std::format("Route: {}", DialogRoute(c, d)),
|
||||
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
||||
std::format("To: {}", to),
|
||||
std::format("Call-ID: {}", d.callid),
|
||||
"CSeq: 1 ACK",
|
||||
|
|
@ -411,7 +391,7 @@ export namespace imsd::msg {
|
|||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||
"Max-Forwards: 70",
|
||||
std::format("Route: {}", c.route),
|
||||
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
||||
std::format("To: {}", toHeader),
|
||||
std::format("Call-ID: {}", d.callid),
|
||||
"CSeq: 1 ACK",
|
||||
|
|
@ -427,7 +407,7 @@ export namespace imsd::msg {
|
|||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||
"Max-Forwards: 70",
|
||||
std::format("Route: {}", c.route),
|
||||
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
||||
std::format("To: <{}>", d.ruri),
|
||||
std::format("Call-ID: {}", d.callid),
|
||||
"CSeq: 1 CANCEL",
|
||||
|
|
|
|||
|
|
@ -11,14 +11,7 @@
|
|||
#
|
||||
# Configuration, via the unit's EnvironmentFile /etc/imsd.env (also read
|
||||
# directly so manual runs behave the same):
|
||||
# IMS_APN ims APN name (default ims)
|
||||
# IMS_IP_TYPE bearer ip-type (default ipv6)
|
||||
# IMS_PROFILE_ID 3GPP profile index to connect through (default: looked up
|
||||
# by APN in the modem's profile list; empty/none = connect by
|
||||
# APN string). A profile-indexed call is exempt from the
|
||||
# modem's attach-family forcing that refuses an APN-string
|
||||
# IPv6 request when the attach PDN was granted IPv4-only
|
||||
# (journal/ims 2026-09-16/17 bench: Odido, O2 UK, Swisscom).
|
||||
# IMS_REG_TIMEOUT max seconds to wait for network registration before
|
||||
# connect attempts start counting anyway (default 300)
|
||||
|
||||
|
|
@ -41,9 +34,6 @@ envval() { # $1 = key — for manual runs; under systemd the vars are inherited
|
|||
}
|
||||
IP_TYPE=${IMS_IP_TYPE:-$(envval IMS_IP_TYPE)}
|
||||
IP_TYPE=${IP_TYPE:-ipv6}
|
||||
IMS_APN=${IMS_APN:-$(envval IMS_APN)}
|
||||
IMS_APN=${IMS_APN:-ims}
|
||||
PROFILE_ID=${IMS_PROFILE_ID:-$(envval IMS_PROFILE_ID)}
|
||||
REG_TIMEOUT=${IMS_REG_TIMEOUT:-$(envval IMS_REG_TIMEOUT)}
|
||||
REG_TIMEOUT=${REG_TIMEOUT:-300}
|
||||
|
||||
|
|
@ -58,30 +48,11 @@ while :; do
|
|||
done
|
||||
log "modem $MODEM"
|
||||
|
||||
# ---- the ims profile index (WDS profile list), unless configured
|
||||
ims_profile_index() { # $1 = apn
|
||||
qmicli -d qrtr://0 --wds-get-profile-list=3gpp 2>/dev/null | awk -v apn="$1" '
|
||||
/^[ \t]*\[[0-9]+\] 3gpp/ { idx = $1; gsub(/[^0-9]/, "", idx) }
|
||||
/APN:/ { a = $0; sub(/.*APN: '"'"'/, "", a); sub(/'"'"'.*/, "", a);
|
||||
if (tolower(a) == tolower(apn) && idx != "") { print idx; exit } }'
|
||||
}
|
||||
if [ -z "$PROFILE_ID" ]; then
|
||||
PROFILE_ID=$(ims_profile_index "$IMS_APN")
|
||||
[ -n "$PROFILE_ID" ] && log "ims profile: index $PROFILE_ID (apn $IMS_APN)" || log "ims profile: none for apn $IMS_APN, connecting by APN string"
|
||||
elif [ "$PROFILE_ID" = none ]; then
|
||||
PROFILE_ID=
|
||||
fi
|
||||
|
||||
# ---- find a connected ims bearer; else find-or-create one and connect it
|
||||
find_ims_bearer() { # $1 = required bearer.status.connected value
|
||||
for B in $(bearer_paths); do
|
||||
INFO=$(kv -b "$B") || continue
|
||||
if [ -n "$PROFILE_ID" ]; then
|
||||
echo "$INFO" | grep -q "^bearer\.properties\.profile-id *: *$PROFILE_ID\$" ||
|
||||
echo "$INFO" | grep -q "^bearer\.properties\.apn *: *$IMS_APN\$" || continue
|
||||
else
|
||||
echo "$INFO" | grep -q "^bearer\.properties\.apn *: *$IMS_APN\$" || continue
|
||||
fi
|
||||
echo "$INFO" | grep -q '^bearer\.properties\.apn *: *ims$' || continue
|
||||
echo "$INFO" | grep -q "^bearer\.properties\.ip-type *: *$IP_TYPE\$" || continue
|
||||
echo "$INFO" | grep -q "^bearer\.status\.connected *: *$1\$" || continue
|
||||
echo "$B"
|
||||
|
|
@ -119,15 +90,10 @@ while [ -z "$BEARER" ]; do
|
|||
[ "$n" -gt 10 ] && { log "bearer connect failed after 10 attempts"; exit 1; }
|
||||
B=$(find_ims_bearer no) # reuse a stale disconnected ims bearer
|
||||
if [ -z "$B" ]; then
|
||||
if [ -n "$PROFILE_ID" ]; then
|
||||
SPEC="profile-id=$PROFILE_ID,ip-type=$IP_TYPE"
|
||||
else
|
||||
SPEC="apn=$IMS_APN,ip-type=$IP_TYPE"
|
||||
fi
|
||||
OUT=$(mmcli -m "$MODEM" --create-bearer="$SPEC" 2>&1)
|
||||
OUT=$(mmcli -m "$MODEM" --create-bearer="apn=ims,ip-type=$IP_TYPE" 2>&1)
|
||||
B=$(printf '%s' "$OUT" | sed -n 's,.*\(/org/freedesktop/ModemManager1/Bearer/[0-9]*\).*,\1,p')
|
||||
if [ -n "$B" ]; then
|
||||
log "created bearer $B ($SPEC)"
|
||||
log "created bearer $B (ip-type=$IP_TYPE)"
|
||||
else
|
||||
log "create-bearer attempt $n failed: $(squash "$OUT"); retrying in 10 s"
|
||||
sleep 10
|
||||
|
|
|
|||
|
|
@ -164,8 +164,6 @@ int main() {
|
|||
CallMachine m(c, rng, "ims-call-1", "1233", 50004, false);
|
||||
auto start = m.Start();
|
||||
Check(Sent(start, "INVITE ") != nullptr, "Start sends INVITE");
|
||||
Check(Sent(start, "INVITE ")->text.contains("\r\nFrom: <tel:+31611111111>;tag="), "MO INVITE From is our public (tel:) identity, not the IMSI IMPU");
|
||||
Check(!Sent(start, "INVITE ")->text.contains("From: <sip:001010123456789@"), "MO INVITE From never the temporary IMPU once an identity is known");
|
||||
Check(Has(start, Action::Type::SetDeadline), "Start arms ring timeout");
|
||||
Check(m.State() == CallState::Dialing, "starts dialing");
|
||||
|
||||
|
|
@ -612,8 +610,7 @@ int main() {
|
|||
Check(bye && bye->text.starts_with("BYE sip:origgw@[2001:db8::9]:5060 SIP/2.0"), "BYE targets the caller's Contact");
|
||||
Check(bye && bye->text.contains("CSeq: 1 BYE\r\n"), "our UAS CSeq space starts at 1");
|
||||
Check(bye && bye->text.contains("To: <sip:+31612345678@ims.mnc001.mcc001.3gppnetwork.org;user=phone>" ";tag=caller1\r\n"), "BYE To is the caller (their tag)");
|
||||
Check(bye && bye->text.contains("From: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone>;tag="), "UAS BYE From is the INVITE's To URI (our tag), not the IMSI IMPU");
|
||||
Check(bye && !bye->text.contains("001010123456789@ims"), "UAS BYE carries no IMSI-derived identity");
|
||||
Check(bye && bye->text.contains("From: <sip:001010123456789@ims.mnc001.mcc001." "3gppnetwork.org>;tag="), "BYE From is us (our tag)");
|
||||
// UAS route set: Record-Route in RECEIVED order
|
||||
Check(bye && bye->text.contains("Route: <sip:[2001:db8::105]:5100;lr>, " "<sip:[2001:db8::9]:5060;lr>\r\n"), "BYE routes via Record-Route in received order");
|
||||
const Action* t = Find(ah, Action::Type::State);
|
||||
|
|
@ -831,22 +828,6 @@ int main() {
|
|||
Check(inv && inv->text.contains("a=rtpmap:100 telephone-event/8000\r\n") && !inv->text.contains("telephone-event/16000"), "CODECS=PCMA,PCMU: narrowband DTMF only");
|
||||
}
|
||||
|
||||
// ---- MO with the registered sip: identity learned (the normal case on a
|
||||
// live network): From, CANCEL and BYE all carry it, never the IMSI
|
||||
{
|
||||
Context ca = MakeCtx();
|
||||
ca.aor = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org";
|
||||
imsd::util::Rng rng(61);
|
||||
CallMachine m(ca, rng, "ims-call-30", "1233", 50004, false);
|
||||
auto start = m.Start();
|
||||
const Action* inv = Sent(start, "INVITE ");
|
||||
Check(inv && inv->text.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag="), "MO INVITE From is the registered sip: identity");
|
||||
auto cancel = m.OnHangup();
|
||||
const Action* can = Sent(cancel, "CANCEL ");
|
||||
Check(can && can->text.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag="), "CANCEL From is the same identity");
|
||||
Check(can && !can->text.contains("001010123456789@ims"), "CANCEL carries no IMSI");
|
||||
}
|
||||
|
||||
if (Failures == 0) std::println("Engine: all tests passed");
|
||||
return Failures;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ namespace {
|
|||
"Via: SIP/2.0/TCP [2001:db8::db43]:45062;branch=z9hG4bKINVBRANCH0000000000;rport\r\n"
|
||||
"Max-Forwards: 70\r\n"
|
||||
"Route: <sip:[2001:db8::105]:6000;lr>\r\n"
|
||||
"From: <tel:+31611111111>;tag=ITAG123456\r\n"
|
||||
"From: <sip:001010123456789@ims.mnc001.mcc001.3gppnetwork.org>;tag=ITAG123456\r\n"
|
||||
"To: <sip:1233;phone-context=ims.mnc001.mcc001.3gppnetwork.org@ims.mnc001.mcc001.3gppnetwork.org;user=phone>\r\n"
|
||||
"Call-ID: CALLID789@2001:db8::db43\r\n"
|
||||
"CSeq: 1 INVITE\r\n"
|
||||
|
|
@ -230,38 +230,6 @@ int main() {
|
|||
Eq(BuildInvite(c, d, Sdp, /*precond=*/false), Invite, "INVITE + AMR-WB SDP offer");
|
||||
}
|
||||
|
||||
// ---- From carries our PUBLIC identity, never the IMSI-derived temporary
|
||||
// IMPU once anything better is known (ledger F1: a Telia P-CSCF showed
|
||||
// the reporter's IMSI to the callee). aor > tel: ppi > temp IMPU; the
|
||||
// same URI on every request of the dialog; a UAS signs with the
|
||||
// INVITE's To.
|
||||
{
|
||||
Dialog d;
|
||||
d.ruri = RuriFor("1233", c.id.domain);
|
||||
d.callid = "CID@x"; d.itag = "it"; d.invBranch = "ib";
|
||||
Context c2 = MakeCtx();
|
||||
c2.aor = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org";
|
||||
std::string inv = BuildInvite(c2, d, "v=0\r\n", false);
|
||||
Check(inv.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "INVITE From is the registered sip: identity when known");
|
||||
Check(!inv.contains("001010123456789@ims"), "INVITE never carries the IMSI IMPU once the aor is known");
|
||||
Check(inv.contains("\r\nP-Preferred-Identity: <tel:+31611111111>\r\n"), "P-Preferred-Identity unchanged");
|
||||
Check(BuildCancel(c2, d).contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "CANCEL From follows");
|
||||
Check(BuildAckNon2xx(c2, d, "<sip:x@y>;tag=r").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "non-2xx ACK From follows");
|
||||
d.dialogTo = "<sip:x@y>;tag=r"; d.remoteTarget = "sip:x@[2001:db8::9]:5060";
|
||||
Check(BuildAck2xx(c2, d, "ab", "<sip:x@y>;tag=r").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "2xx ACK From follows");
|
||||
Check(BuildInDialog(c2, d, "BYE", 2, "bb").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "BYE From follows");
|
||||
// only tel: known (pre-aor): the tel: identity, as the pinned INVITE shows
|
||||
Check(BuildInvite(c, d, "v=0\r\n", false).contains("\r\nFrom: <tel:+31611111111>;tag=it\r\n"), "INVITE From falls back to the tel: identity");
|
||||
// nothing learned yet: the temporary IMPU is all there is
|
||||
Context c4 = MakeCtx();
|
||||
c4.ppi.clear();
|
||||
Check(BuildInvite(c4, d, "v=0\r\n", false).contains("\r\nFrom: <sip:001010123456789@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "INVITE From is the temporary IMPU only before any identity is learned");
|
||||
// UAS: the dialog's local URI is the INVITE's To, whatever we registered as
|
||||
Dialog du = d;
|
||||
du.localUri = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone";
|
||||
Check(BuildInDialog(c4, du, "BYE", 1, "cc").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone>;tag=it\r\n"), "UAS BYE From is the INVITE's To URI");
|
||||
}
|
||||
|
||||
// ---- emergency INVITE: byte-identical to the plain INVITE except the
|
||||
// Request-URI and To carry the service URN (RFC 5031 / TS 24.229 5.1.6)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue