Skip to main content

pcf_debug/plugin/
mod.rs

1//! The partition-decoder plugin system.
2//!
3//! A *decoder* turns a partition's raw bytes into a renderer-agnostic tree of
4//! named fields ([`FieldNode`]). The CLI and HTML renderers both consume that
5//! tree, so a decoder is written once and displayed everywhere.
6//!
7//! Decoders are registered statically (compiled into the binary). Adding a new
8//! format means writing a module that implements [`PartitionDecoder`] and adding
9//! one line to [`DecoderRegistry::with_builtins`]. The trait is deliberately
10//! object-safe and the data types carry no borrowed state, so a future dynamic
11//! (shared-library) backend could be added behind a feature without reworking
12//! any decoder.
13
14mod pcfsig;
15mod pfs;
16mod raw;
17
18pub use pcfsig::{PcfSigKeyDecoder, PcfSigSignatureDecoder};
19pub use pfs::{PfsNodeDecoder, PfsSessionDecoder};
20pub use raw::RawDecoder;
21
22/// A decoded field's value, kept independent of any output format.
23#[derive(Debug, Clone, PartialEq)]
24pub enum FieldValue {
25    /// A grouping node with no value of its own.
26    None,
27    U64(u64),
28    Bytes(Vec<u8>),
29    Text(String),
30    Uid([u8; 16]),
31    /// A numeric code with a human name, e.g. `kind = 1 (file)`.
32    Enum {
33        raw: u64,
34        name: String,
35    },
36    /// A bitset with the names of the bits that are set.
37    Flags {
38        raw: u64,
39        set: Vec<String>,
40    },
41}
42
43/// One node in a decoded field tree.
44#[derive(Debug, Clone, PartialEq)]
45pub struct FieldNode {
46    pub name: String,
47    pub value: FieldValue,
48    /// Byte range *within the partition data* this field occupies, if any.
49    pub range: Option<(u64, u64)>,
50    /// An optional remark, e.g. `"magic OK"` or `"reserved must be 0"`.
51    pub note: Option<String>,
52    pub children: Vec<FieldNode>,
53}
54
55impl FieldNode {
56    /// A grouping node (no value, no range).
57    pub fn group(name: impl Into<String>) -> Self {
58        Self {
59            name: name.into(),
60            value: FieldValue::None,
61            range: None,
62            note: None,
63            children: Vec::new(),
64        }
65    }
66
67    /// A leaf node carrying a value and the byte range it covers.
68    pub fn leaf(name: impl Into<String>, value: FieldValue, range: (u64, u64)) -> Self {
69        Self {
70            name: name.into(),
71            value,
72            range: Some(range),
73            note: None,
74            children: Vec::new(),
75        }
76    }
77
78    /// Attach a note (builder style).
79    pub fn with_note(mut self, note: impl Into<String>) -> Self {
80        self.note = Some(note.into());
81        self
82    }
83
84    /// Append a child (builder style).
85    pub fn child(mut self, c: FieldNode) -> Self {
86        self.children.push(c);
87        self
88    }
89
90    /// Append a child in place.
91    pub fn push(&mut self, c: FieldNode) {
92        self.children.push(c);
93    }
94}
95
96/// Metadata handed to a decoder alongside the partition's bytes.
97#[derive(Debug, Clone, Copy)]
98pub struct PartitionMeta<'a> {
99    pub partition_type: u32,
100    pub uid: &'a [u8; 16],
101    pub label: &'a str,
102}
103
104/// The result of decoding one partition.
105#[derive(Debug, Clone)]
106pub struct Decoded {
107    /// Human name of the format that was decoded, e.g. `"PFS_NODE"`.
108    pub format_name: String,
109    pub fields: Vec<FieldNode>,
110    /// Non-fatal spec violations and remarks surfaced to the user.
111    pub warnings: Vec<String>,
112}
113
114/// A plugin that turns partition bytes into a field tree.
115pub trait PartitionDecoder {
116    /// Stable identifier, used for `--decoder` selection and HTML anchors.
117    fn name(&self) -> &'static str;
118
119    /// Cheap test: does this decoder claim the partition? May inspect the type
120    /// and/or sniff a magic prefix.
121    fn matches(&self, meta: &PartitionMeta, data: &[u8]) -> bool;
122
123    /// Full decode. Must never panic: on malformed input it returns whatever
124    /// fields it could read plus `warnings`.
125    fn decode(&self, meta: &PartitionMeta, data: &[u8]) -> Decoded;
126}
127
128/// An ordered set of decoders. The first decoder whose `matches` returns true
129/// wins; [`RawDecoder`] is always last and matches everything.
130pub struct DecoderRegistry {
131    decoders: Vec<Box<dyn PartitionDecoder>>,
132}
133
134impl DecoderRegistry {
135    /// The registry with all built-in decoders: PFS node, PFS session, then the
136    /// raw fallback.
137    pub fn with_builtins() -> Self {
138        Self {
139            decoders: vec![
140                Box::new(PfsNodeDecoder),
141                Box::new(PfsSessionDecoder),
142                Box::new(PcfSigKeyDecoder),
143                Box::new(PcfSigSignatureDecoder),
144                Box::new(RawDecoder),
145            ],
146        }
147    }
148
149    /// Insert a decoder ahead of the raw fallback.
150    pub fn register(&mut self, d: Box<dyn PartitionDecoder>) {
151        let insert_at = self.decoders.len().saturating_sub(1);
152        self.decoders.insert(insert_at, d);
153    }
154
155    /// All decoder names, in priority order.
156    pub fn names(&self) -> Vec<&'static str> {
157        self.decoders.iter().map(|d| d.name()).collect()
158    }
159
160    /// Decode `data`, picking the first matching decoder.
161    pub fn decode(&self, meta: &PartitionMeta, data: &[u8]) -> Decoded {
162        for d in &self.decoders {
163            if d.matches(meta, data) {
164                return d.decode(meta, data);
165            }
166        }
167        // RawDecoder matches everything, so this is unreachable in practice.
168        RawDecoder.decode(meta, data)
169    }
170
171    /// Decode with a specific decoder by name, if present.
172    pub fn decode_with(&self, name: &str, meta: &PartitionMeta, data: &[u8]) -> Option<Decoded> {
173        self.decoders
174            .iter()
175            .find(|d| d.name() == name)
176            .map(|d| d.decode(meta, data))
177    }
178}
179
180impl Default for DecoderRegistry {
181    fn default() -> Self {
182        Self::with_builtins()
183    }
184}
185
186/// Read a little-endian `u16` at `off`, or `None` if out of bounds.
187pub(crate) fn le_u16(data: &[u8], off: usize) -> Option<u16> {
188    Some(u16::from_le_bytes(data.get(off..off + 2)?.try_into().ok()?))
189}
190
191/// Read a little-endian `u32` at `off`, or `None` if out of bounds.
192pub(crate) fn le_u32(data: &[u8], off: usize) -> Option<u32> {
193    Some(u32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?))
194}
195
196/// Read a little-endian `u64` at `off`, or `None` if out of bounds.
197pub(crate) fn le_u64(data: &[u8], off: usize) -> Option<u64> {
198    Some(u64::from_le_bytes(data.get(off..off + 8)?.try_into().ok()?))
199}
200
201/// Read a 16-byte UID at `off`.
202pub(crate) fn uid_at(data: &[u8], off: usize) -> Option<[u8; 16]> {
203    data.get(off..off + 16)?.try_into().ok()
204}