@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -1,168 +0,0 @@
1
- // The classifier's contract, one fixture per class — plus the HONESTY guarantee: an
2
- // ambiguous descriptor set returns `unmanaged` with a truthful reason, never a
3
- // confident guess, and mass storage without a modeswitch trigger is NOT a modem.
4
-
5
- import { describe, expect, test } from 'bun:test';
6
- import { classifyDevice, detectUsbMode, type UsbDeviceSnapshot } from './device-classifier';
7
-
8
- /** A Quectel-style QMI composition: a `qmi_wwan` control port (+ AT serials). */
9
- const QMI: UsbDeviceSnapshot = {
10
- vendorId: '2c7c',
11
- productId: '0125',
12
- model: 'EG25-G',
13
- bDeviceClass: 0,
14
- interfaces: [
15
- { interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'option' },
16
- { interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'qmi_wwan' },
17
- ],
18
- };
19
-
20
- /** An MBIM composition: the standard 0x02/0x0e control + 0x0a data pair. */
21
- const MBIM: UsbDeviceSnapshot = {
22
- vendorId: '1199',
23
- productId: '9071',
24
- model: 'Sierra EM-series',
25
- bDeviceClass: 0,
26
- interfaces: [
27
- { interfaceClass: 0x02, interfaceSubClass: 0x0e, interfaceProtocol: 0x00, driver: 'cdc_mbim' },
28
- { interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x02, driver: 'cdc_mbim' },
29
- ],
30
- };
31
-
32
- /** A Huawei HiLink personality: CDC-ECM Ethernet, no control port — firmware, not switchable. */
33
- const HILINK_ECM: UsbDeviceSnapshot = {
34
- vendorId: '12d1',
35
- productId: '14db',
36
- model: 'HUAWEI HiLink',
37
- bDeviceClass: 0x02,
38
- interfaces: [
39
- { interfaceClass: 0x02, interfaceSubClass: 0x06, interfaceProtocol: 0x00, driver: 'cdc_ether' },
40
- { interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x00, driver: 'cdc_ether' },
41
- ],
42
- };
43
-
44
- /** An RNDIS tether: the wireless-RNDIS descriptor, no control port. */
45
- const RNDIS: UsbDeviceSnapshot = {
46
- vendorId: '19d2',
47
- productId: '0601',
48
- model: 'RNDIS tether',
49
- bDeviceClass: 0x00,
50
- interfaces: [
51
- {
52
- interfaceClass: 0xe0,
53
- interfaceSubClass: 0x01,
54
- interfaceProtocol: 0x03,
55
- driver: 'rndis_host',
56
- },
57
- {
58
- interfaceClass: 0x0a,
59
- interfaceSubClass: 0x00,
60
- interfaceProtocol: 0x00,
61
- driver: 'rndis_host',
62
- },
63
- ],
64
- };
65
-
66
- /** A modem in mass-storage installer mode, flagged for usb_modeswitch. */
67
- const STORAGE_PRE_MODESWITCH: UsbDeviceSnapshot = {
68
- vendorId: '12d1',
69
- productId: '1f01',
70
- model: 'HUAWEI Mobile',
71
- bDeviceClass: 0x00,
72
- interfaces: [
73
- {
74
- interfaceClass: 0x08,
75
- interfaceSubClass: 0x06,
76
- interfaceProtocol: 0x50,
77
- driver: 'usb-storage',
78
- },
79
- ],
80
- udevProperties: { ID_USB_MODESWITCH: '1' },
81
- };
82
-
83
- /** A composite that matches NO known modem pattern — vendor + HID, no driver. */
84
- const AMBIGUOUS: UsbDeviceSnapshot = {
85
- vendorId: '1234',
86
- productId: '5678',
87
- model: 'Unknown Composite',
88
- bDeviceClass: 0x00,
89
- interfaces: [
90
- { interfaceClass: 0x03, interfaceSubClass: 0x00, interfaceProtocol: 0x00 },
91
- { interfaceClass: 0xff, interfaceSubClass: 0x42, interfaceProtocol: 0x01 },
92
- ],
93
- };
94
-
95
- describe('classifyDevice — the six canonical fixtures', () => {
96
- test('QMI composition → mm-managed', () => {
97
- expect(classifyDevice(QMI).deviceClass).toBe('mm-managed');
98
- expect(detectUsbMode(QMI)).toBe('qmi');
99
- });
100
-
101
- test('MBIM composition → mm-managed', () => {
102
- const result = classifyDevice(MBIM);
103
- expect(result.deviceClass).toBe('mm-managed');
104
- expect(result.reason).toContain('MBIM');
105
- expect(detectUsbMode(MBIM)).toBe('mbim');
106
- });
107
-
108
- test('Huawei HiLink-ECM (firmware personality) → router-mode', () => {
109
- const result = classifyDevice(HILINK_ECM);
110
- expect(result.deviceClass).toBe('router-mode');
111
- expect(result.reason).toContain('ECM');
112
- expect(detectUsbMode(HILINK_ECM)).toBe('router-ethernet');
113
- });
114
-
115
- test('RNDIS tether → router-mode', () => {
116
- const result = classifyDevice(RNDIS);
117
- expect(result.deviceClass).toBe('router-mode');
118
- expect(result.reason).toContain('RNDIS');
119
- expect(detectUsbMode(RNDIS)).toBe('rndis');
120
- });
121
-
122
- test('mass-storage before usb_modeswitch → pending-modeswitch (a distinct state)', () => {
123
- const result = classifyDevice(STORAGE_PRE_MODESWITCH);
124
- expect(result.deviceClass).toBe('pending-modeswitch');
125
- expect(result.reason).toContain('usb_modeswitch');
126
- });
127
-
128
- test('ambiguous composite → unmanaged, with an HONEST reason (never guessed)', () => {
129
- const result = classifyDevice(AMBIGUOUS);
130
- expect(result.deviceClass).toBe('unmanaged');
131
- expect(result.reason).toContain('vendor-specific');
132
- // Never a confident modem/router class.
133
- expect(result.deviceClass).not.toBe('mm-managed');
134
- expect(result.deviceClass).not.toBe('router-mode');
135
- });
136
- });
137
-
138
- describe('classifyDevice — honesty guards', () => {
139
- test('mass storage WITHOUT a modeswitch trigger is unmanaged, not pending-modeswitch', () => {
140
- const plainDisk: UsbDeviceSnapshot = {
141
- vendorId: '0781',
142
- productId: '5567',
143
- model: 'SanDisk Cruzer',
144
- bDeviceClass: 0x00,
145
- interfaces: [
146
- {
147
- interfaceClass: 0x08,
148
- interfaceSubClass: 0x06,
149
- interfaceProtocol: 0x50,
150
- driver: 'usb-storage',
151
- },
152
- ],
153
- };
154
- const result = classifyDevice(plainDisk);
155
- expect(result.deviceClass).toBe('unmanaged');
156
- expect(result.reason).toContain('usb_modeswitch');
157
- });
158
-
159
- test('a bare vendor interface with no driver is NOT assumed to be a QMI modem', () => {
160
- const bareVendor: UsbDeviceSnapshot = {
161
- vendorId: '2357',
162
- productId: '0001',
163
- bDeviceClass: 0x00,
164
- interfaces: [{ interfaceClass: 0xff, interfaceSubClass: 0x00, interfaceProtocol: 0x00 }],
165
- };
166
- expect(classifyDevice(bareVendor).deviceClass).toBe('unmanaged');
167
- });
168
- });
@@ -1,248 +0,0 @@
1
- // Device classification — deciding, from a udev/sysfs snapshot alone, whether a USB
2
- // device is a ModemManager-manageable modem, a router-ethernet uplink MM cannot
3
- // control, a modem still in mass-storage installer mode, or something we simply do
4
- // not recognize.
5
- //
6
- // THE HONESTY RULE (draft §gap sweep, matrix §1): the classifier NEVER guesses. An
7
- // ambiguous descriptor set returns `unmanaged` with a truthful reason, not a
8
- // confident-sounding wrong class. A control-port presence — not a vendor id — decides
9
- // `mm-managed`; a bare vendor-specific interface with no recognized driver is NOT a
10
- // modem. `pending-modeswitch` is a DISTINCT state (a modem installer awaiting
11
- // `usb_modeswitch`), never conflated with `unmanaged`.
12
- //
13
- // SCOPE — USB ONLY: the whole input here is a `UsbDeviceSnapshot`, a udev/sysfs view of a
14
- // USB device. PCIe modems are out of scope by construction and get NO entry in this model —
15
- // a PCI `vendor:device` pair is never smuggled in as a pseudo-USB identity. The Fibocom
16
- // FM350 is the canonical example: it is a PCIe module (PCI `14c3:4d75`, bound by the
17
- // `mtk_t7xx` driver on the `wwan`/`net` subsystems, with no USB VID:PID), so it is
18
- // documented-deferred rather than classified here. See `docs/FM350-DECISION.md` for the
19
- // evidence and the three-gate ledger behind that decision.
20
-
21
- import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode';
22
-
23
- /** One USB interface's descriptor bytes plus its bound kernel driver, if any. */
24
- export interface UsbInterface {
25
- readonly interfaceClass: number;
26
- readonly interfaceSubClass: number;
27
- readonly interfaceProtocol: number;
28
- /** The bound kernel driver (`qmi_wwan`, `cdc_mbim`, `option`, `cdc_ether`, …). */
29
- readonly driver?: string;
30
- }
31
-
32
- /** A single USB device as observed from udev/sysfs — the classifier's whole input. */
33
- export interface UsbDeviceSnapshot {
34
- readonly vendorId: string;
35
- readonly productId: string;
36
- readonly model?: string;
37
- readonly firmwareRevision?: string;
38
- /** The device-descriptor `bDeviceClass` byte (0 ⇒ class is per-interface). */
39
- readonly bDeviceClass: number;
40
- readonly interfaces: readonly UsbInterface[];
41
- /** Stable physical-topology UID (udev `ID_PATH` / physdev) — survives a mode change. */
42
- readonly physicalUid?: string;
43
- /** The bound network interface name, if the device presents one (`wwan0`, `usb0`). */
44
- readonly ifname?: string;
45
- /** Raw udev properties (`ID_USB_MODESWITCH`, `ID_MM_CANDIDATE`, …). */
46
- readonly udevProperties?: Readonly<Record<string, string>>;
47
- }
48
-
49
- /** The four device classes. `pending-modeswitch` is distinct from `unmanaged`. */
50
- export type DeviceClass = 'mm-managed' | 'router-mode' | 'unmanaged' | 'pending-modeswitch';
51
-
52
- /** A classification plus a human-readable reason (always populated). */
53
- export interface DeviceClassification {
54
- readonly deviceClass: DeviceClass;
55
- readonly reason: string;
56
- }
57
-
58
- // USB-IF class / subclass / protocol codes used below.
59
- const CLASS_COMM = 0x02; // CDC communications (control interface)
60
- const CLASS_MASS_STORAGE = 0x08;
61
- const CLASS_WIRELESS = 0xe0; // wireless controller (RNDIS lives here)
62
- const CLASS_VENDOR = 0xff;
63
- const SUB_ACM = 0x02; // abstract control model (AT commands)
64
- const SUB_ECM = 0x06;
65
- const SUB_NCM = 0x0d;
66
- const SUB_MBIM = 0x0e;
67
- const SUB_RNDIS_WIRELESS = 0x01;
68
- const PROTO_VENDOR = 0xff;
69
- const PROTO_RNDIS = 0x03;
70
-
71
- const QMI_DRIVERS: ReadonlySet<string> = new Set(['qmi_wwan']);
72
- const AT_DRIVERS: ReadonlySet<string> = new Set(['option', 'qcserial', 'cdc_acm']);
73
- const ECM_NCM_DRIVERS: ReadonlySet<string> = new Set(['cdc_ether', 'cdc_ncm']);
74
- const RNDIS_DRIVERS: ReadonlySet<string> = new Set(['rndis_host']);
75
- const STORAGE_DRIVERS: ReadonlySet<string> = new Set(['usb-storage', 'uas']);
76
-
77
- function isMbimControl(i: UsbInterface): boolean {
78
- return i.interfaceClass === CLASS_COMM && i.interfaceSubClass === SUB_MBIM;
79
- }
80
-
81
- function isQmiControl(i: UsbInterface): boolean {
82
- return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && QMI_DRIVERS.has(i.driver);
83
- }
84
-
85
- function isAtControl(i: UsbInterface): boolean {
86
- // Standard CDC-ACM AT port (protocol 0xff on 0x02/0x02 is RNDIS, not AT).
87
- if (
88
- i.interfaceClass === CLASS_COMM &&
89
- i.interfaceSubClass === SUB_ACM &&
90
- i.interfaceProtocol !== PROTO_VENDOR
91
- ) {
92
- return true;
93
- }
94
- // Vendor-specific serial port bound to a known AT driver.
95
- return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && AT_DRIVERS.has(i.driver);
96
- }
97
-
98
- function isRndis(i: UsbInterface): boolean {
99
- if (
100
- i.interfaceClass === CLASS_WIRELESS &&
101
- i.interfaceSubClass === SUB_RNDIS_WIRELESS &&
102
- i.interfaceProtocol === PROTO_RNDIS
103
- ) {
104
- return true;
105
- }
106
- if (
107
- i.interfaceClass === CLASS_COMM &&
108
- i.interfaceSubClass === SUB_ACM &&
109
- i.interfaceProtocol === PROTO_VENDOR
110
- ) {
111
- return true;
112
- }
113
- return i.driver !== undefined && RNDIS_DRIVERS.has(i.driver);
114
- }
115
-
116
- function isEcmNcmData(i: UsbInterface): boolean {
117
- if (
118
- i.interfaceClass === CLASS_COMM &&
119
- (i.interfaceSubClass === SUB_ECM || i.interfaceSubClass === SUB_NCM)
120
- ) {
121
- return true;
122
- }
123
- return i.driver !== undefined && ECM_NCM_DRIVERS.has(i.driver);
124
- }
125
-
126
- function isMassStorage(i: UsbInterface): boolean {
127
- if (i.interfaceClass === CLASS_MASS_STORAGE) {
128
- return true;
129
- }
130
- return i.driver !== undefined && STORAGE_DRIVERS.has(i.driver);
131
- }
132
-
133
- function isVendorSpecific(i: UsbInterface): boolean {
134
- return i.interfaceClass === CLASS_VENDOR;
135
- }
136
-
137
- function controlKind(i: UsbInterface): string {
138
- if (isMbimControl(i)) {
139
- return 'MBIM';
140
- }
141
- return isQmiControl(i) ? 'QMI' : 'AT';
142
- }
143
-
144
- /** Whether a mass-storage device is a modem awaiting `usb_modeswitch` (not a plain disk). */
145
- function isModeswitchCandidate(snapshot: UsbDeviceSnapshot): boolean {
146
- const flag = snapshot.udevProperties?.ID_USB_MODESWITCH;
147
- if (flag !== undefined && flag !== '' && flag !== '0') {
148
- return true;
149
- }
150
- const model = (snapshot.model ?? '').toLowerCase();
151
- return ['cd-rom', 'installer', 'zerocd', 'autoinstall'].some((marker) => model.includes(marker));
152
- }
153
-
154
- function unmanagedReason(snapshot: UsbDeviceSnapshot, hasStorage: boolean): string {
155
- if (hasStorage) {
156
- return 'mass-storage device with no usb_modeswitch trigger — not a recognized modem installer';
157
- }
158
- if (snapshot.interfaces.some(isVendorSpecific)) {
159
- return 'vendor-specific interface(s) with no recognized modem driver — cannot confidently classify';
160
- }
161
- return 'no recognized modem control port, Ethernet tether, or installer interface';
162
- }
163
-
164
- /**
165
- * Classify one USB device from its udev/sysfs snapshot. Precedence:
166
- * 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
167
- * 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
168
- * 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
169
- * 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
170
- */
171
- export function classifyDevice(snapshot: UsbDeviceSnapshot): DeviceClassification {
172
- const ifaces = snapshot.interfaces;
173
-
174
- const control = ifaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
175
- if (control !== undefined) {
176
- return {
177
- deviceClass: 'mm-managed',
178
- reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
179
- };
180
- }
181
-
182
- const tether = ifaces.find((i) => isRndis(i) || isEcmNcmData(i));
183
- if (tether !== undefined) {
184
- const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
185
- return {
186
- deviceClass: 'router-mode',
187
- reason: `${kind} Ethernet tether with no modem control port — router-ethernet class, not MM-manageable`,
188
- };
189
- }
190
-
191
- const hasStorage = ifaces.some(isMassStorage) || snapshot.bDeviceClass === CLASS_MASS_STORAGE;
192
- if (hasStorage && isModeswitchCandidate(snapshot)) {
193
- return {
194
- deviceClass: 'pending-modeswitch',
195
- reason: 'mass-storage installer mode with a usb_modeswitch trigger — awaiting mode switch',
196
- };
197
- }
198
-
199
- return { deviceClass: 'unmanaged', reason: unmanagedReason(snapshot, hasStorage) };
200
- }
201
-
202
- /**
203
- * Derive the USB composition MODE a device is currently in (for a transition's
204
- * postcondition). Distinct from `classifyDevice`: this reads the data-plane
205
- * composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
206
- * data interface being `ecm-ncm` only when a control/vendor port accompanies it
207
- * (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
208
- * `qmi`. Returns `undefined` when nothing recognizable is present.
209
- */
210
- export function detectUsbMode(snapshot: UsbDeviceSnapshot): CanonicalUsbMode | undefined {
211
- const ifaces = snapshot.interfaces;
212
- if (ifaces.some(isMbimControl)) {
213
- return 'mbim';
214
- }
215
- if (ifaces.some(isRndis)) {
216
- return 'rndis';
217
- }
218
- if (ifaces.some(isEcmNcmData)) {
219
- const hasControl = ifaces.some((i) => isAtControl(i) || isVendorSpecific(i));
220
- return hasControl ? 'ecm-ncm' : 'router-ethernet';
221
- }
222
- if (ifaces.some((i) => isQmiControl(i) || isVendorSpecific(i))) {
223
- return 'qmi';
224
- }
225
- return undefined;
226
- }
227
-
228
- /**
229
- * Whether a device presents (at least) the descriptors a catalog transition expects
230
- * after re-enumeration — the descriptor half of the postcondition. Every expected
231
- * interface triple must be present and `bDeviceClass` must match.
232
- */
233
- export function descriptorsMatch(
234
- snapshot: UsbDeviceSnapshot,
235
- expected: ExpectedDescriptors,
236
- ): boolean {
237
- if (snapshot.bDeviceClass !== expected.deviceClass) {
238
- return false;
239
- }
240
- return expected.interfaces.every((exp) =>
241
- snapshot.interfaces.some(
242
- (i) =>
243
- i.interfaceClass === exp.interfaceClass &&
244
- i.interfaceSubClass === exp.interfaceSubClass &&
245
- i.interfaceProtocol === exp.interfaceProtocol,
246
- ),
247
- );
248
- }
@@ -1,96 +0,0 @@
1
- // Read-only status enrichment — firmware revision, eSIM facts, signal cadence,
2
- // and normalized cell info, assembled for one modem's status surface.
3
- //
4
- // These are additive, read-only details layered on top of the lifecycle snapshot
5
- // (A3.1) — they never gate anything and never fail a backend start. `Modem.Revision`
6
- // and the eSIM `SimType` / `EsimStatus` (MM 1.20+) are read straight from the decoded
7
- // `GetManagedObjects` tree; `signalCadence` comes from the Signal.Setup manager; cell
8
- // info is normalized from a `GetCellInfo` reply by `./cell-info`.
9
-
10
- import type { CellReading } from './cell-info';
11
- import { selectServingCell } from './cell-info';
12
- import { MODEM_IFACE, SIM_IFACE } from './constants';
13
- import type { DecodedManagedObjects } from './managed-objects';
14
- import { findInterface, followObjectPath, numberProp, stringProp } from './managed-objects';
15
- import type { SignalCadence } from './signal-setup';
16
-
17
- /** eSIM provisioning type — `Sim.SimType` (MMSimType), 1.20+. */
18
- export type SimType = 'physical' | 'esim' | 'unknown';
19
-
20
- /** eSIM profile status — `Sim.EsimStatus` (MMSimEsimStatus), 1.20+. */
21
- export type EsimStatus = 'no-profiles' | 'with-profiles' | 'unknown';
22
-
23
- /** The eSIM facts of a modem's active SIM. */
24
- export interface EsimInfo {
25
- readonly simType: SimType;
26
- readonly esimStatus: EsimStatus;
27
- }
28
-
29
- /** The full read-only enrichment surfaced beside a modem's lifecycle snapshot. */
30
- export interface ModemEnrichment {
31
- /** `Modem.Revision` — firmware/hardware revision string, when present. */
32
- readonly revision?: string;
33
- readonly esim: EsimInfo;
34
- readonly signalCadence: SignalCadence;
35
- readonly cellInfo: readonly CellReading[];
36
- /** The selected serving cell (total-order winner), when any cell is present. */
37
- readonly servingCell?: CellReading;
38
- }
39
-
40
- /** MMSimType (`Sim.SimType`) → the domain `SimType`. */
41
- function mapSimType(value: number | undefined): SimType {
42
- switch (value) {
43
- case 1:
44
- return 'physical';
45
- case 2:
46
- return 'esim';
47
- default:
48
- return 'unknown';
49
- }
50
- }
51
-
52
- /** MMSimEsimStatus (`Sim.EsimStatus`) → the domain `EsimStatus`. */
53
- function mapEsimStatus(value: number | undefined): EsimStatus {
54
- switch (value) {
55
- case 1:
56
- return 'no-profiles';
57
- case 2:
58
- return 'with-profiles';
59
- default:
60
- return 'unknown';
61
- }
62
- }
63
-
64
- /** Read `Modem.Revision`, when present. */
65
- export function readRevision(tree: DecodedManagedObjects, modemPath: string): string | undefined {
66
- const revision = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Revision');
67
- return revision !== undefined && revision.length > 0 ? revision : undefined;
68
- }
69
-
70
- /** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
71
- export function readEsimInfo(tree: DecodedManagedObjects, modemPath: string): EsimInfo {
72
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
73
- const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
74
- return {
75
- simType: mapSimType(numberProp(sim, 'SimType')),
76
- esimStatus: mapEsimStatus(numberProp(sim, 'EsimStatus')),
77
- };
78
- }
79
-
80
- /** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
81
- export function buildEnrichment(
82
- tree: DecodedManagedObjects,
83
- modemPath: string,
84
- signalCadence: SignalCadence,
85
- cellInfo: readonly CellReading[],
86
- ): ModemEnrichment {
87
- const revision = readRevision(tree, modemPath);
88
- const serving = selectServingCell(cellInfo);
89
- return {
90
- ...(revision !== undefined ? { revision } : {}),
91
- esim: readEsimInfo(tree, modemPath),
92
- signalCadence,
93
- cellInfo,
94
- ...(serving !== undefined ? { servingCell: serving } : {}),
95
- };
96
- }
@@ -1,162 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import {
3
- detectMmFeatures,
4
- type MmPropertyProbe,
5
- parseMmVersion,
6
- probeModemProperties,
7
- } from './features';
8
- import type { DecodedManagedObjects } from './managed-objects';
9
-
10
- const BASE_MODEM_PROPS = new Set(['Manufacturer', 'Model', 'Device', 'State', 'SimType']);
11
-
12
- function probe(overrides: Partial<MmPropertyProbe> = {}): MmPropertyProbe {
13
- return { properties: BASE_MODEM_PROPS, ...overrides };
14
- }
15
-
16
- function withPhysdev(): MmPropertyProbe {
17
- return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']) };
18
- }
19
-
20
- describe('parseMmVersion', () => {
21
- test('parses major.minor.patch', () => {
22
- expect(parseMmVersion('1.24.0')).toEqual({ major: 1, minor: 24 });
23
- });
24
-
25
- test('parses the 1.24.2 FM350-fix release', () => {
26
- expect(parseMmVersion('1.24.2')).toEqual({ major: 1, minor: 24 });
27
- });
28
-
29
- test('parses major.minor without a patch', () => {
30
- expect(parseMmVersion('1.20')).toEqual({ major: 1, minor: 20 });
31
- });
32
-
33
- test('tolerates a pre-release suffix', () => {
34
- expect(parseMmVersion('1.26.0-rc1')).toEqual({ major: 1, minor: 26 });
35
- });
36
-
37
- test('returns null for an unparseable string', () => {
38
- expect(parseMmVersion('not-a-version')).toBeNull();
39
- });
40
- });
41
-
42
- describe('detectMmFeatures — 3 property shapes', () => {
43
- test('1.20 shape: no Physdev, basic cell info, eSIM, serialization', () => {
44
- expect(detectMmFeatures('1.20.0', probe())).toEqual({
45
- physdev: false,
46
- cellInfo: 'basic',
47
- esimStatus: true,
48
- opSerialization: true,
49
- });
50
- });
51
-
52
- test('1.22 shape: Physdev present, rich cell info', () => {
53
- expect(detectMmFeatures('1.22.0', withPhysdev())).toEqual({
54
- physdev: true,
55
- cellInfo: 'rich',
56
- esimStatus: true,
57
- opSerialization: true,
58
- });
59
- });
60
-
61
- test('1.24 shape: Physdev present, rich cell info', () => {
62
- expect(detectMmFeatures('1.24.0', withPhysdev())).toEqual({
63
- physdev: true,
64
- cellInfo: 'rich',
65
- esimStatus: true,
66
- opSerialization: true,
67
- });
68
- });
69
- });
70
-
71
- describe('detectMmFeatures — property presence overrides the version', () => {
72
- test('physdev is property-driven: 1.24 version but no Physdev property ⇒ false', () => {
73
- expect(detectMmFeatures('1.24.0', probe()).physdev).toBe(false);
74
- });
75
-
76
- test('a 1.22+ modem that omits Physdev still reports rich cell info by version', () => {
77
- expect(detectMmFeatures('1.22.0', probe()).cellInfo).toBe('rich');
78
- });
79
-
80
- test('rich cell-info fields promote the tier even without a version signal', () => {
81
- const withFields = probe({ cellInfoFields: new Set(['serving-type', 'bandwidth']) });
82
- expect(detectMmFeatures('1.20.0', withFields).cellInfo).toBe('rich');
83
- });
84
- });
85
-
86
- describe('detectMmFeatures — degrade paths', () => {
87
- test('below 1.20 has no cell info, no eSIM, no serialization', () => {
88
- expect(detectMmFeatures('1.18.0', probe({ properties: new Set(['Device']) }))).toEqual({
89
- physdev: false,
90
- cellInfo: 'none',
91
- esimStatus: false,
92
- opSerialization: false,
93
- });
94
- });
95
-
96
- test('a modem advertising no cell-info capability degrades to none on a supported version', () => {
97
- expect(detectMmFeatures('1.24.0', withPhysdevNoCellInfo()).cellInfo).toBe('none');
98
- });
99
-
100
- test('an eSIM property forces esimStatus true even on an unparseable version', () => {
101
- const esimProbe = probe({ properties: new Set(['EsimStatus']) });
102
- expect(detectMmFeatures('garbage', esimProbe).esimStatus).toBe(true);
103
- });
104
- });
105
-
106
- describe('detectMmFeatures — unknown future version probes, never throws', () => {
107
- test('1.26.0 falls back to property probing, not a whitelist throw', () => {
108
- expect(() => detectMmFeatures('1.26.0', withPhysdev())).not.toThrow();
109
- expect(detectMmFeatures('1.26.0', withPhysdev())).toEqual({
110
- physdev: true,
111
- cellInfo: 'rich',
112
- esimStatus: true,
113
- opSerialization: true,
114
- });
115
- });
116
-
117
- test('a totally unparseable version degrades to the safe floor (property-only physdev)', () => {
118
- const physdevOnly = { properties: new Set(['Device', 'Physdev']) };
119
- expect(detectMmFeatures('???', physdevOnly)).toEqual({
120
- physdev: true,
121
- cellInfo: 'none',
122
- esimStatus: false,
123
- opSerialization: false,
124
- });
125
- });
126
- });
127
-
128
- function withPhysdevNoCellInfo(): MmPropertyProbe {
129
- return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']), cellInfoAvailable: false };
130
- }
131
-
132
- describe('probeModemProperties', () => {
133
- test('collects property names across Modem, Modem3gpp and the active SIM', () => {
134
- const tree: DecodedManagedObjects = [
135
- [
136
- '/org/freedesktop/ModemManager1/Modem/0',
137
- [
138
- [
139
- 'org.freedesktop.ModemManager1.Modem',
140
- [
141
- ['Device', { signature: 's', value: 'slot-usb2-1' }],
142
- ['Physdev', { signature: 's', value: '/sys/devices/usb2' }],
143
- ['Sim', { signature: 'o', value: '/org/freedesktop/ModemManager1/SIM/0' }],
144
- ],
145
- ],
146
- [
147
- 'org.freedesktop.ModemManager1.Modem.Modem3gpp',
148
- [['Imei', { signature: 's', value: '490154203237518' }]],
149
- ],
150
- ],
151
- ],
152
- [
153
- '/org/freedesktop/ModemManager1/SIM/0',
154
- [['org.freedesktop.ModemManager1.Sim', [['SimType', { signature: 'u', value: 2 }]]]],
155
- ],
156
- ];
157
- const result = probeModemProperties(tree, '/org/freedesktop/ModemManager1/Modem/0');
158
- expect(result.properties.has('Physdev')).toBe(true);
159
- expect(result.properties.has('Imei')).toBe(true);
160
- expect(result.properties.has('SimType')).toBe(true);
161
- });
162
- });