Skip to main content

mediatek_optical/
product_identity.rs

1//! Structured product identity shared by live INQUIRY and firmware metadata.
2
3/// A borrowed, display-oriented view of an optical product identity.
4/// Raw vendor/product/revision values are preserved; parsing never changes flash compatibility.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct ProductIdentity<'a> {
7    /// Raw manufacturer/vendor identification.
8    pub vendor: &'a str,
9    /// Raw product identification, including media class and any OEM suffix.
10    pub product: &'a str,
11    /// Drive model: the first product token after any standard media-class prefixes.
12    pub model: Option<&'a str>,
13    /// Remaining product context, such as `PP-100` or a board variant letter.
14    pub product_suffix: &'a str,
15    /// Raw firmware revision.
16    pub revision: &'a str,
17}
18
19fn product_prefix(word: &str) -> bool {
20    const PREFIXES: &[&str] = &[
21        "BDRE",
22        "BD-RE",
23        "BDRW",
24        "BD-RW",
25        "BDROM",
26        "BD-ROM",
27        "BDDVDRW",
28        "BDDVDW",
29        "DVDRW",
30        "DVD-RW",
31        "DVDROM",
32        "DVD-ROM",
33        "DVDRAM",
34        "DVD-RAM",
35        "DVD+RW",
36        "CDDVDW",
37        "CDRW",
38        "CD-RW",
39        "BD",
40        "DVD",
41        "CD",
42        "ROM",
43        "RW",
44        "R/RW",
45        "DVDR",
46        "BD-R",
47        "DVD+/-RW",
48        "DVD-R/RW",
49        "DUAL",
50        "MULTI",
51        "COMBO",
52        "BD-MLT",
53        "DVD-MLT",
54        "DVD-MULTI",
55    ];
56    PREFIXES
57        .iter()
58        .any(|prefix| prefix.eq_ignore_ascii_case(word))
59        || (word.bytes().any(|b| b == b'x' || b == b'X')
60            && word.bytes().any(|b| b.is_ascii_digit())
61            && word
62                .bytes()
63                .all(|b| b.is_ascii_digit() || b == b'x' || b == b'X'))
64}
65
66impl<'a> ProductIdentity<'a> {
67    /// Interpret separate INQUIRY/descriptor fields without filename heuristics.
68    pub fn from_fields(vendor: &'a str, product: &'a str, revision: &'a str) -> Self {
69        let mut rest = product.trim();
70        let model = loop {
71            if rest.is_empty() {
72                break None;
73            }
74            let end = rest.find(char::is_whitespace).unwrap_or(rest.len());
75            let word = &rest[..end];
76            rest = rest[end..].trim_start();
77            if !product_prefix(word) {
78                break Some(word);
79            }
80        };
81        Self {
82            vendor,
83            product,
84            model,
85            product_suffix: rest,
86            revision,
87        }
88    }
89
90    /// Interpret a firmware `ID` field containing vendor followed by product.
91    /// A missing separator remains unknown rather than inventing a vendor/model split.
92    pub fn from_id(id: &'a str, revision: &'a str) -> Option<Self> {
93        let (vendor, product) = id.trim().split_once(char::is_whitespace)?;
94        if product_prefix(vendor) {
95            Some(Self::from_fields("", id.trim(), revision))
96        } else {
97            Some(Self::from_fields(vendor, product.trim_start(), revision))
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    #[test]
106    fn product_context_does_not_replace_the_drive_model() {
107        let id = ProductIdentity::from_id("PIONEER BDR-206PA PP-100", "2.02").unwrap();
108        assert_eq!(id.vendor, "PIONEER");
109        assert_eq!(id.model, Some("BDR-206PA"));
110        assert_eq!(id.product_suffix, "PP-100");
111        assert_eq!(id.product, "BDR-206PA PP-100");
112        assert_eq!(
113            ProductIdentity::from_id("PIONEER BDR-206PB PP-100", "2.02")
114                .unwrap()
115                .model,
116            Some("BDR-206PB")
117        );
118    }
119    #[test]
120    fn archived_oem_product_grammars_keep_the_actual_drive_model() {
121        for (raw, model) in [
122            ("AOPEN 8X8 DVD Dual APP", "APP"),
123            ("Optiarc BD ROM BC-5100S", "BC-5100S"),
124            ("PIONEER DVD RW DVR-106D", "DVR-106D"),
125            ("PLEXTOR DVDR PX-608CU", "PX-608CU"),
126            ("PLEXTOR DVDR PX-810SA", "PX-810SA"),
127            ("PLEXTOR BD-R PX-B940SA", "PX-B940SA"),
128            ("Memorex DVD+/-RW True-8X", "True-8X"),
129            ("PIONEER BDE-PR1EP PP100", "BDE-PR1EP"),
130            ("PIONEER BDR-PR1EPA PP100", "BDR-PR1EPA"),
131        ] {
132            assert_eq!(
133                ProductIdentity::from_id(raw, "").unwrap().model,
134                Some(model),
135                "{raw}"
136            );
137        }
138        let missing_vendor = ProductIdentity::from_id("DVD RW DVR-MCC", "").unwrap();
139        assert_eq!(missing_vendor.vendor, "");
140        assert_eq!(missing_vendor.model, Some("DVR-MCC"));
141    }
142
143    #[test]
144    fn media_prefixes_and_variant_suffixes_are_preserved_separately() {
145        for (raw, model, suffix) in [
146            ("BD-RW   BDR-212U", "BDR-212U", ""),
147            ("BDRE BU40N", "BU40N", ""),
148            ("BD-RE BH16NS40", "BH16NS40", ""),
149            ("BC-12B1ST b", "BC-12B1ST", "b"),
150        ] {
151            let id = ProductIdentity::from_fields("vendor", raw, "1.00");
152            assert_eq!(id.model, Some(model));
153            assert_eq!(id.product_suffix, suffix);
154            assert_eq!(id.product, raw);
155        }
156        assert_eq!(
157            ProductIdentity::from_fields("vendor", " BD-RW ", "").model,
158            None
159        );
160        assert_eq!(ProductIdentity::from_fields("vendor", "", "").model, None);
161        assert!(ProductIdentity::from_id("missing-separator", "").is_none());
162    }
163}