//! ORPC structures shared by `IObjectExporter` and `IRemUnknown` requests. //! //! Direct port of `src/MxNativeClient/OrpcStructures.cs`. Provides: //! //! - [`ComVersion`] — 4-byte (Major u16, Minor u16) DCOM version pair. //! - [`OrpcThis`] — 32-byte ORPC request header (`OrpcStructures.cs:10-52`). //! - [`OrpcThat`] — 8-byte ORPC response header (`OrpcStructures.cs:54-77`). //! - [`MInterfacePointer`] — length-prefixed OBJREF wrapper //! (`OrpcStructures.cs:79-109`). //! - [`StdObjRef`] — 40-byte STDOBJREF body (`OrpcStructures.cs:111-140`). //! //! All multi-byte fields are little-endian. //! //! These types are M2 wave 2 prerequisites for [`crate::object_exporter`] and //! [`crate::rem_unknown`]; the wave 2 agents import them rather than each //! defining their own ORPC framing. #![allow(clippy::indexing_slicing)] use crate::error::RpcError; use crate::guid::Guid; /// `OrpcStructures.cs:5-8` — DCOM version pair. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ComVersion { pub major: u16, pub minor: u16, } impl ComVersion { /// Default version `5.7` per `OrpcStructures.cs:7`. pub const VERSION_5_7: ComVersion = ComVersion { major: 5, minor: 7 }; pub const fn new(major: u16, minor: u16) -> Self { Self { major, minor } } } impl Default for ComVersion { fn default() -> Self { Self::VERSION_5_7 } } /// 32-byte ORPC request header (without extensions). /// Mirrors `OrpcThis` (`OrpcStructures.cs:10-52`). /// /// ```text /// offset size field /// 0 2 version.major u16 LE /// 2 2 version.minor u16 LE /// 4 4 flags u32 LE /// 8 4 reserved1 u32 LE /// 12 16 cid GUID /// 28 4 extensions_referent_id u32 LE /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct OrpcThis { pub version: ComVersion, pub flags: u32, pub reserved1: u32, pub cid: Guid, pub extensions_referent_id: u32, } impl OrpcThis { /// Encoded length without extensions — `OrpcStructures.cs:17`. pub const ENCODED_LEN: usize = 32; /// Construct with default version 5.7 and zeroed flags/extensions. /// Mirrors `OrpcThis.Create(cid, version)` (`OrpcStructures.cs:19-22`). pub fn create(cid: Guid, version: Option) -> Self { Self { version: version.unwrap_or_default(), flags: 0, reserved1: 0, cid, extensions_referent_id: 0, } } /// Decode the 32-byte header. Mirrors `OrpcThis.Parse` /// (`OrpcStructures.cs:24-39`). /// /// # Errors /// Returns [`RpcError::ShortRead`] if `buffer.len() < 32`. pub fn parse(buffer: &[u8]) -> Result { if buffer.len() < Self::ENCODED_LEN { return Err(RpcError::ShortRead { expected: Self::ENCODED_LEN, actual: buffer.len(), }); } Ok(Self { version: ComVersion::new( u16::from_le_bytes([buffer[0], buffer[1]]), u16::from_le_bytes([buffer[2], buffer[3]]), ), flags: u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]), reserved1: u32::from_le_bytes([buffer[8], buffer[9], buffer[10], buffer[11]]), cid: Guid::parse(&buffer[12..28])?, extensions_referent_id: u32::from_le_bytes([ buffer[28], buffer[29], buffer[30], buffer[31], ]), }) } /// Encode to 32 bytes. Mirrors `OrpcThis.Encode` /// (`OrpcStructures.cs:41-51`). pub fn encode(&self) -> [u8; Self::ENCODED_LEN] { let mut buf = [0u8; Self::ENCODED_LEN]; buf[0..2].copy_from_slice(&self.version.major.to_le_bytes()); buf[2..4].copy_from_slice(&self.version.minor.to_le_bytes()); buf[4..8].copy_from_slice(&self.flags.to_le_bytes()); buf[8..12].copy_from_slice(&self.reserved1.to_le_bytes()); buf[12..28].copy_from_slice(self.cid.as_bytes()); buf[28..32].copy_from_slice(&self.extensions_referent_id.to_le_bytes()); buf } } /// 8-byte ORPC response header (without extensions). /// Mirrors `OrpcThat` (`OrpcStructures.cs:54-77`). /// /// ```text /// offset size field /// 0 4 flags u32 LE /// 4 4 extensions_referent_id u32 LE /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct OrpcThat { pub flags: u32, pub extensions_referent_id: u32, } impl OrpcThat { /// Encoded length without extensions — `OrpcStructures.cs:56`. pub const ENCODED_LEN: usize = 8; /// Decode 8 bytes. Mirrors `OrpcThat.Parse` (`OrpcStructures.cs:58-68`). /// /// # Errors /// Returns [`RpcError::ShortRead`] if `buffer.len() < 8`. pub fn parse(buffer: &[u8]) -> Result { if buffer.len() < Self::ENCODED_LEN { return Err(RpcError::ShortRead { expected: Self::ENCODED_LEN, actual: buffer.len(), }); } Ok(Self { flags: u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]), extensions_referent_id: u32::from_le_bytes([ buffer[4], buffer[5], buffer[6], buffer[7], ]), }) } /// Encode to 8 bytes. Mirrors `OrpcThat.Encode` /// (`OrpcStructures.cs:70-76`). pub fn encode(&self) -> [u8; Self::ENCODED_LEN] { let mut buf = [0u8; Self::ENCODED_LEN]; buf[0..4].copy_from_slice(&self.flags.to_le_bytes()); buf[4..8].copy_from_slice(&self.extensions_referent_id.to_le_bytes()); buf } } /// Length-prefixed OBJREF byte wrapper used to carry interface pointers in /// ORPC bodies. Mirrors `MInterfacePointer` (`OrpcStructures.cs:79-109`). /// /// Wire layout: `u32 LE size || size bytes of OBJREF`. The Rust port owns the /// `objref_bytes` `Vec` (matching the .NET `byte[] ObjRefBytes`). #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MInterfacePointer { pub objref_bytes: Vec, } impl MInterfacePointer { /// Header length (the `u32` size prefix). pub const SIZE_PREFIX_LEN: usize = 4; pub fn new(objref_bytes: Vec) -> Self { Self { objref_bytes } } /// Encode as `size_le32 || objref_bytes`. Mirrors `Encode` /// (`OrpcStructures.cs:81-87`). pub fn encode(&self) -> Vec { let len = self.objref_bytes.len(); let mut buf = Vec::with_capacity(Self::SIZE_PREFIX_LEN + len); let len_u32: u32 = len.try_into().unwrap_or(u32::MAX); buf.extend_from_slice(&len_u32.to_le_bytes()); buf.extend_from_slice(&self.objref_bytes); buf } /// Parse `size_le32 || size bytes` into an owned `MInterfacePointer`. /// Mirrors `Parse` (`OrpcStructures.cs:89-103`). /// /// # Errors /// Returns [`RpcError::ShortRead`] if the buffer is shorter than the /// 4-byte size prefix, or [`RpcError::Decode`] if the declared size /// runs past the buffer. pub fn parse(buffer: &[u8]) -> Result { if buffer.len() < Self::SIZE_PREFIX_LEN { return Err(RpcError::ShortRead { expected: Self::SIZE_PREFIX_LEN, actual: buffer.len(), }); } let size = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) as usize; if size > buffer.len() - Self::SIZE_PREFIX_LEN { return Err(RpcError::Decode { offset: Self::SIZE_PREFIX_LEN, reason: "MInterfacePointer OBJREF payload truncated", buffer_len: buffer.len(), }); } Ok(Self { objref_bytes: buffer[Self::SIZE_PREFIX_LEN..Self::SIZE_PREFIX_LEN + size].to_vec(), }) } /// Parse the inner OBJREF bytes through [`crate::objref::ComObjRef::parse`]. /// Mirrors `MInterfacePointer.ParseObjRef` (`OrpcStructures.cs:105-108`). pub fn parse_objref(&self) -> Result { crate::objref::ComObjRef::parse(&self.objref_bytes) } } /// 40-byte STDOBJREF body. Mirrors `StdObjRef` (`OrpcStructures.cs:111-140`). /// /// ```text /// offset size field /// 0 4 flags u32 LE /// 4 4 public_refs u32 LE /// 8 8 oxid u64 LE /// 16 8 oid u64 LE /// 24 16 ipid GUID /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct StdObjRef { pub flags: u32, pub public_refs: u32, pub oxid: u64, pub oid: u64, pub ipid: Guid, } impl StdObjRef { /// Encoded length — `OrpcStructures.cs:113`. pub const ENCODED_LEN: usize = 40; /// Decode 40 bytes. Mirrors `StdObjRef.Parse` /// (`OrpcStructures.cs:115-128`). /// /// # Errors /// Returns [`RpcError::ShortRead`] if `buffer.len() < 40`. pub fn parse(buffer: &[u8]) -> Result { if buffer.len() < Self::ENCODED_LEN { return Err(RpcError::ShortRead { expected: Self::ENCODED_LEN, actual: buffer.len(), }); } Ok(Self { flags: u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]), public_refs: u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]), oxid: u64::from_le_bytes([ buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15], ]), oid: u64::from_le_bytes([ buffer[16], buffer[17], buffer[18], buffer[19], buffer[20], buffer[21], buffer[22], buffer[23], ]), ipid: Guid::parse(&buffer[24..40])?, }) } /// Encode to 40 bytes. Mirrors `StdObjRef.Encode` /// (`OrpcStructures.cs:130-139`). pub fn encode(&self) -> [u8; Self::ENCODED_LEN] { let mut buf = [0u8; Self::ENCODED_LEN]; buf[0..4].copy_from_slice(&self.flags.to_le_bytes()); buf[4..8].copy_from_slice(&self.public_refs.to_le_bytes()); buf[8..16].copy_from_slice(&self.oxid.to_le_bytes()); buf[16..24].copy_from_slice(&self.oid.to_le_bytes()); buf[24..40].copy_from_slice(self.ipid.as_bytes()); buf } } #[cfg(test)] #[allow( clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic )] mod tests { use super::*; fn sample_guid(seed: u8) -> Guid { let mut b = [0u8; 16]; for (i, slot) in b.iter_mut().enumerate() { *slot = seed.wrapping_add(i as u8); } Guid::new(b) } #[test] fn com_version_default_is_5_7() { assert_eq!(ComVersion::default(), ComVersion::new(5, 7)); } #[test] fn orpc_this_round_trip() { let cid = sample_guid(0x10); let original = OrpcThis::create(cid, None); let encoded = original.encode(); assert_eq!(encoded.len(), OrpcThis::ENCODED_LEN); let decoded = OrpcThis::parse(&encoded).unwrap(); assert_eq!(decoded, original); } #[test] fn orpc_this_short_buffer_errors() { assert!(matches!( OrpcThis::parse(&[0u8; 31]), Err(RpcError::ShortRead { expected: 32, actual: 31 }) )); } #[test] fn orpc_that_round_trip() { let original = OrpcThat { flags: 0xDEAD_BEEF, extensions_referent_id: 0x1234_5678, }; let encoded = original.encode(); assert_eq!(encoded.len(), OrpcThat::ENCODED_LEN); let decoded = OrpcThat::parse(&encoded).unwrap(); assert_eq!(decoded, original); } #[test] fn m_interface_pointer_round_trip() { let mip = MInterfacePointer::new(vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE]); let encoded = mip.encode(); assert_eq!(encoded.len(), 4 + 5); // Size prefix is 5. assert_eq!(&encoded[0..4], &5u32.to_le_bytes()); let decoded = MInterfacePointer::parse(&encoded).unwrap(); assert_eq!(decoded, mip); } #[test] fn m_interface_pointer_truncated_payload_errors() { // Declares 16 bytes but only supplies 4 after the prefix. let mut bad = Vec::new(); bad.extend_from_slice(&16u32.to_le_bytes()); bad.extend_from_slice(&[0u8; 4]); let err = MInterfacePointer::parse(&bad).unwrap_err(); assert!(matches!(err, RpcError::Decode { .. })); } #[test] fn std_objref_round_trip() { let original = StdObjRef { flags: 0, public_refs: 5, oxid: 0x1122_3344_5566_7788, oid: 0x99AA_BBCC_DDEE_FF00, ipid: sample_guid(0x55), }; let encoded = original.encode(); assert_eq!(encoded.len(), StdObjRef::ENCODED_LEN); let decoded = StdObjRef::parse(&encoded).unwrap(); assert_eq!(decoded, original); } #[test] fn std_objref_short_buffer_errors() { assert!(matches!( StdObjRef::parse(&[0u8; 39]), Err(RpcError::ShortRead { expected: 40, actual: 39 }) )); } }