@ceralive/modem-control 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +101 -0
- package/dist/backend/transition-preconditions.js +132 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +216 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +27 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +61 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +137 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +171 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +88 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +138 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +6 -0
- package/dist/usb-mode/index.js +16 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +106 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -236
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
// Persistence contract — mode 0600 (real fs.stat), fail-soft corruption recovery
|
|
2
|
-
// with METADATA-ONLY logging, and the no-PII guarantee on the serialized bytes.
|
|
3
|
-
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
5
|
-
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import {
|
|
9
|
-
createUsageFileStore,
|
|
10
|
-
type PersistedUsage,
|
|
11
|
-
USAGE_SCHEMA_VERSION,
|
|
12
|
-
type UsageLogEvent,
|
|
13
|
-
} from './store';
|
|
14
|
-
|
|
15
|
-
let dir: string;
|
|
16
|
-
let path: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(async () => {
|
|
19
|
-
dir = await mkdtemp(join(tmpdir(), 'usage-store-'));
|
|
20
|
-
path = join(dir, 'usage.json');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await rm(dir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const sample: PersistedUsage = {
|
|
28
|
-
schemaVersion: USAGE_SCHEMA_VERSION,
|
|
29
|
-
bootId: 'boot-uuid-1',
|
|
30
|
-
savedAtMs: 1_700_000_000_000,
|
|
31
|
-
slots: [
|
|
32
|
-
{ logicalSlotId: 'slot-a', cycleBytes: 1234, cycleStartMs: 1_699_000_000_000 },
|
|
33
|
-
{
|
|
34
|
-
logicalSlotId: 'slot-b',
|
|
35
|
-
cycleBytes: 42,
|
|
36
|
-
cycleStartMs: 1_699_000_000_000,
|
|
37
|
-
mappingGeneration: 2,
|
|
38
|
-
ifname: 'wwan0',
|
|
39
|
-
lastObserved: 999,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
describe('UsageStore — versioned round-trip', () => {
|
|
45
|
-
test('save then load returns identical, schema-versioned state', async () => {
|
|
46
|
-
const store = createUsageFileStore({ path });
|
|
47
|
-
await store.save(sample);
|
|
48
|
-
const loaded = await store.load('boot-uuid-1', 1_700_000_100_000);
|
|
49
|
-
expect(loaded.schemaVersion).toBe(USAGE_SCHEMA_VERSION);
|
|
50
|
-
expect(loaded).toEqual(sample);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('an absent file loads as fresh empty state (no write, no warning)', async () => {
|
|
54
|
-
const events: UsageLogEvent[] = [];
|
|
55
|
-
const store = createUsageFileStore({ path, logger: (e) => events.push(e) });
|
|
56
|
-
const loaded = await store.load('boot-uuid-9', 5);
|
|
57
|
-
expect(loaded.slots).toEqual([]);
|
|
58
|
-
expect(loaded.bootId).toBe('boot-uuid-9');
|
|
59
|
-
expect(events).toHaveLength(0);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('UsageStore — mode 0600 via chmod-after-write', () => {
|
|
64
|
-
test('the persisted file ends up owner-read/write only (real fs.stat)', async () => {
|
|
65
|
-
const store = createUsageFileStore({ path });
|
|
66
|
-
await store.save(sample);
|
|
67
|
-
const info = await stat(path);
|
|
68
|
-
expect(info.mode & 0o777).toBe(0o600);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('UsageStore — fail-soft corruption recovery', () => {
|
|
73
|
-
test('a corrupt file logs METADATA ONLY and recreates a fresh 0600 file', async () => {
|
|
74
|
-
// Corrupt content carrying a secret-looking token we must never echo in the log.
|
|
75
|
-
const corrupt = '{ this is not json ICCID=8988211000000123456 }';
|
|
76
|
-
await writeFile(path, corrupt);
|
|
77
|
-
const events: UsageLogEvent[] = [];
|
|
78
|
-
const store = createUsageFileStore({ path, logger: (e) => events.push(e) });
|
|
79
|
-
|
|
80
|
-
const loaded = await store.load('boot-uuid-2', 77);
|
|
81
|
-
|
|
82
|
-
// Exactly one metadata-only warning.
|
|
83
|
-
expect(events).toHaveLength(1);
|
|
84
|
-
const event = events[0];
|
|
85
|
-
expect(event?.kind).toBe('corrupt-state');
|
|
86
|
-
expect(event?.bytes).toBe(Buffer.byteLength(corrupt, 'utf8'));
|
|
87
|
-
expect(typeof event?.reason).toBe('string');
|
|
88
|
-
// The log NEVER contains the raw content (no secret token leak).
|
|
89
|
-
expect(JSON.stringify(event)).not.toContain('ICCID');
|
|
90
|
-
expect(JSON.stringify(event)).not.toContain('8988211000000123456');
|
|
91
|
-
|
|
92
|
-
// Fail-soft: fresh empty state returned and the file recreated at mode 0600.
|
|
93
|
-
expect(loaded.slots).toEqual([]);
|
|
94
|
-
expect(loaded.bootId).toBe('boot-uuid-2');
|
|
95
|
-
const info = await stat(path);
|
|
96
|
-
expect(info.mode & 0o777).toBe(0o600);
|
|
97
|
-
|
|
98
|
-
// The recreated file is now valid — a second load is clean (no new warning).
|
|
99
|
-
const reloaded = await store.load('boot-uuid-2', 78);
|
|
100
|
-
expect(reloaded.slots).toEqual([]);
|
|
101
|
-
expect(events).toHaveLength(1);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('an incompatible schema version is treated as corruption and recreated', async () => {
|
|
105
|
-
await writeFile(
|
|
106
|
-
path,
|
|
107
|
-
JSON.stringify({ schemaVersion: 999, bootId: 'x', savedAtMs: 0, slots: [] }),
|
|
108
|
-
);
|
|
109
|
-
const events: UsageLogEvent[] = [];
|
|
110
|
-
const store = createUsageFileStore({ path, logger: (e) => events.push(e) });
|
|
111
|
-
const loaded = await store.load('boot-uuid-3', 1);
|
|
112
|
-
expect(events).toHaveLength(1);
|
|
113
|
-
expect(events[0]?.reason).toContain('schemaVersion');
|
|
114
|
-
expect(loaded.slots).toEqual([]);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('UsageStore — no PII in the persisted bytes', () => {
|
|
119
|
-
test('the serialized file contains only opaque ids and numbers', async () => {
|
|
120
|
-
const store = createUsageFileStore({ path });
|
|
121
|
-
await store.save(sample);
|
|
122
|
-
const raw = await readFile(path, 'utf8');
|
|
123
|
-
|
|
124
|
-
// No subscriber/device-identifying fields, by construction. Word-bounded so a
|
|
125
|
-
// benign key like `mappingGeneration` (contains "pin") is not a false positive.
|
|
126
|
-
expect(raw).not.toMatch(
|
|
127
|
-
/\b(iccid|imsi|imei|eid|msisdn|operator|manufacturer|subscriber|pin|puk|password)\b/i,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// Every slot object exposes only the allowed keys; ids are opaque strings.
|
|
131
|
-
const parsed = JSON.parse(raw) as PersistedUsage;
|
|
132
|
-
const allowed = new Set([
|
|
133
|
-
'logicalSlotId',
|
|
134
|
-
'cycleBytes',
|
|
135
|
-
'cycleStartMs',
|
|
136
|
-
'mappingGeneration',
|
|
137
|
-
'ifname',
|
|
138
|
-
'lastObserved',
|
|
139
|
-
]);
|
|
140
|
-
for (const slot of parsed.slots) {
|
|
141
|
-
for (const keyName of Object.keys(slot)) {
|
|
142
|
-
expect(allowed.has(keyName)).toBe(true);
|
|
143
|
-
}
|
|
144
|
-
expect(typeof slot.logicalSlotId).toBe('string');
|
|
145
|
-
expect(typeof slot.cycleBytes).toBe('number');
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
// Versioned, fail-soft persistence for the usage sampler.
|
|
2
|
-
//
|
|
3
|
-
// The persisted file is a small JSON document carrying ONLY opaque slot ids,
|
|
4
|
-
// interface names, and numbers — never any subscriber/device identity (no ICCID,
|
|
5
|
-
// IMSI, IMEI, operator or model). By construction the sampler holds no such fields,
|
|
6
|
-
// and `no-pii.test.ts` proves the serialized bytes stay clean.
|
|
7
|
-
//
|
|
8
|
-
// Two hard guarantees:
|
|
9
|
-
// - MODE 0600: the file is written to a temp path, chmod'd to 0600 AFTER the
|
|
10
|
-
// write, then atomically renamed over the target — so the on-disk file always
|
|
11
|
-
// ends up owner-read/write-only regardless of the process umask.
|
|
12
|
-
// - FAIL-SOFT ON CORRUPTION: an unparseable/incompatible file is never fatal. We
|
|
13
|
-
// log METADATA ONLY (byte length + a classification reason — never the raw,
|
|
14
|
-
// possibly-sensitive content), recreate a fresh empty 0600 file, and carry on.
|
|
15
|
-
|
|
16
|
-
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
17
|
-
import { dirname } from 'node:path';
|
|
18
|
-
|
|
19
|
-
/** The current on-disk schema version. Bump when the persisted shape changes. */
|
|
20
|
-
export const USAGE_SCHEMA_VERSION = 1;
|
|
21
|
-
|
|
22
|
-
/** One slot's persisted accounting row — opaque ids and numbers only. */
|
|
23
|
-
export interface PersistedSlot {
|
|
24
|
-
readonly logicalSlotId: string;
|
|
25
|
-
readonly cycleBytes: number;
|
|
26
|
-
readonly cycleStartMs: number;
|
|
27
|
-
readonly mappingGeneration?: number;
|
|
28
|
-
readonly ifname?: string;
|
|
29
|
-
readonly lastObserved?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** The full persisted document. `bootId` scopes the baselines to one kernel session. */
|
|
33
|
-
export interface PersistedUsage {
|
|
34
|
-
readonly schemaVersion: typeof USAGE_SCHEMA_VERSION;
|
|
35
|
-
readonly bootId: string;
|
|
36
|
-
readonly savedAtMs: number;
|
|
37
|
-
readonly slots: readonly PersistedSlot[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** A metadata-only log event. Corruption NEVER carries the raw file content. */
|
|
41
|
-
export type UsageLogEvent = {
|
|
42
|
-
readonly kind: 'corrupt-state';
|
|
43
|
-
readonly bytes: number;
|
|
44
|
-
readonly reason: string;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/** Sink for sampler log events. Defaults to a metadata-only `console.warn`. */
|
|
48
|
-
export type UsageLogger = (event: UsageLogEvent) => void;
|
|
49
|
-
|
|
50
|
-
/** The persistence seam the sampler drives. */
|
|
51
|
-
export interface UsageStore {
|
|
52
|
-
/** Load persisted state; recreate a fresh 0600 file if absent or corrupt. */
|
|
53
|
-
load(currentBootId: string, nowMs: number): Promise<PersistedUsage>;
|
|
54
|
-
/** Atomically write state with mode 0600 (temp → chmod → rename). */
|
|
55
|
-
save(state: PersistedUsage): Promise<void>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface UsageFileStoreOptions {
|
|
59
|
-
readonly path: string;
|
|
60
|
-
readonly logger?: UsageLogger;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function defaultLogger(event: UsageLogEvent): void {
|
|
64
|
-
console.warn(`[usage-sampler] ${event.kind}: bytes=${event.bytes} reason=${event.reason}`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function freshState(bootId: string, nowMs: number): PersistedUsage {
|
|
68
|
-
return { schemaVersion: USAGE_SCHEMA_VERSION, bootId, savedAtMs: nowMs, slots: [] };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function isFiniteNumber(value: unknown): value is number {
|
|
72
|
-
return typeof value === 'number' && Number.isFinite(value);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function validateSlot(raw: unknown): PersistedSlot {
|
|
76
|
-
if (typeof raw !== 'object' || raw === null) {
|
|
77
|
-
throw new SchemaError('slot');
|
|
78
|
-
}
|
|
79
|
-
const slot = raw as Record<string, unknown>;
|
|
80
|
-
if (typeof slot.logicalSlotId !== 'string') {
|
|
81
|
-
throw new SchemaError('logicalSlotId');
|
|
82
|
-
}
|
|
83
|
-
if (!isFiniteNumber(slot.cycleBytes) || !isFiniteNumber(slot.cycleStartMs)) {
|
|
84
|
-
throw new SchemaError('cycleBytes|cycleStartMs');
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
logicalSlotId: slot.logicalSlotId,
|
|
88
|
-
cycleBytes: slot.cycleBytes,
|
|
89
|
-
cycleStartMs: slot.cycleStartMs,
|
|
90
|
-
...(isFiniteNumber(slot.mappingGeneration)
|
|
91
|
-
? { mappingGeneration: slot.mappingGeneration }
|
|
92
|
-
: {}),
|
|
93
|
-
...(typeof slot.ifname === 'string' ? { ifname: slot.ifname } : {}),
|
|
94
|
-
...(isFiniteNumber(slot.lastObserved) ? { lastObserved: slot.lastObserved } : {}),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Parse + validate the document. Throws `SchemaError` (metadata-only) on mismatch. */
|
|
99
|
-
function validate(raw: unknown): PersistedUsage {
|
|
100
|
-
if (typeof raw !== 'object' || raw === null) {
|
|
101
|
-
throw new SchemaError('document');
|
|
102
|
-
}
|
|
103
|
-
const doc = raw as Record<string, unknown>;
|
|
104
|
-
if (doc.schemaVersion !== USAGE_SCHEMA_VERSION) {
|
|
105
|
-
throw new SchemaError('schemaVersion');
|
|
106
|
-
}
|
|
107
|
-
if (typeof doc.bootId !== 'string' || !isFiniteNumber(doc.savedAtMs)) {
|
|
108
|
-
throw new SchemaError('bootId|savedAtMs');
|
|
109
|
-
}
|
|
110
|
-
if (!Array.isArray(doc.slots)) {
|
|
111
|
-
throw new SchemaError('slots');
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
schemaVersion: USAGE_SCHEMA_VERSION,
|
|
115
|
-
bootId: doc.bootId,
|
|
116
|
-
savedAtMs: doc.savedAtMs,
|
|
117
|
-
slots: doc.slots.map(validateSlot),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** A schema violation naming only the offending FIELD (never file content). */
|
|
122
|
-
class SchemaError extends Error {
|
|
123
|
-
constructor(field: string) {
|
|
124
|
-
super(`schema-mismatch: ${field}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Classify a load failure into a metadata-only reason string (no raw content). */
|
|
129
|
-
function classifyFailure(error: unknown): string {
|
|
130
|
-
if (error instanceof SchemaError) {
|
|
131
|
-
return error.message;
|
|
132
|
-
}
|
|
133
|
-
if (error instanceof SyntaxError) {
|
|
134
|
-
const offset = /position (\d+)/.exec(error.message)?.[1];
|
|
135
|
-
return offset !== undefined ? `invalid-json at offset ${offset}` : 'invalid-json';
|
|
136
|
-
}
|
|
137
|
-
return 'unreadable';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function createUsageFileStore(options: UsageFileStoreOptions): UsageStore {
|
|
141
|
-
const logger = options.logger ?? defaultLogger;
|
|
142
|
-
const { path } = options;
|
|
143
|
-
|
|
144
|
-
async function writeAtomic(state: PersistedUsage): Promise<void> {
|
|
145
|
-
await mkdir(dirname(path), { recursive: true });
|
|
146
|
-
const tmp = `${path}.tmp`;
|
|
147
|
-
await writeFile(tmp, JSON.stringify(state));
|
|
148
|
-
// chmod AFTER the write (not an open flag) so mode is 0600 regardless of umask.
|
|
149
|
-
await chmod(tmp, 0o600);
|
|
150
|
-
await rename(tmp, path);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
async load(currentBootId: string, nowMs: number): Promise<PersistedUsage> {
|
|
155
|
-
let text: string;
|
|
156
|
-
try {
|
|
157
|
-
text = await readFile(path, 'utf8');
|
|
158
|
-
} catch {
|
|
159
|
-
// Absent (or unreadable) → start empty; the first save lays down a 0600 file.
|
|
160
|
-
return freshState(currentBootId, nowMs);
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
return validate(JSON.parse(text));
|
|
164
|
-
} catch (error) {
|
|
165
|
-
logger({
|
|
166
|
-
kind: 'corrupt-state',
|
|
167
|
-
bytes: Buffer.byteLength(text, 'utf8'),
|
|
168
|
-
reason: classifyFailure(error),
|
|
169
|
-
});
|
|
170
|
-
const fresh = freshState(currentBootId, nowMs);
|
|
171
|
-
await writeAtomic(fresh);
|
|
172
|
-
return fresh;
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
save: writeAtomic,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// The enumerator parses `udevadm info --export-db` into device snapshots: interface
|
|
2
|
-
// class/subclass/protocol come from the parent device's `ID_USB_INTERFACES`, each
|
|
3
|
-
// interface's driver from its own `usb_interface` record. The raw read is injectable
|
|
4
|
-
// so this runs with canned udev text and no hardware, and `enumerate` re-reads every
|
|
5
|
-
// call (refresh-triggered, never cached).
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
import { createUsbEnumerator, parseUdevDatabase } from './usb-enumerator';
|
|
9
|
-
|
|
10
|
-
const UDEV_DB = `P: /devices/pci0000:00/usb1/1-1
|
|
11
|
-
E: SUBSYSTEM=usb
|
|
12
|
-
E: DEVTYPE=usb_device
|
|
13
|
-
E: ID_VENDOR_ID=2c7c
|
|
14
|
-
E: ID_MODEL_ID=0125
|
|
15
|
-
E: ID_MODEL=EG25-G
|
|
16
|
-
E: ID_REVISION=0318
|
|
17
|
-
E: ID_PATH=pci-0000:00-usb-0:1
|
|
18
|
-
E: ID_USB_INTERFACES=:ff0000:ffffff:
|
|
19
|
-
|
|
20
|
-
P: /devices/pci0000:00/usb1/1-1/1-1:1.0
|
|
21
|
-
E: SUBSYSTEM=usb
|
|
22
|
-
E: DEVTYPE=usb_interface
|
|
23
|
-
E: DRIVER=option
|
|
24
|
-
|
|
25
|
-
P: /devices/pci0000:00/usb1/1-1/1-1:1.1
|
|
26
|
-
E: SUBSYSTEM=usb
|
|
27
|
-
E: DEVTYPE=usb_interface
|
|
28
|
-
E: DRIVER=qmi_wwan
|
|
29
|
-
|
|
30
|
-
P: /devices/pci0000:00/usb2/2-1
|
|
31
|
-
E: SUBSYSTEM=usb
|
|
32
|
-
E: DEVTYPE=usb_device
|
|
33
|
-
E: ID_VENDOR_ID=12d1
|
|
34
|
-
E: ID_MODEL_ID=14db
|
|
35
|
-
E: ID_MODEL=HUAWEI_HiLink
|
|
36
|
-
E: ID_USB_INTERFACES=:020600:0a0000:
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
describe('parseUdevDatabase', () => {
|
|
40
|
-
test('parses two devices with their vendor/product/model', () => {
|
|
41
|
-
const devices = parseUdevDatabase(UDEV_DB);
|
|
42
|
-
expect(devices).toHaveLength(2);
|
|
43
|
-
const quectel = devices.find((d) => d.vendorId === '2c7c');
|
|
44
|
-
expect(quectel?.productId).toBe('0125');
|
|
45
|
-
expect(quectel?.model).toBe('EG25-G');
|
|
46
|
-
expect(quectel?.firmwareRevision).toBe('0318');
|
|
47
|
-
expect(quectel?.physicalUid).toBe('pci-0000:00-usb-0:1');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('parses interface class triples from ID_USB_INTERFACES', () => {
|
|
51
|
-
const devices = parseUdevDatabase(UDEV_DB);
|
|
52
|
-
const huawei = devices.find((d) => d.vendorId === '12d1');
|
|
53
|
-
expect(huawei?.interfaces).toEqual([
|
|
54
|
-
{ interfaceClass: 0x02, interfaceSubClass: 0x06, interfaceProtocol: 0x00 },
|
|
55
|
-
{ interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x00 },
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('stitches each interface driver onto its parent device', () => {
|
|
60
|
-
const devices = parseUdevDatabase(UDEV_DB);
|
|
61
|
-
const quectel = devices.find((d) => d.vendorId === '2c7c');
|
|
62
|
-
expect(quectel?.interfaces[0]?.driver).toBe('option');
|
|
63
|
-
expect(quectel?.interfaces[1]?.driver).toBe('qmi_wwan');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('a record without a vendor/product id is skipped', () => {
|
|
67
|
-
const devices = parseUdevDatabase('P: /devices/x\nE: DEVTYPE=usb_device\nE: SUBSYSTEM=usb\n');
|
|
68
|
-
expect(devices).toEqual([]);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('createUsbEnumerator', () => {
|
|
73
|
-
test('enumerate() re-reads the injected source every call (refresh-triggered)', async () => {
|
|
74
|
-
let reads = 0;
|
|
75
|
-
const enumerator = createUsbEnumerator({
|
|
76
|
-
readUdevDatabase: () => {
|
|
77
|
-
reads += 1;
|
|
78
|
-
return Promise.resolve(UDEV_DB);
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
const first = await enumerator.enumerate();
|
|
82
|
-
const second = await enumerator.enumerate();
|
|
83
|
-
expect(first).toHaveLength(2);
|
|
84
|
-
expect(second).toHaveLength(2);
|
|
85
|
-
expect(reads).toBe(2);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
// Production USB enumeration — a refresh-triggered snapshot of udev/sysfs state.
|
|
2
|
-
//
|
|
3
|
-
// `enumerate()` shells out to `udevadm info --export-db` (via `Bun.spawn`) each call
|
|
4
|
-
// — it is deliberately NOT cached, so a caller re-reads current state after a hot-plug
|
|
5
|
-
// or a mode switch. The raw-database read is an injectable seam (`readUdevDatabase`)
|
|
6
|
-
// so tests drive canned udev output with no real hardware, and the parser itself is a
|
|
7
|
-
// pure, exported function. udev exports every interface's class/subclass/protocol on
|
|
8
|
-
// the parent device as `ID_USB_INTERFACES` (`:ff0000:0a0000:` …), and each interface's
|
|
9
|
-
// bound DRIVER on its own `usb_interface` record — this parser stitches the two.
|
|
10
|
-
|
|
11
|
-
import type { UsbDeviceSnapshot, UsbInterface } from './device-classifier';
|
|
12
|
-
|
|
13
|
-
/** The injectable dependencies for the enumerator. */
|
|
14
|
-
export interface UsbEnumeratorDeps {
|
|
15
|
-
/** Provides the raw `udevadm info --export-db` text. Defaults to a `Bun.spawn` call. */
|
|
16
|
-
readonly readUdevDatabase?: () => Promise<string>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** A refresh-triggered USB device enumerator. */
|
|
20
|
-
export interface UsbEnumerator {
|
|
21
|
-
/** Snapshot current USB state — re-reads udev every call (never cached). */
|
|
22
|
-
enumerate(): Promise<readonly UsbDeviceSnapshot[]>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function defaultReadUdevDatabase(): Promise<string> {
|
|
26
|
-
const proc = Bun.spawn(['udevadm', 'info', '--export-db'], { stdout: 'pipe', stderr: 'pipe' });
|
|
27
|
-
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
28
|
-
if (exitCode !== 0) {
|
|
29
|
-
const stderr = await new Response(proc.stderr).text();
|
|
30
|
-
throw new Error(`udevadm info --export-db exited ${exitCode}: ${stderr.trim()}`);
|
|
31
|
-
}
|
|
32
|
-
return stdout;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface UdevRecord {
|
|
36
|
-
readonly devpath: string;
|
|
37
|
-
readonly env: ReadonlyMap<string, string>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function parseRecords(text: string): UdevRecord[] {
|
|
41
|
-
const records: UdevRecord[] = [];
|
|
42
|
-
let devpath = '';
|
|
43
|
-
let env = new Map<string, string>();
|
|
44
|
-
const flush = (): void => {
|
|
45
|
-
if (env.size > 0 || devpath !== '') {
|
|
46
|
-
records.push({ devpath, env });
|
|
47
|
-
}
|
|
48
|
-
devpath = '';
|
|
49
|
-
env = new Map<string, string>();
|
|
50
|
-
};
|
|
51
|
-
for (const line of text.split('\n')) {
|
|
52
|
-
if (line.trim() === '') {
|
|
53
|
-
flush();
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const kind = line.slice(0, 2);
|
|
57
|
-
const rest = line.slice(3);
|
|
58
|
-
if (kind === 'P:') {
|
|
59
|
-
devpath = rest;
|
|
60
|
-
} else if (kind === 'E:') {
|
|
61
|
-
const eq = rest.indexOf('=');
|
|
62
|
-
if (eq > 0) {
|
|
63
|
-
env.set(rest.slice(0, eq), rest.slice(eq + 1));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
flush();
|
|
68
|
-
return records;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Parse an `ID_USB_INTERFACES` value (`:ff0000:0a0000:`) into class/subclass/protocol triples. */
|
|
72
|
-
function parseInterfaces(value: string | undefined): UsbInterface[] {
|
|
73
|
-
if (value === undefined) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
const interfaces: UsbInterface[] = [];
|
|
77
|
-
for (const token of value.split(':')) {
|
|
78
|
-
if (token.length !== 6) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
interfaces.push({
|
|
82
|
-
interfaceClass: Number.parseInt(token.slice(0, 2), 16),
|
|
83
|
-
interfaceSubClass: Number.parseInt(token.slice(2, 4), 16),
|
|
84
|
-
interfaceProtocol: Number.parseInt(token.slice(4, 6), 16),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return interfaces;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function interfaceIndex(devpath: string): number | undefined {
|
|
91
|
-
const dot = devpath.lastIndexOf('.');
|
|
92
|
-
if (dot < 0) {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
const index = Number.parseInt(devpath.slice(dot + 1), 10);
|
|
96
|
-
return Number.isNaN(index) ? undefined : index;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function buildSnapshot(env: ReadonlyMap<string, string>): UsbDeviceSnapshot | undefined {
|
|
100
|
-
const vendorId = env.get('ID_VENDOR_ID');
|
|
101
|
-
const productId = env.get('ID_MODEL_ID');
|
|
102
|
-
if (vendorId === undefined || productId === undefined) {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
const props: Record<string, string> = {};
|
|
106
|
-
for (const [key, value] of env) {
|
|
107
|
-
props[key] = value;
|
|
108
|
-
}
|
|
109
|
-
const model = env.get('ID_MODEL');
|
|
110
|
-
const firmwareRevision = env.get('ID_REVISION');
|
|
111
|
-
const physicalUid = env.get('ID_PATH');
|
|
112
|
-
return {
|
|
113
|
-
vendorId,
|
|
114
|
-
productId,
|
|
115
|
-
bDeviceClass: 0,
|
|
116
|
-
interfaces: parseInterfaces(env.get('ID_USB_INTERFACES')),
|
|
117
|
-
udevProperties: props,
|
|
118
|
-
...(model !== undefined ? { model } : {}),
|
|
119
|
-
...(firmwareRevision !== undefined ? { firmwareRevision } : {}),
|
|
120
|
-
...(physicalUid !== undefined ? { physicalUid } : {}),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Parse `udevadm info --export-db` output into device snapshots. Pure and exported so
|
|
126
|
-
* the production parse is unit-testable against canned udev text. Interface drivers
|
|
127
|
-
* are stitched from each `usb_interface` record onto its parent `usb_device`.
|
|
128
|
-
*/
|
|
129
|
-
export function parseUdevDatabase(text: string): UsbDeviceSnapshot[] {
|
|
130
|
-
const records = parseRecords(text);
|
|
131
|
-
const devices = new Map<string, UsbInterface[]>();
|
|
132
|
-
const snapshots = new Map<string, UsbDeviceSnapshot>();
|
|
133
|
-
|
|
134
|
-
for (const record of records) {
|
|
135
|
-
if (record.env.get('DEVTYPE') !== 'usb_device') {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
const snapshot = buildSnapshot(record.env);
|
|
139
|
-
if (snapshot !== undefined) {
|
|
140
|
-
snapshots.set(record.devpath, snapshot);
|
|
141
|
-
devices.set(record.devpath, [...snapshot.interfaces]);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
for (const record of records) {
|
|
146
|
-
if (record.env.get('DEVTYPE') !== 'usb_interface') {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const driver = record.env.get('DRIVER');
|
|
150
|
-
const index = interfaceIndex(record.devpath);
|
|
151
|
-
if (driver === undefined || index === undefined) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
const parent = record.devpath.slice(0, record.devpath.lastIndexOf('/'));
|
|
155
|
-
const ifaces = devices.get(parent);
|
|
156
|
-
const iface = ifaces?.[index];
|
|
157
|
-
if (ifaces !== undefined && iface !== undefined) {
|
|
158
|
-
ifaces[index] = { ...iface, driver };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return [...snapshots.entries()].map(([devpath, snapshot]) => ({
|
|
163
|
-
...snapshot,
|
|
164
|
-
interfaces: devices.get(devpath) ?? snapshot.interfaces,
|
|
165
|
-
}));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Create a refresh-triggered USB enumerator over an injectable udev reader. */
|
|
169
|
-
export function createUsbEnumerator(deps: UsbEnumeratorDeps = {}): UsbEnumerator {
|
|
170
|
-
const read = deps.readUdevDatabase ?? defaultReadUdevDatabase;
|
|
171
|
-
return {
|
|
172
|
-
async enumerate(): Promise<readonly UsbDeviceSnapshot[]> {
|
|
173
|
-
return parseUdevDatabase(await read());
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Convenience: enumerate current USB devices once via the default udev reader. */
|
|
179
|
-
export function enumerateUsbDevices(): Promise<readonly UsbDeviceSnapshot[]> {
|
|
180
|
-
return createUsbEnumerator().enumerate();
|
|
181
|
-
}
|