#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use core::fmt::{self, Debug};
use mctp::MsgIC;
pub mod util;
use util::*;
pub const PLDM_MAX_MSGSIZE: usize = 1024;
#[derive(Debug)]
pub enum PldmError {
Protocol(ErrStr),
Mctp(mctp::Error),
InvalidArgument,
NoSpace,
}
impl core::fmt::Display for PldmError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Protocol(s) => write!(f, "PLDM protocol error: {s}"),
Self::Mctp(s) => write!(f, "MCTP error: {s}"),
Self::InvalidArgument => write!(f, "Invalid Argument"),
Self::NoSpace => write!(f, "Insufficient buffer space available"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PldmError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Mctp(s) => Some(s),
_ => None,
}
}
}
impl From<mctp::Error> for PldmError {
fn from(e: mctp::Error) -> PldmError {
PldmError::Mctp(e)
}
}
#[cfg(feature = "alloc")]
type ErrStr = String;
#[cfg(not(feature = "alloc"))]
type ErrStr = &'static str;
#[macro_export]
#[cfg(feature = "alloc")]
macro_rules! proto_error {
($msg: expr, $desc_str: expr) => {
$crate::PldmError::Protocol(format!("{}. {}", $msg, $desc_str))
};
($msg: expr) => {
$crate::PldmError::Protocol(format!("{}.", $msg))
};
}
#[macro_export]
#[cfg(not(feature = "alloc"))]
macro_rules! proto_error {
($msg: expr, $desc_str: expr) => {
$crate::PldmError::Protocol($msg)
};
($msg: expr) => {
$crate::PldmError::Protocol($msg)
};
}
pub type Result<T> = core::result::Result<T, PldmError>;
#[allow(missing_docs)]
#[repr(u8)]
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq)]
pub enum CCode {
SUCCESS = 0,
ERROR = 1,
ERROR_INVALID_DATA = 2,
ERROR_INVALID_LENGTH = 3,
ERROR_NOT_READY = 4,
ERROR_UNSUPPORTED_PLDM_CMD = 5,
ERROR_INVALID_PLDM_TYPE = 32,
}
pub struct PldmRequest<'a> {
pub iid: u8,
pub typ: u8,
pub cmd: u8,
pub data: VecOrSlice<'a, u8>,
}
impl<'a> Debug for PldmRequest<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let vs = match self.data {
#[cfg(feature = "alloc")]
VecOrSlice::Owned(_) => "Owned",
VecOrSlice::Borrowed(_) => "Borrowed",
};
f.debug_struct("PldmRequest")
.field("iid", &self.iid)
.field("typ", &self.typ)
.field("cmd", &self.cmd)
.field("data.len()", &self.data.len())
.field("data..10", &&self.data[..self.data.len().min(10)])
.field("storage", &vs)
.finish()
}
}
#[cfg(feature = "alloc")]
impl<'a> PldmRequest<'a> {
pub fn make_owned(self) -> PldmRequest<'static> {
let d = match self.data {
VecOrSlice::Borrowed(b) => b.to_vec().into(),
VecOrSlice::Owned(b) => VecOrSlice::Owned(b),
};
PldmRequest { data: d, ..self }
}
pub fn set_data(&mut self, data: Vec<u8>) {
self.data = data.into()
}
pub fn response(&self) -> PldmResponse<'_> {
PldmResponse {
iid: self.iid,
typ: self.typ,
cmd: self.cmd,
cc: 0,
data: Vec::new().into(),
}
}
}
#[cfg(feature = "alloc")]
impl PldmRequest<'static> {
pub fn new(typ: u8, cmd: u8) -> Self {
Self::new_data(typ, cmd, Vec::new())
}
pub fn new_data(typ: u8, cmd: u8, data: Vec<u8>) -> Self {
Self {
iid: 0,
typ,
cmd,
data: data.into(),
}
}
pub fn from_buf<'f>(data: &'f [u8]) -> Result<Self> {
PldmRequest::from_buf_borrowed(data).map(|p| p.make_owned())
}
}
impl<'a> PldmRequest<'a> {
pub fn new_borrowed(typ: u8, cmd: u8, data: &'a [u8]) -> Self {
Self {
iid: 0,
typ,
cmd,
data: data.into(),
}
}
pub fn from_buf_borrowed(data: &'a [u8]) -> Result<PldmRequest<'a>> {
if data.len() < 3 {
return Err(proto_error!(
"Short request",
format!("{} bytes", data.len())
));
}
let rq = (data[0] & 0x80) != 0;
let iid = data[0] & 0x1f;
let typ = data[1] & 0x3f;
let cmd = data[2];
if !rq {
return Err(proto_error!("PLDM response, expected request"));
}
Ok(PldmRequest {
iid,
typ,
cmd,
data: (&data[3..]).into(),
})
}
pub fn response_borrowed<'f>(&self, data: &'f [u8]) -> PldmResponse<'f> {
PldmResponse {
iid: self.iid,
typ: self.typ,
cmd: self.cmd,
cc: 0,
data: data.into(),
}
}
}
pub struct PldmResponse<'a> {
pub iid: u8,
pub typ: u8,
pub cmd: u8,
pub cc: u8,
pub data: VecOrSlice<'a, u8>,
}
impl<'a> Debug for PldmResponse<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let vs = match self.data {
#[cfg(feature = "alloc")]
VecOrSlice::Owned(_) => "Owned",
VecOrSlice::Borrowed(_) => "Borrowed",
};
f.debug_struct("PldmResponse")
.field("iid", &self.iid)
.field("typ", &self.typ)
.field("cmd", &self.cmd)
.field("cc", &self.cc)
.field("data.len()", &self.data.len())
.field("data..10", &&self.data[..self.data.len().min(10)])
.field("storage", &vs)
.finish()
}
}
#[cfg(feature = "alloc")]
impl<'a> PldmResponse<'a> {
pub fn set_data(&mut self, data: Vec<u8>) {
self.data = data.into()
}
pub fn make_owned(self) -> PldmResponse<'static> {
let d = match self.data {
VecOrSlice::Borrowed(b) => b.to_vec().into(),
VecOrSlice::Owned(b) => VecOrSlice::Owned(b),
};
PldmResponse { data: d, ..self }
}
}
impl<'a> PldmResponse<'a> {
pub fn from_buf_borrowed(rx_buf: &'a [u8]) -> Result<Self> {
if rx_buf.len() < 4 {
return Err(proto_error!(
"Short response",
format!("{} bytes", rx_buf.len())
));
}
let rq = (rx_buf[0] & 0x80) != 0;
let iid = rx_buf[0] & 0x1f;
let typ = rx_buf[1] & 0x3f;
let cmd = rx_buf[2];
let cc = rx_buf[3];
if rq {
return Err(proto_error!("PLDM request, expected response"));
}
let rsp = PldmResponse {
iid,
typ,
cmd,
cc,
data: (&rx_buf[4..]).into(),
};
Ok(rsp)
}
}
#[derive(Debug)]
pub enum PldmMessage<'a> {
Request(PldmRequest<'a>),
Response(PldmResponse<'a>),
}
#[cfg(feature = "alloc")]
pub fn pldm_xfer(
ep: &mut impl mctp::ReqChannel,
req: PldmRequest,
) -> Result<PldmResponse<'static>> {
let mut rx_buf = [0u8; PLDM_MAX_MSGSIZE]; pldm_xfer_buf(ep, req, &mut rx_buf).map(|r| r.make_owned())
}
pub fn pldm_xfer_buf<'buf>(
ep: &mut impl mctp::ReqChannel,
mut req: PldmRequest,
rx_buf: &'buf mut [u8],
) -> Result<PldmResponse<'buf>> {
pldm_tx_req(ep, &mut req)?;
let rsp = pldm_rx_resp_borrowed(ep, rx_buf)?;
if rsp.iid != req.iid {
return Err(proto_error!(
"Incorrect instance ID in reply",
format!("Expected 0x{:02x} got 0x{:02x}", req.iid, rsp.iid)
));
}
if rsp.typ != req.typ {
return Err(proto_error!(
"Incorrect PLDM type in reply",
format!("Expected 0x{:02x} got 0x{:02x}", req.typ, rsp.typ)
));
}
if rsp.cmd != req.cmd {
return Err(proto_error!(
"Incorrect PLDM command in reply",
format!("Expected 0x{:02x} got 0x{:02x}", req.cmd, rsp.cmd)
));
}
Ok(rsp)
}
#[cfg(feature = "alloc")]
pub fn pldm_rx_req<'lis, L>(
listener: &'lis mut L,
) -> Result<(PldmRequest<'static>, L::RespChannel<'lis>)>
where
L: mctp::Listener,
{
let mut rx_buf = [0u8; PLDM_MAX_MSGSIZE]; let (req, ep) = pldm_rx_req_borrowed(listener, &mut rx_buf)?;
Ok((req.make_owned(), ep))
}
pub fn pldm_rx_req_borrowed<'lis, 'buf, L>(
listener: &'lis mut L,
rx_buf: &'buf mut [u8],
) -> Result<(PldmRequest<'buf>, L::RespChannel<'lis>)>
where
L: mctp::Listener,
{
let (typ, ic, rx_buf, ep) = listener.recv(rx_buf)?;
if ic.0 {
return Err(proto_error!("IC bit set"));
}
if typ != mctp::MCTP_TYPE_PLDM {
return Err(PldmError::InvalidArgument);
}
let req = PldmRequest::from_buf_borrowed(rx_buf)?;
Ok((req, ep))
}
pub fn pldm_rx_resp_borrowed<'buf>(
ep: &mut impl mctp::ReqChannel,
rx_buf: &'buf mut [u8],
) -> Result<PldmResponse<'buf>> {
let (_typ, ic, rx_buf) = ep.recv(rx_buf)?;
if ic.0 {
return Err(proto_error!("IC bit set"));
}
PldmResponse::from_buf_borrowed(rx_buf)
}
pub fn pldm_tx_resp(
ep: &mut impl mctp::RespChannel,
resp: &PldmResponse,
) -> Result<()> {
let tx_buf = [resp.iid, resp.typ, resp.cmd, resp.cc];
let txs = &[&tx_buf, resp.data.as_ref()];
ep.send_vectored(MsgIC(false), txs)?;
Ok(())
}
pub fn pldm_tx_req(
ep: &mut impl mctp::ReqChannel,
req: &mut PldmRequest,
) -> Result<()> {
const REQ_IID: u8 = 0;
req.iid = REQ_IID;
let tx_buf = [1 << 7 | req.iid, req.typ & 0x3f, req.cmd];
let txs = &[&tx_buf, req.data.as_ref()];
ep.send_vectored(mctp::MCTP_TYPE_PLDM, MsgIC(false), txs)?;
Ok(())
}