Sweep the handshake variants, since the pod answers back
The first two candidate runs looked like failures and were not. Buried in
them: a `0x3e` frame arriving 90 ms after our write, in both runs, never
otherwise — `{1: 255, 2: 5}`. The pod parsed what we sent and rejected it
with a reason. That is a feedback channel, and it turns this from
guessing into navigating.
Both candidates drew the *same* reason, so the field-2 marker is not what
it objects to. `--sweep` therefore sends every variant down one
connection and prints the reply to each: field 1 alone, the pod's own
trailer echoed back, an uncompressed 65-byte point, and the documented
2023 Play handshake verbatim (`RideOn 01 02` + a raw 64-byte key, no
protobuf at all) — which we had never actually tried, having assumed the
protobuf shape from the offer.
It needs no button presses. That matters now: this pod has stopped
reporting buttons entirely, so the paddle oracle the rest of the command
depends on is unavailable, and a sweep that reads only the reply code
still works.
Fuzzing a pod is not fuzzing a trainer. §2.3 refused unknown writes to
the D100 because it puts resistance under a rider; a Click has no
actuator and the worst it can do is ignore us. The OAD characteristics
stay untouched — those can brick a sealed unit.
Two corrections to the tool while here. Button frames were counted but
never printed, so an operator pressing into a silent terminal could not
tell a working run from a dead pod and reasonably concluded the latter.
And the cliff is now taken from an actual `flag 0 -> 1` transition rather
than the first sighting of a 1 — these runs opened with the flag already
set, the pod having kept that state across the reconnect, and reporting
"cliff at 2.3s" for it was a reading dressed as a measurement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-1
@@ -22,7 +22,9 @@ SUBCOMMANDS:
|
||||
set <ADDR> <TARGET> Take control and apply a target, then reset the trainer to zero
|
||||
unlock <ADDR> Answer the pod's key offer and see whether its paddles survive
|
||||
past the ~50 s cliff (--candidate ours|play|echo; omit for a
|
||||
control run that answers nothing)
|
||||
control run that answers nothing). --sweep sends every known
|
||||
variant in one connection and compares the pod's replies —
|
||||
no button presses needed
|
||||
zwift <ADDR> Talk to Zwift's custom service: handshake, then log every frame
|
||||
listen <ADDR> Raw GATT: dump services, characteristics and descriptors, then
|
||||
subscribe to everything and print each notification's
|
||||
@@ -103,6 +105,10 @@ pub enum Command {
|
||||
/// Which field-2 marker to answer with; `None` sends nothing and is the
|
||||
/// control run.
|
||||
candidate: Option<String>,
|
||||
/// Send every variant in one connection and compare the pod's replies.
|
||||
/// Needs no button presses, so it works on a pod that has stopped
|
||||
/// reporting them.
|
||||
sweep: bool,
|
||||
},
|
||||
/// Phase 3 / TASK-0: exercise Zwift's custom service on whatever advertises
|
||||
/// it — a Click, or the trainer itself.
|
||||
@@ -140,6 +146,7 @@ pub fn parse<I: IntoIterator<Item = String>>(argv: I) -> Result<Args> {
|
||||
let mut buttons_only = false;
|
||||
let mut name: Option<String> = None;
|
||||
let mut candidate: Option<String> = None;
|
||||
let mut sweep = false;
|
||||
let mut help = false;
|
||||
let mut positional: Vec<String> = Vec::new();
|
||||
|
||||
@@ -163,6 +170,7 @@ pub fn parse<I: IntoIterator<Item = String>>(argv: I) -> Result<Args> {
|
||||
.map_err(|_| anyhow!("--secs expects a whole number of seconds, got {v:?}"))?,
|
||||
);
|
||||
}
|
||||
"--sweep" => sweep = true,
|
||||
"--candidate" => {
|
||||
i += 1;
|
||||
candidate = Some(
|
||||
@@ -244,6 +252,7 @@ pub fn parse<I: IntoIterator<Item = String>>(argv: I) -> Result<Args> {
|
||||
// merely delays the failure must not read as one that fixed it.
|
||||
duration: Duration::from_secs(secs.unwrap_or(150)),
|
||||
candidate: candidate.clone(),
|
||||
sweep,
|
||||
},
|
||||
"zwift" => Command::Zwift {
|
||||
device: device(&positional, 1)?,
|
||||
|
||||
Reference in New Issue
Block a user