umsh_bsp_nrf52840/
panic_persist.rs1pub struct SyncNoinit<T>(pub core::cell::UnsafeCell<core::mem::MaybeUninit<T>>);
39
40unsafe impl<T> Sync for SyncNoinit<T> {}
42
43impl<T> SyncNoinit<T> {
44 pub const fn uninit() -> Self {
45 Self(core::cell::UnsafeCell::new(core::mem::MaybeUninit::uninit()))
46 }
47
48 pub unsafe fn as_bytes_mut(&self) -> &'static mut [u8] {
53 let ptr: *mut u8 = self.0.get().cast::<u8>();
54 unsafe { core::slice::from_raw_parts_mut(ptr, core::mem::size_of::<T>()) }
56 }
57}
58
59pub struct SliceWriter<'a> {
65 pub buf: &'a mut [u8],
66 pub pos: usize,
67}
68
69impl<'a> core::fmt::Write for SliceWriter<'a> {
70 fn write_str(&mut self, s: &str) -> core::fmt::Result {
71 let avail = self.buf.len().saturating_sub(self.pos);
72 let n = s.len().min(avail);
73 self.buf[self.pos..self.pos + n].copy_from_slice(&s.as_bytes()[..n]);
74 self.pos += n;
75 Ok(())
76 }
77}
78
79const PANIC_MAGIC: u32 = 0x554D_5350; const HEADER_LEN: usize = 8;
81
82#[derive(Debug)]
89pub struct PanicSlot<'a> {
90 region: &'a mut [u8],
91}
92
93impl<'a> PanicSlot<'a> {
94 pub fn new(region: &'a mut [u8]) -> Self {
98 debug_assert!(
99 region.len() > HEADER_LEN,
100 "panic slot region must hold at least the 8-byte header plus 1 payload byte"
101 );
102 Self { region }
103 }
104
105 pub fn payload_capacity(&self) -> usize {
107 self.region.len().saturating_sub(HEADER_LEN)
108 }
109
110 pub fn capture(&mut self, msg: &[u8]) -> usize {
115 if self.region.len() <= HEADER_LEN {
116 return 0;
117 }
118 let cap = self.payload_capacity();
119 let len = msg.len().min(cap);
120
121 self.region[0..4].copy_from_slice(&PANIC_MAGIC.to_le_bytes());
122 self.region[4..6].copy_from_slice(&(len as u16).to_le_bytes());
123 self.region[8..8 + len].copy_from_slice(&msg[..len]);
124
125 let cksum = checksum(&self.region[0..6], &self.region[8..8 + len]);
126 self.region[6..8].copy_from_slice(&cksum.to_le_bytes());
127 len
128 }
129
130 pub fn read(&self) -> Option<&[u8]> {
135 if self.region.len() <= HEADER_LEN {
136 return None;
137 }
138
139 let magic = u32::from_le_bytes(self.region[0..4].try_into().ok()?);
140 if magic != PANIC_MAGIC {
141 return None;
142 }
143
144 let len = u16::from_le_bytes(self.region[4..6].try_into().ok()?) as usize;
145 if len > self.payload_capacity() {
146 return None;
147 }
148
149 let stored_cksum = u16::from_le_bytes(self.region[6..8].try_into().ok()?);
150 let computed = checksum(&self.region[0..6], &self.region[8..8 + len]);
151 if stored_cksum != computed {
152 return None;
153 }
154
155 Some(&self.region[8..8 + len])
156 }
157
158 pub fn clear(&mut self) {
162 if self.region.len() >= 4 {
163 self.region[0..4].fill(0);
164 }
165 }
166}
167
168fn checksum(header_front: &[u8], payload: &[u8]) -> u16 {
173 let mut s1: u16 = 0;
174 let mut s2: u16 = 0;
175 for &b in header_front.iter().chain(payload.iter()) {
176 s1 = (s1.wrapping_add(b as u16)) % 255;
177 s2 = (s2.wrapping_add(s1)) % 255;
178 }
179 (s2 << 8) | s1
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn slot(region: &mut [u8]) -> PanicSlot<'_> {
187 PanicSlot::new(region)
188 }
189
190 #[test]
191 fn empty_region_returns_none() {
192 let mut buf = [0u8; 64];
193 let s = slot(&mut buf);
194 assert_eq!(s.read(), None);
195 }
196
197 #[test]
198 fn round_trip_basic_message() {
199 let mut buf = [0u8; 64];
200 let mut s = slot(&mut buf);
201 assert_eq!(s.capture(b"oops"), 4);
202 assert_eq!(s.read(), Some(&b"oops"[..]));
203 }
204
205 #[test]
206 fn round_trip_full_capacity() {
207 let mut buf = [0u8; 64];
208 let cap = buf.len() - HEADER_LEN;
209 let payload = [b'x'; 56]; assert_eq!(payload.len(), cap);
211 let mut s = slot(&mut buf);
212 assert_eq!(s.capture(&payload), cap);
213 assert_eq!(s.read(), Some(&payload[..]));
214 }
215
216 #[test]
217 fn truncates_when_payload_exceeds_capacity() {
218 let mut buf = [0u8; 16]; let mut s = slot(&mut buf);
220 assert_eq!(s.capture(b"this is way too long"), 8);
221 assert_eq!(s.read(), Some(&b"this is "[..]));
222 }
223
224 #[test]
225 fn random_uninitialized_bytes_do_not_read_as_valid() {
226 let mut buf: [u8; 64] = [0xA5; 64]; let s = slot(&mut buf);
228 assert_eq!(s.read(), None);
229 }
230
231 #[test]
232 fn clear_invalidates_record() {
233 let mut buf = [0u8; 64];
234 let mut s = slot(&mut buf);
235 s.capture(b"oops");
236 assert!(s.read().is_some());
237 s.clear();
238 assert_eq!(s.read(), None);
239 }
240
241 #[test]
242 fn corrupted_payload_is_rejected() {
243 let mut buf = [0u8; 64];
244 {
245 let mut s = slot(&mut buf);
246 s.capture(b"hello world");
247 }
248 buf[10] ^= 0xFF;
250 let s = slot(&mut buf);
251 assert_eq!(s.read(), None);
252 }
253
254 #[test]
255 fn corrupted_length_is_rejected() {
256 let mut buf = [0u8; 64];
257 {
258 let mut s = slot(&mut buf);
259 s.capture(b"hello");
260 }
261 buf[4] = 200;
263 let s = slot(&mut buf);
264 assert_eq!(s.read(), None);
265 }
266
267 #[test]
268 fn length_beyond_region_is_rejected() {
269 let mut buf = [0u8; 64];
270 {
271 let mut s = slot(&mut buf);
272 s.capture(b"x");
273 }
274 let bogus_len: u16 = 1024;
276 buf[4..6].copy_from_slice(&bogus_len.to_le_bytes());
277 let s = slot(&mut buf);
278 assert_eq!(s.read(), None);
279 }
280
281 #[test]
282 fn second_capture_overwrites_first() {
283 let mut buf = [0u8; 64];
284 let mut s = slot(&mut buf);
285 s.capture(b"first");
286 s.capture(b"second");
287 assert_eq!(s.read(), Some(&b"second"[..]));
288 }
289
290 #[test]
291 fn read_does_not_mutate_region() {
292 let mut buf = [0u8; 64];
293 {
294 let mut s = slot(&mut buf);
295 s.capture(b"persisting");
296 }
297 let snapshot = buf;
298 {
299 let s = slot(&mut buf);
300 let _ = s.read();
301 }
302 assert_eq!(snapshot, buf);
303 }
304
305 #[test]
306 fn payload_capacity_excludes_header() {
307 let mut buf = [0u8; 100];
308 let s = slot(&mut buf);
309 assert_eq!(s.payload_capacity(), 100 - HEADER_LEN);
310 }
311
312 #[test]
313 fn empty_payload_is_valid() {
314 let mut buf = [0u8; 64];
315 let mut s = slot(&mut buf);
316 assert_eq!(s.capture(b""), 0);
317 assert_eq!(s.read(), Some(&b""[..]));
318 }
319}