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}