refactor: move firmware workspace under firmware
This commit is contained in:
1 parent
153b0bf262
commit
f03fc119a4
222 files changed
+56
-13
No files matched your search
@@ -0,0 +1,184 @@
|
||||
//! Retained crash-record codec primitives.
|
||||
//!
|
||||
//! This module defines and validates a fixed record only. It does not install a
|
||||
//! panic handler, write retention memory, access flash, or transmit MAVLink.
|
||||
|
||||
use crate::KaptanError;
|
||||
|
||||
const MAGIC: u32 = u32::from_le_bytes(*b"KLOG");
|
||||
const VERSION: u16 = 1;
|
||||
const COMMITTED: u32 = u32::MAX;
|
||||
/// Fixed endianness-independent retained record size.
|
||||
pub const CRASH_RECORD_BYTES: usize = 32;
|
||||
|
||||
/// Stored reason for a previous abnormal or orderly runtime end.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum CrashKind {
|
||||
/// A panic was captured by a future target-specific handler.
|
||||
Panic = 1,
|
||||
/// A hard fault was captured by a future target-specific handler.
|
||||
HardFault = 2,
|
||||
/// A requested shutdown sequence was captured.
|
||||
Shutdown = 3,
|
||||
/// A platform reset cause was captured.
|
||||
Reset = 4,
|
||||
}
|
||||
|
||||
impl CrashKind {
|
||||
const fn as_u8(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
const fn from_u8(value: u8) -> Option<Self> {
|
||||
match value {
|
||||
1 => Some(Self::Panic),
|
||||
2 => Some(Self::HardFault),
|
||||
3 => Some(Self::Shutdown),
|
||||
4 => Some(Self::Reset),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validation failure for a retained record.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)]
|
||||
pub enum CrashRecordError {
|
||||
/// Magic/version/commit marker did not identify a completed record.
|
||||
InvalidHeader,
|
||||
/// Record payload did not match its checksum.
|
||||
BadChecksum,
|
||||
}
|
||||
|
||||
/// Fixed, commit-last retained record value.
|
||||
///
|
||||
/// A board integration writes every field except `commit`, writes `checksum`,
|
||||
/// then writes `commit` last. This pure type only validates that protocol.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct CrashRecord {
|
||||
kind: CrashKind,
|
||||
boot_id: u64,
|
||||
detail: u64,
|
||||
}
|
||||
|
||||
impl CrashRecord {
|
||||
/// Creates one completed record for tests or a future retention backend.
|
||||
pub fn new(kind: CrashKind, boot_id: u64, detail: u64) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
boot_id,
|
||||
detail,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes an endian-independent record. A target writes the final four
|
||||
/// commit-marker bytes last; this function itself performs no I/O.
|
||||
pub fn encode(self) -> [u8; CRASH_RECORD_BYTES] {
|
||||
let mut bytes = [0_u8; CRASH_RECORD_BYTES];
|
||||
bytes[0..4].copy_from_slice(&MAGIC.to_le_bytes());
|
||||
bytes[4..6].copy_from_slice(&VERSION.to_le_bytes());
|
||||
bytes[6] = self.kind.as_u8();
|
||||
bytes[7] = 0;
|
||||
bytes[8..16].copy_from_slice(&self.boot_id.to_le_bytes());
|
||||
bytes[16..24].copy_from_slice(&self.detail.to_le_bytes());
|
||||
let record_checksum = checksum(&bytes[..24]);
|
||||
bytes[24..28].copy_from_slice(&record_checksum.to_le_bytes());
|
||||
bytes[28..32].copy_from_slice(&COMMITTED.to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Decodes and validates an explicitly little-endian retained record.
|
||||
/// Arbitrary retained bytes are never reinterpreted as Rust enums.
|
||||
pub fn decode(bytes: [u8; CRASH_RECORD_BYTES]) -> Result<Self, CrashRecordError> {
|
||||
if u32::from_le_bytes(bytes[0..4].try_into().unwrap()) != MAGIC
|
||||
|| u16::from_le_bytes(bytes[4..6].try_into().unwrap()) != VERSION
|
||||
|| bytes[7] != 0
|
||||
|| u32::from_le_bytes(bytes[28..32].try_into().unwrap()) != COMMITTED
|
||||
{
|
||||
return Err(CrashRecordError::InvalidHeader);
|
||||
}
|
||||
if u32::from_le_bytes(bytes[24..28].try_into().unwrap()) != checksum(&bytes[..24]) {
|
||||
return Err(CrashRecordError::BadChecksum);
|
||||
}
|
||||
let Some(kind) = CrashKind::from_u8(bytes[6]) else {
|
||||
return Err(CrashRecordError::InvalidHeader);
|
||||
};
|
||||
Ok(Self {
|
||||
kind,
|
||||
boot_id: u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
|
||||
detail: u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Captured end reason.
|
||||
pub const fn kind(self) -> CrashKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Board-local boot identity.
|
||||
pub const fn boot_id(self) -> u64 {
|
||||
self.boot_id
|
||||
}
|
||||
|
||||
/// Target-defined compact panic/fault/reset detail.
|
||||
pub const fn detail(self) -> u64 {
|
||||
self.detail
|
||||
}
|
||||
}
|
||||
|
||||
const fn checksum(bytes: &[u8]) -> u32 {
|
||||
let mut state = 0xffff_ffff_u32;
|
||||
let mut index = 0;
|
||||
while index < bytes.len() {
|
||||
state = crc32c_byte(state, bytes[index]);
|
||||
index += 1;
|
||||
}
|
||||
!state
|
||||
}
|
||||
|
||||
const fn crc32c_byte(mut state: u32, byte: u8) -> u32 {
|
||||
state ^= byte as u32;
|
||||
let mut index = 0;
|
||||
while index != 8 {
|
||||
state = if state & 1 != 0 {
|
||||
(state >> 1) ^ 0x82f6_3b78
|
||||
} else {
|
||||
state >> 1
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CrashKind, CrashRecord, CrashRecordError};
|
||||
use crate::ErrorReport;
|
||||
|
||||
#[test]
|
||||
fn retained_record_rejects_torn_or_corrupt_payloads() {
|
||||
let record = CrashRecord::new(CrashKind::Panic, 7, 11);
|
||||
let bytes = record.encode();
|
||||
assert_eq!(CrashRecord::decode(bytes), Ok(record));
|
||||
let mut corrupt = bytes;
|
||||
corrupt[24] ^= 1;
|
||||
assert_eq!(
|
||||
CrashRecord::decode(corrupt),
|
||||
Err(CrashRecordError::BadChecksum)
|
||||
);
|
||||
let mut torn = bytes;
|
||||
torn[28..32].copy_from_slice(&0_u32.to_le_bytes());
|
||||
assert_eq!(
|
||||
CrashRecord::decode(torn),
|
||||
Err(CrashRecordError::InvalidHeader)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_error_has_a_static_derived_message() {
|
||||
assert_eq!(
|
||||
CrashRecordError::BadChecksum.static_error().path(),
|
||||
"kaptan_log::crash::CrashRecordError::BadChecksum"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user