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(¶ms, 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}