region.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. package device
  2. import (
  3. "fmt"
  4. "strings"
  5. "unicode"
  6. "vocat/internal/i18n"
  7. )
  8. // BlockedMCCs lists the mobile country codes whose SIM cards must not be
  9. // served by this product. The product does not provide service to mainland
  10. // China cards; the set mirrors the CN entry of the MCC table used for upstream
  11. // proxy routing (460/461). It is keyed by MCC with a display name for logs and
  12. // user-facing messaging.
  13. var BlockedMCCs = map[string]string{
  14. "460": "中国",
  15. "461": "中国",
  16. }
  17. // CardMCCMNC splits an IMSI into its mobile country code and mobile network
  18. // code. The MCC is the leading three digits and the MNC the following two or
  19. // three. Empty strings are returned for an unusable IMSI.
  20. func CardMCCMNC(imsi string) (mcc string, mnc string) {
  21. digits := strings.TrimSpace(imsi)
  22. if len(digits) < 5 ||
  23. strings.IndexFunc(digits, func(r rune) bool { return !unicode.IsDigit(r) }) >= 0 {
  24. return "", ""
  25. }
  26. mcc = digits[:3]
  27. mnc = digits[3:]
  28. if len(mnc) > 3 {
  29. mnc = mnc[:3]
  30. }
  31. return mcc, mnc
  32. }
  33. // RegionBlockReason returns a human-readable reason when the SIM identified by
  34. // the IMSI belongs to a blocked region. It returns an empty string when the
  35. // card is allowed or when the IMSI is unavailable: only a confirmed blocked
  36. // MCC triggers a block (fail-open), so a transient IMSI read failure never
  37. // denies service to a legitimate card.
  38. func RegionBlockReason(imsi string) string {
  39. mcc, _ := CardMCCMNC(imsi)
  40. country, blocked := BlockedMCCs[mcc]
  41. if !blocked {
  42. return ""
  43. }
  44. return i18n.Tf("SIM 卡归属地为%s(MCC %s),本服务不向该地区卡片提供数据/短信/VoWiFi", i18n.T(country), mcc)
  45. }
  46. // regionBlockError reports whether the currently inserted SIM must not be
  47. // served. It reads only the cached snapshot IMSI and never issues an extra AT
  48. // command, so it adds no modem round-trip and leaves guarded command
  49. // transcripts untouched. A missing snapshot or IMSI yields nil (fail-open);
  50. // the periodic region enforcement forces airplane mode as the authoritative
  51. // backstop.
  52. func (manager *Manager) regionBlockError(state *managedDevice) error {
  53. manager.mu.RLock()
  54. var snapshot *Snapshot
  55. if state.snapshot != nil {
  56. value := *state.snapshot
  57. snapshot = &value
  58. }
  59. manager.mu.RUnlock()
  60. if snapshot == nil {
  61. return nil
  62. }
  63. if reason := RegionBlockReason(snapshot.IMSI); reason != "" {
  64. return fmt.Errorf("%w: %s", ErrRegionBlocked, reason)
  65. }
  66. return nil
  67. }