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