use crate::Value;
use nom::bytes::streaming::{tag, take};
use nom::combinator::cond;
use nom::multi::count;
use nom::number::streaming::{
f32, f64, i16, i32, i64, le_i8, le_u16, le_u32, le_u8, u16, u32, u64,
};
use nom::number::Endianness;
use nom::sequence::tuple;
use nom::{Err, IResult, Needed};
use std::collections::HashMap;
use std::convert::From;
use std::fmt::Display;
use std::sync::Arc;
impl Value {
fn is_valid(&self) -> bool {
match self {
Value::Enum(val) => *val != 0xFF,
Value::SInt8(val) => *val != 0x7F,
Value::UInt8(val) => *val != 0xFF,
Value::SInt16(val) => *val != 0x7FFF,
Value::UInt16(val) => *val != 0xFFFF,
Value::SInt32(val) => *val != 0x7FFF_FFFF,
Value::UInt32(val) => *val != 0xFFFF_FFFF,
Value::String(val) => !val.contains('\0'),
Value::Timestamp(_) => true, Value::Float32(val) => val.is_finite(),
Value::Float64(val) => val.is_finite(),
Value::UInt8z(val) => *val != 0x0,
Value::UInt16z(val) => *val != 0x0,
Value::UInt32z(val) => *val != 0x0,
Value::Byte(val) => *val != 0xFF,
Value::SInt64(val) => *val != 0x7FFF_FFFF_FFFF_FFFF,
Value::UInt64(val) => *val != 0xFFFF_FFFF_FFFF_FFFF,
Value::UInt64z(val) => *val != 0x0,
Value::Array(vals) => !vals.is_empty() && vals.iter().all(|v| v.is_valid()),
}
}
}
#[derive(Clone, Debug)]
pub struct FitFileHeader {
header_size: u8,
protocol_ver_enc: f32,
profile_ver_enc: f32,
data_size: u32,
crc: Option<u16>,
}
impl FitFileHeader {
pub fn header_size(&self) -> u8 {
self.header_size
}
pub fn protocol_ver_enc(&self) -> f32 {
self.protocol_ver_enc
}
pub fn profile_ver_enc(&self) -> f32 {
self.profile_ver_enc
}
pub fn data_size(&self) -> u32 {
self.data_size
}
pub fn crc(&self) -> Option<u16> {
self.crc
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum FitMessageType {
Data,
Definition,
}
#[derive(Clone, Debug)]
struct FitMessageHeader {
contains_developer_data: bool,
local_message_number: u8,
message_type: FitMessageType,
time_offset: Option<u8>,
}
pub enum FitMessage {
Data(FitDataMessage),
Definition(FitDefinitionMessage),
MissingDefinitionMessage(u8),
}
#[derive(Clone, Debug)]
pub struct FitDefinitionMessage {
byte_order: Endianness,
local_message_number: u8,
global_message_number: u16,
field_definitions: Vec<FieldDefinition>,
developer_field_definitions: Vec<DeveloperFieldDefinition>,
}
impl FitDefinitionMessage {
pub fn byte_order(&self) -> Endianness {
self.byte_order
}
pub fn local_message_number(&self) -> u8 {
self.local_message_number
}
pub fn global_message_number(&self) -> u16 {
self.global_message_number
}
pub fn field_definitions(&self) -> &[FieldDefinition] {
&self.field_definitions
}
pub fn developer_field_definitions(&self) -> &[DeveloperFieldDefinition] {
&self.developer_field_definitions
}
pub fn data_message_size(&self) -> usize {
self.field_definitions
.iter()
.fold(1, |l, f| l + f.size as usize)
+ self
.developer_field_definitions
.iter()
.fold(0, |l, f| l + f.size as usize)
}
}
#[derive(Clone, Debug)]
pub struct FieldDefinition {
field_definition_number: u8, size: u8, base_type: BaseType,
}
#[derive(Clone, Debug)]
pub struct DeveloperFieldDefinition {
field_number: u8,
size: u8,
developer_data_index: u8,
}
#[derive(Clone, Debug)]
pub struct FitDataMessage {
global_message_number: u16,
time_offset: Option<u8>,
fields: HashMap<u8, Option<Value>>,
developer_fields: Vec<Option<Value>>,
}
impl FitDataMessage {
pub fn global_message_number(&self) -> u16 {
self.global_message_number
}
pub fn time_offset(&self) -> Option<u8> {
self.time_offset
}
pub fn fields(&self) -> &HashMap<u8, Option<Value>> {
&self.fields
}
#[allow(dead_code)]
pub fn developer_fields(&self) -> &[Option<Value>] {
&self.developer_fields
}
}
#[derive(Clone, Copy, Debug)]
enum BaseType {
Enum = 0x00,
SInt8 = 0x01,
UInt8 = 0x02,
SInt16 = 0x83,
UInt16 = 0x84,
SInt32 = 0x85,
UInt32 = 0x86,
String = 0x07,
Float32 = 0x88,
Float64 = 0x89,
UInt8z = 0x0A,
UInt16z = 0x8B,
UInt32z = 0x8C,
Byte = 0x0D,
SInt64 = 0x8E,
UInt64 = 0x8F,
UInt64z = 0x90,
}
impl BaseType {
fn size(&self) -> u8 {
match *self {
BaseType::Enum => 1,
BaseType::SInt8 => 1,
BaseType::UInt8 => 1,
BaseType::SInt16 => 2,
BaseType::UInt16 => 2,
BaseType::SInt32 => 4,
BaseType::UInt32 => 4,
BaseType::String => 1,
BaseType::Float32 => 4,
BaseType::Float64 => 8,
BaseType::UInt8z => 1,
BaseType::UInt16z => 2,
BaseType::UInt32z => 4,
BaseType::Byte => 1,
BaseType::SInt64 => 8,
BaseType::UInt64 => 8,
BaseType::UInt64z => 8,
}
}
}
impl From<u8> for BaseType {
fn from(value: u8) -> Self {
match value & 0x9f {
0x00 => BaseType::Enum,
0x01 => BaseType::SInt8,
0x02 => BaseType::UInt8,
0x83 => BaseType::SInt16,
0x84 => BaseType::UInt16,
0x85 => BaseType::SInt32,
0x86 => BaseType::UInt32,
0x07 => BaseType::String,
0x88 => BaseType::Float32,
0x89 => BaseType::Float64,
0x0A => BaseType::UInt8z,
0x8B => BaseType::UInt16z,
0x8C => BaseType::UInt32z,
0x8E => BaseType::SInt64,
0x8F => BaseType::UInt64,
0x90 => BaseType::UInt64z,
0x0D => BaseType::Byte,
_ => BaseType::Byte,
}
}
}
pub fn fit_file_header(input: &[u8]) -> IResult<&[u8], FitFileHeader> {
match fit_file_header_impl(input) {
Ok(r) => Ok(r),
Err(Err::Incomplete(_)) => {
Err(Err::Incomplete(Needed::new(
input.first().map_or(14, |v| *v as usize - input.len()),
)))
}
Err(r) => Err(r),
}
}
fn fit_file_header_impl(input: &[u8]) -> IResult<&[u8], FitFileHeader> {
let (input, (header_size, proto, prof, data_size)) =
tuple((le_u8, le_u8, le_u16, le_u32))(input)?;
let (input, _) = tag(".FIT")(input)?;
let (input, crc) = cond(header_size > 12, le_u16)(input)?;
let protocol_ver_enc = split_decimal_to_float(proto >> 4, proto & ((1 << 4) - 1));
let profile_ver_enc = split_decimal_to_float(prof / 100, prof % 100);
Ok((
input,
FitFileHeader {
header_size,
protocol_ver_enc,
profile_ver_enc,
data_size,
crc,
},
))
}
fn split_decimal_to_float<T: Display>(left: T, right: T) -> f32 {
format!("{}.{}", left, right).parse().unwrap()
}
pub fn fit_message<'a>(
input: &'a [u8],
definitions: &HashMap<u8, Arc<FitDefinitionMessage>>,
) -> IResult<&'a [u8], FitMessage> {
let (input, header) = message_header(input)?;
match header.message_type {
FitMessageType::Data => {
if let Some(def_mesg) = definitions.get(&header.local_message_number) {
let (input, (fields, developer_fields)) = data_message_fields(input, def_mesg)?;
Ok((
input,
FitMessage::Data(FitDataMessage {
fields,
developer_fields,
global_message_number: def_mesg.global_message_number,
time_offset: header.time_offset,
}),
))
} else {
Ok((
input,
FitMessage::MissingDefinitionMessage(header.local_message_number),
))
}
}
FitMessageType::Definition => {
let (input, message) = definition_message(input, &header)?;
Ok((input, FitMessage::Definition(message)))
}
}
}
fn message_header(input: &[u8]) -> IResult<&[u8], FitMessageHeader> {
let (input, msg_header_byte) = le_u8(input)?;
let contains_developer_data: bool;
let local_message_number: u8;
let message_type: FitMessageType;
let time_offset: Option<u8>;
if msg_header_byte & 0x80 == 0x80 {
contains_developer_data = false;
local_message_number = (msg_header_byte >> 5) & 0x3; message_type = FitMessageType::Data;
time_offset = Some(msg_header_byte & 0x1F);
} else if (msg_header_byte & 0x40) == 0x40 {
contains_developer_data = msg_header_byte & 0x20 == 0x20;
local_message_number = msg_header_byte & 0xF;
message_type = FitMessageType::Definition;
time_offset = None;
} else {
contains_developer_data = false;
local_message_number = msg_header_byte & 0xF;
message_type = FitMessageType::Data;
time_offset = None;
}
Ok((
input,
FitMessageHeader {
contains_developer_data,
local_message_number,
message_type,
time_offset,
},
))
}
fn definition_message<'a>(
input: &'a [u8],
header: &FitMessageHeader,
) -> IResult<&'a [u8], FitDefinitionMessage> {
let (input, _) = take(1usize)(input)?; let (input, arch_byte) = le_u8(input)?;
let byte_order = if arch_byte == 1 {
Endianness::Big
} else {
Endianness::Little
};
let (input, global_message_number) = u16(byte_order)(input)?;
let (input, number_of_fields) = le_u8(input)?;
let (input, field_definitions) = count(field_definition, number_of_fields as usize)(input)?;
let (input, developer_field_definitions) = if header.contains_developer_data {
let (input, nflds) = le_u8(input)?;
let (input, dev_fld_defs) = count(developer_field_definition, nflds as usize)(input)?;
(input, dev_fld_defs)
} else {
(input, Vec::new())
};
Ok((
input,
FitDefinitionMessage {
byte_order,
local_message_number: header.local_message_number,
global_message_number,
field_definitions,
developer_field_definitions,
},
))
}
fn field_definition(input: &[u8]) -> IResult<&[u8], FieldDefinition> {
let (input, field_definition_number) = le_u8(input)?;
let (input, size) = le_u8(input)?;
let (input, base_type_field) = le_u8(input)?;
let mut base_type = BaseType::from(base_type_field);
if size % base_type.size() != 0 {
eprintln!(
"ERROR: field size: {} is not a multiple of the base type {:?} (size {}) parsing as a byte array",
size,
base_type,
base_type.size()
);
base_type = BaseType::Byte;
}
Ok((
input,
FieldDefinition {
field_definition_number,
size,
base_type,
},
))
}
fn developer_field_definition(input: &[u8]) -> IResult<&[u8], DeveloperFieldDefinition> {
let (input, field_number) = le_u8(input)?;
let (input, size) = le_u8(input)?;
let (input, developer_data_index) = le_u8(input)?;
Ok((
input,
DeveloperFieldDefinition {
field_number,
size,
developer_data_index,
},
))
}
#[allow(clippy::type_complexity)]
fn data_message_fields<'a>(
input: &'a [u8],
def_mesg: &FitDefinitionMessage,
) -> IResult<&'a [u8], (HashMap<u8, Option<Value>>, Vec<Option<Value>>)> {
match data_message_fields_impl(input, def_mesg) {
Ok(r) => Ok(r),
Err(Err::Incomplete(_)) => {
Err(Err::Incomplete(Needed::new(
def_mesg.data_message_size() as usize - input.len() - 1,
)))
}
Err(r) => Err(r),
}
}
#[allow(clippy::type_complexity)]
fn data_message_fields_impl<'a>(
input: &'a [u8],
def_mesg: &FitDefinitionMessage,
) -> IResult<&'a [u8], (HashMap<u8, Option<Value>>, Vec<Option<Value>>)> {
let mut fields = HashMap::new();
let mut input = input;
for field_def in &def_mesg.field_definitions {
let (i, value) = data_field_value(
input,
field_def.base_type,
def_mesg.byte_order,
field_def.size,
)?;
fields.insert(field_def.field_definition_number, value);
input = i;
}
let mut developer_fields = Vec::new();
for field_def in &def_mesg.developer_field_definitions {
let (i, value) = data_field_value(
input,
BaseType::Byte,
def_mesg.byte_order, field_def.size, )?; developer_fields.push(value);
input = i;
}
Ok((input, (fields, developer_fields)))
}
fn data_field_value(
input: &[u8],
base_type: BaseType,
byte_order: Endianness,
size: u8,
) -> IResult<&[u8], Option<Value>> {
let mut input = input;
let mut bytes_consumed = 0;
let mut values: Vec<Value> = Vec::new();
while bytes_consumed < size {
let (i, value) = match base_type {
BaseType::Enum => le_u8(input).map(|(i, v)| (i, Value::Enum(v)))?,
BaseType::SInt8 => le_i8(input).map(|(i, v)| (i, Value::SInt8(v)))?,
BaseType::UInt8 => le_u8(input).map(|(i, v)| (i, Value::UInt8(v)))?,
BaseType::SInt16 => i16(byte_order)(input).map(|(i, v)| (i, Value::SInt16(v)))?,
BaseType::UInt16 => u16(byte_order)(input).map(|(i, v)| (i, Value::UInt16(v)))?,
BaseType::SInt32 => i32(byte_order)(input).map(|(i, v)| (i, Value::SInt32(v)))?,
BaseType::UInt32 => u32(byte_order)(input).map(|(i, v)| (i, Value::UInt32(v)))?,
BaseType::String => {
let (input, field_value) = take(size as usize)(input)?;
let mut value = Vec::new();
for char in field_value {
if *char == 0u8 {
break;
}
value.push(*char);
}
if let Ok(value) = String::from_utf8(value) {
return Ok((input, Some(Value::String(value))));
} else {
return Ok((input, None));
}
}
BaseType::Float32 => f32(byte_order)(input).map(|(i, v)| (i, Value::Float32(v)))?,
BaseType::Float64 => f64(byte_order)(input).map(|(i, v)| (i, Value::Float64(v)))?,
BaseType::UInt8z => le_u8(input).map(|(i, v)| (i, Value::UInt8z(v)))?,
BaseType::UInt16z => u16(byte_order)(input).map(|(i, v)| (i, Value::UInt16z(v)))?,
BaseType::UInt32z => u32(byte_order)(input).map(|(i, v)| (i, Value::UInt32z(v)))?,
BaseType::Byte => le_u8(input).map(|(i, v)| (i, Value::UInt8(v)))?,
BaseType::SInt64 => i64(byte_order)(input).map(|(i, v)| (i, Value::SInt64(v)))?,
BaseType::UInt64 => u64(byte_order)(input).map(|(i, v)| (i, Value::UInt64(v)))?,
BaseType::UInt64z => u64(byte_order)(input).map(|(i, v)| (i, Value::UInt64z(v)))?,
};
bytes_consumed += base_type.size();
values.push(value);
input = i;
}
let value = if values.len() == 1 {
values.swap_remove(0)
} else {
Value::Array(values)
};
if value.is_valid() {
Ok((input, Some(value)))
} else {
Ok((input, None))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_test() {
let data = include_bytes!("../../tests/fixtures/Activity.fit");
let (_, hdr) = fit_file_header(data).unwrap();
assert_eq!(hdr.header_size, 12);
assert_eq!(hdr.protocol_ver_enc, 1.0);
assert_eq!(hdr.profile_ver_enc, 1.0);
assert_eq!(hdr.data_size, 757);
assert_eq!(hdr.crc, None);
}
#[test]
fn definition_message_header_test() {
let data = include_bytes!("../../tests/fixtures/Activity.fit");
let sl = &data[12..];
let (_, hdr) = message_header(sl).unwrap();
assert_eq!(hdr.contains_developer_data, false);
assert_eq!(hdr.local_message_number, 0);
assert_eq!(hdr.message_type, FitMessageType::Definition);
assert_eq!(hdr.time_offset, None);
}
#[test]
fn data_field_value_test_single_value() {
let data = [0x01, 0xFF];
let (rem, val) = data_field_value(&data, BaseType::UInt8, Endianness::Native, 1).unwrap();
match val {
Some(v) => assert_eq!(v, Value::UInt8(0x01)),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[0xFF]);
let (rem, val) = data_field_value(rem, BaseType::UInt8, Endianness::Native, 1).unwrap();
match val {
Some(_) => assert!(false, "None should be returned for invalid bytes."),
None => {}
}
assert_eq!(rem, &[]);
let (rem, val) = data_field_value(&data, BaseType::UInt16, Endianness::Big, 2).unwrap();
match val {
Some(v) => assert_eq!(v, Value::UInt16(0x01FF)),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[]);
let (rem, val) = data_field_value(&data, BaseType::UInt16, Endianness::Little, 2).unwrap();
match val {
Some(v) => assert_eq!(v, Value::UInt16(0xFF01)),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[]);
}
#[test]
fn data_field_value_test_array_value() {
let data = [0x00, 0x01, 0x02, 0x03, 0xFF];
let (rem, val) = data_field_value(&data, BaseType::UInt8, Endianness::Native, 4).unwrap();
match val {
Some(v) => assert_eq!(
v,
Value::Array(vec![
Value::UInt8(0x00),
Value::UInt8(0x01),
Value::UInt8(0x02),
Value::UInt8(0x03)
])
),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[0xFF]);
let (rem, val) = data_field_value(&data, BaseType::UInt8, Endianness::Native, 5).unwrap();
match val {
Some(_) => assert!(false, "None should be returned for invalid bytes."),
None => {}
}
assert_eq!(rem, &[]);
match val {
Some(_) => assert!(
false,
"None should be returned for array with an invalid size."
),
None => {}
}
assert_eq!(rem, &[]);
}
#[test]
fn data_field_value_test_string_value() {
let data = [71, 65, 82, 77, 73, 78, 0, 63, 255];
let (rem, val) = data_field_value(&data, BaseType::String, Endianness::Native, 8).unwrap();
match val {
Some(v) => assert_eq!(v, Value::String(String::from("GARMIN"))),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[0xFF]);
let data = [71, 195, 40, 77, 73, 78, 0, 63, 255];
let (rem, val) = data_field_value(&data, BaseType::String, Endianness::Native, 8).unwrap();
match val {
Some(_) => assert!(false, "None should be returned for invalid string."),
None => {}
}
assert_eq!(rem, &[0xFF]);
let data = [71, 65, 82, 77, 0, 195, 40, 63, 255];
let (rem, val) = data_field_value(&data, BaseType::String, Endianness::Native, 8).unwrap();
match val {
Some(v) => assert_eq!(v, Value::String(String::from("GARM"))),
None => assert!(false, "No value returned."),
}
assert_eq!(rem, &[0xFF]);
}
#[test]
#[should_panic(expected = "attempt to add with overflow")]
fn data_field_value_test_size_mismatch_array_value() {
let data: Vec<u8> = (0..=255).collect();
match data_field_value(&data, BaseType::UInt16, Endianness::Native, 255) {
Ok(..) => {}
Err(..) => {}
};
}
}