mediatek-optical 1.0.0

MediaTek MT1959/MT1939 optical drive (BD/DVD) protocol: command encoding, identity and flash sequences over any SCSI transport, chip detection, hardware families, AES-CMAC integrity, family-gated updates and OEM image reconstruction. no_std by default.
Documentation
use super::*;
use crate::fixtures::{self, Board, BU3, JB7, JB8, TEMPLATE_AT};

/// Known answers: the families the reference implementation of
/// `mediatek.hardware.v1` computes for the three fixtures. They equal the
/// families of the real JB7, JB8 and BU3 board lines.
#[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 ships an "MT1959 Boot JB8" banner, so its chip is MT1959 even
            // though its descriptor tag is the stale MTEKMT1939 label.
            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());
    }
}

/// Each field of the key separates boards that differ only in that field.
#[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
        },
        // The chip field is separated by the BOOT BANNER (the authoritative
        // chip signal), not the descriptor tag: an "MT1959 Boot" banner makes
        // this MT1959 while keeping the "Code" loader.
        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"));
}

/// The record's offset in a fixture image.
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(&copy);
            }),
        ),
        (
            // The record tag naming the *other* supported chip is no longer an
            // inconsistency (the tag is a stale label the banner overrides), but
            // a tag naming NO supported MT19xx generation still fails the
            // structural guard.
            "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| {
                // Swap Core (12 bytes) and feature 0x0002 (8 bytes).
                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() {
    // Feature 0x0003 renamed: the template still walks, but has no medium.
    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(&copy);
    });
    assert!(family(&image).is_some());
}

/// An untagged descriptor carries no platform to compare; the banner names
/// the chip.
#[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);
}

/// The template search never reads the boot area, so a drive read cannot
/// change the result through it.
#[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);
    // The template straddles the end of the boot area.
    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() {
    // The OEM record places its six platform bytes at record+0x24. All
    // 0x2a bytes used for family identity must remain in immutable code.
    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() {
    // MMC descriptors: code BE, version, payload length, then payload. This
    // minimal table has one BD-ROM profile followed by the Core interface.
    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])]);
        // Zero, misaligned, and overrun profile lengths are not a valid table.
        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());
    }
}