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

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:
2026-07-23 17:48:17 +02:00
co-authored by Claude Fable 5
parent f938174d86
commit 988b5742ec
+22 -2
View File
@@ -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])),