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, Eq, PartialEq, PartialOrd, Ord)]
74pub enum FirmwareUpdateStep {
75 DetectingBootloader,
77 BootloaderFound(BootloaderType),
79 ParsingFirmwareImage,
81 QueryingDeviceState,
83 UpdateInfo {
85 current_version: Option<(String, Option<Vec<u8>>)>,
87 new_version: (String, Vec<u8>),
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!(
118 f,
119 "-{}",
120 hex::encode(&version_hash[..SHOWN_HASH_DIGITS.min(version_hash.len())])
121 )?;
122 }
123 } else {
124 f.write_str("Empty")?;
125 };
126
127 write!(
128 f,
129 " -> {}-{}",
130 new_version.0,
131 hex::encode(&new_version.1[..SHOWN_HASH_DIGITS.min(new_version.1.len())])
132 )
133 }
134 Self::UploadingFirmware => f.write_str("Uploading new firmware ..."),
135 Self::ActivatingFirmware => f.write_str("Activating new firmware ..."),
136 Self::TriggeringReboot => f.write_str("Triggering device reboot ..."),
137 }
138 }
139}
140
141pub type FirmwareUpdateProgressCallback<'a> =
153 dyn FnMut(FirmwareUpdateStep, Option<(u64, u64)>) -> bool + 'a;
154
155const SHOWN_HASH_DIGITS: usize = 4;
156
157pub(crate) fn firmware_update(
168 client: &MCUmgrClient,
169 firmware: impl AsRef<[u8]>,
170 checksum: Option<[u8; 32]>,
171 params: FirmwareUpdateParams,
172 mut progress: Option<&mut FirmwareUpdateProgressCallback>,
173) -> Result<(), FirmwareUpdateError> {
174 let target_image: Option<u32> = Default::default();
176 let actual_target_image = target_image.unwrap_or(0);
177
178 let firmware = firmware.as_ref();
179
180 let has_progress = progress.is_some();
181 let mut progress = |state: FirmwareUpdateStep, prog| {
182 if let Some(progress) = &mut progress {
183 if !progress(state, prog) {
184 return Err(FirmwareUpdateError::ProgressCallbackError);
185 }
186 }
187 Ok(())
188 };
189
190 let bootloader_type = if let Some(bootloader_type) = params.bootloader_type {
191 bootloader_type
192 } else {
193 progress(FirmwareUpdateStep::DetectingBootloader, None)?;
194
195 let bootloader_type = client
196 .os_bootloader_info()
197 .map_err(FirmwareUpdateError::BootloaderDetectionFailed)?
198 .get_bootloader_type()
199 .map_err(FirmwareUpdateError::BootloaderNotSupported)?;
200
201 progress(FirmwareUpdateStep::BootloaderFound(bootloader_type), None)?;
202
203 bootloader_type
204 };
205
206 progress(FirmwareUpdateStep::ParsingFirmwareImage, None)?;
207 let (image_version, image_id_hash) = match bootloader_type {
208 BootloaderType::MCUboot => {
209 let info = mcuboot::get_image_info(std::io::Cursor::new(firmware))?;
210 (info.version, Vec::<u8>::from(info.hash))
211 }
212 };
213
214 progress(FirmwareUpdateStep::QueryingDeviceState, None)?;
215 let image_state = client
216 .image_get_state()
217 .map_err(FirmwareUpdateError::GetStateFailed)?;
218
219 let active_image = image_state
220 .iter()
221 .find(|img| img.image == actual_target_image && img.active)
222 .or_else(|| {
223 image_state
224 .iter()
225 .find(|img| img.image == actual_target_image && img.slot == 0)
226 });
227
228 progress(
229 FirmwareUpdateStep::UpdateInfo {
230 current_version: active_image.map(|img| (img.version.clone(), img.hash.clone())),
231 new_version: (image_version.to_string(), image_id_hash.clone()),
232 },
233 None,
234 )?;
235
236 if active_image.and_then(|img| img.hash.as_ref()) == Some(&image_id_hash) {
237 return Err(FirmwareUpdateError::AlreadyInstalled);
238 }
239
240 progress(FirmwareUpdateStep::UploadingFirmware, None)?;
241 let mut upload_progress_cb = |current, total| {
242 progress(
243 FirmwareUpdateStep::UploadingFirmware,
244 Some((current, total)),
245 )
246 .is_ok()
247 };
248
249 client
250 .image_upload(
251 firmware,
252 target_image,
253 checksum,
254 params.upgrade_only,
255 has_progress.then_some(&mut upload_progress_cb),
256 )
257 .map_err(|err| {
258 if let MCUmgrClientError::ProgressCallbackError = err {
259 FirmwareUpdateError::ProgressCallbackError
261 } else {
262 FirmwareUpdateError::ImageUploadFailed(err)
263 }
264 })?;
265
266 progress(FirmwareUpdateStep::ActivatingFirmware, None)?;
267 let set_state_result = client.image_set_state(Some(&image_id_hash), params.force_confirm);
268 if let Err(set_state_error) = set_state_result {
269 let mut image_already_active = false;
270
271 if bootloader_type == BootloaderType::MCUboot && set_state_error.command_not_supported() {
277 progress(FirmwareUpdateStep::QueryingDeviceState, None)?;
278 let image_state = client
279 .image_get_state()
280 .map_err(FirmwareUpdateError::GetStateFailed)?;
281 if image_state.iter().any(|img| {
282 img.image == actual_target_image
283 && img.slot == 0
284 && img.hash.as_ref() == Some(&image_id_hash)
285 }) {
286 image_already_active = true;
287 }
288 }
289
290 if !image_already_active {
291 return Err(FirmwareUpdateError::SetStateFailed(set_state_error));
292 }
293 }
294
295 if !params.skip_reboot {
296 progress(FirmwareUpdateStep::TriggeringReboot, None)?;
297 client
298 .os_system_reset(false, None)
299 .map_err(FirmwareUpdateError::RebootFailed)?;
300 }
301
302 Ok(())
303}