opentitanlib/image/
image.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::borrow::Cow;
6use std::collections::HashSet;
7use std::convert::TryInto;
8use std::fs::File;
9use std::io::{Read, Write};
10use std::mem::{align_of, offset_of, size_of};
11use std::path::{Path, PathBuf};
12
13use anyhow::{Result, bail, ensure};
14use sphincsplus::{SphincsPlus, SpxDomain, SpxPublicKey};
15use thiserror::Error;
16use zerocopy::FromBytes;
17
18use crate::crypto::ecdsa::{EcdsaPublicKey, EcdsaRawPublicKey, EcdsaRawSignature};
19use crate::crypto::rsa::Modulus;
20use crate::crypto::rsa::RsaPublicKey;
21use crate::crypto::rsa::Signature as RsaSignature;
22use crate::crypto::sha256::Sha256Digest;
23use crate::image::manifest::{
24    CHIP_MANIFEST_VERSION_MAJOR1, CHIP_MANIFEST_VERSION_MAJOR2, CHIP_MANIFEST_VERSION_MINOR1,
25    CHIP_ROM_EXT_IDENTIFIER, CHIP_ROM_EXT_SIZE_MAX, MANIFEST_EXT_ID_SPX_KEY,
26    MANIFEST_EXT_ID_SPX_SIGNATURE, Manifest, ManifestKind, SigverifySpxSignature,
27};
28use crate::image::manifest_def::{ManifestSigverifyBuffer, ManifestSpec};
29use crate::image::manifest_ext::{ManifestExtEntry, ManifestExtSpec};
30use crate::util::file::{FromReader, ToWriter};
31use crate::util::parse_int::ParseInt;
32
33#[derive(Debug, Error)]
34pub enum ImageError {
35    #[error("Incomplete read: expected to read {0} bytes but read {1} bytes")]
36    IncompleteRead(usize, usize),
37    #[error("Failed to parse image manifest.")]
38    Parse,
39    #[error("Extension data overflows flash image.")]
40    ExtensionOverflow,
41    #[error("Extension table index is out of bounds.")]
42    BadExtensionTableIndex,
43    #[error("Extension ID 0x{0:x} not in manifest table.")]
44    NoExtensionTableEntry(u32),
45    #[error("Extension 0x{0:x} is not aligned to word boundary.")]
46    BadExtensionAlignment(u32),
47    #[error("Invalid placement of signed extension 0x{0:x}.")]
48    MisplacedSignedExtension(u32),
49    #[error("Invalid manifest major version: {0}. ECDSA support requires major version {1}.")]
50    InvalidManifestVersionforEcdsa(u16, u16),
51}
52
53pub enum MainSignatureParams {
54    Rsa(RsaPublicKey, RsaSignature),
55    Ecdsa(EcdsaRawPublicKey, EcdsaRawSignature),
56}
57
58pub struct SpxSignatureParams {
59    key: SpxPublicKey,
60    signature: [u8; 7856],
61}
62
63// Binary image is signed either RSA or ECDSA. SPX+ signature could be added as
64// an extension.
65pub struct SigverifyParams {
66    pub main_sig_params: MainSignatureParams,
67    pub spx_sig_params: Option<SpxSignatureParams>,
68    pub spx_hash_reversal_bug: bool,
69}
70
71impl SigverifyParams {
72    pub fn new(
73        main_sig_params: MainSignatureParams,
74        spx_sig_params: Option<SpxSignatureParams>,
75    ) -> Self {
76        SigverifyParams {
77            main_sig_params,
78            spx_sig_params,
79            spx_hash_reversal_bug: false,
80        }
81    }
82    pub fn with_hash_reversal_bug(mut self, bug: bool) -> Self {
83        self.spx_hash_reversal_bug = bug;
84        self
85    }
86
87    // Verify the main image signature.
88    pub fn verify(&self, digest: &Sha256Digest) -> Result<()> {
89        match &self.main_sig_params {
90            MainSignatureParams::Rsa(key, sig) => {
91                key.verify(digest, sig)?;
92            }
93            MainSignatureParams::Ecdsa(key, sig) => {
94                let ecdsa_key: EcdsaPublicKey = key.try_into()?;
95                ecdsa_key.verify(digest, sig)?;
96            }
97        }
98        Ok(())
99    }
100
101    // Verify the optional SPX+ signature.
102    pub fn spx_verify(&self, b: &[u8], domain: SpxDomain) -> Result<()> {
103        if let Some(spx) = &self.spx_sig_params {
104            let msg = match domain {
105                SpxDomain::PreHashedSha256 => Cow::from(if self.spx_hash_reversal_bug {
106                    Sha256Digest::hash(b).to_vec_rev()
107                } else {
108                    Sha256Digest::hash(b).to_vec()
109                }),
110                _ => Cow::from(b),
111            };
112            spx.key.verify(domain, &spx.signature, &msg)?;
113        } else {
114            bail!("No SPX signature found");
115        }
116
117        Ok(())
118    }
119}
120
121/// A buffer with the same alignment as `Manifest` for storing image data.
122#[repr(C)]
123#[derive(Debug)]
124pub struct ImageData {
125    pub bytes: [u8; Image::MAX_SIZE],
126    _align: [Manifest; 0],
127}
128
129impl Default for ImageData {
130    fn default() -> Self {
131        ImageData {
132            bytes: [0xFF; Image::MAX_SIZE],
133            _align: [],
134        }
135    }
136}
137
138#[derive(Debug, Default)]
139pub struct Image {
140    // TODO(cfrantz): We should use Box::new_uninit to create this, as the
141    // scheme of allowing Box::default to initialize this creates a copy on
142    // the stack and then copies it into the Box.  Unfortunately, Box::new_uninit
143    // is nightly-only (rust-lang#63291).
144    //
145    // For now, I've increased the thread stack size for opentitanlib_test to
146    // avoid the test overflowing its stack while initializing this item.
147    data: Box<ImageData>,
148    pub size: usize,
149}
150
151#[derive(Debug)]
152pub struct SubImage<'a> {
153    pub kind: ManifestKind,
154    pub offset: usize,
155    pub manifest: &'a Manifest,
156    pub data: &'a [u8],
157}
158
159#[derive(Debug)]
160pub enum ImageChunk {
161    Concat(PathBuf),
162    Offset(PathBuf, usize),
163}
164
165#[derive(Debug, Default)]
166pub struct ImageAssembler {
167    pub size: usize,
168    pub mirrored: bool,
169    pub chunks: Vec<ImageChunk>,
170}
171
172impl FromReader for Image {
173    /// Reads in an `Image`.
174    fn from_reader(mut r: impl Read) -> Result<Self> {
175        let mut image = Image::default();
176        image.size = r.read(&mut image.data.bytes)?;
177        Ok(image)
178    }
179}
180
181impl ToWriter for Image {
182    /// Writes out the `Image`.
183    fn to_writer(&self, w: &mut impl Write) -> Result<()> {
184        w.write_all(&self.data.bytes[..self.size])?;
185        Ok(())
186    }
187}
188
189impl Image {
190    pub const MAX_SIZE: usize = 1024 * 1024;
191
192    /// Perform a sanity check to ensure that the image comes with manifest.
193    ///
194    /// Note that passing the sanity check doesn't guarantee that the manifest is valid.
195    pub fn manifest_sanity_check(&self) -> Result<()> {
196        let manifest = self.borrow_manifest()?;
197        let len = self.data.bytes.len() as u32;
198
199        ensure!(manifest.signed_region_end <= len);
200        ensure!(manifest.length <= len);
201        ensure!(manifest.code_start < len);
202        ensure!(manifest.code_end < len);
203        ensure!(manifest.entry_point < len);
204        ensure!(manifest.extensions.entries.iter().all(|x| x.offset < len));
205
206        // We allow the ROM_EXT to be larger than maximum slot size to support provisioning flows
207        // and future ROM_EXT expansion if needed.
208        if (manifest.identifier == CHIP_ROM_EXT_IDENTIFIER)
209            && (manifest.length > CHIP_ROM_EXT_SIZE_MAX)
210        {
211            log::warn!("ROM_EXT is larger than 64k. Link offsets may need recalculating.");
212        }
213
214        Ok(())
215    }
216
217    // Retrieve the SPX+ signature from the image, if present.
218    fn get_spx_signature(&self) -> Result<Option<SpxSignatureParams>> {
219        let ext_tab = self.borrow_manifest()?.extensions.entries;
220        let key_o = ext_tab
221            .iter()
222            .find(|e| e.identifier == MANIFEST_EXT_ID_SPX_KEY);
223        let sig_o = ext_tab
224            .iter()
225            .find(|e| e.identifier == MANIFEST_EXT_ID_SPX_SIGNATURE);
226
227        match (key_o, sig_o) {
228            (Some(key_e), Some(sig_e)) => {
229                const KEY_SIZE: usize = 32; // SPX+ public key size.
230                const SIG_SIZE: usize = std::mem::size_of::<SigverifySpxSignature>();
231
232                let mut key_bytes = [0u8; KEY_SIZE];
233                let mut signature = [0u8; SIG_SIZE];
234                let k_ofs = (key_e.offset + 8) as usize;
235                let s_ofs = (sig_e.offset + 8) as usize;
236
237                key_bytes.copy_from_slice(&self.data.bytes[k_ofs..k_ofs + KEY_SIZE]);
238                signature.copy_from_slice(&self.data.bytes[s_ofs..s_ofs + SIG_SIZE]);
239
240                let key = SpxPublicKey::from_bytes(SphincsPlus::Sha2128sSimple, &key_bytes)?;
241
242                Ok(Some(SpxSignatureParams { key, signature }))
243            }
244            (_, _) => Ok(None),
245        }
246    }
247
248    pub fn get_sigverify_params_from_manifest(&self) -> Result<SigverifyParams> {
249        let manifest = self.borrow_manifest()?;
250        let manifest_def: ManifestSpec = manifest.try_into()?;
251
252        let spx_sig_params = self.get_spx_signature()?;
253
254        let pub_key = manifest_def
255            .pub_key()
256            .ok_or(ImageError::Parse)?
257            .to_le_bytes();
258
259        let signature = manifest_def
260            .signature()
261            .ok_or(ImageError::Parse)?
262            .to_le_bytes();
263
264        if (manifest.manifest_version.major == CHIP_MANIFEST_VERSION_MAJOR1)
265            && (manifest.manifest_version.minor == CHIP_MANIFEST_VERSION_MINOR1)
266        {
267            let rsa_key = RsaPublicKey::new(Modulus::from_le_bytes(pub_key)?)?;
268            let rsa_sig = RsaSignature::from_le_bytes(signature)?;
269            return Ok(SigverifyParams::new(
270                MainSignatureParams::Rsa(rsa_key, rsa_sig),
271                spx_sig_params,
272            ));
273        }
274
275        let ecdsa_pub_key = EcdsaRawPublicKey::read(&mut std::io::Cursor::new(pub_key))?;
276        let ecdsa_sig = EcdsaRawSignature::read(&mut std::io::Cursor::new(signature))?;
277
278        Ok(SigverifyParams::new(
279            MainSignatureParams::Ecdsa(ecdsa_pub_key, ecdsa_sig),
280            spx_sig_params,
281        ))
282    }
283
284    /// Overwrites all fields in the image's manifest that are defined in `other`.
285    pub fn overwrite_manifest(&mut self, other: ManifestSpec) -> Result<()> {
286        let manifest = self.borrow_manifest_mut()?;
287        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
288        manifest_def.overwrite_fields(other);
289        *manifest = manifest_def.try_into()?;
290        Ok(())
291    }
292
293    /// Adds an extension to the signed region of this `Image`.
294    ///
295    /// This will take all the extensions in `spec.signed_region` and append them to the image.
296    /// This should be called before adding any unsigned extensions to ensure all extensions that
297    /// are a part of the signature exist within the contiguous signed region of the image.
298    pub fn add_signed_manifest_extensions(&mut self, spec: &ManifestExtSpec) -> Result<()> {
299        for entry_spec in &spec.signed_region {
300            self.add_manifest_extension(ManifestExtEntry::from_spec(
301                entry_spec,
302                spec.source_path(),
303            )?)?;
304        }
305        Ok(())
306    }
307
308    /// Adds an extension to the unsigned region of this `Image`.
309    ///
310    /// This will take all the extensions in `spec.unsigned_region` and append them to the image.
311    /// This should only be called once all signed extensions have been added.
312    pub fn add_unsigned_manifest_extensions(&mut self, spec: &ManifestExtSpec) -> Result<()> {
313        for entry_spec in &spec.unsigned_region {
314            self.add_manifest_extension(ManifestExtEntry::from_spec(
315                entry_spec,
316                spec.source_path(),
317            )?)?;
318        }
319        Ok(())
320    }
321
322    /// Adds an extension to the end of this `Image`.
323    pub fn add_manifest_extension(&mut self, entry: ManifestExtEntry) -> Result<()> {
324        let manifest = self.borrow_manifest()?;
325
326        // A copy of the extension table since we can't borrow as mutable.
327        let mut ext_table = manifest.extensions.entries;
328
329        let entry_id = entry.header().identifier;
330
331        // Update the offset in the extension table.
332        let ext_table_entry = ext_table
333            .iter_mut()
334            .find(|e| e.identifier == entry_id)
335            .ok_or(ImageError::NoExtensionTableEntry(entry_id))?;
336
337        // If the extension already exists, overwrite it, else append it to the end of the
338        // image.
339        let offset = if ext_table_entry.offset != 0 {
340            ext_table_entry.offset
341        } else {
342            ensure!(
343                self.size.is_multiple_of(align_of::<u32>()),
344                ImageError::BadExtensionAlignment(entry_id)
345            );
346            self.size.try_into()?
347        };
348        ext_table_entry.offset = offset;
349
350        // Write the extension to the end of the image.
351        let ext_bytes = entry.to_vec();
352        let end_index = offset
353            .checked_add(ext_bytes.len().try_into()?)
354            .ok_or(ImageError::ExtensionOverflow)?;
355        let extension_slice = self
356            .data
357            .bytes
358            .get_mut(offset as usize..end_index as usize)
359            .ok_or(ImageError::ExtensionOverflow)?;
360        extension_slice.copy_from_slice(ext_bytes.as_slice());
361        self.size = std::cmp::max(end_index as usize, self.size);
362
363        let manifest = self.borrow_manifest_mut()?;
364        manifest.extensions.entries = ext_table;
365
366        Ok(())
367    }
368
369    /// Allocates space for a manifest extension and sets the offset in the extension table.
370    ///
371    /// This function is similar to `add_manifest_extension`, but doesn't populate any data for the
372    /// extension. This is necessary to properly set the `length` field of the manifest and the
373    /// `offset` field of the manifest extension entry for signing.
374    pub fn allocate_manifest_extension(&mut self, id: u32, len: usize) -> Result<()> {
375        let offset = self.size as u32;
376        self.borrow_manifest_mut()?
377            .extensions
378            .entries
379            .iter_mut()
380            .find(|e| e.identifier == id)
381            .ok_or(ImageError::NoExtensionTableEntry(id))?
382            .offset = offset;
383
384        self.size = self
385            .size
386            .checked_add(len)
387            .ok_or(ImageError::ExtensionOverflow)?;
388
389        Ok(())
390    }
391
392    /// Clears any entry in the manifest extension table that doesn't have an offset.
393    ///
394    /// This allows a manifest definition with a populated extension table to be used even when
395    /// extensions aren't provided.
396    pub fn drop_null_extensions(&mut self) -> Result<()> {
397        let manifest = self.borrow_manifest_mut()?;
398
399        manifest.extensions.entries.iter_mut().for_each(|e| {
400            if e.offset == 0 {
401                e.identifier = 0;
402            }
403        });
404
405        Ok(())
406    }
407
408    /// Updates the signature field in the `Manifest` with the provided RSA signature.
409    pub fn update_rsa_signature(&mut self, signature: RsaSignature) -> Result<()> {
410        let manifest = self.borrow_manifest_mut()?;
411
412        // Convert to a `ManifestSpec` so we can supply the signature as a `BigInt`.
413        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
414        manifest_def.update_signature(ManifestSigverifyBuffer::from_le_bytes(
415            signature.to_le_bytes(),
416        )?);
417        *manifest = manifest_def.try_into()?;
418        Ok(())
419    }
420
421    /// Updates the signature field in the `Manifest` with the provided ECDSA signature.
422    pub fn update_ecdsa_signature(&mut self, signature: EcdsaRawSignature) -> Result<()> {
423        let manifest = self.borrow_manifest_mut()?;
424
425        // TODO(moidx): Remove check once we have migrated away from RSA keys
426        // and start using a key type field in the manifest.
427        //
428        // Note(cfrantz): I have disabled this error return because we want to test
429        // manifests with bad version numbers.  I've replaced the error return with
430        // a log message.  As stated by moidx@, we'll remove this once we've
431        // completely migrated away from RSA keys.
432        // ensure!(
433        //     manifest.manifest_version.major == CHIP_MANIFEST_VERSION_MAJOR2,
434        //     ImageError::InvalidManifestVersionforEcdsa(
435        //         manifest.manifest_version.major,
436        //         CHIP_MANIFEST_VERSION_MAJOR2
437        //     )
438        // );
439        if manifest.manifest_version.major != CHIP_MANIFEST_VERSION_MAJOR2 {
440            log::error!(
441                "Invalid manifest version for ECDSA: {:?}",
442                manifest.manifest_version
443            );
444        }
445
446        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
447
448        // Convert the signature to a byte array and pad it to 3072 bits.
449        let signature_bytes = signature
450            .r
451            .iter()
452            .chain(signature.s.iter())
453            .copied()
454            .collect::<Vec<u8>>();
455        let sig_padding = vec![0xa5u8; 384 - 64];
456        let signature_bytes = signature_bytes
457            .iter()
458            .chain(sig_padding.iter())
459            .copied()
460            .collect::<Vec<u8>>();
461
462        manifest_def.update_signature(ManifestSigverifyBuffer::from_le_bytes(signature_bytes)?);
463        *manifest = manifest_def.try_into()?;
464        Ok(())
465    }
466
467    /// Updates the pub_key field in the `Manifest` with the RSA Modulus.
468    pub fn update_modulus(&mut self, rsa_modulus: Modulus) -> Result<()> {
469        let manifest = self.borrow_manifest_mut()?;
470
471        // Convert to a `ManifestSpec` so we can supply the rsa_modulus as a `BigInt`.
472        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
473        manifest_def.update_pub_key(ManifestSigverifyBuffer::from_le_bytes(
474            rsa_modulus.to_le_bytes(),
475        )?);
476        *manifest = manifest_def.try_into()?;
477        Ok(())
478    }
479
480    /// Updates the pub_key field in the `Manifest` with the ECDSA public key.
481    pub fn update_ecdsa_public_key(&mut self, ecdsa_public_key: EcdsaRawPublicKey) -> Result<()> {
482        let manifest = self.borrow_manifest_mut()?;
483        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
484
485        // Convert the public key to a byte array and pad it to 3072 bits.
486        let key_bytes = ecdsa_public_key
487            .x
488            .iter()
489            .chain(ecdsa_public_key.y.iter())
490            .copied()
491            .collect::<Vec<u8>>();
492        let key_padding = vec![0xa5u8; 384 - 64];
493        let key_bytes = key_bytes
494            .iter()
495            .chain(key_padding.iter())
496            .copied()
497            .collect::<Vec<u8>>();
498
499        manifest_def.update_pub_key(ManifestSigverifyBuffer::from_le_bytes(key_bytes)?);
500        *manifest = manifest_def.try_into()?;
501
502        // Update the manifest major version to be able to detect the signing
503        // key type.
504        // TODO(moidx): Replace this with a key type field in the manifest once
505        // support for RSA keys is removed.
506        if manifest.manifest_version.major == CHIP_MANIFEST_VERSION_MAJOR1 {
507            manifest.manifest_version.major = CHIP_MANIFEST_VERSION_MAJOR2;
508        }
509        Ok(())
510    }
511
512    pub fn subimages(&self) -> Result<Vec<SubImage<'_>>> {
513        let mut result = Vec::new();
514        let mut offset = 0;
515        while offset < self.size {
516            let m = &self.data.bytes[offset..offset + size_of::<Manifest>()];
517            let manifest = Manifest::ref_from_bytes(m).map_err(|_| ImageError::Parse)?;
518            let kind = ManifestKind(manifest.identifier);
519            let mut size = 1;
520            if kind.is_known_value() {
521                size = manifest.length as usize;
522                result.push(SubImage {
523                    kind,
524                    offset,
525                    manifest,
526                    data: &self.data.bytes[offset..offset + size],
527                });
528            }
529            // Round up to next 64K offset.
530            offset += (size + 65535) & !65535;
531        }
532        Ok(result)
533    }
534
535    pub fn bytes(&self) -> &[u8] {
536        &self.data.bytes[..self.size]
537    }
538
539    pub fn borrow_manifest(&self) -> Result<&Manifest> {
540        let manifest_slice = &self.data.bytes[0..size_of::<Manifest>()];
541        let manifest = Manifest::ref_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
542        Ok(manifest)
543    }
544
545    pub fn borrow_manifest_mut(&mut self) -> Result<&mut Manifest> {
546        let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
547        let manifest = Manifest::mut_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
548        Ok(manifest)
549    }
550
551    /// Updates the length field in the `Manifest`.
552    pub fn update_length(&mut self) -> Result<usize> {
553        self.borrow_manifest_mut()?.length = self.size as u32;
554        Ok(self.size)
555    }
556
557    /// Sets the `signed_region_end` field to the end of the last signed extension.
558    ///
559    /// The end of the signed region is computed as the offset of the first extension that is not
560    /// in `signed_ids` or the size of the image if there is no such extension.
561    pub fn update_signed_region(&mut self, signed_ids: &HashSet<u32>) -> Result<()> {
562        let image_size = self.size as u32;
563        let mut first_unsigned_ext = 0u32;
564        let manifest = self.borrow_manifest_mut()?;
565
566        let mut ext_table = manifest.extensions.entries;
567        ext_table.sort_by(|a, b| a.offset.cmp(&b.offset));
568        for e in ext_table {
569            // Ignore any extensions that haven't been added to the image.
570            if e.offset == 0 {
571                continue;
572            }
573            if signed_ids.contains(&e.identifier) {
574                // Since extensions are sorted by offset, if we've already seen an unsigned
575                // extension then the signed region is discontinuous.
576                ensure!(
577                    first_unsigned_ext == 0,
578                    ImageError::MisplacedSignedExtension(e.identifier)
579                );
580            } else if first_unsigned_ext == 0 {
581                first_unsigned_ext = e.offset;
582            }
583        }
584
585        manifest.signed_region_end = if first_unsigned_ext == 0 {
586            image_size
587        } else {
588            first_unsigned_ext
589        };
590
591        Ok(())
592    }
593
594    /// Operates on the signed region of the image.
595    pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
596    where
597        F: FnOnce(&[u8]) -> R,
598    {
599        Ok(f(&self.data.bytes[offset_of!(Manifest, usage_constraints)
600            ..self.borrow_manifest()?.signed_region_end as usize]))
601    }
602
603    /// Compute the SHA256 digest for the signed portion of the `Image`.
604    pub fn compute_digest(&self) -> Result<Sha256Digest> {
605        self.map_signed_region(|v| Sha256Digest::hash(v))
606    }
607}
608
609impl SubImage<'_> {
610    /// Operates on the signed region of the image.
611    pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
612    where
613        F: FnOnce(&[u8]) -> R,
614    {
615        Ok(f(&self.data[offset_of!(Manifest, usage_constraints)
616            ..self.manifest.signed_region_end as usize]))
617    }
618
619    /// Compute the SHA256 digest for the signed portion of the `Image`.
620    pub fn compute_digest(&self) -> Result<Sha256Digest> {
621        self.map_signed_region(|v| Sha256Digest::hash(v))
622    }
623}
624
625impl ImageAssembler {
626    /// Creates an `ImageAssembler` with a given `size` and mirroring parameters.
627    pub fn with_params(size: usize, mirrored: bool) -> Self {
628        ImageAssembler {
629            size,
630            mirrored,
631            ..Default::default()
632        }
633    }
634
635    /// Creates an `ImageAssembler` with default parameters for OpenTitan: a 1MiB image which is mirrored.
636    pub fn new() -> Self {
637        Self::with_params(0x100000, true)
638    }
639
640    /// Parse a list of strings into chunks to be assembled.
641    /// Each string may be a filename or a filename@offset describing where in the assembled image the contents of the file should appear.
642    /// The offset is an integer expressed in any of the bases accepted by [`ParseInt`].
643    pub fn parse(&mut self, chunks: &[impl AsRef<str>]) -> Result<()> {
644        for chunk in chunks {
645            if let Some((file, offset)) = chunk.as_ref().split_once('@') {
646                self.chunks.push(ImageChunk::Offset(
647                    PathBuf::from(file),
648                    usize::from_str(offset)?,
649                ));
650            } else {
651                self.chunks
652                    .push(ImageChunk::Concat(PathBuf::from(chunk.as_ref())));
653            }
654        }
655        Ok(())
656    }
657
658    // Read a file into a buffer.  Ensure the entire file is read.
659    fn read(path: &Path, buf: &mut [u8]) -> Result<usize> {
660        let mut file = File::open(path)?;
661        let len = file.metadata()?.len() as usize;
662        let n = file.read(buf)?;
663        ensure!(len == n, ImageError::IncompleteRead(len, n));
664        Ok(n)
665    }
666
667    /// Assemble the image according to the parameters and parsed chunk specifications.
668    pub fn assemble(&self) -> Result<Vec<u8>> {
669        let size = if self.mirrored {
670            self.size / 2
671        } else {
672            self.size
673        };
674        let mut image = vec![0xff; size];
675        let mut pos = 0;
676        for chunk in &self.chunks {
677            match chunk {
678                ImageChunk::Concat(path) => {
679                    let n = Self::read(path, &mut image[pos..])?;
680                    pos += n;
681                }
682                ImageChunk::Offset(path, offset) => {
683                    let n = Self::read(path, &mut image[*offset..])?;
684                    pos = offset + n;
685                }
686            }
687        }
688        if self.mirrored {
689            image.extend_from_within(..size);
690        }
691        Ok(image)
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use crate::util::testdata;
699
700    #[test]
701    fn test_assemble_concat() -> Result<()> {
702        // Test image assembly by concatenation.
703        let mut image = ImageAssembler::with_params(16, false);
704        image.parse(&[
705            testdata("image/hello.txt").to_str().unwrap(),
706            testdata("image/world.txt").to_str().unwrap(),
707        ])?;
708        let data = image.assemble()?;
709        assert_eq!(data, b"HelloWorld\xff\xff\xff\xff\xff\xff");
710        Ok(())
711    }
712
713    #[test]
714    fn test_assemble_offset() -> Result<()> {
715        // Test image assembly by explicit offsets.
716        let mut image = ImageAssembler::with_params(16, false);
717        image.parse(&[
718            testdata("image/hello.txt@0").to_str().unwrap(),
719            testdata("image/world.txt@0x8").to_str().unwrap(),
720        ])?;
721        let data = image.assemble()?;
722        assert_eq!(data, b"Hello\xff\xff\xffWorld\xff\xff\xff");
723        Ok(())
724    }
725
726    #[test]
727    fn test_assemble_mirrored() -> Result<()> {
728        // Test image assembly with mirroring.
729        let mut image = ImageAssembler::with_params(20, true);
730        image.parse(&[
731            testdata("image/hello.txt").to_str().unwrap(),
732            testdata("image/world.txt").to_str().unwrap(),
733        ])?;
734        let data = image.assemble()?;
735        assert_eq!(data, b"HelloWorldHelloWorld");
736        Ok(())
737    }
738
739    #[test]
740    fn test_assemble_mirrored_offset_error() -> Result<()> {
741        // Test image assembly where one of the source files isn't read completely.
742        let mut image = ImageAssembler::with_params(16, true);
743        image.parse(&[
744            testdata("image/hello.txt@0").to_str().unwrap(),
745            testdata("image/world.txt@0x5").to_str().unwrap(),
746        ])?;
747        let err = image.assemble().unwrap_err();
748        assert_eq!(
749            err.to_string(),
750            "Incomplete read: expected to read 5 bytes but read 3 bytes"
751        );
752        Ok(())
753    }
754
755    #[test]
756    fn test_load_image() {
757        // Read and write back image.
758        let image = Image::read_from_file(&testdata("image/test_image.bin")).unwrap();
759        image
760            .write_to_file(&testdata("image/test_image_out.bin"))
761            .unwrap();
762
763        // Ensure the result is identical to the original.
764        let (mut orig_bytes, mut res_bytes) = (Vec::<u8>::new(), Vec::<u8>::new());
765        File::open(testdata("image/test_image.bin"))
766            .unwrap()
767            .read_to_end(&mut orig_bytes)
768            .unwrap();
769        File::open(testdata("image/test_image_out.bin"))
770            .unwrap()
771            .read_to_end(&mut res_bytes)
772            .unwrap();
773        assert_eq!(orig_bytes, res_bytes);
774    }
775}