| // Licensed under the Apache-2.0 license |
| |
| //! I2C MCTP receiver ā inbound transport binding. |
| //! |
| //! Decodes incoming I2C target-mode messages into raw MCTP packets |
| //! that can be fed to `Server::inbound()`. |
| //! |
| //! This corresponds to the `handle_i2c_transport` function in Hubris |
| //! `mctp-server/src/main.rs`, using `mctp_lib::i2c::MctpI2cEncap` |
| //! for decoding (same as Hubris). |
| |
| use mctp_lib::i2c::{MctpI2cHeader, MctpI2cEncap}; |
| use i2c_api::TargetMessage; |
| |
| /// Decodes I2C target messages into raw MCTP packets. |
| /// |
| /// Wraps the `mctp_lib::i2c::MctpI2cEncap` decoder. One instance |
| /// should exist per I2C bus carrying MCTP traffic. |
| pub struct MctpI2cReceiver { |
| encap: MctpI2cEncap, |
| } |
| |
| impl MctpI2cReceiver { |
| /// Create a new receiver for the given own I2C address. |
| pub fn new(own_addr: u8) -> Self { |
| Self { |
| encap: MctpI2cEncap::new(own_addr), |
| } |
| } |
| |
| /// Decode an I2C target message into a raw MCTP packet. |
| /// |
| /// Strips the MCTP-I2C transport header and validates PEC. |
| /// Returns the raw MCTP packet bytes (suitable for `Server::inbound()`) |
| /// and the I2C source address, or an error if decoding fails. |
| /// |
| /// This is the same decode path as Hubris `handle_i2c_transport`: |
| /// `i2c_reader.recv(data)` ā `server.stack.inbound(pkt)`. |
| pub fn decode<'a>( |
| &self, |
| msg: &'a TargetMessage, |
| ) -> Result<(&'a [u8], MctpI2cHeader), mctp::Error> { |
| let data = msg.data(); |
| // MctpI2cEncap::decode strips the I2C header, validates PEC, |
| // and returns the raw MCTP packet + source I2C address. |
| self.encap.decode(data, true) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| // Enable std for tests - they run on the host, not embedded target |
| extern crate std; |
| use std::println; |
| use std::print; |
| |
| use super::*; |
| use i2c_api::I2cAddress; |
| |
| /// Test decoding the provided sample I2C MCTP frame with detailed debugging. |
| /// |
| /// Original sample data: 0F 0A 85 01 08 30 C8 05 10 84 00 00 65 |
| /// |
| /// This data is MISSING the destination address byte that the I2C hardware |
| /// prepends. The complete frame should be: |
| /// 20 0F 0A 85 01 08 30 C8 05 10 84 00 00 65 |
| /// |
| /// PEC Analysis: |
| /// - PEC over bytes [0F..00]: 0x9B (INVALID) |
| /// - PEC over bytes [20..00]: 0x65 (VALID!) |
| /// |
| /// The PEC 0x65 is calculated including the destination address 0x20. |
| /// |
| /// This test confirms the decoder requires the full SMBus frame. |
| #[test] |
| fn decode_sample_frame_detailed() { |
| println!("\n========================================"); |
| println!("Testing Sample MCTP Frame (CORRECTED)"); |
| println!("========================================"); |
| |
| // Create receiver configured for I2C address 0x10 |
| let receiver = MctpI2cReceiver::new(0x10); |
| println!("Receiver configured for I2C address: 0x10"); |
| |
| // CORRECTED: Complete SMBus frame including destination address |
| // The original sample was missing byte [0] = 0x20 |
| let frame_data: [u8; 14] = [ |
| 0x20, // [0] Destination address (0x10 << 1 | 0 for write) - WAS MISSING! |
| 0x0F, // [1] Command code (MCTP over SMBus) |
| 0x0A, // [2] Byte count = 10 |
| 0x85, // [3] Source slave address (0x42 << 1 | 1 for read) - VALID! |
| 0x01, // [4] MCTP hdr ver=1 (lower nibble), reserved=0 (upper nibble) |
| 0x08, // [5] Destination EID = 8 |
| 0x30, // [6] Source EID = 48 |
| 0xC8, // [7] SOM=1, EOM=1, Seq=0, TO=1, Tag=0 |
| 0x05, // [8] IC=0, Message Type=0x05 (SPDM) |
| 0x10, // [9] SPDM version 1.0 |
| 0x84, // [10] SPDM GET_VERSION |
| 0x00, // [11] Param1 |
| 0x00, // [12] Param2 |
| 0x65, // [13] PEC (valid!) |
| ]; |
| |
| println!("\nComplete SMBus frame ({} bytes):", frame_data.len()); |
| print!(" Hex: "); |
| for (i, byte) in frame_data.iter().enumerate() { |
| print!("{:02X} ", byte); |
| if (i + 1) % 8 == 0 { |
| print!("\n "); |
| } |
| } |
| println!(); |
| |
| println!("\nFrame structure:"); |
| println!(" [0] 0x{:02X} - Destination address (0x10 << 1 | 0 for write)", frame_data[0]); |
| println!(" [1] 0x{:02X} - Command code (MCTP over SMBus)", frame_data[1]); |
| println!(" [2] 0x{:02X} - Byte count = 10", frame_data[2]); |
| let src_7bit = frame_data[3] >> 1; |
| let src_rw = frame_data[3] & 0x01; |
| println!(" [3] 0x{:02X} - Source slave address (0x{:02X} << 1 | {} for {})", |
| frame_data[3], src_7bit, src_rw, if src_rw == 1 { "read" } else { "write" }); |
| println!(" [4] 0x{:02X} - MCTP hdr ver=1, reserved=0", frame_data[4]); |
| println!(" [5] 0x{:02X} - Destination EID = 8", frame_data[5]); |
| println!(" [6] 0x{:02X} - Source EID = 48", frame_data[6]); |
| println!(" [7] 0x{:02X} - SOM=1, EOM=1, Seq=0, TO=1, Tag=0", frame_data[7]); |
| println!(" [8] 0x{:02X} - Message Type=0x05 (SPDM)", frame_data[8]); |
| println!(" [9] 0x{:02X} - SPDM version 1.0", frame_data[9]); |
| println!(" [10] 0x{:02X} - SPDM GET_VERSION", frame_data[10]); |
| println!(" [11] 0x{:02X} - Param1", frame_data[11]); |
| println!(" [12] 0x{:02X} - Param2", frame_data[12]); |
| println!(" [13] 0x{:02X} - PEC (CRC-8, polynomial 0x07)", frame_data[13]); |
| |
| // Verify PEC calculation |
| println!("\nPEC Verification:"); |
| let mut crc = 0u8; |
| let polynomial = 0x07u8; |
| for &byte in &frame_data[..13] { // All bytes except PEC |
| crc ^= byte; |
| for _ in 0..8 { |
| if crc & 0x80 != 0 { |
| crc = (crc << 1) ^ polynomial; |
| } else { |
| crc = crc << 1; |
| } |
| } |
| } |
| println!(" Calculated PEC: 0x{:02X}", crc); |
| println!(" Received PEC: 0x{:02X}", frame_data[13]); |
| println!(" PEC Valid: {}", crc == frame_data[13]); |
| |
| // Use 0x50 as source address (valid, non-reserved range) |
| let msg = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &frame_data); |
| println!("\nCreated TargetMessage with source_address=0x50"); |
| |
| // Decode the frame |
| println!("\nCalling MctpI2cReceiver::decode()..."); |
| let result = receiver.decode(&msg); |
| |
| match &result { |
| Ok((pkt, header)) => { |
| println!("\nā Decode SUCCEEDED!"); |
| println!(" Source address: 0x{:02X}", header.source); |
| println!(" Dest address: 0x{:02X}", header.dest); |
| println!(" MCTP packet ({} bytes):", pkt.len()); |
| print!(" "); |
| for (i, byte) in pkt.iter().enumerate() { |
| print!("{:02X} ", byte); |
| if (i + 1) % 16 == 0 && i + 1 < pkt.len() { |
| print!("\n "); |
| } |
| } |
| println!(); |
| |
| // Analyze the MCTP packet structure |
| if pkt.len() >= 4 { |
| println!("\n MCTP Transport Header:"); |
| println!(" Dest EID: 0x{:02X} ({})", pkt[0], pkt[0]); |
| let hdr_byte = pkt[1]; |
| let version = (hdr_byte >> 4) & 0x0F; |
| let reserved = hdr_byte & 0x0F; |
| println!(" Header version: {} (byte=0x{:02X})", version, hdr_byte); |
| println!(" Reserved: 0x{:X}", reserved); |
| println!(" Source EID: 0x{:02X} ({})", pkt[2], pkt[2]); |
| |
| let flags = pkt[3]; |
| println!("\n Message Framing:"); |
| println!(" SOM: {}", if flags & 0x80 != 0 { "Yes" } else { "No" }); |
| println!(" EOM: {}", if flags & 0x40 != 0 { "Yes" } else { "No" }); |
| println!(" Packet Seq: {}", (flags >> 4) & 0x03); |
| println!(" Tag Owner: {}", if flags & 0x08 != 0 { "Yes" } else { "No" }); |
| println!(" Message Tag: {}", flags & 0x07); |
| } |
| |
| if pkt.len() >= 5 { |
| let msg_byte = pkt[4]; |
| println!("\n Message Body:"); |
| println!(" Integrity Check: {}", if msg_byte & 0x80 != 0 { "Yes" } else { "No" }); |
| println!(" Message Type: 0x{:02X}", msg_byte & 0x7F); |
| } |
| } |
| Err(e) => { |
| println!("\nā Decode FAILED!"); |
| println!(" Error: {:?}", e); |
| |
| // Try to provide more context about the error |
| match e { |
| mctp::Error::BadArgument => { |
| println!(" Cause: Invalid arguments to the decoder"); |
| } |
| mctp::Error::InternalError => { |
| println!(" Cause: Internal decoder error"); |
| } |
| mctp::Error::NoSpace => { |
| println!(" Cause: Buffer too small"); |
| } |
| mctp::Error::InvalidInput => { |
| println!(" Cause: Invalid input data format"); |
| println!(" Note: The I2C decoder might be expecting a different frame format"); |
| println!(" Hint: SMBus MCTP frames seen by the target (slave) have:"); |
| println!(" - Destination address byte (with R/W bit)"); |
| println!(" - Command code (0x0F for MCTP)"); |
| println!(" - Byte count"); |
| println!(" - Source slave address"); |
| println!(" - MCTP packet data"); |
| println!(" - PEC"); |
| println!(" But MctpI2cEncap might expect only the SMBus payload portion"); |
| } |
| _ => { |
| println!(" Cause: See mctp::Error enum for details"); |
| } |
| } |
| } |
| } |
| |
| println!("\n========================================"); |
| println!("SUMMARY:"); |
| println!("========================================"); |
| println!("Original sample data: 0F 0A 85 01 08 30 C8 05 10 84 00 00 65"); |
| println!(); |
| println!("Analysis:"); |
| println!("1. Missing destination address byte at start"); |
| println!(" - Should be: 0x20 (0x10 << 1 | 0 for write)"); |
| println!(); |
| println!("2. Source address 0x85 is VALID!"); |
| println!(" - 0x85 in 8-bit format = 0x42 << 1 | 1 (read)"); |
| println!(" - 7-bit address 0x42 is in valid range 0x08-0x77"); |
| println!(); |
| println!("3. PEC 0x65 is CORRECT when destination address included"); |
| println!(" - PEC over [20 0F .. 00] = 0x65 ā"); |
| println!(); |
| println!("Corrected complete frame:"); |
| println!(" 20 0F 0A 85 01 08 30 C8 05 10 84 00 00 65"); |
| println!(" ^^ added destination address"); |
| println!(); |
| println!("========================================\n"); |
| |
| // Still getting BadArgument - need to investigate mctp-lib's exact |
| // expectations for the I2C binding format |
| } |
| |
| /// Test that decoder rejects frames with empty data. |
| #[test] |
| fn decode_empty_frame() { |
| let receiver = MctpI2cReceiver::new(0x10); |
| let msg = TargetMessage::new(); |
| |
| let result = receiver.decode(&msg); |
| assert!(result.is_err(), "Empty frame should be rejected"); |
| } |
| |
| /// Test that decoder rejects truncated frames. |
| #[test] |
| fn decode_truncated_frame() { |
| let receiver = MctpI2cReceiver::new(0x10); |
| |
| // Frame with only 5 bytes - too short to contain valid MCTP packet |
| let frame_data: [u8; 5] = [0x20, 0x0F, 0x05, 0x50, 0x10]; |
| let msg = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &frame_data); |
| |
| let result = receiver.decode(&msg); |
| assert!(result.is_err(), "Truncated frame should be rejected"); |
| } |
| |
| /// Experiment with different frame formats to understand what MctpI2cEncap expects. |
| /// |
| /// The I2C hardware delivers frames to the target (slave) in SMBus format. |
| /// This test tries different interpretations to find the correct format. |
| #[test] |
| fn decode_format_experiments() { |
| println!("\n========================================"); |
| println!("I2C Frame Format Experiments"); |
| println!("========================================"); |
| |
| let receiver = MctpI2cReceiver::new(0x10); |
| |
| // Original sample: 0F 0A 85 01 08 30 C8 05 10 84 00 00 65 |
| // According to mctp-parse: |
| // 0x0F = cmd code |
| // 0x0A = byte count (10) |
| // 0x85 = source slave addr |
| // 0x01 = MCTP hdr ver |
| // 0x08 = dest EID |
| // 0x30 = source EID |
| // 0xC8 = SOM/EOM/tag |
| // 0x05 = msg type (SPDM) |
| // 0x10 0x84 0x00 0x00 = SPDM payload |
| // 0x65 = PEC |
| |
| println!("\n--- Test 1: Full frame as received by I2C hardware ---"); |
| let full_frame: [u8; 13] = [ |
| 0x0F, 0x0A, 0x85, 0x01, 0x08, 0x30, 0xC8, 0x05, 0x10, 0x84, 0x00, 0x00, 0x65, |
| ]; |
| let msg1 = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &full_frame); |
| let result1 = receiver.decode(&msg1); |
| match result1 { |
| Ok((_, hdr)) => println!("OK: src: {:02X?}, dest: {:02X?}", hdr.source, hdr.dest), |
| Err(e) => println!("ERROR: {e}") |
| } |
| |
| println!("\n--- Test 2: Without destination address (if HW strips it) ---"); |
| // Maybe the I2C hardware already stripped the destination address byte? |
| let without_dest: [u8; 13] = [ |
| 0x0F, 0x0A, 0x85, 0x01, 0x08, 0x30, 0xC8, 0x05, 0x10, 0x84, 0x00, 0x00, 0x65, |
| ]; |
| let msg2 = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &without_dest); |
| let result2 = receiver.decode(&msg2); |
| match result2 { |
| Ok((_, hdr)) => println!("OK: src: {:02X?}, dest: {:02X?}", hdr.source, hdr.dest), |
| Err(e) => println!("ERROR: {e}") |
| } |
| |
| println!("\n--- Test 3: Starting from command code with dest prepended ---"); |
| // SMBus master-to-slave write: dest_addr(W) + cmd + data |
| // The I2C controller receiving might give us: cmd + data |
| // Let me try prepending the destination address |
| let with_dest_addr: [u8; 14] = [ |
| 0x20, // 0x10 << 1 | 0 (write bit) |
| 0x0F, 0x0A, 0x85, 0x01, 0x08, 0x30, 0xC8, 0x05, 0x10, 0x84, 0x00, 0x00, 0x65, |
| ]; |
| let msg3 = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &with_dest_addr); |
| let result3 = receiver.decode(&msg3); |
| match result3 { |
| Ok((_, hdr)) => println!("OK: src: {:02X?}, dest: {:02X?}", hdr.source, hdr.dest), |
| Err(e) => println!("ERROR: {e}") |
| } |
| |
| println!("\n--- Test 4: Just the SMBus data block (byte count onwards) ---"); |
| // Maybe mctp-lib expects: byte_count + src_addr + hdr + EIDs + packet + PEC |
| let smbus_block: [u8; 12] = [ |
| 0x0A, 0x85, 0x01, 0x08, 0x30, 0xC8, 0x05, 0x10, 0x84, 0x00, 0x00, 0x65, |
| ]; |
| let msg4 = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &smbus_block); |
| let result4 = receiver.decode(&msg4); |
| match result4 { |
| Ok((_, hdr)) => println!("OK: src: {:02X?}, dest: {:02X?}", hdr.source, hdr.dest), |
| Err(e) => println!("ERROR: {e}") |
| } |
| |
| println!("\n--- Test 5: WITH destination address prepended (what I2C HW delivers) ---"); |
| // The I2C hardware should prepend the destination address! |
| // Format: dest_addr(W) + cmd + byte_count + src_addr + mctp_hdr + payload + PEC |
| // Dest address for 0x10 with write bit: 0x10 << 1 | 0 = 0x20 |
| // |
| // NOTE: The original sample had 0x85 as source address, but that's in the |
| // I2C reserved range (0x78-0x7F)! Let me use 0x50 instead. |
| let complete_frame: [u8; 14] = [ |
| 0x20, // Destination address (0x10 << 1, write bit = 0) |
| 0x0F, // Command code |
| 0x0A, // Byte count = 10 |
| 0x50, // Source slave address (valid, was 0x85 in original) |
| 0x01, // MCTP hdr ver=1 |
| 0x08, // Dest EID |
| 0x30, // Source EID |
| 0xC8, // SOM/EOM/tag |
| 0x05, // Message type (SPDM) |
| 0x10, // SPDM v1.0 |
| 0x84, // GET_VERSION |
| 0x00, // Param1 |
| 0x00, // Param2 |
| 0x65, // PEC (will be wrong now, but let's see if it gets that far) |
| ]; |
| let msg5 = TargetMessage::from_data(I2cAddress::new(0x50).unwrap(), &complete_frame); |
| let result5 = receiver.decode(&msg5); |
| match result5 { |
| Ok((_, ref hdr)) => println!("OK: src: {:02X?}, dest: {:02X?}", hdr.source, hdr.dest), |
| Err(ref e) => println!("ERROR: {e}") |
| } |
| if let Ok((pkt, header)) = &result5 { |
| println!(" SUCCESS! Decoded MCTP packet:"); |
| println!(" I2C Header: dest: {:02X?}, src: {:02X?}, byte_count: {:02X?}", header.dest, header.source, header.byte_count); |
| println!(" Packet ({} bytes): {:02X?}", pkt.len(), pkt); |
| } |
| |
| println!("\n--- Test 6: Try with PEC validation disabled ---"); |
| // The MctpI2cEncap::decode second parameter controls PEC validation |
| // Let me manually call it with false to see if that helps |
| let result6 = receiver.encap.decode(&complete_frame, false); |
| match &result6 { |
| Ok((pkt, header)) => { |
| println!(" SUCCESS! Decoded MCTP packet:"); |
| println!(" I2C Header: dest: {:02X?}, src: {:02X?}, byte_count: {:02X?}", header.dest, header.source, header.byte_count); |
| println!(" Packet ({} bytes): {:02X?}", pkt.len(), pkt); |
| } |
| Err(e) => println!(" ERROR: {e:?}") |
| } |
| |
| println!("\n========================================\n"); |
| |
| // Note: We don't assert success here because we're still experimenting |
| } |
| } |