Skip to main content

cadmpeg_core/decode/
space.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Address-space identifiers and stable descriptors.
3//!
4//! Every byte a decode reads belongs to exactly one address space: the root
5//! input, an inflated entry, a reconstructed stream. A [`SpaceId`] names one
6//! space within a single decode session; a [`View`](crate::decode::View)
7//! carries the id so error locations fall out of the type. Coordinates are
8//! absolute within a space: offset zero is that space's first byte.
9//!
10//! A [`SpaceId`] is session-local. [`SpaceDescriptor`] records the stable
11//! label, parent, and derivation needed to resolve a location into a
12//! root-to-leaf [`ResolvedAddress`] before the decode context is dropped.
13
14/// Names one address space within a single decode.
15///
16/// Ids are dense and assigned in registration order; the root is always
17/// [`SpaceId::ROOT`]. Error locations use the id to qualify offsets.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct SpaceId(u32);
20
21impl SpaceId {
22    /// The root input space, registered first by every decode.
23    pub const ROOT: SpaceId = SpaceId(0);
24
25    /// Creates a session-local address-space identifier.
26    pub(crate) const fn from_index(index: u32) -> Self {
27        Self(index)
28    }
29
30    /// Returns the dense index of this space.
31    pub fn index(self) -> u32 {
32        self.0
33    }
34}
35
36/// A half-open byte range `[start, end)` within one space.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct ByteRange {
39    /// Inclusive start offset.
40    pub start: u64,
41    /// Exclusive end offset.
42    pub end: u64,
43}
44
45/// How a registered space was derived from its parent.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum SpaceDerivation {
48    /// The root input image.
49    Root,
50    /// A stored (uncompressed) child range borrowed from the parent.
51    StoredSlice {
52        /// Parent space.
53        parent: SpaceId,
54        /// Range in the parent space.
55        range: ByteRange,
56    },
57    /// Decompressed or otherwise expanded output from a parent range.
58    Expanded {
59        /// Parent space that supplied the compressed bytes.
60        parent: SpaceId,
61        /// Compressed source range in the parent space.
62        source_range: ByteRange,
63    },
64    /// Concatenation of several parent windows.
65    Concatenated {
66        /// Parent spaces, in concatenation order.
67        parents: Vec<SpaceId>,
68    },
69}
70
71/// Stable description of one registered address space.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct SpaceDescriptor {
74    /// Session-local identifier.
75    pub id: SpaceId,
76    /// Stable label (archive member name, stream path, or `"root"`).
77    pub label: String,
78    /// How this space was derived.
79    pub derivation: SpaceDerivation,
80}
81
82/// One step in a root-to-leaf resolved address.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct AddressStep {
85    /// Stable label for this step.
86    pub label: String,
87    /// Whether this step is the root, a stored member, or an expansion.
88    pub kind: AddressStepKind,
89}
90
91/// Kind of one address step, for inspect replay.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum AddressStepKind {
94    /// Root input file.
95    Root,
96    /// Stored archive member (borrowed parent bytes).
97    StoredMember,
98    /// Expanded (inflated) archive member.
99    ExpandedMember,
100}
101
102/// Owned root-to-leaf address that survives the decode session.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct ResolvedAddress {
105    /// Path from root to the space that owns the offset.
106    pub steps: Vec<AddressStep>,
107    /// Absolute offset within the leaf space.
108    pub offset: u64,
109}
110
111impl ResolvedAddress {
112    /// Formats the address as a stable path string.
113    pub fn path(&self) -> String {
114        let mut out = String::new();
115        for (index, step) in self.steps.iter().enumerate() {
116            if index > 0 {
117                out.push('/');
118            }
119            out.push_str(&step.label);
120        }
121        out.push('@');
122        out.push_str(&self.offset.to_string());
123        out
124    }
125
126    /// Returns `cadmpeg inspect` commands that replay this byte location.
127    ///
128    /// Nested archive members emit `inspect extract` then `inspect hex` on the
129    /// extracted member. Root-only addresses emit `inspect hex` on the file.
130    pub fn inspect_commands(&self, file: &str) -> Vec<String> {
131        let leaf = self.steps.last();
132        match leaf.map(|step| step.kind) {
133            None | Some(AddressStepKind::Root) => {
134                vec![format!(
135                    "cadmpeg inspect hex {file} --offset {} --len 64",
136                    self.offset
137                )]
138            }
139            Some(AddressStepKind::StoredMember | AddressStepKind::ExpandedMember) => {
140                let member = leaf.expect("leaf present").label.clone();
141                let extracted = format!("{file}.member");
142                vec![
143                    format!("cadmpeg inspect extract {file} {member} -o {extracted}"),
144                    format!(
145                        "cadmpeg inspect hex {extracted} --offset {} --len 64",
146                        self.offset
147                    ),
148                ]
149            }
150        }
151    }
152}
153
154/// Resolves a session-local location against a descriptor table.
155pub fn resolve_address(
156    descriptors: &[SpaceDescriptor],
157    location: super::error::SourceLocation,
158) -> ResolvedAddress {
159    let mut steps = Vec::new();
160    let mut current = location.space;
161    let mut guard = 0u32;
162    while guard < descriptors.len() as u32 + 1 {
163        guard = guard.saturating_add(1);
164        let Some(descriptor) = descriptors.iter().find(|entry| entry.id == current) else {
165            break;
166        };
167        let kind = match descriptor.derivation {
168            SpaceDerivation::Root => AddressStepKind::Root,
169            SpaceDerivation::StoredSlice { .. } => AddressStepKind::StoredMember,
170            SpaceDerivation::Expanded { .. } | SpaceDerivation::Concatenated { .. } => {
171                AddressStepKind::ExpandedMember
172            }
173        };
174        steps.push(AddressStep {
175            label: descriptor.label.clone(),
176            kind,
177        });
178        match descriptor.derivation {
179            SpaceDerivation::Root => break,
180            SpaceDerivation::StoredSlice { parent, .. }
181            | SpaceDerivation::Expanded { parent, .. } => {
182                current = parent;
183            }
184            SpaceDerivation::Concatenated { ref parents } => {
185                if let Some(parent) = parents.first().copied() {
186                    current = parent;
187                } else {
188                    break;
189                }
190            }
191        }
192    }
193    steps.reverse();
194    if steps.is_empty() {
195        steps.push(AddressStep {
196            label: "root".into(),
197            kind: AddressStepKind::Root,
198        });
199    }
200    ResolvedAddress {
201        steps,
202        offset: location.offset,
203    }
204}