Skip to main content

pldm_fw/
pkg.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2/*
3 * PLDM firmware update utility: PLDM type 5 package parsing
4 *
5 * Copyright (c) 2023 Code Construct
6 */
7
8use nom::{
9    bytes::complete::take,
10    combinator::{all_consuming, map, map_res},
11    multi::{count, length_count},
12    number::complete::{le_u16, le_u32, le_u8},
13    sequence::tuple,
14    Finish, IResult,
15};
16use std::io::{BufReader, Read};
17use std::os::unix::fs::FileExt;
18use thiserror::Error;
19use uuid::{uuid, Uuid};
20
21const PKG_UUID_1_0_X: Uuid = uuid!("f018878c-cb7d-4943-9800-a02f059aca02");
22const PKG_UUID_1_1_X: Uuid = uuid!("1244d264-8d7d-4718-a030-fc8a56587d5a");
23
24use crate::{
25    parse_string, parse_string_adjacent, ComponentClassification, Descriptor,
26    DescriptorString, DeviceIdentifiers,
27};
28
29type VResult<I, O> = IResult<I, O>;
30
31#[derive(Error, Debug)]
32pub enum PldmPackageError {
33    #[error("IO error: {0}")]
34    Io(#[from] std::io::Error),
35    // TODO: would be nice to extract this directly from a nom ParseError,
36    // including Context...
37    #[error("PLDM package format error: {0}")]
38    Format(String),
39}
40
41impl PldmPackageError {
42    fn new_format(s: &str) -> Self {
43        Self::Format(s.into())
44    }
45}
46
47type Result<T> = std::result::Result<T, PldmPackageError>;
48
49#[derive(Debug)]
50pub struct ComponentBitmap {
51    n_bits: usize,
52    bits: Vec<u8>,
53}
54
55impl<'a> ComponentBitmap {
56    pub fn parse(
57        component_bits: u16,
58    ) -> impl FnMut(&'a [u8]) -> VResult<&'a [u8], Self> {
59        let bytes = (component_bits + 7) / 8;
60        map(take(bytes), move |b: &[u8]| ComponentBitmap {
61            n_bits: component_bits as usize,
62            bits: b.to_vec(),
63        })
64    }
65
66    pub fn bit(&self, i: usize) -> bool {
67        let idx = i / 8;
68        let offt = i % 8;
69        self.bits[idx] & (1 << offt) != 0
70    }
71
72    pub fn as_index_str(&self) -> String {
73        let mut s = String::new();
74        let mut first = true;
75        for i in 0usize..self.n_bits {
76            if self.bit(i) {
77                s.push_str(&format!("{}{}", if first { "" } else { ", " }, i));
78                first = false;
79            }
80        }
81        s
82    }
83
84    pub fn as_index_vec(&self) -> Vec<usize> {
85        let mut v = Vec::new();
86        for i in 0usize..self.n_bits {
87            if self.bit(i) {
88                v.push(i)
89            }
90        }
91        v
92    }
93}
94
95#[derive(Debug)]
96pub struct PackageDevice {
97    pub ids: DeviceIdentifiers,
98    pub option_flags: u32,
99    pub version: DescriptorString,
100    pub components: ComponentBitmap,
101}
102
103impl PackageDevice {
104    pub fn parse(buf: &[u8], component_bits: u16) -> VResult<&[u8], Self> {
105        let (
106            r,
107            (len, desc_count, flags, set_ver_type, set_ver_len, pkg_data_len),
108        ) = tuple((le_u16, le_u8, le_u32, le_u8, le_u8, le_u16))(buf)?;
109
110        // split the length bytes into r
111        let (rest, r) = take(len - 11)(r)?;
112
113        let (r, components) = ComponentBitmap::parse(component_bits)(r)?;
114        let (r, set_ver) = parse_string(set_ver_type, set_ver_len)(r)?;
115        let (r, ids) = count(Descriptor::parse, desc_count as usize)(r)?;
116        let (_, _pkg_data) = all_consuming(take(pkg_data_len))(r)?;
117
118        let pkgdev = PackageDevice {
119            ids: DeviceIdentifiers { ids },
120            option_flags: flags,
121            version: set_ver,
122            components,
123        };
124
125        Ok((rest, pkgdev))
126    }
127}
128
129#[derive(Debug)]
130pub struct PackageComponent {
131    pub classification: ComponentClassification,
132    pub identifier: u16,
133    pub comparison_stamp: u32,
134    pub options: u16,
135    pub activation_method: u16,
136    pub file_offset: usize,
137    pub file_size: usize,
138    pub version: DescriptorString,
139}
140
141impl PackageComponent {
142    pub fn parse(buf: &[u8]) -> VResult<&[u8], Self> {
143        let (
144            r,
145            (
146                classification,
147                identifier,
148                comparison_stamp,
149                options,
150                activation_method,
151                file_offset,
152                file_size,
153                version,
154            ),
155        ) = tuple((
156            le_u16,
157            le_u16,
158            le_u32,
159            le_u16,
160            le_u16,
161            le_u32,
162            le_u32,
163            parse_string_adjacent,
164        ))(buf)?;
165
166        let c = PackageComponent {
167            classification: classification.into(),
168            identifier,
169            comparison_stamp,
170            options,
171            activation_method,
172            file_offset: file_offset as usize,
173            file_size: file_size as usize,
174            version,
175        };
176        Ok((r, c))
177    }
178}
179
180#[derive(Debug)]
181pub struct Package {
182    pub identifier: Uuid,
183    pub version: DescriptorString,
184    pub devices: Vec<PackageDevice>,
185    pub components: Vec<PackageComponent>,
186    file: std::fs::File,
187}
188
189impl Package {
190    pub fn parse(file: std::fs::File) -> Result<Self> {
191        // just enough length to retrieve the header size field, after which
192        // we can parse the rest of the header.
193        const HDR_INIT_SIZE: usize = 16 + 1 + 2;
194
195        let mut reader = BufReader::new(&file);
196        let mut init = [0u8; HDR_INIT_SIZE];
197        reader.read_exact(&mut init)?;
198
199        let (_, (identifier, _hdr_format, hdr_size)) = all_consuming(tuple((
200            map_res(
201                take::<_, _, nom::error::Error<_>>(16usize),
202                Uuid::from_slice,
203            ),
204            le_u8,
205            le_u16,
206        )))(&init)
207        .map_err(|_| PldmPackageError::new_format("can't parse header"))?;
208
209        let mut hdr_usize = hdr_size as usize;
210        if hdr_usize < HDR_INIT_SIZE {
211            return Err(PldmPackageError::new_format("invalid header size"));
212        }
213
214        hdr_usize -= HDR_INIT_SIZE;
215
216        let mut buf = vec![0; hdr_usize];
217        reader.read_exact(&mut buf).map_err(|_| {
218            PldmPackageError::new_format(
219                "reported header size is larger than file",
220            )
221        })?;
222
223        let (r, (_release_date_time, component_bitmap_length, version)) =
224            tuple((take(13usize), le_u16, parse_string_adjacent))(&buf)
225                .finish()
226                .map_err(|_| {
227                    PldmPackageError::new_format("can't parse header")
228                })?;
229
230        let f = |d| PackageDevice::parse(d, component_bitmap_length);
231        let (r, devices) = length_count(le_u8, f)(r)
232            .finish()
233            .map_err(|_| PldmPackageError::new_format("can't parse devices"))?;
234
235        /* this is the first divegence in package format versions; the
236         * downstream device identification area is only present in 1.1.x
237         */
238        let r = match identifier {
239            PKG_UUID_1_0_X => r,
240            PKG_UUID_1_1_X => {
241                let (r, _downstream_devices) =
242                    length_count(le_u8, f)(r).finish().map_err(|_| {
243                        PldmPackageError::new_format(
244                            "can't parse downstream devices",
245                        )
246                    })?;
247                r
248            }
249            _ => {
250                return Err(PldmPackageError::new_format(&format!(
251                    "unknown package UUID {}",
252                    identifier
253                )))
254            }
255        };
256
257        let f = |d| PackageComponent::parse(d);
258        let (_, components) =
259            length_count(le_u16, f)(r).finish().map_err(|_| {
260                PldmPackageError::new_format("can't parse components")
261            })?;
262
263        let mut whole_header = Vec::new();
264        whole_header.extend_from_slice(&init);
265        whole_header.extend_from_slice(&buf);
266        let (cs_payload, checksum) =
267            whole_header.split_at(whole_header.len() - 4);
268        // safe unwrap, know init.len() > 4
269        let checksum = u32::from_le_bytes(checksum.try_into().unwrap());
270        let crc32 = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
271        let cs_calc = crc32.checksum(cs_payload);
272        if cs_calc != checksum {
273            return Err(PldmPackageError::new_format(
274                "Incorrect header checksum",
275            ));
276        }
277
278        Ok(Package {
279            identifier,
280            version,
281            devices,
282            components,
283            file,
284        })
285    }
286
287    pub fn new_virtual(
288        classification: ComponentClassification,
289        identifier: u16,
290        payload_file: std::fs::File,
291    ) -> Result<Self> {
292        let metadata = payload_file.metadata()?;
293        let payload_len = metadata
294            .len()
295            .try_into()
296            .map_err(|_| PldmPackageError::new_format("invalid file size?"))?;
297
298        let comp = PackageComponent {
299            classification,
300            identifier,
301            comparison_stamp: 0,
302            options: 0,
303            activation_method: 0,
304            file_offset: 0,
305            file_size: payload_len,
306            version: DescriptorString::String("0000".into()),
307        };
308        Ok(Self {
309            identifier: PKG_UUID_1_1_X,
310            version: DescriptorString::String("0000".into()),
311            components: vec![comp],
312            devices: vec![],
313            file: payload_file,
314        })
315    }
316
317    pub fn read_component(
318        &self,
319        component: &PackageComponent,
320        offset: u32,
321        buf: &mut [u8],
322    ) -> Result<usize> {
323        let file_offset = offset as u64 + component.file_offset as u64;
324        Ok(self.file.read_at(buf, file_offset)?)
325    }
326}