pldm/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2/*
3 * PLDM base message definitions.
4 *
5 * Copyright (c) 2023 Code Construct
6 */
7#![cfg_attr(not(any(feature = "std", test)), no_std)]
8#![forbid(unsafe_code)]
9#![warn(missing_docs)]
10
11//! Platform Level Data Model (PLDM) base protocol support
12//!
13//! This crate implements some base communication primitives for PLDM,
14//! used to construct higher-level PLDM messaging applications.
15
16use core::fmt::{self, Debug};
17
18use mctp::MsgIC;
19
20pub mod util;
21use util::*;
22
23/// Maximum size of a PLDM message, defining our buffer sizes.
24///
25/// The `pldm` crate currently has a maximum message size.
26pub const PLDM_MAX_MSGSIZE: usize = 1024;
27
28/// Generic PLDM error type
29#[derive(Debug)]
30pub enum PldmError {
31    /// PLDM protocol error
32    Protocol(ErrStr),
33    /// MCTP communication error
34    Mctp(mctp::Error),
35    /// Invalid argument
36    InvalidArgument,
37    /// No buffer space available
38    NoSpace,
39}
40
41impl core::fmt::Display for PldmError {
42    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43        match self {
44            Self::Protocol(s) => write!(f, "PLDM protocol error: {s}"),
45            Self::Mctp(s) => write!(f, "MCTP error: {s}"),
46            Self::InvalidArgument => write!(f, "Invalid Argument"),
47            Self::NoSpace => write!(f, "Insufficient buffer space available"),
48        }
49    }
50}
51
52#[cfg(feature = "std")]
53impl std::error::Error for PldmError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        match self {
56            Self::Mctp(s) => Some(s),
57            _ => None,
58        }
59    }
60}
61
62impl From<mctp::Error> for PldmError {
63    fn from(e: mctp::Error) -> PldmError {
64        PldmError::Mctp(e)
65    }
66}
67
68#[cfg(feature = "alloc")]
69type ErrStr = String;
70#[cfg(not(feature = "alloc"))]
71type ErrStr = &'static str;
72
73/// Create a `PldmError::Protocol` from a message and optional description.
74///
75/// When building without `alloc` feature only the message is kept.
76///
77/// Example
78///
79/// ```
80/// # let iid = 1;
81/// # let actual_iid = 2;
82/// use pldm::proto_error;
83/// proto_error!("Mismatching IID", "Expected {iid:02x}, received {actual_iid:02x}");
84/// proto_error!("Rq bit wasn't expected");
85/// ```
86#[macro_export]
87#[cfg(feature = "alloc")]
88macro_rules! proto_error {
89    ($msg: expr, $desc_str: expr) => {
90        $crate::PldmError::Protocol(format!("{}. {}", $msg, $desc_str))
91    };
92    ($msg: expr) => {
93        $crate::PldmError::Protocol(format!("{}.", $msg))
94    };
95}
96
97/// Create a `PldmError::Protocol` from a message and optional description.
98///
99/// When building without `alloc` feature only the message is kept.
100///
101/// Example
102///
103/// ```
104/// # let iid = 1;
105/// # let actual_iid = 2;
106/// use pldm::proto_error;
107/// proto_error!("Mismatching IID", "Expected {iid:02x}, received {actual_iid:02x}");
108/// proto_error!("Rq bit wasn't expected");
109/// ```
110#[macro_export]
111#[cfg(not(feature = "alloc"))]
112macro_rules! proto_error {
113    ($msg: expr, $desc_str: expr) => {
114        $crate::PldmError::Protocol($msg)
115    };
116    ($msg: expr) => {
117        $crate::PldmError::Protocol($msg)
118    };
119}
120
121/// PLDM protocol return type
122pub type Result<T> = core::result::Result<T, PldmError>;
123
124#[allow(missing_docs)]
125#[repr(u8)]
126#[allow(non_camel_case_types)]
127#[derive(Debug, PartialEq)]
128pub enum CCode {
129    SUCCESS = 0,
130    ERROR = 1,
131    ERROR_INVALID_DATA = 2,
132    ERROR_INVALID_LENGTH = 3,
133    ERROR_NOT_READY = 4,
134    ERROR_UNSUPPORTED_PLDM_CMD = 5,
135    ERROR_INVALID_PLDM_TYPE = 32,
136}
137
138/// Base PLDM request type
139pub struct PldmRequest<'a> {
140    /// PLDM Instance ID
141    pub iid: u8,
142    /// PLDM type.
143    pub typ: u8,
144    /// PLDM command code
145    pub cmd: u8,
146    /// PLDM command data payload
147    pub data: VecOrSlice<'a, u8>,
148}
149
150impl<'a> Debug for PldmRequest<'a> {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        let vs = match self.data {
153            #[cfg(feature = "alloc")]
154            VecOrSlice::Owned(_) => "Owned",
155            VecOrSlice::Borrowed(_) => "Borrowed",
156        };
157        f.debug_struct("PldmRequest")
158            .field("iid", &self.iid)
159            .field("typ", &self.typ)
160            .field("cmd", &self.cmd)
161            .field("data.len()", &self.data.len())
162            .field("data..10", &&self.data[..self.data.len().min(10)])
163            .field("storage", &vs)
164            .finish()
165    }
166}
167
168#[cfg(feature = "alloc")]
169impl<'a> PldmRequest<'a> {
170    /// Converts any `PldmRequest` into one with allocated storage
171    pub fn make_owned(self) -> PldmRequest<'static> {
172        let d = match self.data {
173            VecOrSlice::Borrowed(b) => b.to_vec().into(),
174            VecOrSlice::Owned(b) => VecOrSlice::Owned(b),
175        };
176        PldmRequest { data: d, ..self }
177    }
178
179    /// Set the data payload for this request
180    pub fn set_data(&mut self, data: Vec<u8>) {
181        self.data = data.into()
182    }
183
184    /// Create a new PLDM request for a given PLDM message type and command
185    ///
186    /// Convert this request to a response, using the instance, type and command
187    /// from the original request.
188    pub fn response(&self) -> PldmResponse<'_> {
189        PldmResponse {
190            iid: self.iid,
191            typ: self.typ,
192            cmd: self.cmd,
193            cc: 0,
194            data: Vec::new().into(),
195        }
196    }
197}
198
199// Constructors for allocated requests
200#[cfg(feature = "alloc")]
201impl PldmRequest<'static> {
202    /// Create a new PLDM request for a given PLDM message type and command
203    /// number.
204    pub fn new(typ: u8, cmd: u8) -> Self {
205        Self::new_data(typ, cmd, Vec::new())
206    }
207
208    /// Create a new PLDM request with a data payload.
209    pub fn new_data(typ: u8, cmd: u8, data: Vec<u8>) -> Self {
210        Self {
211            iid: 0,
212            typ,
213            cmd,
214            data: data.into(),
215        }
216    }
217
218    /// Create a PLDM request from message data.
219    ///
220    /// May fail if the message data is not parsable as a PLDM message.
221    pub fn from_buf<'f>(data: &'f [u8]) -> Result<Self> {
222        PldmRequest::from_buf_borrowed(data).map(|p| p.make_owned())
223    }
224}
225
226impl<'a> PldmRequest<'a> {
227    /// Create a new PLDM request with a data payload borrowed from a slice.
228    pub fn new_borrowed(typ: u8, cmd: u8, data: &'a [u8]) -> Self {
229        Self {
230            iid: 0,
231            typ,
232            cmd,
233            data: data.into(),
234        }
235    }
236
237    /// Create a PLDM request from message data.
238    ///
239    /// The payload is borrowed from the input data.
240    /// May fail if the message data is not parsable as a PLDM message.
241    pub fn from_buf_borrowed(data: &'a [u8]) -> Result<PldmRequest<'a>> {
242        if data.len() < 3 {
243            return Err(proto_error!(
244                "Short request",
245                format!("{} bytes", data.len())
246            ));
247        }
248
249        let rq = (data[0] & 0x80) != 0;
250        let iid = data[0] & 0x1f;
251        let typ = data[1] & 0x3f;
252        let cmd = data[2];
253
254        if !rq {
255            return Err(proto_error!("PLDM response, expected request"));
256        }
257
258        Ok(PldmRequest {
259            iid,
260            typ,
261            cmd,
262            data: (&data[3..]).into(),
263        })
264    }
265
266    /// Create a new PLDM response for a request
267    ///
268    /// Convert this request to a response, using the instance, type and command
269    /// from the original request.
270    ///
271    /// The payload buffer is borrowed from input.
272    pub fn response_borrowed<'f>(&self, data: &'f [u8]) -> PldmResponse<'f> {
273        PldmResponse {
274            iid: self.iid,
275            typ: self.typ,
276            cmd: self.cmd,
277            cc: 0,
278            data: data.into(),
279        }
280    }
281}
282
283/// Base PLDM response type
284pub struct PldmResponse<'a> {
285    /// PLDM Instance ID
286    pub iid: u8,
287    /// PLDM type
288    pub typ: u8,
289    /// PLDM command code (defined by the original request)
290    pub cmd: u8,
291    /// PLDM completion code
292    pub cc: u8,
293    /// PLDM response data payload. Does not include the cc field.
294    pub data: VecOrSlice<'a, u8>,
295}
296
297impl<'a> Debug for PldmResponse<'a> {
298    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299        let vs = match self.data {
300            #[cfg(feature = "alloc")]
301            VecOrSlice::Owned(_) => "Owned",
302            VecOrSlice::Borrowed(_) => "Borrowed",
303        };
304        f.debug_struct("PldmResponse")
305            .field("iid", &self.iid)
306            .field("typ", &self.typ)
307            .field("cmd", &self.cmd)
308            .field("cc", &self.cc)
309            .field("data.len()", &self.data.len())
310            .field("data..10", &&self.data[..self.data.len().min(10)])
311            .field("storage", &vs)
312            .finish()
313    }
314}
315
316#[cfg(feature = "alloc")]
317impl<'a> PldmResponse<'a> {
318    /// Set the data payload for this response
319    pub fn set_data(&mut self, data: Vec<u8>) {
320        self.data = data.into()
321    }
322
323    /// Converts any `PldmResponse` into one with allocated storage
324    pub fn make_owned(self) -> PldmResponse<'static> {
325        let d = match self.data {
326            VecOrSlice::Borrowed(b) => b.to_vec().into(),
327            VecOrSlice::Owned(b) => VecOrSlice::Owned(b),
328        };
329        PldmResponse { data: d, ..self }
330    }
331}
332
333impl<'a> PldmResponse<'a> {
334    /// Create a `PldmResponse` from a payload
335    pub fn from_buf_borrowed(rx_buf: &'a [u8]) -> Result<Self> {
336        if rx_buf.len() < 4 {
337            return Err(proto_error!(
338                "Short response",
339                format!("{} bytes", rx_buf.len())
340            ));
341        }
342
343        let rq = (rx_buf[0] & 0x80) != 0;
344        let iid = rx_buf[0] & 0x1f;
345        let typ = rx_buf[1] & 0x3f;
346        let cmd = rx_buf[2];
347        let cc = rx_buf[3];
348
349        if rq {
350            return Err(proto_error!("PLDM request, expected response"));
351        }
352
353        let rsp = PldmResponse {
354            iid,
355            typ,
356            cmd,
357            cc,
358            data: (&rx_buf[4..]).into(),
359        };
360
361        Ok(rsp)
362    }
363}
364
365/// Represents either a PLDM request or response message
366#[derive(Debug)]
367pub enum PldmMessage<'a> {
368    /// A PLDM Request
369    Request(PldmRequest<'a>),
370    /// A PLDM Response
371    Response(PldmResponse<'a>),
372}
373
374/// Main PLDM transfer operation.
375///
376/// Sends a Request, and waits for a response, blocking. This is generally
377/// used by PLDM Requesters, which issue commands to Responders.
378#[cfg(feature = "alloc")]
379pub fn pldm_xfer(
380    ep: &mut impl mctp::ReqChannel,
381    req: PldmRequest,
382) -> Result<PldmResponse<'static>> {
383    let mut rx_buf = [0u8; PLDM_MAX_MSGSIZE]; // todo: set size? peek?
384    pldm_xfer_buf(ep, req, &mut rx_buf).map(|r| r.make_owned())
385}
386
387/// Main PLDM transfer operation.
388///
389/// Sends a Request, and waits for a response, blocking. This is generally
390/// used by PLDM Requesters, which issue commands to Responders.
391///
392/// This function requires an external `rx_buf`.
393pub fn pldm_xfer_buf<'buf>(
394    ep: &mut impl mctp::ReqChannel,
395    mut req: PldmRequest,
396    rx_buf: &'buf mut [u8],
397) -> Result<PldmResponse<'buf>> {
398    pldm_tx_req(ep, &mut req)?;
399
400    let rsp = pldm_rx_resp_borrowed(ep, rx_buf)?;
401
402    if rsp.iid != req.iid {
403        return Err(proto_error!(
404            "Incorrect instance ID in reply",
405            format!("Expected 0x{:02x} got 0x{:02x}", req.iid, rsp.iid)
406        ));
407    }
408
409    if rsp.typ != req.typ {
410        return Err(proto_error!(
411            "Incorrect PLDM type in reply",
412            format!("Expected 0x{:02x} got 0x{:02x}", req.typ, rsp.typ)
413        ));
414    }
415
416    if rsp.cmd != req.cmd {
417        return Err(proto_error!(
418            "Incorrect PLDM command in reply",
419            format!("Expected 0x{:02x} got 0x{:02x}", req.cmd, rsp.cmd)
420        ));
421    }
422
423    Ok(rsp)
424}
425
426/// Receive an incoming PLDM request.
427///
428/// This uses [`mctp::Listener::recv`], which performs a blocking wait for
429/// incoming messages.
430/// The listener should be listening on the PLDM message type.
431///
432/// Responder implementations will typically want to respond via
433/// [`pldm_tx_resp`].
434#[cfg(feature = "alloc")]
435pub fn pldm_rx_req<'lis, L>(
436    listener: &'lis mut L,
437) -> Result<(PldmRequest<'static>, L::RespChannel<'lis>)>
438where
439    L: mctp::Listener,
440{
441    let mut rx_buf = [0u8; PLDM_MAX_MSGSIZE]; // todo: set size? peek?
442    let (req, ep) = pldm_rx_req_borrowed(listener, &mut rx_buf)?;
443    Ok((req.make_owned(), ep))
444}
445
446/// Receive an incoming PLDM request in a borrowed buffer.
447///
448/// This uses [`mctp::Listener::recv`], which performs a blocking wait for
449/// incoming messages.
450/// The listener should be listening on the PLDM message type.
451///
452/// Responder implementations will typically want to respond via
453/// [`pldm_tx_resp`].
454pub fn pldm_rx_req_borrowed<'lis, 'buf, L>(
455    listener: &'lis mut L,
456    rx_buf: &'buf mut [u8],
457) -> Result<(PldmRequest<'buf>, L::RespChannel<'lis>)>
458where
459    L: mctp::Listener,
460{
461    let (typ, ic, rx_buf, ep) = listener.recv(rx_buf)?;
462    if ic.0 {
463        return Err(proto_error!("IC bit set"));
464    }
465    if typ != mctp::MCTP_TYPE_PLDM {
466        // Not a PLDM listener?
467        return Err(PldmError::InvalidArgument);
468    }
469    let req = PldmRequest::from_buf_borrowed(rx_buf)?;
470
471    Ok((req, ep))
472}
473
474/// Receive an incoming PLDM response in a borrowed buffer.
475pub fn pldm_rx_resp_borrowed<'buf>(
476    ep: &mut impl mctp::ReqChannel,
477    rx_buf: &'buf mut [u8],
478) -> Result<PldmResponse<'buf>> {
479    let (_typ, ic, rx_buf) = ep.recv(rx_buf)?;
480    if ic.0 {
481        return Err(proto_error!("IC bit set"));
482    }
483    PldmResponse::from_buf_borrowed(rx_buf)
484}
485
486/// Transmit an outgoing PLDM response
487///
488/// Performs a blocking send on the specified ep.
489pub fn pldm_tx_resp(
490    ep: &mut impl mctp::RespChannel,
491    resp: &PldmResponse,
492) -> Result<()> {
493    let tx_buf = [resp.iid, resp.typ, resp.cmd, resp.cc];
494    let txs = &[&tx_buf, resp.data.as_ref()];
495    ep.send_vectored(MsgIC(false), txs)?;
496    Ok(())
497}
498
499/// Transmit an outgoing PLDM request
500///
501/// Performs a blocking send on the specified ep. The iid will be
502/// updated in `req`.
503pub fn pldm_tx_req(
504    ep: &mut impl mctp::ReqChannel,
505    req: &mut PldmRequest,
506) -> Result<()> {
507    // TODO IID allocation
508    const REQ_IID: u8 = 0;
509    req.iid = REQ_IID;
510
511    let tx_buf = [1 << 7 | req.iid, req.typ & 0x3f, req.cmd];
512
513    let txs = &[&tx_buf, req.data.as_ref()];
514    ep.send_vectored(mctp::MCTP_TYPE_PLDM, MsgIC(false), txs)?;
515    Ok(())
516}