mboot/
mboot.rs

1// Copyright 2025 NXP
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5use color_print::cstr;
6use indicatif::{ProgressBar, ProgressStyle};
7use log::{info, trace};
8use packets::{
9    Packet, PacketParse,
10    command::{CmdResponse, CommandHeader, CommandPacket},
11    data_phase::DataPhasePacket,
12};
13use protocols::Protocol;
14use tags::{
15    ToAddress,
16    command::{CommandTag, CommandToParams, KeyProvOperation, TrustProvOperation},
17    command_flag::CommandFlag,
18    command_response::CmdResponseTag,
19    property::{PropertyTag, PropertyTagDiscriminants},
20    status::StatusCode,
21};
22
23use crate::CommunicationError;
24
25mod formatters;
26pub mod memory;
27pub mod packets;
28pub mod protocols;
29pub mod tags;
30
31/// Response structure for [`CommandTag::GetProperty`] command
32///
33/// Contains the status code, raw response words, and parsed property value.
34#[derive(Clone, Debug)]
35pub struct GetPropertyResponse {
36    /// Status code of the operation
37    pub status: StatusCode,
38    /// Raw response words from the device
39    pub response_words: Box<[u32]>,
40    /// Parsed property value
41    pub property: PropertyTag,
42}
43
44/// Response structure for [`CommandTag::ReadMemory`] command
45///
46/// Contains the status code, response metadata, and actual data bytes read.
47#[derive(Clone, Debug)]
48pub struct ReadMemoryResponse {
49    /// Status code of the operation
50    pub status: StatusCode,
51    /// Response metadata (typically contains the byte count)
52    pub response_words: Box<[u32]>,
53    /// Actual data bytes read from memory
54    pub bytes: Box<[u8]>,
55}
56
57/// Response types for [`CommandTag::KeyProvisioning`] operations
58#[derive(Clone, Debug)]
59pub enum KeyProvisioningResponse {
60    /// Simple status response for most key provisioning operations
61    Status(StatusCode),
62    /// Extended response for [`KeyProvOperation::ReadKeyStore`] operation containing key data
63    KeyStore {
64        /// Status code of the operation
65        status: StatusCode,
66        /// Response metadata
67        response_words: Box<[u32]>,
68        /// Key store data bytes
69        bytes: Box<[u8]>,
70    },
71}
72
73trait InvalidData<T> {
74    /// Convert a type to [`Result`] of [`CommunicationError`].
75    fn or_invalid(self) -> Result<T, CommunicationError>;
76}
77
78impl<T, E> InvalidData<T> for Result<T, E> {
79    /// Transforms `E` into [`CommunicationError::InvalidData`]
80    fn or_invalid(self) -> Result<T, CommunicationError> {
81        self.or(Err(CommunicationError::InvalidData))
82    }
83}
84
85/// Main MCU Boot communication structure
86///
87/// Provides high-level interface for bootloader communication over various protocols.
88///
89/// # Type Parameters
90///
91/// * `T` - The underlying communication protocol (UART, USB, etc.)
92pub struct McuBoot<T>
93where
94    T: Protocol,
95{
96    device: T,
97    /// Enable/disable progress bar for data transfers
98    pub progress_bar: bool,
99    pub mask_read_data_phase: bool,
100}
101
102/// Result type for communication operations returning a value
103pub type ResultComm<T> = Result<T, CommunicationError>;
104/// Result type for operations returning only a status code
105pub type ResultStatus = ResultComm<StatusCode>;
106
107impl<T> McuBoot<T>
108where
109    T: Protocol,
110{
111    /// Creates a new [`McuBoot`] instance with the specified protocol
112    ///
113    /// # Arguments
114    ///
115    /// * `device` - The communication protocol instance
116    ///
117    /// # Returns
118    ///
119    /// A new [`McuBoot`] instance
120    #[must_use]
121    pub fn new(device: T) -> Self {
122        info!(
123            "Initialized MCU Boot with device identifier: {}",
124            device.get_identifier()
125        );
126        McuBoot {
127            device,
128            progress_bar: false,
129            mask_read_data_phase: false,
130        }
131    }
132
133    /// Get a specific property value from the device
134    ///
135    /// # Arguments
136    ///
137    /// * `tag` - The property tag to query
138    /// * `memory_index` - External memory ID or internal memory region index
139    ///
140    /// # Returns
141    ///
142    /// Property response containing the value and status
143    ///
144    /// # Errors
145    ///
146    /// Returns [`CommunicationError`] if:
147    /// - Communication with device fails
148    /// - Invalid response is received
149    /// - Property is not supported
150    pub fn get_property(
151        &mut self,
152        tag: PropertyTagDiscriminants,
153        memory_index: u32,
154    ) -> ResultComm<GetPropertyResponse> {
155        let command = CommandPacket::new_none_flag(CommandTag::GetProperty { tag, memory_index });
156        self.send_command(&command)?;
157
158        let response = self.read_cmd_response()?;
159
160        if let CmdResponseTag::GetProperty(val) = response.tag {
161            Ok(GetPropertyResponse {
162                status: response.status,
163                property: PropertyTag::from_code(tag, &val),
164                response_words: val,
165            })
166        } else {
167            Err(CommunicationError::InvalidPacketReceived)
168        }
169    }
170
171    /// Set a property value on the device
172    ///
173    /// # Arguments
174    ///
175    /// * `tag` - The property tag to set
176    /// * `value` - The value to set
177    ///
178    /// # Returns
179    ///
180    /// Status code indicating success or failure
181    ///
182    /// # Errors
183    ///
184    /// Returns [`CommunicationError`] if communication fails
185    pub fn set_property(&mut self, tag: PropertyTagDiscriminants, value: u32) -> ResultStatus {
186        let command = CommandPacket::new_none_flag(CommandTag::SetProperty { tag, value });
187        self.send_command(&command)?;
188
189        let response = self.read_cmd_response()?;
190        Ok(response.status)
191    }
192
193    /// Reset the MCU
194    ///
195    /// Sends a reset command to the device. Note that the connection may be lost
196    /// after reset and need to be re-established.
197    ///
198    /// # Returns
199    ///
200    /// Status code (may be [`StatusCode::NoResponse`] if device resets successfully)
201    ///
202    /// # Errors
203    ///
204    /// Any [`CommunicationError`], almost all variants are possible.
205    pub fn reset(&mut self) -> ResultStatus {
206        let command = CommandPacket::new_none_flag(CommandTag::Reset);
207        self.send_command(&command)?;
208        let response = self.read_cmd_response()?;
209        Ok(response.status)
210    }
211
212    /// Call a function at the specified address
213    ///
214    /// # Arguments
215    ///
216    /// * `start_address` - Function address to call (must be word aligned)
217    /// * `argument` - Function argument
218    ///
219    /// # Returns
220    ///
221    /// Status code indicating success or failure
222    ///
223    /// # Errors
224    ///
225    /// Any [`CommunicationError`], almost all variants are possible.
226    pub fn call(&mut self, start_address: u32, argument: u32) -> ResultStatus {
227        let command = CommandPacket::new_none_flag(CommandTag::Call {
228            start_address,
229            argument,
230        });
231        self.send_command(&command)?;
232        let response = self.read_cmd_response()?;
233        Ok(response.status)
234    }
235
236    /// Execute program at the specified address
237    ///
238    /// # Arguments
239    ///
240    /// * `start_address` - Jump address (must be word aligned)
241    /// * `argument` - Function argument address
242    /// * `stackpointer` - Stack pointer address
243    ///
244    /// # Returns
245    ///
246    /// Status code indicating success or failure
247    ///
248    /// # Errors
249    ///
250    /// Any [`CommunicationError`], almost all variants are possible.
251    pub fn execute(&mut self, start_address: u32, argument: u32, stackpointer: u32) -> ResultStatus {
252        let command = CommandPacket::new_none_flag(CommandTag::Execute {
253            start_address,
254            argument,
255            stackpointer,
256        });
257        self.send_command(&command)?;
258        let response = self.read_cmd_response()?;
259        Ok(response.status)
260    }
261
262    /// Fill memory region with a pattern
263    ///
264    /// # Arguments
265    ///
266    /// * `start_address` - Start address (must be word aligned)
267    /// * `byte_count` - Number of bytes to fill (must be word aligned)
268    /// * `pattern` - 32-bit pattern to fill with
269    ///
270    /// # Returns
271    ///
272    /// Status code indicating success or failure
273    ///
274    /// # Errors
275    ///
276    /// Any [`CommunicationError`], almost all variants are possible.
277    pub fn fill_memory(&mut self, start_address: u32, byte_count: u32, pattern: u32) -> ResultStatus {
278        let command = CommandPacket::new_none_flag(CommandTag::FillMemory {
279            start_address,
280            byte_count,
281            pattern,
282        });
283        self.send_command(&command)?;
284        let response = self.read_cmd_response()?;
285        Ok(response.status)
286    }
287
288    /// Write data to MCU memory
289    ///
290    /// # Arguments
291    ///
292    /// * `start_address` - Start address for writing
293    /// * `memory_id` - Memory ID (0 for internal memory, see [`memory::mem_id`] for external)
294    /// * `bytes` - Data to write
295    ///
296    /// # Returns
297    ///
298    /// Status code indicating success or failure
299    ///
300    /// # Note
301    ///
302    /// Data will be automatically split into chunks based on max packet size
303    ///
304    /// # Errors
305    ///
306    /// Any [`CommunicationError`], almost all variants are possible.
307    pub fn write_memory(&mut self, start_address: u32, memory_id: u32, bytes: &[u8]) -> ResultStatus {
308        let command = CommandPacket::new_data_phase(CommandTag::WriteMemory {
309            start_address,
310            memory_id,
311            bytes,
312        });
313        self.send_command(&command)?;
314
315        let response = self.read_cmd_response()?;
316        Ok(response.status)
317    }
318
319    /// Erase all flash memory
320    ///
321    /// # Arguments
322    ///
323    /// * `memory_id` - Memory ID (0 for internal flash)
324    ///
325    /// # Returns
326    ///
327    /// Status code indicating success or failure
328    ///
329    /// # Warning
330    ///
331    /// This operation will erase the entire flash memory without recovering
332    /// the flash security section.
333    ///
334    /// # Errors
335    ///
336    /// Any [`CommunicationError`], almost all variants are possible.
337    pub fn flash_erase_all(&mut self, memory_id: u32) -> ResultStatus {
338        let command = CommandPacket::new_none_flag(CommandTag::FlashEraseAll { memory_id });
339        self.send_command(&command)?;
340        let response = self.read_cmd_response()?;
341        Ok(response.status)
342    }
343
344    /// Erase a specific flash region
345    ///
346    /// # Arguments
347    ///
348    /// * `start_address` - Start address of region to erase
349    /// * `byte_count` - Number of bytes to erase
350    /// * `memory_id` - Memory ID (0 for internal flash)
351    ///
352    /// # Returns
353    ///
354    /// Status code indicating success or failure
355    ///
356    /// # Errors
357    ///
358    /// Any [`CommunicationError`], almost all variants are possible.
359    pub fn flash_erase_region(&mut self, start_address: u32, byte_count: u32, memory_id: u32) -> ResultStatus {
360        let command = CommandPacket::new_none_flag(CommandTag::FlashEraseRegion {
361            start_address,
362            byte_count,
363            memory_id,
364        });
365        self.send_command(&command)?;
366        let response = self.read_cmd_response()?;
367        Ok(response.status)
368    }
369
370    /// Erase all flash and recover security section
371    ///
372    /// This command erases the entire flash memory and recovers the flash security section,
373    /// effectively unsecuring the device.
374    ///
375    /// # Returns
376    ///
377    /// Status code indicating success or failure
378    ///
379    /// # Errors
380    ///
381    /// Any [`CommunicationError`], almost all variants are possible.
382    pub fn flash_erase_all_unsecure(&mut self) -> ResultStatus {
383        let command = CommandPacket::new_none_flag(CommandTag::FlashEraseAllUnsecure);
384        self.send_command(&command)?;
385        let response = self.read_cmd_response()?;
386        Ok(response.status)
387    }
388
389    /// Read data from MCU memory
390    ///
391    /// # Arguments
392    ///
393    /// * `start_address` - Start address to read from
394    /// * `byte_count` - Number of bytes to read
395    /// * `memory_id` - Memory ID (0 for internal memory)
396    ///
397    /// # Returns
398    ///
399    /// Response containing the read data and status
400    ///
401    /// # Errors
402    ///
403    /// Returns [`CommunicationError`] if:
404    /// - Communication fails
405    /// - Invalid response is received
406    /// - Memory is protected or inaccessible
407    pub fn read_memory(
408        &mut self,
409        start_address: u32,
410        byte_count: u32,
411        memory_id: u32,
412    ) -> ResultComm<ReadMemoryResponse> {
413        let command = CommandPacket::new_none_flag(CommandTag::ReadMemory {
414            start_address,
415            byte_count,
416            memory_id,
417        });
418        self.send_command(&command)?;
419
420        // allowing one more status code when reading memory
421        let response = self.read_command()?;
422        let status = &response.status;
423        if !(status.is_success() || status.is_memory_blank_page_read_disallowed()) {
424            return Err((*status).into());
425        }
426
427        if let CmdResponseTag::ReadMemory(bytes) = response.tag {
428            Ok(ReadMemoryResponse {
429                status: response.status,
430                response_words: Box::new([bytes.len() as u32]),
431                bytes,
432            })
433        } else {
434            Err(CommunicationError::InvalidPacketReceived)
435        }
436    }
437
438    /// Configure external memory
439    ///
440    /// # Arguments
441    ///
442    /// * `memory_id` - Memory ID to configure
443    /// * `address` - Address containing configuration data
444    ///
445    /// # Returns
446    ///
447    /// Status code indicating success or failure
448    ///
449    /// # Errors
450    ///
451    /// Any [`CommunicationError`], almost all variants are possible.
452    pub fn configure_memory(&mut self, memory_id: u32, address: u32) -> ResultStatus {
453        let command = CommandPacket::new_none_flag(CommandTag::ConfigureMemory { memory_id, address });
454        self.send_command(&command)?;
455        let response = self.read_cmd_response()?;
456        Ok(response.status)
457    }
458
459    /// Receive and process a Secure Binary (SB) file
460    ///
461    /// # Arguments
462    ///
463    /// * `bytes` - SB file data
464    ///
465    /// # Returns
466    ///
467    /// Status code indicating success or failure
468    ///
469    /// # Note
470    ///
471    /// The SB file will be processed and executed by the bootloader.
472    /// Progress bar will be shown if enabled.
473    ///
474    /// # Errors
475    ///
476    /// Any [`CommunicationError`], almost all variants are possible.
477    pub fn receive_sb_file(&mut self, bytes: &[u8]) -> ResultStatus {
478        let command = CommandPacket::new_data_phase(CommandTag::ReceiveSBFile { bytes });
479        match self.send_command(&command) {
480            Ok(()) | Err(CommunicationError::Aborted) => {
481                let response = self.read_cmd_response()?;
482                Ok(response.status)
483            }
484            Err(err) => Err(err),
485        }
486    }
487
488    /// Execute trust provisioning operation
489    ///
490    /// Performs various trust provisioning operations on the device, such as
491    /// proving genuinity, setting wrapped data, or other security-related operations.
492    ///
493    /// # Arguments
494    ///
495    /// * `operation` - The trust provisioning operation to execute
496    ///
497    /// # Returns
498    ///
499    /// A tuple containing:
500    /// - Status code indicating success or failure
501    /// - Response data specific to the operation
502    ///
503    /// # Errors
504    ///
505    /// Any [`CommunicationError`], almost all variants are possible.
506    pub fn trust_provisioning(&mut self, operation: &TrustProvOperation) -> ResultComm<(StatusCode, Box<[u32]>)> {
507        let command = CommandPacket::new_none_flag(CommandTag::TrustProvisioning(operation));
508        self.send_command(&command)?;
509
510        let response = self.read_cmd_response()?;
511        match response.tag {
512            CmdResponseTag::TrustProvisioning(data) => Ok((response.status, data)),
513            _ => Err(CommunicationError::InvalidPacketReceived),
514        }
515    }
516
517    /// Execute key provisioning operation
518    ///
519    /// Handles various key provisioning operations including enrolling PUF,
520    /// setting intrinsic keys, writing to non-volatile memory, and managing user keys.
521    /// The behavior varies based on the operation type:
522    /// - [`KeyProvOperation::SetUserKey`] operations include a data phase for key transmission
523    /// - [`KeyProvOperation::ReadKeyStore`] operations return the actual key store data
524    /// - Other operations return simple status responses
525    ///
526    /// # Arguments
527    ///
528    /// * `operation` - The key provisioning operation to execute
529    ///
530    /// # Returns
531    ///
532    /// Response type depends on the operation:
533    /// - [`KeyProvisioningResponse::Status`] for most operations
534    /// - [`KeyProvisioningResponse::KeyStore`] for [`KeyProvOperation::ReadKeyStore`] with key data
535    ///
536    /// # Errors
537    ///
538    /// Returns [`CommunicationError`] if:
539    /// - Communication with device fails
540    /// - Invalid response is received
541    /// - Data phase transmission fails for `SetUserKey`
542    pub fn key_provisioning(
543        &mut self,
544        operation: &KeyProvOperation,
545    ) -> Result<KeyProvisioningResponse, CommunicationError> {
546        let command = CommandPacket::new_none_flag(CommandTag::KeyProvisioning(operation));
547        if let KeyProvOperation::ReadKeyStore { .. } = operation {
548            self.send_command(&command)?;
549            let response = self.read_cmd_response()?;
550            // Extract the data based on the response tag
551            match response.tag {
552                CmdResponseTag::KeyProvisioning(data, data_phase) => {
553                    // The data phase should contain the actual key store data
554                    Ok(KeyProvisioningResponse::KeyStore {
555                        status: response.status,
556                        response_words: data,
557                        bytes: data_phase.unwrap_or_default(),
558                    })
559                }
560                _ => Err(CommunicationError::InvalidPacketReceived),
561            }
562        } else {
563            self.mask_read_data_phase = true;
564            self.send_command(&command)?;
565            self.mask_read_data_phase = false;
566            let response = self.read_cmd_response()?;
567            Ok(KeyProvisioningResponse::Status(response.status))
568        }
569    }
570
571    /// Read from MCU flash program once region (eFuse/OTP)
572    ///
573    /// Reads a 32-bit value from the one-time programmable (OTP) memory region.
574    /// This memory can only be written once and is typically used for storing
575    /// permanent configuration or security keys.
576    ///
577    /// # Arguments
578    ///
579    /// * `index` - Start index of the eFuse/OTP region
580    /// * `count` - Number of bytes to read (must be 4)
581    ///
582    /// # Returns
583    ///
584    /// The read value as a 32-bit unsigned integer
585    ///
586    /// # Errors
587    ///
588    /// Returns [`CommunicationError`] if:
589    /// - Communication with device fails
590    /// - Invalid response type is received
591    /// - The specified index is out of range
592    /// - The OTP region is locked or inaccessible
593    pub fn flash_read_once(&mut self, index: u32, count: u32) -> ResultComm<u32> {
594        let command = CommandPacket::new_none_flag(CommandTag::FlashReadOnce { index, count });
595        self.send_command(&command)?;
596
597        let response = self.read_cmd_response()?;
598        match response.tag {
599            CmdResponseTag::FlashReadOnce(value) => Ok(value),
600            _ => Err(CommunicationError::InvalidPacketReceived),
601        }
602    }
603
604    /// Write into MCU once program region (eFuse/OTP)
605    ///
606    /// Programs a 32-bit value into the one-time programmable memory region.
607    /// This operation is irreversible - once programmed, the memory cannot be
608    /// erased or reprogrammed. In OTP memory, bits can only change from 0 to 1,
609    /// never from 1 to 0.
610    ///
611    /// # Arguments
612    ///
613    /// * `index` - Start index of the eFuse/OTP region
614    /// * `count` - Number of bytes to write (must be 4)
615    /// * `data` - 32-bit value to write
616    /// * `verify` - If true, reads back and verifies the written value
617    ///
618    /// # Returns
619    ///
620    /// Status code indicating success or failure. If verification is enabled
621    /// and fails, returns [`StatusCode::OtpVerifyFail`].
622    ///
623    /// # Notes
624    ///
625    /// - The verification process checks if all bits that were supposed to be
626    ///   set to 1 are actually set. It uses bitwise AND to accommodate the fact
627    ///   that some bits might have already been programmed.
628    /// - The index is masked to 24 bits during verification read
629    /// - ROM might not report errors when attempting to write to locked OTP,
630    ///   so verification is recommended for critical operations
631    ///
632    /// # Errors
633    ///
634    /// Any [`CommunicationError`], almost all variants are possible. Verification fail is not an
635    /// error.
636    pub fn flash_program_once(&mut self, index: u32, count: u32, data: u32, verify: bool) -> ResultStatus {
637        let command = CommandPacket::new_none_flag(CommandTag::FlashProgramOnce { index, count, data });
638        self.send_command(&command)?;
639
640        let response = self.read_cmd_response()?;
641
642        if verify && response.status.is_success() {
643            // For verification, we read back the value and check if the bits we set are still set
644            // Note: In OTP, we can only set bits from 0 to 1, not vice versa
645            match self.flash_read_once(index & ((1 << 24) - 1), count) {
646                Ok(read_value) => {
647                    if read_value & data == data {
648                        Ok(response.status)
649                    } else {
650                        // Custom status code for verification failure
651                        Ok(StatusCode::OtpVerifyFail)
652                    }
653                }
654                Err(e) => Err(e),
655            }
656        } else {
657            Ok(response.status)
658        }
659    }
660
661    /// Read fuse data
662    ///
663    /// Reads data from the device's fuse memory region. Fuses are one-time
664    /// programmable memory bits used for permanent configuration, security
665    /// settings, and device-specific information.
666    ///
667    /// # Arguments
668    ///
669    /// * `start_address` - Starting address in the fuse memory region
670    /// * `byte_count` - Number of bytes to read
671    /// * `memory_id` - Memory identifier (device-specific)
672    ///
673    /// # Returns
674    ///
675    /// [`ReadMemoryResponse`] containing:
676    /// - Status code of the operation
677    /// - Response metadata (byte count)
678    /// - Actual fuse data bytes
679    ///
680    /// # Errors
681    ///
682    /// Returns [`CommunicationError`] if:
683    /// - The operation fails (converted from status code)
684    /// - Invalid response type is received
685    /// - Fuse region is inaccessible or protected
686    pub fn fuse_read(&mut self, start_address: u32, byte_count: u32, memory_id: u32) -> ResultComm<ReadMemoryResponse> {
687        let command = CommandPacket::new_none_flag(CommandTag::FuseRead {
688            start_address,
689            byte_count,
690            memory_id,
691        });
692        self.send_command(&command)?;
693        let response = self.read_cmd_response()?;
694        let status = &response.status;
695        if !status.is_success() {
696            return Err((*status).into());
697        }
698        match response.tag {
699            CmdResponseTag::ReadMemory(bytes) => Ok(ReadMemoryResponse {
700                status: response.status,
701                response_words: Box::new([bytes.len() as u32]),
702                bytes,
703            }),
704            _ => Err(CommunicationError::InvalidPacketReceived),
705        }
706    }
707
708    /// Program fuse data
709    ///
710    /// Writes data to the device's fuse memory region. This operation is
711    /// permanent and irreversible. Once a fuse is programmed (blown), it
712    /// cannot be restored to its original state.
713    ///
714    /// # Arguments
715    ///
716    /// * `start_address` - Starting address in the fuse memory region
717    /// * `memory_id` - Memory identifier (device-specific)
718    /// * `bytes` - Data to write to the fuses
719    ///
720    /// # Returns
721    ///
722    /// Status code indicating success or failure
723    ///
724    /// # Warning
725    ///
726    /// This operation permanently modifies the device hardware. Incorrect
727    /// fuse programming can render the device unusable. Always verify the
728    /// correct fuse addresses and values before programming.
729    ///
730    /// # Errors
731    ///
732    /// Any [`CommunicationError`], almost all variants are possible.
733    pub fn fuse_program(&mut self, start_address: u32, memory_id: u32, bytes: &[u8]) -> ResultStatus {
734        let command = CommandPacket::new_data_phase(CommandTag::FuseProgram {
735            start_address,
736            memory_id,
737            bytes,
738        });
739        self.send_command(&command)?;
740        let response = self.read_cmd_response()?;
741        Ok(response.status)
742    }
743
744    /// Load image data directly to the device
745    ///
746    /// Sends raw image data to the device without a specific command header.
747    ///
748    /// # Arguments
749    ///
750    /// * `bytes` - Raw image data to be loaded
751    ///
752    /// # Returns
753    ///
754    /// Status code indicating success or failure
755    ///
756    /// # Errors
757    ///
758    /// Any [`CommunicationError`], almost all variants are possible.
759    pub fn load_image(&mut self, bytes: &[u8]) -> ResultStatus {
760        let command = CommandPacket::new_data_phase(CommandTag::NoCommand { bytes });
761        self.send_command(&command)?;
762        Ok(StatusCode::Success)
763    }
764
765    /// Read command response and validate status
766    ///
767    /// Internal helper method that reads a command response from the device
768    /// and validates its status. If the status indicates an error, it converts
769    /// the status code into a [`CommunicationError`].
770    ///
771    /// # Returns
772    ///
773    /// The command response if status indicates success
774    ///
775    /// # Errors
776    ///
777    /// Returns [`CommunicationError`] converted from the status code if the
778    /// operation was not successful
779    fn read_cmd_response(&mut self) -> ResultComm<CmdResponse> {
780        let response = self.read_command()?;
781        info!("{}: {response:02X?}", cstr!("<bold>Received"));
782        if response.status.is_success() {
783            Ok(response)
784        } else {
785            Err(response.status.into())
786        }
787    }
788    /// Send a command packet to the device
789    ///
790    /// Internal helper method that handles the complete command transmission
791    /// process, including data phase handling for commands that require it.
792    /// For commands with data phases, it automatically queries the device's
793    /// maximum packet size and splits the data accordingly.
794    ///
795    /// # Arguments
796    ///
797    /// * `command` - The command packet to send
798    ///
799    /// # Returns
800    ///
801    /// Ok(()) if the command was sent successfully
802    ///
803    /// # Errors
804    ///
805    /// Returns [`CommunicationError`] if:
806    /// - Failed to get max packet size property
807    /// - Device communication fails
808    /// - Data phase transmission fails
809    ///
810    /// # Workflow
811    ///
812    /// 1. Extracts parameters and data phase from the command
813    /// 2. Constructs and sends the initial command packet
814    /// 3. If data phase exists:
815    ///    - Queries max packet size from device
816    ///    - Reads intermediate response
817    ///    - Splits data into chunks
818    ///    - Sends each chunk with optional progress tracking
819    fn send_command(&mut self, command: &CommandPacket) -> ResultComm<()> {
820        let tag = &command.tag;
821        let (params, data_phase) = tag.to_params();
822        let packet = command.header.construct_frame(&params, tag.code());
823        info!("{}: {command:02X?}", cstr!("<bold>Sending"));
824
825        if let Some(data) = data_phase {
826            info!("Sending data phase: {data:02X?}");
827            let max_packet_size: u32 = {
828                let response = self.get_property(PropertyTagDiscriminants::MaxPacketSize, 0)?;
829                match response.property {
830                    PropertyTag::MaxPacketSize(size) => size,
831                    _ => return Err(CommunicationError::InvalidData),
832                }
833            };
834            if !matches!(tag, CommandTag::NoCommand { .. }) {
835                self.device.write_packet_raw(&packet)?;
836                // this is the intermediate generic response
837                self.read_cmd_response()?;
838            }
839            // Block for progress bar
840            {
841                let progress_bar = self.create_progress_bar(data.len() as u64, "Sending data");
842                for bytes in data.chunks(
843                    max_packet_size
844                        .try_into()
845                        .expect("pointer size of this platform is too small"),
846                ) {
847                    self.device.write_packet_concrete(DataPhasePacket::parse(bytes)?)?;
848                    if let Some(bar) = progress_bar.as_ref() {
849                        bar.inc(max_packet_size.into());
850                    }
851                }
852            }
853        } else {
854            self.device.write_packet_raw(&packet)?;
855        }
856        Ok(())
857    }
858
859    /// Read a command response from the device
860    ///
861    /// Internal helper method that reads and parses command responses,
862    /// handling both simple responses and those with data phases.
863    ///
864    /// # Returns
865    ///
866    /// Parsed command response
867    ///
868    /// # Errors
869    ///
870    /// Returns [`CommunicationError`] if:
871    /// - Communication timeout occurs
872    /// - Invalid data format is received
873    /// - Command flag is unrecognized
874    /// - Data phase read fails
875    ///
876    /// # Data Phase Handling
877    ///
878    /// When a response includes a data phase:
879    /// 1. Reads the initial response header
880    /// 2. Extracts the data phase length
881    /// 3. Reads data packets until complete
882    /// 4. Shows progress bar if enabled
883    /// 5. Reads final status response
884    fn read_command(&mut self) -> ResultComm<CmdResponse> {
885        trace!("Starting to read command");
886        let data = self.device.read_packet_raw(CmdResponse::get_code())?;
887        let params_slice = &data[8..];
888
889        // data[3] = param count
890        if params_slice.len() % 4 != 0 && params_slice.len() != 4 * data[3] as usize {
891            return Err(CommunicationError::InvalidData);
892        }
893
894        let header = CommandHeader {
895            flag: CommandFlag::try_from(data[1]).or(Err(CommunicationError::InvalidData))?,
896            reserved: data[2],
897        };
898        let status = parse_status(data[4..8].try_into().or_invalid()?)?;
899
900        // If we dont expect data phase, we can force to return response without data
901        // This is necessary for key-provisionning commands since their intermediate
902        // generic response commands have data phase flag set, which is incorrect
903        if self.mask_read_data_phase {
904            return Ok(CmdResponse {
905                header,
906                status,
907                tag: CmdResponseTag::from_code(data[0], params_slice, None).ok_or(CommunicationError::InvalidData)?,
908            });
909        }
910
911        match header.flag {
912            CommandFlag::NoData => Ok(CmdResponse {
913                header,
914                status,
915                tag: CmdResponseTag::from_code(data[0], params_slice, None).ok_or(CommunicationError::InvalidData)?,
916            }),
917            CommandFlag::HasDataPhase => {
918                let length = u32::from_le_bytes(params_slice[0..4].try_into().or_invalid()?);
919                trace!("Data phase length: {length}");
920
921                let mut data_phase = Vec::new();
922                // Block for progress bar
923                {
924                    let progress_bar = self.create_progress_bar(length.into(), "Receiving data");
925                    while data_phase.len() != length as usize {
926                        trace!("Reading data phase packet");
927                        data_phase.extend(match self.device.read_packet_concrete::<DataPhasePacket>() {
928                            Ok(data) => {
929                                if let Some(bar) = progress_bar.as_ref() {
930                                    bar.inc(data.data.len() as u64);
931                                }
932                                data.data
933                            }
934                            Err(CommunicationError::Aborted) => break,
935                            Err(err) => return Err(err),
936                        });
937                    }
938                }
939
940                trace!("Reading final response");
941                let final_response = self.device.read_packet_raw(CmdResponse::get_code())?;
942                let status = parse_status(final_response[4..8].try_into().or_invalid()?)?;
943
944                Ok(CmdResponse {
945                    header: CommandHeader {
946                        flag: CommandFlag::NoData,
947                        reserved: data[2],
948                    },
949                    status,
950                    tag: CmdResponseTag::from_code(data[0], params_slice, Some(&data_phase))
951                        .ok_or(CommunicationError::InvalidData)?,
952                })
953            }
954        }
955    }
956
957    /// Create a progress bar for data transfers
958    ///
959    /// Internal helper method that creates a progress bar if progress tracking is enabled.
960    /// The progress bar displays the transfer status with binary size formatting.
961    ///
962    /// # Arguments
963    ///
964    /// * `len` - Total length of data to transfer in bytes
965    /// * `prefix` - Descriptive prefix for the progress bar
966    ///
967    /// # Returns
968    ///
969    /// Optional progress bar instance:
970    /// - Some(ProgressBar) if progress tracking is enabled
971    /// - None if progress tracking is disabled
972    ///
973    /// # Progress Bar Format
974    ///
975    /// The progress bar displays:
976    /// - Custom prefix text
977    /// - Visual progress indicator (40 characters wide)
978    /// - Current bytes transferred / total bytes
979    fn create_progress_bar(&self, len: u64, prefix: &'static str) -> Option<ProgressBar> {
980        if self.progress_bar {
981            let bar = ProgressBar::new(len);
982            bar.set_style(
983                ProgressStyle::with_template("{prefix} [{bar:40}] {binary_bytes:>}/{binary_total_bytes}")
984                    .unwrap()
985                    .progress_chars("##-"),
986            );
987            bar.set_prefix(prefix);
988            Some(bar)
989        } else {
990            None
991        }
992    }
993}
994
995/// Parse status code from raw bytes
996///
997/// Converts a 4-byte little-endian value into a [`StatusCode`] enum.
998/// This function is used throughout the module to interpret device responses.
999///
1000/// # Arguments
1001///
1002/// * `data` - 4 bytes containing the status code in little-endian format
1003///
1004/// # Returns
1005///
1006/// Parsed status code enum variant
1007///
1008/// # Errors
1009///
1010/// Returns [`CommunicationError::UnexpectedStatus`] if:
1011/// - The status code value is not recognized
1012/// - The discriminant doesn't match any known [`StatusCode`] variant
1013///
1014/// The error includes both the unknown status placeholder and the actual
1015/// numeric value for debugging purposes.
1016fn parse_status(data: [u8; 4]) -> ResultComm<StatusCode> {
1017    let discriminant = u32::from_le_bytes(data);
1018    StatusCode::try_from(discriminant).or(Err(CommunicationError::UnexpectedStatus(
1019        StatusCode::UnknownStatusCode,
1020        discriminant,
1021    )))
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use crate::mboot::{
1027        McuBoot,
1028        protocols::{ProtocolOpen, uart::UARTProtocol},
1029        tags::property::{PropertyTag, PropertyTagDiscriminants},
1030    };
1031
1032    const DEVICE: &str = "COM3";
1033    fn get_boot() -> McuBoot<UARTProtocol> {
1034        McuBoot::new(UARTProtocol::open(DEVICE).unwrap())
1035    }
1036
1037    #[test]
1038    #[ignore = "Requires hardware connection to board"]
1039    fn test_board_get_version() {
1040        let mut boot = get_boot();
1041        let version = boot.get_property(PropertyTagDiscriminants::CurrentVersion, 0).unwrap();
1042        if let PropertyTag::CurrentVersion(ver) = version.property {
1043            assert_eq!(ver.mark, 'K');
1044            assert_eq!(ver.major, 3);
1045            assert_eq!(ver.minor, 1);
1046            assert_eq!(ver.fixation, 1);
1047        } else {
1048            panic!()
1049        }
1050    }
1051}