vca test
diff --git a/MODULE.bazel b/MODULE.bazel index 3df741a..ee3af5a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -142,6 +142,15 @@ # ── Python deps for QEMU tooling scripts (cfggen, flashgen) ── pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") pip.parse( + # AMD's Zscaler proxy intercepts TLS to pypi.org with its own cert; skip + # transport verification. Package integrity is still enforced via the + # sha256 hashes in requirements_lock. + extra_pip_args = [ + "--trusted-host", + "pypi.org", + "--trusted-host", + "files.pythonhosted.org", + ], hub_name = "openprot_python_deps", python_version = "3.11", requirements_lock = "//third_party/qemu:requirements.txt",
diff --git a/services/spdm/requester/tests/vca_host.rs b/services/spdm/requester/tests/vca_host.rs index 332bddc..fc976d2 100644 --- a/services/spdm/requester/tests/vca_host.rs +++ b/services/spdm/requester/tests/vca_host.rs
@@ -32,6 +32,7 @@ use openprot_mctp_api::MctpClient; use openprot_mctp_server::Server; use openprot_spdm_requester::{RequesterConfig, SpdmRequester}; +use openprot_spdm_responder::{ResponderConfig, SpdmResponder}; use openprot_spdm_transport_mctp::MctpSpdmTransport; use spdm_lib::codec::MessageBuf; use spdm_lib::commands::algorithms::request::generate_negotiate_algorithms_request; @@ -39,7 +40,6 @@ use spdm_lib::commands::version::request::generate_get_version; use spdm_lib::commands::version::VersionReqPayload; use spdm_lib::platform::transport::SpdmTransport; -use openprot_spdm_responder::{ResponderConfig, SpdmResponder}; use common::{ transfer, BufferSender, DemoPeerCertStore, DirectClient, MockCertStore, MockEvidence, MockHash,
diff --git a/target/ast10x0/tests/mctp/server/BUILD.bazel b/target/ast10x0/tests/mctp/server/BUILD.bazel index b3dd47e..e955524 100644 --- a/target/ast10x0/tests/mctp/server/BUILD.bazel +++ b/target/ast10x0/tests/mctp/server/BUILD.bazel
@@ -221,5 +221,4 @@ "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], "//conditions:default": [], }), - timeout = "eternal", )
diff --git a/target/ast10x0/tests/spdm/vca/BUILD.bazel b/target/ast10x0/tests/spdm/vca/BUILD.bazel new file mode 100644 index 0000000..71ed9a3 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/BUILD.bazel
@@ -0,0 +1,236 @@ +# 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_vca_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_vca_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 = "spdm_vca_test", + image = ":spdm_vca_image", + slave_image = ":spdm_vca_peer_image", + tags = [ + "hardware", + "manual", + ], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), +)
diff --git a/target/ast10x0/tests/spdm/vca/i2c_server_main.rs b/target/ast10x0/tests/spdm/vca/i2c_server_main.rs new file mode 100644 index 0000000..1bdaabd --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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/vca/i2c_server_main_peer.rs b/target/ast10x0/tests/spdm/vca/i2c_server_main_peer.rs new file mode 100644 index 0000000..e26b51f --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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/vca/mctp_server_main.rs b/target/ast10x0/tests/spdm/vca/mctp_server_main.rs new file mode 100644 index 0000000..eee4f52 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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/vca/mctp_server_main_peer.rs b/target/ast10x0/tests/spdm/vca/mctp_server_main_peer.rs new file mode 100644 index 0000000..f936064 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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/vca/mock_peer_cert_store.rs b/target/ast10x0/tests/spdm/vca/mock_peer_cert_store.rs new file mode 100644 index 0000000..371d8e2 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/mock_peer_cert_store.rs
@@ -0,0 +1,253 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! no_std mock peer certificate store for the SPDM VCA stress test. +//! +//! Requester-only: the responder's `SpdmResponder::new` takes no peer cert +//! store, so this lives outside the shared `mock_platform` module. + +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/vca/mock_platform.rs b/target/ast10x0/tests/spdm/vca/mock_platform.rs new file mode 100644 index 0000000..55df7db --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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 VCA 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/vca/peer_system.json5 b/target/ast10x0/tests/spdm/vca/peer_system.json5 new file mode 100644 index 0000000..8c618fe --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/peer_system.json5
@@ -0,0 +1,116 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 SPDM VCA 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/vca/spdm_requester_main.rs b/target/ast10x0/tests/spdm/vca/spdm_requester_main.rs new file mode 100644 index 0000000..897a241 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/spdm_requester_main.rs
@@ -0,0 +1,190 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPDM VCA stress test — requester side. +//! +//! Repeatedly performs the full VCA handshake (GET_VERSION → GET_CAPABILITIES +//! → NEGOTIATE_ALGORITHMS) against the peer responder over real I2C/MCTP +//! transport. Runs indefinitely; the test harness observes correct cycling +//! via the TEST_RESULT sentinel on timeout or error. + +#![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::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; + +#[entry] +fn entry() { + match run() { + Ok(()) => { + pw_log::info!("SPDM requester stress test completed"); + let _ = syscall::debug_shutdown(Ok(())); + } + Err(e) => { + pw_log::error!("SPDM requester stress test FAILED: {}", e as u32); + let _ = syscall::debug_shutdown(Err(Error::Internal)); + } + } + loop {} +} + +fn run() -> Result<(), u32> { + pw_log::info!("SPDM VCA 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 + })?; + buf.reset(); + 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_local 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 + })?; + buf.reset(); + 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 + })?; + buf.reset(); + requester + .context_mut() + .requester_process_message(&mut buf) + .map_err(|_| { + pw_log::error!("process ALGORITHMS failed on round {}", round as u32); + 12u32 + })?; + + round += 1; + pw_log::info!("VCA 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/vca/spdm_responder_main.rs b/target/ast10x0/tests/spdm/vca/spdm_responder_main.rs new file mode 100644 index 0000000..6de1238 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/spdm_responder_main.rs
@@ -0,0 +1,102 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPDM VCA stress test — responder side. +//! +//! Continuously processes incoming SPDM messages from the peer requester. +//! Runs indefinitely alongside the requester's VCA loop. + +#![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 VCA 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/vca/system.json5 b/target/ast10x0/tests/spdm/vca/system.json5 new file mode 100644 index 0000000..6681da4 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/system.json5
@@ -0,0 +1,121 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 SPDM VCA 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 requester VCA 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/vca/target.rs b/target/ast10x0/tests/spdm/vca/target.rs new file mode 100644 index 0000000..665fd65 --- /dev/null +++ b/target/ast10x0/tests/spdm/vca/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 VCA 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);