esp_idf_part/partition/
mod.rs

1use core::cmp::{max, min};
2
3use deku::DekuRead;
4use serde::{Deserialize, Serialize};
5use strum::{EnumIter, EnumString, FromRepr, IntoEnumIterator, VariantNames};
6
7pub(crate) use self::de::{DeserializedBinPartition, DeserializedCsvPartition};
8
9mod de;
10
11pub(crate) const APP_PARTITION_ALIGNMENT: u32 = 0x10000;
12pub(crate) const DATA_PARTITION_ALIGNMENT: u32 = 0x1000;
13pub(crate) const MAX_NAME_LEN: usize = 16;
14
15/// Supported partition types
16///
17/// User-defined partition types are allowed as long as their type ID does not
18/// confict with [`Type::App`] or [`Type::Data`]. Custom type IDs must not
19/// exceed `0xFE`.
20///
21/// For additional information regarding the supported partition types, please
22/// refer to the ESP-IDF documentation:
23/// <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#type-field>
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, DekuRead)]
25#[deku(endian = "little", id_type = "u8")]
26#[serde(rename_all = "lowercase")]
27pub enum Type {
28    #[deku(id = "0x00")]
29    App,
30    #[deku(id = "0x01")]
31    Data,
32    #[deku(id_pat = "0x02..=0xFE")]
33    Custom(u8),
34}
35
36impl core::fmt::Display for Type {
37    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
38        write!(
39            f,
40            "{}",
41            match self {
42                Type::App | Type::Data => serde_plain::to_string(self).unwrap(),
43                Type::Custom(ty) => serde_plain::to_string(&format_args!("{:#04x}", ty)).unwrap(),
44            }
45        )
46    }
47}
48
49impl From<u8> for Type {
50    fn from(ty: u8) -> Self {
51        match ty {
52            0x00 => Type::App,
53            0x01 => Type::Data,
54            ty => Type::Custom(ty),
55        }
56    }
57}
58
59impl From<Type> for u8 {
60    fn from(value: Type) -> Self {
61        match value {
62            Type::App => 0x00,
63            Type::Data => 0x01,
64            Type::Custom(ty) => ty,
65        }
66    }
67}
68
69impl Type {
70    /// Return a `String` stating which subtypes are allowed for the given type.
71    ///
72    /// This is useful for error handling in dependent packages.
73    pub fn subtype_hint(&self) -> String {
74        match self {
75            Type::App => "'factory', 'ota_0' through 'ota_15', or 'test'".into(),
76            Type::Data => {
77                let types = DataType::iter()
78                    .map(|dt| format!("'{}'", serde_plain::to_string(&dt).unwrap()))
79                    .collect::<Vec<_>>();
80
81                let (tail, head) = types.split_last().unwrap();
82
83                format!("{}, and {}", head.join(", "), tail)
84            }
85            Type::Custom(..) => "0x02 through 0xFE".into(),
86        }
87    }
88}
89
90/// Supported partition subtypes
91///
92/// User-defined partition subtypes are allowed as long as the partitions `Type`
93/// is [`Type::Custom`].
94///
95/// For additional information regarding the supported partition subtypes,
96/// please refer to the ESP-IDF documentation:
97/// <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#subtype>
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
99#[serde(untagged)]
100pub enum SubType {
101    App(AppType),
102    Data(DataType),
103    Custom(u8),
104}
105
106impl core::fmt::Display for SubType {
107    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
108        write!(
109            f,
110            "{}",
111            match self {
112                SubType::App(ty) => serde_plain::to_string(ty).unwrap(),
113                SubType::Data(ty) => serde_plain::to_string(ty).unwrap(),
114                SubType::Custom(ty) =>
115                    serde_plain::to_string(&format_args!("{:#04x}", ty)).unwrap(),
116            }
117        )
118    }
119}
120
121impl From<AppType> for SubType {
122    fn from(ty: AppType) -> Self {
123        SubType::App(ty)
124    }
125}
126
127impl From<DataType> for SubType {
128    fn from(ty: DataType) -> Self {
129        SubType::Data(ty)
130    }
131}
132
133impl From<u8> for SubType {
134    fn from(ty: u8) -> Self {
135        SubType::Custom(ty)
136    }
137}
138
139impl From<SubType> for u8 {
140    fn from(value: SubType) -> Self {
141        match value {
142            SubType::App(ty) => ty as u8,
143            SubType::Data(ty) => ty as u8,
144            SubType::Custom(ty) => ty,
145        }
146    }
147}
148
149impl SubType {
150    /// Create a [SubType::App] variant from an integer value
151    pub fn app(value: u8) -> Self {
152        Self::App(AppType::from_repr(value as usize).unwrap())
153    }
154
155    /// Create a [SubType::Data] variant from an integer value
156    pub fn data(value: u8) -> Self {
157        Self::Data(DataType::from_repr(value as usize).unwrap())
158    }
159}
160
161/// Partition sub-types which can be used with [`Type::App`] partitions
162///
163/// A full list of support subtypes can be found in the ESP-IDF documentation:
164/// <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#subtype>
165#[allow(non_camel_case_types)]
166#[derive(
167    Debug,
168    Clone,
169    Copy,
170    PartialEq,
171    Eq,
172    Deserialize,
173    EnumIter,
174    EnumString,
175    VariantNames,
176    FromRepr,
177    Serialize,
178    DekuRead,
179)]
180#[deku(endian = "little", id_type = "u8")]
181#[serde(rename_all = "snake_case")]
182#[strum(serialize_all = "snake_case")]
183pub enum AppType {
184    Factory = 0x00,
185    Ota_0   = 0x10,
186    Ota_1   = 0x11,
187    Ota_2   = 0x12,
188    Ota_3   = 0x13,
189    Ota_4   = 0x14,
190    Ota_5   = 0x15,
191    Ota_6   = 0x16,
192    Ota_7   = 0x17,
193    Ota_8   = 0x18,
194    Ota_9   = 0x19,
195    Ota_10  = 0x1A,
196    Ota_11  = 0x1B,
197    Ota_12  = 0x1C,
198    Ota_13  = 0x1D,
199    Ota_14  = 0x1E,
200    Ota_15  = 0x1F,
201    Test    = 0x20,
202}
203
204/// Partition sub-types which can be used with [`Type::Data`] partitions
205///
206/// A full list of support subtypes can be found in the ESP-IDF documentation:
207/// <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#subtype>
208#[derive(
209    Debug,
210    Clone,
211    Copy,
212    PartialEq,
213    Eq,
214    Deserialize,
215    EnumIter,
216    EnumString,
217    VariantNames,
218    FromRepr,
219    Serialize,
220    DekuRead,
221)]
222#[deku(endian = "little", id_type = "u8")]
223#[serde(rename_all = "snake_case")]
224#[strum(serialize_all = "snake_case")]
225pub enum DataType {
226    Ota       = 0x00,
227    Phy       = 0x01,
228    Nvs       = 0x02,
229    Coredump  = 0x03,
230    NvsKeys   = 0x04,
231    #[serde(rename = "efuse")]
232    #[strum(serialize = "efuse")]
233    EfuseEm   = 0x05,
234    Undefined = 0x06,
235    Esphttpd  = 0x80,
236    Fat       = 0x81,
237    Spiffs    = 0x82,
238    Littlefs  = 0x83,
239}
240
241bitflags::bitflags! {
242    /// Supported partition flags
243    ///
244    /// Two flags are currently supported, `encrypted` and `readonly`:
245    ///
246    /// - If `encrypted` flag is set, the partition will be encrypted if [Flash
247    ///   Encryption] is enabled.
248    ///     - Note: `app` type partitions will always be encrypted, regardless of
249    ///       whether this flag is set or not.
250    /// - If `readonly` flag is set, the partition will be read-only. This flag is
251    ///   only supported for `data` type partitions except `ota` and `coredump`
252    ///   subtypes. This flag can help to protect against accidental writes to a
253    ///   partition that contains critical device-specific configuration data, e.g.
254    ///   factory data partition.
255    ///
256    /// You can specify multiple flags by separating them with a colon. For example,
257    /// `encrypted:readonly`.
258    ///
259    /// For more information, see the ESP-IDF documentation:
260    /// <https://docs.espressif.com/projects/esp-idf/en/v5.3.1/esp32/api-guides/partition-tables.html#flags>
261    ///
262    /// [Flash Encryption]: https://docs.espressif.com/projects/esp-idf/en/v5.3.1/esp32/security/flash-encryption.html
263    #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
264    pub struct Flags: u32 {
265        /// Encrypted partition
266        const ENCRYPTED = 0b0001;
267        /// Read-only partition
268        const READONLY  = 0b0010;
269    }
270}
271
272/// A single partition definition
273#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
274pub struct Partition {
275    name: String,
276    ty: Type,
277    subtype: SubType,
278    offset: u32,
279    size: u32,
280    flags: Flags,
281}
282
283impl Partition {
284    /// Construct a new partition
285    pub fn new<S>(name: S, ty: Type, subtype: SubType, offset: u32, size: u32, flags: Flags) -> Self
286    where
287        S: Into<String>,
288    {
289        Self {
290            name: name.into(),
291            ty,
292            subtype,
293            offset,
294            size,
295            flags,
296        }
297    }
298
299    /// Return the partition's name
300    pub fn name(&self) -> String {
301        self.name.clone()
302    }
303
304    /// Return the partition's [Type]
305    pub fn ty(&self) -> Type {
306        self.ty
307    }
308
309    /// Return the partition's [SubType]
310    pub fn subtype(&self) -> SubType {
311        self.subtype
312    }
313
314    /// Return the partition's offset
315    pub fn offset(&self) -> u32 {
316        self.offset
317    }
318
319    /// Return the partition's size
320    pub fn size(&self) -> u32 {
321        self.size
322    }
323
324    /// Return the partition's flags
325    pub fn flags(&self) -> Flags {
326        self.flags
327    }
328
329    /// Does this partition overlap with another?
330    pub fn overlaps(&self, other: &Partition) -> bool {
331        max(self.offset, other.offset) < min(self.offset + self.size, other.offset + other.size)
332    }
333
334    /// Write a record to the provided binary writer
335    pub fn write_bin<W>(&self, writer: &mut W) -> std::io::Result<()>
336    where
337        W: std::io::Write,
338    {
339        const MAGIC_BYTES: [u8; 2] = [0xAA, 0x50];
340
341        writer.write_all(&MAGIC_BYTES)?;
342        writer.write_all(&[self.ty.into(), self.subtype.into()])?;
343        writer.write_all(&self.offset.to_le_bytes())?;
344        writer.write_all(&self.size.to_le_bytes())?;
345
346        let mut name_bytes = [0u8; 16];
347        for (source, dest) in self.name.bytes().zip(name_bytes.iter_mut()) {
348            *dest = source;
349        }
350        writer.write_all(&name_bytes)?;
351
352        writer.write_all(&self.flags.bits().to_le_bytes())?;
353
354        Ok(())
355    }
356
357    /// Write a record to the provided [`csv::Writer`]
358    pub fn write_csv<W>(&self, csv: &mut csv::Writer<W>) -> std::io::Result<()>
359    where
360        W: std::io::Write,
361    {
362        let mut flags = Vec::<&str>::new();
363        if self.flags.contains(Flags::ENCRYPTED) {
364            flags.push("encrypted");
365        }
366        if self.flags.contains(Flags::READONLY) {
367            flags.push("readonly");
368        }
369
370        let flags = flags.join(":");
371
372        csv.write_record(&[
373            self.name(),
374            self.ty.to_string(),
375            self.subtype.to_string(),
376            format!("{:#x}", self.offset),
377            format!("{:#x}", self.size),
378            flags,
379        ])?;
380
381        Ok(())
382    }
383}