mboot/mboot/
protocols.rs

1// Copyright 2025 NXP
2//
3// SPDX-License-Identifier: BSD-3-Clause
4//! McuBoot Protocol Abstraction Layer
5//!
6//! This module provides a unified interface for communicating with McuBoot devices
7//! across different physical transports (UART, USB, I2C). It defines common traits and
8//! error types that allow the same high-level McuBoot commands to work regardless of the
9//! underlying communication medium.
10//!
11//! The protocol abstraction handles the low-level details of packet framing, acknowledgment
12//! handling, and error recovery while providing a consistent interface for sending and
13//! receiving McuBoot packets.
14//!
15//! # Supported Protocols
16//! - UART: Serial communication over UART interfaces
17//! - USB: USB HID communication for direct device connection
18//! - I2C: I2C bus communication for embedded applications
19
20#[cfg(feature = "python")]
21use pyo3::{PyErr, exceptions::PyValueError};
22
23use std::time::Duration;
24
25use super::{
26    ResultComm,
27    packets::{Packet, PacketConstruct, PacketParse},
28    tags::status::StatusCode,
29};
30
31pub mod i2c;
32pub mod uart;
33pub mod usb;
34
35/// Communication error types for McuBoot protocol operations
36///
37/// This enum covers all possible error conditions that can occur during
38/// communication with McuBoot devices, from low-level transport errors
39/// to protocol-level issues.
40#[derive(thiserror::Error, Debug)]
41pub enum CommunicationError {
42    /// Error from the underlying serial port library
43    #[error("error raised by UART library")]
44    SerialPortError(#[from] serialport::Error),
45
46    /// General I/O error during read/write operations
47    #[error("error occurred while reading or writing to device")]
48    IOError(#[from] std::io::Error),
49
50    /// File system error during file operations
51    #[error("error while reading or writing a file")]
52    FileError(#[source] std::io::Error),
53
54    /// Target device sent a NACK (negative acknowledgment)
55    #[error("board sent NACK")]
56    NACKSent,
57
58    /// Received packet has incorrect CRC checksum
59    #[error("received incorrect CRC")]
60    InvalidCrc,
61
62    /// Packet header is malformed or invalid
63    #[error("invalid response header")]
64    InvalidHeader,
65
66    /// Packet data is invalid or corrupted
67    #[error("data in the packet is invalid")]
68    InvalidData,
69
70    /// Received unexpected packet type
71    #[error("received another packet type than was expected")]
72    InvalidPacketReceived,
73
74    /// Error during packet parsing
75    #[error("error occured while parsing: {0}")]
76    ParseError(String),
77
78    /// Command returned an error status code
79    #[error("unexpected status code: {1} ({1:#X}) {0}")]
80    UnexpectedStatus(StatusCode, u32),
81
82    /// Communication was aborted by user or system
83    #[error("communication was aborted")]
84    Aborted,
85
86    /// Feature not supported on current platform
87    #[error("this functionality is not supported on the current platform")]
88    UnsupportedPlatform,
89
90    /// Timeout occurred while waiting for response
91    #[error("timeout occured while waiting for response")]
92    Timeout,
93}
94
95impl From<StatusCode> for CommunicationError {
96    /// Convert a McuBoot status code to a communication error
97    fn from(value: StatusCode) -> Self {
98        CommunicationError::UnexpectedStatus(value, value.into())
99    }
100}
101
102#[cfg(feature = "python")]
103impl From<CommunicationError> for PyErr {
104    /// Convert communication error to Python exception (when Python bindings are enabled)
105    fn from(value: CommunicationError) -> Self {
106        PyValueError::new_err(value.to_string())
107    }
108}
109
110/// Core protocol trait for McuBoot communication
111///
112/// This trait defines the methods that all McuBoot protocol implementations
113/// must provide.
114#[cfg_attr(any(feature = "python", feature = "c_api"), enum_dispatch::enum_dispatch)]
115pub trait Protocol {
116    /// Get the configured timeout duration for operations
117    fn get_timeout(&self) -> Duration;
118
119    /// Get the polling interval for checking responses
120    fn get_polling_interval(&self) -> Duration;
121
122    /// Get a string identifier for this protocol instance
123    fn get_identifier(&self) -> &str;
124
125    /// Read raw bytes from the device
126    ///
127    /// # Arguments
128    /// * `bytes` - Number of bytes to read
129    ///
130    /// # Returns
131    /// A Result containing the read bytes or an error
132    ///
133    /// # Errors
134    /// Any errors that occured while reading, from being unable to read to invalid CRC checksum.
135    fn read(&mut self, bytes: usize) -> ResultComm<Vec<u8>>;
136
137    /// Write raw packet data to the device
138    ///
139    /// # Arguments
140    /// * `data` - Raw packet bytes to send
141    ///
142    /// # Returns
143    /// A Result indicating success or error
144    ///
145    /// # Errors
146    /// Any errors that occured while writing, from being unable to write to invalid CRC checksum.
147    fn write_packet_raw(&mut self, data: &[u8]) -> ResultComm<()>;
148
149    /// Read a raw packet with specific type code
150    ///
151    /// # Arguments
152    /// * `packet_code` - Expected packet type code
153    ///
154    /// # Returns
155    /// A Result containing the packet payload or an error
156    ///
157    /// # Errors
158    /// Any errors that occured while reading, from being unable to read to invalid CRC checksum.
159    fn read_packet_raw(&mut self, packet_code: u8) -> ResultComm<Vec<u8>>;
160
161    /// Write a strongly-typed packet to the device
162    ///
163    /// This method handles packet construction and transmission for any type
164    /// that implements the required packet traits.
165    ///
166    /// # Arguments
167    /// * `packet` - The packet to send
168    ///
169    /// # Returns
170    /// A Result indicating success or error
171    ///
172    /// # Errors
173    /// Any errors that occured while writing, from being unable to write to invalid CRC checksum.
174    fn write_packet_concrete<T>(&mut self, packet: T) -> ResultComm<()>
175    where
176        T: PacketConstruct + Packet,
177    {
178        self.write_packet_raw(&packet.construct())
179    }
180
181    /// Read a strongly-typed packet from the device
182    ///
183    /// This method handles packet reception and parsing for any type
184    /// that implements the required packet traits.
185    ///
186    /// # Returns
187    /// A Result containing the parsed packet or an error
188    ///
189    /// # Errors
190    /// Any errors that occured while reading, from being unable to read to invalid CRC checksum.
191    fn read_packet_concrete<T>(&mut self) -> ResultComm<T>
192    where
193        T: PacketParse + Packet,
194    {
195        let data_slice = self.read_packet_raw(T::get_code())?;
196        T::parse(&data_slice)
197    }
198}
199
200/// Trait for opening protocol connections
201pub trait ProtocolOpen: Protocol {
202    /// Open a protocol connection with basic identifier
203    ///
204    /// # Arguments
205    /// * `identifier` - Connection identifier (e.g., COM port, device path)
206    ///
207    /// # Returns
208    /// A Result containing the opened protocol instance or an error
209    ///
210    /// # Errors
211    /// Any error raised by the specific protocol library, mostly informing that the selected device does not exist.
212    fn open(identifier: &str) -> ResultComm<Self>
213    where
214        Self: Sized;
215
216    /// Open a protocol connection with advanced options
217    ///
218    /// # Arguments
219    /// * `identifier` - Connection identifier
220    /// * `baudrate` - Communication baudrate (protocol-specific)
221    /// * `timeout` - Operation timeout duration
222    /// * `polling_interval` - Response polling interval
223    ///
224    /// # Returns
225    /// A Result containing the opened protocol instance or an error
226    ///
227    /// # Errors
228    /// Any error raised by the specific protocol library, mostly informing that the selected device does not exist.
229    ///
230    /// # Note
231    /// Default implementation ignores advanced options and calls basic `open()`
232    #[expect(
233        unused_variables,
234        reason = "rust-analyzer would show the underscores for inlay hints"
235    )]
236    fn open_with_options(
237        identifier: &str,
238        baudrate: u32,
239        timeout: Duration,
240        polling_interval: Duration,
241    ) -> ResultComm<Self>
242    where
243        Self: Sized,
244    {
245        Self::open(identifier)
246    }
247}
248
249// Define a protocol enum that can be used instead of dyn Protocol
250#[cfg(any(feature = "c_api", feature = "python"))]
251pub mod protocol_impl;
252
253// Protocol acknowledgment constants as defined by McuBoot specification
254/// Positive acknowledgment - command accepted
255const ACK: u8 = 0xA1;
256/// Negative acknowledgment - command rejected
257const NACK: u8 = 0xA2;
258/// Abort acknowledgment - operation aborted
259const ACK_ABORT: u8 = 0xA3;