diff --git a/internal/vowifi/ims/call_runtime.go b/internal/vowifi/ims/call_runtime.go index 1105122..9e4d3d9 100644 --- a/internal/vowifi/ims/call_runtime.go +++ b/internal/vowifi/ims/call_runtime.go @@ -115,7 +115,9 @@ func (session *Session) DialCall(ctx context.Context, number string) (vowifi.Cal "P-Preferred-Identity: <"+preferredIdentity+">", "P-Preferred-Service: "+mmtelServiceURN, `Accept-Contact: *;+g.3gpp.icsi-ref="`+mmtelFeatureTag+`"`, - "P-Access-Network-Info: "+session.pAccessNetworkInfo(), + ) + lines = session.appendPAccessNetworkInfoHeader(lines) + lines = append(lines, "User-Agent: "+session.imsUserAgent(), "Allow: INVITE, ACK, CANCEL, BYE, OPTIONS, MESSAGE, PRACK, UPDATE, INFO", "Supported: 100rel, timer, replaces", @@ -499,7 +501,9 @@ func (session *Session) sendRejectedInviteACK(call *imsCall, response *sipRespon "To: "+to, "Call-ID: "+call.callID, fmt.Sprintf("CSeq: %d ACK", call.cseq), - "P-Access-Network-Info: "+session.pAccessNetworkInfo(), + ) + lines = session.appendPAccessNetworkInfoHeader(lines) + lines = append(lines, "User-Agent: "+session.imsUserAgent(), "Content-Length: 0", "", "", ) @@ -577,7 +581,9 @@ func (session *Session) sendPRACK(call *imsCall, response *sipResponse) { lines = append(lines, "From: "+from, "To: "+to, "Call-ID: "+call.callID, fmt.Sprintf("CSeq: %d PRACK", cseq), "RAck: "+rseq+" "+inviteCSeq, - "P-Access-Network-Info: "+session.pAccessNetworkInfo(), + ) + lines = session.appendPAccessNetworkInfoHeader(lines) + lines = append(lines, "User-Agent: "+session.imsUserAgent(), "Content-Length: 0", "", "", ) @@ -712,7 +718,7 @@ func (session *Session) buildDialogRequest(call *imsCall, method string, cseq ui "User-Agent: "+session.imsUserAgent(), ) if method != "CANCEL" { - lines = append(lines, "P-Access-Network-Info: "+session.pAccessNetworkInfo()) + lines = session.appendPAccessNetworkInfoHeader(lines) } if method == "UPDATE" { lines = append(lines, session.dialogContactHeader()) @@ -827,6 +833,13 @@ func (session *Session) pAccessNetworkInfo() string { return resolveSessionPAccessNetworkInfo(session.request.Identity, session.imsLogger()) } +func (session *Session) appendPAccessNetworkInfoHeader(lines []string) []string { + if pani := session.pAccessNetworkInfo(); pani != "" { + return append(lines, "P-Access-Network-Info: "+pani) + } + return lines +} + func callResponseDiagnostic(response *sipResponse) string { if response == nil { return "" diff --git a/internal/vowifi/ims/call_runtime_test.go b/internal/vowifi/ims/call_runtime_test.go index fe96d3b..ea911da 100644 --- a/internal/vowifi/ims/call_runtime_test.go +++ b/internal/vowifi/ims/call_runtime_test.go @@ -4,6 +4,8 @@ import ( "context" "io" "net" + "os" + "path/filepath" "strings" "testing" "time" @@ -239,6 +241,50 @@ func TestOutgoingLocalNumberUsesIMSPhoneContextAndMMTelHeaders(t *testing.T) { } } +func TestDialogRequestOmitsPAccessNetworkInfoWhenProfileDisablesPANI(t *testing.T) { + profileDir := t.TempDir() + profile := `{"version":1,"profiles":[{"id":"test-pani-disabled","match":{"home_plmns":["00101"]},"ims":{"pani_enabled":false}}]}` + if err := os.WriteFile(filepath.Join(profileDir, "pani-disabled.json"), []byte(profile), 0o600); err != nil { + t.Fatal(err) + } + emptyProfileDir := t.TempDir() + t.Cleanup(func() { + if err := vowifi.LoadCarrierProfileDirectory(emptyProfileDir); err != nil { + t.Errorf("clear external carrier profiles: %v", err) + } + }) + if err := vowifi.LoadCarrierProfileDirectory(profileDir); err != nil { + t.Fatal(err) + } + + identity := vowifi.SIMIdentity{HomeMCC: "001", HomeMNC: "01", IMSI: "001010123456789"} + pani := resolveSessionPAccessNetworkInfo(identity, nil) + if pani != "" { + t.Fatalf("disabled profile PANI = %q, want empty", pani) + } + client, peer := net.Pipe() + defer client.Close() + defer peer.Close() + session := &Session{ + request: vowifi.IMSRequest{Identity: identity}, + transport: "tcp", + conn: client, + pani: pani, + paniResolved: true, + } + call := &imsCall{ + target: "sip:callee@example.test", + from: ";tag=local", + to: ";tag=remote", + callID: "pani-disabled-call", + } + + request := string(session.buildDialogRequest(call, "BYE", 2)) + if strings.Contains(request, "\r\nP-Access-Network-Info:") { + t.Fatalf("BYE contains disabled P-Access-Network-Info header:\n%s", request) + } +} + func TestCallOriginatingIdentitiesFallBackToRegisteredIMPU(t *testing.T) { session := &Session{ identity: identitySet{