@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,24 @@
|
|
|
1
|
+
/** The well-known bus name ModemManager owns. */
|
|
2
|
+
export declare const MM_BUS_NAME = "org.freedesktop.ModemManager1";
|
|
3
|
+
/** The root ObjectManager object path. */
|
|
4
|
+
export declare const MM_ROOT_PATH = "/org/freedesktop/ModemManager1";
|
|
5
|
+
/** `org.freedesktop.DBus.ObjectManager` — `GetManagedObjects`, `InterfacesAdded/Removed`. */
|
|
6
|
+
export declare const OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager";
|
|
7
|
+
/** `org.freedesktop.DBus.Properties` — `PropertiesChanged`. */
|
|
8
|
+
export declare const PROPERTIES_IFACE = "org.freedesktop.DBus.Properties";
|
|
9
|
+
/** The root manager interface — `InhibitDevice`, `ScanDevices`, `Version`. */
|
|
10
|
+
export declare const MM_MANAGER_IFACE = "org.freedesktop.ModemManager1";
|
|
11
|
+
/** The core `Modem` interface. */
|
|
12
|
+
export declare const MODEM_IFACE = "org.freedesktop.ModemManager1.Modem";
|
|
13
|
+
/** The separate `Modem.Modem3gpp` interface (never merged into `Modem`). */
|
|
14
|
+
export declare const MODEM3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp";
|
|
15
|
+
/** `Modem.Modem3gpp.Ussd` — a SEPARATE interface a modem may omit entirely. */
|
|
16
|
+
export declare const MODEM3GPP_USSD_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd";
|
|
17
|
+
/** The `Modem.Location` interface — GNSS capabilities, `Setup`, `GetLocation`. */
|
|
18
|
+
export declare const MODEM_LOCATION_IFACE = "org.freedesktop.ModemManager1.Modem.Location";
|
|
19
|
+
/** A SIM object's `Sim` interface (SIMs are separate `/SIM/<n>` objects). */
|
|
20
|
+
export declare const SIM_IFACE = "org.freedesktop.ModemManager1.Sim";
|
|
21
|
+
/** The bus daemon itself — `NameOwnerChanged`, `GetNameOwner`. */
|
|
22
|
+
export declare const DBUS_IFACE = "org.freedesktop.DBus";
|
|
23
|
+
export declare const DBUS_PATH = "/org/freedesktop/DBus";
|
|
24
|
+
export declare const DBUS_DESTINATION = "org.freedesktop.DBus";
|
|
@@ -4,31 +4,26 @@
|
|
|
4
4
|
// than imported from the A2.3 test fake) on purpose: `control/test-support/` is not
|
|
5
5
|
// published in the npm package, so `src` must never depend on it — the observer that
|
|
6
6
|
// SHIPS owns its own copy of the constants it needs.
|
|
7
|
-
|
|
8
7
|
/** The well-known bus name ModemManager owns. */
|
|
9
8
|
export const MM_BUS_NAME = 'org.freedesktop.ModemManager1';
|
|
10
|
-
|
|
11
9
|
/** The root ObjectManager object path. */
|
|
12
10
|
export const MM_ROOT_PATH = '/org/freedesktop/ModemManager1';
|
|
13
|
-
|
|
14
11
|
/** `org.freedesktop.DBus.ObjectManager` — `GetManagedObjects`, `InterfacesAdded/Removed`. */
|
|
15
12
|
export const OBJECT_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager';
|
|
16
|
-
|
|
17
13
|
/** `org.freedesktop.DBus.Properties` — `PropertiesChanged`. */
|
|
18
14
|
export const PROPERTIES_IFACE = 'org.freedesktop.DBus.Properties';
|
|
19
|
-
|
|
20
15
|
/** The root manager interface — `InhibitDevice`, `ScanDevices`, `Version`. */
|
|
21
16
|
export const MM_MANAGER_IFACE = 'org.freedesktop.ModemManager1';
|
|
22
|
-
|
|
23
17
|
/** The core `Modem` interface. */
|
|
24
18
|
export const MODEM_IFACE = 'org.freedesktop.ModemManager1.Modem';
|
|
25
|
-
|
|
26
19
|
/** The separate `Modem.Modem3gpp` interface (never merged into `Modem`). */
|
|
27
20
|
export const MODEM3GPP_IFACE = 'org.freedesktop.ModemManager1.Modem.Modem3gpp';
|
|
28
|
-
|
|
21
|
+
/** `Modem.Modem3gpp.Ussd` — a SEPARATE interface a modem may omit entirely. */
|
|
22
|
+
export const MODEM3GPP_USSD_IFACE = `${MODEM3GPP_IFACE}.Ussd`;
|
|
23
|
+
/** The `Modem.Location` interface — GNSS capabilities, `Setup`, `GetLocation`. */
|
|
24
|
+
export const MODEM_LOCATION_IFACE = 'org.freedesktop.ModemManager1.Modem.Location';
|
|
29
25
|
/** A SIM object's `Sim` interface (SIMs are separate `/SIM/<n>` objects). */
|
|
30
26
|
export const SIM_IFACE = 'org.freedesktop.ModemManager1.Sim';
|
|
31
|
-
|
|
32
27
|
/** The bus daemon itself — `NameOwnerChanged`, `GetNameOwner`. */
|
|
33
28
|
export const DBUS_IFACE = 'org.freedesktop.DBus';
|
|
34
29
|
export const DBUS_PATH = '/org/freedesktop/DBus';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode/index.js';
|
|
2
|
+
import type { UsbDeviceSnapshot } from './usb-device-snapshot.js';
|
|
3
|
+
export type { UsbDeviceSnapshot, UsbInterface } from './usb-device-snapshot.js';
|
|
4
|
+
/** The four device classes. `pending-modeswitch` is distinct from `unmanaged`. */
|
|
5
|
+
export type DeviceClass = 'mm-managed' | 'router-mode' | 'unmanaged' | 'pending-modeswitch';
|
|
6
|
+
export type UsbNetClass = 'mm-managed' | 'router-cellular' | 'wired-ethernet' | 'unknown';
|
|
7
|
+
/** A classification plus a human-readable reason (always populated). */
|
|
8
|
+
export interface DeviceClassification {
|
|
9
|
+
readonly deviceClass: DeviceClass;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
}
|
|
12
|
+
export interface UsbNetClassification {
|
|
13
|
+
readonly deviceClass: UsbNetClass;
|
|
14
|
+
readonly reason: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const CELLULAR_USB_VENDOR_IDS: ReadonlyMap<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Classify one USB device from its udev/sysfs snapshot. Precedence:
|
|
19
|
+
* 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
|
|
20
|
+
* 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
|
|
21
|
+
* 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
|
|
22
|
+
* 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classifyDevice(snapshot: UsbDeviceSnapshot): DeviceClassification;
|
|
25
|
+
export declare function cellularVendorName(vendorId: string): string | undefined;
|
|
26
|
+
export declare function cellularEvidence(device: UsbDeviceSnapshot): string | undefined;
|
|
27
|
+
export declare function classifyUsbNetDevice(device: UsbDeviceSnapshot): UsbNetClassification;
|
|
28
|
+
export declare function publishesGenericIdentity(device: UsbDeviceSnapshot): boolean;
|
|
29
|
+
export declare function vendorLabel(device: UsbDeviceSnapshot): string;
|
|
30
|
+
export declare function modelLabel(device: UsbDeviceSnapshot): string;
|
|
31
|
+
export declare function unitDiscriminator(device: UsbDeviceSnapshot): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Derive the USB composition MODE a device is currently in (for a transition's
|
|
34
|
+
* postcondition). Distinct from `classifyDevice`: this reads the data-plane
|
|
35
|
+
* composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
|
|
36
|
+
* data interface being `ecm-ncm` only when a control/vendor port accompanies it
|
|
37
|
+
* (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
|
|
38
|
+
* `qmi`. Returns `undefined` when nothing recognizable is present.
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectUsbMode(snapshot: UsbDeviceSnapshot): CanonicalUsbMode | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a device presents (at least) the descriptors a catalog transition expects
|
|
43
|
+
* after re-enumeration — the descriptor half of the postcondition. Every expected
|
|
44
|
+
* interface triple must be present and `bDeviceClass` must match.
|
|
45
|
+
*/
|
|
46
|
+
export declare function descriptorsMatch(snapshot: UsbDeviceSnapshot, expected: ExpectedDescriptors): boolean;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// Device classification — deciding, from a udev/sysfs snapshot alone, whether a USB
|
|
2
|
+
// device is a ModemManager-manageable modem, a router-ethernet uplink MM cannot
|
|
3
|
+
// control, a modem still in mass-storage installer mode, or something we simply do
|
|
4
|
+
// not recognize.
|
|
5
|
+
//
|
|
6
|
+
// THE HONESTY RULE (draft §gap sweep, matrix §1): the classifier NEVER guesses. An
|
|
7
|
+
// ambiguous descriptor set returns `unmanaged` with a truthful reason, not a
|
|
8
|
+
// confident-sounding wrong class. A control-port presence — not a vendor id — decides
|
|
9
|
+
// `mm-managed`; a bare vendor-specific interface with no recognized driver is NOT a
|
|
10
|
+
// modem. `pending-modeswitch` is a DISTINCT state (a modem installer awaiting
|
|
11
|
+
// `usb_modeswitch`), never conflated with `unmanaged`.
|
|
12
|
+
//
|
|
13
|
+
// SCOPE — USB ONLY: the whole input here is a `UsbDeviceSnapshot`, a udev/sysfs view of a
|
|
14
|
+
// USB device. PCIe modems are out of scope by construction and get NO entry in this model —
|
|
15
|
+
// a PCI `vendor:device` pair is never smuggled in as a pseudo-USB identity. The Fibocom
|
|
16
|
+
// FM350 is the canonical example: it is a PCIe module (PCI `14c3:4d75`, bound by the
|
|
17
|
+
// `mtk_t7xx` driver on the `wwan`/`net` subsystems, with no USB VID:PID), so it is
|
|
18
|
+
// documented-deferred rather than classified here. See `docs/FM350-DECISION.md` for the
|
|
19
|
+
// evidence and the three-gate ledger behind that decision.
|
|
20
|
+
// USB-IF class / subclass / protocol codes used below.
|
|
21
|
+
const CLASS_COMM = 0x02; // CDC communications (control interface)
|
|
22
|
+
const CLASS_CDC_DATA = 0x0a;
|
|
23
|
+
const CLASS_MASS_STORAGE = 0x08;
|
|
24
|
+
const CLASS_WIRELESS = 0xe0; // wireless controller (RNDIS lives here)
|
|
25
|
+
const CLASS_VENDOR = 0xff;
|
|
26
|
+
const SUB_ACM = 0x02; // abstract control model (AT commands)
|
|
27
|
+
const SUB_ECM = 0x06;
|
|
28
|
+
const SUB_NCM = 0x0d;
|
|
29
|
+
const SUB_MBIM = 0x0e;
|
|
30
|
+
const SUB_RNDIS_WIRELESS = 0x01;
|
|
31
|
+
const PROTO_VENDOR = 0xff;
|
|
32
|
+
const PROTO_RNDIS = 0x03;
|
|
33
|
+
const QMI_DRIVERS = new Set(['qmi_wwan']);
|
|
34
|
+
const AT_DRIVERS = new Set(['option', 'qcserial', 'cdc_acm']);
|
|
35
|
+
const ECM_NCM_DRIVERS = new Set(['cdc_ether', 'cdc_ncm']);
|
|
36
|
+
const RNDIS_DRIVERS = new Set(['rndis_host']);
|
|
37
|
+
const STORAGE_DRIVERS = new Set(['usb-storage', 'uas']);
|
|
38
|
+
export const CELLULAR_USB_VENDOR_IDS = new Map([
|
|
39
|
+
['05c6', 'Qualcomm'],
|
|
40
|
+
['0af0', 'Option'],
|
|
41
|
+
['1199', 'Sierra Wireless'],
|
|
42
|
+
['12d1', 'Huawei'],
|
|
43
|
+
['1546', 'u-blox'],
|
|
44
|
+
['19d2', 'ZTE'],
|
|
45
|
+
['1bbb', 'TCL/Alcatel'],
|
|
46
|
+
['1c9e', 'Longcheer'],
|
|
47
|
+
['1e0e', 'SIMCom'],
|
|
48
|
+
['2c7c', 'Quectel'],
|
|
49
|
+
['2cb7', 'Fibocom'],
|
|
50
|
+
['413c', 'Dell'],
|
|
51
|
+
]);
|
|
52
|
+
function isMbimControl(i) {
|
|
53
|
+
return i.interfaceClass === CLASS_COMM && i.interfaceSubClass === SUB_MBIM;
|
|
54
|
+
}
|
|
55
|
+
function isQmiControl(i) {
|
|
56
|
+
return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && QMI_DRIVERS.has(i.driver);
|
|
57
|
+
}
|
|
58
|
+
function isAtControl(i) {
|
|
59
|
+
// Standard CDC-ACM AT port (protocol 0xff on 0x02/0x02 is RNDIS, not AT).
|
|
60
|
+
if (i.interfaceClass === CLASS_COMM &&
|
|
61
|
+
i.interfaceSubClass === SUB_ACM &&
|
|
62
|
+
i.interfaceProtocol !== PROTO_VENDOR) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// Vendor-specific serial port bound to a known AT driver.
|
|
66
|
+
return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && AT_DRIVERS.has(i.driver);
|
|
67
|
+
}
|
|
68
|
+
function isRndis(i) {
|
|
69
|
+
if (i.interfaceClass === CLASS_WIRELESS &&
|
|
70
|
+
i.interfaceSubClass === SUB_RNDIS_WIRELESS &&
|
|
71
|
+
i.interfaceProtocol === PROTO_RNDIS) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (i.interfaceClass === CLASS_COMM &&
|
|
75
|
+
i.interfaceSubClass === SUB_ACM &&
|
|
76
|
+
i.interfaceProtocol === PROTO_VENDOR) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return i.driver !== undefined && RNDIS_DRIVERS.has(i.driver);
|
|
80
|
+
}
|
|
81
|
+
function isEcmNcmData(i) {
|
|
82
|
+
if (i.interfaceClass === CLASS_COMM &&
|
|
83
|
+
(i.interfaceSubClass === SUB_ECM || i.interfaceSubClass === SUB_NCM)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return i.driver !== undefined && ECM_NCM_DRIVERS.has(i.driver);
|
|
87
|
+
}
|
|
88
|
+
function isMassStorage(i) {
|
|
89
|
+
if (i.interfaceClass === CLASS_MASS_STORAGE) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return i.driver !== undefined && STORAGE_DRIVERS.has(i.driver);
|
|
93
|
+
}
|
|
94
|
+
function isVendorSpecific(i) {
|
|
95
|
+
return i.interfaceClass === CLASS_VENDOR;
|
|
96
|
+
}
|
|
97
|
+
function controlKind(i) {
|
|
98
|
+
if (isMbimControl(i)) {
|
|
99
|
+
return 'MBIM';
|
|
100
|
+
}
|
|
101
|
+
return isQmiControl(i) ? 'QMI' : 'AT';
|
|
102
|
+
}
|
|
103
|
+
/** Whether a mass-storage device is a modem awaiting `usb_modeswitch` (not a plain disk). */
|
|
104
|
+
function isModeswitchCandidate(snapshot) {
|
|
105
|
+
const flag = snapshot.udevProperties?.ID_USB_MODESWITCH;
|
|
106
|
+
if (flag !== undefined && flag !== '' && flag !== '0') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const model = (snapshot.model ?? '').toLowerCase();
|
|
110
|
+
return ['cd-rom', 'installer', 'zerocd', 'autoinstall'].some((marker) => model.includes(marker));
|
|
111
|
+
}
|
|
112
|
+
function unmanagedReason(snapshot, hasStorage) {
|
|
113
|
+
if (hasStorage) {
|
|
114
|
+
return 'mass-storage device with no usb_modeswitch trigger — not a recognized modem installer';
|
|
115
|
+
}
|
|
116
|
+
if (snapshot.interfaces.some(isVendorSpecific)) {
|
|
117
|
+
return 'vendor-specific interface(s) with no recognized modem driver — cannot confidently classify';
|
|
118
|
+
}
|
|
119
|
+
return 'no recognized modem control port, Ethernet tether, or installer interface';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Classify one USB device from its udev/sysfs snapshot. Precedence:
|
|
123
|
+
* 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
|
|
124
|
+
* 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
|
|
125
|
+
* 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
|
|
126
|
+
* 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
|
|
127
|
+
*/
|
|
128
|
+
export function classifyDevice(snapshot) {
|
|
129
|
+
const ifaces = snapshot.interfaces;
|
|
130
|
+
const control = ifaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
|
|
131
|
+
if (control !== undefined) {
|
|
132
|
+
return {
|
|
133
|
+
deviceClass: 'mm-managed',
|
|
134
|
+
reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const tether = ifaces.find((i) => isRndis(i) || isEcmNcmData(i));
|
|
138
|
+
if (tether !== undefined) {
|
|
139
|
+
const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
|
|
140
|
+
return {
|
|
141
|
+
deviceClass: 'router-mode',
|
|
142
|
+
reason: `${kind} Ethernet tether with no modem control port — router-ethernet class, not MM-manageable`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const hasStorage = ifaces.some(isMassStorage) || snapshot.bDeviceClass === CLASS_MASS_STORAGE;
|
|
146
|
+
if (hasStorage && isModeswitchCandidate(snapshot)) {
|
|
147
|
+
return {
|
|
148
|
+
deviceClass: 'pending-modeswitch',
|
|
149
|
+
reason: 'mass-storage installer mode with a usb_modeswitch trigger — awaiting mode switch',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return { deviceClass: 'unmanaged', reason: unmanagedReason(snapshot, hasStorage) };
|
|
153
|
+
}
|
|
154
|
+
export function cellularVendorName(vendorId) {
|
|
155
|
+
return CELLULAR_USB_VENDOR_IDS.get(vendorId.toLowerCase());
|
|
156
|
+
}
|
|
157
|
+
export function cellularEvidence(device) {
|
|
158
|
+
const vendor = cellularVendorName(device.vendorId);
|
|
159
|
+
if (vendor !== undefined)
|
|
160
|
+
return `USB vendor ${device.vendorId} is ${vendor}, a cellular-module vendor`;
|
|
161
|
+
const modeswitch = device.udevProperties?.ID_USB_MODESWITCH;
|
|
162
|
+
if (modeswitch !== undefined && modeswitch !== '' && modeswitch !== '0')
|
|
163
|
+
return 'device carries a usb_modeswitch trigger — a mode-switching dongle';
|
|
164
|
+
return device.interfaces.some(isMassStorage)
|
|
165
|
+
? 'device also presents a mass-storage installer interface — the ZeroCD personality of a router-mode dongle'
|
|
166
|
+
: undefined;
|
|
167
|
+
}
|
|
168
|
+
export function classifyUsbNetDevice(device) {
|
|
169
|
+
const control = device.interfaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
|
|
170
|
+
if (control !== undefined)
|
|
171
|
+
return {
|
|
172
|
+
deviceClass: 'mm-managed',
|
|
173
|
+
reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
|
|
174
|
+
};
|
|
175
|
+
const tether = device.interfaces.find((i) => isRndis(i) || isEcmNcmData(i) || i.interfaceClass === CLASS_CDC_DATA);
|
|
176
|
+
if (tether !== undefined) {
|
|
177
|
+
const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
|
|
178
|
+
const evidence = cellularEvidence(device);
|
|
179
|
+
return evidence === undefined
|
|
180
|
+
? {
|
|
181
|
+
deviceClass: 'wired-ethernet',
|
|
182
|
+
reason: `${kind} Ethernet tether with no modem control port and no cellular evidence — a USB network adapter`,
|
|
183
|
+
}
|
|
184
|
+
: {
|
|
185
|
+
deviceClass: 'router-cellular',
|
|
186
|
+
reason: `${kind} Ethernet tether with no modem control port; ${evidence}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
deviceClass: 'unknown',
|
|
191
|
+
reason: unmanagedReason(device, device.interfaces.some(isMassStorage) || device.bDeviceClass === CLASS_MASS_STORAGE),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
export function publishesGenericIdentity(device) {
|
|
195
|
+
const manufacturer = device.manufacturer?.trim();
|
|
196
|
+
const product = device.product?.trim();
|
|
197
|
+
return Boolean(manufacturer && product && manufacturer.toLowerCase() === product.toLowerCase());
|
|
198
|
+
}
|
|
199
|
+
export function vendorLabel(device) {
|
|
200
|
+
const published = device.manufacturer?.trim();
|
|
201
|
+
if (published && !publishesGenericIdentity(device))
|
|
202
|
+
return published;
|
|
203
|
+
return cellularVendorName(device.vendorId) ?? device.databaseVendor?.trim() ?? device.vendorId;
|
|
204
|
+
}
|
|
205
|
+
export function modelLabel(device) {
|
|
206
|
+
const published = device.product?.trim();
|
|
207
|
+
if (published && !publishesGenericIdentity(device))
|
|
208
|
+
return published;
|
|
209
|
+
return device.databaseModel?.trim() ?? device.productId;
|
|
210
|
+
}
|
|
211
|
+
export function unitDiscriminator(device) {
|
|
212
|
+
const serial = device.serialNumber?.trim();
|
|
213
|
+
if (!serial)
|
|
214
|
+
return undefined;
|
|
215
|
+
const folded = serial.toLowerCase();
|
|
216
|
+
return folded === device.manufacturer?.trim().toLowerCase() ||
|
|
217
|
+
folded === device.product?.trim().toLowerCase()
|
|
218
|
+
? undefined
|
|
219
|
+
: serial;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Derive the USB composition MODE a device is currently in (for a transition's
|
|
223
|
+
* postcondition). Distinct from `classifyDevice`: this reads the data-plane
|
|
224
|
+
* composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
|
|
225
|
+
* data interface being `ecm-ncm` only when a control/vendor port accompanies it
|
|
226
|
+
* (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
|
|
227
|
+
* `qmi`. Returns `undefined` when nothing recognizable is present.
|
|
228
|
+
*/
|
|
229
|
+
export function detectUsbMode(snapshot) {
|
|
230
|
+
const ifaces = snapshot.interfaces;
|
|
231
|
+
if (ifaces.some(isMbimControl)) {
|
|
232
|
+
return 'mbim';
|
|
233
|
+
}
|
|
234
|
+
if (ifaces.some(isRndis)) {
|
|
235
|
+
return 'rndis';
|
|
236
|
+
}
|
|
237
|
+
if (ifaces.some(isEcmNcmData)) {
|
|
238
|
+
const hasControl = ifaces.some((i) => isAtControl(i) || isVendorSpecific(i));
|
|
239
|
+
return hasControl ? 'ecm-ncm' : 'router-ethernet';
|
|
240
|
+
}
|
|
241
|
+
if (ifaces.some((i) => isQmiControl(i) || isVendorSpecific(i))) {
|
|
242
|
+
return 'qmi';
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Whether a device presents (at least) the descriptors a catalog transition expects
|
|
248
|
+
* after re-enumeration — the descriptor half of the postcondition. Every expected
|
|
249
|
+
* interface triple must be present and `bDeviceClass` must match.
|
|
250
|
+
*/
|
|
251
|
+
export function descriptorsMatch(snapshot, expected) {
|
|
252
|
+
if (snapshot.bDeviceClass !== expected.deviceClass) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return expected.interfaces.every((exp) => snapshot.interfaces.some((i) => i.interfaceClass === exp.interfaceClass &&
|
|
256
|
+
i.interfaceSubClass === exp.interfaceSubClass &&
|
|
257
|
+
i.interfaceProtocol === exp.interfaceProtocol));
|
|
258
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CellReading } from './cell-info.js';
|
|
2
|
+
import type { DecodedManagedObjects } from './managed-objects.js';
|
|
3
|
+
import type { SignalCadence } from './signal-setup.js';
|
|
4
|
+
/** eSIM provisioning type — `Sim.SimType` (MMSimType), 1.20+. */
|
|
5
|
+
export type SimType = 'physical' | 'esim' | 'unknown';
|
|
6
|
+
/** eSIM profile status — `Sim.EsimStatus` (MMSimEsimStatus), 1.20+. */
|
|
7
|
+
export type EsimStatus = 'no-profiles' | 'with-profiles' | 'unknown';
|
|
8
|
+
/** The eSIM facts of a modem's active SIM. */
|
|
9
|
+
export interface EsimInfo {
|
|
10
|
+
readonly simType: SimType;
|
|
11
|
+
readonly esimStatus: EsimStatus;
|
|
12
|
+
}
|
|
13
|
+
/** The full read-only enrichment surfaced beside a modem's lifecycle snapshot. */
|
|
14
|
+
export interface ModemEnrichment {
|
|
15
|
+
/** `Modem.Revision` — firmware/hardware revision string, when present. */
|
|
16
|
+
readonly revision?: string;
|
|
17
|
+
readonly esim: EsimInfo;
|
|
18
|
+
readonly signalCadence: SignalCadence;
|
|
19
|
+
readonly cellInfo: readonly CellReading[];
|
|
20
|
+
/** The selected serving cell (total-order winner), when any cell is present. */
|
|
21
|
+
readonly servingCell?: CellReading;
|
|
22
|
+
}
|
|
23
|
+
/** Read `Modem.Revision`, when present. */
|
|
24
|
+
export declare function readRevision(tree: DecodedManagedObjects, modemPath: string): string | undefined;
|
|
25
|
+
/** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
|
|
26
|
+
export declare function readEsimInfo(tree: DecodedManagedObjects, modemPath: string): EsimInfo;
|
|
27
|
+
/** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
|
|
28
|
+
export declare function buildEnrichment(tree: DecodedManagedObjects, modemPath: string, signalCadence: SignalCadence, cellInfo: readonly CellReading[]): ModemEnrichment;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Read-only status enrichment — firmware revision, eSIM facts, signal cadence,
|
|
2
|
+
// and normalized cell info, assembled for one modem's status surface.
|
|
3
|
+
//
|
|
4
|
+
// These are additive, read-only details layered on top of the lifecycle snapshot
|
|
5
|
+
// (A3.1) — they never gate anything and never fail a backend start. `Modem.Revision`
|
|
6
|
+
// and the eSIM `SimType` / `EsimStatus` (MM 1.20+) are read straight from the decoded
|
|
7
|
+
// `GetManagedObjects` tree; `signalCadence` comes from the Signal.Setup manager; cell
|
|
8
|
+
// info is normalized from a `GetCellInfo` reply by `./cell-info`.
|
|
9
|
+
import { selectServingCell } from './cell-info.js';
|
|
10
|
+
import { MODEM_IFACE, SIM_IFACE } from './constants.js';
|
|
11
|
+
import { findInterface, followObjectPath, numberProp, stringProp } from './managed-objects.js';
|
|
12
|
+
/** MMSimType (`Sim.SimType`) → the domain `SimType`. */
|
|
13
|
+
function mapSimType(value) {
|
|
14
|
+
switch (value) {
|
|
15
|
+
case 1:
|
|
16
|
+
return 'physical';
|
|
17
|
+
case 2:
|
|
18
|
+
return 'esim';
|
|
19
|
+
default:
|
|
20
|
+
return 'unknown';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** MMSimEsimStatus (`Sim.EsimStatus`) → the domain `EsimStatus`. */
|
|
24
|
+
function mapEsimStatus(value) {
|
|
25
|
+
switch (value) {
|
|
26
|
+
case 1:
|
|
27
|
+
return 'no-profiles';
|
|
28
|
+
case 2:
|
|
29
|
+
return 'with-profiles';
|
|
30
|
+
default:
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Read `Modem.Revision`, when present. */
|
|
35
|
+
export function readRevision(tree, modemPath) {
|
|
36
|
+
const revision = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Revision');
|
|
37
|
+
return revision !== undefined && revision.length > 0 ? revision : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
|
|
40
|
+
export function readEsimInfo(tree, modemPath) {
|
|
41
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
42
|
+
const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
|
|
43
|
+
return {
|
|
44
|
+
simType: mapSimType(numberProp(sim, 'SimType')),
|
|
45
|
+
esimStatus: mapEsimStatus(numberProp(sim, 'EsimStatus')),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
|
|
49
|
+
export function buildEnrichment(tree, modemPath, signalCadence, cellInfo) {
|
|
50
|
+
const revision = readRevision(tree, modemPath);
|
|
51
|
+
const serving = selectServingCell(cellInfo);
|
|
52
|
+
return {
|
|
53
|
+
...(revision !== undefined ? { revision } : {}),
|
|
54
|
+
esim: readEsimInfo(tree, modemPath),
|
|
55
|
+
signalCadence,
|
|
56
|
+
cellInfo,
|
|
57
|
+
...(serving !== undefined ? { servingCell: serving } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type DecodedManagedObjects } from './managed-objects.js';
|
|
2
|
+
/** The capabilities the D-Bus backend gates its behaviour on. */
|
|
3
|
+
export interface MmFeatures {
|
|
4
|
+
/** `Modem.Physdev` present (physical topology path, 1.22+). Property-probed. */
|
|
5
|
+
readonly physdev: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Cell-info tier:
|
|
8
|
+
* - `none` — below 1.20, or the modem advertises no cell-info capability.
|
|
9
|
+
* - `basic` — 1.20/1.21 `GetCellInfo`.
|
|
10
|
+
* - `rich` — 1.22+ (serving-type / bandwidth fields).
|
|
11
|
+
*/
|
|
12
|
+
readonly cellInfo: 'none' | 'basic' | 'rich';
|
|
13
|
+
/** eSIM read model (`Sim.SimType` / `Sim.EsimStatus`), 1.20+. */
|
|
14
|
+
readonly esimStatus: boolean;
|
|
15
|
+
/** Per-modem operation serialization is available (the shared disruptive actor). */
|
|
16
|
+
readonly opSerialization: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What A3.2 observed about ONE modem's property surface.
|
|
20
|
+
*
|
|
21
|
+
* `properties` is the union of property NAMES seen across the modem's interfaces
|
|
22
|
+
* (built by `probeModemProperties` from a decoded `GetManagedObjects` tree). The
|
|
23
|
+
* two optional fields let a caller feed richer cell-info evidence than the static
|
|
24
|
+
* object tree carries — MM does not advertise cell-info richness as a Modem
|
|
25
|
+
* property (it comes back from the `GetCellInfo` method), so a backend that has
|
|
26
|
+
* probed cell info can pass what it saw; absent that, detection falls back to the
|
|
27
|
+
* version + `Physdev` co-introduction signal.
|
|
28
|
+
*/
|
|
29
|
+
export interface MmPropertyProbe {
|
|
30
|
+
/** Property names present on the observed modem (Modem + Modem3gpp + active SIM). */
|
|
31
|
+
readonly properties: ReadonlySet<string>;
|
|
32
|
+
/** Explicit "modem advertises cell info": `false` forces `cellInfo: 'none'`. */
|
|
33
|
+
readonly cellInfoAvailable?: boolean;
|
|
34
|
+
/** Cell-info field names seen in a probe; `serving-type`/`bandwidth` ⇒ `rich`. */
|
|
35
|
+
readonly cellInfoFields?: ReadonlySet<string>;
|
|
36
|
+
}
|
|
37
|
+
/** A parsed `major.minor` MM version. `null` when the string is not parseable. */
|
|
38
|
+
export interface MmVersion {
|
|
39
|
+
readonly major: number;
|
|
40
|
+
readonly minor: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse an MM `Version` string (`1.20.0`, `1.24`, `1.26.0-rc1`, …) to `major.minor`.
|
|
44
|
+
* Returns `null` on anything unparseable — callers must degrade, never throw.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseMmVersion(version: string): MmVersion | null;
|
|
47
|
+
/**
|
|
48
|
+
* Detect what an MM daemon can do from its version string AND the properties
|
|
49
|
+
* observed on a modem. Pure — no I/O, never throws (an unparseable version yields
|
|
50
|
+
* the safe floor: property-probed `physdev`, everything else off).
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectMmFeatures(version: string, probe: MmPropertyProbe): MmFeatures;
|
|
53
|
+
/**
|
|
54
|
+
* Build an `MmPropertyProbe` for one modem from a decoded `GetManagedObjects` tree:
|
|
55
|
+
* the union of property names on its `Modem`, `Modem.Modem3gpp`, and active `Sim`
|
|
56
|
+
* interfaces. Cell-info fields are not carried by the static tree, so the returned
|
|
57
|
+
* probe leaves them unset (detection falls back to version + `Physdev`).
|
|
58
|
+
*/
|
|
59
|
+
export declare function probeModemProperties(tree: DecodedManagedObjects, modemPath: string): MmPropertyProbe;
|
|
60
|
+
/** Convenience: detect features straight off a decoded tree for one modem. */
|
|
61
|
+
export declare function detectModemFeatures(version: string, tree: DecodedManagedObjects, modemPath: string): MmFeatures;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ModemManager feature detection — what a given MM daemon can actually do.
|
|
2
|
+
//
|
|
3
|
+
// A3.2 must run against MM 1.20 (the bookworm floor), 1.22, 1.24, and versions
|
|
4
|
+
// nobody has shipped yet. The plan is explicit that this is NOT a version-string
|
|
5
|
+
// whitelist: a version we have never seen must degrade gracefully by PROBING the
|
|
6
|
+
// observed property set, never throw. So detection combines two signals:
|
|
7
|
+
//
|
|
8
|
+
// - the daemon `Version` string (a floor: e.g. eSIM read model is 1.20+), and
|
|
9
|
+
// - the properties actually present on the observed modem (authoritative for
|
|
10
|
+
// `physdev` — the plan requires `physdev` be true ONLY when the `Physdev`
|
|
11
|
+
// property is really there, never merely inferred from a version number).
|
|
12
|
+
//
|
|
13
|
+
// Property facts pinned by the reviewed design (draft §Oracle round-2 #9 / round-4):
|
|
14
|
+
// - `Modem.Device` carries the udev slot UID on ALL versions.
|
|
15
|
+
// - `Modem.Physdev` is the physical topology path, 1.22+ ONLY.
|
|
16
|
+
// - there is NO `PhysdevUid` property anywhere — do not look for one.
|
|
17
|
+
import { MODEM_IFACE, MODEM3GPP_IFACE, SIM_IFACE } from './constants.js';
|
|
18
|
+
import { findInterface, followObjectPath } from './managed-objects.js';
|
|
19
|
+
const VERSION_PATTERN = /^(\d+)\.(\d+)(?:\.\d+)*/;
|
|
20
|
+
/**
|
|
21
|
+
* Parse an MM `Version` string (`1.20.0`, `1.24`, `1.26.0-rc1`, …) to `major.minor`.
|
|
22
|
+
* Returns `null` on anything unparseable — callers must degrade, never throw.
|
|
23
|
+
*/
|
|
24
|
+
export function parseMmVersion(version) {
|
|
25
|
+
const match = VERSION_PATTERN.exec(version.trim());
|
|
26
|
+
if (match === null) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const major = Number(match[1]);
|
|
30
|
+
const minor = Number(match[2]);
|
|
31
|
+
if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return { major, minor };
|
|
35
|
+
}
|
|
36
|
+
/** Whether `version` is at least `major.minor` (false when unparseable). */
|
|
37
|
+
function atLeast(version, major, minor) {
|
|
38
|
+
if (version === null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return version.major > major || (version.major === major && version.minor >= minor);
|
|
42
|
+
}
|
|
43
|
+
/** MM baseline this stack supports — eSIM read model, `GetCellInfo`, Signal.Setup. */
|
|
44
|
+
const MM_MIN = { major: 1, minor: 20 };
|
|
45
|
+
/** Physdev + rich cell-info were co-introduced at 1.22. */
|
|
46
|
+
const MM_RICH = { major: 1, minor: 22 };
|
|
47
|
+
function detectCellInfo(probe, supported, modern, physdev) {
|
|
48
|
+
// Version floor first: below 1.20 there is no GetCellInfo at all.
|
|
49
|
+
if (!supported) {
|
|
50
|
+
return 'none';
|
|
51
|
+
}
|
|
52
|
+
// A modem that explicitly advertises no cell-info capability degrades to none,
|
|
53
|
+
// even on a version that would otherwise support it.
|
|
54
|
+
if (probe.cellInfoAvailable === false) {
|
|
55
|
+
return 'none';
|
|
56
|
+
}
|
|
57
|
+
const fields = probe.cellInfoFields;
|
|
58
|
+
if (fields !== undefined && (fields.has('serving-type') || fields.has('bandwidth'))) {
|
|
59
|
+
return 'rich';
|
|
60
|
+
}
|
|
61
|
+
// 1.22 co-introduced `Physdev` and the rich cell-info fields, so either signal
|
|
62
|
+
// promotes the tier — this is what makes an unseen future version (e.g. 1.26)
|
|
63
|
+
// resolve to `rich` by probing rather than by matching a whitelist.
|
|
64
|
+
if (modern || physdev) {
|
|
65
|
+
return 'rich';
|
|
66
|
+
}
|
|
67
|
+
return 'basic';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Detect what an MM daemon can do from its version string AND the properties
|
|
71
|
+
* observed on a modem. Pure — no I/O, never throws (an unparseable version yields
|
|
72
|
+
* the safe floor: property-probed `physdev`, everything else off).
|
|
73
|
+
*/
|
|
74
|
+
export function detectMmFeatures(version, probe) {
|
|
75
|
+
const parsed = parseMmVersion(version);
|
|
76
|
+
const supported = atLeast(parsed, MM_MIN.major, MM_MIN.minor);
|
|
77
|
+
const modern = atLeast(parsed, MM_RICH.major, MM_RICH.minor);
|
|
78
|
+
// `physdev` is property-authoritative: present iff the `Physdev` property is
|
|
79
|
+
// really in the observed set (never inferred from the version alone).
|
|
80
|
+
const physdev = probe.properties.has('Physdev');
|
|
81
|
+
const esimStatus = probe.properties.has('SimType') || probe.properties.has('EsimStatus') || supported;
|
|
82
|
+
const cellInfo = detectCellInfo(probe, supported, modern, physdev);
|
|
83
|
+
return { physdev, cellInfo, esimStatus, opSerialization: supported };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build an `MmPropertyProbe` for one modem from a decoded `GetManagedObjects` tree:
|
|
87
|
+
* the union of property names on its `Modem`, `Modem.Modem3gpp`, and active `Sim`
|
|
88
|
+
* interfaces. Cell-info fields are not carried by the static tree, so the returned
|
|
89
|
+
* probe leaves them unset (detection falls back to version + `Physdev`).
|
|
90
|
+
*/
|
|
91
|
+
export function probeModemProperties(tree, modemPath) {
|
|
92
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
93
|
+
const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
|
|
94
|
+
const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
|
|
95
|
+
const properties = new Set();
|
|
96
|
+
for (const source of [modem, modem3gpp, sim]) {
|
|
97
|
+
if (source === undefined) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
for (const [name] of source) {
|
|
101
|
+
properties.add(name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { properties };
|
|
105
|
+
}
|
|
106
|
+
/** Convenience: detect features straight off a decoded tree for one modem. */
|
|
107
|
+
export function detectModemFeatures(version, tree, modemPath) {
|
|
108
|
+
return detectMmFeatures(version, probeModemProperties(tree, modemPath));
|
|
109
|
+
}
|