[M2] mxaccess-rpc: OXID + RemQI body codecs (wave 2)
Lands M2 wave 2 — two pure-Rust body-codec modules under crates/mxaccess-rpc, plus a small inline ORPC framing port and a crate-level type consolidation. Resolves F7+F8 from wave 1. New modules - guid.rs (4 tests) — hoisted from objref::Guid; shared by all of mxaccess-rpc. Resolves F7. - error.rs — hoisted RpcError union (ShortRead, UnexpectedPacketType, UnknownPacketType, InvalidFragmentLength, TruncatedBindBody, InvalidAuthTrailer, MissingAuthValue, Decode). Resolves F8. - orpc.rs (8 tests) — port of OrpcStructures.cs:1-141. ComVersion, OrpcThis (32-byte header), OrpcThat (8-byte header), MInterfacePointer (length-prefixed OBJREF), StdObjRef (40 bytes). - object_exporter.rs (~530 LoC, 20 tests) — port of ObjectExporterMessages.cs:1-141. IObjectExporter IID, opnums, ResolveOxid request encoder + ResolveOxidResult/Failure parsers. Owned-string protocol labels cleaned up via Cow upgrade rather than Box::leak (ComDualStringEntry::protocol is now Cow<'static, str>). - rem_unknown.rs (~340 LoC, 11 tests) — port of RemUnknownMessages.cs. IRemUnknown IID, RemQueryInterface request/response, RemQiResult. 4-byte NDR pad in REMQIRESULT preserved as pad_after_hresult per CLAUDE.md unknown-bytes rule. Test count delta: 277 -> 319 (+42; codec 215 unchanged, mxaccess-rpc 60 -> 102, codec parity 2 unchanged). Open followups touched: F7 + F8 resolved; F9, F10, F11 added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,10 @@
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use thiserror::Error;
|
||||
// `Guid` and `RpcError` are crate-shared since M2 wave 2 — see
|
||||
// `design/followups.md` F7+F8.
|
||||
pub use crate::error::RpcError;
|
||||
pub use crate::guid::Guid;
|
||||
|
||||
/// Encoded layout per `ComObjRef.cs:25-39`:
|
||||
///
|
||||
@@ -64,65 +67,6 @@ const IPID_OFFSET: usize = 48;
|
||||
const DUAL_STRING_ENTRIES_OFFSET: usize = 64;
|
||||
const DUAL_STRING_SECURITY_OFFSET_OFFSET: usize = 66;
|
||||
|
||||
/// 16-byte GUID. Stored as little-endian wire bytes for the first three groups
|
||||
/// (Data1 u32 LE, Data2 u16 LE, Data3 u16 LE) followed by 8 big-endian
|
||||
/// `Data4` bytes — matches the byte layout produced by .NET
|
||||
/// `new Guid(ReadOnlySpan<byte>)` (`ComObjRef.cs:31,36`).
|
||||
///
|
||||
/// Kept as a self-contained type to avoid pulling `uuid` into `mxaccess-rpc`;
|
||||
/// the sibling DCE/RPC PDU codec may consolidate to a shared type at the
|
||||
/// loop-driver level.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Guid(pub [u8; 16]);
|
||||
|
||||
impl Guid {
|
||||
pub const fn new(bytes: [u8; 16]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub const fn as_bytes(&self) -> &[u8; 16] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Guid {
|
||||
/// Mirrors .NET `Guid.ToString("D")`: dashed hex, lowercase, e.g.
|
||||
/// `b49f92f7-c748-4169-8eca-a0670b012746`. The first three groups are
|
||||
/// little-endian on the wire so are byte-swapped on display.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let b = &self.0;
|
||||
write!(
|
||||
f,
|
||||
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
b[3],
|
||||
b[2],
|
||||
b[1],
|
||||
b[0],
|
||||
b[5],
|
||||
b[4],
|
||||
b[7],
|
||||
b[6],
|
||||
b[8],
|
||||
b[9],
|
||||
b[10],
|
||||
b[11],
|
||||
b[12],
|
||||
b[13],
|
||||
b[14],
|
||||
b[15],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors produced by the OBJREF parser.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum RpcError {
|
||||
/// Buffer too short to satisfy a fixed-layout read.
|
||||
#[error("short read: expected {expected} bytes, got {actual}")]
|
||||
ShortRead { expected: usize, actual: usize },
|
||||
}
|
||||
|
||||
/// One decoded entry of the OBJREF dual-string array. `value` is the
|
||||
/// printable-ASCII escaping of the UTF-16 string per `ComObjRef.cs:82-91` —
|
||||
/// non-printable code units appear as `<XXXX>` lowercase hex. `is_security_binding`
|
||||
@@ -130,10 +74,17 @@ pub enum RpcError {
|
||||
/// `DualStringSecurityOffset`.
|
||||
///
|
||||
/// Mirrors `ComDualStringEntry` (`ComObjRef.cs:138-145`).
|
||||
///
|
||||
/// `protocol` is `Cow<'static, str>` because the OBJREF parser uses the
|
||||
/// 7-entry static table (`Cow::Borrowed`) while the M2 wave 2 OXID-resolve
|
||||
/// parser uses `format!("protseq_0x{:04x}", tower_id)` (`Cow::Owned`) for
|
||||
/// unknown tower ids (`ObjectExporterMessages.cs:120`). The two parsers
|
||||
/// share the entry type but emit different protocol labels for the same
|
||||
/// tower id — this is intentional and matches the .NET reference.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ComDualStringEntry {
|
||||
pub tower_id: u16,
|
||||
pub protocol: &'static str,
|
||||
pub protocol: std::borrow::Cow<'static, str>,
|
||||
pub value: String,
|
||||
pub is_security_binding: bool,
|
||||
}
|
||||
@@ -310,7 +261,7 @@ fn decode_dual_string_array(
|
||||
|
||||
strings.push(ComDualStringEntry {
|
||||
tower_id,
|
||||
protocol: protocol_tower_name(tower_id),
|
||||
protocol: std::borrow::Cow::Borrowed(protocol_tower_name(tower_id)),
|
||||
value: text,
|
||||
is_security_binding: entry_start >= security_offset as usize,
|
||||
});
|
||||
@@ -368,7 +319,12 @@ const _: () = assert!(OBJREF_HEADER_LEN == 68);
|
||||
const _: () = assert!(OBJREF_SIGNATURE == 0x574F_454D);
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
|
||||
#[allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::indexing_slicing,
|
||||
clippy::panic
|
||||
)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -474,6 +430,7 @@ mod tests {
|
||||
assert_eq!(expected, 68);
|
||||
assert_eq!(actual, 67);
|
||||
}
|
||||
other => panic!("expected ShortRead, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user