1pub const HEADER_LEN: usize = 72;
59
60pub const PLANE_RECORD_LEN: usize = 56;
65
66pub const SUPPORTED_REQUIRED_MASK: u64 = 0;
70
71#[cfg(unix)]
76type BlobFd = std::os::fd::RawFd;
77#[cfg(not(unix))]
78type BlobFd = i32;
79
80#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum BlobError {
88 Truncated {
90 need: usize,
92 have: usize,
94 },
95 UnsupportedRequirement(u64),
99 ForgedLength {
102 field: &'static str,
104 value: u64,
106 },
107 InvalidUtf8(&'static str),
109 Malformed(&'static str),
111}
112
113impl std::fmt::Display for BlobError {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 match self {
116 Self::Truncated { need, have } => {
117 write!(f, "blob truncated: needs {need} bytes, buffer has {have}")
118 }
119 Self::UnsupportedRequirement(m) => write!(
120 f,
121 "blob requires unsupported features (required_mask {m:#x}); \
122 refusing rather than misreading it"
123 ),
124 Self::ForgedLength { field, value } => {
125 write!(f, "blob field `{field}` has implausible value {value}")
126 }
127 Self::InvalidUtf8(which) => write!(f, "blob string `{which}` is not valid UTF-8"),
128 Self::Malformed(why) => write!(f, "malformed blob: {why}"),
129 }
130 }
131}
132
133impl std::error::Error for BlobError {}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141pub struct BlobHeader {
142 pub size: u64,
146 pub required_mask: u64,
149 pub planes_bytes: u64,
151 pub storage_kind: u32,
153 pub pid: u32,
156 pub fence_fd: i32,
158 pub dtype: u32,
160 pub quant_axis: i32,
162 pub ndim: u32,
164 pub plane_count: u32,
166 pub quant_scales_len: u32,
168 pub quant_zero_points_len: u32,
170 pub strings_bytes: u32,
172 pub strides_len: u32,
178}
179
180impl BlobHeader {
181 pub fn empty() -> Self {
183 Self::default()
184 }
185
186 pub fn write_to(&self, buf: &mut [u8]) {
191 assert!(buf.len() >= HEADER_LEN, "header buffer too small");
192 buf[0..8].copy_from_slice(&self.size.to_le_bytes());
193 buf[8..16].copy_from_slice(&self.required_mask.to_le_bytes());
194 buf[16..24].copy_from_slice(&self.planes_bytes.to_le_bytes());
195 buf[24..28].copy_from_slice(&self.storage_kind.to_le_bytes());
196 buf[28..32].copy_from_slice(&self.pid.to_le_bytes());
197 buf[32..36].copy_from_slice(&self.fence_fd.to_le_bytes());
198 buf[36..40].copy_from_slice(&self.dtype.to_le_bytes());
199 buf[40..44].copy_from_slice(&self.quant_axis.to_le_bytes());
200 buf[44..48].copy_from_slice(&self.ndim.to_le_bytes());
201 buf[48..52].copy_from_slice(&self.plane_count.to_le_bytes());
202 buf[52..56].copy_from_slice(&self.quant_scales_len.to_le_bytes());
203 buf[56..60].copy_from_slice(&self.quant_zero_points_len.to_le_bytes());
204 buf[60..64].copy_from_slice(&self.strings_bytes.to_le_bytes());
205 buf[64..68].copy_from_slice(&self.strides_len.to_le_bytes());
206 buf[68..72].copy_from_slice(&0u32.to_le_bytes()); }
208}
209
210fn rd_u64(buf: &[u8], off: usize) -> u64 {
212 let mut b = [0u8; 8];
213 b.copy_from_slice(&buf[off..off + 8]);
214 u64::from_le_bytes(b)
215}
216
217fn rd_u32(buf: &[u8], off: usize) -> u32 {
219 let mut b = [0u8; 4];
220 b.copy_from_slice(&buf[off..off + 4]);
221 u32::from_le_bytes(b)
222}
223
224pub fn parse_header(buf: &[u8]) -> Result<BlobHeader, BlobError> {
235 if buf.len() < HEADER_LEN {
236 return Err(BlobError::Truncated {
237 need: HEADER_LEN,
238 have: buf.len(),
239 });
240 }
241 let required_mask = rd_u64(buf, 8);
242 if required_mask & !SUPPORTED_REQUIRED_MASK != 0 {
246 return Err(BlobError::UnsupportedRequirement(required_mask));
247 }
248 let size = rd_u64(buf, 0);
249 if size < HEADER_LEN as u64 {
250 return Err(BlobError::ForgedLength {
251 field: "size",
252 value: size,
253 });
254 }
255 if size > buf.len() as u64 {
258 return Err(BlobError::Truncated {
259 need: size as usize,
260 have: buf.len(),
261 });
262 }
263 if rd_u32(buf, 68) != 0 {
266 return Err(BlobError::Malformed("reserved header word is not zero"));
267 }
268 Ok(BlobHeader {
269 size,
270 required_mask,
271 planes_bytes: rd_u64(buf, 16),
272 storage_kind: rd_u32(buf, 24),
273 pid: rd_u32(buf, 28),
274 fence_fd: rd_u32(buf, 32) as i32,
275 dtype: rd_u32(buf, 36),
276 quant_axis: rd_u32(buf, 40) as i32,
277 ndim: rd_u32(buf, 44),
278 plane_count: rd_u32(buf, 48),
279 quant_scales_len: rd_u32(buf, 52),
280 quant_zero_points_len: rd_u32(buf, 56),
281 strings_bytes: rd_u32(buf, 60),
282 strides_len: rd_u32(buf, 64),
283 })
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub struct Regions {
292 pub shape: usize,
294 pub strides: usize,
296 pub quant_scales: usize,
298 pub quant_zero_points: usize,
300 pub strings: usize,
302 pub planes: usize,
304 pub end: usize,
306}
307
308fn advance(
315 off: usize,
316 count: u32,
317 elem: usize,
318 limit: usize,
319 field: &'static str,
320) -> Result<usize, BlobError> {
321 let bytes = (count as usize)
322 .checked_mul(elem)
323 .ok_or(BlobError::ForgedLength {
324 field,
325 value: count as u64,
326 })?;
327 let next = off.checked_add(bytes).ok_or(BlobError::ForgedLength {
328 field,
329 value: count as u64,
330 })?;
331 if next > limit {
332 return Err(BlobError::ForgedLength {
333 field,
334 value: count as u64,
335 });
336 }
337 Ok(next)
338}
339
340pub fn region_offsets(h: &BlobHeader) -> Result<Regions, BlobError> {
348 let limit = h.size as usize;
349 if limit < HEADER_LEN {
350 return Err(BlobError::ForgedLength {
351 field: "size",
352 value: h.size,
353 });
354 }
355 if h.strides_len != 0 && h.strides_len != h.ndim {
358 return Err(BlobError::ForgedLength {
359 field: "strides_len",
360 value: h.strides_len as u64,
361 });
362 }
363 let shape = HEADER_LEN;
364 let strides = advance(shape, h.ndim, 8, limit, "ndim")?;
365 let quant_scales = advance(strides, h.strides_len, 8, limit, "strides_len")?;
366 let quant_zero_points = advance(
367 quant_scales,
368 h.quant_scales_len,
369 4,
370 limit,
371 "quant_scales_len",
372 )?;
373 let strings = advance(
374 quant_zero_points,
375 h.quant_zero_points_len,
376 4,
377 limit,
378 "quant_zero_points_len",
379 )?;
380 let planes = advance(strings, h.strings_bytes, 1, limit, "strings_bytes")?;
381 if !planes.is_multiple_of(8) {
385 return Err(BlobError::Malformed(
386 "planes region is not 8-aligned; strings_bytes must include padding",
387 ));
388 }
389 let planes_bytes = usize::try_from(h.planes_bytes).map_err(|_| BlobError::ForgedLength {
390 field: "planes_bytes",
391 value: h.planes_bytes,
392 })?;
393 let end = planes
394 .checked_add(planes_bytes)
395 .ok_or(BlobError::ForgedLength {
396 field: "planes_bytes",
397 value: h.planes_bytes,
398 })?;
399 if end > limit {
400 return Err(BlobError::ForgedLength {
401 field: "planes_bytes",
402 value: h.planes_bytes,
403 });
404 }
405 let max_planes = planes_bytes / PLANE_RECORD_LEN;
409 if h.plane_count as usize > max_planes {
410 return Err(BlobError::ForgedLength {
411 field: "plane_count",
412 value: h.plane_count as u64,
413 });
414 }
415 Ok(Regions {
416 shape,
417 strides,
418 quant_scales,
419 quant_zero_points,
420 strings,
421 planes,
422 end,
423 })
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
437pub struct BlobStrings<'a> {
438 pub format: &'a str,
440 pub color_space: &'a str,
442 pub color_transfer: &'a str,
444 pub color_encoding: &'a str,
446 pub color_range: &'a str,
448}
449
450const STRING_COUNT: usize = 5;
452
453pub fn strings_encoded_len(s: &BlobStrings<'_>) -> usize {
456 let raw: usize = STRING_COUNT * 4
457 + s.format.len()
458 + s.color_space.len()
459 + s.color_transfer.len()
460 + s.color_encoding.len()
461 + s.color_range.len();
462 raw.next_multiple_of(8)
463}
464
465pub fn write_strings(s: &BlobStrings<'_>, out: &mut Vec<u8>) {
472 let start = out.len();
473 for part in [
474 s.format,
475 s.color_space,
476 s.color_transfer,
477 s.color_encoding,
478 s.color_range,
479 ] {
480 out.extend_from_slice(&(part.len() as u32).to_le_bytes());
481 out.extend_from_slice(part.as_bytes());
482 }
483 while !(out.len() - start).is_multiple_of(8) {
484 out.push(0);
485 }
486}
487
488pub fn parse_strings(buf: &[u8]) -> Result<BlobStrings<'_>, BlobError> {
492 const NAMES: [&str; STRING_COUNT] = [
493 "format",
494 "color_space",
495 "color_transfer",
496 "color_encoding",
497 "color_range",
498 ];
499 let mut out: [&str; STRING_COUNT] = [""; STRING_COUNT];
500 let mut off = 0usize;
501 for (i, name) in NAMES.iter().enumerate() {
502 let name = *name;
503 let end = off
504 .checked_add(4)
505 .ok_or(BlobError::Malformed("string count overflows"))?;
506 if end > buf.len() {
507 return Err(BlobError::Truncated {
508 need: end,
509 have: buf.len(),
510 });
511 }
512 let len = rd_u32(buf, off) as usize;
513 off = end;
514 let stop = off.checked_add(len).ok_or(BlobError::ForgedLength {
515 field: name,
516 value: len as u64,
517 })?;
518 if stop > buf.len() {
519 return Err(BlobError::ForgedLength {
520 field: name,
521 value: len as u64,
522 });
523 }
524 out[i] = std::str::from_utf8(&buf[off..stop]).map_err(|_| BlobError::InvalidUtf8(name))?;
527 off = stop;
528 }
529 Ok(BlobStrings {
530 format: out[0],
531 color_space: out[1],
532 color_transfer: out[2],
533 color_encoding: out[3],
534 color_range: out[4],
535 })
536}
537
538#[derive(Debug, Clone, Copy, PartialEq, Eq)]
553pub struct BlobPlane<'a> {
554 pub handle: i64,
567 pub offset: u64,
569 pub stride: u64,
571 pub size: u64,
573 pub used: u64,
575 pub modifier: u64,
578 pub handle_bytes: &'a [u8],
581 pub data: &'a [u8],
583}
584
585impl BlobPlane<'_> {
586 pub fn is_inline(&self) -> bool {
588 self.handle < 0
589 }
590
591 pub fn encoded_len(&self) -> usize {
594 (PLANE_RECORD_LEN + self.handle_bytes.len() + self.data.len()).next_multiple_of(8)
595 }
596}
597
598pub fn write_plane(p: &BlobPlane<'_>, out: &mut Vec<u8>) {
600 let start = out.len();
601 out.extend_from_slice(&p.handle.to_le_bytes());
602 out.extend_from_slice(&p.offset.to_le_bytes());
603 out.extend_from_slice(&p.stride.to_le_bytes());
604 out.extend_from_slice(&p.size.to_le_bytes());
605 out.extend_from_slice(&p.used.to_le_bytes());
606 out.extend_from_slice(&p.modifier.to_le_bytes());
607 out.extend_from_slice(&(p.handle_bytes.len() as u32).to_le_bytes());
608 out.extend_from_slice(&(p.data.len() as u32).to_le_bytes());
609 out.extend_from_slice(p.handle_bytes);
610 out.extend_from_slice(p.data);
611 while !(out.len() - start).is_multiple_of(8) {
612 out.push(0);
613 }
614}
615
616fn rd_i64(buf: &[u8], off: usize) -> i64 {
618 let mut b = [0u8; 8];
619 b.copy_from_slice(&buf[off..off + 8]);
620 i64::from_le_bytes(b)
621}
622
623pub fn parse_planes(buf: &[u8], count: u32) -> Result<Vec<BlobPlane<'_>>, BlobError> {
630 let mut planes = Vec::with_capacity(count as usize);
631 let mut off = 0usize;
632 for _ in 0..count {
633 let head = off
634 .checked_add(PLANE_RECORD_LEN)
635 .ok_or(BlobError::Malformed("plane record offset overflows"))?;
636 if head > buf.len() {
637 return Err(BlobError::Truncated {
638 need: head,
639 have: buf.len(),
640 });
641 }
642 let hb_len = rd_u32(buf, off + 48) as usize;
643 let data_len = rd_u32(buf, off + 52) as usize;
644 let hb_end = head.checked_add(hb_len).ok_or(BlobError::ForgedLength {
645 field: "handle_bytes_len",
646 value: hb_len as u64,
647 })?;
648 let data_end = hb_end
649 .checked_add(data_len)
650 .ok_or(BlobError::ForgedLength {
651 field: "data_len",
652 value: data_len as u64,
653 })?;
654 if hb_end > buf.len() {
655 return Err(BlobError::ForgedLength {
656 field: "handle_bytes_len",
657 value: hb_len as u64,
658 });
659 }
660 if data_end > buf.len() {
661 return Err(BlobError::ForgedLength {
662 field: "data_len",
663 value: data_len as u64,
664 });
665 }
666 planes.push(BlobPlane {
667 handle: rd_i64(buf, off),
668 offset: rd_u64(buf, off + 8),
669 stride: rd_u64(buf, off + 16),
670 size: rd_u64(buf, off + 24),
671 used: rd_u64(buf, off + 32),
672 modifier: rd_u64(buf, off + 40),
673 handle_bytes: &buf[head..hb_end],
674 data: &buf[hb_end..data_end],
675 });
676 off = data_end.next_multiple_of(8);
677 }
678 Ok(planes)
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
687pub enum TransportMode {
688 Reference,
691 Inline,
695}
696
697#[derive(Debug, Clone, Copy)]
701pub struct BlobView<'a> {
702 buf: &'a [u8],
703 header: BlobHeader,
704 regions: Regions,
705}
706
707impl<'a> BlobView<'a> {
708 pub fn parse(buf: &'a [u8]) -> Result<Self, BlobError> {
714 let header = parse_header(buf)?;
715 let regions = region_offsets(&header)?;
716 Ok(Self {
717 buf,
718 header,
719 regions,
720 })
721 }
722
723 pub fn header(&self) -> BlobHeader {
725 self.header
726 }
727
728 pub fn shape(&self) -> Vec<u64> {
730 (0..self.header.ndim as usize)
731 .map(|i| rd_u64(self.buf, self.regions.shape + i * 8))
732 .collect()
733 }
734
735 pub fn strides(&self) -> Vec<i64> {
737 (0..self.header.strides_len as usize)
738 .map(|i| rd_i64(self.buf, self.regions.strides + i * 8))
739 .collect()
740 }
741
742 pub fn strings(&self) -> BlobStrings<'a> {
744 parse_strings(&self.buf[self.regions.strings..self.regions.planes]).unwrap_or_default()
745 }
746
747 pub fn planes(&self) -> Result<Vec<BlobPlane<'a>>, BlobError> {
749 parse_planes(
750 &self.buf[self.regions.planes..self.regions.end],
751 self.header.plane_count,
752 )
753 }
754}
755
756pub fn export(t: &crate::TensorDyn, mode: TransportMode) -> crate::Result<(Vec<u8>, Vec<BlobFd>)> {
767 let fmt = t.format();
768 let shape = export_addressing_shape(t, fmt)?;
771 let esz = t.dtype().size() as i64;
772 let strides = export_c_strides(t, fmt, &shape, esz);
773
774 let quant = t.quantization();
775 let (quant_axis, scales, zeros): (i32, &[f32], &[i32]) = match quant {
776 None => (-2, &[], &[]),
777 Some(q) => (
778 q.axis().map(|a| a as i32).unwrap_or(-1),
779 q.scale(),
780 q.zero_point().unwrap_or(&[]),
781 ),
782 };
783
784 let colorimetry = t.colorimetry().unwrap_or_default();
785 let strings = BlobStrings {
786 format: fmt.map(|f| f.as_str()).unwrap_or(""),
787 color_space: colorimetry.space.map(|v| v.as_str()).unwrap_or(""),
788 color_transfer: colorimetry.transfer.map(|v| v.as_str()).unwrap_or(""),
789 color_encoding: colorimetry.encoding.map(|v| v.as_str()).unwrap_or(""),
790 color_range: colorimetry.range.map(|v| v.as_str()).unwrap_or(""),
791 };
792
793 let geoms = export_plane_geoms(t, fmt, esz)?;
796
797 let pin = match mode {
800 TransportMode::Inline => Some(t.pin_host(crate::CpuAccess::Read)?),
801 TransportMode::Reference => None,
802 };
803 let all_bytes: &[u8] = match &pin {
804 Some(p) => unsafe { std::slice::from_raw_parts(p.as_mut_ptr(), p.len()) },
805 None => &[],
806 };
807
808 let mut fds: Vec<BlobFd> = Vec::new();
812 let handle: i64 = match mode {
813 TransportMode::Inline => -1,
814 TransportMode::Reference => {
815 let raw = reference_handle(t)?;
816 let raw = i32::try_from(raw).map_err(|_| {
817 crate::Error::InvalidOperation("handle is not representable as an fd".into())
818 })?;
819 fds.push(raw);
820 0 }
822 };
823
824 let mut planes_buf = Vec::new();
825 write_export_planes(&geoms, mode, handle, all_bytes, &mut planes_buf)?;
826
827 let strings_bytes = strings_encoded_len(&strings);
828 let header = BlobHeader {
829 size: 0, required_mask: 0,
831 planes_bytes: planes_buf.len() as u64,
832 storage_kind: t.memory().code(),
833 pid: match mode {
835 TransportMode::Inline => 0,
836 TransportMode::Reference => std::process::id(),
837 },
838 fence_fd: -1,
841 dtype: t.dtype().code(),
842 quant_axis,
843 ndim: shape.len() as u32,
844 plane_count: geoms.len() as u32,
845 quant_scales_len: scales.len() as u32,
846 quant_zero_points_len: zeros.len() as u32,
847 strings_bytes: strings_bytes as u32,
848 strides_len: strides.len() as u32,
849 };
850
851 let mut out = vec![0u8; HEADER_LEN];
852 for d in &shape {
853 out.extend_from_slice(&d.to_le_bytes());
854 }
855 for st in &strides {
856 out.extend_from_slice(&st.to_le_bytes());
857 }
858 for sc in scales {
859 out.extend_from_slice(&sc.to_le_bytes());
860 }
861 for z in zeros {
862 out.extend_from_slice(&z.to_le_bytes());
863 }
864 write_strings(&strings, &mut out);
865 out.extend_from_slice(&planes_buf);
866
867 let mut header = header;
868 header.size = out.len() as u64;
869 header.write_to(&mut out);
870 Ok((out, fds))
871}
872
873fn export_addressing_shape(
874 t: &crate::TensorDyn,
875 fmt: Option<crate::PixelFormat>,
876) -> crate::Result<Vec<u64>> {
877 match fmt {
878 Some(f) => {
879 let (w, h) = image_dims(t, f)?;
880 Ok(f.addressing_shape(w, h)
881 .ok_or_else(|| {
882 crate::Error::InvalidArgument(format!("no addressing shape for {f:?}"))
883 })?
884 .iter()
885 .map(|d| *d as u64)
886 .collect())
887 }
888 None => Ok(t.shape().iter().map(|d| *d as u64).collect()),
889 }
890}
891
892fn export_c_strides(
893 t: &crate::TensorDyn,
894 fmt: Option<crate::PixelFormat>,
895 shape: &[u64],
896 esz: i64,
897) -> Vec<i64> {
898 let mut acc = esz;
899 let mut v = vec![0i64; shape.len()];
900 for i in (0..shape.len()).rev() {
901 v[i] = acc;
902 acc *= shape[i] as i64;
903 }
904 if let (Some(rs), true) = (t.row_stride(), shape.len() >= 2) {
905 let row_dim = match fmt.map(|f| f.layout()) {
906 Some(crate::PixelLayout::Planar) if shape.len() >= 3 => 1,
907 _ => 0,
908 };
909 v[row_dim] = rs as i64;
910 }
911 v
912}
913
914fn export_plane_geoms(
915 t: &crate::TensorDyn,
916 fmt: Option<crate::PixelFormat>,
917 esz: i64,
918) -> crate::Result<Vec<crate::PlaneGeometry>> {
919 match fmt {
920 Some(f) => {
921 let (w, h) = image_dims(t, f)?;
922 let rs = t
923 .effective_row_stride()
924 .unwrap_or(w * f.channels() * esz as usize);
925 f.plane_table(w, h, rs)
926 .ok_or_else(|| crate::Error::InvalidArgument(format!("no plane table for {f:?}")))
927 }
928 None => Ok(vec![crate::PlaneGeometry {
929 offset: 0,
930 stride: t.capacity_bytes() as u64,
931 size: t.capacity_bytes() as u64,
932 }]),
933 }
934}
935
936fn write_export_planes(
937 geoms: &[crate::PlaneGeometry],
938 mode: TransportMode,
939 handle: i64,
940 all_bytes: &[u8],
941 planes_buf: &mut Vec<u8>,
942) -> crate::Result<()> {
943 for g in geoms {
944 let data: &[u8] = match mode {
945 TransportMode::Inline => {
946 let start = g.offset as usize;
947 let end = start
948 .checked_add(g.size as usize)
949 .ok_or(crate::Error::InvalidSize(g.size as usize))?;
950 all_bytes
951 .get(start..end)
952 .ok_or(crate::Error::InvalidSize(end))?
953 }
954 TransportMode::Reference => &[],
955 };
956 write_plane(
957 &BlobPlane {
958 handle,
959 offset: if mode == TransportMode::Inline {
962 0
963 } else {
964 g.offset
965 },
966 stride: g.stride,
967 size: if mode == TransportMode::Inline {
969 data.len() as u64
970 } else {
971 g.size
972 },
973 used: if mode == TransportMode::Inline {
974 data.len() as u64
975 } else {
976 g.size
977 },
978 modifier: 0,
979 handle_bytes: &[],
980 data,
981 },
982 planes_buf,
983 );
984 }
985 Ok(())
986}
987
988fn image_dims(t: &crate::TensorDyn, f: crate::PixelFormat) -> crate::Result<(usize, usize)> {
993 let shape = t.shape();
994 let bad = || crate::Error::InvalidShape(format!("shape {shape:?} is not an image for {f:?}"));
995 match f.layout() {
996 crate::PixelLayout::Packed => {
997 if shape.len() < 2 {
998 return Err(bad());
999 }
1000 Ok((shape[1], shape[0]))
1001 }
1002 crate::PixelLayout::Planar => {
1003 if shape.len() < 3 {
1004 return Err(bad());
1005 }
1006 Ok((shape[2], shape[1]))
1007 }
1008 crate::PixelLayout::SemiPlanar => {
1009 if shape.len() < 2 {
1010 return Err(bad());
1011 }
1012 let total = shape[0];
1016 let h = (0..=total)
1017 .find(|&h| f.combined_plane_height(h) == Some(total))
1018 .ok_or_else(bad)?;
1019 Ok((shape[1], h))
1020 }
1021 }
1022}
1023
1024fn reference_handle(t: &crate::TensorDyn) -> crate::Result<i64> {
1027 match t.memory() {
1028 #[cfg(target_os = "linux")]
1029 crate::TensorMemory::DmaBuf => {
1030 use std::os::fd::AsRawFd;
1031 t.dmabuf().map(|fd| fd.as_raw_fd() as i64).map_err(|e| {
1034 crate::Error::InvalidOperation(format!("dma-buf tensor has no fd: {e}"))
1035 })
1036 }
1037 #[cfg(any(target_os = "macos", target_os = "ios"))]
1038 crate::TensorMemory::DmaBuf => t
1039 .iosurface_id()
1040 .map(|id| id as i64)
1041 .ok_or_else(|| crate::Error::InvalidOperation("IOSurface tensor has no id".into())),
1042 other => Err(crate::Error::InvalidOperation(format!(
1046 "{other:?} has no shareable handle; export it with TransportMode::Inline"
1047 ))),
1048 }
1049}
1050
1051pub fn import(blob: &[u8], fds: &[BlobFd]) -> crate::Result<crate::TensorDyn> {
1065 #[cfg(not(unix))]
1066 let _ = fds;
1067 let v = BlobView::parse(blob).map_err(|e| crate::Error::InvalidArgument(e.to_string()))?;
1068 let h = v.header();
1069 let planes = v
1070 .planes()
1071 .map_err(|e| crate::Error::InvalidArgument(e.to_string()))?;
1072
1073 let inline = planes.first().map(BlobPlane::is_inline).unwrap_or(true);
1077 validate_import_planes(&planes, inline)?;
1078
1079 let dtype = crate::protocol::dtype_to_dtype(h.dtype)
1080 .ok_or_else(|| crate::Error::NotImplemented(format!("blob dtype code {}", h.dtype)))?;
1081 let strings = v.strings();
1082 let format = if strings.format.is_empty() {
1083 None
1084 } else {
1085 Some(
1086 crate::PixelFormat::from_str_code(strings.format).ok_or_else(|| {
1087 crate::Error::NotImplemented(format!("blob format {:?}", strings.format))
1088 })?,
1089 )
1090 };
1091
1092 if !inline {
1093 #[cfg(not(unix))]
1094 {
1095 return Err(crate::Error::NotImplemented(
1096 "reference-mode blob import is Unix-only".into(),
1097 ));
1098 }
1099 #[cfg(unix)]
1100 {
1101 return import_referenced_blob(&v, &h, &planes, fds, format, &strings, dtype);
1102 }
1103 }
1104
1105 let grid: Vec<usize> = v.shape().iter().map(|d| *d as usize).collect();
1109 let alloc_shape = alloc_shape_from_grid(format, &grid)?;
1110
1111 let mut t = crate::TensorDyn::new(&alloc_shape, dtype, Some(crate::TensorMemory::Mem), None)?;
1112 if let Some(f) = format {
1113 t.set_format(f)?;
1114 }
1115
1116 blit_inline_planes(&mut t, &planes, format, &grid, dtype)?;
1121
1122 t.set_colorimetry(colorimetry_from(&strings));
1123 if h.quant_axis != -2 {
1124 let scales: Vec<f32> = (0..h.quant_scales_len as usize)
1125 .map(|i| {
1126 let mut b = [0u8; 4];
1127 let off = v.regions.quant_scales + i * 4;
1128 b.copy_from_slice(&blob[off..off + 4]);
1129 f32::from_le_bytes(b)
1130 })
1131 .collect();
1132 let zeros: Vec<i32> = (0..h.quant_zero_points_len as usize)
1133 .map(|i| {
1134 let off = v.regions.quant_zero_points + i * 4;
1135 rd_u32(blob, off) as i32
1136 })
1137 .collect();
1138 if let Some(q) = quantization_from(h.quant_axis, &scales, &zeros) {
1139 t.set_quantization(q)?;
1140 }
1141 }
1142 Ok(t)
1143}
1144
1145fn validate_import_planes(planes: &[BlobPlane<'_>], inline: bool) -> crate::Result<()> {
1146 if planes.iter().any(|p| p.is_inline() != inline) {
1147 return Err(crate::Error::InvalidArgument(
1148 "blob mixes inline and referenced planes; a tensor has one transport mode".into(),
1149 ));
1150 }
1151 for p in planes {
1152 if p.is_inline() {
1153 if p.size as usize != p.data.len() {
1155 return Err(crate::Error::InvalidArgument(format!(
1156 "inline plane declares size {} but carries {} bytes",
1157 p.size,
1158 p.data.len()
1159 )));
1160 }
1161 if p.modifier != 0 || !p.handle_bytes.is_empty() {
1162 return Err(crate::Error::InvalidArgument(
1163 "inline plane must have modifier 0 and no handle_bytes".into(),
1164 ));
1165 }
1166 } else if !p.data.is_empty() {
1167 return Err(crate::Error::InvalidArgument(
1168 "referenced plane must not also carry inline bytes".into(),
1169 ));
1170 }
1171 }
1172 Ok(())
1173}
1174
1175fn alloc_shape_from_grid(
1176 format: Option<crate::PixelFormat>,
1177 grid: &[usize],
1178) -> crate::Result<Vec<usize>> {
1179 match format {
1180 Some(f) => {
1181 let (w, h_px) = dims_from_grid(f, grid)?;
1182 f.allocation_shape(w, h_px)
1183 .ok_or_else(|| crate::Error::InvalidShape(format!("no allocation shape for {f:?}")))
1184 }
1185 None => Ok(grid.to_vec()),
1186 }
1187}
1188
1189#[cfg(unix)]
1190fn import_referenced_blob(
1191 v: &BlobView<'_>,
1192 h: &BlobHeader,
1193 planes: &[BlobPlane<'_>],
1194 fds: &[BlobFd],
1195 format: Option<crate::PixelFormat>,
1196 strings: &BlobStrings<'_>,
1197 dtype: crate::DType,
1198) -> crate::Result<crate::TensorDyn> {
1199 let first = planes
1204 .first()
1205 .ok_or_else(|| crate::Error::InvalidArgument("referenced blob carries no planes".into()))?;
1206 let idx = usize::try_from(first.handle).map_err(|_| {
1207 crate::Error::InvalidArgument(format!("plane handle {} is not an index", first.handle))
1208 })?;
1209 let raw = *fds.get(idx).ok_or_else(|| {
1210 crate::Error::InvalidArgument(format!(
1211 "plane handle indexes fd {idx} but only {} were provided",
1212 fds.len()
1213 ))
1214 })?;
1215 for p in planes {
1216 let i = usize::try_from(p.handle).unwrap_or(usize::MAX);
1217 if i >= fds.len() {
1218 return Err(crate::Error::InvalidArgument(format!(
1219 "plane handle indexes fd {i} but only {} were provided",
1220 fds.len()
1221 )));
1222 }
1223 }
1224
1225 let grid: Vec<usize> = v.shape().iter().map(|d| *d as usize).collect();
1226 let alloc_shape = alloc_shape_from_grid(format, &grid)?;
1227 let mut t = open_referenced_tensor(h.storage_kind, raw, &alloc_shape, dtype)?;
1228 if let Some(f) = format {
1229 t.set_format(f)?;
1230 }
1231 if first.stride > 0 {
1232 let _ = t.set_row_stride(first.stride as usize);
1234 }
1235 t.set_colorimetry(colorimetry_from(strings));
1236 Ok(t)
1237}
1238
1239#[cfg(unix)]
1240fn open_referenced_tensor(
1241 storage_kind: u32,
1242 raw: BlobFd,
1243 alloc_shape: &[usize],
1244 dtype: crate::DType,
1245) -> crate::Result<crate::TensorDyn> {
1246 match crate::TensorMemory::from_code(storage_kind) {
1252 #[cfg(any(target_os = "macos", target_os = "ios"))]
1253 Some(crate::TensorMemory::DmaBuf) | Some(crate::TensorMemory::IoSurface) => {
1254 let id = u32::try_from(raw).map_err(|_| {
1255 crate::Error::InvalidArgument(format!("{raw} is not an IOSurface id"))
1256 })?;
1257 crate::TensorDyn::from_iosurface_id(id, alloc_shape, dtype, None)
1258 }
1259 #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
1260 Some(crate::TensorMemory::DmaBuf) | Some(crate::TensorMemory::Shm) => {
1261 use std::os::fd::{BorrowedFd, OwnedFd};
1262 let borrowed = unsafe { BorrowedFd::borrow_raw(raw) };
1267 let owned: OwnedFd = borrowed
1268 .try_clone_to_owned()
1269 .map_err(crate::Error::IoError)?;
1270 crate::TensorDyn::from_fd(owned, alloc_shape, dtype, None)
1271 }
1272 other => Err(crate::Error::NotImplemented(format!(
1273 "reference-mode import for {other:?} on this platform"
1274 ))),
1275 }
1276}
1277
1278fn blit_inline_planes(
1279 t: &mut crate::TensorDyn,
1280 planes: &[BlobPlane<'_>],
1281 format: Option<crate::PixelFormat>,
1282 grid: &[usize],
1283 dtype: crate::DType,
1284) -> crate::Result<()> {
1285 let pin = t.pin_host(crate::CpuAccess::Write)?;
1286 let dst = unsafe { std::slice::from_raw_parts_mut(pin.as_mut_ptr(), pin.len()) };
1287 let geoms: Vec<crate::PlaneGeometry> = match format {
1288 Some(f) => {
1289 let (w, h_px) = dims_from_grid(f, grid)?;
1290 let rs = t
1291 .effective_row_stride()
1292 .unwrap_or(w * f.channels() * dtype.size());
1293 f.plane_table(w, h_px, rs)
1294 .ok_or_else(|| crate::Error::InvalidShape(format!("no plane table for {f:?}")))?
1295 }
1296 None => vec![crate::PlaneGeometry {
1297 offset: 0,
1298 stride: dst.len() as u64,
1299 size: dst.len() as u64,
1300 }],
1301 };
1302 if geoms.len() != planes.len() {
1303 return Err(crate::Error::InvalidArgument(format!(
1304 "blob carries {} planes but {:?} has {}",
1305 planes.len(),
1306 format,
1307 geoms.len()
1308 )));
1309 }
1310 for (g, p) in geoms.iter().zip(planes) {
1311 let start = g.offset as usize;
1312 let end = start
1313 .checked_add(p.data.len())
1314 .ok_or(crate::Error::InvalidSize(p.data.len()))?;
1315 let slot = dst
1316 .get_mut(start..end)
1317 .ok_or(crate::Error::InvalidSize(end))?;
1318 slot.copy_from_slice(p.data);
1319 }
1320 Ok(())
1321}
1322
1323fn colorimetry_from(s: &BlobStrings<'_>) -> Option<crate::Colorimetry> {
1327 let c = crate::Colorimetry {
1328 space: crate::ColorSpace::from_str_code(s.color_space),
1329 transfer: crate::ColorTransfer::from_str_code(s.color_transfer),
1330 encoding: crate::ColorEncoding::from_str_code(s.color_encoding),
1331 range: crate::ColorRange::from_str_code(s.color_range),
1332 };
1333 if c == crate::Colorimetry::default() {
1334 None
1335 } else {
1336 Some(c)
1337 }
1338}
1339
1340fn quantization_from(axis: i32, scales: &[f32], zeros: &[i32]) -> Option<crate::Quantization> {
1342 let first = *scales.first()?;
1343 match (axis, zeros.first()) {
1344 (-1, None) => Some(crate::Quantization::per_tensor_symmetric(first)),
1345 (-1, Some(z)) => Some(crate::Quantization::per_tensor(first, *z)),
1346 (a, _) if a >= 0 => {
1347 crate::Quantization::per_channel_symmetric(scales.to_vec(), a as usize).ok()
1348 }
1349 _ => None,
1350 }
1351}
1352
1353fn dims_from_grid(f: crate::PixelFormat, grid: &[usize]) -> crate::Result<(usize, usize)> {
1357 let bad = || crate::Error::InvalidShape(format!("grid {grid:?} is not an image for {f:?}"));
1358 match f.layout() {
1359 crate::PixelLayout::Planar => {
1360 if grid.len() < 3 {
1361 return Err(bad());
1362 }
1363 Ok((grid[2], grid[1]))
1364 }
1365 _ => {
1368 if grid.len() < 2 {
1369 return Err(bad());
1370 }
1371 Ok((grid[1], grid[0]))
1372 }
1373 }
1374}
1375
1376#[cfg(test)]
1377mod tests {
1378 use super::*;
1379
1380 #[test]
1381 fn every_region_offset_is_constant_time_arithmetic() {
1382 let h = BlobHeader {
1384 size: 4096,
1385 ndim: 3,
1386 strides_len: 3,
1387 quant_scales_len: 4,
1388 quant_zero_points_len: 4,
1389 strings_bytes: 24,
1390 plane_count: 2,
1391 planes_bytes: 128,
1392 ..BlobHeader::empty()
1393 };
1394 let r = region_offsets(&h).unwrap();
1395 assert_eq!(r.shape, HEADER_LEN);
1396 assert_eq!(r.strides, HEADER_LEN + 3 * 8);
1397 assert_eq!(r.quant_scales, HEADER_LEN + 3 * 8 + 3 * 8);
1398 assert_eq!(r.quant_zero_points, r.quant_scales + 4 * 4);
1399 assert_eq!(r.strings, r.quant_zero_points + 4 * 4);
1400 assert_eq!(r.planes, r.strings + 24);
1401 assert_eq!(r.end, r.planes + 128);
1402 }
1403
1404 #[test]
1405 fn empty_strides_leave_the_region_zero_width() {
1406 let h = BlobHeader {
1407 size: 4096,
1408 ndim: 2,
1409 strides_len: 0,
1410 ..BlobHeader::empty()
1411 };
1412 let r = region_offsets(&h).unwrap();
1413 assert_eq!(r.strides, HEADER_LEN + 2 * 8);
1414 assert_eq!(
1415 r.quant_scales, r.strides,
1416 "densely-packed strides occupy no bytes"
1417 );
1418 }
1419
1420 #[test]
1421 fn strides_must_be_ndim_entries_or_none() {
1422 let h = BlobHeader {
1424 size: 4096,
1425 ndim: 3,
1426 strides_len: 2,
1427 ..BlobHeader::empty()
1428 };
1429 assert!(matches!(
1430 region_offsets(&h),
1431 Err(BlobError::ForgedLength {
1432 field: "strides_len",
1433 ..
1434 })
1435 ));
1436 }
1437
1438 #[test]
1439 fn a_forged_ndim_cannot_derive_an_offset_past_the_buffer() {
1440 let h = BlobHeader {
1441 size: 72,
1442 ndim: u32::MAX,
1443 strides_len: u32::MAX,
1444 ..BlobHeader::empty()
1445 };
1446 assert!(
1447 region_offsets(&h).is_err(),
1448 "ndim * 8 must be checked, not wrapped"
1449 );
1450 }
1451
1452 #[test]
1453 fn regions_that_overrun_size_are_refused() {
1454 let h = BlobHeader {
1457 size: 80,
1458 ndim: 2,
1459 strides_len: 2,
1460 plane_count: 1,
1461 planes_bytes: 4096,
1462 ..BlobHeader::empty()
1463 };
1464 assert!(region_offsets(&h).is_err());
1465 }
1466
1467 #[test]
1468 fn plane_count_is_bounded_by_the_planes_region_not_by_a_constant() {
1469 let h = BlobHeader {
1472 size: 4096,
1473 plane_count: u32::MAX,
1474 planes_bytes: 56,
1475 ..BlobHeader::empty()
1476 };
1477 assert!(matches!(
1478 region_offsets(&h),
1479 Err(BlobError::ForgedLength {
1480 field: "plane_count",
1481 ..
1482 })
1483 ));
1484 }
1485
1486 fn fill_ramp(t: &TensorDyn) {
1489 let pin = t.pin_host(CpuAccess::ReadWrite).expect("pin");
1490 let s = unsafe { std::slice::from_raw_parts_mut(pin.as_mut_ptr(), pin.len()) };
1491 for (i, b) in s.iter_mut().enumerate() {
1492 *b = (i % 251) as u8;
1493 }
1494 }
1495
1496 fn bytes_of(t: &TensorDyn) -> Vec<u8> {
1497 let pin = t.pin_host(CpuAccess::Read).expect("pin");
1498 unsafe { std::slice::from_raw_parts(pin.as_mut_ptr(), pin.len()) }.to_vec()
1499 }
1500
1501 fn reference_capable(w: usize, h: usize) -> Option<TensorDyn> {
1504 match Tensor::<u8>::image(
1505 w,
1506 h,
1507 PixelFormat::Nv12,
1508 Some(TensorMemory::DmaBuf),
1509 CpuAccess::ReadWrite,
1510 ) {
1511 Ok(t) => Some(TensorDyn::from(t)),
1512 Err(e) => {
1513 use std::io::Write;
1514 let _ = writeln!(
1515 std::io::stderr(),
1516 "SKIP: no shareable backing on this host ({e:?}); \
1517 reference transport not exercised"
1518 );
1519 None
1520 }
1521 }
1522 }
1523
1524 #[test]
1525 fn a_reference_export_puts_an_index_in_the_handle_not_a_raw_fd() {
1526 let Some(src) = reference_capable(64, 48) else {
1527 return;
1528 };
1529 let (blob, fds) = export(&src, TransportMode::Reference).unwrap();
1530 let v = BlobView::parse(&blob).unwrap();
1531 let planes = v.planes().unwrap();
1532 assert!(!fds.is_empty(), "reference mode must carry an fd table");
1533 for p in &planes {
1534 assert!(!p.is_inline());
1535 assert!(
1536 (p.handle as usize) < fds.len(),
1537 "handle {} must index the {}-entry fd table, not be a raw fd",
1538 p.handle,
1539 fds.len()
1540 );
1541 }
1542 assert_eq!(fds.len(), 1, "planes sharing a buffer share a table entry");
1546 assert!(planes.iter().all(|p| p.handle == 0));
1547 }
1548
1549 #[test]
1550 fn a_reference_round_trip_preserves_content_and_the_source_may_die() {
1551 let Some(src) = reference_capable(64, 48) else {
1552 return;
1553 };
1554 fill_ramp(&src);
1555 let expected = bytes_of(&src);
1556 let (blob, fds) = export(&src, TransportMode::Reference).unwrap();
1557 let src_id = src.buffer_identity().id();
1558 let got = import(&blob, &fds).unwrap();
1559
1560 assert_eq!(
1566 got.buffer_identity().id(),
1567 src_id,
1568 "reference mode must share the buffer, not copy it"
1569 );
1570
1571 drop(src);
1573 assert_eq!(
1574 bytes_of(&got),
1575 expected,
1576 "the imported tensor sees the bytes"
1577 );
1578 assert_eq!(got.format(), Some(PixelFormat::Nv12));
1579 }
1580
1581 #[test]
1582 fn an_fd_index_past_the_table_is_refused() {
1583 let Some(src) = reference_capable(64, 48) else {
1586 return;
1587 };
1588 let (blob, fds) = export(&src, TransportMode::Reference).unwrap();
1589 assert!(
1590 import(&blob, &[]).is_err(),
1591 "an empty table cannot satisfy an index"
1592 );
1593 let mut tampered = blob.clone();
1594 let r = region_offsets(&BlobView::parse(&blob).unwrap().header()).unwrap();
1595 tampered[r.planes..r.planes + 8].copy_from_slice(&99i64.to_le_bytes());
1596 assert!(
1597 import(&tampered, &fds).is_err(),
1598 "an out-of-range index must be refused, not read"
1599 );
1600 }
1601
1602 #[test]
1603 fn inline_round_trip_preserves_content_and_every_metadata_field() {
1604 let src = nv12_mem(64, 48).with_colorimetry(crate::Colorimetry::jfif());
1605 fill_ramp(&src);
1606 let blob = export(&src, TransportMode::Inline).unwrap().0;
1607 let got = import(&blob, &[]).unwrap();
1608
1609 assert_eq!(got.dtype(), src.dtype());
1610 assert_eq!(got.format(), src.format());
1611 assert_eq!(got.colorimetry(), src.colorimetry());
1612 assert_eq!(got.shape(), src.shape(), "allocation geometry is rebuilt");
1613 assert_eq!(bytes_of(&got), bytes_of(&src), "every byte survived");
1614 }
1615
1616 #[test]
1617 fn inline_import_is_a_new_allocation_and_the_source_may_die() {
1618 let src = nv12_mem(64, 48);
1619 fill_ramp(&src);
1620 let expected = bytes_of(&src);
1621 let blob = export(&src, TransportMode::Inline).unwrap().0;
1622 drop(src);
1623 let got = import(&blob, &[]).unwrap();
1624 assert_eq!(bytes_of(&got), expected);
1625 }
1626
1627 #[test]
1628 fn a_bare_tensor_round_trips_including_byte_strides() {
1629 let src =
1630 TensorDyn::from(Tensor::<f32>::new(&[3, 8], Some(TensorMemory::Mem), None).unwrap());
1631 fill_ramp(&src);
1632 let blob = export(&src, TransportMode::Inline).unwrap().0;
1633 let got = import(&blob, &[]).unwrap();
1634 assert_eq!(got.shape(), &[3, 8]);
1635 assert_eq!(got.dtype(), crate::DType::F32);
1636 assert_eq!(got.format(), None);
1637 assert_eq!(bytes_of(&got), bytes_of(&src));
1638 }
1639
1640 fn build_blob(planes: &[BlobPlane<'_>]) -> Vec<u8> {
1648 let strings = BlobStrings::default();
1649 let mut planes_buf = Vec::new();
1650 for p in planes {
1651 write_plane(p, &mut planes_buf);
1652 }
1653 let mut out = vec![0u8; HEADER_LEN];
1654 out.extend_from_slice(&1u64.to_le_bytes()); write_strings(&strings, &mut out);
1656 out.extend_from_slice(&planes_buf);
1657 let h = BlobHeader {
1658 size: out.len() as u64,
1659 planes_bytes: planes_buf.len() as u64,
1660 dtype: crate::DType::U8.code(),
1661 quant_axis: -2,
1662 ndim: 1,
1663 plane_count: planes.len() as u32,
1664 strings_bytes: strings_encoded_len(&strings) as u32,
1665 ..BlobHeader::empty()
1666 };
1667 h.write_to(&mut out);
1668 out
1669 }
1670
1671 #[test]
1672 fn no_corrupted_header_value_can_panic_the_parser() {
1673 let good = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1679 for word in 0..HEADER_LEN / 4 {
1680 for pattern in [u32::MAX, u32::MAX / 2, 1, 0x8000_0000] {
1681 let mut bad = good.clone();
1682 bad[word * 4..word * 4 + 4].copy_from_slice(&pattern.to_le_bytes());
1683 if let Ok(v) = BlobView::parse(&bad) {
1686 let _ = v.shape();
1687 let _ = v.strides();
1688 let _ = v.strings();
1689 let _ = v.planes();
1690 }
1691 let _ = import(&bad, &[]);
1692 }
1693 }
1694 }
1695
1696 #[test]
1697 fn no_corrupted_plane_record_can_panic_the_parser() {
1698 let good = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1699 let r = region_offsets(&BlobView::parse(&good).unwrap().header()).unwrap();
1700 for word in 0..PLANE_RECORD_LEN / 4 {
1701 for pattern in [u32::MAX, u32::MAX / 2, 0x8000_0000] {
1702 let mut bad = good.clone();
1703 let at = r.planes + word * 4;
1704 bad[at..at + 4].copy_from_slice(&pattern.to_le_bytes());
1705 if let Ok(v) = BlobView::parse(&bad) {
1706 let _ = v.planes();
1707 }
1708 let _ = import(&bad, &[]);
1709 }
1710 }
1711 }
1712
1713 #[test]
1714 fn a_mixed_inline_and_referenced_plane_set_is_refused() {
1715 let payload = [1u8, 2, 3, 4];
1723 let referenced = BlobPlane {
1724 handle: 7,
1725 offset: 0,
1726 stride: 4,
1727 size: 4,
1728 used: 4,
1729 modifier: 0,
1730 handle_bytes: &[],
1731 data: &[],
1732 };
1733 let inlined = BlobPlane {
1734 handle: -1,
1735 offset: 0,
1736 stride: 4,
1737 size: payload.len() as u64,
1738 used: payload.len() as u64,
1739 modifier: 0,
1740 handle_bytes: &[],
1741 data: &payload,
1742 };
1743 let blob = build_blob(&[referenced, inlined]);
1744 let err =
1745 import(&blob, &[]).expect_err("a plane set mixing transport modes must be refused");
1746 assert!(
1747 err.to_string().contains("one transport mode"),
1748 "must fail on the mode mix, not incidentally: {err}"
1749 );
1750 }
1751
1752 #[test]
1753 fn a_uniformly_referenced_plane_set_is_not_rejected_as_mixed() {
1754 let referenced = BlobPlane {
1759 handle: 7,
1760 offset: 0,
1761 stride: 4,
1762 size: 4,
1763 used: 4,
1764 modifier: 0,
1765 handle_bytes: &[],
1766 data: &[],
1767 };
1768 let blob = build_blob(&[referenced, referenced]);
1769 let err = import(&blob, &[]).expect_err("reference-mode import is not implemented yet");
1770 assert!(
1771 !err.to_string().contains("one transport mode"),
1772 "a uniform plane set must clear the mode check: {err}"
1773 );
1774 }
1775
1776 #[test]
1777 fn an_inline_plane_whose_size_disagrees_with_its_data_is_refused() {
1778 let blob = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1779 let mut tampered = blob.clone();
1780 let r = region_offsets(&BlobView::parse(&blob).unwrap().header()).unwrap();
1781 tampered[r.planes + 24..r.planes + 32].copy_from_slice(&9999u64.to_le_bytes());
1782 assert!(import(&tampered, &[]).is_err());
1783 }
1784
1785 #[test]
1786 fn quantization_survives_the_round_trip() {
1787 let mut src =
1788 TensorDyn::from(Tensor::<i8>::new(&[3, 8], Some(TensorMemory::Mem), None).unwrap());
1789 src.set_quantization(crate::Quantization::per_tensor(0.5, -7))
1790 .unwrap();
1791 let blob = export(&src, TransportMode::Inline).unwrap().0;
1792 let got = import(&blob, &[]).unwrap();
1793 let q = got.quantization().expect("quantization survived");
1794 assert_eq!(q.scale(), &[0.5]);
1795 assert_eq!(q.zero_point(), Some(&[-7i32][..]));
1796 }
1797
1798 use crate::{CpuAccess, PixelFormat, Tensor, TensorDyn, TensorMemory};
1799
1800 fn nv12_mem(w: usize, h: usize) -> TensorDyn {
1801 TensorDyn::from(
1802 Tensor::<u8>::image(
1803 w,
1804 h,
1805 PixelFormat::Nv12,
1806 Some(TensorMemory::Mem),
1807 CpuAccess::ReadWrite,
1808 )
1809 .expect("alloc"),
1810 )
1811 }
1812
1813 #[test]
1814 fn export_carries_the_addressing_grid_not_the_allocation() {
1815 let blob = export(&nv12_mem(640, 480), TransportMode::Inline)
1816 .unwrap()
1817 .0;
1818 let v = BlobView::parse(&blob).unwrap();
1819 assert_eq!(v.shape(), &[480, 640]);
1821 assert_eq!(v.header().dtype, crate::DType::U8.code());
1822 assert_eq!(v.strings().format, "NV12");
1823 }
1824
1825 #[test]
1826 fn export_emits_one_plane_per_plane_table_entry() {
1827 let blob = export(&nv12_mem(640, 480), TransportMode::Inline)
1828 .unwrap()
1829 .0;
1830 let v = BlobView::parse(&blob).unwrap();
1831 let planes = v.planes().unwrap();
1832 assert_eq!(planes.len(), 2, "NV12 is two planes");
1833 assert_eq!(planes[0].size as usize, planes[0].data.len());
1835 assert_eq!(planes[1].size as usize, planes[1].data.len());
1836 assert_eq!(planes[0].size, 307_200);
1837 assert_eq!(planes[1].size, 153_600);
1838 }
1839
1840 #[test]
1841 fn inline_export_clears_pid_and_fence_fd() {
1842 let blob = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1845 let h = BlobView::parse(&blob).unwrap().header();
1846 assert_eq!(h.pid, 0, "no handle to reopen");
1847 assert_eq!(h.fence_fd, -1, "inlining means the producer already waited");
1848 }
1849
1850 #[test]
1851 fn inline_planes_carry_no_modifier_and_no_handle_bytes() {
1852 let blob = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1854 for p in BlobView::parse(&blob).unwrap().planes().unwrap() {
1855 assert!(p.is_inline());
1856 assert_eq!(p.modifier, 0);
1857 assert!(p.handle_bytes.is_empty());
1858 assert_eq!(p.size as usize, p.data.len());
1859 }
1860 }
1861
1862 #[test]
1863 fn reference_export_of_a_mem_tensor_is_refused() {
1864 assert!(export(&nv12_mem(64, 48), TransportMode::Reference).is_err());
1868 }
1869
1870 #[test]
1871 fn a_blob_parses_correctly_from_a_non_zero_offset() {
1872 let blob = export(&nv12_mem(64, 48), TransportMode::Inline).unwrap().0;
1875 let mut framed = vec![0xAAu8; 16];
1876 framed.extend_from_slice(&blob);
1877 let direct = BlobView::parse(&blob).unwrap();
1878 let framed_view = BlobView::parse(&framed[16..]).unwrap();
1879 assert_eq!(framed_view.header(), direct.header());
1880 assert_eq!(framed_view.shape(), direct.shape());
1881 assert_eq!(framed_view.strings(), direct.strings());
1882 }
1883
1884 #[test]
1885 fn colorimetry_axes_are_carried_as_strings() {
1886 let t = nv12_mem(64, 48).with_colorimetry(crate::Colorimetry::jfif());
1887 let blob = export(&t, TransportMode::Inline).unwrap().0;
1888 let v = BlobView::parse(&blob).unwrap();
1889 let s = v.strings();
1890 assert_eq!(s.color_space, "srgb");
1891 assert_eq!(s.color_transfer, "srgb");
1892 assert_eq!(s.color_encoding, "bt601");
1893 assert_eq!(s.color_range, "full");
1894 }
1895
1896 #[test]
1897 fn a_bare_tensor_carries_no_format_and_its_shape_is_its_grid() {
1898 let t =
1899 TensorDyn::from(Tensor::<f32>::new(&[3, 8], Some(TensorMemory::Mem), None).unwrap());
1900 let blob = export(&t, TransportMode::Inline).unwrap().0;
1901 let v = BlobView::parse(&blob).unwrap();
1902 assert_eq!(v.strings().format, "", "not an image");
1903 assert_eq!(v.shape(), &[3, 8]);
1904 assert_eq!(v.strides(), &[32i64, 4], "byte strides for f32");
1905 }
1906
1907 fn ref_plane() -> BlobPlane<'static> {
1908 BlobPlane {
1909 handle: 7,
1910 offset: 0,
1911 stride: 640,
1912 size: 307_200,
1913 used: 307_200,
1914 modifier: 0,
1915 handle_bytes: &[],
1916 data: &[],
1917 }
1918 }
1919
1920 #[test]
1921 fn a_plane_record_round_trips() {
1922 let mut out = Vec::new();
1923 write_plane(&ref_plane(), &mut out);
1924 let got = parse_planes(&out, 1).unwrap();
1925 assert_eq!(got.len(), 1);
1926 assert_eq!(got[0], ref_plane());
1927 }
1928
1929 #[test]
1930 fn a_plane_scalar_block_is_56_bytes() {
1931 let mut out = Vec::new();
1934 write_plane(&ref_plane(), &mut out);
1935 assert_eq!(out.len(), PLANE_RECORD_LEN);
1936 assert_eq!(PLANE_RECORD_LEN, 56);
1937 }
1938
1939 #[test]
1940 fn every_plane_record_starts_8_aligned_whatever_the_payload() {
1941 for n in 0..17usize {
1944 let payload: Vec<u8> = (0..n as u8).collect();
1945 let mut out = Vec::new();
1946 write_plane(
1947 &BlobPlane {
1948 handle: -1,
1949 size: n as u64,
1950 data: &payload,
1951 ..ref_plane()
1952 },
1953 &mut out,
1954 );
1955 write_plane(&ref_plane(), &mut out);
1956 assert_eq!(
1957 out.len() % 8,
1958 0,
1959 "payload of {n} bytes left the region unaligned"
1960 );
1961 let got = parse_planes(&out, 2).unwrap();
1962 assert_eq!(got[0].data, &payload[..], "payload {n} survived padding");
1963 assert_eq!(
1964 got[1],
1965 ref_plane(),
1966 "the second record parsed from an aligned offset"
1967 );
1968 }
1969 }
1970
1971 #[test]
1972 fn plane_payloads_round_trip_independently() {
1973 let hb = [0xDEu8, 0xAD, 0xBE, 0xEF];
1976 let data = [1u8, 2, 3];
1977 let mut out = Vec::new();
1978 write_plane(
1979 &BlobPlane {
1980 handle_bytes: &hb,
1981 data: &data,
1982 ..ref_plane()
1983 },
1984 &mut out,
1985 );
1986 let got = parse_planes(&out, 1).unwrap();
1987 assert_eq!(got[0].handle_bytes, &hb[..]);
1988 assert_eq!(got[0].data, &data[..]);
1989 }
1990
1991 #[test]
1992 fn a_forged_plane_payload_length_is_refused() {
1993 let mut out = vec![0u8; PLANE_RECORD_LEN];
1994 out[48..52].copy_from_slice(&u32::MAX.to_le_bytes()); assert!(matches!(
1996 parse_planes(&out, 1),
1997 Err(BlobError::ForgedLength { .. })
1998 ));
1999 }
2000
2001 #[test]
2002 fn a_plane_count_larger_than_the_records_present_is_refused() {
2003 let mut out = Vec::new();
2004 write_plane(&ref_plane(), &mut out);
2005 assert!(
2006 parse_planes(&out, 2).is_err(),
2007 "claims two records, holds one"
2008 );
2009 }
2010
2011 fn sample_strings() -> BlobStrings<'static> {
2012 BlobStrings {
2013 format: "NV12",
2014 color_space: "bt709",
2015 color_transfer: "bt709",
2016 color_encoding: "bt709",
2017 color_range: "limited",
2018 }
2019 }
2020
2021 #[test]
2022 fn the_five_strings_round_trip_in_message_order() {
2023 let mut out = Vec::new();
2024 write_strings(&sample_strings(), &mut out);
2025 let got = parse_strings(&out).unwrap();
2026 assert_eq!(got, sample_strings());
2027 }
2028
2029 #[test]
2030 fn string_order_is_positional_and_matches_the_message() {
2031 let s = BlobStrings {
2035 format: "a",
2036 color_space: "bb",
2037 color_transfer: "ccc",
2038 color_encoding: "dddd",
2039 color_range: "eeeee",
2040 };
2041 let mut out = Vec::new();
2042 write_strings(&s, &mut out);
2043 assert_eq!(parse_strings(&out).unwrap(), s);
2044 }
2045
2046 #[test]
2047 fn empty_strings_are_legal_and_distinct_from_absent() {
2048 let s = BlobStrings::default();
2050 let mut out = Vec::new();
2051 write_strings(&s, &mut out);
2052 assert_eq!(parse_strings(&out).unwrap(), s);
2053 assert!(
2054 !out.is_empty(),
2055 "five empty strings still occupy their counts"
2056 );
2057 }
2058
2059 #[test]
2060 fn the_strings_region_is_padded_so_planes_start_8_aligned() {
2061 for f in ["", "a", "ab", "abc", "NV12", "rgb8_planar"] {
2064 let mut out = Vec::new();
2065 write_strings(
2066 &BlobStrings {
2067 format: f,
2068 ..Default::default()
2069 },
2070 &mut out,
2071 );
2072 assert_eq!(out.len() % 8, 0, "region for format {f:?} is not 8-aligned");
2073 }
2074 }
2075
2076 #[test]
2077 fn a_non_utf8_string_is_refused() {
2078 let mut out = Vec::new();
2081 out.extend_from_slice(&2u32.to_le_bytes());
2082 out.extend_from_slice(&[0xff, 0xfe]);
2083 for _ in 0..4 {
2084 out.extend_from_slice(&0u32.to_le_bytes());
2085 }
2086 while out.len() % 8 != 0 {
2087 out.push(0);
2088 }
2089 assert!(matches!(
2090 parse_strings(&out),
2091 Err(BlobError::InvalidUtf8(_))
2092 ));
2093 }
2094
2095 #[test]
2096 fn a_forged_string_length_cannot_read_past_the_region() {
2097 let mut out = Vec::new();
2098 out.extend_from_slice(&u32::MAX.to_le_bytes());
2099 out.extend_from_slice(b"NV12");
2100 while out.len() % 8 != 0 {
2101 out.push(0);
2102 }
2103 assert!(matches!(
2104 parse_strings(&out),
2105 Err(BlobError::ForgedLength { .. })
2106 ));
2107 }
2108
2109 #[test]
2110 fn a_truncated_strings_region_is_refused() {
2111 assert!(parse_strings(&[0u8; 4]).is_err(), "needs five counts");
2112 }
2113
2114 #[test]
2115 fn an_overrunning_region_is_attributed_to_its_own_field() {
2116 let h = BlobHeader {
2122 size: 4096,
2123 ndim: 2,
2124 strides_len: 2,
2125 strings_bytes: u32::MAX,
2126 ..BlobHeader::empty()
2127 };
2128 assert!(
2129 matches!(
2130 region_offsets(&h),
2131 Err(BlobError::ForgedLength {
2132 field: "strings_bytes",
2133 ..
2134 })
2135 ),
2136 "the overrun must name strings_bytes, not a later field"
2137 );
2138 }
2139
2140 #[test]
2141 fn header_is_72_bytes_and_every_scalar_is_naturally_aligned() {
2142 assert_eq!(HEADER_LEN, 72);
2143 assert_eq!(HEADER_LEN % 8, 0, "the variable tail starts 8-aligned");
2144 for off in [0usize, 8, 16] {
2145 assert_eq!(off % 8, 0, "u64 field at {off} is not 8-aligned");
2146 }
2147 for off in [24usize, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64] {
2148 assert_eq!(off % 4, 0, "u32 field at {off} is not 4-aligned");
2149 }
2150 }
2151
2152 #[test]
2153 fn a_header_round_trips_through_bytes() {
2154 let h = BlobHeader {
2155 size: 72,
2156 required_mask: 0,
2157 planes_bytes: 0,
2158 storage_kind: 2,
2159 pid: 4242,
2160 fence_fd: -1,
2161 dtype: 1,
2162 quant_axis: -2,
2163 ndim: 2,
2164 plane_count: 0,
2165 quant_scales_len: 0,
2166 quant_zero_points_len: 0,
2167 strings_bytes: 0,
2168 strides_len: 2,
2169 };
2170 let mut buf = vec![0u8; HEADER_LEN];
2171 h.write_to(&mut buf);
2172 assert_eq!(parse_header(&buf).unwrap(), h);
2173 }
2174
2175 #[test]
2176 fn the_header_is_little_endian_on_every_host() {
2177 let mut buf = vec![0u8; HEADER_LEN];
2178 BlobHeader {
2179 size: 0x0102_0304_0506_0708,
2180 ..BlobHeader::empty()
2181 }
2182 .write_to(&mut buf);
2183 assert_eq!(
2184 &buf[0..8],
2185 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01],
2186 "size must be little-endian regardless of host byte order"
2187 );
2188 }
2189
2190 #[test]
2191 fn a_truncated_buffer_is_refused_not_read() {
2192 assert!(matches!(
2193 parse_header(&[0u8; 71]),
2194 Err(BlobError::Truncated { .. })
2195 ));
2196 }
2197
2198 #[test]
2199 fn an_unknown_required_bit_is_refused() {
2200 let mut buf = vec![0u8; HEADER_LEN];
2201 BlobHeader {
2202 size: 72,
2203 required_mask: 1 << 63,
2204 ..BlobHeader::empty()
2205 }
2206 .write_to(&mut buf);
2207 assert!(matches!(
2208 parse_header(&buf),
2209 Err(BlobError::UnsupportedRequirement(_))
2210 ));
2211 }
2212
2213 #[test]
2214 fn a_size_smaller_than_the_header_is_refused() {
2215 let mut buf = vec![0u8; HEADER_LEN];
2216 BlobHeader {
2217 size: 8,
2218 ..BlobHeader::empty()
2219 }
2220 .write_to(&mut buf);
2221 assert!(
2222 parse_header(&buf).is_err(),
2223 "size must cover at least the header"
2224 );
2225 }
2226}