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}