1use crate::{
12 cdb,
13 drive::{self, Transport},
14 INQUIRY_LEN,
15};
16
17pub mod entry;
18
19#[cfg(all(feature = "image", feature = "enc"))]
20pub mod analysis;
21#[cfg(all(feature = "image", feature = "enc"))]
22pub mod decoding;
23#[cfg(all(feature = "image", feature = "enc"))]
24pub mod layout;
25#[cfg(all(feature = "image", feature = "enc"))]
26pub mod programming;
27
28#[cfg(all(feature = "image", feature = "enc"))]
29mod mixed;
30#[cfg(all(feature = "image", feature = "enc"))]
31mod prepared;
32#[cfg(all(feature = "image", feature = "enc"))]
33pub use prepared::{ExecuteError, PreparedUpdate, TransferEncoding, TransferPlan};
34#[cfg(all(feature = "image", feature = "enc"))]
35mod profile;
36#[cfg(all(feature = "image", feature = "enc"))]
37pub mod reference;
38#[cfg(all(feature = "image", feature = "enc"))]
39pub use profile::detect;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Mode {
44 Boot,
46 Firmware,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum Encoding {
55 Plaintext,
57 Encrypted,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct DetectedMode {
64 mode: Mode,
65 inquiry: [u8; INQUIRY_LEN],
66}
67impl DetectedMode {
68 pub fn mode(&self) -> Mode {
70 self.mode
71 }
72 pub fn inquiry(&self) -> &[u8; INQUIRY_LEN] {
74 &self.inquiry
75 }
76}
77
78#[derive(Debug)]
80pub enum Error<E> {
81 Read(drive::Error<E>),
83 InvalidIdentity,
85 Unknown,
87 Conflict,
89 Changed,
91 UnknownEntry,
93}
94impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 match self {
97 Self::Read(e) => write!(f, "receiver read failed: {e}"),
98 Self::InvalidIdentity => f.write_str("invalid MediaTek receiver INQUIRY identity"),
99 Self::Unknown => f.write_str(
100 "active MediaTek receiver signature unreadable or unknown; encoding not selected",
101 ),
102 Self::Conflict => {
103 f.write_str("conflicting MediaTek receiver signatures; encoding not selected")
104 }
105 Self::Changed => {
106 f.write_str("MediaTek receiver changed since detection; re-plan the flash")
107 }
108 Self::UnknownEntry => f.write_str(
109 "active MediaTek entry contract unreadable or unknown; no PREPARE selected",
110 ),
111 }
112 }
113}
114#[cfg(feature = "std")]
115impl<E: core::fmt::Debug + 'static> std::error::Error for Error<E> {
116 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
117 match self {
118 Self::Read(source) => Some(source),
119 _ => None,
120 }
121 }
122}
123
124pub fn detect_mode<T: Transport>(t: &mut T) -> Result<DetectedMode, Error<T::Error>> {
127 let mut inquiry = [0; INQUIRY_LEN];
128 drive::read(
129 t,
130 &cdb::inquiry(INQUIRY_LEN as u16),
131 &mut inquiry,
132 INQUIRY_LEN,
133 )
134 .map_err(Error::Read)?;
135 let revision = &inquiry[32..36];
136 if inquiry[0] & 0x1f != 5
137 || inquiry[4] < 31
138 || !revision.iter().all(|b| b.is_ascii_graphic() || *b == b' ')
139 || revision.iter().all(|b| *b == b' ')
140 {
141 return Err(Error::InvalidIdentity);
142 }
143 let mode = if revision == b"BOOT" {
144 Mode::Boot
145 } else {
146 Mode::Firmware
147 };
148 Ok(DetectedMode { mode, inquiry })
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152struct Evidence {
153 address: u32,
154 len: usize,
155 bytes: [u8; 64],
156}
157impl Evidence {
158 fn new(address: u32, bytes: &[u8]) -> Self {
159 let mut e = Self {
160 address,
161 len: bytes.len(),
162 bytes: [0; 64],
163 };
164 e.bytes[..bytes.len()].copy_from_slice(bytes);
165 e
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct Receiver {
172 identity: DetectedMode,
173 encoding: Encoding,
174 site: Evidence,
175 transform: Option<Evidence>,
176 #[cfg(all(feature = "image", feature = "enc"))]
177 profile: Option<profile::Profile>,
178 #[cfg(all(feature = "image", feature = "enc"))]
179 reference: Option<reference::Binding>,
180}
181impl Receiver {
182 #[cfg(all(feature = "image", feature = "enc"))]
185 pub fn application_analysis(&self) -> Option<&analysis::ApplicationAnalysis> {
186 self.profile.as_ref()?.application.as_ref()
187 }
188 pub fn mode(&self) -> Mode {
190 self.identity.mode
191 }
192 pub fn encoding(&self) -> Encoding {
195 self.encoding
196 }
197 #[cfg(all(feature = "image", feature = "enc"))]
200 pub fn prepare_command(&self) -> Option<cdb::Prepare> {
201 self.profile.as_ref().map(|profile| profile.prepare)
202 }
203 pub fn signature_address(&self) -> u32 {
205 self.site.address
206 }
207 pub fn revalidate<T: Transport>(&self, t: &mut T) -> Result<(), Error<T::Error>> {
209 #[cfg(all(feature = "image", feature = "enc"))]
210 if let Some(reference) = &self.reference {
211 return reference.revalidate(t);
212 }
213 self.revalidate_signature(t)?;
214 #[cfg(all(feature = "image", feature = "enc"))]
215 if let Some(profile) = &self.profile {
216 profile.revalidate(t)?;
217 }
218 Ok(())
219 }
220
221 fn revalidate_signature<T: Transport>(&self, t: &mut T) -> Result<(), Error<T::Error>> {
224 if detect_mode(t)? != self.identity {
225 return Err(Error::Changed);
226 }
227 for e in core::iter::once(&self.site).chain(self.transform.iter()) {
228 let mut bytes = [0; 64];
229 drive::read_memory(t, e.address, &mut bytes[..e.len]).map_err(Error::Read)?;
230 if bytes[..e.len] != e.bytes[..e.len] {
231 return Err(Error::Changed);
232 }
233 }
234 Ok(())
235 }
236}
237
238const ANY: u16 = 0x100;
241const PLAIN_DIRECT: &[u16] = &[
242 0x03, 0x21, 0x09, 0x03, 0x61, 0x18, ANY, 0xa8, 0x00, 0x5d, 0x09, 0x78, 0x88, 0x42,
243];
244const PLAIN_APP: &[u16] = &[
245 0x03, 0x20, ANY, 0x49, 0x00, 0x03, 0x20, 0x18, 0x09, 0x68, 0x00, 0x02, 0x00, 0x0a, 0x40, 0x18,
246 ANY, 0xa9, 0x09, 0x5d, 0x00, 0x78, 0x88, 0x42,
247];
248const PLAIN_BOOT: &[u16] = &[
249 0x03, 0x20, 0x00, 0x03, 0x20, 0x18, 0xf9, 0x69, 0x00, 0x02, 0x00, 0x0a, 0x40, 0x18, ANY, 0xa9,
250 0x09, 0x5d, 0x00, 0x78, 0x88, 0x42,
251];
252const PLAIN_LOGGED: &[u16] = &[
253 0x03, 0x20, 0x00, 0x03, 0x20, 0x18, 0x05, 0x02, 0xf8, 0x69, 0x2d, 0x0a, 0x28, 0x18, 0x02, 0x78,
254 ANY, 0x48, 0x21, 0x00, ANY, ANY, ANY, ANY, 0xf8, 0x69, 0x28, 0x18, 0x00, 0x78, 0x31, 0x5d,
255 0x88, 0x42,
256];
257const AES: &[u16] = &[
258 0xf7, 0xb5, 0x06, 0x00, 0x14, 0x00, ANY, 0x48, 0x11, 0x00, 0x84, 0xb0, ANY, ANY, ANY, ANY,
259 0x00, 0x2c, 0x01, 0xd0, 0x20, 0x07, 0x02, 0xd0, 0x00, 0x20, 0x07, 0xb0, 0xf0, 0xbd, 0xa0, 0x08,
260 0x01, 0x22, 0x92, 0x06, 0x02, 0x90, 0x10, 0x68, 0x10, 0x21, 0x88, 0x43, 0x10, 0x60,
261];
262fn matches(bytes: &[u8], pattern: &[u16]) -> bool {
263 bytes.len() >= pattern.len()
264 && bytes
265 .iter()
266 .zip(pattern)
267 .all(|(&b, &p)| p == ANY || p == b as u16)
268}
269fn contains(bytes: &[u8], needle: &[u8]) -> bool {
270 bytes.windows(needle.len()).any(|b| b == needle)
271}
272fn branch_target(bytes: &[u8], address: u32) -> Option<u32> {
273 let a = u16::from_le_bytes([bytes[0], bytes[1]]);
274 let b = u16::from_le_bytes([bytes[2], bytes[3]]);
275 if a & 0xf800 != 0xf000 || b & 0xf800 != 0xf800 {
276 return None;
277 }
278 let delta = (((a & 2047) as i32) << 12) | (((b & 2047) as i32) << 1);
279 let delta = (delta << 9) >> 9;
280 u32::try_from(address as i64 + 4 + delta as i64).ok()
281}
282
283pub fn detect_encoding<T: Transport>(
287 t: &mut T,
288 mode: &DetectedMode,
289) -> Result<Receiver, Error<T::Error>> {
290 let (start, end) = match mode.mode {
291 Mode::Boot => (0x3400, 0x3800),
292 Mode::Firmware => (0x98000, 0xb4000),
293 };
294 let mut found: Option<Receiver> = None;
295 let mut block = [0; 1088];
297 let mut base = start;
298 while base < end {
299 let len = core::cmp::min(block.len(), (end - base) as usize);
300 if drive::read_memory(t, base, &mut block[..len]).is_ok() {
301 for off in (0..len.saturating_sub(4)).step_by(2) {
302 let bytes = &block[off..len];
303 let address = base + off as u32;
304 let mut candidate = None;
305 for pattern in [PLAIN_DIRECT, PLAIN_APP, PLAIN_BOOT, PLAIN_LOGGED] {
306 if matches(bytes, pattern) {
307 candidate = Some(Receiver {
308 identity: mode.clone(),
309 encoding: Encoding::Plaintext,
310 site: Evidence::new(address, &bytes[..pattern.len()]),
311 transform: None,
312 #[cfg(all(feature = "image", feature = "enc"))]
313 profile: None,
314 #[cfg(all(feature = "image", feature = "enc"))]
315 reference: None,
316 });
317 }
318 }
319 let before = &block[off.saturating_sub(32)..off];
322 if off >= 32
323 && contains(before, &[0x10, 0x22])
324 && (contains(before, &[3, 0x20, 0, 3])
325 || contains(before, &[3, 0x23]) && contains(before, &[0x1b, 3]))
326 {
327 if let Some(target) = branch_target(bytes, address) {
328 let valid_range = match mode.mode {
329 Mode::Boot => target < 0x10000,
330 Mode::Firmware => (0x10000..0x1c0000).contains(&target),
331 };
332 let mut routine = [0; 46];
333 if valid_range
334 && drive::read_memory(t, target, &mut routine).is_ok()
335 && matches(&routine, AES)
336 {
337 let begin = off.saturating_sub(32);
338 candidate = Some(Receiver {
339 identity: mode.clone(),
340 encoding: Encoding::Encrypted,
341 site: Evidence::new(base + begin as u32, &block[begin..off + 4]),
342 transform: Some(Evidence::new(target, &routine)),
343 #[cfg(all(feature = "image", feature = "enc"))]
344 profile: None,
345 #[cfg(all(feature = "image", feature = "enc"))]
346 reference: None,
347 });
348 }
349 }
350 }
351 if let Some(candidate) = candidate {
352 if let Some(previous) = &found {
353 if previous != &candidate {
354 return Err(Error::Conflict);
355 }
356 } else {
357 found = Some(candidate);
358 }
359 }
360 }
361 }
362 base += 1024;
363 }
364 let receiver = found.ok_or(Error::Unknown)?;
365 receiver.revalidate(t)?;
366 Ok(receiver)
367}
368
369#[cfg(test)]
370mod tests;