1use crate::file_ffi::file_document_mut;
12use alloc::ffi::CString;
13use core::ffi::{CStr, c_char, c_int, c_void};
14
15use asdf_core::compression::Compression;
16use asdf_core::core::datatype::{Datatype, ScalarType};
17use asdf_core::core::elements::{Element, decode_all};
18use asdf_core::core::ndarray::{Ndarray, Source};
19
20use crate::ffi::{CMallocBuf, write_out};
21use crate::panic::guard;
22use crate::types::AsdfArrayStorage;
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
26#[repr(i32)]
27pub enum NdarrayErr {
28 Ok = 0,
30 OutOfBounds,
32 Oom,
34 Inval,
36 Overflow,
38 Conversion,
40}
41
42#[repr(C)]
46#[derive(Debug)]
47pub struct asdf_datatype_t {
48 pub type_: ScalarTypeAbi,
50 pub size: u64,
52 pub name: *const c_char,
54 pub byteorder: ByteOrderAbi,
56 pub ndim: u32,
58 pub shape: *const u64,
60 pub nfields: u32,
62 pub fields: *const asdf_datatype_t,
64}
65
66pub type ScalarTypeAbi = i32;
68pub type ByteOrderAbi = i32;
70
71#[repr(C)]
76#[derive(Debug)]
77pub struct asdf_ndarray_t {
78 pub source: usize,
80 pub ndim: u32,
82 pub shape: *const u64,
84 pub datatype: asdf_datatype_t,
86 pub byteorder: ByteOrderAbi,
88 pub offset: u64,
90 pub strides: *const i64,
92 pub _reserved: *mut c_void,
94}
95
96#[repr(align(16))]
103#[derive(Clone, Copy, Debug)]
104struct Chunk(#[allow(dead_code)] [u8; 16]);
105
106#[derive(Clone, Debug)]
127pub(crate) struct AlignedBuf {
128 chunks: Vec<Chunk>,
129 len: usize,
131}
132
133impl AlignedBuf {
134 fn zeroed(len: usize) -> Self {
136 Self { chunks: vec![Chunk([0; 16]); len.div_ceil(16)], len }
137 }
138
139 fn from_slice(bytes: &[u8]) -> Self {
141 let mut buf = Self::zeroed(bytes.len());
142 buf.as_mut_slice()[..bytes.len()].copy_from_slice(bytes);
143 buf
144 }
145
146 fn as_slice(&self) -> &[u8] {
147 let bytes: &[u8] = unsafe {
151 core::slice::from_raw_parts(self.chunks.as_ptr().cast::<u8>(), self.chunks.len() * 16)
152 };
153 &bytes[..self.len]
154 }
155
156 fn as_mut_slice(&mut self) -> &mut [u8] {
157 let bytes: &mut [u8] = unsafe {
159 core::slice::from_raw_parts_mut(
160 self.chunks.as_mut_ptr().cast::<u8>(),
161 self.chunks.len() * 16,
162 )
163 };
164 &mut bytes[..self.len]
165 }
166
167 fn as_mut_ptr(&mut self) -> *mut u8 {
169 self.chunks.as_mut_ptr().cast::<u8>()
170 }
171}
172
173impl core::ops::Deref for AlignedBuf {
174 type Target = [u8];
175
176 fn deref(&self) -> &[u8] {
177 self.as_slice()
178 }
179}
180
181struct NdarrayState {
186 shape: Vec<u64>,
187 strides: Option<Vec<i64>>,
188 fields: Vec<asdf_datatype_t>,
190 field_names: Vec<CString>,
192 field_shapes: Vec<Vec<u64>>,
194 parsed: Ndarray,
196 data: Option<AlignedBuf>,
198 allocated: Option<AlignedBuf>,
200 compression: Compression,
202 storage: AsdfArrayStorage,
204 file: *mut crate::file_ffi::AsdfFile,
206 block_index: Option<usize>,
208 block: *mut crate::block_ffi::AsdfBlock,
210}
211
212fn scalar_abi(t: ScalarType) -> ScalarTypeAbi {
213 t as i32
214}
215
216pub(crate) fn scalar_from_abi_public(v: ScalarTypeAbi) -> ScalarType {
218 scalar_from_abi(v)
219}
220
221fn scalar_from_abi(v: ScalarTypeAbi) -> ScalarType {
222 match v {
223 1 => ScalarType::Int8,
224 2 => ScalarType::Uint8,
225 3 => ScalarType::Int16,
226 4 => ScalarType::Uint16,
227 5 => ScalarType::Int32,
228 6 => ScalarType::Uint32,
229 7 => ScalarType::Int64,
230 8 => ScalarType::Uint64,
231 9 => ScalarType::Float16,
232 10 => ScalarType::Float32,
233 11 => ScalarType::Float64,
234 12 => ScalarType::Complex64,
235 13 => ScalarType::Complex128,
236 14 => ScalarType::Bool8,
237 15 => ScalarType::Ascii,
238 16 => ScalarType::Ucs4,
239 17 => ScalarType::Structured,
240 _ => ScalarType::Unknown,
241 }
242}
243
244fn build_datatype(
246 datatype: &Datatype,
247 array_order: asdf_core::core::datatype::ByteOrder,
248 state: &mut NdarrayState,
249) -> asdf_datatype_t {
250 use asdf_core::core::datatype::ByteOrder;
251
252 let effective = |own: ByteOrder| if own == ByteOrder::Default { array_order } else { own };
255
256 for field in &datatype.fields {
257 let name = field.name.as_deref().and_then(|n| CString::new(n).ok()).unwrap_or_default();
258 state.field_names.push(name);
259 state.field_shapes.push(field.datatype.shape.clone());
260 }
261
262 let base = state.fields.len();
263 for (index, field) in datatype.fields.iter().enumerate() {
264 let name_ptr = state.field_names[base + index].as_ptr();
265 let shape = &state.field_shapes[base + index];
266 state.fields.push(asdf_datatype_t {
267 type_: scalar_abi(field.datatype.scalar),
268 size: field.datatype.item_size(),
269 name: name_ptr,
270 byteorder: effective(field.datatype.byteorder) as i32,
271 ndim: u32::try_from(shape.len()).unwrap_or(0),
272 shape: if shape.is_empty() { core::ptr::null() } else { shape.as_ptr() },
273 nfields: 0,
274 fields: core::ptr::null(),
275 });
276 }
277
278 asdf_datatype_t {
279 type_: scalar_abi(datatype.scalar),
280 size: datatype.item_size(),
281 name: core::ptr::null(),
282 byteorder: effective(datatype.byteorder) as i32,
283 ndim: 0,
284 shape: core::ptr::null(),
285 nfields: u32::try_from(datatype.fields.len()).unwrap_or(0),
286 fields: if state.fields.is_empty() {
287 core::ptr::null()
288 } else {
289 state.fields[base..].as_ptr()
290 },
291 }
292}
293
294pub(crate) fn make_ndarray(parsed: Ndarray, shape: Vec<u64>) -> *mut asdf_ndarray_t {
296 let mut state = Box::new(NdarrayState {
297 shape,
298 strides: parsed.strides.clone(),
299 fields: Vec::new(),
300 field_names: Vec::new(),
301 field_shapes: Vec::new(),
302 parsed: parsed.clone(),
303 data: None,
304 allocated: None,
305 compression: Compression::None,
306 storage: match parsed.source {
309 Source::Inline(_) => AsdfArrayStorage::Inline,
310 Source::External(_) => AsdfArrayStorage::External,
311 _ => AsdfArrayStorage::Internal,
312 },
313 file: core::ptr::null_mut(),
314 block_index: None,
315 block: core::ptr::null_mut(),
316 });
317
318 state.fields.reserve(parsed.datatype.fields.len());
321 state.field_names.reserve(parsed.datatype.fields.len());
322 state.field_shapes.reserve(parsed.datatype.fields.len());
323 let datatype = build_datatype(&parsed.datatype, parsed.byteorder, &mut state);
324
325 let source = match parsed.source {
326 Source::Block(index) => index,
327 _ => 0,
328 };
329
330 let shape_ptr = if state.shape.is_empty() { core::ptr::null() } else { state.shape.as_ptr() };
331 let strides_ptr = state.strides.as_ref().map_or(core::ptr::null(), |s| s.as_ptr());
332
333 let array = Box::new(asdf_ndarray_t {
334 source,
335 ndim: u32::try_from(state.shape.len()).unwrap_or(0),
336 shape: shape_ptr,
337 datatype,
338 byteorder: parsed.byteorder as i32,
339 offset: parsed.offset,
340 strides: strides_ptr,
341 _reserved: Box::into_raw(state).cast::<c_void>(),
342 });
343 Box::into_raw(array)
344}
345
346fn state_of<'a>(array: *mut asdf_ndarray_t) -> Option<&'a mut NdarrayState> {
347 if array.is_null() {
348 return None;
349 }
350 let reserved = unsafe { &*array }._reserved;
351 unsafe { crate::ffi::as_mut(reserved.cast::<NdarrayState>()) }
352}
353
354fn ensure_state<'a>(array: *mut asdf_ndarray_t) -> Option<&'a mut NdarrayState> {
364 if array.is_null() {
365 return None;
366 }
367 if state_of(array).is_some() {
368 return state_of(array);
369 }
370
371 let view = unsafe { &*array };
372 let shape: Vec<u64> = if view.shape.is_null() || view.ndim == 0 {
373 Vec::new()
374 } else {
375 unsafe { core::slice::from_raw_parts(view.shape, view.ndim as usize) }.to_vec()
376 };
377 let strides: Option<Vec<i64>> = (!view.strides.is_null() && view.ndim > 0)
378 .then(|| unsafe { core::slice::from_raw_parts(view.strides, view.ndim as usize) }.to_vec());
379
380 let scalar = scalar_from_abi(view.datatype.type_);
382 let mut datatype = Datatype::scalar(scalar);
383 if view.datatype.size != 0 {
384 datatype.size = view.datatype.size;
385 }
386 let parsed = Ndarray {
387 source: Source::Block(view.source),
388 shape: shape.iter().map(|d| Some(*d)).collect(),
389 datatype,
390 byteorder: match view.byteorder {
391 62 => asdf_core::core::datatype::ByteOrder::Big,
392 60 => asdf_core::core::datatype::ByteOrder::Little,
393 _ => asdf_core::core::datatype::ByteOrder::native(),
394 },
395 offset: view.offset,
396 strides: strides.clone(),
397 mask: None,
398 };
399
400 let state = Box::new(NdarrayState {
401 shape,
402 strides,
403 fields: Vec::new(),
404 field_names: Vec::new(),
405 field_shapes: Vec::new(),
406 parsed,
407 data: None,
408 allocated: None,
409 compression: Compression::None,
410 storage: AsdfArrayStorage::Internal,
411 file: core::ptr::null_mut(),
412 block_index: None,
413 block: core::ptr::null_mut(),
414 });
415 unsafe { (*array)._reserved = Box::into_raw(state).cast::<c_void>() };
416 state_of(array)
417}
418
419#[unsafe(no_mangle)]
424pub unsafe extern "C" fn asdf_ndarray_size(ndarray: *const asdf_ndarray_t) -> u64 {
425 guard("asdf_ndarray_size", 0, || ndarray_size(ndarray))
426}
427
428pub(crate) fn ndarray_size(ndarray: *const asdf_ndarray_t) -> u64 {
434 if ndarray.is_null() {
435 return 0;
436 }
437 let array = unsafe { &*ndarray };
438 if array.shape.is_null() || array.ndim == 0 {
439 return 0;
440 }
441 let shape = unsafe { core::slice::from_raw_parts(array.shape, array.ndim as usize) };
442 shape.iter().try_fold(1u64, |acc, dim| acc.checked_mul(*dim)).unwrap_or(u64::MAX)
450}
451
452#[unsafe(no_mangle)]
457pub unsafe extern "C" fn asdf_ndarray_nbytes(ndarray: *const asdf_ndarray_t) -> u64 {
458 guard("asdf_ndarray_nbytes", 0, || ndarray_nbytes(ndarray))
459}
460
461pub(crate) fn ndarray_nbytes(ndarray: *const asdf_ndarray_t) -> u64 {
467 if ndarray.is_null() {
468 return 0;
469 }
470 let count = ndarray_size(ndarray);
471 let datatype = unsafe { &raw const (*ndarray).datatype };
473 count.saturating_mul(datatype_size(datatype.cast_mut()))
474}
475
476#[unsafe(no_mangle)]
481pub unsafe extern "C" fn asdf_datatype_size(datatype: *mut asdf_datatype_t) -> u64 {
482 guard("asdf_datatype_size", 0, || datatype_size(datatype))
483}
484
485pub(crate) fn datatype_size(datatype: *mut asdf_datatype_t) -> u64 {
491 if datatype.is_null() {
492 return 0;
493 }
494 let dt = unsafe { &mut *datatype };
495 if dt.size != 0 {
496 return dt.size;
497 }
498 let scalar = scalar_from_abi(dt.type_);
502 let computed = scalar.size();
503 dt.size = computed;
504 computed
505}
506
507#[unsafe(no_mangle)]
512pub unsafe extern "C" fn asdf_scalar_datatype_from_string(name: *const c_char) -> ScalarTypeAbi {
513 guard("asdf_scalar_datatype_from_string", 0, || {
514 if name.is_null() {
515 return 0;
516 }
517 let text = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
518 scalar_abi(ScalarType::from_name(&text))
519 })
520}
521
522#[unsafe(no_mangle)]
527pub extern "C" fn asdf_scalar_datatype_to_string(datatype: ScalarTypeAbi) -> *const c_char {
528 let name: &'static CStr = match scalar_from_abi(datatype) {
530 ScalarType::Int8 => c"int8",
531 ScalarType::Uint8 => c"uint8",
532 ScalarType::Int16 => c"int16",
533 ScalarType::Uint16 => c"uint16",
534 ScalarType::Int32 => c"int32",
535 ScalarType::Uint32 => c"uint32",
536 ScalarType::Int64 => c"int64",
537 ScalarType::Uint64 => c"uint64",
538 ScalarType::Float16 => c"float16",
539 ScalarType::Float32 => c"float32",
540 ScalarType::Float64 => c"float64",
541 ScalarType::Complex64 => c"complex64",
542 ScalarType::Complex128 => c"complex128",
543 ScalarType::Bool8 => c"bool8",
544 ScalarType::Ascii => c"ascii",
545 ScalarType::Ucs4 => c"ucs4",
546 ScalarType::Structured => c"structured",
547 ScalarType::Unknown => c"unknown",
548 };
549 name.as_ptr()
550}
551
552#[unsafe(no_mangle)]
560pub unsafe extern "C" fn asdf_ndarray_data_alloc(ndarray: *mut asdf_ndarray_t) -> *mut c_void {
561 guard("asdf_ndarray_data_alloc", core::ptr::null_mut(), || ndarray_data_alloc(ndarray))
562}
563
564pub(crate) fn ndarray_data_alloc(ndarray: *mut asdf_ndarray_t) -> *mut c_void {
570 let nbytes = ndarray_nbytes(ndarray);
571 let Some(state) = ensure_state(ndarray) else {
572 return core::ptr::null_mut();
573 };
574 let Ok(len) = usize::try_from(nbytes) else {
575 return core::ptr::null_mut();
576 };
577 if state.allocated.is_none() {
578 state.allocated = Some(AlignedBuf::zeroed(len));
579 }
580 state.allocated.as_mut().map_or(core::ptr::null_mut(), |b| b.as_mut_ptr().cast::<c_void>())
581}
582
583#[unsafe(no_mangle)]
590pub unsafe extern "C" fn asdf_ndarray_data_dealloc(ndarray: *mut asdf_ndarray_t) {
591 guard("asdf_ndarray_data_dealloc", (), || {
592 if let Some(state) = state_of(ndarray) {
593 state.allocated = None;
594 }
595 })
596}
597
598#[unsafe(no_mangle)]
603pub unsafe extern "C" fn asdf_ndarray_data_copy(
604 ndarray: *mut asdf_ndarray_t,
605 src: *const c_void,
606) -> NdarrayErr {
607 guard("asdf_ndarray_data_copy", NdarrayErr::Inval, || {
608 if ndarray.is_null() || src.is_null() {
609 return NdarrayErr::Inval;
610 }
611 let nbytes = ndarray_nbytes(ndarray);
612 let Ok(len) = usize::try_from(nbytes) else {
613 return NdarrayErr::Inval;
614 };
615 let destination = ndarray_data_alloc(ndarray);
616 if destination.is_null() {
617 return NdarrayErr::Oom;
618 }
619 unsafe {
620 core::ptr::copy_nonoverlapping(src.cast::<u8>(), destination.cast::<u8>(), len);
621 }
622 NdarrayErr::Ok
623 })
624}
625
626#[unsafe(no_mangle)]
632pub unsafe extern "C" fn asdf_ndarray_data(
633 ndarray: *mut asdf_ndarray_t,
634 size: *mut usize,
635) -> *const c_void {
636 guard("asdf_ndarray_data", core::ptr::null(), || ndarray_data(ndarray, size))
637}
638
639pub(crate) fn ndarray_data(ndarray: *mut asdf_ndarray_t, size: *mut usize) -> *const c_void {
645 let Some(state) = state_of(ndarray) else {
646 if !size.is_null() {
647 unsafe { write_out(size, 0) };
648 }
649 return core::ptr::null();
650 };
651 let bytes = match (&state.allocated, &state.data) {
653 (Some(buffer), _) => buffer,
654 (None, Some(data)) => data,
655 (None, None) => {
656 if !size.is_null() {
657 unsafe { write_out(size, 0) };
658 }
659 return core::ptr::null();
660 }
661 };
662 if !size.is_null() {
663 unsafe { write_out(size, bytes.len()) };
664 }
665 bytes.as_ptr().cast::<c_void>()
666}
667
668#[unsafe(no_mangle)]
673pub unsafe extern "C" fn asdf_ndarray_data_raw(
674 ndarray: *mut asdf_ndarray_t,
675 size: *mut usize,
676) -> *const c_void {
677 ndarray_data(ndarray, size)
680}
681
682#[unsafe(no_mangle)]
687pub unsafe extern "C" fn asdf_ndarray_compression_set(
688 ndarray: *mut asdf_ndarray_t,
689 compression: *const c_char,
690) -> c_int {
691 guard("asdf_ndarray_compression_set", -1, || {
692 let Some(state) = ensure_state(ndarray) else { return -1 };
693 let name = if compression.is_null() {
694 String::new()
695 } else {
696 unsafe { CStr::from_ptr(compression) }.to_string_lossy().into_owned()
697 };
698 let Ok(method) = Compression::from_name(&name) else {
699 return -1;
700 };
701 if !method.is_available() {
702 return -1;
703 }
704 state.compression = method;
705 0
706 })
707}
708
709#[unsafe(no_mangle)]
714pub unsafe extern "C" fn asdf_ndarray_storage(ndarray: *mut asdf_ndarray_t) -> AsdfArrayStorage {
715 guard("asdf_ndarray_storage", AsdfArrayStorage::Default, || {
716 state_of(ndarray).map_or(AsdfArrayStorage::Default, |s| s.storage)
717 })
718}
719
720#[unsafe(no_mangle)]
728pub unsafe extern "C" fn asdf_ndarray_storage_set(
729 ndarray: *mut asdf_ndarray_t,
730 storage: AsdfArrayStorage,
731) {
732 guard("asdf_ndarray_storage_set", (), || {
733 if storage == AsdfArrayStorage::External {
734 return;
735 }
736 if let Some(state) = ensure_state(ndarray) {
737 state.storage = storage;
738 }
739 })
740}
741
742fn write_converted(element: &Element, target: ScalarType, out: &mut [u8]) -> NdarrayErr {
755 macro_rules! emit {
757 ($bytes:expr) => {{
758 let bytes = $bytes;
759 if out.len() < bytes.len() {
760 return NdarrayErr::Inval;
761 }
762 out[..bytes.len()].copy_from_slice(&bytes);
763 }};
764 }
765
766 macro_rules! put_int {
767 ($ty:ty) => {{
768 let (wide, saturated): (i128, bool) = match element {
769 Element::Int(v) => (i128::from(*v), false),
770 Element::Uint(v) => (i128::from(*v), false),
771 Element::Bool(v) => (i128::from(*v), false),
772 Element::Float(v) => {
773 if v.is_nan() {
774 (0, false)
778 } else if v.is_infinite() {
779 (
780 if *v > 0.0 { i128::from(<$ty>::MAX) } else { i128::from(<$ty>::MIN) },
781 true,
782 )
783 } else {
784 (v.trunc() as i128, false)
786 }
787 }
788 _ => return NdarrayErr::Conversion,
789 };
790
791 let clamped = wide.clamp(i128::from(<$ty>::MIN), i128::from(<$ty>::MAX));
792 emit!((clamped as $ty).to_ne_bytes());
793 if saturated || clamped != wide { NdarrayErr::Overflow } else { NdarrayErr::Ok }
794 }};
795 }
796
797 macro_rules! put_float {
800 ($convert:expr, $bytes:expr) => {{
801 let source: f64 = match element {
802 Element::Float(v) => *v,
803 Element::Int(v) => *v as f64,
804 Element::Uint(v) => *v as f64,
805 Element::Bool(v) => f64::from(*v),
806 _ => return NdarrayErr::Conversion,
807 };
808 #[allow(clippy::redundant_closure_call)]
809 let value = ($convert)(source);
810 #[allow(clippy::redundant_closure_call)]
811 let bytes = ($bytes)(value);
812 emit!(bytes);
813 if source.is_finite() && !is_finite_result(value.into()) {
814 NdarrayErr::Overflow
815 } else {
816 NdarrayErr::Ok
817 }
818 }};
819 }
820
821 match target {
822 ScalarType::Int8 => put_int!(i8),
823 ScalarType::Int16 => put_int!(i16),
824 ScalarType::Int32 => put_int!(i32),
825 ScalarType::Int64 => put_int!(i64),
826 ScalarType::Uint8 => put_int!(u8),
827 ScalarType::Uint16 => put_int!(u16),
828 ScalarType::Uint32 => put_int!(u32),
829 ScalarType::Uint64 => put_int!(u64),
830 ScalarType::Bool8 => {
831 let value = match element {
832 Element::Bool(v) => u8::from(*v),
833 Element::Int(v) => u8::from(*v != 0),
834 Element::Uint(v) => u8::from(*v != 0),
835 _ => return NdarrayErr::Conversion,
836 };
837 emit!(value.to_ne_bytes());
838 NdarrayErr::Ok
839 }
840 ScalarType::Float32 => put_float!(|v: f64| v as f32, |v: f32| v.to_ne_bytes()),
841 ScalarType::Float64 => put_float!(|v: f64| v, |v: f64| v.to_ne_bytes()),
842 ScalarType::Float16 => {
843 put_float!(half::f16::from_f64, |v: half::f16| v.to_bits().to_ne_bytes())
844 }
845 _ => NdarrayErr::Conversion,
846 }
847}
848
849fn is_finite_result(value: f64) -> bool {
851 value.is_finite()
852}
853
854fn elements_of(state: &NdarrayState) -> Option<Vec<Element>> {
856 let data = state.allocated.as_ref().or(state.data.as_ref())?;
857 decode_all(&state.parsed, &state.shape, data).ok()
858}
859
860#[unsafe(no_mangle)]
869pub unsafe extern "C" fn asdf_ndarray_read_all(
870 ndarray: *mut asdf_ndarray_t,
871 dst_t: ScalarTypeAbi,
872 dst: *mut *mut c_void,
873) -> NdarrayErr {
874 guard("asdf_ndarray_read_all", NdarrayErr::Inval, || {
875 let Some(state) = state_of(ndarray) else {
876 return NdarrayErr::Inval;
877 };
878 let Some(elements) = elements_of(state) else {
879 return NdarrayErr::Inval;
880 };
881
882 let target = match scalar_from_abi(dst_t) {
883 ScalarType::Unknown => state.parsed.datatype.scalar,
886 other => other,
887 };
888 let width = target.size();
889 if width == 0 {
890 return NdarrayErr::Conversion;
891 }
892 let Ok(width) = usize::try_from(width) else {
893 return NdarrayErr::Inval;
894 };
895
896 let Some(total) = elements.len().checked_mul(width) else {
897 return NdarrayErr::Inval;
898 };
899 let mut buffer = vec![0u8; total];
900 let mut overflowed = false;
904 for (index, element) in elements.iter().enumerate() {
905 let slot = &mut buffer[index * width..(index + 1) * width];
906 match write_converted(element, target, slot) {
907 NdarrayErr::Ok => {}
908 NdarrayErr::Overflow => overflowed = true,
909 err => return err,
910 }
911 }
912
913 let delivered = deliver(&buffer, dst);
914 if delivered != NdarrayErr::Ok {
915 return delivered;
916 }
917 if overflowed { NdarrayErr::Overflow } else { NdarrayErr::Ok }
918 })
919}
920
921fn flat_index(shape: &[u64], indices: &[u64]) -> Option<usize> {
923 if shape.len() != indices.len() {
924 return None;
925 }
926 let mut flat = 0u64;
927 for (dim, index) in shape.iter().zip(indices.iter()) {
928 if index >= dim {
929 return None;
930 }
931 flat = flat.checked_mul(*dim)?.checked_add(*index)?;
932 }
933 usize::try_from(flat).ok()
934}
935
936macro_rules! read_at {
938 ($name:ident, $ty:ty, $convert:expr) => {
939 #[unsafe(no_mangle)]
945 pub unsafe extern "C" fn $name(
946 ndarray: *mut asdf_ndarray_t,
947 indices: *const u64,
948 err: *mut c_int,
949 ) -> $ty {
950 guard(stringify!($name), <$ty>::default(), || {
951 let set = |code: NdarrayErr| {
952 if !err.is_null() {
953 unsafe { write_out(err, code as c_int) };
954 }
955 };
956 let Some(state) = state_of(ndarray) else {
957 set(NdarrayErr::Inval);
958 return <$ty>::default();
959 };
960 if indices.is_null() {
961 set(NdarrayErr::Inval);
962 return <$ty>::default();
963 }
964 let idx = unsafe { core::slice::from_raw_parts(indices, state.shape.len()) };
965 let Some(flat) = flat_index(&state.shape, idx) else {
966 set(NdarrayErr::OutOfBounds);
967 return <$ty>::default();
968 };
969 let Some(elements) = elements_of(state) else {
970 set(NdarrayErr::Inval);
971 return <$ty>::default();
972 };
973 let Some(element) = elements.get(flat) else {
974 set(NdarrayErr::OutOfBounds);
975 return <$ty>::default();
976 };
977 #[allow(clippy::redundant_closure_call)]
978 match ($convert)(element) {
979 Some(v) => {
980 set(NdarrayErr::Ok);
981 v
982 }
983 None => {
984 set(NdarrayErr::Conversion);
985 <$ty>::default()
986 }
987 }
988 })
989 }
990 };
991}
992
993macro_rules! to_int {
995 ($ty:ty) => {
996 |element: &Element| -> Option<$ty> {
997 let wide: i128 = match element {
998 Element::Int(v) => i128::from(*v),
999 Element::Uint(v) => i128::from(*v),
1000 Element::Bool(v) => i128::from(*v),
1001 Element::Float(v) if v.fract() == 0.0 => *v as i128,
1002 _ => return None,
1003 };
1004 <$ty>::try_from(wide).ok()
1005 }
1006 };
1007}
1008
1009read_at!(asdf_ndarray_read_int8_at, i8, to_int!(i8));
1010read_at!(asdf_ndarray_read_int16_at, i16, to_int!(i16));
1011read_at!(asdf_ndarray_read_int32_at, i32, to_int!(i32));
1012read_at!(asdf_ndarray_read_int64_at, i64, to_int!(i64));
1013read_at!(asdf_ndarray_read_uint8_at, u8, to_int!(u8));
1014read_at!(asdf_ndarray_read_uint16_at, u16, to_int!(u16));
1015read_at!(asdf_ndarray_read_uint32_at, u32, to_int!(u32));
1016read_at!(asdf_ndarray_read_uint64_at, u64, to_int!(u64));
1017
1018read_at!(asdf_ndarray_read_float32_at, f32, |element: &Element| {
1019 match element {
1020 Element::Float(v) => Some(*v as f32),
1021 Element::Int(v) => Some(*v as f32),
1022 Element::Uint(v) => Some(*v as f32),
1023 _ => None,
1024 }
1025});
1026read_at!(asdf_ndarray_read_float64_at, f64, |element: &Element| {
1027 match element {
1028 Element::Float(v) => Some(*v),
1029 Element::Int(v) => Some(*v as f64),
1030 Element::Uint(v) => Some(*v as f64),
1031 _ => None,
1032 }
1033});
1034
1035#[unsafe(no_mangle)]
1046pub unsafe extern "C" fn asdf_shim_ndarray_read_float16_bits_at(
1047 ndarray: *mut c_void,
1048 indices: *const u64,
1049 err: *mut c_int,
1050) -> u16 {
1051 guard("asdf_shim_ndarray_read_float16_bits_at", 0u16, || {
1052 let value =
1053 unsafe { asdf_ndarray_read_float64_at(ndarray.cast::<asdf_ndarray_t>(), indices, err) };
1054 half::f16::from_f64(value).to_bits()
1055 })
1056}
1057
1058#[unsafe(no_mangle)]
1064pub unsafe extern "C" fn asdf_ndarray_destroy(ndarray: *mut asdf_ndarray_t) {
1065 guard("asdf_ndarray_destroy", (), || ndarray_destroy(ndarray))
1066}
1067
1068pub(crate) fn ndarray_destroy(ndarray: *mut asdf_ndarray_t) {
1074 if ndarray.is_null() {
1075 return;
1076 }
1077 let boxed = unsafe { Box::from_raw(ndarray) };
1078 if !boxed._reserved.is_null() {
1079 drop(unsafe { Box::from_raw(boxed._reserved.cast::<NdarrayState>()) });
1080 }
1081}
1082
1083pub(crate) fn set_data(array: *mut asdf_ndarray_t, data: &[u8]) {
1085 if let Some(state) = state_of(array) {
1086 state.data = Some(AlignedBuf::from_slice(data));
1087 }
1088}
1089
1090fn ndarray_from_value(value: *mut crate::file_ffi::AsdfValue) -> *mut asdf_ndarray_t {
1092 use crate::file_ffi::{file_reader, value_document, value_file, value_node};
1093
1094 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
1095 return core::ptr::null_mut();
1096 };
1097 let Ok(parsed) = Ndarray::parse(doc, node) else {
1098 return core::ptr::null_mut();
1099 };
1100
1101 let mut data: Option<Vec<u8>> = None;
1104 let mut block_len = None;
1105 let mut block_index = None;
1106 if let Some(file) = value_file(value)
1107 && let Some(reader) = file_reader(file)
1108 {
1109 let index = match parsed.source {
1110 Source::Block(index) => Some(index),
1111 Source::LastBlock => reader.block_count().checked_sub(1),
1112 _ => None,
1117 };
1118 block_index = index;
1119 if let Some(index) = index
1120 && let Ok(bytes) = reader.block_data(index)
1121 {
1122 block_len = Some(bytes.len() as u64);
1123 data = Some(bytes.into_owned());
1124 }
1125 }
1126
1127 let Ok(shape) = parsed.resolved_shape(block_len) else {
1128 return core::ptr::null_mut();
1129 };
1130
1131 if data.is_none() && matches!(parsed.source, Source::Inline(_)) {
1135 data = encode_inline(doc, &parsed, &shape);
1136 }
1137
1138 let array = make_ndarray(parsed, shape);
1139 if let Some(bytes) = &data {
1140 set_data(array, bytes);
1141 }
1142 if let Some(state) = state_of(array) {
1145 state.file = value_file(value).unwrap_or(core::ptr::null_mut());
1146 state.block_index = block_index;
1147 }
1148 array
1149}
1150
1151fn encode_inline(
1157 doc: &asdf_core::yaml::Document,
1158 parsed: &Ndarray,
1159 shape: &[u64],
1160) -> Option<Vec<u8>> {
1161 let scalar = parsed.datatype.scalar;
1162 if !parsed.datatype.fields.is_empty() {
1163 return None;
1164 }
1165 let width = usize::try_from(scalar.size()).ok()?;
1166 if width == 0 {
1167 return None;
1168 }
1169
1170 let elements = asdf_core::core::decode_inline(doc, parsed, shape).ok()?;
1171 let mut out = vec![0u8; elements.len().checked_mul(width)?];
1172 for (index, element) in elements.iter().enumerate() {
1173 let slot = &mut out[index * width..(index + 1) * width];
1174 if write_converted(element, scalar, slot) != NdarrayErr::Ok {
1175 return None;
1176 }
1177 }
1178 Some(out)
1179}
1180
1181#[unsafe(no_mangle)]
1188pub unsafe extern "C" fn asdf_get_ndarray(
1189 file: *mut crate::file_ffi::AsdfFile,
1190 path: *const c_char,
1191 out: *mut *mut asdf_ndarray_t,
1192) -> crate::types::AsdfValueErr {
1193 use crate::types::AsdfValueErr;
1194
1195 guard("asdf_get_ndarray", AsdfValueErr::Unknown, || {
1196 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
1197 if value.is_null() {
1198 return AsdfValueErr::NotFound;
1199 }
1200 let array = ndarray_from_value(value);
1201 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1202
1203 if array.is_null() {
1204 return AsdfValueErr::TypeMismatch;
1205 }
1206 if !out.is_null() {
1207 unsafe { write_out(out, array) };
1208 } else {
1209 ndarray_destroy(array);
1210 }
1211 AsdfValueErr::Ok
1212 })
1213}
1214
1215#[unsafe(no_mangle)]
1221pub unsafe extern "C" fn asdf_value_as_ndarray(
1222 value: *mut crate::file_ffi::AsdfValue,
1223 out: *mut *mut asdf_ndarray_t,
1224) -> crate::types::AsdfValueErr {
1225 use crate::types::AsdfValueErr;
1226
1227 guard("asdf_value_as_ndarray", AsdfValueErr::Unknown, || {
1228 let array = ndarray_from_value(value);
1229 if array.is_null() {
1230 return AsdfValueErr::TypeMismatch;
1231 }
1232 if !out.is_null() {
1233 unsafe { write_out(out, array) };
1234 } else {
1235 ndarray_destroy(array);
1236 }
1237 AsdfValueErr::Ok
1238 })
1239}
1240
1241#[unsafe(no_mangle)]
1246pub unsafe extern "C" fn asdf_is_ndarray(
1247 file: *mut crate::file_ffi::AsdfFile,
1248 path: *const c_char,
1249) -> bool {
1250 guard("asdf_is_ndarray", false, || {
1251 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
1252 if value.is_null() {
1253 return false;
1254 }
1255 let is_array = value_is_ndarray(value);
1256 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1257 is_array
1258 })
1259}
1260
1261#[unsafe(no_mangle)]
1266pub unsafe extern "C" fn asdf_value_is_ndarray(value: *mut crate::file_ffi::AsdfValue) -> bool {
1267 guard("asdf_value_is_ndarray", false, || value_is_ndarray(value))
1268}
1269
1270pub(crate) fn value_is_ndarray(value: *mut crate::file_ffi::AsdfValue) -> bool {
1276 use crate::file_ffi::{value_document, value_node};
1277 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
1278 return false;
1279 };
1280 doc.tag_of(node).is_some_and(|t| t.split_version().0 == "core/ndarray")
1281}
1282
1283#[unsafe(no_mangle)]
1290pub unsafe extern "C" fn asdf_ndarray_deinit(ndarray: *mut asdf_ndarray_t) {
1291 guard("asdf_ndarray_deinit", (), || ndarray_deinit(ndarray))
1292}
1293
1294pub(crate) fn ndarray_deinit(ndarray: *mut asdf_ndarray_t) {
1300 if ndarray.is_null() {
1301 return;
1302 }
1303 let array = unsafe { &mut *ndarray };
1304 if !array._reserved.is_null() {
1305 let state = unsafe { Box::from_raw(array._reserved.cast::<NdarrayState>()) };
1306 if !state.block.is_null() {
1307 unsafe { crate::block_ffi::asdf_block_close(state.block) };
1308 }
1309 drop(state);
1310 array._reserved = core::ptr::null_mut();
1311 }
1312 array.shape = core::ptr::null();
1314 array.strides = core::ptr::null();
1315 array.datatype.fields = core::ptr::null();
1316 array.datatype.nfields = 0;
1317 array.ndim = 0;
1318}
1319
1320#[unsafe(no_mangle)]
1328pub unsafe extern "C" fn asdf_ndarray_copy_into(
1329 file: *mut crate::file_ffi::AsdfFile,
1330 src: *const asdf_ndarray_t,
1331 dst: *mut asdf_ndarray_t,
1332) -> bool {
1333 guard("asdf_ndarray_copy_into", false, || ndarray_copy_into(file, src, dst))
1334}
1335
1336pub(crate) fn ndarray_copy_into(
1342 file: *mut crate::file_ffi::AsdfFile,
1343 src: *const asdf_ndarray_t,
1344 dst: *mut asdf_ndarray_t,
1345) -> bool {
1346 let _ = file;
1347 if src.is_null() || dst.is_null() {
1348 return false;
1349 }
1350 let Some(state) = state_of(src.cast_mut()) else {
1351 return false;
1352 };
1353
1354 let rebuilt = make_ndarray(state.parsed.clone(), state.shape.clone());
1356 if rebuilt.is_null() {
1357 return false;
1358 }
1359 if let Some(data) = state.allocated.as_ref().or(state.data.as_ref()) {
1360 set_data(rebuilt, data);
1361 }
1362 if let Some(fresh) = state_of(rebuilt) {
1363 fresh.compression = state.compression;
1364 fresh.storage = state.storage;
1365 }
1366
1367 let boxed = unsafe { Box::from_raw(rebuilt) };
1369 unsafe { core::ptr::write(dst, *boxed) };
1370 true
1371}
1372
1373#[unsafe(no_mangle)]
1379pub unsafe extern "C" fn asdf_ndarray_copy(
1380 file: *mut crate::file_ffi::AsdfFile,
1381 src: *const asdf_ndarray_t,
1382) -> *mut asdf_ndarray_t {
1383 guard("asdf_ndarray_copy", core::ptr::null_mut(), || ndarray_copy(file, src))
1384}
1385
1386pub(crate) fn ndarray_copy(
1392 file: *mut crate::file_ffi::AsdfFile,
1393 src: *const asdf_ndarray_t,
1394) -> *mut asdf_ndarray_t {
1395 if src.is_null() {
1396 return core::ptr::null_mut();
1397 }
1398 let raw = Box::into_raw(Box::new(asdf_ndarray_t {
1399 source: 0,
1400 ndim: 0,
1401 shape: core::ptr::null(),
1402 datatype: asdf_datatype_t {
1403 type_: 0,
1404 size: 0,
1405 name: core::ptr::null(),
1406 byteorder: 0,
1407 ndim: 0,
1408 shape: core::ptr::null(),
1409 nfields: 0,
1410 fields: core::ptr::null(),
1411 },
1412 byteorder: 0,
1413 offset: 0,
1414 strides: core::ptr::null(),
1415 _reserved: core::ptr::null_mut(),
1416 }));
1417 if ndarray_copy_into(file, src, raw) {
1418 raw
1419 } else {
1420 drop(unsafe { Box::from_raw(raw) });
1421 core::ptr::null_mut()
1422 }
1423}
1424
1425#[unsafe(no_mangle)]
1430pub unsafe extern "C" fn asdf_ndarray_array_copy(
1431 file: *mut crate::file_ffi::AsdfFile,
1432 src: *mut *const asdf_ndarray_t,
1433) -> *mut *mut asdf_ndarray_t {
1434 guard("asdf_ndarray_array_copy", core::ptr::null_mut(), || {
1435 if src.is_null() {
1436 return core::ptr::null_mut();
1437 }
1438 let mut count = 0isize;
1439 while !unsafe { *src.offset(count) }.is_null() {
1440 count += 1;
1441 }
1442
1443 let mut copies: Vec<*mut asdf_ndarray_t> = Vec::with_capacity(count as usize + 1);
1444 for index in 0..count {
1445 let entry = unsafe { *src.offset(index) };
1447 let copy = ndarray_copy(file, entry);
1448 if copy.is_null() {
1449 for made in copies {
1451 ndarray_destroy(made);
1452 }
1453 return core::ptr::null_mut();
1454 }
1455 copies.push(copy);
1456 }
1457 copies.push(core::ptr::null_mut());
1458 Box::into_raw(copies.into_boxed_slice()).cast::<*mut asdf_ndarray_t>()
1459 })
1460}
1461
1462#[unsafe(no_mangle)]
1471pub unsafe extern "C" fn asdf_value_of_ndarray(
1472 file: *mut crate::file_ffi::AsdfFile,
1473 obj: *const asdf_ndarray_t,
1474) -> *mut crate::file_ffi::AsdfValue {
1475 guard("asdf_value_of_ndarray", core::ptr::null_mut(), || {
1476 if file.is_null() || obj.is_null() {
1477 return core::ptr::null_mut();
1478 }
1479 let value = value_of_ndarray_inner(file, obj);
1480 if !value.is_null() {
1488 release_ndarray_state(obj.cast_mut());
1489 }
1490 value
1491 })
1492}
1493
1494fn release_ndarray_state(ndarray: *mut asdf_ndarray_t) {
1499 if ndarray.is_null() {
1500 return;
1501 }
1502 let slot = unsafe { &raw mut (*ndarray)._reserved };
1507 let reserved = unsafe { slot.read() };
1508 if reserved.is_null() {
1509 return;
1510 }
1511 let state = unsafe { Box::from_raw(reserved.cast::<NdarrayState>()) };
1512 if !state.block.is_null() {
1513 unsafe { crate::block_ffi::asdf_block_close(state.block) };
1514 }
1515 drop(state);
1516 unsafe { slot.write(core::ptr::null_mut()) };
1517}
1518
1519fn value_of_ndarray_inner(
1520 file: *mut crate::file_ffi::AsdfFile,
1521 obj: *const asdf_ndarray_t,
1522) -> *mut crate::file_ffi::AsdfValue {
1523 use asdf_core::yaml::{CollectionStyle, NodeData, Tag};
1524
1525 {
1526 let array = unsafe { &*obj };
1527
1528 let shape: Vec<u64> = if array.shape.is_null() || array.ndim == 0 {
1532 Vec::new()
1533 } else {
1534 unsafe { core::slice::from_raw_parts(array.shape, array.ndim as usize) }.to_vec()
1535 };
1536 let scalar = scalar_from_abi(array.datatype.type_);
1537 let item_size = if array.datatype.size != 0 { array.datatype.size } else { scalar.size() };
1538 if item_size == 0 {
1539 return core::ptr::null_mut();
1540 }
1541
1542 let Ok(element_count) = asdf_core::core::ndarray::element_count(&shape) else {
1546 return core::ptr::null_mut();
1547 };
1548 let Some(expected) =
1549 element_count.max(1).checked_mul(item_size).and_then(|n| usize::try_from(n).ok())
1550 else {
1551 return core::ptr::null_mut();
1552 };
1553 let payload: Vec<u8> = match ensure_state(obj.cast_mut()) {
1554 Some(state) => state
1555 .allocated
1556 .as_ref()
1557 .or(state.data.as_ref())
1558 .map(|b| b.as_slice().to_vec())
1559 .unwrap_or_else(|| vec![0u8; expected]),
1560 None => vec![0u8; expected],
1561 };
1562 let (compression, array_storage) = state_of(obj.cast_mut())
1563 .map(|s| (s.compression, s.storage))
1564 .unwrap_or((Compression::None, AsdfArrayStorage::Default));
1565
1566 let config = crate::file_ffi::file_config(file).unwrap_or_default();
1569 let storage = if config.array_storage == AsdfArrayStorage::Default {
1570 array_storage
1571 } else {
1572 config.array_storage
1573 };
1574
1575 if storage == AsdfArrayStorage::Inline {
1578 let count: u64 = shape.iter().product::<u64>().max(1);
1579 warn_if_inline_is_large(file, count, config.inline_ndarray_warning_thresh);
1580 return inline_value_of_ndarray(file, array, &shape, &payload);
1581 }
1582
1583 let Some(blocks) = crate::file_ffi::file_blocks_mut(file) else {
1585 return core::ptr::null_mut();
1586 };
1587 blocks.push(asdf_core::PendingBlock::compressed(payload, compression));
1588 let index = blocks.len() - 1;
1589
1590 let Some(doc) = file_document_mut(file) else {
1591 return core::ptr::null_mut();
1592 };
1593
1594 let source = doc.add_scalar(index.to_string());
1595 let datatype = doc.add_scalar(scalar.name());
1596 let order = match array.byteorder {
1597 62 => "big",
1598 60 => "little",
1599 _ => ByteOrderNative,
1601 };
1602 let byteorder = doc.add_scalar(order);
1603
1604 let dims: Vec<_> = shape.iter().map(|d| doc.add_scalar(d.to_string())).collect();
1605 let shape_node = doc.add_sequence(dims);
1606 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(shape_node).data {
1607 *style = CollectionStyle::Flow;
1608 }
1609
1610 let keys: Vec<_> = ["source", "datatype", "byteorder", "shape"]
1611 .iter()
1612 .map(|k| doc.add_scalar(*k))
1613 .collect();
1614 let mut pairs = vec![
1615 (keys[0], source),
1616 (keys[1], datatype),
1617 (keys[2], byteorder),
1618 (keys[3], shape_node),
1619 ];
1620
1621 if array.offset != 0 {
1625 let key = doc.add_scalar("offset");
1626 let value = doc.add_scalar(array.offset.to_string());
1627 pairs.push((key, value));
1628 }
1629 if !array.strides.is_null() && array.ndim > 0 {
1630 let strides =
1631 unsafe { core::slice::from_raw_parts(array.strides, array.ndim as usize) };
1632 let items: Vec<_> = strides.iter().map(|s| doc.add_scalar(s.to_string())).collect();
1633 let node = doc.add_sequence(items);
1634 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(node).data {
1635 *style = CollectionStyle::Flow;
1636 }
1637 let key = doc.add_scalar("strides");
1638 pairs.push((key, node));
1639 }
1640
1641 let node = doc.add_mapping(pairs);
1642 doc.node_mut(node).tag = Some(Tag::parse("tag:stsci.edu:asdf/core/ndarray-1.1.0"));
1643
1644 Box::into_raw(Box::new(crate::file_ffi::AsdfValue::new(file, node)))
1645 }
1646}
1647
1648fn warn_if_inline_is_large(file: *mut crate::file_ffi::AsdfFile, elements: u64, threshold: usize) {
1653 if threshold == 0 || elements <= threshold as u64 {
1654 return;
1655 }
1656 crate::error_ffi::log_to_file(
1657 file,
1658 crate::error_ffi::LogLevel::Warn,
1659 &format!(
1660 "inline ndarray has {elements} elements, exceeding the threshold of {threshold}; \
1661 consider using binary block storage instead"
1662 ),
1663 );
1664}
1665
1666fn inline_value_of_ndarray(
1673 file: *mut crate::file_ffi::AsdfFile,
1674 array: &asdf_ndarray_t,
1675 shape: &[u64],
1676 payload: &[u8],
1677) -> *mut crate::file_ffi::AsdfValue {
1678 use asdf_core::core::datatype::Datatype;
1679 use asdf_core::core::ndarray::Ndarray;
1680 use asdf_core::yaml::{CollectionStyle, NodeData, Tag};
1681
1682 let scalar = scalar_from_abi(array.datatype.type_);
1683 let mut datatype = Datatype::scalar(scalar);
1684 if array.datatype.size != 0 {
1685 datatype.size = array.datatype.size;
1686 }
1687 let parsed = Ndarray {
1688 source: Source::Block(0),
1689 shape: shape.iter().map(|d| Some(*d)).collect(),
1690 datatype,
1691 byteorder: match array.byteorder {
1692 62 => asdf_core::core::datatype::ByteOrder::Big,
1693 60 => asdf_core::core::datatype::ByteOrder::Little,
1694 _ => asdf_core::core::datatype::ByteOrder::native(),
1695 },
1696 offset: array.offset,
1697 strides: None,
1698 mask: None,
1699 };
1700
1701 let elements = if shape.is_empty() {
1705 Vec::new()
1706 } else {
1707 match asdf_core::core::decode_all(&parsed, shape, payload) {
1708 Ok(elements) => elements,
1709 Err(_) => return core::ptr::null_mut(),
1710 }
1711 };
1712
1713 let Some(doc) = file_document_mut(file) else {
1714 return core::ptr::null_mut();
1715 };
1716
1717 let data = if elements.is_empty() {
1718 let node = doc.add_sequence(Vec::new());
1719 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(node).data {
1720 *style = CollectionStyle::Flow;
1721 }
1722 node
1723 } else {
1724 asdf_core::core::elements::nest(doc, &elements, shape)
1725 };
1726 let datatype_node = doc.add_scalar(scalar.name());
1727
1728 let dims: Vec<_> = shape.iter().map(|d| doc.add_scalar(d.to_string())).collect();
1729 let shape_node = doc.add_sequence(dims);
1730 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(shape_node).data {
1731 *style = CollectionStyle::Flow;
1732 }
1733
1734 let keys: Vec<_> = ["datatype", "data", "shape"].iter().map(|k| doc.add_scalar(*k)).collect();
1735 let node =
1736 doc.add_mapping(vec![(keys[0], datatype_node), (keys[1], data), (keys[2], shape_node)]);
1737 doc.node_mut(node).tag = Some(Tag::parse("tag:stsci.edu:asdf/core/ndarray-1.1.0"));
1738
1739 Box::into_raw(Box::new(crate::file_ffi::AsdfValue::new(file, node)))
1740}
1741
1742#[allow(non_upper_case_globals)]
1744const ByteOrderNative: &str = if cfg!(target_endian = "big") { "big" } else { "little" };
1745
1746#[unsafe(no_mangle)]
1751pub unsafe extern "C" fn asdf_set_ndarray(
1752 file: *mut crate::file_ffi::AsdfFile,
1753 path: *const c_char,
1754 obj: *const asdf_ndarray_t,
1755) -> crate::types::AsdfValueErr {
1756 use crate::types::AsdfValueErr;
1757
1758 guard("asdf_set_ndarray", AsdfValueErr::Unknown, || {
1759 let value = unsafe { asdf_value_of_ndarray(file, obj) };
1760 if value.is_null() {
1761 return AsdfValueErr::EmitFailure;
1762 }
1763 let result = unsafe { crate::file_ffi::set_value_at(file, path, value) };
1764 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1765 result
1766 })
1767}
1768
1769#[unsafe(no_mangle)]
1779pub unsafe extern "C" fn asdf_ndarray_block(
1780 ndarray: *mut asdf_ndarray_t,
1781) -> *mut crate::block_ffi::AsdfBlock {
1782 guard("asdf_ndarray_block", core::ptr::null_mut(), || {
1783 let Some(state) = state_of(ndarray) else {
1784 return core::ptr::null_mut();
1785 };
1786 if !state.block.is_null() {
1787 return state.block;
1788 }
1789 let (Some(index), false) = (state.block_index, state.file.is_null()) else {
1790 return core::ptr::null_mut();
1791 };
1792 state.block = unsafe { crate::block_ffi::asdf_block_open(state.file, index) };
1793 state.block
1794 })
1795}
1796
1797fn write_elements(
1799 elements: &[Element],
1800 flat: usize,
1801 count: usize,
1802 target: ScalarType,
1803 width: usize,
1804 dst: &mut [u8],
1805) -> NdarrayErr {
1806 let mut overflowed = false;
1807 for step in 0..count {
1808 let Some(element) = elements.get(flat + step) else {
1809 return NdarrayErr::OutOfBounds;
1810 };
1811 let slot = &mut dst[step * width..(step + 1) * width];
1812 match write_converted(element, target, slot) {
1813 NdarrayErr::Ok => {}
1814 NdarrayErr::Overflow => overflowed = true,
1817 err => return err,
1818 }
1819 }
1820 if overflowed { NdarrayErr::Overflow } else { NdarrayErr::Ok }
1821}
1822
1823fn deliver(buffer: &[u8], dst: *mut *mut c_void) -> NdarrayErr {
1829 if dst.is_null() {
1830 return NdarrayErr::Inval;
1831 }
1832 let existing = unsafe { *dst };
1833 if existing.is_null() {
1834 let Some(allocation) = CMallocBuf::copy_from(buffer) else {
1837 return NdarrayErr::Oom;
1838 };
1839 unsafe { write_out(dst, allocation.into_raw()) };
1840 } else {
1841 unsafe {
1842 core::ptr::copy_nonoverlapping(buffer.as_ptr(), existing.cast::<u8>(), buffer.len());
1843 }
1844 }
1845 NdarrayErr::Ok
1846}
1847
1848#[unsafe(no_mangle)]
1855pub unsafe extern "C" fn asdf_ndarray_read_at(
1856 ndarray: *mut asdf_ndarray_t,
1857 indices: *const u64,
1858 dst_t: ScalarTypeAbi,
1859 dst: *mut c_void,
1860) -> NdarrayErr {
1861 guard("asdf_ndarray_read_at", NdarrayErr::Inval, || {
1862 let Some(state) = state_of(ndarray) else {
1863 return NdarrayErr::Inval;
1864 };
1865 if indices.is_null() || dst.is_null() {
1866 return NdarrayErr::Inval;
1867 }
1868 let idx = unsafe { core::slice::from_raw_parts(indices, state.shape.len()) };
1869 let Some(flat) = flat_index(&state.shape, idx) else {
1870 return NdarrayErr::OutOfBounds;
1871 };
1872 let Some(elements) = elements_of(state) else {
1873 return NdarrayErr::Inval;
1874 };
1875 let Some(element) = elements.get(flat) else {
1876 return NdarrayErr::OutOfBounds;
1877 };
1878
1879 let target = match scalar_from_abi(dst_t) {
1880 ScalarType::Unknown => state.parsed.datatype.scalar,
1881 other => other,
1882 };
1883 let Ok(width) = usize::try_from(target.size()) else {
1884 return NdarrayErr::Inval;
1885 };
1886 if width == 0 {
1887 return NdarrayErr::Conversion;
1888 }
1889 let mut scratch = vec![0u8; width];
1892 let err = write_converted(element, target, &mut scratch);
1893 if err != NdarrayErr::Ok {
1894 return err;
1895 }
1896 unsafe { core::ptr::copy_nonoverlapping(scratch.as_ptr(), dst.cast::<u8>(), width) };
1897 NdarrayErr::Ok
1898 })
1899}
1900
1901#[unsafe(no_mangle)]
1909pub unsafe extern "C" fn asdf_ndarray_read_tile_ndim(
1910 ndarray: *mut asdf_ndarray_t,
1911 origin: *const u64,
1912 shape: *const u64,
1913 dst_t: ScalarTypeAbi,
1914 dst: *mut *mut c_void,
1915) -> NdarrayErr {
1916 guard("asdf_ndarray_read_tile_ndim", NdarrayErr::Inval, || {
1917 ndarray_read_tile_ndim(ndarray, origin, shape, dst_t, dst)
1918 })
1919}
1920
1921pub(crate) fn ndarray_read_tile_ndim(
1927 ndarray: *mut asdf_ndarray_t,
1928 origin: *const u64,
1929 shape: *const u64,
1930 dst_t: ScalarTypeAbi,
1931 dst: *mut *mut c_void,
1932) -> NdarrayErr {
1933 let Some(state) = state_of(ndarray) else {
1934 return NdarrayErr::Inval;
1935 };
1936 if origin.is_null() || shape.is_null() {
1937 return NdarrayErr::Inval;
1938 }
1939 let ndim = state.shape.len();
1940 if ndim == 0 {
1941 return NdarrayErr::Inval;
1942 }
1943 let origin = unsafe { core::slice::from_raw_parts(origin, ndim) }.to_vec();
1944 let tile = unsafe { core::slice::from_raw_parts(shape, ndim) }.to_vec();
1945
1946 for axis in 0..ndim {
1948 let Some(end) = origin[axis].checked_add(tile[axis]) else {
1949 return NdarrayErr::OutOfBounds;
1950 };
1951 if end > state.shape[axis] {
1952 return NdarrayErr::OutOfBounds;
1953 }
1954 }
1955
1956 let target = match scalar_from_abi(dst_t) {
1957 ScalarType::Unknown => state.parsed.datatype.scalar,
1958 other => other,
1959 };
1960 let Ok(width) = usize::try_from(target.size()) else {
1961 return NdarrayErr::Inval;
1962 };
1963 if width == 0 {
1964 return NdarrayErr::Conversion;
1965 }
1966
1967 let mut count: u64 = 1;
1968 for extent in &tile {
1969 let Some(next) = count.checked_mul(*extent) else {
1970 return NdarrayErr::Inval;
1971 };
1972 count = next;
1973 }
1974 let Ok(count) = usize::try_from(count) else {
1975 return NdarrayErr::Inval;
1976 };
1977 if count == 0 {
1978 return deliver(&[], dst);
1979 }
1980
1981 let Some(elements) = elements_of(state) else {
1982 return NdarrayErr::Inval;
1983 };
1984
1985 let run = tile[ndim - 1] as usize;
1988 let Some(total) = count.checked_mul(width) else {
1989 return NdarrayErr::Inval;
1990 };
1991 let mut buffer = vec![0u8; total];
1992 let mut cursor = origin.clone();
1993 let mut written = 0usize;
1994 let mut overflowed = false;
1995 loop {
1996 let Some(flat) = flat_index(&state.shape, &cursor) else {
1997 return NdarrayErr::OutOfBounds;
1998 };
1999 let slice = &mut buffer[written * width..(written + run) * width];
2000 match write_elements(&elements, flat, run, target, width, slice) {
2001 NdarrayErr::Ok => {}
2002 NdarrayErr::Overflow => overflowed = true,
2003 err => return err,
2004 }
2005 written += run;
2006
2007 let mut axis = ndim as isize - 2;
2009 loop {
2010 if axis < 0 {
2011 let delivered = deliver(&buffer, dst);
2012 if delivered != NdarrayErr::Ok {
2013 return delivered;
2014 }
2015 return if overflowed { NdarrayErr::Overflow } else { NdarrayErr::Ok };
2016 }
2017 let a = axis as usize;
2018 cursor[a] += 1;
2019 if cursor[a] < origin[a] + tile[a] {
2020 break;
2021 }
2022 cursor[a] = origin[a];
2023 axis -= 1;
2024 }
2025 }
2026}
2027
2028#[unsafe(no_mangle)]
2038#[allow(clippy::too_many_arguments)]
2039pub unsafe extern "C" fn asdf_ndarray_read_tile_2d(
2040 ndarray: *mut asdf_ndarray_t,
2041 x: u64,
2042 y: u64,
2043 width: u64,
2044 height: u64,
2045 plane_origin: *const u64,
2046 dst_t: ScalarTypeAbi,
2047 dst: *mut *mut c_void,
2048) -> NdarrayErr {
2049 guard("asdf_ndarray_read_tile_2d", NdarrayErr::Inval, || {
2050 let Some(state) = state_of(ndarray) else {
2051 return NdarrayErr::Inval;
2052 };
2053 let ndim = state.shape.len();
2054 if ndim < 2 {
2055 return NdarrayErr::Inval;
2056 }
2057 let planes = ndim - 2;
2058
2059 let mut origin = vec![0u64; ndim];
2060 let mut tile = vec![1u64; ndim];
2061 if planes > 0 && !plane_origin.is_null() {
2062 let outer = unsafe { core::slice::from_raw_parts(plane_origin, planes) };
2063 origin[..planes].copy_from_slice(outer);
2064 }
2065 origin[ndim - 2] = y;
2066 origin[ndim - 1] = x;
2067 tile[ndim - 2] = height;
2068 tile[ndim - 1] = width;
2069
2070 ndarray_read_tile_ndim(ndarray, origin.as_ptr(), tile.as_ptr(), dst_t, dst)
2071 })
2072}
2073
2074unsafe extern "C" fn ndarray_ext_deserialize(
2087 value: *mut crate::file_ffi::AsdfValue,
2088 _userdata: *const c_void,
2089 out: *mut *mut c_void,
2090) -> crate::types::AsdfValueErr {
2091 let mut typed: *mut asdf_ndarray_t = core::ptr::null_mut();
2092 let err = unsafe { asdf_value_as_ndarray(value, &mut typed) };
2093 if err == crate::types::AsdfValueErr::Ok && !out.is_null() {
2094 unsafe { write_out(out, typed.cast::<c_void>()) };
2095 }
2096 err
2097}
2098
2099unsafe extern "C" fn ndarray_ext_serialize(
2104 file: *mut crate::file_ffi::AsdfFile,
2105 obj: *const c_void,
2106 _userdata: *const c_void,
2107) -> *mut crate::file_ffi::AsdfValue {
2108 unsafe { asdf_value_of_ndarray(file, obj.cast::<asdf_ndarray_t>()) }
2109}
2110
2111unsafe extern "C" fn ndarray_ext_copy(
2116 file: *mut crate::file_ffi::AsdfFile,
2117 src: *const c_void,
2118 dst: *mut c_void,
2119) -> bool {
2120 unsafe {
2121 asdf_ndarray_copy_into(file, src.cast::<asdf_ndarray_t>(), dst.cast::<asdf_ndarray_t>())
2122 }
2123}
2124
2125unsafe extern "C" fn ndarray_ext_deinit(obj: *mut c_void) {
2130 ndarray_deinit(obj.cast::<asdf_ndarray_t>());
2131}
2132
2133pub(crate) fn build_ndarray_extension() -> *mut crate::extension_ffi::asdf_extension_t {
2139 use crate::extension_ffi::{
2140 asdf_extension_t, asdf_extension_vtab_t, asdf_software_t, libasdf_software,
2141 };
2142
2143 let tags: Vec<*const c_char> = vec![
2144 c"tag:stsci.edu:asdf/core/ndarray-1.1.0".as_ptr(),
2145 c"tag:stsci.edu:asdf/core/ndarray-1.0.0".as_ptr(),
2146 core::ptr::null(),
2147 ];
2148 let tags = Box::leak(tags.into_boxed_slice());
2149
2150 let vtab = Box::leak(Box::new(asdf_extension_vtab_t {
2151 serialize: Some(ndarray_ext_serialize),
2152 deserialize: Some(ndarray_ext_deserialize),
2153 copy: Some(ndarray_ext_copy),
2154 deinit: Some(ndarray_ext_deinit),
2155 _reserved: [None; 4],
2156 }));
2157
2158 Box::leak(Box::new(asdf_extension_t {
2159 tags: tags.as_ptr(),
2160 software: (&raw const libasdf_software).cast::<asdf_software_t>().cast_mut(),
2161 vtab: core::ptr::from_ref(vtab),
2162 size: core::mem::size_of::<asdf_ndarray_t>(),
2163 userdata: core::ptr::null_mut(),
2164 }))
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use super::*;
2170 use asdf_core::core::datatype::ByteOrder;
2171 use asdf_core::core::ndarray::Ndarray as CoreNdarray;
2172 use asdf_core::yaml::parse_document;
2173
2174 fn int32_array(values: &[i32]) -> *mut asdf_ndarray_t {
2176 let doc = parse_document(&format!(
2177 "a:\n source: 0\n shape: [{}]\n datatype: int32\n byteorder: little\n",
2178 values.len()
2179 ))
2180 .unwrap();
2181 let root = doc.root().unwrap();
2182 let parsed = CoreNdarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
2183
2184 let array = make_ndarray(parsed, vec![values.len() as u64]);
2185 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
2186 set_data(array, &bytes);
2187 array
2188 }
2189
2190 fn int32_grid() -> *mut asdf_ndarray_t {
2192 let doc = parse_document(
2193 "a:\n source: 0\n shape: [3, 4]\n datatype: int32\n byteorder: little\n",
2194 )
2195 .unwrap();
2196 let root = doc.root().unwrap();
2197 let parsed = CoreNdarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
2198 let array = make_ndarray(parsed, vec![3, 4]);
2199 let bytes: Vec<u8> = (0i32..12).flat_map(i32::to_le_bytes).collect();
2200 set_data(array, &bytes);
2201 array
2202 }
2203
2204 #[test]
2207 fn out_of_range_values_saturate_and_report_overflow() {
2208 let mut out = [0u8; 8];
2209
2210 assert_eq!(
2212 write_converted(&Element::Int(-5), ScalarType::Uint8, &mut out[..1]),
2213 NdarrayErr::Overflow
2214 );
2215 assert_eq!(out[0], 0);
2216
2217 assert_eq!(
2219 write_converted(&Element::Int(70_000), ScalarType::Uint16, &mut out[..2]),
2220 NdarrayErr::Overflow
2221 );
2222 assert_eq!(u16::from_ne_bytes([out[0], out[1]]), u16::MAX);
2223
2224 assert_eq!(
2226 write_converted(&Element::Int(-70_000), ScalarType::Int16, &mut out[..2]),
2227 NdarrayErr::Overflow
2228 );
2229 assert_eq!(i16::from_ne_bytes([out[0], out[1]]), i16::MIN);
2230
2231 assert_eq!(
2233 write_converted(&Element::Int(42), ScalarType::Uint8, &mut out[..1]),
2234 NdarrayErr::Ok
2235 );
2236 assert_eq!(out[0], 42);
2237 }
2238
2239 #[test]
2241 fn only_leaving_the_range_counts_as_overflow() {
2242 let mut out = [0u8; 8];
2243
2244 assert_eq!(
2246 write_converted(&Element::Int(i64::from(i32::MAX)), ScalarType::Float32, &mut out[..4]),
2247 NdarrayErr::Ok
2248 );
2249
2250 assert_eq!(
2252 write_converted(&Element::Float(f64::MAX), ScalarType::Float32, &mut out[..4]),
2253 NdarrayErr::Overflow
2254 );
2255 assert!(f32::from_ne_bytes([out[0], out[1], out[2], out[3]]).is_infinite());
2256
2257 assert_eq!(
2260 write_converted(&Element::Float(f64::INFINITY), ScalarType::Float32, &mut out[..4]),
2261 NdarrayErr::Ok
2262 );
2263 }
2264
2265 #[test]
2268 fn floats_convert_to_integers_the_way_a_c_cast_does() {
2269 let mut out = [0u8; 8];
2270
2271 assert_eq!(
2272 write_converted(&Element::Float(3.9), ScalarType::Int32, &mut out[..4]),
2273 NdarrayErr::Ok
2274 );
2275 assert_eq!(i32::from_ne_bytes([out[0], out[1], out[2], out[3]]), 3);
2276
2277 assert_eq!(
2278 write_converted(&Element::Float(-3.9), ScalarType::Int32, &mut out[..4]),
2279 NdarrayErr::Ok
2280 );
2281 assert_eq!(i32::from_ne_bytes([out[0], out[1], out[2], out[3]]), -3);
2282
2283 assert_eq!(
2284 write_converted(&Element::Float(f64::INFINITY), ScalarType::Int32, &mut out[..4]),
2285 NdarrayErr::Overflow
2286 );
2287 assert_eq!(i32::from_ne_bytes([out[0], out[1], out[2], out[3]]), i32::MAX);
2288
2289 assert_eq!(
2290 write_converted(&Element::Float(f64::NEG_INFINITY), ScalarType::Int32, &mut out[..4]),
2291 NdarrayErr::Overflow
2292 );
2293 assert_eq!(i32::from_ne_bytes([out[0], out[1], out[2], out[3]]), i32::MIN);
2294
2295 assert_eq!(
2298 write_converted(&Element::Float(f64::NAN), ScalarType::Int32, &mut out[..4]),
2299 NdarrayErr::Ok
2300 );
2301 }
2302
2303 #[test]
2305 fn an_overflow_does_not_abandon_the_read() {
2306 let array = int32_array(&[1, -1, 300]);
2307 let mut dst: *mut c_void = core::ptr::null_mut();
2308 assert_eq!(
2309 unsafe { asdf_ndarray_read_all(array, ScalarType::Uint8 as ScalarTypeAbi, &mut dst) },
2310 NdarrayErr::Overflow
2311 );
2312 assert!(!dst.is_null(), "the converted buffer is still delivered");
2313
2314 let got = unsafe { core::slice::from_raw_parts(dst.cast::<u8>(), 3) };
2315 assert_eq!(got, [1, 0, 255], "each element saturates on its own");
2316
2317 unsafe { libc::free(dst) };
2318 unsafe { asdf_ndarray_destroy(array) };
2319 }
2320
2321 #[test]
2322 fn reads_one_element_at_indices() {
2323 let array = int32_grid();
2324 let indices: [u64; 2] = [2, 1];
2325 let mut out: i32 = 0;
2326 assert_eq!(
2327 unsafe {
2328 asdf_ndarray_read_at(
2329 array,
2330 indices.as_ptr(),
2331 ScalarType::Int32 as ScalarTypeAbi,
2332 core::ptr::from_mut(&mut out).cast(),
2333 )
2334 },
2335 NdarrayErr::Ok
2336 );
2337 assert_eq!(out, 9, "row 2, column 1 of a 3x4 array holding 0..12");
2338
2339 let mut wide: f64 = 0.0;
2341 assert_eq!(
2342 unsafe {
2343 asdf_ndarray_read_at(
2344 array,
2345 indices.as_ptr(),
2346 ScalarType::Float64 as ScalarTypeAbi,
2347 core::ptr::from_mut(&mut wide).cast(),
2348 )
2349 },
2350 NdarrayErr::Ok
2351 );
2352 assert!((wide - 9.0).abs() < f64::EPSILON);
2353
2354 let past_end: [u64; 2] = [3, 0];
2355 assert_eq!(
2356 unsafe {
2357 asdf_ndarray_read_at(
2358 array,
2359 past_end.as_ptr(),
2360 ScalarType::Int32 as ScalarTypeAbi,
2361 core::ptr::from_mut(&mut out).cast(),
2362 )
2363 },
2364 NdarrayErr::OutOfBounds
2365 );
2366 unsafe { asdf_ndarray_destroy(array) };
2367 }
2368
2369 #[test]
2370 fn reads_a_2d_tile() {
2371 let array = int32_grid();
2372 let mut buffer = [0i32; 4];
2374 let mut dst = buffer.as_mut_ptr().cast::<c_void>();
2375 assert_eq!(
2376 unsafe {
2377 asdf_ndarray_read_tile_2d(
2378 array,
2379 1,
2380 1,
2381 2,
2382 2,
2383 core::ptr::null(),
2384 ScalarType::Int32 as ScalarTypeAbi,
2385 &mut dst,
2386 )
2387 },
2388 NdarrayErr::Ok
2389 );
2390 assert_eq!(buffer, [5, 6, 9, 10]);
2391 unsafe { asdf_ndarray_destroy(array) };
2392 }
2393
2394 #[test]
2395 fn a_tile_may_not_run_past_the_edge() {
2396 let array = int32_grid();
2397 let mut buffer = [0i32; 4];
2398 let mut dst = buffer.as_mut_ptr().cast::<c_void>();
2399 assert_eq!(
2400 unsafe {
2401 asdf_ndarray_read_tile_2d(
2402 array,
2403 3,
2404 1,
2405 2,
2406 2,
2407 core::ptr::null(),
2408 ScalarType::Int32 as ScalarTypeAbi,
2409 &mut dst,
2410 )
2411 },
2412 NdarrayErr::OutOfBounds
2413 );
2414 unsafe { asdf_ndarray_destroy(array) };
2415 }
2416
2417 #[test]
2418 fn an_ndim_tile_can_allocate_its_own_buffer() {
2419 let array = int32_grid();
2420 let origin: [u64; 2] = [0, 2];
2421 let shape: [u64; 2] = [3, 2];
2422 let mut dst: *mut c_void = core::ptr::null_mut();
2424 assert_eq!(
2425 unsafe {
2426 asdf_ndarray_read_tile_ndim(
2427 array,
2428 origin.as_ptr(),
2429 shape.as_ptr(),
2430 ScalarType::Int32 as ScalarTypeAbi,
2431 &mut dst,
2432 )
2433 },
2434 NdarrayErr::Ok
2435 );
2436 assert!(!dst.is_null());
2437 let got = unsafe { core::slice::from_raw_parts(dst.cast::<i32>(), 6) };
2438 assert_eq!(got, [2, 3, 6, 7, 10, 11]);
2439 unsafe { libc::free(dst) };
2440 unsafe { asdf_ndarray_destroy(array) };
2441 }
2442
2443 #[test]
2444 fn an_inline_array_has_no_block() {
2445 let array = int32_grid();
2446 assert!(unsafe { asdf_ndarray_block(array) }.is_null());
2448 unsafe { asdf_ndarray_destroy(array) };
2449 }
2450
2451 #[test]
2452 fn reports_shape_and_sizes_through_the_public_fields() {
2453 let array = int32_array(&[1, 2, 3, 4]);
2454 let view = unsafe { &*array };
2455
2456 assert_eq!(view.ndim, 1);
2457 assert_eq!(view.source, 0);
2458 assert!(!view.shape.is_null());
2459 assert_eq!(unsafe { *view.shape }, 4);
2460 assert_eq!(view.byteorder, ByteOrder::Little as i32);
2461
2462 assert_eq!(unsafe { asdf_ndarray_size(array) }, 4);
2463 assert_eq!(unsafe { asdf_ndarray_nbytes(array) }, 16);
2464
2465 unsafe { asdf_ndarray_destroy(array) };
2466 }
2467
2468 #[test]
2469 fn datatype_size_is_computed_when_left_zero() {
2470 let mut dt = asdf_datatype_t {
2471 type_: scalar_abi(ScalarType::Float64),
2472 size: 0,
2473 name: core::ptr::null(),
2474 byteorder: 0,
2475 ndim: 0,
2476 shape: core::ptr::null(),
2477 nfields: 0,
2478 fields: core::ptr::null(),
2479 };
2480 assert_eq!(unsafe { asdf_datatype_size(&mut dt) }, 8);
2481 assert_eq!(dt.size, 8);
2483 }
2484
2485 #[test]
2486 fn scalar_type_names_round_trip() {
2487 for name in ["int8", "uint64", "float32", "complex128", "bool8", "ascii", "ucs4"] {
2488 let c = CString::new(name).unwrap();
2489 let code = unsafe { asdf_scalar_datatype_from_string(c.as_ptr()) };
2490 assert_ne!(code, 0, "{name}");
2491 let back = unsafe { CStr::from_ptr(asdf_scalar_datatype_to_string(code)) };
2492 assert_eq!(back.to_str().unwrap(), name);
2493 }
2494 let bogus = CString::new("float128").unwrap();
2496 assert_eq!(unsafe { asdf_scalar_datatype_from_string(bogus.as_ptr()) }, 0);
2497 }
2498
2499 #[test]
2500 fn reads_the_whole_array_in_its_own_type() {
2501 let array = int32_array(&[10, -20, 30]);
2502 let mut dst: *mut c_void = core::ptr::null_mut();
2503 assert_eq!(unsafe { asdf_ndarray_read_all(array, 0, &mut dst) }, NdarrayErr::Ok);
2505 assert!(!dst.is_null());
2506 let values = unsafe { core::slice::from_raw_parts(dst.cast::<i32>(), 3) };
2507 assert_eq!(values, [10, -20, 30]);
2508
2509 unsafe { libc::free(dst) };
2510 unsafe { asdf_ndarray_destroy(array) };
2511 }
2512
2513 #[test]
2514 fn reads_the_whole_array_converted() {
2515 let array = int32_array(&[1, 2, 3]);
2516 let mut dst: *mut c_void = core::ptr::null_mut();
2517 assert_eq!(
2518 unsafe { asdf_ndarray_read_all(array, scalar_abi(ScalarType::Float64), &mut dst) },
2519 NdarrayErr::Ok
2520 );
2521 let values = unsafe { core::slice::from_raw_parts(dst.cast::<f64>(), 3) };
2522 assert_eq!(values, [1.0, 2.0, 3.0]);
2523 unsafe { libc::free(dst) };
2524 unsafe { asdf_ndarray_destroy(array) };
2525 }
2526
2527 #[test]
2528 fn read_all_fills_a_caller_supplied_buffer() {
2529 let array = int32_array(&[7, 8]);
2530 let mut buffer = [0i32; 2];
2531 let mut dst: *mut c_void = buffer.as_mut_ptr().cast();
2532 assert_eq!(unsafe { asdf_ndarray_read_all(array, 0, &mut dst) }, NdarrayErr::Ok);
2533 assert_eq!(buffer, [7, 8]);
2534 unsafe { asdf_ndarray_destroy(array) };
2535 }
2536
2537 #[test]
2538 fn converting_a_value_that_does_not_fit_overflows() {
2539 let array = int32_array(&[1000]);
2540 let mut dst: *mut c_void = core::ptr::null_mut();
2541 assert_eq!(
2542 unsafe { asdf_ndarray_read_all(array, scalar_abi(ScalarType::Int8), &mut dst) },
2543 NdarrayErr::Overflow
2544 );
2545
2546 assert!(!dst.is_null());
2550 assert_eq!(unsafe { *dst.cast::<i8>() }, i8::MAX);
2551 unsafe { libc::free(dst) };
2552
2553 unsafe { asdf_ndarray_destroy(array) };
2554 }
2555
2556 #[test]
2557 fn reads_single_elements_by_index() {
2558 let array = int32_array(&[5, 6, 7]);
2559
2560 for (index, expected) in [(0u64, 5i64), (1, 6), (2, 7)] {
2561 let mut err: c_int = -1;
2562 let value = unsafe { asdf_ndarray_read_int64_at(array, &index, &mut err) };
2563 assert_eq!(err, NdarrayErr::Ok as c_int);
2564 assert_eq!(value, expected);
2565 }
2566 unsafe { asdf_ndarray_destroy(array) };
2567 }
2568
2569 #[test]
2570 fn out_of_bounds_reads_are_reported() {
2571 let array = int32_array(&[1, 2]);
2572 let index = 5u64;
2573 let mut err: c_int = -1;
2574 let value = unsafe { asdf_ndarray_read_int64_at(array, &index, &mut err) };
2575 assert_eq!(err, NdarrayErr::OutOfBounds as c_int);
2576 assert_eq!(value, 0, "a failed read returns a zero value");
2577 unsafe { asdf_ndarray_destroy(array) };
2578 }
2579
2580 #[test]
2581 fn single_element_reads_convert_and_overflow() {
2582 let array = int32_array(&[300]);
2583 let index = 0u64;
2584
2585 let mut err: c_int = -1;
2586 let wide = unsafe { asdf_ndarray_read_float64_at(array, &index, &mut err) };
2587 assert_eq!(err, NdarrayErr::Ok as c_int);
2588 assert_eq!(wide, 300.0);
2589
2590 let mut err: c_int = -1;
2591 let narrow = unsafe { asdf_ndarray_read_uint8_at(array, &index, &mut err) };
2592 assert_eq!(err, NdarrayErr::Conversion as c_int);
2593 assert_eq!(narrow, 0);
2594
2595 unsafe { asdf_ndarray_destroy(array) };
2596 }
2597
2598 #[test]
2599 fn float16_reads_go_through_the_bit_pattern() {
2600 let array = int32_array(&[3]);
2601 let index = 0u64;
2602 let mut err: c_int = -1;
2603 let bits =
2604 unsafe { asdf_shim_ndarray_read_float16_bits_at(array.cast(), &index, &mut err) };
2605 assert_eq!(err, NdarrayErr::Ok as c_int);
2606 assert_eq!(half::f16::from_bits(bits).to_f64(), 3.0);
2607 unsafe { asdf_ndarray_destroy(array) };
2608 }
2609
2610 #[test]
2611 fn data_alloc_is_idempotent_and_freeable() {
2612 let array = int32_array(&[0; 4]);
2613 let first = unsafe { asdf_ndarray_data_alloc(array) };
2614 let second = unsafe { asdf_ndarray_data_alloc(array) };
2615 assert!(!first.is_null());
2616 assert_eq!(first, second, "repeated calls return the same buffer");
2617
2618 unsafe { asdf_ndarray_data_dealloc(array) };
2619 unsafe { asdf_ndarray_data_dealloc(array) };
2621 unsafe { asdf_ndarray_destroy(array) };
2622 }
2623
2624 #[test]
2625 fn data_copy_fills_the_allocated_buffer() {
2626 let array = int32_array(&[0; 3]);
2627 let source: Vec<i32> = vec![11, 22, 33];
2628 assert_eq!(
2629 unsafe { asdf_ndarray_data_copy(array, source.as_ptr().cast()) },
2630 NdarrayErr::Ok
2631 );
2632
2633 let mut size = 0usize;
2634 let data = unsafe { asdf_ndarray_data(array, &mut size) };
2635 assert_eq!(size, 12);
2636 let values = unsafe { core::slice::from_raw_parts(data.cast::<i32>(), 3) };
2637 assert_eq!(values, [11, 22, 33]);
2638
2639 unsafe { asdf_ndarray_data_dealloc(array) };
2640 unsafe { asdf_ndarray_destroy(array) };
2641 }
2642
2643 #[test]
2657 fn the_data_pointer_is_aligned_for_any_element_type() {
2658 for len in [1usize, 2, 3, 4, 6, 12, 20, 36] {
2661 let array = int32_array(&vec![0; len]);
2662
2663 let allocated = unsafe { asdf_ndarray_data_alloc(array) };
2664 assert!(!allocated.is_null());
2665 assert_eq!(
2666 allocated as usize % 16,
2667 0,
2668 "data_alloc returned a {}-byte buffer aligned to {}",
2669 len * 4,
2670 1 << (allocated as usize).trailing_zeros().min(4)
2671 );
2672
2673 let mut size = 0usize;
2674 let data = unsafe { asdf_ndarray_data(array, &mut size) };
2675 assert_eq!(data as usize % 16, 0, "asdf_ndarray_data returned an unaligned pointer");
2676 assert_eq!(size, len * 4);
2677
2678 unsafe { asdf_ndarray_data_dealloc(array) };
2679
2680 let bytes: Vec<u8> = (0..len as i32).flat_map(i32::to_le_bytes).collect();
2683 set_data(array, &bytes);
2684 let read = unsafe { asdf_ndarray_data(array, &mut size) };
2685 assert_eq!(read as usize % 16, 0, "file data was handed out unaligned");
2686 assert_eq!(unsafe { core::slice::from_raw_parts(read.cast::<u8>(), size) }, &bytes[..]);
2687
2688 unsafe { asdf_ndarray_destroy(array) };
2689 }
2690 }
2691
2692 #[test]
2693 fn compression_and_storage_settings() {
2694 let array = int32_array(&[1]);
2695
2696 let zlib = CString::new("zlib").unwrap();
2697 assert_eq!(unsafe { asdf_ndarray_compression_set(array, zlib.as_ptr()) }, 0);
2698 let bogus = CString::new("zstd").unwrap();
2699 assert_eq!(unsafe { asdf_ndarray_compression_set(array, bogus.as_ptr()) }, -1);
2700
2701 assert_eq!(unsafe { asdf_ndarray_storage(array) }, AsdfArrayStorage::Internal);
2703 unsafe { asdf_ndarray_storage_set(array, AsdfArrayStorage::Inline) };
2704 assert_eq!(unsafe { asdf_ndarray_storage(array) }, AsdfArrayStorage::Inline);
2705
2706 unsafe { asdf_ndarray_storage_set(array, AsdfArrayStorage::External) };
2708 assert_eq!(unsafe { asdf_ndarray_storage(array) }, AsdfArrayStorage::Inline);
2709
2710 unsafe { asdf_ndarray_destroy(array) };
2711 }
2712
2713 #[test]
2714 fn a_compound_datatype_exposes_its_fields() {
2715 let doc = parse_document(
2716 "a:\n source: 0\n shape: [2]\n byteorder: little\n \
2717 datatype:\n - name: x\n datatype: float64\n \
2718 - name: y\n datatype: int32\n",
2719 )
2720 .unwrap();
2721 let root = doc.root().unwrap();
2722 let parsed = CoreNdarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
2723 let array = make_ndarray(parsed, vec![2]);
2724
2725 let view = unsafe { &*array };
2726 assert_eq!(view.datatype.nfields, 2);
2727 assert!(!view.datatype.fields.is_null());
2728
2729 let fields = unsafe { core::slice::from_raw_parts(view.datatype.fields, 2) };
2730 assert_eq!(fields[0].type_, scalar_abi(ScalarType::Float64));
2731 assert_eq!(fields[0].size, 8);
2732 assert_eq!(unsafe { CStr::from_ptr(fields[0].name) }.to_str().unwrap(), "x");
2733 assert_eq!(fields[1].type_, scalar_abi(ScalarType::Int32));
2734 assert_eq!(unsafe { CStr::from_ptr(fields[1].name) }.to_str().unwrap(), "y");
2735
2736 unsafe { asdf_ndarray_destroy(array) };
2737 }
2738
2739 #[test]
2740 fn multi_dimensional_indexing() {
2741 let doc = parse_document(
2742 "a:\n source: 0\n shape: [2, 3]\n datatype: uint8\n byteorder: little\n",
2743 )
2744 .unwrap();
2745 let root = doc.root().unwrap();
2746 let parsed = CoreNdarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
2747 let array = make_ndarray(parsed, vec![2, 3]);
2748 set_data(array, &[1, 2, 3, 4, 5, 6]);
2749
2750 let indices = [1u64, 2];
2752 let mut err: c_int = -1;
2753 let value = unsafe { asdf_ndarray_read_uint8_at(array, indices.as_ptr(), &mut err) };
2754 assert_eq!(err, NdarrayErr::Ok as c_int);
2755 assert_eq!(value, 6);
2756
2757 let bad = [0u64, 3];
2759 let mut err: c_int = -1;
2760 unsafe { asdf_ndarray_read_uint8_at(array, bad.as_ptr(), &mut err) };
2761 assert_eq!(err, NdarrayErr::OutOfBounds as c_int);
2762
2763 unsafe { asdf_ndarray_destroy(array) };
2764 }
2765
2766 #[test]
2767 fn err_discriminants_match_the_c_abi() {
2768 assert_eq!(NdarrayErr::Ok as i32, 0);
2769 assert_eq!(NdarrayErr::OutOfBounds as i32, 1);
2770 assert_eq!(NdarrayErr::Oom as i32, 2);
2771 assert_eq!(NdarrayErr::Inval as i32, 3);
2772 assert_eq!(NdarrayErr::Overflow as i32, 4);
2773 assert_eq!(NdarrayErr::Conversion as i32, 5);
2774 }
2775
2776 #[test]
2777 fn null_handles_are_tolerated() {
2778 let null: *mut asdf_ndarray_t = core::ptr::null_mut();
2779 assert_eq!(unsafe { asdf_ndarray_size(null) }, 0);
2780 assert_eq!(unsafe { asdf_ndarray_nbytes(null) }, 0);
2781 assert!(unsafe { asdf_ndarray_data_alloc(null) }.is_null());
2782 unsafe { asdf_ndarray_data_dealloc(null) };
2783 assert_eq!(unsafe { asdf_ndarray_data_copy(null, core::ptr::null()) }, NdarrayErr::Inval);
2784 assert_eq!(unsafe { asdf_datatype_size(core::ptr::null_mut()) }, 0);
2785 unsafe { asdf_ndarray_destroy(null) };
2786
2787 let mut size = 99usize;
2788 assert!(unsafe { asdf_ndarray_data(null, &mut size) }.is_null());
2789 assert_eq!(size, 0);
2790 }
2791 #[test]
2800 fn serializing_an_array_reclaims_its_internal_state() {
2801 use crate::file_ffi::{asdf_close, asdf_open_mem_ex};
2802
2803 let file = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
2804 assert!(!file.is_null());
2805
2806 let shape = [2u64, 2];
2807 let mut array = asdf_ndarray_t {
2808 source: 0,
2809 ndim: 2,
2810 shape: shape.as_ptr(),
2811 datatype: asdf_datatype_t {
2813 type_: 11,
2814 size: 0,
2815 name: core::ptr::null(),
2816 byteorder: 60,
2817 ndim: 0,
2818 shape: core::ptr::null(),
2819 nfields: 0,
2820 fields: core::ptr::null(),
2821 },
2822 byteorder: 60,
2823 offset: 0,
2824 strides: core::ptr::null(),
2825 _reserved: core::ptr::null_mut(),
2826 };
2827
2828 let array_ptr: *mut asdf_ndarray_t = &raw mut array;
2831
2832 unsafe { asdf_ndarray_storage_set(array_ptr, AsdfArrayStorage::Inline) };
2835 let data = [1.0f64, 2.0, 3.0, 4.0];
2836 assert_eq!(
2837 unsafe { asdf_ndarray_data_copy(array_ptr, data.as_ptr().cast()) },
2838 NdarrayErr::Ok
2839 );
2840 assert!(
2841 !unsafe { (*array_ptr)._reserved }.is_null(),
2842 "the copy should have attached state"
2843 );
2844
2845 let value = unsafe { asdf_value_of_ndarray(file, array_ptr) };
2846 assert!(!value.is_null());
2847 assert!(
2848 unsafe { (*array_ptr)._reserved }.is_null(),
2849 "the file owns the data now, so the array's state must be released"
2850 );
2851
2852 assert_eq!(unsafe { (*array_ptr).ndim }, 2);
2855 assert!(core::ptr::eq(unsafe { (*array_ptr).shape }, shape.as_ptr()));
2856
2857 unsafe { crate::file_ffi::asdf_value_destroy(value) };
2858 unsafe { asdf_close(file) };
2859 }
2860}