data.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package device
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "vocat/internal/modem"
  10. )
  11. var apnPattern = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$`)
  12. func (manager *Manager) SetNetwork(
  13. ctx context.Context,
  14. id string,
  15. request NetworkRequest,
  16. ) (NetworkResult, error) {
  17. state, err := manager.lookup(id)
  18. if err != nil {
  19. return NetworkResult{}, err
  20. }
  21. apn := strings.TrimSpace(request.APN)
  22. if request.Enabled && !apnPattern.MatchString(apn) {
  23. return NetworkResult{}, ErrInvalidNetworkAPN
  24. }
  25. ipVersion := normalizeIPVersion(request.IPVersion)
  26. if ipVersion == "" {
  27. return NetworkResult{}, errors.New("IP version must be IP, IPV6, or IPV4V6")
  28. }
  29. state.opMu.Lock()
  30. defer state.opMu.Unlock()
  31. if err := manager.validateActive(id, state); err != nil {
  32. return NetworkResult{}, err
  33. }
  34. if request.Enabled {
  35. if err := manager.regionBlockError(state); err != nil {
  36. manager.setResult(id, state, nil, err)
  37. return NetworkResult{}, err
  38. }
  39. }
  40. candidate := manager.candidateFor(state)
  41. if candidate.QMIControl != "" && candidate.NetworkInterface != "" {
  42. return setQMINetwork(ctx, candidate, request.Enabled, apn, ipVersion)
  43. }
  44. client, err := manager.clientLocked(ctx, state, candidate)
  45. if err != nil {
  46. manager.setResult(id, state, nil, err)
  47. return NetworkResult{}, err
  48. }
  49. if request.Enabled {
  50. commands := []string{
  51. fmt.Sprintf(`AT+CGDCONT=1,"%s","%s"`, ipVersion, apn),
  52. "AT+CGATT=1",
  53. "AT+CGACT=1,1",
  54. }
  55. for _, command := range commands {
  56. if _, err := manager.command(ctx, client, command); err != nil {
  57. manager.setResult(id, state, nil, err)
  58. return NetworkResult{}, err
  59. }
  60. }
  61. } else {
  62. if _, err := manager.command(ctx, client, "AT+CGACT=0,1"); err != nil {
  63. manager.setResult(id, state, nil, err)
  64. return NetworkResult{}, err
  65. }
  66. }
  67. manager.setResult(id, state, nil, nil)
  68. return NetworkResult{
  69. Enabled: request.Enabled,
  70. Backend: "at",
  71. Interface: candidate.NetworkInterface,
  72. APN: apn,
  73. IPVersion: ipVersion,
  74. Detail: map[bool]string{true: "PDP context activated", false: "PDP context deactivated"}[request.Enabled],
  75. }, nil
  76. }
  77. func normalizeIPVersion(value string) string {
  78. switch strings.ToUpper(strings.TrimSpace(value)) {
  79. case "", "IP", "IPV4":
  80. return "IP"
  81. case "IPV6":
  82. return "IPV6"
  83. case "IPV4V6", "IPV6V4":
  84. return "IPV4V6"
  85. default:
  86. return ""
  87. }
  88. }
  89. func (manager *Manager) USBNetMode(ctx context.Context, id string) (USBNetMode, error) {
  90. response, err := manager.ExecuteAT(ctx, id, `AT+QCFG="usbnet"`)
  91. if err != nil {
  92. return USBNetMode{}, err
  93. }
  94. for _, line := range response.Lines {
  95. upper := strings.ToUpper(strings.TrimSpace(line))
  96. if !strings.HasPrefix(upper, `+QCFG: "USBNET",`) {
  97. continue
  98. }
  99. value := strings.TrimSpace(strings.TrimPrefix(upper, `+QCFG: "USBNET",`))
  100. mode, parseErr := strconv.Atoi(value)
  101. if parseErr == nil {
  102. return USBNetMode{Mode: mode, Name: usbNetModeName(mode)}, nil
  103. }
  104. }
  105. return USBNetMode{}, errors.New("modem did not return a valid USB network mode")
  106. }
  107. func (manager *Manager) SetUSBNetMode(ctx context.Context, id string, mode int) (USBNetMode, error) {
  108. if mode < 0 || mode > 3 {
  109. return USBNetMode{}, errors.New("USB network mode must be between 0 and 3")
  110. }
  111. response, err := manager.ExecuteSensitiveAT(ctx, id, fmt.Sprintf(`AT+QCFG="usbnet",%d`, mode))
  112. if err != nil {
  113. return USBNetMode{}, err
  114. }
  115. if !response.OK() {
  116. return USBNetMode{}, &modem.CommandError{Command: response.Command, Final: response.Final, Lines: response.Lines}
  117. }
  118. return USBNetMode{Mode: mode, Name: usbNetModeName(mode)}, nil
  119. }
  120. // SetUSBNetModeByPort sets the USB network mode on a device that has only been
  121. // discovered (not yet taken over), addressed by its AT port path. The port must
  122. // belong to a currently discovered candidate, so the endpoint cannot be used to
  123. // open arbitrary host paths.
  124. func (manager *Manager) SetUSBNetModeByPort(
  125. ctx context.Context,
  126. atPortPath string,
  127. mode int,
  128. ) (USBNetMode, error) {
  129. if mode < 0 || mode > 3 {
  130. return USBNetMode{}, errors.New("USB network mode must be between 0 and 3")
  131. }
  132. atPortPath = strings.TrimSpace(atPortPath)
  133. if atPortPath == "" {
  134. return USBNetMode{}, errors.New("an AT port path is required")
  135. }
  136. manager.mu.RLock()
  137. var candidate modem.Candidate
  138. found := false
  139. for _, state := range manager.devices {
  140. if state.discovered &&
  141. (state.candidate.ATPort.OpenPath() == atPortPath || state.candidate.ATPort.Path == atPortPath) {
  142. candidate = copyCandidate(state.candidate)
  143. found = true
  144. break
  145. }
  146. }
  147. manager.mu.RUnlock()
  148. if !found {
  149. return USBNetMode{}, fmt.Errorf("no discovered device owns AT port %q", atPortPath)
  150. }
  151. client, err := manager.opener.Open(ctx, candidate.ATPort)
  152. if err != nil {
  153. return USBNetMode{}, err
  154. }
  155. defer func() { _ = client.Close() }()
  156. commandCtx, cancel := manager.withTimeout(ctx, manager.commandTimeout)
  157. defer cancel()
  158. response, err := client.Execute(commandCtx, fmt.Sprintf(`AT+QCFG="usbnet",%d`, mode))
  159. if err != nil {
  160. return USBNetMode{}, err
  161. }
  162. if !response.OK() {
  163. return USBNetMode{}, &modem.CommandError{Command: response.Command, Final: response.Final, Lines: response.Lines}
  164. }
  165. return USBNetMode{Mode: mode, Name: usbNetModeName(mode)}, nil
  166. }
  167. func usbNetModeName(mode int) string {
  168. switch mode {
  169. case 0:
  170. return "QMI"
  171. case 1:
  172. return "ECM"
  173. case 2:
  174. return "MBIM"
  175. case 3:
  176. return "RNDIS"
  177. default:
  178. return "unknown"
  179. }
  180. }
  181. func (manager *Manager) OperatorSelection(ctx context.Context, id string) (OperatorSelection, error) {
  182. response, err := manager.ExecuteAT(ctx, id, "AT+COPS?")
  183. if err != nil {
  184. return OperatorSelection{}, err
  185. }
  186. return parseOperatorSelection(response)
  187. }
  188. func parseOperatorSelection(response modem.Response) (OperatorSelection, error) {
  189. values := csvValues(valueAfterPrefix(response, "+COPS:"))
  190. if len(values) < 1 {
  191. return OperatorSelection{}, errors.New("modem did not return operator selection state")
  192. }
  193. result := OperatorSelection{}
  194. result.Mode, _ = strconv.Atoi(values[0])
  195. if len(values) > 1 {
  196. result.Format, _ = strconv.Atoi(values[1])
  197. }
  198. if len(values) > 2 {
  199. result.Operator = strings.Trim(values[2], `"`)
  200. }
  201. if len(values) > 3 {
  202. result.AccessTechnology = accessTechnology(values[3])
  203. }
  204. return result, nil
  205. }
  206. func (manager *Manager) SetOperatorSelection(
  207. ctx context.Context,
  208. id string,
  209. automatic bool,
  210. plmn string,
  211. accessTechnologyValue *int,
  212. ) (OperatorSelection, error) {
  213. result := OperatorSelection{Mode: 0}
  214. command := "AT+COPS=0"
  215. if !automatic {
  216. plmn = strings.TrimSpace(plmn)
  217. if len(plmn) < 5 || len(plmn) > 6 || strings.IndexFunc(plmn, func(r rune) bool { return r < '0' || r > '9' }) >= 0 {
  218. return OperatorSelection{}, errors.New("operator PLMN must contain 5 or 6 digits")
  219. }
  220. // Mode 1 is a real manual lock. Mode 4 is only a manual attempt with
  221. // automatic fallback; using it made a rejected registration silently
  222. // return to COPS=0 while the UI incorrectly reported a successful lock.
  223. command = fmt.Sprintf(`AT+COPS=1,2,"%s"`, plmn)
  224. actName := ""
  225. if accessTechnologyValue != nil {
  226. if *accessTechnologyValue < 0 || *accessTechnologyValue > 9 {
  227. return OperatorSelection{}, errors.New("invalid operator access technology")
  228. }
  229. command += fmt.Sprintf(",%d", *accessTechnologyValue)
  230. actName = accessTechnology(strconv.Itoa(*accessTechnologyValue))
  231. }
  232. result = OperatorSelection{Mode: 1, Format: 2, Operator: plmn, AccessTechnology: actName}
  233. }
  234. state, err := manager.lookup(id)
  235. if err != nil {
  236. return OperatorSelection{}, err
  237. }
  238. state.opMu.Lock()
  239. defer state.opMu.Unlock()
  240. if err := manager.validateActive(id, state); err != nil {
  241. return OperatorSelection{}, err
  242. }
  243. client, err := manager.clientLocked(ctx, state, manager.candidateFor(state))
  244. if err != nil {
  245. manager.setResult(id, state, nil, err)
  246. return OperatorSelection{}, err
  247. }
  248. // Manual PLMN selection makes the modem search for and register on the
  249. // requested network, which can take tens of seconds — far longer than the
  250. // normal command timeout. Use the same deadline budget as operator scan so
  251. // the lock is not aborted while registration is still in progress.
  252. lockCtx, cancel := manager.withTimeout(ctx, manager.scanTimeout)
  253. defer cancel()
  254. if _, err := client.Execute(lockCtx, command); err != nil {
  255. manager.setResult(id, state, nil, errors.New("operator selection command failed"))
  256. return OperatorSelection{}, err
  257. }
  258. if !automatic {
  259. response, err := client.Execute(lockCtx, "AT+COPS?")
  260. if err != nil {
  261. manager.setResult(id, state, nil, err)
  262. return OperatorSelection{}, fmt.Errorf("verify manual operator selection: %w", err)
  263. }
  264. actual, err := parseOperatorSelection(response)
  265. if err != nil {
  266. manager.setResult(id, state, nil, err)
  267. return OperatorSelection{}, err
  268. }
  269. if actual.Mode != 1 || actual.Operator != plmn {
  270. err := fmt.Errorf("network %s did not accept registration; modem reports mode=%d operator=%q", plmn, actual.Mode, actual.Operator)
  271. manager.setResult(id, state, nil, err)
  272. return OperatorSelection{}, err
  273. }
  274. result = actual
  275. }
  276. manager.setResult(id, state, nil, nil)
  277. return result, nil
  278. }