SPDM

utils.py

SPDM conformance test case utilities.

LARGE_RESPONSE = 15

The response is bigger than the DataTransferSize the requester advertised, so it has to be collected with CHUNK_GET. Carries a Handle.

NO_PENDING_REQUESTS = frozenset({69})

Defined response when the responder has no encapsulated request outstanding.

OUT_OF_FLOW_ERRORS = frozenset({1, 4, 5, 11})

ErrorCodes a conformant responder may return when a request that belongs to a secure session, or to a transfer already in progress, is issued on its own. The conformance suite has no way to establish a secure session, so these are a consequence of how the case is driven, not a defect in the responder.

RESPONSE_TOO_LARGE = 13

The response is bigger than the MaxSPDMmsgSize the requester advertised in GET_CAPABILITIES, so it cannot be received at all.

SPDM_ERROR = 127

RequestResponseCode of an SPDM ERROR response.

class TestSetup(steps=None)

Composable SPDM TestSetup step pipeline.

get_capabilities(*, required_capabilities: dict[str, int] | None = None, **kwargs: Any) TestSetup

Run GET_CAPABILITIES. Skip test if provided capabilities are not met.

get_version(required_spdm_version: int | None = None) TestSetup

Run GET_VERSION and select a negotiated version.

The test is skipped when no entry in VERSION.VersionNumberEntry satisfies the requirement.

negotiate_algorithms(**kwargs) TestSetup

Run NEGOTIATE_ALGORITHMS using the supplied kwargs.

run(dut: DUT) dict[str, Any]

Execute SPDM pipeline. Returns the accumulated context.

step(action: Callable[[DUT], None]) TestSetup

Add step to SPDM pipeline.

Steps may write into self.context to expose values to the main function (e.g. the result of GET_CAPABILITIES).

UNSUPPORTED_REQUEST = frozenset({7})

Some responders reject a session-only request with UnsupportedRequest rather than SessionRequired. Accepted where the request is issued out of session.

common_req_alg_struct(spdm_version: int, *, include_pqc: bool = False, **kwargs) list

Generate ReqAlgStruct with all requester supported algorithms set, based on the SPDM version

common_spdm_test(setup: ~utils.TestSetup = <utils.TestSetup object>, teardown: ~collections.abc.Callable[[~sanblaze.script.dut.DUT], None] | None = None)

Run SPDM TestSetup and TestTeardown

doe_capability_version(dut) int

Return the device’s PCIe DOE capability version, or skip if it has none.

The DOE transport needs the PCIe Data Object Exchange extended capability. Without it the doe binary exits 9 with “failed to find PCIe ‘Data Object Exchange’ capability”, which is the device not being a candidate for the case rather than a test failure. SBSUPPORT-10273.

get_capabilities_common_params() dict

Default GET_CAPABILITIES requester flags from SPDM-Responder-Validator.

See spdm_test_case_algorithms_setup_version_capabilities() in spdm_responder_test_3_algorithms.c

multi_key_conn_rsp(MULTI_KEY_CAP: int, ResponderMultiKeyConn: int) bool | None

Table 38 — MULTI_KEY_CONN_RSP value calculation

negotiate_algorithms_common_params(spdm_version: int, *, include_pqc: bool | None = None) dict

Generate common NEGOTIATE_ALGORITHIMS parameters based on SPDM version

Based on: SPDM-Responder-Validator/library/spdm_responder_conformance_test_lib/spdm_responder_test_3_algorithms.c

NEGOTIATE_ALGORITHMS {SPDMVersion=NegotiatedVersion, …}

Code=E3h(NEGOTIATE_ALGORITHMS) NumAlgStruct=4 (NumAlgStruct=6 for SPDM 1.4+ with PQC) Length=48 (Length=56 for SPDM 1.4+ with PQC) MeasureSpec=1h OtherParamsSupport=2h BaseAsymAlgo=FFFh BaseHashAlgo=7Fh ExtAsymCount=0 ExtHashCount=0 ReqAlgStruct=02207F00,03200F00,0420FF0F,05200100 (+0620FF7F00,07200700 for SPDM 1.4+ with PQC)

require_response(result: dict[str, Any], request_name: str, response_name: str = 'response') Any

Return the parsed response, failing the case when there is none.

A responder that answers nothing at all leaves result['data'] unset, so every assertion that reads the message raises AttributeError on None and the case ends in Warning with a traceback instead of a verdict. Fail it with the transport’s reason instead.

Use this where the case does not already stop on a failed check_result – typically the ERROR-expecting cases, which pass fail_type='notify' because the request under test is meant to fail.

Parameters:
  • result (dict) – The dictionary returned by the SPDM API call.

  • request_name (str) – Request that was sent, e.g. 'GET_CSR'. Used in the log message.

  • response_name (str) – What the case expected back, e.g. 'ERROR(VersionMismatch)'.

Return type:

The parsed response message.

resolve_large_response(dut: DUT, result: dict[str, Any], request_name: str, response_name: str, structure: str) dict[str, Any]

Return the response, collecting it with CHUNK_GET when it is too large.

A response bigger than the DataTransferSize the requester advertised is not returned directly. The responder answers ERROR(LargeResponse) with a Handle, and the response has to be fetched with CHUNK_GET; this helper does that and hands back the reassembled message so the case can validate it as if it had arrived in one piece.

A response bigger than the advertised MaxSPDMmsgSize cannot be received at all, and ERROR(ResponseTooLarge) is the conformant answer. There is then no response_name to validate, so the case is Skipped rather than Failed. Certificate chains signed with a post-quantum algorithm are the case that reaches this in practice.

Anything else is returned untouched, for the caller to check as usual.

Parameters:
  • result (dict) – The dictionary returned by the SPDM API call.

  • request_name (str) – Request that was sent, e.g. 'SLOT_MANAGEMENT'. For log messages.

  • response_name (str) – Response the case validates, e.g. 'SLOT_MANAGEMENT_RESP'.

  • structure (str) – Name of the response template the reassembled bytes are parsed with.

skip_on_expected_error(result: dict[str, Any], request_name: str, response_name: str, expected_errors, note: str | None = None) None

Skip the case when the responder answers a request with a legal ERROR.

Several SPDM messages are only meaningful inside a secure session or a transfer that is already under way. The conformance suite issues them on their own, so a conformant responder is entitled to answer ERROR instead of the response under test. There is then no response_name to validate and the case is Skipped; any other failure is still a failure.

Parameters:
  • result (dict) – The dictionary returned by the SPDM API call.

  • request_name (str) – Request that was sent, e.g. 'CHUNK_GET'. Used in log messages.

  • response_name (str) – Response the case validates, e.g. 'CHUNK_RESPONSE'.

  • expected_errors (set of int) – ErrorCodes accepted for this case, e.g. OUT_OF_FLOW_ERRORS.

  • note (str) – Optional extra sentence logged with the skip message.

spdm_test_ctx(dut: DUT) dict[str, Any]

Return TestSetup context from @common_spdm_test(...).

Scripts can use this with def main(dut) so main(dut) in if __name__ == '__main__' keeps working without a ctx parameter.