fit_rust/protocol/
mod.rs

1mod consts;
2pub mod data_field;
3mod get_field_offset;
4mod get_field_scale;
5mod get_field_string_value;
6mod get_field_type;
7pub mod io;
8pub mod macros;
9pub mod message_type;
10pub mod value;
11
12use crate::protocol::consts::{
13    COMPRESSED_HEADER_LOCAL_MESSAGE_NUMBER_MASK, COMPRESSED_HEADER_MASK,
14    COMPRESSED_HEADER_TIME_OFFSET_MASK, CRC_TABLE, DEFINITION_HEADER_MASK, DEVELOPER_FIELDS_MASK,
15    FIELD_DEFINITION_BASE_ENDIAN, FIELD_DEFINITION_BASE_NUMBER, LOCAL_MESSAGE_NUMBER_MASK,
16};
17use crate::protocol::data_field::DataField;
18use crate::protocol::get_field_string_value::FieldType;
19use crate::protocol::message_type::MessageType;
20use binrw::{binrw, BinRead, BinResult, BinWrite, Endian};
21use std::fmt::Debug;
22use std::io::{Seek, Write};
23
24pub type MatchScaleFn = fn(usize) -> Option<f32>;
25pub type MatchOffsetFn = fn(usize) -> Option<i16>;
26pub type MatchFieldTypeFn = fn(usize) -> FieldType;
27
28#[derive(Debug, Clone)]
29#[binrw]
30#[brw(little)]
31pub struct FitHeader {
32    /// https://developer.garmin.com/fit/protocol/
33    /// Indicates the length of this file header including header size. Minimum size is 12.
34    /// This may be increased in future to add additional optional information
35    pub header_size: u8,
36
37    /// Protocol version number as provided in SDK
38    pub protocol_version: u8,
39
40    /// Profile version number as provided in SDK
41    pub profile_version: u16,
42
43    /// Length of the Data Records section in bytesDoes not include Header or CRC
44    pub data_size: u32,
45
46    /// ASCII values for “.FIT”. A FIT binary file opened with a text editor will contain a readable “.FIT” in the first line.
47    #[br(map = | x: [u8;4] | String::from_utf8_lossy(&x).to_string())]
48    #[bw(map = | _ | ".FIT".as_bytes())]
49    pub data_type: String,
50
51    /// Contains the value of the CRC (see CRC ) of Bytes 0 through 11, or may be set to 0x0000. This field is optional.
52    #[br(if(header_size == 14))]
53    pub crc: Option<u16>,
54}
55
56#[derive(Clone, Debug)]
57pub enum FitMessage {
58    Definition(FitDefinitionMessage),
59    Data(FitDataMessage),
60}
61
62#[derive(BinWrite, Debug, Clone, PartialEq)]
63#[bw(little)]
64pub struct FitDefinitionMessage {
65    pub header: FitMessageHeader,
66    pub data: DefinitionMessage,
67}
68
69#[derive(Clone, Debug, PartialEq)]
70#[binrw]
71#[br(import(dev_fields: bool))]
72#[bw(little)]
73pub struct DefinitionMessage {
74    pub reserved: u8,
75
76    #[br(map = DefinitionMessage::read_endian)]
77    #[bw(map = DefinitionMessage::write_endian)]
78    pub endian: Endian,
79
80    #[br(is_little = (endian == Endian::Little))]
81    pub global_message_number: u16,
82
83    pub num_fields: u8,
84
85    #[br(count = num_fields)]
86    pub fields: Vec<FieldDefinition>,
87
88    #[br(if(dev_fields))]
89    pub dev_num_fields: Option<u8>,
90
91    #[br(if(dev_fields), count = dev_num_fields.unwrap_or_default())]
92    pub dev_fields: Option<Vec<DevFieldDefinition>>,
93}
94
95impl DefinitionMessage {
96    fn read_endian(x: u8) -> Endian {
97        if x == 0x0 {
98            return Endian::Little;
99        }
100        Endian::Big
101    }
102    fn write_endian(b: &Endian) -> u8 {
103        match b {
104            Endian::Big => 0x1,
105            Endian::Little => 0x0,
106        }
107    }
108
109    pub fn new(
110        is_big: bool,
111        num_fields: u8,
112        fields: Vec<FieldDefinition>,
113        msg_type: MessageType,
114    ) -> Self {
115        let endian = match is_big {
116            true => Endian::Big,
117            false => Endian::Little,
118        };
119        Self {
120            reserved: 0,
121            endian,
122            global_message_number: msg_type.to_primitive(),
123            num_fields,
124            fields,
125            dev_num_fields: None,
126            dev_fields: None,
127        }
128    }
129}
130
131#[derive(Debug, Clone, PartialEq)]
132pub struct FitDataMessage {
133    pub header: FitMessageHeader,
134    pub data: DataMessage,
135}
136
137#[derive(Clone, Debug, PartialEq, BinRead)]
138#[br(import(definition: &FitDefinitionMessage))]
139pub struct DataMessage {
140    #[br(parse_with = message_type::parse_message_type, args(definition.data.global_message_number))]
141    pub message_type: MessageType,
142
143    #[br(parse_with = DataField::parse_data_field, args(message_type, &definition.data.fields), is_little = (definition.data.endian == Endian::Little))]
144    pub values: Vec<DataField>,
145}
146
147impl DataMessage {
148    pub fn write<W>(&self, writer: &mut W, def_msg: &DefinitionMessage) -> BinResult<()>
149    where
150        W: Write + Seek,
151    {
152        DataField::write_data_field(
153            &self.values,
154            writer,
155            Endian::Little,
156            (self.message_type, def_msg),
157        )
158    }
159}
160
161#[derive(Debug, Copy, Clone, PartialEq)]
162#[binrw]
163pub struct FieldDefinition {
164    pub definition_number: u8,
165
166    pub size: u8,
167
168    pub base_type: FieldDefBaseType,
169}
170
171impl FieldDefinition {
172    pub fn new(def_num: u8, size: u8, is_big: bool, val: u8) -> Self {
173        Self {
174            definition_number: def_num,
175            size,
176            base_type: FieldDefBaseType::new(is_big, val),
177        }
178    }
179}
180
181#[derive(Debug, Copy, Clone, PartialEq)]
182#[binrw]
183#[br(map = FieldDefBaseType::from_bytes)]
184#[bw(map = FieldDefBaseType::to_bytes)]
185pub struct FieldDefBaseType {
186    pub val: u8,
187
188    pub endian: Endian,
189}
190
191impl FieldDefBaseType {
192    fn to_bytes(&self) -> u8 {
193        if self.endian == Endian::Little {
194            self.val
195        } else {
196            self.val | FIELD_DEFINITION_BASE_ENDIAN
197        }
198    }
199
200    fn from_bytes(x: u8) -> FieldDefBaseType {
201        let mut endian = Endian::Little;
202        if x & FIELD_DEFINITION_BASE_ENDIAN == FIELD_DEFINITION_BASE_ENDIAN {
203            endian = Endian::Big;
204        }
205        Self {
206            val: x & FIELD_DEFINITION_BASE_NUMBER,
207            endian,
208        }
209    }
210
211    pub fn new(is_big: bool, val: u8) -> Self {
212        let endian = match is_big {
213            true => Endian::Big,
214            false => Endian::Little,
215        };
216        Self { val, endian }
217    }
218}
219
220#[derive(Debug, Copy, Clone, PartialEq)]
221#[binrw]
222pub struct DevFieldDefinition {
223    pub field_number: u8,
224    pub size: u8,
225    pub dev_data_index: u8,
226}
227
228#[derive(Clone, Debug, PartialEq)]
229#[binrw]
230#[br(map = FitMessageHeader::from_bytes)]
231#[bw(map = FitMessageHeader::to_bytes)]
232pub struct FitMessageHeader {
233    pub compressed_header: bool,
234    pub definition: bool,
235    pub dev_fields: bool,
236    pub local_num: u8,
237    pub time_offset: Option<u8>,
238}
239
240impl FitMessageHeader {
241    fn to_bytes(&self) -> u8 {
242        if self.compressed_header {
243            let mut x = self.time_offset.unwrap_or_default();
244            x |= self.local_num << 5;
245            x |= COMPRESSED_HEADER_MASK;
246            x
247        } else {
248            let mut x = self.local_num;
249            if self.definition {
250                x |= DEFINITION_HEADER_MASK;
251            }
252            if self.dev_fields {
253                x |= DEVELOPER_FIELDS_MASK;
254            }
255            x
256        }
257    }
258
259    fn from_bytes(x: u8) -> FitMessageHeader {
260        if (x & COMPRESSED_HEADER_MASK) == COMPRESSED_HEADER_MASK {
261            Self {
262                compressed_header: true,
263                definition: false,
264                dev_fields: false,
265                local_num: (x & COMPRESSED_HEADER_LOCAL_MESSAGE_NUMBER_MASK) >> 5,
266                time_offset: Some(x & COMPRESSED_HEADER_TIME_OFFSET_MASK),
267            }
268        } else {
269            Self {
270                compressed_header: false,
271                definition: x & DEFINITION_HEADER_MASK == DEFINITION_HEADER_MASK,
272                dev_fields: x & DEVELOPER_FIELDS_MASK == DEVELOPER_FIELDS_MASK,
273                local_num: x & LOCAL_MESSAGE_NUMBER_MASK,
274                time_offset: None,
275            }
276        }
277    }
278
279    pub fn new(is_def: bool, local_num: u8) -> Self {
280        Self {
281            compressed_header: false,
282            definition: is_def,
283            dev_fields: false,
284            local_num,
285            time_offset: None,
286        }
287    }
288}
289
290pub(crate) fn fit_crc_get16(crc: u16, byte: u8) -> u16 {
291    // Compute checksum of lower four bits of byte
292    let mut tmp = CRC_TABLE[(crc & 0xF) as usize];
293    let mut crc = (crc >> 4) & 0x0FFF;
294    crc = crc ^ tmp ^ CRC_TABLE[(byte & 0xF) as usize];
295
296    // Now compute checksum of upper four bits of byte
297    tmp = CRC_TABLE[(crc & 0xF) as usize];
298    crc = (crc >> 4) & 0x0FFF;
299    crc = crc ^ tmp ^ CRC_TABLE[((byte >> 4) & 0xF) as usize];
300
301    crc
302}
303
304pub(crate) fn calculate_fit_crc(header: &[u8]) -> u16 {
305    let mut crc: u16 = 0;
306    for &byte in header {
307        crc = fit_crc_get16(crc, byte);
308    }
309    crc
310}
311
312#[cfg(test)]
313mod tests {
314    use crate::protocol::calculate_fit_crc;
315
316    #[test]
317    fn fit_crc_get16_test() {
318        let example_header: [u8; 12] = [
319            0x0E, 0x10, 0x90, 0x05, 0xB3, 0x10, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54,
320        ];
321        let header_crc = calculate_fit_crc(&example_header);
322        println!("The CRC of the FIT header is: 0x{:X}", header_crc);
323        assert_eq!(0x800, header_crc);
324    }
325}