blob: b770bec9ce9bb6472d0c5ec8709df047a1704a2a [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION &
* AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
*
* Tests for encode_nsm_runtime_ist_complete_event /
* decode_nsm_runtime_ist_complete_event (libnsm/diagnostics.{h,c}).
*
* The Runtime IST Complete v1 event is fixed-length, so coverage focuses on:
* - happy-path encode (header bits, version v1, event id/class, fields)
* - happy-path decode (round-trip, including negative NvS24.8 temperatures
* and a non-zero event_state parameter)
* - little-endian wire byte order of multi-byte fields
* - all NULL pointer error paths
* - too-short message length
* - data_size inconsistency vs msg_len
* - data_size != sizeof(payload) (wrong-sized "RIST" frame)
*/
#include "base.h"
#include "diagnostics.h"
#include <cstring>
#include <gtest/gtest.h>
#include <vector>
namespace
{
constexpr size_t kRistMsgLen = sizeof(nsm_msg_hdr) + NSM_EVENT_MIN_LEN +
sizeof(nsm_runtime_ist_complete_event_payload);
nsm_runtime_ist_complete_event_payload makeSamplePayload()
{
nsm_runtime_ist_complete_event_payload p{};
// Device UUID and app version: short ASCII strings; remainder
// zero-padded.
std::strncpy(p.gpu_identifier, "GPU-12345-abcde",
NSM_RIST_GPU_UUID_LEN - 1);
std::strncpy(p.app_version, "rist-1.2.3", NSM_RIST_APP_VERSION_LEN - 1);
p.timestamp = 0x0123456789ABCDEFULL;
p.result = 1; // Pass per spec
p.status_code = 0xDEADBEEFCAFEBABEULL;
// NvS24.8: 100.00 C => 100 << 8 = 0x6400
p.max_temperature = static_cast<int32_t>(100 * (1 << 8));
// 75.5 C => 75.5 * 256 = 19328 = 0x4B80
p.avg_temperature = static_cast<int32_t>(75.5 * (1 << 8));
return p;
}
} // namespace
// -------- encode happy path ----------------------------------------------
TEST(RuntimeISTCompleteEvent, EncodeHappyPath)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
const auto payload = makeSamplePayload();
const uint16_t event_state = 0; // spec value
int rc = encode_nsm_runtime_ist_complete_event(
/*instance_id=*/3,
/*ackr=*/true, event_state, &payload, msg);
EXPECT_EQ(rc, NSM_SW_SUCCESS);
// Header sanity: request flag and datagram bit set for events.
EXPECT_EQ(msg->hdr.request, 1);
EXPECT_EQ(msg->hdr.datagram, 1);
EXPECT_EQ(msg->hdr.nvidia_msg_type, NSM_TYPE_DIAGNOSTIC);
auto *event = reinterpret_cast<nsm_event *>(msg->payload);
EXPECT_EQ(event->version, NSM_EVENT_VERSION_V1);
EXPECT_EQ(event->ackr, 1u);
EXPECT_EQ(event->event_id, NSM_RUNTIME_IST_COMPLETE_EVENT);
EXPECT_EQ(event->event_class, NSM_GENERAL_EVENT_CLASS);
EXPECT_EQ(event->event_state, 0);
EXPECT_EQ(event->data_size,
sizeof(nsm_runtime_ist_complete_event_payload));
}
TEST(RuntimeISTCompleteEvent, EncodePassesThroughEventState)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
const auto payload = makeSamplePayload();
const uint16_t event_state = 0xBEEF;
int rc = encode_nsm_runtime_ist_complete_event(0, false, event_state,
&payload, msg);
EXPECT_EQ(rc, NSM_SW_SUCCESS);
auto *event = reinterpret_cast<nsm_event *>(msg->payload);
// event_state on the wire is little-endian; on LE hosts the field reads
// back as-is via direct struct access.
EXPECT_EQ(le16toh(event->event_state), event_state);
}
TEST(RuntimeISTCompleteEvent, EncodeAckrFalseClearsAckrBit)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
const auto payload = makeSamplePayload();
int rc =
encode_nsm_runtime_ist_complete_event(0, false, 0, &payload, msg);
EXPECT_EQ(rc, NSM_SW_SUCCESS);
auto *event = reinterpret_cast<nsm_event *>(msg->payload);
EXPECT_EQ(event->ackr, 0u);
}
TEST(RuntimeISTCompleteEvent, EncodeDoesNotMutateCallerPayload)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
nsm_runtime_ist_complete_event_payload payload = makeSamplePayload();
const auto original = payload;
int rc =
encode_nsm_runtime_ist_complete_event(0, true, 0, &payload, msg);
EXPECT_EQ(rc, NSM_SW_SUCCESS);
EXPECT_EQ(payload.timestamp, original.timestamp);
EXPECT_EQ(payload.status_code, original.status_code);
EXPECT_EQ(payload.max_temperature, original.max_temperature);
EXPECT_EQ(payload.avg_temperature, original.avg_temperature);
EXPECT_EQ(0,
std::memcmp(payload.gpu_identifier, original.gpu_identifier,
NSM_RIST_GPU_UUID_LEN));
EXPECT_EQ(0, std::memcmp(payload.app_version, original.app_version,
NSM_RIST_APP_VERSION_LEN));
}
TEST(RuntimeISTCompleteEvent, EncodeWritesLittleEndianWireBytes)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
nsm_runtime_ist_complete_event_payload payload{};
payload.timestamp = 0x0011223344556677ULL;
payload.status_code = 0x8899AABBCCDDEEFFULL;
payload.max_temperature = static_cast<int32_t>(0x12345678);
payload.avg_temperature = static_cast<int32_t>(0x7FFFFFFF);
payload.result = 1;
int rc =
encode_nsm_runtime_ist_complete_event(0, true, 0, &payload, msg);
ASSERT_EQ(rc, NSM_SW_SUCCESS);
auto *event = reinterpret_cast<nsm_event *>(msg->payload);
auto *wire =
reinterpret_cast<const nsm_runtime_ist_complete_event_payload *>(
event->data);
// Read the raw little-endian bytes from the wire field, convert with
// le*toh, and confirm we recover the host values. This is the most
// portable way to assert byte order without making assumptions about
// host endianness.
EXPECT_EQ(le64toh(wire->timestamp), 0x0011223344556677ULL);
EXPECT_EQ(le64toh(wire->status_code), 0x8899AABBCCDDEEFFULL);
EXPECT_EQ(static_cast<int32_t>(
le32toh(static_cast<uint32_t>(wire->max_temperature))),
0x12345678);
EXPECT_EQ(static_cast<int32_t>(
le32toh(static_cast<uint32_t>(wire->avg_temperature))),
0x7FFFFFFF);
}
// -------- encode error paths ---------------------------------------------
TEST(RuntimeISTCompleteEvent, EncodeNullPayload)
{
std::vector<uint8_t> buf(kRistMsgLen);
auto *msg = reinterpret_cast<nsm_msg *>(buf.data());
int rc =
encode_nsm_runtime_ist_complete_event(0, true, 0, nullptr, msg);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
TEST(RuntimeISTCompleteEvent, EncodeNullMsg)
{
const auto payload = makeSamplePayload();
int rc = encode_nsm_runtime_ist_complete_event(0, true, 0, &payload,
nullptr);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
// -------- decode happy paths --------------------------------------------
class RuntimeISTCompleteEventDecode : public testing::Test
{
protected:
RuntimeISTCompleteEventDecode() : event_msg(kRistMsgLen) {}
void encodePayload(const nsm_runtime_ist_complete_event_payload &p,
uint16_t event_state = 0)
{
auto rc = encode_nsm_runtime_ist_complete_event(
0, true, event_state, &p,
reinterpret_cast<nsm_msg *>(event_msg.data()));
ASSERT_EQ(rc, NSM_SW_SUCCESS);
}
nsm_msg *msg() { return reinterpret_cast<nsm_msg *>(event_msg.data()); }
std::vector<uint8_t> event_msg;
};
TEST_F(RuntimeISTCompleteEventDecode, RoundTripPass)
{
const auto encoded_payload = makeSamplePayload();
encodePayload(encoded_payload, /*event_state=*/0);
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, &event_state, &decoded);
ASSERT_EQ(rc, NSM_SW_SUCCESS);
EXPECT_EQ(event_class, NSM_GENERAL_EVENT_CLASS);
EXPECT_EQ(event_state, 0);
EXPECT_EQ(decoded.timestamp, encoded_payload.timestamp);
EXPECT_EQ(decoded.status_code, encoded_payload.status_code);
EXPECT_EQ(decoded.result, encoded_payload.result);
EXPECT_EQ(decoded.max_temperature, encoded_payload.max_temperature);
EXPECT_EQ(decoded.avg_temperature, encoded_payload.avg_temperature);
EXPECT_EQ(0, std::memcmp(decoded.gpu_identifier,
encoded_payload.gpu_identifier,
NSM_RIST_GPU_UUID_LEN));
EXPECT_EQ(0,
std::memcmp(decoded.app_version, encoded_payload.app_version,
NSM_RIST_APP_VERSION_LEN));
}
TEST_F(RuntimeISTCompleteEventDecode, RoundTripFailWithNonZeroEventState)
{
auto encoded_payload = makeSamplePayload();
encoded_payload.result = 0; // Fail
encoded_payload.status_code = 0x42;
encodePayload(encoded_payload, /*event_state=*/0xCAFE);
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, &event_state, &decoded);
ASSERT_EQ(rc, NSM_SW_SUCCESS);
EXPECT_EQ(event_state, 0xCAFE);
EXPECT_EQ(decoded.result, 0u);
EXPECT_EQ(decoded.status_code, 0x42u);
}
TEST_F(RuntimeISTCompleteEventDecode, RoundTripNegativeTemperatures)
{
auto encoded_payload = makeSamplePayload();
// -25.5 C in NvS24.8: -25.5 * 256 = -6528 = 0xFFFFE680 (two's
// complement).
encoded_payload.max_temperature =
static_cast<int32_t>(-25.5 * (1 << 8));
encoded_payload.avg_temperature = static_cast<int32_t>(-40 * (1 << 8));
encodePayload(encoded_payload);
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, &event_state, &decoded);
ASSERT_EQ(rc, NSM_SW_SUCCESS);
EXPECT_EQ(decoded.max_temperature, encoded_payload.max_temperature);
EXPECT_EQ(decoded.avg_temperature, encoded_payload.avg_temperature);
// Sanity check the NvS24.8 -> double mapping that consumers will use.
EXPECT_DOUBLE_EQ(static_cast<double>(decoded.max_temperature) / 256.0,
-25.5);
EXPECT_DOUBLE_EQ(static_cast<double>(decoded.avg_temperature) / 256.0,
-40.0);
}
// -------- decode error paths --------------------------------------------
TEST_F(RuntimeISTCompleteEventDecode, DecodeNullMsg)
{
encodePayload(makeSamplePayload());
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
nullptr, event_msg.size(), &event_class, &event_state, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeNullEventClass)
{
encodePayload(makeSamplePayload());
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), nullptr, &event_state, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeNullEventState)
{
encodePayload(makeSamplePayload());
uint8_t event_class{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, nullptr, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeNullPayload)
{
encodePayload(makeSamplePayload());
uint8_t event_class{};
uint16_t event_state{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, &event_state, nullptr);
EXPECT_EQ(rc, NSM_SW_ERROR_NULL);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeShortMsgLen)
{
encodePayload(makeSamplePayload());
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
// msg_len < hdr + NSM_EVENT_MIN_LEN -> LENGTH error.
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), sizeof(nsm_msg_hdr) + NSM_EVENT_MIN_LEN - 1, &event_class,
&event_state, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_LENGTH);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeDataSizeInconsistentWithMsgLen)
{
encodePayload(makeSamplePayload());
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
// Trim msg_len so that the wire data_size (= sizeof(payload)) no longer
// equals msg_len - hdr - NSM_EVENT_MIN_LEN. RIST is fixed-length, so
// any mismatch is a hard failure.
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size() - 1, &event_class, &event_state, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_DATA);
}
TEST_F(RuntimeISTCompleteEventDecode, DecodeDataSizeNotEqualPayloadSize)
{
encodePayload(makeSamplePayload());
// Tamper with the wire frame so it advertises a payload size that
// matches the buffer (passes the consistency check) but does not match
// sizeof(*payload). Append 1 byte and bump data_size by 1 to keep the
// first check happy and exercise the second check.
event_msg.push_back(0xAB);
auto *event = reinterpret_cast<nsm_event *>(msg()->payload);
event->data_size += 1;
uint8_t event_class{};
uint16_t event_state{};
nsm_runtime_ist_complete_event_payload decoded{};
auto rc = decode_nsm_runtime_ist_complete_event(
msg(), event_msg.size(), &event_class, &event_state, &decoded);
EXPECT_EQ(rc, NSM_SW_ERROR_DATA);
}