#![deny(unsafe_code)]
pub mod base_type;
pub mod crc;
pub mod datetime;
mod decoder;
mod definition;
pub mod dev_fields;
mod encoder;
mod error;
mod header;
pub mod output_stream;
pub mod profile;
mod raw_value;
mod record_header;
mod stream;
pub mod transforms;
mod typed_decoder;
mod value;
pub use base_type::BaseType;
pub use decoder::{Decoder, RawDevField, RawField, RawMessage};
pub use definition::{
DeveloperFieldDefinition, FieldDefinition, LocalDefinitions, MessageDefinition,
LOCAL_DEFINITION_SLOTS,
};
pub use dev_fields::{DevFieldInfo, DevFieldRegistry};
pub use encoder::{Encoder, EncoderBuilder};
pub use error::{FieldTooLargeKind, FitError};
pub use header::FileHeader;
pub use raw_value::RawValue;
pub use record_header::RecordHeader;
pub use stream::{ByteStream, Endian};
pub use transforms::decode_memo_glob;
#[cfg(feature = "chrono")]
pub use transforms::merge_heart_rates;
pub use typed_decoder::{DecoderBuilder, TransformOptions, TypedDecoder};
pub use value::{Field, FieldKind, Message, Value};
pub fn crc16(data: &[u8]) -> u16 {
crc::calculate(data)
}
pub fn is_fit(bytes: &[u8]) -> bool {
let Ok(header) = FileHeader::parse(bytes) else {
return false;
};
bytes.len() >= header.header_size as usize + 2
}
pub fn check_integrity(bytes: &[u8]) -> Result<(), FitError> {
let header = FileHeader::parse(bytes)?;
let total = header.total_file_size();
if bytes.len() < total {
return Err(FitError::TooShort {
expected: total,
actual: bytes.len(),
});
}
if let Some(stored) = header.header_crc {
if stored != 0 {
let calculated = crc::calculate(&bytes[..12]);
if stored != calculated {
return Err(FitError::HeaderCrcMismatch { stored, calculated });
}
}
}
let crc_offset = header.file_crc_offset();
let stored_file_crc = u16::from_le_bytes([bytes[crc_offset], bytes[crc_offset + 1]]);
let calculated_file_crc = crc::calculate(&bytes[..crc_offset]);
if stored_file_crc != calculated_file_crc {
return Err(FitError::FileCrcMismatch {
stored: stored_file_crc,
calculated: calculated_file_crc,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_fit_rejects_too_short() {
assert!(!is_fit(&[]));
assert!(!is_fit(&[14u8]));
}
#[test]
fn is_fit_rejects_bad_signature() {
let mut bytes = [0u8; 16];
bytes[0] = 14;
bytes[8..12].copy_from_slice(b"NOPE");
assert!(!is_fit(&bytes));
}
#[test]
fn is_fit_rejects_invalid_header_size_byte() {
let mut bytes = [0u8; 16];
bytes[0] = 16; bytes[8..12].copy_from_slice(b".FIT");
assert!(!is_fit(&bytes));
}
#[test]
fn is_fit_accepts_well_formed_14_byte() {
let mut bytes = [0u8; 16];
bytes[0] = 14;
bytes[8..12].copy_from_slice(b".FIT");
assert!(is_fit(&bytes));
}
}