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);