1#![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 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 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}