1use std::{
68 fmt::Display,
69 marker::PhantomData,
70 mem,
71 ptr::{self, NonNull},
72};
73
74use mediatype::ReadParams;
75
76#[cfg(feature = "zero-copy-transport")]
77use crate::zero_copy::LoanedPayloadUninitMut;
78use crate::PayloadEncoding;
79
80#[cfg(feature = "zero-copy-transport")]
81use super::loan::{LoanUninitPayload, LoanedUninitPayload};
82use super::{
83 codec::{PayloadCodec, PayloadLayout},
84 loan::{BorrowPayload, LoanPayload},
85 UWireError,
86};
87
88const STABLE_CONTAINER_ENCODING_ID: &str = "up.stable-container";
89const STABLE_CONTAINER_MEDIA_TYPE: &str = "application/vnd.uprotocol.stable-container";
90
91#[must_use = "return this completion witness to the transmit path or consume it explicitly"]
97pub struct InitializedStablePayload<'a, T> {
98 _marker: PhantomData<fn(&'a mut T) -> &'a mut T>,
99}
100
101impl<'a, T> core::fmt::Debug for InitializedStablePayload<'a, T> {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103 f.debug_struct("InitializedStablePayload")
104 .finish_non_exhaustive()
105 }
106}
107
108impl<'a, T> InitializedStablePayload<'a, T> {
109 #[must_use = "return this completion witness to the transmit path or consume it explicitly"]
116 #[inline(always)]
117 unsafe fn new_unchecked() -> Self {
118 Self {
119 _marker: PhantomData,
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
129pub enum StablePayloadVariant {
130 FixedSize,
132}
133
134impl StablePayloadVariant {
135 #[must_use]
137 pub const fn as_str(self) -> &'static str {
138 match self {
139 Self::FixedSize => "fixed",
140 }
141 }
142
143 fn parse(value: &str) -> Option<Self> {
144 match value {
145 "fixed" => Some(Self::FixedSize),
146 _ => None,
147 }
148 }
149}
150
151impl Display for StablePayloadVariant {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.write_str(self.as_str())
154 }
155}
156
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159pub struct StableTypeDetail<'a> {
160 pub variant: StablePayloadVariant,
162 pub type_name: &'a str,
164 pub size: usize,
166 pub alignment: usize,
168}
169
170impl Display for StableTypeDetail<'_> {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 write!(
173 f,
174 "type={};variant={};size={};align={}",
175 self.type_name, self.variant, self.size, self.alignment
176 )
177 }
178}
179
180#[doc(hidden)]
192pub unsafe trait StablePayloadField: Sized + 'static {
193 #[doc(hidden)]
194 fn __stable_payload_field_check(&self) {}
195}
196
197macro_rules! impl_stable_payload_field {
198 ($($ty:ty),* $(,)?) => {
199 $(
200 unsafe impl StablePayloadField for $ty {}
203 )*
204 };
205}
206
207impl_stable_payload_field!(
208 (),
209 bool,
210 char,
211 u8,
212 u16,
213 u32,
214 u64,
215 u128,
216 usize,
217 i8,
218 i16,
219 i32,
220 i64,
221 i128,
222 isize,
223 f32,
224 f64,
225);
226
227unsafe impl<T, const N: usize> StablePayloadField for [T; N]
230where
231 T: StablePayloadField,
232{
233 fn __stable_payload_field_check(&self) {
234 for item in self {
235 item.__stable_payload_field_check();
236 }
237 }
238}
239
240#[diagnostic::on_unimplemented(
252 message = "`{Self}` does not implement the stable-payload contract",
253 label = "this type cannot be carried as a stable payload",
254 note = "add `#[repr(C)]`, derive `StablePayload`, and provide `#[stable_payload(type_name = \"...\")]`",
255 note = "derive `ByteBackedStablePayload` only when every field and byte representation satisfies its stronger safety contract"
256)]
257pub unsafe trait StablePayload: Sized + 'static {
258 const VARIANT: StablePayloadVariant = StablePayloadVariant::FixedSize;
260
261 const TYPE_NAME: &'static str;
263
264 #[must_use]
266 fn stable_type_name() -> &'static str {
267 Self::TYPE_NAME
268 }
269
270 #[must_use]
272 fn stable_type_detail() -> StableTypeDetail<'static> {
273 StableTypeDetail {
274 variant: Self::VARIANT,
275 type_name: Self::stable_type_name(),
276 size: mem::size_of::<Self>(),
277 alignment: mem::align_of::<Self>(),
278 }
279 }
280
281 #[doc(hidden)]
283 fn __stable_payload_field_check(&self) {}
284}
285
286mod byte_backed_stable_field_seal {
287 pub trait Sealed {}
288
289 macro_rules! impl_sealed_field {
290 ($($ty:ty),* $(,)?) => {
291 $(
292 impl Sealed for $ty {}
293 )*
294 };
295 }
296
297 impl_sealed_field!(
298 (),
299 bool,
300 char,
301 u8,
302 u16,
303 u32,
304 u64,
305 u128,
306 usize,
307 i8,
308 i16,
309 i32,
310 i64,
311 i128,
312 isize,
313 f32,
314 f64,
315 );
316
317 impl<T, const N: usize> Sealed for [T; N] where T: Sealed {}
318
319 impl<T> Sealed for T where T: super::ByteBackedStablePayload {}
320}
321
322#[doc(hidden)]
327#[allow(private_bounds)]
328#[diagnostic::on_unimplemented(
329 message = "`{Self}` is not a byte-backed stable-payload field",
330 label = "this field does not satisfy the recursive byte-backed contract",
331 note = "use a supported primitive/array field, or derive `ByteBackedStablePayload` for an eligible nested stable type"
332)]
333pub trait ByteBackedStablePayloadField:
334 byte_backed_stable_field_seal::Sealed + Sized + 'static
335{
336 #[doc(hidden)]
337 const SUPPORTS_BYTE_BACKED_STABLE_FIELD: bool;
338 #[doc(hidden)]
340 fn init_bytes(&self) -> &[u8] {
341 unsafe {
346 core::slice::from_raw_parts(
347 (self as *const Self).cast::<u8>(),
348 core::mem::size_of::<Self>(),
349 )
350 }
351 }
352}
353
354macro_rules! impl_byte_backed_stable_field {
355 ($($ty:ty),* $(,)?) => {
356 $(
357 impl ByteBackedStablePayloadField for $ty {
358 const SUPPORTS_BYTE_BACKED_STABLE_FIELD: bool = true;
359 }
360 )*
361 };
362}
363
364impl_byte_backed_stable_field!(
365 (),
366 bool,
367 char,
368 u8,
369 u16,
370 u32,
371 u64,
372 u128,
373 usize,
374 i8,
375 i16,
376 i32,
377 i64,
378 i128,
379 isize,
380 f32,
381 f64,
382);
383
384impl<T, const N: usize> ByteBackedStablePayloadField for [T; N]
385where
386 T: ByteBackedStablePayloadField,
387{
388 const SUPPORTS_BYTE_BACKED_STABLE_FIELD: bool = T::SUPPORTS_BYTE_BACKED_STABLE_FIELD;
389}
390
391impl<T> ByteBackedStablePayloadField for T
392where
393 T: ByteBackedStablePayload,
394{
395 const SUPPORTS_BYTE_BACKED_STABLE_FIELD: bool = true;
396}
397
398#[diagnostic::on_unimplemented(
417 message = "`{Self}` does not implement the byte-backed stable-payload contract",
418 label = "this type cannot use byte-backed stable-container paths",
419 note = "derive `ByteBackedStablePayload` only for an eligible `#[repr(C)]` type with no implicit padding and recursively byte-backed fields"
420)]
421pub unsafe trait ByteBackedStablePayload: StablePayload {
422 const SUPPORTS_BYTE_BACKED_UNINIT: bool = true;
425
426 #[doc(hidden)]
427 const BYTE_BACKED_STABLE_PAYLOAD_CHECK: () = {
428 assert!(Self::SUPPORTS_BYTE_BACKED_UNINIT);
429 assert!(!mem::needs_drop::<Self>());
430 };
431}
432
433#[must_use]
435pub const fn stable_payload_supports_byte_backed_uninit<T: ByteBackedStablePayload>() -> bool {
436 T::SUPPORTS_BYTE_BACKED_UNINIT && !mem::needs_drop::<T>()
437}
438
439pub const fn assert_stable_payload_byte_backed_uninit<T>()
441where
442 T: ByteBackedStablePayload,
443{
444 assert!(T::SUPPORTS_BYTE_BACKED_UNINIT);
445 assert!(!mem::needs_drop::<T>());
446 #[allow(path_statements)]
447 T::BYTE_BACKED_STABLE_PAYLOAD_CHECK;
448}
449
450mod stable_payload_init_complete_value_seal {
451 pub trait Sealed {}
452
453 impl<T> Sealed for T where T: super::ByteBackedStablePayload {}
454
455 impl<T, const N: usize> Sealed for [T; N] where T: super::ByteBackedStablePayloadField {}
456}
457
458#[doc(hidden)]
464#[allow(private_bounds)]
465pub trait StablePayloadInitCompleteValue<T>:
466 stable_payload_init_complete_value_seal::Sealed + Sized
467{
468 #[doc(hidden)]
469 fn into_complete_value(self) -> T;
470}
471
472impl<T> StablePayloadInitCompleteValue<T> for T
473where
474 T: ByteBackedStablePayload,
475{
476 fn into_complete_value(self) -> T {
477 self
478 }
479}
480
481impl<T, const N: usize> StablePayloadInitCompleteValue<[T; N]> for [T; N]
482where
483 T: ByteBackedStablePayloadField,
484{
485 fn into_complete_value(self) -> [T; N] {
486 self
487 }
488}
489
490#[doc(hidden)]
492#[derive(Debug)]
493pub enum StablePayloadInitUnset {}
494
495#[doc(hidden)]
497#[derive(Debug)]
498pub enum StablePayloadInitSet {}
499
500#[must_use = "consume this context to initialize the loaned stable-payload slot"]
507pub struct StablePayloadInitContext<'slot, T: StablePayloadInit> {
508 init: T::Init<'slot>,
509 _invariant: PhantomData<fn(&'slot mut T) -> &'slot mut T>,
510}
511
512impl<'slot, T: StablePayloadInit> core::fmt::Debug for StablePayloadInitContext<'slot, T> {
513 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
514 f.debug_struct("StablePayloadInitContext")
515 .finish_non_exhaustive()
516 }
517}
518
519impl<'slot, T: StablePayloadInit> StablePayloadInitContext<'slot, T> {
520 #[inline(always)]
521 pub(crate) fn new(init: T::Init<'slot>) -> Self {
522 Self {
523 init,
524 _invariant: PhantomData,
525 }
526 }
527
528 #[must_use]
530 #[inline(always)]
531 pub fn into_init(self) -> T::Init<'slot> {
532 self.init
533 }
534}
535
536#[diagnostic::on_unimplemented(
552 message = "`{Self}` has no stable-payload initializer",
553 label = "missing `StablePayloadInit` implementation",
554 note = "derive `StablePayloadInit` to get the typestate builder used by `send_uninit_stable_payload`"
555)]
556pub unsafe trait StablePayloadInit: StablePayload {
557 type Init<'a>;
559
560 fn init_from_uninit_bytes<'a>(
567 payload: &'a mut [mem::MaybeUninit<u8>],
568 ) -> Result<Self::Init<'a>, UWireError> {
569 let slot = StablePayloadInitSlot::<Self>::from_uninit_bytes(payload)?;
570 Self::__init_from_slot(slot)
571 }
572
573 #[cfg(feature = "zero-copy-transport")]
580 fn init_from_uninit_payload<'a>(
581 payload: LoanedPayloadUninitMut<'a>,
582 ) -> Result<Self::Init<'a>, UWireError> {
583 Self::init_from_uninit_bytes(payload.into_uninit_bytes_mut_internal())
584 }
585
586 #[doc(hidden)]
588 fn __init_from_slot<'a>(
589 slot: StablePayloadInitSlot<'a, Self>,
590 ) -> Result<Self::Init<'a>, UWireError>;
591
592 #[doc(hidden)]
594 #[inline(always)]
595 fn __init_context_from_slot<'a>(
596 slot: StablePayloadInitSlot<'a, Self>,
597 ) -> Result<StablePayloadInitContext<'a, Self>, UWireError> {
598 Self::__init_from_slot(slot).map(StablePayloadInitContext::new)
599 }
600}
601
602#[doc(hidden)]
608pub struct StablePayloadInitSlot<'a, T> {
609 bytes: &'a mut [mem::MaybeUninit<u8>],
615 _marker: PhantomData<&'a mut mem::MaybeUninit<T>>,
616}
617
618impl<'a, T> core::fmt::Debug for StablePayloadInitSlot<'a, T> {
619 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
620 f.debug_struct("StablePayloadInitSlot")
621 .finish_non_exhaustive()
622 }
623}
624
625impl<'a, T> StablePayloadInitSlot<'a, T> {
626 #[inline(always)]
628 fn from_uninit_bytes(bytes: &'a mut [mem::MaybeUninit<u8>]) -> Result<Self, UWireError>
629 where
630 T: StablePayload,
631 {
632 StableContainerPayload::<T>::check_uninit_layout(bytes)?;
633 Ok(Self {
634 bytes,
635 _marker: PhantomData,
636 })
637 }
638
639 #[inline(always)]
647 fn copy_to_uninit(dst: &mut [mem::MaybeUninit<u8>], src: &[u8]) {
648 debug_assert_eq!(dst.len(), src.len());
649 unsafe {
655 ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().cast::<u8>(), src.len());
656 }
657 }
658
659 #[inline(always)]
664 fn fill_uninit(dst: &mut [mem::MaybeUninit<u8>], value: u8) {
665 unsafe {
668 ptr::write_bytes(dst.as_mut_ptr().cast::<u8>(), value, dst.len());
669 }
670 }
671
672 #[inline(always)]
673 fn checked_range(&mut self, offset: usize, len: usize) -> &mut [mem::MaybeUninit<u8>] {
674 let end = offset
675 .checked_add(len)
676 .expect("stable payload field range overflow: layout and derive offsets disagree");
677 self.bytes
678 .get_mut(offset..end)
679 .expect("stable payload field write out of bounds: layout and derive offsets disagree")
680 }
681
682 #[doc(hidden)]
684 #[inline(always)]
685 pub fn write_padding(&mut self, offset: usize, len: usize) {
686 Self::fill_uninit(self.checked_range(offset, len), 0);
687 }
688
689 #[doc(hidden)]
691 #[inline(always)]
692 pub fn write_field<U: ByteBackedStablePayloadField>(&mut self, offset: usize, value: U) {
693 let src = value.init_bytes();
694 Self::copy_to_uninit(self.checked_range(offset, src.len()), src);
695 }
696
697 #[doc(hidden)]
699 #[inline(always)]
700 pub fn write_bytes(&mut self, offset: usize, src: &[u8]) {
701 Self::copy_to_uninit(self.checked_range(offset, src.len()), src);
702 }
703
704 #[doc(hidden)]
706 #[inline(always)]
707 pub fn fill_bytes(&mut self, offset: usize, len: usize, value: u8) {
708 Self::fill_uninit(self.checked_range(offset, len), value);
709 }
710
711 #[doc(hidden)]
713 #[inline(always)]
714 pub fn fill_bytes_with(
715 &mut self,
716 offset: usize,
717 len: usize,
718 mut value: impl FnMut(usize) -> u8,
719 ) {
720 for (index, dst) in self.checked_range(offset, len).iter_mut().enumerate() {
721 *dst = mem::MaybeUninit::new(value(index));
722 }
723 }
724
725 #[doc(hidden)]
727 #[inline(always)]
728 pub fn copy_array_from_slice<U: ByteBackedStablePayloadField>(
729 &mut self,
730 offset: usize,
731 src: &[U],
732 expected: usize,
733 ) -> Result<(), UWireError> {
734 if src.len() != expected {
735 return Err(UWireError::invalid_payload_length(expected, src.len()));
736 }
737 let byte_len = mem::size_of_val(src);
740 let src_bytes = unsafe { core::slice::from_raw_parts(src.as_ptr().cast::<u8>(), byte_len) };
744 Self::copy_to_uninit(self.checked_range(offset, byte_len), src_bytes);
745 Ok(())
746 }
747
748 #[doc(hidden)]
750 #[inline(always)]
751 pub fn fill_array<U: ByteBackedStablePayloadField>(
752 &mut self,
753 offset: usize,
754 len: usize,
755 value: U,
756 ) {
757 let elem = mem::size_of::<U>();
758 let byte_len = elem
759 .checked_mul(len)
760 .expect("stable payload array fill length overflow");
761 if elem == 0 {
762 return;
763 }
764 let bytes = value.init_bytes();
765 for element in self.checked_range(offset, byte_len).chunks_exact_mut(elem) {
766 Self::copy_to_uninit(element, bytes);
767 }
768 }
769
770 #[doc(hidden)]
772 #[inline(always)]
773 pub fn field_slot<U>(&mut self, offset: usize) -> StablePayloadInitSlot<'_, U> {
774 let len = mem::size_of::<U>();
775 let end = offset
776 .checked_add(len)
777 .expect("stable payload field slot offset overflow");
778 let bytes = self
779 .bytes
780 .get_mut(offset..end)
781 .expect("stable payload field slot out of bounds");
782 assert_eq!(
783 bytes.as_mut_ptr().align_offset(mem::align_of::<U>()),
784 0,
785 "stable payload field slot is misaligned"
786 );
787 StablePayloadInitSlot {
788 bytes,
789 _marker: PhantomData,
790 }
791 }
792
793 #[doc(hidden)]
795 #[inline(always)]
796 pub fn array_element_slot<U>(
797 &mut self,
798 offset: usize,
799 index: usize,
800 ) -> StablePayloadInitSlot<'_, U> {
801 let element_offset = index
802 .checked_mul(mem::size_of::<U>())
803 .and_then(|relative| offset.checked_add(relative))
804 .expect("stable payload array element offset overflow");
805 self.field_slot::<U>(element_offset)
806 }
807
808 #[doc(hidden)]
813 #[must_use = "the discharged witness must be returned to or consumed by the completion path"]
814 #[inline(always)]
815 pub fn assume_init(self) -> InitializedStablePayload<'a, T> {
816 unsafe { InitializedStablePayload::new_unchecked() }
819 }
820}
821
822#[derive(Clone, Debug, Eq, PartialEq)]
824pub struct StableContainerPayloadInfo {
825 pub type_name: String,
827 pub variant: StablePayloadVariant,
829 pub size: usize,
831 pub alignment: usize,
833}
834
835impl StableContainerPayloadInfo {
836 pub const ENCODING_ID: &'static str = STABLE_CONTAINER_ENCODING_ID;
838
839 pub fn parse(encoding: &PayloadEncoding) -> Result<Self, UWireError> {
846 let expected = PayloadEncoding::custom(Self::ENCODING_ID, STABLE_CONTAINER_MEDIA_TYPE)
847 .expect("stable-container media type is valid");
848 let Some((id, content_type)) = encoding.custom_identity() else {
849 return Err(UWireError::UnsupportedEncoding {
850 expected: Box::new(expected),
851 actual: Box::new(encoding.clone()),
852 });
853 };
854 if id != Self::ENCODING_ID {
855 return Err(UWireError::UnsupportedEncoding {
856 expected: Box::new(expected),
857 actual: Box::new(encoding.clone()),
858 });
859 }
860 Self::parse_content_type(content_type).map_err(UWireError::invalid_payload)
861 }
862
863 #[must_use]
865 pub fn is_compatible_with<T>(&self) -> bool
866 where
867 T: StablePayload,
868 {
869 let expected = T::stable_type_detail();
870 self.type_name == expected.type_name
871 && self.variant == expected.variant
872 && self.size == expected.size
873 && self.alignment >= expected.alignment
874 }
875
876 fn parse_content_type(content_type: &str) -> Result<Self, String> {
877 let media_type = mediatype::MediaType::parse(content_type)
878 .map_err(|error| format!("invalid media type: {error}"))?;
879 let expected_media_type = mediatype::MediaType::parse(STABLE_CONTAINER_MEDIA_TYPE)
880 .expect("stable-container media type is valid");
881 if media_type.essence() != expected_media_type {
882 return Err(format!(
883 "media type must be {}",
884 expected_media_type.essence()
885 ));
886 }
887
888 let type_name = required_stable_parameter(&media_type, "type")?;
889 if type_name.is_empty() {
890 return Err("type parameter must not be empty".to_string());
891 }
892 let variant = required_stable_parameter(&media_type, "variant")?;
893 let variant = StablePayloadVariant::parse(&variant).ok_or_else(|| {
894 format!(
895 "variant parameter must be {}",
896 StablePayloadVariant::FixedSize
897 )
898 })?;
899 let size = required_stable_parameter(&media_type, "size")?
900 .parse::<usize>()
901 .map_err(|error| format!("invalid size parameter: {error}"))?;
902 let alignment = required_stable_parameter(&media_type, "align")?
903 .parse::<usize>()
904 .map_err(|error| format!("invalid align parameter: {error}"))?;
905 PayloadLayout::new(size, alignment).map_err(|error| error.to_string())?;
906
907 Ok(Self {
908 type_name,
909 variant,
910 size,
911 alignment,
912 })
913 }
914}
915
916fn required_stable_parameter(
917 media_type: &mediatype::MediaType<'_>,
918 name: &'static str,
919) -> Result<String, String> {
920 media_type
921 .get_param(mediatype::Name::new_unchecked(name))
922 .map(|value| value.unquoted_str().into_owned())
923 .ok_or_else(|| format!("missing {name} parameter"))
924}
925
926pub struct StableContainerPayload<T>(PhantomData<T>);
931
932impl<T> core::fmt::Debug for StableContainerPayload<T> {
933 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
934 f.debug_struct("StableContainerPayload")
935 .finish_non_exhaustive()
936 }
937}
938
939impl<T: StablePayload> StableContainerPayload<T> {
940 pub const ENCODING_ID: &'static str = STABLE_CONTAINER_ENCODING_ID;
942
943 const MEDIA_TYPE: &'static str = STABLE_CONTAINER_MEDIA_TYPE;
944
945 #[must_use]
947 pub fn encoding() -> PayloadEncoding {
948 <Self as PayloadCodec>::payload_encoding()
949 }
950
951 fn stable_content_type() -> String {
952 let detail = T::stable_type_detail();
953 format!(
954 "{};type={};variant={};size={};align={}",
955 Self::MEDIA_TYPE,
956 Self::quote_parameter(detail.type_name),
957 detail.variant,
958 detail.size,
959 detail.alignment
960 )
961 }
962
963 fn quote_parameter(value: &str) -> String {
964 let quoted = mediatype::Value::quote(value);
965 if quoted.starts_with('"') {
966 quoted.into_owned()
967 } else {
968 format!("\"{quoted}\"")
969 }
970 }
971
972 fn verify_content_type(content_type: &str) -> Result<(), String> {
973 let info = StableContainerPayloadInfo::parse_content_type(content_type)?;
974 let expected = T::stable_type_detail();
975 if info.type_name != expected.type_name {
976 return Err(format!("type parameter must be {}", expected.type_name));
977 }
978 if info.variant != expected.variant {
979 return Err(format!(
980 "variant parameter must be {}",
981 expected.variant.as_str()
982 ));
983 }
984 if info.size != expected.size {
985 return Err(format!("size parameter must be {}", expected.size));
986 }
987 if info.alignment < expected.alignment {
988 return Err(format!(
989 "align parameter must be at least {}",
990 expected.alignment
991 ));
992 }
993 Ok(())
994 }
995
996 pub(crate) fn borrow_checked_payload(src: &[u8]) -> Result<&T, UWireError> {
997 let expected_len = mem::size_of::<T>();
998 if src.len() != expected_len {
999 return Err(UWireError::invalid_payload(format!(
1000 "payload length must be {expected_len}, got {}",
1001 src.len()
1002 )));
1003 }
1004
1005 let alignment = mem::align_of::<T>();
1006 let address = src.as_ptr() as usize;
1007 if !address.is_multiple_of(alignment) {
1008 return Err(UWireError::invalid_payload(format!(
1009 "payload address {address} is not aligned to {alignment}"
1010 )));
1011 }
1012
1013 Ok(unsafe { &*src.as_ptr().cast::<T>() })
1017 }
1018
1019 pub(crate) fn check_uninit_layout(src: &[mem::MaybeUninit<u8>]) -> Result<(), UWireError> {
1020 let expected_len = mem::size_of::<T>();
1021 if src.len() != expected_len {
1022 return Err(UWireError::invalid_payload_length(expected_len, src.len()));
1023 }
1024
1025 let alignment = mem::align_of::<T>();
1026 let address = src.as_ptr() as usize;
1027 if !address.is_multiple_of(alignment) {
1028 return Err(UWireError::invalid_payload(format!(
1029 "payload address {address} is not aligned to {alignment}"
1030 )));
1031 }
1032 Ok(())
1033 }
1034
1035 fn check_initialized_layout(src: &[u8]) -> Result<(), UWireError> {
1036 let expected_len = mem::size_of::<T>();
1037 if src.len() != expected_len {
1038 return Err(UWireError::invalid_payload_length(expected_len, src.len()));
1039 }
1040
1041 if expected_len == 0 {
1042 return Ok(());
1043 }
1044
1045 let alignment = mem::align_of::<T>();
1046 let address = src.as_ptr() as usize;
1047 if !address.is_multiple_of(alignment) {
1048 return Err(UWireError::invalid_payload(format!(
1049 "payload address {address} is not aligned to {alignment}"
1050 )));
1051 }
1052 Ok(())
1053 }
1054}
1055
1056impl<T> PayloadCodec for StableContainerPayload<T>
1057where
1058 T: StablePayload,
1059{
1060 fn codec_name() -> &'static str {
1061 "stable-container"
1062 }
1063
1064 fn payload_encoding() -> PayloadEncoding {
1065 PayloadEncoding::custom(Self::ENCODING_ID, Self::stable_content_type())
1066 .expect("stable-container payload encoding is valid")
1067 }
1068
1069 fn verify_encoding(actual: Option<&PayloadEncoding>) -> Result<(), UWireError> {
1070 let expected = Self::payload_encoding();
1071 let actual = actual.ok_or(UWireError::MissingEncoding)?;
1072 let Some((id, content_type)) = actual.custom_identity() else {
1073 return Err(UWireError::UnsupportedEncoding {
1074 expected: Box::new(expected),
1075 actual: Box::new(actual.clone()),
1076 });
1077 };
1078 if id != Self::ENCODING_ID {
1079 return Err(UWireError::UnsupportedEncoding {
1080 expected: Box::new(expected),
1081 actual: Box::new(actual.clone()),
1082 });
1083 }
1084 Self::verify_content_type(content_type).map_err(|reason| {
1085 UWireError::invalid_payload(format!(
1086 "incompatible stable payload: expected {}; actual {} ({reason})",
1087 T::stable_type_detail(),
1088 content_type
1089 ))
1090 })
1091 }
1092}
1093
1094unsafe impl<T> LoanPayload<T> for StableContainerPayload<T>
1100where
1101 T: ByteBackedStablePayload + Default,
1102{
1103 fn loan_layout() -> Result<PayloadLayout, UWireError> {
1104 PayloadLayout::new(mem::size_of::<T>(), mem::align_of::<T>())
1105 }
1106
1107 fn loan_payload(dst: &mut [u8]) -> Result<&mut T, UWireError> {
1108 Self::check_initialized_layout(dst)?;
1109 dst.fill(0);
1110 let ptr = if mem::size_of::<T>() == 0 {
1111 NonNull::<T>::dangling().as_ptr()
1112 } else {
1113 dst.as_mut_ptr().cast::<T>()
1114 };
1115 unsafe { ptr.write(T::default()) };
1120 Ok(unsafe { &mut *ptr })
1124 }
1125}
1126
1127#[cfg(feature = "zero-copy-transport")]
1134unsafe impl<T> LoanUninitPayload<T> for StableContainerPayload<T>
1135where
1136 T: ByteBackedStablePayload,
1137{
1138 fn loan_uninit_layout() -> Result<PayloadLayout, UWireError> {
1139 PayloadLayout::new(mem::size_of::<T>(), mem::align_of::<T>())
1140 }
1141
1142 fn loan_uninit_payload<'a>(
1143 mut dst: LoanedPayloadUninitMut<'a>,
1144 ) -> Result<LoanedUninitPayload<'a, T>, UWireError> {
1145 let bytes = dst.as_uninit_bytes_mut_internal();
1146 Self::check_uninit_layout(bytes)?;
1147 let slot = unsafe { &mut *bytes.as_mut_ptr().cast::<mem::MaybeUninit<T>>() };
1155 Ok(LoanedUninitPayload::new(slot))
1156 }
1157}
1158
1159unsafe impl<T> BorrowPayload<T> for StableContainerPayload<T>
1164where
1165 T: StablePayload,
1166{
1167 fn borrow_payload(src: &[u8]) -> Result<&T, UWireError> {
1168 Self::borrow_checked_payload(src)
1169 }
1170}
1171
1172#[cfg(test)]
1173mod tests {
1174 #[cfg(feature = "owned-frame-transport")]
1175 use bytes::Bytes;
1176
1177 #[cfg(feature = "owned-frame-transport")]
1178 use crate::{UMessageBuilder, UOwnedFrame, UUri};
1179
1180 use super::*;
1181
1182 #[cfg(feature = "owned-frame-transport")]
1183 fn topic() -> UUri {
1184 UUri::try_from_parts("vehicle", 0x4210, 0x01, 0x9000).expect("topic")
1185 }
1186
1187 #[repr(C)]
1188 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1189 struct VehiclePose {
1190 x: i32,
1191 y: i32,
1192 }
1193
1194 unsafe impl StablePayload for VehiclePose {
1197 const TYPE_NAME: &'static str = "example.vehicle.VehiclePose";
1198 }
1199
1200 fn stable_container_encoding(
1201 type_name: &str,
1202 variant: &str,
1203 size: usize,
1204 align: usize,
1205 ) -> PayloadEncoding {
1206 PayloadEncoding::custom(
1207 StableContainerPayload::<VehiclePose>::ENCODING_ID,
1208 format!(
1209 "application/vnd.uprotocol.stable-container;type=\"{type_name}\";variant={variant};size={size};align={align}"
1210 ),
1211 )
1212 .expect("valid custom encoding")
1213 }
1214
1215 #[test]
1216 fn stable_payload_type_detail_is_used_in_encoding() {
1217 let detail = VehiclePose::stable_type_detail();
1218
1219 assert_eq!(detail.variant, StablePayloadVariant::FixedSize);
1220 assert_eq!(detail.type_name, "example.vehicle.VehiclePose");
1221 assert_eq!(detail.size, mem::size_of::<VehiclePose>());
1222 assert_eq!(detail.alignment, mem::align_of::<VehiclePose>());
1223
1224 let encoding = StableContainerPayload::<VehiclePose>::encoding();
1225 let (id, content_type) = encoding.custom_identity().expect("custom encoding");
1226 assert_eq!(id, StableContainerPayload::<VehiclePose>::ENCODING_ID);
1227 assert_eq!(
1228 content_type,
1229 "application/vnd.uprotocol.stable-container;type=\"example.vehicle.VehiclePose\";variant=fixed;size=8;align=4"
1230 );
1231 }
1232
1233 #[test]
1234 fn stable_container_payload_info_parses_type_agnostic_metadata() {
1235 let info =
1236 StableContainerPayloadInfo::parse(&StableContainerPayload::<VehiclePose>::encoding())
1237 .expect("parse stable metadata");
1238
1239 assert_eq!(info.type_name, "example.vehicle.VehiclePose");
1240 assert_eq!(info.variant, StablePayloadVariant::FixedSize);
1241 assert_eq!(info.size, mem::size_of::<VehiclePose>());
1242 assert_eq!(info.alignment, mem::align_of::<VehiclePose>());
1243 assert!(info.is_compatible_with::<VehiclePose>());
1244 }
1245
1246 #[test]
1247 fn stable_container_payload_info_rejects_malformed_metadata() {
1248 let encoding = PayloadEncoding::custom(
1249 StableContainerPayloadInfo::ENCODING_ID,
1250 "application/vnd.uprotocol.stable-container;variant=fixed;size=8;align=4",
1251 )
1252 .expect("custom encoding");
1253
1254 let error = StableContainerPayloadInfo::parse(&encoding).unwrap_err();
1255
1256 assert!(
1257 matches!(error, UWireError::InvalidPayload(message) if message.contains("missing type"))
1258 );
1259 }
1260
1261 #[test]
1262 fn stable_container_verify_accepts_larger_advertised_alignment() {
1263 let encoding = stable_container_encoding(
1264 "example.vehicle.VehiclePose",
1265 "fixed",
1266 mem::size_of::<VehiclePose>(),
1267 mem::align_of::<VehiclePose>() * 2,
1268 );
1269
1270 StableContainerPayload::<VehiclePose>::verify_encoding(Some(&encoding))
1271 .expect("larger alignment is compatible");
1272 }
1273
1274 #[test]
1275 fn stable_container_verify_rejects_incompatible_metadata() {
1276 let wrong_type = stable_container_encoding(
1277 "example.vehicle.OtherPose",
1278 "fixed",
1279 mem::size_of::<VehiclePose>(),
1280 mem::align_of::<VehiclePose>(),
1281 );
1282 let wrong_size = stable_container_encoding(
1283 "example.vehicle.VehiclePose",
1284 "fixed",
1285 mem::size_of::<VehiclePose>() + 1,
1286 mem::align_of::<VehiclePose>(),
1287 );
1288 let insufficient_alignment = stable_container_encoding(
1289 "example.vehicle.VehiclePose",
1290 "fixed",
1291 mem::size_of::<VehiclePose>(),
1292 mem::align_of::<VehiclePose>() / 2,
1293 );
1294
1295 for encoding in [wrong_type, wrong_size, insufficient_alignment] {
1296 assert!(matches!(
1297 StableContainerPayload::<VehiclePose>::verify_encoding(Some(&encoding)),
1298 Err(UWireError::InvalidPayload(_))
1299 ));
1300 }
1301 }
1302
1303 #[cfg(feature = "owned-frame-transport")]
1304 #[test]
1305 fn stable_container_owned_frame_preserves_bytes_and_custom_metadata() {
1306 let payload = Bytes::from_static(b"\x0a\x00\x00\x00\x14\x00\x00\x00");
1307 assert_eq!(payload.len(), mem::size_of::<VehiclePose>());
1308 let message = UMessageBuilder::publish(topic()).build().expect("message");
1309 let metadata = crate::frame::metadata::try_project_attributes_to_frame_metadata(
1310 message.attributes(),
1311 Some(StableContainerPayload::<VehiclePose>::encoding()),
1312 )
1313 .expect("metadata");
1314
1315 let frame = UOwnedFrame::with_payload(metadata, payload.clone()).expect("owned frame");
1316
1317 assert_eq!(
1318 frame.metadata().payload_encoding(),
1319 Some(&StableContainerPayload::<VehiclePose>::encoding())
1320 );
1321 assert_eq!(frame.payload(), Some(&payload));
1322 assert_eq!(frame.payload_bytes(), payload.as_ref());
1323 }
1324}