1use std::ops::RangeInclusive;
27
28use crate::image::{cmac, detect_chip, family, is_drive_read, Family};
29use crate::{cdb, layout, Chip, DriveClass, Identity};
30
31#[cfg(feature = "drive")]
32mod completion;
33#[cfg(feature = "drive")]
34pub use completion::{Completion, ReadbackQuality, Startup, StartupError};
35#[cfg(feature = "drive")]
36pub(crate) mod flash;
37#[cfg(feature = "drive")]
38pub use flash::{FlashError, FlashRuntime, FlashTransport, Stage, Status, Verified};
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42#[non_exhaustive]
43pub enum PrepareError {
44 ReceiverEntry,
46 ReceiverContract,
48 Size(usize),
50 Integrity,
52 DriveRead,
55 UnknownChip,
57 UnknownDriveChip,
59 ChipMismatch {
61 image: Chip,
63 drive: Chip,
65 },
66 InstalledMismatch,
69 UnknownInstalledFamily,
71 UnknownTargetFamily,
73 FamilyMismatch {
75 installed: Family,
77 target: Family,
79 },
80}
81
82impl core::fmt::Display for PrepareError {
83 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84 match self {
85 Self::ReceiverEntry => f.write_str(
86 "receiver entry profile is unknown; detect the live receiver before preparing",
87 ),
88 Self::ReceiverContract => {
89 f.write_str("receiver transfer contract is incomplete; no firmware was sent")
90 }
91 Self::Size(n) => write!(
92 f,
93 "image is {n} bytes; an update image is exactly {} bytes",
94 layout::IMAGE_SIZE
95 ),
96 Self::Integrity => f.write_str("image fails its AES-CMAC integrity check"),
97 Self::DriveRead => f.write_str(
98 "image is a raw drive read (its boot page is the read-back copy), not a \
99 flashable image",
100 ),
101 Self::UnknownChip => f.write_str("image names no MT1959 or MT1939 chip"),
102 Self::UnknownDriveChip => f.write_str(
103 "could not read this drive's controller generation (MT1959/MT1939) from its \
104 boot banner",
105 ),
106 Self::ChipMismatch { image, drive } => write!(
107 f,
108 "image is {image} firmware but this drive is {drive} silicon — refusing to \
109 flash across chip generations"
110 ),
111 Self::InstalledMismatch => f.write_str(
112 "installed firmware is not a complete image of the live drive (size, identity \
113 descriptor or boot banner differ)",
114 ),
115 Self::UnknownInstalledFamily => {
116 f.write_str("could not determine the installed firmware family")
117 }
118 Self::UnknownTargetFamily => {
119 f.write_str("could not determine the target firmware family")
120 }
121 Self::FamilyMismatch { installed, target } => write!(
122 f,
123 "family mismatch: installed firmware is family {installed}, target is family \
124 {target}"
125 ),
126 }
127 }
128}
129
130impl std::error::Error for PrepareError {}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134pub enum Direction {
135 In,
137 Out,
139 None,
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub struct Step {
146 pub label: &'static str,
148 pub cdb: [u8; 12],
150 pub direction: Direction,
152 pub length: usize,
154}
155
156impl DriveClass {
157 pub fn steps(self) -> Vec<Step> {
161 let chunk = self.chunk();
162 let step = |label, cdb, direction, length| Step {
163 label,
164 cdb,
165 direction,
166 length,
167 };
168 let mut steps = vec![
169 step("PROBE", cdb::probe(), Direction::In, cdb::PROBE_LEN),
170 step("READY", cdb::test_unit_ready(), Direction::None, 0),
171 step("PREPARE", cdb::enter_update(), Direction::Out, 0),
172 ];
173 steps.extend((0..layout::IMAGE_SIZE).step_by(chunk).map(|offset| {
174 step(
175 "STREAM",
176 cdb::transfer(offset as u32, chunk as u16),
177 Direction::Out,
178 chunk,
179 )
180 }));
181 steps.extend([
182 step("COMMIT", cdb::finish(), Direction::Out, 0),
183 step("READY", cdb::test_unit_ready(), Direction::None, 0),
184 step(
185 "STATUS",
186 cdb::request_sense(),
187 Direction::In,
188 cdb::REQUEST_SENSE_LEN,
189 ),
190 ]);
191 steps
192 }
193
194 pub fn verification_ranges(self, image: &[u8]) -> Vec<RangeInclusive<usize>> {
198 let Ok(entries) = cmac::parse_table(image) else {
199 return Vec::new();
200 };
201 entries
202 .iter()
203 .filter(|entry| entry.is_active())
204 .filter_map(|entry| {
205 let start = (entry.start as usize).max(self.verify_floor());
206 let end = entry.end as usize;
207 (start <= end).then_some(start..=end)
208 })
209 .collect()
210 }
211}
212
213#[derive(Clone, Debug)]
215pub struct InstalledFirmware {
216 identity: Identity,
217 family: Option<Family>,
218}
219
220impl InstalledFirmware {
221 pub fn new(identity: Identity, image: &[u8]) -> Result<Self, PrepareError> {
229 if image.len() != layout::IMAGE_SIZE {
230 return Err(PrepareError::InstalledMismatch);
231 }
232 let descriptor = identity
233 .has_descriptor()
234 .then(|| layout::DESCRIPTOR.of(image) == Some(&identity.descriptor_bytes()[..]));
235 let banner = (!identity.banner().is_empty())
236 .then(|| layout::BANNER.of(image) == Some(&identity.banner_bytes()[..]));
237 match (descriptor, banner) {
238 (None, None) | (Some(false), _) | (_, Some(false)) => {
239 Err(PrepareError::InstalledMismatch)
240 }
241 _ => Ok(Self {
242 family: family(image),
243 identity,
244 }),
245 }
246 }
247
248 pub fn identity(&self) -> &Identity {
250 &self.identity
251 }
252
253 pub fn family(&self) -> Option<Family> {
255 self.family
256 }
257}
258
259#[derive(Clone, Debug)]
261pub struct PreparedUpdate {
262 class: DriveClass,
263 family: Family,
264 image: Vec<u8>,
265 transfer: Option<Vec<u8>>,
266}
267
268impl PreparedUpdate {
269 pub fn check(image: &[u8], drive: &Identity) -> Result<Family, PrepareError> {
276 Self::checked(image, drive).map(|(_, family)| family)
277 }
278
279 fn checked(image: &[u8], drive: &Identity) -> Result<(DriveClass, Family), PrepareError> {
281 if image.len() != layout::IMAGE_SIZE {
282 return Err(PrepareError::Size(image.len()));
283 }
284 if !cmac::verify(image) {
285 return Err(PrepareError::Integrity);
286 }
287 if is_drive_read(image) {
288 return Err(PrepareError::DriveRead);
289 }
290 let image_chip = detect_chip(image).map_err(|_| PrepareError::UnknownChip)?;
291 let drive_chip =
295 Chip::from_banner(drive.banner_bytes()).ok_or(PrepareError::UnknownDriveChip)?;
296 if image_chip.family != drive_chip {
297 return Err(PrepareError::ChipMismatch {
298 image: image_chip.family,
299 drive: drive_chip,
300 });
301 }
302 let target = family(image).ok_or(PrepareError::UnknownTargetFamily)?;
303 Ok((DriveClass::of(drive_chip), target))
304 }
305
306 pub fn prepare(image: &[u8], installed: &InstalledFirmware) -> Result<Self, PrepareError> {
310 let (class, target) = Self::checked(image, installed.identity())?;
311 let installed_family = installed
312 .family()
313 .ok_or(PrepareError::UnknownInstalledFamily)?;
314 if installed_family != target {
315 return Err(PrepareError::FamilyMismatch {
316 installed: installed_family,
317 target,
318 });
319 }
320 Ok(Self {
321 class,
322 family: target,
323 image: image.to_vec(),
324 transfer: None,
325 })
326 }
327
328 pub fn prepare_without_installed(image: &[u8], live: &Identity) -> Result<Self, PrepareError> {
333 let (class, target) = Self::checked(image, live)?;
334 Ok(Self {
335 class,
336 family: target,
337 image: image.to_vec(),
338 transfer: None,
339 })
340 }
341
342 #[cfg(all(feature = "enc", test))]
345 pub(crate) fn encrypted(mut self) -> Self {
346 let mut transfer = self.image.clone();
347 crate::enc::encrypt(&mut transfer).expect("an update image is whole AES blocks");
348 self.transfer = Some(transfer);
349 self
350 }
351
352 pub fn class(&self) -> DriveClass {
354 self.class
355 }
356
357 pub fn family(&self) -> Family {
360 self.family
361 }
362
363 pub fn image(&self) -> &[u8] {
365 &self.image
366 }
367
368 pub fn transfer(&self) -> &[u8] {
370 self.transfer.as_deref().unwrap_or(&self.image)
371 }
372
373 pub fn steps(&self) -> Vec<Step> {
375 self.class.steps()
376 }
377
378 pub fn verification_ranges(&self) -> Vec<RangeInclusive<usize>> {
381 self.class.verification_ranges(&self.image)
382 }
383}
384
385#[cfg(test)]
386#[path = "update_tests.rs"]
387mod tests;