Files
mxaccess/rust/crates/mxaccess-codec/src/metadata_query.rs
T
Joseph Doherty e79e289743 [F42] cargo doc --workspace --no-deps clean (0 warnings)
Fix all 33 rustdoc warnings across the workspace:

- Unresolved intra-doc links: rewrite [`name`] → either backtick text
  (when not actually a link) or fully-qualified `[Type::method]` /
  `[crate::module::name]` form. Affected: mxaccess-codec
  (asb_variant, item_control, metadata_query, observed_write_template,
  reference_handle, write_message), mxaccess-rpc (pdu), mxaccess-nmx
  (client), mxaccess-asb-nettcp (nmf), mxaccess-callback (exporter),
  mxaccess (asb_session, session, lib).
- Bracket-text being interpreted as link refs (e.g. `body[17]` →
  `` `body[17]` ``).
- Private-item references in public docs (CALLBACK_BROADCAST_CAPACITY,
  recover_connection_core, mxvalue_to_writevalue) reduced to
  backtick-text since they aren't part of the public API.

`RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` now
exits clean. Workspace 759 tests pass; clippy clean.

Defers `#![warn(missing_docs)]` lint to a future pass — the cleanup
target is the broken-link warnings, which are signal; missing-docs
would surface hundreds of low-priority public-item gaps that are out
of scope for this F-number.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:39:51 -04:00

193 lines
8.9 KiB
Rust

//! `NmxMetadataQueryMessage` — observed pre-advise metadata-query body.
//!
//! Direct port of `src/MxNativeCodec/NmxMetadataQueryMessage.cs`. The .NET
//! reference exposes a single static helper, `encode_observed_pre_advise`,
//! which returns a fixed observed body with a 16-byte item-correlation GUID
//! patched in at offset `0x8a`.
//!
//! The body is a captured constant — both segments of the hex literal in
//! `NmxMetadataQueryMessage.cs:10-11` are reproduced byte-for-byte below.
//! It encodes two metadata queries against `$DevPlatform.GR.TimeOfLastDeploy`
//! and `$DevPlatform.GR.TimeOfLastConfigChange`. The Rust port preserves
//! every byte; the only mutation is the GUID at offset `0x8a`.
// Direct byte indexing — see reference_handle.rs for rationale.
#![allow(clippy::indexing_slicing)]
/// Offset of the 16-byte item-correlation GUID inside the observed body
/// (`NmxMetadataQueryMessage.cs:5`).
pub const PRE_ADVISE_CORRELATION_OFFSET: usize = 0x8a;
/// Length of the first hex segment in bytes — `NmxMetadataQueryMessage.cs:10`.
const SEGMENT_1_LEN: usize = 160;
/// Length of the second hex segment in bytes — `NmxMetadataQueryMessage.cs:11`.
const SEGMENT_2_LEN: usize = 154;
/// Length of the observed body in bytes (160 + 154 = 314).
pub const PRE_ADVISE_BODY_LEN: usize = SEGMENT_1_LEN + SEGMENT_2_LEN;
/// First hex segment from `NmxMetadataQueryMessage.cs:10`. Decoded byte-for-byte
/// from `Convert.FromHexString(...)` of the literal in the .NET source.
const SEGMENT_1: [u8; SEGMENT_1_LEN] = [
0x17, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x71, 0x00, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x6a, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x81, 0x44, 0x00, 0x65,
0x00, 0x76, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72,
0x00, 0x6d, 0x00, 0x2e, 0x00, 0x47, 0x00, 0x52, 0x00, 0x2e, 0x00, 0x54, 0x00, 0x69, 0x00, 0x6d,
0x00, 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x4c, 0x00, 0x61, 0x00, 0x73, 0x00, 0x74, 0x00, 0x44,
0x00, 0x65, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x79, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xd0, 0xfc, 0x40, 0x09, 0x1f, 0x01, 0x00, 0xc0, 0xca, 0x9c, 0xcd, 0x32, 0x65,
0xb0, 0x46, 0xa5, 0x85, 0xa5, 0x83, 0xb2, 0xe7, 0x7a, 0x5d, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
];
/// Second hex segment from `NmxMetadataQueryMessage.cs:11`. Decoded
/// byte-for-byte from `Convert.FromHexString(...)` of the literal in the
/// .NET source.
const SEGMENT_2: [u8; SEGMENT_2_LEN] = [
0x17, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x71, 0x00, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x76, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x81, 0x44, 0x00, 0x65,
0x00, 0x76, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72,
0x00, 0x6d, 0x00, 0x2e, 0x00, 0x47, 0x00, 0x52, 0x00, 0x2e, 0x00, 0x54, 0x00, 0x69, 0x00, 0x6d,
0x00, 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x4c, 0x00, 0x61, 0x00, 0x73, 0x00, 0x74, 0x00, 0x43,
0x00, 0x6f, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x69, 0x00, 0x67, 0x00, 0x43, 0x00, 0x68, 0x00, 0x61,
0x00, 0x6e, 0x00, 0x67, 0x00, 0x65, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50,
0x03, 0x41, 0x09, 0x20, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
];
/// Concatenation of `SEGMENT_1 || SEGMENT_2`. Equivalent to the result of
/// `Convert.FromHexString` on the joined hex literal at
/// `NmxMetadataQueryMessage.cs:10-11`.
const OBSERVED_PRE_ADVISE_BODY: [u8; PRE_ADVISE_BODY_LEN] = {
let mut out = [0u8; PRE_ADVISE_BODY_LEN];
let mut i = 0;
while i < SEGMENT_1_LEN {
out[i] = SEGMENT_1[i];
i += 1;
}
let mut j = 0;
while j < SEGMENT_2_LEN {
out[SEGMENT_1_LEN + j] = SEGMENT_2[j];
j += 1;
}
out
};
/// Stateless helpers around the observed metadata-query body.
///
/// Mirrors the static class `NmxMetadataQueryMessage`
/// (`NmxMetadataQueryMessage.cs:3-15`).
pub struct NmxMetadataQueryMessage;
impl NmxMetadataQueryMessage {
/// Encode the observed pre-advise body, patching the supplied 16-byte
/// GUID into offset `0x8a` (`NmxMetadataQueryMessage.cs:7-14`).
///
/// `item_correlation_id` is the raw 16-byte little-endian Guid layout —
/// the same byte order .NET's `Guid.TryWriteBytes` emits. Callers
/// constructing a Guid from Rust types are responsible for using the
/// same wire layout (e.g. `windows::core::GUID::to_u128_le().to_le_bytes()`
/// or equivalent).
pub fn encode_observed_pre_advise(item_correlation_id: [u8; 16]) -> Vec<u8> {
let mut body = OBSERVED_PRE_ADVISE_BODY.to_vec();
body[PRE_ADVISE_CORRELATION_OFFSET..PRE_ADVISE_CORRELATION_OFFSET + 16]
.copy_from_slice(&item_correlation_id);
body
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
mod tests {
use super::*;
#[test]
fn body_length_is_314() {
// 160 + 154 = 314 bytes — derived from the two hex segments at
// `NmxMetadataQueryMessage.cs:10-11`.
assert_eq!(SEGMENT_1_LEN, 160);
assert_eq!(SEGMENT_2_LEN, 154);
assert_eq!(PRE_ADVISE_BODY_LEN, 314);
assert_eq!(OBSERVED_PRE_ADVISE_BODY.len(), 314);
}
// Compile-time bounds checks: clippy denies `assert!(<const expr>)` at
// runtime, so anchor these as `const _: () = assert!(...)` instead. They
// still fail the build if the constants drift — at compile time, before
// the test runner even spins up.
const _: () = assert!(PRE_ADVISE_CORRELATION_OFFSET + 16 <= PRE_ADVISE_BODY_LEN);
const _: () = assert!(PRE_ADVISE_CORRELATION_OFFSET + 16 <= SEGMENT_1_LEN);
#[test]
fn correlation_offset_is_0x8a() {
assert_eq!(PRE_ADVISE_CORRELATION_OFFSET, 0x8a);
// 0x8a (138) + 16 = 154, which is inside the first 160-byte segment.
// Anchor checks are above as `const _: () = assert!(...)`.
}
#[test]
fn observed_guid_in_template_matches_dotnet_capture() {
// The captured GUID at offset 0x8a in the literal body
// (`NmxMetadataQueryMessage.cs:10` — after the `0xc0` byte at offset 138).
let expected = [
0xc0, 0xca, 0x9c, 0xcd, 0x32, 0x65, 0xb0, 0x46, 0xa5, 0x85, 0xa5, 0x83, 0xb2, 0xe7,
0x7a, 0x5d,
];
assert_eq!(
&OBSERVED_PRE_ADVISE_BODY[0x8a..0x8a + 16],
&expected,
"the embedded GUID must match the .NET literal byte-for-byte"
);
}
#[test]
fn guid_is_patched_at_0x8a() {
let guid = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff, 0x00,
];
let body = NmxMetadataQueryMessage::encode_observed_pre_advise(guid);
assert_eq!(body.len(), PRE_ADVISE_BODY_LEN);
assert_eq!(&body[0x8a..0x8a + 16], &guid);
}
#[test]
fn bytes_outside_correlation_window_are_unchanged() {
// Encode with an all-zero GUID and an all-0xff GUID, compare every
// byte outside the patch window — they must be identical.
let body_a = NmxMetadataQueryMessage::encode_observed_pre_advise([0u8; 16]);
let body_b = NmxMetadataQueryMessage::encode_observed_pre_advise([0xffu8; 16]);
for i in 0..PRE_ADVISE_BODY_LEN {
if (PRE_ADVISE_CORRELATION_OFFSET..PRE_ADVISE_CORRELATION_OFFSET + 16).contains(&i) {
continue;
}
assert_eq!(body_a[i], body_b[i], "byte {i} should be unchanged");
}
}
#[test]
fn encoded_body_matches_observed_template_at_known_offsets() {
// Spot-check anchor bytes from the .NET hex string. Offsets 0..10
// are the `17 01 00 01 01 00 01 00 00 00` header
// (`NmxMetadataQueryMessage.cs:10`); offset 160 starts the second
// segment with the same 10-byte preamble (`NmxMetadataQueryMessage.cs:11`).
let body = NmxMetadataQueryMessage::encode_observed_pre_advise([0u8; 16]);
let preamble = [0x17, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00];
assert_eq!(&body[0..10], &preamble);
assert_eq!(&body[SEGMENT_1_LEN..SEGMENT_1_LEN + 10], &preamble);
}
#[test]
fn fresh_call_does_not_mutate_template() {
// Each call must return an independent buffer — patching the result
// of one call must not affect a subsequent call.
let mut a = NmxMetadataQueryMessage::encode_observed_pre_advise([0u8; 16]);
a[0] = 0x99;
let b = NmxMetadataQueryMessage::encode_observed_pre_advise([0u8; 16]);
assert_eq!(b[0], 0x17, "second call must not see mutation of first");
}
}