Skip to main content

sigmf/
datatype.rs

1//! SigMF sample datatypes (`core:datatype`).
2//!
3//! A SigMF datatype string describes how each sample is stored in a
4//! `.sigmf-data` file. It is composed of:
5//!
6//! * a domain prefix: `r` for real or `c` for complex (interleaved I/Q),
7//! * a component type: `f32`, `f64`, `i32`, `i16`, `u32`, `u16`, `i8`, or `u8`,
8//! * for multi-byte components, an endianness suffix: `_le` or `_be`.
9//!
10//! Examples include `cf32_le`, `ri16_be`, and `ru8`. All 28 core datatypes
11//! defined by the SigMF specification are supported; other strings are
12//! rejected. [`Datatype`] stores the validated string and exposes its parts
13//! through [`SampleDomain`], [`ComponentType`], [`Endianness`], and
14//! [`ByteType`].
15//!
16//! ```rust
17//! use sigmf::datatype::{ComponentType, Datatype, Endianness, SampleDomain};
18//! use std::str::FromStr;
19//!
20//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! let datatype = Datatype::from_str("cf32_le")?;
22//! assert!(datatype.is_complex());
23//! assert_eq!(datatype.get_domain(), SampleDomain::Complex);
24//! assert_eq!(datatype.get_dtype(), Some(ComponentType::F32));
25//! assert_eq!(datatype.get_endian(), Some(Endianness::LE));
26//! assert_eq!(datatype.component_bytes(), 4);
27//! assert_eq!(datatype.bytes_per_channel_sample(), 8);
28//! assert_eq!(datatype.to_string(), "cf32_le");
29//!
30//! assert!(Datatype::from_str("cf32").is_err()); // missing endianness
31//! # Ok(())
32//! # }
33//! ```
34
35use crate::SigmfError;
36use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
37use std::fmt;
38use std::str::FromStr;
39
40/// Whether samples are real-valued or complex-valued.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum SampleDomain {
43    /// Real samples (`r` prefix); one component per sample.
44    Real,
45    /// Complex samples (`c` prefix); interleaved in-phase and quadrature
46    /// components, two per sample.
47    Complex,
48}
49
50/// Multi-byte component type of a datatype.
51///
52/// Single-byte components are described by [`ByteType`] instead.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum ComponentType {
55    /// 32-bit IEEE 754 floating point (`f32`).
56    F32,
57    /// 64-bit IEEE 754 floating point (`f64`).
58    F64,
59    /// 16-bit signed integer (`i16`).
60    I16,
61    /// 32-bit signed integer (`i32`).
62    I32,
63    /// 16-bit unsigned integer (`u16`).
64    U16,
65    /// 32-bit unsigned integer (`u32`).
66    U32,
67}
68
69/// Byte order of a multi-byte component type.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Endianness {
72    /// Little-endian (`_le` suffix).
73    LE,
74    /// Big-endian (`_be` suffix).
75    BE,
76}
77
78/// Single-byte component type of a datatype.
79///
80/// Single-byte datatypes such as `ri8` and `cu8` have no endianness suffix.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum ByteType {
83    /// 8-bit signed integer (`i8`).
84    I8,
85    /// 8-bit unsigned integer (`u8`).
86    U8,
87}
88
89/// A validated SigMF `core:datatype` value such as `cf32_le` or `ru8`.
90///
91/// Construct one by parsing a string with [`FromStr`] or from its parts with
92/// [`Datatype::new`]. It serializes to and deserializes from its string form,
93/// and deserialization rejects strings that are not core SigMF datatypes.
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Datatype {
96    wire_value: String,
97}
98
99impl Datatype {
100    /// Builds a datatype from its domain and component description.
101    ///
102    /// Supply either `component` and `endianness` (for multi-byte types) or
103    /// only `byte_type` (for single-byte types).
104    ///
105    /// # Errors
106    ///
107    /// Returns [`SigmfError::InvalidDataType`] if any other combination of
108    /// `component`, `endianness`, and `byte_type` is given.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use sigmf::datatype::{ByteType, ComponentType, Datatype, Endianness, SampleDomain};
114    ///
115    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
116    /// let iq = Datatype::new(
117    ///     SampleDomain::Complex,
118    ///     Some(ComponentType::I16),
119    ///     Some(Endianness::BE),
120    ///     None,
121    /// )?;
122    /// assert_eq!(iq.as_str(), "ci16_be");
123    ///
124    /// let bytes = Datatype::new(SampleDomain::Real, None, None, Some(ByteType::U8))?;
125    /// assert_eq!(bytes.as_str(), "ru8");
126    ///
127    /// assert!(Datatype::new(SampleDomain::Real, Some(ComponentType::F32), None, None).is_err());
128    /// # Ok(())
129    /// # }
130    /// ```
131    pub fn new(
132        domain: SampleDomain,
133        component: Option<ComponentType>,
134        endianness: Option<Endianness>,
135        byte_type: Option<ByteType>,
136    ) -> Result<Self, SigmfError> {
137        let domain = match domain {
138            SampleDomain::Real => "r",
139            SampleDomain::Complex => "c",
140        };
141        let wire_value = match (component, endianness, byte_type) {
142            (Some(component), Some(endianness), None) => {
143                let component = match component {
144                    ComponentType::F32 => "f32",
145                    ComponentType::F64 => "f64",
146                    ComponentType::I16 => "i16",
147                    ComponentType::I32 => "i32",
148                    ComponentType::U16 => "u16",
149                    ComponentType::U32 => "u32",
150                };
151                let endian = match endianness {
152                    Endianness::LE => "le",
153                    Endianness::BE => "be",
154                };
155                format!("{domain}{component}_{endian}")
156            }
157            (None, None, Some(byte_type)) => {
158                let byte = match byte_type {
159                    ByteType::I8 => "i8",
160                    ByteType::U8 => "u8",
161                };
162                format!("{domain}{byte}")
163            }
164            _ => return Err(SigmfError::InvalidDataType(
165                "datatype requires either an 8-bit byte type or a component type and endianness"
166                    .to_string(),
167            )),
168        };
169        Ok(Self { wire_value })
170    }
171
172    /// Returns whether samples are real or complex.
173    pub fn get_domain(&self) -> SampleDomain {
174        match self.wire_value.as_bytes()[0] {
175            b'r' => SampleDomain::Real,
176            b'c' => SampleDomain::Complex,
177            _ => unreachable!("Datatype can only be constructed from a validated wire value"),
178        }
179    }
180
181    /// Returns the multi-byte component type, or `None` for single-byte
182    /// datatypes (see [`get_byte`](Self::get_byte)).
183    pub fn get_dtype(&self) -> Option<ComponentType> {
184        match self
185            .wire_value
186            .split('_')
187            .next()
188            .unwrap_or_default()
189            .get(1..)
190        {
191            Some("f32") => Some(ComponentType::F32),
192            Some("f64") => Some(ComponentType::F64),
193            Some("i16") => Some(ComponentType::I16),
194            Some("i32") => Some(ComponentType::I32),
195            Some("u16") => Some(ComponentType::U16),
196            Some("u32") => Some(ComponentType::U32),
197            _ => None,
198        }
199    }
200
201    /// Returns the byte order, or `None` for single-byte datatypes.
202    pub fn get_endian(&self) -> Option<Endianness> {
203        match self.wire_value.rsplit_once('_').map(|(_, endian)| endian) {
204            Some("le") => Some(Endianness::LE),
205            Some("be") => Some(Endianness::BE),
206            _ => None,
207        }
208    }
209
210    /// Returns the single-byte component type, or `None` for multi-byte
211    /// datatypes (see [`get_dtype`](Self::get_dtype)).
212    pub fn get_byte(&self) -> Option<ByteType> {
213        match self.wire_value.get(1..) {
214            Some("i8") => Some(ByteType::I8),
215            Some("u8") => Some(ByteType::U8),
216            _ => None,
217        }
218    }
219
220    /// Returns the datatype string as it appears in SigMF metadata.
221    pub fn as_str(&self) -> &str {
222        &self.wire_value
223    }
224
225    /// Returns `true` if the datatype is complex (`c` prefix).
226    pub fn is_complex(&self) -> bool {
227        self.get_domain() == SampleDomain::Complex
228    }
229
230    /// Returns the size in bytes of one component (one real value, or one of
231    /// the I/Q halves of a complex value).
232    pub fn component_bytes(&self) -> usize {
233        match self.get_byte() {
234            Some(_) => 1,
235            None => match self.get_dtype() {
236                Some(ComponentType::F32 | ComponentType::I32 | ComponentType::U32) => 4,
237                Some(ComponentType::F64) => 8,
238                Some(ComponentType::I16 | ComponentType::U16) => 2,
239                None => unreachable!("validated datatype has a component type"),
240            },
241        }
242    }
243
244    /// Returns the size in bytes of one sample on one channel.
245    ///
246    /// This is [`component_bytes`](Self::component_bytes) for real datatypes
247    /// and twice that for complex datatypes. Multiply by `core:num_channels`
248    /// to get the size of a frame.
249    pub fn bytes_per_channel_sample(&self) -> usize {
250        self.component_bytes() * if self.is_complex() { 2 } else { 1 }
251    }
252}
253
254impl FromStr for Datatype {
255    type Err = SigmfError;
256
257    /// Parses a core SigMF datatype string such as `cf32_le` or `ri8`.
258    ///
259    /// # Errors
260    ///
261    /// Returns [`SigmfError::InvalidDataType`] if the string is empty, has an
262    /// unknown domain or component type, is missing or has an invalid
263    /// endianness suffix, or adds a suffix to a single-byte type.
264    fn from_str(value: &str) -> Result<Self, Self::Err> {
265        let bytes = value.as_bytes();
266        let Some((&domain, components)) = bytes.split_first() else {
267            return Err(SigmfError::InvalidDataType(
268                "datatype must not be empty".to_string(),
269            ));
270        };
271        if domain != b'r' && domain != b'c' {
272            return Err(SigmfError::InvalidDataType(format!(
273                "invalid datatype domain in {value:?}"
274            )));
275        }
276
277        let body = std::str::from_utf8(components).map_err(|_| {
278            SigmfError::InvalidDataType(format!("datatype is not ASCII: {value:?}"))
279        })?;
280        if matches!(body, "i8" | "u8") {
281            return Ok(Self {
282                wire_value: value.to_string(),
283            });
284        }
285        let (component, endian) = body.split_once('_').ok_or_else(|| {
286            SigmfError::InvalidDataType(format!("datatype {value:?} requires an endian suffix"))
287        })?;
288        if body.matches('_').count() != 1 {
289            return Err(SigmfError::InvalidDataType(format!(
290                "invalid datatype suffix in {value:?}"
291            )));
292        }
293        if !matches!(component, "f32" | "f64" | "i16" | "i32" | "u16" | "u32") {
294            return Err(SigmfError::InvalidDataType(format!(
295                "unsupported core component type in {value:?}"
296            )));
297        }
298        if !matches!(endian, "le" | "be") {
299            return Err(SigmfError::InvalidDataType(format!(
300                "invalid datatype byte order in {value:?}"
301            )));
302        }
303        Ok(Self {
304            wire_value: value.to_string(),
305        })
306    }
307}
308
309impl fmt::Display for Datatype {
310    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
311        formatter.write_str(self.as_str())
312    }
313}
314
315impl Serialize for Datatype {
316    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
317    where
318        S: Serializer,
319    {
320        serializer.serialize_str(self.as_str())
321    }
322}
323
324impl<'de> Deserialize<'de> for Datatype {
325    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
326    where
327        D: Deserializer<'de>,
328    {
329        let value = String::deserialize(deserializer)?;
330        Self::from_str(&value).map_err(de::Error::custom)
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn all_28_core_datatypes_round_trip() {
340        let components = [
341            "i8", "u8", "f32_le", "f32_be", "f64_le", "f64_be", "i16_le", "i16_be", "i32_le",
342            "i32_be", "u16_le", "u16_be", "u32_le", "u32_be",
343        ];
344        for domain in ["r", "c"] {
345            for component in components {
346                let value = format!("{domain}{component}");
347                let datatype = Datatype::from_str(&value).unwrap();
348                assert_eq!(datatype.to_string(), value);
349                assert_eq!(
350                    serde_json::to_string(&datatype).unwrap(),
351                    format!("\"{value}\"")
352                );
353                assert_eq!(
354                    serde_json::from_str::<Datatype>(&format!("\"{value}\"")).unwrap(),
355                    datatype
356                );
357            }
358        }
359    }
360
361    #[test]
362    fn rejects_non_core_and_malformed_datatypes() {
363        for value in [
364            "",
365            "xf32_le",
366            "cf32_le_extra",
367            "cf32",
368            "ci8_le",
369            "ci64_le",
370            "rf32_xx",
371            "cf32_le_extra",
372            "cf32__le",
373        ] {
374            assert!(Datatype::from_str(value).is_err(), "accepted {value:?}");
375        }
376    }
377}