1use alloc::ffi::CString;
19use core::ffi::{CStr, c_char, c_int};
20
21use asdf_core::yaml::{Document, NodeId, Tag};
22
23use crate::extension_ffi::{asdf_software_t, asdf_tag_t};
24use crate::file_ffi::{AsdfFile, AsdfValue, value_document, value_file, value_node};
25use crate::panic::guard;
26use crate::types::AsdfValueErr;
27use crate::version_ffi::{asdf_version_parse, asdf_version_t};
28
29fn to_c_string(text: &str) -> *const c_char {
31 CString::new(text).map_or(core::ptr::null(), |c| c.into_raw().cast_const())
32}
33
34unsafe fn free_c_string(ptr: *const c_char) {
36 if !ptr.is_null() {
37 drop(unsafe { CString::from_raw(ptr.cast_mut()) });
38 }
39}
40
41unsafe fn clone_c_string(ptr: *const c_char) -> *const c_char {
43 let Some(text) = (unsafe { crate::ffi::c_str(ptr) }) else {
44 return core::ptr::null();
45 };
46 CString::new(text.to_bytes()).map_or(core::ptr::null(), |c| c.into_raw().cast_const())
47}
48
49fn string_field(doc: &Document, node: NodeId, key: &str) -> *const c_char {
51 doc.mapping_get(node, key)
52 .and_then(|id| doc.resolved(id).as_str().map(to_c_string))
53 .unwrap_or(core::ptr::null())
54}
55
56fn version_field(doc: &Document, node: NodeId, key: &str) -> *const asdf_version_t {
58 let Some(text) =
59 doc.mapping_get(node, key).and_then(|id| doc.resolved(id).as_str().map(str::to_string))
60 else {
61 return core::ptr::null();
62 };
63 let Ok(c) = CString::new(text) else {
64 return core::ptr::null();
65 };
66 unsafe { asdf_version_parse(c.as_ptr()) }.cast_const()
67}
68
69macro_rules! declare_extension {
75 (
76 name: $name:ident,
77 ty: $ty:ty,
78 tag: $tag:expr,
79 deserialize: $deserialize:path,
80 serialize: $serialize:path,
81 deinit: $deinit:path,
82 copy: $copy:path,
83 is_fn: $is_fn:ident,
84 value_is_fn: $value_is_fn:ident,
85 value_as_fn: $value_as_fn:ident,
86 value_of_fn: $value_of_fn:ident,
87 get_fn: $get_fn:ident,
88 set_fn: $set_fn:ident,
89 copy_fn: $copy_fn:ident,
90 copy_into_fn: $copy_into_fn:ident,
91 array_copy_fn: $array_copy_fn:ident,
92 deinit_fn: $deinit_fn:ident,
93 destroy_fn: $destroy_fn:ident,
94 tags: $tags:expr,
95 ext_build_fn: $ext_build_fn:ident,
96 ext_deserialize_fn: $ext_deserialize_fn:ident,
97 ext_serialize_fn: $ext_serialize_fn:ident,
98 ext_copy_fn: $ext_copy_fn:ident,
99 ext_deinit_fn: $ext_deinit_fn:ident,
100 ) => {
101 #[unsafe(no_mangle)]
111 pub unsafe extern "C" fn $value_is_fn(value: *mut AsdfValue) -> bool {
112 guard(stringify!($value_is_fn), false, || {
113 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
114 return false;
115 };
116 doc.tag_of(node).is_some_and(|found| {
117 let found = found.full();
118 let tags: &[&CStr] = $tags;
119 tags.iter().any(|t| t.to_bytes() == found.as_bytes())
120 })
121 })
122 }
123
124 #[unsafe(no_mangle)]
130 pub unsafe extern "C" fn $is_fn(file: *mut AsdfFile, path: *const c_char) -> bool {
131 guard(stringify!($is_fn), false, || {
132 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
133 if value.is_null() {
134 return false;
135 }
136 let matched = unsafe { $value_is_fn(value) };
137 unsafe { crate::file_ffi::asdf_value_destroy(value) };
138 matched
139 })
140 }
141
142 #[unsafe(no_mangle)]
148 pub unsafe extern "C" fn $value_as_fn(
149 value: *mut AsdfValue,
150 out: *mut *mut $ty,
151 ) -> AsdfValueErr {
152 guard(stringify!($value_as_fn), AsdfValueErr::Unknown, || {
153 if out.is_null() {
154 return AsdfValueErr::Unknown;
155 }
156 if !unsafe { $value_is_fn(value) } {
157 return AsdfValueErr::TypeMismatch;
158 }
159 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
160 return AsdfValueErr::Unknown;
161 };
162 let file = value_file(value).unwrap_or(core::ptr::null_mut());
166 let boxed: Box<$ty> = Box::new(<$ty>::zeroed());
167 let raw = Box::into_raw(boxed);
168 match $deserialize(doc, node, file, raw) {
169 AsdfValueErr::Ok => {
170 unsafe { write_out(out, raw) };
171 AsdfValueErr::Ok
172 }
173 err => {
174 unsafe { $destroy_fn(raw) };
175 err
176 }
177 }
178 })
179 }
180
181 #[unsafe(no_mangle)]
186 pub unsafe extern "C" fn $get_fn(
187 file: *mut AsdfFile,
188 path: *const c_char,
189 out: *mut *mut $ty,
190 ) -> AsdfValueErr {
191 guard(stringify!($get_fn), AsdfValueErr::Unknown, || {
192 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
193 if value.is_null() {
194 return AsdfValueErr::NotFound;
195 }
196 let result = unsafe { $value_as_fn(value, out) };
197 unsafe { crate::file_ffi::asdf_value_destroy(value) };
198 result
199 })
200 }
201
202 #[unsafe(no_mangle)]
208 pub unsafe extern "C" fn $value_of_fn(
209 file: *mut AsdfFile,
210 obj: *const $ty,
211 ) -> *mut AsdfValue {
212 guard(stringify!($value_of_fn), core::ptr::null_mut(), || {
213 if file.is_null() || obj.is_null() {
214 return core::ptr::null_mut();
215 }
216 let Some(doc) = $crate::file_ffi::file_document_mut(file) else {
217 return core::ptr::null_mut();
218 };
219 let Some(node) = $serialize(doc, unsafe { &*obj }) else {
220 return core::ptr::null_mut();
221 };
222 doc.node_mut(node).tag = Some(Tag::parse($tag));
223 Box::into_raw(Box::new(AsdfValue::new(file, node)))
224 })
225 }
226
227 #[unsafe(no_mangle)]
232 pub unsafe extern "C" fn $set_fn(
233 file: *mut AsdfFile,
234 path: *const c_char,
235 obj: *const $ty,
236 ) -> AsdfValueErr {
237 guard(stringify!($set_fn), AsdfValueErr::Unknown, || {
238 let value = unsafe { $value_of_fn(file, obj) };
239 if value.is_null() {
240 return AsdfValueErr::EmitFailure;
241 }
242 let result = unsafe { crate::file_ffi::set_value_at(file, path, value) };
243 unsafe { crate::file_ffi::asdf_value_destroy(value) };
244 result
245 })
246 }
247
248 #[unsafe(no_mangle)]
257 pub unsafe extern "C" fn $deinit_fn(obj: *mut $ty) {
258 guard(stringify!($deinit_fn), (), || {
259 if !obj.is_null() {
260 unsafe { $deinit(obj) };
261 }
262 })
263 }
264
265 #[unsafe(no_mangle)]
271 pub unsafe extern "C" fn $destroy_fn(obj: *mut $ty) {
272 guard(stringify!($destroy_fn), (), || {
273 if obj.is_null() {
274 return;
275 }
276 unsafe { $deinit(obj) };
277 drop(unsafe { Box::from_raw(obj) });
278 })
279 }
280
281 #[unsafe(no_mangle)]
289 pub unsafe extern "C" fn $copy_into_fn(
290 file: *mut AsdfFile,
291 src: *const $ty,
292 dst: *mut $ty,
293 ) -> bool {
294 guard(stringify!($copy_into_fn), false, || {
295 let _ = file;
296 if src.is_null() || dst.is_null() {
297 return false;
298 }
299 unsafe { core::ptr::write(dst, <$ty>::zeroed()) };
300 if unsafe { $copy(&*src, dst) } {
301 true
302 } else {
303 unsafe { $deinit(dst) };
304 false
305 }
306 })
307 }
308
309 #[unsafe(no_mangle)]
315 pub unsafe extern "C" fn $copy_fn(file: *mut AsdfFile, src: *const $ty) -> *mut $ty {
316 guard(stringify!($copy_fn), core::ptr::null_mut(), || {
317 if src.is_null() {
318 return core::ptr::null_mut();
319 }
320 let raw = Box::into_raw(Box::new(<$ty>::zeroed()));
321 if unsafe { $copy_into_fn(file, src, raw) } {
322 raw
323 } else {
324 drop(unsafe { Box::from_raw(raw) });
325 core::ptr::null_mut()
326 }
327 })
328 }
329
330 #[unsafe(no_mangle)]
335 pub unsafe extern "C" fn $array_copy_fn(
336 file: *mut AsdfFile,
337 src: *mut *const $ty,
338 ) -> *mut *mut $ty {
339 guard(stringify!($array_copy_fn), core::ptr::null_mut(), || {
340 if src.is_null() {
341 return core::ptr::null_mut();
342 }
343 let mut count = 0isize;
344 while !unsafe { *src.offset(count) }.is_null() {
345 count += 1;
346 }
347
348 let mut copies: Vec<*mut $ty> = Vec::with_capacity(count as usize + 1);
349 for index in 0..count {
350 let element = unsafe { *src.offset(index) };
351 let copy = unsafe { $copy_fn(file, element) };
352 if copy.is_null() {
353 for made in copies {
355 unsafe { $destroy_fn(made) };
356 }
357 return core::ptr::null_mut();
358 }
359 copies.push(copy);
360 }
361 copies.push(core::ptr::null_mut());
362 copies.shrink_to_fit();
363 let boxed = copies.into_boxed_slice();
364 Box::into_raw(boxed).cast::<*mut $ty>()
365 })
366 }
367
368 unsafe extern "C" fn $ext_deserialize_fn(
390 value: *mut AsdfValue,
391 _userdata: *const core::ffi::c_void,
392 out: *mut *mut core::ffi::c_void,
393 ) -> AsdfValueErr {
394 guard(stringify!($ext_deserialize_fn), AsdfValueErr::Unknown, || {
395 if out.is_null() {
396 return AsdfValueErr::Unknown;
397 }
398 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
399 return AsdfValueErr::Unknown;
400 };
401 let file = value_file(value).unwrap_or(core::ptr::null_mut());
402 let raw = Box::into_raw(Box::new(<$ty>::zeroed()));
403 match $deserialize(doc, node, file, raw) {
404 AsdfValueErr::Ok => {
405 unsafe { write_out(out, raw.cast::<core::ffi::c_void>()) };
406 AsdfValueErr::Ok
407 }
408 err => {
409 unsafe { $destroy_fn(raw) };
410 err
411 }
412 }
413 })
414 }
415
416 unsafe extern "C" fn $ext_serialize_fn(
421 file: *mut AsdfFile,
422 obj: *const core::ffi::c_void,
423 _userdata: *const core::ffi::c_void,
424 ) -> *mut AsdfValue {
425 unsafe { $value_of_fn(file, obj.cast::<$ty>()) }
426 }
427
428 unsafe extern "C" fn $ext_copy_fn(
433 file: *mut AsdfFile,
434 src: *const core::ffi::c_void,
435 dst: *mut core::ffi::c_void,
436 ) -> bool {
437 unsafe { $copy_into_fn(file, src.cast::<$ty>(), dst.cast::<$ty>()) }
438 }
439
440 unsafe extern "C" fn $ext_deinit_fn(obj: *mut core::ffi::c_void) {
445 unsafe { $deinit_fn(obj.cast::<$ty>()) };
446 }
447
448 fn $ext_build_fn() -> *mut crate::extension_ffi::asdf_extension_t {
455 use crate::extension_ffi::{asdf_extension_t, asdf_extension_vtab_t, libasdf_software};
456
457 let mut tags: Vec<*const c_char> = $tags.iter().map(|t: &&CStr| t.as_ptr()).collect();
458 tags.push(core::ptr::null());
459 let tags = Box::leak(tags.into_boxed_slice());
460
461 let vtab = Box::leak(Box::new(asdf_extension_vtab_t {
462 serialize: Some($ext_serialize_fn),
463 deserialize: Some($ext_deserialize_fn),
464 copy: Some($ext_copy_fn),
465 deinit: Some($ext_deinit_fn),
466 _reserved: [None; 4],
467 }));
468
469 Box::leak(Box::new(asdf_extension_t {
470 tags: tags.as_ptr(),
471 software: (&raw const libasdf_software)
472 .cast::<crate::extension_ffi::asdf_software_t>()
473 .cast_mut(),
474 vtab: core::ptr::from_ref(vtab),
475 size: core::mem::size_of::<$ty>(),
476 userdata: core::ptr::null_mut(),
477 }))
478 }
479 };
480}
481
482pub const SOFTWARE_TAG: &str = "tag:stsci.edu:asdf/core/software-1.0.0";
486
487impl asdf_software_t {
488 fn zeroed() -> Self {
490 Self {
491 name: core::ptr::null(),
492 version: core::ptr::null(),
493 author: core::ptr::null(),
494 homepage: core::ptr::null(),
495 }
496 }
497}
498
499fn software_deserialize(
500 doc: &Document,
501 node: NodeId,
502 _file: *mut AsdfFile,
503 out: *mut asdf_software_t,
504) -> AsdfValueErr {
505 let name = string_field(doc, node, "name");
507 let version = version_field(doc, node, "version");
508 if name.is_null() || version.is_null() {
509 unsafe { free_c_string(name) };
510 if !version.is_null() {
511 unsafe { crate::version_ffi::asdf_version_destroy(version.cast_mut()) };
512 }
513 return AsdfValueErr::ParseFailure;
514 }
515
516 unsafe {
517 (*out).name = name;
518 (*out).version = version;
519 (*out).author = string_field(doc, node, "author");
520 (*out).homepage = string_field(doc, node, "homepage");
521 }
522 AsdfValueErr::Ok
523}
524
525fn software_serialize(doc: &mut Document, obj: &asdf_software_t) -> Option<NodeId> {
526 let mut pairs = Vec::new();
527
528 let mut put = |doc: &mut Document, key: &str, ptr: *const c_char| {
529 if ptr.is_null() {
530 return;
531 }
532 let text = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned();
533 let k = doc.add_scalar(key);
534 let v = doc.add_scalar_styled(text, asdf_core::yaml::ScalarStyle::SingleQuoted);
535 pairs.push((k, v));
536 };
537
538 put(doc, "name", obj.name);
539 if !obj.version.is_null() {
540 let version = unsafe { &*obj.version };
541 put(doc, "version", version.version);
542 }
543 put(doc, "author", obj.author);
544 put(doc, "homepage", obj.homepage);
545
546 (!pairs.is_empty()).then(|| doc.add_mapping(pairs))
547}
548
549unsafe fn software_deinit(obj: *mut asdf_software_t) {
550 let software = unsafe { &mut *obj };
551 unsafe { free_c_string(software.name) };
552 unsafe { free_c_string(software.author) };
553 unsafe { free_c_string(software.homepage) };
554 if !software.version.is_null() {
555 unsafe { crate::version_ffi::asdf_version_destroy(software.version.cast_mut()) };
556 }
557 *software = asdf_software_t::zeroed();
558}
559
560unsafe fn software_copy(src: &asdf_software_t, dst: *mut asdf_software_t) -> bool {
561 let out = unsafe { &mut *dst };
562 out.name = unsafe { clone_c_string(src.name) };
563 out.author = unsafe { clone_c_string(src.author) };
564 out.homepage = unsafe { clone_c_string(src.homepage) };
565 out.version = if src.version.is_null() {
566 core::ptr::null()
567 } else {
568 unsafe { crate::version_ffi::asdf_version_copy(src.version) }.cast_const()
569 };
570 !(out.name.is_null() && !src.name.is_null())
572}
573
574declare_extension! {
575 name: software,
576 ty: asdf_software_t,
577 tag: SOFTWARE_TAG,
578 deserialize: software_deserialize,
579 serialize: software_serialize,
580 deinit: software_deinit,
581 copy: software_copy,
582 is_fn: asdf_is_software,
583 value_is_fn: asdf_value_is_software,
584 value_as_fn: asdf_value_as_software,
585 value_of_fn: asdf_value_of_software,
586 get_fn: asdf_get_software,
587 set_fn: asdf_set_software,
588 copy_fn: asdf_software_copy,
589 copy_into_fn: asdf_software_copy_into,
590 array_copy_fn: asdf_software_array_copy,
591 deinit_fn: asdf_software_deinit,
592 destroy_fn: asdf_software_destroy,
593 tags: &[c"tag:stsci.edu:asdf/core/software-1.0.0"],
595 ext_build_fn: build_software_extension,
596 ext_deserialize_fn: software_ext_deserialize,
597 ext_serialize_fn: software_ext_serialize,
598 ext_copy_fn: software_ext_copy,
599 ext_deinit_fn: software_ext_deinit,
600}
601
602pub const EXTENSION_METADATA_TAG: &str = "tag:stsci.edu:asdf/core/extension_metadata-1.0.0";
606
607#[repr(C)]
609#[derive(Debug)]
610pub struct asdf_extension_metadata_t {
611 pub extension_class: *const c_char,
613 pub package: *const asdf_software_t,
615 pub metadata: *mut AsdfValue,
617}
618
619impl asdf_extension_metadata_t {
620 fn zeroed() -> Self {
621 Self {
622 extension_class: core::ptr::null(),
623 package: core::ptr::null(),
624 metadata: core::ptr::null_mut(),
625 }
626 }
627}
628
629fn extension_metadata_deserialize(
630 doc: &Document,
631 node: NodeId,
632 file: *mut AsdfFile,
633 out: *mut asdf_extension_metadata_t,
634) -> AsdfValueErr {
635 let class = string_field(doc, node, "extension_class");
636 if class.is_null() {
637 return AsdfValueErr::ParseFailure;
638 }
639
640 let package = doc
645 .mapping_get(node, "package")
646 .map(|id| {
647 let raw = Box::into_raw(Box::new(asdf_software_t::zeroed()));
648 if software_deserialize(doc, id, file, raw) == AsdfValueErr::Ok {
649 raw.cast_const()
650 } else {
651 drop(unsafe { Box::from_raw(raw) });
652 core::ptr::null()
653 }
654 })
655 .unwrap_or(core::ptr::null());
656
657 let metadata = if file.is_null() {
660 core::ptr::null_mut()
661 } else {
662 crate::value_ffi::make_value(file, node)
663 };
664
665 unsafe {
666 (*out).extension_class = class;
667 (*out).package = package;
668 (*out).metadata = metadata;
669 }
670 AsdfValueErr::Ok
671}
672
673fn extension_metadata_serialize(
674 doc: &mut Document,
675 obj: &asdf_extension_metadata_t,
676) -> Option<NodeId> {
677 let mut pairs = Vec::new();
678 if !obj.extension_class.is_null() {
679 let text = unsafe { CStr::from_ptr(obj.extension_class) }.to_string_lossy().into_owned();
680 let k = doc.add_scalar("extension_class");
681 let v = doc.add_scalar_styled(text, asdf_core::yaml::ScalarStyle::SingleQuoted);
682 pairs.push((k, v));
683 }
684 if !obj.package.is_null()
685 && let Some(node) = software_serialize(doc, unsafe { &*obj.package })
686 {
687 doc.node_mut(node).tag = Some(Tag::parse(SOFTWARE_TAG));
688 let k = doc.add_scalar("package");
692 pairs.push((k, node));
693 }
694
695 if let Some(node) = value_node(obj.metadata)
699 && let Some(entries) = doc.mapping_entries(doc.resolve(node)).map(<[_]>::to_vec)
700 {
701 for entry in entries {
702 let key = doc.resolved(entry.key).as_str().map(str::to_string);
703 if matches!(key.as_deref(), Some("extension_class" | "package")) {
704 continue;
705 }
706 pairs.push((entry.key, entry.value));
707 }
708 }
709
710 (!pairs.is_empty()).then(|| doc.add_mapping(pairs))
711}
712
713unsafe fn extension_metadata_deinit(obj: *mut asdf_extension_metadata_t) {
714 let metadata = unsafe { &mut *obj };
715 unsafe { free_c_string(metadata.extension_class) };
716 if !metadata.package.is_null() {
717 unsafe { asdf_software_destroy(metadata.package.cast_mut()) };
718 }
719 if !metadata.metadata.is_null() {
720 unsafe { crate::file_ffi::asdf_value_destroy(metadata.metadata) };
721 }
722 *metadata = asdf_extension_metadata_t::zeroed();
723}
724
725unsafe fn extension_metadata_copy(
726 src: &asdf_extension_metadata_t,
727 dst: *mut asdf_extension_metadata_t,
728) -> bool {
729 let out = unsafe { &mut *dst };
730 out.extension_class = unsafe { clone_c_string(src.extension_class) };
731 out.package = if src.package.is_null() {
732 core::ptr::null()
733 } else {
734 unsafe { asdf_software_copy(core::ptr::null_mut(), src.package) }.cast_const()
735 };
736 out.metadata = core::ptr::null_mut();
737 true
738}
739
740declare_extension! {
741 name: extension_metadata,
742 ty: asdf_extension_metadata_t,
743 tag: EXTENSION_METADATA_TAG,
744 deserialize: extension_metadata_deserialize,
745 serialize: extension_metadata_serialize,
746 deinit: extension_metadata_deinit,
747 copy: extension_metadata_copy,
748 is_fn: asdf_is_extension_metadata,
749 value_is_fn: asdf_value_is_extension_metadata,
750 value_as_fn: asdf_value_as_extension_metadata,
751 value_of_fn: asdf_value_of_extension_metadata,
752 get_fn: asdf_get_extension_metadata,
753 set_fn: asdf_set_extension_metadata,
754 copy_fn: asdf_extension_metadata_copy,
755 copy_into_fn: asdf_extension_metadata_copy_into,
756 array_copy_fn: asdf_extension_metadata_array_copy,
757 deinit_fn: asdf_extension_metadata_deinit,
758 destroy_fn: asdf_extension_metadata_destroy,
759 tags: &[c"tag:stsci.edu:asdf/core/extension_metadata-1.0.0"],
761 ext_build_fn: build_extension_metadata_extension,
762 ext_deserialize_fn: extension_metadata_ext_deserialize,
763 ext_serialize_fn: extension_metadata_ext_serialize,
764 ext_copy_fn: extension_metadata_ext_copy,
765 ext_deinit_fn: extension_metadata_ext_deinit,
766}
767
768#[unsafe(no_mangle)]
774pub unsafe extern "C" fn asdf_library_set(file: *mut AsdfFile, software: *const asdf_software_t) {
775 guard("asdf_library_set", (), || {
776 if file.is_null() || software.is_null() {
777 return;
778 }
779 let path = c"asdf_library";
780 unsafe { asdf_set_software(file, path.as_ptr(), software) };
781 })
782}
783
784#[unsafe(no_mangle)]
790pub unsafe extern "C" fn asdf_library_set_version(file: *mut AsdfFile, version: *const c_char) {
791 guard("asdf_library_set_version", (), || {
792 if file.is_null() || version.is_null() {
793 return;
794 }
795 let path = c"asdf_library/version";
796 let value = unsafe { crate::value_ffi::asdf_value_of_string0(file, version) };
797 if value.is_null() {
798 return;
799 }
800 unsafe { crate::file_ffi::set_value_at(file, path.as_ptr(), value) };
801 unsafe { crate::file_ffi::asdf_value_destroy(value) };
802 })
803}
804
805pub unsafe fn parse_tag(tag: *const c_char) -> *mut asdf_tag_t {
810 unsafe { crate::extension_ffi::asdf_tag_parse(tag) }
811}
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816 use crate::file_ffi::{asdf_close, asdf_open_mem_ex, asdf_write_to_mem};
817
818 struct Handle(*mut AsdfFile);
819 impl Drop for Handle {
820 fn drop(&mut self) {
821 unsafe { asdf_close(self.0) };
822 }
823 }
824
825 fn sample() -> Handle {
826 let mut buf = Vec::new();
827 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
828 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
829 buf.extend_from_slice(
830 b"asdf_library: !core/software-1.0.0\n \
831 author: The ASDF Developers\n \
832 homepage: 'http://github.com/asdf-format/asdf'\n \
833 name: asdf\n version: 4.1.0\n\
834 history:\n extensions:\n - !core/extension_metadata-1.0.0\n \
835 extension_class: asdf.extension._manifest.ManifestExtension\n \
836 extension_uri: asdf://asdf-format.org/core/extensions/core-1.6.0\n \
837 package: !core/software-1.0.0 {name: asdf_standard, version: 1.1.1}\n \
838 software: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n \
839 - !core/extension_metadata-1.0.0\n \
840 extension_class: some.other.Extension\n \
841 software: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n",
842 );
843 buf.extend_from_slice(b"...\n");
844 let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
845 assert!(!f.is_null());
846 Handle(f)
847 }
848
849 #[test]
850 fn reads_the_asdf_library_software() {
851 let h = sample();
852 let path = c"asdf_library";
853 assert!(unsafe { asdf_is_software(h.0, path.as_ptr()) });
854
855 let mut software: *mut asdf_software_t = core::ptr::null_mut();
856 assert_eq!(
857 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) },
858 AsdfValueErr::Ok
859 );
860 assert!(!software.is_null());
861
862 let view = unsafe { &*software };
863 assert_eq!(unsafe { CStr::from_ptr(view.name) }.to_str().unwrap(), "asdf");
864 assert!(!view.version.is_null());
865 let version = unsafe { &*view.version };
866 assert_eq!((version.major, version.minor, version.patch), (4, 1, 0));
867 assert_eq!(unsafe { CStr::from_ptr(view.author) }.to_str().unwrap(), "The ASDF Developers");
868
869 unsafe { asdf_software_destroy(software) };
870 }
871
872 #[test]
873 fn a_wrong_tag_is_a_mismatch() {
874 let h = sample();
875 let path = c"history";
876 assert!(!unsafe { asdf_is_software(h.0, path.as_ptr()) });
877
878 let mut software: *mut asdf_software_t = core::ptr::null_mut();
879 assert_eq!(
880 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) },
881 AsdfValueErr::TypeMismatch
882 );
883 assert!(software.is_null());
884 }
885
886 #[test]
887 fn a_missing_path_is_not_found() {
888 let h = sample();
889 let path = c"nope";
890 let mut software: *mut asdf_software_t = core::ptr::null_mut();
891 assert_eq!(
892 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) },
893 AsdfValueErr::NotFound
894 );
895 }
896
897 #[test]
898 fn software_round_trips_through_a_written_file() {
899 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
900 let h = Handle(f);
901
902 let version = unsafe { asdf_version_parse(c"2.3.4".as_ptr()) };
903 let software = asdf_software_t {
904 name: c"my-writer".as_ptr(),
905 version: version.cast_const(),
906 author: c"Someone".as_ptr(),
907 homepage: c"https://example.com".as_ptr(),
908 };
909
910 let path = c"asdf_library";
911 assert_eq!(unsafe { asdf_set_software(h.0, path.as_ptr(), &software) }, AsdfValueErr::Ok);
912
913 let mut buf: *mut core::ffi::c_void = core::ptr::null_mut();
914 let mut size = 0usize;
915 assert_eq!(unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) }, 0);
916
917 let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
918 let r = Handle(reopened);
919
920 let mut read_back: *mut asdf_software_t = core::ptr::null_mut();
921 assert_eq!(
922 unsafe { asdf_get_software(r.0, path.as_ptr(), &mut read_back) },
923 AsdfValueErr::Ok
924 );
925 let view = unsafe { &*read_back };
926 assert_eq!(unsafe { CStr::from_ptr(view.name) }.to_str().unwrap(), "my-writer");
927 assert_eq!(unsafe { &*view.version }.minor, 3);
928
929 unsafe { asdf_software_destroy(read_back) };
930 unsafe { libc::free(buf) };
931 unsafe { crate::version_ffi::asdf_version_destroy(version) };
932 }
933
934 #[test]
935 fn copies_are_independent() {
936 let h = sample();
937 let path = c"asdf_library";
938 let mut software: *mut asdf_software_t = core::ptr::null_mut();
939 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) };
940
941 let copy = unsafe { asdf_software_copy(h.0, software) };
942 assert!(!copy.is_null());
943 unsafe {
945 assert_ne!((*copy).name, (*software).name);
946 assert_ne!((*copy).version, (*software).version);
947 }
948
949 unsafe { asdf_software_destroy(software) };
951 assert_eq!(unsafe { CStr::from_ptr((*copy).name) }.to_str().unwrap(), "asdf");
952 unsafe { asdf_software_destroy(copy) };
953 }
954
955 #[test]
956 fn deinit_is_safe_on_a_zeroed_object() {
957 let mut zeroed = asdf_software_t::zeroed();
960 unsafe { asdf_software_deinit(&mut zeroed) };
961 unsafe { asdf_software_deinit(&mut zeroed) };
962 unsafe { asdf_software_deinit(core::ptr::null_mut()) };
963 }
964
965 #[test]
966 fn copy_into_zeroes_the_destination_first() {
967 let h = sample();
968 let path = c"asdf_library";
969 let mut software: *mut asdf_software_t = core::ptr::null_mut();
970 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) };
971
972 let mut destination = asdf_software_t::zeroed();
973 assert!(unsafe { asdf_software_copy_into(h.0, software, &mut destination) });
974 assert_eq!(unsafe { CStr::from_ptr(destination.name) }.to_str().unwrap(), "asdf");
975
976 unsafe { asdf_software_deinit(&mut destination) };
977 unsafe { asdf_software_destroy(software) };
978 }
979
980 #[test]
981 fn arrays_of_objects_copy() {
982 let h = sample();
983 let path = c"asdf_library";
984 let mut software: *mut asdf_software_t = core::ptr::null_mut();
985 unsafe { asdf_get_software(h.0, path.as_ptr(), &mut software) };
986
987 let mut array: [*const asdf_software_t; 2] = [software, core::ptr::null()];
988 let copies = unsafe { asdf_software_array_copy(h.0, array.as_mut_ptr()) };
989 assert!(!copies.is_null());
990
991 let first = unsafe { *copies };
992 assert!(!first.is_null());
993 assert_eq!(unsafe { CStr::from_ptr((*first).name) }.to_str().unwrap(), "asdf");
994 assert!(unsafe { *copies.offset(1) }.is_null());
996
997 unsafe { asdf_software_destroy(first) };
998 drop(unsafe { Box::from_raw(core::ptr::slice_from_raw_parts_mut(copies, 2)) });
999 unsafe { asdf_software_destroy(software) };
1000 }
1001
1002 #[test]
1003 fn reads_extension_metadata() {
1004 let h = sample();
1005 let path = c"history/extensions/0";
1006 assert!(unsafe { asdf_is_extension_metadata(h.0, path.as_ptr()) });
1007
1008 let mut metadata: *mut asdf_extension_metadata_t = core::ptr::null_mut();
1009 assert_eq!(
1010 unsafe { asdf_get_extension_metadata(h.0, path.as_ptr(), &mut metadata) },
1011 AsdfValueErr::Ok
1012 );
1013 let view = unsafe { &*metadata };
1014 assert_eq!(
1015 unsafe { CStr::from_ptr(view.extension_class) }.to_str().unwrap(),
1016 "asdf.extension._manifest.ManifestExtension"
1017 );
1018 assert!(!view.package.is_null());
1022 assert_eq!(
1023 unsafe { CStr::from_ptr((*view.package).name) }.to_str().unwrap(),
1024 "asdf_standard"
1025 );
1026
1027 assert!(!view.metadata.is_null());
1030 let uri = c"extension_uri";
1031 let entry = unsafe { crate::value_ffi::asdf_mapping_get(view.metadata, uri.as_ptr()) };
1032 assert!(!entry.is_null());
1033 let mut text = core::ptr::null();
1034 assert_eq!(
1035 unsafe { crate::value_ffi::asdf_value_as_string0(entry, &mut text) },
1036 AsdfValueErr::Ok
1037 );
1038 assert_eq!(
1039 unsafe { CStr::from_ptr(text) }.to_str().unwrap(),
1040 "asdf://asdf-format.org/core/extensions/core-1.6.0"
1041 );
1042 unsafe { crate::file_ffi::asdf_value_destroy(entry) };
1043 unsafe { asdf_extension_metadata_destroy(metadata) };
1044 }
1045
1046 #[test]
1049 fn extension_metadata_without_a_package_reports_none() {
1050 let h = sample();
1051 let path = c"history/extensions/1";
1052 let mut metadata: *mut asdf_extension_metadata_t = core::ptr::null_mut();
1053 assert_eq!(
1054 unsafe { asdf_get_extension_metadata(h.0, path.as_ptr(), &mut metadata) },
1055 AsdfValueErr::Ok
1056 );
1057 let view = unsafe { &*metadata };
1058 assert!(view.package.is_null());
1059 assert!(!view.metadata.is_null(), "the mapping is still reachable");
1060 unsafe { asdf_extension_metadata_destroy(metadata) };
1061 }
1062
1063 #[test]
1064 fn null_handles_are_tolerated() {
1065 let mut out: *mut asdf_software_t = core::ptr::null_mut();
1066 assert_eq!(
1067 unsafe { asdf_value_as_software(core::ptr::null_mut(), &mut out) },
1068 AsdfValueErr::TypeMismatch
1069 );
1070 assert!(
1071 unsafe { asdf_value_of_software(core::ptr::null_mut(), core::ptr::null()) }.is_null()
1072 );
1073 assert!(unsafe { asdf_software_copy(core::ptr::null_mut(), core::ptr::null()) }.is_null());
1074 assert!(!unsafe {
1075 asdf_software_copy_into(core::ptr::null_mut(), core::ptr::null(), core::ptr::null_mut())
1076 });
1077 unsafe { asdf_software_destroy(core::ptr::null_mut()) };
1078 unsafe { asdf_library_set(core::ptr::null_mut(), core::ptr::null()) };
1079 unsafe { asdf_library_set_version(core::ptr::null_mut(), core::ptr::null()) };
1080 }
1081}
1082
1083use crate::time_ffi::{
1086 TIME_TAG, asdf_time_t, time_copy, time_deinit, time_deserialize, time_serialize,
1087};
1088
1089declare_extension! {
1090 name: time,
1091 ty: asdf_time_t,
1092 tag: TIME_TAG,
1093 deserialize: time_deserialize,
1094 serialize: time_serialize,
1095 deinit: time_deinit,
1096 copy: time_copy,
1097 is_fn: asdf_is_time,
1098 value_is_fn: asdf_value_is_time,
1099 value_as_fn: asdf_value_as_time,
1100 value_of_fn: asdf_value_of_time,
1101 get_fn: asdf_get_time,
1102 set_fn: asdf_set_time,
1103 copy_fn: asdf_time_copy,
1104 copy_into_fn: asdf_time_copy_into,
1105 array_copy_fn: asdf_time_array_copy,
1106 deinit_fn: asdf_time_deinit,
1107 destroy_fn: asdf_time_destroy,
1108 tags: &[
1110 c"tag:stsci.edu:asdf/time/time-1.4.0",
1111 c"tag:stsci.edu:asdf/time/time-1.3.0",
1112 c"tag:stsci.edu:asdf/time/time-1.2.0",
1113 c"tag:stsci.edu:asdf/time/time-1.1.0",
1114 c"tag:stsci.edu:asdf/time/time-1.0.0",
1115 ],
1116 ext_build_fn: build_time_extension,
1117 ext_deserialize_fn: time_ext_deserialize,
1118 ext_serialize_fn: time_ext_serialize,
1119 ext_copy_fn: time_ext_copy,
1120 ext_deinit_fn: time_ext_deinit,
1121}
1122
1123pub const HISTORY_ENTRY_TAG: &str = "tag:stsci.edu:asdf/core/history_entry-1.0.0";
1127
1128#[repr(C)]
1130#[derive(Debug)]
1131pub struct asdf_history_entry_t {
1132 pub description: *const c_char,
1134 pub time: *const asdf_time_t,
1136 pub software: *mut *const asdf_software_t,
1138}
1139
1140impl asdf_history_entry_t {
1141 fn zeroed() -> Self {
1142 Self {
1143 description: core::ptr::null(),
1144 time: core::ptr::null(),
1145 software: core::ptr::null_mut(),
1146 }
1147 }
1148}
1149
1150fn read_software_list(
1154 doc: &Document,
1155 node: NodeId,
1156 file: *mut AsdfFile,
1157) -> *mut *const asdf_software_t {
1158 let Some(entry) = doc.mapping_get(node, "software") else {
1159 return core::ptr::null_mut();
1160 };
1161
1162 let nodes: Vec<NodeId> = match doc.sequence_items(entry) {
1163 Some(items) => items.to_vec(),
1164 None => vec![entry],
1166 };
1167
1168 let mut list: Vec<*const asdf_software_t> = Vec::with_capacity(nodes.len() + 1);
1169 for item in nodes {
1170 let raw = Box::into_raw(Box::new(asdf_software_t::zeroed()));
1171 if software_deserialize(doc, item, file, raw) == AsdfValueErr::Ok {
1172 list.push(raw.cast_const());
1173 } else {
1174 drop(unsafe { Box::from_raw(raw) });
1175 }
1176 }
1177 if list.is_empty() {
1178 return core::ptr::null_mut();
1179 }
1180 list.push(core::ptr::null());
1181 list.shrink_to_fit();
1182 Box::into_raw(list.into_boxed_slice()).cast::<*const asdf_software_t>()
1183}
1184
1185unsafe fn free_software_list(list: *mut *const asdf_software_t) {
1187 if list.is_null() {
1188 return;
1189 }
1190 let mut count = 0isize;
1191 while !unsafe { *list.offset(count) }.is_null() {
1192 unsafe { asdf_software_destroy((*list.offset(count)).cast_mut()) };
1193 count += 1;
1194 }
1195 let slice = core::ptr::slice_from_raw_parts_mut(list, count as usize + 1);
1197 drop(unsafe { Box::from_raw(slice) });
1198}
1199
1200fn history_entry_deserialize(
1201 doc: &Document,
1202 node: NodeId,
1203 file: *mut AsdfFile,
1204 out: *mut asdf_history_entry_t,
1205) -> AsdfValueErr {
1206 let description = string_field(doc, node, "description");
1207
1208 let time = doc
1209 .mapping_get(node, "time")
1210 .map(|id| {
1211 let raw = Box::into_raw(Box::new(asdf_time_t::zeroed()));
1212 if time_deserialize(doc, id, file, raw) == AsdfValueErr::Ok {
1213 raw.cast_const()
1214 } else {
1215 drop(unsafe { Box::from_raw(raw) });
1216 core::ptr::null()
1217 }
1218 })
1219 .unwrap_or(core::ptr::null());
1220
1221 unsafe {
1222 (*out).description = description;
1223 (*out).time = time;
1224 (*out).software = read_software_list(doc, node, file);
1225 }
1226 AsdfValueErr::Ok
1227}
1228
1229fn history_entry_serialize(doc: &mut Document, obj: &asdf_history_entry_t) -> Option<NodeId> {
1230 let mut pairs = Vec::new();
1231
1232 if !obj.description.is_null() {
1233 let text = unsafe { CStr::from_ptr(obj.description) }.to_string_lossy().into_owned();
1234 let key = doc.add_scalar("description");
1235 let value = doc.add_scalar_styled(text, asdf_core::yaml::ScalarStyle::SingleQuoted);
1236 pairs.push((key, value));
1237 }
1238 if !obj.time.is_null()
1239 && let Some(node) = time_serialize(doc, unsafe { &*obj.time })
1240 {
1241 doc.node_mut(node).tag = Some(Tag::parse(TIME_TAG));
1242 let key = doc.add_scalar("time");
1243 pairs.push((key, node));
1244 }
1245 if !obj.software.is_null() {
1246 let mut items = Vec::new();
1247 let mut index = 0isize;
1248 while !unsafe { *obj.software.offset(index) }.is_null() {
1249 let entry = unsafe { *obj.software.offset(index) };
1250 if let Some(node) = software_serialize(doc, unsafe { &*entry }) {
1251 doc.node_mut(node).tag = Some(Tag::parse(SOFTWARE_TAG));
1252 items.push(node);
1253 }
1254 index += 1;
1255 }
1256 if !items.is_empty() {
1257 let list = doc.add_sequence(items);
1258 let key = doc.add_scalar("software");
1259 pairs.push((key, list));
1260 }
1261 }
1262
1263 (!pairs.is_empty()).then(|| doc.add_mapping(pairs))
1264}
1265
1266unsafe fn history_entry_deinit(obj: *mut asdf_history_entry_t) {
1267 let entry = unsafe { &mut *obj };
1268 unsafe { free_c_string(entry.description) };
1269 if !entry.time.is_null() {
1270 unsafe { asdf_time_destroy(entry.time.cast_mut()) };
1271 }
1272 unsafe { free_software_list(entry.software) };
1273 *entry = asdf_history_entry_t::zeroed();
1274}
1275
1276unsafe fn history_entry_copy(src: &asdf_history_entry_t, dst: *mut asdf_history_entry_t) -> bool {
1277 let out = unsafe { &mut *dst };
1278 out.description = unsafe { clone_c_string(src.description) };
1279 out.time = if src.time.is_null() {
1280 core::ptr::null()
1281 } else {
1282 unsafe { asdf_time_copy(core::ptr::null_mut(), src.time) }.cast_const()
1283 };
1284 out.software = if src.software.is_null() {
1285 core::ptr::null_mut()
1286 } else {
1287 unsafe { asdf_software_array_copy(core::ptr::null_mut(), src.software) }
1288 .cast::<*const asdf_software_t>()
1289 };
1290 true
1291}
1292
1293declare_extension! {
1294 name: history_entry,
1295 ty: asdf_history_entry_t,
1296 tag: HISTORY_ENTRY_TAG,
1297 deserialize: history_entry_deserialize,
1298 serialize: history_entry_serialize,
1299 deinit: history_entry_deinit,
1300 copy: history_entry_copy,
1301 is_fn: asdf_is_history_entry,
1302 value_is_fn: asdf_value_is_history_entry,
1303 value_as_fn: asdf_value_as_history_entry,
1304 value_of_fn: asdf_value_of_history_entry,
1305 get_fn: asdf_get_history_entry,
1306 set_fn: asdf_set_history_entry,
1307 copy_fn: asdf_history_entry_copy,
1308 copy_into_fn: asdf_history_entry_copy_into,
1309 array_copy_fn: asdf_history_entry_array_copy,
1310 deinit_fn: asdf_history_entry_deinit,
1311 destroy_fn: asdf_history_entry_destroy,
1312 tags: &[c"tag:stsci.edu:asdf/core/history_entry-1.0.0"],
1314 ext_build_fn: build_history_entry_extension,
1315 ext_deserialize_fn: history_entry_ext_deserialize,
1316 ext_serialize_fn: history_entry_ext_serialize,
1317 ext_copy_fn: history_entry_ext_copy,
1318 ext_deinit_fn: history_entry_ext_deinit,
1319}
1320
1321#[unsafe(no_mangle)]
1327pub unsafe extern "C" fn asdf_history_entry_add(
1328 file: *mut AsdfFile,
1329 description: *const c_char,
1330) -> c_int {
1331 guard("asdf_history_entry_add", -1, || {
1332 if file.is_null() || description.is_null() {
1333 return -1;
1334 }
1335 let entry = asdf_history_entry_t {
1336 description,
1337 time: core::ptr::null(),
1338 software: core::ptr::null_mut(),
1339 };
1340 let value = unsafe { asdf_value_of_history_entry(file, &entry) };
1341 if value.is_null() {
1342 return -1;
1343 }
1344
1345 let handle = unsafe { &mut *file };
1348 let Some(node) = crate::file_ffi::value_node(value) else {
1349 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1350 return -1;
1351 };
1352 let Some(doc) = handle.document_for_values() else {
1353 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1354 return -1;
1355 };
1356
1357 let existing = doc.lookup_str("history/entries");
1358 let list = match existing {
1359 Some(list) if doc.resolved(list).is_sequence() => doc.resolve(list),
1360 _ => {
1361 let fresh = doc.add(asdf_core::yaml::Node::sequence());
1362 if doc.insert_at_str("history/entries", fresh).is_err() {
1363 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1364 return -1;
1365 }
1366 fresh
1367 }
1368 };
1369 if let asdf_core::yaml::NodeData::Sequence { items, .. } = &mut doc.node_mut(list).data {
1370 items.push(node);
1371 }
1372
1373 unsafe { crate::file_ffi::asdf_value_destroy(value) };
1374 0
1375 })
1376}
1377
1378#[cfg(test)]
1379mod history_tests {
1380 use super::*;
1381 use crate::file_ffi::{asdf_close, asdf_open_mem_ex, asdf_write_to_mem};
1382 use crate::time_ffi::asdf_time_t;
1383
1384 struct Handle(*mut AsdfFile);
1385 impl Drop for Handle {
1386 fn drop(&mut self) {
1387 unsafe { asdf_close(self.0) };
1388 }
1389 }
1390
1391 fn open(tree: &str) -> Handle {
1392 let mut buf = Vec::new();
1393 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
1394 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
1395 buf.extend_from_slice(tree.as_bytes());
1396 buf.extend_from_slice(b"...\n");
1397 let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
1398 assert!(!f.is_null());
1399 Handle(f)
1400 }
1401
1402 #[test]
1403 fn reads_a_history_entry() {
1404 let h = open(
1405 "entry: !core/history_entry-1.0.0\n \
1406 description: 'reprocessed with a new flat'\n \
1407 time: !time/time-1.4.0 '2026-09-04T12:00:00'\n \
1408 software:\n - !core/software-1.0.0 {name: mypipeline, version: 1.2.3}\n",
1409 );
1410
1411 let path = c"entry";
1412 assert!(unsafe { asdf_is_history_entry(h.0, path.as_ptr()) });
1413
1414 let mut entry: *mut asdf_history_entry_t = core::ptr::null_mut();
1415 assert_eq!(
1416 unsafe { asdf_get_history_entry(h.0, path.as_ptr(), &mut entry) },
1417 AsdfValueErr::Ok
1418 );
1419 let view = unsafe { &*entry };
1420 assert_eq!(
1421 unsafe { CStr::from_ptr(view.description) }.to_str().unwrap(),
1422 "reprocessed with a new flat"
1423 );
1424
1425 assert!(!view.time.is_null());
1427 let time = unsafe { &*view.time };
1428 assert_eq!(time.info.tm.tm_year, 2026 - 1900);
1429 assert_eq!(time.info.tm.tm_mday, 4);
1430
1431 assert!(!view.software.is_null());
1433 let first = unsafe { *view.software };
1434 assert!(!first.is_null());
1435 assert_eq!(unsafe { CStr::from_ptr((*first).name) }.to_str().unwrap(), "mypipeline");
1436 assert!(unsafe { *view.software.offset(1) }.is_null(), "list must be terminated");
1437
1438 unsafe { asdf_history_entry_destroy(entry) };
1439 }
1440
1441 #[test]
1442 fn a_single_software_object_is_accepted() {
1443 let h = open(
1445 "entry: !core/history_entry-1.0.0\n description: 'x'\n \
1446 software: !core/software-1.0.0 {name: solo, version: 0.1.0}\n",
1447 );
1448 let mut entry: *mut asdf_history_entry_t = core::ptr::null_mut();
1449 unsafe { asdf_get_history_entry(h.0, c"entry".as_ptr(), &mut entry) };
1450 let view = unsafe { &*entry };
1451 assert!(!view.software.is_null());
1452 let first = unsafe { *view.software };
1453 assert_eq!(unsafe { CStr::from_ptr((*first).name) }.to_str().unwrap(), "solo");
1454 unsafe { asdf_history_entry_destroy(entry) };
1455 }
1456
1457 #[test]
1458 fn history_entries_can_be_added_and_read_back() {
1459 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1460 let h = Handle(f);
1461
1462 assert_eq!(unsafe { asdf_history_entry_add(h.0, c"first change".as_ptr()) }, 0);
1463 assert_eq!(unsafe { asdf_history_entry_add(h.0, c"second change".as_ptr()) }, 0);
1464
1465 let mut buf: *mut core::ffi::c_void = core::ptr::null_mut();
1466 let mut size = 0usize;
1467 assert_eq!(unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) }, 0);
1468
1469 let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
1470 let r = Handle(reopened);
1471
1472 for (index, expected) in [(0, "first change"), (1, "second change")] {
1474 let path = CString::new(format!("history/entries/{index}")).unwrap();
1475 let mut entry: *mut asdf_history_entry_t = core::ptr::null_mut();
1476 assert_eq!(
1477 unsafe { asdf_get_history_entry(r.0, path.as_ptr(), &mut entry) },
1478 AsdfValueErr::Ok,
1479 "entry {index}"
1480 );
1481 assert_eq!(unsafe { CStr::from_ptr((*entry).description) }.to_str().unwrap(), expected);
1482 unsafe { asdf_history_entry_destroy(entry) };
1483 }
1484
1485 unsafe { libc::free(buf) };
1486 }
1487
1488 #[test]
1489 fn a_history_entry_round_trips_through_a_written_file() {
1490 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1491 let h = Handle(f);
1492
1493 let value = CString::new("2026-09-04T12:00:00").unwrap();
1494 let time = asdf_time_t { value: value.as_ptr().cast_mut(), ..asdf_time_t::zeroed() };
1495 let entry = asdf_history_entry_t {
1496 description: c"a described change".as_ptr(),
1497 time: &time,
1498 software: core::ptr::null_mut(),
1499 };
1500
1501 assert_eq!(
1502 unsafe { asdf_set_history_entry(h.0, c"note".as_ptr(), &entry) },
1503 AsdfValueErr::Ok
1504 );
1505
1506 let mut buf: *mut core::ffi::c_void = core::ptr::null_mut();
1507 let mut size = 0usize;
1508 unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) };
1509 let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
1510 let r = Handle(reopened);
1511
1512 let mut read_back: *mut asdf_history_entry_t = core::ptr::null_mut();
1513 assert_eq!(
1514 unsafe { asdf_get_history_entry(r.0, c"note".as_ptr(), &mut read_back) },
1515 AsdfValueErr::Ok
1516 );
1517 let view = unsafe { &*read_back };
1518 assert_eq!(
1519 unsafe { CStr::from_ptr(view.description) }.to_str().unwrap(),
1520 "a described change"
1521 );
1522 assert!(!view.time.is_null(), "the nested time must survive");
1523
1524 unsafe { asdf_history_entry_destroy(read_back) };
1525 unsafe { libc::free(buf) };
1526 }
1527
1528 #[test]
1529 fn a_time_value_round_trips() {
1530 let h = open("t: !time/time-1.4.0 '2026-09-04T12:34:56'\n");
1531 assert!(unsafe { asdf_is_time(h.0, c"t".as_ptr()) });
1532
1533 let mut time: *mut asdf_time_t = core::ptr::null_mut();
1534 assert_eq!(unsafe { asdf_get_time(h.0, c"t".as_ptr(), &mut time) }, AsdfValueErr::Ok);
1535 let view = unsafe { &*time };
1536 assert_eq!(unsafe { CStr::from_ptr(view.value) }.to_str().unwrap(), "2026-09-04T12:34:56");
1537 assert_eq!(view.info.tm.tm_hour, 12);
1538
1539 let copy = unsafe { asdf_time_copy(h.0, time) };
1541 assert!(!copy.is_null());
1542 unsafe { asdf_time_destroy(time) };
1543 assert_eq!(
1544 unsafe { CStr::from_ptr((*copy).value) }.to_str().unwrap(),
1545 "2026-09-04T12:34:56"
1546 );
1547 unsafe { asdf_time_destroy(copy) };
1548 }
1549
1550 #[test]
1551 fn deinit_is_safe_on_zeroed_objects() {
1552 let mut entry = asdf_history_entry_t::zeroed();
1553 unsafe { asdf_history_entry_deinit(&mut entry) };
1554 unsafe { asdf_history_entry_deinit(&mut entry) };
1555
1556 let mut time = asdf_time_t::zeroed();
1557 unsafe { asdf_time_deinit(&mut time) };
1558 unsafe { asdf_time_deinit(core::ptr::null_mut()) };
1559 }
1560}
1561
1562use crate::ffi::write_out;
1565use crate::ndarray_ffi::asdf_datatype_t;
1566
1567pub const DATATYPE_TAG: &str = "tag:stsci.edu:asdf/core/datatype-1.0.0";
1569
1570impl asdf_datatype_t {
1571 pub(crate) fn zeroed() -> Self {
1573 Self {
1574 type_: 0,
1575 size: 0,
1576 name: core::ptr::null(),
1577 byteorder: 0,
1578 ndim: 0,
1579 shape: core::ptr::null(),
1580 nfields: 0,
1581 fields: core::ptr::null(),
1582 }
1583 }
1584}
1585
1586fn datatype_deserialize(
1587 doc: &Document,
1588 node: NodeId,
1589 _file: *mut AsdfFile,
1590 out: *mut asdf_datatype_t,
1591) -> AsdfValueErr {
1592 use asdf_core::core::datatype::Datatype;
1593
1594 let Ok(parsed) = Datatype::parse(doc, node) else {
1595 return AsdfValueErr::ParseFailure;
1596 };
1597
1598 let default_order = asdf_core::core::datatype::ByteOrder::Little;
1603 let order_of = |order: asdf_core::core::datatype::ByteOrder| {
1604 if order == asdf_core::core::datatype::ByteOrder::Default { default_order } else { order }
1605 };
1606
1607 let mut fields: Vec<asdf_datatype_t> = Vec::with_capacity(parsed.fields.len());
1611 for field in &parsed.fields {
1612 let name = field.name.as_deref().map(to_c_string).unwrap_or(core::ptr::null());
1613 let shape: Vec<u64> = field.datatype.shape.clone();
1614 let (shape_ptr, ndim) = if shape.is_empty() {
1615 (core::ptr::null(), 0)
1616 } else {
1617 let boxed = shape.into_boxed_slice();
1618 let len = boxed.len() as u32;
1619 (Box::into_raw(boxed).cast::<u64>().cast_const(), len)
1620 };
1621 fields.push(asdf_datatype_t {
1622 type_: field.datatype.scalar as i32,
1623 size: field.datatype.item_size(),
1624 name,
1625 byteorder: order_of(field.datatype.byteorder) as i32,
1626 ndim,
1627 shape: shape_ptr,
1628 nfields: 0,
1629 fields: core::ptr::null(),
1630 });
1631 }
1632
1633 let (fields_ptr, nfields) = if fields.is_empty() {
1634 (core::ptr::null(), 0)
1635 } else {
1636 let len = fields.len() as u32;
1637 (Box::into_raw(fields.into_boxed_slice()).cast::<asdf_datatype_t>().cast_const(), len)
1638 };
1639
1640 unsafe {
1641 (*out).type_ = parsed.scalar as i32;
1642 (*out).size = parsed.item_size();
1643 (*out).name = core::ptr::null();
1644 (*out).byteorder = order_of(parsed.byteorder) as i32;
1645 (*out).ndim = 0;
1646 (*out).shape = core::ptr::null();
1647 (*out).nfields = nfields;
1648 (*out).fields = fields_ptr;
1649 }
1650 AsdfValueErr::Ok
1651}
1652
1653fn is_simple_scalar(obj: &asdf_datatype_t) -> bool {
1658 use asdf_core::core::datatype::ByteOrder;
1659 let scalar = crate::ndarray_ffi::scalar_from_abi_public(obj.type_);
1660 scalar != asdf_core::core::datatype::ScalarType::Structured
1661 && (obj.byteorder == 0 || obj.byteorder == ByteOrder::Little as i32)
1662 && obj.name.is_null()
1663 && obj.ndim == 0
1664 && obj.nfields == 0
1665}
1666
1667fn datatype_serialize_scalar(doc: &mut Document, obj: &asdf_datatype_t) -> Option<NodeId> {
1669 use asdf_core::core::datatype::ScalarType;
1670
1671 let scalar = crate::ndarray_ffi::scalar_from_abi_public(obj.type_);
1672 if scalar.is_string() {
1673 let characters = obj.size / scalar.bytes_per_char().max(1);
1675 let kind = doc.add_scalar(scalar.name());
1676 let length = doc.add_scalar(characters.to_string());
1677 let seq = doc.add_sequence(vec![kind, length]);
1678 if let asdf_core::yaml::NodeData::Sequence { style, .. } = &mut doc.node_mut(seq).data {
1679 *style = asdf_core::yaml::CollectionStyle::Flow;
1680 }
1681 return Some(seq);
1682 }
1683 (scalar != ScalarType::Unknown).then(|| doc.add_scalar(scalar.name()))
1684}
1685
1686fn datatype_serialize_field(doc: &mut Document, field: &asdf_datatype_t) -> Option<NodeId> {
1691 use asdf_core::core::datatype::ScalarType;
1692
1693 let scalar = crate::ndarray_ffi::scalar_from_abi_public(field.type_);
1694 let mut pairs = Vec::new();
1695
1696 if !field.name.is_null() {
1697 let text = unsafe { CStr::from_ptr(field.name) }.to_string_lossy().into_owned();
1698 let key = doc.add_scalar("name");
1699 let value = doc.add_scalar_styled(text, asdf_core::yaml::ScalarStyle::Plain);
1700 pairs.push((key, value));
1701 }
1702
1703 let inner = if scalar == ScalarType::Structured {
1704 datatype_serialize_impl(doc, field, false)?
1705 } else {
1706 datatype_serialize_scalar(doc, field)?
1707 };
1708 let key = doc.add_scalar("datatype");
1709 pairs.push((key, inner));
1710
1711 if field.byteorder != 0
1712 && let Some(order) = byteorder_name(field.byteorder)
1713 {
1714 let key = doc.add_scalar("byteorder");
1715 let value = doc.add_scalar(order);
1716 pairs.push((key, value));
1717 }
1718
1719 if field.ndim > 0 && !field.shape.is_null() {
1720 let dims = unsafe { core::slice::from_raw_parts(field.shape, field.ndim as usize) };
1721 let items: Vec<NodeId> = dims.iter().map(|d| doc.add_scalar(d.to_string())).collect();
1722 let seq = doc.add_sequence(items);
1723 if let asdf_core::yaml::NodeData::Sequence { style, .. } = &mut doc.node_mut(seq).data {
1724 *style = asdf_core::yaml::CollectionStyle::Flow;
1725 }
1726 let key = doc.add_scalar("shape");
1727 pairs.push((key, seq));
1728 }
1729
1730 let node = doc.add_mapping(pairs);
1731 if scalar != ScalarType::Structured
1734 && !scalar.is_string()
1735 && field.ndim == 0
1736 && let asdf_core::yaml::NodeData::Mapping { style, .. } = &mut doc.node_mut(node).data
1737 {
1738 *style = asdf_core::yaml::CollectionStyle::Flow;
1739 }
1740 Some(node)
1741}
1742
1743fn byteorder_name(byteorder: i32) -> Option<&'static str> {
1745 use asdf_core::core::datatype::ByteOrder;
1746 if byteorder == ByteOrder::Little as i32 {
1747 Some("little")
1748 } else if byteorder == ByteOrder::Big as i32 {
1749 Some("big")
1750 } else {
1751 None
1752 }
1753}
1754
1755fn datatype_serialize_impl(
1757 doc: &mut Document,
1758 obj: &asdf_datatype_t,
1759 is_field: bool,
1760) -> Option<NodeId> {
1761 use asdf_core::core::datatype::ScalarType;
1762
1763 let scalar = crate::ndarray_ffi::scalar_from_abi_public(obj.type_);
1764
1765 if is_simple_scalar(obj) {
1766 return datatype_serialize_scalar(doc, obj);
1767 }
1768 if !is_field && scalar != ScalarType::Structured && obj.ndim == 0 {
1769 return datatype_serialize_scalar(doc, obj);
1774 }
1775 if is_field {
1776 return datatype_serialize_field(doc, obj);
1777 }
1778 if scalar == ScalarType::Structured {
1779 let fields = if obj.nfields > 0 && !obj.fields.is_null() {
1780 unsafe { core::slice::from_raw_parts(obj.fields, obj.nfields as usize) }
1781 } else {
1782 &[]
1783 };
1784 let items: Vec<NodeId> = fields
1785 .iter()
1786 .map(|field| datatype_serialize_impl(doc, field, true))
1787 .collect::<Option<Vec<_>>>()?;
1788 return Some(doc.add_sequence(items));
1789 }
1790 None
1791}
1792
1793fn datatype_serialize(doc: &mut Document, obj: &asdf_datatype_t) -> Option<NodeId> {
1794 datatype_serialize_impl(doc, obj, false)
1795}
1796
1797unsafe fn datatype_deinit(obj: *mut asdf_datatype_t) {
1798 let datatype = unsafe { &mut *obj };
1799 unsafe { datatype_free_storage(datatype) };
1800 *datatype = asdf_datatype_t::zeroed();
1801}
1802
1803unsafe fn datatype_free_storage(datatype: &mut asdf_datatype_t) {
1812 if !datatype.fields.is_null() && datatype.nfields > 0 {
1813 let count = datatype.nfields as usize;
1814 let slice = core::ptr::slice_from_raw_parts_mut(datatype.fields.cast_mut(), count);
1815 for index in 0..count {
1816 let field = unsafe { &mut *datatype.fields.cast_mut().add(index) };
1817 unsafe { datatype_free_storage(field) };
1818 }
1819 drop(unsafe { Box::from_raw(slice) });
1820 datatype.fields = core::ptr::null();
1821 datatype.nfields = 0;
1822 }
1823 if !datatype.shape.is_null() && datatype.ndim > 0 {
1824 let shape =
1825 core::ptr::slice_from_raw_parts_mut(datatype.shape.cast_mut(), datatype.ndim as usize);
1826 drop(unsafe { Box::from_raw(shape) });
1827 datatype.shape = core::ptr::null();
1828 datatype.ndim = 0;
1829 }
1830 unsafe { free_c_string(datatype.name) };
1831 datatype.name = core::ptr::null();
1832}
1833
1834unsafe fn datatype_copy(src: &asdf_datatype_t, dst: *mut asdf_datatype_t) -> bool {
1835 let out = unsafe { &mut *dst };
1836 out.type_ = src.type_;
1837 out.size = src.size;
1838 out.byteorder = src.byteorder;
1839 out.name = unsafe { clone_c_string(src.name) };
1840
1841 if src.ndim > 0 && !src.shape.is_null() {
1844 let dims = unsafe { core::slice::from_raw_parts(src.shape, src.ndim as usize) };
1845 out.ndim = src.ndim;
1846 out.shape = Box::into_raw(dims.to_vec().into_boxed_slice()).cast::<u64>().cast_const();
1847 } else {
1848 out.ndim = 0;
1849 out.shape = core::ptr::null();
1850 }
1851
1852 if src.nfields == 0 || src.fields.is_null() {
1853 out.nfields = 0;
1854 out.fields = core::ptr::null();
1855 return true;
1856 }
1857
1858 let source = unsafe { core::slice::from_raw_parts(src.fields, src.nfields as usize) };
1859 let mut copies: Vec<asdf_datatype_t> = Vec::with_capacity(source.len());
1860 for field in source {
1861 let mut copy = asdf_datatype_t::zeroed();
1862 if !unsafe { datatype_copy(field, &mut copy) } {
1865 return false;
1866 }
1867 copies.push(copy);
1868 }
1869 out.nfields = src.nfields;
1870 out.fields = Box::into_raw(copies.into_boxed_slice()).cast::<asdf_datatype_t>().cast_const();
1871 true
1872}
1873
1874declare_extension! {
1875 name: datatype,
1876 ty: asdf_datatype_t,
1877 tag: DATATYPE_TAG,
1878 deserialize: datatype_deserialize,
1879 serialize: datatype_serialize,
1880 deinit: datatype_deinit,
1881 copy: datatype_copy,
1882 is_fn: asdf_is_datatype,
1883 value_is_fn: asdf_value_is_datatype,
1884 value_as_fn: asdf_value_as_datatype,
1885 value_of_fn: asdf_value_of_datatype,
1886 get_fn: asdf_get_datatype,
1887 set_fn: asdf_set_datatype,
1888 copy_fn: asdf_datatype_copy,
1889 copy_into_fn: asdf_datatype_copy_into,
1890 array_copy_fn: asdf_datatype_array_copy,
1891 deinit_fn: asdf_datatype_deinit,
1892 destroy_fn: asdf_datatype_destroy,
1893 tags: &[c"tag:stsci.edu:asdf/core/datatype-1.0.0"],
1895 ext_build_fn: build_datatype_extension,
1896 ext_deserialize_fn: datatype_ext_deserialize,
1897 ext_serialize_fn: datatype_ext_serialize,
1898 ext_copy_fn: datatype_ext_copy,
1899 ext_deinit_fn: datatype_ext_deinit,
1900}
1901
1902pub const META_TAG: &str = "tag:stsci.edu:asdf/core/asdf-1.1.0";
1906
1907#[repr(C)]
1909#[derive(Debug)]
1910pub struct asdf_meta_history_t {
1911 pub extensions: *mut *const asdf_extension_metadata_t,
1913 pub entries: *mut *const asdf_history_entry_t,
1915}
1916
1917#[repr(C)]
1919#[derive(Debug)]
1920pub struct asdf_meta_t {
1921 pub asdf_library: *mut asdf_software_t,
1923 pub history: asdf_meta_history_t,
1925}
1926
1927impl asdf_meta_t {
1928 fn zeroed() -> Self {
1929 Self {
1930 asdf_library: core::ptr::null_mut(),
1931 history: asdf_meta_history_t {
1932 extensions: core::ptr::null_mut(),
1933 entries: core::ptr::null_mut(),
1934 },
1935 }
1936 }
1937}
1938
1939fn read_list<T>(
1941 doc: &Document,
1942 node: Option<NodeId>,
1943 file: *mut AsdfFile,
1944 zeroed: fn() -> T,
1945 deserialize: fn(&Document, NodeId, *mut AsdfFile, *mut T) -> AsdfValueErr,
1946) -> *mut *const T {
1947 let Some(node) = node else {
1948 return core::ptr::null_mut();
1949 };
1950 let items: Vec<NodeId> = match doc.sequence_items(node) {
1951 Some(items) => items.to_vec(),
1952 None => vec![node],
1953 };
1954
1955 let mut list: Vec<*const T> = Vec::with_capacity(items.len() + 1);
1956 for item in items {
1957 let raw = Box::into_raw(Box::new(zeroed()));
1958 if deserialize(doc, item, file, raw) == AsdfValueErr::Ok {
1959 list.push(raw.cast_const());
1960 } else {
1961 drop(unsafe { Box::from_raw(raw) });
1962 }
1963 }
1964 if list.is_empty() {
1965 return core::ptr::null_mut();
1966 }
1967 list.push(core::ptr::null());
1968 Box::into_raw(list.into_boxed_slice()).cast::<*const T>()
1969}
1970
1971unsafe fn free_list<T>(list: *mut *const T, destroy: unsafe extern "C" fn(*mut T)) {
1976 if list.is_null() {
1977 return;
1978 }
1979 let mut count = 0isize;
1980 while !unsafe { *list.offset(count) }.is_null() {
1981 unsafe { destroy((*list.offset(count)).cast_mut()) };
1982 count += 1;
1983 }
1984 let slice = core::ptr::slice_from_raw_parts_mut(list, count as usize + 1);
1985 drop(unsafe { Box::from_raw(slice) });
1986}
1987
1988fn meta_deserialize(
1989 doc: &Document,
1990 node: NodeId,
1991 file: *mut AsdfFile,
1992 out: *mut asdf_meta_t,
1993) -> AsdfValueErr {
1994 let library = doc
1995 .mapping_get(node, "asdf_library")
1996 .map(|id| {
1997 let raw = Box::into_raw(Box::new(asdf_software_t::zeroed()));
1998 if software_deserialize(doc, id, file, raw) == AsdfValueErr::Ok {
1999 raw
2000 } else {
2001 drop(unsafe { Box::from_raw(raw) });
2002 core::ptr::null_mut()
2003 }
2004 })
2005 .unwrap_or(core::ptr::null_mut());
2006
2007 let history = doc.mapping_get(node, "history");
2010 let (extensions_node, entries_node) = match history {
2011 Some(history) if doc.resolved(history).is_mapping() => {
2012 (doc.mapping_get(history, "extensions"), doc.mapping_get(history, "entries"))
2013 }
2014 Some(history) => (None, Some(history)),
2015 None => (None, None),
2016 };
2017
2018 unsafe {
2019 (*out).asdf_library = library;
2020 (*out).history.extensions = read_list(
2021 doc,
2022 extensions_node,
2023 file,
2024 asdf_extension_metadata_t::zeroed,
2025 extension_metadata_deserialize,
2026 );
2027 (*out).history.entries = read_list(
2028 doc,
2029 entries_node,
2030 file,
2031 asdf_history_entry_t::zeroed,
2032 history_entry_deserialize,
2033 );
2034 }
2035 AsdfValueErr::Ok
2036}
2037
2038fn meta_serialize(doc: &mut Document, obj: &asdf_meta_t) -> Option<NodeId> {
2039 let mut pairs = Vec::new();
2040
2041 if !obj.asdf_library.is_null()
2042 && let Some(node) = software_serialize(doc, unsafe { &*obj.asdf_library })
2043 {
2044 doc.node_mut(node).tag = Some(Tag::parse(SOFTWARE_TAG));
2045 let key = doc.add_scalar("asdf_library");
2046 pairs.push((key, node));
2047 }
2048
2049 let mut history_pairs = Vec::new();
2050 if !obj.history.extensions.is_null() {
2051 let mut items = Vec::new();
2052 let mut index = 0isize;
2053 while !unsafe { *obj.history.extensions.offset(index) }.is_null() {
2054 let entry = unsafe { *obj.history.extensions.offset(index) };
2055 if let Some(node) = extension_metadata_serialize(doc, unsafe { &*entry }) {
2056 doc.node_mut(node).tag = Some(Tag::parse(EXTENSION_METADATA_TAG));
2057 items.push(node);
2058 }
2059 index += 1;
2060 }
2061 if !items.is_empty() {
2062 let list = doc.add_sequence(items);
2063 let key = doc.add_scalar("extensions");
2064 history_pairs.push((key, list));
2065 }
2066 }
2067 if !obj.history.entries.is_null() {
2068 let mut items = Vec::new();
2069 let mut index = 0isize;
2070 while !unsafe { *obj.history.entries.offset(index) }.is_null() {
2071 let entry = unsafe { *obj.history.entries.offset(index) };
2072 if let Some(node) = history_entry_serialize(doc, unsafe { &*entry }) {
2073 doc.node_mut(node).tag = Some(Tag::parse(HISTORY_ENTRY_TAG));
2074 items.push(node);
2075 }
2076 index += 1;
2077 }
2078 if !items.is_empty() {
2079 let list = doc.add_sequence(items);
2080 let key = doc.add_scalar("entries");
2081 history_pairs.push((key, list));
2082 }
2083 }
2084 if !history_pairs.is_empty() {
2085 let history = doc.add_mapping(history_pairs);
2086 let key = doc.add_scalar("history");
2087 pairs.push((key, history));
2088 }
2089
2090 Some(doc.add_mapping(pairs))
2091}
2092
2093unsafe fn meta_deinit(obj: *mut asdf_meta_t) {
2094 let meta = unsafe { &mut *obj };
2095 if !meta.asdf_library.is_null() {
2096 unsafe { asdf_software_destroy(meta.asdf_library) };
2097 }
2098 unsafe { free_list(meta.history.extensions, asdf_extension_metadata_destroy) };
2099 unsafe { free_list(meta.history.entries, asdf_history_entry_destroy) };
2100 *meta = asdf_meta_t::zeroed();
2101}
2102
2103unsafe fn meta_copy(src: &asdf_meta_t, dst: *mut asdf_meta_t) -> bool {
2104 let out = unsafe { &mut *dst };
2105 out.asdf_library = if src.asdf_library.is_null() {
2106 core::ptr::null_mut()
2107 } else {
2108 unsafe { asdf_software_copy(core::ptr::null_mut(), src.asdf_library) }
2109 };
2110 out.history.extensions = if src.history.extensions.is_null() {
2111 core::ptr::null_mut()
2112 } else {
2113 unsafe { asdf_extension_metadata_array_copy(core::ptr::null_mut(), src.history.extensions) }
2114 .cast::<*const asdf_extension_metadata_t>()
2115 };
2116 out.history.entries = if src.history.entries.is_null() {
2117 core::ptr::null_mut()
2118 } else {
2119 unsafe { asdf_history_entry_array_copy(core::ptr::null_mut(), src.history.entries) }
2120 .cast::<*const asdf_history_entry_t>()
2121 };
2122 true
2123}
2124
2125declare_extension! {
2126 name: meta,
2127 ty: asdf_meta_t,
2128 tag: META_TAG,
2129 deserialize: meta_deserialize,
2130 serialize: meta_serialize,
2131 deinit: meta_deinit,
2132 copy: meta_copy,
2133 is_fn: asdf_is_meta,
2134 value_is_fn: asdf_value_is_meta,
2135 value_as_fn: asdf_value_as_meta,
2136 value_of_fn: asdf_value_of_meta,
2137 get_fn: asdf_get_meta,
2138 set_fn: asdf_set_meta,
2139 copy_fn: asdf_meta_copy,
2140 copy_into_fn: asdf_meta_copy_into,
2141 array_copy_fn: asdf_meta_array_copy,
2142 deinit_fn: asdf_meta_deinit,
2143 destroy_fn: asdf_meta_destroy,
2144 tags: &[
2146 c"tag:stsci.edu:asdf/core/asdf-1.1.0",
2147 c"tag:stsci.edu:asdf/core/asdf-1.0.0",
2148 ],
2149 ext_build_fn: build_meta_extension,
2150 ext_deserialize_fn: meta_ext_deserialize,
2151 ext_serialize_fn: meta_ext_serialize,
2152 ext_copy_fn: meta_ext_copy,
2153 ext_deinit_fn: meta_ext_deinit,
2154}
2155
2156#[cfg(test)]
2157mod meta_tests {
2158 use super::*;
2159 use crate::file_ffi::{asdf_close, asdf_open_mem_ex};
2160 use crate::ndarray_ffi::asdf_datatype_t;
2161
2162 struct Handle(*mut AsdfFile);
2163 impl Drop for Handle {
2164 fn drop(&mut self) {
2165 unsafe { asdf_close(self.0) };
2166 }
2167 }
2168
2169 fn open_full() -> Handle {
2171 let mut buf = Vec::new();
2172 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
2173 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
2174 buf.extend_from_slice(
2175 b"asdf_library: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n\
2176 history:\n extensions:\n - !core/extension_metadata-1.0.0\n \
2177 extension_class: asdf.extension._manifest.ManifestExtension\n \
2178 software: !core/software-1.0.0 {name: asdf_standard, version: 1.1.1}\n \
2179 entries:\n - !core/history_entry-1.0.0 {description: 'made it'}\n\
2180 dt: !core/datatype-1.0.0 float64\n\
2181 compound: !core/datatype-1.0.0\n - name: x\n datatype: float64\n \
2182 - name: y\n datatype: int32\n",
2183 );
2184 buf.extend_from_slice(b"...\n");
2185 let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
2186 assert!(!f.is_null());
2187 Handle(f)
2188 }
2189
2190 #[test]
2191 fn reads_the_tree_metadata() {
2192 let h = open_full();
2193 assert!(unsafe { asdf_is_meta(h.0, c"".as_ptr()) });
2195
2196 let mut meta: *mut asdf_meta_t = core::ptr::null_mut();
2197 assert_eq!(unsafe { asdf_get_meta(h.0, c"".as_ptr(), &mut meta) }, AsdfValueErr::Ok);
2198 let view = unsafe { &*meta };
2199
2200 assert!(!view.asdf_library.is_null());
2201 assert_eq!(unsafe { CStr::from_ptr((*view.asdf_library).name) }.to_str().unwrap(), "asdf");
2202
2203 assert!(!view.history.extensions.is_null());
2205 let first = unsafe { *view.history.extensions };
2206 assert_eq!(
2207 unsafe { CStr::from_ptr((*first).extension_class) }.to_str().unwrap(),
2208 "asdf.extension._manifest.ManifestExtension"
2209 );
2210 assert!(unsafe { *view.history.extensions.offset(1) }.is_null());
2211
2212 assert!(!view.history.entries.is_null());
2213 let entry = unsafe { *view.history.entries };
2214 assert_eq!(unsafe { CStr::from_ptr((*entry).description) }.to_str().unwrap(), "made it");
2215
2216 unsafe { asdf_meta_destroy(meta) };
2217 }
2218
2219 #[test]
2222 fn the_legacy_history_form_is_accepted() {
2223 let mut buf = Vec::new();
2224 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
2225 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
2226 buf.extend_from_slice(
2227 b"history:\n- !core/history_entry-1.0.0 {description: 'old style'}\n",
2228 );
2229 buf.extend_from_slice(b"...\n");
2230 let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
2231 let h = Handle(f);
2232
2233 let mut meta: *mut asdf_meta_t = core::ptr::null_mut();
2234 assert_eq!(unsafe { asdf_get_meta(h.0, c"".as_ptr(), &mut meta) }, AsdfValueErr::Ok);
2235 let view = unsafe { &*meta };
2236 assert!(view.history.extensions.is_null(), "no extensions in the old form");
2237 assert!(!view.history.entries.is_null());
2238 assert_eq!(
2239 unsafe { CStr::from_ptr((**view.history.entries).description) }.to_str().unwrap(),
2240 "old style"
2241 );
2242 unsafe { asdf_meta_destroy(meta) };
2243 }
2244
2245 #[test]
2246 fn metadata_copies_are_independent() {
2247 let h = open_full();
2248 let mut meta: *mut asdf_meta_t = core::ptr::null_mut();
2249 unsafe { asdf_get_meta(h.0, c"".as_ptr(), &mut meta) };
2250
2251 let copy = unsafe { asdf_meta_copy(h.0, meta) };
2252 assert!(!copy.is_null());
2253 unsafe {
2254 assert_ne!((*copy).asdf_library, (*meta).asdf_library);
2255 assert_ne!((*copy).history.entries, (*meta).history.entries);
2256 }
2257
2258 unsafe { asdf_meta_destroy(meta) };
2260 assert_eq!(
2261 unsafe { CStr::from_ptr((*(*copy).asdf_library).name) }.to_str().unwrap(),
2262 "asdf"
2263 );
2264 unsafe { asdf_meta_destroy(copy) };
2265 }
2266
2267 #[test]
2268 fn reads_a_scalar_datatype() {
2269 let h = open_full();
2270 assert!(unsafe { asdf_is_datatype(h.0, c"dt".as_ptr()) });
2271
2272 let mut datatype: *mut asdf_datatype_t = core::ptr::null_mut();
2273 assert_eq!(
2274 unsafe { asdf_get_datatype(h.0, c"dt".as_ptr(), &mut datatype) },
2275 AsdfValueErr::Ok
2276 );
2277 let view = unsafe { &*datatype };
2278 assert_eq!(view.type_, 11);
2280 assert_eq!(view.size, 8);
2281 assert_eq!(view.nfields, 0);
2282 unsafe { asdf_datatype_destroy(datatype) };
2283 }
2284
2285 #[test]
2286 fn reads_a_compound_datatype_with_named_fields() {
2287 let h = open_full();
2288 let mut datatype: *mut asdf_datatype_t = core::ptr::null_mut();
2289 assert_eq!(
2290 unsafe { asdf_get_datatype(h.0, c"compound".as_ptr(), &mut datatype) },
2291 AsdfValueErr::Ok
2292 );
2293 let view = unsafe { &*datatype };
2294 assert_eq!(view.nfields, 2);
2295 assert!(!view.fields.is_null());
2296 assert_eq!(view.size, 12);
2298
2299 let fields = unsafe { core::slice::from_raw_parts(view.fields, 2) };
2300 assert_eq!(unsafe { CStr::from_ptr(fields[0].name) }.to_str().unwrap(), "x");
2301 assert_eq!(fields[0].size, 8);
2302 assert_eq!(unsafe { CStr::from_ptr(fields[1].name) }.to_str().unwrap(), "y");
2303 assert_eq!(fields[1].size, 4);
2304
2305 let copy = unsafe { asdf_datatype_copy(h.0, datatype) };
2307 assert!(!copy.is_null());
2308 unsafe { assert_ne!((*copy).fields, view.fields) };
2309 unsafe { asdf_datatype_destroy(datatype) };
2310 assert_eq!(unsafe { (*copy).nfields }, 2);
2311 unsafe { asdf_datatype_destroy(copy) };
2312 }
2313
2314 #[test]
2315 fn deinit_is_safe_on_zeroed_objects() {
2316 let mut meta = asdf_meta_t::zeroed();
2317 unsafe { asdf_meta_deinit(&mut meta) };
2318 unsafe { asdf_meta_deinit(&mut meta) };
2319
2320 let mut datatype = asdf_datatype_t::zeroed();
2321 unsafe { asdf_datatype_deinit(&mut datatype) };
2322 unsafe { asdf_datatype_deinit(core::ptr::null_mut()) };
2323 }
2324}
2325
2326pub fn register_core_extensions() {
2339 use core::sync::atomic::{AtomicBool, Ordering};
2340
2341 static REGISTERED: AtomicBool = AtomicBool::new(false);
2342 if REGISTERED.swap(true, Ordering::SeqCst) {
2343 return;
2344 }
2345
2346 let extensions = [
2347 build_meta_extension(),
2348 build_software_extension(),
2349 build_extension_metadata_extension(),
2350 build_history_entry_extension(),
2351 build_datatype_extension(),
2352 build_time_extension(),
2353 crate::ndarray_ffi::build_ndarray_extension(),
2354 ];
2355 for extension in extensions {
2356 unsafe { crate::extension_ffi::asdf_extension_register(extension) };
2359 }
2360}
2361
2362#[unsafe(no_mangle)]
2367pub unsafe extern "C" fn asdf_shim_register_core_extensions() {
2368 guard("asdf_shim_register_core_extensions", (), register_core_extensions);
2369}