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;