@ceralive/modem-control 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseHilinkSession, parseHilinkUserState, parseHilinkXmlValue, } from '../../hardware/router-parsers.js';
|
|
3
|
+
import { HILINK_PATHS } from './provider.js';
|
|
4
|
+
function base64(value) {
|
|
5
|
+
return Buffer.from(value).toString('base64');
|
|
6
|
+
}
|
|
7
|
+
function derivePassword(profile, username, password, token) {
|
|
8
|
+
if (profile.passwordType === 3)
|
|
9
|
+
return base64(password);
|
|
10
|
+
const passwordHash = base64(createHash('sha256').update(password).digest('hex'));
|
|
11
|
+
const loginHash = createHash('sha256').update(`${username}${passwordHash}${token}`).digest('hex');
|
|
12
|
+
return base64(loginHash);
|
|
13
|
+
}
|
|
14
|
+
function escapeXml(value) {
|
|
15
|
+
return value
|
|
16
|
+
.replace(/&/g, '&')
|
|
17
|
+
.replace(/</g, '<')
|
|
18
|
+
.replace(/>/g, '>')
|
|
19
|
+
.replace(/"/g, '"')
|
|
20
|
+
.replace(/'/g, ''');
|
|
21
|
+
}
|
|
22
|
+
function loginDocument(profile, username, password, token) {
|
|
23
|
+
return `<?xml version="1.0" encoding="UTF-8"?><request><Username>${escapeXml(username)}</Username><Password>${derivePassword(profile, username, password, token)}</Password><password_type>${profile.passwordType}</password_type></request>`;
|
|
24
|
+
}
|
|
25
|
+
function responseHeader(response, name) {
|
|
26
|
+
const expected = name.toLowerCase();
|
|
27
|
+
return Object.entries(response.headers ?? {}).find(([key]) => key.toLowerCase() === expected)?.[1];
|
|
28
|
+
}
|
|
29
|
+
function loggedInSession(response, fallback) {
|
|
30
|
+
const cookie = responseHeader(response, 'set-cookie')?.split(';', 1)[0]?.trim() ?? fallback.cookie;
|
|
31
|
+
const tokenHeader = responseHeader(response, '__requestverificationtoken') ??
|
|
32
|
+
responseHeader(response, '__requestverificationtokenone');
|
|
33
|
+
const token = tokenHeader?.split('#', 1)[0]?.trim() ?? fallback.token;
|
|
34
|
+
return { cookie, token };
|
|
35
|
+
}
|
|
36
|
+
export class HilinkSessionRuntime {
|
|
37
|
+
options;
|
|
38
|
+
#authenticated = new Map();
|
|
39
|
+
#serial = new Map();
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
async withDevice(context, run) {
|
|
44
|
+
const key = String(context.physicalModemId);
|
|
45
|
+
const prior = this.#serial.get(key) ?? Promise.resolve();
|
|
46
|
+
let release = () => { };
|
|
47
|
+
const current = new Promise((resolve) => {
|
|
48
|
+
release = resolve;
|
|
49
|
+
});
|
|
50
|
+
const chain = prior.then(() => current);
|
|
51
|
+
this.#serial.set(key, chain);
|
|
52
|
+
await prior;
|
|
53
|
+
try {
|
|
54
|
+
return await run();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
release();
|
|
58
|
+
if (this.#serial.get(key) === chain)
|
|
59
|
+
this.#serial.delete(key);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
sessionKey(context) {
|
|
63
|
+
return `${context.physicalModemId}:${context.generation}`;
|
|
64
|
+
}
|
|
65
|
+
cachedSession(context) {
|
|
66
|
+
return this.#authenticated.get(this.sessionKey(context));
|
|
67
|
+
}
|
|
68
|
+
async authenticate(context, profile, cache) {
|
|
69
|
+
const sessionResponse = await this.request('GET', HILINK_PATHS.session);
|
|
70
|
+
if (sessionResponse.status === 401 || sessionResponse.status === 403)
|
|
71
|
+
return { status: 'refused', reason: 'auth-expired' };
|
|
72
|
+
if (sessionResponse.status !== 200)
|
|
73
|
+
return { status: 'refused', reason: 'unreachable' };
|
|
74
|
+
const initial = parseHilinkSession(sessionResponse.body);
|
|
75
|
+
if (initial === undefined)
|
|
76
|
+
return { status: 'refused', reason: 'malformed' };
|
|
77
|
+
const stateResponse = await this.getResponse(HILINK_PATHS.loginState, initial);
|
|
78
|
+
if (this.authExpired(stateResponse))
|
|
79
|
+
return { status: 'refused', reason: 'auth-expired' };
|
|
80
|
+
const state = parseHilinkUserState(stateResponse.body);
|
|
81
|
+
if (state === undefined)
|
|
82
|
+
return { status: 'refused', reason: 'malformed' };
|
|
83
|
+
if (state.passwordType !== profile.passwordType)
|
|
84
|
+
return { status: 'refused', reason: 'profile-mismatch' };
|
|
85
|
+
const login = await this.postResponse(HILINK_PATHS.login, loginDocument(profile, this.options.credentials.username, this.options.credentials.password, initial.token), initial);
|
|
86
|
+
if (this.authExpired(login))
|
|
87
|
+
return { status: 'refused', reason: 'auth-expired' };
|
|
88
|
+
if (login.status !== 200 || !/<response>\s*OK\s*<\/response>/i.test(login.body))
|
|
89
|
+
return { status: 'refused', reason: 'http-failure' };
|
|
90
|
+
const session = loggedInSession(login, initial);
|
|
91
|
+
if (cache)
|
|
92
|
+
this.#authenticated.set(this.sessionKey(context), session);
|
|
93
|
+
return { status: 'ready', session };
|
|
94
|
+
}
|
|
95
|
+
authExpired(response) {
|
|
96
|
+
return (response.status === 401 ||
|
|
97
|
+
response.status === 403 ||
|
|
98
|
+
parseHilinkXmlValue(response.body, 'code') === '125002');
|
|
99
|
+
}
|
|
100
|
+
getResponse(path, session) {
|
|
101
|
+
return this.request('GET', path, undefined, [`Cookie: ${session.cookie}`]);
|
|
102
|
+
}
|
|
103
|
+
postResponse(path, body, session) {
|
|
104
|
+
return this.request('POST', path, body, [
|
|
105
|
+
`Cookie: ${session.cookie}`,
|
|
106
|
+
`__RequestVerificationToken: ${session.token}`,
|
|
107
|
+
'Content-Type: application/xml',
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
request(method, path, body, headers = []) {
|
|
111
|
+
return this.options.transport.request({
|
|
112
|
+
method,
|
|
113
|
+
url: `${this.options.adminUrl}${path}`,
|
|
114
|
+
...(body === undefined ? {} : { body }),
|
|
115
|
+
headers,
|
|
116
|
+
interfaceName: this.options.interfaceName,
|
|
117
|
+
redirect: 'error',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type HilinkHttpRequest = {
|
|
2
|
+
readonly method: 'GET' | 'POST';
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly body?: string;
|
|
5
|
+
readonly headers: readonly string[];
|
|
6
|
+
readonly interfaceName: string;
|
|
7
|
+
readonly redirect: 'error';
|
|
8
|
+
};
|
|
9
|
+
export type HilinkHttpResponse = {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly body: string;
|
|
12
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
13
|
+
};
|
|
14
|
+
export interface HilinkTransport {
|
|
15
|
+
request(request: HilinkHttpRequest): Promise<HilinkHttpResponse>;
|
|
16
|
+
}
|
|
17
|
+
export type HilinkReplayExchange = HilinkHttpRequest & {
|
|
18
|
+
readonly response: HilinkHttpResponse;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './contracts.js';
|
|
2
|
+
export * from './huawei-hilink/index.js';
|
|
3
|
+
export * from './matcher.js';
|
|
4
|
+
export * from './modem-manager/index.js';
|
|
5
|
+
export * from './network-manager/index.js';
|
|
6
|
+
export * from './registry.js';
|
|
7
|
+
export * from './ufi-himi/index.js';
|
|
8
|
+
export * from './zte-goform/index.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './contracts.js';
|
|
2
|
+
export * from './huawei-hilink/index.js';
|
|
3
|
+
export * from './matcher.js';
|
|
4
|
+
export * from './modem-manager/index.js';
|
|
5
|
+
export * from './network-manager/index.js';
|
|
6
|
+
export * from './registry.js';
|
|
7
|
+
export * from './ufi-himi/index.js';
|
|
8
|
+
export * from './zte-goform/index.js';
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const STRENGTH_POINTS = {
|
|
2
|
+
none: 0,
|
|
3
|
+
weak: 1,
|
|
4
|
+
moderate: 2,
|
|
5
|
+
strong: 3,
|
|
6
|
+
};
|
|
7
|
+
function scoreOf(points) {
|
|
8
|
+
if (points <= 0)
|
|
9
|
+
return 'unsupported';
|
|
10
|
+
if (points === 1)
|
|
11
|
+
return 'maybe';
|
|
12
|
+
if (points === 2)
|
|
13
|
+
return 'likely';
|
|
14
|
+
return 'supported';
|
|
15
|
+
}
|
|
16
|
+
function confidenceOf(points) {
|
|
17
|
+
return Math.min(1, points / 3);
|
|
18
|
+
}
|
|
19
|
+
function cacheSignature(request, registryRevision) {
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
registryRevision,
|
|
22
|
+
generation: request.generation,
|
|
23
|
+
transport: request.transport,
|
|
24
|
+
passiveFacts: request.passiveFacts,
|
|
25
|
+
composition: request.composition,
|
|
26
|
+
firmware: request.firmware ?? null,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function addCandidate(candidates, provider, profile, strength) {
|
|
30
|
+
const key = `${provider.id}\u0000${profile}`;
|
|
31
|
+
const existing = candidates.get(key);
|
|
32
|
+
if (existing === undefined) {
|
|
33
|
+
candidates.set(key, { provider, profile, points: STRENGTH_POINTS[strength] });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
existing.points += STRENGTH_POINTS[strength];
|
|
37
|
+
}
|
|
38
|
+
function unresolvedResult(status, request, points, evidence, conflicts) {
|
|
39
|
+
return {
|
|
40
|
+
status,
|
|
41
|
+
provider: null,
|
|
42
|
+
profile: null,
|
|
43
|
+
score: scoreOf(points),
|
|
44
|
+
confidence: confidenceOf(points),
|
|
45
|
+
evidence,
|
|
46
|
+
conflicts,
|
|
47
|
+
operations: null,
|
|
48
|
+
writable: false,
|
|
49
|
+
generation: request.generation,
|
|
50
|
+
physicalModemId: request.physicalModemId,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function evaluate(registry, request) {
|
|
54
|
+
const evidence = [];
|
|
55
|
+
const conflicts = [];
|
|
56
|
+
const candidates = new Map();
|
|
57
|
+
const eligible = [];
|
|
58
|
+
for (const provider of registry.list()) {
|
|
59
|
+
const transportMatches = provider.eligibleTransports.includes(request.transport);
|
|
60
|
+
evidence.push({
|
|
61
|
+
provider: provider.id,
|
|
62
|
+
profile: null,
|
|
63
|
+
stage: 'transport-eligibility',
|
|
64
|
+
source: request.transport,
|
|
65
|
+
signal: transportMatches ? 'match' : 'mismatch',
|
|
66
|
+
strength: 'none',
|
|
67
|
+
detail: transportMatches ? 'eligible-transport' : 'ineligible-transport',
|
|
68
|
+
});
|
|
69
|
+
if (transportMatches)
|
|
70
|
+
eligible.push(provider);
|
|
71
|
+
}
|
|
72
|
+
for (const provider of eligible) {
|
|
73
|
+
for (const matcher of provider.passiveMatchers) {
|
|
74
|
+
const actual = request.passiveFacts
|
|
75
|
+
.filter((fact) => fact.kind === matcher.fact)
|
|
76
|
+
.map((fact) => fact.value);
|
|
77
|
+
const signal = actual.length === 0
|
|
78
|
+
? 'unknown'
|
|
79
|
+
: actual.some((value) => matcher.expected.includes(value))
|
|
80
|
+
? 'match'
|
|
81
|
+
: 'mismatch';
|
|
82
|
+
const item = {
|
|
83
|
+
provider: provider.id,
|
|
84
|
+
profile: matcher.profiles.length === 1 ? (matcher.profiles[0] ?? null) : null,
|
|
85
|
+
stage: 'passive-facts',
|
|
86
|
+
source: matcher.id,
|
|
87
|
+
signal,
|
|
88
|
+
strength: matcher.strength,
|
|
89
|
+
detail: signal === 'match' ? 'expected-fact-present' : 'expected-fact-not-proven',
|
|
90
|
+
};
|
|
91
|
+
evidence.push(item);
|
|
92
|
+
if (signal === 'mismatch' && matcher.required)
|
|
93
|
+
conflicts.push(item);
|
|
94
|
+
if (signal === 'match') {
|
|
95
|
+
for (const profile of matcher.profiles) {
|
|
96
|
+
addCandidate(candidates, provider, profile, matcher.strength);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const provider of eligible) {
|
|
102
|
+
for (const probe of provider.unauthenticatedProbes) {
|
|
103
|
+
const result = await probe.run(request);
|
|
104
|
+
const item = {
|
|
105
|
+
provider: provider.id,
|
|
106
|
+
profile: result.profiles.length === 1 ? (result.profiles[0] ?? null) : null,
|
|
107
|
+
stage: 'unauthenticated-fingerprint',
|
|
108
|
+
source: probe.id,
|
|
109
|
+
signal: result.signal,
|
|
110
|
+
strength: result.strength,
|
|
111
|
+
detail: result.detail,
|
|
112
|
+
};
|
|
113
|
+
evidence.push(item);
|
|
114
|
+
if (result.signal === 'mismatch')
|
|
115
|
+
conflicts.push(item);
|
|
116
|
+
if (result.signal === 'match') {
|
|
117
|
+
for (const profile of result.profiles) {
|
|
118
|
+
addCandidate(candidates, provider, profile, result.strength);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const ranked = [...candidates.values()].sort((left, right) => right.points - left.points);
|
|
124
|
+
const top = ranked[0];
|
|
125
|
+
if (top === undefined)
|
|
126
|
+
return unresolvedResult('unsupported', request, 0, evidence, conflicts);
|
|
127
|
+
if (ranked[1]?.points === top.points) {
|
|
128
|
+
return unresolvedResult('ambiguous', request, top.points, evidence, conflicts);
|
|
129
|
+
}
|
|
130
|
+
if (top.provider.authenticatedProfile !== undefined) {
|
|
131
|
+
const auth = await top.provider.authenticatedProfile.authenticate(request, [top.profile]);
|
|
132
|
+
const matched = auth.status === 'matched' && auth.profile === top.profile;
|
|
133
|
+
const item = {
|
|
134
|
+
provider: top.provider.id,
|
|
135
|
+
profile: top.profile,
|
|
136
|
+
stage: 'authenticated-profile',
|
|
137
|
+
source: top.provider.authenticatedProfile.algorithm,
|
|
138
|
+
signal: matched ? 'match' : auth.status === 'unavailable' ? 'unknown' : 'mismatch',
|
|
139
|
+
strength: 'strong',
|
|
140
|
+
detail: auth.detail,
|
|
141
|
+
};
|
|
142
|
+
evidence.push(item);
|
|
143
|
+
if (!matched) {
|
|
144
|
+
conflicts.push(item);
|
|
145
|
+
return unresolvedResult('ambiguous', request, top.points, evidence, conflicts);
|
|
146
|
+
}
|
|
147
|
+
top.points += STRENGTH_POINTS.strong;
|
|
148
|
+
}
|
|
149
|
+
const context = { ...request, profile: top.profile };
|
|
150
|
+
for (const reader of top.provider.capabilityReaders) {
|
|
151
|
+
const result = await reader.read(context);
|
|
152
|
+
const item = {
|
|
153
|
+
provider: top.provider.id,
|
|
154
|
+
profile: top.profile,
|
|
155
|
+
stage: 'capability-read',
|
|
156
|
+
source: reader.id,
|
|
157
|
+
signal: result.signal,
|
|
158
|
+
strength: result.strength,
|
|
159
|
+
detail: result.detail,
|
|
160
|
+
};
|
|
161
|
+
evidence.push(item);
|
|
162
|
+
if (result.signal === 'match')
|
|
163
|
+
top.points += STRENGTH_POINTS[result.strength];
|
|
164
|
+
if (result.signal === 'mismatch')
|
|
165
|
+
conflicts.push(item);
|
|
166
|
+
}
|
|
167
|
+
if (scoreOf(top.points) !== 'supported') {
|
|
168
|
+
return unresolvedResult('ambiguous', request, top.points, evidence, conflicts);
|
|
169
|
+
}
|
|
170
|
+
const operations = top.provider.operations(top.profile);
|
|
171
|
+
return {
|
|
172
|
+
status: 'selected',
|
|
173
|
+
provider: top.provider.id,
|
|
174
|
+
profile: top.profile,
|
|
175
|
+
score: 'supported',
|
|
176
|
+
confidence: confidenceOf(top.points),
|
|
177
|
+
evidence,
|
|
178
|
+
conflicts,
|
|
179
|
+
operations,
|
|
180
|
+
writable: operations.access === 'read-write',
|
|
181
|
+
generation: request.generation,
|
|
182
|
+
physicalModemId: request.physicalModemId,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
class GenerationScopedProviderMatcher {
|
|
186
|
+
registry;
|
|
187
|
+
/** Cache mutation is scoped by physical modem, generation, firmware and composition. */
|
|
188
|
+
#cache = new Map();
|
|
189
|
+
constructor(registry) {
|
|
190
|
+
this.registry = registry;
|
|
191
|
+
}
|
|
192
|
+
async match(request) {
|
|
193
|
+
const key = request.physicalModemId;
|
|
194
|
+
const signature = cacheSignature(request, this.registry.revision);
|
|
195
|
+
const cached = this.#cache.get(key);
|
|
196
|
+
if (cached?.signature === signature)
|
|
197
|
+
return cached.result;
|
|
198
|
+
const result = await evaluate(this.registry, request);
|
|
199
|
+
this.#cache.set(key, { signature, result });
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export function createProviderMatcher(registry) {
|
|
204
|
+
return new GenerationScopedProviderMatcher(registry);
|
|
205
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const MODEM_MANAGER_REFUSAL_REASONS: readonly ["unauthorized", "unsupported", "wrong-state", "busy", "not-found", "timed-out", "disconnected", "failed"];
|
|
2
|
+
export type ModemManagerRefusalReason = (typeof MODEM_MANAGER_REFUSAL_REASONS)[number];
|
|
3
|
+
export type ModemManagerRefusal = {
|
|
4
|
+
readonly reason: ModemManagerRefusalReason;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function mapModemManagerError(error: unknown): ModemManagerRefusal;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DisconnectedError, TransportError } from '../../transport/index.js';
|
|
2
|
+
export const MODEM_MANAGER_REFUSAL_REASONS = [
|
|
3
|
+
'unauthorized',
|
|
4
|
+
'unsupported',
|
|
5
|
+
'wrong-state',
|
|
6
|
+
'busy',
|
|
7
|
+
'not-found',
|
|
8
|
+
'timed-out',
|
|
9
|
+
'disconnected',
|
|
10
|
+
'failed',
|
|
11
|
+
];
|
|
12
|
+
function errorIdentity(error) {
|
|
13
|
+
if (!(error instanceof Error))
|
|
14
|
+
return String(error);
|
|
15
|
+
const dbusName = Reflect.get(error, 'dbusName');
|
|
16
|
+
return `${typeof dbusName === 'string' ? dbusName : error.name} ${error.message}`;
|
|
17
|
+
}
|
|
18
|
+
export function mapModemManagerError(error) {
|
|
19
|
+
if (error instanceof DisconnectedError)
|
|
20
|
+
return { reason: 'disconnected', retryable: true };
|
|
21
|
+
const identity = errorIdentity(error);
|
|
22
|
+
if (/Unauthorized|AccessDenied/i.test(identity))
|
|
23
|
+
return { reason: 'unauthorized', retryable: false };
|
|
24
|
+
if (/Unsupported|NotSupported/i.test(identity))
|
|
25
|
+
return { reason: 'unsupported', retryable: false };
|
|
26
|
+
if (/WrongState|InvalidState/i.test(identity))
|
|
27
|
+
return { reason: 'wrong-state', retryable: true };
|
|
28
|
+
if (/InProgress|Busy/i.test(identity))
|
|
29
|
+
return { reason: 'busy', retryable: true };
|
|
30
|
+
if (/NotFound|UnknownObject|UnknownModem/i.test(identity)) {
|
|
31
|
+
return { reason: 'not-found', retryable: false };
|
|
32
|
+
}
|
|
33
|
+
if (error instanceof TransportError && /timed out/i.test(error.message)) {
|
|
34
|
+
return { reason: 'timed-out', retryable: true };
|
|
35
|
+
}
|
|
36
|
+
return { reason: 'failed', retryable: false };
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MmDbusBackend } from '../../backend/index.js';
|
|
2
|
+
import type { ProviderExecutionContext } from '../contracts.js';
|
|
3
|
+
import type { ModemManagerProviderOperations, ModemManagerSnapshotResult } from './types.js';
|
|
4
|
+
export interface GenericOperationsDeps {
|
|
5
|
+
readonly backend: MmDbusBackend;
|
|
6
|
+
readSnapshot(context: ProviderExecutionContext): Promise<ModemManagerSnapshotResult>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createGenericOperations(deps: GenericOperationsDeps): Pick<ModemManagerProviderOperations, 'radio' | 'modes' | 'bands' | 'signal' | 'sim' | 'power'>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { classifyOperationCompletion, defineOperationDescriptor, runtimePath, } from '../../domain/index.js';
|
|
2
|
+
import { bandWriteReadbackMatches, buildBandWriteDescriptor, buildModeWriteDescriptor, encodeModeSelection, matchAdvertisedCombination, sameSelection, selectionOf, } from '../../radio/index.js';
|
|
3
|
+
import { mapModemManagerError } from './errors.js';
|
|
4
|
+
const PROVIDER_ID = 'modemmanager';
|
|
5
|
+
const PROFILE = 'generic-mm';
|
|
6
|
+
function readDescriptor(id) {
|
|
7
|
+
return defineOperationDescriptor({
|
|
8
|
+
id,
|
|
9
|
+
support: { read: { supported: true }, write: { supported: false, reason: 'read-only' } },
|
|
10
|
+
authority: 'provider',
|
|
11
|
+
provider: PROVIDER_ID,
|
|
12
|
+
constraints: { kind: 'unconstrained' },
|
|
13
|
+
livePreconditions: ['modem-present', 'runtime-interface-present'],
|
|
14
|
+
availability: { state: 'available' },
|
|
15
|
+
mutationImpact: 'read',
|
|
16
|
+
retryClass: 'idempotent-read',
|
|
17
|
+
readback: { required: false },
|
|
18
|
+
rollback: { required: false },
|
|
19
|
+
journal: { required: false },
|
|
20
|
+
admission: { required: false },
|
|
21
|
+
evidence: { profiles: [PROFILE], firmware: [] },
|
|
22
|
+
confidence: 'high',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function writeDescriptor(id) {
|
|
26
|
+
return defineOperationDescriptor({
|
|
27
|
+
id,
|
|
28
|
+
support: { read: { supported: true }, write: { supported: true } },
|
|
29
|
+
authority: 'provider',
|
|
30
|
+
provider: PROVIDER_ID,
|
|
31
|
+
constraints: { kind: 'unconstrained' },
|
|
32
|
+
livePreconditions: ['modem-present', 'runtime-interface-present'],
|
|
33
|
+
availability: { state: 'available' },
|
|
34
|
+
mutationImpact: 'disruptive',
|
|
35
|
+
retryClass: 'never',
|
|
36
|
+
readback: { required: false },
|
|
37
|
+
rollback: { required: false },
|
|
38
|
+
journal: { required: true, reason: 'disruptive-radio-write' },
|
|
39
|
+
admission: { required: true, reason: 'provider-mutation' },
|
|
40
|
+
evidence: { profiles: [PROFILE], firmware: [] },
|
|
41
|
+
confidence: 'high',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function applied(context, value) {
|
|
45
|
+
return classifyOperationCompletion({
|
|
46
|
+
operation: 'read',
|
|
47
|
+
completionGeneration: context.generation,
|
|
48
|
+
currentGeneration: context.generation,
|
|
49
|
+
completion: { status: 'applied', value },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function refused(context, reason) {
|
|
53
|
+
return classifyOperationCompletion({
|
|
54
|
+
operation: 'read',
|
|
55
|
+
completionGeneration: context.generation,
|
|
56
|
+
currentGeneration: context.generation,
|
|
57
|
+
completion: { status: 'refused', reason },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function receiptReason(reason) {
|
|
61
|
+
const mapped = mapModemManagerError(new Error(reason));
|
|
62
|
+
return mapped.reason === 'failed' ? reason : mapped.reason;
|
|
63
|
+
}
|
|
64
|
+
export function createGenericOperations(deps) {
|
|
65
|
+
const readField = async (context, capability, select, reason) => {
|
|
66
|
+
const snapshot = await deps.readSnapshot(context);
|
|
67
|
+
if (!snapshot.ok)
|
|
68
|
+
return refused(context, snapshot.reason);
|
|
69
|
+
return snapshot.capabilities[capability]
|
|
70
|
+
? applied(context, select(snapshot))
|
|
71
|
+
: refused(context, reason);
|
|
72
|
+
};
|
|
73
|
+
const readBands = async (context) => {
|
|
74
|
+
const snapshot = await deps.readSnapshot(context);
|
|
75
|
+
if (!snapshot.ok)
|
|
76
|
+
return refused(context, snapshot.reason);
|
|
77
|
+
if (!snapshot.capabilities.bandRead)
|
|
78
|
+
return refused(context, 'band-read-unsupported');
|
|
79
|
+
const result = await deps.backend.readBands(runtimePath(snapshot.modemPath));
|
|
80
|
+
return result.ok
|
|
81
|
+
? applied(context, result.bands)
|
|
82
|
+
: refused(context, receiptReason(result.reason));
|
|
83
|
+
};
|
|
84
|
+
const radioDescriptor = writeDescriptor('modemmanager.radio-modes');
|
|
85
|
+
const radio = {
|
|
86
|
+
descriptor: radioDescriptor,
|
|
87
|
+
describe: () => Promise.resolve(radioDescriptor),
|
|
88
|
+
read: (context) => readField(context, 'modeRead', (snapshot) => snapshot.radio, 'mode-read-unsupported'),
|
|
89
|
+
write: async (context, input) => {
|
|
90
|
+
const snapshot = await deps.readSnapshot(context);
|
|
91
|
+
if (!snapshot.ok)
|
|
92
|
+
return refused(context, snapshot.reason);
|
|
93
|
+
if (!snapshot.capabilities.modeWrite)
|
|
94
|
+
return refused(context, 'mode-write-unsupported');
|
|
95
|
+
const receipt = await deps.backend.setRadioModes(runtimePath(snapshot.modemPath), input);
|
|
96
|
+
if (receipt.status !== 'applied')
|
|
97
|
+
return refused(context, receiptReason(receipt.reason));
|
|
98
|
+
return readField(context, 'modeRead', (next) => next.radio, 'mode-read-unsupported');
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const readModeTruth = async (context) => readField(context, 'modeRead', (snapshot) => snapshot.radio.truth, 'mode-read-unsupported');
|
|
102
|
+
const modes = {
|
|
103
|
+
descriptor: buildModeWriteDescriptor({
|
|
104
|
+
provider: PROVIDER_ID,
|
|
105
|
+
profile: PROFILE,
|
|
106
|
+
truth: {
|
|
107
|
+
current: { state: 'not-reported' },
|
|
108
|
+
supported: { combinations: [], undecodable: [] },
|
|
109
|
+
},
|
|
110
|
+
writeSupported: true,
|
|
111
|
+
}),
|
|
112
|
+
describe: async (context) => {
|
|
113
|
+
const snapshot = await deps.readSnapshot(context);
|
|
114
|
+
return buildModeWriteDescriptor({
|
|
115
|
+
provider: PROVIDER_ID,
|
|
116
|
+
profile: PROFILE,
|
|
117
|
+
truth: snapshot.ok
|
|
118
|
+
? snapshot.radio.truth
|
|
119
|
+
: {
|
|
120
|
+
current: { state: 'not-reported' },
|
|
121
|
+
supported: { combinations: [], undecodable: [] },
|
|
122
|
+
},
|
|
123
|
+
writeSupported: snapshot.ok && snapshot.capabilities.modeWrite,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
read: readModeTruth,
|
|
127
|
+
write: async (context, selection) => {
|
|
128
|
+
const snapshot = await deps.readSnapshot(context);
|
|
129
|
+
if (!snapshot.ok)
|
|
130
|
+
return refused(context, snapshot.reason);
|
|
131
|
+
if (!snapshot.capabilities.modeWrite)
|
|
132
|
+
return refused(context, 'mode-write-unsupported');
|
|
133
|
+
// A selection the modem never advertised is refused outright — never rounded to
|
|
134
|
+
// the nearest advertised one. Substituting is how "prefer 4G" on a marginal cell
|
|
135
|
+
// silently becomes 5G-first, which `five-g-preference.ts` refuses for the same reason.
|
|
136
|
+
if (matchAdvertisedCombination(snapshot.radio.truth.supported, selection) === undefined) {
|
|
137
|
+
return refused(context, 'mode-combination-not-advertised');
|
|
138
|
+
}
|
|
139
|
+
const encoded = encodeModeSelection(selection);
|
|
140
|
+
if (!encoded.ok)
|
|
141
|
+
return refused(context, 'mode-name-unknown');
|
|
142
|
+
const receipt = await deps.backend.setModeCombination(runtimePath(snapshot.modemPath), encoded.allowedMask, encoded.preferredMask);
|
|
143
|
+
if (receipt.status !== 'applied')
|
|
144
|
+
return refused(context, receiptReason(receipt.reason));
|
|
145
|
+
const readback = await readModeTruth(context);
|
|
146
|
+
if (readback.status !== 'applied')
|
|
147
|
+
return readback;
|
|
148
|
+
// An accepted-but-ignored `SetCurrentModes` is indistinguishable from success at
|
|
149
|
+
// the call site, so the descriptor requires a readback and this enforces it.
|
|
150
|
+
return readback.value.current.state === 'reported' &&
|
|
151
|
+
sameSelection(selectionOf(readback.value.current.combination), selection)
|
|
152
|
+
? readback
|
|
153
|
+
: refused(context, 'mode-write-readback-mismatch');
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const bandDescriptorFor = (snapshot) => buildBandWriteDescriptor({
|
|
157
|
+
provider: PROVIDER_ID,
|
|
158
|
+
profile: PROFILE,
|
|
159
|
+
certification: snapshot.ok
|
|
160
|
+
? snapshot.bandCertification
|
|
161
|
+
: {
|
|
162
|
+
required: true,
|
|
163
|
+
satisfied: false,
|
|
164
|
+
reason: 'band-certification-required',
|
|
165
|
+
offerable: [],
|
|
166
|
+
},
|
|
167
|
+
readSupported: snapshot.ok && snapshot.capabilities.bandRead,
|
|
168
|
+
});
|
|
169
|
+
const bands = {
|
|
170
|
+
descriptor: bandDescriptorFor({ ok: false, reason: 'not-found' }),
|
|
171
|
+
describe: async (context) => bandDescriptorFor(await deps.readSnapshot(context)),
|
|
172
|
+
read: readBands,
|
|
173
|
+
write: async (context, input) => {
|
|
174
|
+
const snapshot = await deps.readSnapshot(context);
|
|
175
|
+
if (!snapshot.ok)
|
|
176
|
+
return refused(context, snapshot.reason);
|
|
177
|
+
// The certification gate, second of two. `bandWrite` is already false without a
|
|
178
|
+
// catalog entry, and the descriptor already reads `refused` — but a band lock can
|
|
179
|
+
// take a working uplink off the air, so the call path refuses independently
|
|
180
|
+
// rather than trusting a consumer to have read the descriptor.
|
|
181
|
+
if (!snapshot.bandCertification.satisfied || !snapshot.capabilities.bandWrite)
|
|
182
|
+
return refused(context, 'band-write-certification-required');
|
|
183
|
+
const receipt = await deps.backend.setCurrentBands(runtimePath(snapshot.modemPath), input);
|
|
184
|
+
if (receipt.status !== 'applied')
|
|
185
|
+
return refused(context, receiptReason(receipt.reason));
|
|
186
|
+
const readback = await readBands(context);
|
|
187
|
+
if (readback.status !== 'applied')
|
|
188
|
+
return readback;
|
|
189
|
+
return bandWriteReadbackMatches(input, readback.value)
|
|
190
|
+
? readback
|
|
191
|
+
: refused(context, 'band-write-readback-mismatch');
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const signal = {
|
|
195
|
+
descriptor: readDescriptor('modemmanager.signal'),
|
|
196
|
+
read: (context) => readField(context, 'signalRead', (snapshot) => snapshot.signal, 'signal-read-unsupported'),
|
|
197
|
+
};
|
|
198
|
+
const sim = {
|
|
199
|
+
descriptor: readDescriptor('modemmanager.sim'),
|
|
200
|
+
read: (context) => readField(context, 'simRead', (snapshot) => snapshot.sim, 'sim-read-unsupported'),
|
|
201
|
+
};
|
|
202
|
+
const power = {
|
|
203
|
+
descriptor: readDescriptor('modemmanager.power'),
|
|
204
|
+
read: (context) => readField(context, 'powerRead', (snapshot) => snapshot.power, 'power-read-unsupported'),
|
|
205
|
+
};
|
|
206
|
+
return { radio, modes, bands, signal, sim, power };
|
|
207
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MmLocation } from '../../backend/index.js';
|
|
2
|
+
import type { DbusTransport } from '../../transport/index.js';
|
|
3
|
+
import type { MmUssd } from '../../ussd/index.js';
|
|
4
|
+
import type { ProviderExecutionContext } from '../contracts.js';
|
|
5
|
+
import type { ModemManagerProviderOperations, ModemManagerSnapshotResult } from './types.js';
|
|
6
|
+
export interface ModuleOperationsOptions {
|
|
7
|
+
readonly transport: DbusTransport;
|
|
8
|
+
readonly destination: string;
|
|
9
|
+
readonly location: MmLocation;
|
|
10
|
+
readonly ussd: MmUssd;
|
|
11
|
+
readonly now: () => number;
|
|
12
|
+
readSnapshot(context: ProviderExecutionContext): Promise<ModemManagerSnapshotResult>;
|
|
13
|
+
}
|
|
14
|
+
type ModuleOperationSurface = Pick<ModemManagerProviderOperations, 'location' | 'sms' | 'ussd' | 'initiateUssd' | 'respondUssd' | 'cancelUssd' | 'fccCoverage'>;
|
|
15
|
+
export declare class ModemManagerModuleOperations {
|
|
16
|
+
#private;
|
|
17
|
+
readonly operations: ModuleOperationSurface;
|
|
18
|
+
constructor(options: ModuleOperationsOptions);
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|