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