@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,137 @@
|
|
|
1
|
+
import type { DesiredRadio, ObservationEnvelope, OperationDescriptor, OperationResult, RadioPower } from '../../domain/index.js';
|
|
2
|
+
import type { SimPresence, SimPresenceEvidence } from '../../hardware/router-parsers.js';
|
|
3
|
+
import type { GnssFixState } from '../../location/index.js';
|
|
4
|
+
import type { NormalizedModemObservation } from '../../observations/index.js';
|
|
5
|
+
import type { FixRead, GnssSource, LocationStatusResult, LocationToggleResult, ModemBands, ObservationList, ObservationListener, SmsObservationPort } from '../../ports/index.js';
|
|
6
|
+
import type { BandWriteCertification, ModeSelection, RadioModeTruth } from '../../radio/index.js';
|
|
7
|
+
import type { MmUssd, UssdVerbResult } from '../../ussd/index.js';
|
|
8
|
+
import type { ProviderExecutionContext, ProviderOperationsSurface } from '../contracts.js';
|
|
9
|
+
import type { ModemManagerRefusalReason } from './errors.js';
|
|
10
|
+
import type { RuntimeCompositionOperation } from './runtime-composition-operation.js';
|
|
11
|
+
export type ModemManagerCapabilities = {
|
|
12
|
+
readonly modeRead: boolean;
|
|
13
|
+
readonly modeWrite: boolean;
|
|
14
|
+
readonly bandRead: boolean;
|
|
15
|
+
readonly bandWrite: boolean;
|
|
16
|
+
readonly signalRead: boolean;
|
|
17
|
+
readonly simRead: boolean;
|
|
18
|
+
readonly multiSim: boolean;
|
|
19
|
+
readonly location: boolean;
|
|
20
|
+
readonly sms: boolean;
|
|
21
|
+
readonly ussd: boolean;
|
|
22
|
+
readonly powerRead: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type ModemManagerRadioState = {
|
|
25
|
+
readonly current: {
|
|
26
|
+
readonly allowed: number;
|
|
27
|
+
readonly preferred: number;
|
|
28
|
+
};
|
|
29
|
+
readonly supported: readonly {
|
|
30
|
+
readonly allowed: number;
|
|
31
|
+
readonly preferred: number;
|
|
32
|
+
}[];
|
|
33
|
+
/**
|
|
34
|
+
* The SAME properties decoded without loss — `preferred: 'none'` stays `none`, an
|
|
35
|
+
* unfamiliar mode bit stays offerable as `unknown-combination`, and a member that
|
|
36
|
+
* is not a `(uu)` lands in `undecodable` rather than vanishing. `supported` above
|
|
37
|
+
* is the pre-existing raw-mask view and DROPS a zero-allowed member; this one does
|
|
38
|
+
* not, which is the whole point of carrying both.
|
|
39
|
+
*/
|
|
40
|
+
readonly truth: RadioModeTruth;
|
|
41
|
+
};
|
|
42
|
+
export type ModemManagerSignalState = {
|
|
43
|
+
readonly quality?: number;
|
|
44
|
+
readonly recent?: boolean;
|
|
45
|
+
readonly extendedAvailable: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type ModemManagerSimState = {
|
|
48
|
+
/**
|
|
49
|
+
* The modem exports an ACTIVE SIM object path. `true` is positive evidence of a
|
|
50
|
+
* SIM; `false` is NOT evidence of absence — read `presence` for that.
|
|
51
|
+
*/
|
|
52
|
+
readonly present: boolean;
|
|
53
|
+
/** `absent` only ever comes from `StateFailedReason: sim-missing`. */
|
|
54
|
+
readonly presence: SimPresence;
|
|
55
|
+
readonly presenceEvidence: SimPresenceEvidence;
|
|
56
|
+
readonly slotCount: number;
|
|
57
|
+
readonly primarySlot: number;
|
|
58
|
+
readonly lockRequired?: number;
|
|
59
|
+
readonly simType?: number;
|
|
60
|
+
readonly esimStatus?: number;
|
|
61
|
+
};
|
|
62
|
+
export type ModemManagerProviderSnapshot = {
|
|
63
|
+
readonly modemPath: string;
|
|
64
|
+
readonly capabilities: ModemManagerCapabilities;
|
|
65
|
+
readonly radio: ModemManagerRadioState;
|
|
66
|
+
/** The band-lock gate for THIS device, resolved from the shipped certification catalog. */
|
|
67
|
+
readonly bandCertification: BandWriteCertification;
|
|
68
|
+
readonly signal: ModemManagerSignalState;
|
|
69
|
+
readonly sim: ModemManagerSimState;
|
|
70
|
+
readonly power: RadioPower;
|
|
71
|
+
readonly observation: ObservationEnvelope<NormalizedModemObservation>;
|
|
72
|
+
};
|
|
73
|
+
export type ModemManagerSnapshotResult = ({
|
|
74
|
+
readonly ok: true;
|
|
75
|
+
} & ModemManagerProviderSnapshot) | {
|
|
76
|
+
readonly ok: false;
|
|
77
|
+
readonly reason: ModemManagerRefusalReason;
|
|
78
|
+
};
|
|
79
|
+
export interface ContextReadOperation<O> {
|
|
80
|
+
readonly descriptor: OperationDescriptor<never, O>;
|
|
81
|
+
read(context: ProviderExecutionContext): Promise<OperationResult<O>>;
|
|
82
|
+
}
|
|
83
|
+
export interface ContextWriteOperation<I, O> {
|
|
84
|
+
/** The shape-only descriptor: what this operation is, before any device answered. */
|
|
85
|
+
readonly descriptor: OperationDescriptor<I, O>;
|
|
86
|
+
/**
|
|
87
|
+
* The descriptor as the LIVE device entitles it.
|
|
88
|
+
*
|
|
89
|
+
* A static descriptor cannot carry a device's own catalog, so it cannot say which
|
|
90
|
+
* mode combinations this modem advertises or whether this SKU's band lock is
|
|
91
|
+
* certified. Those are exactly the facts a consumer needs before it offers a
|
|
92
|
+
* control, so they are read here instead of being inferred from a capability flag.
|
|
93
|
+
*/
|
|
94
|
+
describe(context: ProviderExecutionContext): Promise<OperationDescriptor<I, O>>;
|
|
95
|
+
read(context: ProviderExecutionContext): Promise<OperationResult<O>>;
|
|
96
|
+
write(context: ProviderExecutionContext, input: I): Promise<OperationResult<O>>;
|
|
97
|
+
}
|
|
98
|
+
export type SmsPortResult = {
|
|
99
|
+
readonly ok: true;
|
|
100
|
+
readonly port: SmsObservationPort;
|
|
101
|
+
} | {
|
|
102
|
+
readonly ok: false;
|
|
103
|
+
readonly reason: ModemManagerRefusalReason;
|
|
104
|
+
};
|
|
105
|
+
export interface ModemManagerProviderOperations extends ProviderOperationsSurface {
|
|
106
|
+
readonly radio: ContextWriteOperation<DesiredRadio, ModemManagerRadioState>;
|
|
107
|
+
/**
|
|
108
|
+
* The modem's OWN mode vocabulary — an advertised `(allowed, preferred)` pair,
|
|
109
|
+
* selected verbatim. Separate from `radio` because `DesiredRadio` is an ordered RAT
|
|
110
|
+
* preference and cannot express `preferred: none`, which real hardware advertises.
|
|
111
|
+
*/
|
|
112
|
+
readonly modes: ContextWriteOperation<ModeSelection, RadioModeTruth>;
|
|
113
|
+
readonly bands: ContextWriteOperation<readonly string[], ModemBands>;
|
|
114
|
+
readonly signal: ContextReadOperation<ModemManagerSignalState>;
|
|
115
|
+
readonly sim: ContextReadOperation<ModemManagerSimState>;
|
|
116
|
+
readonly power: ContextReadOperation<RadioPower>;
|
|
117
|
+
readonly usbComposition: RuntimeCompositionOperation;
|
|
118
|
+
readonly location: {
|
|
119
|
+
status(context: ProviderExecutionContext): Promise<LocationStatusResult>;
|
|
120
|
+
enable(context: ProviderExecutionContext, sources: readonly GnssSource[]): Promise<LocationToggleResult>;
|
|
121
|
+
disable(context: ProviderExecutionContext): Promise<LocationToggleResult>;
|
|
122
|
+
readFix(context: ProviderExecutionContext): Promise<FixRead>;
|
|
123
|
+
state(context: ProviderExecutionContext): GnssFixState;
|
|
124
|
+
tick(context: ProviderExecutionContext): GnssFixState;
|
|
125
|
+
};
|
|
126
|
+
sms(context: ProviderExecutionContext): Promise<SmsPortResult>;
|
|
127
|
+
readonly ussd: MmUssd;
|
|
128
|
+
initiateUssd(context: ProviderExecutionContext, ussdCommand: string): Promise<UssdVerbResult>;
|
|
129
|
+
respondUssd(context: ProviderExecutionContext, ussdResponse: string): Promise<UssdVerbResult>;
|
|
130
|
+
cancelUssd(context: ProviderExecutionContext): Promise<UssdVerbResult>;
|
|
131
|
+
readonly fccCoverage: (vid: string | undefined, pid: string | undefined) => 'present' | 'absent' | 'unknown';
|
|
132
|
+
}
|
|
133
|
+
export interface ModemManagerProviderLifecycle {
|
|
134
|
+
start(): Promise<ObservationList>;
|
|
135
|
+
observe(listener: ObservationListener): () => void;
|
|
136
|
+
stop(): Promise<void>;
|
|
137
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { DeviceGeneration } from '../../domain/index.js';
|
|
2
|
+
import type { AppliedConfiguration, DesiredProfile, ModemStateView, NormalizationContext, ObservedState, StateDivergence } from '../../observations/index.js';
|
|
3
|
+
import type { ConnectionId, DeviceIfname, GsmProfile, NetworkManagerPort } from '../../ports/index.js';
|
|
4
|
+
import type { NmApplyResult, NmBearerState, NmDesiredRequest, NmObservationInput, NmObservationResult, NmSaveResult } from './types.js';
|
|
5
|
+
export interface NetworkManagerAdapterOptions {
|
|
6
|
+
readonly port: NetworkManagerPort;
|
|
7
|
+
/** Clock for the desired/applied slots only; observations carry their own. */
|
|
8
|
+
readonly now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export interface NmApplyOptions {
|
|
11
|
+
readonly operationId: string;
|
|
12
|
+
readonly generation: DeviceGeneration;
|
|
13
|
+
}
|
|
14
|
+
export declare class NetworkManagerAdapter {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(options: NetworkManagerAdapterOptions);
|
|
17
|
+
/** Every connection this adapter holds a slot for, in insertion order. */
|
|
18
|
+
trackedConnections(): readonly ConnectionId[];
|
|
19
|
+
/**
|
|
20
|
+
* Save an operator's profile and record it as DESIRED.
|
|
21
|
+
*
|
|
22
|
+
* The desired slot is built from what the caller ASKED for, not from what NM read
|
|
23
|
+
* back. That is the whole point of the slot: if NM normalized a field on the way
|
|
24
|
+
* in, the next `describeStateDivergence` is supposed to report it, and seeding
|
|
25
|
+
* desired from the readback would make that divergence structurally invisible.
|
|
26
|
+
*/
|
|
27
|
+
saveDesiredProfile(request: NmDesiredRequest): Promise<NmSaveResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Record that the operator wants NO bearer on this connection's device.
|
|
30
|
+
*
|
|
31
|
+
* It performs no I/O on purpose — desired is a record of intent, and the
|
|
32
|
+
* deactivation it implies happens when `applyDesired` is called, so a request and
|
|
33
|
+
* its execution stay two separately observable facts.
|
|
34
|
+
*/
|
|
35
|
+
releaseDesired(id: ConnectionId, requestedBy: string): NmSaveResult;
|
|
36
|
+
desiredFor(id: ConnectionId): DesiredProfile<NmBearerState> | null;
|
|
37
|
+
/** The saved profile as NM itself holds it — a straight read, no slot involved. */
|
|
38
|
+
readSavedProfile(id: ConnectionId): Promise<GsmProfile | undefined>;
|
|
39
|
+
/**
|
|
40
|
+
* Put the desired state into force and record what was applied.
|
|
41
|
+
*
|
|
42
|
+
* The applied slot is built from the profile NM READ BACK, not from the request:
|
|
43
|
+
* "what was actually put into force" is a claim about NM's content, and asserting
|
|
44
|
+
* it from the input would make a silently-rejected field look applied.
|
|
45
|
+
*/
|
|
46
|
+
applyDesired(id: ConnectionId, options: NmApplyOptions): Promise<NmApplyResult>;
|
|
47
|
+
appliedFor(id: ConnectionId): AppliedConfiguration<NmBearerState> | null;
|
|
48
|
+
/**
|
|
49
|
+
* The interface the applied bearer landed on, or `null` when no bearer is in
|
|
50
|
+
* force. An applied `unbound` state resolves to `null` rather than to its device:
|
|
51
|
+
* there is a device, but no bearer on it, and returning the name would read as one.
|
|
52
|
+
*/
|
|
53
|
+
resolveAppliedInterface(id: ConnectionId): DeviceIfname | null;
|
|
54
|
+
/**
|
|
55
|
+
* Fold one complete NM readout.
|
|
56
|
+
*
|
|
57
|
+
* Never touches the desired slot. Clears the applied slot only when the readout
|
|
58
|
+
* positively contradicts it, and always says which of the four contradictions it
|
|
59
|
+
* was.
|
|
60
|
+
*/
|
|
61
|
+
observe(input: NmObservationInput): NmObservationResult;
|
|
62
|
+
observedFor(id: ConnectionId): ObservedState<NmBearerState> | null;
|
|
63
|
+
/**
|
|
64
|
+
* The three-slot view. `context` supplies the provenance for the "we have not
|
|
65
|
+
* observed this yet" case — this layer has no clock or epoch counter of its own
|
|
66
|
+
* for observations, so it cannot manufacture one.
|
|
67
|
+
*/
|
|
68
|
+
stateView(id: ConnectionId, context: NormalizationContext): ModemStateView<NmBearerState, NmBearerState, NmBearerState> | null;
|
|
69
|
+
/** `desiredVsApplied` ("did our write happen") and `appliedVsObserved` ("did it stick"). */
|
|
70
|
+
divergence(id: ConnectionId, context: NormalizationContext): StateDivergence | null;
|
|
71
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// The thin NetworkManager adapter: desired profiles, applied bearers, and the
|
|
2
|
+
// interface each one landed on — the saved-vs-applied boundary and nothing else.
|
|
3
|
+
//
|
|
4
|
+
// It is the ONLY bearer/APN authority surface in the package. `ModemManagerPort` has
|
|
5
|
+
// no connect verb by construction (`ports/forbidden-surface.test.ts`), and the
|
|
6
|
+
// ModemManager provider has no bearer operation, so nothing else can put a bearer into
|
|
7
|
+
// force. Correspondingly, this adapter performs no radio, band, SIM or power operation:
|
|
8
|
+
// those belong to the ModemManager provider, and expressing them here would create a
|
|
9
|
+
// second writer for a resource the ownership matrix gives to exactly one.
|
|
10
|
+
//
|
|
11
|
+
// Two rules shape every method below.
|
|
12
|
+
//
|
|
13
|
+
// 1. OBSERVED STATE NEVER WRITES THE DESIRED SLOT. `observe()` may clear the applied
|
|
14
|
+
// slot and always rewrites the observed one; it does not touch `desired` on any
|
|
15
|
+
// path. Reality overtaking a write does not un-ask the question the operator asked
|
|
16
|
+
// — and if it did, a re-enumeration would silently erase the configuration the
|
|
17
|
+
// controller is supposed to restore.
|
|
18
|
+
// 2. WRITES GO THROUGH THE PORT'S TYPED OPERATIONS, AND ONLY THOSE. The NM half of the
|
|
19
|
+
// port-tagged op set (`ports/ops.ts` `NmOp`) is create / update / activate /
|
|
20
|
+
// deactivate. There is deliberately no delete path here: profile removal is not in
|
|
21
|
+
// that set, so this adapter cannot express it.
|
|
22
|
+
import { epochMillis } from '../../domain/index.js';
|
|
23
|
+
import { appliedConfiguration, describeStateDivergence, desiredProfile, freshObservation, observedState, unavailableObservation, } from '../../observations/index.js';
|
|
24
|
+
import { receipt } from '../../ports/index.js';
|
|
25
|
+
import { boundBearer, nmBearerStateEquals, unboundBearer } from './types.js';
|
|
26
|
+
const SOURCE = 'networkmanager';
|
|
27
|
+
/** The device states in which NM is still settling an activation, not losing it. */
|
|
28
|
+
const TRANSITIONAL_STATES = new Set([
|
|
29
|
+
'prepare',
|
|
30
|
+
'config',
|
|
31
|
+
'need-auth',
|
|
32
|
+
'ip-config',
|
|
33
|
+
'ip-check',
|
|
34
|
+
'secondaries',
|
|
35
|
+
'deactivating',
|
|
36
|
+
]);
|
|
37
|
+
export class NetworkManagerAdapter {
|
|
38
|
+
#port;
|
|
39
|
+
#now;
|
|
40
|
+
#slots = new Map();
|
|
41
|
+
#observedGeneration = null;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.#port = options.port;
|
|
44
|
+
this.#now = options.now ?? Date.now;
|
|
45
|
+
}
|
|
46
|
+
/** Every connection this adapter holds a slot for, in insertion order. */
|
|
47
|
+
trackedConnections() {
|
|
48
|
+
return [...this.#slots.keys()];
|
|
49
|
+
}
|
|
50
|
+
// ── desired ────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Save an operator's profile and record it as DESIRED.
|
|
53
|
+
*
|
|
54
|
+
* The desired slot is built from what the caller ASKED for, not from what NM read
|
|
55
|
+
* back. That is the whole point of the slot: if NM normalized a field on the way
|
|
56
|
+
* in, the next `describeStateDivergence` is supposed to report it, and seeding
|
|
57
|
+
* desired from the readback would make that divergence structurally invisible.
|
|
58
|
+
*/
|
|
59
|
+
async saveDesiredProfile(request) {
|
|
60
|
+
let saved;
|
|
61
|
+
try {
|
|
62
|
+
saved =
|
|
63
|
+
request.connectionId === undefined
|
|
64
|
+
? await this.#port.createGsmProfile(request.profile)
|
|
65
|
+
: await this.#port.updateGsmProfile(request.connectionId, request.profile);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
reason: 'write-failed',
|
|
71
|
+
receipt: receipt('connection', 'failed', describeError(error)),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const id = saved.connectionId;
|
|
75
|
+
const desired = desiredProfile(boundBearer({
|
|
76
|
+
connectionId: id,
|
|
77
|
+
deviceIfname: request.deviceIfname,
|
|
78
|
+
apn: request.profile.apn,
|
|
79
|
+
autoConfig: request.profile.autoConfig,
|
|
80
|
+
homeOnly: request.profile.homeOnly,
|
|
81
|
+
}), epochMillis(this.#now()), request.requestedBy);
|
|
82
|
+
this.#slotsFor(id).desired = desired;
|
|
83
|
+
return { ok: true, connectionId: id, desired };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Record that the operator wants NO bearer on this connection's device.
|
|
87
|
+
*
|
|
88
|
+
* It performs no I/O on purpose — desired is a record of intent, and the
|
|
89
|
+
* deactivation it implies happens when `applyDesired` is called, so a request and
|
|
90
|
+
* its execution stay two separately observable facts.
|
|
91
|
+
*/
|
|
92
|
+
releaseDesired(id, requestedBy) {
|
|
93
|
+
const slots = this.#slots.get(id);
|
|
94
|
+
const ifname = slots === undefined ? undefined : targetIfname(slots);
|
|
95
|
+
if (ifname === undefined) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: 'no-desired-profile',
|
|
99
|
+
receipt: receipt('connection', 'failed', `no tracked device for connection ${id}`),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const desired = desiredProfile(unboundBearer(ifname), epochMillis(this.#now()), requestedBy);
|
|
103
|
+
this.#slotsFor(id).desired = desired;
|
|
104
|
+
return { ok: true, connectionId: id, desired };
|
|
105
|
+
}
|
|
106
|
+
desiredFor(id) {
|
|
107
|
+
return this.#slots.get(id)?.desired ?? null;
|
|
108
|
+
}
|
|
109
|
+
/** The saved profile as NM itself holds it — a straight read, no slot involved. */
|
|
110
|
+
async readSavedProfile(id) {
|
|
111
|
+
return await this.#port.readGsmProfile(id);
|
|
112
|
+
}
|
|
113
|
+
// ── applied ────────────────────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Put the desired state into force and record what was applied.
|
|
116
|
+
*
|
|
117
|
+
* The applied slot is built from the profile NM READ BACK, not from the request:
|
|
118
|
+
* "what was actually put into force" is a claim about NM's content, and asserting
|
|
119
|
+
* it from the input would make a silently-rejected field look applied.
|
|
120
|
+
*/
|
|
121
|
+
async applyDesired(id, options) {
|
|
122
|
+
const desired = this.desiredFor(id);
|
|
123
|
+
if (desired === null) {
|
|
124
|
+
return refuse('no-desired-profile', `connection ${id} has no desired profile`);
|
|
125
|
+
}
|
|
126
|
+
return desired.profile.kind === 'unbound'
|
|
127
|
+
? await this.#deactivate(id, desired.profile.deviceIfname, options)
|
|
128
|
+
: await this.#activate(id, desired.profile, options);
|
|
129
|
+
}
|
|
130
|
+
appliedFor(id) {
|
|
131
|
+
return this.#slots.get(id)?.applied ?? null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The interface the applied bearer landed on, or `null` when no bearer is in
|
|
135
|
+
* force. An applied `unbound` state resolves to `null` rather than to its device:
|
|
136
|
+
* there is a device, but no bearer on it, and returning the name would read as one.
|
|
137
|
+
*/
|
|
138
|
+
resolveAppliedInterface(id) {
|
|
139
|
+
const applied = this.appliedFor(id);
|
|
140
|
+
if (applied === null || applied.configuration.kind !== 'bound') {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return applied.configuration.binding.deviceIfname;
|
|
144
|
+
}
|
|
145
|
+
// ── observed ───────────────────────────────────────────────────────────────
|
|
146
|
+
/**
|
|
147
|
+
* Fold one complete NM readout.
|
|
148
|
+
*
|
|
149
|
+
* Never touches the desired slot. Clears the applied slot only when the readout
|
|
150
|
+
* positively contradicts it, and always says which of the four contradictions it
|
|
151
|
+
* was.
|
|
152
|
+
*/
|
|
153
|
+
observe(input) {
|
|
154
|
+
const generation = input.context.generation;
|
|
155
|
+
if (this.#observedGeneration !== null && generation < this.#observedGeneration) {
|
|
156
|
+
return {
|
|
157
|
+
kind: 'refused',
|
|
158
|
+
reason: 'superseded-generation',
|
|
159
|
+
currentGeneration: this.#observedGeneration,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
this.#observedGeneration = generation;
|
|
163
|
+
const devices = new Map(input.devices.map((device) => [device.ifname, device]));
|
|
164
|
+
const outcomes = [];
|
|
165
|
+
const losses = [];
|
|
166
|
+
for (const [id, slots] of this.#slots) {
|
|
167
|
+
const ifname = targetIfname(slots);
|
|
168
|
+
if (ifname === undefined) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const device = devices.get(ifname);
|
|
172
|
+
slots.observed = observedState(device === undefined
|
|
173
|
+
? unavailableObservation(SOURCE, input.context, 'device-absent')
|
|
174
|
+
: freshObservation(SOURCE, input.context, observedBearer(device)));
|
|
175
|
+
const outcome = this.#classify(id, slots, ifname, device, input.context.observedAt);
|
|
176
|
+
outcomes.push({ connectionId: id, outcome });
|
|
177
|
+
if (outcome.status === 'lost') {
|
|
178
|
+
losses.push(outcome.loss);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
kind: 'accepted',
|
|
183
|
+
generation,
|
|
184
|
+
observedAt: input.context.observedAt,
|
|
185
|
+
outcomes,
|
|
186
|
+
losses,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
observedFor(id) {
|
|
190
|
+
return this.#slots.get(id)?.observed ?? null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The three-slot view. `context` supplies the provenance for the "we have not
|
|
194
|
+
* observed this yet" case — this layer has no clock or epoch counter of its own
|
|
195
|
+
* for observations, so it cannot manufacture one.
|
|
196
|
+
*/
|
|
197
|
+
stateView(id, context) {
|
|
198
|
+
const slots = this.#slots.get(id);
|
|
199
|
+
if (slots === undefined) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
desired: slots.desired,
|
|
204
|
+
applied: slots.applied,
|
|
205
|
+
observed: slots.observed ??
|
|
206
|
+
observedState(unavailableObservation(SOURCE, context, 'provider-unavailable')),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** `desiredVsApplied` ("did our write happen") and `appliedVsObserved` ("did it stick"). */
|
|
210
|
+
divergence(id, context) {
|
|
211
|
+
const view = this.stateView(id, context);
|
|
212
|
+
return view === null ? null : describeStateDivergence(view, nmBearerStateEquals);
|
|
213
|
+
}
|
|
214
|
+
// ── internals ──────────────────────────────────────────────────────────────
|
|
215
|
+
async #activate(id, desired, options) {
|
|
216
|
+
if (desired.kind !== 'bound') {
|
|
217
|
+
return refuse('no-desired-profile', `connection ${id} has no bearer to activate`);
|
|
218
|
+
}
|
|
219
|
+
const ifname = desired.binding.deviceIfname;
|
|
220
|
+
const saved = await this.#port.readGsmProfile(id);
|
|
221
|
+
if (saved === undefined) {
|
|
222
|
+
return refuse('profile-absent', `connection ${id} is no longer saved in NetworkManager`);
|
|
223
|
+
}
|
|
224
|
+
let activation;
|
|
225
|
+
try {
|
|
226
|
+
activation = await this.#port.activate(id, ifname);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return refuse('activation-failed', describeError(error));
|
|
230
|
+
}
|
|
231
|
+
if (activation.status !== 'applied') {
|
|
232
|
+
return { ok: false, reason: 'activation-failed', receipt: activation };
|
|
233
|
+
}
|
|
234
|
+
const applied = appliedConfiguration({
|
|
235
|
+
configuration: boundBearer({
|
|
236
|
+
connectionId: id,
|
|
237
|
+
deviceIfname: ifname,
|
|
238
|
+
apn: saved.apn,
|
|
239
|
+
autoConfig: saved.autoConfig,
|
|
240
|
+
homeOnly: saved.homeOnly,
|
|
241
|
+
}),
|
|
242
|
+
appliedAt: epochMillis(this.#now()),
|
|
243
|
+
generation: options.generation,
|
|
244
|
+
operationId: options.operationId,
|
|
245
|
+
});
|
|
246
|
+
this.#slotsFor(id).applied = applied;
|
|
247
|
+
return { ok: true, applied, receipt: activation };
|
|
248
|
+
}
|
|
249
|
+
async #deactivate(id, ifname, options) {
|
|
250
|
+
let result;
|
|
251
|
+
try {
|
|
252
|
+
result = await this.#port.deactivate(id, ifname);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return refuse('deactivation-failed', describeError(error));
|
|
256
|
+
}
|
|
257
|
+
if (result.status !== 'applied') {
|
|
258
|
+
return { ok: false, reason: 'deactivation-failed', receipt: result };
|
|
259
|
+
}
|
|
260
|
+
const applied = appliedConfiguration({
|
|
261
|
+
configuration: unboundBearer(ifname),
|
|
262
|
+
appliedAt: epochMillis(this.#now()),
|
|
263
|
+
generation: options.generation,
|
|
264
|
+
operationId: options.operationId,
|
|
265
|
+
});
|
|
266
|
+
this.#slotsFor(id).applied = applied;
|
|
267
|
+
return { ok: true, applied, receipt: result };
|
|
268
|
+
}
|
|
269
|
+
#classify(id, slots, ifname, device, observedAt) {
|
|
270
|
+
const applied = slots.applied;
|
|
271
|
+
if (applied === null) {
|
|
272
|
+
return { status: 'unapplied' };
|
|
273
|
+
}
|
|
274
|
+
const lose = (reason) => {
|
|
275
|
+
slots.applied = null;
|
|
276
|
+
return {
|
|
277
|
+
status: 'lost',
|
|
278
|
+
loss: {
|
|
279
|
+
connectionId: id,
|
|
280
|
+
deviceIfname: ifname,
|
|
281
|
+
reason,
|
|
282
|
+
lostAt: observedAt,
|
|
283
|
+
generation: applied.generation,
|
|
284
|
+
previous: applied,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
if (device === undefined) {
|
|
289
|
+
return lose('interface-absent');
|
|
290
|
+
}
|
|
291
|
+
if (device.state === 'failed') {
|
|
292
|
+
return lose('activation-failed');
|
|
293
|
+
}
|
|
294
|
+
if (applied.configuration.kind === 'unbound') {
|
|
295
|
+
// We deliberately took the bearer down; anything active here is somebody else.
|
|
296
|
+
return device.activeConnection === undefined
|
|
297
|
+
? { status: 'retained', applied }
|
|
298
|
+
: lose('connection-replaced');
|
|
299
|
+
}
|
|
300
|
+
if (device.activeConnection === undefined) {
|
|
301
|
+
return lose('interface-detached');
|
|
302
|
+
}
|
|
303
|
+
if (device.activeConnection.connectionId !== id) {
|
|
304
|
+
return lose('connection-replaced');
|
|
305
|
+
}
|
|
306
|
+
if (device.state === 'activated') {
|
|
307
|
+
return { status: 'retained', applied };
|
|
308
|
+
}
|
|
309
|
+
return TRANSITIONAL_STATES.has(device.state)
|
|
310
|
+
? { status: 'pending', applied, deviceState: device.state }
|
|
311
|
+
: lose('interface-detached');
|
|
312
|
+
}
|
|
313
|
+
#slotsFor(id) {
|
|
314
|
+
const existing = this.#slots.get(id);
|
|
315
|
+
if (existing !== undefined) {
|
|
316
|
+
return existing;
|
|
317
|
+
}
|
|
318
|
+
const created = { desired: null, applied: null, observed: null };
|
|
319
|
+
this.#slots.set(id, created);
|
|
320
|
+
return created;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** The device the slots are about: where the bearer IS, else where it was asked for. */
|
|
324
|
+
function targetIfname(slots) {
|
|
325
|
+
const state = slots.applied?.configuration ?? slots.desired?.profile;
|
|
326
|
+
if (state === undefined) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
return state.kind === 'bound' ? state.binding.deviceIfname : state.deviceIfname;
|
|
330
|
+
}
|
|
331
|
+
function observedBearer(device) {
|
|
332
|
+
const active = device.activeConnection;
|
|
333
|
+
return active === undefined
|
|
334
|
+
? unboundBearer(device.ifname)
|
|
335
|
+
: boundBearer({
|
|
336
|
+
connectionId: active.connectionId,
|
|
337
|
+
deviceIfname: device.ifname,
|
|
338
|
+
apn: active.apn,
|
|
339
|
+
autoConfig: active.autoConfig,
|
|
340
|
+
homeOnly: active.homeOnly,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function refuse(reason, message) {
|
|
344
|
+
return { ok: false, reason, receipt: receipt('connection', 'failed', message) };
|
|
345
|
+
}
|
|
346
|
+
function describeError(error) {
|
|
347
|
+
return error instanceof Error ? error.message : 'NetworkManager rejected the request';
|
|
348
|
+
}
|