Skip to main content

mcumgr_toolkit/mcuboot/image/
mod.rs

1use std::io;
2
3/// The firmware version
4#[derive(Debug, Clone, Copy, Eq, PartialEq)]
5pub struct ImageVersion {
6    /// Major version
7    pub major: u8,
8    /// Minor version
9    pub minor: u8,
10    /// Revision
11    pub revision: u16,
12    /// Build number
13    pub build_num: u32,
14}
15impl std::fmt::Display for ImageVersion {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "{}.{}.{}", self.major, self.minor, self.revision)?;
18        if self.build_num != 0 {
19            write!(f, ".{}", self.build_num)?;
20        }
21        Ok(())
22    }
23}
24
25/// Information about an MCUboot firmware image
26#[derive(Debug, Clone, Copy, Eq, PartialEq)]
27pub struct ImageInfo {
28    /// Firmware version
29    pub version: ImageVersion,
30    /// The identifying hash for the firmware
31    ///
32    /// Note that this will not be the same as the SHA256 of the whole file, it is the field in the
33    /// MCUboot TLV section that contains a hash of the data which is used for signature
34    /// verification purposes.
35    pub hash: [u8; SHA256_LEN],
36}
37
38/// Possible error values of [`get_image_info`].
39#[derive(thiserror::Error, Debug, miette::Diagnostic)]
40pub enum ImageParseError {
41    /// The given image file is not an MCUboot image.
42    #[error("Image is not an MCUboot image")]
43    #[diagnostic(code(mcumgr_toolkit::mcuboot::image::unknown_type))]
44    UnknownImageType,
45    /// The given image file does not contain TLV entries.
46    #[error("Image does not contain TLV entries")]
47    #[diagnostic(code(mcumgr_toolkit::mcuboot::image::tlv_missing))]
48    TlvMissing,
49    /// The given image file does not contain an SHA256 id hash.
50    #[error("Image does not contain an SHA256 id hash")]
51    #[diagnostic(code(mcumgr_toolkit::mcuboot::image::id_hash_missing))]
52    IdHashMissing,
53    /// Failed to read from the image
54    #[error("Image read failed")]
55    #[diagnostic(code(mcumgr_toolkit::mcuboot::image::read))]
56    ReadFailed(#[from] std::io::Error),
57}
58
59fn read_u32(data: &mut dyn std::io::Read) -> Result<u32, std::io::Error> {
60    let mut bytes = [0u8; 4];
61    data.read_exact(&mut bytes)?;
62    Ok(u32::from_le_bytes(bytes))
63}
64
65fn read_u16(data: &mut dyn std::io::Read) -> Result<u16, std::io::Error> {
66    let mut bytes = [0u8; 2];
67    data.read_exact(&mut bytes)?;
68    Ok(u16::from_le_bytes(bytes))
69}
70
71fn read_u8(data: &mut dyn std::io::Read) -> Result<u8, std::io::Error> {
72    let mut byte = 0u8;
73    data.read_exact(std::slice::from_mut(&mut byte))?;
74    Ok(byte)
75}
76
77/// The identifying header of an MCUboot image
78const IMAGE_MAGIC: u32 = 0x96f3b83d;
79const IMAGE_TLV_INFO_MAGIC: u16 = 0x6907;
80const IMAGE_TLV_SHA256: u8 = 0x10;
81const SHA256_LEN: usize = 32;
82const TLV_INFO_HEADER_SIZE: u32 = 4;
83const TLV_ELEMENT_HEADER_SIZE: u32 = 4;
84
85/// Extract information from an MCUboot image file
86pub fn get_image_info(
87    mut image_data: impl io::Read + io::Seek,
88) -> Result<ImageInfo, ImageParseError> {
89    let image_data = &mut image_data;
90
91    let ih_magic = read_u32(image_data)?;
92    log::debug!("ih_magic: 0x{ih_magic:08x}");
93    if ih_magic != IMAGE_MAGIC {
94        return Err(ImageParseError::UnknownImageType);
95    }
96
97    let ih_load_addr = read_u32(image_data)?;
98    log::debug!("ih_load_addr: 0x{ih_load_addr:08x}");
99
100    let ih_hdr_size = read_u16(image_data)?;
101    log::debug!("ih_hdr_size: 0x{ih_hdr_size:04x}");
102
103    let ih_protect_tlv_size = read_u16(image_data)?;
104    log::debug!("ih_protect_tlv_size: 0x{ih_protect_tlv_size:04x}");
105
106    let ih_img_size = read_u32(image_data)?;
107    log::debug!("ih_img_size: 0x{ih_img_size:08x}");
108
109    let ih_flags = read_u32(image_data)?;
110    log::debug!("ih_flags: 0x{ih_flags:08x}");
111
112    let ih_ver = ImageVersion {
113        major: read_u8(image_data)?,
114        minor: read_u8(image_data)?,
115        revision: read_u16(image_data)?,
116        build_num: read_u32(image_data)?,
117    };
118    log::debug!("ih_ver: {ih_ver:?}");
119
120    image_data.seek(io::SeekFrom::Start(
121        u64::from(ih_hdr_size) + u64::from(ih_protect_tlv_size) + u64::from(ih_img_size),
122    ))?;
123
124    let it_magic = read_u16(image_data)?;
125    log::debug!("it_magic: 0x{it_magic:04x}");
126    if it_magic != IMAGE_TLV_INFO_MAGIC {
127        return Err(ImageParseError::TlvMissing);
128    }
129
130    let it_tlv_tot = read_u16(image_data)?;
131    log::debug!("it_tlv_tot: 0x{it_tlv_tot:04x}");
132
133    let mut id_hash = None;
134    {
135        let mut tlv_read: u32 = 0;
136        // Loop while at least one tlv header can still be read
137        while tlv_read + TLV_INFO_HEADER_SIZE + TLV_ELEMENT_HEADER_SIZE <= u32::from(it_tlv_tot) {
138            let it_type = read_u8(image_data)?;
139            read_u8(image_data)?;
140            let it_len = read_u16(image_data)?;
141
142            if it_type == IMAGE_TLV_SHA256 && usize::from(it_len) == SHA256_LEN {
143                let mut sha256_hash = [0u8; SHA256_LEN];
144                image_data.read_exact(&mut sha256_hash)?;
145                id_hash = Some(sha256_hash);
146            } else {
147                image_data.seek_relative(it_len.into())?;
148            }
149
150            log::debug!("- it_type: 0x{it_type:02x}, it_len: 0x{it_len:02x}");
151            tlv_read += u32::from(it_len) + 4;
152        }
153    }
154
155    if let Some(id_hash) = id_hash {
156        Ok(ImageInfo {
157            version: ih_ver,
158            hash: id_hash,
159        })
160    } else {
161        Err(ImageParseError::IdHashMissing)
162    }
163}