diff --git a/internal/vowifi/carrier_ipcc.go b/internal/vowifi/carrier_ipcc.go index e84e181..a84c026 100644 --- a/internal/vowifi/carrier_ipcc.go +++ b/internal/vowifi/carrier_ipcc.go @@ -661,6 +661,12 @@ func inspectIgnoredCarrierFields(plists []ipccPlist, warnings *ipccWarningSet) { warnings.add("apn_settings_ignored", "APN settings and credentials are outside the VoCat carrier-profile importer", fullPath) case key == "media" && strings.Contains(strings.ToLower(strings.Join(keyPath, ".")), "imsconfig"): warnings.add("device_media_overrides_ignored", "device-family media and codec overrides require hardware validation and were not imported", fullPath) + case key == "countryoforiginationformat": + warnings.add( + "country_of_origination_format_not_imported", + "CountryOfOriginationFormat was not imported because VoCat has no trusted runtime country source; a P-Access-Network-Info value must not be fabricated", + fullPath, + ) case strings.Contains(key, "emergency") || strings.Contains(key, "e911"): warnings.add("emergency_settings_ignored", "emergency-service settings are never imported", fullPath) } diff --git a/internal/vowifi/carrier_ipcc_test.go b/internal/vowifi/carrier_ipcc_test.go index 67b1a9e..a9a4a9e 100644 --- a/internal/vowifi/carrier_ipcc_test.go +++ b/internal/vowifi/carrier_ipcc_test.go @@ -42,9 +42,12 @@ func TestImportCarrierIPCCConvertsBinaryAndXMLPlistsSafely(t *testing.T) { }, "IMSConfig": map[string]any{ "EnableWiFiCallingWithoutEntitlement": true, - "Signaling": map[string]any{"UseIPSec": true}, - "Media": map[string]any{"SupportPCMA": false}, - "Emergency": map[string]any{"E911OverITechSupported": true}, + "Signaling": map[string]any{ + "UseIPSec": true, + "CountryOfOriginationFormat": "PANI", + }, + "Media": map[string]any{"SupportPCMA": false}, + "Emergency": map[string]any{"E911OverITechSupported": true}, }, }, }, @@ -79,6 +82,7 @@ func TestImportCarrierIPCCConvertsBinaryAndXMLPlistsSafely(t *testing.T) { "entitlement_bypass_ignored", "apn_settings_ignored", "device_media_overrides_ignored", + "country_of_origination_format_not_imported", "emergency_settings_ignored", } { if !hasIPCCWarning(result.Warnings, code) { diff --git a/internal/vowifi/ims/provider.go b/internal/vowifi/ims/provider.go index c68fc90..1237c44 100644 --- a/internal/vowifi/ims/provider.go +++ b/internal/vowifi/ims/provider.go @@ -991,19 +991,17 @@ func (session *Session) buildRegister( } lines = append(lines, "User-Agent: "+userAgent) - defaultPANI := "IEEE-802.11;i-wlan-node-id=000000000000;network-provided" - pani := defaultPANI - if registerOptions.PAccessNetworkInfo != nil { - pani = *registerOptions.PAccessNetworkInfo - } - if registerOptions.PPreferredIdentity { lines = append(lines, "P-Preferred-Identity: <"+session.identity.public+">") } if value := strings.TrimSpace(registerOptions.PVisitedNetworkID); value != "" { lines = append(lines, `P-Visited-Network-ID: "`+value+`"`) } - if pani != "" { + // PANI carries access/location information and must not be fabricated. + // In particular, "network-provided" identifies a value inserted by a + // trusted network proxy, not one generated by this UE. Send the header + // only when a carrier profile explicitly supplies a reviewed value. + if pani := optionalRegisterHeader(registerOptions.PAccessNetworkInfo); pani != "" { lines = append(lines, "P-Access-Network-Info: "+pani) } if value := strings.TrimSpace(registerOptions.CellularNetworkInfo); value != "" { @@ -1047,6 +1045,13 @@ func (session *Session) buildRegister( return []byte(strings.Join(lines, "\r\n")), nil } +func optionalRegisterHeader(value *string) string { + if value == nil { + return "" + } + return strings.TrimSpace(*value) +} + func (session *Session) buildContact(contactAddress string, registerOptions vowifi.IMSRegisterOptions) string { base := fmt.Sprintf("", session.identity.user, contactAddress, session.transport) instanceID := session.instanceID diff --git a/internal/vowifi/ims/provider_test.go b/internal/vowifi/ims/provider_test.go index 25d4341..4792dff 100644 --- a/internal/vowifi/ims/provider_test.go +++ b/internal/vowifi/ims/provider_test.go @@ -377,6 +377,7 @@ func serveRegistration(listener *net.UDPConn, nonce string, confirmSMS bool) err for _, forbidden := range []string{ "p-visited-network-id", "p-preferred-identity", + "p-access-network-info", } { if headers[forbidden] != "" { return fmt.Errorf( @@ -386,9 +387,6 @@ func serveRegistration(listener *net.UDPConn, nonce string, confirmSMS bool) err ) } } - if headers["p-access-network-info"] != "IEEE-802.11;i-wlan-node-id=000000000000;network-provided" { - return fmt.Errorf("REGISTER P-Access-Network-Info = %q", headers["p-access-network-info"]) - } if !strings.Contains(headers["allow"], "MESSAGE") || !strings.Contains(string(packet[:count]), "Accept-Contact: *;+g.3gpp.smsip") { return fmt.Errorf("REGISTER omitted SMS-over-IMS capability: Allow=%q", headers["allow"]) @@ -497,6 +495,26 @@ func serveRegistration(listener *net.UDPConn, nonce string, confirmSMS bool) err return nil } +func TestOptionalRegisterHeaderRequiresExplicitNonemptyValue(t *testing.T) { + explicit := " IEEE-802.11;i-wlan-node-id=aabbccddeeff " + empty := " " + for _, test := range []struct { + name string + value *string + want string + }{ + {name: "unspecified", value: nil, want: ""}, + {name: "explicit omission", value: &empty, want: ""}, + {name: "explicit value", value: &explicit, want: "IEEE-802.11;i-wlan-node-id=aabbccddeeff"}, + } { + t.Run(test.name, func(t *testing.T) { + if got := optionalRegisterHeader(test.value); got != test.want { + t.Fatalf("optionalRegisterHeader() = %q, want %q", got, test.want) + } + }) + } +} + func serveRefreshFailure(listener *net.UDPConn, nonce string) error { var callID string for step := 0; step < 3; step++ {