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)]
86#[non_exhaustive]
87pub enum Error<E> {
88 Read(drive::Error<E>),
90 InvalidIdentity,
92 Unknown,
94 Conflict,
96 Changed,
98 UnknownEntry,
100}
101impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103 match self {
104 Self::Read(e) => write!(f, "receiver read failed: {e}"),
105 Self::InvalidIdentity => f.write_str("invalid MediaTek receiver INQUIRY identity"),
106 Self::Unknown => f.write_str(
107 "active MediaTek receiver signature unreadable or unknown; encoding not selected",
108 ),
109 Self::Conflict => {
110 f.write_str("conflicting MediaTek receiver signatures; encoding not selected")
111 }
112 Self::Changed => {
113 f.write_str("MediaTek receiver changed since detection; re-plan the flash")
114 }
115 Self::UnknownEntry => f.write_str(
116 "active MediaTek entry contract unreadable or unknown; no PREPARE selected",
117 ),
118 }
119 }
120}
121#[cfg(feature = "std")]
122impl<E: core::fmt::Debug + 'static> std::error::Error for Error<E> {
123 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124 match self {
125 Self::Read(source) => Some(source),
126 Self::InvalidIdentity
127 | Self::Unknown
128 | Self::Conflict
129 | Self::Changed
130 | Self::UnknownEntry => None,
131 }
132 }
133}
134
135pub fn detect_mode<T: Transport>(t: &mut T) -> Result<DetectedMode, Error<T::Error>> {
138 let mut inquiry = [0; INQUIRY_LEN];
139 drive::read(
140 t,
141 &cdb::inquiry(INQUIRY_LEN as u16),
142 &mut inquiry,
143 INQUIRY_LEN,
144 )
145 .map_err(Error::Read)?;
146 let revision = &inquiry[32..36];
147 if inquiry[0] & 0x1f != 5
148 || inquiry[4] < 31
149 || !revision.iter().all(|b| b.is_ascii_graphic() || *b == b' ')
150 || revision.iter().all(|b| *b == b' ')
151 {
152 return Err(Error::InvalidIdentity);
153 }
154 let mode = if revision == b"BOOT" {
155 Mode::Boot
156 } else {
157 Mode::Firmware
158 };
159 Ok(DetectedMode { mode, inquiry })
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163struct Evidence {
164 address: u32,
165 len: usize,
166 bytes: [u8; 64],
167}
168impl Evidence {
169 fn new(address: u32, bytes: &[u8]) -> Self {
170 let mut e = Self {
171 address,
172 len: bytes.len(),
173 bytes: [0; 64],
174 };
175 e.bytes[..bytes.len()].copy_from_slice(bytes);
176 e
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct Receiver {
183 identity: DetectedMode,
184 encoding: Encoding,
185 site: Evidence,
186 transform: Option<Evidence>,
187 #[cfg(all(feature = "image", feature = "enc"))]
188 profile: Option<profile::Profile>,
189 #[cfg(all(feature = "image", feature = "enc"))]
190 reference: Option<reference::Binding>,
191}
192impl Receiver {
193 #[cfg(all(feature = "image", feature = "enc"))]
196 pub fn application_analysis(&self) -> Option<&analysis::ApplicationAnalysis> {
197 self.profile.as_ref()?.application.as_ref()
198 }
199 pub fn mode(&self) -> Mode {
201 self.identity.mode
202 }
203 pub fn encoding(&self) -> Encoding {
206 self.encoding
207 }
208 #[cfg(all(feature = "image", feature = "enc"))]
211 pub fn prepare_command(&self) -> Option<cdb::Prepare> {
212 self.profile.as_ref().map(|profile| profile.prepare)
213 }
214 pub fn signature_address(&self) -> u32 {
216 self.site.address
217 }
218 pub fn revalidate<T: Transport>(&self, t: &mut T) -> Result<(), Error<T::Error>> {
220 #[cfg(all(feature = "image", feature = "enc"))]
221 if let Some(reference) = &self.reference {
222 return reference.revalidate(t);
223 }
224 self.revalidate_signature(t)?;
225 #[cfg(all(feature = "image", feature = "enc"))]
226 if let Some(profile) = &self.profile {
227 profile.revalidate(t)?;
228 }
229 Ok(())
230 }
231
232 fn revalidate_signature<T: Transport>(&self, t: &mut T) -> Result<(), Error<T::Error>> {
235 if detect_mode(t)? != self.identity {
236 return Err(Error::Changed);
237 }
238 for e in core::iter::once(&self.site).chain(self.transform.iter()) {
239 let mut bytes = [0; 64];
240 drive::read_memory(t, e.address, &mut bytes[..e.len]).map_err(Error::Read)?;
241 if bytes[..e.len] != e.bytes[..e.len] {
242 return Err(Error::Changed);
243 }
244 }
245 Ok(())
246 }
247}
248
249const ANY: u16 = 0x100;
252const PLAIN_DIRECT: &[u16] = &[
253 0x03, 0x21, 0x09, 0x03, 0x61, 0x18, ANY, 0xa8, 0x00, 0x5d, 0x09, 0x78, 0x88, 0x42,
254];
255const PLAIN_APP: &[u16] = &[
256 0x03, 0x20, ANY, 0x49, 0x00, 0x03, 0x20, 0x18, 0x09, 0x68, 0x00, 0x02, 0x00, 0x0a, 0x40, 0x18,
257 ANY, 0xa9, 0x09, 0x5d, 0x00, 0x78, 0x88, 0x42,
258];
259const PLAIN_BOOT: &[u16] = &[
260 0x03, 0x20, 0x00, 0x03, 0x20, 0x18, 0xf9, 0x69, 0x00, 0x02, 0x00, 0x0a, 0x40, 0x18, ANY, 0xa9,
261 0x09, 0x5d, 0x00, 0x78, 0x88, 0x42,
262];
263const PLAIN_LOGGED: &[u16] = &[
264 0x03, 0x20, 0x00, 0x03, 0x20, 0x18, 0x05, 0x02, 0xf8, 0x69, 0x2d, 0x0a, 0x28, 0x18, 0x02, 0x78,
265 ANY, 0x48, 0x21, 0x00, ANY, ANY, ANY, ANY, 0xf8, 0x69, 0x28, 0x18, 0x00, 0x78, 0x31, 0x5d,
266 0x88, 0x42,
267];
268const AES: &[u16] = &[
269 0xf7, 0xb5, 0x06, 0x00, 0x14, 0x00, ANY, 0x48, 0x11, 0x00, 0x84, 0xb0, ANY, ANY, ANY, ANY,
270 0x00, 0x2c, 0x01, 0xd0, 0x20, 0x07, 0x02, 0xd0, 0x00, 0x20, 0x07, 0xb0, 0xf0, 0xbd, 0xa0, 0x08,
271 0x01, 0x22, 0x92, 0x06, 0x02, 0x90, 0x10, 0x68, 0x10, 0x21, 0x88, 0x43, 0x10, 0x60,
272];
273fn matches(bytes: &[u8], pattern: &[u16]) -> bool {
274 bytes.len() >= pattern.len()
275 && bytes
276 .iter()
277 .zip(pattern)
278 .all(|(&b, &p)| p == ANY || p == b as u16)
279}
280fn contains(bytes: &[u8], needle: &[u8]) -> bool {
281 bytes.windows(needle.len()).any(|b| b == needle)
282}
283fn branch_target(bytes: &[u8], address: u32) -> Option<u32> {
284 let a = u16::from_le_bytes([bytes[0], bytes[1]]);
285 let b = u16::from_le_bytes([bytes[2], bytes[3]]);
286 if a & 0xf800 != 0xf000 || b & 0xf800 != 0xf800 {
287 return None;
288 }
289 let delta = (((a & 2047) as i32) << 12) | (((b & 2047) as i32) << 1);
290 let delta = (delta << 9) >> 9;
291 u32::try_from(address as i64 + 4 + delta as i64).ok()
292}
293
294pub fn detect_encoding<T: Transport>(
298 t: &mut T,
299 mode: &DetectedMode,
300) -> Result<Receiver, Error<T::Error>> {
301 let (start, end) = match mode.mode {
302 Mode::Boot => (0x3400, 0x3800),
303 Mode::Firmware => (0x98000, 0xb4000),
304 };
305 let mut found: Option<Receiver> = None;
306 let mut block = [0; 1088];
308 let mut base = start;
309 while base < end {
310 let len = core::cmp::min(block.len(), (end - base) as usize);
311 if drive::read_memory(t, base, &mut block[..len]).is_ok() {
312 for off in (0..len.saturating_sub(4)).step_by(2) {
313 let bytes = &block[off..len];
314 let address = base + off as u32;
315 let mut candidate = None;
316 for pattern in [PLAIN_DIRECT, PLAIN_APP, PLAIN_BOOT, PLAIN_LOGGED] {
317 if matches(bytes, pattern) {
318 candidate = Some(Receiver {
319 identity: mode.clone(),
320 encoding: Encoding::Plaintext,
321 site: Evidence::new(address, &bytes[..pattern.len()]),
322 transform: None,
323 #[cfg(all(feature = "image", feature = "enc"))]
324 profile: None,
325 #[cfg(all(feature = "image", feature = "enc"))]
326 reference: None,
327 });
328 }
329 }
330 let before = &block[off.saturating_sub(32)..off];
333 if off >= 32
334 && contains(before, &[0x10, 0x22])
335 && (contains(before, &[3, 0x20, 0, 3])
336 || contains(before, &[3, 0x23]) && contains(before, &[0x1b, 3]))
337 {
338 if let Some(target) = branch_target(bytes, address) {
339 let valid_range = match mode.mode {
340 Mode::Boot => target < 0x10000,
341 Mode::Firmware => (0x10000..0x1c0000).contains(&target),
342 };
343 let mut routine = [0; 46];
344 if valid_range
345 && drive::read_memory(t, target, &mut routine).is_ok()
346 && matches(&routine, AES)
347 {
348 let begin = off.saturating_sub(32);
349 candidate = Some(Receiver {
350 identity: mode.clone(),
351 encoding: Encoding::Encrypted,
352 site: Evidence::new(base + begin as u32, &block[begin..off + 4]),
353 transform: Some(Evidence::new(target, &routine)),
354 #[cfg(all(feature = "image", feature = "enc"))]
355 profile: None,
356 #[cfg(all(feature = "image", feature = "enc"))]
357 reference: None,
358 });
359 }
360 }
361 }
362 if let Some(candidate) = candidate {
363 if let Some(previous) = &found {
364 if previous != &candidate {
365 return Err(Error::Conflict);
366 }
367 } else {
368 found = Some(candidate);
369 }
370 }
371 }
372 }
373 base += 1024;
374 }
375 let receiver = found.ok_or(Error::Unknown)?;
376 receiver.revalidate(t)?;
377 Ok(receiver)
378}
379
380#[cfg(test)]
381mod tests;