Skip to main content

pamoja_ffi/
mavlink_enums.rs

1//! The C ABI for the MAVLink dialect's named values.
2//!
3//! An enum field rides on the wire as a plain integer, so a caller that sets `type` on a
4//! heartbeat or reads `result` off an acknowledgment works in numbers. These calls turn the
5//! names the dialect writes, such as `MAV_TYPE_QUADROTOR`, into those numbers and back, for
6//! every enumeration a field of a typed message names, so a caller never copies a value out of
7//! the dialect by hand.
8
9use std::ffi::c_char;
10
11use pamoja_mavlink::dialect::{entry_value, enum_named, EnumDescriptor, ENUMS};
12
13use crate::{read_str, set_last_error, PamojaStatus, PamojaString};
14
15unsafe fn enumeration(name: *const c_char) -> Result<&'static EnumDescriptor, PamojaStatus> {
16    let Some(name) = read_str(name, "enumeration") else {
17        return Err(PamojaStatus::InvalidArgument);
18    };
19    enum_named(name).ok_or_else(|| {
20        set_last_error(format!("{name} is not a dialect enumeration"));
21        PamojaStatus::InvalidArgument
22    })
23}
24
25/// Looks up the value a dialect entry name stands for, whichever enumeration it belongs to.
26///
27/// # Arguments
28///
29/// * `entry` - the entry's name, such as `MAV_CMD_COMPONENT_ARM_DISARM`, as null-terminated
30///   UTF-8.
31/// * `out_value` - receives the value.
32///
33/// # Returns
34///
35/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if no entry has that name or an
36/// argument is null.
37///
38/// # Safety
39///
40/// `entry` must be a valid null-terminated UTF-8 string or null, and `out_value` must be
41/// valid for a write or null.
42#[no_mangle]
43pub unsafe extern "C" fn pamoja_mavlink_enum_value(
44    entry: *const c_char,
45    out_value: *mut u64,
46) -> PamojaStatus {
47    if out_value.is_null() {
48        set_last_error("out_value must not be null".to_owned());
49        return PamojaStatus::InvalidArgument;
50    }
51    let Some(entry) = read_str(entry, "entry") else {
52        return PamojaStatus::InvalidArgument;
53    };
54    match entry_value(entry) {
55        Some(value) => {
56            *out_value = value;
57            PamojaStatus::Ok
58        }
59        None => {
60            set_last_error(format!(
61                "{entry} is not an entry of any dialect enumeration"
62            ));
63            PamojaStatus::InvalidArgument
64        }
65    }
66}
67
68/// Names the entry of an enumeration that stands for a value.
69///
70/// # Arguments
71///
72/// * `enumeration` - the enumeration's name, such as `MAV_STATE`, as null-terminated UTF-8.
73/// * `value` - the value a field carried.
74/// * `out_name` - receives the entry's name, which the caller releases with
75///   [`pamoja_string_free`](crate::pamoja_string_free), or null when no entry names the value.
76///
77/// # Returns
78///
79/// [`PamojaStatus::Ok`], whether or not an entry names the value, or
80/// [`PamojaStatus::InvalidArgument`] if the enumeration is unknown or an argument is null.
81///
82/// # Safety
83///
84/// `enumeration` must be a valid null-terminated UTF-8 string or null, and `out_name` must be
85/// valid for a write or null.
86#[no_mangle]
87pub unsafe extern "C" fn pamoja_mavlink_enum_entry(
88    enumeration: *const c_char,
89    value: u64,
90    out_name: *mut *mut PamojaString,
91) -> PamojaStatus {
92    if out_name.is_null() {
93        set_last_error("out_name must not be null".to_owned());
94        return PamojaStatus::InvalidArgument;
95    }
96    let described = match self::enumeration(enumeration) {
97        Ok(described) => described,
98        Err(status) => return status,
99    };
100    *out_name = match described.entry(value) {
101        Some(name) => PamojaString::into_raw(name.to_owned()),
102        None => std::ptr::null_mut(),
103    };
104    PamojaStatus::Ok
105}
106
107/// Names the entries a value is made of: for a bitmask, each entry whose bits are set in it;
108/// otherwise the one entry that names it.
109///
110/// # Arguments
111///
112/// * `enumeration` - the enumeration's name, as null-terminated UTF-8.
113/// * `value` - the value a field carried.
114/// * `out_names` - receives the names in dialect order joined by `|`, empty when none applies,
115///   which the caller releases with [`pamoja_string_free`](crate::pamoja_string_free).
116///
117/// # Returns
118///
119/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if the enumeration is unknown or
120/// an argument is null.
121///
122/// # Safety
123///
124/// `enumeration` must be a valid null-terminated UTF-8 string or null, and `out_names` must be
125/// valid for a write or null.
126#[no_mangle]
127pub unsafe extern "C" fn pamoja_mavlink_enum_names(
128    enumeration: *const c_char,
129    value: u64,
130    out_names: *mut *mut PamojaString,
131) -> PamojaStatus {
132    if out_names.is_null() {
133        set_last_error("out_names must not be null".to_owned());
134        return PamojaStatus::InvalidArgument;
135    }
136    let described = match self::enumeration(enumeration) {
137        Ok(described) => described,
138        Err(status) => return status,
139    };
140    let names: Vec<&str> = described.names(value).collect();
141    *out_names = PamojaString::into_raw(names.join("|"));
142    PamojaStatus::Ok
143}
144
145/// Returns how many enumerations the dialect table holds.
146///
147/// # Returns
148///
149/// The count, for walking the table with [`pamoja_mavlink_enum_at`].
150#[no_mangle]
151pub extern "C" fn pamoja_mavlink_enum_count() -> usize {
152    ENUMS.len()
153}
154
155/// Returns the name of the enumeration at an index of the dialect table.
156///
157/// # Arguments
158///
159/// * `index` - the position, from `0` below [`pamoja_mavlink_enum_count`].
160/// * `out_name` - receives the name, which the caller releases with
161///   [`pamoja_string_free`](crate::pamoja_string_free).
162///
163/// # Returns
164///
165/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if `index` is past the end or
166/// `out_name` is null.
167///
168/// # Safety
169///
170/// `out_name` must be valid for a write or null.
171#[no_mangle]
172pub unsafe extern "C" fn pamoja_mavlink_enum_at(
173    index: usize,
174    out_name: *mut *mut PamojaString,
175) -> PamojaStatus {
176    if out_name.is_null() {
177        set_last_error("out_name must not be null".to_owned());
178        return PamojaStatus::InvalidArgument;
179    }
180    let Some(described) = ENUMS.get(index) else {
181        set_last_error(format!("{index} is past the end of the dialect table"));
182        return PamojaStatus::InvalidArgument;
183    };
184    *out_name = PamojaString::into_raw(described.name.to_owned());
185    PamojaStatus::Ok
186}
187
188/// Reports whether an enumeration's values combine as bits.
189///
190/// # Arguments
191///
192/// * `enumeration` - the enumeration's name, as null-terminated UTF-8.
193/// * `out_bitmask` - receives `true` for a bitmask.
194///
195/// # Returns
196///
197/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if the enumeration is unknown or
198/// an argument is null.
199///
200/// # Safety
201///
202/// `enumeration` must be a valid null-terminated UTF-8 string or null, and `out_bitmask` must
203/// be valid for a write or null.
204#[no_mangle]
205pub unsafe extern "C" fn pamoja_mavlink_enum_is_bitmask(
206    enumeration: *const c_char,
207    out_bitmask: *mut bool,
208) -> PamojaStatus {
209    if out_bitmask.is_null() {
210        set_last_error("out_bitmask must not be null".to_owned());
211        return PamojaStatus::InvalidArgument;
212    }
213    match self::enumeration(enumeration) {
214        Ok(described) => {
215            *out_bitmask = described.bitmask;
216            PamojaStatus::Ok
217        }
218        Err(status) => status,
219    }
220}
221
222/// Returns how many entries an enumeration names.
223///
224/// # Arguments
225///
226/// * `enumeration` - the enumeration's name, as null-terminated UTF-8.
227/// * `out_len` - receives the count, for walking the entries with
228///   [`pamoja_mavlink_enum_entry_at`].
229///
230/// # Returns
231///
232/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if the enumeration is unknown or
233/// an argument is null.
234///
235/// # Safety
236///
237/// `enumeration` must be a valid null-terminated UTF-8 string or null, and `out_len` must be
238/// valid for a write or null.
239#[no_mangle]
240pub unsafe extern "C" fn pamoja_mavlink_enum_len(
241    enumeration: *const c_char,
242    out_len: *mut usize,
243) -> PamojaStatus {
244    if out_len.is_null() {
245        set_last_error("out_len must not be null".to_owned());
246        return PamojaStatus::InvalidArgument;
247    }
248    match self::enumeration(enumeration) {
249        Ok(described) => {
250            *out_len = described.entries.len();
251            PamojaStatus::Ok
252        }
253        Err(status) => status,
254    }
255}
256
257/// Returns an enumeration's entry at an index, in dialect order.
258///
259/// # Arguments
260///
261/// * `enumeration` - the enumeration's name, as null-terminated UTF-8.
262/// * `index` - the position, from `0` below the count [`pamoja_mavlink_enum_len`] gives.
263/// * `out_name` - receives the entry's name, which the caller releases with
264///   [`pamoja_string_free`](crate::pamoja_string_free).
265/// * `out_value` - receives the value it names.
266///
267/// # Returns
268///
269/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] if the enumeration is unknown,
270/// `index` is past the end, or an argument is null.
271///
272/// # Safety
273///
274/// `enumeration` must be a valid null-terminated UTF-8 string or null, and `out_name` and
275/// `out_value` must be valid for a write or null.
276#[no_mangle]
277pub unsafe extern "C" fn pamoja_mavlink_enum_entry_at(
278    enumeration: *const c_char,
279    index: usize,
280    out_name: *mut *mut PamojaString,
281    out_value: *mut u64,
282) -> PamojaStatus {
283    if out_name.is_null() || out_value.is_null() {
284        set_last_error("out_name and out_value must not be null".to_owned());
285        return PamojaStatus::InvalidArgument;
286    }
287    let described = match self::enumeration(enumeration) {
288        Ok(described) => described,
289        Err(status) => return status,
290    };
291    let Some(entry) = described.entries.get(index) else {
292        set_last_error(format!(
293            "{index} is past the end of {}, which names {}",
294            described.name,
295            described.entries.len()
296        ));
297        return PamojaStatus::InvalidArgument;
298    };
299    *out_name = PamojaString::into_raw(entry.name.to_owned());
300    *out_value = entry.value;
301    PamojaStatus::Ok
302}
303
304#[cfg(test)]
305mod tests {
306    use std::ffi::{CStr, CString};
307    use std::ptr;
308
309    use super::*;
310
311    unsafe fn text(string: *mut PamojaString) -> String {
312        let read = CStr::from_ptr(crate::pamoja_string_data(string))
313            .to_str()
314            .expect("utf-8")
315            .to_owned();
316        crate::pamoja_string_free(string);
317        read
318    }
319
320    #[test]
321    fn a_name_becomes_its_value_and_back() {
322        let entry = CString::new("MAV_TYPE_QUADROTOR").expect("static");
323        let mut value = 0;
324        assert_eq!(
325            unsafe { pamoja_mavlink_enum_value(entry.as_ptr(), &mut value) },
326            PamojaStatus::Ok
327        );
328        assert_eq!(value, 2);
329
330        let enumeration = CString::new("MAV_TYPE").expect("static");
331        let mut name = ptr::null_mut();
332        assert_eq!(
333            unsafe { pamoja_mavlink_enum_entry(enumeration.as_ptr(), 2, &mut name) },
334            PamojaStatus::Ok
335        );
336        assert_eq!(unsafe { text(name) }, "MAV_TYPE_QUADROTOR");
337
338        assert_eq!(
339            unsafe { pamoja_mavlink_enum_entry(enumeration.as_ptr(), 250, &mut name) },
340            PamojaStatus::Ok
341        );
342        assert!(name.is_null(), "no entry names 250");
343    }
344
345    #[test]
346    fn a_bitmask_is_named_bit_by_bit() {
347        let enumeration = CString::new("MAV_MODE_FLAG").expect("static");
348        let mut names = ptr::null_mut();
349        assert_eq!(
350            unsafe { pamoja_mavlink_enum_names(enumeration.as_ptr(), 129, &mut names) },
351            PamojaStatus::Ok
352        );
353        assert_eq!(
354            unsafe { text(names) },
355            "MAV_MODE_FLAG_SAFETY_ARMED|MAV_MODE_FLAG_CUSTOM_MODE_ENABLED"
356        );
357        let mut bitmask = false;
358        assert_eq!(
359            unsafe { pamoja_mavlink_enum_is_bitmask(enumeration.as_ptr(), &mut bitmask) },
360            PamojaStatus::Ok
361        );
362        assert!(bitmask);
363    }
364
365    #[test]
366    fn the_table_can_be_walked() {
367        let count = pamoja_mavlink_enum_count();
368        assert!(count > 0);
369        let mut name = ptr::null_mut();
370        assert_eq!(
371            unsafe { pamoja_mavlink_enum_at(0, &mut name) },
372            PamojaStatus::Ok
373        );
374        let first = CString::new(unsafe { text(name) }).expect("no interior nul");
375        let mut len = 0;
376        assert_eq!(
377            unsafe { pamoja_mavlink_enum_len(first.as_ptr(), &mut len) },
378            PamojaStatus::Ok
379        );
380        let mut value = 0;
381        assert_eq!(
382            unsafe { pamoja_mavlink_enum_entry_at(first.as_ptr(), len - 1, &mut name, &mut value) },
383            PamojaStatus::Ok
384        );
385        unsafe { crate::pamoja_string_free(name) };
386        assert_eq!(
387            unsafe { pamoja_mavlink_enum_entry_at(first.as_ptr(), len, &mut name, &mut value) },
388            PamojaStatus::InvalidArgument
389        );
390        assert_eq!(
391            unsafe { pamoja_mavlink_enum_at(count, &mut name) },
392            PamojaStatus::InvalidArgument
393        );
394    }
395
396    #[test]
397    fn an_unknown_name_is_refused() {
398        let entry = CString::new("MAV_TYPE_TELEPORTER").expect("static");
399        let mut value = 0;
400        assert_eq!(
401            unsafe { pamoja_mavlink_enum_value(entry.as_ptr(), &mut value) },
402            PamojaStatus::InvalidArgument
403        );
404        let enumeration = CString::new("MAV_TYPES").expect("static");
405        let mut name = ptr::null_mut();
406        assert_eq!(
407            unsafe { pamoja_mavlink_enum_entry(enumeration.as_ptr(), 2, &mut name) },
408            PamojaStatus::InvalidArgument
409        );
410        assert_eq!(
411            unsafe { pamoja_mavlink_enum_value(ptr::null(), &mut value) },
412            PamojaStatus::InvalidArgument
413        );
414    }
415}