1 #include "fru_utils.hpp"
2 
3 #include <algorithm>
4 #include <array>
5 #include <iterator>
6 
7 #include "gtest/gtest.h"
8 
9 extern "C"
10 {
11 // Include for I2C_SMBUS_BLOCK_MAX
12 #include <linux/i2c.h>
13 }
14 
15 static constexpr size_t blockSize = I2C_SMBUS_BLOCK_MAX;
16 
17 TEST(ValidateHeaderTest, InvalidFruVersionReturnsFalse)
18 {
19     // Validates the FruVersion is checked for the only legal value.
20     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
21         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
22 
23     EXPECT_FALSE(validateHeader(fruHeader));
24 }
25 
26 TEST(ValidateHeaderTest, InvalidReservedReturnsFalse)
27 {
28     // Validates the reserved bit(7:4) of first bytes.
29     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
30         0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
31 
32     EXPECT_FALSE(validateHeader(fruHeader));
33 }
34 
35 TEST(ValidateHeaderTest, InvalidPaddingReturnsFalse)
36 {
37     // Validates the padding byte (7th byte).
38     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
39         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00};
40 
41     EXPECT_FALSE(validateHeader(fruHeader));
42 }
43 
44 TEST(ValidateHeaderTest, InvalidChecksumReturnsFalse)
45 {
46     // Validates the checksum, check for incorrect value.
47     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
48         0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00};
49 
50     EXPECT_FALSE(validateHeader(fruHeader));
51 }
52 
53 TEST(ValidateHeaderTest, ValidChecksumReturnsTrue)
54 {
55     // Validates the checksum, check for correct value.
56     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
57         0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
58 
59     EXPECT_TRUE(validateHeader(fruHeader));
60 }
61 
62 TEST(VerifyOffsetTest, EmptyFruDataReturnsFalse)
63 {
64     // Validates the FruData size is checked for non empty.
65     std::vector<uint8_t> fruData = {};
66 
67     EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 0));
68 }
69 
70 TEST(VerifyOffsetTest, AreaOutOfRangeReturnsFalse)
71 {
72     // Validates the FruArea value, check if it is within range.
73     const std::vector<uint8_t> fruData = {0x01, 0x00, 0x00, 0x00, 0x00,
74                                           0x00, 0x00, 0x00, 0x00};
75 
76     unsigned int areaOutOfRange = 8;
77     EXPECT_FALSE(
78         verifyOffset(fruData, static_cast<fruAreas>(areaOutOfRange), 0));
79 }
80 
81 TEST(VerifyOffsetTest, OverlapNextAreaReturnsFalse)
82 {
83     // Validates the Overlap of offsets with overlapped values.
84     const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03,
85                                           0x04, 0x00, 0x00, 0x00};
86 
87     EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 2));
88 }
89 
90 TEST(VerifyOffsetTest, OverlapPrevAreaReturnsFalse)
91 {
92     // Validates the Overlap of offsets with overlapped values.
93     const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x03, 0x02,
94                                           0x07, 0x00, 0x00, 0x00};
95 
96     EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaProduct, 2));
97 }
98 
99 TEST(VerifyOffsetTest, ValidInputDataNoOverlapReturnsTrue)
100 {
101     // Validates all inputs with expected value and no overlap.
102     const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03,
103                                           0x04, 0x00, 0x00, 0x00};
104 
105     EXPECT_TRUE(verifyOffset(fruData, fruAreas::fruAreaChassis, 1));
106 }
107 
108 TEST(VerifyChecksumTest, EmptyInput)
109 {
110     std::vector<uint8_t> data = {};
111 
112     EXPECT_EQ(calculateChecksum(data), 0);
113 }
114 
115 TEST(VerifyChecksumTest, SingleOneInput)
116 {
117     std::vector<uint8_t> data(1, 1);
118 
119     EXPECT_EQ(calculateChecksum(data), 255);
120 }
121 
122 TEST(VerifyChecksumTest, AllOneInput)
123 {
124     std::vector<uint8_t> data(256, 1);
125 
126     EXPECT_EQ(calculateChecksum(data), 0);
127 }
128 
129 TEST(VerifyChecksumTest, WrapBoundaryLow)
130 {
131     std::vector<uint8_t> data = {255, 0};
132 
133     EXPECT_EQ(calculateChecksum(data), 1);
134 }
135 
136 TEST(VerifyChecksumTest, WrapBoundaryExact)
137 {
138     std::vector<uint8_t> data = {255, 1};
139 
140     EXPECT_EQ(calculateChecksum(data), 0);
141 }
142 
143 TEST(VerifyChecksumTest, WrapBoundaryHigh)
144 {
145     std::vector<uint8_t> data = {255, 2};
146 
147     EXPECT_EQ(calculateChecksum(data), 255);
148 }
149 
150 int64_t getDataTempl(const std::vector<uint8_t>& data, off_t offset,
151                      size_t length, uint8_t* outBuf)
152 {
153     if (offset >= static_cast<off_t>(data.size()))
154     {
155         return 0;
156     }
157 
158     uint16_t idx = offset;
159     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
160     for (; idx < std::min(data.size(), offset + length); ++idx, ++outBuf)
161     {
162         *outBuf = data[idx];
163     }
164 
165     return idx - offset;
166 }
167 
168 TEST(FRUReaderTest, ReadData)
169 {
170     std::vector<uint8_t> data = {};
171     for (size_t i = 0; i < blockSize * 2; i++)
172     {
173         data.push_back(i);
174     }
175     std::array<uint8_t, blockSize * 2> rdbuf{};
176     auto getData = [&data](auto o, auto l, auto* b) {
177         return getDataTempl(data, o, l, b);
178     };
179     FRUReader reader(getData);
180 
181     EXPECT_EQ(reader.read(0, data.size(), rdbuf.data()),
182               static_cast<ssize_t>(data.size()));
183     EXPECT_TRUE(std::equal(rdbuf.begin(), rdbuf.end(), data.begin()));
184     for (size_t i = 0; i < blockSize * 2; i++)
185     {
186         EXPECT_EQ(reader.read(i, 1, rdbuf.data()), 1);
187         EXPECT_EQ(rdbuf[i], i);
188     }
189     EXPECT_EQ(reader.read(blockSize - 1, 2, rdbuf.data()), 2);
190     EXPECT_EQ(rdbuf[0], blockSize - 1);
191     EXPECT_EQ(rdbuf[1], blockSize);
192 }
193 
194 TEST(FRUReaderTest, StartPastUnknownEOF)
195 {
196     const std::vector<uint8_t> data = {};
197     auto getData = [&data](auto o, auto l, auto* b) {
198         return getDataTempl(data, o, l, b);
199     };
200     FRUReader reader(getData);
201 
202     EXPECT_EQ(reader.read(1, 1, nullptr), 0);
203 }
204 
205 TEST(FRUReaderTest, StartPastKnownEOF)
206 {
207     std::vector<uint8_t> data = {};
208     data.resize(blockSize / 2);
209     std::array<uint8_t, blockSize> blockData{};
210     auto getData = [&data](auto o, auto l, auto* b) {
211         return getDataTempl(data, o, l, b);
212     };
213     FRUReader reader(getData);
214 
215     EXPECT_EQ(reader.read(0, blockSize, blockData.data()),
216               static_cast<ssize_t>(data.size()));
217     EXPECT_EQ(reader.read(data.size(), 1, nullptr), 0);
218     EXPECT_EQ(reader.read(data.size() + 1, 1, nullptr), 0);
219     EXPECT_EQ(reader.read(blockSize, 1, nullptr), 0);
220     EXPECT_EQ(reader.read(blockSize + 1, 1, nullptr), 0);
221 }
222 
223 TEST(FRUReaderTest, DecreasingEOF)
224 {
225     const std::vector<uint8_t> data = {};
226     auto getData = [&data](auto o, auto l, auto* b) {
227         return getDataTempl(data, o, l, b);
228     };
229     FRUReader reader(getData);
230 
231     EXPECT_EQ(reader.read(blockSize * 2, 1, nullptr), 0);
232     EXPECT_EQ(reader.read(blockSize + (blockSize / 2), 1, nullptr), 0);
233     EXPECT_EQ(reader.read(blockSize, 1, nullptr), 0);
234     EXPECT_EQ(reader.read(blockSize / 2, 1, nullptr), 0);
235     EXPECT_EQ(reader.read(0, 1, nullptr), 0);
236 }
237 
238 TEST(FRUReaderTest, CacheHit)
239 {
240     std::vector<uint8_t> data = {'X'};
241     std::array<uint8_t, blockSize> read1{};
242     std::array<uint8_t, blockSize> read2{};
243     auto getData = [&data](auto o, auto l, auto* b) {
244         return getDataTempl(data, o, l, b);
245     };
246     FRUReader reader(getData);
247 
248     // cache hit should return the same data for the second read even if we
249     // change it behind the FRUReader's back after the first
250     EXPECT_EQ(reader.read(0, blockSize, read1.data()), 1);
251     data[0] = 'Y';
252     EXPECT_EQ(reader.read(0, blockSize, read2.data()), 1);
253     EXPECT_EQ(read1[0], read2[0]);
254 }
255 
256 TEST(FRUReaderTest, ReadPastKnownEnd)
257 {
258     const std::vector<uint8_t> data = {'X', 'Y'};
259     std::array<uint8_t, blockSize> rdbuf{};
260     auto getData = [&data](auto o, auto l, auto* b) {
261         return getDataTempl(data, o, l, b);
262     };
263     FRUReader reader(getData);
264 
265     EXPECT_EQ(reader.read(0, data.size(), rdbuf.data()),
266               static_cast<ssize_t>(data.size()));
267     EXPECT_EQ(rdbuf[0], 'X');
268     EXPECT_EQ(rdbuf[1], 'Y');
269     EXPECT_EQ(reader.read(1, data.size(), rdbuf.data()),
270               static_cast<ssize_t>(data.size() - 1));
271     EXPECT_EQ(rdbuf[0], 'Y');
272 }
273 
274 TEST(FRUReaderTest, MultiBlockRead)
275 {
276     std::vector<uint8_t> data = {};
277     data.resize(blockSize, 'X');
278     data.resize(2 * blockSize, 'Y');
279     std::array<uint8_t, 2 * blockSize> rdbuf{};
280     auto getData = [&data](auto o, auto l, auto* b) {
281         return getDataTempl(data, o, l, b);
282     };
283     FRUReader reader(getData);
284 
285     EXPECT_EQ(reader.read(0, 2 * blockSize, rdbuf.data()),
286               static_cast<ssize_t>(2 * blockSize));
287     EXPECT_TRUE(std::equal(rdbuf.begin(), rdbuf.end(), data.begin()));
288 }
289 
290 TEST(FRUReaderTest, ShrinkingEEPROM)
291 {
292     std::vector<uint8_t> data = {};
293     data.resize(3 * blockSize, 'X');
294     std::array<uint8_t, blockSize> rdbuf{};
295     auto getData = [&data](auto o, auto l, auto* b) {
296         return getDataTempl(data, o, l, b);
297     };
298     FRUReader reader(getData);
299 
300     EXPECT_EQ(reader.read(data.size() - 1, 2, rdbuf.data()), 1);
301     data.resize(blockSize);
302     EXPECT_EQ(reader.read(data.size() - 1, 2, rdbuf.data()), 1);
303 }
304 
305 TEST(FindFRUHeaderTest, InvalidHeader)
306 {
307     const std::vector<uint8_t> data = {255, 16};
308     off_t offset = 0;
309     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
310     auto getData = [&data](auto o, auto l, auto* b) {
311         return getDataTempl(data, o, l, b);
312     };
313     FRUReader reader(getData);
314 
315     EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
316 }
317 
318 TEST(FindFRUHeaderTest, NoData)
319 {
320     const std::vector<uint8_t> data = {};
321     off_t offset = 0;
322     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
323     auto getData = [&data](auto o, auto l, auto* b) {
324         return getDataTempl(data, o, l, b);
325     };
326     FRUReader reader(getData);
327 
328     EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
329 }
330 
331 TEST(FindFRUHeaderTest, ValidHeader)
332 {
333     const std::vector<uint8_t> data = {0x01, 0x00, 0x01, 0x02,
334                                        0x03, 0x04, 0x00, 0xf5};
335     off_t offset = 0;
336     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
337     auto getData = [&data](auto o, auto l, auto* b) {
338         return getDataTempl(data, o, l, b);
339     };
340     FRUReader reader(getData);
341 
342     EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset));
343     EXPECT_EQ(0, offset);
344 }
345 
346 TEST(FindFRUHeaderTest, TyanInvalidHeader)
347 {
348     std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
349     data.resize(0x6000 + I2C_SMBUS_BLOCK_MAX);
350     off_t offset = 0;
351     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
352     auto getData = [&data](auto o, auto l, auto* b) {
353         return getDataTempl(data, o, l, b);
354     };
355     FRUReader reader(getData);
356 
357     EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
358 }
359 
360 TEST(FindFRUHeaderTest, TyanNoData)
361 {
362     const std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
363     off_t offset = 0;
364     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
365     auto getData = [&data](auto o, auto l, auto* b) {
366         return getDataTempl(data, o, l, b);
367     };
368     FRUReader reader(getData);
369 
370     EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
371 }
372 
373 TEST(FindFRUHeaderTest, TyanValidHeader)
374 {
375     std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
376     data.resize(0x6000);
377     constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
378         0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
379     copy(fruHeader.begin(), fruHeader.end(), back_inserter(data));
380 
381     off_t offset = 0;
382     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
383     auto getData = [&data](auto o, auto l, auto* b) {
384         return getDataTempl(data, o, l, b);
385     };
386     FRUReader reader(getData);
387 
388     EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset));
389     EXPECT_EQ(0x6000, offset);
390 }
391