1use std::path::Path;
7
8use crate::{Error, FormatPlugin, PhysicalMemoryProvider, PhysicalRange, Result};
9
10const LIME_MAGIC: u32 = 0x4C694D45;
11const LIME_VERSION: u32 = 1;
12const HEADER_SIZE: usize = 32;
13
14fn le_u32(data: &[u8], off: usize) -> u32 {
16 data.get(off..off + 4)
17 .and_then(|s| s.try_into().ok())
18 .map_or(0, u32::from_le_bytes)
19}
20
21fn le_u64(data: &[u8], off: usize) -> u64 {
23 data.get(off..off + 8)
24 .and_then(|s| s.try_into().ok())
25 .map_or(0, u64::from_le_bytes)
26}
27
28#[derive(Debug)]
30struct LimeRecord {
31 range: PhysicalRange,
32 data_offset: usize,
34}
35
36#[derive(Debug)]
41pub struct LimeProvider {
42 data: Vec<u8>,
43 records: Vec<LimeRecord>,
44 ranges: Vec<PhysicalRange>,
46}
47
48impl LimeProvider {
49 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
51 let data = bytes.to_vec();
52 let records = parse_records(&data)?;
53 let ranges = records.iter().map(|r| r.range.clone()).collect();
54 Ok(Self {
55 data,
56 records,
57 ranges,
58 })
59 }
60
61 pub fn from_path(path: &Path) -> Result<Self> {
63 let data = std::fs::read(path)?;
64 let records = parse_records(&data)?;
65 let ranges = records.iter().map(|r| r.range.clone()).collect();
66 Ok(Self {
67 data,
68 records,
69 ranges,
70 })
71 }
72}
73
74fn parse_records(data: &[u8]) -> Result<Vec<LimeRecord>> {
76 let mut records = Vec::new();
77 let mut pos = 0usize;
78
79 while pos < data.len() {
80 if data.len() - pos < HEADER_SIZE {
82 return Err(Error::Corrupt(format!(
83 "truncated header at offset {pos}: only {} bytes remain",
84 data.len() - pos
85 )));
86 }
87
88 let magic = le_u32(data, pos);
89 if magic != LIME_MAGIC {
90 return Err(Error::Corrupt(format!(
91 "bad magic at offset {pos}: expected 0x{LIME_MAGIC:08X}, got 0x{magic:08X}"
92 )));
93 }
94
95 let version = le_u32(data, pos + 4);
96 if version != LIME_VERSION {
97 return Err(Error::Corrupt(format!(
98 "unsupported LiME version {version} at offset {pos}"
99 )));
100 }
101
102 let s_addr = le_u64(data, pos + 8);
103 let e_addr = le_u64(data, pos + 16);
104 if s_addr > e_addr {
107 return Err(Error::Corrupt(format!(
108 "record at offset {pos}: s_addr 0x{s_addr:016X} > e_addr 0x{e_addr:016X}"
109 )));
110 }
111
112 let payload_len = (e_addr - s_addr + 1) as usize;
114 let data_offset = pos + HEADER_SIZE;
115
116 if data.len() - data_offset < payload_len {
117 return Err(Error::Corrupt(format!(
118 "record at offset {pos}: payload truncated (need {payload_len}, have {})",
119 data.len() - data_offset
120 )));
121 }
122
123 records.push(LimeRecord {
124 range: PhysicalRange {
125 start: s_addr,
126 end: e_addr + 1, },
128 data_offset,
129 });
130
131 pos = data_offset + payload_len;
132 }
133
134 Ok(records)
135}
136
137impl PhysicalMemoryProvider for LimeProvider {
138 fn read_phys(&self, addr: u64, buf: &mut [u8]) -> Result<usize> {
139 if buf.is_empty() {
140 return Ok(0);
141 }
142
143 for record in &self.records {
144 if record.range.contains_addr(addr) {
145 let offset_in_range = (addr - record.range.start) as usize;
146 let available = (record.range.end - addr) as usize;
147 let to_read = buf.len().min(available);
148 let src_start = record.data_offset + offset_in_range;
149 buf[..to_read].copy_from_slice(&self.data[src_start..src_start + to_read]);
150 return Ok(to_read);
151 }
152 }
153
154 Ok(0)
156 }
157
158 fn ranges(&self) -> &[PhysicalRange] {
159 &self.ranges
160 }
161
162 fn format_name(&self) -> &str {
163 "LiME"
164 }
165}
166
167pub struct LimePlugin;
169
170impl FormatPlugin for LimePlugin {
171 fn name(&self) -> &str {
172 "LiME"
173 }
174
175 fn probe(&self, header: &[u8]) -> u8 {
176 if header.len() < 8 {
177 return 0;
178 }
179 let magic = le_u32(header, 0);
180 let version = le_u32(header, 4);
181 if magic == LIME_MAGIC && version == LIME_VERSION {
182 90
183 } else {
184 0
185 }
186 }
187
188 fn open(&self, path: &Path) -> Result<Box<dyn PhysicalMemoryProvider>> {
189 Ok(Box::new(LimeProvider::from_path(path)?))
190 }
191}
192
193inventory::submit!(&LimePlugin as &dyn FormatPlugin);
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use crate::test_builders::LimeBuilder;
199
200 fn parse(bytes: &[u8]) -> Result<LimeProvider> {
201 LimeProvider::from_bytes(bytes)
202 }
203
204 #[test]
205 fn probe_lime_magic() {
206 let dump = LimeBuilder::new().add_range(0x1000, &[0u8; 0x1000]).build();
207 let plugin = LimePlugin;
208 assert_eq!(plugin.probe(&dump), 90);
209 }
210
211 #[test]
212 fn probe_non_lime() {
213 let zeros = vec![0u8; 64];
214 let plugin = LimePlugin;
215 assert_eq!(plugin.probe(&zeros), 0);
216 }
217
218 #[test]
219 fn single_range() {
220 let data: Vec<u8> = (0u8..=255).collect();
221 let dump = LimeBuilder::new().add_range(0x1000, &data).build();
222 let provider = parse(&dump).unwrap();
223
224 assert_eq!(provider.ranges().len(), 1);
225 assert_eq!(provider.ranges()[0].start, 0x1000);
226 assert_eq!(provider.ranges()[0].end, 0x1100); assert_eq!(provider.total_size(), 256);
229 assert_eq!(provider.format_name(), "LiME");
230
231 let mut buf = [0u8; 4];
232 let n = provider.read_phys(0x1000, &mut buf).unwrap();
233 assert_eq!(n, 4);
234 assert_eq!(&buf, &[0, 1, 2, 3]);
235 }
236
237 #[test]
238 fn two_ranges() {
239 let data_a = vec![0xAAu8; 0x2000];
240 let data_b = vec![0xBBu8; 0x1000];
241 let dump = LimeBuilder::new()
242 .add_range(0x0000, &data_a)
243 .add_range(0x4000, &data_b)
244 .build();
245 let provider = parse(&dump).unwrap();
246
247 assert_eq!(provider.ranges().len(), 2);
248 assert_eq!(provider.total_size(), 0x3000);
249
250 let mut buf = [0u8; 2];
251
252 let n = provider.read_phys(0x0000, &mut buf).unwrap();
253 assert_eq!(n, 2);
254 assert_eq!(buf, [0xAA, 0xAA]);
255
256 let n = provider.read_phys(0x4000, &mut buf).unwrap();
257 assert_eq!(n, 2);
258 assert_eq!(buf, [0xBB, 0xBB]);
259 }
260
261 #[test]
262 fn read_gap_returns_zero() {
263 let data = vec![0xCCu8; 0x1000];
264 let dump = LimeBuilder::new().add_range(0x1000, &data).build();
265 let provider = parse(&dump).unwrap();
266
267 let mut buf = [0xFFu8; 4];
269 let n = provider.read_phys(0x0000, &mut buf).unwrap();
270 assert_eq!(n, 0);
271 }
272
273 #[test]
274 fn corrupt_magic_errors() {
275 let mut dump = LimeBuilder::new().add_range(0x1000, &[0u8; 64]).build();
276 dump[0] = 0xFF;
278 let err = parse(&dump).unwrap_err();
279 assert!(
280 matches!(err, Error::Corrupt(_)),
281 "expected Corrupt, got {err:?}"
282 );
283 }
284
285 #[test]
286 fn truncated_header_errors() {
287 let truncated = vec![0x45u8, 0x4D, 0x69, 0x4C]; let err = parse(&truncated).unwrap_err();
290 assert!(
291 matches!(err, Error::Corrupt(_)),
292 "expected Corrupt, got {err:?}"
293 );
294 }
295
296 #[test]
297 fn from_path_roundtrip() {
298 let data: Vec<u8> = (0u8..=127).collect();
299 let dump = LimeBuilder::new().add_range(0x2000, &data).build();
300 let path = std::env::temp_dir().join("memf_test_lime_from_path.lime");
301 std::fs::write(&path, &dump).unwrap();
302 let provider = LimeProvider::from_path(&path).unwrap();
303 assert_eq!(provider.ranges().len(), 1);
304 assert_eq!(provider.total_size(), 128);
305 assert_eq!(provider.format_name(), "LiME");
306 let mut buf = [0u8; 4];
307 let n = provider.read_phys(0x2000, &mut buf).unwrap();
308 assert_eq!(n, 4);
309 assert_eq!(&buf, &[0, 1, 2, 3]);
310 std::fs::remove_file(&path).ok();
311 }
312
313 #[test]
314 fn plugin_name() {
315 let plugin = LimePlugin;
316 assert_eq!(plugin.name(), "LiME");
317 }
318
319 #[test]
320 fn probe_short_header_returns_zero() {
321 let plugin = LimePlugin;
322 assert_eq!(plugin.probe(&[0x45, 0x4D, 0x69]), 0); assert_eq!(plugin.probe(&[]), 0);
324 }
325
326 #[test]
327 fn read_phys_empty_buffer() {
328 let dump = LimeBuilder::new().add_range(0x1000, &[0xAA; 64]).build();
329 let provider = parse(&dump).unwrap();
330 let mut buf = [];
331 let n = provider.read_phys(0x1000, &mut buf).unwrap();
332 assert_eq!(n, 0);
333 }
334}