1#![no_std]
2
3mod crc;
4mod error;
5mod ota_data;
6mod ota_data_structs;
7pub mod partitions;
8
9use crate::error::{OtaInternalError, OtaUpdateError};
10use crate::ota_data::{read_ota_data, write_ota_data};
11use crate::ota_data_structs::{EspOTAData, EspOTAState};
12use core::sync::atomic::Ordering;
13use embedded_io_async::Read;
14use embedded_storage::nor_flash::NorFlash;
15use esp_partition_table::{AppPartitionType, NorFlashOpError, PartitionEntry, PartitionType};
16use portable_atomic::AtomicBool;
17use crate::partitions::find_partition_by_type;
18
19const SECTOR_SIZE: usize = 0x1000;
21
22static IS_UPDATING: AtomicBool = AtomicBool::new(false);
23
24pub async fn ota_begin<S: NorFlash, R: Read>(
30 storage: &mut S,
31 mut binary: R,
32 mut progress_fn: impl FnMut(usize),
33) -> Result<(), OtaUpdateError<S, R::Error>> {
34 if IS_UPDATING.swap(true, Ordering::SeqCst) {
36 return Err(OtaUpdateError::AlreadyUpdating);
37 }
38
39 let ota_data = read_ota_data(storage)?;
41 if !ota_data.is_valid() {
42 return Err(OtaUpdateError::PendingVerify);
43 }
44
45 let booted_seq = ota_data.seq;
47 let new_seq = ota_data.seq + 1;
48 let new_part = ((new_seq - 1) % 2) as u8;
49 let ota_app =
50 find_partition_by_type(storage, PartitionType::App(AppPartitionType::Ota(new_part)))?;
51 log::info!("Starting OTA update. Current sequence is {booted_seq}, updating to sequence {new_seq} (partition {}).", ota_app.name());
52
53 storage
55 .erase(ota_app.offset, ota_app.offset + ota_app.size as u32)
56 .map_err(|e| OtaInternalError::NorFlashOpError(NorFlashOpError::StorageError(e)))?;
57
58 let mut data_written = 0;
60 loop {
61 let mut data_buffer = [0; SECTOR_SIZE];
62 let mut read_len = 0;
63
64 let mut is_done = false;
65 while read_len < SECTOR_SIZE {
66 let read = binary
67 .read(&mut data_buffer[read_len..])
68 .await
69 .map_err(|e| OtaUpdateError::ReadError(e))?;
70 if read == 0 {
71 is_done = true;
72 break;
73 }
74 read_len += read;
75 }
76
77 if data_written + read_len > ota_app.size {
78 return Err(OtaUpdateError::OutOfSpace);
79 }
80
81 storage
82 .write(
83 ota_app.offset + data_written as u32,
84 &data_buffer[0..read_len],
85 )
86 .map_err(|e| OtaInternalError::NorFlashOpError(NorFlashOpError::StorageError(e)))?;
87
88 data_written += read_len;
89 progress_fn(data_written);
90
91 if is_done {
92 break;
93 }
94 }
95
96 let data = EspOTAData::new(new_seq, [0xFF; 20]);
98 write_ota_data(storage, data)?;
99
100 Ok(())
101}
102
103pub fn ota_accept<S: NorFlash>(storage: &mut S) -> Result<(), OtaInternalError<S>> {
109 let mut ota_data = read_ota_data(storage)?;
110 match ota_data.state {
111 EspOTAState::PendingVerify => {
112 log::info!("Accepted pending OTA update");
113 ota_data.state = EspOTAState::Valid;
114 write_ota_data(storage, ota_data)?;
115 },
116 EspOTAState::New | EspOTAState::Undefined => {
117 log::warn!("Accepted OTA update from {:?} state", ota_data.state);
118 ota_data.state = EspOTAState::Valid;
119 write_ota_data(storage, ota_data)?;
120 },
121 EspOTAState::Invalid | EspOTAState::Aborted => {
122 log::warn!("Detected rollback that was not processed by bootloader, rolling back manually.");
123 ota_data.state = EspOTAState::Valid;
124 ota_data.seq -= 1;
125 write_ota_data(storage, ota_data)?;
126 }
127 EspOTAState::Valid => {},
128 }
129 Ok(())
130}
131
132pub fn ota_reject<S: NorFlash>(storage: &mut S) -> Result<(), OtaInternalError<S>> {
137 let mut ota_data = read_ota_data(storage)?;
138 match ota_data.state {
139 EspOTAState::PendingVerify => {
140 log::info!("Rejected pending OTA update");
141 ota_data.state = EspOTAState::Invalid;
142 write_ota_data(storage, ota_data)?;
143 }
144 EspOTAState::New | EspOTAState::Undefined => {
145 log::warn!("Rejected OTA update from {:?} state", ota_data.state);
146 ota_data.state = EspOTAState::Invalid;
147 write_ota_data(storage, ota_data)?;
148 }
149 EspOTAState::Valid => {
150 log::error!("Tried to reject OTA update that has already been accepted, ignoring request.");
151 }
152 EspOTAState::Invalid => {
153 log::warn!("Tried to reject OTA update that has already been rejected, ignoring request.");
154 }
155 EspOTAState::Aborted => {
156 log::warn!("Tried to reject OTA update from aborted state, ignoring request.");
157 }
158 }
159 Ok(())
160}
161
162pub fn ota_is_valid<S: NorFlash>(storage: &mut S) -> Result<bool, OtaInternalError<S>> {
169 Ok(read_ota_data(storage)?.is_valid())
170}
171
172pub fn get_booted_partition<S: NorFlash>(storage: &mut S) -> Result<PartitionEntry, OtaInternalError<S>> {
174 let ota_data = read_ota_data(storage)?;
175 let booted_seq = ota_data.seq;
176 let new_part = ((booted_seq - 1) % 2) as u8;
177 find_partition_by_type(storage, PartitionType::App(AppPartitionType::Ota(new_part)))
178}