From 26a673d10b9484123b52bf34399b504a30ff853c Mon Sep 17 00:00:00 2001 From: Alexandr Yusuk Date: Fri, 17 Oct 2025 12:38:48 +0300 Subject: [PATCH 1/3] refactor: add `as_conversions` clippy correctness lint --- Cargo.toml | 2 +- benches/src/perfenc.rs | 5 +- crates/ironrdp-client/src/app.rs | 11 +- crates/ironrdp-client/src/rdp.rs | 4 +- .../src/windows/clipboard_data_ref.rs | 2 +- .../src/windows/clipboard_impl.rs | 14 ++- .../ironrdp-cliprdr-native/src/windows/mod.rs | 10 +- .../src/windows/utils.rs | 2 +- crates/ironrdp-cliprdr/src/lib.rs | 4 - crates/ironrdp-connector/src/connection.rs | 4 +- crates/ironrdp-dvc/src/pdu.rs | 18 ++- crates/ironrdp-fuzzing/src/oracles/mod.rs | 4 +- .../ironrdp-graphics/src/color_conversion.rs | 13 +- crates/ironrdp-graphics/src/dwt.rs | 115 +++++++++++------- crates/ironrdp-graphics/src/lib.rs | 4 - crates/ironrdp-graphics/src/pointer.rs | 9 +- crates/ironrdp-graphics/src/quantization.rs | 4 +- .../src/rdp6/bitmap_stream/decoder.rs | 24 +++- crates/ironrdp-graphics/src/rdp6/rle.rs | 19 +-- crates/ironrdp-graphics/src/rlgr.rs | 110 +++++++++++------ .../src/zgfx/control_messages.rs | 8 +- crates/ironrdp-graphics/src/zgfx/mod.rs | 25 ++-- crates/ironrdp-input/src/lib.rs | 5 + crates/ironrdp-mstsgu/src/lib.rs | 10 +- crates/ironrdp-mstsgu/src/proto.rs | 72 ++++++++--- .../src/gcc/monitor_extended_data.rs | 8 +- .../rdp/capability_sets/bitmap_codecs/mod.rs | 4 + .../ironrdp-pdu/src/rdp/server_error_info.rs | 4 + .../vc/dvc/gfx/graphics_messages/server.rs | 4 + crates/ironrdp-rdpdr/src/lib.rs | 4 - crates/ironrdp-rdpdr/src/pdu/efs.rs | 36 ++++-- crates/ironrdp-rdpdr/src/pdu/esc/mod.rs | 24 ++-- crates/ironrdp-rdpdr/src/pdu/esc/rpce.rs | 4 + crates/ironrdp-rdpdr/src/pdu/mod.rs | 8 ++ crates/ironrdp-rdpsnd-native/src/cpal.rs | 4 + crates/ironrdp-rdpsnd/src/client.rs | 4 +- crates/ironrdp-rdpsnd/src/pdu/mod.rs | 22 ++-- crates/ironrdp-server/src/encoder/bitmap.rs | 2 +- crates/ironrdp-server/src/encoder/mod.rs | 12 +- crates/ironrdp-server/src/handler.rs | 2 + crates/ironrdp-session/src/image.rs | 80 ++++++------ crates/ironrdp-session/src/rfx.rs | 7 +- crates/ironrdp-testsuite-core/src/gcc.rs | 12 ++ crates/ironrdp-testsuite-core/src/rdp.rs | 2 +- .../src/security_data.rs | 2 + .../tests/dvc/data_first.rs | 1 + crates/ironrdp-tokio/src/reqwest.rs | 7 +- crates/ironrdp-web/src/canvas.rs | 3 +- crates/ironrdp-web/src/session.rs | 15 +-- crates/ironrdp/examples/server.rs | 16 ++- xtask/src/cov.rs | 1 + 51 files changed, 527 insertions(+), 259 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14037b6bf..d956ab33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ fn_to_numeric_cast_any = "warn" ptr_cast_constness = "warn" # == Correctness == # -#as_conversions = "warn" +as_conversions = "warn" cast_lossless = "warn" cast_possible_truncation = "warn" cast_possible_wrap = "warn" diff --git a/benches/src/perfenc.rs b/benches/src/perfenc.rs index fc486460c..5abc94f31 100644 --- a/benches/src/perfenc.rs +++ b/benches/src/perfenc.rs @@ -68,7 +68,7 @@ async fn main() -> Result<(), anyhow::Error> { let mut updates = DisplayUpdates::new(file, DesktopSize { width, height }, fps); while let Some(up) = updates.next_update().await? { if let DisplayUpdate::Bitmap(ref up) = up { - total_raw += up.data.len() as u64; + total_raw += u64::try_from(up.data.len())?; } else { eprintln!("Invalid update"); break; @@ -78,7 +78,7 @@ async fn main() -> Result<(), anyhow::Error> { let Some(frag) = iter.next().await else { break; }; - let len = frag?.data.len() as u64; + let len = u64::try_from(frag?.data.len())?; total_enc += len; } n_updates += 1; @@ -87,6 +87,7 @@ async fn main() -> Result<(), anyhow::Error> { } println!(); + #[expect(clippy::as_conversions, reason = "casting u64 to f64")] let ratio = total_enc as f64 / total_raw as f64; let percent = 100.0 - ratio * 100.0; println!("Encoder: {encoder:?}"); diff --git a/crates/ironrdp-client/src/app.rs b/crates/ironrdp-client/src/app.rs index 9f579c178..bcce8d4fd 100644 --- a/crates/ironrdp-client/src/app.rs +++ b/crates/ironrdp-client/src/app.rs @@ -66,6 +66,7 @@ impl App { let Some((window, _)) = self.window.as_mut() else { return; }; + #[expect(clippy::as_conversions, reason = "casting f64 to u32")] let scale_factor = (window.scale_factor() * 100.0) as u32; let width = u16::try_from(size.width).expect("reasonable width"); @@ -222,8 +223,10 @@ impl ApplicationHandler for App { } WindowEvent::CursorMoved { position, .. } => { let win_size = window.inner_size(); - let x = (position.x / win_size.width as f64 * self.buffer_size.0 as f64) as u16; - let y = (position.y / win_size.height as f64 * self.buffer_size.1 as f64) as u16; + #[expect(clippy::as_conversions, reason = "casting f64 to u16")] + let x = (position.x / f64::from(win_size.width) * f64::from(self.buffer_size.0)) as u16; + #[expect(clippy::as_conversions, reason = "casting f64 to u16")] + let y = (position.y / f64::from(win_size.height) * f64::from(self.buffer_size.1)) as u16; let operation = ironrdp::input::Operation::MouseMove(ironrdp::input::MousePosition { x, y }); let input_events = self.input_database.apply(core::iter::once(operation)); @@ -239,6 +242,7 @@ impl ApplicationHandler for App { operations.push(ironrdp::input::Operation::WheelRotations( ironrdp::input::WheelRotations { is_vertical: false, + #[expect(clippy::as_conversions, reason = "casting f32 to i16")] rotation_units: (delta_x * 100.) as i16, }, )); @@ -248,6 +252,7 @@ impl ApplicationHandler for App { operations.push(ironrdp::input::Operation::WheelRotations( ironrdp::input::WheelRotations { is_vertical: true, + #[expect(clippy::as_conversions, reason = "casting f32 to i16")] rotation_units: (delta_y * 100.) as i16, }, )); @@ -258,6 +263,7 @@ impl ApplicationHandler for App { operations.push(ironrdp::input::Operation::WheelRotations( ironrdp::input::WheelRotations { is_vertical: false, + #[expect(clippy::as_conversions, reason = "casting f64 to i16")] rotation_units: delta.x as i16, }, )); @@ -267,6 +273,7 @@ impl ApplicationHandler for App { operations.push(ironrdp::input::Operation::WheelRotations( ironrdp::input::WheelRotations { is_vertical: true, + #[expect(clippy::as_conversions, reason = "casting f64 to i16")] rotation_units: delta.y as i16, }, )); diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 394050dd7..d3118500a 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -235,7 +235,7 @@ async fn connect( let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector); - let erased_stream = Box::new(upgraded_stream) as Box; + let erased_stream: Box = Box::new(upgraded_stream); let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); let connection_result = ironrdp_tokio::connect_finalize( @@ -336,7 +336,7 @@ async fn connect_ws( .await?; let (ws, leftover_bytes) = framed.into_inner(); - let erased_stream = Box::new(ws) as Box; + let erased_stream: Box = Box::new(ws); let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); Ok((connection_result, upgraded_framed)) diff --git a/crates/ironrdp-cliprdr-native/src/windows/clipboard_data_ref.rs b/crates/ironrdp-cliprdr-native/src/windows/clipboard_data_ref.rs index b95d7b3c3..066721428 100644 --- a/crates/ironrdp-cliprdr-native/src/windows/clipboard_data_ref.rs +++ b/crates/ironrdp-cliprdr-native/src/windows/clipboard_data_ref.rs @@ -27,7 +27,7 @@ impl<'a> ClipboardDataRef<'a> { }; // SAFETY: It is safe to call `GlobalLock` on the valid handle. - let data = unsafe { GlobalLock(handle) } as *const u8; + let data = unsafe { GlobalLock(handle) }.cast::().cast_const(); if data.is_null() { // Can't lock data handle, handle is not valid anymore (e.g. clipboard has changed) diff --git a/crates/ironrdp-cliprdr-native/src/windows/clipboard_impl.rs b/crates/ironrdp-cliprdr-native/src/windows/clipboard_impl.rs index ba1dba7ee..666328466 100644 --- a/crates/ironrdp-cliprdr-native/src/windows/clipboard_impl.rs +++ b/crates/ironrdp-cliprdr-native/src/windows/clipboard_impl.rs @@ -1,3 +1,4 @@ +use core::ptr::with_exposed_provenance_mut; use core::time::Duration; use std::collections::HashSet; use std::sync::mpsc; @@ -320,17 +321,19 @@ pub(crate) unsafe extern "system" fn clipboard_subproc( // SAFETY: `data` is a valid pointer, returned by `Box::into_raw`, transferred to OS earlier // via `SetWindowSubclass` call. - let _ = unsafe { Box::from_raw(data as *mut WinClipboardImpl) }; + let _ = unsafe { Box::from_raw(with_exposed_provenance_mut::(data)) }; return LRESULT(0); } // SAFETY: `data` is a valid pointer, returned by `Box::into_raw`, transferred to OS earlier // via `SetWindowSubclass` call. - let ctx = unsafe { &mut *(data as *mut WinClipboardImpl) }; + let ctx = unsafe { &mut *(with_exposed_provenance_mut::(data)) }; match msg { // We need to keep track of window state to distinguish between local and remote copy - WM_ACTIVATE | WM_ACTIVATEAPP => ctx.window_is_active = wparam.0 != WA_INACTIVE as usize, // `as` conversion is fine for constants + WM_ACTIVATE | WM_ACTIVATEAPP => { + ctx.window_is_active = wparam.0 != usize::try_from(WA_INACTIVE).expect("WA_INACTIVE fits into usize") + } // Sent by the OS when OS clipboard content is changed WM_CLIPBOARDUPDATE => { // SAFETY: `GetClipboardOwner` is always safe to call. @@ -347,8 +350,9 @@ pub(crate) unsafe extern "system" fn clipboard_subproc( } // Sent by the OS when delay-rendered data is requested for rendering. WM_RENDERFORMAT => { - #[expect(clippy::cast_possible_truncation)] // should never truncate in practice - ctx.handle_event(BackendEvent::RenderFormat(ClipboardFormatId::new(wparam.0 as u32))); + ctx.handle_event(BackendEvent::RenderFormat(ClipboardFormatId::new( + u32::try_from(wparam.0).expect("should never truncate in practice"), + ))); } // Sent by the OS when all delay-rendered data is requested for rendering. WM_RENDERALLFORMATS => { diff --git a/crates/ironrdp-cliprdr-native/src/windows/mod.rs b/crates/ironrdp-cliprdr-native/src/windows/mod.rs index b488cfb5c..7051fcfb8 100644 --- a/crates/ironrdp-cliprdr-native/src/windows/mod.rs +++ b/crates/ironrdp-cliprdr-native/src/windows/mod.rs @@ -200,8 +200,14 @@ impl WinClipboard { // // SAFETY: `window` is a valid window handle, `clipboard_subproc` is in the static memory, // `ctx` is valid and its ownership is transferred to the subclass via `into_raw`. - let winapi_result = - unsafe { SetWindowSubclass(window, Some(clipboard_subproc), 0, Box::into_raw(ctx) as usize) }; + let winapi_result = unsafe { + SetWindowSubclass( + window, + Some(clipboard_subproc), + 0, + Box::into_raw(ctx).expose_provenance(), + ) + }; if winapi_result == FALSE { return Err(WinCliprdrError::WindowSubclass); diff --git a/crates/ironrdp-cliprdr-native/src/windows/utils.rs b/crates/ironrdp-cliprdr-native/src/windows/utils.rs index bd1120c83..198ca81d9 100644 --- a/crates/ironrdp-cliprdr-native/src/windows/utils.rs +++ b/crates/ironrdp-cliprdr-native/src/windows/utils.rs @@ -26,7 +26,7 @@ impl GlobalMemoryBuffer { // - `dst` is valid for writes of `data.len()` bytes, we allocated enough above. // - Both `data` and `dst` are properly aligned: u8 alignment is 1 // - Memory regions are not overlapping, `dst` was allocated by us just above. - unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), dst as *mut u8, data.len()) }; + unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), dst.cast::(), data.len()) }; // SAFETY: We called `GlobalLock` on this handle just above. if let Err(error) = unsafe { GlobalUnlock(handle) } { diff --git a/crates/ironrdp-cliprdr/src/lib.rs b/crates/ironrdp-cliprdr/src/lib.rs index fa83f1abc..889ca92be 100644 --- a/crates/ironrdp-cliprdr/src/lib.rs +++ b/crates/ironrdp-cliprdr/src/lib.rs @@ -1,10 +1,6 @@ #![cfg_attr(doc, doc = include_str!("../README.md"))] #![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")] #![allow(clippy::arithmetic_side_effects)] // FIXME: remove -#![allow(clippy::cast_lossless)] // FIXME: remove -#![allow(clippy::cast_possible_truncation)] // FIXME: remove -#![allow(clippy::cast_possible_wrap)] // FIXME: remove -#![allow(clippy::cast_sign_loss)] // FIXME: remove pub mod backend; pub mod pdu; diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 1ddd09eea..d763358b6 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -699,9 +699,9 @@ fn create_gcc_blocks<'a>( desktop_physical_width: Some(0), // 0 per FreeRDP desktop_physical_height: Some(0), // 0 per FreeRDP desktop_orientation: if config.desktop_size.width > config.desktop_size.height { - Some(MonitorOrientation::Landscape as u16) + Some(MonitorOrientation::Landscape.as_u16()) } else { - Some(MonitorOrientation::Portrait as u16) + Some(MonitorOrientation::Portrait.as_u16()) }, desktop_scale_factor: Some(config.desktop_scale_factor), device_scale_factor: if config.desktop_scale_factor >= 100 && config.desktop_scale_factor <= 500 { diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 0b081b6db..9918e2fcd 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -200,7 +200,7 @@ impl Header { fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { ensure_fixed_part_size!(in: dst); - dst.write_u8(((self.cmd as u8) << 4) | (Into::::into(self.sp) << 2) | Into::::into(self.cb_id)); + dst.write_u8(((self.cmd.as_u8()) << 4) | (Into::::into(self.sp) << 2) | Into::::into(self.cb_id)); Ok(()) } @@ -235,6 +235,16 @@ enum Cmd { SoftSyncResponse = 0x09, } +impl Cmd { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] + fn as_u8(self) -> u8 { + self as u8 + } +} + impl TryFrom for Cmd { type Error = DecodeError; @@ -666,7 +676,7 @@ impl CapsVersion { fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { ensure_size!(in: dst, size: Self::size()); - dst.write_u16(*self as u16); + dst.write_u16(u16::from(*self)); Ok(()) } @@ -689,6 +699,10 @@ impl TryFrom for CapsVersion { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(version: CapsVersion) -> Self { version as u16 } diff --git a/crates/ironrdp-fuzzing/src/oracles/mod.rs b/crates/ironrdp-fuzzing/src/oracles/mod.rs index b04a6788c..2858316c7 100644 --- a/crates/ironrdp-fuzzing/src/oracles/mod.rs +++ b/crates/ironrdp-fuzzing/src/oracles/mod.rs @@ -114,8 +114,8 @@ pub fn rdp6_decode_bitmap_stream_to_rgb24(input: &BitmapInput<'_>) { let _ = BitmapStreamDecoder::default().decode_bitmap_stream_to_rgb24( input.src, &mut out, - input.width as usize, - input.height as usize, + usize::from(input.width), + usize::from(input.height), ); } diff --git a/crates/ironrdp-graphics/src/color_conversion.rs b/crates/ironrdp-graphics/src/color_conversion.rs index b2554d37d..2ec929b5b 100644 --- a/crates/ironrdp-graphics/src/color_conversion.rs +++ b/crates/ironrdp-graphics/src/color_conversion.rs @@ -83,10 +83,15 @@ pub fn to_64x64_ycbcr_tile( /// Convert a 16-bit RDP color to RGB representation. Input value should be represented in /// little-endian format. pub fn rdp_16bit_to_rgb(color: u16) -> [u8; 3] { - let r = (((((color >> 11) & 0x1f) * 527) + 23) >> 6) as u8; - let g = (((((color >> 5) & 0x3f) * 259) + 33) >> 6) as u8; - let b = ((((color & 0x1f) * 527) + 23) >> 6) as u8; - [r, g, b] + #[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer underflow)")] + let out = { + let r = u8::try_from(((((color >> 11) & 0x1f) * 527) + 23) >> 6).expect("max possible value is 255"); + let g = u8::try_from(((((color >> 5) & 0x3f) * 259) + 33) >> 6).expect("max possible value is 255"); + let b = u8::try_from((((color & 0x1f) * 527) + 23) >> 6).expect("max possible value is 255"); + [r, g, b] + }; + + out } #[derive(Debug)] diff --git a/crates/ironrdp-graphics/src/dwt.rs b/crates/ironrdp-graphics/src/dwt.rs index 6381cb542..5e13b8df6 100644 --- a/crates/ironrdp-graphics/src/dwt.rs +++ b/crates/ironrdp-graphics/src/dwt.rs @@ -22,17 +22,21 @@ fn dwt_vertical(buffer: &[i16], dwt: &mut [i16]) { let h_index = l_index + SUBBAND_WIDTH * total_width; let src_index = y * total_width + x; - dwt[h_index] = ((i32::from(buffer[src_index + total_width]) - - ((i32::from(buffer[src_index]) - + i32::from(buffer[src_index + if n < SUBBAND_WIDTH - 1 { 2 * total_width } else { 0 }])) - >> 1)) - >> 1) as i16; - dwt[l_index] = (i32::from(buffer[src_index]) - + if n == 0 { - i32::from(dwt[h_index]) - } else { - (i32::from(dwt[h_index - total_width]) + i32::from(dwt[h_index])) >> 1 - }) as i16; + dwt[h_index] = i32_to_i16_possible_truncation( + (i32::from(buffer[src_index + total_width]) + - ((i32::from(buffer[src_index]) + + i32::from(buffer[src_index + if n < SUBBAND_WIDTH - 1 { 2 * total_width } else { 0 }])) + >> 1)) + >> 1, + ); + dwt[l_index] = i32_to_i16_possible_truncation( + i32::from(buffer[src_index]) + + if n == 0 { + i32::from(dwt[h_index]) + } else { + (i32::from(dwt[h_index - total_width]) + i32::from(dwt[h_index])) >> 1 + }, + ); } } } @@ -57,16 +61,20 @@ fn dwt_horizontal(mut buffer: &mut [i16], dwt: &[i16 let x = n * 2; // HL - hl[n] = ((i32::from(l_src[x + 1]) - - ((i32::from(l_src[x]) + i32::from(l_src[if n < SUBBAND_WIDTH - 1 { x + 2 } else { x }])) >> 1)) - >> 1) as i16; + hl[n] = i32_to_i16_possible_truncation( + (i32::from(l_src[x + 1]) + - ((i32::from(l_src[x]) + i32::from(l_src[if n < SUBBAND_WIDTH - 1 { x + 2 } else { x }])) >> 1)) + >> 1, + ); // LL - ll[n] = (i32::from(l_src[x]) - + if n == 0 { - i32::from(hl[n]) - } else { - (i32::from(hl[n - 1]) + i32::from(hl[n])) >> 1 - }) as i16; + ll[n] = i32_to_i16_possible_truncation( + i32::from(l_src[x]) + + if n == 0 { + i32::from(hl[n]) + } else { + (i32::from(hl[n - 1]) + i32::from(hl[n])) >> 1 + }, + ); } // H @@ -74,16 +82,20 @@ fn dwt_horizontal(mut buffer: &mut [i16], dwt: &[i16 let x = n * 2; // HH - hh[n] = ((i32::from(h_src[x + 1]) - - ((i32::from(h_src[x]) + i32::from(h_src[if n < SUBBAND_WIDTH - 1 { x + 2 } else { x }])) >> 1)) - >> 1) as i16; + hh[n] = i32_to_i16_possible_truncation( + (i32::from(h_src[x + 1]) + - ((i32::from(h_src[x]) + i32::from(h_src[if n < SUBBAND_WIDTH - 1 { x + 2 } else { x }])) >> 1)) + >> 1, + ); // LH - lh[n] = (i32::from(h_src[x]) - + if n == 0 { - i32::from(hh[n]) - } else { - (i32::from(hh[n - 1]) + i32::from(hh[n])) >> 1 - }) as i16; + lh[n] = i32_to_i16_possible_truncation( + i32::from(h_src[x]) + + if n == 0 { + i32::from(hh[n]) + } else { + (i32::from(hh[n - 1]) + i32::from(hh[n])) >> 1 + }, + ); } hl = &mut hl[SUBBAND_WIDTH..]; @@ -124,24 +136,30 @@ fn inverse_horizontal(mut buffer: &[i16], temp_buffer: &mut [i16], subband_width for _ in 0..subband_width { // Even coefficients - l_dst[0] = (i32::from(ll[0]) - ((i32::from(hl[0]) + i32::from(hl[0]) + 1) >> 1)) as i16; - h_dst[0] = (i32::from(lh[0]) - ((i32::from(hh[0]) + i32::from(hh[0]) + 1) >> 1)) as i16; + l_dst[0] = i32_to_i16_possible_truncation(i32::from(ll[0]) - ((i32::from(hl[0]) + i32::from(hl[0]) + 1) >> 1)); + h_dst[0] = i32_to_i16_possible_truncation(i32::from(lh[0]) - ((i32::from(hh[0]) + i32::from(hh[0]) + 1) >> 1)); for n in 1..subband_width { let x = n * 2; - l_dst[x] = (i32::from(ll[n]) - ((i32::from(hl[n - 1]) + i32::from(hl[n]) + 1) >> 1)) as i16; - h_dst[x] = (i32::from(lh[n]) - ((i32::from(hh[n - 1]) + i32::from(hh[n]) + 1) >> 1)) as i16; + l_dst[x] = + i32_to_i16_possible_truncation(i32::from(ll[n]) - ((i32::from(hl[n - 1]) + i32::from(hl[n]) + 1) >> 1)); + h_dst[x] = + i32_to_i16_possible_truncation(i32::from(lh[n]) - ((i32::from(hh[n - 1]) + i32::from(hh[n]) + 1) >> 1)); } // Odd coefficients for n in 0..subband_width - 1 { let x = n * 2; - l_dst[x + 1] = (i32::from(hl[n] << 1) + ((i32::from(l_dst[x]) + i32::from(l_dst[x + 2])) >> 1)) as i16; - h_dst[x + 1] = (i32::from(hh[n] << 1) + ((i32::from(h_dst[x]) + i32::from(h_dst[x + 2])) >> 1)) as i16; + l_dst[x + 1] = i32_to_i16_possible_truncation( + i32::from(hl[n] << 1) + ((i32::from(l_dst[x]) + i32::from(l_dst[x + 2])) >> 1), + ); + h_dst[x + 1] = i32_to_i16_possible_truncation( + i32::from(hh[n] << 1) + ((i32::from(h_dst[x]) + i32::from(h_dst[x + 2])) >> 1), + ); } let n = subband_width - 1; let x = n * 2; - l_dst[x + 1] = (i32::from(hl[n] << 1) + i32::from(l_dst[x])) as i16; - h_dst[x + 1] = (i32::from(hh[n] << 1) + i32::from(h_dst[x])) as i16; + l_dst[x + 1] = i32_to_i16_possible_truncation(i32::from(hl[n] << 1) + i32::from(l_dst[x])); + h_dst[x + 1] = i32_to_i16_possible_truncation(i32::from(hh[n] << 1) + i32::from(h_dst[x])); hl = &hl[subband_width..]; lh = &lh[subband_width..]; @@ -157,8 +175,9 @@ fn inverse_vertical(mut buffer: &mut [i16], mut temp_buffer: &[i16], subband_wid let total_width = subband_width * 2; for _ in 0..total_width { - buffer[0] = - (i32::from(temp_buffer[0]) - ((i32::from(temp_buffer[subband_width * total_width]) * 2 + 1) >> 1)) as i16; + buffer[0] = i32_to_i16_possible_truncation( + i32::from(temp_buffer[0]) - ((i32::from(temp_buffer[subband_width * total_width]) * 2 + 1) >> 1), + ); let mut l = temp_buffer; let mut lh = &temp_buffer[(subband_width - 1) * total_width..]; @@ -171,18 +190,28 @@ fn inverse_vertical(mut buffer: &mut [i16], mut temp_buffer: &[i16], subband_wid h = &h[total_width..]; // Even coefficients - dst[2 * total_width] = (i32::from(l[0]) - ((i32::from(lh[0]) + i32::from(h[0]) + 1) >> 1)) as i16; + dst[2 * total_width] = + i32_to_i16_possible_truncation(i32::from(l[0]) - ((i32::from(lh[0]) + i32::from(h[0]) + 1) >> 1)); // Odd coefficients - dst[total_width] = - (i32::from(lh[0] << 1) + ((i32::from(dst[0]) + i32::from(dst[2 * total_width])) >> 1)) as i16; + dst[total_width] = i32_to_i16_possible_truncation( + i32::from(lh[0] << 1) + ((i32::from(dst[0]) + i32::from(dst[2 * total_width])) >> 1), + ); dst = &mut dst[2 * total_width..]; } - dst[total_width] = (i32::from(lh[total_width] << 1) + ((i32::from(dst[0]) + i32::from(dst[0])) >> 1)) as i16; + dst[total_width] = i32_to_i16_possible_truncation( + i32::from(lh[total_width] << 1) + ((i32::from(dst[0]) + i32::from(dst[0])) >> 1), + ); temp_buffer = &temp_buffer[1..]; buffer = &mut buffer[1..]; } } + +#[expect(clippy::as_conversions)] +#[expect(clippy::cast_possible_truncation)] +fn i32_to_i16_possible_truncation(value: i32) -> i16 { + value as i16 +} diff --git a/crates/ironrdp-graphics/src/lib.rs b/crates/ironrdp-graphics/src/lib.rs index 03def02e9..02d4104ad 100644 --- a/crates/ironrdp-graphics/src/lib.rs +++ b/crates/ironrdp-graphics/src/lib.rs @@ -1,10 +1,6 @@ #![cfg_attr(doc, doc = include_str!("../README.md"))] #![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")] #![allow(clippy::arithmetic_side_effects)] // FIXME: remove -#![allow(clippy::cast_lossless)] // FIXME: remove -#![allow(clippy::cast_possible_truncation)] // FIXME: remove -#![allow(clippy::cast_possible_wrap)] // FIXME: remove -#![allow(clippy::cast_sign_loss)] // FIXME: remove pub mod color_conversion; pub mod diff; diff --git a/crates/ironrdp-graphics/src/pointer.rs b/crates/ironrdp-graphics/src/pointer.rs index 48b179275..26dbdd40f 100644 --- a/crates/ironrdp-graphics/src/pointer.rs +++ b/crates/ironrdp-graphics/src/pointer.rs @@ -260,9 +260,12 @@ impl DecodedPointer { } else if target.should_premultiply_alpha() { // Calculate premultiplied alpha via integer arithmetic let with_premultiplied_alpha = [ - ((color[0] as u16 * color[0] as u16) >> 8) as u8, - ((color[1] as u16 * color[1] as u16) >> 8) as u8, - ((color[2] as u16 * color[2] as u16) >> 8) as u8, + u8::try_from((u16::from(color[0]) * u16::from(color[0])) >> 8) + .expect("(u16 >> 8) fits into u8"), + u8::try_from((u16::from(color[1]) * u16::from(color[1])) >> 8) + .expect("(u16 >> 8) fits into u8"), + u8::try_from((u16::from(color[2]) * u16::from(color[2])) >> 8) + .expect("(u16 >> 8) fits into u8"), color[3], ]; bitmap_data.extend_from_slice(&with_premultiplied_alpha); diff --git a/crates/ironrdp-graphics/src/quantization.rs b/crates/ironrdp-graphics/src/quantization.rs index 38c0f65b5..c4ab7d97a 100644 --- a/crates/ironrdp-graphics/src/quantization.rs +++ b/crates/ironrdp-graphics/src/quantization.rs @@ -11,7 +11,7 @@ pub fn decode(buffer: &mut [i16], quant: &Quant) { let (first_level, buffer) = buffer.split_at_mut(FIRST_LEVEL_SUBBANDS_COUNT * FIRST_LEVEL_SIZE); let (second_level, third_level) = buffer.split_at_mut(SECOND_LEVEL_SUBBANDS_COUNT * SECOND_LEVEL_SIZE); - let decode_chunk = |a: (&mut [i16], u8)| decode_block(a.0, a.1 as i16 - 1); + let decode_chunk = |a: (&mut [i16], u8)| decode_block(a.0, i16::from(a.1) - 1); first_level .chunks_mut(FIRST_LEVEL_SIZE) @@ -49,7 +49,7 @@ pub fn encode(buffer: &mut [i16], quant: &Quant) { let (first_level, buffer) = buffer.split_at_mut(FIRST_LEVEL_SUBBANDS_COUNT * FIRST_LEVEL_SIZE); let (second_level, third_level) = buffer.split_at_mut(SECOND_LEVEL_SUBBANDS_COUNT * SECOND_LEVEL_SIZE); - let encode_chunk = |a: (&mut [i16], u8)| encode_block(a.0, a.1 as i16 - 1); + let encode_chunk = |a: (&mut [i16], u8)| encode_block(a.0, i16::from(a.1) - 1); first_level .chunks_mut(FIRST_LEVEL_SIZE) diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs index 0e26231ee..b71aba2d7 100644 --- a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs @@ -196,7 +196,7 @@ impl<'a> BitmapStreamDecoderImpl<'a> { fn write_aycocg_planes_to_rgb24(&self, params: AYCoCgParams, planes: &[u8], dst: &mut Vec) { #![allow(clippy::similar_names)] // It’s hard to find better names for co, cg, etc. - let sample_shift = params.chroma_subsampling as usize; + let sample_shift = usize::from(params.chroma_subsampling); let (y_offset, co_offset, cg_offset) = ( self.color_plane_offsets[0], @@ -265,12 +265,13 @@ fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb { // |R| |1 1/2 -1/2| |Y | // |G| = |1 0 1/2| * |Co| // |B| |1 -1/2 -1/2| |Cg| - let chroma_shift = (cll - 1) as usize; + let chroma_shift = cll - 1; - let clip_i16 = |v: i16| v.clamp(0, 255) as u8; + let clip_i16 = + |v: i16| u8::try_from(v.clamp(0, 255)).expect("fits into u8 because the value is clamped to [0..256]"); - let co_signed = (co << chroma_shift) as i8; - let cg_signed = (cg << chroma_shift) as i8; + let co_signed = cast_singed(co << chroma_shift); + let cg_signed = cast_singed(cg << chroma_shift); let y = i16::from(y); let co = i16::from(co_signed); @@ -281,7 +282,18 @@ fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb { let g = clip_i16(y + cg); let b = clip_i16(t - co); - Rgb { r, g, b } + return Rgb { r, g, b }; + + // TODO: Use (`cast_signed`)[https://doc.rust-lang.org/std/primitive.u8.html#method.cast_signed] + // once MSRV is 1.87+. + #[expect( + clippy::as_conversions, + clippy::cast_possible_wrap, + reason = "there is no other way to do this" + )] + fn cast_singed(value: u8) -> i8 { + value as i8 + } } impl BitmapStreamDecoder { diff --git a/crates/ironrdp-graphics/src/rdp6/rle.rs b/crates/ironrdp-graphics/src/rdp6/rle.rs index 7f74cb572..7e9a66fc5 100644 --- a/crates/ironrdp-graphics/src/rdp6/rle.rs +++ b/crates/ironrdp-graphics/src/rdp6/rle.rs @@ -93,9 +93,9 @@ impl RlePlaneDecoder { let raw_bytes_field = (control_byte >> 4) & 0x0F; let (run_length, raw_bytes_count) = match rle_bytes_field { - 1 => (16 + raw_bytes_field as usize, 0), - 2 => (32 + raw_bytes_field as usize, 0), - rle_control => (rle_control as usize, raw_bytes_field as usize), + 1 => (16 + usize::from(raw_bytes_field), 0), + 2 => (32 + usize::from(raw_bytes_field), 0), + rle_control => (usize::from(rle_control), usize::from(raw_bytes_field)), }; self.decoded_data_len = raw_bytes_count + run_length; @@ -207,7 +207,8 @@ impl RleEncoderScanlineIterator { } fn delta_value(prev: u8, next: u8) -> u8 { - let mut result = (next as i16 - prev as i16) as u8; + let mut result = u8::try_from((i16::from(next) - i16::from(prev)) & 0xFF) + .expect("masking with 0xFF ensures that the value fits into u8"); // bit magic from 3.1.9.2.1 of [MS-RDPEGDI]. if result < 128 { @@ -326,7 +327,10 @@ impl RlePlaneEncoder { raw = &raw[15..]; } - let control = ((raw.len() as u8) << 4) + cmp::min(run, 15) as u8; + let raw_len = u8::try_from(raw.len()).expect("max value is guaranteed to be 15 due to the prior while loop"); + let run_capped = u8::try_from(cmp::min(run, 15)).expect("max value is guaranteed to be 15"); + + let control = (raw_len << 4) + run_capped; ensure_size!(dst: dst, size: raw.len() + 1); @@ -352,7 +356,8 @@ impl RlePlaneEncoder { while run >= 16 { ensure_size!(dst: dst, size: 1); - let current = cmp::min(run, MAX_DECODED_SEGMENT_SIZE) as u8; + let current = u8::try_from(cmp::min(run, MAX_DECODED_SEGMENT_SIZE)) + .expect("max value is guaranteed to be MAX_DECODED_SEGMENT_SIZE (47)"); let c_raw_bytes = cmp::min(current / 16, 2); let n_run_length = current - c_raw_bytes * 16; @@ -361,7 +366,7 @@ impl RlePlaneEncoder { dst.write_u8(control); written += 1; - run -= current as usize; + run -= usize::from(current); } if run > 0 { diff --git a/crates/ironrdp-graphics/src/rlgr.rs b/crates/ironrdp-graphics/src/rlgr.rs index 31dc09b2c..1854c3295 100644 --- a/crates/ironrdp-graphics/src/rlgr.rs +++ b/crates/ironrdp-graphics/src/rlgr.rs @@ -95,12 +95,15 @@ pub fn encode(mode: EntropyAlgorithm, input: &[i16], tile: &mut [u8]) -> Result< runmax = 1 << k; } bits.output_bit(1, true); - bits.output_bits(k as usize, nz); + bits.output_bits( + usize::try_from(k).map_err(|_| RlgrError::InvalidIntegralConversion("k"))?, + nz, + ); if let Some(val) = input.next() { - let mag = val.unsigned_abs() as u32; + let mag = u32::from(val.unsigned_abs()); bits.output_bit(1, *val < 0); - code_gr(&mut bits, &mut krp, mag - 1); + code_gr(&mut bits, &mut krp, mag - 1)?; } kp = kp.saturating_sub(DN_GR); k = kp >> LS_GR; @@ -110,11 +113,10 @@ pub fn encode(mode: EntropyAlgorithm, input: &[i16], tile: &mut [u8]) -> Result< let input_first = *input .next() .expect("value is guaranteed to be `Some` due to the prior check"); - match mode { EntropyAlgorithm::Rlgr1 => { let two_ms = get_2magsign(input_first); - code_gr(&mut bits, &mut krp, two_ms); + code_gr(&mut bits, &mut krp, two_ms)?; if two_ms == 0 { kp = min(kp + UP_GR, KP_MAX); } else { @@ -126,9 +128,11 @@ pub fn encode(mode: EntropyAlgorithm, input: &[i16], tile: &mut [u8]) -> Result< let two_ms1 = get_2magsign(input_first); let two_ms2 = input.next().map(|&n| get_2magsign(n)).unwrap_or(1); let sum2ms = two_ms1 + two_ms2; - code_gr(&mut bits, &mut krp, sum2ms); + code_gr(&mut bits, &mut krp, sum2ms)?; - let m = 32 - sum2ms.leading_zeros() as usize; + let m = 32 + - usize::try_from(sum2ms.leading_zeros()) + .map_err(|_| RlgrError::InvalidIntegralConversion("sum2ms leading zeros count"))?; if m != 0 { bits.output_bits(m, two_ms1); } @@ -152,14 +156,15 @@ pub fn encode(mode: EntropyAlgorithm, input: &[i16], tile: &mut [u8]) -> Result< fn get_2magsign(val: i16) -> u32 { let sign = if val < 0 { 1 } else { 0 }; - (val.unsigned_abs() as u32) * 2 - sign + (u32::from(val.unsigned_abs())) * 2 - sign } -fn code_gr(bits: &mut BitStream<'_>, krp: &mut u32, val: u32) { - let kr = (*krp >> LS_GR) as usize; - let vk = (val >> kr) as usize; +fn code_gr(bits: &mut BitStream<'_>, krp: &mut u32, val: u32) -> Result<(), RlgrError> { + let kr = usize::try_from(*krp >> LS_GR).map_err(|_| RlgrError::InvalidIntegralConversion("krp >> LS_GR"))?; + let vk = val >> kr; + let vk_usize = usize::try_from(vk).map_err(|_| RlgrError::InvalidIntegralConversion("val >> kr"))?; - bits.output_bit(vk, true); + bits.output_bit(vk_usize, true); bits.output_bit(1, false); if kr != 0 { let remainder = val & ((1 << kr) - 1); @@ -168,8 +173,10 @@ fn code_gr(bits: &mut BitStream<'_>, krp: &mut u32, val: u32) { if vk == 0 { *krp = krp.saturating_sub(2); } else if vk > 1 { - *krp = min(*krp + vk as u32, KP_MAX); + *krp = min(*krp + vk, KP_MAX); } + + Ok(()) } pub fn decode(mode: EntropyAlgorithm, tile: &[u8], mut output: &mut [i16]) -> Result<(), RlgrError> { @@ -188,22 +195,34 @@ pub fn decode(mode: EntropyAlgorithm, tile: &[u8], mut output: &mut [i16]) -> Re CompressionMode::RunLength => { let number_of_zeros = truncate_leading_value(&mut bits, false); try_split_bits!(bits, 1); - let run = count_run(number_of_zeros, &mut k, &mut kp) + load_be_u32(try_split_bits!(bits, k as usize)); + let run = count_run(number_of_zeros, &mut k, &mut kp) + + load_be_u32(try_split_bits!( + bits, + usize::try_from(k).map_err(|_| RlgrError::InvalidIntegralConversion("k"))? + )); let sign_bit = try_split_bits!(bits, 1).load_be::(); let number_of_ones = truncate_leading_value(&mut bits, true); try_split_bits!(bits, 1); - let code_remainder = load_be_u32(try_split_bits!(bits, kr as usize)) + ((number_of_ones as u32) << kr); + let code_remainder = load_be_u32(try_split_bits!( + bits, + usize::try_from(kr).map_err(|_| RlgrError::InvalidIntegralConversion("kr"))? + )) + (u32::try_from(number_of_ones) + .map_err(|_| RlgrError::InvalidIntegralConversion("number of ones"))? + << kr); - update_parameters_according_to_number_of_ones(number_of_ones, &mut kr, &mut krp); + update_parameters_according_to_number_of_ones(number_of_ones, &mut kr, &mut krp)?; kp = kp.saturating_sub(DN_GR); k = kp >> LS_GR; - let magnitude = compute_rl_magnitude(sign_bit, code_remainder); + let magnitude = compute_rl_magnitude(sign_bit, code_remainder)?; - let size = min(run as usize, output.len()); + let size = min( + usize::try_from(run).map_err(|_| RlgrError::InvalidIntegralConversion("run"))?, + output.len(), + ); fill(&mut output[..size], 0); output = &mut output[size..]; write_byte!(output, magnitude); @@ -212,13 +231,18 @@ pub fn decode(mode: EntropyAlgorithm, tile: &[u8], mut output: &mut [i16]) -> Re let number_of_ones = truncate_leading_value(&mut bits, true); try_split_bits!(bits, 1); - let code_remainder = load_be_u32(try_split_bits!(bits, kr as usize)) + ((number_of_ones as u32) << kr); + let code_remainder = load_be_u32(try_split_bits!( + bits, + usize::try_from(kr).map_err(|_| RlgrError::InvalidIntegralConversion("kr"))? + )) + ((u32::try_from(number_of_ones) + .map_err(|_| RlgrError::InvalidIntegralConversion("number of ones"))?) + << kr); - update_parameters_according_to_number_of_ones(number_of_ones, &mut kr, &mut krp); + update_parameters_according_to_number_of_ones(number_of_ones, &mut kr, &mut krp)?; match mode { EntropyAlgorithm::Rlgr1 => { - let magnitude = compute_rlgr1_magnitude(code_remainder, &mut k, &mut kp); + let magnitude = compute_rlgr1_magnitude(code_remainder, &mut k, &mut kp)?; write_byte!(output, magnitude); } EntropyAlgorithm::Rlgr3 => { @@ -234,10 +258,10 @@ pub fn decode(mode: EntropyAlgorithm, tile: &[u8], mut output: &mut [i16]) -> Re k = kp >> LS_GR; } - let magnitude = compute_rlgr3_magnitude(val1); + let magnitude = compute_rlgr3_magnitude(val1)?; write_byte!(output, magnitude); - let magnitude = compute_rlgr3_magnitude(val2); + let magnitude = compute_rlgr3_magnitude(val2)?; write_byte!(output, magnitude); } } @@ -288,37 +312,41 @@ fn count_run(number_of_zeros: usize, k: &mut u32, kp: &mut u32) -> u32 { .sum() } -fn compute_rl_magnitude(sign_bit: u8, code_remainder: u32) -> i16 { +fn compute_rl_magnitude(sign_bit: u8, code_remainder: u32) -> Result { + let rl_magnitude = + i16::try_from(code_remainder + 1).map_err(|_| RlgrError::InvalidIntegralConversion("code remainder + 1"))?; + if sign_bit != 0 { - -((code_remainder + 1) as i16) + Ok(-rl_magnitude) } else { - (code_remainder + 1) as i16 + Ok(rl_magnitude) } } -fn compute_rlgr1_magnitude(code_remainder: u32, k: &mut u32, kp: &mut u32) -> i16 { +fn compute_rlgr1_magnitude(code_remainder: u32, k: &mut u32, kp: &mut u32) -> Result { if code_remainder == 0 { *kp = min(*kp + UQ_GR, KP_MAX); *k = *kp >> LS_GR; - 0 + Ok(0) } else { *kp = kp.saturating_sub(DQ_GR); *k = *kp >> LS_GR; if code_remainder % 2 != 0 { - -(((code_remainder + 1) >> 1) as i16) + Ok(-i16::try_from((code_remainder + 1) >> 1) + .map_err(|_| RlgrError::InvalidIntegralConversion("(code remainder + 1) >> 1"))?) } else { - (code_remainder >> 1) as i16 + i16::try_from(code_remainder >> 1).map_err(|_| RlgrError::InvalidIntegralConversion("code remainder >> 1")) } } } -fn compute_rlgr3_magnitude(val: u32) -> i16 { +fn compute_rlgr3_magnitude(val: u32) -> Result { if val % 2 != 0 { - -(((val + 1) >> 1) as i16) + Ok(-i16::try_from((val + 1) >> 1).map_err(|_| RlgrError::InvalidIntegralConversion("(val + 1) >> 1"))?) } else { - (val >> 1) as i16 + i16::try_from(val >> 1).map_err(|_| RlgrError::InvalidIntegralConversion("val >> 1")) } } @@ -334,14 +362,23 @@ fn compute_n_index(code_remainder: u32) -> usize { 32 - leading_zeros } -fn update_parameters_according_to_number_of_ones(number_of_ones: usize, kr: &mut u32, krp: &mut u32) { +fn update_parameters_according_to_number_of_ones( + number_of_ones: usize, + kr: &mut u32, + krp: &mut u32, +) -> Result<(), RlgrError> { if number_of_ones == 0 { *krp = (*krp).saturating_sub(2); *kr = *krp >> LS_GR; } else if number_of_ones > 1 { - *krp = min(*krp + number_of_ones as u32, KP_MAX); + *krp = min( + *krp + u32::try_from(number_of_ones).map_err(|_| RlgrError::InvalidIntegralConversion("number of ones"))?, + KP_MAX, + ); *kr = *krp >> LS_GR; } + + Ok(()) } #[derive(Debug, Copy, Clone, PartialEq)] @@ -365,6 +402,7 @@ pub enum RlgrError { Io(io::Error), Yuv(YuvError), EmptyTile, + InvalidIntegralConversion(&'static str), } impl core::fmt::Display for RlgrError { @@ -373,6 +411,7 @@ impl core::fmt::Display for RlgrError { Self::Io(_) => write!(f, "IO error"), Self::Yuv(_) => write!(f, "YUV error"), Self::EmptyTile => write!(f, "the input tile is empty"), + Self::InvalidIntegralConversion(s) => write!(f, "invalid `{s}`: out of range integral type conversion"), } } } @@ -383,6 +422,7 @@ impl core::error::Error for RlgrError { Self::Io(error) => Some(error), Self::Yuv(error) => Some(error), Self::EmptyTile => None, + Self::InvalidIntegralConversion(_) => None, } } } diff --git a/crates/ironrdp-graphics/src/zgfx/control_messages.rs b/crates/ironrdp-graphics/src/zgfx/control_messages.rs index 522319297..9147aedc2 100644 --- a/crates/ironrdp-graphics/src/zgfx/control_messages.rs +++ b/crates/ironrdp-graphics/src/zgfx/control_messages.rs @@ -23,12 +23,14 @@ impl<'a> SegmentedDataPdu<'a> { match descriptor { SegmentedDescriptor::Single => Ok(SegmentedDataPdu::Single(BulkEncodedData::from_buffer(buffer)?)), SegmentedDescriptor::Multipart => { - let segment_count = buffer.read_u16::()? as usize; - let uncompressed_size = buffer.read_u32::()? as usize; + let segment_count = usize::from(buffer.read_u16::()?); + let uncompressed_size = usize::try_from(buffer.read_u32::()?) + .map_err(|_| ZgfxError::InvalidIntegralConversion("segments uncompressed size"))?; let mut segments = Vec::with_capacity(segment_count); for _ in 0..segment_count { - let size = buffer.read_u32::()? as usize; + let size = usize::try_from(buffer.read_u32::()?) + .map_err(|_| ZgfxError::InvalidIntegralConversion("segment data size"))?; let (segment_data, new_buffer) = buffer.split_at(size); buffer = new_buffer; diff --git a/crates/ironrdp-graphics/src/zgfx/mod.rs b/crates/ironrdp-graphics/src/zgfx/mod.rs index 90aaa3a2c..ba6fc0277 100644 --- a/crates/ironrdp-graphics/src/zgfx/mod.rs +++ b/crates/ironrdp-graphics/src/zgfx/mod.rs @@ -78,8 +78,8 @@ impl Decompressor { let mut bits = BitSlice::from_slice(encoded_data); // The value of the last byte indicates the number of unused bits in the final byte - bits = - &bits[..8 * (encoded_data.len() - 1) - *encoded_data.last().expect("encoded_data is not empty") as usize]; + bits = &bits + [..8 * (encoded_data.len() - 1) - usize::from(*encoded_data.last().expect("encoded_data is not empty"))]; let mut bits = Bits::new(bits); let mut bytes_written = 0; @@ -134,14 +134,15 @@ fn handle_match( distance_base: u32, history: &mut FixedCircularBuffer, output: &mut Vec, -) -> io::Result { +) -> Result { // Each token has been assigned a different base distance // and number of additional value bits to be added to compute the full distance. - let distance = (distance_base + bits.split_to(distance_value_size).load_be::()) as usize; + let distance = usize::try_from(distance_base + bits.split_to(distance_value_size).load_be::()) + .map_err(|_| ZgfxError::InvalidIntegralConversion("token's full distance"))?; if distance == 0 { - read_unencoded_bytes(bits, history, output) + read_unencoded_bytes(bits, history, output).map_err(ZgfxError::from) } else { read_encoded_bytes(bits, distance, history, output) } @@ -155,7 +156,7 @@ fn read_unencoded_bytes( // A match distance of zero is a special case, // which indicates that an unencoded run of bytes follows. // The count of bytes is encoded as a 15-bit value - let length = bits.split_to(15).load_be::() as usize; + let length = bits.split_to(15).load_be::(); if bits.remaining_bits_of_last_byte() > 0 { let pad_to_byte_boundary = 8 - bits.remaining_bits_of_last_byte(); @@ -178,7 +179,7 @@ fn read_encoded_bytes( distance: usize, history: &mut FixedCircularBuffer, output: &mut Vec, -) -> io::Result { +) -> Result { // A match length prefix follows the token and indicates // how many additional bits will be needed to get the full length // (the number of bytes to be copied). @@ -191,9 +192,12 @@ fn read_encoded_bytes( 3 } else { - let length = bits.split_to(length_token_size + 1).load_be::() as usize; + let length = bits.split_to(length_token_size + 1).load_be::(); + + let length_token_size = u32::try_from(length_token_size) + .map_err(|_| ZgfxError::InvalidIntegralConversion("length of the token size"))?; - let base = 2u32.pow(length_token_size as u32 + 1) as usize; + let base = 2usize.pow(length_token_size + 1); base + length }; @@ -440,6 +444,7 @@ pub enum ZgfxError { uncompressed_size: usize, }, TokenBitsNotFound, + InvalidIntegralConversion(&'static str), } impl core::fmt::Display for ZgfxError { @@ -456,6 +461,7 @@ impl core::fmt::Display for ZgfxError { "decompressed size of segments ({decompressed_size}) does not equal to uncompressed size ({uncompressed_size})", ), Self::TokenBitsNotFound => write!(f, "token bits not found"), + Self::InvalidIntegralConversion(type_name) => write!(f, "invalid `{type_name}`: out of range integral type conversion"), } } } @@ -468,6 +474,7 @@ impl core::error::Error for ZgfxError { Self::InvalidSegmentedDescriptor => None, Self::InvalidDecompressedSize { .. } => None, Self::TokenBitsNotFound => None, + Self::InvalidIntegralConversion(_) => None, } } } diff --git a/crates/ironrdp-input/src/lib.rs b/crates/ironrdp-input/src/lib.rs index 64a6a1b06..d7cffc0d3 100644 --- a/crates/ironrdp-input/src/lib.rs +++ b/crates/ironrdp-input/src/lib.rs @@ -24,6 +24,10 @@ pub enum MouseButton { } impl MouseButton { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] pub fn as_idx(self) -> usize { self as usize } @@ -78,6 +82,7 @@ impl Scancode { pub const fn from_u16(scancode: u16) -> Self { let extended = scancode & 0xE000 == 0xE000; + #[expect(clippy::as_conversions)] #[expect(clippy::cast_possible_truncation)] // truncating on purpose let code = scancode as u8; diff --git a/crates/ironrdp-mstsgu/src/lib.rs b/crates/ironrdp-mstsgu/src/lib.rs index 8ed962ad9..2a9051117 100644 --- a/crates/ironrdp-mstsgu/src/lib.rs +++ b/crates/ironrdp-mstsgu/src/lib.rs @@ -221,7 +221,8 @@ impl GwClient { let mut cur = ReadCursor::new(&msg); let hdr = PktHdr::decode(&mut cur).map_err(|e| custom_err!("Header Decode", e))?; - assert!(cur.len() >= hdr.length as usize - hdr.size()); + let header_length = usize::try_from(hdr.length).map_err(|_| Error::new("PktHdr too big", GwErrorKind::Decode))?; + assert!(cur.len() >= header_length - hdr.size()); match hdr.ty { PktTy::Keepalive => { continue; @@ -287,7 +288,10 @@ impl GwConn { let mut cur = ReadCursor::new(&msg); let hdr = PktHdr::decode(&mut cur).map_err(|_| Error::new("PktHdr", GwErrorKind::Decode))?; - if cur.len() != hdr.length as usize - hdr.size() { + + let header_length = + usize::try_from(hdr.length).map_err(|_| Error::new("PktHdr too big", GwErrorKind::Decode))?; + if cur.len() != header_length - hdr.size() { return Err(Error::new("read_packet", GwErrorKind::PacketEof)); } @@ -315,7 +319,7 @@ impl GwConn { async fn tunnel(&mut self) -> Result<(), Error> { let req = TunnelReqPkt { // Havent seen any server working without this. - caps: HttpCapsTy::MessagingConsentSign as u32, + caps: HttpCapsTy::MessagingConsentSign.as_u32(), fields_present: 0, ..TunnelReqPkt::default() }; diff --git a/crates/ironrdp-mstsgu/src/proto.rs b/crates/ironrdp-mstsgu/src/proto.rs index 494c7effb..e9de79b25 100644 --- a/crates/ironrdp-mstsgu/src/proto.rs +++ b/crates/ironrdp-mstsgu/src/proto.rs @@ -37,6 +37,16 @@ pub(crate) enum PktTy { Keepalive = 0x0D, } +impl PktTy { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] + fn as_u16(self) -> u16 { + self as u16 + } +} + impl TryFrom for PktTy { type Error = (); @@ -78,7 +88,7 @@ impl Encode for PktHdr { fn encode(&self, dst: &mut WriteCursor<'_>) -> ironrdp_core::EncodeResult<()> { ensure_size!(in: dst, size: self.size()); - dst.write_u16(self.ty as u16); + dst.write_u16(self.ty.as_u16()); dst.write_u16(self._reserved); dst.write_u32(self.length); @@ -215,6 +225,7 @@ impl Encode for TunnelReqPkt { /// 2.2.5.3.9 HTTP_CAPABILITY_TYPE Enumeration #[repr(u32)] #[expect(dead_code)] +#[derive(Copy, Clone)] pub(crate) enum HttpCapsTy { QuarSOH = 1, IdleTimeout = 2, @@ -224,8 +235,19 @@ pub(crate) enum HttpCapsTy { UdpTransport = 0x20, } +impl HttpCapsTy { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] + pub(crate) fn as_u32(self) -> u32 { + self as u32 + } +} + /// 2.2.5.3.8 HTTP_TUNNEL_RESPONSE_FIELDS_PRESENT_FLAGS #[repr(u16)] +#[derive(Copy, Clone)] enum HttpTunnelResponseFields { TunnelID = 1, Caps = 2, @@ -234,6 +256,16 @@ enum HttpTunnelResponseFields { Consent = 0x10, } +impl HttpTunnelResponseFields { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] + fn as_u16(self) -> u16 { + self as u16 + } +} + /// 2.2.10.20 HTTP_TUNNEL_RESPONSE Structure #[derive(Debug, Default)] pub(crate) struct TunnelRespPkt { @@ -266,26 +298,26 @@ impl Decode<'_> for TunnelRespPkt { ..TunnelRespPkt::default() }; - if pkt.fields_present & (HttpTunnelResponseFields::TunnelID as u16) != 0 { + if pkt.fields_present & (HttpTunnelResponseFields::TunnelID.as_u16()) != 0 { ensure_size!(in: src, size: 4); pkt.tunnel_id = Some(src.read_u32()); } - if pkt.fields_present & (HttpTunnelResponseFields::Caps as u16) != 0 { + if pkt.fields_present & (HttpTunnelResponseFields::Caps.as_u16()) != 0 { ensure_size!(in: src, size: 4); pkt.caps_flags = Some(src.read_u32()); } - if pkt.fields_present & (HttpTunnelResponseFields::Soh as u16) != 0 { + if pkt.fields_present & (HttpTunnelResponseFields::Soh.as_u16()) != 0 { ensure_size!(in: src, size: 2 + 2); pkt.nonce = Some(src.read_u16()); - let len = src.read_u16(); - ensure_size!(in: src, size: len as usize); - pkt.server_cert = src.read_slice(len as usize).to_vec(); + let len = usize::from(src.read_u16()); + ensure_size!(in: src, size: len); + pkt.server_cert = src.read_slice(len).to_vec(); } - if pkt.fields_present & (HttpTunnelResponseFields::Consent as u16) != 0 { + if pkt.fields_present & (HttpTunnelResponseFields::Consent.as_u16()) != 0 { ensure_size!(in: src, size: 2); - let len = src.read_u16(); - ensure_size!(in: src, size: len as usize); - pkt.consent_msg = src.read_slice(len as usize).to_vec(); + let len = usize::from(src.read_u16()); + ensure_size!(in: src, size: len); + pkt.consent_msg = src.read_slice(len).to_vec(); } Ok(pkt) @@ -330,12 +362,12 @@ impl Decode<'_> for ExtendedAuthPkt { fn decode(src: &mut ReadCursor<'_>) -> ironrdp_core::DecodeResult { ensure_size!(in: src, size: 4 + 2); let error_code = src.read_u32(); - let len = src.read_u16(); - ensure_size!(in: src, size: len as usize); + let len = usize::from(src.read_u16()); + ensure_size!(in: src, size: len); Ok(ExtendedAuthPkt { error_code, - blob: src.read_slice(len as usize).to_vec(), + blob: src.read_slice(len).to_vec(), }) } } @@ -489,9 +521,9 @@ impl Decode<'_> for ChannelResp { } if resp.fields_present & 4 != 0 { ensure_size!(in: src, size: 2); - let len = src.read_u16(); - ensure_size!(in: src, size: len as usize); - resp.authn_cookie = src.read_slice(len as usize).to_vec(); + let len = usize::from(src.read_u16()); + ensure_size!(in: src, size: len); + resp.authn_cookie = src.read_slice(len).to_vec(); } Ok(resp) } @@ -530,10 +562,10 @@ impl Encode for DataPkt<'_> { impl<'a> Decode<'a> for DataPkt<'a> { fn decode(src: &mut ReadCursor<'a>) -> ironrdp_core::DecodeResult { ensure_size!(in: src, size: 2); - let len = src.read_u16(); - ensure_size!(in: src, size: len as usize); + let len = usize::from(src.read_u16()); + ensure_size!(in: src, size: len); Ok(DataPkt { - data: src.read_slice(len as usize), + data: src.read_slice(len), }) } } diff --git a/crates/ironrdp-pdu/src/gcc/monitor_extended_data.rs b/crates/ironrdp-pdu/src/gcc/monitor_extended_data.rs index f7fc7f9ee..334c327c3 100644 --- a/crates/ironrdp-pdu/src/gcc/monitor_extended_data.rs +++ b/crates/ironrdp-pdu/src/gcc/monitor_extended_data.rs @@ -95,7 +95,7 @@ impl Encode for ExtendedMonitorInfo { dst.write_u32(self.physical_width); dst.write_u32(self.physical_height); - dst.write_u32(self.orientation.as_u32()); + dst.write_u32(u32::from(self.orientation.as_u16())); dst.write_u32(self.desktop_scale_factor); dst.write_u32(self.device_scale_factor); @@ -132,7 +132,7 @@ impl<'de> Decode<'de> for ExtendedMonitorInfo { } } -#[repr(u32)] +#[repr(u16)] #[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] pub enum MonitorOrientation { Landscape = 0, @@ -146,7 +146,7 @@ impl MonitorOrientation { clippy::as_conversions, reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" )] - fn as_u32(self) -> u32 { - self as u32 + pub fn as_u16(self) -> u16 { + self as u16 } } diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs index 66a9f8c01..f588bc426 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs @@ -637,6 +637,10 @@ pub enum EntropyBits { } impl EntropyBits { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn as_u8(self) -> u8 { self as u8 } diff --git a/crates/ironrdp-pdu/src/rdp/server_error_info.rs b/crates/ironrdp-pdu/src/rdp/server_error_info.rs index 01b74beea..28a515215 100644 --- a/crates/ironrdp-pdu/src/rdp/server_error_info.rs +++ b/crates/ironrdp-pdu/src/rdp/server_error_info.rs @@ -412,6 +412,10 @@ impl RdpSpecificCode { } } + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn as_u32(self) -> u32 { self as u32 } diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/server.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/server.rs index 3317895c4..4684d0a9b 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/server.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/server.rs @@ -976,6 +976,10 @@ pub enum PixelFormat { } impl PixelFormat { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn as_u8(self) -> u8 { self as u8 } diff --git a/crates/ironrdp-rdpdr/src/lib.rs b/crates/ironrdp-rdpdr/src/lib.rs index 48062bf68..e5e1191fa 100644 --- a/crates/ironrdp-rdpdr/src/lib.rs +++ b/crates/ironrdp-rdpdr/src/lib.rs @@ -1,10 +1,6 @@ #![cfg_attr(doc, doc = include_str!("../README.md"))] #![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")] #![allow(clippy::arithmetic_side_effects)] // FIXME: remove -#![allow(clippy::cast_lossless)] // FIXME: remove -#![allow(clippy::cast_possible_truncation)] // FIXME: remove -#![allow(clippy::cast_possible_wrap)] // FIXME: remove -#![allow(clippy::cast_sign_loss)] // FIXME: remove use ironrdp_core::{decode_cursor, impl_as_any, ReadCursor}; use ironrdp_pdu::gcc::ChannelName; diff --git a/crates/ironrdp-rdpdr/src/pdu/efs.rs b/crates/ironrdp-rdpdr/src/pdu/efs.rs index 37633d4e0..5c669f29b 100644 --- a/crates/ironrdp-rdpdr/src/pdu/efs.rs +++ b/crates/ironrdp-rdpdr/src/pdu/efs.rs @@ -154,9 +154,15 @@ impl ClientNameRequest { pub fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { ensure_size!(in: dst, size: self.size()); + + let encoded_computer_name_length = cast_length!( + "encoded computer name length", + encoded_str_len(self.computer_name(), self.unicode_flag().into(), true) + )?; + dst.write_u32(self.unicode_flag().into()); dst.write_u32(0); // // CodePage (4 bytes): it MUST be set to 0 - dst.write_u32(encoded_str_len(self.computer_name(), self.unicode_flag().into(), true) as u32); + dst.write_u32(encoded_computer_name_length); write_string_to_cursor(dst, self.computer_name(), self.unicode_flag().into(), true) } @@ -186,6 +192,10 @@ impl From for CharacterSet { } impl From for u32 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(val: ClientNameRequestUnicodeFlag) -> Self { val as u32 } @@ -431,7 +441,7 @@ impl CapabilityHeader { fn new_general() -> Self { Self { cap_type: CapabilityType::General, - length: (Self::SIZE + GeneralCapabilitySet::SIZE) as u16, + length: u16::try_from(Self::SIZE + GeneralCapabilitySet::SIZE).expect("value fits into u16"), version: GENERAL_CAPABILITY_VERSION_02, } } @@ -439,7 +449,7 @@ impl CapabilityHeader { fn new_smartcard() -> Self { Self { cap_type: CapabilityType::Smartcard, - length: Self::SIZE as u16, + length: u16::try_from(Self::SIZE).expect("value fits into u16"), version: SMARTCARD_CAPABILITY_VERSION_01, } } @@ -447,7 +457,7 @@ impl CapabilityHeader { fn new_drive() -> Self { Self { cap_type: CapabilityType::Drive, - length: Self::SIZE as u16, + length: u16::try_from(Self::SIZE).expect("value fits into u16"), version: DRIVE_CAPABILITY_VERSION_02, } } @@ -490,6 +500,10 @@ enum CapabilityType { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(cap_type: CapabilityType) -> Self { cap_type as u16 } @@ -990,6 +1004,10 @@ pub enum DeviceType { } impl From for u32 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(device_type: DeviceType) -> Self { device_type as u32 } @@ -1211,6 +1229,10 @@ impl TryFrom for MajorFunction { } impl From for u32 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(major_function: MajorFunction) -> Self { major_function as u32 } @@ -1253,12 +1275,6 @@ impl From for u32 { } } -impl From for u8 { - fn from(minor_function: MinorFunction) -> Self { - minor_function.0 as u8 - } -} - /// [2.2.1.4.5] Device Control Request (DR_CONTROL_REQ) /// /// [2.2.1.4.5]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/30662c80-ec6e-4ed1-9004-2e6e367bb59f diff --git a/crates/ironrdp-rdpdr/src/pdu/esc/mod.rs b/crates/ironrdp-rdpdr/src/pdu/esc/mod.rs index b7deefde6..1b495a7ba 100644 --- a/crates/ironrdp-rdpdr/src/pdu/esc/mod.rs +++ b/crates/ironrdp-rdpdr/src/pdu/esc/mod.rs @@ -574,6 +574,10 @@ impl ReturnCode { } impl From for u32 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(val: ReturnCode) -> Self { val as u32 } @@ -1244,7 +1248,7 @@ impl rpce::HeaderlessDecode for TransmitCall { #[derive(Debug, PartialEq, Clone)] pub struct SCardIORequest { pub protocol: CardProtocol, - pub extra_bytes_length: u32, + pub extra_bytes_length: usize, pub extra_bytes: Vec, } @@ -1255,7 +1259,7 @@ impl ndr::Decode for SCardIORequest { { ensure_size!(in: src, size: size_of::() * 2); let protocol = CardProtocol::from_bits_retain(src.read_u32()); - let extra_bytes_length = src.read_u32(); + let extra_bytes_length = cast_length!("SCardIORequest", "extra_bytes_length", src.read_u32())?; let _extra_bytes_ptr = ndr::decode_ptr(src, index)?; let extra_bytes = Vec::new(); Ok(Self { @@ -1267,9 +1271,8 @@ impl ndr::Decode for SCardIORequest { fn decode_value(&mut self, src: &mut ReadCursor<'_>, charset: Option) -> DecodeResult<()> { expect_no_charset(charset)?; - let extra_bytes_length: usize = cast_length!("TransmitCall", "extra_bytes_length", self.extra_bytes_length)?; - ensure_size!(in: src, size: extra_bytes_length); - self.extra_bytes = src.read_slice(extra_bytes_length).to_vec(); + ensure_size!(in: src, size: self.extra_bytes_length); + self.extra_bytes = src.read_slice(self.extra_bytes_length).to_vec(); Ok(()) } } @@ -1277,8 +1280,11 @@ impl ndr::Decode for SCardIORequest { impl ndr::Encode for SCardIORequest { fn encode_ptr(&self, index: &mut u32, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { ensure_size!(in: dst, size: self.size_ptr()); + + let extra_bytes_length = cast_length!("SCardIORequest", "extra_bytes_length", self.extra_bytes_length)?; + dst.write_u32(self.protocol.bits()); - ndr::encode_ptr(Some(self.extra_bytes_length), index, dst) + ndr::encode_ptr(Some(extra_bytes_length), index, dst) } fn encode_value(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { @@ -1292,7 +1298,7 @@ impl ndr::Encode for SCardIORequest { } fn size_value(&self) -> usize { - self.extra_bytes_length as usize + self.extra_bytes_length } } @@ -1485,6 +1491,10 @@ pub enum CardState { } impl From for u32 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(val: CardState) -> Self { val as u32 } diff --git a/crates/ironrdp-rdpdr/src/pdu/esc/rpce.rs b/crates/ironrdp-rdpdr/src/pdu/esc/rpce.rs index 31c97bae8..75f6a90f6 100644 --- a/crates/ironrdp-rdpdr/src/pdu/esc/rpce.rs +++ b/crates/ironrdp-rdpdr/src/pdu/esc/rpce.rs @@ -244,6 +244,10 @@ impl TryFrom for Endianness { } impl From for u8 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(endianness: Endianness) -> Self { endianness as u8 } diff --git a/crates/ironrdp-rdpdr/src/pdu/mod.rs b/crates/ironrdp-rdpdr/src/pdu/mod.rs index fa96cba0b..338488829 100644 --- a/crates/ironrdp-rdpdr/src/pdu/mod.rs +++ b/crates/ironrdp-rdpdr/src/pdu/mod.rs @@ -374,6 +374,10 @@ impl TryFrom for Component { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(component: Component) -> Self { component as u16 } @@ -454,6 +458,10 @@ impl Display for PacketId { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(packet_id: PacketId) -> Self { packet_id as u16 } diff --git a/crates/ironrdp-rdpsnd-native/src/cpal.rs b/crates/ironrdp-rdpsnd-native/src/cpal.rs index bd5bf96e5..3dce957fa 100644 --- a/crates/ironrdp-rdpsnd-native/src/cpal.rs +++ b/crates/ironrdp-rdpsnd-native/src/cpal.rs @@ -160,6 +160,10 @@ impl DecodeStream { } }; + #[expect( + clippy::as_conversions, + reason = "opus::Channels has no conversions to usize implemented" + )] let mut pcm = vec![0u8; nb_samples * chan as usize * size_of::()]; if let Err(error) = dec.decode(&pkt, bytemuck::cast_slice_mut(pcm.as_mut_slice()), false) { error!(?error, "Failed to decode an Opus packet"); diff --git a/crates/ironrdp-rdpsnd/src/client.rs b/crates/ironrdp-rdpsnd/src/client.rs index b90e7dc93..0cc8c6ed3 100644 --- a/crates/ironrdp-rdpsnd/src/client.rs +++ b/crates/ironrdp-rdpsnd/src/client.rs @@ -80,7 +80,7 @@ impl Rdpsnd { server_format .formats - .get(format_no as usize) + .get(usize::from(format_no)) .ok_or_else(|| pdu_other_err!("invalid format")) } @@ -196,7 +196,7 @@ impl SvcProcessor for Rdpsnd { match pdu { // TODO: handle WaveInfo for < v8 pdu::ServerAudioOutputPdu::Wave2(pdu) => { - let format_no = pdu.format_no as usize; + let format_no = usize::from(pdu.format_no); let ts = pdu.audio_timestamp; self.handler.wave(format_no, ts, pdu.data); return Ok(self.wave_confirm(pdu.timestamp, pdu.block_no)?.into()); diff --git a/crates/ironrdp-rdpsnd/src/pdu/mod.rs b/crates/ironrdp-rdpsnd/src/pdu/mod.rs index eb3b45e12..258b239a0 100644 --- a/crates/ironrdp-rdpsnd/src/pdu/mod.rs +++ b/crates/ironrdp-rdpsnd/src/pdu/mod.rs @@ -51,6 +51,10 @@ impl TryFrom for Version { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(version: Version) -> Self { version as u16 } @@ -442,9 +446,8 @@ impl<'de> Decode<'de> for ClientAudioFormatPdu { ensure_fixed_part_size!(in: src); let flags = AudioFormatFlags::from_bits_truncate(src.read_u32()); - let volume = src.read_u32(); - let volume_left = (volume & 0xFFFF) as u16; - let volume_right = (volume >> 16) as u16; + let volume_left = src.read_u16(); + let volume_right = src.read_u16(); let pitch = src.read_u32(); let dgram_port = src.read_u16_be(); let n_formats = usize::from(src.read_u16()); @@ -489,6 +492,10 @@ impl TryFrom for QualityMode { } impl From for u16 { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] fn from(mode: QualityMode) -> Self { mode as u16 } @@ -626,7 +633,7 @@ impl<'de> Decode<'de> for TrainingPdu { ensure_fixed_part_size!(in: src); let timestamp = src.read_u16(); - let len = src.read_u16() as usize; + let len = usize::from(src.read_u16()); let data = if len != 0 { if len < Self::FIXED_PART_SIZE + ServerAudioOutputPdu::FIXED_PART_SIZE { return Err(invalid_field_err!("TrainingPdu::wPackSize", "too small")); @@ -839,7 +846,7 @@ impl Encode for WavePdu<'_> { impl WavePdu<'_> { fn decode(src: &mut ReadCursor<'_>, body_size: u16) -> DecodeResult { let info = WaveInfoPdu::decode(src)?; - let body_size = body_size as usize; + let body_size = usize::from(body_size); let data_len = body_size .checked_sub(info.size()) .ok_or_else(|| invalid_field_err!("Length", "WaveInfo body_size is too small"))?; @@ -1090,9 +1097,8 @@ impl<'de> Decode<'de> for VolumePdu { fn decode(src: &mut ReadCursor<'de>) -> DecodeResult { ensure_fixed_part_size!(in: src); - let volume = src.read_u32(); - let volume_left = (volume & 0xFFFF) as u16; - let volume_right = (volume >> 16) as u16; + let volume_left = src.read_u16(); + let volume_right = src.read_u16(); Ok(Self { volume_left, diff --git a/crates/ironrdp-server/src/encoder/bitmap.rs b/crates/ironrdp-server/src/encoder/bitmap.rs index e7a78c7b4..8810e4336 100644 --- a/crates/ironrdp-server/src/encoder/bitmap.rs +++ b/crates/ironrdp-server/src/encoder/bitmap.rs @@ -19,7 +19,7 @@ pub(crate) struct BitmapEncoder { impl BitmapEncoder { pub(crate) fn new() -> Self { Self { - buffer: vec![0; u16::MAX as usize], + buffer: vec![0; usize::from(u16::MAX)], } } diff --git a/crates/ironrdp-server/src/encoder/mod.rs b/crates/ironrdp-server/src/encoder/mod.rs index 1e6950da2..6355c2326 100644 --- a/crates/ironrdp-server/src/encoder/mod.rs +++ b/crates/ironrdp-server/src/encoder/mod.rs @@ -31,6 +31,16 @@ enum CodecId { None = 0x0, } +impl CodecId { + #[expect( + clippy::as_conversions, + reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive" + )] + fn as_u8(self) -> u8 { + self as u8 + } +} + #[cfg_attr(feature = "__bench", visibility::make(pub))] #[derive(Debug)] pub(crate) struct UpdateEncoderCodecs { @@ -389,7 +399,7 @@ impl BitmapUpdateHandler for NoneHandler { for row in bitmap.data.chunks(bitmap.stride.get()).rev() { data.extend_from_slice(&row[..stride]); } - set_surface(bitmap, CodecId::None as u8, &data) + set_surface(bitmap, CodecId::None.as_u8(), &data) } } diff --git a/crates/ironrdp-server/src/handler.rs b/crates/ironrdp-server/src/handler.rs index a1497167d..7bc105234 100644 --- a/crates/ironrdp-server/src/handler.rs +++ b/crates/ironrdp-server/src/handler.rs @@ -97,6 +97,7 @@ impl From<(u16, fast_path::KeyboardFlags)> for KeyboardEvent { } impl From<(u16, scan_code::KeyboardFlags)> for KeyboardEvent { + #[expect(clippy::as_conversions)] #[expect(clippy::cast_possible_truncation)] // we are actually truncating the value fn from((key, flags): (u16, scan_code::KeyboardFlags)) -> Self { let extended = flags.contains(scan_code::KeyboardFlags::EXTENDED); @@ -131,6 +132,7 @@ impl From for KeyboardEvent { } impl From for KeyboardEvent { + #[expect(clippy::as_conversions)] #[expect(clippy::cast_possible_truncation)] // we are actually truncating the value fn from(value: SyncToggleFlags) -> Self { KeyboardEvent::Synchronize(SynchronizeFlags::from_bits_truncate(value.bits() as u8)) diff --git a/crates/ironrdp-session/src/image.rs b/crates/ironrdp-session/src/image.rs index 7e27c3f12..68b403c01 100644 --- a/crates/ironrdp-session/src/image.rs +++ b/crates/ironrdp-session/src/image.rs @@ -72,7 +72,6 @@ struct PointerRenderingState { } #[expect(clippy::too_many_arguments)] -#[expect(clippy::cast_lossless)] // FIXME fn copy_cursor_data( from: &[u8], from_pos: (usize, usize), @@ -125,9 +124,12 @@ fn copy_cursor_data( } // Integer alpha blending, source represented as premultiplied alpha color, calculation in floating point - to[to_start + pixel * PIXEL_SIZE] = src_r + (((dest_r as u16) * (255 - src_a) as u16) >> 8) as u8; - to[to_start + pixel * PIXEL_SIZE + 1] = src_g + (((dest_g as u16) * (255 - src_a) as u16) >> 8) as u8; - to[to_start + pixel * PIXEL_SIZE + 2] = src_b + (((dest_b as u16) * (255 - src_a) as u16) >> 8) as u8; + to[to_start + pixel * PIXEL_SIZE] = src_r + + u8::try_from((u16::from(dest_r) * u16::from(255 - src_a)) >> 8).expect("(u16 >> 8) fits into u8"); + to[to_start + pixel * PIXEL_SIZE + 1] = src_g + + u8::try_from((u16::from(dest_g) * u16::from(255 - src_a)) >> 8).expect("(u16 >> 8) fits into u8"); + to[to_start + pixel * PIXEL_SIZE + 2] = src_b + + u8::try_from((u16::from(dest_b) * u16::from(255 - src_a)) >> 8).expect("(u16 >> 8) fits into u8"); // Framebuffer is always opaque, so we can skip alpha channel change } } else { @@ -227,6 +229,13 @@ impl DecodedImage { return Ok(None); } + let pointer_src_rect_width = usize::from(self.pointer_src_rect.width()); + let pointer_src_rect_height = usize::from(self.pointer_src_rect.height()); + let pointer_draw_x = usize::from(self.pointer_draw_x); + let pointer_draw_y = usize::from(self.pointer_draw_y); + let width = usize::from(self.width); + let height = usize::from(self.height); + match &layer { PointerLayer::Background => { if self.pointer_backbuffer.is_empty() { @@ -237,15 +246,12 @@ impl DecodedImage { copy_cursor_data( &self.pointer_backbuffer, (0, 0), - self.pointer_src_rect.width() as usize * 4, + pointer_src_rect_width * 4, &mut self.data, - self.width as usize * 4, - (self.pointer_draw_x as usize, self.pointer_draw_y as usize), - ( - self.pointer_src_rect.width() as usize, - self.pointer_src_rect.height() as usize, - ), - (self.width as usize, self.height as usize), + width * 4, + (pointer_draw_x, pointer_draw_y), + (pointer_src_rect_width, pointer_src_rect_height), + (width, height), false, ); } @@ -254,37 +260,34 @@ impl DecodedImage { let buffer_size = self .pointer_backbuffer .len() - .max(self.pointer_src_rect.width() as usize * self.pointer_src_rect.height() as usize * 4); + .max(pointer_src_rect_width * pointer_src_rect_height * 4); self.pointer_backbuffer.resize(buffer_size, 0); copy_cursor_data( &self.data, - (self.pointer_draw_x as usize, self.pointer_draw_y as usize), - self.width as usize * 4, + (pointer_draw_x, pointer_draw_y), + width * 4, &mut self.pointer_backbuffer, - self.pointer_src_rect.width() as usize * 4, + pointer_src_rect_width * 4, (0, 0), - ( - self.pointer_src_rect.width() as usize, - self.pointer_src_rect.height() as usize, - ), - (self.width as usize, self.height as usize), + (pointer_src_rect_width, pointer_src_rect_height), + (width, height), false, ); // Draw pointer (with compositing) copy_cursor_data( pointer.bitmap_data.as_slice(), - (self.pointer_src_rect.left as usize, self.pointer_src_rect.top as usize), - usize::from(pointer.width) * 4, - &mut self.data, - self.width as usize * 4, - (self.pointer_draw_x as usize, self.pointer_draw_y as usize), ( - self.pointer_src_rect.width() as usize, - self.pointer_src_rect.height() as usize, + usize::from(self.pointer_src_rect.left), + usize::from(self.pointer_src_rect.top), ), - (self.width as usize, self.height as usize), + usize::from(pointer.width) * 4, + &mut self.data, + width * 4, + (pointer_draw_x, pointer_draw_y), + (pointer_src_rect_width, pointer_src_rect_height), + (width, height), true, ); } @@ -312,7 +315,6 @@ impl DecodedImage { } } - #[expect(clippy::cast_possible_wrap)] // FIXME fn recalculate_pointer_geometry(&mut self) { let x = self.pointer_x; let y = self.pointer_y; @@ -322,10 +324,10 @@ impl DecodedImage { _ => return, }; - let left_virtual = x as i16 - pointer.hotspot_x as i16; - let top_virtual = y as i16 - pointer.hotspot_y as i16; - let right_virtual = left_virtual + pointer.width as i16 - 1; - let bottom_virtual = top_virtual + pointer.height as i16 - 1; + let left_virtual = i32::from(x) - i32::from(pointer.hotspot_x); + let top_virtual = i32::from(y) - i32::from(pointer.hotspot_y); + let right_virtual = left_virtual + i32::from(pointer.width) - 1; + let bottom_virtual = top_virtual + i32::from(pointer.height) - 1; let (left, draw_x) = if left_virtual < 0 { // Cut left side if required @@ -342,7 +344,7 @@ impl DecodedImage { }; // Cut right side if required - let right = if right_virtual >= (self.width - 1) as i16 { + let right = if right_virtual >= i32::from(self.width - 1) { if draw_x + 1 >= self.width { // Pointer is completely out of bounds horizontally self.pointer_visible_on_screen = false; @@ -355,7 +357,7 @@ impl DecodedImage { }; // Cut bottom side if required - let bottom = if bottom_virtual >= (self.height - 1) as i16 { + let bottom = if bottom_virtual >= i32::from(self.height - 1) { if (draw_y + 1) >= self.height { // Pointer is completely out of bounds vertically self.pointer_visible_on_screen = false; @@ -539,7 +541,7 @@ impl DecodedImage { const SRC_COLOR_DEPTH: usize = 2; const DST_COLOR_DEPTH: usize = 4; - let image_width = self.width as usize; + let image_width = usize::from(self.width); let rectangle_width = usize::from(update_rectangle.width()); let top = usize::from(update_rectangle.top); let left = usize::from(update_rectangle.left); @@ -586,7 +588,7 @@ impl DecodedImage { const SRC_COLOR_DEPTH: usize = 3; const DST_COLOR_DEPTH: usize = 4; - let image_width = self.width as usize; + let image_width = usize::from(self.width); let top = usize::from(update_rectangle.top); let left = usize::from(update_rectangle.left); @@ -636,7 +638,7 @@ impl DecodedImage { const SRC_COLOR_DEPTH: usize = 4; const DST_COLOR_DEPTH: usize = 4; - let image_width = self.width as usize; + let image_width = usize::from(self.width); let rectangle_width = usize::from(update_rectangle.width()); let top = usize::from(update_rectangle.top); let left = usize::from(update_rectangle.left); diff --git a/crates/ironrdp-session/src/rfx.rs b/crates/ironrdp-session/src/rfx.rs index 7bd21d6d0..4f115a6b5 100644 --- a/crates/ironrdp-session/src/rfx.rs +++ b/crates/ironrdp-session/src/rfx.rs @@ -187,10 +187,11 @@ struct DecodingTileContext { impl DecodingTileContext { fn new() -> Self { + let tile_size = usize::from(TILE_SIZE); Self { - tile_output: vec![0; TILE_SIZE as usize * TILE_SIZE as usize * 4], - ycbcr_buffer: vec![vec![0; TILE_SIZE as usize * TILE_SIZE as usize]; 3], - ycbcr_temp_buffer: vec![0; TILE_SIZE as usize * TILE_SIZE as usize], + tile_output: vec![0; tile_size * tile_size * 4], + ycbcr_buffer: vec![vec![0; tile_size * tile_size]; 3], + ycbcr_temp_buffer: vec![0; tile_size * tile_size], } } } diff --git a/crates/ironrdp-testsuite-core/src/gcc.rs b/crates/ironrdp-testsuite-core/src/gcc.rs index 6c9a0466b..00ff5bc31 100644 --- a/crates/ironrdp-testsuite-core/src/gcc.rs +++ b/crates/ironrdp-testsuite-core/src/gcc.rs @@ -41,6 +41,7 @@ const fn make_gcc_block_buffer(data_type: u16, buffer: &[u8]) -> let array = copy_slice(&data_type.to_le_bytes(), [0; N], 0); + #[expect(clippy::as_conversions, reason = "must be const casts")] let length = (buffer.len() + USER_HEADER_LEN) as u16; let array = copy_slice(&length.to_le_bytes(), array, 2); @@ -162,6 +163,7 @@ lazy_static! { }; } +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_CORE_BLOCK_BUFFER: [u8; gcc_block_size( CLIENT_OPTIONAL_CORE_DATA_TO_SERVER_SELECTED_PROTOCOL_BUFFER, )] = make_gcc_block_buffer( @@ -169,18 +171,22 @@ pub const CLIENT_GCC_CORE_BLOCK_BUFFER: [u8; gcc_block_size( &CLIENT_OPTIONAL_CORE_DATA_TO_SERVER_SELECTED_PROTOCOL_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_SECURITY_BLOCK_BUFFER: [u8; gcc_block_size(CLIENT_SECURITY_DATA_BUFFER)] = make_gcc_block_buffer(ClientGccType::SecurityData as u16, &CLIENT_SECURITY_DATA_BUFFER); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_NETWORK_BLOCK_BUFFER: [u8; gcc_block_size(CLIENT_NETWORK_DATA_WITH_CHANNELS_BUFFER)] = make_gcc_block_buffer( ClientGccType::NetworkData as u16, &CLIENT_NETWORK_DATA_WITH_CHANNELS_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_CLUSTER_BLOCK_BUFFER: [u8; gcc_block_size(CLUSTER_DATA_BUFFER)] = make_gcc_block_buffer(ClientGccType::ClusterData as u16, &CLUSTER_DATA_BUFFER); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_MONITOR_BLOCK_BUFFER: [u8; gcc_block_size( crate::monitor_data::MONITOR_DATA_WITH_MONITORS_BUFFER, )] = make_gcc_block_buffer( @@ -188,6 +194,7 @@ pub const CLIENT_GCC_MONITOR_BLOCK_BUFFER: [u8; gcc_block_size( &crate::monitor_data::MONITOR_DATA_WITH_MONITORS_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const CLIENT_GCC_MONITOR_EXTENDED_BLOCK_BUFFER: [u8; gcc_block_size( crate::monitor_extended_data::MONITOR_DATA_WITH_MONITORS_BUFFER, )] = make_gcc_block_buffer( @@ -195,24 +202,28 @@ pub const CLIENT_GCC_MONITOR_EXTENDED_BLOCK_BUFFER: [u8; gcc_block_size( &crate::monitor_extended_data::MONITOR_DATA_WITH_MONITORS_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_GCC_CORE_BLOCK_BUFFER: [u8; gcc_block_size(SERVER_CORE_DATA_TO_REQUESTED_PROTOCOL_BUFFER)] = make_gcc_block_buffer( ServerGccType::CoreData as u16, &SERVER_CORE_DATA_TO_REQUESTED_PROTOCOL_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_GCC_NETWORK_BLOCK_BUFFER: [u8; gcc_block_size(SERVER_NETWORK_DATA_WITH_CHANNELS_ID_BUFFER)] = make_gcc_block_buffer( ServerGccType::NetworkData as u16, &SERVER_NETWORK_DATA_WITH_CHANNELS_ID_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_GCC_SECURITY_BLOCK_BUFFER: [u8; gcc_block_size(SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_BUFFER)] = make_gcc_block_buffer( ServerGccType::SecurityData as u16, &SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_GCC_MESSAGE_CHANNEL_BLOCK_BUFFER: [u8; gcc_block_size( crate::message_channel_data::SERVER_GCC_MESSAGE_CHANNEL_BLOCK_BUFFER, )] = make_gcc_block_buffer( @@ -220,6 +231,7 @@ pub const SERVER_GCC_MESSAGE_CHANNEL_BLOCK_BUFFER: [u8; gcc_block_size( &crate::message_channel_data::SERVER_GCC_MESSAGE_CHANNEL_BLOCK_BUFFER, ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_GCC_MULTI_TRANSPORT_CHANNEL_BLOCK_BUFFER: [u8; gcc_block_size( crate::multi_transport_channel_data::SERVER_GCC_MULTI_TRANSPORT_CHANNEL_BLOCK_BUFFER, )] = make_gcc_block_buffer( diff --git a/crates/ironrdp-testsuite-core/src/rdp.rs b/crates/ironrdp-testsuite-core/src/rdp.rs index 488b2c90e..c1e96d01a 100644 --- a/crates/ironrdp-testsuite-core/src/rdp.rs +++ b/crates/ironrdp-testsuite-core/src/rdp.rs @@ -180,7 +180,7 @@ lazy_static! { state_transition: LicensingStateTransition::NoTransition, error_info: Vec::new(), }; - pdu.license_header.preamble_message_size = pdu.size() as u16; + pdu.license_header.preamble_message_size = u16::try_from(pdu.size()).unwrap(); pdu.into() }; pub static ref SERVER_DEMAND_ACTIVE_PDU: ShareControlHeader = ShareControlHeader { diff --git a/crates/ironrdp-testsuite-core/src/security_data.rs b/crates/ironrdp-testsuite-core/src/security_data.rs index 96a1a2f40..a3eda98c7 100644 --- a/crates/ironrdp-testsuite-core/src/security_data.rs +++ b/crates/ironrdp-testsuite-core/src/security_data.rs @@ -64,6 +64,7 @@ lazy_static! { }; } +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_BUFFER: [u8; 232] = concat_arrays!( SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_PREFIX_BUFFER, (SERVER_RANDOM_BUFFER.len() as u32).to_le_bytes(), @@ -72,6 +73,7 @@ pub const SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_BUFFER: [u8; 232] = concat_a SERVER_CERT_BUFFER ); +#[expect(clippy::as_conversions, reason = "must be const casts")] pub const SERVER_SECURITY_DATA_WITH_INVALID_SERVER_RANDOM_BUFFER: [u8; 233] = concat_arrays!( SERVER_SECURITY_DATA_WITH_OPTIONAL_FIELDS_PREFIX_BUFFER, (SERVER_RANDOM_BUFFER.len() as u32 + 1).to_le_bytes(), diff --git a/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs index 81c7605cb..aee11e2bf 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs @@ -9,6 +9,7 @@ const DATA: [u8; 12] = [0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x const EDGE_CASE_LENGTH: u32 = 0x639; const EDGE_CASE_CHANNEL_ID: u32 = 0x07; const EDGE_CASE_PREFIX: [u8; 4] = [0x24, 0x7, 0x39, 0x6]; +#[expect(clippy::as_conversions)] const EDGE_CASE_DATA: [u8; EDGE_CASE_LENGTH as usize] = [ 0xe0, 0x24, 0xa9, 0xba, 0xe0, 0x68, 0xa9, 0xba, 0x8a, 0x73, 0x41, 0x25, 0x12, 0x12, 0x1c, 0x28, 0x3b, 0xa6, 0x34, 0x8, 0x8, 0x7a, 0x38, 0x34, 0x2c, 0xe8, 0xf8, 0xd0, 0xef, 0x18, 0xc2, 0xc, 0x27, 0x1f, 0xb1, 0x83, 0x3c, 0x58, diff --git a/crates/ironrdp-tokio/src/reqwest.rs b/crates/ironrdp-tokio/src/reqwest.rs index 68060c275..b196c2aa6 100644 --- a/crates/ironrdp-tokio/src/reqwest.rs +++ b/crates/ironrdp-tokio/src/reqwest.rs @@ -2,7 +2,7 @@ use core::future::Future; use core::net::{IpAddr, Ipv4Addr}; use core::pin::Pin; -use ironrdp_connector::{custom_err, ConnectorResult}; +use ironrdp_connector::{custom_err, general_err, ConnectorResult}; use reqwest::Client; use sspi::{Error, ErrorKind}; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; @@ -62,7 +62,10 @@ impl ReqwestNetworkClient { .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{e:?}"))) .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; - let mut buf = vec![0; len as usize + 4]; + let len = usize::try_from(len) + .map_err(|_| general_err!("invalid buffer length: out of range integral type conversion"))?; + + let mut buf = vec![0; len + 4]; buf[0..4].copy_from_slice(&(len.to_be_bytes())); stream diff --git a/crates/ironrdp-web/src/canvas.rs b/crates/ironrdp-web/src/canvas.rs index fcb7464ea..96b9df50d 100644 --- a/crates/ironrdp-web/src/canvas.rs +++ b/crates/ironrdp-web/src/canvas.rs @@ -1,5 +1,6 @@ use core::num::NonZeroU32; +use anyhow::Context as _; use ironrdp::pdu::geometry::{InclusiveRectangle, Rectangle as _}; use softbuffer::{NoDisplayHandle, NoWindowHandle}; use web_sys::HtmlCanvasElement; @@ -61,7 +62,7 @@ impl Canvas { let region_width_usize = usize::from(region_width); for dst_row in dst - .chunks_exact_mut(self.width.get() as usize) + .chunks_exact_mut(usize::try_from(self.width.get()).context("canvas width")?) .skip(region_top_usize) .take(region_height_usize) { diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index a3e189a7e..0a190021b 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -69,7 +69,7 @@ struct SessionBuilderInner { use_display_control: bool, enable_credssp: bool, - outbound_message_size_limit: Option, + outbound_message_size_limit: Option, } impl Default for SessionBuilderInner { @@ -216,8 +216,8 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder { |enable_credssp: bool| { self.0.borrow_mut().enable_credssp = enable_credssp }; |outbound_message_size_limit: f64| { let limit = if outbound_message_size_limit >= 0.0 && outbound_message_size_limit <= f64::from(u32::MAX) { - #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - { outbound_message_size_limit as u32 } + #[expect(clippy::as_conversions, clippy::cast_possible_truncation, clippy::cast_sign_loss)] + { outbound_message_size_limit as usize } } else { warn!(outbound_message_size_limit, "Invalid outbound message size limit; fallback to unlimited"); 0 // Fallback to no limit for invalid values. @@ -899,20 +899,20 @@ fn build_config( async fn writer_task( rx: mpsc::UnboundedReceiver>, rdp_writer: WriteHalf, - outbound_limit: Option, + outbound_limit: Option, ) { debug!("writer task started"); async fn inner( mut rx: mpsc::UnboundedReceiver>, mut rdp_writer: WriteHalf, - outbound_limit: Option, + outbound_limit: Option, ) -> anyhow::Result<()> { while let Some(frame) = rx.next().await { match outbound_limit { - Some(max_size) if frame.len() > max_size as usize => { + Some(max_size) if frame.len() > max_size => { // Send in chunks. - for chunk in frame.chunks(max_size as usize) { + for chunk in frame.chunks(max_size) { rdp_writer.write_all(chunk).await.context("couldn't write chunk")?; rdp_writer.flush().await.context("couldn't flush chunk")?; } @@ -1153,6 +1153,7 @@ where } } +#[expect(clippy::as_conversions)] #[expect(clippy::cast_sign_loss)] #[expect(clippy::cast_possible_truncation)] fn f64_to_u16_saturating_cast(value: f64) -> u16 { diff --git a/crates/ironrdp/examples/server.rs b/crates/ironrdp/examples/server.rs index 35e8175de..870bd29db 100644 --- a/crates/ironrdp/examples/server.rs +++ b/crates/ironrdp/examples/server.rs @@ -327,7 +327,13 @@ impl RdpsndServerHandler for SndHandler { let mut phase = 0.0f32; loop { interval.tick().await; - let wave = generate_sine_wave(fmt.n_samples_per_sec, 440.0, 20, &mut phase); + let wave = match generate_sine_wave(fmt.n_samples_per_sec, 440.0, 20, &mut phase) { + Ok(wave) => wave, + Err(err) => { + warn!("Failed to generate sine wave: {err}"); + return; + } + }; let data = if let Some(ref mut enc) = opus_enc { match enc.encode_vec(&wave, wave.len()) { @@ -360,14 +366,15 @@ impl RdpsndServerHandler for SndHandler { } } -fn generate_sine_wave(sample_rate: u32, frequency: f32, duration_ms: u64, phase: &mut f32) -> Vec { +fn generate_sine_wave(sample_rate: u32, frequency: f32, duration_ms: u64, phase: &mut f32) -> anyhow::Result> { use core::f32::consts::PI; let total_samples = (u64::from(sample_rate) * duration_ms) / 1000; + #[expect(clippy::as_conversions)] let delta_phase = 2.0 * PI * frequency / sample_rate as f32; let amplitude = 32767.0; // Max amplitude for 16-bit audio - let capacity = (total_samples as usize) * 2; // 2 channels + let capacity = (usize::try_from(total_samples).context("total samples")?) * 2; // 2 channels let mut samples = Vec::with_capacity(capacity); for _ in 0..total_samples { @@ -376,6 +383,7 @@ fn generate_sine_wave(sample_rate: u32, frequency: f32, duration_ms: u64, phase: // Wrap phase to maintain precision and avoid overflow *phase %= 2.0 * PI; + #[expect(clippy::as_conversions)] #[expect(clippy::cast_possible_truncation)] let sample_i16 = (sample * amplitude) as i16; @@ -384,7 +392,7 @@ fn generate_sine_wave(sample_rate: u32, frequency: f32, duration_ms: u64, phase: samples.push(sample_i16); } - samples + Ok(samples) } async fn run( diff --git a/xtask/src/cov.rs b/xtask/src/cov.rs index f522db526..791d6413c 100644 --- a/xtask/src/cov.rs +++ b/xtask/src/cov.rs @@ -329,6 +329,7 @@ fn get_json_float(value: &tinyjson::JsonValue, key: &str) -> anyhow::Result fn get_json_int(value: &tinyjson::JsonValue, key: &str) -> anyhow::Result { // tinyjson does not expose any integers at all, so we need the f64 to u64 as casting + #[expect(clippy::as_conversions)] #[expect(clippy::cast_sign_loss)] #[expect(clippy::cast_possible_truncation)] get_json_float(value, key).map(|value| value as u64) From 7059245e0d554fa6fcb80cafcdd7b8606bfb77b7 Mon Sep 17 00:00:00 2001 From: Alex Yusiuk <55661041+RRRadicalEdward@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:59:52 +0300 Subject: [PATCH 2/3] Update crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs --- crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs index b71aba2d7..a45d68783 100644 --- a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs @@ -284,7 +284,7 @@ fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb { return Rgb { r, g, b }; - // TODO: Use (`cast_signed`)[https://doc.rust-lang.org/std/primitive.u8.html#method.cast_signed] + // TODO: Use [`cast_signed`](https://doc.rust-lang.org/std/primitive.u8.html#method.cast_signed) // once MSRV is 1.87+. #[expect( clippy::as_conversions, From bd17ea35eb49afc6462af1a41b8b354c96b367c3 Mon Sep 17 00:00:00 2001 From: Alexandr Yusuk Date: Fri, 17 Oct 2025 13:01:41 +0300 Subject: [PATCH 3/3] review: fix typo --- crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs index a45d68783..a4e5e1de3 100644 --- a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs @@ -270,8 +270,8 @@ fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb { let clip_i16 = |v: i16| u8::try_from(v.clamp(0, 255)).expect("fits into u8 because the value is clamped to [0..256]"); - let co_signed = cast_singed(co << chroma_shift); - let cg_signed = cast_singed(cg << chroma_shift); + let co_signed = cast_signed(co << chroma_shift); + let cg_signed = cast_signed(cg << chroma_shift); let y = i16::from(y); let co = i16::from(co_signed); @@ -291,7 +291,7 @@ fn ycocg_with_cll_to_rgb(cll: u8, y: u8, co: u8, cg: u8) -> Rgb { clippy::cast_possible_wrap, reason = "there is no other way to do this" )] - fn cast_singed(value: u8) -> i8 { + fn cast_signed(value: u8) -> i8 { value as i8 } }