Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 105 additions & 18 deletions mctp-estack/src/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,31 @@ const MCTP_I2C_HEADER: usize = 4;
// bytecount is limited to u8, includes MCTP payload + 1 byte i2c source
pub const MCTP_I2C_MAXMTU: usize = u8::MAX as usize - 1;

/// MCTP I2C encapsulation header
pub struct MctpI2cHeader {
/// Destination address
///
/// The 7-bit destination address of the encapsulated packet.
/// (Not including the SMBus R/W# bit)
pub dest: u8,
/// Source address
///
/// The 7-bit source address of the encapsulated packet.
/// (Not including fixed bit `[0]`)
pub source: u8,
/// Byte count
///
/// The count of bytes that follow the _Byte Count_ field up to,
/// but not including, the PEC byte.
pub byte_count: usize,
}

impl MctpI2cHeader {
/// Encode this header
///
/// Creates a 4-byte header with destination, command code, byte count, and source.
/// Returns a [BadArgument](Error::BadArgument) error when the addresses are not 7-bit,
/// or when the byte count is larger than [u8::MAX].
fn encode(&self) -> Result<[u8; 4]> {
if self.dest > 0x7f || self.source > 0x7f {
return Err(Error::BadArgument);
Expand All @@ -43,9 +61,13 @@ impl MctpI2cHeader {
])
}

fn decode(pkt: &[u8]) -> Result<Self> {
/// Decode a 4-byte I2C header
///
/// Checks and decodes destination and source address,
/// command code, and byte count.
fn decode(header: &[u8]) -> Result<Self> {
let [dest, cmd, byte_count, source] =
pkt.try_into().map_err(|_| Error::BadArgument)?;
header.try_into().map_err(|_| Error::BadArgument)?;
if dest & 1 != 0 {
trace!("Bad i2c dest write bit");
return Err(Error::InvalidInput);
Expand All @@ -59,8 +81,8 @@ impl MctpI2cHeader {
return Err(Error::InvalidInput);
}
Ok(Self {
dest,
source,
dest: dest >> 1,
source: source >> 1,
byte_count: byte_count as usize,
})
}
Expand All @@ -81,16 +103,27 @@ impl MctpI2cEncap {
self.own_addr
}

/// Decode a I2C encapsulated packet
///
/// Decodes and verifies the I2C transport header.
/// Optionally an included _PEC_ will be verified.
///
/// The inner MCTP packet and the decoded header are returned on success.
///
/// ### Errors when
/// - The _PEC_ is incorrect
/// - Header decoding fails
/// - The decoded byte count field does not match the packet size
pub fn decode<'f>(
&self,
mut packet: &'f [u8],
pec: bool,
) -> Result<(&'f [u8], u8)> {
) -> Result<(&'f [u8], MctpI2cHeader)> {
if packet.is_empty() {
return Err(Error::InvalidInput);
}
if pec {
// Remove the pec byte, check it.
if packet.is_empty() {
return Err(Error::InvalidInput);
}
let packet_pec;
(packet_pec, packet) = packet.split_last().unwrap();
let calc_pec = smbus_pec::pec(packet);
Expand All @@ -100,31 +133,37 @@ impl MctpI2cEncap {
}
}

let header = MctpI2cHeader::decode(packet)?;
// +1 for i2c source address field
if header.byte_count != packet.len() + 1 {
let header =
MctpI2cHeader::decode(packet.get(..4).ok_or(Error::InvalidInput)?)?;
// total packet len == byte_count + 3 (destination, command code, byte count)
// pec is not included
if header.byte_count + 3 != packet.len() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like we're getting different packet formats input here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the MCTP SMBus/I2C Transport Binding Specification:

This value is the count of bytes that follow the Byte Count field up to, but not including, the PEC byte

The packet holds the complete packet at this point (including the 3 bytes for source address , Command Code and Byte Count), with the PEC already stripped if checked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change is correct - I changed the meaning of packet in a previous commit but missed fixing the check.

3c8a0c7#diff-6b42e42a2518caa9fd6fbc613024316e15f6a53601e202245a1033d9466a7d0fL65-L71

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it against an i2c packet send out by the Linux MCTP stack at this point, seems to work as expected.

trace!("Packet byte count mismatch");
return Err(Error::InvalidInput);
}
if header.dest != self.own_addr {
trace!("I2C destination address mismatch");
}

Ok((&packet[MCTP_I2C_HEADER..], header.source))
Ok((&packet[MCTP_I2C_HEADER..], header))
}

/// Handles a MCTP fragment with the PEC already validated
///
/// `packet` should omit the PEC byte.
/// Returns the MCTP message and the i2c source address.
/// Returns the MCTP message and the i2c header.
pub fn receive_done_pec<'f>(
&self,
packet: &[u8],
mctp: &'f mut Stack,
) -> Result<Option<(MctpMessage<'f>, u8)>> {
let (mctp_packet, i2c_src) = self.decode(packet, false)?;
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
let (mctp_packet, i2c_header) = self.decode(packet, false)?;

// Pass to MCTP stack
let m = mctp.receive(mctp_packet)?;

// Return a (message, i2c_source) tuple on completion
Ok(m.map(|msg| (msg, i2c_src)))
// Return a (message, i2c_header) tuple on completion
Ok(m.map(|msg| (msg, i2c_header)))
}

/// `out` must be sized to hold 8+mctp_mtu, to allow for MCTP and I2C headers
Expand Down Expand Up @@ -252,7 +291,7 @@ impl MctpI2cHandler {
&mut self,
packet: &[u8],
mctp: &'f mut Stack,
) -> Result<Option<(MctpMessage<'f>, u8)>> {
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
self.encap.receive_done_pec(packet, mctp)
}

Expand Down Expand Up @@ -369,3 +408,51 @@ enum HandlerSendState {
i2c_dest: u8,
},
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn i2c_codec_roundtrip() {
let codec = MctpI2cEncap::new(0x0A);
const PACKET: &[u8] =
&[0x01, 0x00, 0x09, 0xc8, 0x00, 0x8a, 0x01, 0x00, 0x0a];

let mut buf = [0; 128];
let i2c_packet = codec.encode(0x0B, PACKET, &mut buf, false).unwrap();

assert_eq!(&i2c_packet[..4], [0x16, 0x0f, 0x0a, 0x15]);

let codec = MctpI2cEncap::new(0x0B);
let (decoded_packet, header) = codec.decode(i2c_packet, false).unwrap();
assert_eq!(decoded_packet, PACKET);
assert_eq!(header.source, 0x0a);
assert_eq!(header.dest, 0x0b);
}

#[test]
fn test_partial_packet_decode() {
let codec = MctpI2cEncap::new(0x0A);

// Test that empty packets are handled correctly
let res = codec.decode(&[], false);
assert!(res.is_err());
let res = codec.decode(&[], true);
assert!(res.is_err());
// Test that packets with only partial header are handled correctly
let res = codec.decode(&[0x16, 0x0f], false);
assert!(res.is_err());
let res = codec.decode(&[0x16, 0x0f], true);
assert!(res.is_err());
}

#[test]
fn test_decode_byte_count_mismatch() {
let codec = MctpI2cEncap::new(0x0A);

// Try to decode a packet with a `byte count` of 0x0a followed by only 3 bytes
let res = codec.decode(&[0x16, 0x0f, 0x0a, 0x15, 0x01, 0x02], false);
assert!(res.is_err_and(|e| matches!(e, Error::InvalidInput)));
}
}