@ceralive/modem-control 1.0.0 → 1.2.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.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +101 -0
- package/dist/backend/transition-preconditions.js +132 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +216 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +27 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +61 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +137 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +171 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +88 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +138 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +6 -0
- package/dist/usb-mode/index.js +16 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +106 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -236
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// The shared per-modem disruptive-operation actor.
|
|
2
|
-
//
|
|
3
|
-
// EVERY disruptive modem operation funnels through ONE serialization queue PER
|
|
4
|
-
// MODEM: radio-mode changes and primary-slot changes (A3.3), the recovery ladder
|
|
5
|
-
// (A3.4), and USB-mode / AT transitions (A4.2) all route through the SAME actor so
|
|
6
|
-
// two disruptive ops on ONE modem can never interleave. Ops on DIFFERENT modems run
|
|
7
|
-
// independently — each modem owns its own queue.
|
|
8
|
-
//
|
|
9
|
-
// The queue is keyed on the STABLE identity key (A3.2's `stableKey`), NOT the
|
|
10
|
-
// transient D-Bus object path. A modem that unplugs and replugs into the same slot
|
|
11
|
-
// resolves to the same stable key, so serialization survives the replug (the path
|
|
12
|
-
// changed, the queue did not).
|
|
13
|
-
//
|
|
14
|
-
// Mode and slot changes additionally coordinate with NetworkManager through the
|
|
15
|
-
// `QuiesceHook` — before a disruptive change NM briefly deactivates the connection so
|
|
16
|
-
// it cannot race MM's own re-activation, then reactivates it on release. In A3.3 the
|
|
17
|
-
// hook is a NO-OP stub with the exact lease shape A4.1's real nmcli adapter will fill
|
|
18
|
-
// in (`NetworkManagerPort.acquireQuiesceLease`).
|
|
19
|
-
|
|
20
|
-
import type { ConnectionId, DeviceIfname } from '../ports';
|
|
21
|
-
|
|
22
|
-
/** What a quiesce lease is taken over — the modem, and (once A4.1 knows them) its
|
|
23
|
-
* NM connection + device. In A3.3 only `stableKey` is populated. */
|
|
24
|
-
export interface QuiesceTarget {
|
|
25
|
-
readonly stableKey: string;
|
|
26
|
-
readonly connectionId?: ConnectionId;
|
|
27
|
-
readonly deviceIfname?: DeviceIfname;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** A held quiesce lease — released (reactivating the connection) when the op ends. */
|
|
31
|
-
export interface QuiesceLeaseHandle {
|
|
32
|
-
release(): Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The NM-quiesce coordination hook the actor calls around a disruptive mode/slot
|
|
37
|
-
* change. A4.1 wires the real `nmcli` lease (verify-active → `device disconnect` →
|
|
38
|
-
* hold → reactivate); A3.3 ships `NO_OP_QUIESCE`, whose call site + shape are the
|
|
39
|
-
* exact seam A4.1 fills in.
|
|
40
|
-
*/
|
|
41
|
-
export interface QuiesceHook {
|
|
42
|
-
acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const NO_OP_LEASE: QuiesceLeaseHandle = {
|
|
46
|
-
release(): Promise<void> {
|
|
47
|
-
return Promise.resolve();
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
|
|
52
|
-
export const NO_OP_QUIESCE: QuiesceHook = {
|
|
53
|
-
acquire(): Promise<QuiesceLeaseHandle> {
|
|
54
|
-
return Promise.resolve(NO_OP_LEASE);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* A per-modem serialized actor. `run` serializes a task behind every prior task for
|
|
60
|
-
* the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
|
|
61
|
-
* task's duration. Different stable keys never block one another.
|
|
62
|
-
*/
|
|
63
|
-
export class ModemActor {
|
|
64
|
-
readonly #quiesce: QuiesceHook;
|
|
65
|
-
// Tail of each modem's queue — a promise that settles when the last-enqueued task
|
|
66
|
-
// for that key finishes (errors swallowed so the chain never breaks).
|
|
67
|
-
readonly #tails = new Map<string, Promise<void>>();
|
|
68
|
-
|
|
69
|
-
constructor(quiesce: QuiesceHook = NO_OP_QUIESCE) {
|
|
70
|
-
this.#quiesce = quiesce;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Run `task` serialized behind every prior task for `stableKey`. */
|
|
74
|
-
run<T>(stableKey: string, task: () => Promise<T>): Promise<T> {
|
|
75
|
-
const prior = this.#tails.get(stableKey) ?? Promise.resolve();
|
|
76
|
-
// `then(task, task)` runs `task` whether the prior task resolved OR rejected,
|
|
77
|
-
// so one failure never stalls the queue.
|
|
78
|
-
const result = prior.then(task, task);
|
|
79
|
-
const settled = result.then(
|
|
80
|
-
() => undefined,
|
|
81
|
-
() => undefined,
|
|
82
|
-
);
|
|
83
|
-
this.#tails.set(stableKey, settled);
|
|
84
|
-
// Drop the entry once this task was the last one, to bound the map.
|
|
85
|
-
void settled.then(() => {
|
|
86
|
-
if (this.#tails.get(stableKey) === settled) {
|
|
87
|
-
this.#tails.delete(stableKey);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
|
|
95
|
-
* its whole duration — the lease is always released, even if `task` throws.
|
|
96
|
-
*/
|
|
97
|
-
runQuiesced<T>(target: QuiesceTarget, task: () => Promise<T>): Promise<T> {
|
|
98
|
-
return this.run(target.stableKey, async () => {
|
|
99
|
-
const lease = await this.#quiesce.acquire(target);
|
|
100
|
-
try {
|
|
101
|
-
return await task();
|
|
102
|
-
} finally {
|
|
103
|
-
await lease.release();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Number of modems with a live queue — exposed for leak / idle assertions. */
|
|
109
|
-
get activeKeyCount(): number {
|
|
110
|
-
return this.#tails.size;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
// Auto-APN: boot capability probe, the two atomic transition argv builders, and the
|
|
2
|
-
// activation-result classifier.
|
|
3
|
-
//
|
|
4
|
-
// Auto-APN (`apn:"auto"` ⇒ NM `gsm.auto-config yes`) needs NetworkManager >= 1.22
|
|
5
|
-
// (bookworm ships 1.42). A boot capability probe reads nmcli's version — replacing any
|
|
6
|
-
// implicit/hardcoded gating — and when NM is too old an auto policy resolves to
|
|
7
|
-
// `unsupported` up front with the `autoApnUnavailable` advisory (no modify at all).
|
|
8
|
-
//
|
|
9
|
-
// When NM DOES support auto-config a bad/unknown SIM can still have no MBPI match: the
|
|
10
|
-
// connection fails to activate with a GSM_APN_FAILED device-state reason. That is
|
|
11
|
-
// classified at ACTIVATION, not guessed ahead of time — reactivate the exact
|
|
12
|
-
// (uuid, ifname) pair, await the terminal state, then:
|
|
13
|
-
// GSM_APN_FAILED under an auto profile → unsupported + autoApnUnavailable advisory
|
|
14
|
-
// any other activation error → failed
|
|
15
|
-
// nmcli's own activation wait timed out → pending
|
|
16
|
-
|
|
17
|
-
import { type Receipt, receipt } from '../ports';
|
|
18
|
-
import { cliArg, passwordFlags } from './nm-gsm-fields';
|
|
19
|
-
import { type NmcliResult, type NmcliRunner, runNmcli } from './nmcli-runner';
|
|
20
|
-
|
|
21
|
-
/** The single advisory Auto-APN raises when it cannot be honored. */
|
|
22
|
-
export const AUTO_APN_ADVISORY = 'autoApnUnavailable';
|
|
23
|
-
export type AutoApnAdvisory = typeof AUTO_APN_ADVISORY;
|
|
24
|
-
|
|
25
|
-
/** The outcome of an Auto-APN transition: a receipt plus an optional advisory flag. */
|
|
26
|
-
export interface AutoApnTransitionResult {
|
|
27
|
-
readonly receipt: Receipt;
|
|
28
|
-
readonly advisory?: AutoApnAdvisory;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Explicit APN + credentials for the manual direction of a transition. */
|
|
32
|
-
export interface ManualApn {
|
|
33
|
-
readonly apn: string;
|
|
34
|
-
readonly username?: string;
|
|
35
|
-
readonly password?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const AUTO_CONFIG_MIN_MAJOR = 1;
|
|
39
|
-
const AUTO_CONFIG_MIN_MINOR = 22;
|
|
40
|
-
|
|
41
|
-
/** nmcli's own `--wait` timeout exit status (nmcli(1): 3 = timeout expired). */
|
|
42
|
-
const NMCLI_EXIT_TIMEOUT = 3;
|
|
43
|
-
|
|
44
|
-
const APN_FAILED = /gsm[_-]?apn[_-]?failed/i;
|
|
45
|
-
|
|
46
|
-
/** Parse `nmcli tool, version 1.42.4` → `{ major, minor }` (null on garbage). */
|
|
47
|
-
export function parseNmVersion(output: string): { major: number; minor: number } | null {
|
|
48
|
-
const match = /version\s+(\d+)\.(\d+)/i.exec(output);
|
|
49
|
-
if (match?.[1] === undefined || match[2] === undefined) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
return { major: Number(match[1]), minor: Number(match[2]) };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** `true` iff the parsed NM version is >= 1.22 (auto-config support floor). */
|
|
56
|
-
export function autoApnSupportedByVersion(version: { major: number; minor: number }): boolean {
|
|
57
|
-
if (version.major !== AUTO_CONFIG_MIN_MAJOR) {
|
|
58
|
-
return version.major > AUTO_CONFIG_MIN_MAJOR;
|
|
59
|
-
}
|
|
60
|
-
return version.minor >= AUTO_CONFIG_MIN_MINOR;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Decide from a `nmcli --version` result whether Auto-APN is available on this install.
|
|
65
|
-
* Fail-CLOSED — an unreadable/garbled version is "not capable", so we never claim
|
|
66
|
-
* Auto-APN works when we cannot prove it does.
|
|
67
|
-
*/
|
|
68
|
-
export function autoApnCapableFromVersion(result: NmcliResult): boolean {
|
|
69
|
-
if (result.exitCode !== 0) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const version = parseNmVersion(result.stdout);
|
|
73
|
-
return version !== null && autoApnSupportedByVersion(version);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Boot capability probe: run `nmcli --version` and resolve Auto-APN availability. */
|
|
77
|
-
export async function probeAutoApnCapability(runner: NmcliRunner): Promise<boolean> {
|
|
78
|
-
return autoApnCapableFromVersion(await runNmcli(runner, ['--version']));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* ONE atomic `connection modify` that flips a profile to Auto-APN: clears every
|
|
83
|
-
* credential AND sets `gsm.auto-config yes` in the SAME invocation. Order/atomicity
|
|
84
|
-
* matter — NM rejects `auto-config yes` while any credential is still set, so the clear
|
|
85
|
-
* and the enable MUST land together (never two modifies).
|
|
86
|
-
*/
|
|
87
|
-
export function toAutoArgs(id: string): string[] {
|
|
88
|
-
return [
|
|
89
|
-
'connection',
|
|
90
|
-
'modify',
|
|
91
|
-
id,
|
|
92
|
-
'gsm.apn',
|
|
93
|
-
'',
|
|
94
|
-
'gsm.username',
|
|
95
|
-
'',
|
|
96
|
-
'gsm.password',
|
|
97
|
-
'',
|
|
98
|
-
'gsm.password-flags',
|
|
99
|
-
'4',
|
|
100
|
-
'gsm.auto-config',
|
|
101
|
-
'yes',
|
|
102
|
-
];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The exact reverse: ONE atomic `connection modify` restoring explicit APN + creds and
|
|
107
|
-
* clearing `gsm.auto-config`, with password-flags matching the (new) password.
|
|
108
|
-
*/
|
|
109
|
-
export function toManualArgs(id: string, creds: ManualApn): string[] {
|
|
110
|
-
return [
|
|
111
|
-
'connection',
|
|
112
|
-
'modify',
|
|
113
|
-
id,
|
|
114
|
-
'gsm.apn',
|
|
115
|
-
cliArg(creds.apn),
|
|
116
|
-
'gsm.username',
|
|
117
|
-
cliArg(creds.username),
|
|
118
|
-
'gsm.password',
|
|
119
|
-
cliArg(creds.password),
|
|
120
|
-
'gsm.password-flags',
|
|
121
|
-
passwordFlags(creds.password),
|
|
122
|
-
'gsm.auto-config',
|
|
123
|
-
'no',
|
|
124
|
-
];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Classify a `connection up` result into a receipt (+ advisory). `underAuto` marks that
|
|
129
|
-
* the profile being activated is an Auto-APN profile, so an APN failure is the "no MBPI
|
|
130
|
-
* match for this SIM" case → unsupported + advisory rather than a hard fail.
|
|
131
|
-
*/
|
|
132
|
-
export function classifyActivation(
|
|
133
|
-
result: NmcliResult,
|
|
134
|
-
options: { readonly underAuto: boolean },
|
|
135
|
-
): AutoApnTransitionResult {
|
|
136
|
-
if (result.exitCode === 0) {
|
|
137
|
-
return { receipt: receipt('connection', 'applied', 'connection activated') };
|
|
138
|
-
}
|
|
139
|
-
if (result.exitCode === NMCLI_EXIT_TIMEOUT || /timeout/i.test(result.stderr)) {
|
|
140
|
-
return {
|
|
141
|
-
receipt: receipt(
|
|
142
|
-
'connection',
|
|
143
|
-
'pending',
|
|
144
|
-
'activation wait timed out; terminal state unknown',
|
|
145
|
-
),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (options.underAuto && (APN_FAILED.test(result.stderr) || APN_FAILED.test(result.stdout))) {
|
|
149
|
-
return {
|
|
150
|
-
receipt: receipt(
|
|
151
|
-
'connection',
|
|
152
|
-
'unsupported',
|
|
153
|
-
'Auto-APN found no operator match for this SIM',
|
|
154
|
-
),
|
|
155
|
-
advisory: AUTO_APN_ADVISORY,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
receipt: receipt('connection', 'failed', result.stderr || 'connection activation failed'),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// Pure nmcli GSM-connection argv builders — the FULL nine-field write parity today's
|
|
2
|
-
// CeraUI carried (modem-registration.ts `sanitizeModemConfigForNetworkManager` +
|
|
3
|
-
// `addConnectionForModem`), rebuilt from scratch for the device-exact adapter so the
|
|
4
|
-
// greenfield port loses nothing the wrap-first path wrote.
|
|
5
|
-
//
|
|
6
|
-
// The nine fields and their exact NM conventions (`gsm.*` + `connection.*` setting
|
|
7
|
-
// semantics) are the contract A4.1 must not regress:
|
|
8
|
-
// gsm.apn / gsm.username / gsm.password — creds ("" when unset)
|
|
9
|
-
// gsm.password-flags — "4" NOT_REQUIRED (no password),
|
|
10
|
-
// "0" NONE / system-stored (password set)
|
|
11
|
-
// gsm.home-only — "yes" ⇒ roaming DISABLED
|
|
12
|
-
// gsm.network-id — manual operator PLMN, only while roaming
|
|
13
|
-
// gsm.auto-config — "yes" ⇒ Auto-APN (creds MUST be empty)
|
|
14
|
-
// connection.autoconnect — always "yes"
|
|
15
|
-
// connection.autoconnect-retries — "2"
|
|
16
|
-
|
|
17
|
-
import type { ConnectionId, GsmProfile, GsmProfileInput, GsmProfilePatch } from '../ports';
|
|
18
|
-
|
|
19
|
-
// Empty-string fallback; Bun runtime limitation with empty CLI args: a bare
|
|
20
|
-
// `undefined` reaching `Bun.spawn` argv drops the slot and shifts every following
|
|
21
|
-
// token, so every optional nmcli value is coerced to "" first — the same `value || ""`
|
|
22
|
-
// convention CeraUI documented on gsm.apn / gsm.username / gsm.password.
|
|
23
|
-
export const cliArg = (value: string | undefined): string => value || '';
|
|
24
|
-
|
|
25
|
-
/** NM secret-flags: no password ⇒ NOT_REQUIRED ("4"); password set ⇒ NONE ("0"). */
|
|
26
|
-
export const passwordFlags = (password: string | undefined): string => (password ? '0' : '4');
|
|
27
|
-
|
|
28
|
-
/** `gsm.network-id` — the manual operator id, and only while roaming (home-only off). */
|
|
29
|
-
const networkIdField = (input: GsmProfileInput): string =>
|
|
30
|
-
input.homeOnly ? '' : cliArg(input.networkId);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The nine `gsm.*` + `connection.*` key/value pairs for a profile, in stable order.
|
|
34
|
-
* With `autoConfig` set the creds are forced empty (NM rejects `auto-config yes` while
|
|
35
|
-
* any credential is present — nm-setting-gsm.c:440-447), so an Auto-APN create is always
|
|
36
|
-
* NM-valid by construction.
|
|
37
|
-
*/
|
|
38
|
-
export function gsmFieldPairs(input: GsmProfileInput): Array<[string, string]> {
|
|
39
|
-
const auto = input.autoConfig;
|
|
40
|
-
return [
|
|
41
|
-
['gsm.apn', auto ? '' : cliArg(input.apn)],
|
|
42
|
-
['gsm.username', auto ? '' : cliArg(input.username)],
|
|
43
|
-
['gsm.password', auto ? '' : cliArg(input.password)],
|
|
44
|
-
['gsm.password-flags', passwordFlags(auto ? undefined : input.password)],
|
|
45
|
-
['gsm.home-only', input.homeOnly ? 'yes' : 'no'],
|
|
46
|
-
['gsm.network-id', networkIdField(input)],
|
|
47
|
-
['gsm.auto-config', auto ? 'yes' : 'no'],
|
|
48
|
-
['connection.autoconnect', 'yes'],
|
|
49
|
-
['connection.autoconnect-retries', '2'],
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Flatten key/value pairs into the alternating argv nmcli expects. */
|
|
54
|
-
export function flattenPairs(pairs: ReadonlyArray<readonly [string, string]>): string[] {
|
|
55
|
-
return pairs.flat();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** `connection add type gsm con-name <name> <nine fields>` — device-exact create argv. */
|
|
59
|
-
export function createGsmArgs(input: GsmProfileInput): string[] {
|
|
60
|
-
return [
|
|
61
|
-
'connection',
|
|
62
|
-
'add',
|
|
63
|
-
'type',
|
|
64
|
-
'gsm',
|
|
65
|
-
'con-name',
|
|
66
|
-
input.connectionName,
|
|
67
|
-
...flattenPairs(gsmFieldPairs(input)),
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The key/value pairs for a `connection modify`. Roaming (home-only) and the manual
|
|
73
|
-
* operator id move together — turning roaming off clears `gsm.network-id`, honoring the
|
|
74
|
-
* `gsm.network-id = roaming ? id : ""` invariant in the SAME modify.
|
|
75
|
-
*/
|
|
76
|
-
export function patchPairs(patch: GsmProfilePatch): string[] {
|
|
77
|
-
const pairs: string[] = [];
|
|
78
|
-
if (patch.connectionName !== undefined) {
|
|
79
|
-
pairs.push('connection.id', patch.connectionName);
|
|
80
|
-
}
|
|
81
|
-
if (patch.apn !== undefined) {
|
|
82
|
-
pairs.push('gsm.apn', cliArg(patch.apn));
|
|
83
|
-
}
|
|
84
|
-
if (patch.username !== undefined) {
|
|
85
|
-
pairs.push('gsm.username', cliArg(patch.username));
|
|
86
|
-
}
|
|
87
|
-
if (patch.password !== undefined) {
|
|
88
|
-
pairs.push(
|
|
89
|
-
'gsm.password',
|
|
90
|
-
cliArg(patch.password),
|
|
91
|
-
'gsm.password-flags',
|
|
92
|
-
passwordFlags(patch.password),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
if (patch.autoConfig !== undefined) {
|
|
96
|
-
pairs.push('gsm.auto-config', patch.autoConfig ? 'yes' : 'no');
|
|
97
|
-
}
|
|
98
|
-
if (patch.homeOnly !== undefined) {
|
|
99
|
-
pairs.push('gsm.home-only', patch.homeOnly ? 'yes' : 'no');
|
|
100
|
-
pairs.push('gsm.network-id', patch.homeOnly ? '' : cliArg(patch.networkId));
|
|
101
|
-
} else if (patch.networkId !== undefined) {
|
|
102
|
-
pairs.push('gsm.network-id', cliArg(patch.networkId));
|
|
103
|
-
}
|
|
104
|
-
return pairs;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Map an nmcli terse readback back onto a `GsmProfile` (inverse of the write parity). */
|
|
108
|
-
export function buildProfile(id: ConnectionId, settings: Map<string, string>): GsmProfile {
|
|
109
|
-
const username = settings.get('gsm.username') ?? '';
|
|
110
|
-
const password = settings.get('gsm.password') ?? '';
|
|
111
|
-
const networkId = settings.get('gsm.network-id') ?? '';
|
|
112
|
-
return {
|
|
113
|
-
connectionId: id,
|
|
114
|
-
connectionName: settings.get('connection.id') ?? '',
|
|
115
|
-
apn: settings.get('gsm.apn') ?? '',
|
|
116
|
-
homeOnly: settings.get('gsm.home-only') === 'yes',
|
|
117
|
-
autoConfig: settings.get('gsm.auto-config') === 'yes',
|
|
118
|
-
...(username !== '' ? { username } : {}),
|
|
119
|
-
...(password !== '' ? { password } : {}),
|
|
120
|
-
...(networkId !== '' ? { networkId } : {}),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
// The device-exact NetworkManager adapter: a FRESH nmcli-based `NetworkManagerPort`
|
|
2
|
-
// (no reused CeraUI code) with the full nine-field GSM write parity, verify-then-
|
|
3
|
-
// device-disconnect deactivation (never id-only `connection down`), abandoned-lease
|
|
4
|
-
// quiesce, and atomic Auto-APN transitions.
|
|
5
|
-
|
|
6
|
-
import { epochMillis } from '../domain';
|
|
7
|
-
import {
|
|
8
|
-
type ConnectionId,
|
|
9
|
-
connectionId,
|
|
10
|
-
type DeviceIfname,
|
|
11
|
-
type GsmProfile,
|
|
12
|
-
type GsmProfileInput,
|
|
13
|
-
type GsmProfilePatch,
|
|
14
|
-
type NetworkManagerPort,
|
|
15
|
-
type QuiesceLease,
|
|
16
|
-
type Receipt,
|
|
17
|
-
receipt,
|
|
18
|
-
} from '../ports';
|
|
19
|
-
import {
|
|
20
|
-
AUTO_APN_ADVISORY,
|
|
21
|
-
type AutoApnTransitionResult,
|
|
22
|
-
classifyActivation,
|
|
23
|
-
type ManualApn,
|
|
24
|
-
toAutoArgs,
|
|
25
|
-
toManualArgs,
|
|
26
|
-
} from './nm-auto-apn';
|
|
27
|
-
import { buildProfile, createGsmArgs, patchPairs } from './nm-gsm-fields';
|
|
28
|
-
import { type NmcliResult, type NmcliRunner, parseTerse, runNmcli } from './nmcli-runner';
|
|
29
|
-
|
|
30
|
-
const READBACK_FIELDS = [
|
|
31
|
-
'connection.id',
|
|
32
|
-
'gsm.apn',
|
|
33
|
-
'gsm.username',
|
|
34
|
-
'gsm.password',
|
|
35
|
-
'gsm.password-flags',
|
|
36
|
-
'gsm.home-only',
|
|
37
|
-
'gsm.network-id',
|
|
38
|
-
'gsm.auto-config',
|
|
39
|
-
'connection.autoconnect',
|
|
40
|
-
'connection.autoconnect-retries',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
/** Default abandoned-lease TTL: a held quiesce lease older than this auto-releases. */
|
|
44
|
-
const DEFAULT_LEASE_TTL_MS = 60_000;
|
|
45
|
-
|
|
46
|
-
export interface NmcliNmPortOptions {
|
|
47
|
-
readonly runner: NmcliRunner;
|
|
48
|
-
/** Resolved once at boot by the capability probe; default `true`. */
|
|
49
|
-
readonly autoApnCapable?: boolean;
|
|
50
|
-
readonly leaseTtlMs?: number;
|
|
51
|
-
readonly now?: () => number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class NmcliNmPort implements NetworkManagerPort {
|
|
55
|
-
readonly #runner: NmcliRunner;
|
|
56
|
-
readonly #autoApnCapable: boolean;
|
|
57
|
-
readonly #leaseTtlMs: number;
|
|
58
|
-
readonly #now: () => number;
|
|
59
|
-
readonly #leases = new Set<QuiesceLease>();
|
|
60
|
-
|
|
61
|
-
constructor(options: NmcliNmPortOptions) {
|
|
62
|
-
this.#runner = options.runner;
|
|
63
|
-
this.#autoApnCapable = options.autoApnCapable ?? true;
|
|
64
|
-
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
65
|
-
this.#now = options.now ?? Date.now;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async createGsmProfile(profile: GsmProfileInput): Promise<GsmProfile> {
|
|
69
|
-
const result = await runNmcli(this.#runner, createGsmArgs(profile));
|
|
70
|
-
const uuid = /\(([^)]+)\) successfully added/.exec(result.stdout)?.[1];
|
|
71
|
-
if (uuid === undefined) {
|
|
72
|
-
throw new Error(`nmcli connection add failed: ${result.stderr || result.stdout}`);
|
|
73
|
-
}
|
|
74
|
-
return this.#requireProfile(connectionId(uuid));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async readGsmProfile(id: ConnectionId): Promise<GsmProfile | undefined> {
|
|
78
|
-
const result = await runNmcli(this.#runner, [
|
|
79
|
-
'-t',
|
|
80
|
-
'-f',
|
|
81
|
-
READBACK_FIELDS.join(','),
|
|
82
|
-
'connection',
|
|
83
|
-
'show',
|
|
84
|
-
id,
|
|
85
|
-
]);
|
|
86
|
-
if (result.exitCode !== 0) {
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
return buildProfile(id, parseTerse(result.stdout));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async updateGsmProfile(id: ConnectionId, patch: GsmProfilePatch): Promise<GsmProfile> {
|
|
93
|
-
const result = await runNmcli(this.#runner, ['connection', 'modify', id, ...patchPairs(patch)]);
|
|
94
|
-
if (result.exitCode !== 0) {
|
|
95
|
-
throw new Error(`nmcli connection modify failed: ${result.stderr}`);
|
|
96
|
-
}
|
|
97
|
-
return this.#requireProfile(id);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async deleteGsmProfile(id: ConnectionId): Promise<void> {
|
|
101
|
-
await runNmcli(this.#runner, ['connection', 'delete', id]);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async activate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt> {
|
|
105
|
-
const result = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
|
|
106
|
-
return activationReceipt(result, `activated ${id} on ${ifname}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async deactivate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt> {
|
|
110
|
-
if ((await this.#activeUuidOn(ifname)) !== id) {
|
|
111
|
-
return receipt('enabled', 'applied', `${id} not active on ${ifname}; nothing to deactivate`);
|
|
112
|
-
}
|
|
113
|
-
const result = await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
|
|
114
|
-
return activationReceipt(result, `deactivated ${id} on ${ifname}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async acquireQuiesceLease(id: ConnectionId, ifname: DeviceIfname): Promise<QuiesceLease> {
|
|
118
|
-
if ((await this.#activeUuidOn(ifname)) === id) {
|
|
119
|
-
await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
|
|
120
|
-
}
|
|
121
|
-
const lease: QuiesceLease = {
|
|
122
|
-
connectionId: id,
|
|
123
|
-
deviceIfname: ifname,
|
|
124
|
-
acquiredAt: epochMillis(this.#now()),
|
|
125
|
-
};
|
|
126
|
-
this.#leases.add(lease);
|
|
127
|
-
return lease;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async releaseQuiesceLease(lease: QuiesceLease): Promise<void> {
|
|
131
|
-
if (this.#leases.delete(lease)) {
|
|
132
|
-
await this.#reactivate(lease);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Reactivate every lease held past the TTL — the abandoned-lease watchdog. */
|
|
137
|
-
async sweepExpiredLeases(now: number = this.#now()): Promise<void> {
|
|
138
|
-
for (const lease of [...this.#leases]) {
|
|
139
|
-
if (now - lease.acquiredAt >= this.#leaseTtlMs) {
|
|
140
|
-
this.#leases.delete(lease);
|
|
141
|
-
await this.#reactivate(lease);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Flip a profile to Auto-APN via ONE atomic modify, then reactivate + classify. */
|
|
147
|
-
async transitionToAuto(id: ConnectionId, ifname: DeviceIfname): Promise<AutoApnTransitionResult> {
|
|
148
|
-
if (!this.#autoApnCapable) {
|
|
149
|
-
return {
|
|
150
|
-
receipt: receipt('connection', 'unsupported', 'Auto-APN requires NetworkManager >= 1.22'),
|
|
151
|
-
advisory: AUTO_APN_ADVISORY,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
const modify = await runNmcli(this.#runner, toAutoArgs(id));
|
|
155
|
-
if (modify.exitCode !== 0) {
|
|
156
|
-
return {
|
|
157
|
-
receipt: receipt('connection', 'failed', modify.stderr || 'auto-config modify rejected'),
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
return this.#reactivateAndClassify(id, ifname, true);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Flip a profile to manual APN via ONE atomic modify, then reactivate + classify. */
|
|
164
|
-
async transitionToManual(
|
|
165
|
-
id: ConnectionId,
|
|
166
|
-
ifname: DeviceIfname,
|
|
167
|
-
creds: ManualApn,
|
|
168
|
-
): Promise<AutoApnTransitionResult> {
|
|
169
|
-
const modify = await runNmcli(this.#runner, toManualArgs(id, creds));
|
|
170
|
-
if (modify.exitCode !== 0) {
|
|
171
|
-
return {
|
|
172
|
-
receipt: receipt('connection', 'failed', modify.stderr || 'manual-apn modify rejected'),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
return this.#reactivateAndClassify(id, ifname, false);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async #reactivateAndClassify(
|
|
179
|
-
id: ConnectionId,
|
|
180
|
-
ifname: DeviceIfname,
|
|
181
|
-
underAuto: boolean,
|
|
182
|
-
): Promise<AutoApnTransitionResult> {
|
|
183
|
-
const up = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
|
|
184
|
-
return classifyActivation(up, { underAuto });
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async #reactivate(lease: QuiesceLease): Promise<void> {
|
|
188
|
-
await runNmcli(this.#runner, [
|
|
189
|
-
'connection',
|
|
190
|
-
'up',
|
|
191
|
-
lease.connectionId,
|
|
192
|
-
'ifname',
|
|
193
|
-
lease.deviceIfname,
|
|
194
|
-
]);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async #activeUuidOn(ifname: DeviceIfname): Promise<string | undefined> {
|
|
198
|
-
const result = await runNmcli(this.#runner, [
|
|
199
|
-
'-t',
|
|
200
|
-
'-f',
|
|
201
|
-
'UUID,DEVICE',
|
|
202
|
-
'connection',
|
|
203
|
-
'show',
|
|
204
|
-
'--active',
|
|
205
|
-
]);
|
|
206
|
-
for (const line of result.stdout.split('\n')) {
|
|
207
|
-
const [uuid, device] = line.split(':');
|
|
208
|
-
if (device === ifname && uuid) {
|
|
209
|
-
return uuid;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async #requireProfile(id: ConnectionId): Promise<GsmProfile> {
|
|
216
|
-
const profile = await this.readGsmProfile(id);
|
|
217
|
-
if (profile === undefined) {
|
|
218
|
-
throw new Error(`nmcli: profile ${id} did not read back`);
|
|
219
|
-
}
|
|
220
|
-
return profile;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function activationReceipt(result: NmcliResult, appliedReason: string): Receipt {
|
|
225
|
-
return result.exitCode === 0
|
|
226
|
-
? receipt('enabled', 'applied', appliedReason)
|
|
227
|
-
: receipt('enabled', 'failed', result.stderr || 'nmcli returned a non-zero exit code');
|
|
228
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// The nmcli invocation seam. The shipping adapter spawns the real `nmcli`; tests
|
|
2
|
-
// inject A2.3's stateful in-memory runner (structurally the same `run(argv)`). The
|
|
3
|
-
// port never spawns directly — it only ever talks to an injected `NmcliRunner`, so
|
|
4
|
-
// the exact code path exercised under test is the one that runs on-device.
|
|
5
|
-
|
|
6
|
-
export interface NmcliResult {
|
|
7
|
-
readonly stdout: string;
|
|
8
|
-
readonly stderr: string;
|
|
9
|
-
readonly exitCode: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A runner over nmcli argv. `run` may be synchronous (the stateful test double) or
|
|
14
|
-
* asynchronous (the real `Bun.spawn` adapter) — the port awaits either way.
|
|
15
|
-
*/
|
|
16
|
-
export interface NmcliRunner {
|
|
17
|
-
run(argv: readonly string[]): NmcliResult | Promise<NmcliResult>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Await a runner's result whether it returned synchronously or as a promise. */
|
|
21
|
-
export async function runNmcli(runner: NmcliRunner, argv: readonly string[]): Promise<NmcliResult> {
|
|
22
|
-
return runner.run(argv);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The device-exact runner: spawns the real `nmcli` via Bun. Every argv the port
|
|
27
|
-
* builds is passed verbatim, so what the tests assert against the stateful stub is
|
|
28
|
-
* byte-for-byte what runs on the device.
|
|
29
|
-
*/
|
|
30
|
-
export class SpawnNmcliRunner implements NmcliRunner {
|
|
31
|
-
async run(argv: readonly string[]): Promise<NmcliResult> {
|
|
32
|
-
const proc = Bun.spawn(['nmcli', ...argv], { stdout: 'pipe', stderr: 'pipe' });
|
|
33
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
34
|
-
new Response(proc.stdout).text(),
|
|
35
|
-
new Response(proc.stderr).text(),
|
|
36
|
-
proc.exited,
|
|
37
|
-
]);
|
|
38
|
-
return { stdout, stderr, exitCode };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Parse nmcli terse (`-t`) `key:value` lines into a map, splitting on the first `:`. */
|
|
43
|
-
export function parseTerse(stdout: string): Map<string, string> {
|
|
44
|
-
const settings = new Map<string, string>();
|
|
45
|
-
for (const line of stdout.split('\n')) {
|
|
46
|
-
const separator = line.indexOf(':');
|
|
47
|
-
if (separator >= 0) {
|
|
48
|
-
settings.set(line.slice(0, separator), line.slice(separator + 1));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return settings;
|
|
52
|
-
}
|