//! `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 { 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!()` 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"); } }