target/ast10x0/tests/spdm/auth: add SPDM auth stress test
diff --git a/target/ast10x0/tests/spdm/auth/BUILD.bazel b/target/ast10x0/tests/spdm/auth/BUILD.bazel new file mode 100644 index 0000000..1deafa5 --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/BUILD.bazel
@@ -0,0 +1,237 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH", "system_image_test") + +filegroup( + name = "system_config", + srcs = ["system.json5"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "peer_system_config", + srcs = ["peer_system.json5"], + visibility = ["//visibility:public"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast10x0:linker_script_template", +) + +rust_binary( + name = "target", + srcs = ["target.rs"], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0/peripherals", + "@ast1060_pac", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + ], +) + +rust_app( + name = "i2c_server", + srcs = ["i2c_server_main.rs"], + codegen_crate_name = "app_i2c_server", + edition = "2024", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/i2c/server-runtime:i2c_server_runtime", + "//target/ast10x0/backend/i2c:i2c_backend_ast10x0", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +rust_app( + name = "i2c_server_peer", + srcs = ["i2c_server_main_peer.rs"], + codegen_crate_name = "app_i2c_server_peer", + edition = "2024", + system_config = ":peer_system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/i2c/server-runtime:i2c_server_runtime", + "//target/ast10x0/backend/i2c:i2c_backend_ast10x0", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +rust_app( + name = "mctp_server", + srcs = ["mctp_server_main.rs"], + codegen_crate_name = "app_mctp_server", + edition = "2024", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/i2c/api:i2c_api", + "//services/i2c/client:i2c_client", + "//services/i2c/client-ipc:i2c_client_ipc", + "//services/mctp/api:mctp_api", + "//services/mctp/server:mctp_server_lib", + "//services/mctp/transport-i2c:mctp_transport_i2c", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:mctp", + "@rust_crates//:mctp-lib", + ], +) + +rust_app( + name = "mctp_server_peer", + srcs = ["mctp_server_main_peer.rs"], + codegen_crate_name = "app_mctp_server_peer", + edition = "2024", + system_config = ":peer_system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/i2c/api:i2c_api", + "//services/i2c/client:i2c_client", + "//services/i2c/client-ipc:i2c_client_ipc", + "//services/mctp/api:mctp_api", + "//services/mctp/server:mctp_server_lib", + "//services/mctp/transport-i2c:mctp_transport_i2c", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:mctp", + "@rust_crates//:mctp-lib", + ], +) + +rust_app( + name = "spdm_requester", + srcs = [ + "mock_peer_cert_store.rs", + "mock_platform.rs", + "spdm_requester_main.rs", + ], + codegen_crate_name = "app_spdm_requester", + crate_root = "spdm_requester_main.rs", + edition = "2024", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/mctp/api:mctp_api", + "//services/mctp/client-ipc:mctp_client_ipc", + "//services/spdm/requester:spdm_requester_lib", + "//services/spdm/transport-mctp:spdm_transport_mctp", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:spdm-lib", + "@rust_crates//:zerocopy", + ], +) + +rust_app( + name = "spdm_responder", + srcs = [ + "mock_platform.rs", + "spdm_responder_main.rs", + ], + codegen_crate_name = "app_spdm_responder", + crate_root = "spdm_responder_main.rs", + edition = "2024", + system_config = ":peer_system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//services/mctp/api:mctp_api", + "//services/mctp/client-ipc:mctp_client_ipc", + "//services/spdm/responder:spdm_responder_lib", + "//services/spdm/transport-mctp:spdm_transport_mctp", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:spdm-lib", + "@rust_crates//:zerocopy", + ], +) + +system_image( + name = "spdm_auth_image", + apps = [ + ":i2c_server", + ":mctp_server", + ":spdm_requester", + ], + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + visibility = ["//visibility:public"], +) + +system_image( + name = "spdm_auth_peer_image", + apps = [ + ":i2c_server_peer", + ":mctp_server_peer", + ":spdm_responder", + ], + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":peer_system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "auth", + timeout = "eternal", + image = ":spdm_auth_image", + slave_image = ":spdm_auth_peer_image", + tags = [ + "hardware", + "manual", + ], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), +)
diff --git a/target/ast10x0/tests/spdm/auth/i2c_server_main.rs b/target/ast10x0/tests/spdm/auth/i2c_server_main.rs new file mode 100644 index 0000000..1bdaabd --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/i2c_server_main.rs
@@ -0,0 +1,50 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] +#![no_std] + +use app_i2c_server::{handle, signals}; +use ast10x0_peripherals::i2c::{ClockConfig, I2cConfig, I2cSpeed, I2cXferMode}; +use i2c_server_runtime::{run, Bus}; +use userspace::entry; + +const SLAVE_CFG: I2cConfig = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: false, + smbus_timeout: false, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), +}; + +#[unsafe(link_section = ".ram_nc")] +static mut MASTER_DMA_BUF: [u8; 4096] = [0u8; 4096]; +#[unsafe(link_section = ".ram_nc")] +static mut SLAVE_DMA_BUF: [u8; 256] = [0u8; 256]; + +#[entry] +fn entry() { + // SAFETY: board init ran init_bus(2) in the kernel; buffers are non-cached and owned here. + let master_dma_buf: &'static mut [u8] = + unsafe { &mut *core::ptr::addr_of_mut!(MASTER_DMA_BUF) }; + let slave_dma_buf: &'static mut [u8] = unsafe { &mut *core::ptr::addr_of_mut!(SLAVE_DMA_BUF) }; + let driver = + match unsafe { i2c_backend::open_bus_dma(2, &SLAVE_CFG, master_dma_buf, slave_dma_buf) } { + Ok(d) => d, + Err(_) => { + pw_log::error!("open_bus_dma(2) failed"); + loop {} + } + }; + + pw_log::info!("I2C server ready on Bus 2"); + + let mut buses = [Bus::new(handle::I2C, handle::I2C2_IRQ, driver)]; + run(handle::WG, signals::I2C2, &mut buses); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/i2c_server_main_peer.rs b/target/ast10x0/tests/spdm/auth/i2c_server_main_peer.rs new file mode 100644 index 0000000..e26b51f --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/i2c_server_main_peer.rs
@@ -0,0 +1,50 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] +#![no_std] + +use app_i2c_server_peer::{handle, signals}; +use ast10x0_peripherals::i2c::{ClockConfig, I2cConfig, I2cSpeed, I2cXferMode}; +use i2c_server_runtime::{run, Bus}; +use userspace::entry; + +const SLAVE_CFG: I2cConfig = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: false, + smbus_timeout: false, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), +}; + +#[unsafe(link_section = ".ram_nc")] +static mut MASTER_DMA_BUF: [u8; 4096] = [0u8; 4096]; +#[unsafe(link_section = ".ram_nc")] +static mut SLAVE_DMA_BUF: [u8; 256] = [0u8; 256]; + +#[entry] +fn entry() { + // SAFETY: board init ran init_bus(2) in the kernel; buffers are non-cached and owned here. + let master_dma_buf: &'static mut [u8] = + unsafe { &mut *core::ptr::addr_of_mut!(MASTER_DMA_BUF) }; + let slave_dma_buf: &'static mut [u8] = unsafe { &mut *core::ptr::addr_of_mut!(SLAVE_DMA_BUF) }; + let driver = + match unsafe { i2c_backend::open_bus_dma(2, &SLAVE_CFG, master_dma_buf, slave_dma_buf) } { + Ok(d) => d, + Err(_) => { + pw_log::error!("open_bus_dma(2) failed"); + loop {} + } + }; + + pw_log::info!("I2C server peer ready on Bus 2"); + + let mut buses = [Bus::new(handle::I2C, handle::I2C2_IRQ, driver)]; + run(handle::WG, signals::I2C2, &mut buses); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/mctp_server_main.rs b/target/ast10x0/tests/spdm/auth/mctp_server_main.rs new file mode 100644 index 0000000..eee4f52 --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/mctp_server_main.rs
@@ -0,0 +1,254 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] +#![no_std] + +use i2c_api::SlaveEvent; +use i2c_client::I2cClient; +use i2c_client_ipc::IpcTransport; +use openprot_mctp_api::wire::{ + self, MctpOp, MctpRequestHeader, MAX_PAYLOAD_SIZE, MAX_REQUEST_SIZE, MAX_RESPONSE_SIZE, +}; +use openprot_mctp_api::{Handle, ResponseCode}; +use openprot_mctp_server::dispatch::{self, DispatchOutcome}; +use openprot_mctp_transport_i2c::{I2cSender, MctpI2cReceiver}; + +use pw_status::Error; +use pw_status::Result; +use userspace::entry; +use userspace::syscall::{self, Signals}; +use userspace::time::{Clock, Duration, Instant, SystemClock}; + +use app_mctp_server::handle; + +const OWN_EID: u8 = 8; +const OWN_I2C_ADDR: u8 = 0x10; +const REMOTE_I2C_ADDR: u8 = 0x42; +const I2C_RX_MAX: usize = MAX_PAYLOAD_SIZE; + +fn mctp_server_loop() -> Result<()> { + pw_log::info!("MCTP server starting"); + let sender = I2cSender::new( + I2cClient::new(IpcTransport::new(handle::I2C)), + OWN_I2C_ADDR, + REMOTE_I2C_ADDR, + ); + let mut i2c_rx_client = I2cClient::new(IpcTransport::new(handle::I2C)); + let i2c_receiver = MctpI2cReceiver::new(OWN_I2C_ADDR); + + i2c_rx_client.configure_slave(OWN_I2C_ADDR).map_err(|_| { + pw_log::error!("configure_slave failed"); + Error::Internal + })?; + + i2c_rx_client.enable_slave().map_err(|_| { + pw_log::error!("enable_slave failed"); + Error::Internal + })?; + + i2c_rx_client.enable_notification().map_err(|_| { + pw_log::error!("enable_notification failed"); + Error::Internal + })?; + + let mut server = openprot_mctp_server::Server::<_, 16>::new(mctp::Eid(OWN_EID), 0, sender); + + let mut request_buf = [0u8; MAX_REQUEST_SIZE]; + let mut response_buf = [0u8; MAX_RESPONSE_SIZE]; + let mut recv_buf = [0u8; MAX_PAYLOAD_SIZE]; + let mut i2c_rx_buf = [0u8; I2C_RX_MAX]; + + struct PendingRecv { + handle: Handle, + deadline: Instant, + } + + let mut pending_recv: Option<PendingRecv> = None; + + syscall::wait_group_add(handle::WG, handle::MCTP, Signals::READABLE, 0usize)?; + syscall::wait_group_add(handle::WG, handle::I2C, Signals::USER, 1usize)?; + + loop { + let wait_deadline = pending_recv + .as_ref() + .map(|pending| pending.deadline) + .unwrap_or(Instant::MAX); + let ev = match syscall::object_wait( + handle::WG, + Signals::READABLE | Signals::USER, + wait_deadline, + ) { + Ok(ev) => ev, + Err(pw_status::Error::DeadlineExceeded) => { + if pending_recv.take().is_some() { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::TimedOut); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + let _ = syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + ); + let _ = syscall::wait_group_add( + handle::WG, + handle::MCTP, + Signals::READABLE, + 0usize, + ); + } + continue; + } + Err(err) => return Err(err), + }; + + if ev.user_data == 1 { + match i2c_rx_client.slave_receive(&mut i2c_rx_buf) { + Ok(event) => { + if event.kind == SlaveEvent::DataReceived && event.data_len > 0 { + if let Ok((pkt, _)) = i2c_receiver.decode(&i2c_rx_buf[..event.data_len]) { + let _ = server.inbound(pkt); + } else { + pw_log::error!("i2c frame decode failed"); + } + } + } + Err(_) => { + pw_log::error!("slave_receive failed"); + } + } + if let Some(pending) = pending_recv.as_ref() { + if let Some(meta) = server.try_recv(pending.handle, &mut recv_buf) { + let payload = &recv_buf[..meta.payload_size]; + let response_len = openprot_mctp_api::wire::encode_recv_response( + &mut response_buf, + meta.msg_type, + meta.msg_ic, + meta.remote_eid, + meta.msg_tag, + payload, + ) + .unwrap_or_else(|_| { + openprot_mctp_api::wire::encode_error_response( + &mut response_buf, + ResponseCode::InternalError, + ) + .unwrap_or(0) + }); + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + pending_recv = None; + syscall::wait_group_add(handle::WG, handle::MCTP, Signals::READABLE, 0usize)?; + } + } + } else { + let len = syscall::channel_read(handle::MCTP, 0, &mut request_buf)?; + if pending_recv.is_some() { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::InternalError); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + if len < MctpRequestHeader::SIZE { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::BadArgument); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + if MctpRequestHeader::from_bytes(&request_buf[..len]) + .and_then(|h| h.operation()) + .map_or(false, |op| matches!(op, MctpOp::Recv)) + { + let header = MctpRequestHeader::from_bytes(&request_buf[..len]).unwrap(); + let recv_handle = Handle(header.handle); + let payload = wire::get_request_payload(&request_buf[..len]); + if payload.len() < 4 { + let resp = openprot_mctp_api::wire::MctpResponseHeader::error( + ResponseCode::BadArgument, + ); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + let timeout_millis = u32::from_le_bytes(payload[..4].try_into().unwrap()); + match server.try_recv(recv_handle, &mut recv_buf) { + Some(meta) => { + let payload = &recv_buf[..meta.payload_size]; + let response_len = openprot_mctp_api::wire::encode_recv_response( + &mut response_buf, + meta.msg_type, + meta.msg_ic, + meta.remote_eid, + meta.msg_tag, + payload, + ) + .unwrap_or_else(|_| { + openprot_mctp_api::wire::encode_error_response( + &mut response_buf, + ResponseCode::InternalError, + ) + .unwrap_or(0) + }); + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + } + None => { + let deadline = if timeout_millis == 0 { + Instant::MAX + } else { + SystemClock::now() + .checked_add_duration(Duration::from_millis(timeout_millis as i64)) + .unwrap_or(Instant::MAX) + }; + pending_recv = Some(PendingRecv { + handle: recv_handle, + deadline, + }); + let _ = syscall::wait_group_remove(handle::WG, handle::MCTP); + } + } + } else { + let response_len = match dispatch::dispatch_mctp_op( + &request_buf[..len], + &mut response_buf, + &mut server, + &mut recv_buf, + 0, + ) { + DispatchOutcome::Reply(n) => n, + DispatchOutcome::Pending { .. } => unreachable!("Recv handled above"), + }; + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + } + } + } +} + +#[entry] +fn entry() { + if let Err(e) = mctp_server_loop() { + pw_log::error!("mctp_server exiting with error"); + let _ = syscall::process_exit(e as u32); + } + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/mctp_server_main_peer.rs b/target/ast10x0/tests/spdm/auth/mctp_server_main_peer.rs new file mode 100644 index 0000000..f936064 --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/mctp_server_main_peer.rs
@@ -0,0 +1,252 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] +#![no_std] + +use i2c_api::SlaveEvent; +use i2c_client::I2cClient; +use i2c_client_ipc::IpcTransport; +use openprot_mctp_api::wire::{ + self, MctpOp, MctpRequestHeader, MAX_PAYLOAD_SIZE, MAX_REQUEST_SIZE, MAX_RESPONSE_SIZE, +}; +use openprot_mctp_api::{Handle, ResponseCode}; +use openprot_mctp_server::dispatch::{self, DispatchOutcome}; +use openprot_mctp_transport_i2c::{I2cSender, MctpI2cReceiver}; + +use pw_status::Error; +use pw_status::Result; +use userspace::entry; +use userspace::syscall::{self, Signals}; +use userspace::time::{Clock, Duration, Instant, SystemClock}; + +use app_mctp_server_peer::handle; + +const OWN_EID: u8 = 9; +const OWN_I2C_ADDR: u8 = 0x42; +const REMOTE_I2C_ADDR: u8 = 0x10; +const I2C_RX_MAX: usize = MAX_PAYLOAD_SIZE; + +fn mctp_server_loop() -> Result<()> { + pw_log::info!("MCTP server peer starting"); + let sender = I2cSender::new( + I2cClient::new(IpcTransport::new(handle::I2C)), + OWN_I2C_ADDR, + REMOTE_I2C_ADDR, + ); + let mut i2c_rx_client = I2cClient::new(IpcTransport::new(handle::I2C)); + let i2c_receiver = MctpI2cReceiver::new(OWN_I2C_ADDR); + + if i2c_rx_client.configure_slave(OWN_I2C_ADDR).is_err() { + pw_log::error!("configure_slave failed"); + return Err(Error::Internal); + } + if i2c_rx_client.enable_slave().is_err() { + pw_log::error!("enable_slave failed"); + return Err(Error::Internal); + } + if i2c_rx_client.enable_notification().is_err() { + pw_log::error!("enable_notification failed"); + return Err(Error::Internal); + } + + let mut server = openprot_mctp_server::Server::<_, 16>::new(mctp::Eid(OWN_EID), 0, sender); + + let mut request_buf = [0u8; MAX_REQUEST_SIZE]; + let mut response_buf = [0u8; MAX_RESPONSE_SIZE]; + let mut recv_buf = [0u8; MAX_PAYLOAD_SIZE]; + let mut i2c_rx_buf = [0u8; I2C_RX_MAX]; + + struct PendingRecv { + handle: Handle, + deadline: Instant, + } + + let mut pending_recv: Option<PendingRecv> = None; + + syscall::wait_group_add(handle::WG, handle::MCTP, Signals::READABLE, 0usize)?; + syscall::wait_group_add(handle::WG, handle::I2C, Signals::USER, 1usize)?; + + loop { + let wait_deadline = pending_recv + .as_ref() + .map(|pending| pending.deadline) + .unwrap_or(Instant::MAX); + let ev = match syscall::object_wait( + handle::WG, + Signals::READABLE | Signals::USER, + wait_deadline, + ) { + Ok(ev) => ev, + Err(pw_status::Error::DeadlineExceeded) => { + if pending_recv.take().is_some() { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::TimedOut); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + let _ = syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + ); + let _ = syscall::wait_group_add( + handle::WG, + handle::MCTP, + Signals::READABLE, + 0usize, + ); + } + continue; + } + Err(err) => return Err(err), + }; + + if ev.user_data == 1 { + match i2c_rx_client.slave_receive(&mut i2c_rx_buf) { + Ok(event) => { + if event.kind == SlaveEvent::DataReceived && event.data_len > 0 { + if let Ok((pkt, _)) = i2c_receiver.decode(&i2c_rx_buf[..event.data_len]) { + let _ = server.inbound(pkt); + } else { + pw_log::error!("i2c frame decode failed"); + } + } + } + Err(_) => { + pw_log::error!("slave_receive failed"); + } + } + if let Some(pending) = pending_recv.as_ref() { + if let Some(meta) = server.try_recv(pending.handle, &mut recv_buf) { + let payload = &recv_buf[..meta.payload_size]; + let response_len = openprot_mctp_api::wire::encode_recv_response( + &mut response_buf, + meta.msg_type, + meta.msg_ic, + meta.remote_eid, + meta.msg_tag, + payload, + ) + .unwrap_or_else(|_| { + openprot_mctp_api::wire::encode_error_response( + &mut response_buf, + ResponseCode::InternalError, + ) + .unwrap_or(0) + }); + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + pending_recv = None; + syscall::wait_group_add(handle::WG, handle::MCTP, Signals::READABLE, 0usize)?; + } + } + } else { + let len = syscall::channel_read(handle::MCTP, 0, &mut request_buf)?; + if pending_recv.is_some() { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::InternalError); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + if len < MctpRequestHeader::SIZE { + let resp = + openprot_mctp_api::wire::MctpResponseHeader::error(ResponseCode::BadArgument); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + if MctpRequestHeader::from_bytes(&request_buf[..len]) + .and_then(|h| h.operation()) + .map_or(false, |op| matches!(op, MctpOp::Recv)) + { + let header = MctpRequestHeader::from_bytes(&request_buf[..len]).unwrap(); + let recv_handle = Handle(header.handle); + let payload = wire::get_request_payload(&request_buf[..len]); + if payload.len() < 4 { + let resp = openprot_mctp_api::wire::MctpResponseHeader::error( + ResponseCode::BadArgument, + ); + response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE] + .copy_from_slice(&resp.to_bytes()); + syscall::channel_respond( + handle::MCTP, + &response_buf[..openprot_mctp_api::wire::MctpResponseHeader::SIZE], + )?; + continue; + } + + let timeout_millis = u32::from_le_bytes(payload[..4].try_into().unwrap()); + match server.try_recv(recv_handle, &mut recv_buf) { + Some(meta) => { + let payload = &recv_buf[..meta.payload_size]; + let response_len = openprot_mctp_api::wire::encode_recv_response( + &mut response_buf, + meta.msg_type, + meta.msg_ic, + meta.remote_eid, + meta.msg_tag, + payload, + ) + .unwrap_or_else(|_| { + openprot_mctp_api::wire::encode_error_response( + &mut response_buf, + ResponseCode::InternalError, + ) + .unwrap_or(0) + }); + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + } + None => { + let deadline = if timeout_millis == 0 { + Instant::MAX + } else { + SystemClock::now() + .checked_add_duration(Duration::from_millis(timeout_millis as i64)) + .unwrap_or(Instant::MAX) + }; + pending_recv = Some(PendingRecv { + handle: recv_handle, + deadline, + }); + let _ = syscall::wait_group_remove(handle::WG, handle::MCTP); + } + } + } else { + let response_len = match dispatch::dispatch_mctp_op( + &request_buf[..len], + &mut response_buf, + &mut server, + &mut recv_buf, + 0, + ) { + DispatchOutcome::Reply(n) => n, + DispatchOutcome::Pending { .. } => unreachable!("Recv handled above"), + }; + syscall::channel_respond(handle::MCTP, &response_buf[..response_len])?; + } + } + } +} + +#[entry] +fn entry() { + if let Err(e) = mctp_server_loop() { + pw_log::error!("mctp_server peer exiting with error"); + let _ = syscall::process_exit(e as u32); + } + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/mock_peer_cert_store.rs b/target/ast10x0/tests/spdm/auth/mock_peer_cert_store.rs new file mode 100644 index 0000000..b3d3517 --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/mock_peer_cert_store.rs
@@ -0,0 +1,254 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! no_std mock peer certificate store for the SPDM auth stress test. +//! +//! Requester-only: stores the responder's cert chain and digest received +//! during GET_DIGESTS / GET_CERTIFICATE, and accepts the mock signature +//! from the responder without real verification. + +use spdm_lib::cert_store::{CertStoreError, CertStoreResult, PeerCertStore}; +use spdm_lib::commands::challenge::MeasurementSummaryHashType; +use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask}; +use spdm_lib::protocol::{BaseHashAlgoType, SpdmCertChainHeader}; +use zerocopy::FromBytes; + +const MAX_CERT_CHAIN_SIZE: usize = 512; +const MAX_DIGEST_SIZE: usize = 64; + +pub struct PeerSlot { + cert_chain: [u8; MAX_CERT_CHAIN_SIZE], + cert_chain_len: usize, + digest: [u8; MAX_DIGEST_SIZE], + digest_len: usize, + keypair_id: Option<u8>, + certificate_info: Option<CertificateInfo>, + key_usage_mask: Option<KeyUsageMask>, + requested_msh_type: Option<MeasurementSummaryHashType>, +} + +impl PeerSlot { + const fn empty() -> Self { + Self { + cert_chain: [0u8; MAX_CERT_CHAIN_SIZE], + cert_chain_len: 0, + digest: [0u8; MAX_DIGEST_SIZE], + digest_len: 0, + keypair_id: None, + certificate_info: None, + key_usage_mask: None, + requested_msh_type: None, + } + } + + fn get_root_hash(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> { + let chain = &self.cert_chain[..self.cert_chain_len]; + let (_, rest) = SpdmCertChainHeader::ref_from_prefix(chain).ok()?; + Some(&rest[..hash_algo.hash_byte_size()]) + } + + fn get_cert_chain_data(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> { + let chain = &self.cert_chain[..self.cert_chain_len]; + let (_, rest) = SpdmCertChainHeader::ref_from_prefix(chain).ok()?; + Some(&rest[hash_algo.hash_byte_size()..]) + } +} + +pub struct MockPeerCertStore { + slot: PeerSlot, + supported_slots_mask: u8, + provisioned_slots_mask: u8, + occupied: bool, +} + +impl MockPeerCertStore { + pub fn new() -> Self { + Self { + slot: PeerSlot::empty(), + supported_slots_mask: 0, + provisioned_slots_mask: 0, + occupied: false, + } + } +} + +impl PeerCertStore for MockPeerCertStore { + fn slot_count(&self) -> u8 { + 1 + } + + fn assemble( + &mut self, + slot_id: u8, + portion: &[u8], + ) -> Result<spdm_lib::cert_store::ReassemblyStatus, CertStoreError> { + if slot_id != 0 { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + if !self.occupied { + return Err(CertStoreError::PlatformError); + } + let remaining = MAX_CERT_CHAIN_SIZE - self.slot.cert_chain_len; + let to_copy = portion.len().min(remaining); + self.slot.cert_chain[self.slot.cert_chain_len..self.slot.cert_chain_len + to_copy] + .copy_from_slice(&portion[..to_copy]); + self.slot.cert_chain_len += to_copy; + Ok(spdm_lib::cert_store::ReassemblyStatus::InProgress) + } + + fn reset(&mut self, slot_id: u8) { + if slot_id == 0 && self.occupied { + self.slot = PeerSlot::empty(); + } + } + + fn get_raw_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + Ok(&self.slot.cert_chain[..self.slot.cert_chain_len]) + } + + fn get_cert_chain(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .get_cert_chain_data(hash_algo) + .ok_or(CertStoreError::CertReadError) + } + + fn set_supported_slots(&mut self, slot_mask: u8) -> CertStoreResult<()> { + if slot_mask & 1 != 0 { + self.occupied = true; + } + self.supported_slots_mask = slot_mask; + Ok(()) + } + + fn get_supported_slots(&self) -> CertStoreResult<u8> { + Ok(self.supported_slots_mask) + } + + fn set_provisioned_slots(&mut self, provisioned_slot_mask: u8) -> CertStoreResult<()> { + self.provisioned_slots_mask = provisioned_slot_mask; + Ok(()) + } + + fn get_provisioned_slots(&self) -> CertStoreResult<u8> { + Ok(self.provisioned_slots_mask) + } + + fn set_cert_chain(&mut self, slot_id: u8, cert_chain: &[u8]) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + let to_copy = cert_chain.len().min(MAX_CERT_CHAIN_SIZE); + self.slot.cert_chain[..to_copy].copy_from_slice(&cert_chain[..to_copy]); + self.slot.cert_chain_len = to_copy; + Ok(()) + } + + fn get_digest(&self, slot_id: u8) -> CertStoreResult<&[u8]> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + Ok(&self.slot.digest[..self.slot.digest_len]) + } + + fn set_digest(&mut self, slot_id: u8, digest: &[u8]) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + let to_copy = digest.len().min(MAX_DIGEST_SIZE); + self.slot.digest[..to_copy].copy_from_slice(&digest[..to_copy]); + self.slot.digest_len = to_copy; + Ok(()) + } + + fn get_cert_info(&self, slot_id: u8) -> CertStoreResult<CertificateInfo> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .certificate_info + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + + fn set_cert_info(&mut self, slot_id: u8, cert_info: CertificateInfo) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot.certificate_info = Some(cert_info); + Ok(()) + } + + fn get_key_usage_mask(&self, slot_id: u8) -> CertStoreResult<KeyUsageMask> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .key_usage_mask + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + + fn set_key_usage_mask( + &mut self, + slot_id: u8, + key_usage_mask: KeyUsageMask, + ) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot.key_usage_mask = Some(key_usage_mask); + Ok(()) + } + + fn get_keypair(&self, slot_id: u8) -> CertStoreResult<u8> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .keypair_id + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + + fn set_keypair(&mut self, slot_id: u8, keypair: u8) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot.keypair_id = Some(keypair); + Ok(()) + } + + fn get_root_hash(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .get_root_hash(hash_algo) + .ok_or(CertStoreError::CertReadError) + } + + fn get_requested_msh_type(&self, slot_id: u8) -> CertStoreResult<MeasurementSummaryHashType> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot + .requested_msh_type + .clone() + .ok_or(CertStoreError::Undefined) + } + + fn set_requested_msh_type( + &mut self, + slot_id: u8, + msh_type: MeasurementSummaryHashType, + ) -> CertStoreResult<()> { + if slot_id != 0 || !self.occupied { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + self.slot.requested_msh_type = Some(msh_type); + Ok(()) + } +}
diff --git a/target/ast10x0/tests/spdm/auth/mock_platform.rs b/target/ast10x0/tests/spdm/auth/mock_platform.rs new file mode 100644 index 0000000..9634e9f --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/mock_platform.rs
@@ -0,0 +1,266 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! no_std mock platform implementations for the SPDM auth stress test. + +use spdm_lib::cert_store::{CertStoreError, CertStoreResult, SpdmCertStore}; +use spdm_lib::platform::evidence::{SpdmEvidence, SpdmEvidenceError, SpdmEvidenceResult}; +use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashError, SpdmHashResult}; +use spdm_lib::platform::rng::{SpdmRng, SpdmRngResult}; +use spdm_lib::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; +use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask}; + +// --------------------------------------------------------------------------- +// MockCertStore +// --------------------------------------------------------------------------- + +pub struct MockCertStore; + +impl MockCertStore { + pub fn new() -> Self { + Self + } +} + +impl SpdmCertStore for MockCertStore { + fn slot_count(&self) -> u8 { + 1 + } + + fn is_provisioned(&self, slot_id: u8) -> bool { + slot_id == 0 + } + + fn cert_chain_len(&mut self, asym_algo: AsymAlgo, slot_id: u8) -> CertStoreResult<usize> { + if slot_id != 0 { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + if asym_algo != AsymAlgo::EccP384 { + return Err(CertStoreError::UnsupportedHashAlgo); + } + Ok(32) + } + + fn get_cert_chain( + &mut self, + slot_id: u8, + asym_algo: AsymAlgo, + offset: usize, + cert_portion: &mut [u8], + ) -> CertStoreResult<usize> { + if slot_id != 0 { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + if asym_algo != AsymAlgo::EccP384 { + return Err(CertStoreError::UnsupportedHashAlgo); + } + + const CERT_CHAIN: [u8; 32] = [0xAA; 32]; + + if offset >= CERT_CHAIN.len() { + return Err(CertStoreError::InvalidOffset); + } + + let remaining = CERT_CHAIN.len() - offset; + let to_copy = remaining.min(cert_portion.len()); + cert_portion[..to_copy].copy_from_slice(&CERT_CHAIN[offset..offset + to_copy]); + + if to_copy < cert_portion.len() { + cert_portion[to_copy..].fill(0); + } + + Ok(to_copy) + } + + fn root_cert_hash( + &mut self, + slot_id: u8, + asym_algo: AsymAlgo, + cert_hash: &mut [u8; SHA384_HASH_SIZE], + ) -> CertStoreResult<()> { + if slot_id != 0 { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + if asym_algo != AsymAlgo::EccP384 { + return Err(CertStoreError::UnsupportedHashAlgo); + } + + const ROOT_HASH: [u8; SHA384_HASH_SIZE] = [0xBB; SHA384_HASH_SIZE]; + cert_hash.copy_from_slice(&ROOT_HASH); + Ok(()) + } + + fn sign_hash( + &self, + slot_id: u8, + _hash: &[u8; SHA384_HASH_SIZE], + signature: &mut [u8; ECC_P384_SIGNATURE_SIZE], + ) -> CertStoreResult<()> { + if slot_id != 0 { + return Err(CertStoreError::InvalidSlotId(slot_id)); + } + + const SIGNATURE: [u8; ECC_P384_SIGNATURE_SIZE] = [0xCC; ECC_P384_SIGNATURE_SIZE]; + signature.copy_from_slice(&SIGNATURE); + Ok(()) + } + + fn key_pair_id(&self, _slot_id: u8) -> Option<u8> { + None + } + + fn cert_info(&self, _slot_id: u8) -> Option<CertificateInfo> { + None + } + + fn key_usage_mask(&self, _slot_id: u8) -> Option<KeyUsageMask> { + None + } +} + +// --------------------------------------------------------------------------- +// MockHash +// --------------------------------------------------------------------------- + +const HASH_BUF_SIZE: usize = 4096; + +pub struct MockHash { + buffer: [u8; HASH_BUF_SIZE], + len: usize, + algo: Option<SpdmHashAlgoType>, +} + +impl MockHash { + pub fn new() -> Self { + Self { + buffer: [0u8; HASH_BUF_SIZE], + len: 0, + algo: None, + } + } +} + +impl SpdmHash for MockHash { + fn init(&mut self, algo: SpdmHashAlgoType, _secret: Option<&[u8]>) -> SpdmHashResult<()> { + self.len = 0; + self.algo = Some(algo); + Ok(()) + } + + fn update(&mut self, data: &[u8]) -> SpdmHashResult<()> { + let remaining = HASH_BUF_SIZE - self.len; + let to_copy = data.len().min(remaining); + self.buffer[self.len..self.len + to_copy].copy_from_slice(&data[..to_copy]); + self.len += to_copy; + Ok(()) + } + + fn finalize(&mut self, dest: &mut [u8]) -> SpdmHashResult<()> { + let hash_size = match self.algo { + Some(SpdmHashAlgoType::SHA384) => 48, + Some(SpdmHashAlgoType::SHA512) => 64, + _ => return Err(SpdmHashError::InvalidAlgorithm), + }; + + if dest.len() < hash_size { + return Err(SpdmHashError::BufferTooSmall); + } + + let mut hash_byte = 0u8; + for byte in &self.buffer[..self.len] { + hash_byte ^= byte; + } + dest[..hash_size].fill(hash_byte); + + Ok(()) + } + + fn hash(&mut self, algo: SpdmHashAlgoType, data: &[u8], dest: &mut [u8]) -> SpdmHashResult<()> { + self.init(algo, None)?; + self.update(data)?; + self.finalize(dest) + } + + fn reset(&mut self) { + self.len = 0; + self.algo = None; + } + + fn algo(&self) -> SpdmHashAlgoType { + self.algo.unwrap_or(SpdmHashAlgoType::SHA384) + } +} + +// --------------------------------------------------------------------------- +// MockRng +// --------------------------------------------------------------------------- + +pub struct MockRng; + +impl MockRng { + pub fn new() -> Self { + Self + } +} + +impl SpdmRng for MockRng { + fn get_random_bytes(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { + for (i, byte) in buf.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + Ok(()) + } + + fn generate_random_number(&mut self, random_number: &mut [u8]) -> SpdmRngResult<()> { + for (i, byte) in random_number.iter_mut().enumerate() { + *byte = ((i + 0x42) & 0xFF) as u8; + } + Ok(()) + } +} + +// --------------------------------------------------------------------------- +// MockEvidence +// --------------------------------------------------------------------------- + +pub struct MockEvidence; + +impl MockEvidence { + pub fn new() -> Self { + Self + } +} + +impl SpdmEvidence for MockEvidence { + fn pcr_quote_size(&self, _with_pqc_sig: bool) -> SpdmEvidenceResult<usize> { + Ok(1 + (1 + 2 + 23) + (1 + 2 + 20)) + } + + fn pcr_quote(&self, dest: &mut [u8], _with_pqc_sig: bool) -> SpdmEvidenceResult<usize> { + let required_size = self.pcr_quote_size(false)?; + if dest.len() < required_size { + return Err(SpdmEvidenceError::InvalidEvidenceFormat); + } + + let mut offset = 0; + + dest[offset] = 2; + offset += 1; + + dest[offset] = 0; + offset += 1; + dest[offset..offset + 2].copy_from_slice(&23u16.to_le_bytes()); + offset += 2; + dest[offset..offset + 23].copy_from_slice(b"OpenPRoT SPDM Loopback"); + offset += 23; + + dest[offset] = 1; + offset += 1; + dest[offset..offset + 2].copy_from_slice(&20u16.to_le_bytes()); + offset += 2; + dest[offset..offset + 20].copy_from_slice(b"MCTP Loopback Test "); + offset += 20; + + Ok(offset) + } +}
diff --git a/target/ast10x0/tests/spdm/auth/peer_system.json5 b/target/ast10x0/tests/spdm/auth/peer_system.json5 new file mode 100644 index 0000000..6dbfa3a --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/peer_system.json5
@@ -0,0 +1,116 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 SPDM Auth Stress Test Configuration (peer image) +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1792, + }, + kernel: { + flash_start_address: 0x00000700, + flash_size_bytes: 129280, + ram_start_address: 0x00060000, + ram_size_bytes: 131072, + }, + apps: [ + { + name: "i2c_server_peer", + flash_size_bytes: 65536, + processes: [ + { + name: "i2c_server_peer_process", + ram_size_bytes: 65536, + objects: [ + { + name: "wg", + type: "wait_group", + }, + { + name: "i2c", + type: "channel_handler", + }, + { + name: "i2c2_irq", + type: "interrupt", + irqs: [ + { + name: "i2c2", + number: 112, + }, + ], + }, + { + type: "thread", + name: "i2c_server_peer_thread", + kernel_stack_size_bytes: 4096, + }, + ], + memory_mappings: [ + { + name: "i2c_regs", + type: "device", + start_address: 0x7e7b0000, + size_bytes: 0x4000, + }, + ], + }, + ], + }, + { + name: "mctp_server_peer", + flash_size_bytes: 65536, + processes: [ + { + name: "mctp_server_peer_process", + ram_size_bytes: 65536, + objects: [ + { + name: "wg", + type: "wait_group", + }, + { + name: "mctp", + type: "channel_handler", + }, + { + name: "i2c", + type: "channel_initiator", + handler_process: "i2c_server_peer_process", + handler_object_name: "i2c", + }, + { + type: "thread", + name: "mctp_server_peer_thread", + kernel_stack_size_bytes: 4096, + }, + ], + }, + ], + }, + { + name: "spdm_responder", + flash_size_bytes: 131072, + processes: [ + { + name: "spdm_responder_process", + ram_size_bytes: 65536, + objects: [ + { + name: "mctp", + type: "channel_initiator", + handler_process: "mctp_server_peer_process", + handler_object_name: "mctp", + }, + { + type: "thread", + name: "spdm_responder_thread", + kernel_stack_size_bytes: 4096, + }, + ], + }, + ], + }, + ], +}
diff --git a/target/ast10x0/tests/spdm/auth/spdm_requester_main.rs b/target/ast10x0/tests/spdm/auth/spdm_requester_main.rs new file mode 100644 index 0000000..043672a --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/spdm_requester_main.rs
@@ -0,0 +1,301 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPDM auth stress test — requester side. +//! +//! Repeatedly performs the full SPDM handshake sequence: +//! VCA (GET_VERSION → GET_CAPABILITIES → NEGOTIATE_ALGORITHMS) +//! Auth (GET_DIGESTS → GET_CERTIFICATE → CHALLENGE → CHALLENGE_AUTH) +//! +//! The mock cert store returns a fixed signature; the requester accepts it +//! without cryptographic verification, making this a protocol-layer stress +//! test rather than a crypto correctness test. + +#![no_main] +#![no_std] + +mod mock_peer_cert_store; +mod mock_platform; + +use app_spdm_requester::handle; +use mock_peer_cert_store::MockPeerCertStore; +use mock_platform::{MockCertStore, MockEvidence, MockHash, MockRng}; +use openprot_mctp_api::stack::Stack; +use openprot_mctp_client_ipc::IpcMctpClient; +use openprot_spdm_requester::SpdmRequester; +use openprot_spdm_transport_mctp::MctpSpdmTransport; +use pw_status::Error; +use spdm_lib::codec::MessageBuf; +use spdm_lib::commands::algorithms::request::generate_negotiate_algorithms_request; +use spdm_lib::commands::capabilities::request::generate_capabilities_request_local; +use spdm_lib::commands::certificate::request::generate_get_certificate; +use spdm_lib::commands::challenge::request::generate_challenge_request; +use spdm_lib::commands::challenge::MeasurementSummaryHashType; +use spdm_lib::commands::digests::request::generate_digest_request; +use spdm_lib::commands::version::request::generate_get_version; +use spdm_lib::commands::version::VersionReqPayload; +use spdm_lib::platform::transport::SpdmTransport as _; +use userspace::{entry, syscall}; + +const RESPONDER_EID: u8 = 9; + +/// Maximum certificate chain size (bytes). +const MAX_CERT_SIZE: u16 = 512; + +#[entry] +fn entry() { + match run() { + Ok(()) => { + pw_log::info!("SPDM auth stress test completed"); + let _ = syscall::debug_shutdown(Ok(())); + } + Err(e) => { + pw_log::error!("SPDM auth stress test FAILED: {}", e as u32); + let _ = syscall::debug_shutdown(Err(Error::Internal)); + } + } + loop {} +} + +fn run() -> Result<(), u32> { + pw_log::info!("SPDM auth stress test starting (requester)"); + + let mctp_client = IpcMctpClient::new(handle::MCTP); + let stack = Stack::new(mctp_client); + + stack.set_eid(8).map_err(|e| { + pw_log::error!("set_eid failed: {}", e.code as u32); + 1u32 + })?; + + let mut round: u32 = 0; + loop { + let mut transport = MctpSpdmTransport::new_requester(&stack, RESPONDER_EID); + transport.init_sequence().map_err(|_| { + pw_log::error!("transport init_sequence failed on round {}", round as u32); + 2u32 + })?; + + let mut cert_store = MockCertStore::new(); + let mut peer_cert_store = MockPeerCertStore::new(); + let mut hash = MockHash::new(); + let mut m1_hash = MockHash::new(); + let mut l1_hash = MockHash::new(); + let mut rng = MockRng::new(); + let evidence = MockEvidence::new(); + + let mut requester = SpdmRequester::new( + &mut transport, + &mut cert_store, + &mut peer_cert_store, + &mut hash, + &mut m1_hash, + &mut l1_hash, + &mut rng, + &evidence, + None, + ) + .map_err(|_| { + pw_log::error!("SpdmRequester::new failed on round {}", round as u32); + 3u32 + })?; + + let mut buf_storage = [0u8; 4096]; + let mut buf = MessageBuf::new(&mut buf_storage); + + // ── GET_VERSION → VERSION ───────────────────────────────────────── + + generate_get_version( + requester.context_mut(), + &mut buf, + VersionReqPayload::new(0, 0), + ) + .map_err(|_| { + pw_log::error!("generate_get_version failed on round {}", round as u32); + 4u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send GET_VERSION failed on round {}", round as u32); + 5u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process VERSION failed on round {}", round as u32); + 6u32 + })?; + + // ── GET_CAPABILITIES → CAPABILITIES ────────────────────────────── + + buf.reset(); + generate_capabilities_request_local(requester.context_mut(), &mut buf).map_err(|_| { + pw_log::error!( + "generate_capabilities_request failed on round {}", + round as u32 + ); + 7u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send GET_CAPABILITIES failed on round {}", round as u32); + 8u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process CAPABILITIES failed on round {}", round as u32); + 9u32 + })?; + + // ── NEGOTIATE_ALGORITHMS → ALGORITHMS ──────────────────────────── + + buf.reset(); + generate_negotiate_algorithms_request( + requester.context_mut(), + &mut buf, + None, + None, + None, + None, + ) + .map_err(|_| { + pw_log::error!( + "generate_negotiate_algorithms_request failed on round {}", + round as u32 + ); + 10u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send NEGOTIATE_ALGORITHMS failed on round {}", round as u32); + 11u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process ALGORITHMS failed on round {}", round as u32); + 12u32 + })?; + + // ── GET_DIGESTS → DIGESTS ───────────────────────────────────────── + + buf.reset(); + generate_digest_request(requester.context_mut(), &mut buf).map_err(|_| { + pw_log::error!("generate_digest_request failed on round {}", round as u32); + 13u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send GET_DIGESTS failed on round {}", round as u32); + 14u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process DIGESTS failed on round {}", round as u32); + 15u32 + })?; + + // ── GET_CERTIFICATE → CERTIFICATE ──────────────────────────────── + // Request the full certificate chain in one shot (offset=0, length=MAX_CERT_SIZE). + + buf.reset(); + generate_get_certificate( + requester.context_mut(), + &mut buf, + 0, + 0, + MAX_CERT_SIZE, + false, + ) + .map_err(|_| { + pw_log::error!("generate_get_certificate failed on round {}", round as u32); + 16u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send GET_CERTIFICATE failed on round {}", round as u32); + 17u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process CERTIFICATE failed on round {}", round as u32); + 18u32 + })?; + + // ── CHALLENGE → CHALLENGE_AUTH ──────────────────────────────────── + // The mock responder signs with a fixed value; we accept without + // cryptographic verification (protocol-layer stress test only). + + let mut nonce = [0u8; 32]; + requester + .context_mut() + .get_random_bytes(&mut nonce) + .map_err(|_| { + pw_log::error!("get_random_bytes failed on round {}", round as u32); + 19u32 + })?; + + buf.reset(); + generate_challenge_request( + requester.context_mut(), + &mut buf, + 0, + MeasurementSummaryHashType::None, + nonce, + None, + ) + .map_err(|_| { + pw_log::error!( + "generate_challenge_request failed on round {}", + round as u32 + ); + 20u32 + })?; + requester + .context_mut() + .requester_send_request(&mut buf, RESPONDER_EID) + .map_err(|_| { + pw_log::error!("send CHALLENGE failed on round {}", round as u32); + 21u32 + })?; + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process CHALLENGE_AUTH failed on round {}", round as u32); + 22u32 + })?; + + // The signature bytes remain in buf; we accept them without cryptographic + // verification — this is a protocol-layer stress test, not a crypto test. + requester.context_mut().set_authenticated(); + + round += 1; + pw_log::info!("auth round {} complete", round as u32); + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("SPDM requester panic"); + let _ = syscall::debug_shutdown(Err(Error::Internal)); + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/spdm_responder_main.rs b/target/ast10x0/tests/spdm/auth/spdm_responder_main.rs new file mode 100644 index 0000000..da610ac --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/spdm_responder_main.rs
@@ -0,0 +1,102 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPDM auth stress test — responder side. +//! +//! Continuously processes incoming SPDM messages from the peer requester, +//! handling VCA followed by the auth sequence (DIGESTS, CERTIFICATE, CHALLENGE_AUTH). + +#![no_main] +#![no_std] + +mod mock_platform; + +use app_spdm_responder::handle; +use mock_platform::{MockCertStore, MockEvidence, MockHash, MockRng}; +use openprot_mctp_api::stack::Stack; +use openprot_mctp_client_ipc::IpcMctpClient; +use openprot_spdm_responder::SpdmResponder; +use openprot_spdm_transport_mctp::MctpSpdmTransport; +use pw_status::Error; +use spdm_lib::codec::MessageBuf; +use spdm_lib::platform::transport::SpdmTransport as _; +use userspace::{entry, syscall}; + +#[entry] +fn entry() { + match run() { + Ok(()) => { + pw_log::info!("SPDM responder stress test completed"); + let _ = syscall::debug_shutdown(Ok(())); + } + Err(e) => { + pw_log::error!("SPDM responder stress test FAILED: {}", e as u32); + let _ = syscall::debug_shutdown(Err(Error::Internal)); + } + } + loop {} +} + +fn run() -> Result<(), u32> { + pw_log::info!("SPDM auth stress test starting (responder)"); + + let mctp_client = IpcMctpClient::new(handle::MCTP); + let stack = Stack::new(mctp_client); + + stack.set_eid(9).map_err(|e| { + pw_log::error!("set_eid failed: {}", e.code as u32); + 1u32 + })?; + + let mut transport = MctpSpdmTransport::new_responder(&stack); + transport.init_sequence().map_err(|_| { + pw_log::error!("transport init_sequence failed"); + 2u32 + })?; + + let mut cert_store = MockCertStore::new(); + let mut hash = MockHash::new(); + let mut m1_hash = MockHash::new(); + let mut l1_hash = MockHash::new(); + let mut rng = MockRng::new(); + let evidence = MockEvidence::new(); + + let mut responder = SpdmResponder::new( + &mut transport, + &mut cert_store, + &mut hash, + &mut m1_hash, + &mut l1_hash, + &mut rng, + &evidence, + None, + ) + .map_err(|_| { + pw_log::error!("SpdmResponder::new failed"); + 3u32 + })?; + + static mut BUF: [u8; 4096] = [0u8; 4096]; + // SAFETY: single-threaded; BUF is not aliased across calls. + let buf_slice: &'static mut [u8] = unsafe { &mut *core::ptr::addr_of_mut!(BUF) }; + let mut buf = MessageBuf::new(buf_slice); + + loop { + buf.reset(); + if responder + .context_mut() + .responder_process_message(&mut buf) + .is_err() + { + pw_log::error!("process_message failed"); + return Err(4u32); + } + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("SPDM responder panic"); + let _ = syscall::debug_shutdown(Err(Error::Internal)); + loop {} +}
diff --git a/target/ast10x0/tests/spdm/auth/system.json5 b/target/ast10x0/tests/spdm/auth/system.json5 new file mode 100644 index 0000000..7907fa4 --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/system.json5
@@ -0,0 +1,121 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 SPDM Auth Stress Test Configuration +// +// Multi-app configuration: +// - i2c_server: owns i2c hardware and runs i2c_server_runtime +// - mctp_server: uses i2c transport over IPC + serves MCTP control channel +// - spdm_requester: runs the SPDM auth loop +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1792, + }, + kernel: { + flash_start_address: 0x00000700, + flash_size_bytes: 129280, + ram_start_address: 0x00060000, + ram_size_bytes: 131072, + }, + apps: [ + { + name: "i2c_server", + flash_size_bytes: 65536, + processes: [ + { + name: "i2c_server_process", + ram_size_bytes: 65536, + objects: [ + { + name: "wg", + type: "wait_group", + }, + { + name: "i2c", + type: "channel_handler", + }, + { + name: "i2c2_irq", + type: "interrupt", + irqs: [ + { + name: "i2c2", + number: 112, + }, + ], + }, + { + type: "thread", + name: "i2c_server_thread", + kernel_stack_size_bytes: 4096, + }, + ], + memory_mappings: [ + { + name: "i2c_regs", + type: "device", + start_address: 0x7e7b0000, + size_bytes: 0x4000, + }, + ], + }, + ], + }, + { + name: "mctp_server", + flash_size_bytes: 65536, + processes: [ + { + name: "mctp_server_process", + ram_size_bytes: 65536, + objects: [ + { + name: "wg", + type: "wait_group", + }, + { + name: "mctp", + type: "channel_handler", + }, + { + name: "i2c", + type: "channel_initiator", + handler_process: "i2c_server_process", + handler_object_name: "i2c", + }, + { + type: "thread", + name: "mctp_server_thread", + kernel_stack_size_bytes: 4096, + }, + ], + }, + ], + }, + { + name: "spdm_requester", + flash_size_bytes: 131072, + processes: [ + { + name: "spdm_requester_process", + ram_size_bytes: 65536, + objects: [ + { + name: "mctp", + type: "channel_initiator", + handler_process: "mctp_server_process", + handler_object_name: "mctp", + }, + { + type: "thread", + name: "spdm_requester_thread", + kernel_stack_size_bytes: 4096, + }, + ], + }, + ], + }, + ], +}
diff --git a/target/ast10x0/tests/spdm/auth/target.rs b/target/ast10x0/tests/spdm/auth/target.rs new file mode 100644 index 0000000..42dd70d --- /dev/null +++ b/target/ast10x0/tests/spdm/auth/target.rs
@@ -0,0 +1,64 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +use ast10x0_board::{Ast10x0Board, Ast10x0BoardDescriptor, I2cBusCfg}; +use ast10x0_peripherals::i2c::{ClockConfig, I2cConfig, I2cSpeed, I2cXferMode}; +use ast10x0_peripherals::scu::pinctrl; +use console_backend::console_backend_write_all; +use entry as _; +use target_common::{declare_target, TargetInterface}; + +const I2C2_CFG: I2cConfig = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: false, + smbus_timeout: false, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), +}; + +static PINCTRL_GROUPS: [&[ast10x0_peripherals::scu::PinctrlPin]; 1] = [pinctrl::PINCTRL_I2C2]; +static I2C_BUSES: [I2cBusCfg; 1] = [I2cBusCfg { + bus: 2, + config: I2C2_CFG, +}]; + +pub struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SPDM Auth Stress Test"; + + fn main() -> ! { + // SAFETY: kernel main() runs once with exclusive hardware ownership. + if unsafe { + Ast10x0Board::new(Ast10x0BoardDescriptor { + pinctrl_groups: &PINCTRL_GROUPS, + i2c_buses: &I2C_BUSES, + }) + .init() + } + .is_err() + { + loop {} + } + + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + let sentinel: &[u8] = if code == 0 { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);