From 27a5967610cc98248ee6300637fba79bb8fd7f14 Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Tue, 20 Oct 2026 20:00:10 -0700 Subject: [PATCH 1/2] Revert "add safe byte codecs for the typed protocol payloads (#51)" This reverts commit 06cfade0fe704032d26aa2c2fab9e682484301df. --- CHANGELOG.md | 6 -- cli/Cargo.lock | 2 +- lib/protocol/Cargo.toml | 2 +- lib/protocol/src/frame.rs | 157 -------------------------------------- 4 files changed, 2 insertions(+), 165 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 019ccef..e1da5df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,6 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4. ## [Unreleased] -## [tinyboot-protocol 0.5.1] - 2026-10-20 - -### Added - -- `InfoData`, `EraseData` and `VerifyData` gain `from_bytes` and `to_bytes`, so a host reads and writes them from plain byte slices without going through the `Data` union. `EraseData` and `VerifyData` gain `SIZE` like `InfoData`. - ## [tinyboot-core 0.6.1] - 2026-10-20 ### Added diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 0e03949..b73b108 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "tinyboot-protocol" -version = "0.5.1" +version = "0.5.0" dependencies = [ "bitflags 2.11.1", "embedded-io", diff --git a/lib/protocol/Cargo.toml b/lib/protocol/Cargo.toml index 2901ad9..0b9a1ab 100644 --- a/lib/protocol/Cargo.toml +++ b/lib/protocol/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tinyboot-protocol" description = "Protocol implementation for tinyboot bootloader" -version = "0.5.1" +version = "0.5.0" edition.workspace = true repository.workspace = true license.workspace = true diff --git a/lib/protocol/src/frame.rs b/lib/protocol/src/frame.rs index 069b195..6ed0a89 100644 --- a/lib/protocol/src/frame.rs +++ b/lib/protocol/src/frame.rs @@ -40,38 +40,6 @@ pub struct InfoData { impl InfoData { /// Encoded size in bytes. pub const SIZE: u16 = core::mem::size_of::() as u16; - - /// Decode from the start of `bytes` (little-endian fields in declaration - /// order); `None` when `bytes` is shorter than [`SIZE`](Self::SIZE). - /// Bytes past `SIZE` are ignored, so a longer reply from a newer device - /// still decodes. - pub fn from_bytes(bytes: &[u8]) -> Option { - let b = bytes.get(..Self::SIZE as usize)?; - Some(Self { - capacity: u32::from_le_bytes([b[0], b[1], b[2], b[3]]), - erase_size: u16::from_le_bytes([b[4], b[5]]), - boot_version: u16::from_le_bytes([b[6], b[7]]), - app_version: u16::from_le_bytes([b[8], b[9]]), - mode: b[10], - boot_baud: b[11], - compat: u32::from_le_bytes([b[12], b[13], b[14], b[15]]), - max_payload: u16::from_le_bytes([b[16], b[17]]), - }) - } - - /// Encode as little-endian fields in declaration order. - pub fn to_bytes(&self) -> [u8; Self::SIZE as usize] { - let mut b = [0; Self::SIZE as usize]; - b[0..4].copy_from_slice(&{ self.capacity }.to_le_bytes()); - b[4..6].copy_from_slice(&{ self.erase_size }.to_le_bytes()); - b[6..8].copy_from_slice(&{ self.boot_version }.to_le_bytes()); - b[8..10].copy_from_slice(&{ self.app_version }.to_le_bytes()); - b[10] = self.mode; - b[11] = self.boot_baud; - b[12..16].copy_from_slice(&{ self.compat }.to_le_bytes()); - b[16..18].copy_from_slice(&{ self.max_payload }.to_le_bytes()); - b - } } /// Typed Erase request data. @@ -82,27 +50,6 @@ pub struct EraseData { pub byte_count: u16, } -impl EraseData { - /// Encoded size in bytes. - pub const SIZE: u16 = core::mem::size_of::() as u16; - - /// Decode from the start of `bytes` (little-endian fields in declaration - /// order); `None` when `bytes` is shorter than [`SIZE`](Self::SIZE). - /// Bytes past `SIZE` are ignored, so a longer reply from a newer device - /// still decodes. - pub fn from_bytes(bytes: &[u8]) -> Option { - let b = bytes.get(..Self::SIZE as usize)?; - Some(Self { - byte_count: u16::from_le_bytes([b[0], b[1]]), - }) - } - - /// Encode as little-endian fields in declaration order. - pub fn to_bytes(&self) -> [u8; Self::SIZE as usize] { - self.byte_count.to_le_bytes() - } -} - /// Typed Verify response data. #[repr(C)] #[derive(Clone, Copy)] @@ -111,27 +58,6 @@ pub struct VerifyData { pub crc: u16, } -impl VerifyData { - /// Encoded size in bytes. - pub const SIZE: u16 = core::mem::size_of::() as u16; - - /// Decode from the start of `bytes` (little-endian fields in declaration - /// order); `None` when `bytes` is shorter than [`SIZE`](Self::SIZE). - /// Bytes past `SIZE` are ignored, so a longer reply from a newer device - /// still decodes. - pub fn from_bytes(bytes: &[u8]) -> Option { - let b = bytes.get(..Self::SIZE as usize)?; - Some(Self { - crc: u16::from_le_bytes([b[0], b[1]]), - }) - } - - /// Encode as little-endian fields in declaration order. - pub fn to_bytes(&self) -> [u8; Self::SIZE as usize] { - self.crc.to_le_bytes() - } -} - /// Per-command flags occupying addr byte 3. /// /// Each command can define its own bitflags type as a union variant. @@ -522,89 +448,6 @@ mod tests { assert_eq!(InfoData::SIZE, 18); } - fn union_bytes(d: &Data) -> [u8; L] { - let mut b = [0; L]; - b.copy_from_slice(unsafe { &d.raw[..L] }); - b - } - - fn info() -> InfoData { - InfoData { - capacity: 0x0403_0201, - erase_size: 0x0605, - boot_version: 0x0807, - app_version: 0x0A09, - mode: 0x0B, - boot_baud: 0x0C, - compat: 0x100F_0E0D, - max_payload: 0x1211, - } - } - - fn assert_info_eq(a: &InfoData, b: &InfoData) { - assert_eq!({ a.capacity }, { b.capacity }); - assert_eq!({ a.erase_size }, { b.erase_size }); - assert_eq!({ a.boot_version }, { b.boot_version }); - assert_eq!({ a.app_version }, { b.app_version }); - assert_eq!(a.mode, b.mode); - assert_eq!(a.boot_baud, b.boot_baud); - assert_eq!({ a.compat }, { b.compat }); - assert_eq!({ a.max_payload }, { b.max_payload }); - } - - #[test] - fn info_bytes_match_union() { - let d = Data { info: info() }; - let raw = union_bytes::<{ InfoData::SIZE as usize }>(&d); - assert_info_eq(&InfoData::from_bytes(&raw).unwrap(), &info()); - assert_eq!(info().to_bytes(), raw); - } - - #[test] - fn info_from_bytes_length() { - let raw = info().to_bytes(); - assert!(InfoData::from_bytes(&raw[..raw.len() - 1]).is_none()); - let mut long = [0xEE; InfoData::SIZE as usize + 3]; - long[..raw.len()].copy_from_slice(&raw); - assert_info_eq(&InfoData::from_bytes(&long).unwrap(), &info()); - } - - #[test] - fn erase_bytes_match_union() { - let d = Data { - erase: EraseData { byte_count: 0x0201 }, - }; - let raw = union_bytes::<{ EraseData::SIZE as usize }>(&d); - assert_eq!(EraseData::from_bytes(&raw).unwrap().byte_count, 0x0201); - assert_eq!(EraseData { byte_count: 0x0201 }.to_bytes(), raw); - } - - #[test] - fn erase_from_bytes_length() { - assert_eq!(EraseData::SIZE, 2); - assert!(EraseData::from_bytes(&[0x01]).is_none()); - let long = [0x01, 0x02, 0xEE, 0xEE, 0xEE]; - assert_eq!(EraseData::from_bytes(&long).unwrap().byte_count, 0x0201); - } - - #[test] - fn verify_bytes_match_union() { - let d = Data { - verify: VerifyData { crc: 0x0201 }, - }; - let raw = union_bytes::<{ VerifyData::SIZE as usize }>(&d); - assert_eq!(VerifyData::from_bytes(&raw).unwrap().crc, 0x0201); - assert_eq!(VerifyData { crc: 0x0201 }.to_bytes(), raw); - } - - #[test] - fn verify_from_bytes_length() { - assert_eq!(VerifyData::SIZE, 2); - assert!(VerifyData::from_bytes(&[0x01]).is_none()); - let long = [0x01, 0x02, 0xEE, 0xEE, 0xEE]; - assert_eq!(VerifyData::from_bytes(&long).unwrap().crc, 0x0201); - } - #[test] fn large_payload_round_trip() { let data = [0x3C; 240]; From fedc6110dcb60a8fa6260efa9c9c76e8850c4a6c Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Tue, 20 Oct 2026 20:00:23 -0700 Subject: [PATCH 2/2] build tinyboot-protocol for little-endian targets only --- CHANGELOG.md | 6 ++++++ cli/Cargo.lock | 2 +- lib/protocol/Cargo.toml | 2 +- lib/protocol/src/lib.rs | 5 +++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1da5df..3e5c346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4. ## [Unreleased] +## [tinyboot-protocol 0.5.1] - 2026-10-20 + +### Changed + +- The crate builds for little-endian targets only. The typed payloads (`InfoData`, `EraseData`, `VerifyData`) are padding-free `repr(C)` structs whose in-memory bytes are their wire bytes, so a host may read them straight from a received buffer. Every tinyboot device and host so far is little-endian. + ## [tinyboot-core 0.6.1] - 2026-10-20 ### Added diff --git a/cli/Cargo.lock b/cli/Cargo.lock index b73b108..0e03949 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "tinyboot-protocol" -version = "0.5.0" +version = "0.5.1" dependencies = [ "bitflags 2.11.1", "embedded-io", diff --git a/lib/protocol/Cargo.toml b/lib/protocol/Cargo.toml index 0b9a1ab..2901ad9 100644 --- a/lib/protocol/Cargo.toml +++ b/lib/protocol/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tinyboot-protocol" description = "Protocol implementation for tinyboot bootloader" -version = "0.5.0" +version = "0.5.1" edition.workspace = true repository.workspace = true license.workspace = true diff --git a/lib/protocol/src/lib.rs b/lib/protocol/src/lib.rs index 0dc8053..c7a1a63 100644 --- a/lib/protocol/src/lib.rs +++ b/lib/protocol/src/lib.rs @@ -6,6 +6,11 @@ //! Defines the frame format, commands, status codes, and CRC used for //! host-device communication over UART / RS-485. +// Typed payloads are padding-free structs whose in-memory bytes are the wire +// bytes; a host may read them straight from a received buffer. +#[cfg(target_endian = "big")] +compile_error!("tinyboot-protocol supports little-endian targets only"); + /// CRC16-CCITT implementation. pub mod crc; /// Frame encoding, decoding, and typed payload access.