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 pub header_size: u8,
36
37 pub protocol_version: u8,
39
40 pub profile_version: u16,
42
43 pub data_size: u32,
45
46 #[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 #[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 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 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}