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}