mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-12-23 12:26:46 +00:00
Merge branch 'master' into ironrdp-web-add-clippy-index-slicing-lint
This commit is contained in:
commit
c0730dc2b9
136 changed files with 1233 additions and 762 deletions
527
Cargo.lock
generated
527
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -94,6 +94,7 @@ ptr_cast_constness = "warn"
|
|||
|
||||
# == Correctness == #
|
||||
#indexing_slicing = "warn" TODO: enable this lint project wide.
|
||||
#as_conversions = "warn"
|
||||
cast_lossless = "warn"
|
||||
cast_possible_truncation = "warn"
|
||||
cast_possible_wrap = "warn"
|
||||
|
|
@ -116,6 +117,7 @@ same_name_method = "warn"
|
|||
string_slice = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
unused_result_ok = "warn"
|
||||
missing_panics_doc = "warn"
|
||||
|
||||
# == Style, readability == #
|
||||
semicolon_outside_block = "warn" # With semicolon-outside-block-ignore-multiline = true
|
||||
|
|
@ -140,8 +142,12 @@ unused_self = "warn"
|
|||
useless_let_if_seq = "warn"
|
||||
string_add = "warn"
|
||||
range_plus_one = "warn"
|
||||
# TODO: self_named_module_files = "warn"
|
||||
self_named_module_files = "warn"
|
||||
# TODO: partial_pub_fields = "warn" (should we enable only in pdu crates?)
|
||||
redundant_type_annotations = "warn"
|
||||
unnecessary_self_imports = "warn"
|
||||
try_err = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
|
||||
# == Compile-time / optimization == #
|
||||
doc_include_without_cfg = "warn"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ qoiz = ["ironrdp/qoiz"]
|
|||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
async-trait = "0.1.89"
|
||||
bytesize = "2.0.1"
|
||||
bytesize = "2.1.0"
|
||||
ironrdp = { path = "../crates/ironrdp", features = [
|
||||
"server",
|
||||
"pdu",
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ impl core::str::FromStr for OptCodec {
|
|||
"qoi" => Ok(Self::Qoi),
|
||||
#[cfg(feature = "qoiz")]
|
||||
"qoiz" => Ok(Self::QoiZ),
|
||||
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
|
||||
_ => anyhow::bail!("unknown codec: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.6.0...iron-remote-desktop-v0.7.0)] - 2025-09-29
|
||||
|
||||
### <!-- 4 -->Bug Fixes
|
||||
|
||||
- [**breaking**] Changed onClipboardChanged to not consume the input (#992) ([6127e13c83](https://github.com/Devolutions/IronRDP/commit/6127e13c836d06764d483b6b55188fd23a4314a2))
|
||||
|
||||
## [[0.6.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.5.0...iron-remote-desktop-v0.6.0)] - 2025-08-29
|
||||
|
||||
### <!-- 1 -->Features
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iron-remote-desktop"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
readme = "README.md"
|
||||
description = "Helper crate for building WASM modules compatible with iron-remote-desktop WebComponent"
|
||||
edition.workspace = true
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ macro_rules! make_bridge {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = onClipboardPaste)]
|
||||
pub async fn on_clipboard_paste(&self, content: ClipboardData) -> Result<(), IronError> {
|
||||
$crate::Session::on_clipboard_paste(&self.0, content.0)
|
||||
pub async fn on_clipboard_paste(&self, content: &ClipboardData) -> Result<(), IronError> {
|
||||
$crate::Session::on_clipboard_paste(&self.0, &content.0)
|
||||
.await
|
||||
.map_err(IronError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ pub trait Session {
|
|||
|
||||
fn on_clipboard_paste(
|
||||
&self,
|
||||
content: Self::ClipboardData,
|
||||
content: &Self::ClipboardData,
|
||||
) -> impl core::future::Future<Output = Result<(), Self::Error>>;
|
||||
|
||||
fn resize(
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ use ironrdp_connector::{
|
|||
reason_err, ConnectorError, ConnectorErrorExt as _, ConnectorResult, Sequence, State, Written,
|
||||
};
|
||||
use ironrdp_core::WriteBuf;
|
||||
use ironrdp_pdu::mcs;
|
||||
use ironrdp_pdu::x224::X224;
|
||||
use ironrdp_pdu::{self as pdu};
|
||||
use pdu::mcs;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -57,13 +56,13 @@ impl State for ChannelConnectionState {
|
|||
}
|
||||
|
||||
impl Sequence for ChannelConnectionSequence {
|
||||
fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> {
|
||||
fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> {
|
||||
match &self.state {
|
||||
ChannelConnectionState::Consumed => None,
|
||||
ChannelConnectionState::WaitErectDomainRequest => Some(&pdu::X224_HINT),
|
||||
ChannelConnectionState::WaitAttachUserRequest => Some(&pdu::X224_HINT),
|
||||
ChannelConnectionState::WaitErectDomainRequest => Some(&ironrdp_pdu::X224_HINT),
|
||||
ChannelConnectionState::WaitAttachUserRequest => Some(&ironrdp_pdu::X224_HINT),
|
||||
ChannelConnectionState::SendAttachUserConfirm => None,
|
||||
ChannelConnectionState::WaitChannelJoinRequest { .. } => Some(&pdu::X224_HINT),
|
||||
ChannelConnectionState::WaitChannelJoinRequest { .. } => Some(&ironrdp_pdu::X224_HINT),
|
||||
ChannelConnectionState::SendChannelJoinConfirm { .. } => None,
|
||||
ChannelConnectionState::AllJoined => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ impl Acceptor {
|
|||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if state is not [AcceptorState::SecurityUpgrade].
|
||||
pub fn mark_security_upgrade_as_done(&mut self) {
|
||||
assert!(self.reached_security_upgrade().is_some());
|
||||
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
|
|
@ -133,6 +136,9 @@ impl Acceptor {
|
|||
matches!(self.state, AcceptorState::Credssp { .. })
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if state is not [AcceptorState::Credssp].
|
||||
pub fn mark_credssp_as_done(&mut self) {
|
||||
assert!(self.should_perform_credssp());
|
||||
let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use ironrdp_connector::{ConnectorError, ConnectorErrorExt as _, ConnectorResult, Sequence, State, Written};
|
||||
use ironrdp_core::WriteBuf;
|
||||
use ironrdp_pdu::rdp;
|
||||
use ironrdp_pdu::x224::X224;
|
||||
use ironrdp_pdu::{self as pdu};
|
||||
use pdu::rdp;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::util::{self, wrap_share_data};
|
||||
|
|
@ -60,13 +59,13 @@ impl State for FinalizationState {
|
|||
}
|
||||
|
||||
impl Sequence for FinalizationSequence {
|
||||
fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> {
|
||||
fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> {
|
||||
match &self.state {
|
||||
FinalizationState::Consumed => None,
|
||||
FinalizationState::WaitSynchronize => Some(&pdu::X224Hint),
|
||||
FinalizationState::WaitControlCooperate => Some(&pdu::X224Hint),
|
||||
FinalizationState::WaitRequestControl => Some(&pdu::X224Hint),
|
||||
FinalizationState::WaitFontList => Some(&pdu::RdpHint),
|
||||
FinalizationState::WaitSynchronize => Some(&ironrdp_pdu::X224Hint),
|
||||
FinalizationState::WaitControlCooperate => Some(&ironrdp_pdu::X224Hint),
|
||||
FinalizationState::WaitRequestControl => Some(&ironrdp_pdu::X224Hint),
|
||||
FinalizationState::WaitFontList => Some(&ironrdp_pdu::RdpHint),
|
||||
FinalizationState::SendSynchronizeConfirm => None,
|
||||
FinalizationState::SendControlCooperateConfirm => None,
|
||||
FinalizationState::SendGrantedControlConfirm => None,
|
||||
|
|
@ -221,7 +220,7 @@ fn create_font_map() -> rdp::headers::ShareDataPdu {
|
|||
}
|
||||
|
||||
fn decode_share_control(input: &[u8]) -> ConnectorResult<rdp::headers::ShareControlHeader> {
|
||||
let data_request = ironrdp_core::decode::<X224<pdu::mcs::SendDataRequest<'_>>>(input)
|
||||
let data_request = ironrdp_core::decode::<X224<ironrdp_pdu::mcs::SendDataRequest<'_>>>(input)
|
||||
.map_err(ConnectorError::decode)
|
||||
.map(|p| p.0)?;
|
||||
let share_control = ironrdp_core::decode::<rdp::headers::ShareControlHeader>(data_request.user_data.as_ref())
|
||||
|
|
@ -230,7 +229,7 @@ fn decode_share_control(input: &[u8]) -> ConnectorResult<rdp::headers::ShareCont
|
|||
}
|
||||
|
||||
fn decode_font_list(input: &[u8]) -> Result<rdp::finalization_messages::FontPdu, ()> {
|
||||
use pdu::rdp::headers::{ShareControlPdu, ShareDataPdu};
|
||||
use ironrdp_pdu::rdp::headers::{ShareControlPdu, ShareDataPdu};
|
||||
|
||||
let share_control = decode_share_control(input).map_err(|_| ())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ where
|
|||
Ok(ShouldUpgrade)
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade].
|
||||
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
|
||||
assert!(connector.should_perform_security_upgrade());
|
||||
ShouldUpgrade
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ where
|
|||
if self.buf.len() >= length {
|
||||
return Ok(self.buf.split_to(length));
|
||||
} else {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer underflow)")]
|
||||
self.buf
|
||||
.reserve(length.checked_sub(self.buf.len()).expect("length > self.buf.len()"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![expect(clippy::missing_panics_doc, reason = "panics in benches are allowed")]
|
||||
|
||||
use core::num::{NonZeroU16, NonZeroUsize};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ where
|
|||
Ok(ShouldUpgrade)
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade].
|
||||
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
|
||||
assert!(connector.should_perform_security_upgrade());
|
||||
ShouldUpgrade
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ where
|
|||
if self.buf.len() >= length {
|
||||
return Ok(self.buf.split_to(length));
|
||||
} else {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked underflow)")]
|
||||
self.buf
|
||||
.reserve(length.checked_sub(self.buf.len()).expect("length > self.buf.len()"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ qoiz = ["ironrdp/qoiz"]
|
|||
|
||||
[dependencies]
|
||||
# Protocols
|
||||
ironrdp = { path = "../ironrdp", version = "0.12", features = [
|
||||
ironrdp = { path = "../ironrdp", version = "0.13", features = [
|
||||
"session",
|
||||
"input",
|
||||
"graphics",
|
||||
|
|
@ -72,7 +72,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|||
# Async, futures
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = { version = "0.7" }
|
||||
tokio-tungstenite = "0.27"
|
||||
tokio-tungstenite = "0.28"
|
||||
transport = { git = "https://github.com/Devolutions/devolutions-gateway", rev = "06e91dfe82751a6502eaf74b6a99663f06f0236d" }
|
||||
futures-util = { version = "0.3", features = ["sink"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ impl WinClipboard {
|
|||
// SAFETY: low-level WinAPI call
|
||||
let atom = unsafe { RegisterClassA(&wc) };
|
||||
if atom == 0 {
|
||||
return Err(Error::from_win32())?;
|
||||
return Err(WinCliprdrError::from(Error::from_win32()));
|
||||
}
|
||||
|
||||
// SAFETY: low-level WinAPI call
|
||||
|
|
@ -184,7 +184,7 @@ impl WinClipboard {
|
|||
};
|
||||
|
||||
if window.is_invalid() {
|
||||
return Err(Error::from_win32())?;
|
||||
return Err(WinCliprdrError::from(Error::from_win32()));
|
||||
}
|
||||
// Init clipboard processing for WinAPI event loop
|
||||
//
|
||||
|
|
@ -94,19 +94,13 @@ impl<'a> FileContentsResponse<'a> {
|
|||
|
||||
/// Read data as u64 size value
|
||||
pub fn data_as_size(&self) -> DecodeResult<u64> {
|
||||
if self.data.len() != 8 {
|
||||
return Err(invalid_field_err!(
|
||||
"requestedFileContentsData",
|
||||
"Invalid data size for u64 size"
|
||||
));
|
||||
}
|
||||
let chunk = self
|
||||
.data
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| invalid_field_err!("requestedFileContentsData", "not enough bytes for u64 size"))?;
|
||||
|
||||
Ok(u64::from_le_bytes(
|
||||
self.data
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("data contains exactly eight u8 elements"),
|
||||
))
|
||||
Ok(u64::from_le_bytes(chunk))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ impl ClientConnector {
|
|||
matches!(self.state, ClientConnectorState::EnhancedSecurityUpgrade { .. })
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if state is not [ClientConnectorState::EnhancedSecurityUpgrade].
|
||||
pub fn mark_security_upgrade_as_done(&mut self) {
|
||||
assert!(self.should_perform_security_upgrade());
|
||||
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
|
|
@ -186,6 +189,9 @@ impl ClientConnector {
|
|||
matches!(self.state, ClientConnectorState::Credssp { .. })
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if state is not [ClientConnectorState::Credssp].
|
||||
pub fn mark_credssp_as_done(&mut self) {
|
||||
assert!(self.should_perform_credssp());
|
||||
let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use core::mem;
|
||||
|
||||
use ironrdp_pdu::rdp;
|
||||
use ironrdp_pdu::rdp::capability_sets::CapabilitySet;
|
||||
use ironrdp_pdu::rdp::{self};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -155,7 +155,7 @@ impl Sequence for ConnectionActivationSequence {
|
|||
});
|
||||
|
||||
let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive(
|
||||
create_client_confirm_active(&self.config, capability_sets, desktop_size)?,
|
||||
create_client_confirm_active(&self.config, capability_sets, desktop_size),
|
||||
);
|
||||
|
||||
debug!(message = ?client_confirm_active, "Send");
|
||||
|
|
@ -263,7 +263,7 @@ fn create_client_confirm_active(
|
|||
config: &Config,
|
||||
mut server_capability_sets: Vec<CapabilitySet>,
|
||||
desktop_size: DesktopSize,
|
||||
) -> ConnectorResult<rdp::capability_sets::ClientConfirmActive> {
|
||||
) -> rdp::capability_sets::ClientConfirmActive {
|
||||
use ironrdp_pdu::rdp::capability_sets::{
|
||||
client_codecs_capabilities, Bitmap, BitmapCache, BitmapDrawingFlags, Brush, CacheDefinition, CacheEntry,
|
||||
ClientConfirmActive, CmdFlags, DemandActive, FrameAcknowledge, General, GeneralExtraFlags, GlyphCache,
|
||||
|
|
@ -386,11 +386,11 @@ fn create_client_confirm_active(
|
|||
}));
|
||||
}
|
||||
|
||||
Ok(ClientConfirmActive {
|
||||
ClientConfirmActive {
|
||||
originator_id: SERVER_CHANNEL_ID,
|
||||
pdu: DemandActive {
|
||||
source_descriptor: "IRONRDP".to_owned(),
|
||||
capability_sets: server_capability_sets,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-pipe-proxy-v0.2.0...ironrdp-dvc-pipe-proxy-v0.2.1)] - 2025-09-24
|
||||
|
||||
### <!-- 4 -->Bug Fixes
|
||||
|
||||
- Change dvc proxy pipe mode from Message to Byte on Windows (#986) ([5f52a44b84](https://github.com/Devolutions/IronRDP/commit/5f52a44b840dd71eae6a355be00f1c4c671b3b58))
|
||||
|
||||
- Add blocking logic for sending dvc pipe messages ([3182a018e2](https://github.com/Devolutions/IronRDP/commit/3182a018e2972eb77c52ea248387c96a9eb6a6a6))
|
||||
|
||||
## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-pipe-proxy-v0.1.0...ironrdp-dvc-pipe-proxy-v0.2.0)] - 2025-08-29
|
||||
|
||||
### <!-- 1 -->Features
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ironrdp-dvc-pipe-proxy"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
readme = "README.md"
|
||||
description = "DVC named pipe proxy for IronRDP"
|
||||
edition.workspace = true
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ pub fn encode_dvc_messages(
|
|||
|
||||
while off < total_length {
|
||||
let first = off == 0;
|
||||
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked underflow)")]
|
||||
let remaining_length = total_length.checked_sub(off).expect("never overflow");
|
||||
let size = core::cmp::min(remaining_length, DrdynvcDataPdu::MAX_DATA_SIZE);
|
||||
let end = off
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [[0.6.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.5.0...ironrdp-graphics-v0.6.0)] - 2025-06-27
|
||||
|
||||
### <!-- 4 -->Bug Fixes
|
||||
|
||||
- `to_64x64_ycbcr_tile` now returns a `Result`
|
||||
|
||||
## [[0.4.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.4.0...ironrdp-graphics-v0.4.1)] - 2025-06-27
|
||||
|
||||
### <!-- 7 -->Build
|
||||
|
|
@ -20,15 +26,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
Add some helper to find "damaged" regions, as 64x64 tiles.
|
||||
|
||||
|
||||
## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.2.0...ironrdp-graphics-v0.3.0)] - 2025-03-12
|
||||
|
||||
### <!-- 7 -->Build
|
||||
|
||||
- Bump ironrdp-pdu
|
||||
|
||||
|
||||
|
||||
## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.1.2...ironrdp-graphics-v0.2.0)] - 2025-03-07
|
||||
|
||||
### Performance
|
||||
|
|
@ -46,8 +49,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
|
||||
|
||||
|
||||
|
||||
## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.1.0...ironrdp-graphics-v0.1.1)] - 2024-12-14
|
||||
|
||||
### Other
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ironrdp-graphics"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
readme = "README.md"
|
||||
description = "RDP image processing primitives"
|
||||
edition.workspace = true
|
||||
|
|
@ -29,7 +29,7 @@ yuv = { version = "0.8", features = ["rdp"] }
|
|||
|
||||
[dev-dependencies]
|
||||
bmp = "0.5"
|
||||
bytemuck = "1.23"
|
||||
bytemuck = "1.24"
|
||||
expect-test.workspace = true
|
||||
|
||||
[lints]
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ pub fn ycbcr_to_rgba(input: YCbCrBuffer<'_>, output: &mut [u8]) -> io::Result<()
|
|||
rdp_yuv444_to_rgba(&planar, output, len).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// - Panics if `width` > 64.
|
||||
/// - Panics if `height` > 64.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn to_64x64_ycbcr_tile(
|
||||
input: &[u8],
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ pub mod zgfx;
|
|||
|
||||
mod utils;
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `input.len()` is not 4096 (64 * 46).
|
||||
pub fn rfx_encode_component(
|
||||
input: &mut [i16],
|
||||
output: &mut [u8],
|
||||
|
|
|
|||
|
|
@ -106,9 +106,11 @@ pub fn encode(mode: EntropyAlgorithm, input: &[i16], tile: &mut [u8]) -> Result<
|
|||
k = kp >> LS_GR;
|
||||
}
|
||||
CompressionMode::GolombRice => {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (prior check)")]
|
||||
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);
|
||||
|
|
@ -360,17 +362,17 @@ impl From<u32> for CompressionMode {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum RlgrError {
|
||||
IoError(io::Error),
|
||||
YuvError(YuvError),
|
||||
Io(io::Error),
|
||||
Yuv(YuvError),
|
||||
EmptyTile,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for RlgrError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::IoError(_error) => write!(f, "IO error"),
|
||||
Self::Io(_) => write!(f, "IO error"),
|
||||
Self::Yuv(_) => write!(f, "YUV error"),
|
||||
Self::EmptyTile => write!(f, "the input tile is empty"),
|
||||
Self::YuvError(error) => write!(f, "YUV error: {error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,8 +380,8 @@ impl core::fmt::Display for RlgrError {
|
|||
impl core::error::Error for RlgrError {
|
||||
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::IoError(error) => Some(error),
|
||||
Self::YuvError(error) => Some(error),
|
||||
Self::Io(error) => Some(error),
|
||||
Self::Yuv(error) => Some(error),
|
||||
Self::EmptyTile => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -387,6 +389,6 @@ impl core::error::Error for RlgrError {
|
|||
|
||||
impl From<io::Error> for RlgrError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::IoError(err)
|
||||
Self::Io(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ impl Scancode {
|
|||
|
||||
pub fn as_idx(self) -> usize {
|
||||
if self.extended {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (integer upcast)")]
|
||||
usize::from(self.code).checked_add(256).expect("never overflow")
|
||||
} else {
|
||||
usize::from(self.code)
|
||||
|
|
@ -343,6 +344,7 @@ impl Database {
|
|||
let mut events = SmallVec::new();
|
||||
|
||||
for idx in self.mouse_buttons.iter_ones() {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer downcast)")]
|
||||
let button = MouseButton::from_idx(idx).expect("in-range index");
|
||||
|
||||
let event = match MouseButtonFlags::from(button) {
|
||||
|
|
@ -365,9 +367,12 @@ impl Database {
|
|||
// The keyboard bit array size is 512.
|
||||
for idx in self.keyboard.iter_ones() {
|
||||
let (scancode, extended) = if idx >= 256 {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer underflow)")]
|
||||
let extended_code = idx.checked_sub(256).expect("never underflow");
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer downcast)")]
|
||||
(u8::try_from(extended_code).expect("always in the range"), true)
|
||||
} else {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer downcast)")]
|
||||
(u8::try_from(idx).expect("always in the range"), false)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["std"] }
|
|||
ironrdp-error = { path = "../ironrdp-error", version = "0.1" }
|
||||
ironrdp-tls = { path = "../ironrdp-tls", version = "0.1" }
|
||||
log = "0.4"
|
||||
tokio-tungstenite = { version = "0.27" }
|
||||
tokio-tungstenite = { version = "0.28" }
|
||||
tokio-util = { version = "0.7" }
|
||||
tokio = { version = "1.43", features = ["macros", "rt"] }
|
||||
uuid = { version = "1.16", features = ["v4"] }
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ impl GwClient {
|
|||
.header(hyper::header::SEC_WEBSOCKET_VERSION, "13")
|
||||
.header(hyper::header::SEC_WEBSOCKET_KEY, generate_key())
|
||||
.body(http_body_util::Empty::<Bytes>::new())
|
||||
.expect("Failed to build request");
|
||||
.map_err(|e| custom_err!("failed to build request", e))?;
|
||||
|
||||
let stream = hyper_util::rt::tokio::TokioIo::new(stream);
|
||||
let (mut sender, mut conn) = hyper::client::conn::http1::handshake(stream)
|
||||
|
|
@ -200,8 +200,7 @@ impl GwClient {
|
|||
|
||||
let work = tokio::spawn(async move {
|
||||
let iv = Duration::from_secs(15 * 60);
|
||||
let mut keepalive_interval: tokio::time::Interval =
|
||||
tokio::time::interval_at(tokio::time::Instant::now() + iv, iv);
|
||||
let mut keepalive_interval = tokio::time::interval_at(tokio::time::Instant::now() + iv, iv);
|
||||
|
||||
loop {
|
||||
let mut wsbuf = [0u8; 8192];
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use core::fmt::{self, Debug};
|
|||
|
||||
use bitflags::bitflags;
|
||||
use ironrdp_core::{
|
||||
ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor,
|
||||
WriteCursor,
|
||||
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
|
||||
ReadCursor, WriteCursor,
|
||||
};
|
||||
|
||||
use crate::geometry::InclusiveRectangle;
|
||||
|
|
@ -41,11 +41,9 @@ impl Encode for BitmapUpdateData<'_> {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
if self.rectangles.len() > u16::MAX as usize {
|
||||
return Err(invalid_field_err!("numberRectangles", "rectangle count is too big"));
|
||||
}
|
||||
let rectangle_count = cast_length!("number of rectangles", self.rectangles.len())?;
|
||||
|
||||
Self::encode_header(self.rectangles.len() as u16, dst)?;
|
||||
Self::encode_header(rectangle_count, dst)?;
|
||||
|
||||
for bitmap_data in self.rectangles.iter() {
|
||||
bitmap_data.encode(dst)?;
|
||||
|
|
@ -74,10 +72,10 @@ impl<'de> Decode<'de> for BitmapUpdateData<'de> {
|
|||
return Err(invalid_field_err!("updateType", "invalid update type"));
|
||||
}
|
||||
|
||||
let rectangles_number = src.read_u16() as usize;
|
||||
let mut rectangles = Vec::with_capacity(rectangles_number);
|
||||
let rectangle_count = usize::from(src.read_u16());
|
||||
let mut rectangles = Vec::with_capacity(rectangle_count);
|
||||
|
||||
for _ in 0..rectangles_number {
|
||||
for _ in 0..rectangle_count {
|
||||
rectangles.push(BitmapData::decode(src)?);
|
||||
}
|
||||
|
||||
|
|
@ -111,16 +109,14 @@ impl Encode for BitmapData<'_> {
|
|||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
let encoded_bitmap_data_length = self.encoded_bitmap_data_length();
|
||||
if encoded_bitmap_data_length > u16::MAX as usize {
|
||||
return Err(invalid_field_err!("bitmapLength", "bitmap data length is too big"));
|
||||
}
|
||||
let encoded_bitmap_data_length = cast_length!("bitmap data length", encoded_bitmap_data_length)?;
|
||||
|
||||
self.rectangle.encode(dst)?;
|
||||
dst.write_u16(self.width);
|
||||
dst.write_u16(self.height);
|
||||
dst.write_u16(self.bits_per_pixel);
|
||||
dst.write_u16(self.compression_flags.bits());
|
||||
dst.write_u16(encoded_bitmap_data_length as u16);
|
||||
dst.write_u16(encoded_bitmap_data_length);
|
||||
if let Some(compressed_data_header) = &self.compressed_data_header {
|
||||
compressed_data_header.encode(dst)?;
|
||||
};
|
||||
|
|
@ -150,25 +146,25 @@ impl<'de> Decode<'de> for BitmapData<'de> {
|
|||
|
||||
// A 16-bit, unsigned integer. The size in bytes of the data in the bitmapComprHdr
|
||||
// and bitmapDataStream fields.
|
||||
let encoded_bitmap_data_length = src.read_u16();
|
||||
let encoded_bitmap_data_length = usize::from(src.read_u16());
|
||||
|
||||
ensure_size!(in: src, size: encoded_bitmap_data_length as usize);
|
||||
ensure_size!(in: src, size: encoded_bitmap_data_length);
|
||||
|
||||
let (compressed_data_header, buffer_length) = if compression_flags.contains(Compression::BITMAP_COMPRESSION)
|
||||
&& !compression_flags.contains(Compression::NO_BITMAP_COMPRESSION_HDR)
|
||||
{
|
||||
// Check if encoded_bitmap_data_length is at least CompressedDataHeader::ENCODED_SIZE
|
||||
if encoded_bitmap_data_length < CompressedDataHeader::ENCODED_SIZE as u16 {
|
||||
if encoded_bitmap_data_length < CompressedDataHeader::ENCODED_SIZE {
|
||||
return Err(invalid_field_err!(
|
||||
"cbCompEncodedBitmapDataLength",
|
||||
"length is less than CompressedDataHeader::ENCODED_SIZE"
|
||||
));
|
||||
}
|
||||
|
||||
let buffer_length = encoded_bitmap_data_length as usize - CompressedDataHeader::ENCODED_SIZE;
|
||||
let buffer_length = encoded_bitmap_data_length - CompressedDataHeader::ENCODED_SIZE;
|
||||
(Some(CompressedDataHeader::decode(src)?), buffer_length)
|
||||
} else {
|
||||
(None, encoded_bitmap_data_length as usize)
|
||||
(None, encoded_bitmap_data_length)
|
||||
};
|
||||
|
||||
let bitmap_data = src.read_slice(buffer_length);
|
||||
|
|
@ -56,7 +56,7 @@ impl Encode for BitmapStreamHeader {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
let mut header = ((self.enable_rle_compression as u8) << 4) | ((!self.use_alpha as u8) << 5);
|
||||
let mut header = (u8::from(self.enable_rle_compression) << 4) | (u8::from(!self.use_alpha) << 5);
|
||||
|
||||
match self.color_plane_definition {
|
||||
ColorPlaneDefinition::Argb => {
|
||||
|
|
@ -68,7 +68,7 @@ impl Encode for BitmapStreamHeader {
|
|||
..
|
||||
} => {
|
||||
// Add cll and cs flags to header
|
||||
header |= (color_loss_level & 0x07) | ((use_chroma_subsampling as u8) << 3);
|
||||
header |= (color_loss_level & 0x07) | (u8::from(use_chroma_subsampling) << 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ mod tests;
|
|||
use bit_field::BitField as _;
|
||||
use bitflags::bitflags;
|
||||
use ironrdp_core::{
|
||||
decode_cursor, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeError, DecodeResult, Encode,
|
||||
EncodeResult, InvalidFieldErr as _, ReadCursor, WriteCursor,
|
||||
cast_length, decode_cursor, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeError,
|
||||
DecodeResult, Encode, EncodeResult, InvalidFieldErr as _, ReadCursor, WriteCursor,
|
||||
};
|
||||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive as _;
|
||||
|
|
@ -42,7 +42,7 @@ impl FastPathHeader {
|
|||
// it may then be +2 if > 0x7f
|
||||
let len = self.data_length + Self::FIXED_PART_SIZE + 1;
|
||||
|
||||
Self::FIXED_PART_SIZE + per::sizeof_length(len as u16)
|
||||
Self::FIXED_PART_SIZE + per::sizeof_length(len)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,15 +56,13 @@ impl Encode for FastPathHeader {
|
|||
dst.write_u8(header);
|
||||
|
||||
let length = self.data_length + self.size();
|
||||
if length > u16::MAX as usize {
|
||||
return Err(invalid_field_err!("length", "fastpath PDU length is too big"));
|
||||
}
|
||||
let length = cast_length!("length", length)?;
|
||||
|
||||
if self.forced_long_length {
|
||||
// Preserve same layout for header as received
|
||||
per::write_long_length(dst, length as u16);
|
||||
per::write_long_length(dst, length);
|
||||
} else {
|
||||
per::write_length(dst, length as u16);
|
||||
per::write_length(dst, length);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -93,14 +91,15 @@ impl<'de> Decode<'de> for FastPathHeader {
|
|||
let (length, sizeof_length) = per::read_length(src).map_err(|e| {
|
||||
DecodeError::invalid_field("", "length", "Invalid encoded fast path PDU length").with_source(e)
|
||||
})?;
|
||||
if (length as usize) < sizeof_length + Self::FIXED_PART_SIZE {
|
||||
let length = usize::from(length);
|
||||
if length < sizeof_length + Self::FIXED_PART_SIZE {
|
||||
return Err(invalid_field_err!(
|
||||
"length",
|
||||
"received fastpath PDU length is smaller than header size"
|
||||
));
|
||||
}
|
||||
let data_length = length as usize - sizeof_length - Self::FIXED_PART_SIZE;
|
||||
// Detect case, when received packet has non-optimal packet length packing
|
||||
let data_length = length - sizeof_length - Self::FIXED_PART_SIZE;
|
||||
// Detect case, when received packet has non-optimal packet length packing.
|
||||
let forced_long_length = per::sizeof_length(length) != sizeof_length;
|
||||
|
||||
Ok(FastPathHeader {
|
||||
|
|
@ -131,9 +130,7 @@ impl Encode for FastPathUpdatePdu<'_> {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
if self.data.len() > u16::MAX as usize {
|
||||
return Err(invalid_field_err!("data", "fastpath PDU data is too big"));
|
||||
}
|
||||
let data_len = cast_length!("data length", self.data.len())?;
|
||||
|
||||
let mut header = 0u8;
|
||||
header.set_bits(0..4, self.update_code.as_u8());
|
||||
|
|
@ -148,7 +145,7 @@ impl Encode for FastPathUpdatePdu<'_> {
|
|||
dst.write_u8(compression_flags_with_type);
|
||||
}
|
||||
|
||||
dst.write_u16(self.data.len() as u16);
|
||||
dst.write_u16(data_len);
|
||||
dst.write_slice(self.data);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -200,7 +197,7 @@ impl<'de> Decode<'de> for FastPathUpdatePdu<'de> {
|
|||
(None, None)
|
||||
};
|
||||
|
||||
let data_length = src.read_u16() as usize;
|
||||
let data_length = usize::from(src.read_u16());
|
||||
ensure_size!(in: src, size: data_length);
|
||||
let data = src.read_slice(data_length);
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use ironrdp_core::{
|
||||
ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor,
|
||||
WriteCursor,
|
||||
cast_int, cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode,
|
||||
EncodeResult, ReadCursor, WriteCursor,
|
||||
};
|
||||
|
||||
// Represents `TS_POINT16` described in [MS-RDPBCGR] 2.2.9.1.1.4.1
|
||||
|
|
@ -69,21 +69,24 @@ macro_rules! check_masks_alignment {
|
|||
($and_mask:expr, $xor_mask:expr, $pointer_height:expr, $large_ptr:expr) => {{
|
||||
const AND_MASK_SIZE_FIELD: &str = "lengthAndMask";
|
||||
const XOR_MASK_SIZE_FIELD: &str = "lengthXorMask";
|
||||
const U32_MAX: usize = 0xFFFFFFFF;
|
||||
|
||||
let pointer_height: usize = cast_int!("pointer height", $pointer_height)?;
|
||||
|
||||
let check_mask = |mask: &[u8], field: &'static str| {
|
||||
if $pointer_height == 0 {
|
||||
return Err(invalid_field_err!(field, "pointer height cannot be zero"));
|
||||
}
|
||||
if $large_ptr && (mask.len() > u32::MAX as usize) {
|
||||
if $large_ptr && (mask.len() > U32_MAX) {
|
||||
return Err(invalid_field_err!(field, "pointer mask is too big for u32 size"));
|
||||
}
|
||||
if !$large_ptr && (mask.len() > u16::MAX as usize) {
|
||||
if !$large_ptr && (mask.len() > usize::from(u16::MAX)) {
|
||||
return Err(invalid_field_err!(field, "pointer mask is too big for u16 size"));
|
||||
}
|
||||
if (mask.len() % $pointer_height as usize) != 0 {
|
||||
if (mask.len() % pointer_height) != 0 {
|
||||
return Err(invalid_field_err!(field, "pointer mask have incomplete scanlines"));
|
||||
}
|
||||
if (mask.len() / $pointer_height as usize) % 2 != 0 {
|
||||
if (mask.len() / pointer_height) % 2 != 0 {
|
||||
return Err(invalid_field_err!(
|
||||
field,
|
||||
"pointer mask scanlines should be aligned to 16 bits"
|
||||
|
|
@ -108,8 +111,8 @@ impl Encode for ColorPointerAttribute<'_> {
|
|||
dst.write_u16(self.width);
|
||||
dst.write_u16(self.height);
|
||||
|
||||
dst.write_u16(self.and_mask.len() as u16);
|
||||
dst.write_u16(self.xor_mask.len() as u16);
|
||||
dst.write_u16(cast_length!("and mask length", self.and_mask.len())?);
|
||||
dst.write_u16(cast_length!("xor mask length", self.xor_mask.len())?);
|
||||
// Note that masks are written in reverse order. It is not a mistake, that is how the
|
||||
// message is defined in [MS-RDPBCGR]
|
||||
dst.write_slice(self.xor_mask);
|
||||
|
|
@ -135,15 +138,15 @@ impl<'a> Decode<'a> for ColorPointerAttribute<'a> {
|
|||
let hot_spot = Point16::decode(src)?;
|
||||
let width = src.read_u16();
|
||||
let height = src.read_u16();
|
||||
let length_and_mask = src.read_u16();
|
||||
let length_xor_mask = src.read_u16();
|
||||
|
||||
// Convert to usize during the addition to prevent overflow and match expected type
|
||||
let expected_masks_size = (length_and_mask as usize) + (length_xor_mask as usize);
|
||||
let length_and_mask = usize::from(src.read_u16());
|
||||
let length_xor_mask = usize::from(src.read_u16());
|
||||
|
||||
let expected_masks_size = length_and_mask + length_xor_mask;
|
||||
ensure_size!(in: src, size: expected_masks_size);
|
||||
|
||||
let xor_mask = src.read_slice(length_xor_mask as usize);
|
||||
let and_mask = src.read_slice(length_and_mask as usize);
|
||||
let xor_mask = src.read_slice(length_xor_mask);
|
||||
let and_mask = src.read_slice(length_and_mask);
|
||||
|
||||
check_masks_alignment!(and_mask, xor_mask, height, false)?;
|
||||
|
||||
|
|
@ -270,8 +273,8 @@ impl Encode for LargePointerAttribute<'_> {
|
|||
dst.write_u16(self.width);
|
||||
dst.write_u16(self.height);
|
||||
|
||||
dst.write_u32(self.and_mask.len() as u32);
|
||||
dst.write_u32(self.xor_mask.len() as u32);
|
||||
dst.write_u32(cast_length!("and mask length", self.and_mask.len())?);
|
||||
dst.write_u32(cast_length!("xor mask length", self.xor_mask.len())?);
|
||||
// See comment in `ColorPointerAttribute::encode` about encoding order
|
||||
dst.write_slice(self.xor_mask);
|
||||
dst.write_slice(self.and_mask);
|
||||
|
|
@ -298,8 +301,8 @@ impl<'a> Decode<'a> for LargePointerAttribute<'a> {
|
|||
let width = src.read_u16();
|
||||
let height = src.read_u16();
|
||||
// Convert to usize to prevent overflow during addition
|
||||
let length_and_mask = src.read_u32() as usize;
|
||||
let length_xor_mask = src.read_u32() as usize;
|
||||
let length_and_mask = cast_length!("and mask length", src.read_u32())?;
|
||||
let length_xor_mask = cast_length!("xor mask length", src.read_u32())?;
|
||||
|
||||
let expected_masks_size = length_and_mask + length_xor_mask;
|
||||
ensure_size!(in: src, size: expected_masks_size);
|
||||
|
|
@ -3,10 +3,10 @@ mod tests;
|
|||
|
||||
use bitflags::bitflags;
|
||||
use ironrdp_core::{
|
||||
ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor,
|
||||
WriteCursor,
|
||||
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
|
||||
ReadCursor, WriteCursor,
|
||||
};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive as _;
|
||||
|
||||
use crate::geometry::ExclusiveRectangle;
|
||||
|
|
@ -126,7 +126,7 @@ impl Encode for FrameMarkerPdu {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_fixed_part_size!(in: dst);
|
||||
|
||||
dst.write_u16(self.frame_action as u16);
|
||||
dst.write_u16(self.frame_action.as_u16());
|
||||
dst.write_u32(self.frame_id.unwrap_or(0));
|
||||
|
||||
Ok(())
|
||||
|
|
@ -197,9 +197,7 @@ impl Encode for ExtendedBitmapDataPdu<'_> {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
if self.data.len() > u32::MAX as usize {
|
||||
return Err(invalid_field_err!("bitmapDataLength", "bitmap data is too big"));
|
||||
}
|
||||
let data_len = cast_length!("bitmap data length", self.data.len())?;
|
||||
|
||||
dst.write_u8(self.bpp);
|
||||
let flags = if self.header.is_some() {
|
||||
|
|
@ -212,7 +210,7 @@ impl Encode for ExtendedBitmapDataPdu<'_> {
|
|||
dst.write_u8(self.codec_id);
|
||||
dst.write_u16(self.width);
|
||||
dst.write_u16(self.height);
|
||||
dst.write_u32(self.data.len() as u32);
|
||||
dst.write_u32(data_len);
|
||||
if let Some(header) = &self.header {
|
||||
header.encode(dst)?;
|
||||
}
|
||||
|
|
@ -240,7 +238,7 @@ impl<'de> Decode<'de> for ExtendedBitmapDataPdu<'de> {
|
|||
let codec_id = src.read_u8();
|
||||
let width = src.read_u16();
|
||||
let height = src.read_u16();
|
||||
let data_length = src.read_u32() as usize;
|
||||
let data_length = cast_length!("bitmap data length", src.read_u32())?;
|
||||
|
||||
let expected_remaining_size = if flags.contains(BitmapDataFlags::COMPRESSED_BITMAP_HEADER_PRESENT) {
|
||||
data_length + BitmapDataHeader::ENCODED_SIZE
|
||||
|
|
@ -352,13 +350,23 @@ impl From<&SurfaceCommand<'_>> for SurfaceCommandType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
|
||||
#[repr(u16)]
|
||||
pub enum FrameAction {
|
||||
Begin = 0x00,
|
||||
End = 0x01,
|
||||
}
|
||||
|
||||
impl FrameAction {
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
|
||||
)]
|
||||
pub fn as_u16(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct BitmapDataFlags: u8 {
|
||||
|
|
@ -3,13 +3,25 @@ use ironrdp_core::{cast_length, ensure_size, invalid_field_err, ReadCursor, Writ
|
|||
use crate::{DecodeResult, EncodeResult};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Pc {
|
||||
Primitive = 0x00,
|
||||
Construct = 0x20,
|
||||
}
|
||||
|
||||
impl Pc {
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[expect(unused)]
|
||||
#[derive(Copy, Clone)]
|
||||
enum Class {
|
||||
Universal = 0x00,
|
||||
Application = 0x40,
|
||||
|
|
@ -17,8 +29,19 @@ enum Class {
|
|||
Private = 0xC0,
|
||||
}
|
||||
|
||||
impl Class {
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[expect(unused)]
|
||||
#[derive(Copy, Clone)]
|
||||
enum Tag {
|
||||
Mask = 0x1F,
|
||||
Boolean = 0x01,
|
||||
|
|
@ -30,6 +53,16 @@ enum Tag {
|
|||
Sequence = 0x10,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const SIZEOF_ENUMERATED: usize = 3;
|
||||
pub(crate) const SIZEOF_BOOL: usize = 3;
|
||||
|
||||
|
|
@ -46,7 +79,7 @@ pub(crate) fn sizeof_sequence_tag(length: u16) -> usize {
|
|||
}
|
||||
|
||||
pub(crate) fn sizeof_octet_string(length: u16) -> usize {
|
||||
1 + sizeof_length(length) + length as usize
|
||||
1 + sizeof_length(length) + usize::from(length)
|
||||
}
|
||||
|
||||
pub(crate) fn sizeof_integer(value: u32) -> usize {
|
||||
|
|
@ -71,7 +104,7 @@ pub(crate) fn read_sequence_tag(stream: &mut ReadCursor<'_>) -> DecodeResult<u16
|
|||
ensure_size!(in: stream, size: 1);
|
||||
let identifier = stream.read_u8();
|
||||
|
||||
if identifier != Class::Universal as u8 | Pc::Construct as u8 | (TAG_MASK & Tag::Sequence as u8) {
|
||||
if identifier != Class::Universal.as_u8() | Pc::Construct.as_u8() | (TAG_MASK & Tag::Sequence.as_u8()) {
|
||||
Err(invalid_field_err!("identifier", "invalid sequence tag identifier"))
|
||||
} else {
|
||||
read_length(stream)
|
||||
|
|
@ -82,11 +115,11 @@ pub(crate) fn write_application_tag(stream: &mut WriteCursor<'_>, tagnum: u8, le
|
|||
ensure_size!(in: stream, size: sizeof_application_tag(tagnum, length));
|
||||
|
||||
let taglen = if tagnum > 0x1E {
|
||||
stream.write_u8(Class::Application as u8 | Pc::Construct as u8 | TAG_MASK);
|
||||
stream.write_u8(Class::Application.as_u8() | Pc::Construct.as_u8() | TAG_MASK);
|
||||
stream.write_u8(tagnum);
|
||||
2
|
||||
} else {
|
||||
stream.write_u8(Class::Application as u8 | Pc::Construct as u8 | (TAG_MASK & tagnum));
|
||||
stream.write_u8(Class::Application.as_u8() | Pc::Construct.as_u8() | (TAG_MASK & tagnum));
|
||||
1
|
||||
};
|
||||
|
||||
|
|
@ -98,14 +131,14 @@ pub(crate) fn read_application_tag(stream: &mut ReadCursor<'_>, tagnum: u8) -> D
|
|||
let identifier = stream.read_u8();
|
||||
|
||||
if tagnum > 0x1E {
|
||||
if identifier != Class::Application as u8 | Pc::Construct as u8 | TAG_MASK {
|
||||
if identifier != Class::Application.as_u8() | Pc::Construct.as_u8() | TAG_MASK {
|
||||
return Err(invalid_field_err!("identifier", "invalid application tag identifier"));
|
||||
}
|
||||
ensure_size!(in: stream, size: 1);
|
||||
if stream.read_u8() != tagnum {
|
||||
return Err(invalid_field_err!("tagnum", "invalid application tag identifier"));
|
||||
}
|
||||
} else if identifier != Class::Application as u8 | Pc::Construct as u8 | (TAG_MASK & tagnum) {
|
||||
} else if identifier != Class::Application.as_u8() | Pc::Construct.as_u8() | (TAG_MASK & tagnum) {
|
||||
return Err(invalid_field_err!("identifier", "invalid application tag identifier"));
|
||||
}
|
||||
|
||||
|
|
@ -146,20 +179,22 @@ pub(crate) fn write_integer(stream: &mut WriteCursor<'_>, value: u32) -> EncodeR
|
|||
if value < 0x0000_0080 {
|
||||
write_length(stream, 1)?;
|
||||
ensure_size!(in: stream, size: 1);
|
||||
stream.write_u8(value as u8);
|
||||
stream.write_u8(u8::try_from(value).expect("value is guaranteed to fit into u8 due to the prior check"));
|
||||
|
||||
Ok(3)
|
||||
} else if value < 0x0000_8000 {
|
||||
write_length(stream, 2)?;
|
||||
ensure_size!(in: stream, size: 2);
|
||||
stream.write_u16_be(value as u16);
|
||||
stream.write_u16_be(u16::try_from(value).expect("value is guaranteed to fit into u16 due to the prior check"));
|
||||
|
||||
Ok(4)
|
||||
} else if value < 0x0080_0000 {
|
||||
write_length(stream, 3)?;
|
||||
ensure_size!(in: stream, size: 3);
|
||||
stream.write_u8((value >> 16) as u8);
|
||||
stream.write_u16_be((value & 0xFFFF) as u16);
|
||||
stream.write_u8(u8::try_from(value >> 16).expect("value is guaranteed to fit into u8 due to the prior check"));
|
||||
stream.write_u16_be(
|
||||
u16::try_from(value & 0xFFFF).expect("masking with 0xFFFF ensures that the value fits into u16"),
|
||||
);
|
||||
|
||||
Ok(5)
|
||||
} else {
|
||||
|
|
@ -251,7 +286,7 @@ pub(crate) fn read_octet_string_tag(stream: &mut ReadCursor<'_>) -> DecodeResult
|
|||
fn write_universal_tag(stream: &mut WriteCursor<'_>, tag: Tag, pc: Pc) -> EncodeResult<usize> {
|
||||
ensure_size!(in: stream, size: 1);
|
||||
|
||||
let identifier = Class::Universal as u8 | pc as u8 | (TAG_MASK & tag as u8);
|
||||
let identifier = Class::Universal.as_u8() | pc.as_u8() | (TAG_MASK & tag.as_u8());
|
||||
stream.write_u8(identifier);
|
||||
|
||||
Ok(1)
|
||||
|
|
@ -262,7 +297,7 @@ fn read_universal_tag(stream: &mut ReadCursor<'_>, tag: Tag, pc: Pc) -> DecodeRe
|
|||
|
||||
let identifier = stream.read_u8();
|
||||
|
||||
if identifier != Class::Universal as u8 | pc as u8 | (TAG_MASK & tag as u8) {
|
||||
if identifier != Class::Universal.as_u8() | pc.as_u8() | (TAG_MASK & tag.as_u8()) {
|
||||
Err(invalid_field_err!("identifier", "invalid universal tag identifier"))
|
||||
} else {
|
||||
Ok(())
|
||||
|
|
@ -279,11 +314,11 @@ fn write_length(stream: &mut WriteCursor<'_>, length: u16) -> EncodeResult<usize
|
|||
Ok(3)
|
||||
} else if length > 0x7F {
|
||||
stream.write_u8(0x80 ^ 0x1);
|
||||
stream.write_u8(length as u8);
|
||||
stream.write_u8(u8::try_from(length).expect("length is guaranteed to fit into u8 due to the prior check"));
|
||||
|
||||
Ok(2)
|
||||
} else {
|
||||
stream.write_u8(length as u8);
|
||||
stream.write_u8(u8::try_from(length).expect("length is guaranteed to fit into u8 due to the prior check"));
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ impl Encode for TileSetPdu<'_> {
|
|||
dst.write_u16(properties);
|
||||
|
||||
dst.write_u8(cast_length!("numQuant", self.quants.len())?);
|
||||
dst.write_u8(TILE_SIZE as u8);
|
||||
dst.write_u8(u8::try_from(TILE_SIZE).expect("TILE_SIZE value fits into u8"));
|
||||
dst.write_u16(cast_length!("numTiles", self.tiles.len())?);
|
||||
|
||||
let tiles_data_size = self.tiles.iter().map(|t| Block::Tile(t.clone()).size()).sum::<usize>();
|
||||
|
|
@ -384,7 +384,7 @@ impl<'de> Decode<'de> for TileSetPdu<'de> {
|
|||
}
|
||||
|
||||
let number_of_tiles = usize::from(src.read_u16());
|
||||
let _tiles_data_size = src.read_u32() as usize;
|
||||
let _tiles_data_size = src.read_u32();
|
||||
|
||||
let quants = iter::repeat_with(|| Quant::decode(src))
|
||||
.take(number_of_quants)
|
||||
|
|
@ -544,24 +544,32 @@ impl Encode for Quant {
|
|||
|
||||
impl<'de> Decode<'de> for Quant {
|
||||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
#![allow(clippy::similar_names)] // It’s hard to do better than ll3, lh3, etc without going overly verbose.
|
||||
#![allow(
|
||||
clippy::similar_names,
|
||||
reason = "it’s hard to do better than ll3, lh3, etc without going overly verbose"
|
||||
)]
|
||||
|
||||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let level3 = src.read_u16();
|
||||
let ll3 = level3.get_bits(0..4) as u8;
|
||||
let lh3 = level3.get_bits(4..8) as u8;
|
||||
let hl3 = level3.get_bits(8..12) as u8;
|
||||
let hh3 = level3.get_bits(12..16) as u8;
|
||||
let ll3lh3 = src.read_u8();
|
||||
let ll3 = ll3lh3.get_bits(0..4);
|
||||
let lh3 = ll3lh3.get_bits(4..8);
|
||||
|
||||
let level2_with_lh1 = src.read_u16();
|
||||
let lh2 = level2_with_lh1.get_bits(0..4) as u8;
|
||||
let hl2 = level2_with_lh1.get_bits(4..8) as u8;
|
||||
let hh2 = level2_with_lh1.get_bits(8..12) as u8;
|
||||
let lh1 = level2_with_lh1.get_bits(12..16) as u8;
|
||||
let hl3hh3 = src.read_u8();
|
||||
let hl3 = hl3hh3.get_bits(0..4);
|
||||
let hh3 = hl3hh3.get_bits(4..8);
|
||||
|
||||
let level1 = src.read_u8();
|
||||
let hl1 = level1.get_bits(0..4);
|
||||
let hh1 = level1.get_bits(4..8);
|
||||
let lh2hl2 = src.read_u8();
|
||||
let lh2 = lh2hl2.get_bits(0..4);
|
||||
let hl2 = lh2hl2.get_bits(4..8);
|
||||
|
||||
let hh2lh1 = src.read_u8();
|
||||
let hh2 = hh2lh1.get_bits(0..4);
|
||||
let lh1 = hh2lh1.get_bits(4..8);
|
||||
|
||||
let hl1hh1 = src.read_u8();
|
||||
let hl1 = hl1hh1.get_bits(0..4);
|
||||
let hh1 = hl1hh1.get_bits(4..8);
|
||||
|
||||
Ok(Self {
|
||||
ll3,
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ impl<'de> Decode<'de> for BlockHeader {
|
|||
|
||||
let ty = src.read_u16();
|
||||
let ty = BlockType::from_u16(ty).ok_or_else(|| invalid_field_err!("blockType", "Invalid block type"))?;
|
||||
let data_length = src.read_u32() as usize;
|
||||
let data_length: usize = cast_length!("block length", src.read_u32())?;
|
||||
data_length
|
||||
.checked_sub(Self::FIXED_PART_SIZE)
|
||||
.ok_or_else(|| invalid_field_err!("blockLen", "Invalid block length"))?;
|
||||
|
|
@ -11,12 +11,12 @@ impl Rc4 {
|
|||
pub(crate) fn new(key: &[u8]) -> Self {
|
||||
// key scheduling
|
||||
let mut state = State::default();
|
||||
for (i, item) in state.iter_mut().enumerate().take(256) {
|
||||
*item = i as u8;
|
||||
for (i, item) in (0..=255).zip(state.iter_mut()) {
|
||||
*item = i;
|
||||
}
|
||||
let mut j = 0usize;
|
||||
for i in 0..256 {
|
||||
j = (j + state[i] as usize + key[i % key.len()] as usize) % 256;
|
||||
j = (j + usize::from(state[i]) + usize::from(key[i % key.len()])) % 256;
|
||||
state.swap(i, j);
|
||||
}
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ impl Rc4 {
|
|||
let mut output = Vec::with_capacity(message.len());
|
||||
while output.capacity() > output.len() {
|
||||
self.i = (self.i + 1) % 256;
|
||||
self.j = (self.j + self.state[self.i] as usize) % 256;
|
||||
self.j = (self.j + usize::from(self.state[self.i])) % 256;
|
||||
self.state.swap(self.i, self.j);
|
||||
let idx_k = (self.state[self.i] as usize + self.state[self.j] as usize) % 256;
|
||||
let idx_k = (usize::from(self.state[self.i]) + usize::from(self.state[self.j])) % 256;
|
||||
let k = self.state[idx_k];
|
||||
let idx_msg = output.len();
|
||||
output.push(k ^ message[idx_msg]);
|
||||
|
|
|
|||
|
|
@ -56,9 +56,8 @@ impl<'de> Decode<'de> for ClientClusterData {
|
|||
|
||||
let flags = RedirectionFlags::from_bits(flags_with_version & !REDIRECTION_VERSION_MASK)
|
||||
.ok_or_else(|| invalid_field_err!("flags", "invalid redirection flags"))?;
|
||||
let redirection_version =
|
||||
RedirectionVersion::from_u8(((flags_with_version & REDIRECTION_VERSION_MASK) >> 2) as u8)
|
||||
.ok_or_else(|| invalid_field_err!("redirVersion", "invalid redirection version"))?;
|
||||
let redirection_version = RedirectionVersion::from_u32((flags_with_version & REDIRECTION_VERSION_MASK) >> 2)
|
||||
.ok_or_else(|| invalid_field_err!("redirVersion", "invalid redirection version"))?;
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@ impl Encode for ConferenceCreateRequest {
|
|||
|
||||
per::CHOICE_SIZE
|
||||
+ CONFERENCE_REQUEST_OBJECT_ID.len()
|
||||
+ per::sizeof_length(req_length)
|
||||
+ per::sizeof_length(usize::from(req_length))
|
||||
+ CONFERENCE_REQUEST_CONNECT_PDU_SIZE
|
||||
+ per::sizeof_length(length)
|
||||
+ per::sizeof_length(usize::from(length))
|
||||
+ gcc_blocks_buffer_length
|
||||
}
|
||||
}
|
||||
|
|
@ -286,9 +286,9 @@ impl Encode for ConferenceCreateResponse {
|
|||
|
||||
per::CHOICE_SIZE
|
||||
+ CONFERENCE_REQUEST_OBJECT_ID.len()
|
||||
+ per::sizeof_length(req_length)
|
||||
+ per::sizeof_length(usize::from(req_length))
|
||||
+ CONFERENCE_RESPONSE_CONNECT_PDU_SIZE
|
||||
+ per::sizeof_length(length)
|
||||
+ per::sizeof_length(usize::from(length))
|
||||
+ gcc_blocks_buffer_length
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,13 +49,13 @@ impl<'de> Decode<'de> for ClientMonitorData {
|
|||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let _flags = src.read_u32(); // is unused
|
||||
let monitor_count = src.read_u32();
|
||||
let monitor_count = cast_length!("number of monitors", src.read_u32())?;
|
||||
|
||||
if monitor_count > MONITOR_COUNT_MAX as u32 {
|
||||
if monitor_count > MONITOR_COUNT_MAX {
|
||||
return Err(invalid_field_err!("nMonitors", "too many monitors"));
|
||||
}
|
||||
|
||||
let mut monitors = Vec::with_capacity(monitor_count as usize);
|
||||
let mut monitors = Vec::with_capacity(monitor_count);
|
||||
for _ in 0..monitor_count {
|
||||
monitors.push(Monitor::decode(src)?);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ const SERVER_CHANNEL_SIZE: usize = 2;
|
|||
/// is using all the code values from 0 to 255, as such any u8 value is a valid ANSI character.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ChannelName {
|
||||
/// INVARIANT: A null-terminated 8-byte array.
|
||||
/// INVARIANT: Contains at most seven ANSI characters.
|
||||
inner: Cow<'static, [u8; Self::SIZE]>,
|
||||
}
|
||||
|
||||
|
|
@ -79,14 +81,16 @@ impl ChannelName {
|
|||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
/// Get a &str if this channel name is a valid ASCII string.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
if self.inner.iter().all(u8::is_ascii) {
|
||||
#[expect(clippy::missing_panics_doc, reason = "never panics per invariant on self.inner")]
|
||||
let terminator_idx = self
|
||||
.inner
|
||||
.iter()
|
||||
.position(|c| *c == 0)
|
||||
.expect("null-terminated ASCII string");
|
||||
|
||||
#[expect(clippy::missing_panics_doc, reason = "never panics per invariant on self.inner")]
|
||||
Some(str::from_utf8(&self.inner[..terminator_idx]).expect("ASCII characters"))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -51,9 +51,7 @@ impl Encode for FastPathInputHeader {
|
|||
|
||||
fn size(&self) -> usize {
|
||||
let num_events_length = if self.num_events < 16 { 0 } else { 1 };
|
||||
Self::FIXED_PART_SIZE
|
||||
+ per::sizeof_length(self.data_length as u16 + num_events_length as u16 + 1)
|
||||
+ num_events_length
|
||||
Self::FIXED_PART_SIZE + per::sizeof_length(self.data_length + num_events_length + 1) + num_events_length
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +76,7 @@ impl<'de> Decode<'de> for FastPathInputHeader {
|
|||
0
|
||||
};
|
||||
|
||||
let data_length = length as usize - sizeof_length - 1 - num_events_length;
|
||||
let data_length = usize::from(length) - sizeof_length - 1 - num_events_length;
|
||||
|
||||
Ok(FastPathInputHeader {
|
||||
flags,
|
||||
|
|
@ -110,7 +108,7 @@ impl FastpathInputEventType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FastPathInputEvent {
|
||||
KeyboardEvent(KeyboardFlags, u8),
|
||||
UnicodeKeyboardEvent(KeyboardFlags, u16),
|
||||
|
|
@ -255,10 +253,31 @@ bitflags! {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FastPathInput(pub Vec<FastPathInputEvent>);
|
||||
pub struct FastPathInput(
|
||||
/// INVARIANT: (1..=255).contains(len()) = at least one, and at most 255 elements.
|
||||
Vec<FastPathInputEvent>,
|
||||
);
|
||||
|
||||
impl FastPathInput {
|
||||
const NAME: &'static str = "FastPathInput";
|
||||
|
||||
pub fn new(input_events: Vec<FastPathInputEvent>) -> DecodeResult<Self> {
|
||||
// Ensure the invariant on `input_events.len()` is respected.
|
||||
if !(1..=255usize).contains(&input_events.len()) {
|
||||
return Err(invalid_field_err!("nEvents", "invalid number of input events"));
|
||||
}
|
||||
|
||||
Ok(Self(input_events))
|
||||
}
|
||||
|
||||
pub fn single(input_event: FastPathInputEvent) -> Self {
|
||||
// A single element upholds the invariant.
|
||||
Self(vec![input_event])
|
||||
}
|
||||
|
||||
pub fn input_events(&self) -> &[FastPathInputEvent] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for FastPathInput {
|
||||
|
|
@ -271,7 +290,7 @@ impl Encode for FastPathInput {
|
|||
|
||||
let data_length = self.0.iter().map(Encode::size).sum::<usize>();
|
||||
let header = FastPathInputHeader {
|
||||
num_events: self.0.len() as u8,
|
||||
num_events: u8::try_from(self.0.len()).expect("per invariant (1..=255).contains(num_events.len())"),
|
||||
flags: EncryptionFlags::empty(),
|
||||
data_length,
|
||||
};
|
||||
|
|
@ -291,7 +310,8 @@ impl Encode for FastPathInput {
|
|||
fn size(&self) -> usize {
|
||||
let data_length = self.0.iter().map(Encode::size).sum::<usize>();
|
||||
let header = FastPathInputHeader {
|
||||
num_events: self.0.len() as u8,
|
||||
num_events: u8::try_from(self.0.len())
|
||||
.expect("INVARIANT: num_events is within the range of 1 to 255, inclusive"),
|
||||
flags: EncryptionFlags::empty(),
|
||||
data_length,
|
||||
};
|
||||
|
|
@ -306,6 +326,6 @@ impl<'de> Decode<'de> for FastPathInput {
|
|||
.take(usize::from(header.num_events))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self(events))
|
||||
Self::new(events)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::io;
|
||||
|
||||
use ironrdp_core::{
|
||||
ensure_fixed_part_size, ensure_size, invalid_field_err, read_padding, write_padding, Decode, DecodeResult, Encode,
|
||||
EncodeResult, ReadCursor, WriteCursor,
|
||||
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, read_padding, write_padding, Decode,
|
||||
DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
|
||||
};
|
||||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive as _;
|
||||
|
|
@ -38,7 +38,7 @@ impl Encode for InputEventPdu {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
dst.write_u16(self.0.len() as u16);
|
||||
dst.write_u16(cast_length!("input events count", self.0.len())?);
|
||||
write_padding!(dst, 2);
|
||||
|
||||
for event in self.0.iter() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bitflags::bitflags;
|
||||
use ironrdp_core::{ensure_fixed_part_size, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MousePdu {
|
||||
pub flags: PointerFlags,
|
||||
pub number_of_wheel_rotation_units: i16,
|
||||
|
|
@ -25,7 +25,14 @@ impl Encode for MousePdu {
|
|||
PointerFlags::empty().bits()
|
||||
};
|
||||
|
||||
let wheel_rotations_bits = u16::from(self.number_of_wheel_rotation_units as u8); // truncate
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "truncation intended"
|
||||
)]
|
||||
let truncated_wheel_rotation_units = self.number_of_wheel_rotation_units as u8;
|
||||
let wheel_rotations_bits = u16::from(truncated_wheel_rotation_units);
|
||||
|
||||
let flags = self.flags.bits() | wheel_negative_bit | wheel_rotations_bits;
|
||||
|
||||
|
|
@ -53,7 +60,12 @@ impl<'de> Decode<'de> for MousePdu {
|
|||
|
||||
let flags = PointerFlags::from_bits_truncate(flags_raw);
|
||||
|
||||
let wheel_rotations_bits = flags_raw as u8; // truncate
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "truncation intended"
|
||||
)]
|
||||
let wheel_rotations_bits = flags_raw as u8;
|
||||
|
||||
let number_of_wheel_rotation_units = if flags.contains(PointerFlags::WHEEL_NEGATIVE) {
|
||||
-i16::from(wheel_rotations_bits)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bitflags::bitflags;
|
||||
use ironrdp_core::{ensure_fixed_part_size, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MouseRelPdu {
|
||||
pub flags: PointerRelFlags,
|
||||
pub x_delta: i16,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bitflags::bitflags;
|
||||
use ironrdp_core::{ensure_fixed_part_size, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MouseXPdu {
|
||||
pub flags: PointerXFlags,
|
||||
pub x_position: u16,
|
||||
|
|
|
|||
|
|
@ -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 core::fmt;
|
||||
|
||||
|
|
@ -104,6 +100,10 @@ impl Action {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
|
||||
)]
|
||||
pub fn as_u8(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,10 @@ impl DomainMcsPdu {
|
|||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
|
@ -622,11 +626,7 @@ impl<'de> McsPdu<'de> for SendDataRequest<'de> {
|
|||
}
|
||||
|
||||
fn mcs_size(&self) -> usize {
|
||||
per::CHOICE_SIZE
|
||||
+ per::U16_SIZE * 2
|
||||
+ 1
|
||||
+ per::sizeof_length(u16::try_from(self.user_data.len()).unwrap_or(u16::MAX))
|
||||
+ self.user_data.len()
|
||||
per::CHOICE_SIZE + per::U16_SIZE * 2 + 1 + per::sizeof_length(self.user_data.len()) + self.user_data.len()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -703,11 +703,7 @@ impl<'de> McsPdu<'de> for SendDataIndication<'de> {
|
|||
}
|
||||
|
||||
fn mcs_size(&self) -> usize {
|
||||
per::CHOICE_SIZE
|
||||
+ per::U16_SIZE * 2
|
||||
+ 1
|
||||
+ per::sizeof_length(u16::try_from(self.user_data.len()).unwrap_or(u16::MAX))
|
||||
+ self.user_data.len()
|
||||
per::CHOICE_SIZE + per::U16_SIZE * 2 + 1 + per::sizeof_length(self.user_data.len()) + self.user_data.len()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -723,7 +719,11 @@ pub enum DisconnectReason {
|
|||
}
|
||||
|
||||
impl DisconnectReason {
|
||||
pub fn as_u8(self) -> u8 {
|
||||
#[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
|
||||
}
|
||||
|
||||
|
|
@ -944,7 +944,7 @@ mod legacy {
|
|||
|
||||
use std::io;
|
||||
|
||||
use ironrdp_core::{Decode, DecodeResult, Encode};
|
||||
use ironrdp_core::{cast_int, Decode, DecodeResult, Encode};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{
|
||||
|
|
@ -977,11 +977,15 @@ mod legacy {
|
|||
const NAME: &'static str = "ConnectInitial";
|
||||
|
||||
fn fields_buffer_ber_length(&self) -> usize {
|
||||
ber::sizeof_octet_string(self.calling_domain_selector.len() as u16)
|
||||
+ ber::sizeof_octet_string(self.called_domain_selector.len() as u16)
|
||||
+ ber::SIZEOF_BOOL
|
||||
+ (self.target_parameters.size() + self.min_parameters.size() + self.max_parameters.size())
|
||||
+ ber::sizeof_octet_string(self.conference_create_request.size() as u16)
|
||||
// Can't rewrite in `as`-less way, because it's used in `Encode::size` which doesn't return an error.
|
||||
#[expect(clippy::cast_possible_truncation, clippy::as_conversions)]
|
||||
{
|
||||
ber::sizeof_octet_string(self.calling_domain_selector.len() as u16)
|
||||
+ ber::sizeof_octet_string(self.called_domain_selector.len() as u16)
|
||||
+ ber::SIZEOF_BOOL
|
||||
+ (self.target_parameters.size() + self.min_parameters.size() + self.max_parameters.size())
|
||||
+ ber::sizeof_octet_string(self.conference_create_request.size() as u16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -989,7 +993,8 @@ mod legacy {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
ber::write_application_tag(dst, MCS_TYPE_CONNECT_INITIAL, self.fields_buffer_ber_length() as u16)?;
|
||||
let field_buffer_ber_length = cast_length!("field_buffer_ber_length", self.fields_buffer_ber_length())?;
|
||||
ber::write_application_tag(dst, MCS_TYPE_CONNECT_INITIAL, field_buffer_ber_length)?;
|
||||
ber::write_octet_string(dst, self.calling_domain_selector.as_ref())?;
|
||||
ber::write_octet_string(dst, self.called_domain_selector.as_ref())?;
|
||||
ber::write_bool(dst, self.upward_flag)?;
|
||||
|
|
@ -1008,9 +1013,12 @@ mod legacy {
|
|||
|
||||
fn size(&self) -> usize {
|
||||
let fields_buffer_ber_length = self.fields_buffer_ber_length();
|
||||
// Can't rewrite in `as`-less way, because it's used in `Encode::size` which doesn't return an error.
|
||||
#[expect(clippy::cast_possible_truncation, clippy::as_conversions)]
|
||||
let fields_buffer_ber_length_u16 = fields_buffer_ber_length as u16;
|
||||
|
||||
fields_buffer_ber_length
|
||||
+ ber::sizeof_application_tag(MCS_TYPE_CONNECT_INITIAL, fields_buffer_ber_length as u16)
|
||||
+ ber::sizeof_application_tag(MCS_TYPE_CONNECT_INITIAL, fields_buffer_ber_length_u16)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1042,10 +1050,14 @@ mod legacy {
|
|||
const NAME: &'static str = "ConnectResponse";
|
||||
|
||||
fn fields_buffer_ber_length(&self) -> usize {
|
||||
ber::SIZEOF_ENUMERATED
|
||||
+ ber::sizeof_integer(self.called_connect_id)
|
||||
+ self.domain_parameters.size()
|
||||
+ ber::sizeof_octet_string(self.conference_create_response.size() as u16)
|
||||
// Can't rewrite in `as`-less way, because it's used in `Encode::size` which doesn't return an error.
|
||||
#[expect(clippy::cast_possible_truncation, clippy::as_conversions)]
|
||||
{
|
||||
ber::SIZEOF_ENUMERATED
|
||||
+ ber::sizeof_integer(self.called_connect_id)
|
||||
+ self.domain_parameters.size()
|
||||
+ ber::sizeof_octet_string(self.conference_create_response.size() as u16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1053,7 +1065,8 @@ mod legacy {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
ber::write_application_tag(dst, MCS_TYPE_CONNECT_RESPONSE, self.fields_buffer_ber_length() as u16)?;
|
||||
let field_buffer_ber_length = cast_length!("field_buffer_ber_length", self.fields_buffer_ber_length())?;
|
||||
ber::write_application_tag(dst, MCS_TYPE_CONNECT_RESPONSE, field_buffer_ber_length)?;
|
||||
ber::write_enumerated(dst, 0)?;
|
||||
ber::write_integer(dst, self.called_connect_id)?;
|
||||
self.domain_parameters.encode(dst)?;
|
||||
|
|
@ -1069,9 +1082,11 @@ mod legacy {
|
|||
|
||||
fn size(&self) -> usize {
|
||||
let fields_buffer_ber_length = self.fields_buffer_ber_length();
|
||||
|
||||
// Can't rewrite in `as`-less way, because it's used in `Encode::size` which doesn't return an error.
|
||||
#[expect(clippy::cast_possible_truncation, clippy::as_conversions)]
|
||||
let fields_buffer_ber_length_u16 = fields_buffer_ber_length as u16;
|
||||
fields_buffer_ber_length
|
||||
+ ber::sizeof_application_tag(MCS_TYPE_CONNECT_RESPONSE, fields_buffer_ber_length as u16)
|
||||
+ ber::sizeof_application_tag(MCS_TYPE_CONNECT_RESPONSE, fields_buffer_ber_length_u16)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1079,7 +1094,7 @@ mod legacy {
|
|||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ber::read_application_tag(src, MCS_TYPE_CONNECT_RESPONSE)?;
|
||||
ber::read_enumerated(src, RESULT_ENUM_LENGTH)?;
|
||||
let called_connect_id = ber::read_integer(src)? as u32;
|
||||
let called_connect_id = cast_int!("called_connect_id", ber::read_integer(src)?)?;
|
||||
let domain_parameters = DomainParameters::decode(src)?;
|
||||
let _user_data_buffer_length = ber::read_octet_string_tag(src)?;
|
||||
let conference_create_response = ConferenceCreateResponse::decode(src)?;
|
||||
|
|
@ -1131,22 +1146,25 @@ mod legacy {
|
|||
fn size(&self) -> usize {
|
||||
let fields_buffer_ber_length = self.fields_buffer_ber_length();
|
||||
|
||||
// Can't rewrite in `as`-less way, because it's used in `Encode::size` which doesn't return an error.
|
||||
#[expect(clippy::cast_possible_truncation, clippy::as_conversions)]
|
||||
let fields_buffer_ber_length_u16 = fields_buffer_ber_length as u16;
|
||||
// FIXME: maybe size should return PduResult...
|
||||
fields_buffer_ber_length + ber::sizeof_sequence_tag(fields_buffer_ber_length as u16)
|
||||
fields_buffer_ber_length + ber::sizeof_sequence_tag(fields_buffer_ber_length_u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Decode<'de> for DomainParameters {
|
||||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ber::read_sequence_tag(src)?;
|
||||
let max_channel_ids = ber::read_integer(src)? as u32;
|
||||
let max_user_ids = ber::read_integer(src)? as u32;
|
||||
let max_token_ids = ber::read_integer(src)? as u32;
|
||||
let num_priorities = ber::read_integer(src)? as u32;
|
||||
let min_throughput = ber::read_integer(src)? as u32;
|
||||
let max_height = ber::read_integer(src)? as u32;
|
||||
let max_mcs_pdu_size = ber::read_integer(src)? as u32;
|
||||
let protocol_version = ber::read_integer(src)? as u32;
|
||||
let max_channel_ids = cast_int!("max_channel_ids", ber::read_integer(src)?)?;
|
||||
let max_user_ids = cast_int!("max_user_ids", ber::read_integer(src)?)?;
|
||||
let max_token_ids = cast_int!("max_token_ids", ber::read_integer(src)?)?;
|
||||
let num_priorities = cast_int!("num_priorities", ber::read_integer(src)?)?;
|
||||
let min_throughput = cast_int!("min_throughput", ber::read_integer(src)?)?;
|
||||
let max_height = cast_int!("max_height", ber::read_integer(src)?)?;
|
||||
let max_mcs_pdu_size = cast_int!("max_mcs_pdu_size", ber::read_integer(src)?)?;
|
||||
let protocol_version = cast_int!("protocol_version", ber::read_integer(src)?)?;
|
||||
|
||||
Ok(Self {
|
||||
max_channel_ids,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ pub(crate) fn write_long_length(dst: &mut WriteCursor<'_>, length: u16) {
|
|||
dst.write_u16_be(length | 0x8000)
|
||||
}
|
||||
|
||||
pub(crate) fn sizeof_length(length: u16) -> usize {
|
||||
pub(crate) fn sizeof_length(length: usize) -> usize {
|
||||
if length > 0x7f {
|
||||
2
|
||||
} else {
|
||||
|
|
@ -243,7 +243,10 @@ pub(crate) fn read_object_id(src: &mut ReadCursor<'_>) -> Result<[u8; OBJECT_ID_
|
|||
}
|
||||
|
||||
pub(crate) fn write_object_id(dst: &mut WriteCursor<'_>, object_ids: [u8; OBJECT_ID_SIZE]) {
|
||||
write_length(dst, OBJECT_ID_SIZE as u16 - 1);
|
||||
write_length(
|
||||
dst,
|
||||
u16::try_from(OBJECT_ID_SIZE).expect("OBJECT_ID_SIZE fits into u16") - 1,
|
||||
);
|
||||
|
||||
let first_two_tuples = object_ids[0] * 40 + object_ids[1];
|
||||
dst.write_u8(first_two_tuples);
|
||||
|
|
@ -338,8 +341,8 @@ pub(crate) mod legacy {
|
|||
Ok(2)
|
||||
}
|
||||
|
||||
pub(crate) fn write_short_length(mut stream: impl io::Write, length: u16) -> io::Result<usize> {
|
||||
stream.write_u8(length as u8)?;
|
||||
pub(crate) fn write_short_length(mut stream: impl io::Write, length: u8) -> io::Result<usize> {
|
||||
stream.write_u8(length)?;
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
|
|
@ -347,6 +350,12 @@ pub(crate) mod legacy {
|
|||
if length > 0x7f {
|
||||
write_long_length(stream, length)
|
||||
} else {
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "cast is valid due to prior check"
|
||||
)]
|
||||
let length = length as u8;
|
||||
write_short_length(stream, length)
|
||||
}
|
||||
}
|
||||
|
|
@ -413,12 +422,25 @@ pub(crate) mod legacy {
|
|||
pub(crate) fn write_u32(mut stream: impl io::Write, value: u32) -> io::Result<usize> {
|
||||
if value <= 0xff {
|
||||
let size = write_length(&mut stream, 1)?;
|
||||
stream.write_u8(value as u8)?;
|
||||
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "cast is valid due to prior check"
|
||||
)]
|
||||
let value = value as u8;
|
||||
stream.write_u8(value)?;
|
||||
|
||||
Ok(size + 1)
|
||||
} else if value <= 0xffff {
|
||||
let size = write_length(&mut stream, 2)?;
|
||||
stream.write_u16::<BigEndian>(value as u16)?;
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "cast is valid due to prior check"
|
||||
)]
|
||||
let value = value as u16;
|
||||
stream.write_u16::<BigEndian>(value)?;
|
||||
|
||||
Ok(size + 2)
|
||||
} else {
|
||||
|
|
@ -488,7 +510,10 @@ pub(crate) mod legacy {
|
|||
}
|
||||
|
||||
pub(crate) fn write_object_id(mut stream: impl io::Write, object_ids: [u8; OBJECT_ID_SIZE]) -> io::Result<usize> {
|
||||
let size = write_length(&mut stream, OBJECT_ID_SIZE as u16 - 1)?;
|
||||
let object_oid_size: u16 = OBJECT_ID_SIZE
|
||||
.try_into()
|
||||
.expect("OBJECT_ID_SIZE is known to fit into u16");
|
||||
let size = write_length(&mut stream, object_oid_size - 1)?;
|
||||
|
||||
let first_two_tuples = object_ids[0] * 40 + object_ids[1];
|
||||
stream.write_u8(first_two_tuples)?;
|
||||
|
|
@ -503,7 +528,7 @@ pub(crate) mod legacy {
|
|||
pub(crate) fn read_octet_string(mut stream: impl io::Read, min: usize) -> io::Result<Vec<u8>> {
|
||||
let (read_length, _) = read_length(&mut stream)?;
|
||||
|
||||
let mut read_octet_string = vec![0; min + read_length as usize];
|
||||
let mut read_octet_string = vec![0; min + usize::from(read_length)];
|
||||
stream.read_exact(read_octet_string.as_mut())?;
|
||||
|
||||
Ok(read_octet_string)
|
||||
|
|
@ -516,7 +541,9 @@ pub(crate) mod legacy {
|
|||
min
|
||||
};
|
||||
|
||||
let size = write_length(&mut stream, length as u16)?;
|
||||
let length = u16::try_from(length)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid octet string length"))?;
|
||||
let size = write_length(&mut stream, length)?;
|
||||
stream.write_all(octet_string)?;
|
||||
|
||||
Ok(size + octet_string.len())
|
||||
|
|
@ -527,7 +554,7 @@ pub(crate) mod legacy {
|
|||
|
||||
let length = (read_length + min).div_ceil(2);
|
||||
|
||||
let mut read_numeric_string = vec![0; length as usize];
|
||||
let mut read_numeric_string = vec![0; usize::from(length)];
|
||||
stream.read_exact(read_numeric_string.as_mut())?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -536,7 +563,9 @@ pub(crate) mod legacy {
|
|||
pub(crate) fn write_numeric_string(mut stream: impl io::Write, num_str: &[u8], min: usize) -> io::Result<usize> {
|
||||
let length = if num_str.len() >= min { num_str.len() - min } else { min };
|
||||
|
||||
let mut size = write_length(&mut stream, length as u16)?;
|
||||
let length = u16::try_from(length)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid numeric string length"))?;
|
||||
let mut size = write_length(&mut stream, length)?;
|
||||
|
||||
let magic_transform = |elem| (elem - 0x30) % 10;
|
||||
|
||||
|
|
|
|||
|
|
@ -141,10 +141,10 @@ impl<'de> Decode<'de> for BitmapCodecs {
|
|||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let codecs_count = src.read_u8();
|
||||
let codec_count = src.read_u8();
|
||||
|
||||
let mut codecs = Vec::with_capacity(codecs_count as usize);
|
||||
for _ in 0..codecs_count {
|
||||
let mut codecs = Vec::with_capacity(usize::from(codec_count));
|
||||
for _ in 0..codec_count {
|
||||
codecs.push(Codec::decode(src)?);
|
||||
}
|
||||
|
||||
|
|
@ -552,7 +552,7 @@ impl<'de> Decode<'de> for RfxCapset {
|
|||
let num_icaps = src.read_u16();
|
||||
let _icaps_len = src.read_u16();
|
||||
|
||||
let mut icaps_data = Vec::with_capacity(num_icaps as usize);
|
||||
let mut icaps_data = Vec::with_capacity(usize::from(num_icaps));
|
||||
for _ in 0..num_icaps {
|
||||
icaps_data.push(RfxICap::decode(src)?);
|
||||
}
|
||||
|
|
@ -215,9 +215,9 @@ impl<'de> Decode<'de> for DemandActive {
|
|||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let source_descriptor_length = src.read_u16() as usize;
|
||||
let source_descriptor_length = usize::from(src.read_u16());
|
||||
// The combined size in bytes of the numberCapabilities, pad2Octets, and capabilitySets fields.
|
||||
let _combined_capabilities_length = src.read_u16() as usize;
|
||||
let _combined_capabilities_length = usize::from(src.read_u16());
|
||||
|
||||
ensure_size!(in: src, size: source_descriptor_length);
|
||||
let source_descriptor = utils::decode_string(
|
||||
|
|
@ -227,7 +227,7 @@ impl<'de> Decode<'de> for DemandActive {
|
|||
)?;
|
||||
|
||||
ensure_size!(in: src, size: 2 + 2);
|
||||
let capability_sets_count = src.read_u16() as usize;
|
||||
let capability_sets_count = usize::from(src.read_u16());
|
||||
let _padding = src.read_u16();
|
||||
|
||||
let mut capability_sets = Vec::with_capacity(capability_sets_count);
|
||||
|
|
@ -509,7 +509,7 @@ impl<'de> Decode<'de> for CapabilitySet {
|
|||
)
|
||||
})?;
|
||||
|
||||
let length = src.read_u16() as usize;
|
||||
let length = usize::from(src.read_u16());
|
||||
|
||||
if length < CAPABILITY_SET_TYPE_FIELD_SIZE + CAPABILITY_SET_LENGTH_FIELD_SIZE {
|
||||
return Err(invalid_field_err!("len", "invalid capability set length"));
|
||||
|
|
@ -9,6 +9,7 @@ const ORD_LEVEL_1_ORDERS: u16 = 1;
|
|||
const SUPPORT_ARRAY_LEN: usize = 32;
|
||||
const DESKTOP_SAVE_Y_GRAN_VAL: u16 = 20;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum OrderSupportIndex {
|
||||
DstBlt = 0x00,
|
||||
|
|
@ -34,6 +35,16 @@ pub enum OrderSupportIndex {
|
|||
Index = 0x1B,
|
||||
}
|
||||
|
||||
impl OrderSupportIndex {
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct OrderFlags: u16 {
|
||||
|
|
@ -83,11 +94,11 @@ impl Order {
|
|||
}
|
||||
|
||||
pub fn set_support_flag(&mut self, flag: OrderSupportIndex, value: bool) {
|
||||
self.order_support[flag as usize] = u8::from(value)
|
||||
self.order_support[usize::from(flag.as_u8())] = u8::from(value)
|
||||
}
|
||||
|
||||
pub fn get_support_flag(&mut self, flag: OrderSupportIndex) -> bool {
|
||||
self.order_support[flag as usize] == 1
|
||||
self.order_support[usize::from(flag.as_u8())] == 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -30,27 +30,27 @@ lazy_static! {
|
|||
order_support: {
|
||||
let mut array = [0u8; 32];
|
||||
|
||||
array[OrderSupportIndex::DstBlt as usize] = 1;
|
||||
array[OrderSupportIndex::PatBlt as usize] = 1;
|
||||
array[OrderSupportIndex::ScrBlt as usize] = 1;
|
||||
array[OrderSupportIndex::MemBlt as usize] = 1;
|
||||
array[OrderSupportIndex::Mem3Blt as usize] = 1;
|
||||
array[OrderSupportIndex::DrawnInEGrid as usize] = 1;
|
||||
array[OrderSupportIndex::LineTo as usize] = 1;
|
||||
array[OrderSupportIndex::MultiDrawnInEGrid as usize] = 1;
|
||||
array[OrderSupportIndex::SaveBitmap as usize] = 1;
|
||||
array[OrderSupportIndex::MultiDstBlt as usize] = 1;
|
||||
array[OrderSupportIndex::MultiPatBlt as usize] = 1;
|
||||
array[OrderSupportIndex::MultiScrBlt as usize] = 1;
|
||||
array[OrderSupportIndex::MultiOpaqueRect as usize] = 1;
|
||||
array[OrderSupportIndex::Fast as usize] = 1;
|
||||
array[OrderSupportIndex::PolygonSC as usize] = 1;
|
||||
array[OrderSupportIndex::PolygonCB as usize] = 1;
|
||||
array[OrderSupportIndex::Polyline as usize] = 1;
|
||||
array[OrderSupportIndex::FastGlyph as usize] = 1;
|
||||
array[OrderSupportIndex::EllipseSC as usize] = 1;
|
||||
array[OrderSupportIndex::EllipseCB as usize] = 1;
|
||||
array[OrderSupportIndex::Index as usize] = 1;
|
||||
array[usize::from(OrderSupportIndex::DstBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::PatBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::ScrBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MemBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::Mem3Blt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::DrawnInEGrid.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::LineTo.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MultiDrawnInEGrid.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::SaveBitmap.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MultiDstBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MultiPatBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MultiScrBlt.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::MultiOpaqueRect.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::Fast.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::PolygonSC.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::PolygonCB.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::Polyline.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::FastGlyph.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::EllipseSC.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::EllipseCB.as_u8())] = 1;
|
||||
array[usize::from(OrderSupportIndex::Index.as_u8())] = 1;
|
||||
|
||||
array
|
||||
},
|
||||
|
|
|
|||
|
|
@ -145,9 +145,8 @@ impl<'de> Decode<'de> for ClientInfo {
|
|||
|
||||
let flags = ClientInfoFlags::from_bits(flags_with_compression_type & !COMPRESSION_TYPE_MASK)
|
||||
.ok_or_else(|| invalid_field_err!("flags", "invalid ClientInfoFlags"))?;
|
||||
let compression_type =
|
||||
CompressionType::from_u8(((flags_with_compression_type & COMPRESSION_TYPE_MASK) >> 9) as u8)
|
||||
.ok_or_else(|| invalid_field_err!("flags", "invalid CompressionType"))?;
|
||||
let compression_type = CompressionType::from_u32((flags_with_compression_type & COMPRESSION_TYPE_MASK) >> 9)
|
||||
.ok_or_else(|| invalid_field_err!("flags", "invalid CompressionType"))?;
|
||||
|
||||
let character_set = if flags.contains(ClientInfoFlags::UNICODE) {
|
||||
CharacterSet::Unicode
|
||||
|
|
@ -157,11 +156,11 @@ impl<'de> Decode<'de> for ClientInfo {
|
|||
|
||||
// Sizes exclude the length of the mandatory null terminator
|
||||
let nt = usize::from(character_set.as_u16());
|
||||
let domain_size = src.read_u16() as usize + nt;
|
||||
let user_name_size = src.read_u16() as usize + nt;
|
||||
let password_size = src.read_u16() as usize + nt;
|
||||
let alternate_shell_size = src.read_u16() as usize + nt;
|
||||
let work_dir_size = src.read_u16() as usize + nt;
|
||||
let domain_size = usize::from(src.read_u16()) + nt;
|
||||
let user_name_size = usize::from(src.read_u16()) + nt;
|
||||
let password_size = usize::from(src.read_u16()) + nt;
|
||||
let alternate_shell_size = usize::from(src.read_u16()) + nt;
|
||||
let work_dir_size = usize::from(src.read_u16()) + nt;
|
||||
ensure_size!(in: src, size: domain_size + user_name_size + password_size + alternate_shell_size + work_dir_size);
|
||||
|
||||
let domain = utils::decode_string(src.read_slice(domain_size), character_set, true)?;
|
||||
|
|
@ -226,12 +225,12 @@ impl ExtendedClientInfo {
|
|||
let address_family = AddressFamily::from_u16(src.read_u16());
|
||||
|
||||
// This size includes the length of the mandatory null terminator.
|
||||
let address_size = src.read_u16() as usize;
|
||||
let address_size = usize::from(src.read_u16());
|
||||
ensure_size!(in: src, size: address_size + CLIENT_DIR_LENGTH_SIZE);
|
||||
|
||||
let address = utils::decode_string(src.read_slice(address_size), character_set, false)?;
|
||||
// This size includes the length of the mandatory null terminator.
|
||||
let dir_size = src.read_u16() as usize;
|
||||
let dir_size = usize::from(src.read_u16());
|
||||
ensure_size!(in: src, size: dir_size);
|
||||
|
||||
let dir = utils::decode_string(src.read_slice(dir_size), character_set, false)?;
|
||||
|
|
@ -324,7 +323,7 @@ impl Encode for ExtendedClientOptionalInfo {
|
|||
dst.write_u32(performance_flags.bits());
|
||||
}
|
||||
if let Some(reconnect_cookie) = self.reconnect_cookie {
|
||||
dst.write_u16(RECONNECT_COOKIE_LEN as u16);
|
||||
dst.write_u16(u16::try_from(RECONNECT_COOKIE_LEN).expect("RECONNECT_COOKIE_LEN fit into u16"));
|
||||
dst.write_array(reconnect_cookie);
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +380,9 @@ impl<'de> Decode<'de> for ExtendedClientOptionalInfo {
|
|||
return Ok(optional_data);
|
||||
}
|
||||
let reconnect_cookie_size = src.read_u16();
|
||||
if reconnect_cookie_size != RECONNECT_COOKIE_LEN as u16 && reconnect_cookie_size != 0 {
|
||||
if reconnect_cookie_size != u16::try_from(RECONNECT_COOKIE_LEN).expect("RECONNECT_COOKIE_LEN fit into u16")
|
||||
&& reconnect_cookie_size != 0
|
||||
{
|
||||
return Err(invalid_field_err!("cbAutoReconnectCookie", "invalid cookie size"));
|
||||
}
|
||||
if reconnect_cookie_size != 0 {
|
||||
|
|
|
|||
|
|
@ -220,7 +220,10 @@ impl<'de> Decode<'de> for MonitorLayoutPdu {
|
|||
return Err(invalid_field_err!("nMonitors", "invalid monitor count"));
|
||||
}
|
||||
|
||||
let mut monitors = Vec::with_capacity(monitor_count as usize);
|
||||
let mut monitors = Vec::with_capacity(
|
||||
usize::try_from(monitor_count)
|
||||
.expect("monitor_count is guaranteed to fit into usize due to the prior check"),
|
||||
);
|
||||
for _ in 0..monitor_count {
|
||||
monitors.push(gcc::Monitor::decode(src)?);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ impl<'de> Decode<'de> for ShareControlHeader {
|
|||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let total_length = src.read_u16() as usize;
|
||||
let total_length = usize::from(src.read_u16());
|
||||
let pdu_type_with_version = src.read_u16();
|
||||
let pdu_source = src.read_u16();
|
||||
let share_id = src.read_u32();
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl ClientLicenseInfo {
|
|||
let mut rc4 = Rc4::new(&license_key);
|
||||
let encrypted_hwid = rc4.process(&hardware_id);
|
||||
|
||||
let mac_data = compute_mac_data(mac_salt_key, &hardware_id);
|
||||
let mac_data = compute_mac_data(mac_salt_key, &hardware_id)?;
|
||||
|
||||
let size = RANDOM_NUMBER_SIZE
|
||||
+ PREAMBLE_SIZE
|
||||
|
|
@ -97,7 +97,8 @@ impl ClientLicenseInfo {
|
|||
preamble_message_type: PreambleType::LicenseInfo,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (size) as u16,
|
||||
preamble_message_size: u16::try_from(size)
|
||||
.map_err(|_| ServerLicenseError::InvalidField("preamble message size"))?,
|
||||
};
|
||||
|
||||
Ok((
|
||||
|
|
|
|||
|
|
@ -98,14 +98,17 @@ impl ClientNewLicenseRequest {
|
|||
preamble_message_type: PreambleType::NewLicenseRequest,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (RANDOM_NUMBER_SIZE
|
||||
+ PREAMBLE_SIZE
|
||||
+ LICENSE_REQUEST_STATIC_FIELDS_SIZE
|
||||
+ encrypted_premaster_secret.len()
|
||||
+ client_machine_name.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE
|
||||
+ client_username.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE) as u16,
|
||||
preamble_message_size: u16::try_from(
|
||||
RANDOM_NUMBER_SIZE
|
||||
+ PREAMBLE_SIZE
|
||||
+ LICENSE_REQUEST_STATIC_FIELDS_SIZE
|
||||
+ encrypted_premaster_secret.len()
|
||||
+ client_machine_name.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE
|
||||
+ client_username.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE,
|
||||
)
|
||||
.map_err(|_| ServerLicenseError::InvalidField("preamble message size"))?,
|
||||
};
|
||||
|
||||
Ok((
|
||||
|
|
@ -68,14 +68,14 @@ lazy_static! {
|
|||
preamble_message_type: PreambleType::NewLicenseRequest,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (PREAMBLE_SIZE
|
||||
preamble_message_size: u16::try_from(PREAMBLE_SIZE
|
||||
+ RANDOM_NUMBER_SIZE
|
||||
+ LICENSE_REQUEST_STATIC_FIELDS_SIZE
|
||||
+ ENCRYPTED_PREMASTER_SECRET.len()
|
||||
+ CLIENT_MACHINE_NAME.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE
|
||||
+ CLIENT_USERNAME.len()
|
||||
+ UTF8_NULL_TERMINATOR_SIZE) as u16,
|
||||
+ UTF8_NULL_TERMINATOR_SIZE).expect("can't panic"),
|
||||
},
|
||||
client_random: Vec::from(CLIENT_RANDOM_BUFFER.as_ref()),
|
||||
encrypted_premaster_secret: Vec::from(ENCRYPTED_PREMASTER_SECRET.as_ref()),
|
||||
|
|
@ -86,11 +86,11 @@ lazy_static! {
|
|||
pub static ref REQUEST_BUFFER: Vec<u8> = {
|
||||
let username_len = CLIENT_USERNAME.len() + UTF8_NULL_TERMINATOR_SIZE;
|
||||
let mut username_len_buf = Vec::new();
|
||||
username_len_buf.write_u16::<LittleEndian>(username_len as u16).unwrap();
|
||||
username_len_buf.write_u16::<LittleEndian>(u16::try_from(username_len).expect("can't panic")).unwrap();
|
||||
|
||||
let machine_name_len = CLIENT_MACHINE_NAME.len() + UTF8_NULL_TERMINATOR_SIZE;
|
||||
let mut machine_name_len_buf = Vec::new();
|
||||
machine_name_len_buf.write_u16::<LittleEndian>(machine_name_len as u16).unwrap();
|
||||
machine_name_len_buf.write_u16::<LittleEndian>(u16::try_from(machine_name_len).unwrap()).unwrap();
|
||||
|
||||
let buf = [
|
||||
&[0x01u8, 0x00, 0x00, 0x00, // preferred_key_exchange_algorithm
|
||||
|
|
@ -109,7 +109,7 @@ lazy_static! {
|
|||
&[0x00]] // null
|
||||
.concat();
|
||||
|
||||
let preamble_size_field = (buf.len() + PREAMBLE_SIZE) as u16;
|
||||
let preamble_size_field = u16::try_from(buf.len() + PREAMBLE_SIZE).expect("can't panic");
|
||||
|
||||
[
|
||||
LICENSE_HEADER_BUFFER_NO_SIZE.as_ref(),
|
||||
|
|
@ -270,7 +270,7 @@ lazy_static! {
|
|||
}),
|
||||
scope_list: vec![Scope(String::from("microsoft.com"))],
|
||||
};
|
||||
req.license_header.preamble_message_size = req.size() as u16;
|
||||
req.license_header.preamble_message_size = u16::try_from(req.size()).expect("can't panic");
|
||||
req.into()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ impl ClientPlatformChallengeResponse {
|
|||
let decrypted_challenge = rc4.process(platform_challenge.encrypted_platform_challenge.as_slice());
|
||||
|
||||
let decrypted_challenge_mac =
|
||||
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_challenge.as_slice());
|
||||
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_challenge.as_slice())?;
|
||||
|
||||
if decrypted_challenge_mac != platform_challenge.mac_data {
|
||||
return Err(ServerLicenseError::InvalidMacData);
|
||||
|
|
@ -56,7 +56,10 @@ impl ClientPlatformChallengeResponse {
|
|||
challenge_response_data.write_u16::<LittleEndian>(RESPONSE_DATA_VERSION)?;
|
||||
challenge_response_data.write_u16::<LittleEndian>(ClientType::Other.as_u16())?;
|
||||
challenge_response_data.write_u16::<LittleEndian>(LicenseDetailLevel::Detail.as_u16())?;
|
||||
challenge_response_data.write_u16::<LittleEndian>(decrypted_challenge.len() as u16)?;
|
||||
challenge_response_data.write_u16::<LittleEndian>(
|
||||
u16::try_from(decrypted_challenge.len())
|
||||
.map_err(|_| ServerLicenseError::InvalidField("decrypted challenge len"))?,
|
||||
)?;
|
||||
challenge_response_data.write_all(&decrypted_challenge)?;
|
||||
|
||||
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
||||
|
|
@ -75,7 +78,7 @@ impl ClientPlatformChallengeResponse {
|
|||
let mac_data = super::compute_mac_data(
|
||||
encryption_data.mac_salt_key.as_slice(),
|
||||
challenge_response_data.as_slice(),
|
||||
);
|
||||
)?;
|
||||
|
||||
let license_header = LicenseHeader {
|
||||
security_header: BasicSecurityHeader {
|
||||
|
|
@ -84,10 +87,12 @@ impl ClientPlatformChallengeResponse {
|
|||
preamble_message_type: PreambleType::PlatformChallengeResponse,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (PREAMBLE_SIZE
|
||||
preamble_message_size: u16::try_from(
|
||||
PREAMBLE_SIZE
|
||||
+ (BLOB_TYPE_SIZE + BLOB_LENGTH_SIZE) * 2 // 2 blobs in this structure
|
||||
+ MAC_SIZE + encrypted_challenge_response_data.len() + encrypted_hwid.len())
|
||||
as u16,
|
||||
+ MAC_SIZE + encrypted_challenge_response_data.len() + encrypted_hwid.len(),
|
||||
)
|
||||
.map_err(|_| ServerLicenseError::InvalidField("preamble message size"))?,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -63,8 +63,10 @@ lazy_static! {
|
|||
preamble_message_type: PreambleType::PlatformChallengeResponse,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (CLIENT_PLATFORM_CHALLENGE_RESPONSE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE)
|
||||
as u16,
|
||||
preamble_message_size: u16::try_from(
|
||||
CLIENT_PLATFORM_CHALLENGE_RESPONSE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE
|
||||
)
|
||||
.expect("can't panic"),
|
||||
},
|
||||
encrypted_challenge_response_data: Vec::from(&CLIENT_PLATFORM_CHALLENGE_RESPONSE_BUFFER[12..30]),
|
||||
encrypted_hwid: Vec::from(&CLIENT_PLATFORM_CHALLENGE_RESPONSE_BUFFER[34..54]),
|
||||
|
|
@ -165,7 +167,8 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
|
|||
preamble_message_type: PreambleType::PlatformChallenge,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (encrypted_platform_challenge.len() + mac_data.len() + PREAMBLE_SIZE) as u16,
|
||||
preamble_message_size: u16::try_from(encrypted_platform_challenge.len() + mac_data.len() + PREAMBLE_SIZE)
|
||||
.expect("can't panic"),
|
||||
},
|
||||
encrypted_platform_challenge,
|
||||
mac_data,
|
||||
|
|
@ -200,7 +203,8 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
|
|||
let mac_data = crate::rdp::server_license::compute_mac_data(
|
||||
encryption_data.mac_salt_key.as_slice(),
|
||||
[response_data.as_ref(), hardware_id.as_slice()].concat().as_slice(),
|
||||
);
|
||||
)
|
||||
.expect("can't panic");
|
||||
|
||||
let correct_challenge_response = ClientPlatformChallengeResponse {
|
||||
license_header: LicenseHeader {
|
||||
|
|
@ -210,10 +214,12 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
|
|||
preamble_message_type: PreambleType::PlatformChallengeResponse,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (PREAMBLE_SIZE
|
||||
preamble_message_size: u16::try_from(
|
||||
PREAMBLE_SIZE
|
||||
+ (BLOB_TYPE_SIZE + BLOB_LENGTH_SIZE) * 2 // 2 blobs in this structure
|
||||
+ MAC_SIZE + encrypted_challenge_response_data.len() + encrypted_hwid.len())
|
||||
as u16,
|
||||
+ MAC_SIZE + encrypted_challenge_response_data.len() + encrypted_hwid.len(),
|
||||
)
|
||||
.expect("can't panic"),
|
||||
},
|
||||
encrypted_challenge_response_data,
|
||||
encrypted_hwid,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,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()).expect("can't panic");
|
||||
pdu.into()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,8 @@ pub enum ServerLicenseError {
|
|||
Utf8Error(#[from] std::string::FromUtf8Error),
|
||||
#[error("DER error: {0}")]
|
||||
DerError(#[from] pkcs1::der::Error),
|
||||
#[error("invalid `{0}`: out of range integral type conversion")]
|
||||
InvalidField(&'static str),
|
||||
#[error("invalid preamble field: {0}")]
|
||||
InvalidPreamble(String),
|
||||
#[error("invalid preamble message type field")]
|
||||
|
|
@ -338,8 +340,10 @@ impl<'de> Decode<'de> for BlobHeader {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_mac_data(mac_salt_key: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
let data_len_buffer = (data.len() as u32).to_le_bytes();
|
||||
fn compute_mac_data(mac_salt_key: &[u8], data: &[u8]) -> Result<Vec<u8>, ServerLicenseError> {
|
||||
let data_len_buffer = u32::try_from(data.len())
|
||||
.map_err(|_| ServerLicenseError::InvalidField("MAC data length"))?
|
||||
.to_le_bytes();
|
||||
|
||||
let pad_one: [u8; 40] = [0x36; 40];
|
||||
|
||||
|
|
@ -360,7 +364,7 @@ fn compute_mac_data(mac_salt_key: &[u8], data: &[u8]) -> Vec<u8> {
|
|||
.as_slice(),
|
||||
);
|
||||
|
||||
md5.finalize().to_vec()
|
||||
Ok(md5.finalize().to_vec())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
@ -108,7 +108,10 @@ impl ServerLicenseRequest {
|
|||
return Err(invalid_field_err!("scopeCount", "invalid scope count"));
|
||||
}
|
||||
|
||||
let mut scope_list = Vec::with_capacity(scope_count as usize);
|
||||
let mut scope_list = Vec::with_capacity(
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer underflow)")]
|
||||
usize::try_from(scope_count).expect("scope_count is guaranteed to fit into usize due to the prior check"),
|
||||
);
|
||||
|
||||
for _ in 0..scope_count {
|
||||
scope_list.push(Scope::decode(src)?);
|
||||
|
|
@ -252,7 +252,7 @@ lazy_static! {
|
|||
}),
|
||||
scope_list: vec![Scope(String::from("microsoft.com"))],
|
||||
};
|
||||
req.license_header.preamble_message_size = req.size() as u16;
|
||||
req.license_header.preamble_message_size = u16::try_from(req.size()).expect("can't panic");
|
||||
req.into()
|
||||
};
|
||||
pub static ref X509_CERTIFICATE: ServerCertificate = ServerCertificate {
|
||||
|
|
@ -323,7 +323,7 @@ fn from_buffer_correctly_parses_server_license_request_no_certificate() {
|
|||
server_certificate: None,
|
||||
scope_list: vec![Scope(String::from("microsoft.com"))],
|
||||
};
|
||||
request.license_header.preamble_message_size = request.size() as u16;
|
||||
request.license_header.preamble_message_size = u16::try_from(request.size()).expect("can't panic");
|
||||
let request: LicensePdu = request.into();
|
||||
|
||||
assert_eq!(request, decode(&request_buffer).unwrap());
|
||||
|
|
@ -370,7 +370,7 @@ fn to_buffer_correctly_serializes_server_license_request() {
|
|||
}),
|
||||
scope_list: vec![Scope(String::from("microsoft.com"))],
|
||||
};
|
||||
request.license_header.preamble_message_size = request.size() as u16;
|
||||
request.license_header.preamble_message_size = u16::try_from(request.size()).unwrap();
|
||||
let request: LicensePdu = request.into();
|
||||
|
||||
let serialized_request = encode_vec(&request).unwrap();
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ lazy_static! {
|
|||
preamble_message_type: PreambleType::PlatformChallenge,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (PLATFORM_CHALLENGE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE) as u16,
|
||||
preamble_message_size: u16::try_from(PLATFORM_CHALLENGE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE)
|
||||
.expect("can't panic"),
|
||||
},
|
||||
encrypted_platform_challenge: Vec::from(CHALLENGE_BUFFER.as_ref()),
|
||||
mac_data: Vec::from(MAC_DATA_BUFFER.as_ref()),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl ServerUpgradeLicense {
|
|||
pub fn verify_server_license(&self, encryption_data: &LicenseEncryptionData) -> Result<(), ServerLicenseError> {
|
||||
let decrypted_license_info = self.decrypted_license_info(encryption_data);
|
||||
let mac_data =
|
||||
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_license_info.as_ref());
|
||||
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_license_info.as_ref())?;
|
||||
|
||||
if mac_data != self.mac_data {
|
||||
return Err(ServerLicenseError::InvalidMacData);
|
||||
|
|
@ -260,7 +260,8 @@ lazy_static! {
|
|||
preamble_message_type: PreambleType::NewLicense,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (SERVER_UPGRADE_LICENSE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE) as u16,
|
||||
preamble_message_size: u16::try_from(SERVER_UPGRADE_LICENSE_BUFFER.len() - BASIC_SECURITY_HEADER_SIZE)
|
||||
.expect("buffer size is too large"),
|
||||
},
|
||||
encrypted_license_info: Vec::from(
|
||||
&SERVER_UPGRADE_LICENSE_BUFFER[12..SERVER_UPGRADE_LICENSE_BUFFER.len() - MAC_SIZE]
|
||||
|
|
@ -618,11 +619,10 @@ fn upgrade_license_verifies_correctly() {
|
|||
preamble_message_type: PreambleType::NewLicense,
|
||||
preamble_flags: PreambleFlags::empty(),
|
||||
preamble_version: PreambleVersion::V3,
|
||||
preamble_message_size: (PREAMBLE_SIZE
|
||||
+ BLOB_LENGTH_SIZE
|
||||
+ BLOB_TYPE_SIZE
|
||||
+ encrypted_license_info.len()
|
||||
+ MAC_SIZE) as u16,
|
||||
preamble_message_size: u16::try_from(
|
||||
PREAMBLE_SIZE + BLOB_LENGTH_SIZE + BLOB_TYPE_SIZE + encrypted_license_info.len() + MAC_SIZE,
|
||||
)
|
||||
.expect("can't panic"),
|
||||
},
|
||||
encrypted_license_info,
|
||||
mac_data,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ fn mac_data_computes_correctly() {
|
|||
let decrypted_server_challenge: [u8; 10] = [0x54, 0x0, 0x45, 0x0, 0x53, 0x0, 0x54, 0x0, 0x0, 0x0];
|
||||
|
||||
assert_eq!(
|
||||
compute_mac_data(mac_salt_key.as_ref(), decrypted_server_challenge.as_ref()),
|
||||
compute_mac_data(mac_salt_key.as_ref(), decrypted_server_challenge.as_ref()).unwrap(),
|
||||
server_mac_data.as_ref()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,8 +112,8 @@ impl Encode for ServerAutoReconnect {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_fixed_part_size!(in: dst);
|
||||
|
||||
dst.write_u32(AUTO_RECONNECT_PACKET_SIZE as u32);
|
||||
dst.write_u32(AUTO_RECONNECT_PACKET_SIZE as u32);
|
||||
dst.write_u32(u32::try_from(AUTO_RECONNECT_PACKET_SIZE).expect("AUTO_RECONNECT_PACKET_SIZE fits into u32"));
|
||||
dst.write_u32(u32::try_from(AUTO_RECONNECT_PACKET_SIZE).expect("AUTO_RECONNECT_PACKET_SIZE fits into u32"));
|
||||
dst.write_u32(AUTO_RECONNECT_VERSION_1);
|
||||
dst.write_u32(self.logon_id);
|
||||
dst.write_slice(self.random_bits.as_ref());
|
||||
|
|
@ -136,7 +136,8 @@ impl<'de> Decode<'de> for ServerAutoReconnect {
|
|||
|
||||
let _data_length = src.read_u32();
|
||||
let packet_length = src.read_u32();
|
||||
if packet_length != AUTO_RECONNECT_PACKET_SIZE as u32 {
|
||||
if packet_length != u32::try_from(AUTO_RECONNECT_PACKET_SIZE).expect("AUTO_RECONNECT_PACKET_SIZE fits into u32")
|
||||
{
|
||||
return Err(invalid_field_err!("packetLen", "invalid auto-reconnect packet size"));
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +172,7 @@ impl Encode for LogonErrorsInfo {
|
|||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||
ensure_fixed_part_size!(in: dst);
|
||||
|
||||
dst.write_u32(LOGON_ERRORS_INFO_SIZE as u32);
|
||||
dst.write_u32(u32::try_from(LOGON_ERRORS_INFO_SIZE).expect("LOGON_ERRORS_INFO_SIZE fits into u32"));
|
||||
dst.write_u32(self.error_type.as_u32());
|
||||
dst.write_u32(self.error_data.to_u32());
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ impl Encode for LogonInfoVersion2 {
|
|||
ensure_size!(in: dst, size: self.size());
|
||||
|
||||
dst.write_u16(SAVE_SESSION_PDU_VERSION_ONE);
|
||||
dst.write_u32(LOGON_INFO_V2_SIZE as u32);
|
||||
dst.write_u32(u32::try_from(LOGON_INFO_V2_SIZE).expect("LOGON_INFO_V2_SIZE fits into u32"));
|
||||
dst.write_u32(self.logon_info.session_id);
|
||||
dst.write_u32(cast_length!(
|
||||
"domainNameSize",
|
||||
|
|
|
|||
|
|
@ -112,9 +112,9 @@ impl<'de> Decode<'de> for Avc420BitmapStream<'de> {
|
|||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||
ensure_fixed_part_size!(in: src);
|
||||
|
||||
let num_regions = src.read_u32();
|
||||
let mut rectangles = Vec::with_capacity(num_regions as usize);
|
||||
let mut quant_qual_vals = Vec::with_capacity(num_regions as usize);
|
||||
let num_regions = cast_length!("number of regions", src.read_u32())?;
|
||||
let mut rectangles = Vec::with_capacity(num_regions);
|
||||
let mut quant_qual_vals = Vec::with_capacity(num_regions);
|
||||
for _ in 0..num_regions {
|
||||
rectangles.push(InclusiveRectangle::decode(src)?);
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ impl Encode for Avc444BitmapStream<'_> {
|
|||
|
||||
let mut stream_info = 0u32;
|
||||
stream_info.set_bits(0..30, cast_length!("stream1size", self.stream1.size())?);
|
||||
stream_info.set_bits(30..32, self.encoding.bits() as u32);
|
||||
stream_info.set_bits(30..32, u32::from(self.encoding.bits()));
|
||||
dst.write_u32(stream_info);
|
||||
self.stream1.encode(dst)?;
|
||||
if let Some(stream) = self.stream2.as_ref() {
|
||||
|
|
@ -188,7 +188,8 @@ impl<'de> Decode<'de> for Avc444BitmapStream<'de> {
|
|||
|
||||
let stream_info = src.read_u32();
|
||||
let stream_len = stream_info.get_bits(0..30);
|
||||
let encoding = Encoding::from_bits_truncate(stream_info.get_bits(30..32) as u8);
|
||||
let encoding =
|
||||
Encoding::from_bits_truncate(u8::try_from(stream_info.get_bits(30..32)).expect("value fits into u8"));
|
||||
|
||||
if stream_len == 0 {
|
||||
if encoding == Encoding::LUMA_AND_CHROMA {
|
||||
|
|
@ -202,7 +203,7 @@ impl<'de> Decode<'de> for Avc444BitmapStream<'de> {
|
|||
stream2: None,
|
||||
})
|
||||
} else {
|
||||
let (mut stream1, mut stream2) = src.split_at(stream_len as usize);
|
||||
let (mut stream1, mut stream2) = src.split_at(cast_length!("first stream length", stream_len)?);
|
||||
let stream1 = Avc420BitmapStream::decode(&mut stream1)?;
|
||||
let stream2 = if encoding == Encoding::LUMA_AND_CHROMA {
|
||||
Some(Avc420BitmapStream::decode(&mut stream2)?)
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ impl Encode for SolidFillPdu {
|
|||
|
||||
dst.write_u16(self.surface_id);
|
||||
self.fill_pixel.encode(dst)?;
|
||||
dst.write_u16(self.rectangles.len() as u16);
|
||||
dst.write_u16(cast_length!("number of rectangles", self.rectangles.len())?);
|
||||
|
||||
for rectangle in self.rectangles.iter() {
|
||||
rectangle.encode(dst)?;
|
||||
|
|
@ -1026,10 +1026,10 @@ impl<'a> Decode<'a> for Timestamp {
|
|||
|
||||
let timestamp = src.read_u32();
|
||||
|
||||
let milliseconds = timestamp.get_bits(..10) as u16;
|
||||
let seconds = timestamp.get_bits(10..16) as u8;
|
||||
let minutes = timestamp.get_bits(16..22) as u8;
|
||||
let hours = timestamp.get_bits(22..) as u16;
|
||||
let milliseconds = u16::try_from(timestamp.get_bits(..10)).expect("value fits into u16");
|
||||
let seconds = u8::try_from(timestamp.get_bits(10..16)).expect("value fits into u8");
|
||||
let minutes = u8::try_from(timestamp.get_bits(16..22)).expect("value fits into u8");
|
||||
let hours = u16::try_from(timestamp.get_bits(22..)).expect("value fits into u16");
|
||||
|
||||
Ok(Self {
|
||||
milliseconds,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@ use num_derive::FromPrimitive;
|
|||
use crate::{DecodeResult, EncodeResult};
|
||||
|
||||
pub fn split_u64(value: u64) -> (u32, u32) {
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer downcast)")]
|
||||
let low =
|
||||
u32::try_from(value & 0xFFFF_FFFF).expect("masking with 0xFFFF_FFFF ensures that the value fits into u32");
|
||||
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer downcast)")]
|
||||
let high = u32::try_from(value >> 32).expect("(u64 >> 32) fits into u32");
|
||||
|
||||
(low, high)
|
||||
|
|
@ -31,6 +34,8 @@ pub fn to_utf16_bytes(value: &str) -> Vec<u8> {
|
|||
|
||||
pub fn from_utf16_bytes(mut value: &[u8]) -> String {
|
||||
let mut value_u16 = vec![0x00; value.len() / 2];
|
||||
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (prior constrain)")]
|
||||
value
|
||||
.read_u16_into::<LittleEndian>(value_u16.as_mut())
|
||||
.expect("read_u16_into cannot fail at this point");
|
||||
|
|
@ -106,6 +111,7 @@ pub fn read_string_from_cursor(
|
|||
let str_buffer = &mut slice;
|
||||
let mut u16_buffer = vec![0u16; str_buffer.len() / 2];
|
||||
|
||||
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (prior constrain)")]
|
||||
str_buffer
|
||||
.read_u16_into::<LittleEndian>(u16_buffer.as_mut())
|
||||
.expect("BUG: str_buffer is always even for UTF16");
|
||||
|
|
@ -295,7 +301,12 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
// Utility function that panics on overflow
|
||||
/// Utility function that panics on overflow
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if sum of values overflows.
|
||||
// FIXME: Is it really something we want to expose from ironrdp-pdu?
|
||||
pub fn strict_sum<T>(values: &[T]) -> T
|
||||
where
|
||||
T: CheckedAdd + Copy + Debug,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdcleanpath-v0.2.0...ironrdp-rdcleanpath-v0.2.1)] - 2025-10-02
|
||||
|
||||
### <!-- 1 -->Features
|
||||
|
||||
- Human-readable descriptions for RDCleanPath errors (#999) ([18c81ed5d8](https://github.com/Devolutions/IronRDP/commit/18c81ed5d8d3bf13b3d10fe15209233c0c10bb62))
|
||||
|
||||
More munging to give human-readable webclient-side errors for
|
||||
RDCleanPath general/negotiation errors, including strings for WSA and
|
||||
TLS and HTTP error conditions.
|
||||
|
||||
## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdcleanpath-v0.1.3...ironrdp-rdcleanpath-v0.2.0)] - 2025-08-29
|
||||
|
||||
### <!-- 1 -->Features
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ironrdp-rdcleanpath"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
readme = "README.md"
|
||||
description = "RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway"
|
||||
edition.workspace = true
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue