//! NMX procedure metadata. //! //! Direct port of `src/MxNativeClient/NmxProcedureMetadata.cs`. Defines the //! `INmxService2` and `INmxSvcCallback` interface IIDs and the per-opnum NDR //! procedure descriptors used by the .NET reference's `ManagedCallbackExporter` //! and `ManagedNmxService2Client`. //! //! These values are wire-load-bearing for M2 wave 3 (callback exporter) and //! M3 (NMX session). Each IID is also enforced by the COM `[Guid(...)]` //! attributes on the matching interfaces in `NmxComContracts.cs:7,52,84`. use crate::guid::Guid; /// `INmxService2` IID `2630A513-A974-4B1A-8025-457A9A7C56B8` /// (`NmxProcedureMetadata.cs:5`, `NmxComContracts.cs:51`). pub const INMX_SERVICE2_IID: Guid = Guid::new([ 0x13, 0xA5, 0x30, 0x26, 0x74, 0xA9, 0x1A, 0x4B, 0x80, 0x25, 0x45, 0x7A, 0x9A, 0x7C, 0x56, 0xB8, ]); /// `INmxSvcCallback` IID `B49F92F7-C748-4169-8ECA-A0670B012746` /// (`NmxProcedureMetadata.cs:6`, `NmxComContracts.cs:84`). pub const INMX_SVC_CALLBACK_IID: Guid = Guid::new([ 0xF7, 0x92, 0x9F, 0xB4, 0x48, 0xC7, 0x69, 0x41, 0x8E, 0xCA, 0xA0, 0x67, 0x0B, 0x01, 0x27, 0x46, ]); /// NDR procedure descriptor — mirrors `NdrProcedureDescriptor` /// (`NmxProcedureMetadata.cs:108-115`). Captures the opnum + the x86 stack /// size and client/server buffer sizes the LMX MIDL stub publishes via /// `NMIDL_PROC_INFO`. The Rust port carries these for parity with the .NET /// reference; a future M3 NMX client may use them to size pre-allocated /// buffers. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NdrProcedureDescriptor { pub interface_id: Guid, pub name: &'static str, pub opnum: u16, pub x86_stack_size: u16, pub client_buffer_size: u16, pub server_buffer_size: u16, pub parameter_count_including_return: u8, } impl NdrProcedureDescriptor { pub const fn new( interface_id: Guid, name: &'static str, opnum: u16, x86_stack_size: u16, client_buffer_size: u16, server_buffer_size: u16, parameter_count_including_return: u8, ) -> Self { Self { interface_id, name, opnum, x86_stack_size, client_buffer_size, server_buffer_size, parameter_count_including_return, } } } // --- INmxService2 procedures (`NmxProcedureMetadata.cs:8-87`) ----------- /// `INmxService2::RegisterEngine` — opnum 3 (`cs:8-15`). pub const REGISTER_ENGINE: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "RegisterEngine", 3, 20, 8, 8, 4); /// `INmxService2::UnRegisterEngine` — opnum 4 (`cs:17-24`). pub const UNREGISTER_ENGINE: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "UnRegisterEngine", 4, 12, 8, 8, 2); /// `INmxService2::Connect` — opnum 5 (`cs:26-33`). pub const CONNECT: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "Connect", 5, 24, 32, 8, 5); /// `INmxService2::TransferData` — opnum 6 (`cs:35-42`). pub const TRANSFER_DATA: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "TransferData", 6, 28, 32, 8, 6); /// `INmxService2::AddSubscriberEngine` — opnum 7 (`cs:44-51`). pub const ADD_SUBSCRIBER_ENGINE: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "AddSubscriberEngine", 7, 24, 32, 8, 5); /// `INmxService2::RemoveSubscriberEngine` — opnum 8 (`cs:53-60`). pub const REMOVE_SUBSCRIBER_ENGINE: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "RemoveSubscriberEngine", 8, 24, 32, 8, 5); /// `INmxService2::SetHeartbeatSendInterval` — opnum 9 (`cs:62-69`). pub const SET_HEARTBEAT_SEND_INTERVAL: NdrProcedureDescriptor = NdrProcedureDescriptor::new( INMX_SERVICE2_IID, "SetHeartbeatSendInterval", 9, 16, 16, 8, 3, ); /// `INmxService2::RegisterEngine2` — opnum 10 (`cs:71-78`). pub const REGISTER_ENGINE_2: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "RegisterEngine2", 10, 24, 16, 8, 5); /// `INmxService2::GetPartnerVersion` — opnum 11 (`cs:80-87`). pub const GET_PARTNER_VERSION: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SERVICE2_IID, "GetPartnerVersion", 11, 24, 24, 36, 5); // --- INmxSvcCallback procedures (`NmxProcedureMetadata.cs:89-105`) ------- /// `INmxSvcCallback::DataReceived` — opnum 3 (`cs:89-96`). pub const DATA_RECEIVED: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SVC_CALLBACK_IID, "DataReceived", 3, 16, 8, 8, 3); /// `INmxSvcCallback::StatusReceived` — opnum 4 (`cs:98-105`). pub const STATUS_RECEIVED: NdrProcedureDescriptor = NdrProcedureDescriptor::new(INMX_SVC_CALLBACK_IID, "StatusReceived", 4, 16, 8, 8, 3); /// All `INmxService2` procedures in opnum order. Convenience for callers /// that want to iterate the table. pub const INMX_SERVICE2_PROCEDURES: &[NdrProcedureDescriptor] = &[ REGISTER_ENGINE, UNREGISTER_ENGINE, CONNECT, TRANSFER_DATA, ADD_SUBSCRIBER_ENGINE, REMOVE_SUBSCRIBER_ENGINE, SET_HEARTBEAT_SEND_INTERVAL, REGISTER_ENGINE_2, GET_PARTNER_VERSION, ]; /// All `INmxSvcCallback` procedures in opnum order. pub const INMX_SVC_CALLBACK_PROCEDURES: &[NdrProcedureDescriptor] = &[DATA_RECEIVED, STATUS_RECEIVED]; #[cfg(test)] #[allow( clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic )] mod tests { use super::*; #[test] fn inmx_service2_iid_matches_dotnet_d_format() { // .NET `new Guid("2630A513-A974-4B1A-8025-457A9A7C56B8").ToString("D")` assert_eq!( INMX_SERVICE2_IID.to_string(), "2630a513-a974-4b1a-8025-457a9a7c56b8" ); } #[test] fn inmx_svc_callback_iid_matches_dotnet_d_format() { // The exact IID re-asserted in the OBJREF capture // `captures/057-managed-callback-route-service-trace-saved/probe.stdout.txt:6` // (objref bytes 8..24). assert_eq!( INMX_SVC_CALLBACK_IID.to_string(), "b49f92f7-c748-4169-8eca-a0670b012746" ); } #[test] fn inmx_service2_opnums_are_3_through_11() { let opnums: Vec = INMX_SERVICE2_PROCEDURES.iter().map(|p| p.opnum).collect(); assert_eq!(opnums, vec![3, 4, 5, 6, 7, 8, 9, 10, 11]); } #[test] fn inmx_svc_callback_opnums_are_3_and_4() { let opnums: Vec = INMX_SVC_CALLBACK_PROCEDURES .iter() .map(|p| p.opnum) .collect(); assert_eq!(opnums, vec![3, 4]); } #[test] fn procedure_names_match_dotnet_nameof() { // .NET uses `nameof(...)` so names match the C# method identifier. assert_eq!(REGISTER_ENGINE.name, "RegisterEngine"); assert_eq!(REGISTER_ENGINE_2.name, "RegisterEngine2"); assert_eq!(GET_PARTNER_VERSION.name, "GetPartnerVersion"); assert_eq!(STATUS_RECEIVED.name, "StatusReceived"); } #[test] fn register_engine_2_metadata() { // Spot-check the parameters most likely to be load-bearing for M3: // opnum 10, 24-byte x86 stack, 16-byte client buffer, 5 params // including the HRESULT return (`cs:71-78`). assert_eq!(REGISTER_ENGINE_2.opnum, 10); assert_eq!(REGISTER_ENGINE_2.x86_stack_size, 24); assert_eq!(REGISTER_ENGINE_2.client_buffer_size, 16); assert_eq!(REGISTER_ENGINE_2.server_buffer_size, 8); assert_eq!(REGISTER_ENGINE_2.parameter_count_including_return, 5); assert_eq!(REGISTER_ENGINE_2.interface_id, INMX_SERVICE2_IID); } #[test] fn transfer_data_is_largest_x86_stack() { // TransferData (opnum 6) has the largest x86 stack at 28 bytes // because it carries the `ref byte messageBody` payload pointer. let max = INMX_SERVICE2_PROCEDURES .iter() .map(|p| p.x86_stack_size) .max() .unwrap(); assert_eq!(max, TRANSFER_DATA.x86_stack_size); assert_eq!(TRANSFER_DATA.opnum, 6); } #[test] fn callback_procedures_use_callback_iid() { for p in INMX_SVC_CALLBACK_PROCEDURES { assert_eq!(p.interface_id, INMX_SVC_CALLBACK_IID); } } #[test] fn service2_procedures_use_service2_iid() { for p in INMX_SERVICE2_PROCEDURES { assert_eq!(p.interface_id, INMX_SERVICE2_IID); } } }