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, Copy, PartialEq, Eq)]
pub enum Confidence {
TagString,
BannerFallback,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChipInfo {
pub family: Chip,
pub confidence: Confidence,
pub banner: String,
pub vendor: String,
pub model: String,
pub rev: String,
pub vendor_specific: String,
pub tag_string: 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 scan_mtek_tags(image: &[u8]) -> Vec<String> {
const PREFIX: &[u8] = b"MTEKMT19";
let mut seen: Vec<String> = Vec::new();
if image.len() < PREFIX.len() + 2 {
return seen;
}
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() {
let tag = String::from_utf8_lossy(&image[i..i + PREFIX.len() + 2]).into_owned();
if !seen.contains(&tag) {
seen.push(tag);
}
i += PREFIX.len() + 2;
continue;
}
}
i += 1;
}
seen
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChipError {
Undetectable,
Unsupported(Vec<String>),
Conflicting(Vec<String>),
}
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 — no MTEKMT19xx identity string and no MT19xx boot banner \
(unpack packed/truncated dumps first)",
),
Self::Unsupported(tags) => write!(
f,
"image names an unsupported MT19xx variant ({}) — only MT1959 and MT1939 are handled",
tags.join(", ")
),
Self::Conflicting(tags) => write!(
f,
"image carries conflicting chip-family identity strings ({}) — corrupt or spliced; \
refusing to guess",
tags.join(", ")
),
}
}
}
impl std::error::Error for ChipError {}
pub fn detect_chip(image: &[u8]) -> Result<ChipInfo, ChipError> {
let tags = scan_mtek_tags(image);
let mut families: Vec<(Chip, String)> = Vec::new();
for tag in &tags {
let b = tag.as_bytes();
if let Some(fam) = Chip::from_digits(b[b.len() - 2], b[b.len() - 1]) {
if !families.iter().any(|(f, _)| *f == fam) {
families.push((fam, tag.clone()));
}
}
}
if families.len() > 1 {
return Err(ChipError::Conflicting(
families.into_iter().map(|(_, t)| t).collect(),
));
}
let banner = read_banner(image);
let banner_family = banner
.as_deref()
.and_then(|b: &str| Chip::from_banner(b.as_bytes()));
let (family, confidence, tag_string) = match families.first() {
Some((fam, tag)) => (*fam, Confidence::TagString, Some(tag.clone())),
None => match banner_family {
Some(fam) => (fam, Confidence::BannerFallback, None),
None => {
if tags.is_empty() {
return Err(ChipError::Undetectable);
}
return Err(ChipError::Unsupported(tags));
}
},
};
let (vendor, model, rev, vendor_specific, marker_0x50, descriptor_present) =
parse_descriptor(image);
Ok(ChipInfo {
family,
confidence,
banner: banner.unwrap_or_default(),
vendor,
model,
rev,
vendor_specific,
tag_string,
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;