Skip to main content

intel_crashlog/record/
decode.rs

1// Copyright (C) 2025 Intel Corporation
2// SPDX-License-Identifier: MIT
3
4use super::Record;
5#[cfg(feature = "collateral_manager")]
6use crate::collateral::{CollateralManager, CollateralTree};
7use crate::error::Error;
8use crate::header::record_types;
9use crate::node::Node;
10use crate::node::NodeType;
11#[cfg(not(feature = "std"))]
12use alloc::{borrow::ToOwned, str, string::String, vec::Vec};
13use log::debug;
14#[cfg(feature = "std")]
15use std::str;
16
17const DELIMITER: char = ';';
18
19#[derive(Default, Debug)]
20struct DecodeDefinitionEntry {
21    pub name: String,
22    pub offset: usize,
23    pub size: usize,
24    pub description: String,
25}
26
27impl Record {
28    fn read_field(&self, offset: usize, size: usize) -> Option<u64> {
29        if size > 64 {
30            // Large fields don't need to be decoded.
31            return None;
32        }
33
34        let mut value = 0;
35        let mut bit = 0;
36
37        while bit < size {
38            let chunk_size = 8;
39            let chunk = (offset + bit) / chunk_size;
40            if chunk >= self.data.len() {
41                return None;
42            }
43
44            let bit_offset = (offset + bit) % chunk_size;
45            let mask = (1 << (size - bit).min(chunk_size)) - 1;
46            value |= ((self.data[chunk] as u64 >> bit_offset) & mask) << bit;
47            bit += chunk_size - bit_offset;
48        }
49
50        Some(value)
51    }
52
53    /// Decodes a section of the [Record] located at the given `offset` into a [Node] tree using an
54    /// arbitrary decode definition (`layout`).
55    ///
56    /// The decode definition must be CSV-encoded, use semi-colons as delimiters, and
57    /// contain the following columns:
58    /// - `name`:
59    ///   Dot-separated path to the field in the decode output (example: `aaa.bbb.ccc`).
60    ///   The path can be relative to the previous entry (example: `..bar.baz`).
61    /// - `offset`: offset of the field in the record in bits.
62    /// - `size`: size of the field in bits.
63    /// - `description`: description of the field.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use intel_crashlog::prelude::*;
69    ///
70    /// let record = Record {
71    ///     header: Header::default(),
72    ///     data: vec![0x42],
73    ///     ..Record::default()
74    /// };
75    ///
76    /// let csv = "name;offset;size;description;bitfield
77    /// foo.bar;0;8;;0";
78    ///
79    /// let root = record.decode_with_csv(csv.as_bytes(), 0).unwrap();
80    /// let field = root.get_by_path("foo.bar").unwrap();
81    /// assert_eq!(field.kind, NodeType::Field { value: 0x42 });
82    /// ```
83    pub fn decode_with_csv(&self, layout: &[u8], offset: usize) -> Result<Node, Error> {
84        let mut root = Node::root();
85
86        let csv = str::from_utf8(layout)?;
87        let mut columns = Vec::new();
88        let mut current_path = Vec::new();
89
90        for (i, line) in csv.lines().enumerate() {
91            if i == 0 {
92                columns = line.split(DELIMITER).collect();
93                debug!("CSV columns: {columns:?}");
94                continue;
95            }
96
97            let mut entry = DecodeDefinitionEntry::default();
98
99            for (i, field) in line.split(DELIMITER).enumerate() {
100                if let Some(column) = columns.get(i) {
101                    match *column {
102                        "name" => entry.name = field.into(),
103                        "offset" => entry.offset = field.parse()?,
104                        "size" => entry.size = field.parse()?,
105                        "description" => entry.description = field.into(),
106                        _ => (),
107                    }
108                }
109            }
110
111            if entry.name.is_empty() {
112                continue;
113            }
114
115            let mut segments = entry.name.split(".");
116            let Some(top) = segments.next() else {
117                continue;
118            };
119
120            if !top.is_empty() {
121                // Absolute path
122                current_path.clear();
123                current_path.push(top.to_owned());
124
125                if root.get(top).is_none() {
126                    // Top-level is assumed to be the record name
127                    root.add(Node::record(top));
128                }
129            }
130
131            for segment in segments {
132                if segment.is_empty() {
133                    let _ = current_path.pop();
134                } else {
135                    current_path.push(segment.to_owned());
136                }
137            }
138
139            let node = root.create_hierarchy_from_iter(&current_path);
140            node.description = entry.description;
141            if let Some(value) = self.read_field(offset * 8 + entry.offset, entry.size) {
142                node.kind = NodeType::Field { value }
143            }
144        }
145        Ok(root)
146    }
147
148    /// Decodes the [Record] header into a [Node] tree.
149    pub fn decode_without_cm(&self) -> Node {
150        let header = self.decode_header();
151
152        let mut root = Node::root();
153        let record_root = if let Some(custom_root) = self.get_root_path() {
154            root.create_hierarchy(&custom_root)
155        } else {
156            &mut root
157        };
158
159        record_root.merge(header);
160        root
161    }
162
163    fn decode_header(&self) -> Node {
164        let mut record = Node::record(self.header.record_type().unwrap_or("record"));
165        record.add(Node::from(&self.header));
166
167        let mut root = Node::root();
168        root.add(record);
169        root
170    }
171
172    fn get_root_path(&self) -> Option<String> {
173        if let Some(custom_root) = self.header.get_root_path() {
174            return Some(custom_root);
175        }
176
177        if let Some(parent_header) = &self.context.parent_header {
178            return parent_header.get_root_path();
179        }
180
181        None
182    }
183
184    #[cfg(feature = "collateral_manager")]
185    fn get_root_path_using_cm<T: CollateralTree>(
186        &self,
187        cm: &mut CollateralManager<T>,
188    ) -> Option<String> {
189        if let Some(custom_root) = self.header.get_root_path_using_cm(cm) {
190            return Some(custom_root);
191        }
192
193        if let Some(parent_header) = &self.context.parent_header
194            && let Some(parent_custom_root) = parent_header.get_root_path_using_cm(cm)
195        {
196            return Some(parent_custom_root);
197        }
198
199        self.get_root_path()
200    }
201
202    /// Decodes a section of the [Record] located at the given `offset` into a [Node] tree using
203    /// an arbitrary decode definition stored in the collateral tree.
204    #[cfg(feature = "collateral_manager")]
205    pub fn decode_with_decode_def<T: CollateralTree>(
206        &self,
207        cm: &mut CollateralManager<T>,
208        decode_def: &str,
209        offset: usize,
210    ) -> Result<Node, Error> {
211        let paths = self.header.decode_definitions_paths(cm)?;
212
213        let mut root = Node::root();
214
215        for mut path in paths {
216            path.push(decode_def);
217            let Ok(layout) = cm.get_item_with_header(&self.header, path) else {
218                continue;
219            };
220            root.merge(self.decode_with_csv(layout, offset)?);
221            return Ok(root);
222        }
223
224        Err(Error::MissingDecodeDefinitions(self.header.version.clone()))
225    }
226
227    /// Decodes the whole [Record] into a [Node] tree using the decode definitions stored in the
228    /// collateral tree.
229    #[cfg(feature = "collateral_manager")]
230    pub fn decode<T: CollateralTree>(&self, cm: &mut CollateralManager<T>) -> Node {
231        let is_core = ((self.header.version.record_type == record_types::PCORE)
232            || (self.header.version.record_type == record_types::ECORE))
233            && !self.header.version.into_errata().type0_legacy_server_box;
234
235        let record = if is_core {
236            self.decode_as_core_record(cm)
237        } else {
238            self.decode_with_decode_def(cm, "layout.csv", 0)
239        };
240
241        let record_node = match record {
242            Ok(node) => node,
243            Err(err) => {
244                log::warn!("Cannot decode record: {err}. Only the header fields will be decoded.");
245                self.decode_header()
246            }
247        };
248
249        let mut root = Node::root();
250        let record_root = if let Some(custom_root) = self.get_root_path_using_cm(cm) {
251            root.create_hierarchy(&custom_root)
252        } else {
253            &mut root
254        };
255
256        record_root.merge(record_node);
257        root
258    }
259}