@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,116 @@
|
|
|
1
|
+
// The evidence-gated recovery ladder — disabled by default.
|
|
2
|
+
//
|
|
3
|
+
// A bounded four-rung ladder [1 nm-cycle: NM deactivate→reactivate exact pair; 2
|
|
4
|
+
// mm-cycle: MM disable→enable; 3 reset: MM Reset(); 4 power-cycle: power hook (only
|
|
5
|
+
// `none` → always unsupported)]. Every disruptive rung routes through A3.3's shared
|
|
6
|
+
// per-modem `ModemActor` (serialised behind all other disruptive ops) and consults
|
|
7
|
+
// the `LifecycleInterlock` first so it never disrupts a streaming modem. GATES, in
|
|
8
|
+
// order: recovery.enabled=false → zero steps · attribution≠modem-fault → zero steps ·
|
|
9
|
+
// budget spent / cooldown → zero steps · per-rung allowDisruptive · interlock.
|
|
10
|
+
// Disabled or un-attributed, NOT ONE side-effecting call is made.
|
|
11
|
+
import { ALLOW_ALL_INTERLOCK } from './lifecycle-interlock.js';
|
|
12
|
+
import { NONE_POWER_HOOK } from './power-contract.js';
|
|
13
|
+
import { beginAttempt, DEFAULT_RECOVERY_BUDGET, INITIAL_BUDGET_STATE, markRecovered, } from './recovery-budget.js';
|
|
14
|
+
export const LADDER_ORDER = ['nmCycle', 'mmCycle', 'reset', 'powerCycle'];
|
|
15
|
+
const ALLOW = { allowDisruptive: true };
|
|
16
|
+
/** Default config: every rung permitted, default budget. */
|
|
17
|
+
export const DEFAULT_LADDER_CONFIG = {
|
|
18
|
+
nmCycle: ALLOW,
|
|
19
|
+
mmCycle: ALLOW,
|
|
20
|
+
reset: ALLOW,
|
|
21
|
+
powerCycle: ALLOW,
|
|
22
|
+
budget: DEFAULT_RECOVERY_BUDGET,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The recovery ladder. Holds per-modem budget state keyed by stable key so the
|
|
26
|
+
* loop-stop can latch a flapping modem `degraded` across invocations.
|
|
27
|
+
*/
|
|
28
|
+
export class RecoveryLadder {
|
|
29
|
+
#actor;
|
|
30
|
+
#steps;
|
|
31
|
+
#powerHook;
|
|
32
|
+
#interlock;
|
|
33
|
+
#config;
|
|
34
|
+
#states = new Map();
|
|
35
|
+
constructor(deps) {
|
|
36
|
+
this.#actor = deps.actor;
|
|
37
|
+
this.#steps = deps.steps;
|
|
38
|
+
this.#powerHook = deps.powerHook ?? NONE_POWER_HOOK;
|
|
39
|
+
this.#interlock = deps.interlock ?? ALLOW_ALL_INTERLOCK;
|
|
40
|
+
this.#config = deps.config ?? DEFAULT_LADDER_CONFIG;
|
|
41
|
+
}
|
|
42
|
+
/** The current budget state for a modem (for observability / assertions). */
|
|
43
|
+
budgetStateFor(stableKey) {
|
|
44
|
+
return this.#states.get(stableKey) ?? INITIAL_BUDGET_STATE;
|
|
45
|
+
}
|
|
46
|
+
/** Clear a modem's budget / degraded latch (operator un-degrade). */
|
|
47
|
+
clear(stableKey) {
|
|
48
|
+
this.#states.delete(stableKey);
|
|
49
|
+
}
|
|
50
|
+
/** Run one recovery attempt. Enforces every gate before any side effect. */
|
|
51
|
+
async run(recovery, request) {
|
|
52
|
+
const { stableKey, attribution } = request;
|
|
53
|
+
// GATE 1 — master switch. Disabled ⇒ literally zero steps, zero side effects.
|
|
54
|
+
if (!recovery.enabled) {
|
|
55
|
+
return this.#end('disabled', request, [], 'recovery disabled by policy');
|
|
56
|
+
}
|
|
57
|
+
// GATE 2 — attribution. Only a confident modem-fault may ever be disruptive.
|
|
58
|
+
if (attribution !== 'modem-fault') {
|
|
59
|
+
return this.#end('not-attributed', request, [], `attribution '${attribution}' is never disruptive`);
|
|
60
|
+
}
|
|
61
|
+
// GATE 3 — budget / cooldown / loop-stop.
|
|
62
|
+
const decision = beginAttempt(this.budgetStateFor(stableKey), this.#config.budget, request.now);
|
|
63
|
+
if (decision.kind === 'loop-stop') {
|
|
64
|
+
this.#states.set(stableKey, decision.state);
|
|
65
|
+
return this.#end('loop-stop', request, [], 'recovery budget exhausted — modem marked degraded');
|
|
66
|
+
}
|
|
67
|
+
if (decision.kind === 'cooldown') {
|
|
68
|
+
return this.#end('cooldown', request, [], 'within cooldown window — attempt refused');
|
|
69
|
+
}
|
|
70
|
+
this.#states.set(stableKey, decision.state);
|
|
71
|
+
return this.#runRungs(request);
|
|
72
|
+
}
|
|
73
|
+
async #runRungs(request) {
|
|
74
|
+
const { stableKey } = request;
|
|
75
|
+
const context = { stableKey, modem: request.modem, at: request.now };
|
|
76
|
+
const steps = [];
|
|
77
|
+
for (const rung of LADDER_ORDER) {
|
|
78
|
+
if (!this.#config[rung].allowDisruptive) {
|
|
79
|
+
steps.push({ rung, status: 'skipped', reason: 'allowDisruptive is false' });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Interlock BEFORE any disruptive step — never disrupt a streaming modem.
|
|
83
|
+
const verdict = await this.#interlock.canDisrupt({ stableKey });
|
|
84
|
+
if (!verdict.allow) {
|
|
85
|
+
steps.push({ rung, status: 'blocked', reason: verdict.reason });
|
|
86
|
+
return this.#end('interlock-blocked', request, steps, `interlock blocked '${rung}': ${verdict.reason}`);
|
|
87
|
+
}
|
|
88
|
+
const report = await this.#runRung(rung, context);
|
|
89
|
+
steps.push(report);
|
|
90
|
+
if (report.status === 'applied' && (await request.probeHealthy())) {
|
|
91
|
+
this.#states.set(stableKey, markRecovered());
|
|
92
|
+
return this.#end('recovered', request, steps, `recovered at rung '${rung}'`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return this.#end('exhausted', request, steps, 'ladder exhausted without restoring health');
|
|
96
|
+
}
|
|
97
|
+
async #runRung(rung, context) {
|
|
98
|
+
if (rung === 'powerCycle') {
|
|
99
|
+
const result = await this.#powerHook.cycle({ stableKey: context.stableKey, at: context.at });
|
|
100
|
+
return { rung, status: result.status, reason: result.reason };
|
|
101
|
+
}
|
|
102
|
+
// Rungs 1–3 route through the shared per-modem actor for serialisation.
|
|
103
|
+
const step = this.#steps[rung];
|
|
104
|
+
const outcome = await this.#actor.run(context.stableKey, () => step(context));
|
|
105
|
+
return { rung, status: outcome.status, reason: outcome.reason };
|
|
106
|
+
}
|
|
107
|
+
#end(kind, request, steps, reason) {
|
|
108
|
+
return {
|
|
109
|
+
kind,
|
|
110
|
+
attribution: request.attribution,
|
|
111
|
+
steps,
|
|
112
|
+
degraded: this.budgetStateFor(request.stableKey).degraded,
|
|
113
|
+
reason,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EpochMillis } from '../domain/index.js';
|
|
2
|
+
import type { DeviceIfname, RouterPort } from '../ports/index.js';
|
|
3
|
+
/** Injectable probes — each defaults to a `Bun.spawn` shell-out; all advisory. */
|
|
4
|
+
export interface RouterEthernetProbeDeps {
|
|
5
|
+
readonly now?: () => EpochMillis;
|
|
6
|
+
/** Whether `ifname` exists and is administratively up. */
|
|
7
|
+
readonly checkLinkUp?: (ifname: DeviceIfname) => Promise<boolean>;
|
|
8
|
+
/** The DHCP-assigned default gateway on `ifname`, if any. */
|
|
9
|
+
readonly resolveGateway?: (ifname: DeviceIfname) => Promise<string | undefined>;
|
|
10
|
+
/** Whether `host` is reachable via `ifname` (ICMP). */
|
|
11
|
+
readonly ping?: (host: string, ifname: DeviceIfname) => Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a router-ethernet probe implementing the advisory `RouterPort`. Presence is
|
|
15
|
+
* link-up; health additionally reports DHCP-gateway reachability and a basic egress
|
|
16
|
+
* probe — all informational, never a gate. `checkHealth` swallows every error into a
|
|
17
|
+
* `false` field so it can never throw.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createRouterEthernetProbe(deps?: RouterEthernetProbeDeps): RouterPort;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Generic router-ethernet detection — presence, DHCP-gateway reachability, and a
|
|
2
|
+
// basic egress-health probe for uplinks ModemManager cannot control (HiLink, RNDIS
|
|
3
|
+
// tether, full router firmware).
|
|
4
|
+
//
|
|
5
|
+
// EVERYTHING here is ADVISORY (ports/router.ts, matrix §1-R): health degradation is
|
|
6
|
+
// informational only. `checkHealth` NEVER throws and NEVER gates anything — a
|
|
7
|
+
// degraded router stays in the routing set; the controller merely reports what it
|
|
8
|
+
// observed. Each probe is an injectable seam (default `Bun.spawn` of `ip` / `ping`)
|
|
9
|
+
// so tests run with no real network.
|
|
10
|
+
import { epochMillis } from '../domain/index.js';
|
|
11
|
+
/** The host used for the basic egress-health probe (public DNS anycast). */
|
|
12
|
+
const EGRESS_PROBE_HOST = '1.1.1.1';
|
|
13
|
+
async function spawnSucceeds(command) {
|
|
14
|
+
try {
|
|
15
|
+
const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
|
|
16
|
+
return (await proc.exited) === 0;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function spawnOutput(command) {
|
|
23
|
+
try {
|
|
24
|
+
const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
|
|
25
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
26
|
+
return code === 0 ? out : '';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function defaultCheckLinkUp(ifname) {
|
|
33
|
+
return spawnSucceeds(['ip', 'link', 'show', 'up', 'dev', String(ifname)]);
|
|
34
|
+
}
|
|
35
|
+
async function defaultResolveGateway(ifname) {
|
|
36
|
+
// `ip -o route show default dev <ifname>` → "default via 192.168.8.1 dev <ifname> …".
|
|
37
|
+
const out = await spawnOutput(['ip', '-o', 'route', 'show', 'default', 'dev', String(ifname)]);
|
|
38
|
+
const match = out.match(/default via (\S+)/);
|
|
39
|
+
return match?.[1];
|
|
40
|
+
}
|
|
41
|
+
function defaultPing(host, ifname) {
|
|
42
|
+
return spawnSucceeds(['ping', '-c', '1', '-W', '2', '-I', String(ifname), host]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a router-ethernet probe implementing the advisory `RouterPort`. Presence is
|
|
46
|
+
* link-up; health additionally reports DHCP-gateway reachability and a basic egress
|
|
47
|
+
* probe — all informational, never a gate. `checkHealth` swallows every error into a
|
|
48
|
+
* `false` field so it can never throw.
|
|
49
|
+
*/
|
|
50
|
+
export function createRouterEthernetProbe(deps = {}) {
|
|
51
|
+
const now = deps.now ?? (() => epochMillis(Date.now()));
|
|
52
|
+
const checkLinkUp = deps.checkLinkUp ?? defaultCheckLinkUp;
|
|
53
|
+
const resolveGateway = deps.resolveGateway ?? defaultResolveGateway;
|
|
54
|
+
const ping = deps.ping ?? defaultPing;
|
|
55
|
+
async function probePresence(ifname) {
|
|
56
|
+
return (await checkLinkUp(ifname).catch(() => false)) ? 'present' : 'absent';
|
|
57
|
+
}
|
|
58
|
+
async function checkHealth(ifname) {
|
|
59
|
+
const presence = await probePresence(ifname);
|
|
60
|
+
const gateway = presence === 'present' ? await resolveGateway(ifname).catch(() => undefined) : undefined;
|
|
61
|
+
const gatewayReachable = gateway !== undefined && (await ping(gateway, ifname).catch(() => false));
|
|
62
|
+
const egressHealthy = gatewayReachable && (await ping(EGRESS_PROBE_HOST, ifname).catch(() => false));
|
|
63
|
+
return { presence, gatewayReachable, egressHealthy, observedAt: now() };
|
|
64
|
+
}
|
|
65
|
+
return { probePresence, checkHealth };
|
|
66
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ObservationFailureReason, ObservationList } from '../ports/index.js';
|
|
2
|
+
import type { DecodedManagedObjects } from './managed-objects.js';
|
|
3
|
+
export declare class ObservationRowStore {
|
|
4
|
+
#private;
|
|
5
|
+
/** Reconcile the authoritative tree into the rows. Returns whether rows changed.
|
|
6
|
+
* This is the SOLE removal path — an omission from a current-epoch snapshot. */
|
|
7
|
+
reconcile(tree: DecodedManagedObjects): boolean;
|
|
8
|
+
/** Mark the source healthy after a successful reconcile. Returns whether health flipped. */
|
|
9
|
+
markHealthy(): boolean;
|
|
10
|
+
/** Flag every row stale (retained, never removed). Returns whether an emission is due. */
|
|
11
|
+
markUnavailable(reason: ObservationFailureReason): boolean;
|
|
12
|
+
list(): ObservationList;
|
|
13
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The observer's row bookkeeping — revisions, source health, and the discriminated
|
|
2
|
+
// list result — split out of the observer so each file stays focused.
|
|
3
|
+
//
|
|
4
|
+
// Removal lives ONLY in `reconcile`: a modem is dropped exactly when a current-epoch
|
|
5
|
+
// authoritative snapshot omits it. `markUnavailable` never removes — it flags rows
|
|
6
|
+
// stale and retains them, so the `ObservationList` failure arm always carries its rows.
|
|
7
|
+
import { createSnapshot, revision as makeRevision, markSourceUnavailable, } from '../domain/index.js';
|
|
8
|
+
import { fingerprint, mapModem, modemPaths } from './mapping.js';
|
|
9
|
+
export class ObservationRowStore {
|
|
10
|
+
#rows = new Map();
|
|
11
|
+
#revCounter = 0;
|
|
12
|
+
#sourceHealthy = false;
|
|
13
|
+
#failureReason = 'not-started';
|
|
14
|
+
/** Reconcile the authoritative tree into the rows. Returns whether rows changed.
|
|
15
|
+
* This is the SOLE removal path — an omission from a current-epoch snapshot. */
|
|
16
|
+
reconcile(tree) {
|
|
17
|
+
let changed = false;
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
for (const path of modemPaths(tree)) {
|
|
20
|
+
seen.add(path);
|
|
21
|
+
if (this.#upsert(path, mapModem(tree, path))) {
|
|
22
|
+
changed = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const path of [...this.#rows.keys()]) {
|
|
26
|
+
if (!seen.has(path)) {
|
|
27
|
+
this.#rows.delete(path);
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return changed;
|
|
32
|
+
}
|
|
33
|
+
/** Mark the source healthy after a successful reconcile. Returns whether health flipped. */
|
|
34
|
+
markHealthy() {
|
|
35
|
+
const flipped = !this.#sourceHealthy;
|
|
36
|
+
this.#sourceHealthy = true;
|
|
37
|
+
return flipped;
|
|
38
|
+
}
|
|
39
|
+
/** Flag every row stale (retained, never removed). Returns whether an emission is due. */
|
|
40
|
+
markUnavailable(reason) {
|
|
41
|
+
const wasHealthy = this.#sourceHealthy;
|
|
42
|
+
let changed = false;
|
|
43
|
+
for (const [path, row] of this.#rows) {
|
|
44
|
+
if (row.snapshot.sourceHealth === 'sourceUnavailable') {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
this.#rows.set(path, {
|
|
48
|
+
snapshot: markSourceUnavailable(row.snapshot),
|
|
49
|
+
fingerprint: row.fingerprint,
|
|
50
|
+
});
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
this.#sourceHealthy = false;
|
|
54
|
+
this.#failureReason = reason;
|
|
55
|
+
return changed || wasHealthy;
|
|
56
|
+
}
|
|
57
|
+
list() {
|
|
58
|
+
const rows = [...this.#rows.values()].map((row) => row.snapshot);
|
|
59
|
+
if (this.#sourceHealthy) {
|
|
60
|
+
return { ok: true, rows };
|
|
61
|
+
}
|
|
62
|
+
return { ok: false, reason: this.#failureReason, rows };
|
|
63
|
+
}
|
|
64
|
+
#upsert(path, mapped) {
|
|
65
|
+
const fp = fingerprint(mapped);
|
|
66
|
+
const existing = this.#rows.get(path);
|
|
67
|
+
if (existing !== undefined &&
|
|
68
|
+
existing.fingerprint === fp &&
|
|
69
|
+
existing.snapshot.sourceHealth === 'live') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
this.#rows.set(path, {
|
|
73
|
+
snapshot: createSnapshot({ ...mapped, revision: this.#nextRevision() }),
|
|
74
|
+
fingerprint: fp,
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
#nextRevision() {
|
|
79
|
+
this.#revCounter += 1;
|
|
80
|
+
return makeRevision(this.#revCounter);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
2
|
+
import type { DecodedManagedObjects } from './managed-objects.js';
|
|
3
|
+
/** Whether periodic signal reporting is configured for a modem. */
|
|
4
|
+
export type SignalCadence = 'active' | 'unsupported' | 'unknown';
|
|
5
|
+
/** MM's default reporting rate is seconds; callers pass whole seconds. */
|
|
6
|
+
export declare const DEFAULT_SIGNAL_INTERVAL_SECONDS = 5;
|
|
7
|
+
export interface SignalSetupManagerOptions {
|
|
8
|
+
readonly transport: DbusTransport;
|
|
9
|
+
/** MM bus name override (defaults to `org.freedesktop.ModemManager1`). */
|
|
10
|
+
readonly destination?: string;
|
|
11
|
+
/** Reporting interval in seconds passed to `Signal.Setup`. */
|
|
12
|
+
readonly intervalSeconds?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Drives `Signal.Setup` across the modem fleet, keyed to the observer's epochs. Feed
|
|
16
|
+
* it every `onEpochRefresh` event; it applies setup to each modem exactly once per
|
|
17
|
+
* epoch, re-applies to survivors on a new epoch, and never calls for an old one.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SignalSetupManager {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(options: SignalSetupManagerOptions);
|
|
22
|
+
/** The last-known cadence for a modem path (`'unknown'` until first applied). */
|
|
23
|
+
cadenceFor(modemPath: string): SignalCadence;
|
|
24
|
+
/**
|
|
25
|
+
* Apply `Signal.Setup` for the current epoch's modems. New epoch ⇒ every survivor
|
|
26
|
+
* is re-applied; an already-applied (epoch, modem) is skipped. Modems absent from
|
|
27
|
+
* this snapshot keep their last cadence but are not re-driven.
|
|
28
|
+
*/
|
|
29
|
+
applyForEpoch(epoch: string, tree: DecodedManagedObjects): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Signal.Setup(interval) full-lifecycle management, epoch-scoped.
|
|
2
|
+
//
|
|
3
|
+
// ModemManager's `Modem.Signal.Setup(rate)` turns on periodic extended signal
|
|
4
|
+
// reporting. Its lifecycle mirrors the observer's epochs (A3.1):
|
|
5
|
+
//
|
|
6
|
+
// - applied once when the backend starts observing a modem;
|
|
7
|
+
// - applied to a hot-plugged modem the moment it appears (`InterfacesAdded`);
|
|
8
|
+
// - RE-APPLIED to every surviving modem after every owner-epoch change (a fresh MM
|
|
9
|
+
// owner has none of the previous owner's cadence configured);
|
|
10
|
+
// - NEVER applied for an OLD epoch — a setup scheduled for an epoch that is no
|
|
11
|
+
// longer current is dropped before the call ever goes out.
|
|
12
|
+
//
|
|
13
|
+
// A modem that lacks the `Modem.Signal` interface entirely reports
|
|
14
|
+
// `signalCadence: 'unsupported'` and is never called — a soft capability gap, never
|
|
15
|
+
// a start failure. The manager is driven purely by the observer's `onEpochRefresh`
|
|
16
|
+
// hook: every current-epoch snapshot re-drives it, and it de-dupes per (epoch, modem)
|
|
17
|
+
// so a modem is set up exactly once per epoch.
|
|
18
|
+
import { MM_BUS_NAME, MODEM_IFACE } from './constants.js';
|
|
19
|
+
import { hasInterface, pathsWithInterface } from './managed-objects.js';
|
|
20
|
+
/** The `Modem.Signal` interface — absent means signal cadence is unsupported. */
|
|
21
|
+
const SIGNAL_IFACE = 'org.freedesktop.ModemManager1.Modem.Signal';
|
|
22
|
+
/** MM's default reporting rate is seconds; callers pass whole seconds. */
|
|
23
|
+
export const DEFAULT_SIGNAL_INTERVAL_SECONDS = 5;
|
|
24
|
+
/**
|
|
25
|
+
* Drives `Signal.Setup` across the modem fleet, keyed to the observer's epochs. Feed
|
|
26
|
+
* it every `onEpochRefresh` event; it applies setup to each modem exactly once per
|
|
27
|
+
* epoch, re-applies to survivors on a new epoch, and never calls for an old one.
|
|
28
|
+
*/
|
|
29
|
+
export class SignalSetupManager {
|
|
30
|
+
#transport;
|
|
31
|
+
#destination;
|
|
32
|
+
#interval;
|
|
33
|
+
#currentEpoch;
|
|
34
|
+
// Guards double-issue within an epoch: `${epoch}\u0000${modemPath}`.
|
|
35
|
+
#applied = new Set();
|
|
36
|
+
#cadence = new Map();
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.#transport = options.transport;
|
|
39
|
+
this.#destination = options.destination ?? MM_BUS_NAME;
|
|
40
|
+
this.#interval = options.intervalSeconds ?? DEFAULT_SIGNAL_INTERVAL_SECONDS;
|
|
41
|
+
}
|
|
42
|
+
/** The last-known cadence for a modem path (`'unknown'` until first applied). */
|
|
43
|
+
cadenceFor(modemPath) {
|
|
44
|
+
return this.#cadence.get(modemPath) ?? 'unknown';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Apply `Signal.Setup` for the current epoch's modems. New epoch ⇒ every survivor
|
|
48
|
+
* is re-applied; an already-applied (epoch, modem) is skipped. Modems absent from
|
|
49
|
+
* this snapshot keep their last cadence but are not re-driven.
|
|
50
|
+
*/
|
|
51
|
+
applyForEpoch(epoch, tree) {
|
|
52
|
+
this.#currentEpoch = epoch;
|
|
53
|
+
for (const modemPath of pathsWithInterface(tree, MODEM_IFACE)) {
|
|
54
|
+
const key = `${epoch}\u0000${modemPath}`;
|
|
55
|
+
if (this.#applied.has(key)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
this.#applied.add(key);
|
|
59
|
+
void this.#setupOne(epoch, modemPath, tree);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async #setupOne(epoch, modemPath, tree) {
|
|
63
|
+
// Epoch changed out from under us before we could issue the call — drop it, so
|
|
64
|
+
// no Signal.Setup ever fires for a superseded epoch.
|
|
65
|
+
if (this.#currentEpoch !== epoch) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!hasInterface(tree, modemPath, SIGNAL_IFACE)) {
|
|
69
|
+
this.#cadence.set(modemPath, 'unsupported');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (this.#currentEpoch !== epoch) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await this.#transport.callMethod({
|
|
77
|
+
destination: this.#destination,
|
|
78
|
+
path: modemPath,
|
|
79
|
+
interface: SIGNAL_IFACE,
|
|
80
|
+
member: 'Setup',
|
|
81
|
+
signature: 'u',
|
|
82
|
+
args: [this.#interval],
|
|
83
|
+
});
|
|
84
|
+
this.#cadence.set(modemPath, 'active');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A Setup call that errors is a soft gap — surfaced as unsupported, never a
|
|
88
|
+
// start failure.
|
|
89
|
+
this.#cadence.set(modemPath, 'unsupported');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SimPukUnlockResult, SimUnlockResult } from '../ports/index.js';
|
|
2
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Submit a SIM PIN read-before-submit and exactly-once. On a wrong PIN the lock state
|
|
5
|
+
* is re-read (never resubmitted) to report remaining attempts or a resulting PUK lock.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sendSimPin(transport: DbusTransport, destination: string, modemPath: string, pin: string): Promise<SimUnlockResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Submit a SIM PUK + new PIN read-before-submit and exactly-once. On a wrong PUK the
|
|
10
|
+
* remaining PUK attempts are re-read (never resubmitted); zero remaining is a
|
|
11
|
+
* permanent block.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sendSimPuk(transport: DbusTransport, destination: string, modemPath: string, puk: string, newPin: string): Promise<SimPukUnlockResult>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SIM PIN / PUK unlock — read-before-submit, exactly-once, CeraUI-taxonomy-faithful.
|
|
2
|
+
//
|
|
3
|
+
// The ordering is the whole point (mirrors CeraUI `mmcli.ts` unlockSimPin/Puk): the
|
|
4
|
+
// lock state is READ first, and the secret is submitted EXACTLY ONCE. A blind
|
|
5
|
+
// resubmit walks the SIM toward an irreversible PUK / permanent lockout, so on a
|
|
6
|
+
// failure we only RE-READ the lock state to report remaining attempts — we never
|
|
7
|
+
// resubmit. A PUK cannot be cleared with a PIN, so a PUK-locked SIM is surfaced, not
|
|
8
|
+
// submitted into.
|
|
9
|
+
//
|
|
10
|
+
// The result outcomes map onto A2.2's `SimUnlockResult` / `SimPukUnlockResult`, which
|
|
11
|
+
// mirror CeraUI's own shapes so Phase-B adoption is a rename, not a rewrite:
|
|
12
|
+
// success → unlocked · wrong-pin → incorrect-pin(+remaining) · puk-required →
|
|
13
|
+
// sim-puk-required · wrong-puk → incorrect-puk(+remaining) · locked(0) →
|
|
14
|
+
// permanently-blocked · read failure → error · nothing-to-unlock → unlocked.
|
|
15
|
+
// The secret is passed only as a call arg and is NEVER placed in a `reason` string.
|
|
16
|
+
import { MODEM_IFACE, SIM_IFACE } from './constants.js';
|
|
17
|
+
import { fetchManagedObjects, findInterface, numberProp, propValue, stringProp, } from './managed-objects.js';
|
|
18
|
+
// MMModemLock values we branch on.
|
|
19
|
+
const LOCK_SIM_PIN = 2;
|
|
20
|
+
const LOCK_SIM_PUK = 4;
|
|
21
|
+
const LOCK_SIM_PUK2 = 5;
|
|
22
|
+
function readLockState(tree, modemPath) {
|
|
23
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
24
|
+
if (modem === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const required = numberProp(modem, 'UnlockRequired') ?? 0;
|
|
28
|
+
const retries = new Map();
|
|
29
|
+
const raw = propValue(modem, 'UnlockRetries');
|
|
30
|
+
if (Array.isArray(raw)) {
|
|
31
|
+
for (const entry of raw) {
|
|
32
|
+
if (Array.isArray(entry) && typeof entry[0] === 'number' && typeof entry[1] === 'number') {
|
|
33
|
+
retries.set(entry[0], entry[1]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { required, retries };
|
|
38
|
+
}
|
|
39
|
+
/** The object path of a modem's active SIM (its `Sim` property), when present. */
|
|
40
|
+
function activeSimPath(tree, modemPath) {
|
|
41
|
+
const path = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Sim');
|
|
42
|
+
return path?.startsWith('/') && path !== '/' ? path : undefined;
|
|
43
|
+
}
|
|
44
|
+
const isPukLock = (lock) => lock === LOCK_SIM_PUK || lock === LOCK_SIM_PUK2;
|
|
45
|
+
/**
|
|
46
|
+
* Submit a SIM PIN read-before-submit and exactly-once. On a wrong PIN the lock state
|
|
47
|
+
* is re-read (never resubmitted) to report remaining attempts or a resulting PUK lock.
|
|
48
|
+
*/
|
|
49
|
+
export async function sendSimPin(transport, destination, modemPath, pin) {
|
|
50
|
+
let tree;
|
|
51
|
+
try {
|
|
52
|
+
tree = await fetchManagedObjects(transport, destination);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { outcome: 'error', reason: 'could not read modem lock state before PIN submit' };
|
|
56
|
+
}
|
|
57
|
+
const state = readLockState(tree, modemPath);
|
|
58
|
+
if (state === undefined) {
|
|
59
|
+
return { outcome: 'error', reason: 'modem not found while reading lock state' };
|
|
60
|
+
}
|
|
61
|
+
if (isPukLock(state.required)) {
|
|
62
|
+
return { outcome: 'sim-puk-required', reason: 'SIM is PUK-locked; a PIN cannot clear it' };
|
|
63
|
+
}
|
|
64
|
+
if (state.required !== LOCK_SIM_PIN) {
|
|
65
|
+
return { outcome: 'unlocked', reason: 'no SIM PIN is pending' };
|
|
66
|
+
}
|
|
67
|
+
const simPath = activeSimPath(tree, modemPath);
|
|
68
|
+
if (simPath === undefined) {
|
|
69
|
+
return { outcome: 'error', reason: 'no active SIM object to submit the PIN to' };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await transport.callMethod({
|
|
73
|
+
destination,
|
|
74
|
+
path: simPath,
|
|
75
|
+
interface: SIM_IFACE,
|
|
76
|
+
member: 'SendPin',
|
|
77
|
+
signature: 's',
|
|
78
|
+
args: [pin],
|
|
79
|
+
});
|
|
80
|
+
return { outcome: 'unlocked', reason: 'PIN accepted' };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return classifyPinFailure(transport, destination, modemPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function classifyPinFailure(transport, destination, modemPath) {
|
|
87
|
+
const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
|
|
88
|
+
if (state !== undefined && isPukLock(state.required)) {
|
|
89
|
+
return { outcome: 'sim-puk-required', reason: 'wrong PIN tripped the SIM into a PUK lock' };
|
|
90
|
+
}
|
|
91
|
+
const remaining = state?.retries.get(LOCK_SIM_PIN);
|
|
92
|
+
return {
|
|
93
|
+
outcome: 'incorrect-pin',
|
|
94
|
+
...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
|
|
95
|
+
reason: 'PIN was rejected',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Submit a SIM PUK + new PIN read-before-submit and exactly-once. On a wrong PUK the
|
|
100
|
+
* remaining PUK attempts are re-read (never resubmitted); zero remaining is a
|
|
101
|
+
* permanent block.
|
|
102
|
+
*/
|
|
103
|
+
export async function sendSimPuk(transport, destination, modemPath, puk, newPin) {
|
|
104
|
+
let tree;
|
|
105
|
+
try {
|
|
106
|
+
tree = await fetchManagedObjects(transport, destination);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { outcome: 'error', reason: 'could not read modem lock state before PUK submit' };
|
|
110
|
+
}
|
|
111
|
+
const state = readLockState(tree, modemPath);
|
|
112
|
+
if (state === undefined) {
|
|
113
|
+
return { outcome: 'error', reason: 'modem not found while reading lock state' };
|
|
114
|
+
}
|
|
115
|
+
if (!isPukLock(state.required)) {
|
|
116
|
+
return { outcome: 'unlocked', reason: 'no SIM PUK is pending' };
|
|
117
|
+
}
|
|
118
|
+
const pukKind = state.required;
|
|
119
|
+
const simPath = activeSimPath(tree, modemPath);
|
|
120
|
+
if (simPath === undefined) {
|
|
121
|
+
return { outcome: 'error', reason: 'no active SIM object to submit the PUK to' };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
await transport.callMethod({
|
|
125
|
+
destination,
|
|
126
|
+
path: simPath,
|
|
127
|
+
interface: SIM_IFACE,
|
|
128
|
+
member: 'SendPuk',
|
|
129
|
+
signature: 'ss',
|
|
130
|
+
args: [puk, newPin],
|
|
131
|
+
});
|
|
132
|
+
return { outcome: 'unlocked', reason: 'PUK accepted; new PIN set' };
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return classifyPukFailure(transport, destination, modemPath, pukKind);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function classifyPukFailure(transport, destination, modemPath, pukKind) {
|
|
139
|
+
const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
|
|
140
|
+
const remaining = state?.retries.get(pukKind);
|
|
141
|
+
if (remaining === 0) {
|
|
142
|
+
return {
|
|
143
|
+
outcome: 'permanently-blocked',
|
|
144
|
+
remainingAttempts: 0,
|
|
145
|
+
reason: 'PUK attempts exhausted; SIM is permanently locked',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
outcome: 'incorrect-puk',
|
|
150
|
+
...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
|
|
151
|
+
reason: 'PUK was rejected',
|
|
152
|
+
};
|
|
153
|
+
}
|