Skip to main content

spice/core/
ffi.rs

1/*!
2Marshalling between Rust types and the C types CSPICE expects.
3
4## Description
5
6CSPICE routines take and return C types: null terminated strings, raw pointers to scalars,
7pointers to (arrays of) doubles, `SpiceBoolean` integers... Rather than teaching the
8[procedural macro][`spice_derive::cspice_proc`] about every one of those conversions, the knowledge
9lives here, in ordinary generic code that the compiler type checks:
10
11+ [`SpiceArg`] describes how a Rust value is handed to a C routine, and [`In`] keeps alive whatever
12  scratch storage that requires (a [`CString`], typically) for the duration of the call.
13+ [`SpiceRet`] describes how an output is allocated, written to by C, then read back, and [`Out`]
14  owns the buffer while the call is in flight.
15+ [`SpiceReturn`] converts a value a C routine returns directly.
16
17Every buffer handed to CSPICE is allocated and zeroed by Rust, and freed when the wrapper returns.
18Nothing here leaks, and no uninitialised memory is ever read back, even when a routine fails
19without writing its outputs.
20*/
21
22use crate::c::{
23    SpiceBoolean, SpiceCell, SpiceChar, SpiceDLADescr, SpiceDSKDescr, SpiceDouble, SpiceEKAttDsc,
24    SpiceEKSegSum, SpiceEllipse, SpiceInt, SpicePlane,
25};
26use crate::MAX_LEN_OUT;
27use std::ffi::{CStr, CString};
28
29/* -------------------------------------------------------------------------------------------- */
30/* Callbacks                                                                                      */
31/* -------------------------------------------------------------------------------------------- */
32
33/*
34The geometry finder calls back into the caller's code. A Rust function can be handed to C as a
35pointer only if it is `extern "C"` and captures nothing, so these are plain function pointer types
36rather than closures: a closure that captured anything could not be represented, and pretending
37otherwise would need a hidden global to smuggle the captures through.
38*/
39
40/// A scalar function of time, writing its value through the pointer.
41pub type UdFunc = unsafe extern "C" fn(x: SpiceDouble, value: *mut SpiceDouble);
42
43/// A scalar quantity the geometry finder searches over; the same shape as [`UdFunc`].
44pub type UdFuns = UdFunc;
45
46/// Whether the quantity computed by a [`UdFuns`] is decreasing at an epoch.
47pub type UdFunb =
48    unsafe extern "C" fn(udfuns: Option<UdFuns>, x: SpiceDouble, xbool: *mut SpiceBoolean);
49
50/// The step to take from an epoch while searching.
51pub type UdStep = unsafe extern "C" fn(et: SpiceDouble, step: *mut SpiceDouble);
52
53/// Refine a bracketing interval towards a root.
54pub type UdRefn = unsafe extern "C" fn(
55    t1: SpiceDouble,
56    t2: SpiceDouble,
57    s1: SpiceBoolean,
58    s2: SpiceBoolean,
59    t: *mut SpiceDouble,
60);
61
62/// Begin a progress report over a confinement window.
63pub type UdRepi =
64    unsafe extern "C" fn(cnfine: *mut SpiceCell, srcpre: *mut SpiceChar, srcsuf: *mut SpiceChar);
65
66/// Update a progress report.
67pub type UdRepu = unsafe extern "C" fn(ivbeg: SpiceDouble, ivend: SpiceDouble, et: SpiceDouble);
68
69/// Finish a progress report.
70pub type UdRepf = unsafe extern "C" fn();
71
72/// Whether an interrupt has been requested, which stops a search.
73pub type UdBail = unsafe extern "C" fn() -> SpiceBoolean;
74
75/* -------------------------------------------------------------------------------------------- */
76/* Buffers                                                                                        */
77/* -------------------------------------------------------------------------------------------- */
78
79/// Inline capacity of the buffers carrying an input string; body names, frames and aberration
80/// corrections all fit, so a hot loop does not hit the allocator once per argument.
81const INLINE_IN: usize = 64;
82
83/**
84A null terminated buffer of `N` bytes, on the stack while what it holds fits.
85
86Both directions go through it: an argument is copied in and handed to CSPICE as a pointer, and an
87output is zeroed, written by CSPICE, then read back.
88*/
89pub struct Buffer<const N: usize> {
90    inline: [SpiceChar; N],
91    /// Used only when the content does not fit inline.
92    heap: Option<Vec<SpiceChar>>,
93}
94
95impl<const N: usize> Buffer<N> {
96    /**
97    A null terminated copy of `value`.
98
99    # Panics
100
101    Panics if `value` contains an interior null byte: C has no way to represent it, so passing one
102    along would silently truncate the argument.
103    */
104    pub fn from_text(value: &str) -> Self {
105        let bytes = value.as_bytes();
106        if bytes.len() < N && !bytes.contains(&0) {
107            let mut inline = [0; N];
108            for (target, byte) in inline.iter_mut().zip(bytes) {
109                *target = *byte as SpiceChar;
110            }
111            return Self { inline, heap: None };
112        }
113
114        let owned = to_cstring(value);
115        let heap = owned
116            .as_bytes_with_nul()
117            .iter()
118            .map(|&byte| byte as SpiceChar)
119            .collect();
120        Self {
121            inline: [0; N],
122            heap: Some(heap),
123        }
124    }
125
126    /// A zeroed buffer of `len` bytes, for CSPICE to write a string into.
127    pub fn with_len(len: usize) -> Self {
128        let len = len.max(1);
129        if len <= N {
130            return Self {
131                inline: [0; N],
132                heap: None,
133            };
134        }
135        Self {
136            inline: [0; N],
137            heap: Some(vec![0; len]),
138        }
139    }
140
141    /// The pointer to hand to CSPICE.
142    #[inline]
143    pub fn as_mut_ptr(&mut self) -> *mut SpiceChar {
144        match &mut self.heap {
145            Some(heap) => heap.as_mut_ptr(),
146            None => self.inline.as_mut_ptr(),
147        }
148    }
149
150    /// Read the buffer back as a Rust string.
151    pub fn into_string(self) -> String {
152        match &self.heap {
153            Some(heap) => from_cbuf(heap),
154            None => from_cbuf(&self.inline),
155        }
156    }
157}
158
159/* -------------------------------------------------------------------------------------------- */
160/* Inputs                                                                                         */
161/* -------------------------------------------------------------------------------------------- */
162
163/**
164A Rust value that can be handed to a CSPICE routine as an input.
165*/
166pub trait SpiceArg {
167    /// Scratch storage that has to outlive the call, `Self` when nothing has to be allocated.
168    type Owned;
169
170    /// The value actually passed to the C routine.
171    type Raw;
172
173    /// Move the value into its scratch storage.
174    fn own(self) -> Self::Owned;
175
176    /// Borrow the scratch storage as the C representation.
177    fn raw(owned: &mut Self::Owned) -> Self::Raw;
178}
179
180/**
181Owns an input argument for the duration of a CSPICE call.
182
183Dropping it releases whatever the conversion had to allocate, so a wrapper leaks nothing even when
184it is called in a tight loop.
185*/
186pub struct In<T: SpiceArg> {
187    owned: T::Owned,
188}
189
190impl<T: SpiceArg> In<T> {
191    /// Marshal `value` into its C representation.
192    #[inline]
193    pub fn new(value: T) -> Self {
194        Self { owned: value.own() }
195    }
196
197    /// The pointer, or value, to hand to the C routine.
198    #[inline]
199    pub fn raw(&mut self) -> T::Raw {
200        T::raw(&mut self.owned)
201    }
202}
203
204/// Scalars are passed by value, widened or narrowed to the type CSPICE declares.
205macro_rules! scalar_arg {
206    ($($ty:ty => $raw:ty),* $(,)?) => {$(
207        impl SpiceArg for $ty {
208            type Owned = $ty;
209            type Raw = $raw;
210
211            #[inline]
212            fn own(self) -> Self::Owned {
213                self
214            }
215
216            #[inline]
217            fn raw(owned: &mut Self::Owned) -> Self::Raw {
218                *owned as $raw
219            }
220        }
221    )*};
222}
223
224scalar_arg! {
225    f32 => SpiceDouble,
226    f64 => SpiceDouble,
227    i8 => SpiceInt,
228    i16 => SpiceInt,
229    i32 => SpiceInt,
230    i64 => SpiceInt,
231    isize => SpiceInt,
232    u8 => SpiceInt,
233    u16 => SpiceInt,
234    u32 => SpiceInt,
235    u64 => SpiceInt,
236    usize => SpiceInt,
237    bool => SpiceBoolean,
238}
239
240/// Fixed size arrays and matrices are passed as a pointer to their first element.
241macro_rules! array_arg {
242    ($($ty:ty => $raw:ty),* $(,)?) => {$(
243        impl<const N: usize> SpiceArg for [$ty; N] {
244            type Owned = [$ty; N];
245            type Raw = *mut $raw;
246
247            #[inline]
248            fn own(self) -> Self::Owned {
249                self
250            }
251
252            #[inline]
253            fn raw(owned: &mut Self::Owned) -> Self::Raw {
254                owned.as_mut_ptr()
255            }
256        }
257
258        impl<const M: usize, const N: usize> SpiceArg for [[$ty; N]; M] {
259            type Owned = [[$ty; N]; M];
260            type Raw = *mut $raw;
261
262            #[inline]
263            fn own(self) -> Self::Owned {
264                self
265            }
266
267            #[inline]
268            fn raw(owned: &mut Self::Owned) -> Self::Raw {
269                owned.as_mut_ptr().cast()
270            }
271        }
272    )*};
273}
274
275array_arg! {
276    f64 => SpiceDouble,
277    i32 => SpiceInt,
278}
279
280/// Slices are passed as a pointer to their first element; CSPICE takes the count separately.
281impl<'a, T> SpiceArg for &'a [T] {
282    type Owned = &'a [T];
283    type Raw = *const T;
284
285    #[inline]
286    fn own(self) -> Self::Owned {
287        self
288    }
289
290    #[inline]
291    fn raw(owned: &mut Self::Owned) -> Self::Raw {
292        owned.as_ptr()
293    }
294}
295
296impl<'a, T> SpiceArg for &'a mut [T] {
297    type Owned = &'a mut [T];
298    type Raw = *mut T;
299
300    #[inline]
301    fn own(self) -> Self::Owned {
302        self
303    }
304
305    #[inline]
306    fn raw(owned: &mut Self::Owned) -> Self::Raw {
307        owned.as_mut_ptr()
308    }
309}
310
311/// A single character, for the few routines that take one rather than a string.
312///
313/// Only the low byte is passed, which is all CSPICE can represent.
314impl SpiceArg for char {
315    type Owned = char;
316    type Raw = SpiceChar;
317
318    #[inline]
319    fn own(self) -> Self::Owned {
320        self
321    }
322
323    #[inline]
324    fn raw(owned: &mut Self::Owned) -> Self::Raw {
325        *owned as u32 as SpiceChar
326    }
327}
328
329impl SpiceArg for &str {
330    type Owned = Buffer<INLINE_IN>;
331    type Raw = *mut SpiceChar;
332
333    #[inline]
334    fn own(self) -> Self::Owned {
335        Buffer::from_text(self)
336    }
337
338    #[inline]
339    fn raw(owned: &mut Self::Owned) -> Self::Raw {
340        owned.as_mut_ptr()
341    }
342}
343
344impl SpiceArg for &String {
345    type Owned = Buffer<INLINE_IN>;
346    type Raw = *mut SpiceChar;
347
348    #[inline]
349    fn own(self) -> Self::Owned {
350        Buffer::from_text(self)
351    }
352
353    #[inline]
354    fn raw(owned: &mut Self::Owned) -> Self::Raw {
355        owned.as_mut_ptr()
356    }
357}
358
359impl SpiceArg for String {
360    type Owned = Buffer<INLINE_IN>;
361    type Raw = *mut SpiceChar;
362
363    #[inline]
364    fn own(self) -> Self::Owned {
365        Buffer::from_text(&self)
366    }
367
368    #[inline]
369    fn raw(owned: &mut Self::Owned) -> Self::Raw {
370        owned.as_mut_ptr()
371    }
372}
373
374/// The descriptors, planes and ellipses are plain C structs CSPICE reads through a pointer.
375macro_rules! struct_arg {
376    ($($ty:ty),* $(,)?) => {$(
377        impl SpiceArg for $ty {
378            type Owned = $ty;
379            type Raw = *mut $ty;
380
381            #[inline]
382            fn own(self) -> Self::Owned {
383                self
384            }
385
386            #[inline]
387            fn raw(owned: &mut Self::Owned) -> Self::Raw {
388                owned as *mut $ty
389            }
390        }
391    )*};
392}
393
394struct_arg!(SpiceDLADescr, SpiceDSKDescr, SpicePlane, SpiceEllipse);
395
396/* -------------------------------------------------------------------------------------------- */
397/* Outputs                                                                                        */
398/* -------------------------------------------------------------------------------------------- */
399
400/**
401A Rust value a CSPICE routine can write through an output pointer.
402*/
403pub trait SpiceRet: Sized {
404    /// Buffer CSPICE writes into.
405    type Buf;
406
407    /// The pointer handed to the C routine.
408    type Raw;
409
410    /// A zeroed buffer of the default size.
411    fn buf() -> Self::Buf;
412
413    /// A zeroed buffer sized by the caller; only string outputs care.
414    fn buf_with_len(len: usize) -> Self::Buf {
415        let _ = len;
416        Self::buf()
417    }
418
419    /// Borrow the buffer as the pointer to pass to C.
420    fn raw(buf: &mut Self::Buf) -> Self::Raw;
421
422    /// Read the value back once the call returned.
423    fn get(buf: Self::Buf) -> Self;
424}
425
426/**
427Owns an output buffer for the duration of a CSPICE call.
428*/
429pub struct Out<T: SpiceRet> {
430    buf: T::Buf,
431}
432
433impl<T: SpiceRet> Out<T> {
434    /// A zeroed output of the default size.
435    #[inline]
436    pub fn new() -> Self {
437        Self { buf: T::buf() }
438    }
439
440    /// A zeroed output of `len` bytes, for the string outputs whose size the caller chooses.
441    #[inline]
442    pub fn with_len(len: usize) -> Self {
443        Self {
444            buf: T::buf_with_len(len),
445        }
446    }
447
448    /// The pointer to hand to the C routine.
449    #[inline]
450    pub fn raw(&mut self) -> T::Raw {
451        T::raw(&mut self.buf)
452    }
453
454    /// Read the output back.
455    #[inline]
456    pub fn get(self) -> T {
457        T::get(self.buf)
458    }
459}
460
461impl<T: SpiceRet> Default for Out<T> {
462    fn default() -> Self {
463        Self::new()
464    }
465}
466
467/// Scalar outputs: a single zeroed cell CSPICE writes through.
468macro_rules! scalar_ret {
469    ($($ty:ty => $raw:ty, $zero:expr, $read:expr);* $(;)?) => {$(
470        impl SpiceRet for $ty {
471            type Buf = $raw;
472            type Raw = *mut $raw;
473
474            #[inline]
475            fn buf() -> Self::Buf {
476                $zero
477            }
478
479            #[inline]
480            fn raw(buf: &mut Self::Buf) -> Self::Raw {
481                buf as *mut $raw
482            }
483
484            #[inline]
485            fn get(buf: Self::Buf) -> Self {
486                #[allow(clippy::redundant_closure_call)]
487                ($read)(buf)
488            }
489        }
490    )*};
491}
492
493scalar_ret! {
494    f64 => SpiceDouble, 0.0, |value| value;
495    i32 => SpiceInt, 0, |value| value;
496    bool => SpiceBoolean, 0, |value: SpiceBoolean| value != 0;
497}
498
499/// Array outputs: zeroed, so a routine that fails without writing still yields a readable value.
500macro_rules! array_ret {
501    ($($ty:ty => $raw:ty),* $(,)?) => {$(
502        impl<const N: usize> SpiceRet for [$ty; N] {
503            type Buf = [$ty; N];
504            type Raw = *mut $raw;
505
506            #[inline]
507            fn buf() -> Self::Buf {
508                [<$ty>::default(); N]
509            }
510
511            #[inline]
512            fn raw(buf: &mut Self::Buf) -> Self::Raw {
513                buf.as_mut_ptr()
514            }
515
516            #[inline]
517            fn get(buf: Self::Buf) -> Self {
518                buf
519            }
520        }
521
522        impl<const M: usize, const N: usize> SpiceRet for [[$ty; N]; M] {
523            type Buf = [[$ty; N]; M];
524            type Raw = *mut $raw;
525
526            #[inline]
527            fn buf() -> Self::Buf {
528                [[<$ty>::default(); N]; M]
529            }
530
531            #[inline]
532            fn raw(buf: &mut Self::Buf) -> Self::Raw {
533                buf.as_mut_ptr().cast()
534            }
535
536            #[inline]
537            fn get(buf: Self::Buf) -> Self {
538                buf
539            }
540        }
541    )*};
542}
543
544array_ret! {
545    f64 => SpiceDouble,
546    i32 => SpiceInt,
547}
548
549impl SpiceRet for String {
550    type Buf = Buffer<MAX_LEN_OUT>;
551    type Raw = *mut SpiceChar;
552
553    #[inline]
554    fn buf() -> Self::Buf {
555        Buffer::with_len(MAX_LEN_OUT)
556    }
557
558    #[inline]
559    fn buf_with_len(len: usize) -> Self::Buf {
560        Buffer::with_len(len)
561    }
562
563    #[inline]
564    fn raw(buf: &mut Self::Buf) -> Self::Raw {
565        buf.as_mut_ptr()
566    }
567
568    #[inline]
569    fn get(buf: Self::Buf) -> Self {
570        buf.into_string()
571    }
572}
573
574/// These are all plain old data, so a zeroed struct is a valid, readable, starting point.
575macro_rules! struct_ret {
576    ($($ty:ty),* $(,)?) => {$(
577        impl SpiceRet for $ty {
578            type Buf = $ty;
579            type Raw = *mut $ty;
580
581            #[inline]
582            fn buf() -> Self::Buf {
583                // SAFETY: every field is an integer or a float, for which all-zero is valid.
584                unsafe { std::mem::zeroed() }
585            }
586
587            #[inline]
588            fn raw(buf: &mut Self::Buf) -> Self::Raw {
589                buf as *mut $ty
590            }
591
592            #[inline]
593            fn get(buf: Self::Buf) -> Self {
594                buf
595            }
596        }
597    )*};
598}
599
600struct_ret!(
601    SpiceDLADescr,
602    SpiceDSKDescr,
603    SpicePlane,
604    SpiceEllipse,
605    SpiceEKAttDsc,
606    SpiceEKSegSum,
607);
608
609/* -------------------------------------------------------------------------------------------- */
610/* Direct returns                                                                                 */
611/* -------------------------------------------------------------------------------------------- */
612
613/**
614A Rust value a CSPICE routine returns directly, rather than through an output pointer.
615*/
616pub trait SpiceReturn {
617    /// What the C routine returns.
618    type Raw;
619
620    /// Convert it to the Rust type.
621    ///
622    /// # Safety
623    ///
624    /// `raw` must be what the C routine actually returned; a pointer return has to point at a
625    /// null terminated string that outlives the call.
626    unsafe fn from_c(raw: Self::Raw) -> Self;
627}
628
629impl SpiceReturn for f64 {
630    type Raw = SpiceDouble;
631
632    #[inline]
633    unsafe fn from_c(raw: Self::Raw) -> Self {
634        raw
635    }
636}
637
638impl SpiceReturn for i32 {
639    type Raw = SpiceInt;
640
641    #[inline]
642    unsafe fn from_c(raw: Self::Raw) -> Self {
643        raw
644    }
645}
646
647impl SpiceReturn for bool {
648    type Raw = SpiceBoolean;
649
650    #[inline]
651    unsafe fn from_c(raw: Self::Raw) -> Self {
652        raw != 0
653    }
654}
655
656impl SpiceReturn for String {
657    type Raw = *mut SpiceChar;
658
659    #[inline]
660    unsafe fn from_c(raw: Self::Raw) -> Self {
661        if raw.is_null() {
662            return String::new();
663        }
664        // SAFETY: the caller guarantees `raw` points at a null terminated string; CSPICE returns
665        // one of its own statics here.
666        unsafe { CStr::from_ptr(raw) }
667            .to_string_lossy()
668            .into_owned()
669    }
670}
671
672/* -------------------------------------------------------------------------------------------- */
673/* Helpers                                                                                        */
674/* -------------------------------------------------------------------------------------------- */
675
676/**
677Build the null terminated string CSPICE expects.
678
679# Panics
680
681Panics if `string` contains an interior null byte: C has no way to represent it, so passing one
682along would silently truncate the argument.
683*/
684pub fn to_cstring<S: AsRef<str>>(string: S) -> CString {
685    let string = string.as_ref();
686    CString::new(string).unwrap_or_else(|_| {
687        panic!("a string passed to CSPICE must not contain a null byte, got {string:?}")
688    })
689}
690
691/**
692Read back a string CSPICE wrote into a buffer.
693
694Stops at the first null byte, then trims the blank padding CSPICE inherits from Fortran. Invalid
695UTF-8 is replaced rather than rejected, so this never panics on whatever the toolkit produced.
696*/
697pub fn from_cbuf(buf: &[SpiceChar]) -> String {
698    let bytes = buf.iter().map(|&byte| byte as u8).collect::<Vec<u8>>();
699    let end = bytes
700        .iter()
701        .position(|&byte| byte == 0)
702        .unwrap_or(bytes.len());
703    String::from_utf8_lossy(&bytes[..end])
704        .trim_end()
705        .to_string()
706}
707
708/**
709Pack strings into the one contiguous, fixed stride, array CSPICE reads them out of.
710
711Returns the buffer and the stride, which is the length of the longest string plus its terminator.
712*/
713pub fn to_strided<S: AsRef<str>>(values: &[S]) -> (Vec<SpiceChar>, usize) {
714    let stride = values
715        .iter()
716        .map(|value| value.as_ref().len() + 1)
717        .max()
718        .unwrap_or(1);
719
720    let mut buffer = vec![0 as SpiceChar; values.len().max(1) * stride];
721    for (index, value) in values.iter().enumerate() {
722        let slot = &mut buffer[index * stride..(index + 1) * stride];
723        for (target, byte) in slot.iter_mut().zip(value.as_ref().as_bytes()) {
724            *target = *byte as SpiceChar;
725        }
726    }
727    (buffer, stride)
728}
729
730/**
731Read `count` strings back out of a buffer of `stride` byte slots.
732*/
733pub fn from_strided(buffer: &[SpiceChar], stride: usize, count: usize) -> Vec<String> {
734    (0..count)
735        .map(|index| from_cbuf(&buffer[index * stride..(index + 1) * stride]))
736        .collect()
737}
738
739/// The size, in elements, of the control area CSPICE keeps at the front of a cell.
740pub(crate) const CELL_CTRLSZ: usize = crate::c::SPICE_CELL_CTRLSZ as usize;
741
742/// A pointer to a cell, for the wrappers that take one as an input.
743impl<'a, T: crate::core::cell::CellItem> SpiceArg for &'a mut crate::core::cell::Cell<T> {
744    type Owned = &'a mut crate::core::cell::Cell<T>;
745    type Raw = *mut SpiceCell;
746
747    #[inline]
748    fn own(self) -> Self::Owned {
749        self
750    }
751
752    #[inline]
753    fn raw(owned: &mut Self::Owned) -> Self::Raw {
754        owned.as_mut_ptr()
755    }
756}