Skip to content

Commit

Permalink
Only limit App partitions to 16MB, allow others to be bigger. (#35)
Browse files Browse the repository at this point in the history
* Only limit App partitions to 16MB, allow others to be bigger.

The bootloader will crash when starting from an app partition which exceeds
16MB, but larger sizes are fine for data partitions.

Fixes #34

* Additional tests.

Test for error on app partition over 16MB.
Test for valid data partition over 16MB.
Test for error on multiple otadata partitions.
Test for error on otadata partition with invalid size.

* Refactor, check otadata partitions the same way as others are checked.

* Formatting fixes.

Should keep rustfmt happy.
  • Loading branch information
AVee authored Jun 19, 2024
1 parent 6d2d0c3 commit c51e30a
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 18 deletions.
39 changes: 21 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ impl PartitionTable {
pub fn validate(&self) -> Result<(), Error> {
use self::partition::{APP_PARTITION_ALIGNMENT, DATA_PARTITION_ALIGNMENT};

const MAX_PART_SIZE: u32 = 0x100_0000; // 16MB
const MAX_APP_PART_SIZE: u32 = 0x100_0000; // 16MB
const OTADATA_SIZE: u32 = 0x2000; // 8kB

// There must be at least one partition with type 'app'
Expand All @@ -302,6 +302,17 @@ impl PartitionTable {
return Err(Error::MultipleFactoryPartitions);
}

// There can be at most one partition of type 'data' and of subtype 'otadata'
if self
.partitions
.iter()
.filter(|p| p.ty() == Type::Data && p.subtype() == SubType::Data(DataType::Ota))
.count()
> 1
{
return Err(Error::MultipleOtadataPartitions);
}

for partition in &self.partitions {
// Partitions of type 'app' have to be placed at offsets aligned to 0x10000
// (64k)
Expand All @@ -315,11 +326,18 @@ impl PartitionTable {
return Err(Error::UnalignedPartition);
}

// Partitions cannot exceed 16MB; see:
// App partitions cannot exceed 16MB; see:
// https://github.com/espressif/esp-idf/blob/c212305/components/bootloader_support/src/esp_image_format.c#L158-L161
if partition.size() > MAX_PART_SIZE {
if partition.ty() == Type::App && partition.size() > MAX_APP_PART_SIZE {
return Err(Error::PartitionTooLarge(partition.name()));
}

if partition.ty() == Type::Data
&& partition.subtype() == SubType::Data(DataType::Ota)
&& partition.size() != OTADATA_SIZE
{
return Err(Error::InvalidOtadataPartitionSize);
}
}

for partition_a in &self.partitions {
Expand All @@ -344,21 +362,6 @@ impl PartitionTable {
}
}

// Check that otadata should be unique
let ota_duplicates = self
.partitions
.iter()
.filter(|p| p.ty() == Type::Data && p.subtype() == SubType::Data(DataType::Ota))
.collect::<Vec<_>>();

if ota_duplicates.len() > 1 {
return Err(Error::MultipleOtadataPartitions);
}

if ota_duplicates.len() == 1 && ota_duplicates[0].size() != OTADATA_SIZE {
return Err(Error::InvalidOtadataPartitionSize);
}

Ok(())
}
}
Expand Down
3 changes: 3 additions & 0 deletions tests/data/err_factory_too_large.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
factory, app, factory, 0x10000, 0x1000001,
5 changes: 5 additions & 0 deletions tests/data/err_multiple_otadata.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
factory, app, factory, 0x10000, 0x100000,
otadata1, data, ota, , 0x2000,
otadata2, data, ota, , 0x2000,
4 changes: 4 additions & 0 deletions tests/data/err_otadata_invalid_size.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
factory, app, factory, 0x10000, 0x100000,
otadata1, data, ota, , 0x3000,
6 changes: 6 additions & 0 deletions tests/data/large_data_partition.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, fat, 0x110000,29M,
50 changes: 50 additions & 0 deletions tests/test_partition_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ fn test_circuitpython_partition_tables() {
}
}

#[test]
fn test_large_data_partition() {
let csv = fs::read_to_string("tests/data/large_data_partition.csv").unwrap();
let table = PartitionTable::try_from(csv).unwrap();
let partitions = table.partitions();

assert_eq!(partitions.len(), 4);
assert_eq!(partitions[0].name(), "nvs");
assert_eq!(partitions[1].name(), "phy_init");
assert_eq!(partitions[2].name(), "factory");
assert_eq!(partitions[3].name(), "storage");
assert_eq!(partitions[3].size(), 29 * 1024 * 1024);
}

#[test]
fn test_error_when_no_app_partition() -> Result<(), String> {
let csv = fs::read_to_string("tests/data/err_no_app_partition.csv").unwrap();
Expand All @@ -133,6 +147,42 @@ fn test_error_when_multiple_factory_partitions() -> Result<(), String> {
}
}

#[test]
fn test_error_factory_partition_too_large() -> Result<(), String> {
let csv = fs::read_to_string("tests/data/err_factory_too_large.csv").unwrap();

match PartitionTable::try_from_str(csv) {
Err(Error::PartitionTooLarge(name)) if name == "factory" => Ok(()),
result => Err(format!(
"expected `Err(PartitionTooLarge(\"factory\"))`, found `{result:?}`"
)),
}
}

#[test]
fn test_error_when_multiple_otadata_partitions() -> Result<(), String> {
let csv = fs::read_to_string("tests/data/err_multiple_otadata.csv").unwrap();

match PartitionTable::try_from_str(csv) {
Err(Error::MultipleOtadataPartitions) => Ok(()),
result => Err(format!(
"expected `Err(Error::MultipleOtadataPartitions)`, found `{result:?}`"
)),
}
}

#[test]
fn test_error_when_otadata_size_invalid() -> Result<(), String> {
let csv = fs::read_to_string("tests/data/err_otadata_invalid_size.csv").unwrap();

match PartitionTable::try_from_str(csv) {
Err(Error::InvalidOtadataPartitionSize) => Ok(()),
result => Err(format!(
"expected `Err(Error::InvalidOtadataPartitionSize)`, found `{result:?}`"
)),
}
}

#[test]
fn test_error_when_unaligned_app_partition() -> Result<(), String> {
let csv = fs::read_to_string("tests/data/err_unaligned_app_partition.csv").unwrap();
Expand Down

0 comments on commit c51e30a

Please sign in to comment.