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, ManifestExtEntrySpec};
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 signed extensions in `spec` 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: &[ManifestExtEntrySpec]) -> Result<()> {
299        spec.iter()
300            .filter(|entry_spec| entry_spec.is_signed())
301            .try_for_each(|entry_spec| {
302                self.add_manifest_extension(ManifestExtEntry::from_spec(entry_spec)?)
303            })
304    }
305
306    /// Adds an extension to the unsigned region of this `Image`.
307    ///
308    /// This will take all the unsigned extensions in `spec` and append them to the image.
309    /// This should only be called once all signed extensions have been added.
310    pub fn add_unsigned_manifest_extensions(
311        &mut self,
312        spec: &[ManifestExtEntrySpec],
313    ) -> Result<()> {
314        spec.iter()
315            .filter(|entry_spec| !entry_spec.is_signed())
316            .try_for_each(|entry_spec| {
317                self.add_manifest_extension(ManifestExtEntry::from_spec(entry_spec)?)
318            })
319    }
320
321    /// Adds an extension to the end of this `Image`.
322    pub fn add_manifest_extension(&mut self, entry: ManifestExtEntry) -> Result<()> {
323        let manifest = self.borrow_manifest()?;
324
325        // A copy of the extension table since we can't borrow as mutable.
326        let mut ext_table = manifest.extensions.entries;
327
328        let entry_id = entry.header().identifier;
329
330        // Update the offset in the extension table.
331        let ext_table_entry = ext_table
332            .iter_mut()
333            .find(|e| e.identifier == entry_id)
334            .ok_or(ImageError::NoExtensionTableEntry(entry_id))?;
335
336        // If the extension already exists, overwrite it, else append it to the end of the
337        // image.
338        let offset = if ext_table_entry.offset != 0 {
339            ext_table_entry.offset
340        } else {
341            ensure!(
342                self.size.is_multiple_of(align_of::<u32>()),
343                ImageError::BadExtensionAlignment(entry_id)
344            );
345            self.size.try_into()?
346        };
347        ext_table_entry.offset = offset;
348
349        // Write the extension to the end of the image.
350        let ext_bytes = entry.to_vec();
351        let end_index = offset
352            .checked_add(ext_bytes.len().try_into()?)
353            .ok_or(ImageError::ExtensionOverflow)?;
354        let extension_slice = self
355            .data
356            .bytes
357            .get_mut(offset as usize..end_index as usize)
358            .ok_or(ImageError::ExtensionOverflow)?;
359        extension_slice.copy_from_slice(ext_bytes.as_slice());
360        self.size = std::cmp::max(end_index as usize, self.size);
361
362        let manifest = self.borrow_manifest_mut()?;
363        manifest.extensions.entries = ext_table;
364
365        Ok(())
366    }
367
368    /// Allocates space for a manifest extension and sets the offset in the extension table.
369    ///
370    /// This function is similar to `add_manifest_extension`, but doesn't populate any data for the
371    /// extension. This is necessary to properly set the `length` field of the manifest and the
372    /// `offset` field of the manifest extension entry for signing.
373    pub fn allocate_manifest_extension(&mut self, id: u32, len: usize) -> Result<()> {
374        let offset = self.size as u32;
375        self.borrow_manifest_mut()?
376            .extensions
377            .entries
378            .iter_mut()
379            .find(|e| e.identifier == id)
380            .ok_or(ImageError::NoExtensionTableEntry(id))?
381            .offset = offset;
382
383        self.size = self
384            .size
385            .checked_add(len)
386            .ok_or(ImageError::ExtensionOverflow)?;
387
388        Ok(())
389    }
390
391    /// Clears any entry in the manifest extension table that doesn't have an offset.
392    ///
393    /// This allows a manifest definition with a populated extension table to be used even when
394    /// extensions aren't provided.
395    pub fn drop_null_extensions(&mut self) -> Result<()> {
396        let manifest = self.borrow_manifest_mut()?;
397
398        manifest.extensions.entries.iter_mut().for_each(|e| {
399            if e.offset == 0 {
400                e.identifier = 0;
401            }
402        });
403
404        Ok(())
405    }
406
407    /// Updates the signature field in the `Manifest` with the provided RSA signature.
408    pub fn update_rsa_signature(&mut self, signature: RsaSignature) -> Result<()> {
409        let manifest = self.borrow_manifest_mut()?;
410
411        // Convert to a `ManifestSpec` so we can supply the signature as a `BigInt`.
412        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
413        manifest_def.update_signature(ManifestSigverifyBuffer::from_le_bytes(
414            signature.to_le_bytes(),
415        )?);
416        *manifest = manifest_def.try_into()?;
417        Ok(())
418    }
419
420    /// Updates the signature field in the `Manifest` with the provided ECDSA signature.
421    pub fn update_ecdsa_signature(&mut self, signature: EcdsaRawSignature) -> Result<()> {
422        let manifest = self.borrow_manifest_mut()?;
423
424        // TODO(moidx): Remove check once we have migrated away from RSA keys
425        // and start using a key type field in the manifest.
426        //
427        // Note(cfrantz): I have disabled this error return because we want to test
428        // manifests with bad version numbers.  I've replaced the error return with
429        // a log message.  As stated by moidx@, we'll remove this once we've
430        // completely migrated away from RSA keys.
431        // ensure!(
432        //     manifest.manifest_version.major == CHIP_MANIFEST_VERSION_MAJOR2,
433        //     ImageError::InvalidManifestVersionforEcdsa(
434        //         manifest.manifest_version.major,
435        //         CHIP_MANIFEST_VERSION_MAJOR2
436        //     )
437        // );
438        if manifest.manifest_version.major != CHIP_MANIFEST_VERSION_MAJOR2 {
439            log::error!(
440                "Invalid manifest version for ECDSA: {:?}",
441                manifest.manifest_version
442            );
443        }
444
445        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
446
447        // Convert the signature to a byte array and pad it to 3072 bits.
448        let signature_bytes = signature
449            .r
450            .iter()
451            .chain(signature.s.iter())
452            .copied()
453            .collect::<Vec<u8>>();
454        let sig_padding = vec![0xa5u8; 384 - 64];
455        let signature_bytes = signature_bytes
456            .iter()
457            .chain(sig_padding.iter())
458            .copied()
459            .collect::<Vec<u8>>();
460
461        manifest_def.update_signature(ManifestSigverifyBuffer::from_le_bytes(signature_bytes)?);
462        *manifest = manifest_def.try_into()?;
463        Ok(())
464    }
465
466    /// Updates the pub_key field in the `Manifest` with the RSA Modulus.
467    pub fn update_modulus(&mut self, rsa_modulus: Modulus) -> Result<()> {
468        let manifest = self.borrow_manifest_mut()?;
469
470        // Convert to a `ManifestSpec` so we can supply the rsa_modulus as a `BigInt`.
471        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
472        manifest_def.update_pub_key(ManifestSigverifyBuffer::from_le_bytes(
473            rsa_modulus.to_le_bytes(),
474        )?);
475        *manifest = manifest_def.try_into()?;
476        Ok(())
477    }
478
479    /// Updates the pub_key field in the `Manifest` with the ECDSA public key.
480    pub fn update_ecdsa_public_key(&mut self, ecdsa_public_key: EcdsaRawPublicKey) -> Result<()> {
481        let manifest = self.borrow_manifest_mut()?;
482        let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
483
484        // Convert the public key to a byte array and pad it to 3072 bits.
485        let key_bytes = ecdsa_public_key
486            .x
487            .iter()
488            .chain(ecdsa_public_key.y.iter())
489            .copied()
490            .collect::<Vec<u8>>();
491        let key_padding = vec![0xa5u8; 384 - 64];
492        let key_bytes = key_bytes
493            .iter()
494            .chain(key_padding.iter())
495            .copied()
496            .collect::<Vec<u8>>();
497
498        manifest_def.update_pub_key(ManifestSigverifyBuffer::from_le_bytes(key_bytes)?);
499        *manifest = manifest_def.try_into()?;
500
501        // Update the manifest major version to be able to detect the signing
502        // key type.
503        // TODO(moidx): Replace this with a key type field in the manifest once
504        // support for RSA keys is removed.
505        if manifest.manifest_version.major == CHIP_MANIFEST_VERSION_MAJOR1 {
506            manifest.manifest_version.major = CHIP_MANIFEST_VERSION_MAJOR2;
507        }
508        Ok(())
509    }
510
511    pub fn subimages(&self) -> Result<Vec<SubImage<'_>>> {
512        let mut result = Vec::new();
513        let mut offset = 0;
514        while offset < self.size {
515            let m = &self.data.bytes[offset..offset + size_of::<Manifest>()];
516            let manifest = Manifest::ref_from_bytes(m).map_err(|_| ImageError::Parse)?;
517            let kind = ManifestKind(manifest.identifier);
518            let mut size = 1;
519            if kind.is_known_value() {
520                size = manifest.length as usize;
521                result.push(SubImage {
522                    kind,
523                    offset,
524                    manifest,
525                    data: &self.data.bytes[offset..offset + size],
526                });
527            }
528            // Round up to next 64K offset.
529            offset += (size + 65535) & !65535;
530        }
531        Ok(result)
532    }
533
534    pub fn bytes(&self) -> &[u8] {
535        &self.data.bytes[..self.size]
536    }
537
538    pub fn borrow_manifest(&self) -> Result<&Manifest> {
539        let manifest_slice = &self.data.bytes[0..size_of::<Manifest>()];
540        let manifest = Manifest::ref_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
541        Ok(manifest)
542    }
543
544    pub fn borrow_manifest_mut(&mut self) -> Result<&mut Manifest> {
545        let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
546        let manifest = Manifest::mut_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
547        Ok(manifest)
548    }
549
550    /// Updates the length field in the `Manifest`.
551    pub fn update_length(&mut self) -> Result<usize> {
552        self.borrow_manifest_mut()?.length = self.size as u32;
553        Ok(self.size)
554    }
555
556    /// Sets the `signed_region_end` field to the end of the last signed extension.
557    ///
558    /// The end of the signed region is computed as the offset of the first extension that is not
559    /// in `signed_ids` or the size of the image if there is no such extension.
560    pub fn update_signed_region(&mut self, signed_ids: &HashSet<u32>) -> Result<()> {
561        let image_size = self.size as u32;
562        let mut first_unsigned_ext = 0u32;
563        let manifest = self.borrow_manifest_mut()?;
564
565        let mut ext_table = manifest.extensions.entries;
566        ext_table.sort_by(|a, b| a.offset.cmp(&b.offset));
567        for e in ext_table {
568            // Ignore any extensions that haven't been added to the image.
569            if e.offset == 0 {
570                continue;
571            }
572            if signed_ids.contains(&e.identifier) {
573                // Since extensions are sorted by offset, if we've already seen an unsigned
574                // extension then the signed region is discontinuous.
575                ensure!(
576                    first_unsigned_ext == 0,
577                    ImageError::MisplacedSignedExtension(e.identifier)
578                );
579            } else if first_unsigned_ext == 0 {
580                first_unsigned_ext = e.offset;
581            }
582        }
583
584        manifest.signed_region_end = if first_unsigned_ext == 0 {
585            image_size
586        } else {
587            first_unsigned_ext
588        };
589
590        Ok(())
591    }
592
593    /// Operates on the signed region of the image.
594    pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
595    where
596        F: FnOnce(&[u8]) -> R,
597    {
598        Ok(f(&self.data.bytes[offset_of!(Manifest, usage_constraints)
599            ..self.borrow_manifest()?.signed_region_end as usize]))
600    }
601
602    /// Compute the SHA256 digest for the signed portion of the `Image`.
603    pub fn compute_digest(&self) -> Result<Sha256Digest> {
604        self.map_signed_region(|v| Sha256Digest::hash(v))
605    }
606}
607
608impl SubImage<'_> {
609    /// Operates on the signed region of the image.
610    pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
611    where
612        F: FnOnce(&[u8]) -> R,
613    {
614        Ok(f(&self.data[offset_of!(Manifest, usage_constraints)
615            ..self.manifest.signed_region_end as usize]))
616    }
617
618    /// Compute the SHA256 digest for the signed portion of the `Image`.
619    pub fn compute_digest(&self) -> Result<Sha256Digest> {
620        self.map_signed_region(|v| Sha256Digest::hash(v))
621    }
622}
623
624impl ImageAssembler {
625    /// Creates an `ImageAssembler` with a given `size` and mirroring parameters.
626    pub fn with_params(size: usize, mirrored: bool) -> Self {
627        ImageAssembler {
628            size,
629            mirrored,
630            ..Default::default()
631        }
632    }
633
634    /// Creates an `ImageAssembler` with default parameters for OpenTitan: a 1MiB image which is mirrored.
635    pub fn new() -> Self {
636        Self::with_params(0x100000, true)
637    }
638
639    /// Parse a list of strings into chunks to be assembled.
640    /// Each string may be a filename or a filename@offset describing where in the assembled image the contents of the file should appear.
641    /// The offset is an integer expressed in any of the bases accepted by [`ParseInt`].
642    pub fn parse(&mut self, chunks: &[impl AsRef<str>]) -> Result<()> {
643        for chunk in chunks {
644            if let Some((file, offset)) = chunk.as_ref().split_once('@') {
645                self.chunks.push(ImageChunk::Offset(
646                    PathBuf::from(file),
647                    usize::from_str(offset)?,
648                ));
649            } else {
650                self.chunks
651                    .push(ImageChunk::Concat(PathBuf::from(chunk.as_ref())));
652            }
653        }
654        Ok(())
655    }
656
657    // Read a file into a buffer.  Ensure the entire file is read.
658    fn read(path: &Path, buf: &mut [u8]) -> Result<usize> {
659        let mut file = File::open(path)?;
660        let len = file.metadata()?.len() as usize;
661        let n = file.read(buf)?;
662        ensure!(len == n, ImageError::IncompleteRead(len, n));
663        Ok(n)
664    }
665
666    /// Assemble the image according to the parameters and parsed chunk specifications.
667    pub fn assemble(&self) -> Result<Vec<u8>> {
668        let size = if self.mirrored {
669            self.size / 2
670        } else {
671            self.size
672        };
673        let mut image = vec![0xff; size];
674        let mut pos = 0;
675        for chunk in &self.chunks {
676            match chunk {
677                ImageChunk::Concat(path) => {
678                    let n = Self::read(path, &mut image[pos..])?;
679                    pos += n;
680                }
681                ImageChunk::Offset(path, offset) => {
682                    let n = Self::read(path, &mut image[*offset..])?;
683                    pos = offset + n;
684                }
685            }
686        }
687        if self.mirrored {
688            image.extend_from_within(..size);
689        }
690        Ok(image)
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697    use crate::util::testdata;
698
699    #[test]
700    fn test_assemble_concat() -> Result<()> {
701        // Test image assembly by concatenation.
702        let mut image = ImageAssembler::with_params(16, false);
703        image.parse(&[
704            testdata("image/hello.txt").to_str().unwrap(),
705            testdata("image/world.txt").to_str().unwrap(),
706        ])?;
707        let data = image.assemble()?;
708        assert_eq!(data, b"HelloWorld\xff\xff\xff\xff\xff\xff");
709        Ok(())
710    }
711
712    #[test]
713    fn test_assemble_offset() -> Result<()> {
714        // Test image assembly by explicit offsets.
715        let mut image = ImageAssembler::with_params(16, false);
716        image.parse(&[
717            testdata("image/hello.txt@0").to_str().unwrap(),
718            testdata("image/world.txt@0x8").to_str().unwrap(),
719        ])?;
720        let data = image.assemble()?;
721        assert_eq!(data, b"Hello\xff\xff\xffWorld\xff\xff\xff");
722        Ok(())
723    }
724
725    #[test]
726    fn test_assemble_mirrored() -> Result<()> {
727        // Test image assembly with mirroring.
728        let mut image = ImageAssembler::with_params(20, true);
729        image.parse(&[
730            testdata("image/hello.txt").to_str().unwrap(),
731            testdata("image/world.txt").to_str().unwrap(),
732        ])?;
733        let data = image.assemble()?;
734        assert_eq!(data, b"HelloWorldHelloWorld");
735        Ok(())
736    }
737
738    #[test]
739    fn test_assemble_mirrored_offset_error() -> Result<()> {
740        // Test image assembly where one of the source files isn't read completely.
741        let mut image = ImageAssembler::with_params(16, true);
742        image.parse(&[
743            testdata("image/hello.txt@0").to_str().unwrap(),
744            testdata("image/world.txt@0x5").to_str().unwrap(),
745        ])?;
746        let err = image.assemble().unwrap_err();
747        assert_eq!(
748            err.to_string(),
749            "Incomplete read: expected to read 5 bytes but read 3 bytes"
750        );
751        Ok(())
752    }
753
754    #[test]
755    fn test_load_image() {
756        // Read and write back image.
757        let image = Image::read_from_file(&testdata("image/test_image.bin")).unwrap();
758        image
759            .write_to_file(&testdata("image/test_image_out.bin"))
760            .unwrap();
761
762        // Ensure the result is identical to the original.
763        let (mut orig_bytes, mut res_bytes) = (Vec::<u8>::new(), Vec::<u8>::new());
764        File::open(testdata("image/test_image.bin"))
765            .unwrap()
766            .read_to_end(&mut orig_bytes)
767            .unwrap();
768        File::open(testdata("image/test_image_out.bin"))
769            .unwrap()
770            .read_to_end(&mut res_bytes)
771            .unwrap();
772        assert_eq!(orig_bytes, res_bytes);
773    }
774}