@ceralive/modem-control 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +101 -0
- package/dist/backend/transition-preconditions.js +132 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +216 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +27 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +61 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +137 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +171 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +88 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +138 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +6 -0
- package/dist/usb-mode/index.js +16 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +106 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -236
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// The ModemManager USSD adapter — `Modem3gpp.Ussd` driven by the pure session
|
|
2
|
+
// machine in `./session`.
|
|
3
|
+
//
|
|
4
|
+
// Three properties are load-bearing and none of them is obvious from the D-Bus
|
|
5
|
+
// API alone:
|
|
6
|
+
//
|
|
7
|
+
// 1. **Every verb runs through the shared per-modem `ModemActor`.** A USSD
|
|
8
|
+
// session is a single network-side resource per subscriber, so two verbs
|
|
9
|
+
// interleaving on one modem is exactly the double-dialogue the network
|
|
10
|
+
// answers busy. The actor is keyed on the STABLE key, so the serialization
|
|
11
|
+
// survives a replug like every other disruptive op in this package.
|
|
12
|
+
//
|
|
13
|
+
// 2. **An unanswered session is closed at a bound, and the CANCEL is attempted.**
|
|
14
|
+
// A session nobody responds to stays open NETWORK-side; letting it expire in
|
|
15
|
+
// silence would leave the next `Initiate` failing busy for reasons the
|
|
16
|
+
// operator cannot see. The bound closes our machine and best-effort releases
|
|
17
|
+
// the network's — best-effort because a modem that did not answer the last
|
|
18
|
+
// call may not answer this one either, and a timeout must still terminate.
|
|
19
|
+
//
|
|
20
|
+
// 3. **A `closed` session resets the STORED state to idle, while the CALLER is
|
|
21
|
+
// told `closed`.** The machine is deliberately terminal so a finished session
|
|
22
|
+
// cannot be resurrected; the adapter's map is per-modem and long-lived, so it
|
|
23
|
+
// starts each new dialogue from a fresh machine.
|
|
24
|
+
//
|
|
25
|
+
// The carrier's text rides `ussdReply` and NOTHING here logs it. That field name
|
|
26
|
+
// is not cosmetic: `../redact` is key-based, so the name IS what guarantees the
|
|
27
|
+
// value is masked in every receipt, bundle, and log line it can reach.
|
|
28
|
+
import { MM_BUS_NAME } from '../backend/constants.js';
|
|
29
|
+
import { callCancel, callInitiate, callRespond, decodeRepliedState, readUssdState, } from './calls.js';
|
|
30
|
+
import { classifyUssdFailure } from './refusal.js';
|
|
31
|
+
import { readUssdRegistrationFacts } from './registration.js';
|
|
32
|
+
import { IDLE_SESSION, reduceUssdSession, } from './session.js';
|
|
33
|
+
/** A network round-trip. USSD legitimately takes tens of seconds. */
|
|
34
|
+
const DEFAULT_CALL_TIMEOUT_MS = 45_000;
|
|
35
|
+
/** How long a session may sit awaiting an operator response before it is closed. */
|
|
36
|
+
const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 120_000;
|
|
37
|
+
const defaultScheduler = (delayMs, run) => {
|
|
38
|
+
const timer = setTimeout(run, delayMs);
|
|
39
|
+
timer.unref?.();
|
|
40
|
+
return {
|
|
41
|
+
cancel: () => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export class MmUssd {
|
|
47
|
+
#deps;
|
|
48
|
+
#destination;
|
|
49
|
+
#callTimeoutMs;
|
|
50
|
+
#idleTimeoutMs;
|
|
51
|
+
#scheduler;
|
|
52
|
+
#sessions = new Map();
|
|
53
|
+
#timers = new Map();
|
|
54
|
+
constructor(deps) {
|
|
55
|
+
this.#deps = deps;
|
|
56
|
+
this.#destination = deps.destination ?? MM_BUS_NAME;
|
|
57
|
+
this.#callTimeoutMs = deps.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
|
|
58
|
+
this.#idleTimeoutMs = deps.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
|
|
59
|
+
this.#scheduler = deps.scheduler ?? defaultScheduler;
|
|
60
|
+
}
|
|
61
|
+
snapshot(modem) {
|
|
62
|
+
return this.#sessions.get(this.#deps.resolveStableKey(modem)) ?? IDLE_SESSION;
|
|
63
|
+
}
|
|
64
|
+
initiate(modem, ussdCommand) {
|
|
65
|
+
return this.#runVerb(modem, { kind: 'initiate' }, (target) => callInitiate(target, ussdCommand));
|
|
66
|
+
}
|
|
67
|
+
respond(modem, ussdResponse) {
|
|
68
|
+
return this.#runVerb(modem, { kind: 'respond' }, (target) => callRespond(target, ussdResponse));
|
|
69
|
+
}
|
|
70
|
+
cancel(modem) {
|
|
71
|
+
const key = this.#deps.resolveStableKey(modem);
|
|
72
|
+
return this.#deps.actor.run(key, async () => {
|
|
73
|
+
const gate = this.#gate(key, { kind: 'cancel' });
|
|
74
|
+
if (!gate.ok) {
|
|
75
|
+
return gate.result;
|
|
76
|
+
}
|
|
77
|
+
const target = this.#target(modem);
|
|
78
|
+
try {
|
|
79
|
+
await callCancel(target);
|
|
80
|
+
return this.#settle(key, { kind: 'cancelled' });
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return this.#fail(key, modem, error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Drop every timer. A live session on the modem is NOT cancelled by this. */
|
|
88
|
+
stop() {
|
|
89
|
+
for (const timer of this.#timers.values()) {
|
|
90
|
+
timer.cancel();
|
|
91
|
+
}
|
|
92
|
+
this.#timers.clear();
|
|
93
|
+
}
|
|
94
|
+
async #runVerb(modem, event, dispatch) {
|
|
95
|
+
const key = this.#deps.resolveStableKey(modem);
|
|
96
|
+
return this.#deps.actor.run(key, async () => {
|
|
97
|
+
const gate = this.#gate(key, event);
|
|
98
|
+
if (!gate.ok) {
|
|
99
|
+
return gate.result;
|
|
100
|
+
}
|
|
101
|
+
const target = this.#target(modem);
|
|
102
|
+
try {
|
|
103
|
+
const ussdReply = await dispatch(target);
|
|
104
|
+
const sessionState = decodeRepliedState(await readUssdState(target));
|
|
105
|
+
const settled = this.#settle(key, { kind: 'replied', sessionState });
|
|
106
|
+
// A session the network kept open — whether or not it asked a
|
|
107
|
+
// question — is what the idle bound exists to release.
|
|
108
|
+
if (sessionState !== 'released') {
|
|
109
|
+
this.#armIdleTimeout(key, modem);
|
|
110
|
+
}
|
|
111
|
+
return { ...settled, ussdReply };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return this.#fail(key, modem, error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Apply the operator's verb to the machine. A refusal is returned WITHOUT
|
|
120
|
+
* touching the stored state or dialling the bus — a doomed verb must not
|
|
121
|
+
* disturb a live session.
|
|
122
|
+
*/
|
|
123
|
+
#gate(key, event) {
|
|
124
|
+
const current = this.#sessions.get(key) ?? IDLE_SESSION;
|
|
125
|
+
const transition = reduceUssdSession(current, event);
|
|
126
|
+
if (!transition.ok) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
result: { ok: false, snapshot: current, refusal: transition.refusal },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
this.#store(key, transition.snapshot);
|
|
133
|
+
return { ok: true };
|
|
134
|
+
}
|
|
135
|
+
#settle(key, event) {
|
|
136
|
+
const current = this.#sessions.get(key) ?? IDLE_SESSION;
|
|
137
|
+
const transition = reduceUssdSession(current, event);
|
|
138
|
+
if (!transition.ok) {
|
|
139
|
+
return { ok: false, snapshot: current, refusal: transition.refusal };
|
|
140
|
+
}
|
|
141
|
+
this.#store(key, transition.snapshot);
|
|
142
|
+
const closedRefusal = transition.snapshot.refusal;
|
|
143
|
+
return {
|
|
144
|
+
ok: closedRefusal === undefined,
|
|
145
|
+
snapshot: transition.snapshot,
|
|
146
|
+
...(closedRefusal === undefined ? {} : { refusal: closedRefusal }),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async #fail(key, modem, error) {
|
|
150
|
+
const registration = await readUssdRegistrationFacts(this.#deps.transport, this.#destination, modem);
|
|
151
|
+
return this.#settle(key, { kind: 'failed', reason: classifyUssdFailure(error, registration) });
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A session reaching `closed` is REPORTED as closed and STORED as idle, so the
|
|
155
|
+
* next `initiate` starts from a fresh machine rather than the terminal one.
|
|
156
|
+
*/
|
|
157
|
+
#store(key, snapshot) {
|
|
158
|
+
this.#clearTimer(key);
|
|
159
|
+
if (snapshot.state === 'closed') {
|
|
160
|
+
this.#sessions.delete(key);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
this.#sessions.set(key, snapshot);
|
|
164
|
+
}
|
|
165
|
+
this.#deps.onSessionChange?.(key, snapshot);
|
|
166
|
+
}
|
|
167
|
+
#armIdleTimeout(key, modem) {
|
|
168
|
+
this.#clearTimer(key);
|
|
169
|
+
this.#timers.set(key, this.#scheduler(this.#idleTimeoutMs, () => {
|
|
170
|
+
void this.#expire(key, modem);
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
#clearTimer(key) {
|
|
174
|
+
this.#timers.get(key)?.cancel();
|
|
175
|
+
this.#timers.delete(key);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The bound elapsed. The machine closes `timed-out` FIRST — that outcome is
|
|
179
|
+
* the operator's answer whether or not the release lands — and the modem-side
|
|
180
|
+
* cancel is attempted afterwards, best-effort.
|
|
181
|
+
*/
|
|
182
|
+
async #expire(key, modem) {
|
|
183
|
+
await this.#deps.actor.run(key, async () => {
|
|
184
|
+
if (!this.#sessions.has(key)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.#settle(key, { kind: 'timeout' });
|
|
188
|
+
try {
|
|
189
|
+
await callCancel(this.#target(modem));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// The modem that did not answer the dialogue may not answer this
|
|
193
|
+
// either; the session is already closed on our side.
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
#target(modem) {
|
|
198
|
+
return {
|
|
199
|
+
transport: this.#deps.transport,
|
|
200
|
+
destination: this.#destination,
|
|
201
|
+
modem,
|
|
202
|
+
timeoutMs: this.#callTimeoutMs,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every way a USSD verb can fail to do what the operator asked.
|
|
3
|
+
*
|
|
4
|
+
* unsupported — this modem exposes no USSD interface at all.
|
|
5
|
+
* lte-only-unsupported — the modem is registered PS-only (LTE/5G with no CS
|
|
6
|
+
* domain) and the network refused. NOT a device fault.
|
|
7
|
+
* carrier-rejected — the network refused for some other reason.
|
|
8
|
+
* not-registered — there is no network to carry the session.
|
|
9
|
+
* session-busy — a session is already open (locally or network-side).
|
|
10
|
+
* no-session — respond/cancel with nothing open.
|
|
11
|
+
* invalid-state — the verb is wrong for the state the session is in.
|
|
12
|
+
* timeout — the bounded wait elapsed with no answer.
|
|
13
|
+
* transport-failed — the bus call itself failed; the modem never answered.
|
|
14
|
+
*/
|
|
15
|
+
export declare const USSD_REFUSAL_REASONS: readonly ['unsupported', 'lte-only-unsupported', 'carrier-rejected', 'not-registered', 'session-busy', 'no-session', 'invalid-state', 'timeout', 'transport-failed'];
|
|
16
|
+
export type UssdRefusalReason = (typeof USSD_REFUSAL_REASONS)[number];
|
|
17
|
+
/**
|
|
18
|
+
* What the modem is registered on, as far as anyone has looked.
|
|
19
|
+
*
|
|
20
|
+
* `csDomain` is the load-bearing field: `false` means the modem is attached with
|
|
21
|
+
* NO circuit-switched domain available (LTE/5G-SA without CSFB), which is the
|
|
22
|
+
* registration on which a plain USSD refusal is a carrier-policy statement rather
|
|
23
|
+
* than a device one. `undefined` means nobody read it, and is never treated as
|
|
24
|
+
* `false`.
|
|
25
|
+
*/
|
|
26
|
+
export interface UssdRegistrationFacts {
|
|
27
|
+
readonly registered: boolean;
|
|
28
|
+
/** Is a circuit-switched domain available on this registration? */
|
|
29
|
+
readonly csDomain?: boolean;
|
|
30
|
+
/** MM access technologies currently in use, lowercased (`lte`, `5gnr`, …). */
|
|
31
|
+
readonly accessTechnologies?: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True when the modem's registration positively cannot carry a circuit-switched
|
|
35
|
+
* service. Requires evidence in BOTH directions: something must say the CS domain
|
|
36
|
+
* is absent, and every technology in use must be a packet-only one. A modem that
|
|
37
|
+
* reported no technologies at all answers `false` — an empty list is a statement
|
|
38
|
+
* about the read.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isPacketSwitchedOnly(facts: UssdRegistrationFacts): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Classify a failed USSD call. Pure, total, never throws.
|
|
43
|
+
*
|
|
44
|
+
* Order is deliberate: the D-Bus error NAME is the strongest signal and is read
|
|
45
|
+
* first; the message text is consulted only for the names MM overloads; anything
|
|
46
|
+
* unrecognised stays `transport-failed`, which is the honest answer for a failure
|
|
47
|
+
* whose origin cannot be attributed to the network.
|
|
48
|
+
*
|
|
49
|
+
* The PS-only promotion runs LAST and applies to exactly the two reasons that are
|
|
50
|
+
* ambiguous between a device limit and a carrier policy — a `not-registered` or a
|
|
51
|
+
* bus failure is neither, and is left alone.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyUssdFailure(error: unknown, registration?: UssdRegistrationFacts): UssdRefusalReason;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Why a USSD verb did not succeed — one typed vocabulary, and the classifier that
|
|
2
|
+
// maps ModemManager's own failure surface onto it.
|
|
3
|
+
//
|
|
4
|
+
// The point of typing these is that every member names a DIFFERENT thing the
|
|
5
|
+
// operator can do, and the one this module exists for is the honest reporting of
|
|
6
|
+
// a carrier that will not carry USSD at all on the registration the modem has.
|
|
7
|
+
//
|
|
8
|
+
// THE LTE-ONLY CASE, stated plainly. USSD is a circuit-switched supplementary
|
|
9
|
+
// service. A modem registered PS-only — LTE or 5G-SA with no CS domain and no
|
|
10
|
+
// CSFB — can only carry it if the operator deploys USSI (USSD over IMS, 3GPP TS
|
|
11
|
+
// 24.390); many do not, and the modem/network then answers a generic
|
|
12
|
+
// unsupported/failed error that is indistinguishable, on its face, from "this
|
|
13
|
+
// modem has no USSD interface". Reporting that as a device limitation would send
|
|
14
|
+
// an operator hunting for a firmware fix for a network policy, so the classifier
|
|
15
|
+
// takes the modem's REGISTRATION alongside the error and separates the two.
|
|
16
|
+
//
|
|
17
|
+
// Nothing here guesses: `lte-only-unsupported` is claimed ONLY when the
|
|
18
|
+
// registration is positively known to be PS-only. An unknown registration keeps
|
|
19
|
+
// the generic reason, because "we did not look" is not evidence.
|
|
20
|
+
/**
|
|
21
|
+
* Every way a USSD verb can fail to do what the operator asked.
|
|
22
|
+
*
|
|
23
|
+
* unsupported — this modem exposes no USSD interface at all.
|
|
24
|
+
* lte-only-unsupported — the modem is registered PS-only (LTE/5G with no CS
|
|
25
|
+
* domain) and the network refused. NOT a device fault.
|
|
26
|
+
* carrier-rejected — the network refused for some other reason.
|
|
27
|
+
* not-registered — there is no network to carry the session.
|
|
28
|
+
* session-busy — a session is already open (locally or network-side).
|
|
29
|
+
* no-session — respond/cancel with nothing open.
|
|
30
|
+
* invalid-state — the verb is wrong for the state the session is in.
|
|
31
|
+
* timeout — the bounded wait elapsed with no answer.
|
|
32
|
+
* transport-failed — the bus call itself failed; the modem never answered.
|
|
33
|
+
*/
|
|
34
|
+
export const USSD_REFUSAL_REASONS = [
|
|
35
|
+
'unsupported',
|
|
36
|
+
'lte-only-unsupported',
|
|
37
|
+
'carrier-rejected',
|
|
38
|
+
'not-registered',
|
|
39
|
+
'session-busy',
|
|
40
|
+
'no-session',
|
|
41
|
+
'invalid-state',
|
|
42
|
+
'timeout',
|
|
43
|
+
'transport-failed',
|
|
44
|
+
];
|
|
45
|
+
/** Access technologies that carry no circuit-switched domain of their own. */
|
|
46
|
+
const PACKET_ONLY_RATS = new Set([
|
|
47
|
+
'lte',
|
|
48
|
+
'5gnr',
|
|
49
|
+
'lte-cat-m',
|
|
50
|
+
'lte-nb-iot',
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* True when the modem's registration positively cannot carry a circuit-switched
|
|
54
|
+
* service. Requires evidence in BOTH directions: something must say the CS domain
|
|
55
|
+
* is absent, and every technology in use must be a packet-only one. A modem that
|
|
56
|
+
* reported no technologies at all answers `false` — an empty list is a statement
|
|
57
|
+
* about the read.
|
|
58
|
+
*/
|
|
59
|
+
export function isPacketSwitchedOnly(facts) {
|
|
60
|
+
if (!facts.registered) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (facts.csDomain !== false) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const rats = facts.accessTechnologies;
|
|
67
|
+
if (rats === undefined || rats.length === 0) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return rats.every((rat) => PACKET_ONLY_RATS.has(rat.toLowerCase()));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* ModemManager D-Bus error names this classifier recognises, matched on the
|
|
74
|
+
* SUFFIX after the last dot so a future `...Error.Core.Unsupported` regrouping
|
|
75
|
+
* does not silently fall through to `transport-failed`.
|
|
76
|
+
*/
|
|
77
|
+
const ERROR_SUFFIX_REASONS = new Map([
|
|
78
|
+
['Unsupported', 'unsupported'],
|
|
79
|
+
['NotSupported', 'unsupported'],
|
|
80
|
+
['InProgress', 'session-busy'],
|
|
81
|
+
['NoNetwork', 'not-registered'],
|
|
82
|
+
['NotRegistered', 'not-registered'],
|
|
83
|
+
['Timeout', 'timeout'],
|
|
84
|
+
['Aborted', 'carrier-rejected'],
|
|
85
|
+
['Failed', 'carrier-rejected'],
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* Message fragments that identify a refusal no error NAME distinguishes. MM
|
|
89
|
+
* folds several modem answers into `Core.Failed`, so the text is the only signal
|
|
90
|
+
* separating "the network said no" from "the bus call broke".
|
|
91
|
+
*/
|
|
92
|
+
const MESSAGE_REASONS = [
|
|
93
|
+
[/ussd.*(?:not supported|unsupported)/i, 'unsupported'],
|
|
94
|
+
[/(?:session|operation) (?:already )?(?:active|in progress)/i, 'session-busy'],
|
|
95
|
+
[/no (?:active )?ussd session/i, 'no-session'],
|
|
96
|
+
[/not registered|no network/i, 'not-registered'],
|
|
97
|
+
[/timed? ?out/i, 'timeout'],
|
|
98
|
+
[/rejected|refused|denied|network error/i, 'carrier-rejected'],
|
|
99
|
+
];
|
|
100
|
+
function errorName(error) {
|
|
101
|
+
if (typeof error !== 'object' || error === null) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const name = error.dbusName;
|
|
105
|
+
if (typeof name === 'string' && name.includes('.')) {
|
|
106
|
+
return name;
|
|
107
|
+
}
|
|
108
|
+
const fallback = error.name;
|
|
109
|
+
return typeof fallback === 'string' && fallback.includes('.') ? fallback : undefined;
|
|
110
|
+
}
|
|
111
|
+
function errorMessage(error) {
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
return error.message;
|
|
114
|
+
}
|
|
115
|
+
if (typeof error === 'object' && error !== null) {
|
|
116
|
+
const message = error.message;
|
|
117
|
+
if (typeof message === 'string') {
|
|
118
|
+
return message;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return String(error);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Classify a failed USSD call. Pure, total, never throws.
|
|
125
|
+
*
|
|
126
|
+
* Order is deliberate: the D-Bus error NAME is the strongest signal and is read
|
|
127
|
+
* first; the message text is consulted only for the names MM overloads; anything
|
|
128
|
+
* unrecognised stays `transport-failed`, which is the honest answer for a failure
|
|
129
|
+
* whose origin cannot be attributed to the network.
|
|
130
|
+
*
|
|
131
|
+
* The PS-only promotion runs LAST and applies to exactly the two reasons that are
|
|
132
|
+
* ambiguous between a device limit and a carrier policy — a `not-registered` or a
|
|
133
|
+
* bus failure is neither, and is left alone.
|
|
134
|
+
*/
|
|
135
|
+
export function classifyUssdFailure(error, registration = { registered: false }) {
|
|
136
|
+
const name = errorName(error);
|
|
137
|
+
const suffix = name?.slice(name.lastIndexOf('.') + 1);
|
|
138
|
+
const message = errorMessage(error);
|
|
139
|
+
let reason = suffix === undefined ? undefined : ERROR_SUFFIX_REASONS.get(suffix);
|
|
140
|
+
// `Core.Failed` is MM's catch-all, so its message is worth more than its name.
|
|
141
|
+
if (reason === undefined || reason === 'carrier-rejected') {
|
|
142
|
+
for (const [pattern, mapped] of MESSAGE_REASONS) {
|
|
143
|
+
if (pattern.test(message)) {
|
|
144
|
+
reason = mapped;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (reason === undefined) {
|
|
150
|
+
return 'transport-failed';
|
|
151
|
+
}
|
|
152
|
+
const ambiguous = reason === 'unsupported' || reason === 'carrier-rejected';
|
|
153
|
+
return ambiguous && isPacketSwitchedOnly(registration) ? 'lte-only-unsupported' : reason;
|
|
154
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DecodedManagedObjects } from '../backend/managed-objects.js';
|
|
2
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
3
|
+
import type { UssdRegistrationFacts } from './refusal.js';
|
|
4
|
+
/** Decode `Modem.AccessTechnologies` into the technology names in use. */
|
|
5
|
+
export declare function decodeAccessTechnologies(mask: number): readonly string[];
|
|
6
|
+
/**
|
|
7
|
+
* Derive the registration facts from an already-fetched managed-objects tree.
|
|
8
|
+
*
|
|
9
|
+
* Pure, so the derivation is testable against a fixture tree without a bus — the
|
|
10
|
+
* `sim-unlock.ts` split between "read the tree" and "decide from the tree".
|
|
11
|
+
*/
|
|
12
|
+
export declare function registrationFactsFromTree(tree: DecodedManagedObjects, modemPath: string): UssdRegistrationFacts;
|
|
13
|
+
/** The unread default — every field withheld, so nothing can be claimed from it. */
|
|
14
|
+
export declare const UNKNOWN_REGISTRATION: UssdRegistrationFacts;
|
|
15
|
+
/**
|
|
16
|
+
* Read the registration facts for one modem. Fail-soft: a tree we could not fetch
|
|
17
|
+
* yields {@link UNKNOWN_REGISTRATION}, which can only ever make the refusal LESS
|
|
18
|
+
* specific.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readUssdRegistrationFacts(transport: DbusTransport, destination: string, modemPath: string): Promise<UssdRegistrationFacts>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Reading the registration facts the USSD refusal classifier needs.
|
|
2
|
+
//
|
|
3
|
+
// ModemManager publishes NO "is a circuit-switched domain available" property, so
|
|
4
|
+
// the fact has to be DERIVED — and the derivation is worth stating, because it is
|
|
5
|
+
// the difference between telling an operator their modem cannot do USSD and
|
|
6
|
+
// telling them their carrier will not carry it on this registration:
|
|
7
|
+
//
|
|
8
|
+
// * `Modem.AccessTechnologies` is a bitmask. When every bit in use is a
|
|
9
|
+
// packet-only radio (LTE, 5G-NR, LTE-M, NB-IoT) the modem is not camped on a
|
|
10
|
+
// circuit-switched radio at all.
|
|
11
|
+
// * `Modem3gpp.RegistrationState` still overrides that, because CS FALLBACK is
|
|
12
|
+
// exactly the case where an LTE-camped modem CAN reach the CS domain. MM has
|
|
13
|
+
// two states that say so outright — `HOME_CSFB_NOT_PREFERRED` (9) and
|
|
14
|
+
// `ROAMING_CSFB_NOT_PREFERRED` (10) — and a modem in either of them is
|
|
15
|
+
// reported CS-capable regardless of its radio.
|
|
16
|
+
//
|
|
17
|
+
// Both reads are best-effort and NEVER throw: a fact nobody could read is left
|
|
18
|
+
// `undefined`, which the classifier treats as "we did not look" rather than as a
|
|
19
|
+
// negative. That asymmetry is the whole safety property — an unread registration
|
|
20
|
+
// can only ever produce the generic refusal, never the more specific claim.
|
|
21
|
+
import { MODEM_IFACE, MODEM3GPP_IFACE } from '../backend/constants.js';
|
|
22
|
+
import { fetchManagedObjects, findInterface, numberProp, } from '../backend/managed-objects.js';
|
|
23
|
+
/** `MMModemAccessTechnology` bits this module names. */
|
|
24
|
+
const ACCESS_TECHNOLOGY_BITS = [
|
|
25
|
+
[1 << 1, 'gsm'],
|
|
26
|
+
[1 << 2, 'gsm-compact'],
|
|
27
|
+
[1 << 3, 'gprs'],
|
|
28
|
+
[1 << 4, 'edge'],
|
|
29
|
+
[1 << 5, 'umts'],
|
|
30
|
+
[1 << 6, 'hsdpa'],
|
|
31
|
+
[1 << 7, 'hsupa'],
|
|
32
|
+
[1 << 8, 'hspa'],
|
|
33
|
+
[1 << 9, 'hspa-plus'],
|
|
34
|
+
[1 << 10, '1xrtt'],
|
|
35
|
+
[1 << 11, 'evdo0'],
|
|
36
|
+
[1 << 12, 'evdoa'],
|
|
37
|
+
[1 << 13, 'evdob'],
|
|
38
|
+
[1 << 14, 'lte'],
|
|
39
|
+
[1 << 15, '5gnr'],
|
|
40
|
+
[1 << 16, 'lte-cat-m'],
|
|
41
|
+
[1 << 17, 'lte-nb-iot'],
|
|
42
|
+
];
|
|
43
|
+
/** `MMModem3gppRegistrationState` values that mean the modem is on a network. */
|
|
44
|
+
const REGISTERED_STATES = new Set([1, 5, 6, 7, 9, 10]);
|
|
45
|
+
/** …and the two that positively advertise a circuit-switched fallback. */
|
|
46
|
+
const CSFB_STATES = new Set([9, 10]);
|
|
47
|
+
/** Decode `Modem.AccessTechnologies` into the technology names in use. */
|
|
48
|
+
export function decodeAccessTechnologies(mask) {
|
|
49
|
+
const names = [];
|
|
50
|
+
for (const [bit, name] of ACCESS_TECHNOLOGY_BITS) {
|
|
51
|
+
if ((mask & bit) !== 0) {
|
|
52
|
+
names.push(name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return names;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Derive the registration facts from an already-fetched managed-objects tree.
|
|
59
|
+
*
|
|
60
|
+
* Pure, so the derivation is testable against a fixture tree without a bus — the
|
|
61
|
+
* `sim-unlock.ts` split between "read the tree" and "decide from the tree".
|
|
62
|
+
*/
|
|
63
|
+
export function registrationFactsFromTree(tree, modemPath) {
|
|
64
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
65
|
+
const threeGpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
|
|
66
|
+
const registrationState = numberProp(threeGpp, 'RegistrationState');
|
|
67
|
+
const accessMask = numberProp(modem, 'AccessTechnologies');
|
|
68
|
+
const registered = registrationState !== undefined && REGISTERED_STATES.has(registrationState);
|
|
69
|
+
const technologies = accessMask === undefined ? undefined : decodeAccessTechnologies(accessMask);
|
|
70
|
+
// A CSFB registration is CS-capable outright. Otherwise the domain is only
|
|
71
|
+
// declared ABSENT when the radios in use were actually read and are all
|
|
72
|
+
// packet-only; an unread mask leaves the field undefined on purpose.
|
|
73
|
+
let csDomain;
|
|
74
|
+
if (registrationState !== undefined && CSFB_STATES.has(registrationState)) {
|
|
75
|
+
csDomain = true;
|
|
76
|
+
}
|
|
77
|
+
else if (technologies !== undefined && technologies.length > 0) {
|
|
78
|
+
csDomain = technologies.some((rat) => rat !== 'lte' && rat !== '5gnr' && rat !== 'lte-cat-m' && rat !== 'lte-nb-iot');
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
registered,
|
|
82
|
+
...(csDomain === undefined ? {} : { csDomain }),
|
|
83
|
+
...(technologies === undefined ? {} : { accessTechnologies: technologies }),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** The unread default — every field withheld, so nothing can be claimed from it. */
|
|
87
|
+
export const UNKNOWN_REGISTRATION = { registered: false };
|
|
88
|
+
/**
|
|
89
|
+
* Read the registration facts for one modem. Fail-soft: a tree we could not fetch
|
|
90
|
+
* yields {@link UNKNOWN_REGISTRATION}, which can only ever make the refusal LESS
|
|
91
|
+
* specific.
|
|
92
|
+
*/
|
|
93
|
+
export async function readUssdRegistrationFacts(transport, destination, modemPath) {
|
|
94
|
+
try {
|
|
95
|
+
const tree = await fetchManagedObjects(transport, destination);
|
|
96
|
+
return registrationFactsFromTree(tree, modemPath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return UNKNOWN_REGISTRATION;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { UssdRefusalReason } from './refusal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Session states.
|
|
4
|
+
*
|
|
5
|
+
* Three of them (`idle`, `active`, `awaiting-reply`) mirror MM's own
|
|
6
|
+
* `MMModem3gppUssdSessionState`; the rest are LOCAL in-flight states, because MM
|
|
7
|
+
* has no state for "we dispatched a call and the reply has not landed". Without
|
|
8
|
+
* them a second `initiate` racing the first would be judged against `idle` and
|
|
9
|
+
* allowed through, which is exactly the double-open the network answers busy.
|
|
10
|
+
*/
|
|
11
|
+
export declare const USSD_SESSION_STATES: readonly [
|
|
12
|
+
/** No session. MM `IDLE`. */
|
|
13
|
+
'idle',
|
|
14
|
+
/** `Initiate` dispatched, reply outstanding. Local. */
|
|
15
|
+
'initiating',
|
|
16
|
+
/** Network answered and the session is open with nothing pending. MM `ACTIVE`. */
|
|
17
|
+
'active',
|
|
18
|
+
/** Network asked a question; a `Respond` is required. MM `USER_RESPONSE`. */
|
|
19
|
+
'awaiting-reply',
|
|
20
|
+
/** `Respond` dispatched, reply outstanding. Local. */
|
|
21
|
+
'responding',
|
|
22
|
+
/** `Cancel` dispatched, confirmation outstanding. Local. */
|
|
23
|
+
'cancelling',
|
|
24
|
+
/** Terminal for this session object. A new session starts from a new machine. */
|
|
25
|
+
'closed'];
|
|
26
|
+
export type UssdSessionState = (typeof USSD_SESSION_STATES)[number];
|
|
27
|
+
/** How a session that reached `closed` got there. */
|
|
28
|
+
export declare const USSD_SESSION_OUTCOMES: readonly [
|
|
29
|
+
/** The network completed the dialogue and released the session. */
|
|
30
|
+
'completed',
|
|
31
|
+
/** The operator cancelled it. */
|
|
32
|
+
'cancelled',
|
|
33
|
+
/** No answer within the bound; the machine closed it locally. */
|
|
34
|
+
'timed-out',
|
|
35
|
+
/** The network or the modem refused. `refusal` names which. */
|
|
36
|
+
'failed'];
|
|
37
|
+
export type UssdSessionOutcome = (typeof USSD_SESSION_OUTCOMES)[number];
|
|
38
|
+
/** MM's post-call session state, decoded. */
|
|
39
|
+
export type UssdRepliedState = 'awaiting-reply' | 'active' | 'released';
|
|
40
|
+
export type UssdSessionEvent =
|
|
41
|
+
/** The operator asked to open a session. */
|
|
42
|
+
{
|
|
43
|
+
readonly kind: 'initiate';
|
|
44
|
+
}
|
|
45
|
+
/** The operator answered a network prompt. */
|
|
46
|
+
| {
|
|
47
|
+
readonly kind: 'respond';
|
|
48
|
+
}
|
|
49
|
+
/** The operator asked to close the session. */
|
|
50
|
+
| {
|
|
51
|
+
readonly kind: 'cancel';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The network answered an `Initiate`/`Respond`. `sessionState` is MM's own
|
|
55
|
+
* post-call `Modem3gpp.Ussd.State`, decoded: the network either wants an
|
|
56
|
+
* answer, is holding the session open with nothing pending, or released it.
|
|
57
|
+
*/
|
|
58
|
+
| {
|
|
59
|
+
readonly kind: 'replied';
|
|
60
|
+
readonly sessionState: UssdRepliedState;
|
|
61
|
+
}
|
|
62
|
+
/** A `Cancel` was confirmed by the modem. */
|
|
63
|
+
| {
|
|
64
|
+
readonly kind: 'cancelled';
|
|
65
|
+
}
|
|
66
|
+
/** The network released the session without our asking (notification path). */
|
|
67
|
+
| {
|
|
68
|
+
readonly kind: 'network-released';
|
|
69
|
+
}
|
|
70
|
+
/** The bounded wait elapsed with no answer. */
|
|
71
|
+
| {
|
|
72
|
+
readonly kind: 'timeout';
|
|
73
|
+
}
|
|
74
|
+
/** The call failed. The reason is carried onto the terminal state verbatim. */
|
|
75
|
+
| {
|
|
76
|
+
readonly kind: 'failed';
|
|
77
|
+
readonly reason: UssdRefusalReason;
|
|
78
|
+
};
|
|
79
|
+
export interface UssdSessionSnapshot {
|
|
80
|
+
readonly state: UssdSessionState;
|
|
81
|
+
/** Present only at `closed`. */
|
|
82
|
+
readonly outcome?: UssdSessionOutcome;
|
|
83
|
+
/** Present only at `closed` with outcome `failed`. */
|
|
84
|
+
readonly refusal?: UssdRefusalReason;
|
|
85
|
+
}
|
|
86
|
+
export type UssdTransition = {
|
|
87
|
+
readonly ok: true;
|
|
88
|
+
readonly snapshot: UssdSessionSnapshot;
|
|
89
|
+
}
|
|
90
|
+
/** The verb is not legal in this state, and the machine did NOT move. */
|
|
91
|
+
| {
|
|
92
|
+
readonly ok: false;
|
|
93
|
+
readonly refusal: UssdRefusalReason;
|
|
94
|
+
};
|
|
95
|
+
export declare const IDLE_SESSION: UssdSessionSnapshot;
|
|
96
|
+
/**
|
|
97
|
+
* Apply one event. TOTAL: every (state, event) pair has an answer, and an answer
|
|
98
|
+
* is either a new snapshot or a typed refusal that leaves the machine untouched.
|
|
99
|
+
*
|
|
100
|
+
* A `closed` machine accepts NOTHING — not even another `cancel`. Re-opening a
|
|
101
|
+
* terminal session would hide the fact that the previous one ended, and the
|
|
102
|
+
* cost of a fresh machine is one object.
|
|
103
|
+
*/
|
|
104
|
+
export declare function reduceUssdSession(snapshot: UssdSessionSnapshot, event: UssdSessionEvent): UssdTransition;
|
|
105
|
+
/** True while the session still holds a network dialogue open. */
|
|
106
|
+
export declare function isUssdSessionOpen(snapshot: UssdSessionSnapshot): boolean;
|