Runtime USB-composition capability, evidence-selected ZTE login, UFI descriptor capture, and the FM350-GL carry - #19
Merged
Merged
Conversation
… lockout before attempting
…ead of a model catalog
… schema Bench tooling for the `05c6:9091` UFI stick: a capture script that writes a redacted descriptor evidence bundle, a schema/classifier module that says what a bundle is and what its interfaces are, and a gate that proves neither can change the device. No bundle content is captured here — that is the hardware drill's job, and no device is needed to land this. The tooling lives in `control/scripts/`, not in the provider directory. The provider's `no-write-path.test.ts` asserts that directory's listing as an exact literal, and bench tooling is not part of the published package (`files: ["dist"]`) — `mf79u-diagnose.sh` is the standing precedent. The module still imports `classifyUfiDiagEvidence`, so the bench DIAG rule cannot drift from the shipped one. Redaction happens at capture time and the rules have exactly one implementation — the script's `--redact-filter` mode, which the test executes rather than re-expresses. Two layers mirroring `redact.ts`: key-based masking of serial, IMEI, IMSI, ICCID, MSISDN, token and password fields, plus a MAC and 14+-digit backstop. Nothing in a USB descriptor is 14 digits long, so class triples, `bcdDevice`, driver names and product strings survive verbatim. The staged bundle is swept before publication and a surviving identifier destroys it; there is no override. Staging is moved into place as a unit, so a published path either holds a complete bundle or does not exist, and a host with no matching device answers `device-not-present` having written nothing. The descriptor triple and the driver binding stay two separate facts. Upstream matches `05c6:9091` in `qmi_wwan.c` under an annotation naming a different device and QCSuper documents the id on another one again, so only this unit's own descriptor answers — and only the captured binding says who claimed it.
… pinned patch series
…rdware hub-cycling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Affected repo & language:
modem-stack— TypeScript (control/,cli/) + Debian packaging (packaging/)What
Three independent strands of modem work, plus the FM350-GL packaging exception they
sit alongside.
Runtime USB-composition capability. The ModemManager provider used to decide which
USB composition modes it could offer by looking the device up in a model catalog. It now
asks the device, through the exact reviewed vendor READ/TEST forms already in the AT
allowlist, and offers only the targets that device's own enumeration reports — and only
when that enumeration also contains the device's current mode, which is what proves a
return path exists. A catalog miss is no longer an
uncertifiedverdict. The operator-facingsuppression vocabulary is exactly four literals (
unknown-vendor,no-return-path,blocked-by-state,provisioning-disabled), and every suppressed state offers zero targets.Success keeps two proof tiers: a reviewed catalog transition still requires both the
canonical mode and the USB descriptors to match (tier 1, unchanged and still strongest);
without one, the re-enumerated device must report the target through its own vendor READ
(tier 2, explicitly weaker). AT
OKis proof in neither. Band writes do not share thispolicy and remain behind their separate four-proof certification gate.
ZTE login-encoding selection. The goform provider chose its password encoding from
firmware text, which cannot distinguish the MF79U B03
LD-salted dialect from the legacybase64 one. It now reads a batched pre-auth
multi_dataGET —LD, remaining attempts,lock time, both version fields — and selects the algorithm from that evidence, refusing a
positively-reported lockout before any credential POST is sent. One bounded attempt per
selection, and no fall-through to a neighbouring algorithm.
UFI/HIMI descriptor capture tooling. A read-only bench capture script and its bundle
schema, interface-role classifier, and independent redaction sweep, for the
05c6:9091stick. Redaction happens at capture time with one implementation (the script's own
--redact-filtermode, which the test executes rather than re-expresses); the staged bundleis swept before publication and a surviving identifier destroys it with no override flag.
No bundle content is committed. The 2026-08-23 hardware drill's classification is recorded
in
docs/UFI-DIAG-PROBE.md: a four-interface QMI + ADB-class composition, interface 2 claimedby
qmi_wwan, noff/ff/30DIAG descriptor, HIMI identity endpoint unreachable throughthe target's own
wwan1. No composition change was attempted.The FM350-GL patch series. Three BELABOX-derived quilt patches on ModemManager 1.24.2 —
the forward-ported
fm350glplugin, the+CPOLcrash avoidance, and the extended+COPSscan format — carried under the narrow owner-approved exception recorded in
POLICY.md§1and
docs/adr/ADR-FM350-RNDIS-BEARER.md. Also here: the fix that made them actually work(
e9d41bc), the hardware validation, and the docs recording it.Why
The composition-catalog change is the substantive one. Gating an interrogable device's
USB-mode switching on a model catalog means the catalog is a de-facto allowlist: a modem
that can tell you exactly which compositions it supports gets hidden because nobody has
reviewed a row for it yet. That is the "uncertified means unsupported" coercion the
support-claim taxonomy exists to prevent. Deriving targets from the device's own
enumeration keeps the catalog valuable as evidence — it still provides the strongest
success proof — without letting it decide whether a control is offered at all. The
return-path requirement is what keeps this safe: a device is offered a switch only when its
own vocabulary demonstrably includes the mode it is currently in, so there is a way back.
The ZTE change fixes a real misdiagnosis. Two MF79U firmwares share a version string and
disagree about the password algorithm, so encoding-from-firmware-text guesses, and a wrong
guess spends one of a small number of login attempts. Reading the lockout state first means
we stop attempting rather than burning the last attempt and locking the device out.
On the FM350 series: the first hardware drill failed, and the failure was ours. The
1.24 forward-port had dropped BELABOX's modem-class first-enable override, so core
ModemManager sent
ATZwhere the original plugin sentATZ0; the modem answeredCME 59and no bearer could ever open.
e9d41bcrestores the override. With it, the correctedseries built for arm64 and passed the full criterion on the carrier-mounted unit:
nmcli connection upsucceeded, registrationhome, packet serviceattached, a routableIPv4 on the RNDIS interface, and
curl --interface enx000011121314 https://example.comreturned HTTP 200.
Two things about that result are deliberately not claimed. It validates the
carrier-mediated USB topology and that firmware — it is not a PCIe /
mtk_t7xxsupport claim, and
docs/FM350-DECISION.md's three-gate ledger stays open. AndPOLICY.md§1 requirement 2 (a filed upstream MR) is still honestly not met. The MR isdrafted at
docs/adr/FM350-UPSTREAM-MR-DRAFT.mdand its filed-MR table reads_not filed_in both rows. What exists is the project owner's dated second-maintainer approval to carry
the series while that filing action stays open — an explicit exception for this one series,
not a general waiver and not a claim that an MR exists. Filing it remains an open follow-up;
the ADR §7 gate table and
POLICY.mdboth say so in as many words, and this PR does notrelabel that.
How to verify
Workspace gates, from the repo root:
verify:consumersrefuses any Node major but 26 on purpose — a green result cannot comefrom an older Node that happens to be first on
PATH.Packaging, the lightweight PR lane (no docker-in-docker, no built
.deb):That lane includes
test-fm350-patch-contract.sh, which is the gate on this series: itasserts each patch retains its BELABOX author, originating commit SHA, rationale and
Forwarded: nostatus, so the attribution cannot silently decay into an anonymousdownstream fork.
The heavy,
.deb-consuming lanes need a build first (packaging/ci/build-bookworm.sh amd64);both were run locally against that output and pass:
The contract suite's upgrade table is worth a look — every source sorts above stock
bookworm with real
dpkg --compare-versions, so the upgrade needs no--allow-downgrades,and the rollback to stock is clean. The smoke run brings the daemon up on a live bus and
confirms
Version1.24.2,mmcli 1.24.2, the ObjectManager root path, the udev andFCC-unlock install paths, and a real PyGObject typelib import plus a Vala compile-link
against
libmm-glib.For the FM350 bearer claim specifically, the hardware transcript is in the ADR; it cannot be
reproduced without the carrier board and a live SIM.
Risks
The composition change widens what gets offered, which is the point but is also the
risk: a device that previously fell through to
uncertifiedmay now be offered targets ontier-2 proof only. The fences are the return-path requirement (no offer unless the device's
own enumeration contains its current mode), the four-literal suppression vocabulary, and the
unchanged write-side fences — admission lease, journal, armed rollback, required readback,
fail-closed identity, streaming interlock, MM inhibit/uninhibit, and the bounded
drop/re-enumeration wait. The AT fence widened by exact name, never by pattern: capability
reads send only allowlisted READ/TEST forms and no SET, and only the selected enumerated
target is unioned into the held lease.
The FM350 series is the one carried patch set in a no-fork repo, so it is a standing
merge liability against ModemManager's device database. It is pinned, attributed, contract-
gated, and must be retired when upstream ships equivalent support. Its validation covers one
topology and one firmware; PCIe remains unproven and ungated by this PR. Rollback is
removing the three
seriesentries — the four sources are otherwise source-unmodified, andthe contract suite proves a clean downgrade to stock.
The ZTE evidence probe adds one pre-auth GET before login. It is unauthenticated and
read-only, and it strictly reduces credential attempts: it can only refuse an attempt that
would previously have been made blindly.
The UFI tooling touches no device. It is bench-only, unpublished (
files: ["dist"]),and lives in
control/scripts/rather than the provider directory precisely becauseno-write-path.test.tsasserts that directory's listing as an exact literal. A static gatescans both files for
usb_modeswitch,setprop, shell-transport invocation, EDL tooling,AT!, uppercase AT write forms and QMI writes, each with a non-vacuity control.No npm publish, no
.debupload, no apt-worker dispatch, and no tag are part of this PR.Checklist