Skip to main content

mediatek_optical/receiver/
prepared.rs

1//! Receiver-bound preparation, immutable entry/encoding selection and
2//! pre-write receiver revalidation.
3use super::{Encoding, Receiver};
4use crate::{envelope::Envelope, update};
5
6/// An update prepared for the receiver that will accept its bytes.
7/// The transfer format cannot be overridden by a caller.
8#[derive(Debug)]
9pub struct PreparedUpdate {
10    plan: TransferPlan,
11}
12
13/// Header and body representation on the wire, derived from receiver code.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct TransferEncoding {
16    /// The 16-byte transport header at image offset 0x3000.
17    pub header: Encoding,
18    /// The image body accepted by the receiver.
19    pub body: Encoding,
20}
21
22/// Immutable encoding, entry sequence, host policy and receiver binding for
23/// one Flash or Recovery operation.
24#[derive(Debug, Clone)]
25pub struct TransferPlan {
26    receiver: Receiver,
27    class: crate::DriveClass,
28    image: Vec<u8>,
29    transfer: Option<Vec<u8>>,
30    encoding: TransferEncoding,
31    prepare: crate::cdb::Prepare,
32    mode: update::flash::ExecutionMode,
33}
34
35/// Receiver revalidation or execution failed.
36///
37/// Hosts must keep a wildcard arm; the set of variants may grow.
38///
39/// ```compile_fail,E0004
40/// fn f(e: mediatek_optical::receiver::ExecuteError<()>) -> u8 { match e { mediatek_optical::receiver::ExecuteError::Receiver { .. } => 0, mediatek_optical::receiver::ExecuteError::Flash { .. } => 1, } }
41/// ```
42#[derive(Debug)]
43#[non_exhaustive]
44pub enum ExecuteError<E> {
45    /// No write was attempted because the receiver could not be revalidated.
46    Receiver(super::Error<E>),
47    /// The shared transfer/verification engine failed.
48    Flash(update::FlashError<E>),
49}
50
51impl<E: core::fmt::Debug> core::fmt::Display for ExecuteError<E> {
52    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53        match self {
54            Self::Receiver(error) => {
55                write!(f, "receiver revalidation failed before writing: {error}")
56            }
57            Self::Flash(error) => write!(f, "{error}"),
58        }
59    }
60}
61impl<E: core::fmt::Debug + 'static> std::error::Error for ExecuteError<E> {
62    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
63        match self {
64            Self::Receiver(source) => Some(source),
65            Self::Flash(source) => Some(source),
66        }
67    }
68}
69
70impl Receiver {
71    /// Bind an already validated update to this receiver without decoding or
72    /// repeating image integrity and installed-family analysis.
73    pub fn bind_prepared(
74        &self,
75        update: &update::PreparedUpdate,
76    ) -> Result<PreparedUpdate, update::PrepareError> {
77        Ok(PreparedUpdate {
78            plan: TransferPlan::new(
79                self,
80                update.image(),
81                update.class(),
82                update::flash::ExecutionMode::Flash,
83            )?,
84        })
85    }
86
87    /// Prepare with explicit unavailable installed-image evidence. The host
88    /// owns consent; target integrity, chip and receiver gates remain mandatory.
89    pub fn prepare_without_installed(
90        &self,
91        envelope: &Envelope,
92        live: &crate::Identity,
93    ) -> Result<PreparedUpdate, update::PrepareError> {
94        let update = update::PreparedUpdate::prepare_without_installed(envelope.image(), live)?;
95        self.bind_prepared(&update)
96    }
97
98    /// Apply normal Flash image/hardware gates, then select wire encoding from
99    /// the positively identified running receiver, regardless of input format.
100    /// This does not perform I/O or waive the caller's backup/consent policy.
101    pub fn prepare(
102        &self,
103        envelope: &Envelope,
104        installed: &update::InstalledFirmware,
105    ) -> Result<PreparedUpdate, update::PrepareError> {
106        // InstalledFirmware is bound by its descriptor and/or BOOT banner;
107        // its INQUIRY may deliberately be absent for offline gate evidence.
108        // The executable plan separately binds and revalidates live INQUIRY.
109        let update = update::PreparedUpdate::prepare(envelope.image(), installed)?;
110        self.bind_prepared(&update)
111    }
112}
113
114impl PreparedUpdate {
115    /// Inspect the immutable execution plan.
116    pub fn plan(&self) -> &TransferPlan {
117        &self.plan
118    }
119    /// Revalidate the bound receiver before entering the shared Flash engine.
120    pub fn flash<R, W, H>(
121        &self,
122        io: update::FlashTransport<'_, R, W>,
123        runtime: &mut H,
124    ) -> Result<update::Verified, ExecuteError<W::Error>>
125    where
126        W: crate::drive::Transport,
127        R: crate::drive::Transport<Error = W::Error>,
128        H: update::FlashRuntime<W::Error>,
129    {
130        self.flash_completed(io, runtime)?
131            .into_verified()
132            .map_err(ExecuteError::Flash)
133    }
134
135    /// Revalidate, execute, and confirm exact target startup. An unreadable
136    /// protected region is retained as limited verification, never full success.
137    pub fn flash_completed<R, W, H>(
138        &self,
139        io: update::FlashTransport<'_, R, W>,
140        runtime: &mut H,
141    ) -> Result<update::Completion, ExecuteError<W::Error>>
142    where
143        W: crate::drive::Transport,
144        R: crate::drive::Transport<Error = W::Error>,
145        H: update::FlashRuntime<W::Error>,
146    {
147        self.plan.execute_completed(io, runtime)
148    }
149}
150
151fn wire(
152    image: &[u8],
153    operations: &[super::layout::Operation],
154) -> Result<Vec<u8>, update::PrepareError> {
155    let layout =
156        super::layout::analyze(image.len(), operations).map_err(update::PrepareError::Layout)?;
157    layout.encode(image).map_err(update::PrepareError::Layout)
158}
159
160// Both Flash and Recovery use this decision. A recognized scratch decoder must
161// never fall back to whole-image encryption merely because it calls AES.
162fn encode_transfer(
163    receiver: &Receiver,
164    image: &[u8],
165) -> Result<(Option<Vec<u8>>, TransferEncoding, bool), update::PrepareError> {
166    if let Some(analysis) = receiver.application_analysis() {
167        if analysis.conflicting_arguments() {
168            return Err(update::PrepareError::ReceiverContract);
169        }
170        if !analysis.scratch_decoders().is_empty() {
171            let contract = receiver
172                .profile
173                .as_ref()
174                .and_then(|p| p.mixed.as_ref())
175                .ok_or(update::PrepareError::ReceiverContract)?;
176            if image.get(contract.descriptor_byte()) != Some(&0xde) {
177                return Err(update::PrepareError::ReceiverContract);
178            }
179            use super::layout::Operation;
180            let mut operations = vec![Operation::CheckDecrypted(contract.header_range())];
181            // Integrity reads must never consume encrypted staging bytes. The
182            // matched preservation branch excludes the separate boot CMAC.
183            for entry in crate::image::cmac::parse_table(image)
184                .map_err(|_| update::PrepareError::Integrity)?
185                .into_iter()
186                .filter(|entry| entry.is_active())
187            {
188                let range = entry.start as usize..entry.end as usize + 1;
189                if !contract.programmed_ranges().iter().any(|programmed| {
190                    programmed.start <= range.start && programmed.end >= range.end
191                }) {
192                    // A complete input cannot require changed authenticated
193                    // bytes in a region this receiver deliberately preserves.
194                    return Err(update::PrepareError::ReceiverContract);
195                }
196                operations.push(Operation::CheckPlaintext(range));
197            }
198            for range in contract.programmed_ranges() {
199                operations.push(Operation::Program(range.clone()));
200            }
201            let wire = wire(image, &operations)?;
202            return Ok((
203                Some(wire),
204                TransferEncoding {
205                    header: Encoding::Encrypted,
206                    body: Encoding::Plaintext,
207                },
208                true,
209            ));
210        }
211        if receiver.encoding() == Encoding::Encrypted {
212            let [bulk] = analysis.bulk_decoders() else {
213                return Err(update::PrepareError::ReceiverContract);
214            };
215            use super::decoding::Value;
216            let Value::Loaded { cell, offset: 0 } = bulk.from_zero.source else {
217                return Err(update::PrepareError::ReceiverContract);
218            };
219            if bulk.from_zero.destination != bulk.from_zero.source
220                || bulk.from_zero.length as usize != image.len()
221                || bulk.header.source
222                    != (Value::Loaded {
223                        cell,
224                        offset: 0x3000,
225                    })
226                || bulk.header.length != 16
227                || !matches!(bulk.header.destination, Value::Stack(_))
228                || bulk.header.target != bulk.from_zero.target
229            {
230                return Err(update::PrepareError::ReceiverContract);
231            }
232            let mut wire = image.to_vec();
233            // The checked start-zero body call consumes precisely this range.
234            crate::enc::encrypt(&mut wire).map_err(|_| update::PrepareError::ReceiverContract)?;
235            return Ok((
236                Some(wire),
237                TransferEncoding {
238                    header: Encoding::Encrypted,
239                    body: Encoding::Encrypted,
240                },
241                false,
242            ));
243        }
244        if !analysis.bulk_decoders().is_empty() {
245            return Err(update::PrepareError::ReceiverContract);
246        }
247    }
248    // BOOT and direct-plaintext receivers retain their existing positive
249    // detection while their complete path analysis is integrated separately.
250    if receiver.encoding() == Encoding::Encrypted {
251        let mut wire = image.to_vec();
252        crate::enc::encrypt(&mut wire).map_err(|_| update::PrepareError::ReceiverContract)?;
253        Ok((
254            Some(wire),
255            TransferEncoding {
256                header: Encoding::Encrypted,
257                body: Encoding::Encrypted,
258            },
259            false,
260        ))
261    } else {
262        Ok((
263            None,
264            TransferEncoding {
265                header: Encoding::Plaintext,
266                body: Encoding::Plaintext,
267            },
268            false,
269        ))
270    }
271}
272
273impl TransferPlan {
274    pub(crate) fn new(
275        receiver: &Receiver,
276        image: &[u8],
277        class: crate::DriveClass,
278        mode: update::flash::ExecutionMode,
279    ) -> Result<Self, update::PrepareError> {
280        let profile = receiver
281            .profile
282            .as_ref()
283            .ok_or(update::PrepareError::ReceiverEntry)?;
284        let image_chip = crate::Chip::from_banner(
285            crate::layout::BANNER
286                .of(image)
287                .ok_or(update::PrepareError::UnknownChip)?,
288        )
289        .ok_or(update::PrepareError::UnknownChip)?;
290        if image_chip != profile.chip {
291            return Err(update::PrepareError::ChipMismatch {
292                image: image_chip,
293                drive: profile.chip,
294            });
295        }
296        if class.verification_ranges(image).is_empty() {
297            return Err(update::PrepareError::Integrity);
298        }
299        let mut prepare = profile.prepare;
300        let (transfer, encoding, preserve_boot) = encode_transfer(receiver, image)?;
301        if preserve_boot {
302            use crate::cdb::Prepare;
303            prepare = match prepare {
304                Prepare::ModeOne | Prepare::ModeOnePreserveBoot => Prepare::ModeOnePreserveBoot,
305                Prepare::ModeSix67 | Prepare::ModeSix67PreserveBoot => {
306                    Prepare::ModeSix67PreserveBoot
307                }
308            };
309        }
310        Ok(Self {
311            receiver: receiver.clone(),
312            class,
313            image: image.to_vec(),
314            transfer,
315            encoding,
316            prepare,
317            mode,
318        })
319    }
320
321    pub(crate) fn execute_completed<R, W, H>(
322        &self,
323        io: update::FlashTransport<'_, R, W>,
324        runtime: &mut H,
325    ) -> Result<update::Completion, ExecuteError<W::Error>>
326    where
327        W: crate::drive::Transport,
328        R: crate::drive::Transport<Error = W::Error>,
329        H: update::FlashRuntime<W::Error>,
330    {
331        self.receiver
332            .revalidate(io.reads)
333            .map_err(ExecuteError::Receiver)?;
334        update::flash::Payload {
335            class: self.class,
336            image: self.image(),
337            transfer: self.transfer(),
338        }
339        .flash_completed(io, runtime, self.mode, self.prepare)
340        .map_err(ExecuteError::Flash)
341    }
342
343    /// Actual header and body formats; an AES header does not imply an AES body.
344    pub fn encoding(&self) -> TransferEncoding {
345        self.encoding
346    }
347
348    /// Programmed image spans when the receiver's complete programming-loop
349    /// signature establishes them. `None` means no complete span inventory,
350    /// not that every image byte will be written.
351    pub fn programmed_ranges(&self) -> Option<&[core::ops::Range<usize>]> {
352        self.receiver
353            .profile
354            .as_ref()?
355            .mixed
356            .as_ref()
357            .map(|contract| contract.programmed_ranges())
358    }
359
360    /// Regions explicitly preserved by a known programming-loop contract.
361    /// These retain installed bytes and are not claimed to match the target.
362    pub fn preserved_ranges(&self) -> Option<Vec<core::ops::Range<usize>>> {
363        let programmed = self.programmed_ranges()?;
364        let mut preserved = Vec::new();
365        let mut end = 0;
366        for range in programmed {
367            if end < range.start {
368                preserved.push(end..range.start);
369            }
370            end = range.end;
371        }
372        if end < self.image.len() {
373            preserved.push(end..self.image.len());
374        }
375        Some(preserved)
376    }
377
378    /// Receiver-derived PREPARE dialect, fixed at preparation.
379    pub fn prepare_command(&self) -> crate::cdb::Prepare {
380        self.prepare
381    }
382    /// Controller dialect retained by the validated update.
383    pub fn class(&self) -> crate::DriveClass {
384        self.class
385    }
386    /// Active receiver whose instructions determine the wire format.
387    pub fn receiver(&self) -> &Receiver {
388        &self.receiver
389    }
390    /// Canonical bytes used for protected readback verification.
391    pub fn image(&self) -> &[u8] {
392        &self.image
393    }
394    /// Actual bytes sent to the running receiver.
395    pub fn transfer(&self) -> &[u8] {
396        self.transfer.as_deref().unwrap_or(&self.image)
397    }
398    /// Ordered session commands from the current shared executor.
399    pub fn steps(&self) -> std::vec::Vec<update::Step> {
400        let mut steps = self.class.steps();
401        if self.mode == update::flash::ExecutionMode::Recovery {
402            // Recovery enters directly: the application-only PROBE and
403            // readiness preconditions must not block a BOOT receiver.
404            steps.drain(..2);
405        }
406        for step in &mut steps {
407            if step.label == "PREPARE" {
408                step.cdb = self.prepare.command();
409            }
410        }
411        steps
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn contradictory_layout_keeps_its_cause() {
421        use super::super::layout::{Error, Operation};
422        let image = vec![0; layout::IMAGE_SIZE];
423        let operations = [
424            Operation::CheckDecrypted(0x3000..0x3010),
425            Operation::CheckPlaintext(0x3000..0x3010),
426            Operation::Program(0..layout::IMAGE_SIZE),
427        ];
428        assert!(matches!(
429            wire(&image, &operations),
430            Err(update::PrepareError::Layout(Error::Conflict { .. }))
431        ));
432    }
433    use crate::{
434        fixtures::{self, BU3},
435        layout, Identity, INQUIRY_LEN,
436    };
437
438    #[test]
439    fn an_image_with_no_verifiable_ranges_is_refused_before_any_write() {
440        let mut image = fixtures::image(&BU3, 7);
441        let at = crate::image::cmac::TABLE_OFFSET;
442        image[at + 4..at + 8].copy_from_slice(&0u32.to_le_bytes());
443        image[at + 8..at + 12].copy_from_slice(&0xFFFu32.to_le_bytes());
444        let image = crate::image::cmac::resign(&image).unwrap();
445        let class = crate::DriveClass::of(crate::Chip::Mt1959);
446        assert!(class.verification_ranges(&image).is_empty());
447        let receiver = Receiver {
448            identity: super::super::DetectedMode {
449                mode: super::super::Mode::Firmware,
450                inquiry: [0; INQUIRY_LEN],
451            },
452            encoding: Encoding::Plaintext,
453            site: super::super::Evidence::new(0x98000, &[1, 2]),
454            transform: None,
455            reference: None,
456            profile: Some(super::super::profile::Profile::testing()),
457        };
458        assert!(matches!(
459            TransferPlan::new(
460                &receiver,
461                &image,
462                class,
463                update::flash::ExecutionMode::Flash
464            ),
465            Err(update::PrepareError::Integrity)
466        ));
467    }
468
469    #[test]
470    fn input_envelope_never_selects_the_running_receivers_wire_encoding() {
471        let image = fixtures::image(&BU3, 7);
472        let identity = Identity::parse(
473            &[0; INQUIRY_LEN],
474            layout::BANNER.of(&image).unwrap(),
475            layout::DESCRIPTOR.of(&image).unwrap(),
476        )
477        .unwrap();
478        let installed =
479            update::InstalledFirmware::new(identity, &fixtures::drive_read(&image)).unwrap();
480        let mut ciphertext = image.clone();
481        crate::enc::encrypt(&mut ciphertext).unwrap();
482        for encoding in [Encoding::Plaintext, Encoding::Encrypted] {
483            let receiver = Receiver {
484                identity: super::super::DetectedMode {
485                    mode: super::super::Mode::Firmware,
486                    inquiry: [0; INQUIRY_LEN],
487                },
488                encoding,
489                site: super::super::Evidence::new(0x98000, &[1, 2]),
490                transform: None,
491                reference: None,
492                profile: Some(super::super::profile::Profile::testing()),
493            };
494            for input in [&image, &ciphertext] {
495                let prepared = receiver
496                    .prepare(&Envelope::load(input).unwrap(), &installed)
497                    .unwrap();
498                assert_eq!(prepared.plan().image(), image);
499                assert_eq!(
500                    prepared.plan().transfer(),
501                    if encoding == Encoding::Encrypted {
502                        &ciphertext
503                    } else {
504                        &image
505                    }
506                );
507                assert_eq!(prepared.plan().receiver(), &receiver);
508                let steps = prepared.plan().steps();
509                assert_eq!(
510                    steps[..3].iter().map(|s| s.label).collect::<Vec<_>>(),
511                    ["PROBE", "READY", "PREPARE"]
512                );
513                let recovery = TransferPlan::new(
514                    &receiver,
515                    &image,
516                    prepared.plan().class(),
517                    update::flash::ExecutionMode::Recovery,
518                )
519                .unwrap();
520                let recovery_steps = recovery.steps();
521                assert_eq!(recovery_steps[0].label, "PREPARE");
522                assert_eq!(recovery_steps.len() + 2, steps.len());
523                for (recovery, flash) in recovery_steps.iter().zip(&steps[2..]) {
524                    assert_eq!(recovery.cdb, flash.cdb);
525                    assert_eq!(recovery.direction, flash.direction);
526                    assert_eq!(recovery.length, flash.length);
527                }
528            }
529        }
530    }
531}