rustot/ota/encoding/
mod.rs

1#[cfg(feature = "ota_mqtt_data")]
2pub mod cbor;
3pub mod json;
4
5use core::ops::{Deref, DerefMut};
6use core::str::FromStr;
7use serde::{Serialize, Serializer};
8
9use crate::jobs::StatusDetailsOwned;
10
11use self::json::{JobStatusReason, OtaJob, Signature};
12
13use super::error::OtaError;
14use super::{config::Config, pal::Version};
15
16#[derive(Clone, PartialEq)]
17pub struct Bitmap(bitmaps::Bitmap<32>);
18
19impl Bitmap {
20    pub fn new(file_size: usize, block_size: usize, block_offset: u32) -> Self {
21        // Total number of blocks in file, rounded up
22        let total_num_blocks = (file_size + block_size - 1) / block_size;
23
24        Self(bitmaps::Bitmap::mask(core::cmp::min(
25            32 - 1,
26            total_num_blocks - block_offset as usize,
27        )))
28    }
29}
30
31impl Deref for Bitmap {
32    type Target = bitmaps::Bitmap<32>;
33
34    fn deref(&self) -> &Self::Target {
35        &self.0
36    }
37}
38
39impl DerefMut for Bitmap {
40    fn deref_mut(&mut self) -> &mut Self::Target {
41        &mut self.0
42    }
43}
44
45impl Serialize for Bitmap {
46    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
47    where
48        S: Serializer,
49    {
50        Serializer::serialize_bytes(serializer, &self.deref().into_value().to_le_bytes())
51    }
52}
53
54/// A `FileContext` denotes an active context of a single file. An ota job can
55/// contain multiple files, each with their own `FileContext` built from a
56/// corresponding `FileDescription`.
57#[derive(Clone)]
58pub struct FileContext {
59    pub filepath: heapless::String<64>,
60    pub filesize: usize,
61    pub fileid: u8,
62    pub certfile: heapless::String<64>,
63    pub update_data_url: Option<heapless::String<64>>,
64    pub auth_scheme: Option<heapless::String<64>>,
65    pub signature: Signature,
66    pub file_type: Option<u32>,
67
68    pub status_details: StatusDetailsOwned,
69    pub block_offset: u32,
70    pub blocks_remaining: usize,
71    pub request_block_remaining: u32,
72    pub job_name: heapless::String<64>,
73    pub stream_name: heapless::String<64>,
74    pub bitmap: Bitmap,
75}
76
77impl FileContext {
78    pub fn new_from(
79        job_name: &str,
80        ota_job: &OtaJob,
81        status_details: Option<StatusDetailsOwned>,
82        file_idx: usize,
83        config: &Config,
84        current_version: Version,
85    ) -> Result<Self, OtaError> {
86        let file_desc = ota_job
87            .files
88            .get(file_idx)
89            .ok_or(OtaError::InvalidFile)?
90            .clone();
91
92        // Initialize new `status_details' if not already present
93        let status = if let Some(details) = status_details {
94            details
95        } else {
96            let mut status = StatusDetailsOwned::new();
97            status
98                .insert(
99                    heapless::String::from("updated_by"),
100                    current_version.to_string(),
101                )
102                .map_err(|_| OtaError::Overflow)?;
103            status
104        };
105
106        let signature = file_desc.signature();
107
108        let block_offset = 0;
109        let bitmap = Bitmap::new(file_desc.filesize, config.block_size, block_offset);
110
111        Ok(FileContext {
112            filepath: heapless::String::from(file_desc.filepath),
113            filesize: file_desc.filesize,
114            fileid: file_desc.fileid,
115            certfile: heapless::String::from(file_desc.certfile),
116            update_data_url: file_desc.update_data_url.map(heapless::String::from),
117            auth_scheme: file_desc.auth_scheme.map(heapless::String::from),
118            signature,
119            file_type: file_desc.file_type,
120
121            status_details: status,
122
123            job_name: heapless::String::from(job_name),
124            block_offset,
125            request_block_remaining: bitmap.len() as u32,
126            blocks_remaining: (file_desc.filesize + config.block_size - 1) / config.block_size,
127            stream_name: heapless::String::from(ota_job.streamname),
128            bitmap,
129        })
130    }
131
132    pub fn self_test(&self) -> bool {
133        self.status_details
134            .get(&heapless::String::from("self_test"))
135            .and_then(|f| f.parse().ok())
136            .map(|reason: JobStatusReason| {
137                reason == JobStatusReason::SigCheckPassed
138                    || reason == JobStatusReason::SelfTestActive
139            })
140            .unwrap_or(false)
141    }
142
143    pub fn updated_by(&self) -> Option<Version> {
144        self.status_details
145            .get(&heapless::String::from("updated_by"))
146            .and_then(|s| Version::from_str(s.as_str()).ok())
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn bitmap_masking() {
156        let bitmap = Bitmap::new(255000, 256, 0);
157
158        let true_indices: Vec<usize> = bitmap.into_iter().collect();
159        assert_eq!((0..31).into_iter().collect::<Vec<usize>>(), true_indices);
160    }
161}