Skip to main content

fit/
error.rs

1//! Error types for the FIT crate.
2//!
3//! All public APIs return `Result<_, FitError>`. The variants are intentionally
4//! specific so callers can distinguish *why* a file was rejected (truncated vs.
5//! wrong signature vs. CRC mismatch) without parsing error message strings.
6
7use thiserror::Error;
8
9/// Anything that can go wrong while reading a FIT file.
10#[derive(Debug, Error, PartialEq, Eq)]
11pub enum FitError {
12    /// The byte slice is shorter than required for the operation in progress.
13    #[error("file too short: needed at least {expected} bytes, got {actual}")]
14    TooShort {
15        /// Minimum number of bytes required.
16        expected: usize,
17        /// Actual number of bytes available.
18        actual: usize,
19    },
20
21    /// The header size byte (offset 0) is neither 12 nor 14.
22    #[error("invalid header size byte: expected 12 or 14, got {0}")]
23    InvalidHeaderSize(u8),
24
25    /// The 4-byte signature at offset 8..12 is not the ASCII string `".FIT"`.
26    #[error("invalid FIT signature at offset 8..12: got {0:?}")]
27    InvalidSignature([u8; 4]),
28
29    /// The 14-byte header carries a non-zero CRC that does not match the
30    /// CRC computed over its own first 12 bytes. Per protocol, a stored CRC
31    /// of `0x0000` is treated as "skip verification" (legacy firmware) and
32    /// will *not* trigger this error.
33    #[error("header CRC mismatch: stored 0x{stored:04X}, calculated 0x{calculated:04X}")]
34    HeaderCrcMismatch {
35        /// CRC value stored in the header.
36        stored: u16,
37        /// CRC computed over the header bytes.
38        calculated: u16,
39    },
40
41    /// The two trailing CRC bytes do not match the CRC computed over the
42    /// header + data region preceding them.
43    #[error("file CRC mismatch: stored 0x{stored:04X}, calculated 0x{calculated:04X}")]
44    FileCrcMismatch {
45        /// CRC value stored in the file trailer.
46        stored: u16,
47        /// CRC computed over header + data region.
48        calculated: u16,
49    },
50
51    /// Attempted to read past the end of the byte stream.
52    #[error("unexpected end of stream at offset {offset}")]
53    UnexpectedEof {
54        /// Byte offset where the read failed.
55        offset: usize,
56    },
57
58    /// A field-definition byte's type code (after `& 0x1F`) does not match
59    /// any of the 17 known base types.
60    #[error("unknown base type byte 0x{0:02X} (after masking, code 0x{1:02X})")]
61    UnknownBaseType(u8, u8),
62
63    /// A Data message references a local message number that has not been
64    /// declared by a preceding Definition message.
65    #[error("data message uses undefined local message number {0}")]
66    UndefinedLocalMesgNum(u8),
67
68    /// The reserved byte at offset 1 of a Definition message was non-zero.
69    /// We don't error on this in production parsing (just a warning), but the
70    /// variant exists for strict-mode tooling.
71    #[error("non-zero reserved byte in definition message: 0x{0:02X}")]
72    NonZeroReserved(u8),
73
74    /// A field's wire size is not a multiple of its base type's element size,
75    /// so the byte stream cannot be cleanly partitioned into elements.
76    #[error(
77        "malformed field {field_def_num}: size {size} not a multiple of base type element size {element_size}"
78    )]
79    MalformedField {
80        /// Field definition number that failed validation.
81        field_def_num: u8,
82        /// Wire size in bytes.
83        size: u8,
84        /// Base type element size in bytes.
85        element_size: usize,
86    },
87
88    /// The encoder was asked to emit more than 16 distinct global message
89    /// numbers in a single FIT segment. Local definition slots are limited to
90    /// 4 bits (0..=15) by the protocol; LRU eviction will arrive in M9.
91    #[error(
92        "encoder: too many distinct mesg_nums ({0}); only 16 local definition slots are available"
93    )]
94    TooManyLocalDefinitions(usize),
95
96    /// A field's wire size or count exceeds 255 bytes (u8 limit).
97    #[error("encoder: {kind:?} of size {size} exceeds 255 byte wire limit")]
98    FieldTooLarge {
99        /// What kind of object exceeded the limit.
100        kind: FieldTooLargeKind,
101        /// The actual (oversized) value.
102        size: usize,
103    },
104}
105
106/// Tag identifying which protocol field exceeded its 255-byte u8 cap. Kept
107/// separate so [`FitError`] stays heap-free.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum FieldTooLargeKind {
110    /// A UTF-8 string field whose null-terminated bytes don't fit in u8.
111    String,
112    /// A `Byte` field whose raw byte array doesn't fit in u8.
113    ByteArray,
114    /// A numeric / typed array whose element count overflows u8.
115    Array,
116    /// A Definition message has more than 255 standard fields.
117    FieldList,
118    /// A Definition message has more than 255 developer fields.
119    DevFieldList,
120}