@ceralive/modem-control 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
// The classifier's contract, one fixture per class — plus the HONESTY guarantee: an
|
|
2
|
-
// ambiguous descriptor set returns `unmanaged` with a truthful reason, never a
|
|
3
|
-
// confident guess, and mass storage without a modeswitch trigger is NOT a modem.
|
|
4
|
-
|
|
5
|
-
import { describe, expect, test } from 'bun:test';
|
|
6
|
-
import { classifyDevice, detectUsbMode, type UsbDeviceSnapshot } from './device-classifier';
|
|
7
|
-
|
|
8
|
-
/** A Quectel-style QMI composition: a `qmi_wwan` control port (+ AT serials). */
|
|
9
|
-
const QMI: UsbDeviceSnapshot = {
|
|
10
|
-
vendorId: '2c7c',
|
|
11
|
-
productId: '0125',
|
|
12
|
-
model: 'EG25-G',
|
|
13
|
-
bDeviceClass: 0,
|
|
14
|
-
interfaces: [
|
|
15
|
-
{ interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'option' },
|
|
16
|
-
{ interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'qmi_wwan' },
|
|
17
|
-
],
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/** An MBIM composition: the standard 0x02/0x0e control + 0x0a data pair. */
|
|
21
|
-
const MBIM: UsbDeviceSnapshot = {
|
|
22
|
-
vendorId: '1199',
|
|
23
|
-
productId: '9071',
|
|
24
|
-
model: 'Sierra EM-series',
|
|
25
|
-
bDeviceClass: 0,
|
|
26
|
-
interfaces: [
|
|
27
|
-
{ interfaceClass: 0x02, interfaceSubClass: 0x0e, interfaceProtocol: 0x00, driver: 'cdc_mbim' },
|
|
28
|
-
{ interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x02, driver: 'cdc_mbim' },
|
|
29
|
-
],
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** A Huawei HiLink personality: CDC-ECM Ethernet, no control port — firmware, not switchable. */
|
|
33
|
-
const HILINK_ECM: UsbDeviceSnapshot = {
|
|
34
|
-
vendorId: '12d1',
|
|
35
|
-
productId: '14db',
|
|
36
|
-
model: 'HUAWEI HiLink',
|
|
37
|
-
bDeviceClass: 0x02,
|
|
38
|
-
interfaces: [
|
|
39
|
-
{ interfaceClass: 0x02, interfaceSubClass: 0x06, interfaceProtocol: 0x00, driver: 'cdc_ether' },
|
|
40
|
-
{ interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x00, driver: 'cdc_ether' },
|
|
41
|
-
],
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** An RNDIS tether: the wireless-RNDIS descriptor, no control port. */
|
|
45
|
-
const RNDIS: UsbDeviceSnapshot = {
|
|
46
|
-
vendorId: '19d2',
|
|
47
|
-
productId: '0601',
|
|
48
|
-
model: 'RNDIS tether',
|
|
49
|
-
bDeviceClass: 0x00,
|
|
50
|
-
interfaces: [
|
|
51
|
-
{
|
|
52
|
-
interfaceClass: 0xe0,
|
|
53
|
-
interfaceSubClass: 0x01,
|
|
54
|
-
interfaceProtocol: 0x03,
|
|
55
|
-
driver: 'rndis_host',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
interfaceClass: 0x0a,
|
|
59
|
-
interfaceSubClass: 0x00,
|
|
60
|
-
interfaceProtocol: 0x00,
|
|
61
|
-
driver: 'rndis_host',
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/** A modem in mass-storage installer mode, flagged for usb_modeswitch. */
|
|
67
|
-
const STORAGE_PRE_MODESWITCH: UsbDeviceSnapshot = {
|
|
68
|
-
vendorId: '12d1',
|
|
69
|
-
productId: '1f01',
|
|
70
|
-
model: 'HUAWEI Mobile',
|
|
71
|
-
bDeviceClass: 0x00,
|
|
72
|
-
interfaces: [
|
|
73
|
-
{
|
|
74
|
-
interfaceClass: 0x08,
|
|
75
|
-
interfaceSubClass: 0x06,
|
|
76
|
-
interfaceProtocol: 0x50,
|
|
77
|
-
driver: 'usb-storage',
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
udevProperties: { ID_USB_MODESWITCH: '1' },
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/** A composite that matches NO known modem pattern — vendor + HID, no driver. */
|
|
84
|
-
const AMBIGUOUS: UsbDeviceSnapshot = {
|
|
85
|
-
vendorId: '1234',
|
|
86
|
-
productId: '5678',
|
|
87
|
-
model: 'Unknown Composite',
|
|
88
|
-
bDeviceClass: 0x00,
|
|
89
|
-
interfaces: [
|
|
90
|
-
{ interfaceClass: 0x03, interfaceSubClass: 0x00, interfaceProtocol: 0x00 },
|
|
91
|
-
{ interfaceClass: 0xff, interfaceSubClass: 0x42, interfaceProtocol: 0x01 },
|
|
92
|
-
],
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
describe('classifyDevice — the six canonical fixtures', () => {
|
|
96
|
-
test('QMI composition → mm-managed', () => {
|
|
97
|
-
expect(classifyDevice(QMI).deviceClass).toBe('mm-managed');
|
|
98
|
-
expect(detectUsbMode(QMI)).toBe('qmi');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('MBIM composition → mm-managed', () => {
|
|
102
|
-
const result = classifyDevice(MBIM);
|
|
103
|
-
expect(result.deviceClass).toBe('mm-managed');
|
|
104
|
-
expect(result.reason).toContain('MBIM');
|
|
105
|
-
expect(detectUsbMode(MBIM)).toBe('mbim');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('Huawei HiLink-ECM (firmware personality) → router-mode', () => {
|
|
109
|
-
const result = classifyDevice(HILINK_ECM);
|
|
110
|
-
expect(result.deviceClass).toBe('router-mode');
|
|
111
|
-
expect(result.reason).toContain('ECM');
|
|
112
|
-
expect(detectUsbMode(HILINK_ECM)).toBe('router-ethernet');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('RNDIS tether → router-mode', () => {
|
|
116
|
-
const result = classifyDevice(RNDIS);
|
|
117
|
-
expect(result.deviceClass).toBe('router-mode');
|
|
118
|
-
expect(result.reason).toContain('RNDIS');
|
|
119
|
-
expect(detectUsbMode(RNDIS)).toBe('rndis');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('mass-storage before usb_modeswitch → pending-modeswitch (a distinct state)', () => {
|
|
123
|
-
const result = classifyDevice(STORAGE_PRE_MODESWITCH);
|
|
124
|
-
expect(result.deviceClass).toBe('pending-modeswitch');
|
|
125
|
-
expect(result.reason).toContain('usb_modeswitch');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('ambiguous composite → unmanaged, with an HONEST reason (never guessed)', () => {
|
|
129
|
-
const result = classifyDevice(AMBIGUOUS);
|
|
130
|
-
expect(result.deviceClass).toBe('unmanaged');
|
|
131
|
-
expect(result.reason).toContain('vendor-specific');
|
|
132
|
-
// Never a confident modem/router class.
|
|
133
|
-
expect(result.deviceClass).not.toBe('mm-managed');
|
|
134
|
-
expect(result.deviceClass).not.toBe('router-mode');
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('classifyDevice — honesty guards', () => {
|
|
139
|
-
test('mass storage WITHOUT a modeswitch trigger is unmanaged, not pending-modeswitch', () => {
|
|
140
|
-
const plainDisk: UsbDeviceSnapshot = {
|
|
141
|
-
vendorId: '0781',
|
|
142
|
-
productId: '5567',
|
|
143
|
-
model: 'SanDisk Cruzer',
|
|
144
|
-
bDeviceClass: 0x00,
|
|
145
|
-
interfaces: [
|
|
146
|
-
{
|
|
147
|
-
interfaceClass: 0x08,
|
|
148
|
-
interfaceSubClass: 0x06,
|
|
149
|
-
interfaceProtocol: 0x50,
|
|
150
|
-
driver: 'usb-storage',
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
};
|
|
154
|
-
const result = classifyDevice(plainDisk);
|
|
155
|
-
expect(result.deviceClass).toBe('unmanaged');
|
|
156
|
-
expect(result.reason).toContain('usb_modeswitch');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('a bare vendor interface with no driver is NOT assumed to be a QMI modem', () => {
|
|
160
|
-
const bareVendor: UsbDeviceSnapshot = {
|
|
161
|
-
vendorId: '2357',
|
|
162
|
-
productId: '0001',
|
|
163
|
-
bDeviceClass: 0x00,
|
|
164
|
-
interfaces: [{ interfaceClass: 0xff, interfaceSubClass: 0x00, interfaceProtocol: 0x00 }],
|
|
165
|
-
};
|
|
166
|
-
expect(classifyDevice(bareVendor).deviceClass).toBe('unmanaged');
|
|
167
|
-
});
|
|
168
|
-
});
|
|
@@ -1,248 +0,0 @@
|
|
|
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
|
-
|
|
21
|
-
import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode';
|
|
22
|
-
|
|
23
|
-
/** One USB interface's descriptor bytes plus its bound kernel driver, if any. */
|
|
24
|
-
export interface UsbInterface {
|
|
25
|
-
readonly interfaceClass: number;
|
|
26
|
-
readonly interfaceSubClass: number;
|
|
27
|
-
readonly interfaceProtocol: number;
|
|
28
|
-
/** The bound kernel driver (`qmi_wwan`, `cdc_mbim`, `option`, `cdc_ether`, …). */
|
|
29
|
-
readonly driver?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** A single USB device as observed from udev/sysfs — the classifier's whole input. */
|
|
33
|
-
export interface UsbDeviceSnapshot {
|
|
34
|
-
readonly vendorId: string;
|
|
35
|
-
readonly productId: string;
|
|
36
|
-
readonly model?: string;
|
|
37
|
-
readonly firmwareRevision?: string;
|
|
38
|
-
/** The device-descriptor `bDeviceClass` byte (0 ⇒ class is per-interface). */
|
|
39
|
-
readonly bDeviceClass: number;
|
|
40
|
-
readonly interfaces: readonly UsbInterface[];
|
|
41
|
-
/** Stable physical-topology UID (udev `ID_PATH` / physdev) — survives a mode change. */
|
|
42
|
-
readonly physicalUid?: string;
|
|
43
|
-
/** The bound network interface name, if the device presents one (`wwan0`, `usb0`). */
|
|
44
|
-
readonly ifname?: string;
|
|
45
|
-
/** Raw udev properties (`ID_USB_MODESWITCH`, `ID_MM_CANDIDATE`, …). */
|
|
46
|
-
readonly udevProperties?: Readonly<Record<string, string>>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** The four device classes. `pending-modeswitch` is distinct from `unmanaged`. */
|
|
50
|
-
export type DeviceClass = 'mm-managed' | 'router-mode' | 'unmanaged' | 'pending-modeswitch';
|
|
51
|
-
|
|
52
|
-
/** A classification plus a human-readable reason (always populated). */
|
|
53
|
-
export interface DeviceClassification {
|
|
54
|
-
readonly deviceClass: DeviceClass;
|
|
55
|
-
readonly reason: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// USB-IF class / subclass / protocol codes used below.
|
|
59
|
-
const CLASS_COMM = 0x02; // CDC communications (control interface)
|
|
60
|
-
const CLASS_MASS_STORAGE = 0x08;
|
|
61
|
-
const CLASS_WIRELESS = 0xe0; // wireless controller (RNDIS lives here)
|
|
62
|
-
const CLASS_VENDOR = 0xff;
|
|
63
|
-
const SUB_ACM = 0x02; // abstract control model (AT commands)
|
|
64
|
-
const SUB_ECM = 0x06;
|
|
65
|
-
const SUB_NCM = 0x0d;
|
|
66
|
-
const SUB_MBIM = 0x0e;
|
|
67
|
-
const SUB_RNDIS_WIRELESS = 0x01;
|
|
68
|
-
const PROTO_VENDOR = 0xff;
|
|
69
|
-
const PROTO_RNDIS = 0x03;
|
|
70
|
-
|
|
71
|
-
const QMI_DRIVERS: ReadonlySet<string> = new Set(['qmi_wwan']);
|
|
72
|
-
const AT_DRIVERS: ReadonlySet<string> = new Set(['option', 'qcserial', 'cdc_acm']);
|
|
73
|
-
const ECM_NCM_DRIVERS: ReadonlySet<string> = new Set(['cdc_ether', 'cdc_ncm']);
|
|
74
|
-
const RNDIS_DRIVERS: ReadonlySet<string> = new Set(['rndis_host']);
|
|
75
|
-
const STORAGE_DRIVERS: ReadonlySet<string> = new Set(['usb-storage', 'uas']);
|
|
76
|
-
|
|
77
|
-
function isMbimControl(i: UsbInterface): boolean {
|
|
78
|
-
return i.interfaceClass === CLASS_COMM && i.interfaceSubClass === SUB_MBIM;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function isQmiControl(i: UsbInterface): boolean {
|
|
82
|
-
return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && QMI_DRIVERS.has(i.driver);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function isAtControl(i: UsbInterface): boolean {
|
|
86
|
-
// Standard CDC-ACM AT port (protocol 0xff on 0x02/0x02 is RNDIS, not AT).
|
|
87
|
-
if (
|
|
88
|
-
i.interfaceClass === CLASS_COMM &&
|
|
89
|
-
i.interfaceSubClass === SUB_ACM &&
|
|
90
|
-
i.interfaceProtocol !== PROTO_VENDOR
|
|
91
|
-
) {
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
// Vendor-specific serial port bound to a known AT driver.
|
|
95
|
-
return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && AT_DRIVERS.has(i.driver);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function isRndis(i: UsbInterface): boolean {
|
|
99
|
-
if (
|
|
100
|
-
i.interfaceClass === CLASS_WIRELESS &&
|
|
101
|
-
i.interfaceSubClass === SUB_RNDIS_WIRELESS &&
|
|
102
|
-
i.interfaceProtocol === PROTO_RNDIS
|
|
103
|
-
) {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
if (
|
|
107
|
-
i.interfaceClass === CLASS_COMM &&
|
|
108
|
-
i.interfaceSubClass === SUB_ACM &&
|
|
109
|
-
i.interfaceProtocol === PROTO_VENDOR
|
|
110
|
-
) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
return i.driver !== undefined && RNDIS_DRIVERS.has(i.driver);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function isEcmNcmData(i: UsbInterface): boolean {
|
|
117
|
-
if (
|
|
118
|
-
i.interfaceClass === CLASS_COMM &&
|
|
119
|
-
(i.interfaceSubClass === SUB_ECM || i.interfaceSubClass === SUB_NCM)
|
|
120
|
-
) {
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
return i.driver !== undefined && ECM_NCM_DRIVERS.has(i.driver);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isMassStorage(i: UsbInterface): boolean {
|
|
127
|
-
if (i.interfaceClass === CLASS_MASS_STORAGE) {
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
return i.driver !== undefined && STORAGE_DRIVERS.has(i.driver);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function isVendorSpecific(i: UsbInterface): boolean {
|
|
134
|
-
return i.interfaceClass === CLASS_VENDOR;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function controlKind(i: UsbInterface): string {
|
|
138
|
-
if (isMbimControl(i)) {
|
|
139
|
-
return 'MBIM';
|
|
140
|
-
}
|
|
141
|
-
return isQmiControl(i) ? 'QMI' : 'AT';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Whether a mass-storage device is a modem awaiting `usb_modeswitch` (not a plain disk). */
|
|
145
|
-
function isModeswitchCandidate(snapshot: UsbDeviceSnapshot): boolean {
|
|
146
|
-
const flag = snapshot.udevProperties?.ID_USB_MODESWITCH;
|
|
147
|
-
if (flag !== undefined && flag !== '' && flag !== '0') {
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
const model = (snapshot.model ?? '').toLowerCase();
|
|
151
|
-
return ['cd-rom', 'installer', 'zerocd', 'autoinstall'].some((marker) => model.includes(marker));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function unmanagedReason(snapshot: UsbDeviceSnapshot, hasStorage: boolean): string {
|
|
155
|
-
if (hasStorage) {
|
|
156
|
-
return 'mass-storage device with no usb_modeswitch trigger — not a recognized modem installer';
|
|
157
|
-
}
|
|
158
|
-
if (snapshot.interfaces.some(isVendorSpecific)) {
|
|
159
|
-
return 'vendor-specific interface(s) with no recognized modem driver — cannot confidently classify';
|
|
160
|
-
}
|
|
161
|
-
return 'no recognized modem control port, Ethernet tether, or installer interface';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Classify one USB device from its udev/sysfs snapshot. Precedence:
|
|
166
|
-
* 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
|
|
167
|
-
* 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
|
|
168
|
-
* 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
|
|
169
|
-
* 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
|
|
170
|
-
*/
|
|
171
|
-
export function classifyDevice(snapshot: UsbDeviceSnapshot): DeviceClassification {
|
|
172
|
-
const ifaces = snapshot.interfaces;
|
|
173
|
-
|
|
174
|
-
const control = ifaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
|
|
175
|
-
if (control !== undefined) {
|
|
176
|
-
return {
|
|
177
|
-
deviceClass: 'mm-managed',
|
|
178
|
-
reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const tether = ifaces.find((i) => isRndis(i) || isEcmNcmData(i));
|
|
183
|
-
if (tether !== undefined) {
|
|
184
|
-
const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
|
|
185
|
-
return {
|
|
186
|
-
deviceClass: 'router-mode',
|
|
187
|
-
reason: `${kind} Ethernet tether with no modem control port — router-ethernet class, not MM-manageable`,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const hasStorage = ifaces.some(isMassStorage) || snapshot.bDeviceClass === CLASS_MASS_STORAGE;
|
|
192
|
-
if (hasStorage && isModeswitchCandidate(snapshot)) {
|
|
193
|
-
return {
|
|
194
|
-
deviceClass: 'pending-modeswitch',
|
|
195
|
-
reason: 'mass-storage installer mode with a usb_modeswitch trigger — awaiting mode switch',
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return { deviceClass: 'unmanaged', reason: unmanagedReason(snapshot, hasStorage) };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Derive the USB composition MODE a device is currently in (for a transition's
|
|
204
|
-
* postcondition). Distinct from `classifyDevice`: this reads the data-plane
|
|
205
|
-
* composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
|
|
206
|
-
* data interface being `ecm-ncm` only when a control/vendor port accompanies it
|
|
207
|
-
* (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
|
|
208
|
-
* `qmi`. Returns `undefined` when nothing recognizable is present.
|
|
209
|
-
*/
|
|
210
|
-
export function detectUsbMode(snapshot: UsbDeviceSnapshot): CanonicalUsbMode | undefined {
|
|
211
|
-
const ifaces = snapshot.interfaces;
|
|
212
|
-
if (ifaces.some(isMbimControl)) {
|
|
213
|
-
return 'mbim';
|
|
214
|
-
}
|
|
215
|
-
if (ifaces.some(isRndis)) {
|
|
216
|
-
return 'rndis';
|
|
217
|
-
}
|
|
218
|
-
if (ifaces.some(isEcmNcmData)) {
|
|
219
|
-
const hasControl = ifaces.some((i) => isAtControl(i) || isVendorSpecific(i));
|
|
220
|
-
return hasControl ? 'ecm-ncm' : 'router-ethernet';
|
|
221
|
-
}
|
|
222
|
-
if (ifaces.some((i) => isQmiControl(i) || isVendorSpecific(i))) {
|
|
223
|
-
return 'qmi';
|
|
224
|
-
}
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Whether a device presents (at least) the descriptors a catalog transition expects
|
|
230
|
-
* after re-enumeration — the descriptor half of the postcondition. Every expected
|
|
231
|
-
* interface triple must be present and `bDeviceClass` must match.
|
|
232
|
-
*/
|
|
233
|
-
export function descriptorsMatch(
|
|
234
|
-
snapshot: UsbDeviceSnapshot,
|
|
235
|
-
expected: ExpectedDescriptors,
|
|
236
|
-
): boolean {
|
|
237
|
-
if (snapshot.bDeviceClass !== expected.deviceClass) {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
return expected.interfaces.every((exp) =>
|
|
241
|
-
snapshot.interfaces.some(
|
|
242
|
-
(i) =>
|
|
243
|
-
i.interfaceClass === exp.interfaceClass &&
|
|
244
|
-
i.interfaceSubClass === exp.interfaceSubClass &&
|
|
245
|
-
i.interfaceProtocol === exp.interfaceProtocol,
|
|
246
|
-
),
|
|
247
|
-
);
|
|
248
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
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
|
-
|
|
10
|
-
import type { CellReading } from './cell-info';
|
|
11
|
-
import { selectServingCell } from './cell-info';
|
|
12
|
-
import { MODEM_IFACE, SIM_IFACE } from './constants';
|
|
13
|
-
import type { DecodedManagedObjects } from './managed-objects';
|
|
14
|
-
import { findInterface, followObjectPath, numberProp, stringProp } from './managed-objects';
|
|
15
|
-
import type { SignalCadence } from './signal-setup';
|
|
16
|
-
|
|
17
|
-
/** eSIM provisioning type — `Sim.SimType` (MMSimType), 1.20+. */
|
|
18
|
-
export type SimType = 'physical' | 'esim' | 'unknown';
|
|
19
|
-
|
|
20
|
-
/** eSIM profile status — `Sim.EsimStatus` (MMSimEsimStatus), 1.20+. */
|
|
21
|
-
export type EsimStatus = 'no-profiles' | 'with-profiles' | 'unknown';
|
|
22
|
-
|
|
23
|
-
/** The eSIM facts of a modem's active SIM. */
|
|
24
|
-
export interface EsimInfo {
|
|
25
|
-
readonly simType: SimType;
|
|
26
|
-
readonly esimStatus: EsimStatus;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The full read-only enrichment surfaced beside a modem's lifecycle snapshot. */
|
|
30
|
-
export interface ModemEnrichment {
|
|
31
|
-
/** `Modem.Revision` — firmware/hardware revision string, when present. */
|
|
32
|
-
readonly revision?: string;
|
|
33
|
-
readonly esim: EsimInfo;
|
|
34
|
-
readonly signalCadence: SignalCadence;
|
|
35
|
-
readonly cellInfo: readonly CellReading[];
|
|
36
|
-
/** The selected serving cell (total-order winner), when any cell is present. */
|
|
37
|
-
readonly servingCell?: CellReading;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** MMSimType (`Sim.SimType`) → the domain `SimType`. */
|
|
41
|
-
function mapSimType(value: number | undefined): SimType {
|
|
42
|
-
switch (value) {
|
|
43
|
-
case 1:
|
|
44
|
-
return 'physical';
|
|
45
|
-
case 2:
|
|
46
|
-
return 'esim';
|
|
47
|
-
default:
|
|
48
|
-
return 'unknown';
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** MMSimEsimStatus (`Sim.EsimStatus`) → the domain `EsimStatus`. */
|
|
53
|
-
function mapEsimStatus(value: number | undefined): EsimStatus {
|
|
54
|
-
switch (value) {
|
|
55
|
-
case 1:
|
|
56
|
-
return 'no-profiles';
|
|
57
|
-
case 2:
|
|
58
|
-
return 'with-profiles';
|
|
59
|
-
default:
|
|
60
|
-
return 'unknown';
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Read `Modem.Revision`, when present. */
|
|
65
|
-
export function readRevision(tree: DecodedManagedObjects, modemPath: string): string | undefined {
|
|
66
|
-
const revision = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Revision');
|
|
67
|
-
return revision !== undefined && revision.length > 0 ? revision : undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
|
|
71
|
-
export function readEsimInfo(tree: DecodedManagedObjects, modemPath: string): EsimInfo {
|
|
72
|
-
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
73
|
-
const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
|
|
74
|
-
return {
|
|
75
|
-
simType: mapSimType(numberProp(sim, 'SimType')),
|
|
76
|
-
esimStatus: mapEsimStatus(numberProp(sim, 'EsimStatus')),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
|
|
81
|
-
export function buildEnrichment(
|
|
82
|
-
tree: DecodedManagedObjects,
|
|
83
|
-
modemPath: string,
|
|
84
|
-
signalCadence: SignalCadence,
|
|
85
|
-
cellInfo: readonly CellReading[],
|
|
86
|
-
): ModemEnrichment {
|
|
87
|
-
const revision = readRevision(tree, modemPath);
|
|
88
|
-
const serving = selectServingCell(cellInfo);
|
|
89
|
-
return {
|
|
90
|
-
...(revision !== undefined ? { revision } : {}),
|
|
91
|
-
esim: readEsimInfo(tree, modemPath),
|
|
92
|
-
signalCadence,
|
|
93
|
-
cellInfo,
|
|
94
|
-
...(serving !== undefined ? { servingCell: serving } : {}),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
detectMmFeatures,
|
|
4
|
-
type MmPropertyProbe,
|
|
5
|
-
parseMmVersion,
|
|
6
|
-
probeModemProperties,
|
|
7
|
-
} from './features';
|
|
8
|
-
import type { DecodedManagedObjects } from './managed-objects';
|
|
9
|
-
|
|
10
|
-
const BASE_MODEM_PROPS = new Set(['Manufacturer', 'Model', 'Device', 'State', 'SimType']);
|
|
11
|
-
|
|
12
|
-
function probe(overrides: Partial<MmPropertyProbe> = {}): MmPropertyProbe {
|
|
13
|
-
return { properties: BASE_MODEM_PROPS, ...overrides };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function withPhysdev(): MmPropertyProbe {
|
|
17
|
-
return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']) };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('parseMmVersion', () => {
|
|
21
|
-
test('parses major.minor.patch', () => {
|
|
22
|
-
expect(parseMmVersion('1.24.0')).toEqual({ major: 1, minor: 24 });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('parses the 1.24.2 FM350-fix release', () => {
|
|
26
|
-
expect(parseMmVersion('1.24.2')).toEqual({ major: 1, minor: 24 });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('parses major.minor without a patch', () => {
|
|
30
|
-
expect(parseMmVersion('1.20')).toEqual({ major: 1, minor: 20 });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('tolerates a pre-release suffix', () => {
|
|
34
|
-
expect(parseMmVersion('1.26.0-rc1')).toEqual({ major: 1, minor: 26 });
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('returns null for an unparseable string', () => {
|
|
38
|
-
expect(parseMmVersion('not-a-version')).toBeNull();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('detectMmFeatures — 3 property shapes', () => {
|
|
43
|
-
test('1.20 shape: no Physdev, basic cell info, eSIM, serialization', () => {
|
|
44
|
-
expect(detectMmFeatures('1.20.0', probe())).toEqual({
|
|
45
|
-
physdev: false,
|
|
46
|
-
cellInfo: 'basic',
|
|
47
|
-
esimStatus: true,
|
|
48
|
-
opSerialization: true,
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('1.22 shape: Physdev present, rich cell info', () => {
|
|
53
|
-
expect(detectMmFeatures('1.22.0', withPhysdev())).toEqual({
|
|
54
|
-
physdev: true,
|
|
55
|
-
cellInfo: 'rich',
|
|
56
|
-
esimStatus: true,
|
|
57
|
-
opSerialization: true,
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('1.24 shape: Physdev present, rich cell info', () => {
|
|
62
|
-
expect(detectMmFeatures('1.24.0', withPhysdev())).toEqual({
|
|
63
|
-
physdev: true,
|
|
64
|
-
cellInfo: 'rich',
|
|
65
|
-
esimStatus: true,
|
|
66
|
-
opSerialization: true,
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('detectMmFeatures — property presence overrides the version', () => {
|
|
72
|
-
test('physdev is property-driven: 1.24 version but no Physdev property ⇒ false', () => {
|
|
73
|
-
expect(detectMmFeatures('1.24.0', probe()).physdev).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('a 1.22+ modem that omits Physdev still reports rich cell info by version', () => {
|
|
77
|
-
expect(detectMmFeatures('1.22.0', probe()).cellInfo).toBe('rich');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('rich cell-info fields promote the tier even without a version signal', () => {
|
|
81
|
-
const withFields = probe({ cellInfoFields: new Set(['serving-type', 'bandwidth']) });
|
|
82
|
-
expect(detectMmFeatures('1.20.0', withFields).cellInfo).toBe('rich');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('detectMmFeatures — degrade paths', () => {
|
|
87
|
-
test('below 1.20 has no cell info, no eSIM, no serialization', () => {
|
|
88
|
-
expect(detectMmFeatures('1.18.0', probe({ properties: new Set(['Device']) }))).toEqual({
|
|
89
|
-
physdev: false,
|
|
90
|
-
cellInfo: 'none',
|
|
91
|
-
esimStatus: false,
|
|
92
|
-
opSerialization: false,
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('a modem advertising no cell-info capability degrades to none on a supported version', () => {
|
|
97
|
-
expect(detectMmFeatures('1.24.0', withPhysdevNoCellInfo()).cellInfo).toBe('none');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('an eSIM property forces esimStatus true even on an unparseable version', () => {
|
|
101
|
-
const esimProbe = probe({ properties: new Set(['EsimStatus']) });
|
|
102
|
-
expect(detectMmFeatures('garbage', esimProbe).esimStatus).toBe(true);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('detectMmFeatures — unknown future version probes, never throws', () => {
|
|
107
|
-
test('1.26.0 falls back to property probing, not a whitelist throw', () => {
|
|
108
|
-
expect(() => detectMmFeatures('1.26.0', withPhysdev())).not.toThrow();
|
|
109
|
-
expect(detectMmFeatures('1.26.0', withPhysdev())).toEqual({
|
|
110
|
-
physdev: true,
|
|
111
|
-
cellInfo: 'rich',
|
|
112
|
-
esimStatus: true,
|
|
113
|
-
opSerialization: true,
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('a totally unparseable version degrades to the safe floor (property-only physdev)', () => {
|
|
118
|
-
const physdevOnly = { properties: new Set(['Device', 'Physdev']) };
|
|
119
|
-
expect(detectMmFeatures('???', physdevOnly)).toEqual({
|
|
120
|
-
physdev: true,
|
|
121
|
-
cellInfo: 'none',
|
|
122
|
-
esimStatus: false,
|
|
123
|
-
opSerialization: false,
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
function withPhysdevNoCellInfo(): MmPropertyProbe {
|
|
129
|
-
return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']), cellInfoAvailable: false };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
describe('probeModemProperties', () => {
|
|
133
|
-
test('collects property names across Modem, Modem3gpp and the active SIM', () => {
|
|
134
|
-
const tree: DecodedManagedObjects = [
|
|
135
|
-
[
|
|
136
|
-
'/org/freedesktop/ModemManager1/Modem/0',
|
|
137
|
-
[
|
|
138
|
-
[
|
|
139
|
-
'org.freedesktop.ModemManager1.Modem',
|
|
140
|
-
[
|
|
141
|
-
['Device', { signature: 's', value: 'slot-usb2-1' }],
|
|
142
|
-
['Physdev', { signature: 's', value: '/sys/devices/usb2' }],
|
|
143
|
-
['Sim', { signature: 'o', value: '/org/freedesktop/ModemManager1/SIM/0' }],
|
|
144
|
-
],
|
|
145
|
-
],
|
|
146
|
-
[
|
|
147
|
-
'org.freedesktop.ModemManager1.Modem.Modem3gpp',
|
|
148
|
-
[['Imei', { signature: 's', value: '490154203237518' }]],
|
|
149
|
-
],
|
|
150
|
-
],
|
|
151
|
-
],
|
|
152
|
-
[
|
|
153
|
-
'/org/freedesktop/ModemManager1/SIM/0',
|
|
154
|
-
[['org.freedesktop.ModemManager1.Sim', [['SimType', { signature: 'u', value: 2 }]]]],
|
|
155
|
-
],
|
|
156
|
-
];
|
|
157
|
-
const result = probeModemProperties(tree, '/org/freedesktop/ModemManager1/Modem/0');
|
|
158
|
-
expect(result.properties.has('Physdev')).toBe(true);
|
|
159
|
-
expect(result.properties.has('Imei')).toBe(true);
|
|
160
|
-
expect(result.properties.has('SimType')).toBe(true);
|
|
161
|
-
});
|
|
162
|
-
});
|