@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.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -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 +76 -0
- package/dist/backend/transition-preconditions.js +64 -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 +196 -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 +19 -0
- package/dist/index.js +26 -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 +47 -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/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 +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -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 +39 -0
- package/dist/providers/modem-manager/provider.js +152 -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 +135 -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 +163 -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 +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -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 +78 -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 +151 -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 +5 -0
- package/dist/usb-mode/index.js +15 -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/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 +84 -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 -221
- 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/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 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- 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 -27
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// The ModemManager mutations — the disruptive-and-SIM half of `ModemManagerPort`.
|
|
2
|
+
//
|
|
3
|
+
// Every disruptive op runs through the shared per-modem `ModemActor` (serialized on
|
|
4
|
+
// the STABLE key, so two ops on one modem never interleave and a replug keeps the
|
|
5
|
+
// same queue). Mode and slot changes additionally run QUIESCED (NM briefly stands
|
|
6
|
+
// down first, via the actor's quiesce hook). PIN / PUK / scan serialize too but do
|
|
7
|
+
// NOT quiesce — they don't touch the bearer. NONE of these methods can reach a bearer
|
|
8
|
+
// or connect verb: the port has none, and the fake's tripwire proves it at test time.
|
|
9
|
+
import { decodeBandList, encodeBandList, isResetSelection } from '../band/index.js';
|
|
10
|
+
import { epochMillis } from '../domain/index.js';
|
|
11
|
+
import { receipt } from '../ports/index.js';
|
|
12
|
+
import { MM_BUS_NAME, MM_MANAGER_IFACE, MM_ROOT_PATH, MODEM_IFACE, MODEM3GPP_IFACE, } from './constants.js';
|
|
13
|
+
import { fetchManagedObjects, findInterface, numberProp, propValue, stringProp, } from './managed-objects.js';
|
|
14
|
+
import { sendSimPin, sendSimPuk } from './sim-unlock.js';
|
|
15
|
+
/** MMModemMode bit per RAT family (2G/3G/4G/5G). */
|
|
16
|
+
const MODE_BIT = { gsm: 2, umts: 4, lte: 8, '5gnr': 16 };
|
|
17
|
+
/** MMModem3gppNetworkAvailability → the port's availability. */
|
|
18
|
+
const AVAILABILITY = {
|
|
19
|
+
0: 'unknown',
|
|
20
|
+
1: 'available',
|
|
21
|
+
2: 'current',
|
|
22
|
+
3: 'forbidden',
|
|
23
|
+
};
|
|
24
|
+
/** A network scan can take a long time — MM's own default is not enough. */
|
|
25
|
+
const DEFAULT_SCAN_TIMEOUT_MS = 300_000;
|
|
26
|
+
/** The disruptive + SIM mutations of `ModemManagerPort`, serialized per modem. */
|
|
27
|
+
export class MmMutations {
|
|
28
|
+
#transport;
|
|
29
|
+
#actor;
|
|
30
|
+
#destination;
|
|
31
|
+
#resolveStableKey;
|
|
32
|
+
#scanTimeoutMs;
|
|
33
|
+
#now;
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.#transport = deps.transport;
|
|
36
|
+
this.#actor = deps.actor;
|
|
37
|
+
this.#destination = deps.destination ?? MM_BUS_NAME;
|
|
38
|
+
this.#resolveStableKey = deps.resolveStableKey;
|
|
39
|
+
this.#scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_SCAN_TIMEOUT_MS;
|
|
40
|
+
this.#now = deps.now ?? Date.now;
|
|
41
|
+
}
|
|
42
|
+
setRadioModes(modem, preference) {
|
|
43
|
+
const allowed = maskOf(preference.allowedSet ?? new Set(preference.preferenceOrdered));
|
|
44
|
+
const preferred = preference.preferenceOrdered[0];
|
|
45
|
+
const preferredMask = preferred !== undefined ? MODE_BIT[preferred] : 0;
|
|
46
|
+
if (allowed === 0) {
|
|
47
|
+
return Promise.resolve(receipt('radio', 'failed', 'no radio modes were requested'));
|
|
48
|
+
}
|
|
49
|
+
return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
|
|
50
|
+
try {
|
|
51
|
+
await this.#transport.callMethod({
|
|
52
|
+
destination: this.#destination,
|
|
53
|
+
path: modem,
|
|
54
|
+
interface: MODEM_IFACE,
|
|
55
|
+
member: 'SetCurrentModes',
|
|
56
|
+
signature: '(uu)',
|
|
57
|
+
args: [[allowed, preferredMask]],
|
|
58
|
+
});
|
|
59
|
+
return receipt('radio', 'applied', 'radio mode preference applied');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return receipt('radio', 'failed', `SetCurrentModes failed: ${describe(error)}`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `SetCurrentModes` over the RAW `(uu)` masks the modem itself advertised.
|
|
68
|
+
*
|
|
69
|
+
* `setRadioModes` above speaks the reconciler's vocabulary — an ordered RAT
|
|
70
|
+
* preference — and structurally cannot express `MM_MODEM_MODE_NONE`: its preferred
|
|
71
|
+
* mask is derived from `preferenceOrdered[0]`, so "allow this set and prefer
|
|
72
|
+
* nothing within it" has no spelling. That is the combination the bench FM350-GL
|
|
73
|
+
* actually advertises, so a caller selecting an advertised combination verbatim
|
|
74
|
+
* needs this entry point. It quiesces for the same reason `setRadioModes` does: a
|
|
75
|
+
* mode change re-registers the radio and drops the bearer underneath NM.
|
|
76
|
+
*
|
|
77
|
+
* An `allowed` mask of 0 is refused — that is not a selection, it is a radio with
|
|
78
|
+
* nothing switched on — while a `preferred` mask of 0 is passed through untouched.
|
|
79
|
+
*/
|
|
80
|
+
setModeCombination(modem, allowed, preferred) {
|
|
81
|
+
if (allowed === 0) {
|
|
82
|
+
return Promise.resolve(receipt('radio', 'failed', 'no radio modes were requested'));
|
|
83
|
+
}
|
|
84
|
+
return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
|
|
85
|
+
try {
|
|
86
|
+
await this.#transport.callMethod({
|
|
87
|
+
destination: this.#destination,
|
|
88
|
+
path: modem,
|
|
89
|
+
interface: MODEM_IFACE,
|
|
90
|
+
member: 'SetCurrentModes',
|
|
91
|
+
signature: '(uu)',
|
|
92
|
+
args: [[allowed, preferred]],
|
|
93
|
+
});
|
|
94
|
+
return receipt('radio', 'applied', 'radio mode combination applied');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return receipt('radio', 'failed', `SetCurrentModes failed: ${describe(error)}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async readBands(modem) {
|
|
102
|
+
try {
|
|
103
|
+
const tree = await fetchManagedObjects(this.#transport, this.#destination);
|
|
104
|
+
const props = findInterface(tree, modem, MODEM_IFACE);
|
|
105
|
+
if (props === undefined) {
|
|
106
|
+
return { ok: false, reason: 'the modem exports no Modem interface' };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
bands: {
|
|
111
|
+
supported: decodeBandList(propValue(props, 'SupportedBands')),
|
|
112
|
+
current: decodeBandList(propValue(props, 'CurrentBands')),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return { ok: false, reason: `reading bands failed: ${describe(error)}` };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Quiesced like `setRadioModes`, and for the same reason: a band change
|
|
121
|
+
// re-registers the radio, so NM must stand down before the bearer drops
|
|
122
|
+
// underneath it rather than after.
|
|
123
|
+
setCurrentBands(modem, bands) {
|
|
124
|
+
if (bands.length === 0) {
|
|
125
|
+
return Promise.resolve(receipt('band', 'failed', 'no bands were requested'));
|
|
126
|
+
}
|
|
127
|
+
const encoded = encodeBandList(bands);
|
|
128
|
+
if (!encoded.ok) {
|
|
129
|
+
return Promise.resolve(receipt('band', 'unsupported', `this build does not know the band "${encoded.unknown}"`));
|
|
130
|
+
}
|
|
131
|
+
const values = encoded.values;
|
|
132
|
+
return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
|
|
133
|
+
try {
|
|
134
|
+
await this.#transport.callMethod({
|
|
135
|
+
destination: this.#destination,
|
|
136
|
+
path: modem,
|
|
137
|
+
interface: MODEM_IFACE,
|
|
138
|
+
member: 'SetCurrentBands',
|
|
139
|
+
signature: 'au',
|
|
140
|
+
args: [values],
|
|
141
|
+
});
|
|
142
|
+
return receipt('band', 'applied', isResetSelection(bands) ? 'band lock released' : `bands set to ${bands.join(', ')}`);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return receipt('band', 'failed', `SetCurrentBands failed: ${describe(error)}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async setPrimarySimSlot(modem, slotIndex) {
|
|
150
|
+
const slots = await this.#readSlotCount(modem);
|
|
151
|
+
if (slots === undefined) {
|
|
152
|
+
return receipt('simSlot', 'failed', 'could not read the modem SIM-slot list');
|
|
153
|
+
}
|
|
154
|
+
if (slots <= 1) {
|
|
155
|
+
return receipt('simSlot', 'unsupported', 'single-slot modem has no primary slot to select');
|
|
156
|
+
}
|
|
157
|
+
if (slotIndex < 1 || slotIndex > slots) {
|
|
158
|
+
return receipt('simSlot', 'failed', `slot ${slotIndex} is out of range (1..${slots})`);
|
|
159
|
+
}
|
|
160
|
+
return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
|
|
161
|
+
try {
|
|
162
|
+
await this.#transport.callMethod({
|
|
163
|
+
destination: this.#destination,
|
|
164
|
+
path: modem,
|
|
165
|
+
interface: MODEM_IFACE,
|
|
166
|
+
member: 'SetPrimarySimSlot',
|
|
167
|
+
signature: 'u',
|
|
168
|
+
args: [slotIndex],
|
|
169
|
+
});
|
|
170
|
+
return receipt('simSlot', 'applied', `primary SIM slot set to ${slotIndex}`);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return receipt('simSlot', 'failed', `SetPrimarySimSlot failed: ${describe(error)}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
sendPin(modem, pin) {
|
|
178
|
+
return this.#actor.run(this.#resolveStableKey(modem), () => sendSimPin(this.#transport, this.#destination, modem, pin));
|
|
179
|
+
}
|
|
180
|
+
sendPuk(modem, puk, newPin) {
|
|
181
|
+
return this.#actor.run(this.#resolveStableKey(modem), () => sendSimPuk(this.#transport, this.#destination, modem, puk, newPin));
|
|
182
|
+
}
|
|
183
|
+
scanNetworks(modem) {
|
|
184
|
+
return this.#actor.run(this.#resolveStableKey(modem), async () => {
|
|
185
|
+
try {
|
|
186
|
+
const reply = await this.#transport.callMethod({
|
|
187
|
+
destination: this.#destination,
|
|
188
|
+
path: modem,
|
|
189
|
+
interface: MODEM3GPP_IFACE,
|
|
190
|
+
member: 'Scan',
|
|
191
|
+
timeoutMs: this.#scanTimeoutMs,
|
|
192
|
+
});
|
|
193
|
+
return { ok: true, networks: parseScan(reply.body[0]) };
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return { ok: false, reason: `network scan failed: ${describe(error)}` };
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async inhibit(uid) {
|
|
201
|
+
await this.#inhibitDevice(uid, true);
|
|
202
|
+
return { uid, acquiredAt: epochMillis(this.#now()) };
|
|
203
|
+
}
|
|
204
|
+
async uninhibit(lease) {
|
|
205
|
+
await this.#inhibitDevice(lease.uid, false);
|
|
206
|
+
}
|
|
207
|
+
#inhibitDevice(uid, inhibit) {
|
|
208
|
+
return this.#transport.callMethod({
|
|
209
|
+
destination: this.#destination,
|
|
210
|
+
path: MM_ROOT_PATH,
|
|
211
|
+
interface: MM_MANAGER_IFACE,
|
|
212
|
+
member: 'InhibitDevice',
|
|
213
|
+
signature: 'sb',
|
|
214
|
+
args: [uid, inhibit],
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async #readSlotCount(modem) {
|
|
218
|
+
try {
|
|
219
|
+
const tree = await fetchManagedObjects(this.#transport, this.#destination);
|
|
220
|
+
const slots = propValue(findInterface(tree, modem, MODEM_IFACE), 'SimSlots');
|
|
221
|
+
return Array.isArray(slots) ? slots.length : undefined;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** OR of the MMModemMode bits for a set of RATs. */
|
|
229
|
+
function maskOf(rats) {
|
|
230
|
+
let mask = 0;
|
|
231
|
+
for (const rat of rats) {
|
|
232
|
+
mask |= MODE_BIT[rat];
|
|
233
|
+
}
|
|
234
|
+
return mask;
|
|
235
|
+
}
|
|
236
|
+
/** Parse a `Modem3gpp.Scan` reply (`aa{sv}` → dicts) into scanned networks. */
|
|
237
|
+
function parseScan(value) {
|
|
238
|
+
if (!Array.isArray(value)) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const networks = [];
|
|
242
|
+
for (const entry of value) {
|
|
243
|
+
const operatorCode = stringProp(entry, 'operator-code');
|
|
244
|
+
if (operatorCode === undefined) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const name = stringProp(entry, 'operator-long') ?? stringProp(entry, 'operator-short');
|
|
248
|
+
const availability = AVAILABILITY[numberProp(entry, 'status') ?? 0] ?? 'unknown';
|
|
249
|
+
networks.push({
|
|
250
|
+
operatorCode,
|
|
251
|
+
...(name !== undefined ? { operatorName: name } : {}),
|
|
252
|
+
availability,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return networks;
|
|
256
|
+
}
|
|
257
|
+
function describe(error) {
|
|
258
|
+
return error instanceof Error ? error.message : String(error);
|
|
259
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ConnectionId, DeviceIfname } from '../ports/index.js';
|
|
2
|
+
/** What a quiesce lease is taken over — the modem, and (once A4.1 knows them) its
|
|
3
|
+
* NM connection + device. In A3.3 only `stableKey` is populated. */
|
|
4
|
+
export interface QuiesceTarget {
|
|
5
|
+
readonly stableKey: string;
|
|
6
|
+
readonly connectionId?: ConnectionId;
|
|
7
|
+
readonly deviceIfname?: DeviceIfname;
|
|
8
|
+
}
|
|
9
|
+
/** A held quiesce lease — released (reactivating the connection) when the op ends. */
|
|
10
|
+
export interface QuiesceLeaseHandle {
|
|
11
|
+
release(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The NM-quiesce coordination hook the actor calls around a disruptive mode/slot
|
|
15
|
+
* change. A4.1 wires the real `nmcli` lease (verify-active → `device disconnect` →
|
|
16
|
+
* hold → reactivate); A3.3 ships `NO_OP_QUIESCE`, whose call site + shape are the
|
|
17
|
+
* exact seam A4.1 fills in.
|
|
18
|
+
*/
|
|
19
|
+
export interface QuiesceHook {
|
|
20
|
+
acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle>;
|
|
21
|
+
}
|
|
22
|
+
/** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
|
|
23
|
+
export declare const NO_OP_QUIESCE: QuiesceHook;
|
|
24
|
+
/**
|
|
25
|
+
* A per-modem serialized actor. `run` serializes a task behind every prior task for
|
|
26
|
+
* the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
|
|
27
|
+
* task's duration. Different stable keys never block one another.
|
|
28
|
+
*/
|
|
29
|
+
export declare class ModemActor {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(quiesce?: QuiesceHook);
|
|
32
|
+
/** Run `task` serialized behind every prior task for `stableKey`. */
|
|
33
|
+
run<T>(stableKey: string, task: () => Promise<T>): Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
|
|
36
|
+
* its whole duration — the lease is always released, even if `task` throws.
|
|
37
|
+
*/
|
|
38
|
+
runQuiesced<T>(target: QuiesceTarget, task: () => Promise<T>): Promise<T>;
|
|
39
|
+
/** Number of modems with a live queue — exposed for leak / idle assertions. */
|
|
40
|
+
get activeKeyCount(): number;
|
|
41
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
const NO_OP_LEASE = {
|
|
20
|
+
release() {
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
|
|
25
|
+
export const NO_OP_QUIESCE = {
|
|
26
|
+
acquire() {
|
|
27
|
+
return Promise.resolve(NO_OP_LEASE);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A per-modem serialized actor. `run` serializes a task behind every prior task for
|
|
32
|
+
* the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
|
|
33
|
+
* task's duration. Different stable keys never block one another.
|
|
34
|
+
*/
|
|
35
|
+
export class ModemActor {
|
|
36
|
+
#quiesce;
|
|
37
|
+
// Tail of each modem's queue — a promise that settles when the last-enqueued task
|
|
38
|
+
// for that key finishes (errors swallowed so the chain never breaks).
|
|
39
|
+
#tails = new Map();
|
|
40
|
+
constructor(quiesce = NO_OP_QUIESCE) {
|
|
41
|
+
this.#quiesce = quiesce;
|
|
42
|
+
}
|
|
43
|
+
/** Run `task` serialized behind every prior task for `stableKey`. */
|
|
44
|
+
run(stableKey, task) {
|
|
45
|
+
const prior = this.#tails.get(stableKey) ?? Promise.resolve();
|
|
46
|
+
// `then(task, task)` runs `task` whether the prior task resolved OR rejected,
|
|
47
|
+
// so one failure never stalls the queue.
|
|
48
|
+
const result = prior.then(task, task);
|
|
49
|
+
const settled = result.then(() => undefined, () => undefined);
|
|
50
|
+
this.#tails.set(stableKey, settled);
|
|
51
|
+
// Drop the entry once this task was the last one, to bound the map.
|
|
52
|
+
void settled.then(() => {
|
|
53
|
+
if (this.#tails.get(stableKey) === settled) {
|
|
54
|
+
this.#tails.delete(stableKey);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
|
|
61
|
+
* its whole duration — the lease is always released, even if `task` throws.
|
|
62
|
+
*/
|
|
63
|
+
runQuiesced(target, task) {
|
|
64
|
+
return this.run(target.stableKey, async () => {
|
|
65
|
+
const lease = await this.#quiesce.acquire(target);
|
|
66
|
+
try {
|
|
67
|
+
return await task();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await lease.release();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Number of modems with a live queue — exposed for leak / idle assertions. */
|
|
75
|
+
get activeKeyCount() {
|
|
76
|
+
return this.#tails.size;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Receipt } from '../ports/index.js';
|
|
2
|
+
import { type NmcliResult, type NmcliRunner } from './nmcli-runner.js';
|
|
3
|
+
/** The single advisory Auto-APN raises when it cannot be honored. */
|
|
4
|
+
export declare const AUTO_APN_ADVISORY = "autoApnUnavailable";
|
|
5
|
+
export type AutoApnAdvisory = typeof AUTO_APN_ADVISORY;
|
|
6
|
+
/** The outcome of an Auto-APN transition: a receipt plus an optional advisory flag. */
|
|
7
|
+
export interface AutoApnTransitionResult {
|
|
8
|
+
readonly receipt: Receipt;
|
|
9
|
+
readonly advisory?: AutoApnAdvisory;
|
|
10
|
+
}
|
|
11
|
+
/** Explicit APN + credentials for the manual direction of a transition. */
|
|
12
|
+
export interface ManualApn {
|
|
13
|
+
readonly apn: string;
|
|
14
|
+
readonly username?: string;
|
|
15
|
+
readonly password?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Parse `nmcli tool, version 1.42.4` → `{ major, minor }` (null on garbage). */
|
|
18
|
+
export declare function parseNmVersion(output: string): {
|
|
19
|
+
major: number;
|
|
20
|
+
minor: number;
|
|
21
|
+
} | null;
|
|
22
|
+
/** `true` iff the parsed NM version is >= 1.22 (auto-config support floor). */
|
|
23
|
+
export declare function autoApnSupportedByVersion(version: {
|
|
24
|
+
major: number;
|
|
25
|
+
minor: number;
|
|
26
|
+
}): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Decide from a `nmcli --version` result whether Auto-APN is available on this install.
|
|
29
|
+
* Fail-CLOSED — an unreadable/garbled version is "not capable", so we never claim
|
|
30
|
+
* Auto-APN works when we cannot prove it does.
|
|
31
|
+
*/
|
|
32
|
+
export declare function autoApnCapableFromVersion(result: NmcliResult): boolean;
|
|
33
|
+
/** Boot capability probe: run `nmcli --version` and resolve Auto-APN availability. */
|
|
34
|
+
export declare function probeAutoApnCapability(runner: NmcliRunner): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* ONE atomic `connection modify` that flips a profile to Auto-APN: clears every
|
|
37
|
+
* credential AND sets `gsm.auto-config yes` in the SAME invocation. Order/atomicity
|
|
38
|
+
* matter — NM rejects `auto-config yes` while any credential is still set, so the clear
|
|
39
|
+
* and the enable MUST land together (never two modifies).
|
|
40
|
+
*/
|
|
41
|
+
export declare function toAutoArgs(id: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* The exact reverse: ONE atomic `connection modify` restoring explicit APN + creds and
|
|
44
|
+
* clearing `gsm.auto-config`, with password-flags matching the (new) password.
|
|
45
|
+
*/
|
|
46
|
+
export declare function toManualArgs(id: string, creds: ManualApn): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Classify a `connection up` result into a receipt (+ advisory). `underAuto` marks that
|
|
49
|
+
* the profile being activated is an Auto-APN profile, so an APN failure is the "no MBPI
|
|
50
|
+
* match for this SIM" case → unsupported + advisory rather than a hard fail.
|
|
51
|
+
*/
|
|
52
|
+
export declare function classifyActivation(result: NmcliResult, options: {
|
|
53
|
+
readonly underAuto: boolean;
|
|
54
|
+
}): AutoApnTransitionResult;
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
import { receipt } from '../ports/index.js';
|
|
17
|
+
import { cliArg, passwordFlags } from './nm-gsm-fields.js';
|
|
18
|
+
import { runNmcli } from './nmcli-runner.js';
|
|
19
|
+
/** The single advisory Auto-APN raises when it cannot be honored. */
|
|
20
|
+
export const AUTO_APN_ADVISORY = 'autoApnUnavailable';
|
|
21
|
+
const AUTO_CONFIG_MIN_MAJOR = 1;
|
|
22
|
+
const AUTO_CONFIG_MIN_MINOR = 22;
|
|
23
|
+
/** nmcli's own `--wait` timeout exit status (nmcli(1): 3 = timeout expired). */
|
|
24
|
+
const NMCLI_EXIT_TIMEOUT = 3;
|
|
25
|
+
const APN_FAILED = /gsm[_-]?apn[_-]?failed/i;
|
|
26
|
+
/** Parse `nmcli tool, version 1.42.4` → `{ major, minor }` (null on garbage). */
|
|
27
|
+
export function parseNmVersion(output) {
|
|
28
|
+
const match = /version\s+(\d+)\.(\d+)/i.exec(output);
|
|
29
|
+
if (match?.[1] === undefined || match[2] === undefined) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return { major: Number(match[1]), minor: Number(match[2]) };
|
|
33
|
+
}
|
|
34
|
+
/** `true` iff the parsed NM version is >= 1.22 (auto-config support floor). */
|
|
35
|
+
export function autoApnSupportedByVersion(version) {
|
|
36
|
+
if (version.major !== AUTO_CONFIG_MIN_MAJOR) {
|
|
37
|
+
return version.major > AUTO_CONFIG_MIN_MAJOR;
|
|
38
|
+
}
|
|
39
|
+
return version.minor >= AUTO_CONFIG_MIN_MINOR;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decide from a `nmcli --version` result whether Auto-APN is available on this install.
|
|
43
|
+
* Fail-CLOSED — an unreadable/garbled version is "not capable", so we never claim
|
|
44
|
+
* Auto-APN works when we cannot prove it does.
|
|
45
|
+
*/
|
|
46
|
+
export function autoApnCapableFromVersion(result) {
|
|
47
|
+
if (result.exitCode !== 0) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const version = parseNmVersion(result.stdout);
|
|
51
|
+
return version !== null && autoApnSupportedByVersion(version);
|
|
52
|
+
}
|
|
53
|
+
/** Boot capability probe: run `nmcli --version` and resolve Auto-APN availability. */
|
|
54
|
+
export async function probeAutoApnCapability(runner) {
|
|
55
|
+
return autoApnCapableFromVersion(await runNmcli(runner, ['--version']));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ONE atomic `connection modify` that flips a profile to Auto-APN: clears every
|
|
59
|
+
* credential AND sets `gsm.auto-config yes` in the SAME invocation. Order/atomicity
|
|
60
|
+
* matter — NM rejects `auto-config yes` while any credential is still set, so the clear
|
|
61
|
+
* and the enable MUST land together (never two modifies).
|
|
62
|
+
*/
|
|
63
|
+
export function toAutoArgs(id) {
|
|
64
|
+
return [
|
|
65
|
+
'connection',
|
|
66
|
+
'modify',
|
|
67
|
+
id,
|
|
68
|
+
'gsm.apn',
|
|
69
|
+
'',
|
|
70
|
+
'gsm.username',
|
|
71
|
+
'',
|
|
72
|
+
'gsm.password',
|
|
73
|
+
'',
|
|
74
|
+
'gsm.password-flags',
|
|
75
|
+
'4',
|
|
76
|
+
'gsm.auto-config',
|
|
77
|
+
'yes',
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The exact reverse: ONE atomic `connection modify` restoring explicit APN + creds and
|
|
82
|
+
* clearing `gsm.auto-config`, with password-flags matching the (new) password.
|
|
83
|
+
*/
|
|
84
|
+
export function toManualArgs(id, creds) {
|
|
85
|
+
return [
|
|
86
|
+
'connection',
|
|
87
|
+
'modify',
|
|
88
|
+
id,
|
|
89
|
+
'gsm.apn',
|
|
90
|
+
cliArg(creds.apn),
|
|
91
|
+
'gsm.username',
|
|
92
|
+
cliArg(creds.username),
|
|
93
|
+
'gsm.password',
|
|
94
|
+
cliArg(creds.password),
|
|
95
|
+
'gsm.password-flags',
|
|
96
|
+
passwordFlags(creds.password),
|
|
97
|
+
'gsm.auto-config',
|
|
98
|
+
'no',
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Classify a `connection up` result into a receipt (+ advisory). `underAuto` marks that
|
|
103
|
+
* the profile being activated is an Auto-APN profile, so an APN failure is the "no MBPI
|
|
104
|
+
* match for this SIM" case → unsupported + advisory rather than a hard fail.
|
|
105
|
+
*/
|
|
106
|
+
export function classifyActivation(result, options) {
|
|
107
|
+
if (result.exitCode === 0) {
|
|
108
|
+
return { receipt: receipt('connection', 'applied', 'connection activated') };
|
|
109
|
+
}
|
|
110
|
+
if (result.exitCode === NMCLI_EXIT_TIMEOUT || /timeout/i.test(result.stderr)) {
|
|
111
|
+
return {
|
|
112
|
+
receipt: receipt('connection', 'pending', 'activation wait timed out; terminal state unknown'),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (options.underAuto && (APN_FAILED.test(result.stderr) || APN_FAILED.test(result.stdout))) {
|
|
116
|
+
return {
|
|
117
|
+
receipt: receipt('connection', 'unsupported', 'Auto-APN found no operator match for this SIM'),
|
|
118
|
+
advisory: AUTO_APN_ADVISORY,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
receipt: receipt('connection', 'failed', result.stderr || 'connection activation failed'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ConnectionId, GsmProfile, GsmProfileInput, GsmProfilePatch } from '../ports/index.js';
|
|
2
|
+
export declare const cliArg: (value: string | undefined) => string;
|
|
3
|
+
/** NM secret-flags: no password ⇒ NOT_REQUIRED ("4"); password set ⇒ NONE ("0"). */
|
|
4
|
+
export declare const passwordFlags: (password: string | undefined) => string;
|
|
5
|
+
/**
|
|
6
|
+
* The nine `gsm.*` + `connection.*` key/value pairs for a profile, in stable order.
|
|
7
|
+
* With `autoConfig` set the creds are forced empty (NM rejects `auto-config yes` while
|
|
8
|
+
* any credential is present — nm-setting-gsm.c:440-447), so an Auto-APN create is always
|
|
9
|
+
* NM-valid by construction.
|
|
10
|
+
*/
|
|
11
|
+
export declare function gsmFieldPairs(input: GsmProfileInput): Array<[string, string]>;
|
|
12
|
+
/** Flatten key/value pairs into the alternating argv nmcli expects. */
|
|
13
|
+
export declare function flattenPairs(pairs: ReadonlyArray<readonly [string, string]>): string[];
|
|
14
|
+
/** `connection add type gsm con-name <name> <nine fields>` — device-exact create argv. */
|
|
15
|
+
export declare function createGsmArgs(input: GsmProfileInput): string[];
|
|
16
|
+
/**
|
|
17
|
+
* The key/value pairs for a `connection modify`. Roaming (home-only) and the manual
|
|
18
|
+
* operator id move together — turning roaming off clears `gsm.network-id`, honoring the
|
|
19
|
+
* `gsm.network-id = roaming ? id : ""` invariant in the SAME modify.
|
|
20
|
+
*/
|
|
21
|
+
export declare function patchPairs(patch: GsmProfilePatch): string[];
|
|
22
|
+
/** Map an nmcli terse readback back onto a `GsmProfile` (inverse of the write parity). */
|
|
23
|
+
export declare function buildProfile(id: ConnectionId, settings: Map<string, string>): GsmProfile;
|