Skip to content

Commit 1ebb3c7

Browse files
committed
rework parsing
- use IFD for the common case - split off scanning from parsing - add scan and check commands - move offset handling to FPT itself, save offset - split off Firmware, ME and FPT - wrap directories and expose ME generation Signed-off-by: Daniel Maslowski <[email protected]>
1 parent 87fbcb2 commit 1ebb3c7

File tree

5 files changed

+304
-216
lines changed

5 files changed

+304
-216
lines changed

src/fpt.rs

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@ use serde::{Deserialize, Serialize};
2222
use zerocopy::{AlignmentError, ConvertError, FromBytes, IntoBytes, Ref, SizeError};
2323
use zerocopy_derive::{FromBytes, Immutable, IntoBytes};
2424

25-
use crate::{
26-
dir::gen2::Directory as Gen2Directory,
27-
dir::gen3::CodePartitionDirectory,
28-
fit::{Fit, FitError},
29-
ver::Version,
30-
};
25+
use crate::ver::Version;
3126

3227
const FPT_MAGIC: &str = "$FPT";
3328

@@ -127,44 +122,65 @@ impl Display for FPTEntry {
127122

128123
#[derive(Serialize, Deserialize, Clone, Debug)]
129124
pub struct FPT {
125+
pub offset: usize,
130126
pub header: FPTHeader,
131127
pub entries: Vec<FPTEntry>,
132128
}
133129

134-
impl<'a> FPT {
135-
pub fn parse(data: &'a [u8]) -> Option<Result<Self, FptError<'a>>> {
136-
let m = &data[..4];
130+
pub const FPT_SIZE: usize = size_of::<FPT>();
131+
132+
// The FPT magic is either at the start or at a 16 bytes offset.
133+
fn determine_offset(data: &[u8]) -> Option<usize> {
134+
let m = &data[..4];
135+
if m.eq(FPT_MAGIC.as_bytes()) {
136+
return Some(0);
137+
} else {
138+
let m = &data[16..20];
137139
if m.eq(FPT_MAGIC.as_bytes()) {
138-
let header = match FPTHeader::read_from_prefix(data) {
139-
Ok((h, _)) => h,
140-
Err(e) => return Some(Err(FptError::HeaderParseError(e))),
141-
};
142-
// NOTE: Skip $FPT (header) itself
143-
let slice = &data[FPT_HEADER_SIZE..];
144-
let count = header.entries as usize;
145-
let entries = match Ref::<_, [FPTEntry]>::from_prefix_with_elems(slice, count) {
146-
Ok((r, _)) => r,
147-
Err(e) => return Some(Err(FptError::EntryParseError(e))),
148-
};
149-
150-
Some(Ok(Self {
151-
header,
152-
entries: entries.to_vec(),
153-
}))
140+
return Some(16);
154141
} else {
155-
None
142+
return None;
156143
}
157144
}
158145
}
159146

160-
#[allow(non_camel_case_types)]
161-
#[derive(Serialize, Deserialize, Clone, Debug)]
162-
pub struct ME_FW {
163-
pub base: usize,
164-
pub fpt: FPT,
165-
pub gen3dirs: Vec<CodePartitionDirectory>,
166-
pub gen2dirs: Vec<Gen2Directory>,
167-
pub fit: Result<Fit, FitError>,
147+
impl<'a> FPT {
148+
pub fn parse(data: &'a [u8]) -> Option<Result<Self, FptError<'a>>> {
149+
let Some(offset) = determine_offset(data) else {
150+
return None;
151+
};
152+
let d = &data[offset..];
153+
let header = match FPTHeader::read_from_prefix(d) {
154+
Ok((h, _)) => h,
155+
Err(e) => return Some(Err(FptError::HeaderParseError(e))),
156+
};
157+
// NOTE: Skip $FPT (header) itself
158+
let slice = &d[FPT_HEADER_SIZE..];
159+
let count = header.entries as usize;
160+
let entries = match Ref::<_, [FPTEntry]>::from_prefix_with_elems(slice, count) {
161+
Ok((r, _)) => r,
162+
Err(e) => return Some(Err(FptError::EntryParseError(e))),
163+
};
164+
165+
Some(Ok(Self {
166+
offset,
167+
header,
168+
entries: entries.to_vec(),
169+
}))
170+
}
171+
172+
// Find an FPT in a given slice, and if the magic is detected, get the
173+
// parse result and the offset.
174+
pub fn scan(data: &'a [u8]) -> Option<(Result<Self, FptError<'a>>, usize)> {
175+
let mut o = 0;
176+
while o + 16 + FPT_SIZE <= data.len() {
177+
if let Some(fpt) = Self::parse(data) {
178+
return Some((fpt, o));
179+
}
180+
o += 16;
181+
}
182+
None
183+
}
168184
}
169185

170186
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -218,7 +234,7 @@ static FPT_CLEANED: &[u8] = include_bytes!("../tests/me11_cleaned.fpt");
218234

219235
#[test]
220236
fn parse_size_error() {
221-
let parsed = FPT::parse(&DATA[16..70]);
237+
let parsed = FPT::parse(&DATA[..70]);
222238
assert!(parsed.is_some());
223239
let fpt_res = parsed.unwrap();
224240
assert!(matches!(fpt_res, Err(FptError::EntryParseError(_))));
@@ -234,9 +250,19 @@ fn parse_okay_fpt() {
234250
assert_eq!(fpt.header.entries as usize, fpt.entries.len());
235251
}
236252

253+
#[test]
254+
fn parse_okay_fpt_with_offset() {
255+
let parsed = FPT::parse(&DATA);
256+
assert!(parsed.is_some());
257+
let fpt_res = parsed.unwrap();
258+
assert!(fpt_res.is_ok());
259+
let fpt = fpt_res.unwrap();
260+
assert_eq!(fpt.header.entries as usize, fpt.entries.len());
261+
}
262+
237263
#[test]
238264
fn checksum() {
239-
let parsed = FPT::parse(&DATA[16..12 * 32 + 16]);
265+
let parsed = FPT::parse(&DATA);
240266
let fpt = parsed.unwrap().unwrap();
241267
assert_eq!(fpt.header.checksum(), fpt.header.checksum);
242268
}

src/lib.rs

Lines changed: 26 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,43 @@
11
#![doc = include_str!("../README.md")]
22

3-
use core::mem;
4-
use log::{error, warn};
3+
use log::{info, warn};
4+
use serde::{Deserialize, Serialize};
55

66
pub mod dir;
77
pub mod fit;
88
pub mod fpt;
99
pub mod ifd;
10+
pub mod me;
1011
pub mod meta;
1112
pub mod ver;
1213

13-
pub use fpt::ME_FW;
14-
use fpt::{AFSP, DLMP, EFFS, FTPR, FTUP, MDMV, MFS, NFTP};
14+
use fit::{Fit, FitError};
15+
use ifd::{IFD, IfdError};
16+
use me::ME;
1517

16-
fn dump48(data: &[u8]) {
17-
println!("Here are the first 48 bytes:");
18-
let b = &data[0..0x10];
19-
println!("{b:02x?}");
20-
let b = &data[0x10..0x20];
21-
println!("{b:02x?}");
22-
let b = &data[0x20..0x30];
23-
println!("{b:02x?}");
18+
#[derive(Serialize, Deserialize, Clone, Debug)]
19+
pub struct Firmware {
20+
pub ifd: Result<IFD, IfdError>,
21+
pub me: Option<Result<ME, String>>,
22+
pub fit: Result<Fit, FitError>,
2423
}
2524

26-
pub fn parse(data: &[u8], debug: bool) -> Result<ME_FW, String> {
27-
let ifd = ifd::IFD::parse(&data);
28-
match ifd {
29-
Ok(ifd) => println!("{ifd}"),
30-
Err(e) => warn!("Not a full image: {e:?}"),
31-
}
32-
33-
let fit = fit::Fit::new(data);
34-
35-
let mut gen2dirs = Vec::<dir::gen2::Directory>::new();
36-
let mut gen3dirs = Vec::<dir::gen3::CodePartitionDirectory>::new();
37-
38-
// Scan for all CPDs (there may be some not listed in FPT)
39-
if debug {
40-
let mut o = 0;
41-
while o < data.len() {
42-
let buf = &data[o..o + 4];
43-
if buf.eq(dir::gen3::CPD_MAGIC_BYTES) {
44-
let Ok(cpd) = dir::gen3::CodePartitionDirectory::new(data[o..].to_vec(), o) else {
45-
continue;
46-
};
47-
gen3dirs.push(cpd);
25+
impl Firmware {
26+
pub fn parse(data: &[u8], debug: bool) -> Self {
27+
let ifd = IFD::parse(&data);
28+
let me = match &ifd {
29+
Ok(ifd) => {
30+
let me_region = ifd.regions.flreg2.range();
31+
let (b, l) = me_region;
32+
info!("ME region start @ {b:08x}");
33+
ME::parse(&data[b..l], b, debug)
4834
}
49-
o += 16;
50-
}
51-
println!("Found {} CPDs doing a full scan", gen3dirs.len());
52-
}
53-
54-
let mut base = 0;
55-
while base + 16 + mem::size_of::<fpt::FPT>() <= data.len() {
56-
// first 16 bytes are potentially other stuff
57-
let o = base + 16;
58-
if let Some(r) = fpt::FPT::parse(&data[o..]) {
59-
let fpt = match r {
60-
Ok(r) => r,
61-
Err(e) => {
62-
error!("Cannot parse ME FPT entries @ {o:08x}: {e:?}");
63-
continue;
64-
}
65-
};
66-
// realign base; what does this indicate?
67-
if base % 0x1000 != 0 {
68-
base = o;
69-
if debug {
70-
println!("Realigned FPT base to {o:08x}");
71-
}
72-
}
73-
74-
for e in &fpt.entries {
75-
let name = match std::str::from_utf8(&e.name) {
76-
// some names are shorter than 4 bytes and padded with 0x0
77-
Ok(n) => n.trim_end_matches('\0').to_string(),
78-
Err(_) => format!("{:02x?}", &e.name),
79-
};
80-
let n = u32::from_be_bytes(e.name);
81-
let o = base + (e.offset & 0x003f_ffff) as usize;
82-
let s = e.size as usize;
83-
match n {
84-
MDMV | DLMP | FTPR | NFTP => {
85-
if o + 4 < data.len() {
86-
let buf = &data[o..o + 4];
87-
if buf.eq(dir::gen3::CPD_MAGIC_BYTES) {
88-
if let Ok(cpd) = dir::gen3::CodePartitionDirectory::new(
89-
data[o..o + s].to_vec(),
90-
o,
91-
) {
92-
gen3dirs.push(cpd);
93-
}
94-
} else if let Ok(dir) = dir::gen2::Directory::new(&data[o..], o) {
95-
gen2dirs.push(dir);
96-
} else if debug {
97-
println!("{name} @ {o:08x} has no CPD signature");
98-
dump48(&data[o..]);
99-
}
100-
}
101-
}
102-
MFS | AFSP | EFFS => {
103-
// TODO: parse MFS
104-
}
105-
_ => {
106-
if !debug {
107-
continue;
108-
}
109-
// We may encounter unknown CPDs.
110-
if n != FTUP && o + 4 < data.len() {
111-
let buf = &data[o..o + 4];
112-
if let Ok(sig) = std::str::from_utf8(buf) {
113-
if sig == dir::gen3::CPD_MAGIC {
114-
println!("Unknown $CPD in {name} @ 0x{o:08x} (0x{s:08x}).");
115-
continue;
116-
}
117-
}
118-
}
119-
if let Ok(m) = dir::man::Manifest::new(&data[o..]) {
120-
println!("Manifest found in {name}: {m}");
121-
continue;
122-
}
123-
println!("Cannot (yet) parse {name} @ 0x{o:08x} (0x{s:08x}), skipping...");
124-
if debug {
125-
dump48(&data[o..]);
126-
}
127-
}
128-
}
35+
Err(e) => {
36+
warn!("Not a full image: {e:?}");
37+
ME::parse(data, 0, debug)
12938
}
130-
131-
return Ok(ME_FW {
132-
base,
133-
fpt,
134-
gen3dirs,
135-
gen2dirs,
136-
fit,
137-
});
138-
}
139-
base += 16;
39+
};
40+
let fit = Fit::new(data);
41+
Self { ifd, me, fit }
14042
}
141-
Err("No $FPT :(".to_string())
14243
}

src/main.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::fs;
22

33
use clap::{Parser, Subcommand, ValueEnum};
4-
use log::{debug, error, info, trace, warn};
4+
use log::{debug, info};
55

66
mod clean;
77
mod show;
88

9-
use intel_fw::parse;
9+
use intel_fw::Firmware;
1010

1111
#[derive(Clone, Copy, Debug, ValueEnum)]
1212
enum Partition {
@@ -55,12 +55,24 @@ enum MeCommand {
5555
/// File to read
5656
file_name: String,
5757
},
58-
/// Display the (CS)ME high-level structures
58+
/// Display the (CS)ME high-level structures (full image or ME region)
5959
#[clap(verbatim_doc_comment)]
6060
Show {
6161
/// File to read
6262
file_name: String,
6363
},
64+
/// Scan for (CS)ME data structures (useful for update images)
65+
#[clap(verbatim_doc_comment)]
66+
Scan {
67+
/// File to read
68+
file_name: String,
69+
},
70+
/// Check for consistency (full image or ME region)
71+
#[clap(verbatim_doc_comment)]
72+
Check {
73+
/// File to read
74+
file_name: String,
75+
},
6476
}
6577

6678
#[derive(Subcommand)]
@@ -146,27 +158,21 @@ fn main() {
146158
}
147159
info!("Reading {file_name}...");
148160
let data = fs::read(file_name).unwrap();
149-
match parse(&data, debug) {
150-
Ok(fpt) => {
151-
show::show(&fpt, verbose);
152-
println!();
153-
todo!("clean");
154-
}
155-
Err(e) => {
156-
error!("Could not parse ME FPT: {e}");
157-
}
158-
}
161+
let fw = Firmware::parse(&data, debug);
162+
show::show(&fw, verbose);
163+
println!();
164+
todo!("clean");
165+
}
166+
MeCommand::Scan { file_name } => {
167+
todo!("scan {file_name}")
168+
}
169+
MeCommand::Check { file_name } => {
170+
todo!("check {file_name}")
159171
}
160172
MeCommand::Show { file_name } => {
161173
let data = fs::read(file_name).unwrap();
162-
match parse(&data, debug) {
163-
Ok(fpt) => {
164-
show::show(&fpt, verbose);
165-
}
166-
Err(e) => {
167-
error!("Could not parse ME FPT: {e}");
168-
}
169-
}
174+
let fw = Firmware::parse(&data, debug);
175+
show::show(&fw, verbose);
170176
}
171177
},
172178
}

0 commit comments

Comments
 (0)