Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

up_rust/bench_fixtures/
payload_contract.rs

1/********************************************************************************
2 * Copyright (c) 2026 Contributors to the Eclipse Foundation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 ********************************************************************************/
6
7// Fixture code intentionally uses fixed-layout protocol structs, deterministic
8// sample indexing, and validators that mirror payload fields one-to-one.
9#![allow(clippy::indexing_slicing, clippy::too_many_arguments)]
10
11use std::{fmt::Debug, mem};
12
13use ::protobuf::{Message, MessageField};
14use serde::Serialize;
15use sha2::{Digest, Sha256};
16
17use crate::{
18    payload::{
19        stable::{
20            InitializedStablePayload, StableContainerPayload, StablePayload, StablePayloadInit,
21            StablePayloadInitContext,
22        },
23        UWireError,
24    },
25    zero_copy::{
26        LoanedPayloadUninitMut, PayloadLoanProvenance, UTxBuffer, UUninitTxBuffer,
27        UVecUninitTxBuffer,
28    },
29    PayloadEncoding, UFrameMetadata, UUri,
30};
31
32pub mod proto;
33pub mod stable;
34
35pub use stable::*;
36
37const SUITE_FAMILY: &str = "payload-contract-representative";
38const CAN_CLASSIC_ID: u32 = 0x18ff_50e5;
39const CAN_FD_ID: u32 = 0x18ff_51e5;
40const CAN_INTERFACE_ID: u32 = 2;
41const SOMEIP_SERVICE_ID: u16 = 0x1234;
42const SOMEIP_METHOD_OR_EVENT_ID: u16 = 0x8001;
43const SOMEIP_CLIENT_ID: u16 = 0x0042;
44const SOMEIP_SESSION_ID: u16 = 0x1001;
45const SOMEIP_PROTOCOL_VERSION: u8 = 1;
46const SOMEIP_INTERFACE_VERSION: u8 = 3;
47const SOMEIP_MESSAGE_TYPE: u8 = 2;
48const SOMEIP_RETURN_CODE: u8 = 0;
49const STREAM_ID: u64 = 0x5354_5245_414d_0001;
50const STREAM_CODEC_RAW: u32 = 1;
51const STREAM_FLAG_KEY_FRAME: u32 = 1;
52const RADAR_SENSOR_ID: u32 = 548;
53#[cfg(feature = "payload-contract-large-fixtures")]
54const LIDAR_ID: u32 = 128;
55#[cfg(feature = "payload-contract-large-fixtures")]
56const CAMERA_ID: u32 = 8;
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
59pub enum PayloadContractCaseKind {
60    CanClassicMax,
61    CanFdMax,
62    SomeIpSingleMtu,
63    Streamer4k,
64    RadarArs548DetectionList,
65    Streamer64k,
66    #[cfg(feature = "payload-contract-large-fixtures")]
67    LidarHesaiAt128PointCloud,
68    #[cfg(feature = "payload-contract-large-fixtures")]
69    Camera8mpBayerRggb12p,
70    #[cfg(feature = "payload-contract-simulator-fixtures")]
71    Camera8mpCarlaBgra32,
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75pub struct PayloadContractCase {
76    case_id: u32,
77    name: &'static str,
78    kind: PayloadContractCaseKind,
79    semantic_reference_len: usize,
80    external_wire_reference_len: Option<usize>,
81}
82
83impl PayloadContractCase {
84    pub const fn new(
85        case_id: u32,
86        name: &'static str,
87        kind: PayloadContractCaseKind,
88        semantic_reference_len: usize,
89        external_wire_reference_len: Option<usize>,
90    ) -> Self {
91        Self {
92            case_id,
93            name,
94            kind,
95            semantic_reference_len,
96            external_wire_reference_len,
97        }
98    }
99
100    pub const fn case_id(&self) -> u32 {
101        self.case_id
102    }
103
104    pub const fn name(&self) -> &'static str {
105        self.name
106    }
107
108    pub const fn kind(&self) -> PayloadContractCaseKind {
109        self.kind
110    }
111
112    pub const fn semantic_reference_len(&self) -> usize {
113        self.semantic_reference_len
114    }
115
116    pub const fn external_wire_reference_len(&self) -> Option<usize> {
117        self.external_wire_reference_len
118    }
119}
120
121const CORE_CASES: &[PayloadContractCase] = &[
122    PayloadContractCase::new(
123        1,
124        "can_classic_max",
125        PayloadContractCaseKind::CanClassicMax,
126        8,
127        None,
128    ),
129    PayloadContractCase::new(2, "can_fd_max", PayloadContractCaseKind::CanFdMax, 64, None),
130    PayloadContractCase::new(
131        3,
132        "someip_single_mtu",
133        PayloadContractCaseKind::SomeIpSingleMtu,
134        SOMEIP_IPV4_UDP_SINGLE_MTU_BUDGET,
135        Some(SOMEIP_IPV4_UDP_SINGLE_MTU_BUDGET),
136    ),
137    PayloadContractCase::new(
138        4,
139        "streamer_4k",
140        PayloadContractCaseKind::Streamer4k,
141        4 * 1_024,
142        None,
143    ),
144    PayloadContractCase::new(
145        5,
146        "radar_ars548_detection_list",
147        PayloadContractCaseKind::RadarArs548DetectionList,
148        RADAR_ARS548_DETECTION_MESSAGE_BYTES,
149        Some(RADAR_ARS548_DETECTION_MESSAGE_BYTES),
150    ),
151    PayloadContractCase::new(
152        6,
153        "streamer_64k",
154        PayloadContractCaseKind::Streamer64k,
155        64 * 1_024,
156        None,
157    ),
158];
159
160#[cfg(feature = "payload-contract-large-fixtures")]
161const LARGE_SENSOR_CASES: &[PayloadContractCase] = &[
162    PayloadContractCase::new(
163        7,
164        "lidar_hesai_at128_1200x128_xyzircaedt",
165        PayloadContractCaseKind::LidarHesaiAt128PointCloud,
166        LIDAR_HESAI_AT128_POINTS_BYTES,
167        None,
168    ),
169    PayloadContractCase::new(
170        8,
171        "camera_8mp_3840x2160_bayer_rggb12p",
172        PayloadContractCaseKind::Camera8mpBayerRggb12p,
173        CAMERA_BAYER_RGGB12P_BYTES,
174        None,
175    ),
176];
177
178#[cfg(not(feature = "payload-contract-large-fixtures"))]
179const LARGE_SENSOR_CASES: &[PayloadContractCase] = &[];
180
181const SIMULATOR_NATIVE_CASES: &[PayloadContractCase] = &[];
182
183pub fn core_cases() -> &'static [PayloadContractCase] {
184    CORE_CASES
185}
186
187pub fn large_sensor_cases() -> &'static [PayloadContractCase] {
188    LARGE_SENSOR_CASES
189}
190
191pub fn simulator_native_cases() -> &'static [PayloadContractCase] {
192    SIMULATOR_NATIVE_CASES
193}
194
195pub fn all_cases() -> impl Iterator<Item = &'static PayloadContractCase> {
196    core_cases()
197        .iter()
198        .chain(large_sensor_cases())
199        .chain(simulator_native_cases())
200}
201
202pub fn case_by_kind(kind: PayloadContractCaseKind) -> &'static PayloadContractCase {
203    all_cases()
204        .find(|case| case.kind == kind)
205        .expect("payload contract case kind must be enabled")
206}
207
208pub fn stable_payload_len(case: &PayloadContractCase) -> usize {
209    match case.kind {
210        PayloadContractCaseKind::CanClassicMax => mem::size_of::<CanClassicFrameV1>(),
211        PayloadContractCaseKind::CanFdMax => mem::size_of::<CanFdFrameV1>(),
212        PayloadContractCaseKind::SomeIpSingleMtu => mem::size_of::<SomeIpSignalBatchMtuV1>(),
213        PayloadContractCaseKind::Streamer4k => mem::size_of::<StreamChunk4kV1>(),
214        PayloadContractCaseKind::RadarArs548DetectionList => {
215            mem::size_of::<RadarDetectionListArs548V1>()
216        }
217        PayloadContractCaseKind::Streamer64k => mem::size_of::<StreamChunk64kV1>(),
218        #[cfg(feature = "payload-contract-large-fixtures")]
219        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
220            mem::size_of::<LidarPointCloudHesaiAt128V1>()
221        }
222        #[cfg(feature = "payload-contract-large-fixtures")]
223        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
224            mem::size_of::<CameraBayerRggb12pFrame8mpV1>()
225        }
226        #[cfg(feature = "payload-contract-simulator-fixtures")]
227        PayloadContractCaseKind::Camera8mpCarlaBgra32 => 0,
228    }
229}
230
231pub fn stable_payload_type_name(case: &PayloadContractCase) -> &'static str {
232    match case.kind {
233        PayloadContractCaseKind::CanClassicMax => CanClassicFrameV1::stable_type_name(),
234        PayloadContractCaseKind::CanFdMax => CanFdFrameV1::stable_type_name(),
235        PayloadContractCaseKind::SomeIpSingleMtu => SomeIpSignalBatchMtuV1::stable_type_name(),
236        PayloadContractCaseKind::Streamer4k => StreamChunk4kV1::stable_type_name(),
237        PayloadContractCaseKind::RadarArs548DetectionList => {
238            RadarDetectionListArs548V1::stable_type_name()
239        }
240        PayloadContractCaseKind::Streamer64k => StreamChunk64kV1::stable_type_name(),
241        #[cfg(feature = "payload-contract-large-fixtures")]
242        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
243            LidarPointCloudHesaiAt128V1::stable_type_name()
244        }
245        #[cfg(feature = "payload-contract-large-fixtures")]
246        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
247            CameraBayerRggb12pFrame8mpV1::stable_type_name()
248        }
249        #[cfg(feature = "payload-contract-simulator-fixtures")]
250        PayloadContractCaseKind::Camera8mpCarlaBgra32 => {
251            "org.eclipse.uprotocol.bench.v1.CameraCarlaBgra32Frame8mpV1"
252        }
253    }
254}
255
256pub fn stable_payload_align(case: &PayloadContractCase) -> usize {
257    match case.kind {
258        PayloadContractCaseKind::CanClassicMax => mem::align_of::<CanClassicFrameV1>(),
259        PayloadContractCaseKind::CanFdMax => mem::align_of::<CanFdFrameV1>(),
260        PayloadContractCaseKind::SomeIpSingleMtu => mem::align_of::<SomeIpSignalBatchMtuV1>(),
261        PayloadContractCaseKind::Streamer4k => mem::align_of::<StreamChunk4kV1>(),
262        PayloadContractCaseKind::RadarArs548DetectionList => {
263            mem::align_of::<RadarDetectionListArs548V1>()
264        }
265        PayloadContractCaseKind::Streamer64k => mem::align_of::<StreamChunk64kV1>(),
266        #[cfg(feature = "payload-contract-large-fixtures")]
267        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
268            mem::align_of::<LidarPointCloudHesaiAt128V1>()
269        }
270        #[cfg(feature = "payload-contract-large-fixtures")]
271        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
272            mem::align_of::<CameraBayerRggb12pFrame8mpV1>()
273        }
274        #[cfg(feature = "payload-contract-simulator-fixtures")]
275        PayloadContractCaseKind::Camera8mpCarlaBgra32 => 1,
276    }
277}
278
279#[derive(Serialize)]
280pub struct PayloadContractCaseManifest {
281    pub case_id: u32,
282    pub name: &'static str,
283    pub kind: PayloadContractCaseKind,
284    pub semantic_reference_len: usize,
285    pub external_wire_reference_len: Option<usize>,
286    pub protobuf_message: &'static str,
287    pub protobuf_transport_len: usize,
288    pub stable_type_name: &'static str,
289    pub stable_transport_len: usize,
290    pub stable_align: usize,
291}
292
293#[derive(Serialize)]
294pub struct PayloadContractFixtureManifest {
295    pub suite_family: &'static str,
296    pub fixture_version: u32,
297    pub cases: Vec<PayloadContractCaseManifest>,
298}
299
300pub fn fixture_manifest() -> PayloadContractFixtureManifest {
301    let mut cases = all_cases()
302        .map(|case| PayloadContractCaseManifest {
303            case_id: case.case_id,
304            name: case.name,
305            kind: case.kind,
306            semantic_reference_len: case.semantic_reference_len,
307            external_wire_reference_len: case.external_wire_reference_len,
308            protobuf_message: protobuf_message_name(case),
309            protobuf_transport_len: protobuf_encoded_len(case, 1),
310            stable_type_name: stable_payload_type_name(case),
311            stable_transport_len: stable_payload_len(case),
312            stable_align: stable_payload_align(case),
313        })
314        .collect::<Vec<_>>();
315    cases.sort_by_key(|case| case.case_id);
316    PayloadContractFixtureManifest {
317        suite_family: SUITE_FAMILY,
318        fixture_version: PAYLOAD_CONTRACT_FIXTURE_VERSION,
319        cases,
320    }
321}
322
323pub fn fixture_manifest_json_canonical() -> String {
324    serde_json::to_string(&fixture_manifest()).expect("fixture manifest serialization succeeds")
325}
326
327pub fn fixture_manifest_sha256_hex() -> String {
328    let hash = Sha256::digest(fixture_manifest_json_canonical().as_bytes());
329    let mut text = String::with_capacity(hash.len() * 2);
330    for byte in hash {
331        use std::fmt::Write as _;
332        write!(&mut text, "{byte:02x}").expect("write to String cannot fail");
333    }
334    text
335}
336
337pub enum ProtobufFixture {
338    CanClassicMax(proto::CanClassicFrame),
339    CanFdMax(proto::CanFdFrame),
340    SomeIpSingleMtu(proto::SomeIpSignalBatch),
341    Streamer4k(proto::StreamChunk),
342    RadarArs548DetectionList(proto::Ars548DetectionList),
343    Streamer64k(proto::StreamChunk),
344    #[cfg(feature = "payload-contract-large-fixtures")]
345    LidarHesaiAt128PointCloud(proto::LidarPointCloudFrame),
346    #[cfg(feature = "payload-contract-large-fixtures")]
347    Camera8mpBayerRggb12p(proto::CameraBayerFrame),
348    #[cfg(feature = "payload-contract-simulator-fixtures")]
349    Camera8mpCarlaBgra32(proto::CameraCarlaBgraFrame),
350}
351
352pub fn protobuf_fixture_for(case: &PayloadContractCase, sequence: u32) -> ProtobufFixture {
353    match case.kind {
354        PayloadContractCaseKind::CanClassicMax => {
355            ProtobufFixture::CanClassicMax(build_proto_can_classic(case, sequence))
356        }
357        PayloadContractCaseKind::CanFdMax => {
358            ProtobufFixture::CanFdMax(build_proto_can_fd(case, sequence))
359        }
360        PayloadContractCaseKind::SomeIpSingleMtu => {
361            ProtobufFixture::SomeIpSingleMtu(build_proto_someip(case, sequence))
362        }
363        PayloadContractCaseKind::Streamer4k => {
364            ProtobufFixture::Streamer4k(build_proto_stream(case, sequence, 4 * 1_024))
365        }
366        PayloadContractCaseKind::RadarArs548DetectionList => {
367            ProtobufFixture::RadarArs548DetectionList(build_proto_radar(case, sequence))
368        }
369        PayloadContractCaseKind::Streamer64k => {
370            ProtobufFixture::Streamer64k(build_proto_stream(case, sequence, 64 * 1_024))
371        }
372        #[cfg(feature = "payload-contract-large-fixtures")]
373        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
374            ProtobufFixture::LidarHesaiAt128PointCloud(build_proto_lidar(case, sequence))
375        }
376        #[cfg(feature = "payload-contract-large-fixtures")]
377        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
378            ProtobufFixture::Camera8mpBayerRggb12p(build_proto_camera_bayer(case, sequence))
379        }
380        #[cfg(feature = "payload-contract-simulator-fixtures")]
381        PayloadContractCaseKind::Camera8mpCarlaBgra32 => {
382            ProtobufFixture::Camera8mpCarlaBgra32(proto::CameraCarlaBgraFrame::new())
383        }
384    }
385}
386
387pub fn protobuf_encoded_len(case: &PayloadContractCase, sequence: u32) -> usize {
388    match protobuf_fixture_for(case, sequence) {
389        ProtobufFixture::CanClassicMax(value) => value.compute_size() as usize,
390        ProtobufFixture::CanFdMax(value) => value.compute_size() as usize,
391        ProtobufFixture::SomeIpSingleMtu(value) => value.compute_size() as usize,
392        ProtobufFixture::Streamer4k(value) | ProtobufFixture::Streamer64k(value) => {
393            value.compute_size() as usize
394        }
395        ProtobufFixture::RadarArs548DetectionList(value) => value.compute_size() as usize,
396        #[cfg(feature = "payload-contract-large-fixtures")]
397        ProtobufFixture::LidarHesaiAt128PointCloud(value) => value.compute_size() as usize,
398        #[cfg(feature = "payload-contract-large-fixtures")]
399        ProtobufFixture::Camera8mpBayerRggb12p(value) => value.compute_size() as usize,
400        #[cfg(feature = "payload-contract-simulator-fixtures")]
401        ProtobufFixture::Camera8mpCarlaBgra32(value) => value.compute_size() as usize,
402    }
403}
404
405pub fn protobuf_encoded_bytes_for(
406    case: &PayloadContractCase,
407    sequence: u32,
408) -> Result<Vec<u8>, UWireError> {
409    let fixture = protobuf_fixture_for(case, sequence);
410    let bytes = match fixture {
411        ProtobufFixture::CanClassicMax(value) => value.write_to_bytes(),
412        ProtobufFixture::CanFdMax(value) => value.write_to_bytes(),
413        ProtobufFixture::SomeIpSingleMtu(value) => value.write_to_bytes(),
414        ProtobufFixture::Streamer4k(value) | ProtobufFixture::Streamer64k(value) => {
415            value.write_to_bytes()
416        }
417        ProtobufFixture::RadarArs548DetectionList(value) => value.write_to_bytes(),
418        #[cfg(feature = "payload-contract-large-fixtures")]
419        ProtobufFixture::LidarHesaiAt128PointCloud(value) => value.write_to_bytes(),
420        #[cfg(feature = "payload-contract-large-fixtures")]
421        ProtobufFixture::Camera8mpBayerRggb12p(value) => value.write_to_bytes(),
422        #[cfg(feature = "payload-contract-simulator-fixtures")]
423        ProtobufFixture::Camera8mpCarlaBgra32(value) => value.write_to_bytes(),
424    };
425    bytes.map_err(|error| UWireError::serialization_error(error.to_string()))
426}
427
428pub fn validate_protobuf_bytes(
429    case: &PayloadContractCase,
430    sequence: u32,
431    bytes: &[u8],
432) -> Result<(), UWireError> {
433    match case.kind {
434        PayloadContractCaseKind::CanClassicMax => {
435            let value = proto::CanClassicFrame::parse_from_bytes(bytes)
436                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
437            validate_proto_can_classic(case, sequence, &value)
438        }
439        PayloadContractCaseKind::CanFdMax => {
440            let value = proto::CanFdFrame::parse_from_bytes(bytes)
441                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
442            validate_proto_can_fd(case, sequence, &value)
443        }
444        PayloadContractCaseKind::SomeIpSingleMtu => {
445            let value = proto::SomeIpSignalBatch::parse_from_bytes(bytes)
446                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
447            validate_proto_someip(case, sequence, &value)
448        }
449        PayloadContractCaseKind::Streamer4k | PayloadContractCaseKind::Streamer64k => {
450            let value = proto::StreamChunk::parse_from_bytes(bytes)
451                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
452            validate_proto_stream(case, sequence, &value)
453        }
454        PayloadContractCaseKind::RadarArs548DetectionList => {
455            let value = proto::Ars548DetectionList::parse_from_bytes(bytes)
456                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
457            validate_proto_radar(case, sequence, &value)
458        }
459        #[cfg(feature = "payload-contract-large-fixtures")]
460        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
461            let value = proto::LidarPointCloudFrame::parse_from_bytes(bytes)
462                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
463            validate_proto_lidar(case, sequence, &value)
464        }
465        #[cfg(feature = "payload-contract-large-fixtures")]
466        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
467            let value = proto::CameraBayerFrame::parse_from_bytes(bytes)
468                .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
469            validate_proto_camera_bayer(case, sequence, &value)
470        }
471        #[cfg(feature = "payload-contract-simulator-fixtures")]
472        PayloadContractCaseKind::Camera8mpCarlaBgra32 => Ok(()),
473    }
474}
475
476pub trait StablePayloadContractView: StablePayload {
477    fn case_kind() -> PayloadContractCaseKind;
478    fn validate_contract(
479        &self,
480        case: &PayloadContractCase,
481        sequence: u32,
482    ) -> Result<(), UWireError>;
483}
484
485pub fn validate_stable_payload<T: StablePayloadContractView>(
486    case: &PayloadContractCase,
487    sequence: u32,
488    payload: &T,
489) -> Result<(), UWireError> {
490    ensure_eq("stable case kind", T::case_kind(), case.kind)?;
491    payload.validate_contract(case, sequence)
492}
493
494pub struct StableOwnedFixture {
495    pub encoding: PayloadEncoding,
496    pub bytes: Vec<u8>,
497    pub stable_type_name: &'static str,
498    pub stable_transport_len: usize,
499    pub stable_align: usize,
500}
501
502pub fn stable_owned_fixture_for(
503    case: &PayloadContractCase,
504    sequence: u32,
505) -> Result<StableOwnedFixture, UWireError> {
506    match case.kind {
507        PayloadContractCaseKind::CanClassicMax => {
508            stable_owned_fixture_for_type::<CanClassicFrameV1>(|init, _| {
509                init_can_classic_max(init, sequence)
510            })
511        }
512        PayloadContractCaseKind::CanFdMax => {
513            stable_owned_fixture_for_type::<CanFdFrameV1>(|init, _| init_can_fd_max(init, sequence))
514        }
515        PayloadContractCaseKind::SomeIpSingleMtu => {
516            stable_owned_fixture_for_type::<SomeIpSignalBatchMtuV1>(|init, _| {
517                init_someip_single_mtu(init, sequence)
518            })
519        }
520        PayloadContractCaseKind::Streamer4k => {
521            stable_owned_fixture_for_type::<StreamChunk4kV1>(|init, _| {
522                init_streamer_4k(init, sequence)
523            })
524        }
525        PayloadContractCaseKind::RadarArs548DetectionList => {
526            stable_owned_fixture_for_type::<RadarDetectionListArs548V1>(|init, _| {
527                init_radar_ars548_detection_list(init, sequence)
528            })
529        }
530        PayloadContractCaseKind::Streamer64k => {
531            stable_owned_fixture_for_type::<StreamChunk64kV1>(|init, _| {
532                init_streamer_64k(init, sequence)
533            })
534        }
535        #[cfg(feature = "payload-contract-large-fixtures")]
536        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
537            stable_owned_fixture_for_type::<LidarPointCloudHesaiAt128V1>(|init, _| {
538                init_lidar_hesai_at128_point_cloud(init, sequence)
539            })
540        }
541        #[cfg(feature = "payload-contract-large-fixtures")]
542        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
543            stable_owned_fixture_for_type::<CameraBayerRggb12pFrame8mpV1>(|init, _| {
544                init_camera_8mp_bayer_rggb12p(init, sequence)
545            })
546        }
547        #[cfg(feature = "payload-contract-simulator-fixtures")]
548        PayloadContractCaseKind::Camera8mpCarlaBgra32 => Err(UWireError::invalid_payload(
549            "CARLA BGRA32 stable fixture is not implemented in representative v1",
550        )),
551    }
552}
553
554pub fn validate_stable_owned_bytes(
555    case: &PayloadContractCase,
556    sequence: u32,
557    encoding: Option<&PayloadEncoding>,
558    bytes: &[u8],
559) -> Result<(), UWireError> {
560    validate_stable_encoding_for_case(case, encoding)?;
561    ensure_eq("stable owned length", bytes.len(), stable_payload_len(case))?;
562    validate_stable_owned_samples(case, sequence, bytes)
563}
564
565pub fn validate_stable_owned_bytes_exact(
566    case: &PayloadContractCase,
567    sequence: u32,
568    encoding: Option<&PayloadEncoding>,
569    bytes: &[u8],
570) -> Result<(), UWireError> {
571    validate_stable_owned_bytes(case, sequence, encoding, bytes)?;
572    let expected = stable_owned_fixture_for(case, sequence)?;
573    if bytes != expected.bytes.as_slice() {
574        return Err(UWireError::invalid_payload(format!(
575            "stable owned bytes for {} do not match canonical fixture",
576            case.name
577        )));
578    }
579    Ok(())
580}
581
582pub fn init_can_classic_max<'a>(
583    init: CanClassicFrameV1Init<'a>,
584    sequence: u32,
585) -> Result<InitializedStablePayload<'a, CanClassicFrameV1>, UWireError> {
586    let case = case_by_kind(PayloadContractCaseKind::CanClassicMax);
587    init.header(|header| init_header(header, case, sequence))?
588        .interface_id(CAN_INTERFACE_ID)
589        .can_id(CAN_CLASSIC_ID)
590        .flags(CAN_FLAG_EFF)
591        .reserved0(0)
592        .bus_timestamp_ns(timestamp_ns(sequence))
593        .len(8)
594        .len8_dlc(8)
595        .data_fill_with(|index| pattern_byte(case.case_id, sequence, index))
596        .reserved1([0; 2])
597        .checksum(fixture_checksum(case, sequence))
598        .finish()
599}
600
601pub fn init_can_fd_max<'a>(
602    init: CanFdFrameV1Init<'a>,
603    sequence: u32,
604) -> Result<InitializedStablePayload<'a, CanFdFrameV1>, UWireError> {
605    let case = case_by_kind(PayloadContractCaseKind::CanFdMax);
606    init.header(|header| init_header(header, case, sequence))?
607        .interface_id(CAN_INTERFACE_ID)
608        .can_id(CAN_FD_ID)
609        .flags(CAN_FLAG_EFF | CAN_FD_FLAG_BRS | CAN_FD_FLAG_ESI)
610        .reserved0(0)
611        .bus_timestamp_ns(timestamp_ns(sequence))
612        .len(64)
613        .data_fill_with(|index| pattern_byte(case.case_id, sequence, index))
614        .reserved1([0; 3])
615        .checksum(fixture_checksum(case, sequence))
616        .finish()
617}
618
619pub fn init_someip_single_mtu<'a>(
620    init: SomeIpSignalBatchMtuV1Init<'a>,
621    sequence: u32,
622) -> Result<InitializedStablePayload<'a, SomeIpSignalBatchMtuV1>, UWireError> {
623    let case = case_by_kind(PayloadContractCaseKind::SomeIpSingleMtu);
624    init.header(|header| init_header(header, case, sequence))?
625        .source_timestamp_ns(timestamp_ns(sequence))
626        .length(SOMEIP_IPV4_UDP_SINGLE_MTU_BUDGET as u32)
627        .sample_count(SOMEIP_MTU_SIGNAL_SAMPLE_COUNT as u32)
628        .service_id(SOMEIP_SERVICE_ID)
629        .method_or_event_id(SOMEIP_METHOD_OR_EVENT_ID)
630        .client_id(SOMEIP_CLIENT_ID)
631        .session_id(SOMEIP_SESSION_ID)
632        .protocol_version(SOMEIP_PROTOCOL_VERSION)
633        .interface_version(SOMEIP_INTERFACE_VERSION)
634        .message_type(SOMEIP_MESSAGE_TYPE)
635        .return_code(SOMEIP_RETURN_CODE)
636        .reserved0(0)
637        .samples(|index, sample| init_signal_sample(sample, sequence, index))?
638        .checksum(fixture_checksum(case, sequence))
639        .reserved1(0)
640        .finish()
641}
642
643pub fn init_streamer_4k<'a>(
644    init: StreamChunk4kV1Init<'a>,
645    sequence: u32,
646) -> Result<InitializedStablePayload<'a, StreamChunk4kV1>, UWireError> {
647    let case = case_by_kind(PayloadContractCaseKind::Streamer4k);
648    init.meta(|meta| init_stream_meta(meta, case, sequence, 4 * 1_024))?
649        .chunk_fill_with(|index| pattern_byte(case.case_id, sequence, index))
650        .finish()
651}
652
653pub fn init_radar_ars548_detection_list<'a>(
654    init: RadarDetectionListArs548V1Init<'a>,
655    sequence: u32,
656) -> Result<InitializedStablePayload<'a, RadarDetectionListArs548V1>, UWireError> {
657    let case = case_by_kind(PayloadContractCaseKind::RadarArs548DetectionList);
658    init.header(|header| init_header(header, case, sequence))?
659        .measurement_timestamp_ns(timestamp_ns(sequence))
660        .sensor_id(RADAR_SENSOR_ID)
661        .measurement_counter(sequence)
662        .cycle_counter(sequence.wrapping_mul(3))
663        .list_numofdetections(RADAR_ARS548_MAX_DETECTIONS as u32)
664        .detections(|index, detection| init_radar_detection(detection, sequence, index))?
665        .checksum(fixture_checksum(case, sequence))
666        .reserved0(0)
667        .finish()
668}
669
670pub fn init_streamer_64k<'a>(
671    init: StreamChunk64kV1Init<'a>,
672    sequence: u32,
673) -> Result<InitializedStablePayload<'a, StreamChunk64kV1>, UWireError> {
674    let case = case_by_kind(PayloadContractCaseKind::Streamer64k);
675    init.meta(|meta| init_stream_meta(meta, case, sequence, 64 * 1_024))?
676        .chunk_fill_with(|index| pattern_byte(case.case_id, sequence, index))
677        .finish()
678}
679
680#[cfg(feature = "payload-contract-large-fixtures")]
681pub fn init_lidar_hesai_at128_point_cloud<'a>(
682    init: LidarPointCloudHesaiAt128V1Init<'a>,
683    sequence: u32,
684) -> Result<InitializedStablePayload<'a, LidarPointCloudHesaiAt128V1>, UWireError> {
685    let case = case_by_kind(PayloadContractCaseKind::LidarHesaiAt128PointCloud);
686    let cache = LidarPointTrigCache::new();
687    init.header(|header| init_header(header, case, sequence))?
688        .frame_counter(sequence as u64)
689        .sensor_timestamp_ns(timestamp_ns(sequence))
690        .scan_duration_ns(LIDAR_HESAI_AT128_SCAN_DURATION_NS)
691        .lidar_id(LIDAR_ID)
692        .width(LIDAR_HESAI_AT128_WIDTH)
693        .height(LIDAR_HESAI_AT128_HEIGHT)
694        .point_count(LIDAR_HESAI_AT128_POINT_COUNT as u32)
695        .point_step(LIDAR_XYZIRCAEDT_POINT_BYTES as u32)
696        .row_step(LIDAR_HESAI_AT128_ROW_STEP_BYTES)
697        .points_per_second(LIDAR_HESAI_AT128_POINTS_PER_SECOND)
698        .horizontal_fov_mdeg(LIDAR_HESAI_AT128_HORIZONTAL_FOV_MDEG)
699        .vertical_fov_mdeg(LIDAR_HESAI_AT128_VERTICAL_FOV_MDEG)
700        .horizontal_resolution_mdeg(LIDAR_HESAI_AT128_HORIZONTAL_RESOLUTION_MDEG)
701        .vertical_resolution_mdeg(LIDAR_HESAI_AT128_VERTICAL_RESOLUTION_MDEG)
702        .range_10pct_reflectivity_m(LIDAR_HESAI_AT128_RANGE_10PCT_REFLECTIVITY_M)
703        .point_format(LIDAR_POINT_FORMAT_XYZIRCAEDT)
704        .is_bigendian(0)
705        .is_dense(1)
706        .reserved0([0; 2])
707        .checksum(fixture_checksum(case, sequence))
708        .extrinsics(|pose| init_pose(pose, sequence))?
709        .points(|index, point| init_lidar_point_cached(point, sequence, index, &cache))?
710        .reserved1(0)
711        .finish()
712}
713
714#[cfg(all(
715    feature = "perf-diagnostics",
716    feature = "payload-contract-large-fixtures"
717))]
718#[doc(hidden)]
719pub fn init_lidar_hesai_at128_point_cloud_per_point_trig<'a>(
720    init: LidarPointCloudHesaiAt128V1Init<'a>,
721    sequence: u32,
722) -> Result<InitializedStablePayload<'a, LidarPointCloudHesaiAt128V1>, UWireError> {
723    let case = case_by_kind(PayloadContractCaseKind::LidarHesaiAt128PointCloud);
724    init.header(|header| init_header(header, case, sequence))?
725        .frame_counter(sequence as u64)
726        .sensor_timestamp_ns(timestamp_ns(sequence))
727        .scan_duration_ns(LIDAR_HESAI_AT128_SCAN_DURATION_NS)
728        .lidar_id(LIDAR_ID)
729        .width(LIDAR_HESAI_AT128_WIDTH)
730        .height(LIDAR_HESAI_AT128_HEIGHT)
731        .point_count(LIDAR_HESAI_AT128_POINT_COUNT as u32)
732        .point_step(LIDAR_XYZIRCAEDT_POINT_BYTES as u32)
733        .row_step(LIDAR_HESAI_AT128_ROW_STEP_BYTES)
734        .points_per_second(LIDAR_HESAI_AT128_POINTS_PER_SECOND)
735        .horizontal_fov_mdeg(LIDAR_HESAI_AT128_HORIZONTAL_FOV_MDEG)
736        .vertical_fov_mdeg(LIDAR_HESAI_AT128_VERTICAL_FOV_MDEG)
737        .horizontal_resolution_mdeg(LIDAR_HESAI_AT128_HORIZONTAL_RESOLUTION_MDEG)
738        .vertical_resolution_mdeg(LIDAR_HESAI_AT128_VERTICAL_RESOLUTION_MDEG)
739        .range_10pct_reflectivity_m(LIDAR_HESAI_AT128_RANGE_10PCT_REFLECTIVITY_M)
740        .point_format(LIDAR_POINT_FORMAT_XYZIRCAEDT)
741        .is_bigendian(0)
742        .is_dense(1)
743        .reserved0([0; 2])
744        .checksum(fixture_checksum(case, sequence))
745        .extrinsics(|pose| init_pose(pose, sequence))?
746        .points(|index, point| init_lidar_point(point, sequence, index))?
747        .reserved1(0)
748        .finish()
749}
750
751#[cfg(feature = "payload-contract-large-fixtures")]
752pub fn init_camera_8mp_bayer_rggb12p<'a>(
753    init: CameraBayerRggb12pFrame8mpV1Init<'a>,
754    sequence: u32,
755) -> Result<InitializedStablePayload<'a, CameraBayerRggb12pFrame8mpV1>, UWireError> {
756    let case = case_by_kind(PayloadContractCaseKind::Camera8mpBayerRggb12p);
757    init.header(|header| init_header(header, case, sequence))?
758        .frame_counter(sequence as u64)
759        .sensor_timestamp_ns(timestamp_ns(sequence))
760        .exposure_start_ns(timestamp_ns(sequence).saturating_sub(1_000_000))
761        .camera_id(CAMERA_ID)
762        .width(CAMERA_8MP_WIDTH)
763        .height(CAMERA_8MP_HEIGHT)
764        .stride_bytes(CAMERA_BAYER_RGGB12P_STRIDE_BYTES)
765        .bayer_pattern(BAYER_PATTERN_RGGB)
766        .bits_per_sample(BITS_PER_SAMPLE_12)
767        .packed_layout(PACKED_LAYOUT_12P_LSB)
768        .exposure_time_us(8_000)
769        .analog_gain(1.5)
770        .digital_gain(1.0)
771        .intrinsics(init_camera_intrinsics)?
772        .extrinsics(|pose| init_pose(pose, sequence))?
773        .roi(|context| {
774            let roi = context.into_init();
775            roi.x(0)
776                .y(0)
777                .width(CAMERA_8MP_WIDTH)
778                .height(CAMERA_8MP_HEIGHT)
779                .finish()
780        })?
781        .checksum(fixture_checksum(case, sequence))
782        .pixels_fill_with(|index| pattern_byte(case.case_id, sequence, index))
783        .finish()
784}
785
786impl StablePayloadContractView for CanClassicFrameV1 {
787    fn case_kind() -> PayloadContractCaseKind {
788        PayloadContractCaseKind::CanClassicMax
789    }
790
791    fn validate_contract(
792        &self,
793        case: &PayloadContractCase,
794        sequence: u32,
795    ) -> Result<(), UWireError> {
796        validate_header(&self.header, case, sequence)?;
797        ensure_eq("CAN interface_id", self.interface_id, CAN_INTERFACE_ID)?;
798        ensure_eq("CAN id", self.can_id, CAN_CLASSIC_ID)?;
799        ensure_eq("CAN flags", self.flags, CAN_FLAG_EFF)?;
800        ensure_eq(
801            "CAN timestamp",
802            self.bus_timestamp_ns,
803            timestamp_ns(sequence),
804        )?;
805        ensure_eq("CAN len", self.len, 8)?;
806        ensure_eq("CAN len8_dlc", self.len8_dlc, 8)?;
807        validate_all_pattern_bytes(case, sequence, &self.data)?;
808        ensure_eq(
809            "CAN checksum",
810            self.checksum,
811            fixture_checksum(case, sequence),
812        )
813    }
814}
815
816impl StablePayloadContractView for CanFdFrameV1 {
817    fn case_kind() -> PayloadContractCaseKind {
818        PayloadContractCaseKind::CanFdMax
819    }
820
821    fn validate_contract(
822        &self,
823        case: &PayloadContractCase,
824        sequence: u32,
825    ) -> Result<(), UWireError> {
826        validate_header(&self.header, case, sequence)?;
827        ensure_eq("CAN FD interface_id", self.interface_id, CAN_INTERFACE_ID)?;
828        ensure_eq("CAN FD id", self.can_id, CAN_FD_ID)?;
829        ensure_eq(
830            "CAN FD flags",
831            self.flags,
832            CAN_FLAG_EFF | CAN_FD_FLAG_BRS | CAN_FD_FLAG_ESI,
833        )?;
834        ensure_eq(
835            "CAN FD timestamp",
836            self.bus_timestamp_ns,
837            timestamp_ns(sequence),
838        )?;
839        ensure_eq("CAN FD len", self.len, 64)?;
840        validate_all_pattern_bytes(case, sequence, &self.data)?;
841        ensure_eq(
842            "CAN FD checksum",
843            self.checksum,
844            fixture_checksum(case, sequence),
845        )
846    }
847}
848
849impl StablePayloadContractView for SomeIpSignalBatchMtuV1 {
850    fn case_kind() -> PayloadContractCaseKind {
851        PayloadContractCaseKind::SomeIpSingleMtu
852    }
853
854    fn validate_contract(
855        &self,
856        case: &PayloadContractCase,
857        sequence: u32,
858    ) -> Result<(), UWireError> {
859        validate_header(&self.header, case, sequence)?;
860        validate_someip_fields(
861            self.source_timestamp_ns,
862            self.length,
863            self.sample_count,
864            self.service_id,
865            self.method_or_event_id,
866            self.client_id,
867            self.session_id,
868            self.protocol_version,
869            self.interface_version,
870            self.message_type,
871            self.return_code,
872            self.checksum,
873            case,
874            sequence,
875        )?;
876        for index in sample_indices(SOMEIP_MTU_SIGNAL_SAMPLE_COUNT) {
877            validate_signal_sample(&self.samples[index], sequence, index)?;
878        }
879        Ok(())
880    }
881}
882
883impl StablePayloadContractView for StreamChunk4kV1 {
884    fn case_kind() -> PayloadContractCaseKind {
885        PayloadContractCaseKind::Streamer4k
886    }
887
888    fn validate_contract(
889        &self,
890        case: &PayloadContractCase,
891        sequence: u32,
892    ) -> Result<(), UWireError> {
893        validate_stream_meta(&self.meta, case, sequence, 4 * 1_024)?;
894        validate_sample_pattern_bytes(case, sequence, &self.chunk)
895    }
896}
897
898impl StablePayloadContractView for StreamChunk64kV1 {
899    fn case_kind() -> PayloadContractCaseKind {
900        PayloadContractCaseKind::Streamer64k
901    }
902
903    fn validate_contract(
904        &self,
905        case: &PayloadContractCase,
906        sequence: u32,
907    ) -> Result<(), UWireError> {
908        validate_stream_meta(&self.meta, case, sequence, 64 * 1_024)?;
909        validate_sample_pattern_bytes(case, sequence, &self.chunk)
910    }
911}
912
913impl StablePayloadContractView for RadarDetectionListArs548V1 {
914    fn case_kind() -> PayloadContractCaseKind {
915        PayloadContractCaseKind::RadarArs548DetectionList
916    }
917
918    fn validate_contract(
919        &self,
920        case: &PayloadContractCase,
921        sequence: u32,
922    ) -> Result<(), UWireError> {
923        validate_header(&self.header, case, sequence)?;
924        ensure_eq(
925            "radar timestamp",
926            self.measurement_timestamp_ns,
927            timestamp_ns(sequence),
928        )?;
929        ensure_eq("radar sensor id", self.sensor_id, RADAR_SENSOR_ID)?;
930        ensure_eq(
931            "radar measurement counter",
932            self.measurement_counter,
933            sequence,
934        )?;
935        ensure_eq(
936            "radar cycle counter",
937            self.cycle_counter,
938            sequence.wrapping_mul(3),
939        )?;
940        ensure_eq(
941            "radar detection count",
942            self.list_numofdetections,
943            RADAR_ARS548_MAX_DETECTIONS as u32,
944        )?;
945        for index in sample_indices(RADAR_ARS548_MAX_DETECTIONS) {
946            validate_radar_detection(&self.detections[index], sequence, index)?;
947        }
948        ensure_eq(
949            "radar checksum",
950            self.checksum,
951            fixture_checksum(case, sequence),
952        )
953    }
954}
955
956#[cfg(feature = "payload-contract-large-fixtures")]
957impl StablePayloadContractView for LidarPointCloudHesaiAt128V1 {
958    fn case_kind() -> PayloadContractCaseKind {
959        PayloadContractCaseKind::LidarHesaiAt128PointCloud
960    }
961
962    fn validate_contract(
963        &self,
964        case: &PayloadContractCase,
965        sequence: u32,
966    ) -> Result<(), UWireError> {
967        validate_header(&self.header, case, sequence)?;
968        validate_lidar_fields(
969            self.frame_counter,
970            self.sensor_timestamp_ns,
971            self.scan_duration_ns,
972            self.lidar_id,
973            self.width,
974            self.height,
975            self.point_count,
976            self.point_step,
977            self.row_step,
978            self.points_per_second,
979            self.horizontal_fov_mdeg,
980            self.vertical_fov_mdeg,
981            self.horizontal_resolution_mdeg,
982            self.vertical_resolution_mdeg,
983            self.range_10pct_reflectivity_m,
984            self.point_format,
985            self.is_bigendian,
986            self.is_dense,
987            self.checksum,
988            case,
989            sequence,
990        )?;
991        validate_pose(&self.extrinsics, sequence)?;
992        for index in sample_indices(LIDAR_HESAI_AT128_POINT_COUNT) {
993            validate_lidar_point(&self.points[index], sequence, index)?;
994        }
995        Ok(())
996    }
997}
998
999#[cfg(feature = "payload-contract-large-fixtures")]
1000impl StablePayloadContractView for CameraBayerRggb12pFrame8mpV1 {
1001    fn case_kind() -> PayloadContractCaseKind {
1002        PayloadContractCaseKind::Camera8mpBayerRggb12p
1003    }
1004
1005    fn validate_contract(
1006        &self,
1007        case: &PayloadContractCase,
1008        sequence: u32,
1009    ) -> Result<(), UWireError> {
1010        validate_header(&self.header, case, sequence)?;
1011        validate_camera_fields(
1012            self.frame_counter,
1013            self.sensor_timestamp_ns,
1014            self.exposure_start_ns,
1015            self.camera_id,
1016            self.width,
1017            self.height,
1018            self.stride_bytes,
1019            self.bayer_pattern,
1020            self.bits_per_sample,
1021            self.packed_layout,
1022            self.exposure_time_us,
1023            self.analog_gain,
1024            self.digital_gain,
1025            self.checksum,
1026            case,
1027            sequence,
1028        )?;
1029        validate_camera_intrinsics(&self.intrinsics)?;
1030        validate_pose(&self.extrinsics, sequence)?;
1031        ensure_eq("camera roi x", self.roi.x, 0)?;
1032        ensure_eq("camera roi y", self.roi.y, 0)?;
1033        ensure_eq("camera roi width", self.roi.width, CAMERA_8MP_WIDTH)?;
1034        ensure_eq("camera roi height", self.roi.height, CAMERA_8MP_HEIGHT)?;
1035        validate_sample_pattern_bytes(case, sequence, &self.pixels)
1036    }
1037}
1038
1039fn protobuf_message_name(case: &PayloadContractCase) -> &'static str {
1040    match case.kind {
1041        PayloadContractCaseKind::CanClassicMax => "uprotocol.bench.v1.CanClassicFrame",
1042        PayloadContractCaseKind::CanFdMax => "uprotocol.bench.v1.CanFdFrame",
1043        PayloadContractCaseKind::SomeIpSingleMtu => "uprotocol.bench.v1.SomeIpSignalBatch",
1044        PayloadContractCaseKind::Streamer4k | PayloadContractCaseKind::Streamer64k => {
1045            "uprotocol.bench.v1.StreamChunk"
1046        }
1047        PayloadContractCaseKind::RadarArs548DetectionList => {
1048            "uprotocol.bench.v1.Ars548DetectionList"
1049        }
1050        #[cfg(feature = "payload-contract-large-fixtures")]
1051        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
1052            "uprotocol.bench.v1.LidarPointCloudFrame"
1053        }
1054        #[cfg(feature = "payload-contract-large-fixtures")]
1055        PayloadContractCaseKind::Camera8mpBayerRggb12p => "uprotocol.bench.v1.CameraBayerFrame",
1056        #[cfg(feature = "payload-contract-simulator-fixtures")]
1057        PayloadContractCaseKind::Camera8mpCarlaBgra32 => "uprotocol.bench.v1.CameraCarlaBgraFrame",
1058    }
1059}
1060
1061fn stable_owned_fixture_for_type<T>(
1062    init: impl for<'a> FnOnce(
1063        <T as StablePayloadInit>::Init<'a>,
1064        std::marker::PhantomData<&'a mut T>,
1065    ) -> Result<InitializedStablePayload<'a, T>, UWireError>,
1066) -> Result<StableOwnedFixture, UWireError>
1067where
1068    T: StablePayload + StablePayloadInit,
1069{
1070    let encoding = StableContainerPayload::<T>::encoding();
1071    let metadata = fixture_metadata(Some(encoding.clone()));
1072    let mut buffer =
1073        UVecUninitTxBuffer::with_alignment(metadata, mem::size_of::<T>(), mem::align_of::<T>())
1074            .map_err(|error| UWireError::invalid_payload(error.to_string()))?;
1075    {
1076        let payload = buffer.payload_uninit_mut();
1077        // SAFETY: `UVecUninitTxBuffer` exposes the exact visible payload range for this emulated loan.
1078        let loaned = unsafe {
1079            LoanedPayloadUninitMut::new_unchecked(
1080                payload,
1081                PayloadLoanProvenance::OpaqueTransportLoan,
1082            )
1083        };
1084        let initializer = T::init_from_uninit_payload(loaned)?;
1085        let _initialized = init(initializer, std::marker::PhantomData)?;
1086    }
1087    // SAFETY: the generated initializer returned the completion proof consumed above.
1088    let buffer = unsafe { buffer.assume_payload_init() };
1089    Ok(StableOwnedFixture {
1090        encoding,
1091        bytes: buffer.payload().to_vec(),
1092        stable_type_name: T::stable_type_name(),
1093        stable_transport_len: mem::size_of::<T>(),
1094        stable_align: mem::align_of::<T>(),
1095    })
1096}
1097
1098fn fixture_metadata(payload_encoding: Option<PayloadEncoding>) -> UFrameMetadata {
1099    let mut builder = UFrameMetadata::publish(
1100        UUri::try_from_parts("payload-contract-fixture", 0x4210, 1, 0x9000)
1101            .expect("fixture URI is valid"),
1102    );
1103    if let Some(payload_encoding) = payload_encoding {
1104        builder = builder.with_payload_encoding(payload_encoding);
1105    }
1106    builder.build().expect("fixture metadata is valid")
1107}
1108
1109fn validate_stable_encoding_for_case(
1110    case: &PayloadContractCase,
1111    encoding: Option<&PayloadEncoding>,
1112) -> Result<(), UWireError> {
1113    match case.kind {
1114        PayloadContractCaseKind::CanClassicMax => {
1115            validate_stable_encoding::<CanClassicFrameV1>(encoding)
1116        }
1117        PayloadContractCaseKind::CanFdMax => validate_stable_encoding::<CanFdFrameV1>(encoding),
1118        PayloadContractCaseKind::SomeIpSingleMtu => {
1119            validate_stable_encoding::<SomeIpSignalBatchMtuV1>(encoding)
1120        }
1121        PayloadContractCaseKind::Streamer4k => {
1122            validate_stable_encoding::<StreamChunk4kV1>(encoding)
1123        }
1124        PayloadContractCaseKind::RadarArs548DetectionList => {
1125            validate_stable_encoding::<RadarDetectionListArs548V1>(encoding)
1126        }
1127        PayloadContractCaseKind::Streamer64k => {
1128            validate_stable_encoding::<StreamChunk64kV1>(encoding)
1129        }
1130        #[cfg(feature = "payload-contract-large-fixtures")]
1131        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
1132            validate_stable_encoding::<LidarPointCloudHesaiAt128V1>(encoding)
1133        }
1134        #[cfg(feature = "payload-contract-large-fixtures")]
1135        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
1136            validate_stable_encoding::<CameraBayerRggb12pFrame8mpV1>(encoding)
1137        }
1138        #[cfg(feature = "payload-contract-simulator-fixtures")]
1139        PayloadContractCaseKind::Camera8mpCarlaBgra32 => Ok(()),
1140    }
1141}
1142
1143fn validate_stable_encoding<T: StablePayload>(
1144    encoding: Option<&PayloadEncoding>,
1145) -> Result<(), UWireError> {
1146    <StableContainerPayload<T> as crate::payload::codec::PayloadCodec>::verify_encoding(encoding)
1147}
1148
1149fn build_proto_can_classic(case: &PayloadContractCase, sequence: u32) -> proto::CanClassicFrame {
1150    let mut message = proto::CanClassicFrame::new();
1151    message.header = MessageField::some(proto_header(case, sequence));
1152    message.interface_id = CAN_INTERFACE_ID;
1153    message.can_id = CAN_CLASSIC_ID;
1154    message.is_extended_id = true;
1155    message.len = 8;
1156    message.len8_dlc = 8;
1157    message.bus_timestamp_ns = timestamp_ns(sequence);
1158    message.data = pattern_vec(case.case_id, sequence, 8);
1159    message.checksum = fixture_checksum(case, sequence);
1160    message
1161}
1162
1163fn build_proto_can_fd(case: &PayloadContractCase, sequence: u32) -> proto::CanFdFrame {
1164    let mut message = proto::CanFdFrame::new();
1165    message.header = MessageField::some(proto_header(case, sequence));
1166    message.interface_id = CAN_INTERFACE_ID;
1167    message.can_id = CAN_FD_ID;
1168    message.is_extended_id = true;
1169    message.bitrate_switch = true;
1170    message.error_state_indicator = true;
1171    message.len = 64;
1172    message.bus_timestamp_ns = timestamp_ns(sequence);
1173    message.data = pattern_vec(case.case_id, sequence, 64);
1174    message.checksum = fixture_checksum(case, sequence);
1175    message
1176}
1177
1178fn build_proto_someip(case: &PayloadContractCase, sequence: u32) -> proto::SomeIpSignalBatch {
1179    let mut message = proto::SomeIpSignalBatch::new();
1180    message.header = MessageField::some(proto_header(case, sequence));
1181    message.service_id = u32::from(SOMEIP_SERVICE_ID);
1182    message.method_or_event_id = u32::from(SOMEIP_METHOD_OR_EVENT_ID);
1183    message.length = SOMEIP_IPV4_UDP_SINGLE_MTU_BUDGET as u32;
1184    message.client_id = u32::from(SOMEIP_CLIENT_ID);
1185    message.session_id = u32::from(SOMEIP_SESSION_ID);
1186    message.protocol_version = u32::from(SOMEIP_PROTOCOL_VERSION);
1187    message.interface_version = u32::from(SOMEIP_INTERFACE_VERSION);
1188    message.message_type = u32::from(SOMEIP_MESSAGE_TYPE);
1189    message.return_code = u32::from(SOMEIP_RETURN_CODE);
1190    message.source_timestamp_ns = timestamp_ns(sequence);
1191    message.samples = (0..SOMEIP_MTU_SIGNAL_SAMPLE_COUNT)
1192        .map(|index| {
1193            let sample = signal_sample_value(sequence, index);
1194            let mut message = proto::SignalSample::new();
1195            message.signal_id = sample.signal_id;
1196            message.status = sample.status;
1197            message.timestamp_ns = sample.timestamp_ns;
1198            message.value = sample.value;
1199            message
1200        })
1201        .collect();
1202    message.checksum = fixture_checksum(case, sequence);
1203    message
1204}
1205
1206fn build_proto_stream(
1207    case: &PayloadContractCase,
1208    sequence: u32,
1209    chunk_len: usize,
1210) -> proto::StreamChunk {
1211    let mut message = proto::StreamChunk::new();
1212    message.header = MessageField::some(proto_header(case, sequence));
1213    message.stream_id = STREAM_ID;
1214    message.codec = STREAM_CODEC_RAW;
1215    message.flags = STREAM_FLAG_KEY_FRAME;
1216    message.byte_offset = u64::from(sequence).saturating_mul(chunk_len as u64);
1217    message.chunk_index = sequence;
1218    message.chunk_count = sequence.saturating_add(7);
1219    message.source_timestamp_ns = timestamp_ns(sequence);
1220    message.chunk = pattern_vec(case.case_id, sequence, chunk_len);
1221    message.checksum = fixture_checksum(case, sequence);
1222    message
1223}
1224
1225fn build_proto_radar(case: &PayloadContractCase, sequence: u32) -> proto::Ars548DetectionList {
1226    let mut message = proto::Ars548DetectionList::new();
1227    message.header = MessageField::some(proto_header(case, sequence));
1228    message.sensor_id = RADAR_SENSOR_ID;
1229    message.measurement_counter = sequence;
1230    message.cycle_counter = sequence.wrapping_mul(3);
1231    message.measurement_timestamp_ns = timestamp_ns(sequence);
1232    message.list_numofdetections = RADAR_ARS548_MAX_DETECTIONS as u32;
1233    message.detections = (0..RADAR_ARS548_MAX_DETECTIONS)
1234        .map(|index| {
1235            let detection = radar_detection_value(sequence, index);
1236            let mut message = proto::Ars548Detection::new();
1237            message.azimuth_angle = detection.azimuth_angle;
1238            message.azimuth_angle_std = detection.azimuth_angle_std;
1239            message.invalid_flags = u32::from(detection.invalid_flags);
1240            message.elevation_angle = detection.elevation_angle;
1241            message.elevation_angle_std = detection.elevation_angle_std;
1242            message.range = detection.range;
1243            message.range_std = detection.range_std;
1244            message.range_rate = detection.range_rate;
1245            message.range_rate_std = detection.range_rate_std;
1246            message.rcs = i32::from(detection.rcs);
1247            message.measurement_id = u32::from(detection.measurement_id);
1248            message.positive_predictive_value = u32::from(detection.positive_predictive_value);
1249            message.classification = u32::from(detection.classification);
1250            message.multi_target_probability = u32::from(detection.multi_target_probability);
1251            message.object_id = u32::from(detection.object_id);
1252            message.ambiguity_flag = u32::from(detection.ambiguity_flag);
1253            message.sort_index = u32::from(detection.sort_index);
1254            message
1255        })
1256        .collect();
1257    message.checksum = fixture_checksum(case, sequence);
1258    message
1259}
1260
1261#[cfg(feature = "payload-contract-large-fixtures")]
1262fn build_proto_lidar(case: &PayloadContractCase, sequence: u32) -> proto::LidarPointCloudFrame {
1263    let mut message = proto::LidarPointCloudFrame::new();
1264    message.header = MessageField::some(proto_header(case, sequence));
1265    message.lidar_id = LIDAR_ID;
1266    message.frame_counter = u64::from(sequence);
1267    message.sensor_timestamp_ns = timestamp_ns(sequence);
1268    message.width = LIDAR_HESAI_AT128_WIDTH;
1269    message.height = LIDAR_HESAI_AT128_HEIGHT;
1270    message.point_count = LIDAR_HESAI_AT128_POINT_COUNT as u32;
1271    message.point_step = LIDAR_XYZIRCAEDT_POINT_BYTES as u32;
1272    message.row_step = LIDAR_HESAI_AT128_ROW_STEP_BYTES;
1273    message.points_per_second = LIDAR_HESAI_AT128_POINTS_PER_SECOND;
1274    message.scan_duration_ns = LIDAR_HESAI_AT128_SCAN_DURATION_NS;
1275    message.horizontal_fov_mdeg = LIDAR_HESAI_AT128_HORIZONTAL_FOV_MDEG;
1276    message.vertical_fov_mdeg = LIDAR_HESAI_AT128_VERTICAL_FOV_MDEG;
1277    message.horizontal_resolution_mdeg = LIDAR_HESAI_AT128_HORIZONTAL_RESOLUTION_MDEG;
1278    message.vertical_resolution_mdeg = LIDAR_HESAI_AT128_VERTICAL_RESOLUTION_MDEG;
1279    message.range_10pct_reflectivity_m = LIDAR_HESAI_AT128_RANGE_10PCT_REFLECTIVITY_M;
1280    message.point_format = LIDAR_POINT_FORMAT_XYZIRCAEDT;
1281    message.is_dense = true;
1282    message.extrinsics = MessageField::some(proto_pose(sequence));
1283    let mut points = vec![0_u8; LIDAR_HESAI_AT128_POINTS_BYTES];
1284    for index in 0..LIDAR_HESAI_AT128_POINT_COUNT {
1285        write_lidar_point_le(&mut points, sequence, index);
1286    }
1287    message.points_xyzircaedt_le = points;
1288    message.checksum = fixture_checksum(case, sequence);
1289    message
1290}
1291
1292#[cfg(feature = "payload-contract-large-fixtures")]
1293fn build_proto_camera_bayer(case: &PayloadContractCase, sequence: u32) -> proto::CameraBayerFrame {
1294    let mut message = proto::CameraBayerFrame::new();
1295    message.header = MessageField::some(proto_header(case, sequence));
1296    message.camera_id = CAMERA_ID;
1297    message.frame_counter = u64::from(sequence);
1298    message.sensor_timestamp_ns = timestamp_ns(sequence);
1299    message.width = CAMERA_8MP_WIDTH;
1300    message.height = CAMERA_8MP_HEIGHT;
1301    message.stride_bytes = CAMERA_BAYER_RGGB12P_STRIDE_BYTES;
1302    message.bayer_pattern = BAYER_PATTERN_RGGB;
1303    message.bits_per_sample = BITS_PER_SAMPLE_12;
1304    message.packed_layout = PACKED_LAYOUT_12P_LSB;
1305    message.exposure_start_ns = timestamp_ns(sequence).saturating_sub(1_000_000);
1306    message.exposure_time_us = 8_000;
1307    message.analog_gain = 1.5;
1308    message.digital_gain = 1.0;
1309    message.intrinsics = MessageField::some(proto_camera_intrinsics());
1310    message.extrinsics = MessageField::some(proto_pose(sequence));
1311    let mut roi = proto::Roi::new();
1312    roi.width = CAMERA_8MP_WIDTH;
1313    roi.height = CAMERA_8MP_HEIGHT;
1314    message.roi = MessageField::some(roi);
1315    message.pixels = pattern_vec(case.case_id, sequence, CAMERA_BAYER_RGGB12P_BYTES);
1316    message.checksum = fixture_checksum(case, sequence);
1317    message
1318}
1319
1320fn proto_header(case: &PayloadContractCase, sequence: u32) -> proto::BenchHeader {
1321    let mut header = proto::BenchHeader::new();
1322    header.case_id = case.case_id;
1323    header.sequence = sequence;
1324    header.semantic_reference_len = case.semantic_reference_len as u32;
1325    header.schema_version = PAYLOAD_CONTRACT_FIXTURE_VERSION;
1326    header
1327}
1328
1329#[cfg(feature = "payload-contract-large-fixtures")]
1330fn proto_pose(sequence: u32) -> proto::Pose3d {
1331    let pose = pose_value(sequence);
1332    let mut translation = proto::Vector3f::new();
1333    translation.x = pose.translation_m.x;
1334    translation.y = pose.translation_m.y;
1335    translation.z = pose.translation_m.z;
1336    let mut rotation = proto::Quaternionf::new();
1337    rotation.x = pose.rotation.x;
1338    rotation.y = pose.rotation.y;
1339    rotation.z = pose.rotation.z;
1340    rotation.w = pose.rotation.w;
1341    let mut message = proto::Pose3d::new();
1342    message.translation_m = MessageField::some(translation);
1343    message.rotation = MessageField::some(rotation);
1344    message
1345}
1346
1347#[cfg(feature = "payload-contract-large-fixtures")]
1348fn proto_camera_intrinsics() -> proto::CameraIntrinsics {
1349    let mut message = proto::CameraIntrinsics::new();
1350    message.fx = 1_950.0;
1351    message.fy = 1_950.0;
1352    message.cx = 1_920.0;
1353    message.cy = 1_080.0;
1354    message.distortion_model = 1;
1355    message.distortion = vec![0.1, -0.03, 0.001, 0.0005, 0.0, 0.0, 0.0, 0.0];
1356    message
1357}
1358
1359fn init_header<'a>(
1360    context: StablePayloadInitContext<'a, BenchHeaderV1>,
1361    case: &PayloadContractCase,
1362    sequence: u32,
1363) -> Result<InitializedStablePayload<'a, BenchHeaderV1>, UWireError> {
1364    let init = context.into_init();
1365    init.case_id(case.case_id)
1366        .sequence(sequence)
1367        .semantic_reference_len(case.semantic_reference_len as u32)
1368        .schema_version(PAYLOAD_CONTRACT_FIXTURE_VERSION)
1369        .finish()
1370}
1371
1372fn init_signal_sample<'a>(
1373    context: StablePayloadInitContext<'a, SignalSampleV1>,
1374    sequence: u32,
1375    index: usize,
1376) -> Result<InitializedStablePayload<'a, SignalSampleV1>, UWireError> {
1377    let init = context.into_init();
1378    let sample = signal_sample_value(sequence, index);
1379    init.signal_id(sample.signal_id)
1380        .status(sample.status)
1381        .timestamp_ns(sample.timestamp_ns)
1382        .value(sample.value)
1383        .finish()
1384}
1385
1386fn init_stream_meta<'a>(
1387    context: StablePayloadInitContext<'a, StreamChunkHeaderV1>,
1388    case: &PayloadContractCase,
1389    sequence: u32,
1390    chunk_len: usize,
1391) -> Result<InitializedStablePayload<'a, StreamChunkHeaderV1>, UWireError> {
1392    let init = context.into_init();
1393    init.header(|header| init_header(header, case, sequence))?
1394        .stream_id(STREAM_ID)
1395        .codec(STREAM_CODEC_RAW)
1396        .flags(STREAM_FLAG_KEY_FRAME)
1397        .byte_offset(u64::from(sequence).saturating_mul(chunk_len as u64))
1398        .chunk_index(sequence)
1399        .chunk_count(sequence.saturating_add(7))
1400        .source_timestamp_ns(timestamp_ns(sequence))
1401        .checksum(fixture_checksum(case, sequence))
1402        .reserved0(0)
1403        .finish()
1404}
1405
1406fn init_radar_detection<'a>(
1407    context: StablePayloadInitContext<'a, Ars548DetectionV1>,
1408    sequence: u32,
1409    index: usize,
1410) -> Result<InitializedStablePayload<'a, Ars548DetectionV1>, UWireError> {
1411    let init = context.into_init();
1412    let detection = radar_detection_value(sequence, index);
1413    init.azimuth_angle(detection.azimuth_angle)
1414        .azimuth_angle_std(detection.azimuth_angle_std)
1415        .elevation_angle(detection.elevation_angle)
1416        .elevation_angle_std(detection.elevation_angle_std)
1417        .range(detection.range)
1418        .range_std(detection.range_std)
1419        .range_rate(detection.range_rate)
1420        .range_rate_std(detection.range_rate_std)
1421        .measurement_id(detection.measurement_id)
1422        .object_id(detection.object_id)
1423        .sort_index(detection.sort_index)
1424        .invalid_flags(detection.invalid_flags)
1425        .rcs(detection.rcs)
1426        .positive_predictive_value(detection.positive_predictive_value)
1427        .classification(detection.classification)
1428        .multi_target_probability(detection.multi_target_probability)
1429        .ambiguity_flag(detection.ambiguity_flag)
1430        .finish()
1431}
1432
1433#[cfg(all(
1434    feature = "perf-diagnostics",
1435    feature = "payload-contract-large-fixtures"
1436))]
1437fn init_lidar_point<'a>(
1438    context: StablePayloadInitContext<'a, LidarPointXyzircaedtV1>,
1439    sequence: u32,
1440    index: usize,
1441) -> Result<InitializedStablePayload<'a, LidarPointXyzircaedtV1>, UWireError> {
1442    let init = context.into_init();
1443    let point = lidar_point_value(sequence, index);
1444    init.x(point.x)
1445        .y(point.y)
1446        .z(point.z)
1447        .intensity(point.intensity)
1448        .return_type(point.return_type)
1449        .channel(point.channel)
1450        .azimuth(point.azimuth)
1451        .elevation(point.elevation)
1452        .distance(point.distance)
1453        .time_offset_ns(point.time_offset_ns)
1454        .finish()
1455}
1456
1457#[cfg(feature = "payload-contract-large-fixtures")]
1458struct LidarPointTrigCache {
1459    azimuth: [(f32, f32, f32); LIDAR_HESAI_AT128_WIDTH as usize],
1460    elevation: [(f32, f32); LIDAR_HESAI_AT128_HEIGHT as usize],
1461}
1462
1463#[cfg(feature = "payload-contract-large-fixtures")]
1464impl LidarPointTrigCache {
1465    fn new() -> Self {
1466        Self {
1467            azimuth: std::array::from_fn(|column| {
1468                let azimuth = -60.0 + column as f32 * 0.1;
1469                (
1470                    azimuth,
1471                    azimuth.to_radians().cos(),
1472                    azimuth.to_radians().sin(),
1473                )
1474            }),
1475            elevation: std::array::from_fn(|row| {
1476                let elevation = -12.7 + row as f32 * 0.2;
1477                (elevation, elevation.to_radians().sin())
1478            }),
1479        }
1480    }
1481}
1482
1483#[cfg(feature = "payload-contract-large-fixtures")]
1484fn init_lidar_point_cached<'a>(
1485    context: StablePayloadInitContext<'a, LidarPointXyzircaedtV1>,
1486    sequence: u32,
1487    index: usize,
1488    cache: &LidarPointTrigCache,
1489) -> Result<InitializedStablePayload<'a, LidarPointXyzircaedtV1>, UWireError> {
1490    let column = index % LIDAR_HESAI_AT128_WIDTH as usize;
1491    let row = index / LIDAR_HESAI_AT128_WIDTH as usize;
1492    let (azimuth, azimuth_cos, azimuth_sin) = cache.azimuth[column];
1493    let (elevation, elevation_sin) = cache.elevation[row];
1494    let distance = 5.0 + (index % 2_000) as f32 * 0.05;
1495    context
1496        .into_init()
1497        .x(distance * azimuth_cos)
1498        .y(distance * azimuth_sin)
1499        .z(distance * elevation_sin)
1500        .intensity(pattern_byte(7, sequence, index))
1501        .return_type(1)
1502        .channel(row as u16)
1503        .azimuth(azimuth)
1504        .elevation(elevation)
1505        .distance(distance)
1506        .time_offset_ns(
1507            ((index as u64 * u64::from(LIDAR_HESAI_AT128_SCAN_DURATION_NS))
1508                / LIDAR_HESAI_AT128_POINT_COUNT as u64) as u32,
1509        )
1510        .finish()
1511}
1512
1513#[cfg(feature = "payload-contract-large-fixtures")]
1514fn init_pose<'a>(
1515    context: StablePayloadInitContext<'a, Pose3dV1>,
1516    sequence: u32,
1517) -> Result<InitializedStablePayload<'a, Pose3dV1>, UWireError> {
1518    let init = context.into_init();
1519    let pose = pose_value(sequence);
1520    init.translation_m(|context| {
1521        let translation = context.into_init();
1522        translation
1523            .x(pose.translation_m.x)
1524            .y(pose.translation_m.y)
1525            .z(pose.translation_m.z)
1526            .finish()
1527    })?
1528    .rotation(|context| {
1529        let rotation = context.into_init();
1530        rotation
1531            .x(pose.rotation.x)
1532            .y(pose.rotation.y)
1533            .z(pose.rotation.z)
1534            .w(pose.rotation.w)
1535            .finish()
1536    })?
1537    .finish()
1538}
1539
1540#[cfg(feature = "payload-contract-large-fixtures")]
1541fn init_camera_intrinsics<'a>(
1542    context: StablePayloadInitContext<'a, CameraIntrinsicsV1>,
1543) -> Result<InitializedStablePayload<'a, CameraIntrinsicsV1>, UWireError> {
1544    let init = context.into_init();
1545    init.fx(1_950.0)
1546        .fy(1_950.0)
1547        .cx(1_920.0)
1548        .cy(1_080.0)
1549        .skew(0.0)
1550        .distortion_model(1)
1551        .distortion_from_slice(&[0.1, -0.03, 0.001, 0.0005, 0.0, 0.0, 0.0, 0.0])?
1552        .finish()
1553}
1554
1555fn validate_proto_can_classic(
1556    case: &PayloadContractCase,
1557    sequence: u32,
1558    message: &proto::CanClassicFrame,
1559) -> Result<(), UWireError> {
1560    validate_proto_header(message.header.as_ref(), case, sequence)?;
1561    ensure_eq(
1562        "protobuf CAN interface_id",
1563        message.interface_id,
1564        CAN_INTERFACE_ID,
1565    )?;
1566    ensure_eq("protobuf CAN id", message.can_id, CAN_CLASSIC_ID)?;
1567    ensure("protobuf CAN EFF", message.is_extended_id)?;
1568    ensure_eq("protobuf CAN RTR", message.is_remote_request, false)?;
1569    ensure_eq("protobuf CAN ERR", message.is_error_frame, false)?;
1570    ensure_eq("protobuf CAN len", message.len, 8)?;
1571    ensure_eq("protobuf CAN len8_dlc", message.len8_dlc, 8)?;
1572    ensure_eq(
1573        "protobuf CAN timestamp",
1574        message.bus_timestamp_ns,
1575        timestamp_ns(sequence),
1576    )?;
1577    validate_all_pattern_bytes(case, sequence, &message.data)?;
1578    ensure_eq(
1579        "protobuf CAN checksum",
1580        message.checksum,
1581        fixture_checksum(case, sequence),
1582    )
1583}
1584
1585fn validate_proto_can_fd(
1586    case: &PayloadContractCase,
1587    sequence: u32,
1588    message: &proto::CanFdFrame,
1589) -> Result<(), UWireError> {
1590    validate_proto_header(message.header.as_ref(), case, sequence)?;
1591    ensure_eq(
1592        "protobuf CAN FD interface_id",
1593        message.interface_id,
1594        CAN_INTERFACE_ID,
1595    )?;
1596    ensure_eq("protobuf CAN FD id", message.can_id, CAN_FD_ID)?;
1597    ensure("protobuf CAN FD EFF", message.is_extended_id)?;
1598    ensure("protobuf CAN FD BRS", message.bitrate_switch)?;
1599    ensure("protobuf CAN FD ESI", message.error_state_indicator)?;
1600    ensure_eq("protobuf CAN FD len", message.len, 64)?;
1601    ensure_eq(
1602        "protobuf CAN FD timestamp",
1603        message.bus_timestamp_ns,
1604        timestamp_ns(sequence),
1605    )?;
1606    validate_all_pattern_bytes(case, sequence, &message.data)?;
1607    ensure_eq(
1608        "protobuf CAN FD checksum",
1609        message.checksum,
1610        fixture_checksum(case, sequence),
1611    )
1612}
1613
1614fn validate_proto_someip(
1615    case: &PayloadContractCase,
1616    sequence: u32,
1617    message: &proto::SomeIpSignalBatch,
1618) -> Result<(), UWireError> {
1619    validate_proto_header(message.header.as_ref(), case, sequence)?;
1620    validate_someip_fields(
1621        message.source_timestamp_ns,
1622        message.length,
1623        message.samples.len() as u32,
1624        message.service_id as u16,
1625        message.method_or_event_id as u16,
1626        message.client_id as u16,
1627        message.session_id as u16,
1628        message.protocol_version as u8,
1629        message.interface_version as u8,
1630        message.message_type as u8,
1631        message.return_code as u8,
1632        message.checksum,
1633        case,
1634        sequence,
1635    )?;
1636    for index in sample_indices(SOMEIP_MTU_SIGNAL_SAMPLE_COUNT) {
1637        let sample = message
1638            .samples
1639            .get(index)
1640            .ok_or_else(|| UWireError::invalid_payload("missing SOME/IP sample"))?;
1641        let expected = signal_sample_value(sequence, index);
1642        ensure_eq(
1643            "protobuf SOME/IP signal_id",
1644            sample.signal_id,
1645            expected.signal_id,
1646        )?;
1647        ensure_eq("protobuf SOME/IP status", sample.status, expected.status)?;
1648        ensure_eq(
1649            "protobuf SOME/IP timestamp",
1650            sample.timestamp_ns,
1651            expected.timestamp_ns,
1652        )?;
1653        ensure_eq("protobuf SOME/IP value", sample.value, expected.value)?;
1654    }
1655    Ok(())
1656}
1657
1658fn validate_proto_stream(
1659    case: &PayloadContractCase,
1660    sequence: u32,
1661    message: &proto::StreamChunk,
1662) -> Result<(), UWireError> {
1663    validate_proto_header(message.header.as_ref(), case, sequence)?;
1664    validate_stream_fields(
1665        message.stream_id,
1666        message.codec,
1667        message.flags,
1668        message.byte_offset,
1669        message.chunk_index,
1670        message.chunk_count,
1671        message.source_timestamp_ns,
1672        message.checksum,
1673        case,
1674        sequence,
1675        message.chunk.len(),
1676    )?;
1677    validate_sample_pattern_bytes(case, sequence, &message.chunk)
1678}
1679
1680fn validate_proto_radar(
1681    case: &PayloadContractCase,
1682    sequence: u32,
1683    message: &proto::Ars548DetectionList,
1684) -> Result<(), UWireError> {
1685    validate_proto_header(message.header.as_ref(), case, sequence)?;
1686    ensure_eq(
1687        "protobuf radar sensor id",
1688        message.sensor_id,
1689        RADAR_SENSOR_ID,
1690    )?;
1691    ensure_eq(
1692        "protobuf radar measurement counter",
1693        message.measurement_counter,
1694        sequence,
1695    )?;
1696    ensure_eq(
1697        "protobuf radar cycle counter",
1698        message.cycle_counter,
1699        sequence.wrapping_mul(3),
1700    )?;
1701    ensure_eq(
1702        "protobuf radar timestamp",
1703        message.measurement_timestamp_ns,
1704        timestamp_ns(sequence),
1705    )?;
1706    ensure_eq(
1707        "protobuf radar detection count",
1708        message.list_numofdetections,
1709        RADAR_ARS548_MAX_DETECTIONS as u32,
1710    )?;
1711    ensure_eq(
1712        "protobuf radar repeated count",
1713        message.detections.len(),
1714        RADAR_ARS548_MAX_DETECTIONS,
1715    )?;
1716    for index in sample_indices(RADAR_ARS548_MAX_DETECTIONS) {
1717        let detection = message
1718            .detections
1719            .get(index)
1720            .ok_or_else(|| UWireError::invalid_payload("missing radar detection"))?;
1721        let expected = radar_detection_value(sequence, index);
1722        ensure_eq("protobuf radar range", detection.range, expected.range)?;
1723        ensure_eq(
1724            "protobuf radar range rate",
1725            detection.range_rate,
1726            expected.range_rate,
1727        )?;
1728        ensure_eq(
1729            "protobuf radar measurement id",
1730            detection.measurement_id,
1731            u32::from(expected.measurement_id),
1732        )?;
1733        ensure_eq(
1734            "protobuf radar object id",
1735            detection.object_id,
1736            u32::from(expected.object_id),
1737        )?;
1738    }
1739    ensure_eq(
1740        "protobuf radar checksum",
1741        message.checksum,
1742        fixture_checksum(case, sequence),
1743    )
1744}
1745
1746#[cfg(feature = "payload-contract-large-fixtures")]
1747fn validate_proto_lidar(
1748    case: &PayloadContractCase,
1749    sequence: u32,
1750    message: &proto::LidarPointCloudFrame,
1751) -> Result<(), UWireError> {
1752    validate_proto_header(message.header.as_ref(), case, sequence)?;
1753    validate_lidar_fields(
1754        message.frame_counter,
1755        message.sensor_timestamp_ns,
1756        message.scan_duration_ns,
1757        message.lidar_id,
1758        message.width,
1759        message.height,
1760        message.point_count,
1761        message.point_step,
1762        message.row_step,
1763        message.points_per_second,
1764        message.horizontal_fov_mdeg,
1765        message.vertical_fov_mdeg,
1766        message.horizontal_resolution_mdeg,
1767        message.vertical_resolution_mdeg,
1768        message.range_10pct_reflectivity_m,
1769        message.point_format,
1770        u8::from(message.is_bigendian),
1771        u8::from(message.is_dense),
1772        message.checksum,
1773        case,
1774        sequence,
1775    )?;
1776    ensure_eq(
1777        "protobuf lidar points bytes",
1778        message.points_xyzircaedt_le.len(),
1779        LIDAR_HESAI_AT128_POINTS_BYTES,
1780    )?;
1781    for index in sample_indices(LIDAR_HESAI_AT128_POINT_COUNT) {
1782        validate_lidar_point_le(&message.points_xyzircaedt_le, sequence, index)?;
1783    }
1784    Ok(())
1785}
1786
1787#[cfg(feature = "payload-contract-large-fixtures")]
1788fn validate_proto_camera_bayer(
1789    case: &PayloadContractCase,
1790    sequence: u32,
1791    message: &proto::CameraBayerFrame,
1792) -> Result<(), UWireError> {
1793    validate_proto_header(message.header.as_ref(), case, sequence)?;
1794    validate_camera_fields(
1795        message.frame_counter,
1796        message.sensor_timestamp_ns,
1797        message.exposure_start_ns,
1798        message.camera_id,
1799        message.width,
1800        message.height,
1801        message.stride_bytes,
1802        message.bayer_pattern,
1803        message.bits_per_sample,
1804        message.packed_layout,
1805        message.exposure_time_us,
1806        message.analog_gain,
1807        message.digital_gain,
1808        message.checksum,
1809        case,
1810        sequence,
1811    )?;
1812    ensure_eq(
1813        "protobuf camera pixels",
1814        message.pixels.len(),
1815        CAMERA_BAYER_RGGB12P_BYTES,
1816    )?;
1817    validate_sample_pattern_bytes(case, sequence, &message.pixels)
1818}
1819
1820fn validate_proto_header(
1821    header: Option<&proto::BenchHeader>,
1822    case: &PayloadContractCase,
1823    sequence: u32,
1824) -> Result<(), UWireError> {
1825    let header = header.ok_or_else(|| UWireError::invalid_payload("missing protobuf header"))?;
1826    ensure_eq("protobuf case_id", header.case_id, case.case_id)?;
1827    ensure_eq("protobuf sequence", header.sequence, sequence)?;
1828    ensure_eq(
1829        "protobuf semantic_reference_len",
1830        header.semantic_reference_len,
1831        case.semantic_reference_len as u32,
1832    )?;
1833    ensure_eq(
1834        "protobuf schema_version",
1835        header.schema_version,
1836        PAYLOAD_CONTRACT_FIXTURE_VERSION,
1837    )
1838}
1839
1840fn validate_header(
1841    header: &BenchHeaderV1,
1842    case: &PayloadContractCase,
1843    sequence: u32,
1844) -> Result<(), UWireError> {
1845    ensure_eq("stable case_id", header.case_id, case.case_id)?;
1846    ensure_eq("stable sequence", header.sequence, sequence)?;
1847    ensure_eq(
1848        "stable semantic_reference_len",
1849        header.semantic_reference_len,
1850        case.semantic_reference_len as u32,
1851    )?;
1852    ensure_eq(
1853        "stable schema_version",
1854        header.schema_version,
1855        PAYLOAD_CONTRACT_FIXTURE_VERSION,
1856    )
1857}
1858
1859fn validate_stable_owned_samples(
1860    case: &PayloadContractCase,
1861    sequence: u32,
1862    bytes: &[u8],
1863) -> Result<(), UWireError> {
1864    match case.kind {
1865        PayloadContractCaseKind::CanClassicMax => {
1866            validate_stable_owned_header(
1867                case,
1868                sequence,
1869                bytes,
1870                mem::offset_of!(CanClassicFrameV1, header),
1871            )?;
1872            ensure_eq(
1873                "owned CAN len",
1874                read_u8(bytes, mem::offset_of!(CanClassicFrameV1, len))?,
1875                8,
1876            )?;
1877            validate_owned_pattern_field(
1878                case,
1879                sequence,
1880                bytes,
1881                mem::offset_of!(CanClassicFrameV1, data),
1882                8,
1883                true,
1884            )?;
1885            ensure_eq(
1886                "owned CAN checksum",
1887                read_u32(bytes, mem::offset_of!(CanClassicFrameV1, checksum))?,
1888                fixture_checksum(case, sequence),
1889            )
1890        }
1891        PayloadContractCaseKind::CanFdMax => {
1892            validate_stable_owned_header(
1893                case,
1894                sequence,
1895                bytes,
1896                mem::offset_of!(CanFdFrameV1, header),
1897            )?;
1898            ensure_eq(
1899                "owned CAN FD len",
1900                read_u8(bytes, mem::offset_of!(CanFdFrameV1, len))?,
1901                64,
1902            )?;
1903            validate_owned_pattern_field(
1904                case,
1905                sequence,
1906                bytes,
1907                mem::offset_of!(CanFdFrameV1, data),
1908                64,
1909                true,
1910            )?;
1911            ensure_eq(
1912                "owned CAN FD checksum",
1913                read_u32(bytes, mem::offset_of!(CanFdFrameV1, checksum))?,
1914                fixture_checksum(case, sequence),
1915            )
1916        }
1917        PayloadContractCaseKind::SomeIpSingleMtu => {
1918            validate_stable_owned_header(
1919                case,
1920                sequence,
1921                bytes,
1922                mem::offset_of!(SomeIpSignalBatchMtuV1, header),
1923            )?;
1924            ensure_eq(
1925                "owned SOME/IP sample count",
1926                read_u32(bytes, mem::offset_of!(SomeIpSignalBatchMtuV1, sample_count))?,
1927                SOMEIP_MTU_SIGNAL_SAMPLE_COUNT as u32,
1928            )?;
1929            let samples_offset = mem::offset_of!(SomeIpSignalBatchMtuV1, samples);
1930            for index in sample_indices(SOMEIP_MTU_SIGNAL_SAMPLE_COUNT) {
1931                let offset = samples_offset + index * mem::size_of::<SignalSampleV1>();
1932                ensure_eq(
1933                    "owned SOME/IP signal id",
1934                    read_u32(bytes, offset + mem::offset_of!(SignalSampleV1, signal_id))?,
1935                    signal_sample_value(sequence, index).signal_id,
1936                )?;
1937            }
1938            Ok(())
1939        }
1940        PayloadContractCaseKind::Streamer4k => validate_stable_owned_stream(
1941            case,
1942            sequence,
1943            bytes,
1944            mem::offset_of!(StreamChunk4kV1, meta),
1945            mem::offset_of!(StreamChunk4kV1, chunk),
1946            4 * 1_024,
1947        ),
1948        PayloadContractCaseKind::RadarArs548DetectionList => {
1949            validate_stable_owned_header(
1950                case,
1951                sequence,
1952                bytes,
1953                mem::offset_of!(RadarDetectionListArs548V1, header),
1954            )?;
1955            ensure_eq(
1956                "owned radar count",
1957                read_u32(
1958                    bytes,
1959                    mem::offset_of!(RadarDetectionListArs548V1, list_numofdetections),
1960                )?,
1961                RADAR_ARS548_MAX_DETECTIONS as u32,
1962            )?;
1963            let detections_offset = mem::offset_of!(RadarDetectionListArs548V1, detections);
1964            for index in sample_indices(RADAR_ARS548_MAX_DETECTIONS) {
1965                let offset = detections_offset + index * mem::size_of::<Ars548DetectionV1>();
1966                ensure_eq(
1967                    "owned radar range",
1968                    read_f32(bytes, offset + mem::offset_of!(Ars548DetectionV1, range))?,
1969                    radar_detection_value(sequence, index).range,
1970                )?;
1971            }
1972            Ok(())
1973        }
1974        PayloadContractCaseKind::Streamer64k => validate_stable_owned_stream(
1975            case,
1976            sequence,
1977            bytes,
1978            mem::offset_of!(StreamChunk64kV1, meta),
1979            mem::offset_of!(StreamChunk64kV1, chunk),
1980            64 * 1_024,
1981        ),
1982        #[cfg(feature = "payload-contract-large-fixtures")]
1983        PayloadContractCaseKind::LidarHesaiAt128PointCloud => {
1984            validate_stable_owned_header(
1985                case,
1986                sequence,
1987                bytes,
1988                mem::offset_of!(LidarPointCloudHesaiAt128V1, header),
1989            )?;
1990            ensure_eq(
1991                "owned lidar point count",
1992                read_u32(
1993                    bytes,
1994                    mem::offset_of!(LidarPointCloudHesaiAt128V1, point_count),
1995                )?,
1996                LIDAR_HESAI_AT128_POINT_COUNT as u32,
1997            )?;
1998            let points_offset = mem::offset_of!(LidarPointCloudHesaiAt128V1, points);
1999            for index in sample_indices(LIDAR_HESAI_AT128_POINT_COUNT) {
2000                let offset = points_offset + index * mem::size_of::<LidarPointXyzircaedtV1>();
2001                ensure_eq(
2002                    "owned lidar x",
2003                    read_f32(bytes, offset + mem::offset_of!(LidarPointXyzircaedtV1, x))?,
2004                    lidar_point_value(sequence, index).x,
2005                )?;
2006            }
2007            Ok(())
2008        }
2009        #[cfg(feature = "payload-contract-large-fixtures")]
2010        PayloadContractCaseKind::Camera8mpBayerRggb12p => {
2011            validate_stable_owned_header(
2012                case,
2013                sequence,
2014                bytes,
2015                mem::offset_of!(CameraBayerRggb12pFrame8mpV1, header),
2016            )?;
2017            ensure_eq(
2018                "owned camera width",
2019                read_u32(bytes, mem::offset_of!(CameraBayerRggb12pFrame8mpV1, width))?,
2020                CAMERA_8MP_WIDTH,
2021            )?;
2022            validate_owned_pattern_field(
2023                case,
2024                sequence,
2025                bytes,
2026                mem::offset_of!(CameraBayerRggb12pFrame8mpV1, pixels),
2027                CAMERA_BAYER_RGGB12P_BYTES,
2028                false,
2029            )
2030        }
2031        #[cfg(feature = "payload-contract-simulator-fixtures")]
2032        PayloadContractCaseKind::Camera8mpCarlaBgra32 => Ok(()),
2033    }
2034}
2035
2036fn validate_stable_owned_header(
2037    case: &PayloadContractCase,
2038    sequence: u32,
2039    bytes: &[u8],
2040    offset: usize,
2041) -> Result<(), UWireError> {
2042    ensure_eq(
2043        "owned stable case_id",
2044        read_u32(bytes, offset)?,
2045        case.case_id,
2046    )?;
2047    ensure_eq(
2048        "owned stable sequence",
2049        read_u32(bytes, offset + 4)?,
2050        sequence,
2051    )?;
2052    ensure_eq(
2053        "owned stable semantic length",
2054        read_u32(bytes, offset + 8)?,
2055        case.semantic_reference_len as u32,
2056    )?;
2057    ensure_eq(
2058        "owned stable schema version",
2059        read_u32(bytes, offset + 12)?,
2060        PAYLOAD_CONTRACT_FIXTURE_VERSION,
2061    )
2062}
2063
2064fn validate_stable_owned_stream(
2065    case: &PayloadContractCase,
2066    sequence: u32,
2067    bytes: &[u8],
2068    meta_offset: usize,
2069    chunk_offset: usize,
2070    chunk_len: usize,
2071) -> Result<(), UWireError> {
2072    ensure_eq(
2073        "owned stream id",
2074        read_u64(
2075            bytes,
2076            meta_offset + mem::offset_of!(StreamChunkHeaderV1, stream_id),
2077        )?,
2078        STREAM_ID,
2079    )?;
2080    ensure_eq(
2081        "owned stream checksum",
2082        read_u32(
2083            bytes,
2084            meta_offset + mem::offset_of!(StreamChunkHeaderV1, checksum),
2085        )?,
2086        fixture_checksum(case, sequence),
2087    )?;
2088    validate_owned_pattern_field(case, sequence, bytes, chunk_offset, chunk_len, false)
2089}
2090
2091fn validate_owned_pattern_field(
2092    case: &PayloadContractCase,
2093    sequence: u32,
2094    bytes: &[u8],
2095    offset: usize,
2096    len: usize,
2097    exhaustive: bool,
2098) -> Result<(), UWireError> {
2099    if exhaustive {
2100        for index in 0..len {
2101            ensure_eq(
2102                "owned pattern byte",
2103                read_u8(bytes, offset + index)?,
2104                pattern_byte(case.case_id, sequence, index),
2105            )?;
2106        }
2107    } else {
2108        for index in sample_indices(len) {
2109            ensure_eq(
2110                "owned pattern byte",
2111                read_u8(bytes, offset + index)?,
2112                pattern_byte(case.case_id, sequence, index),
2113            )?;
2114        }
2115    }
2116    Ok(())
2117}
2118
2119fn validate_someip_fields(
2120    source_timestamp_ns: u64,
2121    length: u32,
2122    sample_count: u32,
2123    service_id: u16,
2124    method_or_event_id: u16,
2125    client_id: u16,
2126    session_id: u16,
2127    protocol_version: u8,
2128    interface_version: u8,
2129    message_type: u8,
2130    return_code: u8,
2131    checksum: u32,
2132    case: &PayloadContractCase,
2133    sequence: u32,
2134) -> Result<(), UWireError> {
2135    ensure_eq(
2136        "SOME/IP timestamp",
2137        source_timestamp_ns,
2138        timestamp_ns(sequence),
2139    )?;
2140    ensure_eq(
2141        "SOME/IP length",
2142        length,
2143        SOMEIP_IPV4_UDP_SINGLE_MTU_BUDGET as u32,
2144    )?;
2145    ensure_eq(
2146        "SOME/IP sample count",
2147        sample_count,
2148        SOMEIP_MTU_SIGNAL_SAMPLE_COUNT as u32,
2149    )?;
2150    ensure_eq("SOME/IP service_id", service_id, SOMEIP_SERVICE_ID)?;
2151    ensure_eq(
2152        "SOME/IP method_or_event_id",
2153        method_or_event_id,
2154        SOMEIP_METHOD_OR_EVENT_ID,
2155    )?;
2156    ensure_eq("SOME/IP client_id", client_id, SOMEIP_CLIENT_ID)?;
2157    ensure_eq("SOME/IP session_id", session_id, SOMEIP_SESSION_ID)?;
2158    ensure_eq(
2159        "SOME/IP protocol_version",
2160        protocol_version,
2161        SOMEIP_PROTOCOL_VERSION,
2162    )?;
2163    ensure_eq(
2164        "SOME/IP interface_version",
2165        interface_version,
2166        SOMEIP_INTERFACE_VERSION,
2167    )?;
2168    ensure_eq("SOME/IP message_type", message_type, SOMEIP_MESSAGE_TYPE)?;
2169    ensure_eq("SOME/IP return_code", return_code, SOMEIP_RETURN_CODE)?;
2170    ensure_eq(
2171        "SOME/IP checksum",
2172        checksum,
2173        fixture_checksum(case, sequence),
2174    )
2175}
2176
2177fn validate_signal_sample(
2178    actual: &SignalSampleV1,
2179    sequence: u32,
2180    index: usize,
2181) -> Result<(), UWireError> {
2182    let expected = signal_sample_value(sequence, index);
2183    ensure_eq("SOME/IP signal_id", actual.signal_id, expected.signal_id)?;
2184    ensure_eq("SOME/IP status", actual.status, expected.status)?;
2185    ensure_eq(
2186        "SOME/IP timestamp",
2187        actual.timestamp_ns,
2188        expected.timestamp_ns,
2189    )?;
2190    ensure_eq("SOME/IP value", actual.value, expected.value)
2191}
2192
2193fn validate_stream_meta(
2194    actual: &StreamChunkHeaderV1,
2195    case: &PayloadContractCase,
2196    sequence: u32,
2197    chunk_len: usize,
2198) -> Result<(), UWireError> {
2199    validate_header(&actual.header, case, sequence)?;
2200    validate_stream_fields(
2201        actual.stream_id,
2202        actual.codec,
2203        actual.flags,
2204        actual.byte_offset,
2205        actual.chunk_index,
2206        actual.chunk_count,
2207        actual.source_timestamp_ns,
2208        actual.checksum,
2209        case,
2210        sequence,
2211        chunk_len,
2212    )
2213}
2214
2215fn validate_stream_fields(
2216    stream_id: u64,
2217    codec: u32,
2218    flags: u32,
2219    byte_offset: u64,
2220    chunk_index: u32,
2221    chunk_count: u32,
2222    source_timestamp_ns: u64,
2223    checksum: u32,
2224    case: &PayloadContractCase,
2225    sequence: u32,
2226    chunk_len: usize,
2227) -> Result<(), UWireError> {
2228    ensure_eq("stream id", stream_id, STREAM_ID)?;
2229    ensure_eq("stream codec", codec, STREAM_CODEC_RAW)?;
2230    ensure_eq("stream flags", flags, STREAM_FLAG_KEY_FRAME)?;
2231    ensure_eq(
2232        "stream byte offset",
2233        byte_offset,
2234        u64::from(sequence).saturating_mul(chunk_len as u64),
2235    )?;
2236    ensure_eq("stream chunk index", chunk_index, sequence)?;
2237    ensure_eq(
2238        "stream chunk count",
2239        chunk_count,
2240        sequence.saturating_add(7),
2241    )?;
2242    ensure_eq(
2243        "stream timestamp",
2244        source_timestamp_ns,
2245        timestamp_ns(sequence),
2246    )?;
2247    ensure_eq(
2248        "stream checksum",
2249        checksum,
2250        fixture_checksum(case, sequence),
2251    )
2252}
2253
2254fn validate_radar_detection(
2255    actual: &Ars548DetectionV1,
2256    sequence: u32,
2257    index: usize,
2258) -> Result<(), UWireError> {
2259    let expected = radar_detection_value(sequence, index);
2260    ensure_eq(
2261        "radar azimuth",
2262        actual.azimuth_angle,
2263        expected.azimuth_angle,
2264    )?;
2265    ensure_eq(
2266        "radar elevation",
2267        actual.elevation_angle,
2268        expected.elevation_angle,
2269    )?;
2270    ensure_eq("radar range", actual.range, expected.range)?;
2271    ensure_eq("radar range rate", actual.range_rate, expected.range_rate)?;
2272    ensure_eq(
2273        "radar measurement id",
2274        actual.measurement_id,
2275        expected.measurement_id,
2276    )?;
2277    ensure_eq("radar object id", actual.object_id, expected.object_id)?;
2278    ensure_eq("radar sort index", actual.sort_index, expected.sort_index)
2279}
2280
2281#[cfg(feature = "payload-contract-large-fixtures")]
2282#[allow(clippy::too_many_arguments)]
2283fn validate_lidar_fields(
2284    frame_counter: u64,
2285    sensor_timestamp_ns: u64,
2286    scan_duration_ns: u32,
2287    lidar_id: u32,
2288    width: u32,
2289    height: u32,
2290    point_count: u32,
2291    point_step: u32,
2292    row_step: u32,
2293    points_per_second: u32,
2294    horizontal_fov_mdeg: u32,
2295    vertical_fov_mdeg: u32,
2296    horizontal_resolution_mdeg: u32,
2297    vertical_resolution_mdeg: u32,
2298    range_10pct_reflectivity_m: u32,
2299    point_format: u32,
2300    is_bigendian: u8,
2301    is_dense: u8,
2302    checksum: u32,
2303    case: &PayloadContractCase,
2304    sequence: u32,
2305) -> Result<(), UWireError> {
2306    ensure_eq("lidar frame_counter", frame_counter, u64::from(sequence))?;
2307    ensure_eq(
2308        "lidar timestamp",
2309        sensor_timestamp_ns,
2310        timestamp_ns(sequence),
2311    )?;
2312    ensure_eq(
2313        "lidar scan_duration_ns",
2314        scan_duration_ns,
2315        LIDAR_HESAI_AT128_SCAN_DURATION_NS,
2316    )?;
2317    ensure_eq("lidar id", lidar_id, LIDAR_ID)?;
2318    ensure_eq("lidar width", width, LIDAR_HESAI_AT128_WIDTH)?;
2319    ensure_eq("lidar height", height, LIDAR_HESAI_AT128_HEIGHT)?;
2320    ensure_eq(
2321        "lidar point_count",
2322        point_count,
2323        LIDAR_HESAI_AT128_POINT_COUNT as u32,
2324    )?;
2325    ensure_eq(
2326        "lidar point_step",
2327        point_step,
2328        LIDAR_XYZIRCAEDT_POINT_BYTES as u32,
2329    )?;
2330    ensure_eq("lidar row_step", row_step, LIDAR_HESAI_AT128_ROW_STEP_BYTES)?;
2331    ensure_eq(
2332        "lidar points_per_second",
2333        points_per_second,
2334        LIDAR_HESAI_AT128_POINTS_PER_SECOND,
2335    )?;
2336    ensure_eq(
2337        "lidar horizontal_fov",
2338        horizontal_fov_mdeg,
2339        LIDAR_HESAI_AT128_HORIZONTAL_FOV_MDEG,
2340    )?;
2341    ensure_eq(
2342        "lidar vertical_fov",
2343        vertical_fov_mdeg,
2344        LIDAR_HESAI_AT128_VERTICAL_FOV_MDEG,
2345    )?;
2346    ensure_eq(
2347        "lidar horizontal_resolution",
2348        horizontal_resolution_mdeg,
2349        LIDAR_HESAI_AT128_HORIZONTAL_RESOLUTION_MDEG,
2350    )?;
2351    ensure_eq(
2352        "lidar vertical_resolution",
2353        vertical_resolution_mdeg,
2354        LIDAR_HESAI_AT128_VERTICAL_RESOLUTION_MDEG,
2355    )?;
2356    ensure_eq(
2357        "lidar range reference",
2358        range_10pct_reflectivity_m,
2359        LIDAR_HESAI_AT128_RANGE_10PCT_REFLECTIVITY_M,
2360    )?;
2361    ensure_eq(
2362        "lidar point_format",
2363        point_format,
2364        LIDAR_POINT_FORMAT_XYZIRCAEDT,
2365    )?;
2366    ensure_eq("lidar is_bigendian", is_bigendian, 0)?;
2367    ensure_eq("lidar is_dense", is_dense, 1)?;
2368    ensure_eq("lidar checksum", checksum, fixture_checksum(case, sequence))
2369}
2370
2371#[cfg(feature = "payload-contract-large-fixtures")]
2372fn validate_lidar_point(
2373    actual: &LidarPointXyzircaedtV1,
2374    sequence: u32,
2375    index: usize,
2376) -> Result<(), UWireError> {
2377    let expected = lidar_point_value(sequence, index);
2378    ensure_eq("lidar x", actual.x, expected.x)?;
2379    ensure_eq("lidar y", actual.y, expected.y)?;
2380    ensure_eq("lidar z", actual.z, expected.z)?;
2381    ensure_eq("lidar intensity", actual.intensity, expected.intensity)?;
2382    ensure_eq("lidar channel", actual.channel, expected.channel)?;
2383    ensure_eq("lidar distance", actual.distance, expected.distance)
2384}
2385
2386#[cfg(feature = "payload-contract-large-fixtures")]
2387#[allow(clippy::too_many_arguments)]
2388fn validate_camera_fields(
2389    frame_counter: u64,
2390    sensor_timestamp_ns: u64,
2391    exposure_start_ns: u64,
2392    camera_id: u32,
2393    width: u32,
2394    height: u32,
2395    stride_bytes: u32,
2396    bayer_pattern: u32,
2397    bits_per_sample: u32,
2398    packed_layout: u32,
2399    exposure_time_us: u32,
2400    analog_gain: f32,
2401    digital_gain: f32,
2402    checksum: u32,
2403    case: &PayloadContractCase,
2404    sequence: u32,
2405) -> Result<(), UWireError> {
2406    ensure_eq("camera frame_counter", frame_counter, u64::from(sequence))?;
2407    ensure_eq(
2408        "camera timestamp",
2409        sensor_timestamp_ns,
2410        timestamp_ns(sequence),
2411    )?;
2412    ensure_eq(
2413        "camera exposure_start",
2414        exposure_start_ns,
2415        timestamp_ns(sequence).saturating_sub(1_000_000),
2416    )?;
2417    ensure_eq("camera id", camera_id, CAMERA_ID)?;
2418    ensure_eq("camera width", width, CAMERA_8MP_WIDTH)?;
2419    ensure_eq("camera height", height, CAMERA_8MP_HEIGHT)?;
2420    ensure_eq(
2421        "camera stride",
2422        stride_bytes,
2423        CAMERA_BAYER_RGGB12P_STRIDE_BYTES,
2424    )?;
2425    ensure_eq("camera bayer pattern", bayer_pattern, BAYER_PATTERN_RGGB)?;
2426    ensure_eq("camera bits", bits_per_sample, BITS_PER_SAMPLE_12)?;
2427    ensure_eq("camera packed layout", packed_layout, PACKED_LAYOUT_12P_LSB)?;
2428    ensure_eq("camera exposure time", exposure_time_us, 8_000)?;
2429    ensure_eq("camera analog gain", analog_gain, 1.5)?;
2430    ensure_eq("camera digital gain", digital_gain, 1.0)?;
2431    ensure_eq(
2432        "camera checksum",
2433        checksum,
2434        fixture_checksum(case, sequence),
2435    )
2436}
2437
2438#[cfg(feature = "payload-contract-large-fixtures")]
2439fn validate_camera_intrinsics(actual: &CameraIntrinsicsV1) -> Result<(), UWireError> {
2440    ensure_eq("camera fx", actual.fx, 1_950.0)?;
2441    ensure_eq("camera fy", actual.fy, 1_950.0)?;
2442    ensure_eq("camera cx", actual.cx, 1_920.0)?;
2443    ensure_eq("camera cy", actual.cy, 1_080.0)?;
2444    ensure_eq("camera distortion model", actual.distortion_model, 1)
2445}
2446
2447#[cfg(feature = "payload-contract-large-fixtures")]
2448fn validate_pose(actual: &Pose3dV1, sequence: u32) -> Result<(), UWireError> {
2449    let expected = pose_value(sequence);
2450    ensure_eq("pose x", actual.translation_m.x, expected.translation_m.x)?;
2451    ensure_eq("pose y", actual.translation_m.y, expected.translation_m.y)?;
2452    ensure_eq("pose z", actual.translation_m.z, expected.translation_m.z)?;
2453    ensure_eq("pose w", actual.rotation.w, expected.rotation.w)
2454}
2455
2456fn validate_all_pattern_bytes(
2457    case: &PayloadContractCase,
2458    sequence: u32,
2459    bytes: &[u8],
2460) -> Result<(), UWireError> {
2461    for (index, byte) in bytes.iter().copied().enumerate() {
2462        ensure_eq(
2463            "pattern byte",
2464            byte,
2465            pattern_byte(case.case_id, sequence, index),
2466        )?;
2467    }
2468    Ok(())
2469}
2470
2471fn validate_sample_pattern_bytes(
2472    case: &PayloadContractCase,
2473    sequence: u32,
2474    bytes: &[u8],
2475) -> Result<(), UWireError> {
2476    ensure_eq("pattern length", bytes.len(), case.semantic_reference_len)?;
2477    for index in sample_indices(bytes.len()) {
2478        let actual = bytes
2479            .get(index)
2480            .copied()
2481            .ok_or_else(|| UWireError::invalid_payload("missing sample byte"))?;
2482        ensure_eq(
2483            "pattern sample byte",
2484            actual,
2485            pattern_byte(case.case_id, sequence, index),
2486        )?;
2487    }
2488    Ok(())
2489}
2490
2491fn signal_sample_value(sequence: u32, index: usize) -> SignalSampleV1 {
2492    SignalSampleV1 {
2493        signal_id: 0x5000 + index as u32,
2494        status: (index as u32) & 0x3,
2495        timestamp_ns: timestamp_ns(sequence).saturating_add(index as u64),
2496        value: f64::from(sequence) + index as f64 * 0.25,
2497    }
2498}
2499
2500fn radar_detection_value(sequence: u32, index: usize) -> Ars548DetectionV1 {
2501    let base = sequence as f32 + index as f32;
2502    Ars548DetectionV1 {
2503        azimuth_angle: -0.6 + base * 0.0001,
2504        azimuth_angle_std: 0.01,
2505        elevation_angle: -0.2 + base * 0.00005,
2506        elevation_angle_std: 0.02,
2507        range: 2.0 + base * 0.1,
2508        range_std: 0.05,
2509        range_rate: -15.0 + index as f32 * 0.01,
2510        range_rate_std: 0.03,
2511        measurement_id: index as u16,
2512        object_id: (index % 256) as u16,
2513        sort_index: (RADAR_ARS548_MAX_DETECTIONS - index - 1) as u16,
2514        invalid_flags: (index & 0x3) as u8,
2515        rcs: ((index % 64) as i8) - 32,
2516        positive_predictive_value: (80 + index % 20) as u8,
2517        classification: (index % 8) as u8,
2518        multi_target_probability: (index % 100) as u8,
2519        ambiguity_flag: (index % 2) as u8,
2520    }
2521}
2522
2523#[cfg(feature = "payload-contract-large-fixtures")]
2524fn lidar_point_value(sequence: u32, index: usize) -> LidarPointXyzircaedtV1 {
2525    let column = (index as u32) % LIDAR_HESAI_AT128_WIDTH;
2526    let row = (index as u32) / LIDAR_HESAI_AT128_WIDTH;
2527    let azimuth = -60.0 + column as f32 * 0.1;
2528    let elevation = -12.7 + row as f32 * 0.2;
2529    let distance = 5.0 + (index % 2_000) as f32 * 0.05;
2530    LidarPointXyzircaedtV1 {
2531        x: distance * azimuth.to_radians().cos(),
2532        y: distance * azimuth.to_radians().sin(),
2533        z: distance * elevation.to_radians().sin(),
2534        intensity: pattern_byte(7, sequence, index),
2535        return_type: 1,
2536        channel: row as u16,
2537        azimuth,
2538        elevation,
2539        distance,
2540        time_offset_ns: ((index as u64 * u64::from(LIDAR_HESAI_AT128_SCAN_DURATION_NS))
2541            / LIDAR_HESAI_AT128_POINT_COUNT as u64) as u32,
2542    }
2543}
2544
2545#[cfg(feature = "payload-contract-large-fixtures")]
2546fn pose_value(sequence: u32) -> Pose3dV1 {
2547    Pose3dV1 {
2548        translation_m: Vector3fV1 {
2549            x: 1.0 + sequence as f32 * 0.01,
2550            y: 0.5,
2551            z: 1.2,
2552        },
2553        rotation: QuaternionfV1 {
2554            x: 0.0,
2555            y: 0.0,
2556            z: 0.0,
2557            w: 1.0,
2558        },
2559    }
2560}
2561
2562#[cfg(feature = "payload-contract-large-fixtures")]
2563fn write_lidar_point_le(bytes: &mut [u8], sequence: u32, index: usize) {
2564    let point = lidar_point_value(sequence, index);
2565    let offset = index * LIDAR_XYZIRCAEDT_POINT_BYTES;
2566    bytes[offset..offset + 4].copy_from_slice(&point.x.to_le_bytes());
2567    bytes[offset + 4..offset + 8].copy_from_slice(&point.y.to_le_bytes());
2568    bytes[offset + 8..offset + 12].copy_from_slice(&point.z.to_le_bytes());
2569    bytes[offset + 12] = point.intensity;
2570    bytes[offset + 13] = point.return_type;
2571    bytes[offset + 14..offset + 16].copy_from_slice(&point.channel.to_le_bytes());
2572    bytes[offset + 16..offset + 20].copy_from_slice(&point.azimuth.to_le_bytes());
2573    bytes[offset + 20..offset + 24].copy_from_slice(&point.elevation.to_le_bytes());
2574    bytes[offset + 24..offset + 28].copy_from_slice(&point.distance.to_le_bytes());
2575    bytes[offset + 28..offset + 32].copy_from_slice(&point.time_offset_ns.to_le_bytes());
2576}
2577
2578#[cfg(feature = "payload-contract-large-fixtures")]
2579fn validate_lidar_point_le(bytes: &[u8], sequence: u32, index: usize) -> Result<(), UWireError> {
2580    let offset = index * LIDAR_XYZIRCAEDT_POINT_BYTES;
2581    let expected = lidar_point_value(sequence, index);
2582    ensure_eq("protobuf lidar x", read_f32_le(bytes, offset)?, expected.x)?;
2583    ensure_eq(
2584        "protobuf lidar intensity",
2585        read_u8(bytes, offset + 12)?,
2586        expected.intensity,
2587    )?;
2588    ensure_eq(
2589        "protobuf lidar channel",
2590        read_u16_le(bytes, offset + 14)?,
2591        expected.channel,
2592    )?;
2593    ensure_eq(
2594        "protobuf lidar distance",
2595        read_f32_le(bytes, offset + 24)?,
2596        expected.distance,
2597    )
2598}
2599
2600fn sample_indices(len: usize) -> [usize; 3] {
2601    if len == 0 {
2602        [0, 0, 0]
2603    } else {
2604        [0, len / 2, len - 1]
2605    }
2606}
2607
2608fn pattern_vec(case_id: u32, sequence: u32, len: usize) -> Vec<u8> {
2609    (0..len)
2610        .map(|index| pattern_byte(case_id, sequence, index))
2611        .collect()
2612}
2613
2614fn pattern_byte(case_id: u32, sequence: u32, index: usize) -> u8 {
2615    let value = (index as u32)
2616        .wrapping_mul(31)
2617        .wrapping_add(sequence.wrapping_mul(17))
2618        .wrapping_add(case_id.wrapping_mul(13));
2619    value as u8
2620}
2621
2622fn fixture_checksum(case: &PayloadContractCase, sequence: u32) -> u32 {
2623    0xa5a5_0000
2624        ^ case.case_id.rotate_left(3)
2625        ^ sequence.rotate_left(11)
2626        ^ (case.semantic_reference_len as u32)
2627}
2628
2629fn timestamp_ns(sequence: u32) -> u64 {
2630    1_700_000_000_000_000_000_u64 + u64::from(sequence) * 1_000_000
2631}
2632
2633fn ensure(condition: &'static str, value: bool) -> Result<(), UWireError> {
2634    if value {
2635        Ok(())
2636    } else {
2637        Err(UWireError::invalid_payload(condition))
2638    }
2639}
2640
2641fn ensure_eq<T>(name: &'static str, actual: T, expected: T) -> Result<(), UWireError>
2642where
2643    T: PartialEq + Debug,
2644{
2645    if actual == expected {
2646        Ok(())
2647    } else {
2648        Err(UWireError::invalid_payload(format!(
2649            "{name} mismatch: expected {expected:?}, got {actual:?}"
2650        )))
2651    }
2652}
2653
2654fn read_u8(bytes: &[u8], offset: usize) -> Result<u8, UWireError> {
2655    bytes
2656        .get(offset)
2657        .copied()
2658        .ok_or_else(|| UWireError::invalid_payload("stable owned byte offset out of bounds"))
2659}
2660
2661#[cfg(feature = "payload-contract-large-fixtures")]
2662fn read_u16_le(bytes: &[u8], offset: usize) -> Result<u16, UWireError> {
2663    let mut value = [0_u8; 2];
2664    value.copy_from_slice(read_exact(bytes, offset, 2)?);
2665    Ok(u16::from_le_bytes(value))
2666}
2667
2668fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, UWireError> {
2669    let mut value = [0_u8; 4];
2670    value.copy_from_slice(read_exact(bytes, offset, 4)?);
2671    Ok(u32::from_ne_bytes(value))
2672}
2673
2674fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, UWireError> {
2675    let mut value = [0_u8; 8];
2676    value.copy_from_slice(read_exact(bytes, offset, 8)?);
2677    Ok(u64::from_ne_bytes(value))
2678}
2679
2680fn read_f32(bytes: &[u8], offset: usize) -> Result<f32, UWireError> {
2681    let mut value = [0_u8; 4];
2682    value.copy_from_slice(read_exact(bytes, offset, 4)?);
2683    Ok(f32::from_ne_bytes(value))
2684}
2685
2686#[cfg(feature = "payload-contract-large-fixtures")]
2687fn read_f32_le(bytes: &[u8], offset: usize) -> Result<f32, UWireError> {
2688    let mut value = [0_u8; 4];
2689    value.copy_from_slice(read_exact(bytes, offset, 4)?);
2690    Ok(f32::from_le_bytes(value))
2691}
2692
2693fn read_exact(bytes: &[u8], offset: usize, len: usize) -> Result<&[u8], UWireError> {
2694    bytes
2695        .get(offset..offset + len)
2696        .ok_or_else(|| UWireError::invalid_payload("stable owned byte range out of bounds"))
2697}