blob: f0585d1e1c91071e1ba98fd614fa04ebc399fe3f [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION &
* AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "../requester/mctp.h"
#include "../../common/test/mockSocketIo.hpp"
#include <poll.h>
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::Return;
class MctpRequesterTest : public MctpTestFixture
{
};
/**
* Test: nsm_send successful message transmission
*/
TEST_F(MctpRequesterTest, NsmSendSuccess)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
auto reqMsg = NsmMessageBuilder::request(0x01, 5, {0xAA, 0xBB});
// Expect sendmsg with proper structure
EXPECT_CALL(*mockIo_, sendmsg(testFd, _, 0))
.WillOnce([=](int, const struct msghdr *msg, int) {
EXPECT_EQ(msg->msg_iovlen, 2);
struct iovec *iov = msg->msg_iov;
// Verify MCTP header
EXPECT_EQ(iov[0].iov_len, 3);
const uint8_t *hdr =
static_cast<const uint8_t *>(iov[0].iov_base);
EXPECT_EQ(hdr[0], MCTP_MSG_TAG_REQ); // Tag
EXPECT_EQ(hdr[1], testEid); // EID
EXPECT_EQ(hdr[2], 0x7E); // VDM type
// Verify NSM payload
EXPECT_EQ(iov[1].iov_len, reqMsg.size());
return iov[0].iov_len + iov[1].iov_len;
});
int rc = nsm_send(testEid, testFd, reqMsg.data(), reqMsg.size());
EXPECT_EQ(rc, NSM_REQUESTER_SUCCESS);
}
/**
* Test: nsm_send failure when sendmsg fails
*/
TEST_F(MctpRequesterTest, NsmSendFailure)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
auto reqMsg = NsmMessageBuilder::request(0x01, 5);
EXPECT_CALL(*mockIo_, sendmsg(testFd, _, 0)).WillOnce(Return(-1));
int rc = nsm_send(testEid, testFd, reqMsg.data(), reqMsg.size());
EXPECT_EQ(rc, NSM_REQUESTER_SEND_FAIL);
}
/**
* Test: nsm_recv_any successful response reception
*/
TEST_F(MctpRequesterTest, NsmRecvAnySuccess)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t testTag = 0x01;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
// Enqueue response
auto nsmResp = NsmMessageBuilder::successResponse(0x01, 5);
capture_->enqueueMctpResponse(testEid, testTag, nsmResp);
// Setup expectations
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
// Expect peek
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
// Expect recvmsg with iovec handling
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Verify we have 2 iovec entries
EXPECT_EQ(msg->msg_iovlen, 2);
// Copy MCTP header (3 bytes) to first iovec
EXPECT_EQ(msg->msg_iov[0].iov_len, 3);
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload to second iovec
size_t nsmLen = resp.mctpPayload.size() - 3;
EXPECT_EQ(msg->msg_iov[1].iov_len, nsmLen);
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, nsmLen);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_SUCCESS);
EXPECT_NE(respMsg, nullptr);
EXPECT_EQ(respLen, nsmResp.size());
EXPECT_EQ(receivedTag, testTag);
if (respMsg) {
// Verify response content
EXPECT_EQ(respMsg[3], 0x00); // Success completion code
free(respMsg);
}
}
/**
* Test: nsm_recv_any timeout scenario
*/
TEST_F(MctpRequesterTest, NsmRecvAnyTimeout)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
// Expect poll returns 0 (timeout)
expectPoll(testFd, RESPONSE_TIME_OUT, 0);
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_RECV_TIMEOUT);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_recv_any EID mismatch
*/
TEST_F(MctpRequesterTest, NsmRecvAnyEidMismatch)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t wrongEid = 0x99;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
// Response from wrong EID
auto nsmResp = NsmMessageBuilder::successResponse(0x01, 5);
std::vector<uint8_t> mctpMsg = {0x01, wrongEid, 0x7E};
mctpMsg.insert(mctpMsg.end(), nsmResp.begin(), nsmResp.end());
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(mctpMsg.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, mctpMsg.data(), 3);
// Copy NSM payload to second iovec
size_t nsmLen = mctpMsg.size() - 3;
memcpy(msg->msg_iov[1].iov_base, mctpMsg.data() + 3,
nsmLen);
return static_cast<ssize_t>(mctpMsg.size());
});
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_EID_MISMATCH);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_recv_any with invalid message length (too short)
*/
TEST_F(MctpRequesterTest, NsmRecvAnyInvalidLengthTooShort)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
// Message too short (less than MCTP_PREFIX_LEN + sizeof(nsm_msg_hdr))
std::vector<uint8_t> shortMsg = {0x01, testEid, 0x7E, 0x00};
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(shortMsg.size()));
// The discard recv in mctp.c uses a VLA that may be optimised away
// by the compiler at -O2 (dead-store elimination). The return code
// is the authoritative assertion; treat the discard call as optional.
EXPECT_CALL(*mockIo_, recv(testFd, _, shortMsg.size(), 0))
.Times(::testing::AnyNumber())
.WillRepeatedly(Return(shortMsg.size()));
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_INVALID_RECV_LEN);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_recv_any with wrong message type (not VDM)
*/
TEST_F(MctpRequesterTest, NsmRecvAnyWrongMsgType)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
auto nsmResp = NsmMessageBuilder::successResponse(0x01, 5);
std::vector<uint8_t> mctpMsg = {0x01, testEid,
0x00}; // Wrong type (not 0x7E)
mctpMsg.insert(mctpMsg.end(), nsmResp.begin(), nsmResp.end());
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(mctpMsg.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, mctpMsg.data(), 3);
// Copy NSM payload to second iovec
size_t nsmLen = mctpMsg.size() - 3;
memcpy(msg->msg_iov[1].iov_base, mctpMsg.data() + 3,
nsmLen);
return static_cast<ssize_t>(mctpMsg.size());
});
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_NOT_NSM_MSG);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_recv_any rejects request messages (only accepts responses)
*/
TEST_F(MctpRequesterTest, NsmRecvAnyRejectsRequest)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
// Build a request message (rqst=1)
auto nsmReq = NsmMessageBuilder::request(0x01, 5);
capture_->enqueueMctpResponse(testEid, 0x01, nsmReq);
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload to second iovec
size_t nsmLen = resp.mctpPayload.size() - 3;
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, nsmLen);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_NOT_RESP_MSG);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_recv with matching instance ID
*/
TEST_F(MctpRequesterTest, NsmRecvSuccess)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t instanceId = 5;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
auto nsmResp = NsmMessageBuilder::successResponse(0x01, instanceId);
capture_->enqueueMctpResponse(testEid, 0x01, nsmResp);
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload to second iovec
size_t nsmLen = resp.mctpPayload.size() - 3;
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, nsmLen);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc = nsm_recv(testEid, testFd, instanceId, &respMsg, &respLen);
EXPECT_EQ(rc, NSM_REQUESTER_SUCCESS);
EXPECT_NE(respMsg, nullptr);
if (respMsg) {
free(respMsg);
}
}
/**
* Test: nsm_recv with instance ID mismatch
*/
TEST_F(MctpRequesterTest, NsmRecvInstanceIdMismatch)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t expectedIid = 5;
const uint8_t receivedIid = 7;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
auto nsmResp = NsmMessageBuilder::successResponse(0x01, receivedIid);
capture_->enqueueMctpResponse(testEid, 0x01, nsmResp);
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload to second iovec
size_t nsmLen = resp.mctpPayload.size() - 3;
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, nsmLen);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc = nsm_recv(testEid, testFd, expectedIid, &respMsg, &respLen);
EXPECT_EQ(rc, NSM_REQUESTER_INSTANCE_ID_MISMATCH);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: nsm_send_recv successful request-response cycle
*/
TEST_F(MctpRequesterTest, NsmSendRecvSuccess)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t instanceId = 5;
auto reqMsg = NsmMessageBuilder::request(0x01, instanceId);
uint8_t *respMsg = nullptr;
size_t respLen = 0;
// Expect send
EXPECT_CALL(*mockIo_, sendmsg(testFd, _, 0))
.WillOnce(Return(3 + reqMsg.size()));
// Enqueue response
auto nsmResp = NsmMessageBuilder::successResponse(0x01, instanceId);
capture_->enqueueMctpResponse(testEid, 0x01, nsmResp);
// Expect recv cycle
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header (3 bytes) to first iovec
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload to second iovec
size_t nsmLen = resp.mctpPayload.size() - 3;
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, nsmLen);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc = nsm_send_recv(testEid, testFd, reqMsg.data(), reqMsg.size(),
&respMsg, &respLen);
EXPECT_EQ(rc, NSM_REQUESTER_SUCCESS);
EXPECT_NE(respMsg, nullptr);
if (respMsg) {
free(respMsg);
}
}
/**
* Test: nsm_send_recv rejects non-request messages
*/
TEST_F(MctpRequesterTest, NsmSendRecvRejectsNonRequest)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
auto respMsg_in = NsmMessageBuilder::successResponse(0x01, 5);
uint8_t *respMsg = nullptr;
size_t respLen = 0;
// Try to send a response message (should fail)
int rc = nsm_send_recv(testEid, testFd, respMsg_in.data(),
respMsg_in.size(), &respMsg, &respLen);
EXPECT_EQ(rc, NSM_REQUESTER_NOT_REQ_MSG);
}
/**
* Test: nsm_send_recv handles recv timeout
*/
TEST_F(MctpRequesterTest, NsmSendRecvTimeout)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
const uint8_t instanceId = 5;
auto reqMsg = NsmMessageBuilder::request(0x01, instanceId);
uint8_t *respMsg = nullptr;
size_t respLen = 0;
// Expect send
EXPECT_CALL(*mockIo_, sendmsg(testFd, _, 0))
.WillOnce(Return(3 + reqMsg.size()));
// Expect poll timeout
expectPoll(testFd, RESPONSE_TIME_OUT, 0);
int rc = nsm_send_recv(testEid, testFd, reqMsg.data(), reqMsg.size(),
&respMsg, &respLen);
EXPECT_EQ(rc, NSM_REQUESTER_RECV_TIMEOUT);
}
/**
* Test: Peek-read pattern with length mismatch
*/
TEST_F(MctpRequesterTest, PeekReadLengthMismatch)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
auto nsmResp = NsmMessageBuilder::successResponse(0x01, 5);
size_t expectedLen = 3 + nsmResp.size();
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
// Peek returns one length
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(expectedLen));
// But recvmsg returns different length
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce(Return(expectedLen - 1)); // Mismatch!
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
EXPECT_EQ(rc, NSM_REQUESTER_INVALID_RECV_LEN);
EXPECT_EQ(respMsg, nullptr);
}
/**
* Test: Response message too small (less than minimum required)
*
* Covers mctp.cpp lines 118-120: Error path when NSM response payload is
* smaller than sizeof(nsm_msg_hdr) + NSM_RESPONSE_MIN_LEN (5 + 4 = 9 bytes
* minimum)
*
* Note: Must pass initial length check (>= 8 bytes total) to reach line 118
*/
TEST_F(MctpRequesterTest, NsmRecvAnyResponseTooSmall)
{
const int testFd = 42;
const uint8_t testEid = 0x10;
uint8_t *respMsg = nullptr;
size_t respLen = 0;
uint8_t receivedTag = 0;
// Create a response with 8 bytes NSM payload (5 header + 3 data)
// This passes initial check (8 >= min_len=8) but fails response size
// check (8 < sizeof(nsm_msg_hdr) + NSM_RESPONSE_MIN_LEN = 5 + 4 = 9)
std::vector<uint8_t> tooSmallResp = {0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08}; // 8 bytes NSM
capture_->enqueueMctpResponse(testEid, 0x01, tooSmallResp);
MctpMessageCapture::Response resp;
ASSERT_TRUE(capture_->getNextResponse(resp));
expectPoll(testFd, RESPONSE_TIME_OUT, 1);
// Peek returns size: 3 MCTP + 8 NSM = 11 bytes (passes initial check)
EXPECT_CALL(*mockIo_, recv(testFd, nullptr, 0, MSG_PEEK | MSG_TRUNC))
.WillOnce(Return(resp.mctpPayload.size()));
// recvmsg returns the response
EXPECT_CALL(*mockIo_, recvmsg(testFd, _, 0))
.WillOnce([&](int, struct msghdr *msg, int) {
// Copy MCTP header
memcpy(msg->msg_iov[0].iov_base, resp.mctpPayload.data(),
3);
// Copy NSM payload (8 bytes)
memcpy(msg->msg_iov[1].iov_base,
resp.mctpPayload.data() + 3, 8);
return static_cast<ssize_t>(resp.mctpPayload.size());
});
int rc =
nsm_recv_any(testEid, testFd, &respMsg, &respLen, &receivedTag);
// Expect error: response message too small (lines 118-120)
EXPECT_EQ(rc, NSM_REQUESTER_RESP_MSG_TOO_SMALL);
EXPECT_EQ(respMsg, nullptr); // Memory should be freed
}