extern crate alloc;
use alloc::vec::Vec;
use crate::block::Block;
use zerocopy::IntoBytes;
#[derive(Debug, Default)]
pub struct Uf2File {
blocks: Vec<Block>,
}
impl Uf2File {
pub fn new() -> Self {
Self { blocks: Vec::new() }
}
pub fn from_blocks(blocks: &[Block]) -> Self {
Self {
blocks: blocks.to_vec(),
}
}
pub fn len(&self) -> usize {
self.blocks.len()
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn blocks(&self) -> &[Block] {
&self.blocks
}
pub fn blocks_mut(&mut self) -> &mut [Block] {
&mut self.blocks
}
pub fn push_block(&mut self, block: Block) {
self.blocks.push(block);
}
pub fn concat(&mut self, other: &Self) {
self.blocks.extend(other.blocks.iter().cloned());
}
pub fn set_target_addresses(&mut self, start_addr: u32) {
let mut offset = start_addr as usize;
for block in &mut self.blocks {
block.target_addr = offset as u32;
offset += block.data().len();
}
}
pub fn get_payload(&self, family_id: Option<u32>) -> Option<Vec<u8>> {
let mut payload = Vec::new();
for block in &self.blocks {
if let Some(id) = family_id
&& block.board_family_id() != Some(id)
{
continue;
}
payload.extend_from_slice(block.data());
}
if payload.is_empty() {
None
} else {
Some(payload)
}
}
pub fn list_family_ids(&self) -> Vec<u32> {
let mut family_ids = Vec::new();
for block in &self.blocks {
if let Some(id) = block.board_family_id()
&& !family_ids.contains(&id)
{
family_ids.push(id);
}
}
family_ids
}
pub fn to_bytes(&self) -> Vec<u8> {
self.blocks
.iter()
.flat_map(|block| block.as_bytes().to_vec())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::{Block, Flags};
#[test]
fn test_new() {
let uf2_file = Uf2File::new();
assert_eq!(uf2_file.len(), 0);
assert!(uf2_file.is_empty());
assert_eq!(uf2_file.blocks().len(), 0);
}
#[test]
fn test_from_blocks() {
let block1 = Block::default();
let block2 = Block::default();
let blocks = vec![block1, block2];
let uf2_file = Uf2File::from_blocks(&blocks);
assert_eq!(uf2_file.len(), 2);
assert!(!uf2_file.is_empty());
assert_eq!(uf2_file.blocks().len(), 2);
}
#[test]
fn test_len_and_is_empty() {
let mut uf2_file = Uf2File::new();
assert_eq!(uf2_file.len(), 0);
assert!(uf2_file.is_empty());
uf2_file.push_block(Block::default());
assert_eq!(uf2_file.len(), 1);
assert!(!uf2_file.is_empty());
uf2_file.push_block(Block::default());
assert_eq!(uf2_file.len(), 2);
assert!(!uf2_file.is_empty());
}
#[test]
fn test_blocks_and_blocks_mut() {
let mut uf2_file = Uf2File::new();
assert_eq!(uf2_file.blocks().len(), 0);
let block1 = Block::new(0, 1, &[0xAA; 100], 0x08000000);
let block2 = Block::new(1, 2, &[0xBB; 100], 0x08000064);
uf2_file.push_block(block1);
uf2_file.push_block(block2);
let blocks = uf2_file.blocks();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].block, 0);
assert_eq!(blocks[1].block, 1);
let blocks_mut = uf2_file.blocks_mut();
assert_eq!(blocks_mut.len(), 2);
blocks_mut[0].block = 10;
blocks_mut[1].block = 11;
assert_eq!(uf2_file.blocks()[0].block, 10);
assert_eq!(uf2_file.blocks()[1].block, 11);
}
#[test]
fn test_push_block() {
let mut uf2_file = Uf2File::new();
assert_eq!(uf2_file.len(), 0);
let block1 = Block::new(0, 1, &[0xCC; 50], 0);
uf2_file.push_block(block1);
assert_eq!(uf2_file.len(), 1);
let block2 = Block::new(1, 2, &[0xDD; 50], 0);
uf2_file.push_block(block2);
assert_eq!(uf2_file.len(), 2);
assert_eq!(uf2_file.blocks()[0].block, 0);
assert_eq!(uf2_file.blocks()[1].block, 1);
}
#[test]
fn test_concat() {
let mut uf2_file1 = Uf2File::new();
uf2_file1.push_block(Block::new(0, 1, &[0xAA; 50], 0));
uf2_file1.push_block(Block::new(1, 2, &[0xBB; 50], 0));
let mut uf2_file2 = Uf2File::new();
uf2_file2.push_block(Block::new(0, 1, &[0xCC; 50], 0));
uf2_file1.concat(&uf2_file2);
assert_eq!(uf2_file1.len(), 3);
assert_eq!(uf2_file2.len(), 1);
assert_eq!(uf2_file1.blocks()[0].data()[0], 0xAA);
assert_eq!(uf2_file1.blocks()[1].data()[0], 0xBB);
assert_eq!(uf2_file1.blocks()[2].data()[0], 0xCC);
}
#[test]
fn test_set_target_addresses() {
let mut uf2_file = Uf2File::new();
uf2_file.push_block(Block::new(0, 2, &[0xAA; 100], 0));
uf2_file.push_block(Block::new(1, 2, &[0xBB; 50], 0));
uf2_file.set_target_addresses(0x08000000);
assert_eq!(uf2_file.blocks()[0].target_addr, 0x08000000);
assert_eq!(uf2_file.blocks()[1].target_addr, 0x08000000 + 100); }
#[test]
fn test_get_payload() {
let mut uf2_file = Uf2File::new();
let mut block1 = Block::new(0, 2, &[0xAA; 100], 0);
block1.flags |= Flags::FamilyId;
block1.board_family_id_or_file_size = 0x12345678;
uf2_file.push_block(block1);
let mut block2 = Block::new(1, 2, &[0xBB; 50], 0);
block2.flags |= Flags::FamilyId;
block2.board_family_id_or_file_size = 0x87654321;
uf2_file.push_block(block2);
let block3 = Block::new(2, 3, &[0xCC; 75], 0);
uf2_file.push_block(block3);
let payload1 = uf2_file.get_payload(Some(0x12345678)).unwrap();
assert_eq!(payload1.len(), 100);
assert_eq!(payload1[0], 0xAA);
let payload2 = uf2_file.get_payload(Some(0x87654321)).unwrap();
assert_eq!(payload2.len(), 50);
assert_eq!(payload2[0], 0xBB);
let payload_none = uf2_file.get_payload(None).unwrap();
assert_eq!(payload_none.len(), 225); }
#[test]
fn test_get_payload_empty() {
let uf2_file = Uf2File::new();
assert_eq!(uf2_file.get_payload(None), None);
assert_eq!(uf2_file.get_payload(Some(0x1234)), None);
}
#[test]
fn test_list_family_ids() {
let mut uf2_file = Uf2File::new();
let mut block1 = Block::new(0, 1, &[0xAA; 50], 0);
block1.flags |= Flags::FamilyId;
block1.board_family_id_or_file_size = 0x12345678;
uf2_file.push_block(block1);
let mut block2 = Block::new(1, 2, &[0xBB; 50], 0);
block2.flags |= Flags::FamilyId;
block2.board_family_id_or_file_size = 0x87654321;
uf2_file.push_block(block2);
uf2_file.push_block(Block::new(2, 3, &[0xCC; 50], 0));
let mut block4 = Block::new(3, 4, &[0xDD; 50], 0);
block4.flags |= Flags::FamilyId;
block4.board_family_id_or_file_size = 0x12345678; uf2_file.push_block(block4);
let family_ids = uf2_file.list_family_ids();
assert_eq!(family_ids.len(), 2); assert!(family_ids.contains(&0x12345678));
assert!(family_ids.contains(&0x87654321));
}
#[test]
fn test_list_family_ids_empty() {
let uf2_file = Uf2File::new();
let family_ids = uf2_file.list_family_ids();
assert!(family_ids.is_empty());
}
#[test]
fn test_to_bytes() {
let mut uf2_file = Uf2File::new();
uf2_file.push_block(Block::new(0, 2, &[0xAA; 100], 0));
uf2_file.push_block(Block::new(1, 2, &[0xBB; 50], 0));
let bytes = uf2_file.to_bytes();
assert_eq!(bytes.len(), 2 * 512);
let first_block = Block::from_bytes(&bytes[0..512]).unwrap();
assert_eq!(first_block.block, 0);
assert_eq!(first_block.data()[0], 0xAA);
}
#[test]
fn test_to_bytes_empty() {
let uf2_file = Uf2File::new();
let bytes = uf2_file.to_bytes();
assert!(bytes.is_empty());
}
#[test]
fn test_roundtrip() {
let mut original = Uf2File::new();
original.push_block(Block::new(0, 3, &[0xAA; 100], 0x08000000));
original.push_block(Block::new(1, 3, &[0xBB; 100], 0x08000064));
original.push_block(Block::new(2, 3, &[0xCC; 100], 0x080000C8));
let bytes = original.to_bytes();
let mut restored = Uf2File::new();
for chunk in bytes.chunks_exact(512) {
let block = Block::from_bytes(chunk).unwrap();
restored.push_block(block);
}
assert_eq!(original.len(), restored.len());
assert_eq!(original.blocks()[0].data(), restored.blocks()[0].data());
assert_eq!(original.blocks()[1].data(), restored.blocks()[1].data());
assert_eq!(original.blocks()[2].data(), restored.blocks()[2].data());
}
}