Skip to content

fix(pdu): decode MousePdu wheel rotation as two's complement, matching encode#1415

Open
Anton Mostovoy (antonmos) wants to merge 1 commit into
Devolutions:masterfrom
antonmos:fix/mouse-wheel-decode-twos-complement
Open

fix(pdu): decode MousePdu wheel rotation as two's complement, matching encode#1415
Anton Mostovoy (antonmos) wants to merge 1 commit into
Devolutions:masterfrom
antonmos:fix/mouse-wheel-decode-twos-complement

Conversation

@antonmos

Copy link
Copy Markdown

Fixes #1414.

Summary

MousePdu::decode computed a negative number_of_wheel_rotation_units as
plain sign-magnitude (-i16::from(byte)), but per MS-RDPBCGR
2.2.8.1.1.3.1.1.3 the WheelRotationMask (0x01FF) field is 9-bit
two's complementWHEEL_NEGATIVE (0x0100) is the sign bit of that
9-bit value, not an independent "negate this magnitude" flag.

MousePdu::encode already does this correctly (a truncating cast,
self.number_of_wheel_rotation_units as u8, is proper two's complement),
so the two sides disagreed and the codec didn't round-trip:
decode(encode(-1)) produced -255, not -1.

Fix

let number_of_wheel_rotation_units = if flags.contains(PointerFlags::WHEEL_NEGATIVE) {
    i16::from(wheel_rotations_bits) - 0x100
} else {
    i16::from(wheel_rotations_bits)
};

Added:

  • wheel_rotation_units_round_trip_through_encode_decode — round-trips
    every i8 value through encode/decode (this fails on master for
    any negative value before this fix).
  • small_negative_wheel_rotation_decodes_correctly — decodes a
    hand-built wire buffer (WHEEL_NEGATIVE + byte 0xFF) and asserts the
    result is -1, not -255.

Test plan

  • cargo test -p ironrdp-pdu --lib — 369 passed (2 new)
  • cargo clippy -p ironrdp-pdu --lib -- -D warnings — clean on the
    changed file (two pre-existing, unrelated errors elsewhere in the
    crate reproduce identically on master without this change —
    confirmed via git stash)
  • rustfmt --check clean

…g encode

Per MS-RDPBCGR 2.2.8.1.1.3.1.1.3, the wheel-rotation field
(WheelRotationMask, 0x01FF) is a 9-bit two's-complement value: the
WHEEL_NEGATIVE (0x0100) bit is the sign bit of that 9-bit field, not an
independent "negate this magnitude" flag. decode instead computed plain
sign-magnitude (-byte for the negative case), while encode already
produces a proper two's-complement byte via a truncating cast
(self.number_of_wheel_rotation_units as u8) — so decode(encode(x)) did
not round-trip for negative x (e.g. -1 decoded back as -255).

Fixes Devolutions#1414.
Copilot AI review requested due to automatic review settings July 6, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ironrdp-pdu’s MousePdu::decode wheel-rotation decoding to interpret the 9-bit WheelRotationMask as two’s complement (matching both MS-RDPBCGR and the existing encode implementation), restoring correct round-tripping for negative wheel deltas.

Changes:

  • Corrected negative wheel-rotation decoding to use 9-bit two’s-complement semantics (byte - 0x100 when the sign bit is set).
  • Added regression tests to ensure encode/decode round-trips and to validate a hand-constructed -1 wheel delta buffer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +129 to +136
// Every representable value must survive an encode/decode round trip.
// This previously failed for small negative values: encode(-1) produced
// byte 0xFF + WHEEL_NEGATIVE, which decode incorrectly read back as -255
// (sign-magnitude) instead of -1 (two's complement, matching encode).
for value in i8::MIN..=i8::MAX {
let value = i16::from(value);
let pdu = mouse_pdu(value);
let buffer = encode_vec(&pdu).unwrap();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

MousePdu::decode mis-decodes negative wheel rotation (sign-magnitude instead of two's complement)

2 participants