package device import ( "fmt" "strings" "unicode" "vocat/internal/i18n" ) // BlockedMCCs lists the mobile country codes whose SIM cards must not be // served by this product. The product does not provide service to mainland // China cards; the set mirrors the CN entry of the MCC table used for upstream // proxy routing (460/461). It is keyed by MCC with a display name for logs and // user-facing messaging. var BlockedMCCs = map[string]string{ "460": "中国", "461": "中国", } // CardMCCMNC splits an IMSI into its mobile country code and mobile network // code. The MCC is the leading three digits and the MNC the following two or // three. Empty strings are returned for an unusable IMSI. func CardMCCMNC(imsi string) (mcc string, mnc string) { digits := strings.TrimSpace(imsi) if len(digits) < 5 || strings.IndexFunc(digits, func(r rune) bool { return !unicode.IsDigit(r) }) >= 0 { return "", "" } mcc = digits[:3] mnc = digits[3:] if len(mnc) > 3 { mnc = mnc[:3] } return mcc, mnc } // RegionBlockReason returns a human-readable reason when the SIM identified by // the IMSI belongs to a blocked region. It returns an empty string when the // card is allowed or when the IMSI is unavailable: only a confirmed blocked // MCC triggers a block (fail-open), so a transient IMSI read failure never // denies service to a legitimate card. func RegionBlockReason(imsi string) string { mcc, _ := CardMCCMNC(imsi) country, blocked := BlockedMCCs[mcc] if !blocked { return "" } return i18n.Tf("SIM 卡归属地为%s(MCC %s),本服务不向该地区卡片提供数据/短信/VoWiFi", i18n.T(country), mcc) } // regionBlockError reports whether the currently inserted SIM must not be // served. It reads only the cached snapshot IMSI and never issues an extra AT // command, so it adds no modem round-trip and leaves guarded command // transcripts untouched. A missing snapshot or IMSI yields nil (fail-open); // the periodic region enforcement forces airplane mode as the authoritative // backstop. func (manager *Manager) regionBlockError(state *managedDevice) error { manager.mu.RLock() var snapshot *Snapshot if state.snapshot != nil { value := *state.snapshot snapshot = &value } manager.mu.RUnlock() if snapshot == nil { return nil } if reason := RegionBlockReason(snapshot.IMSI); reason != "" { return fmt.Errorf("%w: %s", ErrRegionBlocked, reason) } return nil }