use crate::layout;
use crate::Chip;
const BANNER_OFFSET: usize = layout::BANNER.start;
const BANNER_LEN: usize = layout::BANNER.len;
const DESCRIPTOR_OFFSET: usize = layout::DESCRIPTOR.start;
const DESCRIPTOR_LEN: usize = 0x40;
const DESCRIPTOR_TAG_OFF: usize = 0x34;
const DESCRIPTOR_MARKER_OFF: usize = 0x50;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ChipInfo {
pub family: Chip,
pub banner: String,
pub vendor: String,
pub model: String,
pub rev: String,
pub vendor_specific: String,
pub identity_tag: Option<String>,
pub marker_0x50: Option<u8>,
pub descriptor_present: bool,
}
fn ascii_trim(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes)
.trim_matches(|c: char| c == '\0' || c.is_whitespace())
.to_string()
}
fn first_mtek_tag(image: &[u8]) -> Option<String> {
const PREFIX: &[u8] = b"MTEKMT19";
if image.len() < PREFIX.len() + 2 {
return None;
}
let mut i = 0;
while i + PREFIX.len() + 2 <= image.len() {
if &image[i..i + PREFIX.len()] == PREFIX {
let d0 = image[i + PREFIX.len()];
let d1 = image[i + PREFIX.len() + 1];
if d0.is_ascii_digit() && d1.is_ascii_digit() {
return Some(String::from_utf8_lossy(&image[i..i + PREFIX.len() + 2]).into_owned());
}
}
i += 1;
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChipError {
Undetectable,
}
impl std::fmt::Display for ChipError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Undetectable => f.write_str(
"undetectable — the boot loader banner at 0x3000 names no supported MT19xx \
generation (only MT1959 and MT1939 are handled; unpack packed/truncated \
dumps first)",
),
}
}
}
impl std::error::Error for ChipError {}
pub fn detect_chip(image: &[u8]) -> Result<ChipInfo, ChipError> {
let banner = read_banner(image);
let family = banner
.as_deref()
.and_then(|b: &str| Chip::from_banner(b.as_bytes()))
.ok_or(ChipError::Undetectable)?;
let identity_tag = first_mtek_tag(image);
let (vendor, model, rev, vendor_specific, marker_0x50, descriptor_present) =
parse_descriptor(image);
Ok(ChipInfo {
family,
banner: banner.unwrap_or_default(),
vendor,
model,
rev,
vendor_specific,
identity_tag,
marker_0x50,
descriptor_present,
})
}
fn read_banner(image: &[u8]) -> Option<String> {
if image.len() < BANNER_OFFSET + BANNER_LEN {
return None;
}
let banner_bytes = &image[BANNER_OFFSET..BANNER_OFFSET + BANNER_LEN];
let nul = banner_bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(banner_bytes.len());
let s = ascii_trim(&banner_bytes[..nul]);
if s.is_empty() {
None
} else {
Some(s)
}
}
fn parse_descriptor(image: &[u8]) -> (String, String, String, String, Option<u8>, bool) {
if image.len() < DESCRIPTOR_OFFSET + DESCRIPTOR_LEN {
return (
String::new(),
String::new(),
String::new(),
String::new(),
None,
false,
);
}
let desc = &image[DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + DESCRIPTOR_LEN];
let tag_present = desc[DESCRIPTOR_TAG_OFF..].starts_with(b"MTEKMT19");
let vendor = ascii_trim(&desc[0x00..0x08]);
let model = ascii_trim(&desc[0x08..0x18]);
let rev = ascii_trim(&desc[0x18..0x1C]);
let vendor_specific = ascii_trim(&desc[0x1C..0x24]);
let marker = image
.get(DESCRIPTOR_OFFSET + DESCRIPTOR_MARKER_OFF)
.copied();
if tag_present {
(vendor, model, rev, vendor_specific, marker, true)
} else {
(
String::new(),
String::new(),
String::new(),
String::new(),
marker,
false,
)
}
}
#[cfg(test)]
#[path = "chip_tests.rs"]
mod tests;