fix(gamestream): tolerate NaN pressureOrDistance — VoidLink's finger touches were dropped whole
apple / swift (push) Successful in 1m18s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m6s
apple / screenshots (push) Successful in 6m11s
ci / bench (push) Successful in 5m34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
windows-host / package (push) Successful in 16m57s
android / android (push) Successful in 15m58s
docker / deploy-docs (push) Successful in 24s
arch / build-publish (push) Successful in 17m3s
deb / build-publish (push) Successful in 9m26s
deb / build-publish-host (push) Successful in 10m5s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m39s
ci / rust (push) Successful in 25m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m31s
apple / swift (push) Successful in 1m18s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m6s
apple / screenshots (push) Successful in 6m11s
ci / bench (push) Successful in 5m34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
windows-host / package (push) Successful in 16m57s
android / android (push) Successful in 15m58s
docker / deploy-docs (push) Successful in 24s
arch / build-publish (push) Successful in 17m3s
deb / build-publish (push) Successful in 9m26s
deb / build-publish-host (push) Successful in 10m5s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m39s
ci / rust (push) Successful in 25m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m31s
The hex dumps from the on-glass session show textbook SS_TOUCH packets with pressureOrDistance = 0x7fc00000 (NaN): VoidLink encodes 'unknown finger pressure' as NaN (iPad fingers have no force sensor), and the anti-forgery finiteness gate rejected the entire packet — silently disabling the touch plane while pen (real Pencil force, always finite) worked. Coordinates keep the strict gate (they feed injector scaling); pressureOrDistance now sanitizes non-finite to 0.0, the spec's own 'unknown', which the pen path already maps to full-scale ink on contact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -173,10 +173,20 @@ pub fn decode_pointer(plaintext: &[u8]) -> Option<SsPointer> {
|
||||
let p = &plaintext[4..];
|
||||
let magic = u32::from_le_bytes([p[4], p[5], p[6], p[7]]);
|
||||
let b = &p[8..];
|
||||
// Coordinates must be finite (they feed the injectors' scaling); a forged NaN drops the
|
||||
// packet whole.
|
||||
let f32at = |o: usize| -> Option<f32> {
|
||||
let v = f32::from_le_bytes([*b.get(o)?, *b.get(o + 1)?, *b.get(o + 2)?, *b.get(o + 3)?]);
|
||||
v.is_finite().then_some(v)
|
||||
};
|
||||
// pressureOrDistance is different: real clients ship NaN there to mean "unknown" (iPad
|
||||
// fingers have no force sensor — observed live from VoidLink, which NaN'd every touch and
|
||||
// a strict gate silently killed the whole plane). The spec's own unknown value is 0.0, so
|
||||
// non-finite sanitizes to that instead of poisoning the packet.
|
||||
let f32_pressure = |o: usize| -> Option<f32> {
|
||||
let v = f32::from_le_bytes([*b.get(o)?, *b.get(o + 1)?, *b.get(o + 2)?, *b.get(o + 3)?]);
|
||||
Some(if v.is_finite() { v } else { 0.0 })
|
||||
};
|
||||
match magic {
|
||||
// eventType, zero[1], rotation u16, pointerId u32, x, y, pressureOrDistance, areas.
|
||||
MAGIC_SS_TOUCH => Some(SsPointer::Touch(SsTouch {
|
||||
@@ -185,7 +195,7 @@ pub fn decode_pointer(plaintext: &[u8]) -> Option<SsPointer> {
|
||||
pointer_id: u32::from_le_bytes([*b.get(4)?, *b.get(5)?, *b.get(6)?, *b.get(7)?]),
|
||||
x: f32at(8)?,
|
||||
y: f32at(12)?,
|
||||
pressure_or_distance: f32at(16)?,
|
||||
pressure_or_distance: f32_pressure(16)?,
|
||||
})),
|
||||
// eventType, toolType, penButtons, zero[1], x, y, pressureOrDistance, rotation u16,
|
||||
// tilt, zero2[1], areas.
|
||||
@@ -195,7 +205,7 @@ pub fn decode_pointer(plaintext: &[u8]) -> Option<SsPointer> {
|
||||
buttons: *b.get(2)?,
|
||||
x: f32at(4)?,
|
||||
y: f32at(8)?,
|
||||
pressure_or_distance: f32at(12)?,
|
||||
pressure_or_distance: f32_pressure(12)?,
|
||||
rotation: u16::from_le_bytes([*b.get(16)?, *b.get(17)?]),
|
||||
tilt: *b.get(18)?,
|
||||
})),
|
||||
@@ -323,6 +333,16 @@ mod tests {
|
||||
let mut nan = body.clone();
|
||||
nan[8..12].copy_from_slice(&f32::NAN.to_le_bytes());
|
||||
assert_eq!(decode_pointer(&wrap(0x5500_0002, &nan)), None);
|
||||
// …but a NaN pressureOrDistance sanitizes to 0.0 ("unknown") instead of killing the
|
||||
// packet — a REAL client convention, observed live: VoidLink NaN's the pressure of
|
||||
// every iPad finger touch (no force sensor), and the strict gate silently disabled
|
||||
// the whole touch plane.
|
||||
let mut nan_pod = body.clone();
|
||||
nan_pod[16..20].copy_from_slice(&f32::NAN.to_le_bytes());
|
||||
match decode_pointer(&wrap(0x5500_0002, &nan_pod)) {
|
||||
Some(SsPointer::Touch(t)) => assert_eq!(t.pressure_or_distance, 0.0),
|
||||
other => panic!("NaN pressure must decode with pod=0.0, got {other:?}"),
|
||||
}
|
||||
// Non-pointer magics fall through to the classic decoder.
|
||||
assert_eq!(
|
||||
decode_pointer(&wrap(MAGIC_MOUSE_REL_GEN5, &[0, 0, 0, 0])),
|
||||
|
||||
Reference in New Issue
Block a user