@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
|
@@ -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;
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
// Empty-string fallback; Bun runtime limitation with empty CLI args: a bare
|
|
17
|
+
// `undefined` reaching `Bun.spawn` argv drops the slot and shifts every following
|
|
18
|
+
// token, so every optional nmcli value is coerced to "" first — the same `value || ""`
|
|
19
|
+
// convention CeraUI documented on gsm.apn / gsm.username / gsm.password.
|
|
20
|
+
export const cliArg = (value) => value || '';
|
|
21
|
+
/** NM secret-flags: no password ⇒ NOT_REQUIRED ("4"); password set ⇒ NONE ("0"). */
|
|
22
|
+
export const passwordFlags = (password) => (password ? '0' : '4');
|
|
23
|
+
/** `gsm.network-id` — the manual operator id, and only while roaming (home-only off). */
|
|
24
|
+
const networkIdField = (input) => input.homeOnly ? '' : cliArg(input.networkId);
|
|
25
|
+
/**
|
|
26
|
+
* The nine `gsm.*` + `connection.*` key/value pairs for a profile, in stable order.
|
|
27
|
+
* With `autoConfig` set the creds are forced empty (NM rejects `auto-config yes` while
|
|
28
|
+
* any credential is present — nm-setting-gsm.c:440-447), so an Auto-APN create is always
|
|
29
|
+
* NM-valid by construction.
|
|
30
|
+
*/
|
|
31
|
+
export function gsmFieldPairs(input) {
|
|
32
|
+
const auto = input.autoConfig;
|
|
33
|
+
return [
|
|
34
|
+
['gsm.apn', auto ? '' : cliArg(input.apn)],
|
|
35
|
+
['gsm.username', auto ? '' : cliArg(input.username)],
|
|
36
|
+
['gsm.password', auto ? '' : cliArg(input.password)],
|
|
37
|
+
['gsm.password-flags', passwordFlags(auto ? undefined : input.password)],
|
|
38
|
+
['gsm.home-only', input.homeOnly ? 'yes' : 'no'],
|
|
39
|
+
['gsm.network-id', networkIdField(input)],
|
|
40
|
+
['gsm.auto-config', auto ? 'yes' : 'no'],
|
|
41
|
+
['connection.autoconnect', 'yes'],
|
|
42
|
+
['connection.autoconnect-retries', '2'],
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
/** Flatten key/value pairs into the alternating argv nmcli expects. */
|
|
46
|
+
export function flattenPairs(pairs) {
|
|
47
|
+
return pairs.flat();
|
|
48
|
+
}
|
|
49
|
+
/** `connection add type gsm con-name <name> <nine fields>` — device-exact create argv. */
|
|
50
|
+
export function createGsmArgs(input) {
|
|
51
|
+
return [
|
|
52
|
+
'connection',
|
|
53
|
+
'add',
|
|
54
|
+
'type',
|
|
55
|
+
'gsm',
|
|
56
|
+
'con-name',
|
|
57
|
+
input.connectionName,
|
|
58
|
+
...flattenPairs(gsmFieldPairs(input)),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The key/value pairs for a `connection modify`. Roaming (home-only) and the manual
|
|
63
|
+
* operator id move together — turning roaming off clears `gsm.network-id`, honoring the
|
|
64
|
+
* `gsm.network-id = roaming ? id : ""` invariant in the SAME modify.
|
|
65
|
+
*/
|
|
66
|
+
export function patchPairs(patch) {
|
|
67
|
+
const pairs = [];
|
|
68
|
+
if (patch.connectionName !== undefined) {
|
|
69
|
+
pairs.push('connection.id', patch.connectionName);
|
|
70
|
+
}
|
|
71
|
+
if (patch.apn !== undefined) {
|
|
72
|
+
pairs.push('gsm.apn', cliArg(patch.apn));
|
|
73
|
+
}
|
|
74
|
+
if (patch.username !== undefined) {
|
|
75
|
+
pairs.push('gsm.username', cliArg(patch.username));
|
|
76
|
+
}
|
|
77
|
+
if (patch.password !== undefined) {
|
|
78
|
+
pairs.push('gsm.password', cliArg(patch.password), 'gsm.password-flags', passwordFlags(patch.password));
|
|
79
|
+
}
|
|
80
|
+
if (patch.autoConfig !== undefined) {
|
|
81
|
+
pairs.push('gsm.auto-config', patch.autoConfig ? 'yes' : 'no');
|
|
82
|
+
}
|
|
83
|
+
if (patch.homeOnly !== undefined) {
|
|
84
|
+
pairs.push('gsm.home-only', patch.homeOnly ? 'yes' : 'no');
|
|
85
|
+
pairs.push('gsm.network-id', patch.homeOnly ? '' : cliArg(patch.networkId));
|
|
86
|
+
}
|
|
87
|
+
else if (patch.networkId !== undefined) {
|
|
88
|
+
pairs.push('gsm.network-id', cliArg(patch.networkId));
|
|
89
|
+
}
|
|
90
|
+
return pairs;
|
|
91
|
+
}
|
|
92
|
+
/** Map an nmcli terse readback back onto a `GsmProfile` (inverse of the write parity). */
|
|
93
|
+
export function buildProfile(id, settings) {
|
|
94
|
+
const username = settings.get('gsm.username') ?? '';
|
|
95
|
+
const password = settings.get('gsm.password') ?? '';
|
|
96
|
+
const networkId = settings.get('gsm.network-id') ?? '';
|
|
97
|
+
return {
|
|
98
|
+
connectionId: id,
|
|
99
|
+
connectionName: settings.get('connection.id') ?? '',
|
|
100
|
+
apn: settings.get('gsm.apn') ?? '',
|
|
101
|
+
homeOnly: settings.get('gsm.home-only') === 'yes',
|
|
102
|
+
autoConfig: settings.get('gsm.auto-config') === 'yes',
|
|
103
|
+
...(username !== '' ? { username } : {}),
|
|
104
|
+
...(password !== '' ? { password } : {}),
|
|
105
|
+
...(networkId !== '' ? { networkId } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ConnectionId, type DeviceIfname, type GsmProfile, type GsmProfileInput, type GsmProfilePatch, type NetworkManagerPort, type QuiesceLease, type Receipt } from '../ports/index.js';
|
|
2
|
+
import { type AutoApnTransitionResult, type ManualApn } from './nm-auto-apn.js';
|
|
3
|
+
import { type NmcliRunner } from './nmcli-runner.js';
|
|
4
|
+
export interface NmcliNmPortOptions {
|
|
5
|
+
readonly runner: NmcliRunner;
|
|
6
|
+
/** Resolved once at boot by the capability probe; default `true`. */
|
|
7
|
+
readonly autoApnCapable?: boolean;
|
|
8
|
+
readonly leaseTtlMs?: number;
|
|
9
|
+
readonly now?: () => number;
|
|
10
|
+
}
|
|
11
|
+
export declare class NmcliNmPort implements NetworkManagerPort {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(options: NmcliNmPortOptions);
|
|
14
|
+
createGsmProfile(profile: GsmProfileInput): Promise<GsmProfile>;
|
|
15
|
+
readGsmProfile(id: ConnectionId): Promise<GsmProfile | undefined>;
|
|
16
|
+
updateGsmProfile(id: ConnectionId, patch: GsmProfilePatch): Promise<GsmProfile>;
|
|
17
|
+
deleteGsmProfile(id: ConnectionId): Promise<void>;
|
|
18
|
+
activate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
|
|
19
|
+
deactivate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
|
|
20
|
+
acquireQuiesceLease(id: ConnectionId, ifname: DeviceIfname): Promise<QuiesceLease>;
|
|
21
|
+
releaseQuiesceLease(lease: QuiesceLease): Promise<void>;
|
|
22
|
+
/** Reactivate every lease held past the TTL — the abandoned-lease watchdog. */
|
|
23
|
+
sweepExpiredLeases(now?: number): Promise<void>;
|
|
24
|
+
/** Flip a profile to Auto-APN via ONE atomic modify, then reactivate + classify. */
|
|
25
|
+
transitionToAuto(id: ConnectionId, ifname: DeviceIfname): Promise<AutoApnTransitionResult>;
|
|
26
|
+
/** Flip a profile to manual APN via ONE atomic modify, then reactivate + classify. */
|
|
27
|
+
transitionToManual(id: ConnectionId, ifname: DeviceIfname, creds: ManualApn): Promise<AutoApnTransitionResult>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
import { epochMillis } from '../domain/index.js';
|
|
6
|
+
import { connectionId, receipt, } from '../ports/index.js';
|
|
7
|
+
import { AUTO_APN_ADVISORY, classifyActivation, toAutoArgs, toManualArgs, } from './nm-auto-apn.js';
|
|
8
|
+
import { buildProfile, createGsmArgs, patchPairs } from './nm-gsm-fields.js';
|
|
9
|
+
import { parseTerse, runNmcli } from './nmcli-runner.js';
|
|
10
|
+
const READBACK_FIELDS = [
|
|
11
|
+
'connection.id',
|
|
12
|
+
'gsm.apn',
|
|
13
|
+
'gsm.username',
|
|
14
|
+
'gsm.password',
|
|
15
|
+
'gsm.password-flags',
|
|
16
|
+
'gsm.home-only',
|
|
17
|
+
'gsm.network-id',
|
|
18
|
+
'gsm.auto-config',
|
|
19
|
+
'connection.autoconnect',
|
|
20
|
+
'connection.autoconnect-retries',
|
|
21
|
+
];
|
|
22
|
+
/** Default abandoned-lease TTL: a held quiesce lease older than this auto-releases. */
|
|
23
|
+
const DEFAULT_LEASE_TTL_MS = 60_000;
|
|
24
|
+
export class NmcliNmPort {
|
|
25
|
+
#runner;
|
|
26
|
+
#autoApnCapable;
|
|
27
|
+
#leaseTtlMs;
|
|
28
|
+
#now;
|
|
29
|
+
#leases = new Set();
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#runner = options.runner;
|
|
32
|
+
this.#autoApnCapable = options.autoApnCapable ?? true;
|
|
33
|
+
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
34
|
+
this.#now = options.now ?? Date.now;
|
|
35
|
+
}
|
|
36
|
+
async createGsmProfile(profile) {
|
|
37
|
+
const result = await runNmcli(this.#runner, createGsmArgs(profile));
|
|
38
|
+
const uuid = /\(([^)]+)\) successfully added/.exec(result.stdout)?.[1];
|
|
39
|
+
if (uuid === undefined) {
|
|
40
|
+
throw new Error(`nmcli connection add failed: ${result.stderr || result.stdout}`);
|
|
41
|
+
}
|
|
42
|
+
return this.#requireProfile(connectionId(uuid));
|
|
43
|
+
}
|
|
44
|
+
async readGsmProfile(id) {
|
|
45
|
+
const result = await runNmcli(this.#runner, [
|
|
46
|
+
'-t',
|
|
47
|
+
'-f',
|
|
48
|
+
READBACK_FIELDS.join(','),
|
|
49
|
+
'connection',
|
|
50
|
+
'show',
|
|
51
|
+
id,
|
|
52
|
+
]);
|
|
53
|
+
if (result.exitCode !== 0) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return buildProfile(id, parseTerse(result.stdout));
|
|
57
|
+
}
|
|
58
|
+
async updateGsmProfile(id, patch) {
|
|
59
|
+
const result = await runNmcli(this.#runner, ['connection', 'modify', id, ...patchPairs(patch)]);
|
|
60
|
+
if (result.exitCode !== 0) {
|
|
61
|
+
throw new Error(`nmcli connection modify failed: ${result.stderr}`);
|
|
62
|
+
}
|
|
63
|
+
return this.#requireProfile(id);
|
|
64
|
+
}
|
|
65
|
+
async deleteGsmProfile(id) {
|
|
66
|
+
await runNmcli(this.#runner, ['connection', 'delete', id]);
|
|
67
|
+
}
|
|
68
|
+
async activate(id, ifname) {
|
|
69
|
+
const result = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
|
|
70
|
+
return activationReceipt(result, `activated ${id} on ${ifname}`);
|
|
71
|
+
}
|
|
72
|
+
async deactivate(id, ifname) {
|
|
73
|
+
if ((await this.#activeUuidOn(ifname)) !== id) {
|
|
74
|
+
return receipt('enabled', 'applied', `${id} not active on ${ifname}; nothing to deactivate`);
|
|
75
|
+
}
|
|
76
|
+
const result = await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
|
|
77
|
+
return activationReceipt(result, `deactivated ${id} on ${ifname}`);
|
|
78
|
+
}
|
|
79
|
+
async acquireQuiesceLease(id, ifname) {
|
|
80
|
+
if ((await this.#activeUuidOn(ifname)) === id) {
|
|
81
|
+
await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
|
|
82
|
+
}
|
|
83
|
+
const lease = {
|
|
84
|
+
connectionId: id,
|
|
85
|
+
deviceIfname: ifname,
|
|
86
|
+
acquiredAt: epochMillis(this.#now()),
|
|
87
|
+
};
|
|
88
|
+
this.#leases.add(lease);
|
|
89
|
+
return lease;
|
|
90
|
+
}
|
|
91
|
+
async releaseQuiesceLease(lease) {
|
|
92
|
+
if (this.#leases.delete(lease)) {
|
|
93
|
+
await this.#reactivate(lease);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Reactivate every lease held past the TTL — the abandoned-lease watchdog. */
|
|
97
|
+
async sweepExpiredLeases(now = this.#now()) {
|
|
98
|
+
for (const lease of [...this.#leases]) {
|
|
99
|
+
if (now - lease.acquiredAt >= this.#leaseTtlMs) {
|
|
100
|
+
this.#leases.delete(lease);
|
|
101
|
+
await this.#reactivate(lease);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Flip a profile to Auto-APN via ONE atomic modify, then reactivate + classify. */
|
|
106
|
+
async transitionToAuto(id, ifname) {
|
|
107
|
+
if (!this.#autoApnCapable) {
|
|
108
|
+
return {
|
|
109
|
+
receipt: receipt('connection', 'unsupported', 'Auto-APN requires NetworkManager >= 1.22'),
|
|
110
|
+
advisory: AUTO_APN_ADVISORY,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const modify = await runNmcli(this.#runner, toAutoArgs(id));
|
|
114
|
+
if (modify.exitCode !== 0) {
|
|
115
|
+
return {
|
|
116
|
+
receipt: receipt('connection', 'failed', modify.stderr || 'auto-config modify rejected'),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return this.#reactivateAndClassify(id, ifname, true);
|
|
120
|
+
}
|
|
121
|
+
/** Flip a profile to manual APN via ONE atomic modify, then reactivate + classify. */
|
|
122
|
+
async transitionToManual(id, ifname, creds) {
|
|
123
|
+
const modify = await runNmcli(this.#runner, toManualArgs(id, creds));
|
|
124
|
+
if (modify.exitCode !== 0) {
|
|
125
|
+
return {
|
|
126
|
+
receipt: receipt('connection', 'failed', modify.stderr || 'manual-apn modify rejected'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return this.#reactivateAndClassify(id, ifname, false);
|
|
130
|
+
}
|
|
131
|
+
async #reactivateAndClassify(id, ifname, underAuto) {
|
|
132
|
+
const up = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
|
|
133
|
+
return classifyActivation(up, { underAuto });
|
|
134
|
+
}
|
|
135
|
+
async #reactivate(lease) {
|
|
136
|
+
await runNmcli(this.#runner, [
|
|
137
|
+
'connection',
|
|
138
|
+
'up',
|
|
139
|
+
lease.connectionId,
|
|
140
|
+
'ifname',
|
|
141
|
+
lease.deviceIfname,
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
async #activeUuidOn(ifname) {
|
|
145
|
+
const result = await runNmcli(this.#runner, [
|
|
146
|
+
'-t',
|
|
147
|
+
'-f',
|
|
148
|
+
'UUID,DEVICE',
|
|
149
|
+
'connection',
|
|
150
|
+
'show',
|
|
151
|
+
'--active',
|
|
152
|
+
]);
|
|
153
|
+
for (const line of result.stdout.split('\n')) {
|
|
154
|
+
const [uuid, device] = line.split(':');
|
|
155
|
+
if (device === ifname && uuid) {
|
|
156
|
+
return uuid;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
async #requireProfile(id) {
|
|
162
|
+
const profile = await this.readGsmProfile(id);
|
|
163
|
+
if (profile === undefined) {
|
|
164
|
+
throw new Error(`nmcli: profile ${id} did not read back`);
|
|
165
|
+
}
|
|
166
|
+
return profile;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function activationReceipt(result, appliedReason) {
|
|
170
|
+
return result.exitCode === 0
|
|
171
|
+
? receipt('enabled', 'applied', appliedReason)
|
|
172
|
+
: receipt('enabled', 'failed', result.stderr || 'nmcli returned a non-zero exit code');
|
|
173
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface NmcliResult {
|
|
2
|
+
readonly stdout: string;
|
|
3
|
+
readonly stderr: string;
|
|
4
|
+
readonly exitCode: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A runner over nmcli argv. `run` may be synchronous (the stateful test double) or
|
|
8
|
+
* asynchronous (the real `Bun.spawn` adapter) — the port awaits either way.
|
|
9
|
+
*/
|
|
10
|
+
export interface NmcliRunner {
|
|
11
|
+
run(argv: readonly string[]): NmcliResult | Promise<NmcliResult>;
|
|
12
|
+
}
|
|
13
|
+
/** Await a runner's result whether it returned synchronously or as a promise. */
|
|
14
|
+
export declare function runNmcli(runner: NmcliRunner, argv: readonly string[]): Promise<NmcliResult>;
|
|
15
|
+
/**
|
|
16
|
+
* The device-exact runner: spawns the real `nmcli` via Bun. Every argv the port
|
|
17
|
+
* builds is passed verbatim, so what the tests assert against the stateful stub is
|
|
18
|
+
* byte-for-byte what runs on the device.
|
|
19
|
+
*/
|
|
20
|
+
export declare class SpawnNmcliRunner implements NmcliRunner {
|
|
21
|
+
run(argv: readonly string[]): Promise<NmcliResult>;
|
|
22
|
+
}
|
|
23
|
+
/** Parse nmcli terse (`-t`) `key:value` lines into a map, splitting on the first `:`. */
|
|
24
|
+
export declare function parseTerse(stdout: string): Map<string, string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
/** Await a runner's result whether it returned synchronously or as a promise. */
|
|
6
|
+
export async function runNmcli(runner, argv) {
|
|
7
|
+
return runner.run(argv);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The device-exact runner: spawns the real `nmcli` via Bun. Every argv the port
|
|
11
|
+
* builds is passed verbatim, so what the tests assert against the stateful stub is
|
|
12
|
+
* byte-for-byte what runs on the device.
|
|
13
|
+
*/
|
|
14
|
+
export class SpawnNmcliRunner {
|
|
15
|
+
async run(argv) {
|
|
16
|
+
const proc = Bun.spawn(['nmcli', ...argv], { stdout: 'pipe', stderr: 'pipe' });
|
|
17
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
18
|
+
new Response(proc.stdout).text(),
|
|
19
|
+
new Response(proc.stderr).text(),
|
|
20
|
+
proc.exited,
|
|
21
|
+
]);
|
|
22
|
+
return { stdout, stderr, exitCode };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Parse nmcli terse (`-t`) `key:value` lines into a map, splitting on the first `:`. */
|
|
26
|
+
export function parseTerse(stdout) {
|
|
27
|
+
const settings = new Map();
|
|
28
|
+
for (const line of stdout.split('\n')) {
|
|
29
|
+
const separator = line.indexOf(':');
|
|
30
|
+
if (separator >= 0) {
|
|
31
|
+
settings.set(line.slice(0, separator), line.slice(separator + 1));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return settings;
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ModemObservationPort, ObservationList, ObservationListener, Unsubscribe } from '../ports/index.js';
|
|
2
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
3
|
+
import { type DecodedManagedObjects } from './managed-objects.js';
|
|
4
|
+
/**
|
|
5
|
+
* A current-epoch authoritative snapshot, delivered to `onEpochRefresh` AFTER the
|
|
6
|
+
* epoch guard passes. `epoch` is the owning MM unique bus name; `tree` is the decoded
|
|
7
|
+
* `GetManagedObjects` payload the snapshot was reconciled from. The Signal.Setup
|
|
8
|
+
* manager (A3.3) hooks this to (re-)apply cadence per modem per epoch, and the D-Bus
|
|
9
|
+
* backend uses it to refresh its path→stable-key map.
|
|
10
|
+
*/
|
|
11
|
+
export interface EpochRefreshEvent {
|
|
12
|
+
readonly epoch: string;
|
|
13
|
+
readonly tree: DecodedManagedObjects;
|
|
14
|
+
}
|
|
15
|
+
export interface MmDbusObserverOptions {
|
|
16
|
+
/** The transport to talk D-Bus over (A2.4). The observer connects it on `start()`. */
|
|
17
|
+
readonly transport: DbusTransport;
|
|
18
|
+
/** MM bus name override (defaults to `org.freedesktop.ModemManager1`). */
|
|
19
|
+
readonly destination?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Called after EVERY successful current-epoch authoritative snapshot (start,
|
|
22
|
+
* hot-plug, epoch change, property change) — never for a superseded epoch. The
|
|
23
|
+
* hook fires whether or not any row changed, so a consumer always sees the live
|
|
24
|
+
* epoch + tree. It must not throw; a throw is swallowed so it can never break the
|
|
25
|
+
* observer's refresh loop.
|
|
26
|
+
*/
|
|
27
|
+
readonly onEpochRefresh?: (event: EpochRefreshEvent) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare class MmDbusObserver implements ModemObservationPort {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(options: MmDbusObserverOptions);
|
|
32
|
+
start(): Promise<ObservationList>;
|
|
33
|
+
observe(listener: ObservationListener): Unsubscribe;
|
|
34
|
+
stop(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** Construct an epoch-scoped ModemManager observer over an A2.4 transport. */
|
|
37
|
+
export declare function createMmDbusObserver(options: MmDbusObserverOptions): MmDbusObserver;
|