@ceralive/modem-control 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -1,90 +0,0 @@
|
|
|
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
|
-
|
|
11
|
-
import { type EpochMillis, epochMillis } from '../domain';
|
|
12
|
-
import type { DeviceIfname, RouterHealth, RouterPort, RouterPresence } from '../ports';
|
|
13
|
-
|
|
14
|
-
/** Injectable probes — each defaults to a `Bun.spawn` shell-out; all advisory. */
|
|
15
|
-
export interface RouterEthernetProbeDeps {
|
|
16
|
-
readonly now?: () => EpochMillis;
|
|
17
|
-
/** Whether `ifname` exists and is administratively up. */
|
|
18
|
-
readonly checkLinkUp?: (ifname: DeviceIfname) => Promise<boolean>;
|
|
19
|
-
/** The DHCP-assigned default gateway on `ifname`, if any. */
|
|
20
|
-
readonly resolveGateway?: (ifname: DeviceIfname) => Promise<string | undefined>;
|
|
21
|
-
/** Whether `host` is reachable via `ifname` (ICMP). */
|
|
22
|
-
readonly ping?: (host: string, ifname: DeviceIfname) => Promise<boolean>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** The host used for the basic egress-health probe (public DNS anycast). */
|
|
26
|
-
const EGRESS_PROBE_HOST = '1.1.1.1';
|
|
27
|
-
|
|
28
|
-
async function spawnSucceeds(command: readonly string[]): Promise<boolean> {
|
|
29
|
-
try {
|
|
30
|
-
const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
|
|
31
|
-
return (await proc.exited) === 0;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function spawnOutput(command: readonly string[]): Promise<string> {
|
|
38
|
-
try {
|
|
39
|
-
const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
|
|
40
|
-
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
41
|
-
return code === 0 ? out : '';
|
|
42
|
-
} catch {
|
|
43
|
-
return '';
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function defaultCheckLinkUp(ifname: DeviceIfname): Promise<boolean> {
|
|
48
|
-
return spawnSucceeds(['ip', 'link', 'show', 'up', 'dev', String(ifname)]);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function defaultResolveGateway(ifname: DeviceIfname): Promise<string | undefined> {
|
|
52
|
-
// `ip -o route show default dev <ifname>` → "default via 192.168.8.1 dev <ifname> …".
|
|
53
|
-
const out = await spawnOutput(['ip', '-o', 'route', 'show', 'default', 'dev', String(ifname)]);
|
|
54
|
-
const match = out.match(/default via (\S+)/);
|
|
55
|
-
return match?.[1];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function defaultPing(host: string, ifname: DeviceIfname): Promise<boolean> {
|
|
59
|
-
return spawnSucceeds(['ping', '-c', '1', '-W', '2', '-I', String(ifname), host]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Create a router-ethernet probe implementing the advisory `RouterPort`. Presence is
|
|
64
|
-
* link-up; health additionally reports DHCP-gateway reachability and a basic egress
|
|
65
|
-
* probe — all informational, never a gate. `checkHealth` swallows every error into a
|
|
66
|
-
* `false` field so it can never throw.
|
|
67
|
-
*/
|
|
68
|
-
export function createRouterEthernetProbe(deps: RouterEthernetProbeDeps = {}): RouterPort {
|
|
69
|
-
const now = deps.now ?? ((): EpochMillis => epochMillis(Date.now()));
|
|
70
|
-
const checkLinkUp = deps.checkLinkUp ?? defaultCheckLinkUp;
|
|
71
|
-
const resolveGateway = deps.resolveGateway ?? defaultResolveGateway;
|
|
72
|
-
const ping = deps.ping ?? defaultPing;
|
|
73
|
-
|
|
74
|
-
async function probePresence(ifname: DeviceIfname): Promise<RouterPresence> {
|
|
75
|
-
return (await checkLinkUp(ifname).catch(() => false)) ? 'present' : 'absent';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function checkHealth(ifname: DeviceIfname): Promise<RouterHealth> {
|
|
79
|
-
const presence = await probePresence(ifname);
|
|
80
|
-
const gateway =
|
|
81
|
-
presence === 'present' ? await resolveGateway(ifname).catch(() => undefined) : undefined;
|
|
82
|
-
const gatewayReachable =
|
|
83
|
-
gateway !== undefined && (await ping(gateway, ifname).catch(() => false));
|
|
84
|
-
const egressHealthy =
|
|
85
|
-
gatewayReachable && (await ping(EGRESS_PROBE_HOST, ifname).catch(() => false));
|
|
86
|
-
return { presence, gatewayReachable, egressHealthy, observedAt: now() };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { probePresence, checkHealth };
|
|
90
|
-
}
|
package/src/backend/row-store.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
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
|
-
|
|
8
|
-
import {
|
|
9
|
-
type CellularSnapshot,
|
|
10
|
-
createSnapshot,
|
|
11
|
-
revision as makeRevision,
|
|
12
|
-
markSourceUnavailable,
|
|
13
|
-
type Revision,
|
|
14
|
-
} from '../domain';
|
|
15
|
-
import type { ObservationFailureReason, ObservationList } from '../ports';
|
|
16
|
-
import type { DecodedManagedObjects } from './managed-objects';
|
|
17
|
-
import { fingerprint, type MappedModem, mapModem, modemPaths } from './mapping';
|
|
18
|
-
|
|
19
|
-
interface Row {
|
|
20
|
-
snapshot: CellularSnapshot;
|
|
21
|
-
fingerprint: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class ObservationRowStore {
|
|
25
|
-
readonly #rows = new Map<string, Row>();
|
|
26
|
-
#revCounter = 0;
|
|
27
|
-
#sourceHealthy = false;
|
|
28
|
-
#failureReason: ObservationFailureReason = 'not-started';
|
|
29
|
-
|
|
30
|
-
/** Reconcile the authoritative tree into the rows. Returns whether rows changed.
|
|
31
|
-
* This is the SOLE removal path — an omission from a current-epoch snapshot. */
|
|
32
|
-
reconcile(tree: DecodedManagedObjects): boolean {
|
|
33
|
-
let changed = false;
|
|
34
|
-
const seen = new Set<string>();
|
|
35
|
-
for (const path of modemPaths(tree)) {
|
|
36
|
-
seen.add(path);
|
|
37
|
-
if (this.#upsert(path, mapModem(tree, path))) {
|
|
38
|
-
changed = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
for (const path of [...this.#rows.keys()]) {
|
|
42
|
-
if (!seen.has(path)) {
|
|
43
|
-
this.#rows.delete(path);
|
|
44
|
-
changed = true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return changed;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Mark the source healthy after a successful reconcile. Returns whether health flipped. */
|
|
51
|
-
markHealthy(): boolean {
|
|
52
|
-
const flipped = !this.#sourceHealthy;
|
|
53
|
-
this.#sourceHealthy = true;
|
|
54
|
-
return flipped;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Flag every row stale (retained, never removed). Returns whether an emission is due. */
|
|
58
|
-
markUnavailable(reason: ObservationFailureReason): boolean {
|
|
59
|
-
const wasHealthy = this.#sourceHealthy;
|
|
60
|
-
let changed = false;
|
|
61
|
-
for (const [path, row] of this.#rows) {
|
|
62
|
-
if (row.snapshot.sourceHealth === 'sourceUnavailable') {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
this.#rows.set(path, {
|
|
66
|
-
snapshot: markSourceUnavailable(row.snapshot),
|
|
67
|
-
fingerprint: row.fingerprint,
|
|
68
|
-
});
|
|
69
|
-
changed = true;
|
|
70
|
-
}
|
|
71
|
-
this.#sourceHealthy = false;
|
|
72
|
-
this.#failureReason = reason;
|
|
73
|
-
return changed || wasHealthy;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
list(): ObservationList {
|
|
77
|
-
const rows = [...this.#rows.values()].map((row) => row.snapshot);
|
|
78
|
-
if (this.#sourceHealthy) {
|
|
79
|
-
return { ok: true, rows };
|
|
80
|
-
}
|
|
81
|
-
return { ok: false, reason: this.#failureReason, rows };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
#upsert(path: string, mapped: MappedModem): boolean {
|
|
85
|
-
const fp = fingerprint(mapped);
|
|
86
|
-
const existing = this.#rows.get(path);
|
|
87
|
-
if (
|
|
88
|
-
existing !== undefined &&
|
|
89
|
-
existing.fingerprint === fp &&
|
|
90
|
-
existing.snapshot.sourceHealth === 'live'
|
|
91
|
-
) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
this.#rows.set(path, {
|
|
95
|
-
snapshot: createSnapshot({ ...mapped, revision: this.#nextRevision() }),
|
|
96
|
-
fingerprint: fp,
|
|
97
|
-
});
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#nextRevision(): Revision {
|
|
102
|
-
this.#revCounter += 1;
|
|
103
|
-
return makeRevision(this.#revCounter);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
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
|
-
|
|
19
|
-
import type { DbusTransport } from '../transport';
|
|
20
|
-
import { MM_BUS_NAME, MODEM_IFACE } from './constants';
|
|
21
|
-
import type { DecodedManagedObjects } from './managed-objects';
|
|
22
|
-
import { hasInterface, pathsWithInterface } from './managed-objects';
|
|
23
|
-
|
|
24
|
-
/** Whether periodic signal reporting is configured for a modem. */
|
|
25
|
-
export type SignalCadence = 'active' | 'unsupported' | 'unknown';
|
|
26
|
-
|
|
27
|
-
/** The `Modem.Signal` interface — absent means signal cadence is unsupported. */
|
|
28
|
-
const SIGNAL_IFACE = 'org.freedesktop.ModemManager1.Modem.Signal';
|
|
29
|
-
|
|
30
|
-
/** MM's default reporting rate is seconds; callers pass whole seconds. */
|
|
31
|
-
export const DEFAULT_SIGNAL_INTERVAL_SECONDS = 5;
|
|
32
|
-
|
|
33
|
-
export interface SignalSetupManagerOptions {
|
|
34
|
-
readonly transport: DbusTransport;
|
|
35
|
-
/** MM bus name override (defaults to `org.freedesktop.ModemManager1`). */
|
|
36
|
-
readonly destination?: string;
|
|
37
|
-
/** Reporting interval in seconds passed to `Signal.Setup`. */
|
|
38
|
-
readonly intervalSeconds?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Drives `Signal.Setup` across the modem fleet, keyed to the observer's epochs. Feed
|
|
43
|
-
* it every `onEpochRefresh` event; it applies setup to each modem exactly once per
|
|
44
|
-
* epoch, re-applies to survivors on a new epoch, and never calls for an old one.
|
|
45
|
-
*/
|
|
46
|
-
export class SignalSetupManager {
|
|
47
|
-
readonly #transport: DbusTransport;
|
|
48
|
-
readonly #destination: string;
|
|
49
|
-
readonly #interval: number;
|
|
50
|
-
#currentEpoch: string | undefined;
|
|
51
|
-
// Guards double-issue within an epoch: `${epoch}\u0000${modemPath}`.
|
|
52
|
-
readonly #applied = new Set<string>();
|
|
53
|
-
readonly #cadence = new Map<string, SignalCadence>();
|
|
54
|
-
|
|
55
|
-
constructor(options: SignalSetupManagerOptions) {
|
|
56
|
-
this.#transport = options.transport;
|
|
57
|
-
this.#destination = options.destination ?? MM_BUS_NAME;
|
|
58
|
-
this.#interval = options.intervalSeconds ?? DEFAULT_SIGNAL_INTERVAL_SECONDS;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The last-known cadence for a modem path (`'unknown'` until first applied). */
|
|
62
|
-
cadenceFor(modemPath: string): SignalCadence {
|
|
63
|
-
return this.#cadence.get(modemPath) ?? 'unknown';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Apply `Signal.Setup` for the current epoch's modems. New epoch ⇒ every survivor
|
|
68
|
-
* is re-applied; an already-applied (epoch, modem) is skipped. Modems absent from
|
|
69
|
-
* this snapshot keep their last cadence but are not re-driven.
|
|
70
|
-
*/
|
|
71
|
-
applyForEpoch(epoch: string, tree: DecodedManagedObjects): void {
|
|
72
|
-
this.#currentEpoch = epoch;
|
|
73
|
-
for (const modemPath of pathsWithInterface(tree, MODEM_IFACE)) {
|
|
74
|
-
const key = `${epoch}\u0000${modemPath}`;
|
|
75
|
-
if (this.#applied.has(key)) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
this.#applied.add(key);
|
|
79
|
-
void this.#setupOne(epoch, modemPath, tree);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async #setupOne(epoch: string, modemPath: string, tree: DecodedManagedObjects): Promise<void> {
|
|
84
|
-
// Epoch changed out from under us before we could issue the call — drop it, so
|
|
85
|
-
// no Signal.Setup ever fires for a superseded epoch.
|
|
86
|
-
if (this.#currentEpoch !== epoch) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (!hasInterface(tree, modemPath, SIGNAL_IFACE)) {
|
|
90
|
-
this.#cadence.set(modemPath, 'unsupported');
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (this.#currentEpoch !== epoch) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
await this.#transport.callMethod({
|
|
98
|
-
destination: this.#destination,
|
|
99
|
-
path: modemPath,
|
|
100
|
-
interface: SIGNAL_IFACE,
|
|
101
|
-
member: 'Setup',
|
|
102
|
-
signature: 'u',
|
|
103
|
-
args: [this.#interval],
|
|
104
|
-
});
|
|
105
|
-
this.#cadence.set(modemPath, 'active');
|
|
106
|
-
} catch {
|
|
107
|
-
// A Setup call that errors is a soft gap — surfaced as unsupported, never a
|
|
108
|
-
// start failure.
|
|
109
|
-
this.#cadence.set(modemPath, 'unsupported');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
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
|
-
|
|
17
|
-
import type { SimPukUnlockResult, SimUnlockResult } from '../ports';
|
|
18
|
-
import type { DbusTransport } from '../transport';
|
|
19
|
-
import { MODEM_IFACE, SIM_IFACE } from './constants';
|
|
20
|
-
import {
|
|
21
|
-
type DecodedManagedObjects,
|
|
22
|
-
fetchManagedObjects,
|
|
23
|
-
findInterface,
|
|
24
|
-
numberProp,
|
|
25
|
-
propValue,
|
|
26
|
-
stringProp,
|
|
27
|
-
} from './managed-objects';
|
|
28
|
-
|
|
29
|
-
// MMModemLock values we branch on.
|
|
30
|
-
const LOCK_SIM_PIN = 2;
|
|
31
|
-
const LOCK_SIM_PUK = 4;
|
|
32
|
-
const LOCK_SIM_PUK2 = 5;
|
|
33
|
-
|
|
34
|
-
/** A modem's current SIM lock state, read before any submit. */
|
|
35
|
-
interface LockState {
|
|
36
|
-
readonly required: number;
|
|
37
|
-
readonly retries: ReadonlyMap<number, number>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function readLockState(tree: DecodedManagedObjects, modemPath: string): LockState | undefined {
|
|
41
|
-
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
42
|
-
if (modem === undefined) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
const required = numberProp(modem, 'UnlockRequired') ?? 0;
|
|
46
|
-
const retries = new Map<number, number>();
|
|
47
|
-
const raw = propValue(modem, 'UnlockRetries');
|
|
48
|
-
if (Array.isArray(raw)) {
|
|
49
|
-
for (const entry of raw) {
|
|
50
|
-
if (Array.isArray(entry) && typeof entry[0] === 'number' && typeof entry[1] === 'number') {
|
|
51
|
-
retries.set(entry[0], entry[1]);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return { required, retries };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** The object path of a modem's active SIM (its `Sim` property), when present. */
|
|
59
|
-
function activeSimPath(tree: DecodedManagedObjects, modemPath: string): string | undefined {
|
|
60
|
-
const path = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Sim');
|
|
61
|
-
return path?.startsWith('/') && path !== '/' ? path : undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const isPukLock = (lock: number): boolean => lock === LOCK_SIM_PUK || lock === LOCK_SIM_PUK2;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Submit a SIM PIN read-before-submit and exactly-once. On a wrong PIN the lock state
|
|
68
|
-
* is re-read (never resubmitted) to report remaining attempts or a resulting PUK lock.
|
|
69
|
-
*/
|
|
70
|
-
export async function sendSimPin(
|
|
71
|
-
transport: DbusTransport,
|
|
72
|
-
destination: string,
|
|
73
|
-
modemPath: string,
|
|
74
|
-
pin: string,
|
|
75
|
-
): Promise<SimUnlockResult> {
|
|
76
|
-
let tree: DecodedManagedObjects;
|
|
77
|
-
try {
|
|
78
|
-
tree = await fetchManagedObjects(transport, destination);
|
|
79
|
-
} catch {
|
|
80
|
-
return { outcome: 'error', reason: 'could not read modem lock state before PIN submit' };
|
|
81
|
-
}
|
|
82
|
-
const state = readLockState(tree, modemPath);
|
|
83
|
-
if (state === undefined) {
|
|
84
|
-
return { outcome: 'error', reason: 'modem not found while reading lock state' };
|
|
85
|
-
}
|
|
86
|
-
if (isPukLock(state.required)) {
|
|
87
|
-
return { outcome: 'sim-puk-required', reason: 'SIM is PUK-locked; a PIN cannot clear it' };
|
|
88
|
-
}
|
|
89
|
-
if (state.required !== LOCK_SIM_PIN) {
|
|
90
|
-
return { outcome: 'unlocked', reason: 'no SIM PIN is pending' };
|
|
91
|
-
}
|
|
92
|
-
const simPath = activeSimPath(tree, modemPath);
|
|
93
|
-
if (simPath === undefined) {
|
|
94
|
-
return { outcome: 'error', reason: 'no active SIM object to submit the PIN to' };
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
await transport.callMethod({
|
|
98
|
-
destination,
|
|
99
|
-
path: simPath,
|
|
100
|
-
interface: SIM_IFACE,
|
|
101
|
-
member: 'SendPin',
|
|
102
|
-
signature: 's',
|
|
103
|
-
args: [pin],
|
|
104
|
-
});
|
|
105
|
-
return { outcome: 'unlocked', reason: 'PIN accepted' };
|
|
106
|
-
} catch {
|
|
107
|
-
return classifyPinFailure(transport, destination, modemPath);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function classifyPinFailure(
|
|
112
|
-
transport: DbusTransport,
|
|
113
|
-
destination: string,
|
|
114
|
-
modemPath: string,
|
|
115
|
-
): Promise<SimUnlockResult> {
|
|
116
|
-
const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
|
|
117
|
-
if (state !== undefined && isPukLock(state.required)) {
|
|
118
|
-
return { outcome: 'sim-puk-required', reason: 'wrong PIN tripped the SIM into a PUK lock' };
|
|
119
|
-
}
|
|
120
|
-
const remaining = state?.retries.get(LOCK_SIM_PIN);
|
|
121
|
-
return {
|
|
122
|
-
outcome: 'incorrect-pin',
|
|
123
|
-
...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
|
|
124
|
-
reason: 'PIN was rejected',
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Submit a SIM PUK + new PIN read-before-submit and exactly-once. On a wrong PUK the
|
|
130
|
-
* remaining PUK attempts are re-read (never resubmitted); zero remaining is a
|
|
131
|
-
* permanent block.
|
|
132
|
-
*/
|
|
133
|
-
export async function sendSimPuk(
|
|
134
|
-
transport: DbusTransport,
|
|
135
|
-
destination: string,
|
|
136
|
-
modemPath: string,
|
|
137
|
-
puk: string,
|
|
138
|
-
newPin: string,
|
|
139
|
-
): Promise<SimPukUnlockResult> {
|
|
140
|
-
let tree: DecodedManagedObjects;
|
|
141
|
-
try {
|
|
142
|
-
tree = await fetchManagedObjects(transport, destination);
|
|
143
|
-
} catch {
|
|
144
|
-
return { outcome: 'error', reason: 'could not read modem lock state before PUK submit' };
|
|
145
|
-
}
|
|
146
|
-
const state = readLockState(tree, modemPath);
|
|
147
|
-
if (state === undefined) {
|
|
148
|
-
return { outcome: 'error', reason: 'modem not found while reading lock state' };
|
|
149
|
-
}
|
|
150
|
-
if (!isPukLock(state.required)) {
|
|
151
|
-
return { outcome: 'unlocked', reason: 'no SIM PUK is pending' };
|
|
152
|
-
}
|
|
153
|
-
const pukKind = state.required;
|
|
154
|
-
const simPath = activeSimPath(tree, modemPath);
|
|
155
|
-
if (simPath === undefined) {
|
|
156
|
-
return { outcome: 'error', reason: 'no active SIM object to submit the PUK to' };
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
await transport.callMethod({
|
|
160
|
-
destination,
|
|
161
|
-
path: simPath,
|
|
162
|
-
interface: SIM_IFACE,
|
|
163
|
-
member: 'SendPuk',
|
|
164
|
-
signature: 'ss',
|
|
165
|
-
args: [puk, newPin],
|
|
166
|
-
});
|
|
167
|
-
return { outcome: 'unlocked', reason: 'PUK accepted; new PIN set' };
|
|
168
|
-
} catch {
|
|
169
|
-
return classifyPukFailure(transport, destination, modemPath, pukKind);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function classifyPukFailure(
|
|
174
|
-
transport: DbusTransport,
|
|
175
|
-
destination: string,
|
|
176
|
-
modemPath: string,
|
|
177
|
-
pukKind: number,
|
|
178
|
-
): Promise<SimPukUnlockResult> {
|
|
179
|
-
const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
|
|
180
|
-
const remaining = state?.retries.get(pukKind);
|
|
181
|
-
if (remaining === 0) {
|
|
182
|
-
return {
|
|
183
|
-
outcome: 'permanently-blocked',
|
|
184
|
-
remainingAttempts: 0,
|
|
185
|
-
reason: 'PUK attempts exhausted; SIM is permanently locked',
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
outcome: 'incorrect-puk',
|
|
190
|
-
...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
|
|
191
|
-
reason: 'PUK was rejected',
|
|
192
|
-
};
|
|
193
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
// USB-mode transition preconditions + the transition interlock seam.
|
|
2
|
-
//
|
|
3
|
-
// The preconditions gate a mode switch and are checked TWICE (draft §rounds 5/6
|
|
4
|
-
// TOCTOU): once at transaction entry (so a doomed request never enters the actor) and
|
|
5
|
-
// again INSIDE the actor (so a request that was valid at entry but became invalid
|
|
6
|
-
// while queued is caught before any disruptive call). `checkTransitionPreconditions`
|
|
7
|
-
// re-polls the LIVE inputs (`probeReadiness`, `interlock.canDisrupt`) each call, so
|
|
8
|
-
// the two checks can genuinely disagree — that disagreement is the TIER-B race.
|
|
9
|
-
//
|
|
10
|
-
// The interlock is BIDIRECTIONAL: `canDisrupt` is the streaming→transition gate (may
|
|
11
|
-
// I disrupt now?), inherited from A3.4's `LifecycleInterlock`; `hold` is the
|
|
12
|
-
// transition→streaming gate (mark "transition active" until released). The Phase-A
|
|
13
|
-
// stub allows both; Phase B wires CeraUI's streaming-admission check into the same
|
|
14
|
-
// interface without changing the transaction.
|
|
15
|
-
|
|
16
|
-
import type { EpochMillis, IdentityConfidence } from '../domain';
|
|
17
|
-
import type { ConnectionId, DeviceIfname } from '../ports';
|
|
18
|
-
import {
|
|
19
|
-
type CatalogEntry,
|
|
20
|
-
type CertifiedCatalog,
|
|
21
|
-
findCatalogEntry,
|
|
22
|
-
findPermittedTransition,
|
|
23
|
-
type MmUsbMode,
|
|
24
|
-
type PermittedTransition,
|
|
25
|
-
type SkuDiscriminator,
|
|
26
|
-
} from '../usb-mode';
|
|
27
|
-
import type { InterlockTarget, LifecycleInterlock } from './lifecycle-interlock';
|
|
28
|
-
|
|
29
|
-
/** A held "transition active" interlock — released when the transaction ends. */
|
|
30
|
-
export interface InterlockHold {
|
|
31
|
-
release(): Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The transition interlock. `canDisrupt` (from `LifecycleInterlock`) answers "is it
|
|
36
|
-
* safe to disrupt this modem now?"; `hold` marks a transition in progress so a
|
|
37
|
-
* streaming start is blocked for its duration. Both directions, one seam.
|
|
38
|
-
*/
|
|
39
|
-
export interface TransitionInterlock extends LifecycleInterlock {
|
|
40
|
-
hold(target: InterlockTarget): Promise<InterlockHold>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const NO_OP_HOLD: InterlockHold = {
|
|
44
|
-
release(): Promise<void> {
|
|
45
|
-
return Promise.resolve();
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/** The Phase-A interlock: always allows, holds nothing. */
|
|
50
|
-
export const ALLOW_ALL_TRANSITION_INTERLOCK: TransitionInterlock = {
|
|
51
|
-
canDisrupt() {
|
|
52
|
-
return Promise.resolve({ allow: true } as const);
|
|
53
|
-
},
|
|
54
|
-
hold(): Promise<InterlockHold> {
|
|
55
|
-
return Promise.resolve(NO_OP_HOLD);
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/** Live, re-evaluable readiness inputs — re-polled at entry AND in-actor. */
|
|
60
|
-
export interface TransitionReadiness {
|
|
61
|
-
/** The identity confidence from A3.2's ladder; `'low'` refuses the transition. */
|
|
62
|
-
readonly identityConfidence: IdentityConfidence;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** One USB-mode transition request. */
|
|
66
|
-
export interface UsbModeTransitionRequest {
|
|
67
|
-
readonly stableKey: string;
|
|
68
|
-
readonly sku: SkuDiscriminator;
|
|
69
|
-
readonly fromMode: MmUsbMode;
|
|
70
|
-
readonly toMode: MmUsbMode;
|
|
71
|
-
readonly connectionId: ConnectionId;
|
|
72
|
-
readonly deviceIfname: DeviceIfname;
|
|
73
|
-
/** Physical-topology UID captured BEFORE the switch — survives re-enumeration. */
|
|
74
|
-
readonly cachedPhysicalUid: string;
|
|
75
|
-
/** The MM `Device` UID to inhibit by (cached — the modem disappears mid-switch). */
|
|
76
|
-
readonly inhibitUid: string;
|
|
77
|
-
/** Must be `true` (mirrors the CLI `--confirm` flag). */
|
|
78
|
-
readonly confirm: boolean;
|
|
79
|
-
/** Must be `true` (an extra maintenance-mode safety gate). */
|
|
80
|
-
readonly maintenance: boolean;
|
|
81
|
-
readonly now: EpochMillis;
|
|
82
|
-
/** Live readiness, re-polled at entry AND in-actor (TOCTOU-safe). */
|
|
83
|
-
probeReadiness(): Promise<TransitionReadiness>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** How a transition ended. */
|
|
87
|
-
export type UsbModeTransitionOutcome =
|
|
88
|
-
| {
|
|
89
|
-
readonly status: 'refused';
|
|
90
|
-
readonly stage: 'entry' | 'in-actor';
|
|
91
|
-
readonly reason: string;
|
|
92
|
-
readonly steps: readonly string[];
|
|
93
|
-
}
|
|
94
|
-
| {
|
|
95
|
-
readonly status: 'succeeded';
|
|
96
|
-
readonly newIfname: DeviceIfname;
|
|
97
|
-
readonly steps: readonly string[];
|
|
98
|
-
}
|
|
99
|
-
| {
|
|
100
|
-
readonly status: 'failed';
|
|
101
|
-
readonly degraded: boolean;
|
|
102
|
-
readonly reason: string;
|
|
103
|
-
readonly steps: readonly string[];
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/** The result of a precondition check — the matched entry/transition, or a reason. */
|
|
107
|
-
export type PreconditionResult =
|
|
108
|
-
| { readonly ok: true; readonly entry: CatalogEntry; readonly transition: PermittedTransition }
|
|
109
|
-
| { readonly ok: false; readonly reason: string };
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Check every transition precondition against the LIVE inputs. Called at entry and
|
|
113
|
-
* again in-actor; a request that passed at entry can fail here if the identity
|
|
114
|
-
* confidence dropped or the interlock closed in the meantime. Order is cheap-static
|
|
115
|
-
* checks first (confirm, maintenance, catalog, permitted) then the live probes
|
|
116
|
-
* (identity, interlock), so a refusal touches as little as possible.
|
|
117
|
-
*/
|
|
118
|
-
export async function checkTransitionPreconditions(
|
|
119
|
-
request: UsbModeTransitionRequest,
|
|
120
|
-
catalog: CertifiedCatalog,
|
|
121
|
-
interlock: TransitionInterlock,
|
|
122
|
-
): Promise<PreconditionResult> {
|
|
123
|
-
if (!request.confirm) {
|
|
124
|
-
return { ok: false, reason: 'confirm:true is required (missing --confirm)' };
|
|
125
|
-
}
|
|
126
|
-
if (!request.maintenance) {
|
|
127
|
-
return { ok: false, reason: 'maintenance flag is required' };
|
|
128
|
-
}
|
|
129
|
-
const entry = findCatalogEntry(catalog, request.sku);
|
|
130
|
-
if (entry === undefined) {
|
|
131
|
-
return { ok: false, reason: `uncertified SKU ${request.sku.vidPid} ${request.sku.model}` };
|
|
132
|
-
}
|
|
133
|
-
const transition = findPermittedTransition(entry, request.fromMode, request.toMode);
|
|
134
|
-
if (transition === undefined) {
|
|
135
|
-
return {
|
|
136
|
-
ok: false,
|
|
137
|
-
reason: `transition ${request.fromMode}->${request.toMode} not permitted for ${request.sku.model}`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
const readiness = await request.probeReadiness();
|
|
141
|
-
if (readiness.identityConfidence === 'low') {
|
|
142
|
-
return { ok: false, reason: 'low-confidence identity — refusing to transition' };
|
|
143
|
-
}
|
|
144
|
-
const verdict = await interlock.canDisrupt({ stableKey: request.stableKey });
|
|
145
|
-
if (!verdict.allow) {
|
|
146
|
-
return { ok: false, reason: `interlock held: ${verdict.reason}` };
|
|
147
|
-
}
|
|
148
|
-
return { ok: true, entry, transition };
|
|
149
|
-
}
|