Skip to content

Commit 3715f55

Browse files
committed
I2C transport: Unit tests, fix surfaced bugs, small improvements and doc
Signed-off-by: Marvin Gudel <[email protected]>
1 parent 2241851 commit 3715f55

File tree

1 file changed

+102
-14
lines changed

1 file changed

+102
-14
lines changed

mctp-estack/src/i2c.rs

Lines changed: 102 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,20 @@ 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 = MctpI2cHeader::decode(
137+
&packet.get(..4).ok_or(Error::InvalidInput)?,
138+
)?;
139+
// total packet len == byte_count + 3 (destination, command code, byte count)
140+
// pec is not included
141+
if header.byte_count + 3 != packet.len() {
142+
trace!("Packet byte count mismatch");
106143
return Err(Error::InvalidInput);
107144
}
145+
if header.dest != self.own_addr {
146+
trace!("I2C destination address mismatch");
147+
}
108148

109-
Ok((&packet[MCTP_I2C_HEADER..], header.source))
149+
Ok((&packet[MCTP_I2C_HEADER..], header))
110150
}
111151

112152
/// Handles a MCTP fragment with the PEC already validated
@@ -117,7 +157,7 @@ impl MctpI2cEncap {
117157
&self,
118158
packet: &[u8],
119159
mctp: &'f mut Stack,
120-
) -> Result<Option<(MctpMessage<'f>, u8)>> {
160+
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
121161
let (mctp_packet, i2c_src) = self.decode(packet, false)?;
122162

123163
// Pass to MCTP stack
@@ -252,7 +292,7 @@ impl MctpI2cHandler {
252292
&mut self,
253293
packet: &[u8],
254294
mctp: &'f mut Stack,
255-
) -> Result<Option<(MctpMessage<'f>, u8)>> {
295+
) -> Result<Option<(MctpMessage<'f>, MctpI2cHeader)>> {
256296
self.encap.receive_done_pec(packet, mctp)
257297
}
258298

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

0 commit comments

Comments
 (0)