@ceralive/modem-control 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +101 -0
- package/dist/backend/transition-preconditions.js +132 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +216 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +27 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +61 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +137 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +171 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +88 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +138 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +6 -0
- package/dist/usb-mode/index.js +16 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +106 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -236
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Qualcomm UFI / HIMI — a READ-ONLY provider, by construction rather than by policy.
|
|
2
|
+
//
|
|
3
|
+
// This provider normalizes the telemetry the pure parsers already own
|
|
4
|
+
// (`hardware/router-parsers.ts` → `observations/sources/ufi.ts`, migrated in todo 17).
|
|
5
|
+
// It adds a session and a transport around them and nothing else: there is no write
|
|
6
|
+
// descriptor, no write command in the transport vocabulary, and no operation id outside
|
|
7
|
+
// the closed read set that resolves to anything but a typed refusal.
|
|
8
|
+
//
|
|
9
|
+
// The Qualcomm safety model it encodes lives in two neighbouring modules —
|
|
10
|
+
// `qualcomm-evidence.ts` (what a USB id proves) and `prohibitions.ts` (what may never be
|
|
11
|
+
// attempted). Both are inert data; neither can act.
|
|
12
|
+
import { epochMillis, sourceEpoch, stableKeyFromPhysicalModemId } from '../../domain/index.js';
|
|
13
|
+
import { parseUfiDetails, parseUfiSignal } from '../../hardware/router-parsers.js';
|
|
14
|
+
import { normalizeUfiObservation } from '../../observations/sources/ufi.js';
|
|
15
|
+
import { planUfiOperation, UFI_PROFILE, ufiReadDescriptor, ufiReadResult, ufiRefusedResult, } from './operations.js';
|
|
16
|
+
import { UFI_PROHIBITED_OPERATION_IDS } from './prohibitions.js';
|
|
17
|
+
import { UFI_DIAG_PRODUCTION_ACCESS, UFI_MATCHED_USB_IDS } from './qualcomm-evidence.js';
|
|
18
|
+
import { parseUfiReply, UFI_SESSION_REFUSAL, UfiSessionRuntime } from './session.js';
|
|
19
|
+
import { UFI_API_PATH } from './transport.js';
|
|
20
|
+
export class UfiHimiProvider extends UfiSessionRuntime {
|
|
21
|
+
async fingerprint(request) {
|
|
22
|
+
// The HIMI fingerprint needs a SESSION to read anything, so this probe is the one
|
|
23
|
+
// place a "harmless unauthenticated probe" would spend the provider's single
|
|
24
|
+
// bounded login. On a device that enumerated a USB id and it is not a HIMI id,
|
|
25
|
+
// that login would be aimed at somebody else's dongle — a credential attempt
|
|
26
|
+
// against hardware this provider has positive evidence it does not own. An
|
|
27
|
+
// ABSENT usb fact is still probed (nobody enumerated one; that is not a denial).
|
|
28
|
+
if (!this.usbEvidencePermitsProbe(request)) {
|
|
29
|
+
return {
|
|
30
|
+
signal: 'unknown',
|
|
31
|
+
strength: 'strong',
|
|
32
|
+
profiles: [],
|
|
33
|
+
detail: 'usb-id-is-not-himi',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const bodies = await this.fetch(request, ['getallstatus']);
|
|
37
|
+
const reply = parseUfiReply(bodies?.getallstatus ?? '');
|
|
38
|
+
const matched = reply?.reply === 'ok' && reply.params !== undefined;
|
|
39
|
+
return {
|
|
40
|
+
signal: matched ? 'match' : 'unknown',
|
|
41
|
+
strength: 'strong',
|
|
42
|
+
profiles: matched ? [UFI_PROFILE] : [],
|
|
43
|
+
detail: matched ? 'himi-json-shape' : 'himi-json-not-proven',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
capability() {
|
|
47
|
+
return {
|
|
48
|
+
id: 'ufi-himi-telemetry',
|
|
49
|
+
read: async (context) => {
|
|
50
|
+
const bodies = await this.fetch(context, ['getsysinfo']);
|
|
51
|
+
const reply = parseUfiReply(bodies?.getsysinfo ?? '');
|
|
52
|
+
const supported = reply?.reply === 'ok' && reply.params !== undefined;
|
|
53
|
+
return {
|
|
54
|
+
signal: supported ? 'match' : 'mismatch',
|
|
55
|
+
strength: 'strong',
|
|
56
|
+
detail: supported ? 'telemetry-reported' : 'telemetry-refused',
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async observe(context) {
|
|
62
|
+
const bodies = await this.fetch(context, [
|
|
63
|
+
'getsysinfo',
|
|
64
|
+
'getoverview',
|
|
65
|
+
'getallstatus',
|
|
66
|
+
'getproduceinfo',
|
|
67
|
+
]);
|
|
68
|
+
if (bodies === undefined)
|
|
69
|
+
return [];
|
|
70
|
+
const produceInfo = bodies.getproduceinfo;
|
|
71
|
+
return [
|
|
72
|
+
normalizeUfiObservation({
|
|
73
|
+
sysinfo: bodies.getsysinfo ?? '',
|
|
74
|
+
overview: bodies.getoverview ?? '',
|
|
75
|
+
status: bodies.getallstatus ?? '',
|
|
76
|
+
...(produceInfo === undefined ? {} : { produceInfo }),
|
|
77
|
+
}, {
|
|
78
|
+
stableKey: stableKeyFromPhysicalModemId(context.physicalModemId),
|
|
79
|
+
generation: context.generation,
|
|
80
|
+
sourceEpoch: sourceEpoch(1),
|
|
81
|
+
observedAt: epochMillis(this.observedAt()),
|
|
82
|
+
}),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
operations(_profile) {
|
|
86
|
+
return {
|
|
87
|
+
access: 'read-only',
|
|
88
|
+
reads: [
|
|
89
|
+
{
|
|
90
|
+
descriptor: ufiReadDescriptor('ufi.signal.read'),
|
|
91
|
+
read: (context) => this.readSignal(context),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
descriptor: ufiReadDescriptor('ufi.details.read'),
|
|
95
|
+
read: (context) => this.readDetails(context),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
prohibited: UFI_PROHIBITED_OPERATION_IDS,
|
|
99
|
+
diagAccess: UFI_DIAG_PRODUCTION_ACCESS,
|
|
100
|
+
plan: planUfiOperation,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async readSignal(context) {
|
|
104
|
+
const bodies = await this.fetch(context, ['getsysinfo', 'getoverview', 'getallstatus']);
|
|
105
|
+
if (bodies === undefined)
|
|
106
|
+
return ufiRefusedResult(context.generation, 'session-unavailable');
|
|
107
|
+
const value = parseUfiSignal({
|
|
108
|
+
sysinfo: bodies.getsysinfo ?? '',
|
|
109
|
+
overview: bodies.getoverview ?? '',
|
|
110
|
+
status: bodies.getallstatus ?? '',
|
|
111
|
+
});
|
|
112
|
+
return ufiReadResult(context.generation, value);
|
|
113
|
+
}
|
|
114
|
+
async readDetails(context) {
|
|
115
|
+
const bodies = await this.fetch(context, ['getoverview', 'getsysinfo', 'getproduceinfo']);
|
|
116
|
+
if (bodies === undefined)
|
|
117
|
+
return ufiRefusedResult(context.generation, 'session-unavailable');
|
|
118
|
+
const details = parseUfiDetails({
|
|
119
|
+
overview: bodies.getoverview ?? '',
|
|
120
|
+
sysinfo: bodies.getsysinfo ?? '',
|
|
121
|
+
produceInfo: bodies.getproduceinfo ?? '',
|
|
122
|
+
});
|
|
123
|
+
return details === undefined
|
|
124
|
+
? ufiRefusedResult(context.generation, 'not-reported')
|
|
125
|
+
: ufiReadResult(context.generation, details);
|
|
126
|
+
}
|
|
127
|
+
usbEvidencePermitsProbe(request) {
|
|
128
|
+
const enumerated = request.passiveFacts.filter((fact) => fact.kind === 'usb');
|
|
129
|
+
return (enumerated.length === 0 ||
|
|
130
|
+
enumerated.some((fact) => UFI_MATCHED_USB_IDS.some((id) => id === fact.value)));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reads are issued sequentially on ONE session. A `SessionOut` in any body drops the
|
|
134
|
+
* cached session so the NEXT generation may spend its own single login; it never
|
|
135
|
+
* re-authenticates inside this cycle.
|
|
136
|
+
*/
|
|
137
|
+
async fetch(request, commands) {
|
|
138
|
+
const session = await this.session(request);
|
|
139
|
+
if (session === undefined)
|
|
140
|
+
return undefined;
|
|
141
|
+
const bodies = {};
|
|
142
|
+
for (const command of commands) {
|
|
143
|
+
const response = await this.read(command, session);
|
|
144
|
+
bodies[command] = response.status === 200 ? response.body : '';
|
|
145
|
+
}
|
|
146
|
+
const refused = Object.values(bodies).some((body) => parseUfiReply(body)?.reply === UFI_SESSION_REFUSAL);
|
|
147
|
+
if (refused)
|
|
148
|
+
this.forgetSession(request);
|
|
149
|
+
return bodies;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function createUfiHimiDefinition(options) {
|
|
153
|
+
const runtime = new UfiHimiProvider(options);
|
|
154
|
+
return {
|
|
155
|
+
id: 'ufi-himi',
|
|
156
|
+
profileVersion: '1',
|
|
157
|
+
eligibleTransports: ['network'],
|
|
158
|
+
passiveMatchers: [
|
|
159
|
+
{
|
|
160
|
+
// ONE matcher over both ids. `9024` is the RNDIS+ADB composition and `9091` is
|
|
161
|
+
// a firmware-chosen product id; both identify a HIMI stick, and NEITHER is
|
|
162
|
+
// evidence of a DIAG channel (see qualcomm-evidence.ts).
|
|
163
|
+
id: 'usb-qualcomm-himi',
|
|
164
|
+
fact: 'usb',
|
|
165
|
+
expected: UFI_MATCHED_USB_IDS,
|
|
166
|
+
profiles: [UFI_PROFILE],
|
|
167
|
+
strength: 'strong',
|
|
168
|
+
required: true,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
unauthenticatedProbes: [
|
|
172
|
+
{ id: 'ufi-himi-shape', run: (request) => runtime.fingerprint(request) },
|
|
173
|
+
],
|
|
174
|
+
capabilityReaders: [runtime.capability()],
|
|
175
|
+
observe: (context) => runtime.observe(context),
|
|
176
|
+
operations: (profile) => runtime.operations(profile),
|
|
177
|
+
contractFixtures: [
|
|
178
|
+
{
|
|
179
|
+
profile: UFI_PROFILE,
|
|
180
|
+
request: {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
path: UFI_API_PATH,
|
|
183
|
+
cmdid: 'login',
|
|
184
|
+
credentials: '[redacted]',
|
|
185
|
+
interfaceBound: true,
|
|
186
|
+
redirects: 'disabled',
|
|
187
|
+
},
|
|
188
|
+
response: { reply: 'ok', session: '[redacted]' },
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
profile: UFI_PROFILE,
|
|
192
|
+
request: {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
path: UFI_API_PATH,
|
|
195
|
+
cmdid: 'getsysinfo',
|
|
196
|
+
authorization: '[redacted]',
|
|
197
|
+
interfaceBound: true,
|
|
198
|
+
redirects: 'disabled',
|
|
199
|
+
},
|
|
200
|
+
response: { reply: 'ok', params: '[verbatim-diagnostics]' },
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const UFI_RNDIS_ADB_USB_ID = "05c6:9024";
|
|
2
|
+
export declare const UFI_FIRMWARE_SPECIFIC_USB_ID = "05c6:9091";
|
|
3
|
+
export type UfiUsbClaim = 'rndis-network' | 'adb-interface';
|
|
4
|
+
export declare const UFI_USB_EVIDENCE: {
|
|
5
|
+
readonly "05c6:9024": readonly ["rndis-network", "adb-interface"];
|
|
6
|
+
/** Deliberately empty: a firmware-chosen product id claims nothing by itself. */
|
|
7
|
+
readonly "05c6:9091": readonly [];
|
|
8
|
+
};
|
|
9
|
+
export declare const UFI_MATCHED_USB_IDS: readonly ["05c6:9024", "05c6:9091"];
|
|
10
|
+
export declare function ufiUsbClaims(usbId: string): readonly UfiUsbClaim[];
|
|
11
|
+
export type UfiUsbInterfaceDescriptor = {
|
|
12
|
+
readonly number: number;
|
|
13
|
+
readonly interfaceClass: number;
|
|
14
|
+
readonly interfaceSubClass: number;
|
|
15
|
+
readonly interfaceProtocol: number;
|
|
16
|
+
};
|
|
17
|
+
export type UfiDiagEvidence = {
|
|
18
|
+
readonly state: 'descriptor-confirmed';
|
|
19
|
+
readonly interfaceNumber: number;
|
|
20
|
+
} | {
|
|
21
|
+
readonly state: 'not-proven';
|
|
22
|
+
readonly reason: 'product-id-is-not-evidence' | 'no-diag-interface-descriptor';
|
|
23
|
+
};
|
|
24
|
+
export declare function classifyUfiDiagEvidence(input: {
|
|
25
|
+
readonly usbId: string;
|
|
26
|
+
readonly interfaces: readonly UfiUsbInterfaceDescriptor[];
|
|
27
|
+
}): UfiDiagEvidence;
|
|
28
|
+
/**
|
|
29
|
+
* Unconditional. A descriptor-confirmed DIAG channel raises what a SUPERVISED BENCH
|
|
30
|
+
* operator may attempt by hand; it never raises what this package may do on its own.
|
|
31
|
+
*/
|
|
32
|
+
export declare const UFI_DIAG_PRODUCTION_ACCESS: 'prohibited';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// What a Qualcomm USB id does and does not prove.
|
|
2
|
+
//
|
|
3
|
+
// `05c6:9024` is the composition the bench UFI sticks enumerate in: RNDIS networking
|
|
4
|
+
// plus an ADB interface. That is EVIDENCE OF A COMPOSITION, not a permission — the ADB
|
|
5
|
+
// interface being present is exactly why production must be explicit that it never uses
|
|
6
|
+
// it (see `shell.transport-fallback` in prohibitions.ts).
|
|
7
|
+
//
|
|
8
|
+
// `05c6:9091` is a FIRMWARE-SPECIFIC product id. It is routinely read as "DIAG is
|
|
9
|
+
// available", and that reading is wrong: `05c6` is Qualcomm's generic vendor id and the
|
|
10
|
+
// product id is chosen by whoever built the firmware image, so it says nothing about
|
|
11
|
+
// which interfaces the device actually exposes. Treating it as proof of DIAG is how a
|
|
12
|
+
// tool ends up opening a channel that is not there, or worse, one that is there and
|
|
13
|
+
// belongs to something else.
|
|
14
|
+
//
|
|
15
|
+
// The only thing that proves a DIAG channel is a DIAG INTERFACE DESCRIPTOR. And even a
|
|
16
|
+
// confirmed one buys nothing in production: `UFI_DIAG_PRODUCTION_ACCESS` is `prohibited`
|
|
17
|
+
// unconditionally, and the supervised read-only info probe is a bench runbook
|
|
18
|
+
// (docs/UFI-DIAG-PROBE.md), never an operation this package can perform.
|
|
19
|
+
export const UFI_RNDIS_ADB_USB_ID = '05c6:9024';
|
|
20
|
+
export const UFI_FIRMWARE_SPECIFIC_USB_ID = '05c6:9091';
|
|
21
|
+
export const UFI_USB_EVIDENCE = {
|
|
22
|
+
[UFI_RNDIS_ADB_USB_ID]: ['rndis-network', 'adb-interface'],
|
|
23
|
+
/** Deliberately empty: a firmware-chosen product id claims nothing by itself. */
|
|
24
|
+
[UFI_FIRMWARE_SPECIFIC_USB_ID]: [],
|
|
25
|
+
};
|
|
26
|
+
export const UFI_MATCHED_USB_IDS = [UFI_RNDIS_ADB_USB_ID, UFI_FIRMWARE_SPECIFIC_USB_ID];
|
|
27
|
+
export function ufiUsbClaims(usbId) {
|
|
28
|
+
return Object.hasOwn(UFI_USB_EVIDENCE, usbId)
|
|
29
|
+
? UFI_USB_EVIDENCE[usbId]
|
|
30
|
+
: [];
|
|
31
|
+
}
|
|
32
|
+
/** Qualcomm's DIAG interface: vendor-specific class/subclass with protocol 0x30. */
|
|
33
|
+
const DIAG_INTERFACE_CLASS = 0xff;
|
|
34
|
+
const DIAG_INTERFACE_SUBCLASS = 0xff;
|
|
35
|
+
const DIAG_INTERFACE_PROTOCOL = 0x30;
|
|
36
|
+
export function classifyUfiDiagEvidence(input) {
|
|
37
|
+
const diag = input.interfaces.find((descriptor) => descriptor.interfaceClass === DIAG_INTERFACE_CLASS &&
|
|
38
|
+
descriptor.interfaceSubClass === DIAG_INTERFACE_SUBCLASS &&
|
|
39
|
+
descriptor.interfaceProtocol === DIAG_INTERFACE_PROTOCOL);
|
|
40
|
+
if (diag !== undefined) {
|
|
41
|
+
return { state: 'descriptor-confirmed', interfaceNumber: diag.number };
|
|
42
|
+
}
|
|
43
|
+
return input.interfaces.length === 0
|
|
44
|
+
? { state: 'not-proven', reason: 'product-id-is-not-evidence' }
|
|
45
|
+
: { state: 'not-proven', reason: 'no-diag-interface-descriptor' };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Unconditional. A descriptor-confirmed DIAG channel raises what a SUPERVISED BENCH
|
|
49
|
+
* operator may attempt by hand; it never raises what this package may do on its own.
|
|
50
|
+
*/
|
|
51
|
+
export const UFI_DIAG_PRODUCTION_ACCESS = 'prohibited';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ProviderMatchRequest } from '../contracts.js';
|
|
3
|
+
import { type UfiHttpResponse, type UfiReadCommand, type UfiTransport } from './transport.js';
|
|
4
|
+
export type UfiCredentials = {
|
|
5
|
+
readonly username: string;
|
|
6
|
+
readonly password: string;
|
|
7
|
+
};
|
|
8
|
+
export type UfiOptions = {
|
|
9
|
+
readonly interfaceName: string;
|
|
10
|
+
readonly adminUrl: string;
|
|
11
|
+
readonly transport: UfiTransport;
|
|
12
|
+
/** Ephemeral bench input. Injected by the caller, never persisted, never logged. */
|
|
13
|
+
readonly credentials: UfiCredentials;
|
|
14
|
+
readonly now?: () => number;
|
|
15
|
+
};
|
|
16
|
+
/** The HIMI firmware's own word for "your session is gone". */
|
|
17
|
+
export declare const UFI_SESSION_REFUSAL = "SessionOut";
|
|
18
|
+
declare const ufiReplySchema: z.ZodObject<{
|
|
19
|
+
reply: z.ZodString;
|
|
20
|
+
session: z.ZodOptional<z.ZodString>;
|
|
21
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
export type UfiReply = z.infer<typeof ufiReplySchema>;
|
|
24
|
+
export declare function parseUfiReply(body: string): UfiReply | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Session acquisition, bounded to ONE login per physical modem per generation.
|
|
27
|
+
*
|
|
28
|
+
* The bound is a set of spent keys rather than a counter reset on failure: a HIMI
|
|
29
|
+
* device that refused a credential refuses it again, and a provider that re-tries on
|
|
30
|
+
* every poll turns a wrong password into a lockout nobody asked for. A refused or
|
|
31
|
+
* expired session therefore surfaces as an honest auth-expired READING; the next
|
|
32
|
+
* enumeration (a new generation) gets the next single attempt.
|
|
33
|
+
*/
|
|
34
|
+
export declare class UfiSessionRuntime {
|
|
35
|
+
#private;
|
|
36
|
+
protected readonly options: UfiOptions;
|
|
37
|
+
constructor(options: UfiOptions);
|
|
38
|
+
protected session(request: ProviderMatchRequest): Promise<string | undefined>;
|
|
39
|
+
protected read(command: UfiReadCommand, session: string): Promise<UfiHttpResponse>;
|
|
40
|
+
/** A refused session is dropped so the NEXT generation may spend its own attempt. */
|
|
41
|
+
protected forgetSession(request: ProviderMatchRequest): void;
|
|
42
|
+
protected sessionKey(request: ProviderMatchRequest): string;
|
|
43
|
+
protected observedAt(): number;
|
|
44
|
+
private request;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { UFI_API_PATH, } from './transport.js';
|
|
3
|
+
/** The HIMI firmware's own word for "your session is gone". */
|
|
4
|
+
export const UFI_SESSION_REFUSAL = 'SessionOut';
|
|
5
|
+
const flatRecordSchema = z.record(z.string(), z.union([z.string(), z.number()]));
|
|
6
|
+
const ufiReplySchema = z.object({
|
|
7
|
+
reply: z.string(),
|
|
8
|
+
session: z.string().optional(),
|
|
9
|
+
params: flatRecordSchema.optional(),
|
|
10
|
+
});
|
|
11
|
+
export function parseUfiReply(body) {
|
|
12
|
+
const parsed = z
|
|
13
|
+
.string()
|
|
14
|
+
.transform((value, context) => {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(value);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (!(error instanceof SyntaxError))
|
|
20
|
+
throw error;
|
|
21
|
+
context.addIssue({ code: 'custom', message: 'invalid JSON' });
|
|
22
|
+
return z.NEVER;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.pipe(ufiReplySchema)
|
|
26
|
+
.safeParse(body);
|
|
27
|
+
return parsed.success ? parsed.data : undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Session acquisition, bounded to ONE login per physical modem per generation.
|
|
31
|
+
*
|
|
32
|
+
* The bound is a set of spent keys rather than a counter reset on failure: a HIMI
|
|
33
|
+
* device that refused a credential refuses it again, and a provider that re-tries on
|
|
34
|
+
* every poll turns a wrong password into a lockout nobody asked for. A refused or
|
|
35
|
+
* expired session therefore surfaces as an honest auth-expired READING; the next
|
|
36
|
+
* enumeration (a new generation) gets the next single attempt.
|
|
37
|
+
*/
|
|
38
|
+
export class UfiSessionRuntime {
|
|
39
|
+
options;
|
|
40
|
+
#sessions = new Map();
|
|
41
|
+
#spent = new Set();
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = options;
|
|
44
|
+
}
|
|
45
|
+
async session(request) {
|
|
46
|
+
const key = this.sessionKey(request);
|
|
47
|
+
const held = this.#sessions.get(key);
|
|
48
|
+
if (held !== undefined)
|
|
49
|
+
return held;
|
|
50
|
+
if (this.#spent.has(key))
|
|
51
|
+
return undefined;
|
|
52
|
+
this.#spent.add(key);
|
|
53
|
+
const response = await this.request('login', JSON.stringify({
|
|
54
|
+
cmdid: 'login',
|
|
55
|
+
username: this.options.credentials.username,
|
|
56
|
+
password: this.options.credentials.password,
|
|
57
|
+
}));
|
|
58
|
+
const reply = response.status === 200 ? parseUfiReply(response.body) : undefined;
|
|
59
|
+
const session = reply?.reply === 'ok' ? reply.session : undefined;
|
|
60
|
+
if (session === undefined || session === '')
|
|
61
|
+
return undefined;
|
|
62
|
+
this.#sessions.set(key, session);
|
|
63
|
+
return session;
|
|
64
|
+
}
|
|
65
|
+
read(command, session) {
|
|
66
|
+
return this.request(command, JSON.stringify({ cmdid: command, sessionId: session }), session);
|
|
67
|
+
}
|
|
68
|
+
/** A refused session is dropped so the NEXT generation may spend its own attempt. */
|
|
69
|
+
forgetSession(request) {
|
|
70
|
+
this.#sessions.delete(this.sessionKey(request));
|
|
71
|
+
}
|
|
72
|
+
sessionKey(request) {
|
|
73
|
+
return `${request.physicalModemId}:${request.generation}`;
|
|
74
|
+
}
|
|
75
|
+
observedAt() {
|
|
76
|
+
return this.options.now?.() ?? Date.now();
|
|
77
|
+
}
|
|
78
|
+
request(command, body, session) {
|
|
79
|
+
return this.options.transport.request({
|
|
80
|
+
method: 'POST',
|
|
81
|
+
url: `${this.options.adminUrl}${UFI_API_PATH}`,
|
|
82
|
+
command,
|
|
83
|
+
body,
|
|
84
|
+
headers: [
|
|
85
|
+
...(session === undefined ? [] : [`Authorization: ${session}`]),
|
|
86
|
+
'Content-Type: application/json;charset=UTF-8',
|
|
87
|
+
],
|
|
88
|
+
interfaceName: this.options.interfaceName,
|
|
89
|
+
redirect: 'error',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The seven read commands the HIMI firmware answers. Bench-observed, not inferred. */
|
|
2
|
+
export declare const UFI_READ_COMMANDS: readonly ['getoverview', 'getsysinfo', 'getallstatus', 'getapninfo', 'getnetworkmode', 'gethimiusbtether', 'getproduceinfo'];
|
|
3
|
+
export type UfiReadCommand = (typeof UFI_READ_COMMANDS)[number];
|
|
4
|
+
/**
|
|
5
|
+
* The ONE non-read command. It opens a server-side session and changes nothing on the
|
|
6
|
+
* device; the modem's configuration, radio and storage are untouched by it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const UFI_SESSION_COMMANDS: readonly ['login'];
|
|
9
|
+
export type UfiSessionCommand = (typeof UFI_SESSION_COMMANDS)[number];
|
|
10
|
+
export declare const UFI_COMMANDS: readonly ["getoverview", "getsysinfo", "getallstatus", "getapninfo", "getnetworkmode", "gethimiusbtether", "getproduceinfo", "login"];
|
|
11
|
+
export type UfiCommand = UfiReadCommand | UfiSessionCommand;
|
|
12
|
+
export declare const UFI_API_PATH = "/himiapi/json";
|
|
13
|
+
export type UfiHttpRequest = {
|
|
14
|
+
readonly method: 'POST';
|
|
15
|
+
readonly url: string;
|
|
16
|
+
/** Frozen vocabulary: the field a write would have to travel in cannot name one. */
|
|
17
|
+
readonly command: UfiCommand;
|
|
18
|
+
readonly body: string;
|
|
19
|
+
readonly headers: readonly string[];
|
|
20
|
+
readonly interfaceName: string;
|
|
21
|
+
readonly redirect: 'error';
|
|
22
|
+
};
|
|
23
|
+
export type UfiHttpResponse = {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly body: string;
|
|
26
|
+
};
|
|
27
|
+
export interface UfiTransport {
|
|
28
|
+
request(request: UfiHttpRequest): Promise<UfiHttpResponse>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Qualcomm UFI / HIMI transport shape.
|
|
2
|
+
//
|
|
3
|
+
// HIMI is not a REST surface: every call is `POST /himiapi/json` and the VERB lives in
|
|
4
|
+
// the JSON body's `cmdid`. So "read-only" cannot be expressed here as an HTTP-method
|
|
5
|
+
// restriction the way it can for HiLink. The command vocabulary is frozen as a union
|
|
6
|
+
// instead — a request this transport can carry names a member of `UFI_COMMANDS`, and no
|
|
7
|
+
// `set*` / flash / reset member exists — which makes a write UNREPRESENTABLE rather than
|
|
8
|
+
// merely refused at runtime.
|
|
9
|
+
/** The seven read commands the HIMI firmware answers. Bench-observed, not inferred. */
|
|
10
|
+
export const UFI_READ_COMMANDS = [
|
|
11
|
+
'getoverview',
|
|
12
|
+
'getsysinfo',
|
|
13
|
+
'getallstatus',
|
|
14
|
+
'getapninfo',
|
|
15
|
+
'getnetworkmode',
|
|
16
|
+
'gethimiusbtether',
|
|
17
|
+
'getproduceinfo',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* The ONE non-read command. It opens a server-side session and changes nothing on the
|
|
21
|
+
* device; the modem's configuration, radio and storage are untouched by it.
|
|
22
|
+
*/
|
|
23
|
+
export const UFI_SESSION_COMMANDS = ['login'];
|
|
24
|
+
export const UFI_COMMANDS = [...UFI_READ_COMMANDS, ...UFI_SESSION_COMMANDS];
|
|
25
|
+
export const UFI_API_PATH = '/himiapi/json';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ObservationEnvelope } from '../../domain/index.js';
|
|
2
|
+
import type { NormalizedModemObservation } from '../../observations/index.js';
|
|
3
|
+
import type { CapabilityReader, ProviderDefinition, ProviderExecutionContext, ProviderMatchRequest, ProviderOperationsSurface } from '../contracts.js';
|
|
4
|
+
import { ZteSessionRuntime } from './session.js';
|
|
5
|
+
import type { ZteTransport } from './transport.js';
|
|
6
|
+
export declare const ZTE_PATHS: {
|
|
7
|
+
readonly get: '/goform/goform_get_cmd_process';
|
|
8
|
+
readonly set: '/goform/goform_set_cmd_process';
|
|
9
|
+
};
|
|
10
|
+
export declare const ZTE_EVIDENCE_CMD = "LD,psw_fail_num_str,login_lock_time,wa_inner_version,cr_version";
|
|
11
|
+
export type ZteProfile = {
|
|
12
|
+
readonly id: 'mf79u-legacy' | 'mf79u-ld-salted' | 'mf266-salted';
|
|
13
|
+
readonly firmware: 'MF79U' | 'MF266';
|
|
14
|
+
readonly algorithm: 'legacy-base64' | 'login-salted-sha256' | 'multi-user-salted-sha256';
|
|
15
|
+
};
|
|
16
|
+
export declare const ZTE_PROFILES: readonly [{
|
|
17
|
+
readonly id: 'mf79u-legacy';
|
|
18
|
+
readonly firmware: 'MF79U';
|
|
19
|
+
readonly algorithm: 'legacy-base64';
|
|
20
|
+
}, {
|
|
21
|
+
readonly id: 'mf79u-ld-salted';
|
|
22
|
+
readonly firmware: 'MF79U';
|
|
23
|
+
readonly algorithm: 'login-salted-sha256';
|
|
24
|
+
}, {
|
|
25
|
+
readonly id: 'mf266-salted';
|
|
26
|
+
readonly firmware: 'MF266';
|
|
27
|
+
readonly algorithm: 'multi-user-salted-sha256';
|
|
28
|
+
}];
|
|
29
|
+
export declare const ZTE_UNKNOWN_PROFILE = "zte-unknown-read-only";
|
|
30
|
+
export type ZteCredentials = {
|
|
31
|
+
readonly username: string;
|
|
32
|
+
readonly password: string;
|
|
33
|
+
};
|
|
34
|
+
export type ZteOptions = {
|
|
35
|
+
readonly interfaceName: string;
|
|
36
|
+
readonly adminUrl: string;
|
|
37
|
+
readonly transport: ZteTransport;
|
|
38
|
+
readonly credentials: ZteCredentials;
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
};
|
|
41
|
+
type ZteOperations = ProviderOperationsSurface;
|
|
42
|
+
export declare function zteProfilesForFirmware(firmware: string | undefined): readonly ZteProfile[];
|
|
43
|
+
export declare function zteProfileById(profileId: string): ZteProfile | undefined;
|
|
44
|
+
export declare class ZteGoformProvider extends ZteSessionRuntime {
|
|
45
|
+
fingerprint(request: ProviderMatchRequest): Promise<{
|
|
46
|
+
signal: "match" | "unknown";
|
|
47
|
+
strength: 'strong';
|
|
48
|
+
profiles: string[];
|
|
49
|
+
detail: string;
|
|
50
|
+
}>;
|
|
51
|
+
capability(): CapabilityReader;
|
|
52
|
+
observe(context: ProviderExecutionContext): Promise<readonly ObservationEnvelope<NormalizedModemObservation>[]>;
|
|
53
|
+
operations(_profile: string): ZteOperations;
|
|
54
|
+
}
|
|
55
|
+
export declare function createZteGoformDefinition(options: ZteOptions): ProviderDefinition<NormalizedModemObservation, ZteOperations>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { normalizeZteObservation } from '../../observations/sources/zte.js';
|
|
2
|
+
import { parseZteRecord, ZteSessionRuntime } from './session.js';
|
|
3
|
+
export const ZTE_PATHS = {
|
|
4
|
+
get: '/goform/goform_get_cmd_process',
|
|
5
|
+
set: '/goform/goform_set_cmd_process',
|
|
6
|
+
};
|
|
7
|
+
export const ZTE_EVIDENCE_CMD = 'LD,psw_fail_num_str,login_lock_time,wa_inner_version,cr_version';
|
|
8
|
+
export const ZTE_PROFILES = [
|
|
9
|
+
{ id: 'mf79u-legacy', firmware: 'MF79U', algorithm: 'legacy-base64' },
|
|
10
|
+
{ id: 'mf79u-ld-salted', firmware: 'MF79U', algorithm: 'login-salted-sha256' },
|
|
11
|
+
{ id: 'mf266-salted', firmware: 'MF266', algorithm: 'multi-user-salted-sha256' },
|
|
12
|
+
];
|
|
13
|
+
export const ZTE_UNKNOWN_PROFILE = 'zte-unknown-read-only';
|
|
14
|
+
export function zteProfilesForFirmware(firmware) {
|
|
15
|
+
return ZTE_PROFILES.filter((profile) => profile.firmware === firmware);
|
|
16
|
+
}
|
|
17
|
+
export function zteProfileById(profileId) {
|
|
18
|
+
return ZTE_PROFILES.find((profile) => profile.id === profileId);
|
|
19
|
+
}
|
|
20
|
+
export class ZteGoformProvider extends ZteSessionRuntime {
|
|
21
|
+
async fingerprint(request) {
|
|
22
|
+
const evidence = await this.probeEvidence(request);
|
|
23
|
+
const matches = evidence !== undefined;
|
|
24
|
+
return {
|
|
25
|
+
signal: matches ? 'match' : 'unknown',
|
|
26
|
+
strength: 'strong',
|
|
27
|
+
profiles: matches ? [evidence.profile?.id ?? ZTE_UNKNOWN_PROFILE] : [],
|
|
28
|
+
detail: matches ? 'zte-goform-shape' : 'zte-goform-not-proven',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
capability() {
|
|
32
|
+
return {
|
|
33
|
+
id: 'zte-telemetry',
|
|
34
|
+
read: async () => {
|
|
35
|
+
const response = await this.get('network_type,signalbar,rssi,lte_rsrp,lte_rsrq,lte_snr');
|
|
36
|
+
const supported = response.status === 200 && parseZteRecord(response.body) !== undefined;
|
|
37
|
+
return {
|
|
38
|
+
signal: supported ? 'match' : 'mismatch',
|
|
39
|
+
strength: 'strong',
|
|
40
|
+
detail: supported ? 'telemetry-reported' : 'telemetry-refused',
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async observe(context) {
|
|
46
|
+
const response = await this.get('network_type,signalbar,rssi,lte_rsrp,lte_rsrq,lte_snr,network_provider,cell_id', this.sessionCookie(context));
|
|
47
|
+
if (response.status !== 200)
|
|
48
|
+
return [];
|
|
49
|
+
return [
|
|
50
|
+
normalizeZteObservation({ body: response.body }, {
|
|
51
|
+
stableKey: `modem:${this.options.interfaceName}`,
|
|
52
|
+
generation: context.generation,
|
|
53
|
+
sourceEpoch: 1,
|
|
54
|
+
observedAt: (this.options.now?.() ?? Date.now()),
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
operations(_profile) {
|
|
59
|
+
return { access: 'read-only' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function createZteGoformDefinition(options) {
|
|
63
|
+
const runtime = new ZteGoformProvider(options);
|
|
64
|
+
return {
|
|
65
|
+
id: 'zte-goform',
|
|
66
|
+
profileVersion: '2',
|
|
67
|
+
eligibleTransports: ['network'],
|
|
68
|
+
passiveMatchers: ['MF79U', 'MF266'].map((firmware) => ({
|
|
69
|
+
id: `firmware-${firmware}`,
|
|
70
|
+
fact: 'firmware',
|
|
71
|
+
expected: [firmware],
|
|
72
|
+
profiles: zteProfilesForFirmware(firmware).map((profile) => profile.id),
|
|
73
|
+
strength: 'strong',
|
|
74
|
+
required: true,
|
|
75
|
+
})),
|
|
76
|
+
unauthenticatedProbes: [
|
|
77
|
+
{ id: 'zte-goform-shape', run: (request) => runtime.fingerprint(request) },
|
|
78
|
+
],
|
|
79
|
+
authenticatedProfile: {
|
|
80
|
+
algorithm: 'evidence-selected-zte-goform',
|
|
81
|
+
attemptLimit: 1,
|
|
82
|
+
authenticate: (request, candidates) => runtime.authenticateProfile(request, candidates),
|
|
83
|
+
},
|
|
84
|
+
capabilityReaders: [runtime.capability()],
|
|
85
|
+
observe: (context) => runtime.observe(context),
|
|
86
|
+
operations: (profile) => runtime.operations(profile),
|
|
87
|
+
contractFixtures: ZTE_PROFILES.map((profile) => ({
|
|
88
|
+
profile: profile.id,
|
|
89
|
+
request: {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
path: ZTE_PATHS.set,
|
|
92
|
+
goformId: profile.algorithm === 'multi-user-salted-sha256' ? 'LOGIN_MULTI_USER' : 'LOGIN',
|
|
93
|
+
interfaceBound: true,
|
|
94
|
+
redirects: 'disabled',
|
|
95
|
+
},
|
|
96
|
+
response: profile.algorithm === 'multi-user-salted-sha256'
|
|
97
|
+
? { status: 'matched', sessionMaterial: '[redacted]', ad: '[redacted]' }
|
|
98
|
+
: { status: 'matched', sessionMaterial: '[redacted]' },
|
|
99
|
+
})),
|
|
100
|
+
};
|
|
101
|
+
}
|