Skip to main content

fit/
definition.rs

1//! Definition messages and the 16-slot local-definition table.
2//!
3//! A Definition message declares the wire schema for subsequent Data messages
4//! that share the same local message number. Layout (bytes after the
5//! 1-byte record header):
6//!
7//! ```text
8//!   Offset  Size  Field
9//!   ─────────────────────────────────────────
10//!      0     1    Reserved (always 0x00)
11//!      1     1    Architecture (0 = LE, 1 = BE)
12//!      2     2    Global Message Number (per architecture)
13//!      4     1    Field Count N
14//!      5+   3×N   Field definitions: [field_def_num, size, base_type_byte]
15//!   [if header bit 5 set: developer fields]
16//!     ...    1    Developer Field Count M
17//!     ...   3×M   Dev fields: [field_def_num, size, developer_data_index]
18//! ```
19//!
20//! Reference: `guide/fit_binary_learning_notes.md` §2.2 + §2.4.
21
22use std::array;
23
24use smallvec::SmallVec;
25
26use crate::base_type::BaseType;
27use crate::error::FitError;
28use crate::stream::{ByteStream, Endian};
29
30/// Inline capacity for `MessageDefinition::fields` — covers virtually every
31/// real-world FIT message without spilling to the heap. Wire allows up to 255
32/// fields (u8 count) but typical messages use < 30.
33pub const FIELDS_INLINE: usize = 48;
34/// Inline capacity for `MessageDefinition::dev_fields`.
35pub const DEV_FIELDS_INLINE: usize = 8;
36
37/// Maximum number of simultaneously valid local message definitions.
38pub const LOCAL_DEFINITION_SLOTS: usize = 16;
39
40/// One field's wire-level shape inside a Definition message.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct FieldDefinition {
43    /// The Profile-level field identifier.
44    pub field_def_num: u8,
45    /// Total byte width on the wire. May exceed the BaseType element size
46    /// when the field is an array (e.g. `size = 4` with `BaseType::UInt16`
47    /// means a length-2 array of u16).
48    pub size: u8,
49    /// Decoded base type (after masking off the endian flag).
50    pub base_type: BaseType,
51    /// Raw base-type byte preserved verbatim — useful when round-tripping
52    /// in the encoder (M8) and for diagnostics.
53    pub base_type_byte: u8,
54}
55
56impl FieldDefinition {
57    /// Number of array elements implied by `size` and the base-type stride.
58    /// Returns 0 when `size` is not a multiple of the element size (malformed
59    /// definition); callers may treat this as an error or skip the field.
60    pub fn element_count(&self) -> usize {
61        let stride = self.base_type.element_size();
62        if stride == 0 || (self.size as usize) % stride != 0 {
63            0
64        } else {
65            (self.size as usize) / stride
66        }
67    }
68}
69
70/// One developer-field reference inside a Definition.
71///
72/// The actual schema (name, units, base type, scale, …) is registered
73/// dynamically via the `developer_data_id` (mesg 207) and `field_description`
74/// (mesg 206) messages. A decoder must build an
75/// `(developer_data_index, field_def_num) → FieldDescription` lookup before
76/// decoding Data messages that reference dev fields.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct DeveloperFieldDefinition {
79    /// Field definition number within the developer data schema.
80    pub field_def_num: u8,
81    /// Total byte width on the wire.
82    pub size: u8,
83    /// Index into the developer data ID table identifying the data source.
84    pub developer_data_index: u8,
85}
86
87/// A complete Definition message.
88///
89/// `fields` and `dev_fields` use [`SmallVec`] with inline capacity tuned to
90/// fit typical FIT messages on the stack. The whole struct is ~256B inline
91/// so `LocalDefinitions` (16 slots) sits in ~4KB — well within L1d. Cloning
92/// is a `memcpy` with no heap allocation in the common case.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct MessageDefinition {
95    /// Profile-level (global) message number — the index into `MesgNum`.
96    pub global_mesg_num: u16,
97    /// Endianness for multi-byte fields in subsequent Data messages.
98    pub endian: Endian,
99    /// Standard fields, in wire order.
100    pub fields: SmallVec<[FieldDefinition; FIELDS_INLINE]>,
101    /// Developer fields, in wire order. Empty unless the definition's record
102    /// header had the dev-data bit set.
103    pub dev_fields: SmallVec<[DeveloperFieldDefinition; DEV_FIELDS_INLINE]>,
104    /// Reserved byte preserved verbatim (always `0x00` for compliant files,
105    /// but kept so the encoder can reproduce non-compliant input bit-for-bit).
106    pub reserved: u8,
107}
108
109impl MessageDefinition {
110    /// Parse the body of a Definition message from `stream`.
111    ///
112    /// Assumes the 1-byte record header has already been consumed and that
113    /// `has_dev_data` reflects bit 5 of that header.
114    pub fn parse(stream: &mut ByteStream<'_>, has_dev_data: bool) -> Result<Self, FitError> {
115        let reserved = stream.read_u8()?;
116        let architecture = stream.read_u8()?;
117        let endian = if architecture == 0 {
118            Endian::Little
119        } else {
120            Endian::Big
121        };
122
123        let global_mesg_num = stream.read_u16(endian)?;
124        let field_count = stream.read_u8()? as usize;
125
126        let mut fields: SmallVec<[FieldDefinition; FIELDS_INLINE]> =
127            SmallVec::with_capacity(field_count);
128        for _ in 0..field_count {
129            let field_def_num = stream.read_u8()?;
130            let size = stream.read_u8()?;
131            let base_type_byte = stream.read_u8()?;
132            let base_type = BaseType::from_byte(base_type_byte)?;
133            // Eagerly validate that the wire size is compatible with the
134            // base type's element stride. STRING and BYTE are stride-1 by
135            // construction so any non-zero size is allowed; numeric types
136            // must have `size` be a positive multiple of their element size.
137            let stride = base_type.element_size();
138            if !base_type.is_string()
139                && !base_type.is_byte()
140                && (size == 0 || (size as usize) % stride != 0)
141            {
142                return Err(FitError::MalformedField {
143                    field_def_num,
144                    size,
145                    element_size: stride,
146                });
147            }
148            fields.push(FieldDefinition {
149                field_def_num,
150                size,
151                base_type,
152                base_type_byte,
153            });
154        }
155
156        let mut dev_fields: SmallVec<[DeveloperFieldDefinition; DEV_FIELDS_INLINE]> =
157            SmallVec::new();
158        if has_dev_data {
159            let dev_count = stream.read_u8()? as usize;
160            dev_fields.reserve_exact(dev_count);
161            for _ in 0..dev_count {
162                let field_def_num = stream.read_u8()?;
163                let size = stream.read_u8()?;
164                let developer_data_index = stream.read_u8()?;
165                dev_fields.push(DeveloperFieldDefinition {
166                    field_def_num,
167                    size,
168                    developer_data_index,
169                });
170            }
171        }
172
173        Ok(Self {
174            global_mesg_num,
175            endian,
176            fields,
177            dev_fields,
178            reserved,
179        })
180    }
181
182    /// Total bytes consumed by a single Data message (excluding its 1-byte
183    /// header) keyed by this definition.
184    pub fn data_size(&self) -> usize {
185        let core: usize = self.fields.iter().map(|f| f.size as usize).sum();
186        let dev: usize = self.dev_fields.iter().map(|f| f.size as usize).sum();
187        core + dev
188    }
189
190    /// Look up a standard field by its definition number.
191    pub fn field(&self, field_def_num: u8) -> Option<&FieldDefinition> {
192        self.fields
193            .iter()
194            .find(|f| f.field_def_num == field_def_num)
195    }
196}
197
198/// The 16-slot local-definition table.
199///
200/// Each Definition message stores itself at index `local_mesg_num` (0..=15),
201/// overwriting any previous occupant. Each Data message looks up its slot
202/// to learn how to parse its bytes.
203///
204/// At the start of a new chained FIT file (multi-FIT) the table must be
205/// fully cleared via [`Self::clear`].
206#[derive(Debug, Default)]
207pub struct LocalDefinitions {
208    slots: [Option<MessageDefinition>; LOCAL_DEFINITION_SLOTS],
209}
210
211impl LocalDefinitions {
212    /// Create an empty local-definition table.
213    pub fn new() -> Self {
214        Self {
215            slots: array::from_fn(|_| None),
216        }
217    }
218
219    /// Borrow the definition stored in `local_mesg_num`'s slot, if any.
220    pub fn get(&self, local_mesg_num: u8) -> Option<&MessageDefinition> {
221        self.slots
222            .get(local_mesg_num as usize)
223            .and_then(Option::as_ref)
224    }
225
226    /// Borrow or error with [`FitError::UndefinedLocalMesgNum`].
227    pub fn require(&self, local_mesg_num: u8) -> Result<&MessageDefinition, FitError> {
228        self.get(local_mesg_num)
229            .ok_or(FitError::UndefinedLocalMesgNum(local_mesg_num))
230    }
231
232    /// Install a definition, overwriting any prior occupant.
233    pub fn set(&mut self, local_mesg_num: u8, def: MessageDefinition) {
234        if let Some(slot) = self.slots.get_mut(local_mesg_num as usize) {
235            *slot = Some(def);
236        }
237    }
238
239    /// Drop every slot. Required at chained-FIT boundaries.
240    pub fn clear(&mut self) {
241        for slot in self.slots.iter_mut() {
242            *slot = None;
243        }
244    }
245
246    /// Number of currently-occupied slots (≤ 16).
247    pub fn occupied(&self) -> usize {
248        self.slots.iter().filter(|s| s.is_some()).count()
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    /// Hand-written Definition: file_id (mesg_num=0), 5 fields, little-endian.
257    fn sample_le_definition_bytes() -> Vec<u8> {
258        // After the record header (not included here):
259        //   reserved=0x00, architecture=0x00 (LE), global_mesg_num=0x0000 (LE),
260        //   field_count=5, then 5 × {fdn, size, base_type}.
261        // Fields are made-up but match valid base-type codes.
262        vec![
263            0x00, 0x00, 0x00, 0x00, 0x05, // reserved, arch, mesg_num LE, field_count
264            0x00, 0x01, 0x00, // type: fdn=0, size=1, base=Enum
265            0x01, 0x02, 0x84, // manufacturer: fdn=1, size=2, base=UInt16 + endian flag
266            0x02, 0x02, 0x84, // product: fdn=2, size=2, base=UInt16 + endian flag
267            0x03, 0x04, 0x86, // time_created: fdn=3, size=4, base=UInt32 + endian flag
268            0x04, 0x04, 0x8C, // serial_number: fdn=4, size=4, base=UInt32z + endian flag
269        ]
270    }
271
272    #[test]
273    fn parses_canonical_le_definition() {
274        let bytes = sample_le_definition_bytes();
275        let mut stream = ByteStream::new(&bytes);
276        let def = MessageDefinition::parse(&mut stream, false).unwrap();
277
278        assert_eq!(def.global_mesg_num, 0);
279        assert_eq!(def.endian, Endian::Little);
280        assert_eq!(def.reserved, 0);
281        assert_eq!(def.fields.len(), 5);
282        assert!(def.dev_fields.is_empty());
283
284        // Field 0: type
285        assert_eq!(def.fields[0].field_def_num, 0);
286        assert_eq!(def.fields[0].size, 1);
287        assert_eq!(def.fields[0].base_type, BaseType::Enum);
288
289        // Field 1: manufacturer (raw byte 0x84 → endian flag set, code 0x04 → UInt16)
290        assert_eq!(def.fields[1].base_type, BaseType::UInt16);
291        assert!(BaseType::endian_flag_set(def.fields[1].base_type_byte));
292
293        // Field 4: serial_number (uint32z)
294        assert_eq!(def.fields[4].base_type, BaseType::UInt32z);
295        assert_eq!(def.fields[4].element_count(), 1);
296
297        assert_eq!(def.data_size(), 1 + 2 + 2 + 4 + 4);
298    }
299
300    #[test]
301    fn parses_big_endian_definition() {
302        // Same as LE but architecture = 0x01 and global_mesg_num bytes flipped.
303        let bytes = vec![
304            0x00, 0x01, 0x00, 0x14, 0x01, // arch=BE, mesg_num=0x0014 (record), field_count=1
305            253, 0x04, 0x86, // timestamp: fdn=253, size=4, base=UInt32 (BE flag)
306        ];
307        let mut stream = ByteStream::new(&bytes);
308        let def = MessageDefinition::parse(&mut stream, false).unwrap();
309        assert_eq!(def.endian, Endian::Big);
310        assert_eq!(def.global_mesg_num, 20);
311        assert_eq!(def.fields.len(), 1);
312        assert_eq!(def.fields[0].field_def_num, 253);
313        assert_eq!(def.fields[0].base_type, BaseType::UInt32);
314    }
315
316    #[test]
317    fn parses_definition_with_dev_data() {
318        let mut bytes = sample_le_definition_bytes();
319        bytes.extend_from_slice(&[
320            0x02, // dev_count = 2
321            0x00, 0x04, 0x00, // dev field 0: fdn=0, size=4, dev_data_index=0
322            0x05, 0x02, 0x01, // dev field 1: fdn=5, size=2, dev_data_index=1
323        ]);
324        let mut stream = ByteStream::new(&bytes);
325        let def = MessageDefinition::parse(&mut stream, /* has_dev_data = */ true).unwrap();
326
327        assert_eq!(def.dev_fields.len(), 2);
328        assert_eq!(def.dev_fields[0].field_def_num, 0);
329        assert_eq!(def.dev_fields[0].developer_data_index, 0);
330        assert_eq!(def.dev_fields[1].field_def_num, 5);
331        assert_eq!(def.dev_fields[1].developer_data_index, 1);
332
333        // data_size includes both standard and dev fields.
334        assert_eq!(def.data_size(), (1 + 2 + 2 + 4 + 4) + (4 + 2));
335    }
336
337    #[test]
338    fn rejects_unknown_base_type() {
339        let bytes = vec![
340            0x00, 0x00, 0x00, 0x00, 0x01, //
341            0x00, 0x01, 0x1A, // bad base type code 0x1A
342        ];
343        let mut stream = ByteStream::new(&bytes);
344        let err = MessageDefinition::parse(&mut stream, false).unwrap_err();
345        assert!(matches!(err, FitError::UnknownBaseType(_, _)));
346    }
347
348    #[test]
349    fn local_definitions_set_get_clear() {
350        let mut tbl = LocalDefinitions::new();
351        assert_eq!(tbl.occupied(), 0);
352        assert!(tbl.get(0).is_none());
353
354        let bytes = sample_le_definition_bytes();
355        let mut stream = ByteStream::new(&bytes);
356        let def = MessageDefinition::parse(&mut stream, false).unwrap();
357        tbl.set(3, def.clone());
358        assert_eq!(tbl.occupied(), 1);
359        assert_eq!(tbl.get(3).unwrap().global_mesg_num, 0);
360
361        // Overwriting same slot is allowed.
362        tbl.set(3, def);
363        assert_eq!(tbl.occupied(), 1);
364
365        // Out-of-range writes are silently ignored (mask in record_header keeps us safe).
366        tbl.set(
367            99,
368            MessageDefinition {
369                global_mesg_num: 0,
370                endian: Endian::Little,
371                fields: SmallVec::new(),
372                dev_fields: SmallVec::new(),
373                reserved: 0,
374            },
375        );
376        assert_eq!(tbl.occupied(), 1);
377
378        tbl.clear();
379        assert_eq!(tbl.occupied(), 0);
380    }
381
382    #[test]
383    fn require_returns_useful_error() {
384        let tbl = LocalDefinitions::new();
385        let err = tbl.require(7).unwrap_err();
386        assert_eq!(err, FitError::UndefinedLocalMesgNum(7));
387    }
388
389    #[test]
390    fn element_count_handles_arrays_and_misalignment() {
391        let single = FieldDefinition {
392            field_def_num: 0,
393            size: 4,
394            base_type: BaseType::UInt32,
395            base_type_byte: 0x06,
396        };
397        assert_eq!(single.element_count(), 1);
398
399        let array = FieldDefinition {
400            field_def_num: 0,
401            size: 8,
402            base_type: BaseType::UInt16,
403            base_type_byte: 0x04,
404        };
405        assert_eq!(array.element_count(), 4);
406
407        // size not a multiple of element size → 0 (signals malformed definition)
408        let bad = FieldDefinition {
409            field_def_num: 0,
410            size: 3,
411            base_type: BaseType::UInt32,
412            base_type_byte: 0x06,
413        };
414        assert_eq!(bad.element_count(), 0);
415    }
416}