Skip to main content

intel_crashlog/
region.rs

1// Copyright (C) 2025 Intel Corporation
2// SPDX-License-Identifier: MIT
3
4//! Provides access to the records stored in a Crash Log region.
5
6use crate::cper::section::{CperSectionBody, fer};
7use crate::error::Error;
8use crate::header::Header;
9use crate::record::Record;
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12
13/// A container for one or several Crash Log records.
14///
15/// A Crash Log region refers to a region in an on-die memory that is allocated for storing
16/// a contiguous sequence of Crash Log records.
17#[derive(Default)]
18pub struct Region {
19    pub records: Vec<Record>,
20}
21
22impl Region {
23    pub(crate) fn from_cper_section(section: &CperSectionBody) -> Option<Self> {
24        match section {
25            CperSectionBody::FirmwareErrorRecord(fer) => {
26                let guid = fer.header.guid;
27                if guid == fer::guids::RECORD_ID_CRASHLOG {
28                    Region::from_slice(&fer.payload).ok()
29                } else {
30                    log::info!("Ignoring unknown Firmware Error Record: {}", guid);
31                    None
32                }
33            }
34            _ => None,
35        }
36    }
37
38    pub(crate) fn set_child_context(&mut self, hdr: &Header) {
39        for record in self.records.iter_mut() {
40            record.context.parent_header = Some(hdr.clone());
41        }
42    }
43
44    pub fn from_slice(bytes: &[u8]) -> Result<Self, Error> {
45        let mut region = Region::default();
46        let mut cursor = 0;
47
48        while cursor < bytes.len() {
49            let header = match Header::from_slice(&bytes[cursor..]) {
50                Ok(Some(header)) => header,
51                Ok(None) => {
52                    log::debug!("Found termination marker at offset {cursor}");
53                    break;
54                }
55                Err(err) => {
56                    log::warn!("Cannot decode record header: {err}");
57                    if region.records.is_empty() {
58                        // Return the error if no record can be decoded
59                        return Err(err);
60                    }
61                    break;
62                }
63            };
64
65            log::debug!("Record version: {0}", header);
66
67            let record_size = header.record_size();
68            log::debug!("Record size: 0x{record_size:04x}");
69
70            if record_size == 0 {
71                log::warn!(
72                    "{} record has an empty size. Skipping.",
73                    header.record_type().unwrap_or("UNKNOWN")
74                );
75                break;
76            }
77
78            let limit = cursor + record_size;
79            if limit > bytes.len() {
80                log::warn!(
81                    "Truncated record detected: record is expected to be {}B but is {}B",
82                    record_size,
83                    bytes.len() - cursor
84                )
85            }
86
87            region.records.push(Record {
88                header,
89                data: bytes[cursor..limit.min(bytes.len())].into(),
90                ..Default::default()
91            });
92
93            cursor += record_size;
94        }
95
96        if region.records.is_empty() {
97            return Err(Error::EmptyRegion);
98        }
99
100        Ok(region)
101    }
102
103    pub fn to_bytes(&self) -> Vec<u8> {
104        let mut bytes = Vec::new();
105        for record in self.records.iter() {
106            bytes.append(&mut record.data.clone());
107        }
108        bytes
109    }
110}