use crate::error::FitError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum BaseType {
Enum = 0x00,
SInt8 = 0x01,
UInt8 = 0x02,
SInt16 = 0x03,
UInt16 = 0x04,
SInt32 = 0x05,
UInt32 = 0x06,
String = 0x07,
Float32 = 0x08,
Float64 = 0x09,
UInt8z = 0x0A,
UInt16z = 0x0B,
UInt32z = 0x0C,
Byte = 0x0D,
SInt64 = 0x0E,
UInt64 = 0x0F,
UInt64z = 0x10,
}
impl BaseType {
pub const TYPE_CODE_MASK: u8 = 0x1F;
pub const ENDIAN_FLAG: u8 = 0x80;
pub fn from_byte(byte: u8) -> Result<Self, FitError> {
let code = byte & Self::TYPE_CODE_MASK;
let bt = match code {
0x00 => Self::Enum,
0x01 => Self::SInt8,
0x02 => Self::UInt8,
0x03 => Self::SInt16,
0x04 => Self::UInt16,
0x05 => Self::SInt32,
0x06 => Self::UInt32,
0x07 => Self::String,
0x08 => Self::Float32,
0x09 => Self::Float64,
0x0A => Self::UInt8z,
0x0B => Self::UInt16z,
0x0C => Self::UInt32z,
0x0D => Self::Byte,
0x0E => Self::SInt64,
0x0F => Self::UInt64,
0x10 => Self::UInt64z,
_ => return Err(FitError::UnknownBaseType(byte, code)),
};
Ok(bt)
}
pub fn endian_flag_set(byte: u8) -> bool {
byte & Self::ENDIAN_FLAG != 0
}
pub fn element_size(&self) -> usize {
match self {
Self::Enum | Self::SInt8 | Self::UInt8 | Self::UInt8z | Self::Byte | Self::String => 1,
Self::SInt16 | Self::UInt16 | Self::UInt16z => 2,
Self::SInt32 | Self::UInt32 | Self::UInt32z | Self::Float32 => 4,
Self::SInt64 | Self::UInt64 | Self::UInt64z | Self::Float64 => 8,
}
}
pub fn is_z_type(&self) -> bool {
matches!(
self,
Self::UInt8z | Self::UInt16z | Self::UInt32z | Self::UInt64z
)
}
pub fn is_byte(&self) -> bool {
matches!(self, Self::Byte)
}
pub fn is_string(&self) -> bool {
matches!(self, Self::String)
}
pub fn type_code(&self) -> u8 {
*self as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_17_codes_round_trip() {
for code in 0x00..=0x10 {
let bt = BaseType::from_byte(code).expect("code in range must decode");
assert_eq!(bt as u8, code, "round-trip for 0x{code:02X}");
}
}
#[test]
fn endian_flag_is_masked() {
let with_flag = BaseType::from_byte(0x80 | 0x04).unwrap();
let without = BaseType::from_byte(0x04).unwrap();
assert_eq!(with_flag, BaseType::UInt16);
assert_eq!(without, BaseType::UInt16);
assert!(BaseType::endian_flag_set(0x80 | 0x04));
assert!(!BaseType::endian_flag_set(0x04));
}
#[test]
fn invalid_type_code_returns_error() {
for bad in 0x11..=0x1F {
assert!(matches!(
BaseType::from_byte(bad),
Err(FitError::UnknownBaseType(_, _))
));
}
}
#[test]
fn element_sizes_are_correct() {
assert_eq!(BaseType::Enum.element_size(), 1);
assert_eq!(BaseType::UInt16.element_size(), 2);
assert_eq!(BaseType::UInt32.element_size(), 4);
assert_eq!(BaseType::Float32.element_size(), 4);
assert_eq!(BaseType::UInt64.element_size(), 8);
assert_eq!(BaseType::UInt64z.element_size(), 8);
assert_eq!(BaseType::String.element_size(), 1);
assert_eq!(BaseType::Byte.element_size(), 1);
}
#[test]
fn z_type_classification() {
assert!(BaseType::UInt8z.is_z_type());
assert!(BaseType::UInt16z.is_z_type());
assert!(BaseType::UInt32z.is_z_type());
assert!(BaseType::UInt64z.is_z_type());
assert!(!BaseType::UInt8.is_z_type());
assert!(!BaseType::Byte.is_z_type());
}
#[test]
fn uint64z_is_present_at_0x10() {
assert_eq!(BaseType::from_byte(0x10).unwrap(), BaseType::UInt64z);
}
}