Skip to main content

nnrp_core/
control.rs

1use crate::{CommonHeader, MessageType, NnrpError, CURRENT_VERSION_MAJOR, CURRENT_WIRE_FORMAT};
2
3pub const CLIENT_HELLO_METADATA_LEN: usize = 64;
4pub const SERVER_HELLO_ACK_METADATA_LEN: usize = 80;
5pub const SESSION_PATCH_METADATA_LEN: usize = 36;
6pub const SESSION_PATCH_ACK_METADATA_LEN: usize = 48;
7pub const RESULT_HINT_METADATA_LEN: usize = 16;
8pub const CONTROL_REQUEST_METADATA_LEN: usize = 32;
9pub const SCHEDULING_METADATA_LEN: usize = 32;
10pub const SUPERSEDE_METADATA_LEN: usize = 32;
11pub const BUDGET_METADATA_LEN: usize = 40;
12pub const PROGRESS_METADATA_LEN: usize = 32;
13pub const PARTIAL_RESULT_METADATA_LEN: usize = 40;
14pub const PRESSURE_METADATA_LEN: usize = 32;
15pub const CAPABILITY_METADATA_LEN: usize = 32;
16pub const ROUTE_HINT_METADATA_LEN: usize = 32;
17pub const TRACE_CONTEXT_METADATA_LEN: usize = 32;
18pub const RESULT_DROP_REASON_METADATA_LEN: usize = 32;
19pub const RECOVERABLE_ERROR_METADATA_LEN: usize = 32;
20pub const RETRY_AFTER_METADATA_LEN: usize = 32;
21pub const TRANSPORT_PROBE_METADATA_LEN: usize = 16;
22pub const TRANSPORT_PROBE_ACK_METADATA_LEN: usize = 16;
23pub const SESSION_MIGRATE_METADATA_LEN: usize = 24;
24pub const SESSION_MIGRATE_ACK_METADATA_LEN: usize = 24;
25pub const ERROR_METADATA_LEN: usize = 32;
26
27pub const SESSION_PATCH_FIELD_KNOWN_MASK: u32 = 0x0000_007f;
28pub const SERVER_HELLO_ACK_FLAGS_KNOWN_MASK: u32 = 0x0000_0001;
29pub const CONTROL_REQUEST_FLAGS_KNOWN_MASK: u8 = 0x03;
30pub const CONTROL_REQUEST_FLAG_COOPERATIVE_ALLOWED: u8 = 0x01;
31pub const CONTROL_REQUEST_FLAG_HARD_ABORT_ALLOWED: u8 = 0x02;
32pub const SCHEDULING_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
33pub const SCHEDULING_FLAG_DISCARD_STALE: u32 = 0x0000_0001;
34pub const SCHEDULING_FLAG_EMIT_DROP_REASON: u32 = 0x0000_0002;
35pub const SUPERSEDE_FLAGS_KNOWN_MASK: u16 = 0x0001;
36pub const BUDGET_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
37pub const PARTIAL_RESULT_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
38pub const PRESSURE_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
39pub const CAPABILITY_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
40pub const ROUTE_HINT_FLAGS_KNOWN_MASK: u32 = 0x0000_0003;
41pub const TRACE_CONTEXT_FLAGS_KNOWN_MASK: u16 = 0x0003;
42pub const RESULT_DROP_FLAGS_KNOWN_MASK: u8 = 0x03;
43pub const RESULT_DROP_REASON_DEADLINE_EXPIRED: u16 = 0x0001;
44pub const RECOVERABLE_ERROR_FLAGS_KNOWN_MASK: u8 = 0x03;
45pub const RETRY_AFTER_FLAGS_KNOWN_MASK: u8 = 0x03;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[repr(u32)]
49pub enum ResultHintBudgetPolicy {
50    None = 0,
51    Full = 1,
52    Partial = 2,
53    StaleReuse = 3,
54    Drop = 4,
55}
56
57impl ResultHintBudgetPolicy {
58    pub fn try_from_u32(value: u32) -> Result<Self, NnrpError> {
59        match value {
60            0 => Ok(Self::None),
61            1 => Ok(Self::Full),
62            2 => Ok(Self::Partial),
63            3 => Ok(Self::StaleReuse),
64            4 => Ok(Self::Drop),
65            _ => Err(NnrpError::UnknownEnumValue {
66                enum_name: "result_hint_budget_policy",
67                value: value as u64,
68            }),
69        }
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[repr(u32)]
75pub enum ResultHintCongestionState {
76    None = 0,
77    Steady = 1,
78    Elevated = 2,
79    Saturated = 3,
80}
81
82impl ResultHintCongestionState {
83    pub fn try_from_u32(value: u32) -> Result<Self, NnrpError> {
84        match value {
85            0 => Ok(Self::None),
86            1 => Ok(Self::Steady),
87            2 => Ok(Self::Elevated),
88            3 => Ok(Self::Saturated),
89            _ => Err(NnrpError::UnknownEnumValue {
90                enum_name: "result_hint_congestion_state",
91                value: value as u64,
92            }),
93        }
94    }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[repr(u32)]
99pub enum ResultHintReason {
100    None = 0,
101    QueueFull = 1,
102    ServerBusy = 2,
103    BudgetExceeded = 3,
104    Superseded = 4,
105}
106
107impl ResultHintReason {
108    pub fn try_from_u32(value: u32) -> Result<Self, NnrpError> {
109        match value {
110            0 => Ok(Self::None),
111            1 => Ok(Self::QueueFull),
112            2 => Ok(Self::ServerBusy),
113            3 => Ok(Self::BudgetExceeded),
114            4 => Ok(Self::Superseded),
115            _ => Err(NnrpError::UnknownEnumValue {
116                enum_name: "result_hint_reason",
117                value: value as u64,
118            }),
119        }
120    }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124#[repr(u16)]
125pub enum SessionPatchAckStatus {
126    Accepted = 0,
127    PartiallyApplied = 1,
128    Rejected = 2,
129}
130
131impl SessionPatchAckStatus {
132    pub fn try_from_u16(value: u16) -> Result<Self, NnrpError> {
133        match value {
134            0 => Ok(Self::Accepted),
135            1 => Ok(Self::PartiallyApplied),
136            2 => Ok(Self::Rejected),
137            _ => Err(NnrpError::UnknownEnumValue {
138                enum_name: "session_patch_ack_status",
139                value: value as u64,
140            }),
141        }
142    }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146#[repr(u16)]
147pub enum SessionPatchRejectReason {
148    None = 0,
149    UnsupportedField = 1,
150    InvalidRange = 2,
151    UnsupportedStrategy = 3,
152    InvalidLaneMask = 4,
153    RateLimited = 5,
154}
155
156impl SessionPatchRejectReason {
157    pub fn try_from_u16(value: u16) -> Result<Self, NnrpError> {
158        match value {
159            0 => Ok(Self::None),
160            1 => Ok(Self::UnsupportedField),
161            2 => Ok(Self::InvalidRange),
162            3 => Ok(Self::UnsupportedStrategy),
163            4 => Ok(Self::InvalidLaneMask),
164            5 => Ok(Self::RateLimited),
165            _ => Err(NnrpError::UnknownEnumValue {
166                enum_name: "session_patch_reject_reason",
167                value: value as u64,
168            }),
169        }
170    }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174#[repr(u32)]
175pub enum TransportId {
176    Unspecified = 0,
177    Quic = 1,
178    Tcp = 2,
179    Ipc = 3,
180    WebSocket = 4,
181}
182
183impl TransportId {
184    pub fn try_from_u32(value: u32) -> Result<Self, NnrpError> {
185        match value {
186            0 => Ok(Self::Unspecified),
187            1 => Ok(Self::Quic),
188            2 => Ok(Self::Tcp),
189            3 => Ok(Self::Ipc),
190            4 => Ok(Self::WebSocket),
191            _ => Err(NnrpError::UnknownEnumValue {
192                enum_name: "transport_id",
193                value: value as u64,
194            }),
195        }
196    }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200#[repr(u32)]
201pub enum ErrorScope {
202    Connection = 0,
203    Session = 1,
204    Frame = 2,
205}
206
207impl ErrorScope {
208    pub fn try_from_u32(value: u32) -> Result<Self, NnrpError> {
209        match value {
210            0 => Ok(Self::Connection),
211            1 => Ok(Self::Session),
212            2 => Ok(Self::Frame),
213            _ => Err(NnrpError::UnknownEnumValue {
214                enum_name: "error_scope",
215                value: value as u64,
216            }),
217        }
218    }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub struct ClientHelloMetadata {
223    pub min_version_major: u8,
224    pub max_version_major: u8,
225    pub supported_wire_format_bitmap: u16,
226    pub supported_profile_bitmap: u32,
227    pub supported_payload_kind_bitmap: u32,
228    pub supported_codec_bitmap: u32,
229    pub supported_compression_bitmap: u32,
230    pub supported_dtype_bitmap: u32,
231    pub supported_layout_bitmap: u32,
232    pub cache_digest_bitmap: u16,
233    pub cache_object_bitmap: u16,
234    pub cache_namespace_count: u16,
235    pub max_lane_count: u16,
236    pub max_cache_entries: u32,
237    pub max_cache_bytes: u32,
238    pub target_cadence_x100: u16,
239    pub latency_budget_ms: u16,
240    pub quality_tier: u16,
241    pub degrade_policy: u16,
242    pub requested_session_id: u32,
243    pub auth_bytes: u32,
244    pub control_extension_bytes: u32,
245}
246
247impl ClientHelloMetadata {
248    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
249        require_len(source, CLIENT_HELLO_METADATA_LEN)?;
250        let metadata = Self {
251            min_version_major: source[0],
252            max_version_major: source[1],
253            supported_wire_format_bitmap: read_u16(source, 2),
254            supported_profile_bitmap: read_u32(source, 4),
255            supported_payload_kind_bitmap: read_u32(source, 8),
256            supported_codec_bitmap: read_u32(source, 12),
257            supported_compression_bitmap: read_u32(source, 16),
258            supported_dtype_bitmap: read_u32(source, 20),
259            supported_layout_bitmap: read_u32(source, 24),
260            cache_digest_bitmap: read_u16(source, 28),
261            cache_object_bitmap: read_u16(source, 30),
262            cache_namespace_count: read_u16(source, 32),
263            max_lane_count: read_u16(source, 34),
264            max_cache_entries: read_u32(source, 36),
265            max_cache_bytes: read_u32(source, 40),
266            target_cadence_x100: read_u16(source, 44),
267            latency_budget_ms: read_u16(source, 46),
268            quality_tier: read_u16(source, 48),
269            degrade_policy: read_u16(source, 50),
270            requested_session_id: read_u32(source, 52),
271            auth_bytes: read_u32(source, 56),
272            control_extension_bytes: read_u32(source, 60),
273        };
274        metadata.validate_capability_window()?;
275        Ok(metadata)
276    }
277
278    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
279        require_destination_len(destination, CLIENT_HELLO_METADATA_LEN)?;
280        self.validate_capability_window()?;
281
282        destination[..CLIENT_HELLO_METADATA_LEN].fill(0);
283        destination[0] = self.min_version_major;
284        destination[1] = self.max_version_major;
285        write_u16(destination, 2, self.supported_wire_format_bitmap);
286        write_u32(destination, 4, self.supported_profile_bitmap);
287        write_u32(destination, 8, self.supported_payload_kind_bitmap);
288        write_u32(destination, 12, self.supported_codec_bitmap);
289        write_u32(destination, 16, self.supported_compression_bitmap);
290        write_u32(destination, 20, self.supported_dtype_bitmap);
291        write_u32(destination, 24, self.supported_layout_bitmap);
292        write_u16(destination, 28, self.cache_digest_bitmap);
293        write_u16(destination, 30, self.cache_object_bitmap);
294        write_u16(destination, 32, self.cache_namespace_count);
295        write_u16(destination, 34, self.max_lane_count);
296        write_u32(destination, 36, self.max_cache_entries);
297        write_u32(destination, 40, self.max_cache_bytes);
298        write_u16(destination, 44, self.target_cadence_x100);
299        write_u16(destination, 46, self.latency_budget_ms);
300        write_u16(destination, 48, self.quality_tier);
301        write_u16(destination, 50, self.degrade_policy);
302        write_u32(destination, 52, self.requested_session_id);
303        write_u32(destination, 56, self.auth_bytes);
304        write_u32(destination, 60, self.control_extension_bytes);
305        Ok(())
306    }
307
308    pub fn to_bytes(&self) -> Result<[u8; CLIENT_HELLO_METADATA_LEN], NnrpError> {
309        let mut bytes = [0u8; CLIENT_HELLO_METADATA_LEN];
310        self.write(&mut bytes)?;
311        Ok(bytes)
312    }
313
314    pub fn validate_capability_window(&self) -> Result<(), NnrpError> {
315        if self.min_version_major > self.max_version_major {
316            return Err(NnrpError::InvalidProtocolCombination {
317                rule: "CLIENT_HELLO version window must be ordered",
318            });
319        }
320        if CURRENT_VERSION_MAJOR < self.min_version_major
321            || CURRENT_VERSION_MAJOR > self.max_version_major
322        {
323            return Err(NnrpError::InvalidProtocolCombination {
324                rule: "CLIENT_HELLO version window must include NNRP/1",
325            });
326        }
327        if !has_bitmap_bit(
328            self.supported_wire_format_bitmap as u64,
329            CURRENT_WIRE_FORMAT as u32,
330        ) {
331            return Err(NnrpError::InvalidProtocolCombination {
332                rule: "CLIENT_HELLO supported_wire_format_bitmap must include wire_format 0",
333            });
334        }
335        require_nonzero(
336            "CLIENT_HELLO supported_profile_bitmap",
337            self.supported_profile_bitmap,
338        )?;
339        require_nonzero(
340            "CLIENT_HELLO supported_payload_kind_bitmap",
341            self.supported_payload_kind_bitmap,
342        )?;
343        Ok(())
344    }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub struct ServerHelloAckMetadata {
349    pub selected_version_major: u8,
350    pub selected_wire_format: u8,
351    pub auth_status: u8,
352    pub session_id: u32,
353    pub accepted_profile_bitmap: u32,
354    pub accepted_payload_kind_bitmap: u32,
355    pub accepted_codec_bitmap: u32,
356    pub accepted_compression_bitmap: u32,
357    pub accepted_dtype_bitmap: u32,
358    pub accepted_layout_bitmap: u32,
359    pub cache_digest_bitmap: u32,
360    pub cache_object_bitmap: u32,
361    pub max_cache_entries: u32,
362    pub max_cache_bytes: u32,
363    pub max_lane_count: u16,
364    pub max_concurrent_frames: u16,
365    pub target_cadence_x100: u16,
366    pub latency_budget_ms: u16,
367    pub quality_tier: u16,
368    pub degrade_policy: u16,
369    pub max_body_bytes: u32,
370    pub token_ttl_ms: u32,
371    pub retry_after_ms: u32,
372    pub control_extension_bytes: u32,
373    pub server_flags: u32,
374}
375
376impl ServerHelloAckMetadata {
377    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
378        require_len(source, SERVER_HELLO_ACK_METADATA_LEN)?;
379        validate_zero_u8("server_hello_ack.reserved0", source[3])?;
380        let server_flags = read_u32(source, 76);
381        validate_mask_u32(server_flags, SERVER_HELLO_ACK_FLAGS_KNOWN_MASK)?;
382
383        Ok(Self {
384            selected_version_major: source[0],
385            selected_wire_format: source[1],
386            auth_status: source[2],
387            session_id: read_u32(source, 4),
388            accepted_profile_bitmap: read_u32(source, 8),
389            accepted_payload_kind_bitmap: read_u32(source, 12),
390            accepted_codec_bitmap: read_u32(source, 16),
391            accepted_compression_bitmap: read_u32(source, 20),
392            accepted_dtype_bitmap: read_u32(source, 24),
393            accepted_layout_bitmap: read_u32(source, 28),
394            cache_digest_bitmap: read_u32(source, 32),
395            cache_object_bitmap: read_u32(source, 36),
396            max_cache_entries: read_u32(source, 40),
397            max_cache_bytes: read_u32(source, 44),
398            max_lane_count: read_u16(source, 48),
399            max_concurrent_frames: read_u16(source, 50),
400            target_cadence_x100: read_u16(source, 52),
401            latency_budget_ms: read_u16(source, 54),
402            quality_tier: read_u16(source, 56),
403            degrade_policy: read_u16(source, 58),
404            max_body_bytes: read_u32(source, 60),
405            token_ttl_ms: read_u32(source, 64),
406            retry_after_ms: read_u32(source, 68),
407            control_extension_bytes: read_u32(source, 72),
408            server_flags,
409        })
410    }
411
412    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
413        require_destination_len(destination, SERVER_HELLO_ACK_METADATA_LEN)?;
414        validate_mask_u32(self.server_flags, SERVER_HELLO_ACK_FLAGS_KNOWN_MASK)?;
415
416        destination[..SERVER_HELLO_ACK_METADATA_LEN].fill(0);
417        destination[0] = self.selected_version_major;
418        destination[1] = self.selected_wire_format;
419        destination[2] = self.auth_status;
420        write_u32(destination, 4, self.session_id);
421        write_u32(destination, 8, self.accepted_profile_bitmap);
422        write_u32(destination, 12, self.accepted_payload_kind_bitmap);
423        write_u32(destination, 16, self.accepted_codec_bitmap);
424        write_u32(destination, 20, self.accepted_compression_bitmap);
425        write_u32(destination, 24, self.accepted_dtype_bitmap);
426        write_u32(destination, 28, self.accepted_layout_bitmap);
427        write_u32(destination, 32, self.cache_digest_bitmap);
428        write_u32(destination, 36, self.cache_object_bitmap);
429        write_u32(destination, 40, self.max_cache_entries);
430        write_u32(destination, 44, self.max_cache_bytes);
431        write_u16(destination, 48, self.max_lane_count);
432        write_u16(destination, 50, self.max_concurrent_frames);
433        write_u16(destination, 52, self.target_cadence_x100);
434        write_u16(destination, 54, self.latency_budget_ms);
435        write_u16(destination, 56, self.quality_tier);
436        write_u16(destination, 58, self.degrade_policy);
437        write_u32(destination, 60, self.max_body_bytes);
438        write_u32(destination, 64, self.token_ttl_ms);
439        write_u32(destination, 68, self.retry_after_ms);
440        write_u32(destination, 72, self.control_extension_bytes);
441        write_u32(destination, 76, self.server_flags);
442        Ok(())
443    }
444
445    pub fn to_bytes(&self) -> Result<[u8; SERVER_HELLO_ACK_METADATA_LEN], NnrpError> {
446        let mut bytes = [0u8; SERVER_HELLO_ACK_METADATA_LEN];
447        self.write(&mut bytes)?;
448        Ok(bytes)
449    }
450
451    pub fn validate_against_client_hello(
452        &self,
453        client_hello: &ClientHelloMetadata,
454    ) -> Result<(), NnrpError> {
455        if self.selected_version_major < client_hello.min_version_major
456            || self.selected_version_major > client_hello.max_version_major
457        {
458            return Err(NnrpError::InvalidProtocolCombination {
459                rule: "SERVER_HELLO_ACK selected version must be inside client window",
460            });
461        }
462        if !has_bitmap_bit(
463            client_hello.supported_wire_format_bitmap as u64,
464            self.selected_wire_format as u32,
465        ) {
466            return Err(NnrpError::InvalidProtocolCombination {
467                rule: "SERVER_HELLO_ACK selected wire format must be client-supported",
468            });
469        }
470        require_subset(
471            "SERVER_HELLO_ACK accepted_profile_bitmap",
472            self.accepted_profile_bitmap,
473            client_hello.supported_profile_bitmap,
474        )?;
475        require_subset(
476            "SERVER_HELLO_ACK accepted_payload_kind_bitmap",
477            self.accepted_payload_kind_bitmap,
478            client_hello.supported_payload_kind_bitmap,
479        )?;
480        require_subset(
481            "SERVER_HELLO_ACK accepted_codec_bitmap",
482            self.accepted_codec_bitmap,
483            client_hello.supported_codec_bitmap,
484        )?;
485        require_subset(
486            "SERVER_HELLO_ACK accepted_compression_bitmap",
487            self.accepted_compression_bitmap,
488            client_hello.supported_compression_bitmap,
489        )?;
490        require_subset(
491            "SERVER_HELLO_ACK accepted_dtype_bitmap",
492            self.accepted_dtype_bitmap,
493            client_hello.supported_dtype_bitmap,
494        )?;
495        require_subset(
496            "SERVER_HELLO_ACK accepted_layout_bitmap",
497            self.accepted_layout_bitmap,
498            client_hello.supported_layout_bitmap,
499        )?;
500        require_subset(
501            "SERVER_HELLO_ACK cache_digest_bitmap",
502            self.cache_digest_bitmap,
503            client_hello.cache_digest_bitmap as u32,
504        )?;
505        require_subset(
506            "SERVER_HELLO_ACK cache_object_bitmap",
507            self.cache_object_bitmap,
508            client_hello.cache_object_bitmap as u32,
509        )?;
510        Ok(())
511    }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
515pub struct SessionPatchMetadata {
516    pub profile_id: u16,
517    pub patch_mask: u32,
518    pub target_cadence_x100: u32,
519    pub quality_tier: u16,
520    pub degrade_policy: u16,
521    pub active_lane_mask: u64,
522    pub preferred_codec_bitmap: u32,
523    pub preferred_compression_bitmap: u32,
524    pub profile_patch_bytes: u32,
525}
526
527impl SessionPatchMetadata {
528    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
529        require_len(source, SESSION_PATCH_METADATA_LEN)?;
530        validate_zero_u16("session_patch.reserved0", read_u16(source, 2))?;
531        let patch_mask = read_u32(source, 4);
532        validate_mask_u32(patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
533
534        Ok(Self {
535            profile_id: read_u16(source, 0),
536            patch_mask,
537            target_cadence_x100: read_u32(source, 8),
538            quality_tier: read_u16(source, 12),
539            degrade_policy: read_u16(source, 14),
540            active_lane_mask: read_u64(source, 16),
541            preferred_codec_bitmap: read_u32(source, 24),
542            preferred_compression_bitmap: read_u32(source, 28),
543            profile_patch_bytes: read_u32(source, 32),
544        })
545    }
546
547    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
548        require_destination_len(destination, SESSION_PATCH_METADATA_LEN)?;
549        validate_mask_u32(self.patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
550
551        destination[..SESSION_PATCH_METADATA_LEN].fill(0);
552        write_u16(destination, 0, self.profile_id);
553        write_u32(destination, 4, self.patch_mask);
554        write_u32(destination, 8, self.target_cadence_x100);
555        write_u16(destination, 12, self.quality_tier);
556        write_u16(destination, 14, self.degrade_policy);
557        write_u64(destination, 16, self.active_lane_mask);
558        write_u32(destination, 24, self.preferred_codec_bitmap);
559        write_u32(destination, 28, self.preferred_compression_bitmap);
560        write_u32(destination, 32, self.profile_patch_bytes);
561        Ok(())
562    }
563
564    pub fn to_bytes(&self) -> Result<[u8; SESSION_PATCH_METADATA_LEN], NnrpError> {
565        let mut bytes = [0u8; SESSION_PATCH_METADATA_LEN];
566        self.write(&mut bytes)?;
567        Ok(bytes)
568    }
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq)]
572pub struct SessionPatchAckMetadata {
573    pub ack_status: SessionPatchAckStatus,
574    pub reject_reason: SessionPatchRejectReason,
575    pub applied_patch_mask: u32,
576    pub rejected_patch_mask: u32,
577    pub retry_after_ms: u32,
578    pub effective_profile_id: u16,
579    pub effective_target_cadence_x100: u32,
580    pub effective_quality_tier: u16,
581    pub effective_degrade_policy: u16,
582    pub effective_lane_mask: u64,
583    pub effective_codec_bitmap: u32,
584    pub effective_compression_bitmap: u32,
585    pub profile_patch_ack_bytes: u32,
586}
587
588impl SessionPatchAckMetadata {
589    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
590        require_len(source, SESSION_PATCH_ACK_METADATA_LEN)?;
591        validate_zero_u16("session_patch_ack.reserved0", read_u16(source, 18))?;
592        let applied_patch_mask = read_u32(source, 4);
593        let rejected_patch_mask = read_u32(source, 8);
594        validate_mask_u32(applied_patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
595        validate_mask_u32(rejected_patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
596
597        Ok(Self {
598            ack_status: SessionPatchAckStatus::try_from_u16(read_u16(source, 0))?,
599            reject_reason: SessionPatchRejectReason::try_from_u16(read_u16(source, 2))?,
600            applied_patch_mask,
601            rejected_patch_mask,
602            retry_after_ms: read_u32(source, 12),
603            effective_profile_id: read_u16(source, 16),
604            effective_target_cadence_x100: read_u32(source, 20),
605            effective_quality_tier: read_u16(source, 24),
606            effective_degrade_policy: read_u16(source, 26),
607            effective_lane_mask: read_u64(source, 28),
608            effective_codec_bitmap: read_u32(source, 36),
609            effective_compression_bitmap: read_u32(source, 40),
610            profile_patch_ack_bytes: read_u32(source, 44),
611        })
612    }
613
614    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
615        require_destination_len(destination, SESSION_PATCH_ACK_METADATA_LEN)?;
616        validate_mask_u32(self.applied_patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
617        validate_mask_u32(self.rejected_patch_mask, SESSION_PATCH_FIELD_KNOWN_MASK)?;
618
619        destination[..SESSION_PATCH_ACK_METADATA_LEN].fill(0);
620        write_u16(destination, 0, self.ack_status as u16);
621        write_u16(destination, 2, self.reject_reason as u16);
622        write_u32(destination, 4, self.applied_patch_mask);
623        write_u32(destination, 8, self.rejected_patch_mask);
624        write_u32(destination, 12, self.retry_after_ms);
625        write_u16(destination, 16, self.effective_profile_id);
626        write_u32(destination, 20, self.effective_target_cadence_x100);
627        write_u16(destination, 24, self.effective_quality_tier);
628        write_u16(destination, 26, self.effective_degrade_policy);
629        write_u64(destination, 28, self.effective_lane_mask);
630        write_u32(destination, 36, self.effective_codec_bitmap);
631        write_u32(destination, 40, self.effective_compression_bitmap);
632        write_u32(destination, 44, self.profile_patch_ack_bytes);
633        Ok(())
634    }
635
636    pub fn to_bytes(&self) -> Result<[u8; SESSION_PATCH_ACK_METADATA_LEN], NnrpError> {
637        let mut bytes = [0u8; SESSION_PATCH_ACK_METADATA_LEN];
638        self.write(&mut bytes)?;
639        Ok(bytes)
640    }
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq)]
644pub struct ResultHintMetadata {
645    pub applied_budget_policy: ResultHintBudgetPolicy,
646    pub congestion_state: ResultHintCongestionState,
647    pub reason: ResultHintReason,
648    pub retry_after_ms: u32,
649}
650
651impl ResultHintMetadata {
652    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
653        require_len(source, RESULT_HINT_METADATA_LEN)?;
654        Ok(Self {
655            applied_budget_policy: ResultHintBudgetPolicy::try_from_u32(read_u32(source, 0))?,
656            congestion_state: ResultHintCongestionState::try_from_u32(read_u32(source, 4))?,
657            reason: ResultHintReason::try_from_u32(read_u32(source, 8))?,
658            retry_after_ms: read_u32(source, 12),
659        })
660    }
661
662    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
663        require_destination_len(destination, RESULT_HINT_METADATA_LEN)?;
664        write_u32(destination, 0, self.applied_budget_policy as u32);
665        write_u32(destination, 4, self.congestion_state as u32);
666        write_u32(destination, 8, self.reason as u32);
667        write_u32(destination, 12, self.retry_after_ms);
668        Ok(())
669    }
670
671    pub fn to_bytes(&self) -> Result<[u8; RESULT_HINT_METADATA_LEN], NnrpError> {
672        let mut bytes = [0u8; RESULT_HINT_METADATA_LEN];
673        self.write(&mut bytes)?;
674        Ok(bytes)
675    }
676}
677
678#[derive(Debug, Clone, Copy, PartialEq, Eq)]
679pub struct ControlRequestMetadata {
680    pub operation_id: u64,
681    pub control_sequence: u64,
682    pub reason_code: u16,
683    pub source_role: u8,
684    pub flags: u8,
685    pub diagnostic_bytes: u32,
686}
687
688impl ControlRequestMetadata {
689    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
690        require_len(source, CONTROL_REQUEST_METADATA_LEN)?;
691        validate_mask_u8(source[19], CONTROL_REQUEST_FLAGS_KNOWN_MASK)?;
692        validate_zero_u64("control_request.reserved", read_u64(source, 24))?;
693        Ok(Self {
694            operation_id: read_u64(source, 0),
695            control_sequence: read_u64(source, 8),
696            reason_code: read_u16(source, 16),
697            source_role: source[18],
698            flags: source[19],
699            diagnostic_bytes: read_u32(source, 20),
700        })
701    }
702
703    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
704        require_destination_len(destination, CONTROL_REQUEST_METADATA_LEN)?;
705        validate_mask_u8(self.flags, CONTROL_REQUEST_FLAGS_KNOWN_MASK)?;
706        destination[..CONTROL_REQUEST_METADATA_LEN].fill(0);
707        write_u64(destination, 0, self.operation_id);
708        write_u64(destination, 8, self.control_sequence);
709        write_u16(destination, 16, self.reason_code);
710        destination[18] = self.source_role;
711        destination[19] = self.flags;
712        write_u32(destination, 20, self.diagnostic_bytes);
713        Ok(())
714    }
715
716    pub fn to_bytes(&self) -> Result<[u8; CONTROL_REQUEST_METADATA_LEN], NnrpError> {
717        let mut bytes = [0u8; CONTROL_REQUEST_METADATA_LEN];
718        self.write(&mut bytes)?;
719        Ok(bytes)
720    }
721
722    pub fn parse_with_diagnostics(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
723        let metadata = Self::parse(source)?;
724        let diagnostics = split_declared_tail(
725            source,
726            CONTROL_REQUEST_METADATA_LEN,
727            metadata.diagnostic_bytes as usize,
728            "control_request.diagnostic_bytes",
729        )?;
730        Ok((metadata, diagnostics))
731    }
732
733    pub fn to_vec_with_diagnostics(&self, diagnostics: &[u8]) -> Result<Vec<u8>, NnrpError> {
734        require_declared_len(
735            "control_request.diagnostic_bytes",
736            self.diagnostic_bytes as usize,
737            diagnostics.len(),
738        )?;
739        let mut bytes = self.to_bytes()?.to_vec();
740        bytes.extend_from_slice(diagnostics);
741        Ok(bytes)
742    }
743}
744
745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
746pub struct SchedulingMetadata {
747    pub operation_id: u64,
748    pub control_sequence: u64,
749    pub priority_class: u16,
750    pub priority_delta: i16,
751    pub deadline_unix_ms: u64,
752    pub flags: u32,
753}
754
755impl SchedulingMetadata {
756    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
757        require_len(source, SCHEDULING_METADATA_LEN)?;
758        let flags = read_u32(source, 28);
759        validate_mask_u32(flags, SCHEDULING_FLAGS_KNOWN_MASK)?;
760        Ok(Self {
761            operation_id: read_u64(source, 0),
762            control_sequence: read_u64(source, 8),
763            priority_class: read_u16(source, 16),
764            priority_delta: read_i16(source, 18),
765            deadline_unix_ms: read_u64(source, 20),
766            flags,
767        })
768    }
769
770    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
771        require_destination_len(destination, SCHEDULING_METADATA_LEN)?;
772        validate_mask_u32(self.flags, SCHEDULING_FLAGS_KNOWN_MASK)?;
773        write_u64(destination, 0, self.operation_id);
774        write_u64(destination, 8, self.control_sequence);
775        write_u16(destination, 16, self.priority_class);
776        write_i16(destination, 18, self.priority_delta);
777        write_u64(destination, 20, self.deadline_unix_ms);
778        write_u32(destination, 28, self.flags);
779        Ok(())
780    }
781
782    pub fn to_bytes(&self) -> Result<[u8; SCHEDULING_METADATA_LEN], NnrpError> {
783        let mut bytes = [0u8; SCHEDULING_METADATA_LEN];
784        self.write(&mut bytes)?;
785        Ok(bytes)
786    }
787}
788
789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
790pub struct SupersedeMetadata {
791    pub old_operation_id: u64,
792    pub new_operation_id: u64,
793    pub control_sequence: u64,
794    pub drop_reason_code: u16,
795    pub flags: u16,
796    pub diagnostic_bytes: u32,
797}
798
799impl SupersedeMetadata {
800    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
801        require_len(source, SUPERSEDE_METADATA_LEN)?;
802        let flags = read_u16(source, 26);
803        validate_mask_u16(flags, SUPERSEDE_FLAGS_KNOWN_MASK)?;
804        Ok(Self {
805            old_operation_id: read_u64(source, 0),
806            new_operation_id: read_u64(source, 8),
807            control_sequence: read_u64(source, 16),
808            drop_reason_code: read_u16(source, 24),
809            flags,
810            diagnostic_bytes: read_u32(source, 28),
811        })
812    }
813
814    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
815        require_destination_len(destination, SUPERSEDE_METADATA_LEN)?;
816        validate_mask_u16(self.flags, SUPERSEDE_FLAGS_KNOWN_MASK)?;
817        write_u64(destination, 0, self.old_operation_id);
818        write_u64(destination, 8, self.new_operation_id);
819        write_u64(destination, 16, self.control_sequence);
820        write_u16(destination, 24, self.drop_reason_code);
821        write_u16(destination, 26, self.flags);
822        write_u32(destination, 28, self.diagnostic_bytes);
823        Ok(())
824    }
825
826    pub fn to_bytes(&self) -> Result<[u8; SUPERSEDE_METADATA_LEN], NnrpError> {
827        let mut bytes = [0u8; SUPERSEDE_METADATA_LEN];
828        self.write(&mut bytes)?;
829        Ok(bytes)
830    }
831
832    pub fn parse_with_diagnostics(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
833        let metadata = Self::parse(source)?;
834        let diagnostics = split_declared_tail(
835            source,
836            SUPERSEDE_METADATA_LEN,
837            metadata.diagnostic_bytes as usize,
838            "supersede.diagnostic_bytes",
839        )?;
840        Ok((metadata, diagnostics))
841    }
842
843    pub fn to_vec_with_diagnostics(&self, diagnostics: &[u8]) -> Result<Vec<u8>, NnrpError> {
844        require_declared_len(
845            "supersede.diagnostic_bytes",
846            self.diagnostic_bytes as usize,
847            diagnostics.len(),
848        )?;
849        let mut bytes = self.to_bytes()?.to_vec();
850        bytes.extend_from_slice(diagnostics);
851        Ok(bytes)
852    }
853}
854
855#[derive(Debug, Clone, Copy, PartialEq, Eq)]
856pub struct BudgetMetadata {
857    pub operation_id: u64,
858    pub compute_budget_units: u64,
859    pub memory_budget_bytes: u64,
860    pub bandwidth_budget_bytes: u64,
861    pub token_budget: u32,
862    pub flags: u32,
863}
864
865impl BudgetMetadata {
866    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
867        require_len(source, BUDGET_METADATA_LEN)?;
868        let flags = read_u32(source, 36);
869        validate_mask_u32(flags, BUDGET_FLAGS_KNOWN_MASK)?;
870        Ok(Self {
871            operation_id: read_u64(source, 0),
872            compute_budget_units: read_u64(source, 8),
873            memory_budget_bytes: read_u64(source, 16),
874            bandwidth_budget_bytes: read_u64(source, 24),
875            token_budget: read_u32(source, 32),
876            flags,
877        })
878    }
879
880    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
881        require_destination_len(destination, BUDGET_METADATA_LEN)?;
882        validate_mask_u32(self.flags, BUDGET_FLAGS_KNOWN_MASK)?;
883        write_u64(destination, 0, self.operation_id);
884        write_u64(destination, 8, self.compute_budget_units);
885        write_u64(destination, 16, self.memory_budget_bytes);
886        write_u64(destination, 24, self.bandwidth_budget_bytes);
887        write_u32(destination, 32, self.token_budget);
888        write_u32(destination, 36, self.flags);
889        Ok(())
890    }
891
892    pub fn to_bytes(&self) -> Result<[u8; BUDGET_METADATA_LEN], NnrpError> {
893        let mut bytes = [0u8; BUDGET_METADATA_LEN];
894        self.write(&mut bytes)?;
895        Ok(bytes)
896    }
897}
898
899#[derive(Debug, Clone, Copy, PartialEq, Eq)]
900pub struct ProgressMetadata {
901    pub operation_id: u64,
902    pub progress_sequence: u64,
903    pub stage_code: u16,
904    pub percent_x100: u16,
905    pub object_id: u64,
906    pub body_bytes: u32,
907}
908
909impl ProgressMetadata {
910    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
911        require_len(source, PROGRESS_METADATA_LEN)?;
912        let percent_x100 = read_u16(source, 18);
913        validate_percent_x100(percent_x100)?;
914        Ok(Self {
915            operation_id: read_u64(source, 0),
916            progress_sequence: read_u64(source, 8),
917            stage_code: read_u16(source, 16),
918            percent_x100,
919            object_id: read_u64(source, 20),
920            body_bytes: read_u32(source, 28),
921        })
922    }
923
924    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
925        require_destination_len(destination, PROGRESS_METADATA_LEN)?;
926        validate_percent_x100(self.percent_x100)?;
927        write_u64(destination, 0, self.operation_id);
928        write_u64(destination, 8, self.progress_sequence);
929        write_u16(destination, 16, self.stage_code);
930        write_u16(destination, 18, self.percent_x100);
931        write_u64(destination, 20, self.object_id);
932        write_u32(destination, 28, self.body_bytes);
933        Ok(())
934    }
935
936    pub fn to_bytes(&self) -> Result<[u8; PROGRESS_METADATA_LEN], NnrpError> {
937        let mut bytes = [0u8; PROGRESS_METADATA_LEN];
938        self.write(&mut bytes)?;
939        Ok(bytes)
940    }
941
942    pub fn parse_with_body(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
943        let metadata = Self::parse(source)?;
944        let body = split_declared_tail(
945            source,
946            PROGRESS_METADATA_LEN,
947            metadata.body_bytes as usize,
948            "progress.body_bytes",
949        )?;
950        Ok((metadata, body))
951    }
952
953    pub fn to_vec_with_body(&self, body: &[u8]) -> Result<Vec<u8>, NnrpError> {
954        require_declared_len("progress.body_bytes", self.body_bytes as usize, body.len())?;
955        let mut bytes = self.to_bytes()?.to_vec();
956        bytes.extend_from_slice(body);
957        Ok(bytes)
958    }
959}
960
961#[derive(Debug, Clone, Copy, PartialEq, Eq)]
962pub struct PartialResultMetadata {
963    pub operation_id: u64,
964    pub result_sequence: u64,
965    pub object_id: u64,
966    pub delta_sequence: u64,
967    pub body_bytes: u32,
968    pub flags: u32,
969}
970
971impl PartialResultMetadata {
972    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
973        require_len(source, PARTIAL_RESULT_METADATA_LEN)?;
974        let flags = read_u32(source, 36);
975        validate_mask_u32(flags, PARTIAL_RESULT_FLAGS_KNOWN_MASK)?;
976        Ok(Self {
977            operation_id: read_u64(source, 0),
978            result_sequence: read_u64(source, 8),
979            object_id: read_u64(source, 16),
980            delta_sequence: read_u64(source, 24),
981            body_bytes: read_u32(source, 32),
982            flags,
983        })
984    }
985
986    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
987        require_destination_len(destination, PARTIAL_RESULT_METADATA_LEN)?;
988        validate_mask_u32(self.flags, PARTIAL_RESULT_FLAGS_KNOWN_MASK)?;
989        write_u64(destination, 0, self.operation_id);
990        write_u64(destination, 8, self.result_sequence);
991        write_u64(destination, 16, self.object_id);
992        write_u64(destination, 24, self.delta_sequence);
993        write_u32(destination, 32, self.body_bytes);
994        write_u32(destination, 36, self.flags);
995        Ok(())
996    }
997
998    pub fn to_bytes(&self) -> Result<[u8; PARTIAL_RESULT_METADATA_LEN], NnrpError> {
999        let mut bytes = [0u8; PARTIAL_RESULT_METADATA_LEN];
1000        self.write(&mut bytes)?;
1001        Ok(bytes)
1002    }
1003
1004    pub fn parse_with_body(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1005        let metadata = Self::parse(source)?;
1006        let body = split_declared_tail(
1007            source,
1008            PARTIAL_RESULT_METADATA_LEN,
1009            metadata.body_bytes as usize,
1010            "partial_result.body_bytes",
1011        )?;
1012        Ok((metadata, body))
1013    }
1014
1015    pub fn to_vec_with_body(&self, body: &[u8]) -> Result<Vec<u8>, NnrpError> {
1016        require_declared_len(
1017            "partial_result.body_bytes",
1018            self.body_bytes as usize,
1019            body.len(),
1020        )?;
1021        let mut bytes = self.to_bytes()?.to_vec();
1022        bytes.extend_from_slice(body);
1023        Ok(bytes)
1024    }
1025}
1026
1027#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1028pub struct PressureMetadata {
1029    pub scope_id: u64,
1030    pub credit_window: u64,
1031    pub pressure_level: u16,
1032    pub pressure_reason: u16,
1033    pub retry_after_ms: u32,
1034    pub flags: u32,
1035}
1036
1037impl PressureMetadata {
1038    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1039        require_len(source, PRESSURE_METADATA_LEN)?;
1040        let flags = read_u32(source, 24);
1041        validate_mask_u32(flags, PRESSURE_FLAGS_KNOWN_MASK)?;
1042        validate_zero_u32("pressure.reserved", read_u32(source, 28))?;
1043        Ok(Self {
1044            scope_id: read_u64(source, 0),
1045            credit_window: read_u64(source, 8),
1046            pressure_level: read_u16(source, 16),
1047            pressure_reason: read_u16(source, 18),
1048            retry_after_ms: read_u32(source, 20),
1049            flags,
1050        })
1051    }
1052
1053    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1054        require_destination_len(destination, PRESSURE_METADATA_LEN)?;
1055        validate_mask_u32(self.flags, PRESSURE_FLAGS_KNOWN_MASK)?;
1056        destination[..PRESSURE_METADATA_LEN].fill(0);
1057        write_u64(destination, 0, self.scope_id);
1058        write_u64(destination, 8, self.credit_window);
1059        write_u16(destination, 16, self.pressure_level);
1060        write_u16(destination, 18, self.pressure_reason);
1061        write_u32(destination, 20, self.retry_after_ms);
1062        write_u32(destination, 24, self.flags);
1063        Ok(())
1064    }
1065
1066    pub fn to_bytes(&self) -> Result<[u8; PRESSURE_METADATA_LEN], NnrpError> {
1067        let mut bytes = [0u8; PRESSURE_METADATA_LEN];
1068        self.write(&mut bytes)?;
1069        Ok(bytes)
1070    }
1071}
1072
1073#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1074pub struct CapabilityMetadata {
1075    pub profile_id: u16,
1076    pub capability_count: u16,
1077    pub cost_model_id: u16,
1078    pub preference_rank: u16,
1079    pub limit_bytes: u64,
1080    pub limit_units: u64,
1081    pub body_bytes: u32,
1082    pub flags: u32,
1083}
1084
1085impl CapabilityMetadata {
1086    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1087        require_len(source, CAPABILITY_METADATA_LEN)?;
1088        let flags = read_u32(source, 28);
1089        validate_mask_u32(flags, CAPABILITY_FLAGS_KNOWN_MASK)?;
1090        Ok(Self {
1091            profile_id: read_u16(source, 0),
1092            capability_count: read_u16(source, 2),
1093            cost_model_id: read_u16(source, 4),
1094            preference_rank: read_u16(source, 6),
1095            limit_bytes: read_u64(source, 8),
1096            limit_units: read_u64(source, 16),
1097            body_bytes: read_u32(source, 24),
1098            flags,
1099        })
1100    }
1101
1102    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1103        require_destination_len(destination, CAPABILITY_METADATA_LEN)?;
1104        validate_mask_u32(self.flags, CAPABILITY_FLAGS_KNOWN_MASK)?;
1105        write_u16(destination, 0, self.profile_id);
1106        write_u16(destination, 2, self.capability_count);
1107        write_u16(destination, 4, self.cost_model_id);
1108        write_u16(destination, 6, self.preference_rank);
1109        write_u64(destination, 8, self.limit_bytes);
1110        write_u64(destination, 16, self.limit_units);
1111        write_u32(destination, 24, self.body_bytes);
1112        write_u32(destination, 28, self.flags);
1113        Ok(())
1114    }
1115
1116    pub fn to_bytes(&self) -> Result<[u8; CAPABILITY_METADATA_LEN], NnrpError> {
1117        let mut bytes = [0u8; CAPABILITY_METADATA_LEN];
1118        self.write(&mut bytes)?;
1119        Ok(bytes)
1120    }
1121
1122    pub fn parse_with_body(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1123        let metadata = Self::parse(source)?;
1124        let body = split_declared_tail(
1125            source,
1126            CAPABILITY_METADATA_LEN,
1127            metadata.body_bytes as usize,
1128            "capability.body_bytes",
1129        )?;
1130        Ok((metadata, body))
1131    }
1132
1133    pub fn to_vec_with_body(&self, body: &[u8]) -> Result<Vec<u8>, NnrpError> {
1134        require_declared_len(
1135            "capability.body_bytes",
1136            self.body_bytes as usize,
1137            body.len(),
1138        )?;
1139        let mut bytes = self.to_bytes()?.to_vec();
1140        bytes.extend_from_slice(body);
1141        Ok(bytes)
1142    }
1143}
1144
1145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1146pub struct RouteHintMetadata {
1147    pub operation_id: u64,
1148    pub route_id: u32,
1149    pub executor_class: u16,
1150    pub affinity_class: u16,
1151    pub deadline_unix_ms: u64,
1152    pub body_bytes: u32,
1153    pub flags: u32,
1154}
1155
1156impl RouteHintMetadata {
1157    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1158        require_len(source, ROUTE_HINT_METADATA_LEN)?;
1159        let flags = read_u32(source, 28);
1160        validate_mask_u32(flags, ROUTE_HINT_FLAGS_KNOWN_MASK)?;
1161        Ok(Self {
1162            operation_id: read_u64(source, 0),
1163            route_id: read_u32(source, 8),
1164            executor_class: read_u16(source, 12),
1165            affinity_class: read_u16(source, 14),
1166            deadline_unix_ms: read_u64(source, 16),
1167            body_bytes: read_u32(source, 24),
1168            flags,
1169        })
1170    }
1171
1172    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1173        require_destination_len(destination, ROUTE_HINT_METADATA_LEN)?;
1174        validate_mask_u32(self.flags, ROUTE_HINT_FLAGS_KNOWN_MASK)?;
1175        write_u64(destination, 0, self.operation_id);
1176        write_u32(destination, 8, self.route_id);
1177        write_u16(destination, 12, self.executor_class);
1178        write_u16(destination, 14, self.affinity_class);
1179        write_u64(destination, 16, self.deadline_unix_ms);
1180        write_u32(destination, 24, self.body_bytes);
1181        write_u32(destination, 28, self.flags);
1182        Ok(())
1183    }
1184
1185    pub fn to_bytes(&self) -> Result<[u8; ROUTE_HINT_METADATA_LEN], NnrpError> {
1186        let mut bytes = [0u8; ROUTE_HINT_METADATA_LEN];
1187        self.write(&mut bytes)?;
1188        Ok(bytes)
1189    }
1190
1191    pub fn parse_with_body(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1192        let metadata = Self::parse(source)?;
1193        let body = split_declared_tail(
1194            source,
1195            ROUTE_HINT_METADATA_LEN,
1196            metadata.body_bytes as usize,
1197            "route_hint.body_bytes",
1198        )?;
1199        Ok((metadata, body))
1200    }
1201
1202    pub fn to_vec_with_body(&self, body: &[u8]) -> Result<Vec<u8>, NnrpError> {
1203        require_declared_len(
1204            "route_hint.body_bytes",
1205            self.body_bytes as usize,
1206            body.len(),
1207        )?;
1208        let mut bytes = self.to_bytes()?.to_vec();
1209        bytes.extend_from_slice(body);
1210        Ok(bytes)
1211    }
1212}
1213
1214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1215pub struct TraceContextMetadata {
1216    pub trace_id: u64,
1217    pub span_id: u64,
1218    pub parent_span_id: u64,
1219    pub stage_code: u16,
1220    pub flags: u16,
1221    pub body_bytes: u32,
1222}
1223
1224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1225pub struct TraceContextIds {
1226    pub trace_id: u64,
1227    pub span_id: u64,
1228    pub parent_span_id: u64,
1229}
1230
1231#[derive(Debug, PartialEq, Eq)]
1232pub struct TraceContextDecodeError {
1233    pub trace: Option<TraceContextIds>,
1234    pub error: NnrpError,
1235}
1236
1237impl TraceContextMetadata {
1238    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1239        require_len(source, TRACE_CONTEXT_METADATA_LEN)?;
1240        let flags = read_u16(source, 26);
1241        validate_mask_u16(flags, TRACE_CONTEXT_FLAGS_KNOWN_MASK)?;
1242        Ok(Self {
1243            trace_id: read_u64(source, 0),
1244            span_id: read_u64(source, 8),
1245            parent_span_id: read_u64(source, 16),
1246            stage_code: read_u16(source, 24),
1247            flags,
1248            body_bytes: read_u32(source, 28),
1249        })
1250    }
1251
1252    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1253        require_destination_len(destination, TRACE_CONTEXT_METADATA_LEN)?;
1254        validate_mask_u16(self.flags, TRACE_CONTEXT_FLAGS_KNOWN_MASK)?;
1255        write_u64(destination, 0, self.trace_id);
1256        write_u64(destination, 8, self.span_id);
1257        write_u64(destination, 16, self.parent_span_id);
1258        write_u16(destination, 24, self.stage_code);
1259        write_u16(destination, 26, self.flags);
1260        write_u32(destination, 28, self.body_bytes);
1261        Ok(())
1262    }
1263
1264    pub fn to_bytes(&self) -> Result<[u8; TRACE_CONTEXT_METADATA_LEN], NnrpError> {
1265        let mut bytes = [0u8; TRACE_CONTEXT_METADATA_LEN];
1266        self.write(&mut bytes)?;
1267        Ok(bytes)
1268    }
1269
1270    pub fn parse_with_body(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1271        let metadata = Self::parse(source)?;
1272        let body = split_declared_tail(
1273            source,
1274            TRACE_CONTEXT_METADATA_LEN,
1275            metadata.body_bytes as usize,
1276            "trace_context.body_bytes",
1277        )?;
1278        Ok((metadata, body))
1279    }
1280
1281    pub fn parse_with_body_and_error_context(
1282        source: &[u8],
1283    ) -> Result<(Self, &[u8]), TraceContextDecodeError> {
1284        Self::parse_with_body(source)
1285            .and_then(|(metadata, body)| {
1286                validate_trace_context_semantics(&metadata)?;
1287                Ok((metadata, body))
1288            })
1289            .map_err(|error| TraceContextDecodeError {
1290                trace: trace_context_ids_from_source(source),
1291                error,
1292            })
1293    }
1294
1295    pub fn to_vec_with_body(&self, body: &[u8]) -> Result<Vec<u8>, NnrpError> {
1296        require_declared_len(
1297            "trace_context.body_bytes",
1298            self.body_bytes as usize,
1299            body.len(),
1300        )?;
1301        let mut bytes = self.to_bytes()?.to_vec();
1302        bytes.extend_from_slice(body);
1303        Ok(bytes)
1304    }
1305}
1306
1307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1308pub struct ResultDropReasonMetadata {
1309    pub operation_id: u64,
1310    pub result_sequence: u64,
1311    pub drop_reason_code: u16,
1312    pub source_role: u8,
1313    pub flags: u8,
1314    pub diagnostic_bytes: u32,
1315}
1316
1317impl ResultDropReasonMetadata {
1318    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1319        require_len(source, RESULT_DROP_REASON_METADATA_LEN)?;
1320        validate_mask_u8(source[19], RESULT_DROP_FLAGS_KNOWN_MASK)?;
1321        validate_zero_u64("result_drop_reason.reserved", read_u64(source, 24))?;
1322        Ok(Self {
1323            operation_id: read_u64(source, 0),
1324            result_sequence: read_u64(source, 8),
1325            drop_reason_code: read_u16(source, 16),
1326            source_role: source[18],
1327            flags: source[19],
1328            diagnostic_bytes: read_u32(source, 20),
1329        })
1330    }
1331
1332    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1333        require_destination_len(destination, RESULT_DROP_REASON_METADATA_LEN)?;
1334        validate_mask_u8(self.flags, RESULT_DROP_FLAGS_KNOWN_MASK)?;
1335        destination[..RESULT_DROP_REASON_METADATA_LEN].fill(0);
1336        write_u64(destination, 0, self.operation_id);
1337        write_u64(destination, 8, self.result_sequence);
1338        write_u16(destination, 16, self.drop_reason_code);
1339        destination[18] = self.source_role;
1340        destination[19] = self.flags;
1341        write_u32(destination, 20, self.diagnostic_bytes);
1342        Ok(())
1343    }
1344
1345    pub fn to_bytes(&self) -> Result<[u8; RESULT_DROP_REASON_METADATA_LEN], NnrpError> {
1346        let mut bytes = [0u8; RESULT_DROP_REASON_METADATA_LEN];
1347        self.write(&mut bytes)?;
1348        Ok(bytes)
1349    }
1350
1351    pub fn parse_with_diagnostics(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1352        let metadata = Self::parse(source)?;
1353        let diagnostics = split_declared_tail(
1354            source,
1355            RESULT_DROP_REASON_METADATA_LEN,
1356            metadata.diagnostic_bytes as usize,
1357            "result_drop_reason.diagnostic_bytes",
1358        )?;
1359        Ok((metadata, diagnostics))
1360    }
1361
1362    pub fn to_vec_with_diagnostics(&self, diagnostics: &[u8]) -> Result<Vec<u8>, NnrpError> {
1363        require_declared_len(
1364            "result_drop_reason.diagnostic_bytes",
1365            self.diagnostic_bytes as usize,
1366            diagnostics.len(),
1367        )?;
1368        let mut bytes = self.to_bytes()?.to_vec();
1369        bytes.extend_from_slice(diagnostics);
1370        Ok(bytes)
1371    }
1372}
1373
1374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1375pub struct RecoverableErrorMetadata {
1376    pub error_code: u32,
1377    pub error_scope: ErrorScope,
1378    pub recovery_action: u16,
1379    pub source_role: u8,
1380    pub flags: u8,
1381    pub retry_after_ms: u32,
1382    pub related_session_id: u32,
1383    pub related_frame_id: u32,
1384    pub related_view_id: u32,
1385    pub diagnostic_bytes: u32,
1386}
1387
1388impl RecoverableErrorMetadata {
1389    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1390        require_len(source, RECOVERABLE_ERROR_METADATA_LEN)?;
1391        let flags = source[11];
1392        validate_mask_u8(flags, RECOVERABLE_ERROR_FLAGS_KNOWN_MASK)?;
1393        Ok(Self {
1394            error_code: read_u32(source, 0),
1395            error_scope: ErrorScope::try_from_u32(read_u32(source, 4))?,
1396            recovery_action: read_u16(source, 8),
1397            source_role: source[10],
1398            flags,
1399            retry_after_ms: read_u32(source, 12),
1400            related_session_id: read_u32(source, 16),
1401            related_frame_id: read_u32(source, 20),
1402            related_view_id: read_u32(source, 24),
1403            diagnostic_bytes: read_u32(source, 28),
1404        })
1405    }
1406
1407    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1408        require_destination_len(destination, RECOVERABLE_ERROR_METADATA_LEN)?;
1409        validate_mask_u8(self.flags, RECOVERABLE_ERROR_FLAGS_KNOWN_MASK)?;
1410        write_u32(destination, 0, self.error_code);
1411        write_u32(destination, 4, self.error_scope as u32);
1412        write_u16(destination, 8, self.recovery_action);
1413        destination[10] = self.source_role;
1414        destination[11] = self.flags;
1415        write_u32(destination, 12, self.retry_after_ms);
1416        write_u32(destination, 16, self.related_session_id);
1417        write_u32(destination, 20, self.related_frame_id);
1418        write_u32(destination, 24, self.related_view_id);
1419        write_u32(destination, 28, self.diagnostic_bytes);
1420        Ok(())
1421    }
1422
1423    pub fn to_bytes(&self) -> Result<[u8; RECOVERABLE_ERROR_METADATA_LEN], NnrpError> {
1424        let mut bytes = [0u8; RECOVERABLE_ERROR_METADATA_LEN];
1425        self.write(&mut bytes)?;
1426        Ok(bytes)
1427    }
1428
1429    pub fn parse_with_diagnostics(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1430        let metadata = Self::parse(source)?;
1431        let diagnostics = split_declared_tail(
1432            source,
1433            RECOVERABLE_ERROR_METADATA_LEN,
1434            metadata.diagnostic_bytes as usize,
1435            "recoverable_error.diagnostic_bytes",
1436        )?;
1437        Ok((metadata, diagnostics))
1438    }
1439
1440    pub fn to_vec_with_diagnostics(&self, diagnostics: &[u8]) -> Result<Vec<u8>, NnrpError> {
1441        require_declared_len(
1442            "recoverable_error.diagnostic_bytes",
1443            self.diagnostic_bytes as usize,
1444            diagnostics.len(),
1445        )?;
1446        let mut bytes = self.to_bytes()?.to_vec();
1447        bytes.extend_from_slice(diagnostics);
1448        Ok(bytes)
1449    }
1450}
1451
1452#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1453pub struct RetryAfterMetadata {
1454    pub scope_id: u64,
1455    pub control_sequence: u64,
1456    pub retry_after_ms: u32,
1457    pub jitter_ms: u32,
1458    pub reason_code: u16,
1459    pub source_role: u8,
1460    pub flags: u8,
1461    pub diagnostic_bytes: u32,
1462}
1463
1464impl RetryAfterMetadata {
1465    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1466        require_len(source, RETRY_AFTER_METADATA_LEN)?;
1467        let flags = source[27];
1468        validate_mask_u8(flags, RETRY_AFTER_FLAGS_KNOWN_MASK)?;
1469        Ok(Self {
1470            scope_id: read_u64(source, 0),
1471            control_sequence: read_u64(source, 8),
1472            retry_after_ms: read_u32(source, 16),
1473            jitter_ms: read_u32(source, 20),
1474            reason_code: read_u16(source, 24),
1475            source_role: source[26],
1476            flags,
1477            diagnostic_bytes: read_u32(source, 28),
1478        })
1479    }
1480
1481    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1482        require_destination_len(destination, RETRY_AFTER_METADATA_LEN)?;
1483        validate_mask_u8(self.flags, RETRY_AFTER_FLAGS_KNOWN_MASK)?;
1484        write_u64(destination, 0, self.scope_id);
1485        write_u64(destination, 8, self.control_sequence);
1486        write_u32(destination, 16, self.retry_after_ms);
1487        write_u32(destination, 20, self.jitter_ms);
1488        write_u16(destination, 24, self.reason_code);
1489        destination[26] = self.source_role;
1490        destination[27] = self.flags;
1491        write_u32(destination, 28, self.diagnostic_bytes);
1492        Ok(())
1493    }
1494
1495    pub fn to_bytes(&self) -> Result<[u8; RETRY_AFTER_METADATA_LEN], NnrpError> {
1496        let mut bytes = [0u8; RETRY_AFTER_METADATA_LEN];
1497        self.write(&mut bytes)?;
1498        Ok(bytes)
1499    }
1500
1501    pub fn parse_with_diagnostics(source: &[u8]) -> Result<(Self, &[u8]), NnrpError> {
1502        let metadata = Self::parse(source)?;
1503        let diagnostics = split_declared_tail(
1504            source,
1505            RETRY_AFTER_METADATA_LEN,
1506            metadata.diagnostic_bytes as usize,
1507            "retry_after.diagnostic_bytes",
1508        )?;
1509        Ok((metadata, diagnostics))
1510    }
1511
1512    pub fn to_vec_with_diagnostics(&self, diagnostics: &[u8]) -> Result<Vec<u8>, NnrpError> {
1513        require_declared_len(
1514            "retry_after.diagnostic_bytes",
1515            self.diagnostic_bytes as usize,
1516            diagnostics.len(),
1517        )?;
1518        let mut bytes = self.to_bytes()?.to_vec();
1519        bytes.extend_from_slice(diagnostics);
1520        Ok(bytes)
1521    }
1522}
1523
1524pub fn validate_control_request_semantics(
1525    message_type: MessageType,
1526    metadata: &ControlRequestMetadata,
1527) -> Result<(), NnrpError> {
1528    match message_type {
1529        MessageType::Cancel | MessageType::Abort => {}
1530        _ => {
1531            return Err(NnrpError::InvalidProtocolCombination {
1532                rule: "control request metadata requires CANCEL or ABORT",
1533            });
1534        }
1535    }
1536
1537    validate_standard_role(metadata.source_role)?;
1538    Ok(())
1539}
1540
1541pub fn validate_scheduling_semantics(
1542    message_type: MessageType,
1543    metadata: &SchedulingMetadata,
1544) -> Result<(), NnrpError> {
1545    match message_type {
1546        MessageType::PriorityUpdate => Ok(()),
1547        MessageType::Deadline | MessageType::ExpireAt => {
1548            if metadata.deadline_unix_ms == 0 {
1549                return Err(NnrpError::InvalidProtocolCombination {
1550                    rule: "DEADLINE and EXPIRE_AT require deadline_unix_ms",
1551                });
1552            }
1553            Ok(())
1554        }
1555        _ => Err(NnrpError::InvalidProtocolCombination {
1556            rule: "scheduling metadata requires PRIORITY_UPDATE, DEADLINE, or EXPIRE_AT",
1557        }),
1558    }
1559}
1560
1561pub fn validate_progress_semantics(metadata: &ProgressMetadata) -> Result<(), NnrpError> {
1562    if metadata.operation_id == 0 {
1563        return Err(NnrpError::InvalidProtocolCombination {
1564            rule: "PROGRESS requires a non-zero operation_id",
1565        });
1566    }
1567    Ok(())
1568}
1569
1570pub fn validate_partial_result_semantics(
1571    metadata: &PartialResultMetadata,
1572) -> Result<(), NnrpError> {
1573    if metadata.operation_id == 0 {
1574        return Err(NnrpError::InvalidProtocolCombination {
1575            rule: "PARTIAL_RESULT requires a non-zero operation_id",
1576        });
1577    }
1578    if metadata.flags & 0x0000_0002 != 0 && metadata.object_id == 0 {
1579        return Err(NnrpError::InvalidProtocolCombination {
1580            rule: "PARTIAL_RESULT object-ref flag requires object_id",
1581        });
1582    }
1583    if metadata.body_bytes == 0 && metadata.object_id == 0 {
1584        return Err(NnrpError::InvalidProtocolCombination {
1585            rule: "PARTIAL_RESULT requires inline body bytes or object_id",
1586        });
1587    }
1588    Ok(())
1589}
1590
1591pub fn validate_pressure_semantics(
1592    message_type: MessageType,
1593    metadata: &PressureMetadata,
1594) -> Result<(), NnrpError> {
1595    match message_type {
1596        MessageType::Backpressure => {
1597            if metadata.pressure_level == 0 {
1598                return Err(NnrpError::InvalidProtocolCombination {
1599                    rule: "BACKPRESSURE requires non-zero pressure_level",
1600                });
1601            }
1602            validate_pressure_level(metadata.pressure_level)
1603        }
1604        MessageType::CreditUpdate => Ok(()),
1605        _ => Err(NnrpError::InvalidProtocolCombination {
1606            rule: "pressure metadata requires BACKPRESSURE or CREDIT_UPDATE",
1607        }),
1608    }
1609}
1610
1611pub fn validate_trace_context_semantics(metadata: &TraceContextMetadata) -> Result<(), NnrpError> {
1612    if metadata.trace_id == 0 || metadata.span_id == 0 {
1613        return Err(NnrpError::InvalidProtocolCombination {
1614            rule: "TRACE_CONTEXT requires non-zero trace_id and span_id",
1615        });
1616    }
1617    Ok(())
1618}
1619
1620pub fn validate_result_drop_reason_semantics(
1621    metadata: &ResultDropReasonMetadata,
1622) -> Result<(), NnrpError> {
1623    if metadata.operation_id == 0 {
1624        return Err(NnrpError::InvalidProtocolCombination {
1625            rule: "RESULT_DROP_REASON requires a non-zero operation_id",
1626        });
1627    }
1628    if metadata.drop_reason_code == 0 {
1629        return Err(NnrpError::InvalidProtocolCombination {
1630            rule: "RESULT_DROP_REASON requires a non-zero drop_reason_code",
1631        });
1632    }
1633    validate_standard_role(metadata.source_role)?;
1634    Ok(())
1635}
1636
1637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1638pub struct TransportProbeMetadata {
1639    pub probe_id: u32,
1640    pub probe_payload_bytes: u32,
1641    pub client_send_ts_us: u64,
1642}
1643
1644impl TransportProbeMetadata {
1645    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1646        require_len(source, TRANSPORT_PROBE_METADATA_LEN)?;
1647        Ok(Self {
1648            probe_id: read_u32(source, 0),
1649            probe_payload_bytes: read_u32(source, 4),
1650            client_send_ts_us: read_u64(source, 8),
1651        })
1652    }
1653
1654    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1655        require_destination_len(destination, TRANSPORT_PROBE_METADATA_LEN)?;
1656        write_u32(destination, 0, self.probe_id);
1657        write_u32(destination, 4, self.probe_payload_bytes);
1658        write_u64(destination, 8, self.client_send_ts_us);
1659        Ok(())
1660    }
1661
1662    pub fn to_bytes(&self) -> Result<[u8; TRANSPORT_PROBE_METADATA_LEN], NnrpError> {
1663        let mut bytes = [0u8; TRANSPORT_PROBE_METADATA_LEN];
1664        self.write(&mut bytes)?;
1665        Ok(bytes)
1666    }
1667}
1668
1669#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1670pub struct TransportProbeAckMetadata {
1671    pub probe_id: u32,
1672    pub server_recv_ts_us: u64,
1673}
1674
1675impl TransportProbeAckMetadata {
1676    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1677        require_len(source, TRANSPORT_PROBE_ACK_METADATA_LEN)?;
1678        validate_zero_u32("transport_probe_ack.reserved0", read_u32(source, 4))?;
1679        Ok(Self {
1680            probe_id: read_u32(source, 0),
1681            server_recv_ts_us: read_u64(source, 8),
1682        })
1683    }
1684
1685    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1686        require_destination_len(destination, TRANSPORT_PROBE_ACK_METADATA_LEN)?;
1687        destination[..TRANSPORT_PROBE_ACK_METADATA_LEN].fill(0);
1688        write_u32(destination, 0, self.probe_id);
1689        write_u64(destination, 8, self.server_recv_ts_us);
1690        Ok(())
1691    }
1692
1693    pub fn to_bytes(&self) -> Result<[u8; TRANSPORT_PROBE_ACK_METADATA_LEN], NnrpError> {
1694        let mut bytes = [0u8; TRANSPORT_PROBE_ACK_METADATA_LEN];
1695        self.write(&mut bytes)?;
1696        Ok(bytes)
1697    }
1698}
1699
1700#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1701pub struct SessionMigrateMetadata {
1702    pub old_transport_id: TransportId,
1703    pub new_transport_id: TransportId,
1704    pub last_result_frame_id: u64,
1705    pub client_migrate_ts_us: u64,
1706}
1707
1708impl SessionMigrateMetadata {
1709    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1710        require_len(source, SESSION_MIGRATE_METADATA_LEN)?;
1711        let old_transport_id = TransportId::try_from_u32(read_u32(source, 0))?;
1712        let new_transport_id = TransportId::try_from_u32(read_u32(source, 4))?;
1713        validate_specified_transport(old_transport_id, "session_migrate.old_transport_id")?;
1714        validate_specified_transport(new_transport_id, "session_migrate.new_transport_id")?;
1715
1716        Ok(Self {
1717            old_transport_id,
1718            new_transport_id,
1719            last_result_frame_id: read_u64(source, 8),
1720            client_migrate_ts_us: read_u64(source, 16),
1721        })
1722    }
1723
1724    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1725        require_destination_len(destination, SESSION_MIGRATE_METADATA_LEN)?;
1726        validate_specified_transport(self.old_transport_id, "session_migrate.old_transport_id")?;
1727        validate_specified_transport(self.new_transport_id, "session_migrate.new_transport_id")?;
1728
1729        write_u32(destination, 0, self.old_transport_id as u32);
1730        write_u32(destination, 4, self.new_transport_id as u32);
1731        write_u64(destination, 8, self.last_result_frame_id);
1732        write_u64(destination, 16, self.client_migrate_ts_us);
1733        Ok(())
1734    }
1735
1736    pub fn to_bytes(&self) -> Result<[u8; SESSION_MIGRATE_METADATA_LEN], NnrpError> {
1737        let mut bytes = [0u8; SESSION_MIGRATE_METADATA_LEN];
1738        self.write(&mut bytes)?;
1739        Ok(bytes)
1740    }
1741}
1742
1743#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1744pub struct SessionMigrateAckMetadata {
1745    pub accept_code: u32,
1746    pub resume_from_frame_id: u64,
1747    pub grace_window_ms: u32,
1748    pub server_migrate_ts_us: u64,
1749}
1750
1751impl SessionMigrateAckMetadata {
1752    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1753        require_len(source, SESSION_MIGRATE_ACK_METADATA_LEN)?;
1754        Ok(Self {
1755            accept_code: read_u32(source, 0),
1756            resume_from_frame_id: read_u64(source, 4),
1757            grace_window_ms: read_u32(source, 12),
1758            server_migrate_ts_us: read_u64(source, 16),
1759        })
1760    }
1761
1762    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1763        require_destination_len(destination, SESSION_MIGRATE_ACK_METADATA_LEN)?;
1764        write_u32(destination, 0, self.accept_code);
1765        write_u64(destination, 4, self.resume_from_frame_id);
1766        write_u32(destination, 12, self.grace_window_ms);
1767        write_u64(destination, 16, self.server_migrate_ts_us);
1768        Ok(())
1769    }
1770
1771    pub fn to_bytes(&self) -> Result<[u8; SESSION_MIGRATE_ACK_METADATA_LEN], NnrpError> {
1772        let mut bytes = [0u8; SESSION_MIGRATE_ACK_METADATA_LEN];
1773        self.write(&mut bytes)?;
1774        Ok(bytes)
1775    }
1776}
1777
1778#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1779pub struct ErrorMetadata {
1780    pub error_code: u32,
1781    pub error_scope: ErrorScope,
1782    pub is_fatal: bool,
1783    pub retry_after_ms: u32,
1784    pub related_session_id: u32,
1785    pub related_frame_id: u32,
1786    pub related_view_id: u32,
1787    pub diagnostic_bytes: u32,
1788}
1789
1790impl ErrorMetadata {
1791    pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
1792        require_len(source, ERROR_METADATA_LEN)?;
1793        let fatal_flag = read_u32(source, 8);
1794        if fatal_flag > 1 {
1795            return Err(NnrpError::InvalidProtocolCombination {
1796                rule: "ERROR is_fatal must be 0 or 1",
1797            });
1798        }
1799
1800        Ok(Self {
1801            error_code: read_u32(source, 0),
1802            error_scope: ErrorScope::try_from_u32(read_u32(source, 4))?,
1803            is_fatal: fatal_flag != 0,
1804            retry_after_ms: read_u32(source, 12),
1805            related_session_id: read_u32(source, 16),
1806            related_frame_id: read_u32(source, 20),
1807            related_view_id: read_u32(source, 24),
1808            diagnostic_bytes: read_u32(source, 28),
1809        })
1810    }
1811
1812    pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
1813        require_destination_len(destination, ERROR_METADATA_LEN)?;
1814        write_u32(destination, 0, self.error_code);
1815        write_u32(destination, 4, self.error_scope as u32);
1816        write_u32(destination, 8, u32::from(self.is_fatal));
1817        write_u32(destination, 12, self.retry_after_ms);
1818        write_u32(destination, 16, self.related_session_id);
1819        write_u32(destination, 20, self.related_frame_id);
1820        write_u32(destination, 24, self.related_view_id);
1821        write_u32(destination, 28, self.diagnostic_bytes);
1822        Ok(())
1823    }
1824
1825    pub fn to_bytes(&self) -> Result<[u8; ERROR_METADATA_LEN], NnrpError> {
1826        let mut bytes = [0u8; ERROR_METADATA_LEN];
1827        self.write(&mut bytes)?;
1828        Ok(bytes)
1829    }
1830}
1831
1832pub fn validate_empty_control_header(
1833    header: &CommonHeader,
1834    expected_message_type: MessageType,
1835) -> Result<(), NnrpError> {
1836    if header.message_type != expected_message_type || header.meta_len != 0 || header.body_len != 0
1837    {
1838        return Err(NnrpError::InvalidProtocolCombination {
1839            rule: "empty control message requires expected type, meta_len=0, and body_len=0",
1840        });
1841    }
1842    Ok(())
1843}
1844
1845pub fn validate_close_header(header: &CommonHeader) -> Result<(), NnrpError> {
1846    if header.message_type != MessageType::Close || header.meta_len != 0 {
1847        return Err(NnrpError::InvalidProtocolCombination {
1848            rule: "CLOSE requires message_type=CLOSE and meta_len=0",
1849        });
1850    }
1851    Ok(())
1852}
1853
1854fn require_len(source: &[u8], expected: usize) -> Result<(), NnrpError> {
1855    if source.len() < expected {
1856        return Err(NnrpError::SourceTooShort {
1857            expected,
1858            actual: source.len(),
1859        });
1860    }
1861    Ok(())
1862}
1863
1864fn require_destination_len(destination: &[u8], expected: usize) -> Result<(), NnrpError> {
1865    if destination.len() < expected {
1866        return Err(NnrpError::DestinationTooShort {
1867            expected,
1868            actual: destination.len(),
1869        });
1870    }
1871    Ok(())
1872}
1873
1874fn require_declared_len(
1875    field: &'static str,
1876    declared: usize,
1877    actual: usize,
1878) -> Result<(), NnrpError> {
1879    if declared != actual {
1880        return Err(NnrpError::DeclaredLengthMismatch {
1881            field,
1882            declared,
1883            actual,
1884        });
1885    }
1886    Ok(())
1887}
1888
1889fn split_declared_tail<'a>(
1890    source: &'a [u8],
1891    fixed_len: usize,
1892    declared_tail_len: usize,
1893    field: &'static str,
1894) -> Result<&'a [u8], NnrpError> {
1895    require_len(source, fixed_len)?;
1896    let actual_tail_len = source.len() - fixed_len;
1897    require_declared_len(field, declared_tail_len, actual_tail_len)?;
1898    Ok(&source[fixed_len..])
1899}
1900
1901fn trace_context_ids_from_source(source: &[u8]) -> Option<TraceContextIds> {
1902    if source.len() < 24 {
1903        return None;
1904    }
1905    Some(TraceContextIds {
1906        trace_id: read_u64(source, 0),
1907        span_id: read_u64(source, 8),
1908        parent_span_id: read_u64(source, 16),
1909    })
1910}
1911
1912fn require_nonzero(rule: &'static str, value: u32) -> Result<(), NnrpError> {
1913    if value == 0 {
1914        return Err(NnrpError::InvalidProtocolCombination { rule });
1915    }
1916    Ok(())
1917}
1918
1919fn require_subset(rule: &'static str, accepted: u32, supported: u32) -> Result<(), NnrpError> {
1920    if accepted & !supported != 0 {
1921        return Err(NnrpError::InvalidProtocolCombination { rule });
1922    }
1923    Ok(())
1924}
1925
1926fn has_bitmap_bit(bitmap: u64, bit: u32) -> bool {
1927    bit < 64 && bitmap & (1u64 << bit) != 0
1928}
1929
1930fn validate_zero_u8(field: &'static str, value: u8) -> Result<(), NnrpError> {
1931    if value != 0 {
1932        return Err(NnrpError::NonZeroReservedField { field });
1933    }
1934    Ok(())
1935}
1936
1937fn validate_zero_u16(field: &'static str, value: u16) -> Result<(), NnrpError> {
1938    if value != 0 {
1939        return Err(NnrpError::NonZeroReservedField { field });
1940    }
1941    Ok(())
1942}
1943
1944fn validate_zero_u32(field: &'static str, value: u32) -> Result<(), NnrpError> {
1945    if value != 0 {
1946        return Err(NnrpError::NonZeroReservedField { field });
1947    }
1948    Ok(())
1949}
1950
1951fn validate_zero_u64(field: &'static str, value: u64) -> Result<(), NnrpError> {
1952    if value != 0 {
1953        return Err(NnrpError::NonZeroReservedField { field });
1954    }
1955    Ok(())
1956}
1957
1958fn validate_mask_u8(value: u8, allowed: u8) -> Result<(), NnrpError> {
1959    if value & !allowed != 0 {
1960        return Err(NnrpError::ReservedBitsSet {
1961            value: value as u64,
1962            allowed: allowed as u64,
1963        });
1964    }
1965    Ok(())
1966}
1967
1968fn validate_mask_u16(value: u16, allowed: u16) -> Result<(), NnrpError> {
1969    if value & !allowed != 0 {
1970        return Err(NnrpError::ReservedBitsSet {
1971            value: value as u64,
1972            allowed: allowed as u64,
1973        });
1974    }
1975    Ok(())
1976}
1977
1978fn validate_mask_u32(value: u32, allowed: u32) -> Result<(), NnrpError> {
1979    if value & !allowed != 0 {
1980        return Err(NnrpError::ReservedBitsSet {
1981            value: value as u64,
1982            allowed: allowed as u64,
1983        });
1984    }
1985    Ok(())
1986}
1987
1988fn validate_percent_x100(value: u16) -> Result<(), NnrpError> {
1989    if value <= 10_000 || value == u16::MAX {
1990        return Ok(());
1991    }
1992    Err(NnrpError::InvalidProtocolCombination {
1993        rule: "progress.percent_x100 must be 0..10000 or 0xffff",
1994    })
1995}
1996
1997fn validate_pressure_level(value: u16) -> Result<(), NnrpError> {
1998    if value <= 3 {
1999        return Ok(());
2000    }
2001    Err(NnrpError::UnknownEnumValue {
2002        enum_name: "pressure_level",
2003        value: value as u64,
2004    })
2005}
2006
2007fn validate_standard_role(value: u8) -> Result<(), NnrpError> {
2008    if value <= 0x07 || value >= 0x80 {
2009        return Ok(());
2010    }
2011    Err(NnrpError::UnknownEnumValue {
2012        enum_name: "runtime_role",
2013        value: value as u64,
2014    })
2015}
2016
2017fn validate_specified_transport(
2018    transport_id: TransportId,
2019    rule: &'static str,
2020) -> Result<(), NnrpError> {
2021    if transport_id == TransportId::Unspecified {
2022        return Err(NnrpError::InvalidProtocolCombination { rule });
2023    }
2024    Ok(())
2025}
2026
2027fn read_i16(source: &[u8], offset: usize) -> i16 {
2028    i16::from_le_bytes(source[offset..offset + 2].try_into().expect("slice length"))
2029}
2030
2031fn read_u16(source: &[u8], offset: usize) -> u16 {
2032    u16::from_le_bytes(source[offset..offset + 2].try_into().expect("slice length"))
2033}
2034
2035fn read_u32(source: &[u8], offset: usize) -> u32 {
2036    u32::from_le_bytes(source[offset..offset + 4].try_into().expect("slice length"))
2037}
2038
2039fn read_u64(source: &[u8], offset: usize) -> u64 {
2040    u64::from_le_bytes(source[offset..offset + 8].try_into().expect("slice length"))
2041}
2042
2043fn write_u16(destination: &mut [u8], offset: usize, value: u16) {
2044    destination[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
2045}
2046
2047fn write_i16(destination: &mut [u8], offset: usize, value: i16) {
2048    destination[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
2049}
2050
2051fn write_u32(destination: &mut [u8], offset: usize, value: u32) {
2052    destination[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
2053}
2054
2055fn write_u64(destination: &mut [u8], offset: usize, value: u64) {
2056    destination[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
2057}
2058
2059#[cfg(test)]
2060mod tests {
2061    use super::*;
2062    use crate::{HeaderFlags, MessageType};
2063
2064    #[test]
2065    fn client_hello_metadata_round_trips_python_golden_vector() {
2066        let bytes = hex_to_bytes("01010100010000000100000003000000030000002100000003000000010007000100020040000000000001007017640002000000000000006000000000000000");
2067
2068        let metadata = ClientHelloMetadata::parse(&bytes).unwrap();
2069
2070        assert_eq!(metadata.min_version_major, 1);
2071        assert_eq!(metadata.max_version_major, 1);
2072        assert_eq!(metadata.supported_wire_format_bitmap, 1);
2073        assert_eq!(metadata.supported_profile_bitmap, 1);
2074        assert_eq!(metadata.supported_payload_kind_bitmap, 1);
2075        assert_eq!(metadata.max_lane_count, 2);
2076        assert_eq!(metadata.auth_bytes, 96);
2077        assert_eq!(metadata.target_cadence_x100, 6000);
2078        assert_eq!(metadata.to_bytes().unwrap().as_slice(), bytes.as_slice());
2079    }
2080
2081    #[test]
2082    fn hello_ack_rejects_capability_denial_mismatch() {
2083        let hello = ClientHelloMetadata::parse(&hex_to_bytes("01010100010000000100000003000000030000002100000003000000010007000100020040000000000001007017640002000000000000006000000000000000")).unwrap();
2084        let ack = ServerHelloAckMetadata {
2085            selected_version_major: 1,
2086            selected_wire_format: 0,
2087            auth_status: 0,
2088            session_id: 42,
2089            accepted_profile_bitmap: 0x2,
2090            accepted_payload_kind_bitmap: 0x1,
2091            accepted_codec_bitmap: 0x1,
2092            accepted_compression_bitmap: 0x1,
2093            accepted_dtype_bitmap: 0x1,
2094            accepted_layout_bitmap: 0x1,
2095            cache_digest_bitmap: 0,
2096            cache_object_bitmap: 0,
2097            max_cache_entries: 0,
2098            max_cache_bytes: 0,
2099            max_lane_count: 1,
2100            max_concurrent_frames: 1,
2101            target_cadence_x100: 0,
2102            latency_budget_ms: 0,
2103            quality_tier: 0,
2104            degrade_policy: 0,
2105            max_body_bytes: 0,
2106            token_ttl_ms: 0,
2107            retry_after_ms: 0,
2108            control_extension_bytes: 0,
2109            server_flags: 0,
2110        };
2111
2112        assert_eq!(
2113            ack.validate_against_client_hello(&hello),
2114            Err(NnrpError::InvalidProtocolCombination {
2115                rule: "SERVER_HELLO_ACK accepted_profile_bitmap"
2116            })
2117        );
2118    }
2119
2120    #[test]
2121    fn server_hello_ack_round_trips_and_validates_against_client_window() {
2122        let hello = ClientHelloMetadata::parse(&hex_to_bytes("01010100010000000100000003000000030000002100000003000000010007000100020040000000000001007017640002000000000000006000000000000000")).unwrap();
2123        let ack = ServerHelloAckMetadata {
2124            selected_version_major: 1,
2125            selected_wire_format: 0,
2126            auth_status: 0,
2127            session_id: 42,
2128            accepted_profile_bitmap: 0x0001,
2129            accepted_payload_kind_bitmap: 0x0001,
2130            accepted_codec_bitmap: 0x0003,
2131            accepted_compression_bitmap: 0x0003,
2132            accepted_dtype_bitmap: 0x0001,
2133            accepted_layout_bitmap: 0x0001,
2134            cache_digest_bitmap: 0x0001,
2135            cache_object_bitmap: 0x0007,
2136            max_cache_entries: 512,
2137            max_cache_bytes: 16 * 1024 * 1024,
2138            max_lane_count: 2,
2139            max_concurrent_frames: 2,
2140            target_cadence_x100: 6000,
2141            latency_budget_ms: 100,
2142            quality_tier: 2,
2143            degrade_policy: 2,
2144            max_body_bytes: 32 * 1024 * 1024,
2145            token_ttl_ms: 300_000,
2146            retry_after_ms: 0,
2147            control_extension_bytes: 0,
2148            server_flags: 1,
2149        };
2150        let bytes = ack.to_bytes().unwrap();
2151
2152        assert_eq!(ServerHelloAckMetadata::parse(&bytes).unwrap(), ack);
2153        ack.validate_against_client_hello(&hello).unwrap();
2154    }
2155
2156    #[test]
2157    fn hello_and_ack_reject_invalid_windows_reserved_fields_and_flags() {
2158        let mut hello = ClientHelloMetadata::parse(&hex_to_bytes("01010100010000000100000003000000030000002100000003000000010007000100020040000000000001007017640002000000000000006000000000000000")).unwrap();
2159        hello.min_version_major = 2;
2160        hello.max_version_major = 1;
2161        assert_eq!(
2162            hello.validate_capability_window(),
2163            Err(NnrpError::InvalidProtocolCombination {
2164                rule: "CLIENT_HELLO version window must be ordered"
2165            })
2166        );
2167        hello.min_version_major = 2;
2168        hello.max_version_major = 2;
2169        assert_eq!(
2170            hello.validate_capability_window(),
2171            Err(NnrpError::InvalidProtocolCombination {
2172                rule: "CLIENT_HELLO version window must include NNRP/1"
2173            })
2174        );
2175        hello.min_version_major = 1;
2176        hello.max_version_major = 1;
2177        hello.supported_wire_format_bitmap = 0;
2178        assert_eq!(
2179            hello.validate_capability_window(),
2180            Err(NnrpError::InvalidProtocolCombination {
2181                rule: "CLIENT_HELLO supported_wire_format_bitmap must include wire_format 0"
2182            })
2183        );
2184        hello.supported_wire_format_bitmap = 1;
2185        hello.supported_profile_bitmap = 0;
2186        assert_eq!(
2187            hello.validate_capability_window(),
2188            Err(NnrpError::InvalidProtocolCombination {
2189                rule: "CLIENT_HELLO supported_profile_bitmap"
2190            })
2191        );
2192        hello.supported_profile_bitmap = 1;
2193        hello.supported_payload_kind_bitmap = 0;
2194        assert_eq!(
2195            hello.validate_capability_window(),
2196            Err(NnrpError::InvalidProtocolCombination {
2197                rule: "CLIENT_HELLO supported_payload_kind_bitmap"
2198            })
2199        );
2200
2201        let mut ack_bytes = [0u8; SERVER_HELLO_ACK_METADATA_LEN];
2202        ack_bytes[3] = 1;
2203        assert_eq!(
2204            ServerHelloAckMetadata::parse(&ack_bytes),
2205            Err(NnrpError::NonZeroReservedField {
2206                field: "server_hello_ack.reserved0"
2207            })
2208        );
2209        ack_bytes[3] = 0;
2210        write_u32(&mut ack_bytes, 76, 2);
2211        assert_eq!(
2212            ServerHelloAckMetadata::parse(&ack_bytes),
2213            Err(NnrpError::ReservedBitsSet {
2214                value: 2,
2215                allowed: SERVER_HELLO_ACK_FLAGS_KNOWN_MASK as u64
2216            })
2217        );
2218    }
2219
2220    #[test]
2221    fn session_patch_metadata_round_trips_python_golden_vector() {
2222        let bytes = hex_to_bytes(
2223            "1d0000005d00000028230000680105000300000000000000050000000000000010000000",
2224        );
2225
2226        let metadata = SessionPatchMetadata::parse(&bytes).unwrap();
2227
2228        assert_eq!(metadata.profile_id, 29);
2229        assert_eq!(metadata.patch_mask, 0x5d);
2230        assert_eq!(metadata.target_cadence_x100, 9000);
2231        assert_eq!(metadata.active_lane_mask, 3);
2232        assert_eq!(metadata.profile_patch_bytes, 16);
2233        assert_eq!(metadata.to_bytes().unwrap().as_slice(), bytes.as_slice());
2234    }
2235
2236    #[test]
2237    fn session_patch_ack_metadata_round_trips_python_golden_vector() {
2238        let bytes = hex_to_bytes("010003001100000044000000000000000200000028230000680105000300000000000000010000000300000010000000");
2239
2240        let metadata = SessionPatchAckMetadata::parse(&bytes).unwrap();
2241
2242        assert_eq!(metadata.ack_status, SessionPatchAckStatus::PartiallyApplied);
2243        assert_eq!(
2244            metadata.reject_reason,
2245            SessionPatchRejectReason::UnsupportedStrategy
2246        );
2247        assert_eq!(metadata.effective_profile_id, 2);
2248        assert_eq!(metadata.effective_target_cadence_x100, 9000);
2249        assert_eq!(metadata.profile_patch_ack_bytes, 16);
2250        assert_eq!(metadata.to_bytes().unwrap().as_slice(), bytes.as_slice());
2251    }
2252
2253    #[test]
2254    fn result_hint_probe_and_migrate_metadata_round_trip() {
2255        let hint = ResultHintMetadata {
2256            applied_budget_policy: ResultHintBudgetPolicy::Partial,
2257            congestion_state: ResultHintCongestionState::Elevated,
2258            reason: ResultHintReason::ServerBusy,
2259            retry_after_ms: 20,
2260        };
2261        assert_eq!(
2262            ResultHintMetadata::parse(&hint.to_bytes().unwrap()).unwrap(),
2263            hint
2264        );
2265
2266        let probe = TransportProbeMetadata {
2267            probe_id: 17,
2268            probe_payload_bytes: 32768,
2269            client_send_ts_us: 123456789,
2270        };
2271        assert_eq!(
2272            TransportProbeMetadata::parse(&probe.to_bytes().unwrap()).unwrap(),
2273            probe
2274        );
2275
2276        let ack = TransportProbeAckMetadata {
2277            probe_id: 17,
2278            server_recv_ts_us: 223456789,
2279        };
2280        assert_eq!(
2281            TransportProbeAckMetadata::parse(&ack.to_bytes().unwrap()).unwrap(),
2282            ack
2283        );
2284
2285        let migrate = SessionMigrateMetadata {
2286            old_transport_id: TransportId::Quic,
2287            new_transport_id: TransportId::Tcp,
2288            last_result_frame_id: 44,
2289            client_migrate_ts_us: 3000,
2290        };
2291        assert_eq!(
2292            SessionMigrateMetadata::parse(&migrate.to_bytes().unwrap()).unwrap(),
2293            migrate
2294        );
2295
2296        let migrate_ack = SessionMigrateAckMetadata {
2297            accept_code: 0,
2298            resume_from_frame_id: 45,
2299            grace_window_ms: 250,
2300            server_migrate_ts_us: 4000,
2301        };
2302        assert_eq!(
2303            SessionMigrateAckMetadata::parse(&migrate_ack.to_bytes().unwrap()).unwrap(),
2304            migrate_ack
2305        );
2306    }
2307
2308    #[test]
2309    fn runtime_control_metadata_round_trips() {
2310        let control = ControlRequestMetadata {
2311            operation_id: 11,
2312            control_sequence: 12,
2313            reason_code: 1,
2314            source_role: 4,
2315            flags: CONTROL_REQUEST_FLAGS_KNOWN_MASK,
2316            diagnostic_bytes: 128,
2317        };
2318        assert_eq!(
2319            ControlRequestMetadata::parse(&control.to_bytes().unwrap()).unwrap(),
2320            control
2321        );
2322
2323        let scheduling = SchedulingMetadata {
2324            operation_id: 21,
2325            control_sequence: 22,
2326            priority_class: 2,
2327            priority_delta: -3,
2328            deadline_unix_ms: 1_800_000,
2329            flags: SCHEDULING_FLAGS_KNOWN_MASK,
2330        };
2331        assert_eq!(
2332            SchedulingMetadata::parse(&scheduling.to_bytes().unwrap()).unwrap(),
2333            scheduling
2334        );
2335
2336        let supersede = SupersedeMetadata {
2337            old_operation_id: 31,
2338            new_operation_id: 32,
2339            control_sequence: 33,
2340            drop_reason_code: 2,
2341            flags: SUPERSEDE_FLAGS_KNOWN_MASK,
2342            diagnostic_bytes: 64,
2343        };
2344        assert_eq!(
2345            SupersedeMetadata::parse(&supersede.to_bytes().unwrap()).unwrap(),
2346            supersede
2347        );
2348
2349        let budget = BudgetMetadata {
2350            operation_id: 41,
2351            compute_budget_units: 42,
2352            memory_budget_bytes: 43,
2353            bandwidth_budget_bytes: 44,
2354            token_budget: 45,
2355            flags: BUDGET_FLAGS_KNOWN_MASK,
2356        };
2357        assert_eq!(
2358            BudgetMetadata::parse(&budget.to_bytes().unwrap()).unwrap(),
2359            budget
2360        );
2361
2362        let progress = ProgressMetadata {
2363            operation_id: 51,
2364            progress_sequence: 52,
2365            stage_code: 5,
2366            percent_x100: 8750,
2367            object_id: 53,
2368            body_bytes: 54,
2369        };
2370        assert_eq!(
2371            ProgressMetadata::parse(&progress.to_bytes().unwrap()).unwrap(),
2372            progress
2373        );
2374
2375        let partial = PartialResultMetadata {
2376            operation_id: 61,
2377            result_sequence: 62,
2378            object_id: 63,
2379            delta_sequence: 64,
2380            body_bytes: 65,
2381            flags: PARTIAL_RESULT_FLAGS_KNOWN_MASK,
2382        };
2383        assert_eq!(
2384            PartialResultMetadata::parse(&partial.to_bytes().unwrap()).unwrap(),
2385            partial
2386        );
2387
2388        let pressure = PressureMetadata {
2389            scope_id: 71,
2390            credit_window: 72,
2391            pressure_level: 2,
2392            pressure_reason: 4,
2393            retry_after_ms: 73,
2394            flags: PRESSURE_FLAGS_KNOWN_MASK,
2395        };
2396        assert_eq!(
2397            PressureMetadata::parse(&pressure.to_bytes().unwrap()).unwrap(),
2398            pressure
2399        );
2400
2401        let capability = CapabilityMetadata {
2402            profile_id: 0x0100,
2403            capability_count: 3,
2404            cost_model_id: 2,
2405            preference_rank: 1,
2406            limit_bytes: 81,
2407            limit_units: 82,
2408            body_bytes: 83,
2409            flags: CAPABILITY_FLAGS_KNOWN_MASK,
2410        };
2411        assert_eq!(
2412            CapabilityMetadata::parse(&capability.to_bytes().unwrap()).unwrap(),
2413            capability
2414        );
2415
2416        let route = RouteHintMetadata {
2417            operation_id: 91,
2418            route_id: 92,
2419            executor_class: 3,
2420            affinity_class: 4,
2421            deadline_unix_ms: 93,
2422            body_bytes: 94,
2423            flags: ROUTE_HINT_FLAGS_KNOWN_MASK,
2424        };
2425        assert_eq!(
2426            RouteHintMetadata::parse(&route.to_bytes().unwrap()).unwrap(),
2427            route
2428        );
2429
2430        let trace = TraceContextMetadata {
2431            trace_id: 101,
2432            span_id: 102,
2433            parent_span_id: 103,
2434            stage_code: 6,
2435            flags: TRACE_CONTEXT_FLAGS_KNOWN_MASK,
2436            body_bytes: 104,
2437        };
2438        assert_eq!(
2439            TraceContextMetadata::parse(&trace.to_bytes().unwrap()).unwrap(),
2440            trace
2441        );
2442
2443        let drop_reason = ResultDropReasonMetadata {
2444            operation_id: 111,
2445            result_sequence: 112,
2446            drop_reason_code: 3,
2447            source_role: 6,
2448            flags: RESULT_DROP_FLAGS_KNOWN_MASK,
2449            diagnostic_bytes: 113,
2450        };
2451        assert_eq!(
2452            ResultDropReasonMetadata::parse(&drop_reason.to_bytes().unwrap()).unwrap(),
2453            drop_reason
2454        );
2455
2456        let recoverable = RecoverableErrorMetadata {
2457            error_code: 121,
2458            error_scope: ErrorScope::Frame,
2459            recovery_action: 3,
2460            source_role: 6,
2461            flags: RECOVERABLE_ERROR_FLAGS_KNOWN_MASK,
2462            retry_after_ms: 122,
2463            related_session_id: 123,
2464            related_frame_id: 124,
2465            related_view_id: 125,
2466            diagnostic_bytes: 126,
2467        };
2468        assert_eq!(
2469            RecoverableErrorMetadata::parse(&recoverable.to_bytes().unwrap()).unwrap(),
2470            recoverable
2471        );
2472
2473        let retry_after = RetryAfterMetadata {
2474            scope_id: 131,
2475            control_sequence: 132,
2476            retry_after_ms: 133,
2477            jitter_ms: 134,
2478            reason_code: 4,
2479            source_role: 6,
2480            flags: RETRY_AFTER_FLAGS_KNOWN_MASK,
2481            diagnostic_bytes: 135,
2482        };
2483        assert_eq!(
2484            RetryAfterMetadata::parse(&retry_after.to_bytes().unwrap()).unwrap(),
2485            retry_after
2486        );
2487    }
2488
2489    #[test]
2490    fn runtime_control_metadata_round_trips_declared_tail_segments() {
2491        let control = ControlRequestMetadata {
2492            operation_id: 11,
2493            control_sequence: 12,
2494            reason_code: 1,
2495            source_role: 4,
2496            flags: CONTROL_REQUEST_FLAGS_KNOWN_MASK,
2497            diagnostic_bytes: 3,
2498        };
2499        let control_bytes = control.to_vec_with_diagnostics(&[1, 2, 3]).unwrap();
2500        assert_eq!(
2501            ControlRequestMetadata::parse_with_diagnostics(&control_bytes).unwrap(),
2502            (control, &[1, 2, 3][..])
2503        );
2504
2505        let progress = ProgressMetadata {
2506            operation_id: 51,
2507            progress_sequence: 52,
2508            stage_code: 5,
2509            percent_x100: 8750,
2510            object_id: 53,
2511            body_bytes: 2,
2512        };
2513        let progress_bytes = progress.to_vec_with_body(&[9, 8]).unwrap();
2514        assert_eq!(
2515            ProgressMetadata::parse_with_body(&progress_bytes).unwrap(),
2516            (progress, &[9, 8][..])
2517        );
2518
2519        let partial = PartialResultMetadata {
2520            operation_id: 61,
2521            result_sequence: 62,
2522            object_id: 63,
2523            delta_sequence: 64,
2524            body_bytes: 3,
2525            flags: PARTIAL_RESULT_FLAGS_KNOWN_MASK,
2526        };
2527        let partial_bytes = partial.to_vec_with_body(&[7, 8, 9]).unwrap();
2528        assert_eq!(
2529            PartialResultMetadata::parse_with_body(&partial_bytes).unwrap(),
2530            (partial, &[7, 8, 9][..])
2531        );
2532
2533        let capability = CapabilityMetadata {
2534            profile_id: 0x0100,
2535            capability_count: 3,
2536            cost_model_id: 2,
2537            preference_rank: 1,
2538            limit_bytes: 81,
2539            limit_units: 82,
2540            body_bytes: 2,
2541            flags: CAPABILITY_FLAGS_KNOWN_MASK,
2542        };
2543        let capability_bytes = capability.to_vec_with_body(&[1, 0]).unwrap();
2544        assert_eq!(
2545            CapabilityMetadata::parse_with_body(&capability_bytes).unwrap(),
2546            (capability, &[1, 0][..])
2547        );
2548
2549        let route = RouteHintMetadata {
2550            operation_id: 91,
2551            route_id: 92,
2552            executor_class: 3,
2553            affinity_class: 4,
2554            deadline_unix_ms: 93,
2555            body_bytes: 4,
2556            flags: ROUTE_HINT_FLAGS_KNOWN_MASK,
2557        };
2558        let route_bytes = route.to_vec_with_body(&[1, 2, 3, 4]).unwrap();
2559        assert_eq!(
2560            RouteHintMetadata::parse_with_body(&route_bytes).unwrap(),
2561            (route, &[1, 2, 3, 4][..])
2562        );
2563
2564        let trace = TraceContextMetadata {
2565            trace_id: 101,
2566            span_id: 102,
2567            parent_span_id: 103,
2568            stage_code: 6,
2569            flags: TRACE_CONTEXT_FLAGS_KNOWN_MASK,
2570            body_bytes: 1,
2571        };
2572        let trace_bytes = trace.to_vec_with_body(&[5]).unwrap();
2573        assert_eq!(
2574            TraceContextMetadata::parse_with_body(&trace_bytes).unwrap(),
2575            (trace, &[5][..])
2576        );
2577
2578        let drop_reason = ResultDropReasonMetadata {
2579            operation_id: 111,
2580            result_sequence: 112,
2581            drop_reason_code: 3,
2582            source_role: 6,
2583            flags: RESULT_DROP_FLAGS_KNOWN_MASK,
2584            diagnostic_bytes: 2,
2585        };
2586        let drop_reason_bytes = drop_reason.to_vec_with_diagnostics(&[6, 7]).unwrap();
2587        assert_eq!(
2588            ResultDropReasonMetadata::parse_with_diagnostics(&drop_reason_bytes).unwrap(),
2589            (drop_reason, &[6, 7][..])
2590        );
2591
2592        let recoverable = RecoverableErrorMetadata {
2593            error_code: 121,
2594            error_scope: ErrorScope::Frame,
2595            recovery_action: 3,
2596            source_role: 6,
2597            flags: RECOVERABLE_ERROR_FLAGS_KNOWN_MASK,
2598            retry_after_ms: 122,
2599            related_session_id: 123,
2600            related_frame_id: 124,
2601            related_view_id: 125,
2602            diagnostic_bytes: 2,
2603        };
2604        let recoverable_bytes = recoverable.to_vec_with_diagnostics(&[8, 9]).unwrap();
2605        assert_eq!(
2606            RecoverableErrorMetadata::parse_with_diagnostics(&recoverable_bytes).unwrap(),
2607            (recoverable, &[8, 9][..])
2608        );
2609
2610        let retry_after = RetryAfterMetadata {
2611            scope_id: 131,
2612            control_sequence: 132,
2613            retry_after_ms: 133,
2614            jitter_ms: 134,
2615            reason_code: 4,
2616            source_role: 6,
2617            flags: RETRY_AFTER_FLAGS_KNOWN_MASK,
2618            diagnostic_bytes: 3,
2619        };
2620        let retry_after_bytes = retry_after.to_vec_with_diagnostics(&[1, 3, 5]).unwrap();
2621        assert_eq!(
2622            RetryAfterMetadata::parse_with_diagnostics(&retry_after_bytes).unwrap(),
2623            (retry_after, &[1, 3, 5][..])
2624        );
2625    }
2626
2627    #[test]
2628    fn runtime_control_metadata_rejects_declared_tail_length_mismatch() {
2629        let control = ControlRequestMetadata {
2630            operation_id: 11,
2631            control_sequence: 12,
2632            reason_code: 1,
2633            source_role: 4,
2634            flags: CONTROL_REQUEST_FLAGS_KNOWN_MASK,
2635            diagnostic_bytes: 2,
2636        };
2637        assert_eq!(
2638            control.to_vec_with_diagnostics(&[1]),
2639            Err(NnrpError::DeclaredLengthMismatch {
2640                field: "control_request.diagnostic_bytes",
2641                declared: 2,
2642                actual: 1
2643            })
2644        );
2645
2646        let mut control_bytes = control.to_bytes().unwrap().to_vec();
2647        control_bytes.extend_from_slice(&[1, 2, 3]);
2648        assert_eq!(
2649            ControlRequestMetadata::parse_with_diagnostics(&control_bytes),
2650            Err(NnrpError::DeclaredLengthMismatch {
2651                field: "control_request.diagnostic_bytes",
2652                declared: 2,
2653                actual: 3
2654            })
2655        );
2656
2657        let trace = TraceContextMetadata {
2658            trace_id: 101,
2659            span_id: 102,
2660            parent_span_id: 103,
2661            stage_code: 6,
2662            flags: TRACE_CONTEXT_FLAGS_KNOWN_MASK,
2663            body_bytes: 2,
2664        };
2665        assert_eq!(
2666            trace.to_vec_with_body(&[]),
2667            Err(NnrpError::DeclaredLengthMismatch {
2668                field: "trace_context.body_bytes",
2669                declared: 2,
2670                actual: 0
2671            })
2672        );
2673    }
2674
2675    #[test]
2676    fn trace_context_decode_error_preserves_trace_ids_when_present() {
2677        let trace = TraceContextMetadata {
2678            trace_id: 101,
2679            span_id: 102,
2680            parent_span_id: 103,
2681            stage_code: 6,
2682            flags: TRACE_CONTEXT_FLAGS_KNOWN_MASK | 0x8000,
2683            body_bytes: 0,
2684        };
2685        let trace_bytes = trace.to_bytes();
2686        assert_eq!(
2687            trace_bytes,
2688            Err(NnrpError::ReservedBitsSet {
2689                value: TRACE_CONTEXT_FLAGS_KNOWN_MASK as u64 | 0x8000,
2690                allowed: TRACE_CONTEXT_FLAGS_KNOWN_MASK as u64
2691            })
2692        );
2693
2694        let mut raw = [0u8; TRACE_CONTEXT_METADATA_LEN];
2695        write_u64(&mut raw, 0, trace.trace_id);
2696        write_u64(&mut raw, 8, trace.span_id);
2697        write_u64(&mut raw, 16, trace.parent_span_id);
2698        write_u16(&mut raw, 24, trace.stage_code);
2699        write_u16(&mut raw, 26, trace.flags);
2700        assert_eq!(
2701            TraceContextMetadata::parse_with_body_and_error_context(&raw),
2702            Err(TraceContextDecodeError {
2703                trace: Some(TraceContextIds {
2704                    trace_id: 101,
2705                    span_id: 102,
2706                    parent_span_id: 103
2707                }),
2708                error: NnrpError::ReservedBitsSet {
2709                    value: TRACE_CONTEXT_FLAGS_KNOWN_MASK as u64 | 0x8000,
2710                    allowed: TRACE_CONTEXT_FLAGS_KNOWN_MASK as u64
2711                }
2712            })
2713        );
2714    }
2715
2716    #[test]
2717    fn trace_context_decode_error_preserves_trace_ids_for_semantic_errors() {
2718        let trace = TraceContextMetadata {
2719            trace_id: 0,
2720            span_id: 202,
2721            parent_span_id: 203,
2722            stage_code: 6,
2723            flags: TRACE_CONTEXT_FLAGS_KNOWN_MASK,
2724            body_bytes: 1,
2725        };
2726        let trace_bytes = trace.to_vec_with_body(&[7]).unwrap();
2727        assert_eq!(
2728            TraceContextMetadata::parse_with_body_and_error_context(&trace_bytes),
2729            Err(TraceContextDecodeError {
2730                trace: Some(TraceContextIds {
2731                    trace_id: 0,
2732                    span_id: 202,
2733                    parent_span_id: 203
2734                }),
2735                error: NnrpError::InvalidProtocolCombination {
2736                    rule: "TRACE_CONTEXT requires non-zero trace_id and span_id"
2737                }
2738            })
2739        );
2740
2741        assert_eq!(
2742            TraceContextMetadata::parse_with_body_and_error_context(&[0u8; 23]),
2743            Err(TraceContextDecodeError {
2744                trace: None,
2745                error: NnrpError::SourceTooShort {
2746                    expected: TRACE_CONTEXT_METADATA_LEN,
2747                    actual: 23
2748                }
2749            })
2750        );
2751    }
2752
2753    #[test]
2754    fn runtime_control_metadata_rejects_reserved_bits_and_invalid_values() {
2755        let mut control = ControlRequestMetadata {
2756            operation_id: 1,
2757            control_sequence: 2,
2758            reason_code: 0,
2759            source_role: 1,
2760            flags: 0x04,
2761            diagnostic_bytes: 0,
2762        };
2763        assert_eq!(
2764            control.to_bytes(),
2765            Err(NnrpError::ReservedBitsSet {
2766                value: 0x04,
2767                allowed: CONTROL_REQUEST_FLAGS_KNOWN_MASK as u64
2768            })
2769        );
2770        control.flags = 0;
2771        let mut control_bytes = control.to_bytes().unwrap();
2772        write_u64(&mut control_bytes, 24, 1);
2773        assert_eq!(
2774            ControlRequestMetadata::parse(&control_bytes),
2775            Err(NnrpError::NonZeroReservedField {
2776                field: "control_request.reserved"
2777            })
2778        );
2779
2780        let mut progress = ProgressMetadata {
2781            operation_id: 1,
2782            progress_sequence: 2,
2783            stage_code: 0,
2784            percent_x100: 10_001,
2785            object_id: 0,
2786            body_bytes: 0,
2787        };
2788        assert_eq!(
2789            progress.to_bytes(),
2790            Err(NnrpError::InvalidProtocolCombination {
2791                rule: "progress.percent_x100 must be 0..10000 or 0xffff"
2792            })
2793        );
2794        progress.percent_x100 = u16::MAX;
2795        assert!(progress.to_bytes().is_ok());
2796
2797        let mut pressure = PressureMetadata {
2798            scope_id: 0,
2799            credit_window: 0,
2800            pressure_level: 0,
2801            pressure_reason: 0,
2802            retry_after_ms: 0,
2803            flags: 0,
2804        }
2805        .to_bytes()
2806        .unwrap();
2807        write_u32(&mut pressure, 28, 1);
2808        assert_eq!(
2809            PressureMetadata::parse(&pressure),
2810            Err(NnrpError::NonZeroReservedField {
2811                field: "pressure.reserved"
2812            })
2813        );
2814
2815        let mut route = RouteHintMetadata {
2816            operation_id: 0,
2817            route_id: 0,
2818            executor_class: 0,
2819            affinity_class: 0,
2820            deadline_unix_ms: 0,
2821            body_bytes: 0,
2822            flags: 0x04,
2823        };
2824        assert_eq!(
2825            route.to_bytes(),
2826            Err(NnrpError::ReservedBitsSet {
2827                value: 0x04,
2828                allowed: ROUTE_HINT_FLAGS_KNOWN_MASK as u64
2829            })
2830        );
2831        route.flags = 0;
2832        assert!(route.to_bytes().is_ok());
2833
2834        let recoverable = RecoverableErrorMetadata {
2835            error_code: 0,
2836            error_scope: ErrorScope::Frame,
2837            recovery_action: 0,
2838            source_role: 0,
2839            flags: 0x04,
2840            retry_after_ms: 0,
2841            related_session_id: 0,
2842            related_frame_id: 0,
2843            related_view_id: 0,
2844            diagnostic_bytes: 0,
2845        };
2846        assert_eq!(
2847            recoverable.to_bytes(),
2848            Err(NnrpError::ReservedBitsSet {
2849                value: 0x04,
2850                allowed: RECOVERABLE_ERROR_FLAGS_KNOWN_MASK as u64
2851            })
2852        );
2853
2854        let retry_after = RetryAfterMetadata {
2855            scope_id: 0,
2856            control_sequence: 0,
2857            retry_after_ms: 1,
2858            jitter_ms: 0,
2859            reason_code: 0,
2860            source_role: 0,
2861            flags: 0x04,
2862            diagnostic_bytes: 0,
2863        };
2864        assert_eq!(
2865            retry_after.to_bytes(),
2866            Err(NnrpError::ReservedBitsSet {
2867                value: 0x04,
2868                allowed: RETRY_AFTER_FLAGS_KNOWN_MASK as u64
2869            })
2870        );
2871    }
2872
2873    #[test]
2874    fn runtime_control_semantics_validate_message_specific_requirements() {
2875        let control = ControlRequestMetadata {
2876            operation_id: 0,
2877            control_sequence: 1,
2878            reason_code: 1,
2879            source_role: 1,
2880            flags: 0,
2881            diagnostic_bytes: 0,
2882        };
2883        assert_eq!(
2884            validate_control_request_semantics(MessageType::Cancel, &control),
2885            Ok(())
2886        );
2887        assert_eq!(
2888            validate_control_request_semantics(MessageType::Progress, &control),
2889            Err(NnrpError::InvalidProtocolCombination {
2890                rule: "control request metadata requires CANCEL or ABORT"
2891            })
2892        );
2893
2894        let mut scheduling = SchedulingMetadata {
2895            operation_id: 11,
2896            control_sequence: 12,
2897            priority_class: 1,
2898            priority_delta: 0,
2899            deadline_unix_ms: 0,
2900            flags: 0,
2901        };
2902        assert_eq!(
2903            validate_scheduling_semantics(MessageType::PriorityUpdate, &scheduling),
2904            Ok(())
2905        );
2906        assert_eq!(
2907            validate_scheduling_semantics(MessageType::Deadline, &scheduling),
2908            Err(NnrpError::InvalidProtocolCombination {
2909                rule: "DEADLINE and EXPIRE_AT require deadline_unix_ms"
2910            })
2911        );
2912        scheduling.deadline_unix_ms = 1;
2913        assert_eq!(
2914            validate_scheduling_semantics(MessageType::ExpireAt, &scheduling),
2915            Ok(())
2916        );
2917        assert_eq!(
2918            validate_scheduling_semantics(MessageType::Cancel, &scheduling),
2919            Err(NnrpError::InvalidProtocolCombination {
2920                rule: "scheduling metadata requires PRIORITY_UPDATE, DEADLINE, or EXPIRE_AT"
2921            })
2922        );
2923
2924        let mut progress = ProgressMetadata {
2925            operation_id: 0,
2926            progress_sequence: 1,
2927            stage_code: 1,
2928            percent_x100: u16::MAX,
2929            object_id: 0,
2930            body_bytes: 0,
2931        };
2932        assert_eq!(
2933            validate_progress_semantics(&progress),
2934            Err(NnrpError::InvalidProtocolCombination {
2935                rule: "PROGRESS requires a non-zero operation_id"
2936            })
2937        );
2938        progress.operation_id = 1;
2939        assert_eq!(validate_progress_semantics(&progress), Ok(()));
2940
2941        let partial_without_operation = PartialResultMetadata {
2942            operation_id: 0,
2943            result_sequence: 1,
2944            object_id: 0,
2945            delta_sequence: 0,
2946            body_bytes: 1,
2947            flags: 0,
2948        };
2949        assert_eq!(
2950            validate_partial_result_semantics(&partial_without_operation),
2951            Err(NnrpError::InvalidProtocolCombination {
2952                rule: "PARTIAL_RESULT requires a non-zero operation_id"
2953            })
2954        );
2955        let partial_without_payload = PartialResultMetadata {
2956            operation_id: 1,
2957            body_bytes: 0,
2958            ..partial_without_operation
2959        };
2960        assert_eq!(
2961            validate_partial_result_semantics(&partial_without_payload),
2962            Err(NnrpError::InvalidProtocolCombination {
2963                rule: "PARTIAL_RESULT requires inline body bytes or object_id"
2964            })
2965        );
2966        let partial_missing_ref = PartialResultMetadata {
2967            flags: 0x0000_0002,
2968            ..partial_without_payload
2969        };
2970        assert_eq!(
2971            validate_partial_result_semantics(&partial_missing_ref),
2972            Err(NnrpError::InvalidProtocolCombination {
2973                rule: "PARTIAL_RESULT object-ref flag requires object_id"
2974            })
2975        );
2976        assert_eq!(
2977            validate_partial_result_semantics(&PartialResultMetadata {
2978                object_id: 7,
2979                ..partial_missing_ref
2980            }),
2981            Ok(())
2982        );
2983        assert_eq!(
2984            validate_partial_result_semantics(&PartialResultMetadata {
2985                body_bytes: 1,
2986                flags: 0,
2987                ..partial_without_payload
2988            }),
2989            Ok(())
2990        );
2991
2992        let pressure = PressureMetadata {
2993            scope_id: 1,
2994            credit_window: 0,
2995            pressure_level: 0,
2996            pressure_reason: 0,
2997            retry_after_ms: 0,
2998            flags: 0,
2999        };
3000        assert_eq!(
3001            validate_pressure_semantics(MessageType::Backpressure, &pressure),
3002            Err(NnrpError::InvalidProtocolCombination {
3003                rule: "BACKPRESSURE requires non-zero pressure_level"
3004            })
3005        );
3006        assert_eq!(
3007            validate_pressure_semantics(MessageType::CreditUpdate, &pressure),
3008            Ok(())
3009        );
3010        assert_eq!(
3011            validate_pressure_semantics(MessageType::Progress, &pressure),
3012            Err(NnrpError::InvalidProtocolCombination {
3013                rule: "pressure metadata requires BACKPRESSURE or CREDIT_UPDATE"
3014            })
3015        );
3016        assert_eq!(
3017            validate_pressure_semantics(
3018                MessageType::Backpressure,
3019                &PressureMetadata {
3020                    pressure_level: 3,
3021                    ..pressure
3022                }
3023            ),
3024            Ok(())
3025        );
3026        let invalid_pressure = PressureMetadata {
3027            pressure_level: 4,
3028            ..pressure
3029        };
3030        assert_eq!(
3031            validate_pressure_semantics(MessageType::Backpressure, &invalid_pressure),
3032            Err(NnrpError::UnknownEnumValue {
3033                enum_name: "pressure_level",
3034                value: 4
3035            })
3036        );
3037
3038        let trace = TraceContextMetadata {
3039            trace_id: 0,
3040            span_id: 1,
3041            parent_span_id: 0,
3042            stage_code: 0,
3043            flags: 0,
3044            body_bytes: 0,
3045        };
3046        assert_eq!(
3047            validate_trace_context_semantics(&trace),
3048            Err(NnrpError::InvalidProtocolCombination {
3049                rule: "TRACE_CONTEXT requires non-zero trace_id and span_id"
3050            })
3051        );
3052        assert_eq!(
3053            validate_trace_context_semantics(&TraceContextMetadata {
3054                trace_id: 1,
3055                ..trace
3056            }),
3057            Ok(())
3058        );
3059
3060        let drop_reason = ResultDropReasonMetadata {
3061            operation_id: 1,
3062            result_sequence: 0,
3063            drop_reason_code: 0,
3064            source_role: 2,
3065            flags: 0,
3066            diagnostic_bytes: 0,
3067        };
3068        assert_eq!(
3069            validate_result_drop_reason_semantics(&drop_reason),
3070            Err(NnrpError::InvalidProtocolCombination {
3071                rule: "RESULT_DROP_REASON requires a non-zero drop_reason_code"
3072            })
3073        );
3074        assert_eq!(
3075            validate_result_drop_reason_semantics(&ResultDropReasonMetadata {
3076                operation_id: 0,
3077                drop_reason_code: 1,
3078                ..drop_reason
3079            }),
3080            Err(NnrpError::InvalidProtocolCombination {
3081                rule: "RESULT_DROP_REASON requires a non-zero operation_id"
3082            })
3083        );
3084        assert_eq!(
3085            validate_result_drop_reason_semantics(&ResultDropReasonMetadata {
3086                drop_reason_code: 1,
3087                source_role: 0x08,
3088                ..drop_reason
3089            }),
3090            Err(NnrpError::UnknownEnumValue {
3091                enum_name: "runtime_role",
3092                value: 0x08
3093            })
3094        );
3095        assert_eq!(
3096            validate_result_drop_reason_semantics(&ResultDropReasonMetadata {
3097                drop_reason_code: 1,
3098                source_role: 0x80,
3099                ..drop_reason
3100            }),
3101            Ok(())
3102        );
3103        assert_eq!(
3104            validate_control_request_semantics(
3105                MessageType::Abort,
3106                &ControlRequestMetadata {
3107                    source_role: 0x08,
3108                    ..control
3109                }
3110            ),
3111            Err(NnrpError::UnknownEnumValue {
3112                enum_name: "runtime_role",
3113                value: 0x08
3114            })
3115        );
3116    }
3117
3118    #[test]
3119    fn result_hint_rejects_unknown_enum_values() {
3120        let bytes = [1u8, 0, 0, 0, 1, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0];
3121
3122        assert_eq!(
3123            ResultHintMetadata::parse(&bytes),
3124            Err(NnrpError::UnknownEnumValue {
3125                enum_name: "result_hint_reason",
3126                value: 99
3127            })
3128        );
3129    }
3130
3131    #[test]
3132    fn inherited_control_enums_accept_all_stable_assignments() {
3133        for value in 0..=4 {
3134            assert!(ResultHintBudgetPolicy::try_from_u32(value).is_ok());
3135            assert!(ResultHintReason::try_from_u32(value).is_ok());
3136        }
3137        for value in 0..=3 {
3138            assert!(ResultHintCongestionState::try_from_u32(value).is_ok());
3139        }
3140        for value in 0..=2 {
3141            assert!(SessionPatchAckStatus::try_from_u16(value).is_ok());
3142            assert!(ErrorScope::try_from_u32(value as u32).is_ok());
3143        }
3144        for value in 0..=5 {
3145            assert!(SessionPatchRejectReason::try_from_u16(value).is_ok());
3146        }
3147        for value in 0..=4 {
3148            assert!(TransportId::try_from_u32(value).is_ok());
3149        }
3150        assert_eq!(TransportId::try_from_u32(3), Ok(TransportId::Ipc));
3151        assert_eq!(TransportId::try_from_u32(4), Ok(TransportId::WebSocket));
3152
3153        assert!(ResultHintBudgetPolicy::try_from_u32(99).is_err());
3154        assert!(ResultHintCongestionState::try_from_u32(99).is_err());
3155        assert!(SessionPatchAckStatus::try_from_u16(99).is_err());
3156        assert!(SessionPatchRejectReason::try_from_u16(99).is_err());
3157        assert!(TransportId::try_from_u32(99).is_err());
3158        assert!(ErrorScope::try_from_u32(99).is_err());
3159    }
3160
3161    #[test]
3162    fn migrate_rejects_unspecified_transport() {
3163        let mut bytes = [0u8; SESSION_MIGRATE_METADATA_LEN];
3164        write_u32(&mut bytes, 4, TransportId::Tcp as u32);
3165
3166        assert_eq!(
3167            SessionMigrateMetadata::parse(&bytes),
3168            Err(NnrpError::InvalidProtocolCombination {
3169                rule: "session_migrate.old_transport_id"
3170            })
3171        );
3172    }
3173
3174    #[test]
3175    fn error_metadata_and_empty_control_headers_validate() {
3176        let metadata = ErrorMetadata {
3177            error_code: 0x000b,
3178            error_scope: ErrorScope::Session,
3179            is_fatal: false,
3180            retry_after_ms: 500,
3181            related_session_id: 42,
3182            related_frame_id: 0,
3183            related_view_id: 0,
3184            diagnostic_bytes: 24,
3185        };
3186        assert_eq!(
3187            ErrorMetadata::parse(&metadata.to_bytes().unwrap()).unwrap(),
3188            metadata
3189        );
3190
3191        let mut ping = CommonHeader::new(MessageType::Ping, 0, 0);
3192        ping.flags = HeaderFlags::CAN_DROP;
3193        validate_empty_control_header(&ping, MessageType::Ping).unwrap();
3194        assert_eq!(
3195            validate_empty_control_header(&ping, MessageType::Pong),
3196            Err(NnrpError::InvalidProtocolCombination {
3197                rule: "empty control message requires expected type, meta_len=0, and body_len=0"
3198            })
3199        );
3200
3201        let mut close = CommonHeader::new(MessageType::Close, 0, 5);
3202        close.body_len = 5;
3203        validate_close_header(&close).unwrap();
3204    }
3205
3206    #[test]
3207    fn control_metadata_rejects_short_buffers_and_bad_error_fatal_flag() {
3208        assert_eq!(
3209            ClientHelloMetadata::parse(&[0u8; CLIENT_HELLO_METADATA_LEN - 1]),
3210            Err(NnrpError::SourceTooShort {
3211                expected: CLIENT_HELLO_METADATA_LEN,
3212                actual: CLIENT_HELLO_METADATA_LEN - 1
3213            })
3214        );
3215        let hello = ClientHelloMetadata::parse(&hex_to_bytes("01010100010000000100000003000000030000002100000003000000010007000100020040000000000001007017640002000000000000006000000000000000")).unwrap();
3216        assert_eq!(
3217            hello.write(&mut [0u8; CLIENT_HELLO_METADATA_LEN - 1]),
3218            Err(NnrpError::DestinationTooShort {
3219                expected: CLIENT_HELLO_METADATA_LEN,
3220                actual: CLIENT_HELLO_METADATA_LEN - 1
3221            })
3222        );
3223
3224        assert_eq!(
3225            ServerHelloAckMetadata::parse(&[0u8; SERVER_HELLO_ACK_METADATA_LEN - 1]),
3226            Err(NnrpError::SourceTooShort {
3227                expected: SERVER_HELLO_ACK_METADATA_LEN,
3228                actual: SERVER_HELLO_ACK_METADATA_LEN - 1
3229            })
3230        );
3231
3232        let mut error = [0u8; ERROR_METADATA_LEN];
3233        write_u32(&mut error, 8, 2);
3234        assert_eq!(
3235            ErrorMetadata::parse(&error),
3236            Err(NnrpError::InvalidProtocolCombination {
3237                rule: "ERROR is_fatal must be 0 or 1"
3238            })
3239        );
3240
3241        assert_eq!(
3242            ResultHintMetadata::parse(&[0u8; RESULT_HINT_METADATA_LEN - 1]),
3243            Err(NnrpError::SourceTooShort {
3244                expected: RESULT_HINT_METADATA_LEN,
3245                actual: RESULT_HINT_METADATA_LEN - 1
3246            })
3247        );
3248        let hint = ResultHintMetadata {
3249            applied_budget_policy: ResultHintBudgetPolicy::None,
3250            congestion_state: ResultHintCongestionState::None,
3251            reason: ResultHintReason::None,
3252            retry_after_ms: 0,
3253        };
3254        assert_eq!(
3255            hint.write(&mut [0u8; RESULT_HINT_METADATA_LEN - 1]),
3256            Err(NnrpError::DestinationTooShort {
3257                expected: RESULT_HINT_METADATA_LEN,
3258                actual: RESULT_HINT_METADATA_LEN - 1
3259            })
3260        );
3261    }
3262
3263    fn hex_to_bytes(hex: &str) -> Vec<u8> {
3264        assert_eq!(hex.len() % 2, 0);
3265        (0..hex.len())
3266            .step_by(2)
3267            .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap())
3268            .collect()
3269    }
3270}