Skip to content

Commit 71b23c6

Browse files
committed
I2C transport: Unit tests, fix surfaced bugs, small improvements and doc
Signed-off-by: Marvin Gudel <marvin.gudel@9elements.com>
1 parent 2241851 commit 71b23c6

1 file changed

Lines changed: 101 additions & 14 deletions

File tree

mctp-estack/src/i2c.rs

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,31 @@ const MCTP_I2C_HEADER: usize = 4;
2121
// bytecount is limited to u8, includes MCTP payload + 1 byte i2c source
2222
pub const MCTP_I2C_MAXMTU: usize = u8::MAX as usize - 1;
2323

24+
/// MCTP I2C encapsulation header
2425
pub struct MctpI2cHeader {
26+
/// Destination address
27+
///
28+
/// The 7-bit destination address of the encapsulated packet.
29+
/// (Not including the SMBus R/W# bit)
2530
pub dest: u8,
31+
/// Source address
32+
///
33+
/// The 7-bit source address of the encapsulated packet.
34+
/// (Not including fixed bit `[0]`)
2635
pub source: u8,
36+
/// Byte count
37+
///
38+
/// The count of bytes that follow the _Byte Count_ field up to,
39+
/// but not including, the PEC byte.
2740
pub byte_count: usize,
2841
}
2942

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

46-
fn decode(pkt: &[u8]) -> Result<Self> {
64+
/// Decode a 4-byte I2C header
65+
///
66+
/// Checks and decodes destination and source address,
67+
/// command code, and byte count.
68+
fn decode(header: &[u8]) -> Result<Self> {
4769
let [dest, cmd, byte_count, source] =
48-
pkt.try_into().map_err(|_| Error::BadArgument)?;
70+
header.try_into().map_err(|_| Error::BadArgument)?;
4971
if dest & 1 != 0 {
5072
trace!("Bad i2c dest write bit");
5173
return Err(Error::InvalidInput);
@@ -59,8 +81,8 @@ impl MctpI2cHeader {
5981
return Err(Error::InvalidInput);
6082
}
6183
Ok(Self {
62-
dest,
63-
source,
84+
dest: dest >> 1,
85+
source: source >> 1,
6486
byte_count: byte_count as usize,
6587
})
6688
}
@@ -81,16 +103,27 @@ impl MctpI2cEncap {
81103
self.own_addr
82104
}
83105

106+
/// Decode a I2C encapsulated packet
107+
///
108+
/// Decodes and verifies the I2C transport header.
109+
/// Optionally an included _PEC_ will be verified.
110+
///
111+
/// The inner MCTP packet and the decoded header are returned on success.
112+
///
113+
/// ### Errors when
114+
/// - The _PEC_ is incorrect
115+
/// - Header decoding fails
116+
/// - The decoded byte count field does not match the packet size
84117
pub fn decode<'f>(
85118
&self,
86119
mut packet: &'f [u8],
87120
pec: bool,
88-
) -> Result<(&'f [u8], u8)> {
121+
) -> Result<(&'f [u8], MctpI2cHeader)> {
122+
if packet.is_empty() {
123+
return Err(Error::InvalidInput);
124+
}
89125
if pec {
90126
// Remove the pec byte, check it.
91-
if packet.is_empty() {
92-
return Err(Error::InvalidInput);
93-
}
94127
let packet_pec;
95128
(packet_pec, packet) = packet.split_last().unwrap();
96129
let calc_pec = smbus_pec::pec(packet);
@@ -100,13 +133,19 @@ impl MctpI2cEncap {
100133
}
101134
}
102135

103-
let header = MctpI2cHeader::decode(packet)?;
104-
// +1 for i2c source address field
105-
if header.byte_count != packet.len() + 1 {
136+
let header =
137+
MctpI2cHeader::decode(packet.get(..4).ok_or(Error::InvalidInput)?)?;
138+
// total packet len == byte_count + 3 (destination, command code, byte count)
139+
// pec is not included
140+
if header.byte_count + 3 != packet.len() {
141+
trace!("Packet byte count mismatch");
106142
return Err(Error::InvalidInput);
107143
}
144+
if header.dest != self.own_addr {
145+
trace!("I2C destination address mismatch");
146+
}
108147

109-
Ok((&packet[MCTP_I2C_HEADER..], header.source))
148+
Ok((&packet[MCTP_I2C_HEADER..], header))
110149
}
111150

112151
/// Handles a MCTP fragment with the PEC already validated
@@ -117,7 +156,7 @@ impl MctpI2cEncap {
117156
&self,
118157
packet: &[u8],
119158
mctp: &'f mut Stack,
120-
) -> Result<Option<(MctpMessage<'f>, u8)>> {
159+
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
121160
let (mctp_packet, i2c_src) = self.decode(packet, false)?;
122161

123162
// Pass to MCTP stack
@@ -252,7 +291,7 @@ impl MctpI2cHandler {
252291
&mut self,
253292
packet: &[u8],
254293
mctp: &'f mut Stack,
255-
) -> Result<Option<(MctpMessage<'f>, u8)>> {
294+
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
256295
self.encap.receive_done_pec(packet, mctp)
257296
}
258297

@@ -369,3 +408,51 @@ enum HandlerSendState {
369408
i2c_dest: u8,
370409
},
371410
}
411+
412+
#[cfg(test)]
413+
mod tests {
414+
use super::*;
415+
416+
#[test]
417+
fn i2c_codec_roundtrip() {
418+
let codec = MctpI2cEncap::new(0x0A);
419+
const PACKET: &[u8] =
420+
&[0x01, 0x00, 0x09, 0xc8, 0x00, 0x8a, 0x01, 0x00, 0x0a];
421+
422+
let mut buf = [0; 128];
423+
let i2c_packet = codec.encode(0x0B, PACKET, &mut buf, false).unwrap();
424+
425+
assert_eq!(&i2c_packet[..4], [0x16, 0x0f, 0x0a, 0x15]);
426+
427+
let codec = MctpI2cEncap::new(0x0B);
428+
let (decoded_packet, header) = codec.decode(i2c_packet, false).unwrap();
429+
assert_eq!(decoded_packet, PACKET);
430+
assert_eq!(header.source, 0x0a);
431+
assert_eq!(header.dest, 0x0b);
432+
}
433+
434+
#[test]
435+
fn test_partial_packet_decode() {
436+
let codec = MctpI2cEncap::new(0x0A);
437+
438+
// Test that empty packets are handled correctly
439+
let res = codec.decode(&[], false);
440+
assert!(res.is_err());
441+
let res = codec.decode(&[], true);
442+
assert!(res.is_err());
443+
// Test that packets with only partial header are handled correctly
444+
let res = codec.decode(&[0x16, 0x0f], false);
445+
assert!(res.is_err());
446+
let res = codec.decode(&[0x16, 0x0f], true);
447+
assert!(res.is_err());
448+
}
449+
450+
#[test]
451+
fn test_decode_byte_count_mismatch() {
452+
let codec = MctpI2cEncap::new(0x0A);
453+
454+
// Try to decode a packet with a `byte count` of 0x0a followed by only 3 bytes
455+
let res = codec.decode(&[0x16, 0x0f, 0x0a, 0x15, 0x01, 0x02], false);
456+
assert!(res.is_err_and(|e| matches!(e, Error::InvalidInput)));
457+
}
458+
}

0 commit comments

Comments
 (0)