Skip to main content

memf_format/
raw.rs

1//! Raw (contiguous) memory dump format provider.
2//!
3//! The simplest possible format: a flat byte array representing contiguous
4//! physical memory starting at address 0.
5
6use std::path::Path;
7
8use crate::{FormatPlugin, PhysicalMemoryProvider, PhysicalRange, Result};
9
10/// Provider that exposes physical memory from a raw (flat) dump.
11///
12/// The entire file is treated as a single range `[0, data.len())`.
13#[derive(Debug)]
14pub struct RawProvider {
15    data: Vec<u8>,
16    /// Pre-extracted ranges for the `ranges()` slice return.
17    ranges: Vec<PhysicalRange>,
18}
19
20impl RawProvider {
21    /// Construct a `RawProvider` from an in-memory byte slice (infallible).
22    pub fn from_bytes(bytes: &[u8]) -> Self {
23        let data = bytes.to_vec();
24        let ranges = if data.is_empty() {
25            vec![]
26        } else {
27            vec![PhysicalRange {
28                start: 0,
29                end: data.len() as u64,
30            }]
31        };
32        Self { data, ranges }
33    }
34
35    /// Construct a `RawProvider` by reading a file from the given path.
36    pub fn from_path(path: &Path) -> Result<Self> {
37        let data = std::fs::read(path)?;
38        Ok(Self::from_bytes(&data))
39    }
40}
41
42impl PhysicalMemoryProvider for RawProvider {
43    fn read_phys(&self, addr: u64, buf: &mut [u8]) -> Result<usize> {
44        if buf.is_empty() {
45            return Ok(0);
46        }
47
48        let data_len = self.data.len() as u64;
49        if addr >= data_len {
50            return Ok(0);
51        }
52
53        let src_start = addr as usize;
54        let available = self.data.len() - src_start;
55        let to_read = buf.len().min(available);
56        buf[..to_read].copy_from_slice(&self.data[src_start..src_start + to_read]);
57        Ok(to_read)
58    }
59
60    fn ranges(&self) -> &[PhysicalRange] {
61        &self.ranges
62    }
63
64    fn format_name(&self) -> &str {
65        "Raw"
66    }
67}
68
69/// FormatPlugin implementation for raw (flat) dumps.
70///
71/// This is the lowest-confidence fallback: any non-empty file can be treated
72/// as a raw dump, so the probe returns 5 (not 0) for non-empty files.
73pub struct RawPlugin;
74
75impl FormatPlugin for RawPlugin {
76    fn name(&self) -> &str {
77        "Raw"
78    }
79
80    fn probe(&self, header: &[u8]) -> u8 {
81        if header.is_empty() {
82            0
83        } else {
84            5
85        }
86    }
87
88    fn open(&self, path: &Path) -> Result<Box<dyn PhysicalMemoryProvider>> {
89        Ok(Box::new(RawProvider::from_path(path)?))
90    }
91}
92
93inventory::submit!(&RawPlugin as &dyn FormatPlugin);
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    // Test 1: probe returns 5 for non-empty, 0 for empty.
100    #[test]
101    fn probe_confidence() {
102        let plugin = RawPlugin;
103        assert_eq!(plugin.probe(&[0u8; 64]), 5);
104        assert_eq!(plugin.probe(&[]), 0);
105    }
106
107    // Test 2: basic read from start.
108    #[test]
109    fn read_from_start() {
110        let data: Vec<u8> = (0u8..=255).collect();
111        let provider = RawProvider::from_bytes(&data);
112
113        assert_eq!(provider.ranges().len(), 1);
114        assert_eq!(provider.ranges()[0].start, 0);
115        assert_eq!(provider.ranges()[0].end, 256);
116        assert_eq!(provider.total_size(), 256);
117
118        let mut buf = [0u8; 4];
119        let n = provider.read_phys(0, &mut buf).unwrap();
120        assert_eq!(n, 4);
121        assert_eq!(&buf, &[0, 1, 2, 3]);
122    }
123
124    // Test 3: reading past end returns 0.
125    #[test]
126    fn read_past_end() {
127        let data = vec![0xFFu8; 64];
128        let provider = RawProvider::from_bytes(&data);
129
130        let mut buf = [0u8; 4];
131        let n = provider.read_phys(64, &mut buf).unwrap();
132        assert_eq!(n, 0);
133
134        let n2 = provider.read_phys(1000, &mut buf).unwrap();
135        assert_eq!(n2, 0);
136    }
137
138    // Test 4: partial read when buffer extends past end.
139    #[test]
140    fn read_partial() {
141        let data = vec![0xABu8; 10];
142        let provider = RawProvider::from_bytes(&data);
143
144        let mut buf = [0u8; 8];
145        let n = provider.read_phys(6, &mut buf).unwrap();
146        assert_eq!(n, 4); // only 4 bytes remain (10 - 6)
147        assert_eq!(&buf[..4], &[0xABu8; 4]);
148    }
149
150    // Test 5: empty dump has no ranges and total_size 0.
151    #[test]
152    fn empty_dump() {
153        let provider = RawProvider::from_bytes(&[]);
154        assert_eq!(provider.ranges().len(), 0);
155        assert_eq!(provider.total_size(), 0);
156    }
157
158    #[test]
159    fn from_path_roundtrip() {
160        let data: Vec<u8> = (0u8..=127).collect();
161        let path = std::env::temp_dir().join("memf_test_raw_from_path.raw");
162        std::fs::write(&path, &data).unwrap();
163        let provider = RawProvider::from_path(&path).unwrap();
164        assert_eq!(provider.ranges().len(), 1);
165        assert_eq!(provider.total_size(), 128);
166        assert_eq!(provider.format_name(), "Raw");
167        let mut buf = [0u8; 4];
168        let n = provider.read_phys(0, &mut buf).unwrap();
169        assert_eq!(n, 4);
170        assert_eq!(&buf, &[0, 1, 2, 3]);
171        std::fs::remove_file(&path).ok();
172    }
173
174    #[test]
175    fn plugin_name() {
176        let plugin = RawPlugin;
177        assert_eq!(plugin.name(), "Raw");
178    }
179
180    #[test]
181    fn read_phys_empty_buffer() {
182        let data = vec![0xFFu8; 64];
183        let provider = RawProvider::from_bytes(&data);
184        let mut buf = [];
185        let n = provider.read_phys(0, &mut buf).unwrap();
186        assert_eq!(n, 0);
187    }
188}