1use std::fmt::Display;
2
3use miette::Diagnostic;
4use thiserror::Error;
5
6use crate::{MCUmgrClient, bootloader::BootloaderType, client::MCUmgrClientError, mcuboot};
7
8#[derive(Error, Debug, Diagnostic)]
10pub enum FirmwareUpdateError {
11 #[error("Progress callback returned an error")]
13 #[diagnostic(code(mcumgr_toolkit::firmware_update::progress_cb_error))]
14 ProgressCallbackError,
15 #[error("Failed to detect bootloader")]
17 #[diagnostic(code(mcumgr_toolkit::firmware_update::detect_bootloader))]
18 #[diagnostic(help("try to specify the bootloader type manually"))]
19 BootloaderDetectionFailed(#[source] MCUmgrClientError),
20 #[error("Bootloader '{0}' not supported")]
22 #[diagnostic(code(mcumgr_toolkit::firmware_update::unknown_bootloader))]
23 BootloaderNotSupported(String),
24 #[error("Firmware is not a valid MCUboot image")]
26 #[diagnostic(code(mcumgr_toolkit::firmware_update::mcuboot_image))]
27 InvalidMcuBootFirmwareImage(#[from] mcuboot::ImageParseError),
28 #[error("Failed to fetch image state from device")]
30 #[diagnostic(code(mcumgr_toolkit::firmware_update::get_image_state))]
31 GetStateFailed(#[source] MCUmgrClientError),
32 #[error("Failed to upload firmware image to device")]
34 #[diagnostic(code(mcumgr_toolkit::firmware_update::image_upload))]
35 ImageUploadFailed(#[source] MCUmgrClientError),
36 #[error("Failed to activate new firmware image")]
38 #[diagnostic(code(mcumgr_toolkit::firmware_update::set_image_state))]
39 SetStateFailed(#[source] MCUmgrClientError),
40 #[error("Failed to trigger device reboot")]
42 #[diagnostic(code(mcumgr_toolkit::firmware_update::reboot))]
43 RebootFailed(#[source] MCUmgrClientError),
44 #[error("The device is already running the given firmware")]
46 #[diagnostic(code(mcumgr_toolkit::firmware_update::already_installed))]
47 AlreadyInstalled,
48}
49
50#[derive(Clone, Debug, Default)]
52pub struct FirmwareUpdateParams {
53 pub bootloader_type: Option<BootloaderType>,
58 pub skip_reboot: bool,
62 pub force_confirm: bool,
66 pub upgrade_only: bool,
70}
71
72#[derive(Clone, Debug)]
74pub enum FirmwareUpdateStep {
75 DetectingBootloader,
77 BootloaderFound(BootloaderType),
79 ParsingFirmwareImage,
81 QueryingDeviceState,
83 UpdateInfo {
85 current_version: Option<(String, Option<[u8; 32]>)>,
87 new_version: (String, [u8; 32]),
89 },
90 UploadingFirmware,
92 ActivatingFirmware,
94 TriggeringReboot,
96}
97
98impl Display for FirmwareUpdateStep {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 Self::DetectingBootloader => f.write_str("Detecting bootloader ..."),
102 Self::BootloaderFound(bootloader_type) => {
103 write!(f, "Found bootloader: {bootloader_type}")
104 }
105 Self::ParsingFirmwareImage => f.write_str("Parsing firmware image ..."),
106 Self::QueryingDeviceState => f.write_str("Querying device state ..."),
107 Self::UpdateInfo {
108 current_version,
109 new_version,
110 } => {
111 f.write_str("Update: ")?;
112
113 if let Some((version_str, version_hash)) = ¤t_version {
114 f.write_str(version_str)?;
115
116 if let Some(version_hash) = version_hash {
117 write!(f, "-{}", hex::encode(&version_hash[..SHOWN_HASH_DIGITS]))?;
118 }
119 } else {
120 f.write_str("Empty")?;
121 };
122
123 write!(
124 f,
125 " -> {}-{}",
126 new_version.0,
127 hex::encode(&new_version.1[..SHOWN_HASH_DIGITS])
128 )
129 }
130 Self::UploadingFirmware => f.write_str("Uploading new firmware ..."),
131 Self::ActivatingFirmware => f.write_str("Activating new firmware ..."),
132 Self::TriggeringReboot => f.write_str("Triggering device reboot ..."),
133 }
134 }
135}
136
137pub type FirmwareUpdateProgressCallback<'a> =
149 dyn FnMut(FirmwareUpdateStep, Option<(u64, u64)>) -> bool + 'a;
150
151const SHOWN_HASH_DIGITS: usize = 4;
152
153pub(crate) fn firmware_update(
164 client: &MCUmgrClient,
165 firmware: impl AsRef<[u8]>,
166 checksum: Option<[u8; 32]>,
167 params: FirmwareUpdateParams,
168 mut progress: Option<&mut FirmwareUpdateProgressCallback>,
169) -> Result<(), FirmwareUpdateError> {
170 let target_image: Option<u32> = Default::default();
172 let actual_target_image = target_image.unwrap_or(0);
173
174 let firmware = firmware.as_ref();
175
176 let has_progress = progress.is_some();
177 let mut progress = |state: FirmwareUpdateStep, prog| {
178 if let Some(progress) = &mut progress {
179 if !progress(state, prog) {
180 return Err(FirmwareUpdateError::ProgressCallbackError);
181 }
182 }
183 Ok(())
184 };
185
186 let bootloader_type = if let Some(bootloader_type) = params.bootloader_type {
187 bootloader_type
188 } else {
189 progress(FirmwareUpdateStep::DetectingBootloader, None)?;
190
191 let bootloader_type = client
192 .os_bootloader_info()
193 .map_err(FirmwareUpdateError::BootloaderDetectionFailed)?
194 .get_bootloader_type()
195 .map_err(FirmwareUpdateError::BootloaderNotSupported)?;
196
197 progress(FirmwareUpdateStep::BootloaderFound(bootloader_type), None)?;
198
199 bootloader_type
200 };
201
202 progress(FirmwareUpdateStep::ParsingFirmwareImage, None)?;
203 let (image_version, image_id_hash) = match bootloader_type {
204 BootloaderType::MCUboot => {
205 let info = mcuboot::get_image_info(std::io::Cursor::new(firmware))?;
206 (info.version, info.hash)
207 }
208 };
209
210 progress(FirmwareUpdateStep::QueryingDeviceState, None)?;
211 let image_state = client
212 .image_get_state()
213 .map_err(FirmwareUpdateError::GetStateFailed)?;
214
215 let active_image = image_state
216 .iter()
217 .find(|img| img.image == actual_target_image && img.active)
218 .or_else(|| {
219 image_state
220 .iter()
221 .find(|img| img.image == actual_target_image && img.slot == 0)
222 });
223
224 progress(
225 FirmwareUpdateStep::UpdateInfo {
226 current_version: active_image.map(|img| (img.version.clone(), img.hash)),
227 new_version: (image_version.to_string(), image_id_hash),
228 },
229 None,
230 )?;
231
232 if active_image.and_then(|img| img.hash) == Some(image_id_hash) {
233 return Err(FirmwareUpdateError::AlreadyInstalled);
234 }
235
236 progress(FirmwareUpdateStep::UploadingFirmware, None)?;
237 let mut upload_progress_cb = |current, total| {
238 progress(
239 FirmwareUpdateStep::UploadingFirmware,
240 Some((current, total)),
241 )
242 .is_ok()
243 };
244
245 client
246 .image_upload(
247 firmware,
248 target_image,
249 checksum,
250 params.upgrade_only,
251 has_progress.then_some(&mut upload_progress_cb),
252 )
253 .map_err(|err| {
254 if let MCUmgrClientError::ProgressCallbackError = err {
255 FirmwareUpdateError::ProgressCallbackError
257 } else {
258 FirmwareUpdateError::ImageUploadFailed(err)
259 }
260 })?;
261
262 progress(FirmwareUpdateStep::ActivatingFirmware, None)?;
263 let set_state_result = client.image_set_state(Some(image_id_hash), params.force_confirm);
264 if let Err(set_state_error) = set_state_result {
265 let mut image_already_active = false;
266
267 if bootloader_type == BootloaderType::MCUboot && set_state_error.command_not_supported() {
273 progress(FirmwareUpdateStep::QueryingDeviceState, None)?;
274 let image_state = client
275 .image_get_state()
276 .map_err(FirmwareUpdateError::GetStateFailed)?;
277 if image_state.iter().any(|img| {
278 img.image == actual_target_image && img.slot == 0 && img.hash == Some(image_id_hash)
279 }) {
280 image_already_active = true;
281 }
282 }
283
284 if !image_already_active {
285 return Err(FirmwareUpdateError::SetStateFailed(set_state_error));
286 }
287 }
288
289 if !params.skip_reboot {
290 progress(FirmwareUpdateStep::TriggeringReboot, None)?;
291 client
292 .os_system_reset(false, None)
293 .map_err(FirmwareUpdateError::RebootFailed)?;
294 }
295
296 Ok(())
297}