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