1#![no_std]
2#![warn(missing_docs)]
3
4pub mod crc;
11pub mod frame;
13pub(crate) mod sync;
14
15pub use frame::{Data, EraseData, InfoData, MAX_PAYLOAD, VerifyData};
16
17pub const fn pack_version(major: u8, minor: u8, patch: u8) -> u16 {
23 ((major as u16) << 11) | ((minor as u16) << 6) | (patch as u16)
24}
25
26pub const fn unpack_version(v: u16) -> (u8, u8, u8) {
28 let major = (v >> 11) as u8 & 0x1F;
29 let minor = (v >> 6) as u8 & 0x1F;
30 let patch = v as u8 & 0x3F;
31 (major, minor, patch)
32}
33
34pub const fn const_parse_u8(s: &str) -> u8 {
37 let bytes = s.as_bytes();
38 let mut i = 0;
39 let mut result: u16 = 0;
40 while i < bytes.len() {
41 let d = bytes[i];
42 assert!(d >= b'0' && d <= b'9', "non-digit in version string");
43 result = result * 10 + (d - b'0') as u16;
44 i += 1;
45 }
46 assert!(result <= 255, "version component exceeds u8");
47 result as u8
48}
49
50#[macro_export]
55macro_rules! pkg_version {
56 () => {
57 $crate::pack_version(
58 $crate::const_parse_u8(env!("CARGO_PKG_VERSION_MAJOR")),
59 $crate::const_parse_u8(env!("CARGO_PKG_VERSION_MINOR")),
60 $crate::const_parse_u8(env!("CARGO_PKG_VERSION_PATCH")),
61 )
62 };
63}
64
65#[repr(u8)]
67#[derive(Debug, Clone, Copy, PartialEq)]
68#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69pub enum Cmd {
70 Info = 0x00,
72 Erase = 0x01,
74 Write = 0x02,
76 Verify = 0x03,
78 Reset = 0x04,
80}
81
82impl Cmd {
83 pub fn is_valid(b: u8) -> bool {
85 b <= 0x04
86 }
87}
88
89#[repr(u8)]
91#[derive(Debug, Clone, Copy, PartialEq)]
92#[cfg_attr(feature = "defmt", derive(defmt::Format))]
93pub enum Status {
94 Request = 0x00,
96 Ok = 0x01,
98 WriteError = 0x02,
100 CrcMismatch = 0x03,
102 AddrOutOfBounds = 0x04,
104 Unsupported = 0x05,
106 PayloadOverflow = 0x06,
108}
109
110impl Status {
111 pub fn is_valid(b: u8) -> bool {
113 b <= 0x06
114 }
115}
116
117#[derive(Debug, PartialEq)]
123pub struct ReadError;
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn cmd_is_valid() {
131 assert!(Cmd::is_valid(Cmd::Info as u8));
132 assert!(Cmd::is_valid(Cmd::Reset as u8));
133 assert!(!Cmd::is_valid(0x05));
134 assert!(!Cmd::is_valid(0xFF));
135 }
136
137 #[test]
138 fn status_is_valid() {
139 assert!(Status::is_valid(Status::Request as u8));
140 assert!(Status::is_valid(Status::Unsupported as u8));
141 assert!(Status::is_valid(Status::PayloadOverflow as u8));
142 assert!(!Status::is_valid(0x07));
143 assert!(!Status::is_valid(0xFF));
144 }
145
146 #[test]
147 fn pack_unpack_round_trip() {
148 assert_eq!(unpack_version(pack_version(0, 0, 1)), (0, 0, 1));
149 assert_eq!(unpack_version(pack_version(1, 2, 3)), (1, 2, 3));
150 assert_eq!(unpack_version(pack_version(31, 31, 63)), (31, 31, 63));
151 assert_eq!(pack_version(0, 0, 0), 0);
152 }
153
154 #[test]
155 fn erased_flash_sentinel() {
156 let (m, n, p) = unpack_version(0xFFFF);
158 assert_eq!((m, n, p), (31, 31, 63));
159 }
160
161 #[test]
162 fn pkg_version_macro() {
163 let v = pkg_version!();
164 let expected = pack_version(
165 const_parse_u8(env!("CARGO_PKG_VERSION_MAJOR")),
166 const_parse_u8(env!("CARGO_PKG_VERSION_MINOR")),
167 const_parse_u8(env!("CARGO_PKG_VERSION_PATCH")),
168 );
169 assert_eq!(v, expected);
170 }
171}