use super::*;
use crate::fixtures::{self, Board, BU3, JB7, JB8, TEMPLATE_AT};
#[test]
fn fixtures_have_the_reference_families() {
for (board, key, want) in [
(
JB7,
"chip=MT1939\x1fplatform=JB7\x1floader=Code\x1fiface=00000007\x1fmedium=39\x1fbdwrite=1",
"11e7527216405467",
),
(
JB8,
"chip=MT1959\x1fplatform=JB8\x1floader=JB8\x1fiface=00000007\x1fmedium=39\x1fbdwrite=1",
"336e449ab6a827e3",
),
(
BU3,
"chip=MT1959\x1fplatform=BU3\x1floader=BU5\x1fiface=00000007\x1fmedium=2b\x1fbdwrite=1",
"309829e6c3c8f31d",
),
] {
let image = fixtures::image(&board, 1);
let hardware = hardware(&image).expect(board.platform);
assert_eq!(hardware.key(), key);
assert_eq!(family(&image).unwrap().to_string(), want);
assert_eq!(format!("{:?}", hardware.family()), format!("Family({want})"));
}
assert_eq!(FAMILY_SCHEME, "mediatek.hardware.v1");
}
#[test]
fn family_ignores_code_outside_the_record() {
assert_eq!(
family(&fixtures::image(&JB7, 1)),
family(&fixtures::image(&JB7, 2))
);
}
#[test]
fn family_survives_a_drive_read() {
for board in [JB7, JB8, BU3] {
let image = fixtures::image(&board, 3);
let read = fixtures::drive_read(&image);
assert_ne!(image[..0x400], read[..0x400]);
assert_eq!(family(&read), family(&image));
assert!(family(&read).is_some());
}
}
#[test]
fn every_key_field_separates_boards() {
let base = family(&fixtures::image(&JB7, 1)).unwrap();
let variants = [
Board {
platform: "JB9",
descriptor_platform: "JB9",
..JB7
},
Board {
banner: "MT1939 Boot JB7",
..JB7
},
Board {
interface: [0, 0, 0, 8],
..JB7
},
Board {
medium: 0x2B,
..JB7
},
Board {
bd_write: false,
..JB7
},
Board {
banner: "MT1959 Boot Code",
..JB7
},
];
let mut seen = vec![base];
for board in variants {
let found = family(&fixtures::image(&board, 1)).unwrap_or_else(|| panic!("{board:?}"));
assert!(!seen.contains(&found), "{board:?} collides");
seen.push(found);
}
}
#[test]
fn disabled_bd_profiles_clear_bd_write() {
let write = hardware(&fixtures::image(&JB7, 1)).unwrap();
let read_only = hardware(&fixtures::image(
&Board {
bd_write: false,
..JB7
},
1,
))
.unwrap();
assert!(write.bd_write);
assert!(!read_only.bd_write);
assert!(read_only.key().ends_with("bdwrite=0"));
}
fn record_at(board: &Board) -> usize {
TEMPLATE_AT + fixtures::template(board).len()
}
fn edited(edit: impl FnOnce(&mut Vec<u8>)) -> Vec<u8> {
let mut image = fixtures::image(&JB7, 1);
edit(&mut image);
image
}
#[test]
fn incomplete_or_inconsistent_hardware_has_no_family() {
let record = record_at(&JB7);
let unique = record + 2;
let descriptor = layout::DESCRIPTOR.start;
let banner = layout::BANNER.start;
let cases: Vec<(&str, Vec<u8>)> = vec![
(
"short image",
edited(|i| i.truncate(layout::IMAGE_SIZE - 1)),
),
("long image", edited(|i| i.push(0))),
(
"no chip anywhere",
edited(|i| {
i[descriptor + 0x34..descriptor + 0x3E].fill(b' ');
i[banner..banner + 4].copy_from_slice(b"XT19");
}),
),
(
"no supported chip",
edited(|i| {
i[descriptor + 0x34..descriptor + 0x3E].fill(b' ');
i[banner + 4..banner + 6].copy_from_slice(b"69");
}),
),
(
"not an MT19 boot banner",
edited(|i| i[banner..banner + 2].copy_from_slice(b"XX")),
),
(
"banner names no loader",
edited(|i| i[banner + 7..banner + 11].copy_from_slice(b"Bxot")),
),
(
"empty loader",
edited(|i| i[banner + 12..banner + 16].fill(0)),
),
("no record", edited(|i| i[record] = 0x54)),
(
"two records",
edited(|i| {
let copy = i[record..record + 40].to_vec();
i[0x18_0000..0x18_0000 + 40].copy_from_slice(©);
}),
),
(
"record tag names no supported MT19xx generation",
edited(|i| i[unique + 0x18..unique + 0x1A].copy_from_slice(b"69")),
),
(
"descriptor platform differs",
edited(|i| i[descriptor + 0x46..descriptor + 0x49].copy_from_slice(b"JB8")),
),
(
"unprintable platform",
edited(|i| {
i[unique + 0x23] = 0x01;
i[descriptor + 0x47] = 0x01;
}),
),
(
"empty platform",
edited(|i| {
i[unique + 0x22..unique + 0x28].fill(b' ');
i[descriptor + 0x46..descriptor + 0x4C].fill(b' ');
}),
),
(
"template does not start with Profile List and Core",
edited(|i| i[TEMPLATE_AT + 4 + 32 + 1] = 0x05),
),
(
"Core is not the second feature",
edited(|i| {
let core = TEMPLATE_AT + 4 + 32;
let block = i[core..core + 20].to_vec();
i[core..core + 8].copy_from_slice(&block[12..20]);
i[core + 8..core + 20].copy_from_slice(&block[..12]);
}),
),
(
"template does not end at the record",
edited(|i| i[record - 9] = 9),
),
];
for (what, image) in cases {
assert_eq!(hardware(&image), None, "{what}");
assert_eq!(family(&image), None, "{what}");
}
}
#[test]
fn template_needs_core_and_the_medium_feature() {
let medium = TEMPLATE_AT + 4 + 32 + 4 + 8 + 4 + 4;
let image = edited(|i| i[medium + 1] = 0x05);
assert_eq!(family(&image), None);
}
#[test]
fn a_record_outside_the_code_area_is_not_searched() {
let record = record_at(&JB7);
let image = edited(|i| {
let copy = i[record - 0x80..record + 40].to_vec();
let at = layout::CALIBRATION.start + 0x100;
i[at..at + copy.len()].copy_from_slice(©);
});
assert!(family(&image).is_some());
}
#[test]
fn untagged_descriptor_falls_back_to_the_banner() {
let tagged = family(&fixtures::image(&JB7, 1));
let image = edited(|i| {
let d = layout::DESCRIPTOR.start;
i[d + 0x34..d + 0x3E].fill(b' ');
i[d + 0x46..d + 0x4C].copy_from_slice(b"XXXXXX");
});
assert_eq!(family(&image), tagged);
}
#[test]
fn template_search_stops_at_the_boot_area() {
let board = JB7;
let template = fixtures::template(&board);
let record = fixtures::record(&board);
let mut image = fixtures::image(&board, 1);
let (old_template, old_record) = (TEMPLATE_AT, record_at(&board));
image[old_template..old_record + record.len()].fill(1);
let start = layout::BOOT_AREA.len - 8;
image[start..start + template.len()].copy_from_slice(&template);
let at = start + template.len();
image[at..at + record.len()].copy_from_slice(&record);
assert_eq!(family(&image), None);
image[..layout::BOOT_AREA.len].fill(0);
assert_eq!(family(&image), None);
}
#[test]
fn family_record_must_not_take_platform_bytes_from_drive_written_data() {
let original = fixtures::image(&JB7, 0x17);
let expected = hardware(&original).unwrap();
let features = fixtures::template(&JB7);
let record = fixtures::record(&JB7);
for (remaining, accepted) in [(0x2a, true), (0x29, false), (0x1c, false), (0, false)] {
let mut image = original.clone();
image[TEMPLATE_AT..TEMPLATE_AT + features.len() + record.len()].fill(0);
let at = 0x1d8000 - remaining;
image[at - features.len()..at].copy_from_slice(&features);
image[at..at + record.len()].copy_from_slice(&record);
if accepted {
assert_eq!(hardware(&image), Some(expected.clone()));
} else {
assert_eq!(
hardware(&image),
None,
"record at {at:#x} crosses immutable code boundary"
);
}
}
}
#[test]
fn hardware_record_prefix_and_boot_loader_tokens_are_bounded() {
let record = b"\x55\xaaUNIQUE1210311917MTEKMT1939";
for n in 0..record.len() {
assert!(!is_record(&record[..n]));
}
assert!(is_record(record));
for bad in [
b"MT19".as_slice(),
b"MT193",
b"MT19x9 Boot Code",
b"MT193x Boot Code",
b"MT1939 Boot ",
] {
assert_eq!(loader(bad), None);
}
assert_eq!(loader(b"MT1939 Boot Code").as_deref(), Some("Code"));
assert_eq!(loader(b"MT1959 Boot BUP3 ").as_deref(), Some("BUP3"));
}
#[test]
fn feature_template_requires_aligned_nonempty_profiles_and_core() {
let good = [0, 0, 3, 4, 0, 0x40, 0, 0, 0, 1, 11, 4, 0, 0, 0, 7];
for start in [0x1000, 0x1001, 0x1800] {
let mut image = vec![0xff; start + good.len()];
image[start..].copy_from_slice(&good);
let parsed = template(&image, image.len()).unwrap();
assert_eq!(parsed, vec![(0, &good[4..8]), (1, &good[12..16])]);
for length in [0, 1, 3, 5, 252] {
let mut bad = image.clone();
bad[start + 3] = length;
assert!(template(&bad, bad.len()).is_none());
}
for first_code in [1, 2, 0xff] {
let mut bad = image.clone();
bad[start + 1] = first_code;
assert!(template(&bad, bad.len()).is_none());
}
let only_profile = &image[..start + 8];
assert!(template(only_profile, only_profile.len()).is_none());
}
}