Skip to main content

usbpd/protocol_layer/message/
mod.rs

1//! Definitions of message content.
2
3pub mod data;
4pub mod extended;
5pub mod header;
6
7#[cfg(test)]
8mod epr_messages_test;
9
10use byteorder::{ByteOrder, LittleEndian};
11use header::{Header, MessageType};
12
13use crate::protocol_layer::message::extended::ExtendedHeader;
14
15/// Errors that can occur during message/header parsing.
16#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub enum ParseError {
19    /// The input buffer has an invalid length.
20    /// * `expected` - The expected length.
21    /// * `found` - The actual length found.
22    #[error("invalid input buffer length (expected {expected:?}, found {found:?})")]
23    InvalidLength {
24        /// The expected length.
25        expected: usize,
26        /// The actual length found.
27        found: usize,
28    },
29    /// The specification revision field is not supported.
30    #[error("unsupported specification revision `{0}`")]
31    UnsupportedSpecificationRevision(u8),
32    /// An unknown or reserved message type was encountered.
33    #[error("unknown or reserved message type `{0}`")]
34    InvalidMessageType(u8),
35    /// An unknown or reserved data message type was encountered.
36    #[error("unknown or reserved data message type `{0}`")]
37    InvalidDataMessageType(u8),
38    /// An unknown or reserved control message type was encountered.
39    #[error("unknown or reserved control message type `{0}`")]
40    InvalidControlMessageType(u8),
41    /// Received a chunked extended message that requires assembly.
42    /// Use `ChunkedMessageAssembler` to handle these messages.
43    #[error("chunked extended message (chunk {chunk_number}, total size {data_size})")]
44    ChunkedExtendedMessage {
45        /// The chunk number (0 = first chunk).
46        chunk_number: u8,
47        /// Total data size across all chunks.
48        data_size: u16,
49        /// Whether this is a chunk request.
50        request_chunk: bool,
51        /// The extended message type.
52        message_type: header::ExtendedMessageType,
53    },
54    /// Received a chunk larger than the maximum allowed size (26 bytes).
55    #[error("chunk size {0} exceeds maximum {1}")]
56    ChunkOverflow(usize, usize),
57    /// Attempt to reuse a ChunkedMessageAssembler that is already processing a message.
58    /// The user must create a new assembler or explicitly call reset() first.
59    #[error("parser already in use, create a new assembler or call reset()")]
60    ParserReuse,
61    /// Other parsing error with a message.
62    #[error("other parse error: {0}")]
63    Other(&'static str),
64}
65
66/// Payload of a USB PD message, if any.
67#[derive(Debug, Clone)]
68#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub enum Payload {
71    /// Payload for a data message.
72    Data(data::Data),
73    /// Payload for an extended message.
74    Extended(extended::Extended),
75}
76
77/// A USB PD message.
78#[derive(Debug, Clone)]
79#[cfg_attr(feature = "defmt", derive(defmt::Format))]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct Message {
82    /// The message header.
83    pub header: Header,
84    /// Optional payload for  messages.
85    pub payload: Option<Payload>,
86}
87
88impl Message {
89    /// Create a new message from a message header.
90    pub fn new(header: Header) -> Self {
91        Self { header, payload: None }
92    }
93
94    /// Create a new message from a message header and payload data.
95    pub fn new_with_data(header: Header, data: data::Data) -> Self {
96        Self {
97            header,
98            payload: Some(Payload::Data(data)),
99        }
100    }
101
102    /// Serialize a message to a slice, returning the number of written bytes.
103    pub fn to_bytes(&self, buffer: &mut [u8]) -> usize {
104        let header_len = self.header.to_bytes(buffer);
105
106        match self.payload.as_ref() {
107            Some(Payload::Data(data)) => header_len + data.to_bytes(&mut buffer[header_len..]),
108            Some(Payload::Extended(extended)) => {
109                // Per USB PD spec 6.2.1.2.1: use chunked mode for compatibility with more PHYs.
110                // Most power supplies don't support unchunked extended messages.
111                let extended_header = ExtendedHeader::new(extended.data_size())
112                    .with_chunked(true)
113                    .with_chunk_number(0);
114                let ext_header_len = extended_header.to_bytes(&mut buffer[header_len..]);
115                header_len + ext_header_len + extended.to_bytes(&mut buffer[header_len + ext_header_len..])
116            }
117            None => header_len,
118        }
119    }
120
121    /// Parse assembled extended message payload into an Extended enum.
122    ///
123    /// This is used after `ChunkedMessageAssembler` has assembled all chunks.
124    ///
125    /// # Arguments
126    /// * `message_type` - The extended message type
127    /// * `payload` - The complete assembled payload data
128    pub fn parse_extended_payload(message_type: header::ExtendedMessageType, payload: &[u8]) -> extended::Extended {
129        match message_type {
130            header::ExtendedMessageType::ExtendedControl => {
131                if payload.len() >= 2 {
132                    extended::Extended::ExtendedControl(extended::extended_control::ExtendedControl(
133                        LittleEndian::read_u16(payload),
134                    ))
135                } else {
136                    extended::Extended::Unknown
137                }
138            }
139            header::ExtendedMessageType::EprSourceCapabilities => extended::Extended::EprSourceCapabilities(
140                payload
141                    .chunks_exact(4)
142                    .map(|buf| {
143                        crate::protocol_layer::message::data::source_capabilities::parse_raw_pdo(
144                            LittleEndian::read_u32(buf),
145                        )
146                    })
147                    .collect(),
148            ),
149            _ => extended::Extended::Unknown,
150        }
151    }
152
153    /// Parse an extended message chunk, returning the header info and chunk data.
154    ///
155    /// This is used for handling chunked extended messages when `from_bytes`
156    /// returns `ParseError::ChunkedExtendedMessage`.
157    ///
158    /// Returns (Header, ExtendedHeader, chunk_payload_data).
159    pub fn parse_extended_chunk(data: &[u8]) -> Result<(Header, ExtendedHeader, &[u8]), ParseError> {
160        if data.len() < 4 {
161            return Err(ParseError::InvalidLength {
162                expected: 4,
163                found: data.len(),
164            });
165        }
166
167        let header = Header::from_bytes(&data[..2])?;
168        let ext_header = ExtendedHeader::from_bytes(&data[2..]);
169
170        // Chunk payload starts after headers (2 + 2 = 4 bytes)
171        let chunk_payload = &data[4..];
172
173        Ok((header, ext_header, chunk_payload))
174    }
175
176    /// Parse a message from a slice of bytes.
177    pub fn from_bytes(data: &[u8]) -> Result<Self, ParseError> {
178        let header = Header::from_bytes(&data[..2])?;
179        let message = Self::new(header);
180        let payload = &data[2..];
181
182        match message.header.message_type() {
183            MessageType::Control(_) => Ok(message),
184            MessageType::Extended(message_type) => {
185                let ext_header = ExtendedHeader::from_bytes(payload);
186                let data_size = ext_header.data_size() as usize;
187
188                // Check if this is a true multi-chunk message that needs assembly
189                // Single-chunk messages (chunk 0 with all data present) can be parsed directly
190                if ext_header.chunked() {
191                    let is_chunk_request = ext_header.request_chunk();
192                    let chunk_number = ext_header.chunk_number();
193                    let available_payload = payload.len().saturating_sub(2);
194
195                    // Multi-chunk required if:
196                    // - This is a chunk request, OR
197                    // - Chunk number > 0 (continuation chunk), OR
198                    // - Data size exceeds what's available in this chunk
199                    let needs_assembly = is_chunk_request || chunk_number > 0 || data_size > available_payload;
200
201                    if needs_assembly {
202                        return Err(ParseError::ChunkedExtendedMessage {
203                            chunk_number,
204                            data_size: ext_header.data_size(),
205                            request_chunk: is_chunk_request,
206                            message_type,
207                        });
208                    }
209                    // Otherwise, it's a single-chunk message - parse normally
210                }
211                if payload.len() < 2 + data_size {
212                    return Err(ParseError::InvalidLength {
213                        expected: 2 + data_size,
214                        found: payload.len(),
215                    });
216                }
217
218                let payload_bytes = &payload[2..2 + data_size];
219                Ok(Self {
220                    payload: Some(Payload::Extended(match message_type {
221                        header::ExtendedMessageType::ExtendedControl => extended::Extended::ExtendedControl(
222                            extended::extended_control::ExtendedControl(LittleEndian::read_u16(payload_bytes)),
223                        ),
224                        header::ExtendedMessageType::EprSourceCapabilities => {
225                            extended::Extended::EprSourceCapabilities(
226                                payload_bytes
227                                    .chunks_exact(4)
228                                    .map(|buf| {
229                                        crate::protocol_layer::message::data::source_capabilities::parse_raw_pdo(
230                                            LittleEndian::read_u32(buf),
231                                        )
232                                    })
233                                    .collect(),
234                            )
235                        }
236                        _ => extended::Extended::Unknown,
237                    })),
238                    ..message
239                })
240            }
241            MessageType::Data(message_type) => data::Data::parse_message(message, message_type, payload, &()),
242        }
243    }
244}