1use crate::{
2 CacheObjectKind, CommonHeader, ErrorMetadata, ErrorScope, MessageType, NnrpError,
3 TypedPayloadDescriptor, CACHE_ERROR_MISS, TYPED_PAYLOAD_DESCRIPTOR_LEN,
4};
5
6pub const FRAME_SUBMIT_METADATA_LEN: usize = 72;
7pub const RESULT_PUSH_METADATA_LEN: usize = 64;
8pub const BODY_REGION_PRELUDE_LEN: usize = 32;
9pub const OBJECT_REFERENCE_BLOCK_LEN: usize = 24;
10
11pub const BUDGET_POLICY_KNOWN_MASK: u8 = 0x0f;
12pub const RESULT_FLAGS_KNOWN_MASK: u16 = 0x0007;
13pub const PAYLOAD_KIND_KNOWN_MASK: u32 = 0x0000_007f;
14pub const SUBMIT_OBJECT_REF_MASK_KNOWN_BITS: u32 = 0x0000_000f;
15pub const STANDARD_PROFILE_UNSPECIFIED: u16 = 0x0000;
16pub const STANDARD_PROFILE_TENSOR: u16 = 0x0001;
17pub const STANDARD_PROFILE_TOKEN: u16 = 0x0002;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum InputProfile {
22 Unspecified = 0,
23 ChangedTilesLuma = 1,
24 DenseLumaFrame = 2,
25}
26
27impl InputProfile {
28 pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
29 match value {
30 0 => Ok(Self::Unspecified),
31 1 => Ok(Self::ChangedTilesLuma),
32 2 => Ok(Self::DenseLumaFrame),
33 _ => Err(NnrpError::UnknownEnumValue {
34 enum_name: "input_profile",
35 value: value as u64,
36 }),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[repr(u8)]
43pub enum TileIndexMode {
44 DenseRange = 0,
45 RawU16 = 1,
46 DeltaU16 = 2,
47 Bitset = 3,
48}
49
50impl TileIndexMode {
51 pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
52 match value {
53 0 => Ok(Self::DenseRange),
54 1 => Ok(Self::RawU16),
55 2 => Ok(Self::DeltaU16),
56 3 => Ok(Self::Bitset),
57 _ => Err(NnrpError::UnknownEnumValue {
58 enum_name: "tile_index_mode",
59 value: value as u64,
60 }),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[repr(u8)]
67pub enum SubmitMode {
68 Inline = 0,
69 Reference = 1,
70 Mixed = 2,
71}
72
73impl SubmitMode {
74 pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
75 match value {
76 0 => Ok(Self::Inline),
77 1 => Ok(Self::Reference),
78 2 => Ok(Self::Mixed),
79 _ => Err(NnrpError::UnknownEnumValue {
80 enum_name: "submit_mode",
81 value: value as u64,
82 }),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88#[repr(u8)]
89pub enum ResultClass {
90 Complete = 0,
91 Partial = 1,
92 StaleReuse = 2,
93 Degraded = 3,
94}
95
96impl ResultClass {
97 pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
98 match value {
99 0 => Ok(Self::Complete),
100 1 => Ok(Self::Partial),
101 2 => Ok(Self::StaleReuse),
102 3 => Ok(Self::Degraded),
103 _ => Err(NnrpError::UnknownEnumValue {
104 enum_name: "result_class",
105 value: value as u64,
106 }),
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct PayloadKindBitmap(pub u32);
113
114impl PayloadKindBitmap {
115 pub const TENSOR: u32 = 0x0000_0001;
116 pub const TOKEN_CHUNK: u32 = 0x0000_0002;
117 pub const AUDIO_CHUNK: u32 = 0x0000_0004;
118 pub const VIDEO_CHUNK: u32 = 0x0000_0008;
119 pub const STRUCTURED_EVENT: u32 = 0x0000_0010;
120 pub const TOOL_DELTA: u32 = 0x0000_0020;
121 pub const OPAQUE_BYTES: u32 = 0x0000_0040;
122
123 pub fn validate(self) -> Result<(), NnrpError> {
124 validate_mask_u32(self.0, PAYLOAD_KIND_KNOWN_MASK)
125 }
126
127 pub fn contains_tensor(self) -> bool {
128 self.0 & Self::TENSOR != 0
129 }
130
131 pub fn contains(self, family: PayloadFamily) -> bool {
132 self.0 & family.bit() != 0
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137#[repr(u32)]
138pub enum PayloadFamily {
139 Tensor = PayloadKindBitmap::TENSOR,
140 TokenChunk = PayloadKindBitmap::TOKEN_CHUNK,
141 AudioChunk = PayloadKindBitmap::AUDIO_CHUNK,
142 VideoChunk = PayloadKindBitmap::VIDEO_CHUNK,
143 StructuredEvent = PayloadKindBitmap::STRUCTURED_EVENT,
144 ToolDelta = PayloadKindBitmap::TOOL_DELTA,
145 OpaqueBytes = PayloadKindBitmap::OPAQUE_BYTES,
146}
147
148impl PayloadFamily {
149 pub fn try_from_bit(bit: u32) -> Result<Self, NnrpError> {
150 match bit {
151 PayloadKindBitmap::TENSOR => Ok(Self::Tensor),
152 PayloadKindBitmap::TOKEN_CHUNK => Ok(Self::TokenChunk),
153 PayloadKindBitmap::AUDIO_CHUNK => Ok(Self::AudioChunk),
154 PayloadKindBitmap::VIDEO_CHUNK => Ok(Self::VideoChunk),
155 PayloadKindBitmap::STRUCTURED_EVENT => Ok(Self::StructuredEvent),
156 PayloadKindBitmap::TOOL_DELTA => Ok(Self::ToolDelta),
157 PayloadKindBitmap::OPAQUE_BYTES => Ok(Self::OpaqueBytes),
158 _ => Err(NnrpError::UnknownEnumValue {
159 enum_name: "payload_family_bit",
160 value: bit as u64,
161 }),
162 }
163 }
164
165 pub fn bit(self) -> u32 {
166 self as u32
167 }
168
169 pub fn is_standard_profile(self) -> bool {
170 matches!(self, Self::Tensor | Self::TokenChunk)
171 }
172
173 pub fn is_registry_bound_family(self) -> bool {
174 matches!(
175 self,
176 Self::AudioChunk
177 | Self::VideoChunk
178 | Self::StructuredEvent
179 | Self::ToolDelta
180 | Self::OpaqueBytes
181 )
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub struct FrameSubmitMetadata {
187 pub src_width: u16,
188 pub src_height: u16,
189 pub tile_width: u16,
190 pub tile_height: u16,
191 pub tile_count: u16,
192 pub section_count: u16,
193 pub frame_class: u8,
194 pub input_profile: InputProfile,
195 pub tile_index_mode: TileIndexMode,
196 pub latency_budget_ms: u16,
197 pub target_fps_x100: u16,
198 pub retry_of_frame: u32,
199 pub tile_base_id: u32,
200 pub camera_bytes: u32,
201 pub tile_index_bytes: u32,
202 pub operation_id: u64,
203 pub submit_mode: SubmitMode,
204 pub budget_policy: u8,
205 pub loss_tolerance_policy: u8,
206 pub object_ref_mask: u32,
207 pub dependency_frame_id: u32,
208 pub payload_kind_bitmap: PayloadKindBitmap,
209 pub payload_frame_count: u16,
210}
211
212impl FrameSubmitMetadata {
213 pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
214 require_len(source, FRAME_SUBMIT_METADATA_LEN)?;
215 validate_zero_u8("frame_submit.reserved0", source[15])?;
216 validate_zero_u32("frame_submit.reserved1", read_u32(source, 36))?;
217 validate_zero_u32("frame_submit.reserved2", read_u32(source, 48))?;
218 validate_zero_u8("frame_submit.reserved3", source[55])?;
219 validate_zero_u16("frame_submit.reserved4", read_u16(source, 70))?;
220
221 let budget_policy = source[53];
222 validate_mask_u8(budget_policy, BUDGET_POLICY_KNOWN_MASK)?;
223 let payload_kind_bitmap = PayloadKindBitmap(read_u32(source, 64));
224 payload_kind_bitmap.validate()?;
225
226 let metadata = Self {
227 src_width: read_u16(source, 0),
228 src_height: read_u16(source, 2),
229 tile_width: read_u16(source, 4),
230 tile_height: read_u16(source, 6),
231 tile_count: read_u16(source, 8),
232 section_count: read_u16(source, 10),
233 frame_class: source[12],
234 input_profile: InputProfile::try_from_u8(source[13])?,
235 tile_index_mode: TileIndexMode::try_from_u8(source[14])?,
236 latency_budget_ms: read_u16(source, 16),
237 target_fps_x100: read_u16(source, 18),
238 retry_of_frame: read_u32(source, 20),
239 tile_base_id: read_u32(source, 24),
240 camera_bytes: read_u32(source, 28),
241 tile_index_bytes: read_u32(source, 32),
242 operation_id: read_u64(source, 40),
243 submit_mode: SubmitMode::try_from_u8(source[52])?,
244 budget_policy,
245 loss_tolerance_policy: source[54],
246 object_ref_mask: read_u32(source, 56),
247 dependency_frame_id: read_u32(source, 60),
248 payload_kind_bitmap,
249 payload_frame_count: read_u16(source, 68),
250 };
251 metadata.validate_payload_shape()?;
252 Ok(metadata)
253 }
254
255 pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
256 require_destination_len(destination, FRAME_SUBMIT_METADATA_LEN)?;
257 validate_mask_u8(self.budget_policy, BUDGET_POLICY_KNOWN_MASK)?;
258 self.payload_kind_bitmap.validate()?;
259 self.validate_payload_shape()?;
260
261 destination[..FRAME_SUBMIT_METADATA_LEN].fill(0);
262 write_u16(destination, 0, self.src_width);
263 write_u16(destination, 2, self.src_height);
264 write_u16(destination, 4, self.tile_width);
265 write_u16(destination, 6, self.tile_height);
266 write_u16(destination, 8, self.tile_count);
267 write_u16(destination, 10, self.section_count);
268 destination[12] = self.frame_class;
269 destination[13] = self.input_profile as u8;
270 destination[14] = self.tile_index_mode as u8;
271 write_u16(destination, 16, self.latency_budget_ms);
272 write_u16(destination, 18, self.target_fps_x100);
273 write_u32(destination, 20, self.retry_of_frame);
274 write_u32(destination, 24, self.tile_base_id);
275 write_u32(destination, 28, self.camera_bytes);
276 write_u32(destination, 32, self.tile_index_bytes);
277 write_u64(destination, 40, self.operation_id);
278 destination[52] = self.submit_mode as u8;
279 destination[53] = self.budget_policy;
280 destination[54] = self.loss_tolerance_policy;
281 write_u32(destination, 56, self.object_ref_mask);
282 write_u32(destination, 60, self.dependency_frame_id);
283 write_u32(destination, 64, self.payload_kind_bitmap.0);
284 write_u16(destination, 68, self.payload_frame_count);
285 Ok(())
286 }
287
288 pub fn to_bytes(&self) -> Result<[u8; FRAME_SUBMIT_METADATA_LEN], NnrpError> {
289 let mut bytes = [0u8; FRAME_SUBMIT_METADATA_LEN];
290 self.write(&mut bytes)?;
291 Ok(bytes)
292 }
293
294 pub fn validate_payload_shape(&self) -> Result<(), NnrpError> {
295 if self.operation_id == 0 {
296 return Err(NnrpError::InvalidProtocolCombination {
297 rule: "FRAME_SUBMIT operation_id must not be zero",
298 });
299 }
300 if self.payload_kind_bitmap.contains_tensor() {
301 return Ok(());
302 }
303
304 if self.src_width != 0
305 || self.src_height != 0
306 || self.tile_width != 0
307 || self.tile_height != 0
308 || self.tile_count != 0
309 || self.section_count != 0
310 || self.tile_base_id != 0
311 || self.camera_bytes != 0
312 || self.tile_index_bytes != 0
313 || self.input_profile != InputProfile::Unspecified
314 {
315 return Err(NnrpError::InvalidProtocolCombination {
316 rule: "non-tensor FRAME_SUBMIT must clear tensor tile fields",
317 });
318 }
319
320 Ok(())
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub struct ResultPushMetadata {
326 pub status_code: u16,
327 pub result_flags: u16,
328 pub section_count: u16,
329 pub tile_count: u16,
330 pub active_profile_id: u16,
331 pub inference_ms: u16,
332 pub queue_ms: u16,
333 pub server_total_ms: u16,
334 pub tile_base_id: u32,
335 pub tile_index_bytes: u32,
336 pub result_class: ResultClass,
337 pub applied_budget_policy: u8,
338 pub reused_frame_id: u32,
339 pub covered_tile_count: u16,
340 pub dropped_tile_count: u16,
341 pub payload_kind_bitmap: PayloadKindBitmap,
342 pub payload_frame_count: u16,
343}
344
345impl ResultPushMetadata {
346 pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
347 require_len(source, RESULT_PUSH_METADATA_LEN)?;
348 validate_zero_u16("result_push.reserved0", read_u16(source, 10))?;
349 validate_zero_u16("result_push.reserved1", read_u16(source, 18))?;
350 validate_zero_u64("result_push.reserved2", read_u64(source, 28))?;
351 validate_zero_u64("result_push.reserved3", read_u64(source, 36))?;
352 validate_zero_u16("result_push.reserved4", read_u16(source, 46))?;
353 validate_zero_u16("result_push.reserved5", read_u16(source, 62))?;
354
355 let result_flags = read_u16(source, 2);
356 validate_mask_u16(result_flags, RESULT_FLAGS_KNOWN_MASK)?;
357 let applied_budget_policy = source[45];
358 validate_mask_u8(applied_budget_policy, BUDGET_POLICY_KNOWN_MASK)?;
359 let payload_kind_bitmap = PayloadKindBitmap(read_u32(source, 56));
360 payload_kind_bitmap.validate()?;
361
362 let metadata = Self {
363 status_code: read_u16(source, 0),
364 result_flags,
365 section_count: read_u16(source, 4),
366 tile_count: read_u16(source, 6),
367 active_profile_id: read_u16(source, 8),
368 inference_ms: read_u16(source, 12),
369 queue_ms: read_u16(source, 14),
370 server_total_ms: read_u16(source, 16),
371 tile_base_id: read_u32(source, 20),
372 tile_index_bytes: read_u32(source, 24),
373 result_class: ResultClass::try_from_u8(source[44])?,
374 applied_budget_policy,
375 reused_frame_id: read_u32(source, 48),
376 covered_tile_count: read_u16(source, 52),
377 dropped_tile_count: read_u16(source, 54),
378 payload_kind_bitmap,
379 payload_frame_count: read_u16(source, 60),
380 };
381 metadata.validate_payload_shape()?;
382 Ok(metadata)
383 }
384
385 pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
386 require_destination_len(destination, RESULT_PUSH_METADATA_LEN)?;
387 validate_mask_u16(self.result_flags, RESULT_FLAGS_KNOWN_MASK)?;
388 validate_mask_u8(self.applied_budget_policy, BUDGET_POLICY_KNOWN_MASK)?;
389 self.payload_kind_bitmap.validate()?;
390 self.validate_payload_shape()?;
391
392 destination[..RESULT_PUSH_METADATA_LEN].fill(0);
393 write_u16(destination, 0, self.status_code);
394 write_u16(destination, 2, self.result_flags);
395 write_u16(destination, 4, self.section_count);
396 write_u16(destination, 6, self.tile_count);
397 write_u16(destination, 8, self.active_profile_id);
398 write_u16(destination, 12, self.inference_ms);
399 write_u16(destination, 14, self.queue_ms);
400 write_u16(destination, 16, self.server_total_ms);
401 write_u32(destination, 20, self.tile_base_id);
402 write_u32(destination, 24, self.tile_index_bytes);
403 destination[44] = self.result_class as u8;
404 destination[45] = self.applied_budget_policy;
405 write_u32(destination, 48, self.reused_frame_id);
406 write_u16(destination, 52, self.covered_tile_count);
407 write_u16(destination, 54, self.dropped_tile_count);
408 write_u32(destination, 56, self.payload_kind_bitmap.0);
409 write_u16(destination, 60, self.payload_frame_count);
410 Ok(())
411 }
412
413 pub fn to_bytes(&self) -> Result<[u8; RESULT_PUSH_METADATA_LEN], NnrpError> {
414 let mut bytes = [0u8; RESULT_PUSH_METADATA_LEN];
415 self.write(&mut bytes)?;
416 Ok(bytes)
417 }
418
419 pub fn validate_payload_shape(&self) -> Result<(), NnrpError> {
420 if self.payload_kind_bitmap.contains_tensor() {
421 return Ok(());
422 }
423
424 if self.section_count != 0
425 || self.tile_count != 0
426 || self.tile_base_id != 0
427 || self.tile_index_bytes != 0
428 || self.covered_tile_count != 0
429 || self.dropped_tile_count != 0
430 {
431 return Err(NnrpError::InvalidProtocolCombination {
432 rule: "non-tensor RESULT_PUSH must clear tensor coverage fields",
433 });
434 }
435
436 Ok(())
437 }
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441pub struct BodyRegionPrelude {
442 pub inline_object_bytes: u32,
443 pub object_reference_bytes: u32,
444 pub typed_payload_descriptor_bytes: u32,
445 pub typed_payload_frame_bytes: u32,
446 pub extension_descriptor_bytes: u32,
447 pub extension_payload_bytes: u32,
448}
449
450impl BodyRegionPrelude {
451 pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
452 require_len(source, BODY_REGION_PRELUDE_LEN)?;
453 validate_zero_u32("body_region_prelude.body_flags", read_u32(source, 24))?;
454 validate_zero_u32("body_region_prelude.reserved", read_u32(source, 28))?;
455
456 let prelude = Self {
457 inline_object_bytes: read_u32(source, 0),
458 object_reference_bytes: read_u32(source, 4),
459 typed_payload_descriptor_bytes: read_u32(source, 8),
460 typed_payload_frame_bytes: read_u32(source, 12),
461 extension_descriptor_bytes: read_u32(source, 16),
462 extension_payload_bytes: read_u32(source, 20),
463 };
464 prelude.validate_alignment()?;
465 Ok(prelude)
466 }
467
468 pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
469 require_destination_len(destination, BODY_REGION_PRELUDE_LEN)?;
470 self.validate_alignment()?;
471
472 destination[..BODY_REGION_PRELUDE_LEN].fill(0);
473 write_u32(destination, 0, self.inline_object_bytes);
474 write_u32(destination, 4, self.object_reference_bytes);
475 write_u32(destination, 8, self.typed_payload_descriptor_bytes);
476 write_u32(destination, 12, self.typed_payload_frame_bytes);
477 write_u32(destination, 16, self.extension_descriptor_bytes);
478 write_u32(destination, 20, self.extension_payload_bytes);
479 Ok(())
480 }
481
482 pub fn to_bytes(&self) -> Result<[u8; BODY_REGION_PRELUDE_LEN], NnrpError> {
483 let mut bytes = [0u8; BODY_REGION_PRELUDE_LEN];
484 self.write(&mut bytes)?;
485 Ok(bytes)
486 }
487
488 pub fn total_region_bytes(&self) -> Result<u32, NnrpError> {
489 [
490 self.inline_object_bytes,
491 self.object_reference_bytes,
492 self.typed_payload_descriptor_bytes,
493 self.typed_payload_frame_bytes,
494 self.extension_descriptor_bytes,
495 self.extension_payload_bytes,
496 ]
497 .into_iter()
498 .try_fold(0u32, |sum, value| {
499 sum.checked_add(value)
500 .ok_or(NnrpError::MessageLengthOverflow)
501 })
502 }
503
504 fn validate_alignment(&self) -> Result<(), NnrpError> {
505 if self.object_reference_bytes as usize % OBJECT_REFERENCE_BLOCK_LEN != 0 {
506 return Err(NnrpError::InvalidProtocolCombination {
507 rule: "object_reference_bytes must be a multiple of object reference block length",
508 });
509 }
510 Ok(())
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
515pub struct ObjectReferenceBlock {
516 pub object_kind: CacheObjectKind,
517 pub ref_flags: u16,
518 pub cache_namespace: u32,
519 pub cache_key_hi: u64,
520 pub cache_key_lo: u64,
521}
522
523impl ObjectReferenceBlock {
524 pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
525 require_len(source, OBJECT_REFERENCE_BLOCK_LEN)?;
526 let ref_flags = read_u16(source, 2);
527 validate_zero_u16("object_reference.ref_flags", ref_flags)?;
528
529 Ok(Self {
530 object_kind: CacheObjectKind::try_from_u32(read_u16(source, 0) as u32)?,
531 ref_flags,
532 cache_namespace: read_u32(source, 4),
533 cache_key_hi: read_u64(source, 8),
534 cache_key_lo: read_u64(source, 16),
535 })
536 }
537
538 pub fn write(&self, destination: &mut [u8]) -> Result<(), NnrpError> {
539 require_destination_len(destination, OBJECT_REFERENCE_BLOCK_LEN)?;
540 validate_zero_u16("object_reference.ref_flags", self.ref_flags)?;
541
542 destination[..OBJECT_REFERENCE_BLOCK_LEN].fill(0);
543 write_u16(destination, 0, self.object_kind as u16);
544 write_u32(destination, 4, self.cache_namespace);
545 write_u64(destination, 8, self.cache_key_hi);
546 write_u64(destination, 16, self.cache_key_lo);
547 Ok(())
548 }
549
550 pub fn to_bytes(&self) -> Result<[u8; OBJECT_REFERENCE_BLOCK_LEN], NnrpError> {
551 let mut bytes = [0u8; OBJECT_REFERENCE_BLOCK_LEN];
552 self.write(&mut bytes)?;
553 Ok(bytes)
554 }
555
556 pub fn cache_miss_error_metadata(
557 &self,
558 related_session_id: u32,
559 related_frame_id: u32,
560 related_view_id: u32,
561 diagnostic_bytes: u32,
562 ) -> ErrorMetadata {
563 ErrorMetadata {
564 error_code: CACHE_ERROR_MISS,
565 error_scope: ErrorScope::Frame,
566 is_fatal: false,
567 retry_after_ms: 0,
568 related_session_id,
569 related_frame_id,
570 related_view_id,
571 diagnostic_bytes,
572 }
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Eq)]
577pub struct ObjectReferenceRegion {
578 blocks: Vec<ObjectReferenceBlock>,
579}
580
581impl ObjectReferenceRegion {
582 pub fn parse(source: &[u8]) -> Result<Self, NnrpError> {
583 if source.len() % OBJECT_REFERENCE_BLOCK_LEN != 0 {
584 return Err(NnrpError::InvalidProtocolCombination {
585 rule: "object reference region length must be a multiple of object reference block length",
586 });
587 }
588
589 let blocks = source
590 .chunks_exact(OBJECT_REFERENCE_BLOCK_LEN)
591 .map(ObjectReferenceBlock::parse)
592 .collect::<Result<Vec<_>, _>>()?;
593
594 Ok(Self { blocks })
595 }
596
597 pub fn from_blocks(blocks: Vec<ObjectReferenceBlock>) -> Self {
598 Self { blocks }
599 }
600
601 pub fn blocks(&self) -> &[ObjectReferenceBlock] {
602 &self.blocks
603 }
604
605 pub fn to_bytes(&self) -> Result<Vec<u8>, NnrpError> {
606 let mut bytes = vec![0u8; self.blocks.len() * OBJECT_REFERENCE_BLOCK_LEN];
607 for (index, block) in self.blocks.iter().enumerate() {
608 block.write(
609 &mut bytes
610 [index * OBJECT_REFERENCE_BLOCK_LEN..(index + 1) * OBJECT_REFERENCE_BLOCK_LEN],
611 )?;
612 }
613 Ok(bytes)
614 }
615
616 pub fn validate_submit_mask(
617 &self,
618 submit_mode: SubmitMode,
619 object_ref_mask: u32,
620 ) -> Result<(), NnrpError> {
621 validate_submit_object_ref_mask(submit_mode, object_ref_mask)?;
622
623 let mut expected_slot = 0usize;
624 let mut seen_mask = 0u32;
625 for block in &self.blocks {
626 let Some(slot) = submit_object_slot_index(block.object_kind) else {
627 continue;
628 };
629 if slot < expected_slot {
630 return Err(NnrpError::InvalidProtocolCombination {
631 rule: "object reference region standard slots must be sorted",
632 });
633 }
634 expected_slot = slot;
635 let bit = 1u32 << slot;
636 if seen_mask & bit != 0 {
637 return Err(NnrpError::InvalidProtocolCombination {
638 rule: "object reference region must not duplicate standard slots",
639 });
640 }
641 if object_ref_mask & bit == 0 {
642 return Err(NnrpError::InvalidProtocolCombination {
643 rule: "object reference block requires matching object_ref_mask bit",
644 });
645 }
646 seen_mask |= bit;
647 }
648
649 if seen_mask != object_ref_mask {
650 return Err(NnrpError::InvalidProtocolCombination {
651 rule: "object reference region must exactly match object_ref_mask",
652 });
653 }
654
655 Ok(())
656 }
657
658 pub fn validate_resolved<F>(&self, mut contains: F) -> Result<(), NnrpError>
659 where
660 F: FnMut(&ObjectReferenceBlock) -> bool,
661 {
662 for block in &self.blocks {
663 if !contains(block) {
664 return Err(NnrpError::InvalidProtocolCombination {
665 rule: "object reference must resolve from cache",
666 });
667 }
668 }
669 Ok(())
670 }
671
672 pub fn first_unresolved<F>(&self, mut contains: F) -> Option<ObjectReferenceBlock>
673 where
674 F: FnMut(&ObjectReferenceBlock) -> bool,
675 {
676 self.blocks.iter().copied().find(|block| !contains(block))
677 }
678
679 pub fn validate_resolved_or_cache_miss<F>(
680 &self,
681 contains: F,
682 related_session_id: u32,
683 related_frame_id: u32,
684 related_view_id: u32,
685 ) -> Result<(), ErrorMetadata>
686 where
687 F: FnMut(&ObjectReferenceBlock) -> bool,
688 {
689 match self.first_unresolved(contains) {
690 Some(block) => Err(block.cache_miss_error_metadata(
691 related_session_id,
692 related_frame_id,
693 related_view_id,
694 0,
695 )),
696 None => Ok(()),
697 }
698 }
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq)]
702pub struct TypedPayloadFrameView<'a> {
703 pub descriptor: TypedPayloadDescriptor,
704 pub payload: &'a [u8],
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub struct TypedPayloadRegion<'a> {
709 descriptors: Vec<TypedPayloadDescriptor>,
710 payload_region: &'a [u8],
711}
712
713impl<'a> TypedPayloadRegion<'a> {
714 pub fn parse(
715 payload_kind_bitmap: PayloadKindBitmap,
716 payload_frame_count: u16,
717 descriptor_region: &[u8],
718 payload_region: &'a [u8],
719 ) -> Result<Self, NnrpError> {
720 payload_kind_bitmap.validate()?;
721 let expected_descriptor_bytes = usize::from(payload_frame_count)
722 .checked_mul(TYPED_PAYLOAD_DESCRIPTOR_LEN)
723 .ok_or(NnrpError::MessageLengthOverflow)?;
724 if descriptor_region.len() != expected_descriptor_bytes {
725 return Err(NnrpError::InvalidProtocolCombination {
726 rule: "typed payload descriptor region length must match payload_frame_count",
727 });
728 }
729
730 if payload_frame_count == 0 {
731 if !descriptor_region.is_empty() || !payload_region.is_empty() {
732 return Err(NnrpError::InvalidProtocolCombination {
733 rule: "zero typed payload frames require empty descriptor and frame regions",
734 });
735 }
736 return Ok(Self {
737 descriptors: Vec::new(),
738 payload_region,
739 });
740 }
741
742 let descriptors = descriptor_region
743 .chunks_exact(TYPED_PAYLOAD_DESCRIPTOR_LEN)
744 .map(TypedPayloadDescriptor::parse)
745 .collect::<Result<Vec<_>, _>>()?;
746 let region = Self {
747 descriptors,
748 payload_region,
749 };
750 region.validate(payload_kind_bitmap, payload_frame_count)?;
751 Ok(region)
752 }
753
754 pub fn from_parts(
755 payload_kind_bitmap: PayloadKindBitmap,
756 descriptors: Vec<TypedPayloadDescriptor>,
757 payload_region: &'a [u8],
758 ) -> Result<Self, NnrpError> {
759 let payload_frame_count =
760 u16::try_from(descriptors.len()).map_err(|_| NnrpError::MessageLengthOverflow)?;
761 let region = Self {
762 descriptors,
763 payload_region,
764 };
765 region.validate(payload_kind_bitmap, payload_frame_count)?;
766 Ok(region)
767 }
768
769 pub fn descriptors(&self) -> &[TypedPayloadDescriptor] {
770 &self.descriptors
771 }
772
773 pub fn payload_region(&self) -> &'a [u8] {
774 self.payload_region
775 }
776
777 pub fn frame_views(&self) -> Result<Vec<TypedPayloadFrameView<'a>>, NnrpError> {
778 self.descriptors
779 .iter()
780 .map(|descriptor| {
781 let start = descriptor.offset as usize;
782 let end = checked_payload_end(descriptor)?;
783 Ok(TypedPayloadFrameView {
784 descriptor: *descriptor,
785 payload: &self.payload_region[start..end],
786 })
787 })
788 .collect()
789 }
790
791 pub fn descriptor_region_bytes(&self) -> Result<Vec<u8>, NnrpError> {
792 let mut bytes = vec![0u8; self.descriptors.len() * TYPED_PAYLOAD_DESCRIPTOR_LEN];
793 for (index, descriptor) in self.descriptors.iter().enumerate() {
794 descriptor.write(
795 &mut bytes[index * TYPED_PAYLOAD_DESCRIPTOR_LEN
796 ..(index + 1) * TYPED_PAYLOAD_DESCRIPTOR_LEN],
797 )?;
798 }
799 Ok(bytes)
800 }
801
802 fn validate(
803 &self,
804 payload_kind_bitmap: PayloadKindBitmap,
805 payload_frame_count: u16,
806 ) -> Result<(), NnrpError> {
807 if self.descriptors.len() != usize::from(payload_frame_count) {
808 return Err(NnrpError::InvalidProtocolCombination {
809 rule: "typed payload descriptor count must match payload_frame_count",
810 });
811 }
812
813 let mut next_expected_offset = 0usize;
814 for descriptor in &self.descriptors {
815 validate_descriptor_profile(payload_kind_bitmap, descriptor)?;
816 if descriptor.offset as usize != next_expected_offset {
817 return Err(NnrpError::InvalidProtocolCombination {
818 rule: "typed payload descriptors must be packed in strictly contiguous order",
819 });
820 }
821 next_expected_offset = checked_payload_end(descriptor)?;
822 if next_expected_offset > self.payload_region.len() {
823 return Err(NnrpError::InvalidProtocolCombination {
824 rule: "typed payload descriptor range must fit the frame region",
825 });
826 }
827 }
828
829 if next_expected_offset != self.payload_region.len() {
830 return Err(NnrpError::InvalidProtocolCombination {
831 rule: "typed payload frame region must be exactly covered by descriptors",
832 });
833 }
834
835 Ok(())
836 }
837}
838
839fn validate_descriptor_profile(
840 payload_kind_bitmap: PayloadKindBitmap,
841 descriptor: &TypedPayloadDescriptor,
842) -> Result<(), NnrpError> {
843 let non_tensor_payloads = payload_kind_bitmap.0 & !PayloadKindBitmap::TENSOR;
844 if non_tensor_payloads != 0 && descriptor.profile_id == STANDARD_PROFILE_TENSOR {
845 return Err(NnrpError::InvalidProtocolCombination {
846 rule: "non-tensor typed payload frames must not use tensor profile",
847 });
848 }
849
850 if payload_kind_bitmap.0 == PayloadKindBitmap::TOKEN_CHUNK
851 && descriptor.profile_id != STANDARD_PROFILE_TOKEN
852 {
853 return Err(NnrpError::InvalidProtocolCombination {
854 rule: "token-only typed payload frames require token profile",
855 });
856 }
857
858 Ok(())
859}
860
861fn checked_payload_end(descriptor: &TypedPayloadDescriptor) -> Result<usize, NnrpError> {
862 let end = descriptor
863 .offset
864 .checked_add(descriptor.length)
865 .ok_or(NnrpError::MessageLengthOverflow)?;
866 usize::try_from(end).map_err(|_| NnrpError::MessageLengthOverflow)
867}
868
869pub fn validate_submit_object_ref_mask(
870 submit_mode: SubmitMode,
871 object_ref_mask: u32,
872) -> Result<(), NnrpError> {
873 validate_mask_u32(object_ref_mask, SUBMIT_OBJECT_REF_MASK_KNOWN_BITS)?;
874
875 match submit_mode {
876 SubmitMode::Inline => {
877 if object_ref_mask != 0 {
878 return Err(NnrpError::InvalidProtocolCombination {
879 rule: "inline FRAME_SUBMIT must not declare object_ref_mask",
880 });
881 }
882 }
883 SubmitMode::Reference | SubmitMode::Mixed => {
884 if object_ref_mask == 0 {
885 return Err(NnrpError::InvalidProtocolCombination {
886 rule: "reference or mixed FRAME_SUBMIT requires non-zero object_ref_mask",
887 });
888 }
889 }
890 }
891
892 Ok(())
893}
894
895fn submit_object_slot_index(object_kind: CacheObjectKind) -> Option<usize> {
896 match object_kind {
897 CacheObjectKind::CameraBlock => Some(0),
898 CacheObjectKind::TileIndexBlock => Some(1),
899 CacheObjectKind::TensorSectionTable => Some(2),
900 CacheObjectKind::PayloadLayoutTemplate => Some(3),
901 _ => None,
902 }
903}
904
905pub fn validate_result_drop_header(header: &CommonHeader) -> Result<(), NnrpError> {
906 if header.message_type != MessageType::ResultDrop
907 || header.meta_len != 0
908 || header.body_len != 0
909 {
910 return Err(NnrpError::InvalidProtocolCombination {
911 rule: "RESULT_DROP is header-only and requires meta_len=0 and body_len=0",
912 });
913 }
914 Ok(())
915}
916
917fn require_len(source: &[u8], expected: usize) -> Result<(), NnrpError> {
918 if source.len() < expected {
919 return Err(NnrpError::SourceTooShort {
920 expected,
921 actual: source.len(),
922 });
923 }
924 Ok(())
925}
926
927fn require_destination_len(destination: &[u8], expected: usize) -> Result<(), NnrpError> {
928 if destination.len() < expected {
929 return Err(NnrpError::DestinationTooShort {
930 expected,
931 actual: destination.len(),
932 });
933 }
934 Ok(())
935}
936
937fn validate_zero_u8(field: &'static str, value: u8) -> Result<(), NnrpError> {
938 if value != 0 {
939 return Err(NnrpError::NonZeroReservedField { field });
940 }
941 Ok(())
942}
943
944fn validate_zero_u16(field: &'static str, value: u16) -> Result<(), NnrpError> {
945 if value != 0 {
946 return Err(NnrpError::NonZeroReservedField { field });
947 }
948 Ok(())
949}
950
951fn validate_zero_u32(field: &'static str, value: u32) -> Result<(), NnrpError> {
952 if value != 0 {
953 return Err(NnrpError::NonZeroReservedField { field });
954 }
955 Ok(())
956}
957
958fn validate_zero_u64(field: &'static str, value: u64) -> Result<(), NnrpError> {
959 if value != 0 {
960 return Err(NnrpError::NonZeroReservedField { field });
961 }
962 Ok(())
963}
964
965fn validate_mask_u8(value: u8, allowed: u8) -> Result<(), NnrpError> {
966 if value & !allowed != 0 {
967 return Err(NnrpError::ReservedBitsSet {
968 value: value as u64,
969 allowed: allowed as u64,
970 });
971 }
972 Ok(())
973}
974
975fn validate_mask_u16(value: u16, allowed: u16) -> Result<(), NnrpError> {
976 if value & !allowed != 0 {
977 return Err(NnrpError::ReservedBitsSet {
978 value: value as u64,
979 allowed: allowed as u64,
980 });
981 }
982 Ok(())
983}
984
985fn validate_mask_u32(value: u32, allowed: u32) -> Result<(), NnrpError> {
986 if value & !allowed != 0 {
987 return Err(NnrpError::ReservedBitsSet {
988 value: value as u64,
989 allowed: allowed as u64,
990 });
991 }
992 Ok(())
993}
994
995fn read_u16(source: &[u8], offset: usize) -> u16 {
996 u16::from_le_bytes(source[offset..offset + 2].try_into().expect("slice length"))
997}
998
999fn read_u32(source: &[u8], offset: usize) -> u32 {
1000 u32::from_le_bytes(source[offset..offset + 4].try_into().expect("slice length"))
1001}
1002
1003fn read_u64(source: &[u8], offset: usize) -> u64 {
1004 u64::from_le_bytes(source[offset..offset + 8].try_into().expect("slice length"))
1005}
1006
1007fn write_u16(destination: &mut [u8], offset: usize, value: u16) {
1008 destination[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
1009}
1010
1011fn write_u32(destination: &mut [u8], offset: usize, value: u32) {
1012 destination[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1013}
1014
1015fn write_u64(destination: &mut [u8], offset: usize, value: u64) {
1016 destination[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022
1023 #[test]
1024 fn frame_submit_metadata_round_trips_frozen_layout() {
1025 let metadata = FrameSubmitMetadata {
1026 src_width: 640,
1027 src_height: 360,
1028 tile_width: 32,
1029 tile_height: 32,
1030 tile_count: 84,
1031 section_count: 2,
1032 frame_class: 1,
1033 input_profile: InputProfile::DenseLumaFrame,
1034 tile_index_mode: TileIndexMode::DenseRange,
1035 latency_budget_ms: 100,
1036 target_fps_x100: 6000,
1037 retry_of_frame: 7,
1038 tile_base_id: 0,
1039 camera_bytes: 192,
1040 tile_index_bytes: 24,
1041 operation_id: 0x0102_0304_0506_0708,
1042 submit_mode: SubmitMode::Mixed,
1043 budget_policy: 0x05,
1044 loss_tolerance_policy: 0xff,
1045 object_ref_mask: 0x0000_0003,
1046 dependency_frame_id: 41,
1047 payload_kind_bitmap: PayloadKindBitmap(
1048 PayloadKindBitmap::TENSOR | PayloadKindBitmap::STRUCTURED_EVENT,
1049 ),
1050 payload_frame_count: 2,
1051 };
1052 let bytes = metadata.to_bytes().unwrap();
1053
1054 assert_eq!(bytes.len(), FRAME_SUBMIT_METADATA_LEN);
1055 assert_eq!(&bytes[32..36], &24u32.to_le_bytes());
1056 assert_eq!(&bytes[36..40], &[0; 4]);
1057 assert_eq!(&bytes[40..48], &0x0102_0304_0506_0708u64.to_le_bytes());
1058 assert_eq!(FrameSubmitMetadata::parse(&bytes).unwrap(), metadata);
1059 }
1060
1061 #[test]
1062 fn frame_submit_rejects_reserved_bytes_and_zero_operation_id() {
1063 let metadata = FrameSubmitMetadata {
1064 src_width: 0,
1065 src_height: 0,
1066 tile_width: 0,
1067 tile_height: 0,
1068 tile_count: 0,
1069 section_count: 0,
1070 frame_class: 0,
1071 input_profile: InputProfile::Unspecified,
1072 tile_index_mode: TileIndexMode::DenseRange,
1073 latency_budget_ms: 0,
1074 target_fps_x100: 0,
1075 retry_of_frame: 0,
1076 tile_base_id: 0,
1077 camera_bytes: 0,
1078 tile_index_bytes: 0,
1079 operation_id: 1,
1080 submit_mode: SubmitMode::Inline,
1081 budget_policy: 0,
1082 loss_tolerance_policy: 0,
1083 object_ref_mask: 0,
1084 dependency_frame_id: 0,
1085 payload_kind_bitmap: PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1086 payload_frame_count: 1,
1087 };
1088 let mut bytes = metadata.to_bytes().unwrap();
1089 bytes[36] = 1;
1090 assert_eq!(
1091 FrameSubmitMetadata::parse(&bytes),
1092 Err(NnrpError::NonZeroReservedField {
1093 field: "frame_submit.reserved1",
1094 })
1095 );
1096
1097 assert_eq!(
1098 FrameSubmitMetadata {
1099 operation_id: 0,
1100 ..metadata
1101 }
1102 .to_bytes(),
1103 Err(NnrpError::InvalidProtocolCombination {
1104 rule: "FRAME_SUBMIT operation_id must not be zero"
1105 })
1106 );
1107 }
1108
1109 #[test]
1110 fn frame_submit_rejects_unknown_budget_and_non_tensor_shape() {
1111 let mut bytes = [0u8; FRAME_SUBMIT_METADATA_LEN];
1112 bytes[53] = 0x80;
1113 write_u32(&mut bytes, 64, PayloadKindBitmap::TENSOR);
1114 assert_eq!(
1115 FrameSubmitMetadata::parse(&bytes),
1116 Err(NnrpError::ReservedBitsSet {
1117 value: 0x80,
1118 allowed: BUDGET_POLICY_KNOWN_MASK as u64
1119 })
1120 );
1121
1122 let metadata = FrameSubmitMetadata {
1123 src_width: 0,
1124 src_height: 0,
1125 tile_width: 0,
1126 tile_height: 0,
1127 tile_count: 1,
1128 section_count: 0,
1129 frame_class: 0,
1130 input_profile: InputProfile::Unspecified,
1131 tile_index_mode: TileIndexMode::DenseRange,
1132 latency_budget_ms: 0,
1133 target_fps_x100: 0,
1134 retry_of_frame: 0,
1135 tile_base_id: 0,
1136 camera_bytes: 0,
1137 tile_index_bytes: 0,
1138 operation_id: 1,
1139 submit_mode: SubmitMode::Inline,
1140 budget_policy: 0,
1141 loss_tolerance_policy: 0xff,
1142 object_ref_mask: 0,
1143 dependency_frame_id: 0,
1144 payload_kind_bitmap: PayloadKindBitmap(PayloadKindBitmap::STRUCTURED_EVENT),
1145 payload_frame_count: 1,
1146 };
1147 assert_eq!(
1148 metadata.to_bytes(),
1149 Err(NnrpError::InvalidProtocolCombination {
1150 rule: "non-tensor FRAME_SUBMIT must clear tensor tile fields"
1151 })
1152 );
1153 }
1154
1155 #[test]
1156 fn result_push_metadata_round_trips_frozen_layout() {
1157 let metadata = ResultPushMetadata {
1158 status_code: 0,
1159 result_flags: 0x0004,
1160 section_count: 1,
1161 tile_count: 84,
1162 active_profile_id: 2,
1163 inference_ms: 843,
1164 queue_ms: 2,
1165 server_total_ms: 846,
1166 tile_base_id: 0,
1167 tile_index_bytes: 16,
1168 result_class: ResultClass::Partial,
1169 applied_budget_policy: 0x01,
1170 reused_frame_id: 41,
1171 covered_tile_count: 53,
1172 dropped_tile_count: 31,
1173 payload_kind_bitmap: PayloadKindBitmap(
1174 PayloadKindBitmap::TENSOR | PayloadKindBitmap::TOKEN_CHUNK,
1175 ),
1176 payload_frame_count: 3,
1177 };
1178 let bytes = metadata.to_bytes().unwrap();
1179
1180 assert_eq!(bytes.len(), RESULT_PUSH_METADATA_LEN);
1181 assert_eq!(ResultPushMetadata::parse(&bytes).unwrap(), metadata);
1182 }
1183
1184 #[test]
1185 fn result_push_rejects_unknown_payload_bits_and_non_tensor_coverage() {
1186 let mut bytes = [0u8; RESULT_PUSH_METADATA_LEN];
1187 write_u32(&mut bytes, 56, 0x8000_0000);
1188 assert_eq!(
1189 ResultPushMetadata::parse(&bytes),
1190 Err(NnrpError::ReservedBitsSet {
1191 value: 0x8000_0000,
1192 allowed: PAYLOAD_KIND_KNOWN_MASK as u64
1193 })
1194 );
1195
1196 let metadata = ResultPushMetadata {
1197 status_code: 0,
1198 result_flags: 0,
1199 section_count: 0,
1200 tile_count: 0,
1201 active_profile_id: 0,
1202 inference_ms: 0,
1203 queue_ms: 0,
1204 server_total_ms: 0,
1205 tile_base_id: 0,
1206 tile_index_bytes: 0,
1207 result_class: ResultClass::Complete,
1208 applied_budget_policy: 0,
1209 reused_frame_id: 0,
1210 covered_tile_count: 1,
1211 dropped_tile_count: 0,
1212 payload_kind_bitmap: PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1213 payload_frame_count: 1,
1214 };
1215 assert_eq!(
1216 metadata.to_bytes(),
1217 Err(NnrpError::InvalidProtocolCombination {
1218 rule: "non-tensor RESULT_PUSH must clear tensor coverage fields"
1219 })
1220 );
1221 }
1222
1223 #[test]
1224 fn payload_family_boundary_keeps_tool_and_event_out_of_profile_space() {
1225 let bitmap = PayloadKindBitmap(
1226 PayloadKindBitmap::TOKEN_CHUNK
1227 | PayloadKindBitmap::STRUCTURED_EVENT
1228 | PayloadKindBitmap::TOOL_DELTA,
1229 );
1230
1231 assert!(bitmap.contains(PayloadFamily::TokenChunk));
1232 assert!(bitmap.contains(PayloadFamily::StructuredEvent));
1233 assert!(PayloadFamily::TokenChunk.is_standard_profile());
1234 assert!(!PayloadFamily::StructuredEvent.is_standard_profile());
1235 assert!(!PayloadFamily::ToolDelta.is_standard_profile());
1236 assert!(PayloadFamily::StructuredEvent.is_registry_bound_family());
1237 assert!(PayloadFamily::ToolDelta.is_registry_bound_family());
1238 assert_eq!(
1239 PayloadFamily::try_from_bit(PayloadKindBitmap::TOOL_DELTA),
1240 Ok(PayloadFamily::ToolDelta)
1241 );
1242 assert_eq!(
1243 PayloadFamily::try_from_bit(0x8000_0000),
1244 Err(NnrpError::UnknownEnumValue {
1245 enum_name: "payload_family_bit",
1246 value: 0x8000_0000
1247 })
1248 );
1249 }
1250
1251 #[test]
1252 fn body_region_prelude_and_object_reference_round_trip() {
1253 let prelude = BodyRegionPrelude {
1254 inline_object_bytes: 16,
1255 object_reference_bytes: OBJECT_REFERENCE_BLOCK_LEN as u32,
1256 typed_payload_descriptor_bytes: 24,
1257 typed_payload_frame_bytes: 64,
1258 extension_descriptor_bytes: 0,
1259 extension_payload_bytes: 0,
1260 };
1261 let prelude_bytes = prelude.to_bytes().unwrap();
1262
1263 assert_eq!(BodyRegionPrelude::parse(&prelude_bytes).unwrap(), prelude);
1264 assert_eq!(prelude.total_region_bytes().unwrap(), 128);
1265
1266 let object_ref = ObjectReferenceBlock {
1267 object_kind: CacheObjectKind::TileIndexBlock,
1268 ref_flags: 0,
1269 cache_namespace: 7,
1270 cache_key_hi: 0x1122_3344_5566_7788,
1271 cache_key_lo: 0x99aa_bbcc_ddee_ff00,
1272 };
1273 let object_ref_bytes = object_ref.to_bytes().unwrap();
1274
1275 assert_eq!(
1276 ObjectReferenceBlock::parse(&object_ref_bytes).unwrap(),
1277 object_ref
1278 );
1279
1280 let region = ObjectReferenceRegion::from_blocks(vec![object_ref]);
1281 let region_bytes = region.to_bytes().unwrap();
1282 let parsed_region = ObjectReferenceRegion::parse(®ion_bytes).unwrap();
1283 assert_eq!(parsed_region.blocks(), &[object_ref]);
1284 parsed_region
1285 .validate_submit_mask(SubmitMode::Mixed, 1 << 1)
1286 .unwrap();
1287 parsed_region
1288 .validate_resolved(|block| block.cache_namespace == 7)
1289 .unwrap();
1290 }
1291
1292 #[test]
1293 fn body_region_prelude_rejects_reserved_and_misaligned_reference_region() {
1294 let mut bytes = [0u8; BODY_REGION_PRELUDE_LEN];
1295 write_u32(&mut bytes, 24, 1);
1296 assert_eq!(
1297 BodyRegionPrelude::parse(&bytes),
1298 Err(NnrpError::NonZeroReservedField {
1299 field: "body_region_prelude.body_flags"
1300 })
1301 );
1302
1303 let prelude = BodyRegionPrelude {
1304 inline_object_bytes: 0,
1305 object_reference_bytes: 1,
1306 typed_payload_descriptor_bytes: 0,
1307 typed_payload_frame_bytes: 0,
1308 extension_descriptor_bytes: 0,
1309 extension_payload_bytes: 0,
1310 };
1311 assert_eq!(
1312 prelude.to_bytes(),
1313 Err(NnrpError::InvalidProtocolCombination {
1314 rule: "object_reference_bytes must be a multiple of object reference block length"
1315 })
1316 );
1317 }
1318
1319 #[test]
1320 fn object_reference_region_rejects_mask_order_duplicate_and_unresolved_cases() {
1321 assert_eq!(
1322 ObjectReferenceRegion::parse(&[0u8; OBJECT_REFERENCE_BLOCK_LEN - 1]),
1323 Err(NnrpError::InvalidProtocolCombination {
1324 rule: "object reference region length must be a multiple of object reference block length"
1325 })
1326 );
1327
1328 let camera = ObjectReferenceBlock {
1329 object_kind: CacheObjectKind::CameraBlock,
1330 ref_flags: 0,
1331 cache_namespace: 1,
1332 cache_key_hi: 1,
1333 cache_key_lo: 1,
1334 };
1335 let tile = ObjectReferenceBlock {
1336 object_kind: CacheObjectKind::TileIndexBlock,
1337 ref_flags: 0,
1338 cache_namespace: 1,
1339 cache_key_hi: 2,
1340 cache_key_lo: 2,
1341 };
1342
1343 assert_eq!(
1344 validate_submit_object_ref_mask(SubmitMode::Inline, 1),
1345 Err(NnrpError::InvalidProtocolCombination {
1346 rule: "inline FRAME_SUBMIT must not declare object_ref_mask"
1347 })
1348 );
1349 assert_eq!(
1350 validate_submit_object_ref_mask(SubmitMode::Reference, 0),
1351 Err(NnrpError::InvalidProtocolCombination {
1352 rule: "reference or mixed FRAME_SUBMIT requires non-zero object_ref_mask"
1353 })
1354 );
1355
1356 let reversed = ObjectReferenceRegion::from_blocks(vec![tile, camera]);
1357 assert_eq!(
1358 reversed.validate_submit_mask(SubmitMode::Mixed, 0b0011),
1359 Err(NnrpError::InvalidProtocolCombination {
1360 rule: "object reference region standard slots must be sorted"
1361 })
1362 );
1363
1364 let duplicate = ObjectReferenceRegion::from_blocks(vec![camera, camera]);
1365 assert_eq!(
1366 duplicate.validate_submit_mask(SubmitMode::Mixed, 0b0001),
1367 Err(NnrpError::InvalidProtocolCombination {
1368 rule: "object reference region must not duplicate standard slots"
1369 })
1370 );
1371
1372 let missing_mask_bit = ObjectReferenceRegion::from_blocks(vec![camera]);
1373 assert_eq!(
1374 missing_mask_bit.validate_submit_mask(SubmitMode::Mixed, 0b0010),
1375 Err(NnrpError::InvalidProtocolCombination {
1376 rule: "object reference block requires matching object_ref_mask bit"
1377 })
1378 );
1379 assert_eq!(
1380 missing_mask_bit.validate_submit_mask(SubmitMode::Mixed, 0b0011),
1381 Err(NnrpError::InvalidProtocolCombination {
1382 rule: "object reference region must exactly match object_ref_mask"
1383 })
1384 );
1385 assert_eq!(
1386 missing_mask_bit.validate_resolved(|_| false),
1387 Err(NnrpError::InvalidProtocolCombination {
1388 rule: "object reference must resolve from cache"
1389 })
1390 );
1391
1392 assert_eq!(missing_mask_bit.first_unresolved(|_| false), Some(camera));
1393 assert_eq!(
1394 missing_mask_bit.validate_resolved_or_cache_miss(|_| false, 10, 20, 30),
1395 Err(ErrorMetadata {
1396 error_code: CACHE_ERROR_MISS,
1397 error_scope: ErrorScope::Frame,
1398 is_fatal: false,
1399 retry_after_ms: 0,
1400 related_session_id: 10,
1401 related_frame_id: 20,
1402 related_view_id: 30,
1403 diagnostic_bytes: 0,
1404 })
1405 );
1406 assert!(missing_mask_bit
1407 .validate_resolved_or_cache_miss(|_| true, 10, 20, 30)
1408 .is_ok());
1409 }
1410
1411 #[test]
1412 fn typed_payload_region_packs_descriptors_and_projects_frames() {
1413 let first = TypedPayloadDescriptor {
1414 profile_id: STANDARD_PROFILE_TOKEN,
1415 descriptor_flags: 0x0002,
1416 schema_id: 0x0000_1001,
1417 schema_version: 3,
1418 stream_semantics: 2,
1419 offset: 0,
1420 length: 2,
1421 };
1422 let second = TypedPayloadDescriptor {
1423 profile_id: STANDARD_PROFILE_TOKEN,
1424 descriptor_flags: 0x0001,
1425 schema_id: 0x0000_1001,
1426 schema_version: 3,
1427 stream_semantics: 2,
1428 offset: 2,
1429 length: 3,
1430 };
1431 let payload = b"hello";
1432 let region = TypedPayloadRegion::from_parts(
1433 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1434 vec![first, second],
1435 payload,
1436 )
1437 .unwrap();
1438
1439 let descriptor_bytes = region.descriptor_region_bytes().unwrap();
1440 let parsed = TypedPayloadRegion::parse(
1441 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1442 2,
1443 &descriptor_bytes,
1444 payload,
1445 )
1446 .unwrap();
1447 let frames = parsed.frame_views().unwrap();
1448
1449 assert_eq!(parsed.descriptors(), &[first, second]);
1450 assert_eq!(frames[0].payload, b"he");
1451 assert_eq!(frames[1].payload, b"llo");
1452 }
1453
1454 #[test]
1455 fn typed_payload_region_rejects_bad_lengths_offsets_and_profiles() {
1456 let token = TypedPayloadDescriptor {
1457 profile_id: STANDARD_PROFILE_TOKEN,
1458 descriptor_flags: 0,
1459 schema_id: 0x0000_1001,
1460 schema_version: 3,
1461 stream_semantics: 2,
1462 offset: 1,
1463 length: 2,
1464 };
1465
1466 assert_eq!(
1467 TypedPayloadRegion::parse(
1468 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1469 1,
1470 &[],
1471 b""
1472 ),
1473 Err(NnrpError::InvalidProtocolCombination {
1474 rule: "typed payload descriptor region length must match payload_frame_count"
1475 })
1476 );
1477 assert_eq!(
1478 TypedPayloadRegion::parse(
1479 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1480 0,
1481 &[],
1482 b"x"
1483 ),
1484 Err(NnrpError::InvalidProtocolCombination {
1485 rule: "zero typed payload frames require empty descriptor and frame regions"
1486 })
1487 );
1488 assert_eq!(
1489 TypedPayloadRegion::from_parts(
1490 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1491 vec![token],
1492 b"abc"
1493 ),
1494 Err(NnrpError::InvalidProtocolCombination {
1495 rule: "typed payload descriptors must be packed in strictly contiguous order"
1496 })
1497 );
1498
1499 let too_long = TypedPayloadDescriptor { offset: 0, ..token };
1500 assert_eq!(
1501 TypedPayloadRegion::from_parts(
1502 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1503 vec![too_long],
1504 b"a"
1505 ),
1506 Err(NnrpError::InvalidProtocolCombination {
1507 rule: "typed payload descriptor range must fit the frame region"
1508 })
1509 );
1510
1511 let short_cover = TypedPayloadDescriptor {
1512 length: 1,
1513 ..too_long
1514 };
1515 assert_eq!(
1516 TypedPayloadRegion::from_parts(
1517 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1518 vec![short_cover],
1519 b"ab"
1520 ),
1521 Err(NnrpError::InvalidProtocolCombination {
1522 rule: "typed payload frame region must be exactly covered by descriptors"
1523 })
1524 );
1525
1526 let tensor_profile = TypedPayloadDescriptor {
1527 profile_id: STANDARD_PROFILE_TENSOR,
1528 offset: 0,
1529 length: 1,
1530 ..token
1531 };
1532 assert_eq!(
1533 TypedPayloadRegion::from_parts(
1534 PayloadKindBitmap(PayloadKindBitmap::STRUCTURED_EVENT),
1535 vec![tensor_profile],
1536 b"x"
1537 ),
1538 Err(NnrpError::InvalidProtocolCombination {
1539 rule: "non-tensor typed payload frames must not use tensor profile"
1540 })
1541 );
1542 assert_eq!(
1543 TypedPayloadRegion::from_parts(
1544 PayloadKindBitmap(PayloadKindBitmap::TOKEN_CHUNK),
1545 vec![TypedPayloadDescriptor {
1546 profile_id: STANDARD_PROFILE_UNSPECIFIED,
1547 ..tensor_profile
1548 }],
1549 b"x"
1550 ),
1551 Err(NnrpError::InvalidProtocolCombination {
1552 rule: "token-only typed payload frames require token profile"
1553 })
1554 );
1555 }
1556
1557 #[test]
1558 fn result_drop_is_header_only() {
1559 let header = CommonHeader::new(MessageType::ResultDrop, 0, 0);
1560 validate_result_drop_header(&header).unwrap();
1561
1562 let bad = CommonHeader::new(MessageType::ResultDrop, 1, 0);
1563 assert_eq!(
1564 validate_result_drop_header(&bad),
1565 Err(NnrpError::InvalidProtocolCombination {
1566 rule: "RESULT_DROP is header-only and requires meta_len=0 and body_len=0"
1567 })
1568 );
1569 }
1570}