1 #include "helper.hpp"
2 #include "ipmi.hpp"
3 #include "manager_mock.hpp"
4 #include "process.hpp"
5 
6 #include <cstring>
7 #include <ipmiblob/test/crc_mock.hpp>
8 #include <span>
9 
10 #include <gtest/gtest.h>
11 
12 // ipmid.hpp isn't installed where we can grab it and this value is per BMC
13 // SoC.
14 #define MAX_IPMI_BUFFER 64
15 
16 using ::testing::_;
17 using ::testing::ElementsAre;
18 using ::testing::Eq;
19 using ::testing::Return;
20 using ::testing::StrictMock;
21 
22 namespace ipmiblob
23 {
24 CrcInterface* crcIntf = nullptr;
25 
26 std::uint16_t generateCrc(const std::vector<std::uint8_t>& data)
27 {
28     return (crcIntf) ? crcIntf->generateCrc(data) : 0x00;
29 }
30 } // namespace ipmiblob
31 
32 namespace blobs
33 {
34 namespace
35 {
36 
37 void EqualFunctions(IpmiBlobHandler lhs, IpmiBlobHandler rhs)
38 {
39     EXPECT_FALSE(lhs == nullptr);
40     EXPECT_FALSE(rhs == nullptr);
41 
42     Resp (*const* lPtr)(ManagerInterface*, std::span<const uint8_t>) =
43         lhs.target<Resp (*)(ManagerInterface*, std::span<const uint8_t>)>();
44 
45     Resp (*const* rPtr)(ManagerInterface*, std::span<const uint8_t>) =
46         rhs.target<Resp (*)(ManagerInterface*, std::span<const uint8_t>)>();
47 
48     EXPECT_TRUE(lPtr);
49     EXPECT_TRUE(rPtr);
50     EXPECT_EQ(*lPtr, *rPtr);
51 }
52 
53 } // namespace
54 
55 class ValidateBlobCommandTest : public ::testing::Test
56 {
57   protected:
58     void SetUp() override
59     {
60         ipmiblob::crcIntf = &crcMock;
61     }
62 
63     ipmiblob::CrcMock crcMock;
64 };
65 
66 TEST_F(ValidateBlobCommandTest, InvalidCommandReturnsFailure)
67 {
68     // Verify we handle an invalid command.
69     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
70     // There is no command 0xff.
71     IpmiBlobHandler handler = validateBlobCommand(0xff, request);
72     EXPECT_EQ(ipmi::responseInvalidFieldRequest(), handler(nullptr, {}));
73 }
74 
75 TEST_F(ValidateBlobCommandTest, ValidCommandWithoutPayload)
76 {
77     // Verify we handle a valid command that doesn't have a payload.
78     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
79     IpmiBlobHandler handler = validateBlobCommand(
80         static_cast<std::uint8_t>(BlobOEMCommands::bmcBlobGetCount), request);
81     EXPECT_FALSE(handler == nullptr);
82     EqualFunctions(getBlobCount, handler);
83 }
84 
85 TEST_F(ValidateBlobCommandTest, WithPayloadMinimumLengthIs3VerifyChecks)
86 {
87     // Verify that if there's a payload, it's at least one command byte and
88     // two bytes for the crc16 and then one data byte.
89 
90     std::vector<uint8_t> request(sizeof(uint16_t));
91     // There is a payload, but there are insufficient bytes.
92 
93     IpmiBlobHandler handler = validateBlobCommand(
94         static_cast<std::uint8_t>(BlobOEMCommands::bmcBlobGetCount), request);
95     EXPECT_EQ(ipmi::responseReqDataLenInvalid(), handler(nullptr, {}));
96 }
97 
98 TEST_F(ValidateBlobCommandTest, WithPayloadAndInvalidCrc)
99 {
100     // Verify that the CRC is checked, and failure is reported.
101     std::vector<uint8_t> request;
102     BmcBlobWriteTx req;
103     req.crc = 0x34;
104     req.sessionId = 0x54;
105     req.offset = 0x100;
106 
107     std::array<uint8_t, 2> expectedBytes = {0x66, 0x67};
108     request.resize(sizeof(struct BmcBlobWriteTx));
109     std::memcpy(request.data(), &req, sizeof(struct BmcBlobWriteTx));
110     request.insert(request.end(), expectedBytes.begin(), expectedBytes.end());
111 
112     // skip over cmd and crc.
113     std::vector<uint8_t> bytes(request.begin() + sizeof(req.crc),
114                                request.end());
115     EXPECT_CALL(crcMock, generateCrc(Eq(bytes))).WillOnce(Return(0x1234));
116 
117     IpmiBlobHandler handler = validateBlobCommand(
118         static_cast<std::uint8_t>(BlobOEMCommands::bmcBlobWrite), request);
119     EXPECT_EQ(ipmi::responseUnspecifiedError(), handler(nullptr, {}));
120 }
121 
122 TEST_F(ValidateBlobCommandTest, WithPayloadAndValidCrc)
123 {
124     // Verify the CRC is checked and if it matches, return the handler.
125     std::vector<uint8_t> request;
126     BmcBlobWriteTx req;
127     req.crc = 0x3412;
128     req.sessionId = 0x54;
129     req.offset = 0x100;
130 
131     std::array<uint8_t, 2> expectedBytes = {0x66, 0x67};
132     request.resize(sizeof(struct BmcBlobWriteTx));
133     std::memcpy(request.data(), &req, sizeof(struct BmcBlobWriteTx));
134     request.insert(request.end(), expectedBytes.begin(), expectedBytes.end());
135 
136     // skip over cmd and crc.
137     std::vector<uint8_t> bytes(request.begin() + sizeof(req.crc),
138                                request.end());
139     EXPECT_CALL(crcMock, generateCrc(Eq(bytes))).WillOnce(Return(0x3412));
140 
141     IpmiBlobHandler handler = validateBlobCommand(
142         static_cast<std::uint8_t>(BlobOEMCommands::bmcBlobWrite), request);
143     EXPECT_FALSE(handler == nullptr);
144     EqualFunctions(writeBlob, handler);
145 }
146 
147 class ProcessBlobCommandTest : public ::testing::Test
148 {
149   protected:
150     void SetUp() override
151     {
152         ipmiblob::crcIntf = &crcMock;
153     }
154 
155     ipmiblob::CrcMock crcMock;
156 };
157 
158 TEST_F(ProcessBlobCommandTest, CommandReturnsNotOk)
159 {
160     // Verify that if the IPMI command handler returns not OK that this is
161     // noticed and returned.
162 
163     StrictMock<ManagerMock> manager;
164     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
165 
166     IpmiBlobHandler h = [](ManagerInterface*, std::span<const uint8_t>) {
167         return ipmi::responseInvalidCommand();
168     };
169 
170     EXPECT_EQ(ipmi::responseInvalidCommand(),
171               processBlobCommand(h, &manager, request, MAX_IPMI_BUFFER));
172 }
173 
174 TEST_F(ProcessBlobCommandTest, CommandReturnsOkWithNoPayload)
175 {
176     // Verify that if the IPMI command handler returns OK but without a payload
177     // it doesn't try to compute a CRC.
178 
179     StrictMock<ManagerMock> manager;
180     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
181 
182     IpmiBlobHandler h = [](ManagerInterface*, std::span<const uint8_t>) {
183         return ipmi::responseSuccess(std::vector<uint8_t>());
184     };
185 
186     EXPECT_EQ(ipmi::responseSuccess(std::vector<uint8_t>()),
187               processBlobCommand(h, &manager, request, MAX_IPMI_BUFFER));
188 }
189 
190 TEST_F(ProcessBlobCommandTest, CommandReturnsOkWithInvalidPayloadLength)
191 {
192     // There is a minimum payload length of 2 bytes (the CRC only, no data, for
193     // read), this returns 1.
194 
195     StrictMock<ManagerMock> manager;
196     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
197 
198     IpmiBlobHandler h = [](ManagerInterface*, std::span<const uint8_t>) {
199         return ipmi::responseSuccess(std::vector<uint8_t>(1));
200     };
201 
202     EXPECT_EQ(ipmi::responseUnspecifiedError(),
203               processBlobCommand(h, &manager, request, MAX_IPMI_BUFFER));
204 }
205 
206 TEST_F(ProcessBlobCommandTest, CommandReturnsOkWithValidPayloadLength)
207 {
208     // There is a minimum payload length of 3 bytes, this command returns a
209     // payload of 3 bytes and the crc code is called to process the payload.
210 
211     StrictMock<ManagerMock> manager;
212     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
213     uint32_t payloadLen = sizeof(uint16_t) + sizeof(uint8_t);
214 
215     IpmiBlobHandler h = [payloadLen](ManagerInterface*,
216                                      std::span<const uint8_t>) {
217         std::vector<uint8_t> output(payloadLen, 0);
218         output[2] = 0x56;
219         return ipmi::responseSuccess(output);
220     };
221 
222     EXPECT_CALL(crcMock, generateCrc(_)).WillOnce(Return(0x3412));
223 
224     auto result = validateReply(
225         processBlobCommand(h, &manager, request, MAX_IPMI_BUFFER));
226 
227     EXPECT_EQ(result.size(), payloadLen);
228     EXPECT_THAT(result, ElementsAre(0x12, 0x34, 0x56));
229 }
230 
231 TEST_F(ProcessBlobCommandTest,
232        CommandReturnsErrorWithReplyExceededMaxTransferSize)
233 {
234     // There is a minimum payload length of 3 bytes, this command returns a
235     // payload of 3 bytes and the crc code is called to process the payload.
236 
237     StrictMock<ManagerMock> manager;
238     std::vector<uint8_t> request(MAX_IPMI_BUFFER - 1);
239     uint32_t payloadLen = sizeof(uint16_t) + sizeof(uint8_t);
240 
241     IpmiBlobHandler h = [payloadLen](ManagerInterface*,
242                                      std::span<const uint8_t>) {
243         std::vector<uint8_t> output(payloadLen, 0);
244         output[2] = 0x56;
245         return ipmi::responseSuccess(output);
246     };
247 
248     EXPECT_EQ(ipmi::responseResponseError(),
249               processBlobCommand(h, &manager, request, 0));
250 }
251 } // namespace blobs
252