diff --git a/crates/ironrdp-pdu/src/input/fast_path.rs b/crates/ironrdp-pdu/src/input/fast_path.rs index 7835d73113..a22ec8414c 100644 --- a/crates/ironrdp-pdu/src/input/fast_path.rs +++ b/crates/ironrdp-pdu/src/input/fast_path.rs @@ -198,8 +198,13 @@ impl<'de> Decode<'de> for FastPathInputEvent { FastpathInputEventType::ScanCode => { ensure_size!(in: src, size: 1); let code = src.read_u8(); - let flags = KeyboardFlags::from_bits(flags) - .ok_or_else(|| invalid_field_err!("flags", "input keyboard flags unsupported", in: src))?; + // Retain unknown eventFlags bits (crate-wide policy since + // #1144): the slow-path decoder for the same keystroke + // already does (scan_code.rs), and a rejection here is + // session-fatal mid-use. [MS-RDPBCGR] 3.3.5.8.2's SHOULD-drop + // covers unknown event TYPES, which stays enforced above, + // not unknown flag bits. + let flags = KeyboardFlags::from_bits_retain(flags); FastPathInputEvent::KeyboardEvent(flags, code) } FastpathInputEventType::Mouse => { @@ -215,15 +220,16 @@ impl<'de> Decode<'de> for FastPathInputEvent { FastPathInputEvent::MouseEventRel(mouse_event) } FastpathInputEventType::Sync => { - let flags = SynchronizeFlags::from_bits(flags) - .ok_or_else(|| invalid_field_err!("flags", "input synchronize flags unsupported", in: src))?; + // Same rationale as ScanCode above; the slow-path sync + // decoder (sync.rs) already retains unknown toggle bits. + let flags = SynchronizeFlags::from_bits_retain(flags); FastPathInputEvent::SyncEvent(flags) } FastpathInputEventType::Unicode => { ensure_size!(in: src, size: 2); let code = src.read_u16(); - let flags = KeyboardFlags::from_bits(flags) - .ok_or_else(|| invalid_field_err!("flags", "input keyboard flags unsupported", in: src))?; + // Same rationale as ScanCode above. + let flags = KeyboardFlags::from_bits_retain(flags); FastPathInputEvent::UnicodeKeyboardEvent(flags, code) } FastpathInputEventType::QoeTimestamp => { diff --git a/crates/ironrdp-testsuite-core/tests/input/fastpath_packets.rs b/crates/ironrdp-testsuite-core/tests/input/fastpath_packets.rs index 92064d2171..a0219f6996 100644 --- a/crates/ironrdp-testsuite-core/tests/input/fastpath_packets.rs +++ b/crates/ironrdp-testsuite-core/tests/input/fastpath_packets.rs @@ -361,3 +361,52 @@ fn wheel_rotations_out_of_range_is_clamped(#[case] rotation_units: i16, #[case] }) ); } + +#[test] +fn fastpath_keyboard_event_with_undefined_flag_bits_decodes_and_retains_them() { + // The 5-bit fast-path eventFlags field defines RELEASE, EXTENDED and + // EXTENDED1 for keyboard events ([MS-RDPBCGR] 2.2.8.1.2.2.1); an unknown + // bit must not kill a live session, and the slow-path decoder for the + // same keystroke already tolerates it. The bit is retained so + // re-encoding preserves the wire value. + const UNDEFINED_BIT: u8 = 0x10; + + // eventCode = FASTPATH_INPUT_EVENT_SCANCODE (0) in bits 5..8, + // eventFlags = RELEASE | UNDEFINED_BIT in bits 0..5, then the scancode. + let buffer = [0x01 | UNDEFINED_BIT, 0x1d]; + + let event: FastPathInputEvent = ironrdp_core::decode(&buffer).expect("undefined flag bits are not fatal"); + + let FastPathInputEvent::KeyboardEvent(flags, code) = event else { + panic!("expected a keyboard event"); + }; + assert_eq!(code, 0x1d); + assert!(flags.contains(KeyboardFlags::RELEASE)); + assert_ne!(flags.bits() & UNDEFINED_BIT, 0); + assert_eq!( + ironrdp_core::encode_vec(&FastPathInputEvent::KeyboardEvent(flags, code)).unwrap(), + buffer + ); +} + +#[test] +fn fastpath_sync_event_with_undefined_flag_bits_decodes_and_retains_them() { + // [MS-RDPBCGR] 2.2.8.1.2.2.5 defines the four lock flags; bit 0x10 of + // the 5-bit eventFlags field is undefined and must not be fatal. + const UNDEFINED_BIT: u8 = 0x10; + + // eventCode = FASTPATH_INPUT_EVENT_SYNC (3) in bits 5..8. + let buffer = [(3 << 5) | 0x01 | UNDEFINED_BIT]; + + let event: FastPathInputEvent = ironrdp_core::decode(&buffer).expect("undefined flag bits are not fatal"); + + let FastPathInputEvent::SyncEvent(flags) = event else { + panic!("expected a sync event"); + }; + assert!(flags.contains(SynchronizeFlags::SCROLL_LOCK)); + assert_ne!(flags.bits() & UNDEFINED_BIT, 0); + assert_eq!( + ironrdp_core::encode_vec(&FastPathInputEvent::SyncEvent(flags)).unwrap(), + buffer + ); +}