bootloader_message/
lib.rs1use anyhow::{Error, anyhow};
6use bstr::ByteSlice as _;
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9#[repr(C)]
11#[derive(Copy, Clone, KnownLayout, FromBytes, IntoBytes, Immutable)]
12pub struct BootloaderMessageRaw {
13 command: [u8; 32],
14 status: [u8; 32],
15 recovery: [u8; 768],
16 _stage: [u8; 32],
17 _reserved: [u8; 1184],
18}
19
20impl Default for BootloaderMessageRaw {
21 fn default() -> Self {
22 Self {
23 command: [0; _],
24 status: [0; _],
25 recovery: [0; _],
26 _stage: [0; _],
27 _reserved: [0; _],
28 }
29 }
30}
31
32#[derive(Debug, Clone, Default)]
34pub struct BootloaderMessage {
35 command: String,
36 status: String,
37 recovery: String,
38}
39
40impl BootloaderMessage {
41 pub fn with_args(args: &str) -> Self {
43 Self { recovery: args.into(), ..Default::default() }
44 }
45
46 pub fn recovery_args(&self) -> impl Iterator<Item = &str> {
49 self.recovery.split('\n')
50 }
51}
52
53impl From<BootloaderMessageRaw> for BootloaderMessage {
54 fn from(raw: BootloaderMessageRaw) -> Self {
55 Self {
56 command: bytes_to_string(&raw.command),
57 status: bytes_to_string(&raw.status),
58 recovery: bytes_to_string(&raw.recovery),
59 }
60 }
61}
62
63impl TryFrom<BootloaderMessage> for BootloaderMessageRaw {
64 type Error = Error;
65
66 fn try_from(message: BootloaderMessage) -> Result<Self, Error> {
67 let mut raw = BootloaderMessageRaw::default();
68
69 let BootloaderMessage { command, status, recovery } = message;
70
71 if command.len() > raw.command.len() {
73 return Err(anyhow!("command field exceeds storage size"));
74 }
75 if status.len() > raw.status.len() {
76 return Err(anyhow!("status field exceeds storage size"));
77 }
78 if recovery.len() > raw.recovery.len() {
79 return Err(anyhow!("recovery arguments exceed storage size"));
80 }
81
82 raw.command[0..command.len()].copy_from_slice(command.as_bytes());
83 raw.status[0..status.len()].copy_from_slice(status.as_bytes());
84 raw.recovery[0..recovery.len()].copy_from_slice(recovery.as_bytes());
85
86 Ok(raw)
87 }
88}
89
90fn bytes_to_string(buf: &[u8]) -> String {
94 if let Some((contents, _)) = buf.split_once_str(&[0u8]) {
95 contents.as_bstr().to_string()
96 } else {
97 buf.as_bstr().to_string()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_bytes_to_string_with_null() {
107 let buf = b"hello\0world";
108 assert_eq!(bytes_to_string(buf), "hello");
109 }
110
111 #[test]
112 fn test_bytes_to_string_without_null() {
113 let buf = b"hello";
114 assert_eq!(bytes_to_string(buf), "hello");
115 }
116
117 #[test]
118 fn test_bytes_to_string_invalid_utf8() {
119 let buf = b"hello\xffworld";
120 assert_eq!(bytes_to_string(buf), "hello\u{FFFD}world");
122 }
123
124 #[test]
125 fn test_with_args() {
126 let msg = BootloaderMessage::with_args("test\nargs");
127 assert_eq!(msg.recovery, "test\nargs");
128 let args: Vec<_> = msg.recovery_args().collect();
129 assert_eq!(args, ["test", "args"]);
130 }
131
132 #[test]
133 fn test_raw_roundtrip() {
134 let original = BootloaderMessage {
135 command: "cmd".to_string(),
136 status: "stat".to_string(),
137 recovery: "rec".to_string(),
138 };
139
140 let raw: BootloaderMessageRaw = original.clone().try_into().expect("convert to raw");
141 let converted: BootloaderMessage = raw.into();
142
143 assert_eq!(converted.command, original.command);
144 assert_eq!(converted.status, original.status);
145 assert_eq!(converted.recovery, original.recovery);
146 }
147
148 #[test]
149 fn test_raw_overflow() {
150 let long_string = "a".repeat(1000);
151 let msg = BootloaderMessage { command: long_string, ..Default::default() };
152 let result: Result<BootloaderMessageRaw, _> = msg.try_into();
153 assert!(result.is_err());
154 }
155}