xref: /openbmc/entity-manager/src/fru_device/fru_utils.cpp (revision 4e1142d6f418f48ea260132ebb5a4995b2310c90)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3 
4 #include "fru_utils.hpp"
5 
6 #include <phosphor-logging/lg2.hpp>
7 
8 #include <array>
9 #include <cstddef>
10 #include <cstdint>
11 #include <filesystem>
12 #include <iomanip>
13 #include <iostream>
14 #include <numeric>
15 #include <set>
16 #include <sstream>
17 #include <string>
18 #include <vector>
19 
20 extern "C"
21 {
22 // Include for I2C_SMBUS_BLOCK_MAX
23 #include <linux/i2c.h>
24 }
25 
26 constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
27 
28 std::tm intelEpoch()
29 {
30     std::tm val = {};
31     val.tm_year = 1996 - 1900;
32     val.tm_mday = 1;
33     return val;
34 }
35 
36 char sixBitToChar(uint8_t val)
37 {
38     return static_cast<char>((val & 0x3f) + ' ');
39 }
40 
41 char bcdPlusToChar(uint8_t val)
42 {
43     val &= 0xf;
44     return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10];
45 }
46 
47 enum FRUDataEncoding
48 {
49     binary = 0x0,
50     bcdPlus = 0x1,
51     sixBitASCII = 0x2,
52     languageDependent = 0x3,
53 };
54 
55 enum MultiRecordType : uint8_t
56 {
57     powerSupplyInfo = 0x00,
58     dcOutput = 0x01,
59     dcLoad = 0x02,
60     managementAccessRecord = 0x03,
61     baseCompatibilityRecord = 0x04,
62     extendedCompatibilityRecord = 0x05,
63     resvASFSMBusDeviceRecord = 0x06,
64     resvASFLegacyDeviceAlerts = 0x07,
65     resvASFRemoteControl = 0x08,
66     extendedDCOutput = 0x09,
67     extendedDCLoad = 0x0A
68 };
69 
70 enum SubManagementAccessRecord : uint8_t
71 {
72     systemManagementURL = 0x01,
73     systemName = 0x02,
74     systemPingAddress = 0x03,
75     componentManagementURL = 0x04,
76     componentName = 0x05,
77     componentPingAddress = 0x06,
78     systemUniqueID = 0x07
79 };
80 
81 /* Decode FRU data into a std::string, given an input iterator and end. If the
82  * state returned is fruDataOk, then the resulting string is the decoded FRU
83  * data. The input iterator is advanced past the data consumed.
84  *
85  * On fruDataErr, we have lost synchronisation with the length bytes, so the
86  * iterator is no longer usable.
87  */
88 std::pair<DecodeState, std::string> decodeFRUData(
89     std::span<const uint8_t>::const_iterator& iter,
90     std::span<const uint8_t>::const_iterator& end, bool isLangEng)
91 {
92     std::string value;
93     unsigned int i = 0;
94 
95     /* we need at least one byte to decode the type/len header */
96     if (iter == end)
97     {
98         std::cerr << "Truncated FRU data\n";
99         return make_pair(DecodeState::err, value);
100     }
101 
102     uint8_t c = *(iter++);
103 
104     /* 0xc1 is the end marker */
105     if (c == 0xc1)
106     {
107         return make_pair(DecodeState::end, value);
108     }
109 
110     /* decode type/len byte */
111     uint8_t type = static_cast<uint8_t>(c >> 6);
112     uint8_t len = static_cast<uint8_t>(c & 0x3f);
113 
114     /* we should have at least len bytes of data available overall */
115     if (iter + len > end)
116     {
117         std::cerr << "FRU data field extends past end of FRU area data\n";
118         return make_pair(DecodeState::err, value);
119     }
120 
121     switch (type)
122     {
123         case FRUDataEncoding::binary:
124         {
125             std::stringstream ss;
126             ss << std::hex << std::setfill('0');
127             for (i = 0; i < len; i++, iter++)
128             {
129                 uint8_t val = static_cast<uint8_t>(*iter);
130                 ss << std::setw(2) << static_cast<int>(val);
131             }
132             value = ss.str();
133             break;
134         }
135         case FRUDataEncoding::languageDependent:
136             /* For language-code dependent encodings, assume 8-bit ASCII */
137             value = std::string(iter, iter + len);
138             iter += len;
139 
140             /* English text is encoded in 8-bit ASCII + Latin 1. All other
141              * languages are required to use 2-byte unicode. FruDevice does not
142              * handle unicode.
143              */
144             if (!isLangEng)
145             {
146                 std::cerr << "Error: Non english string is not supported \n";
147                 return make_pair(DecodeState::err, value);
148             }
149 
150             break;
151 
152         case FRUDataEncoding::bcdPlus:
153             value = std::string();
154             for (i = 0; i < len; i++, iter++)
155             {
156                 uint8_t val = *iter;
157                 value.push_back(bcdPlusToChar(val >> 4));
158                 value.push_back(bcdPlusToChar(val & 0xf));
159             }
160             break;
161 
162         case FRUDataEncoding::sixBitASCII:
163         {
164             unsigned int accum = 0;
165             unsigned int accumBitLen = 0;
166             value = std::string();
167             for (i = 0; i < len; i++, iter++)
168             {
169                 accum |= *iter << accumBitLen;
170                 accumBitLen += 8;
171                 while (accumBitLen >= 6)
172                 {
173                     value.push_back(sixBitToChar(accum & 0x3f));
174                     accum >>= 6;
175                     accumBitLen -= 6;
176                 }
177             }
178         }
179         break;
180 
181         default:
182         {
183             return make_pair(DecodeState::err, value);
184         }
185     }
186 
187     return make_pair(DecodeState::ok, value);
188 }
189 
190 bool checkLangEng(uint8_t lang)
191 {
192     // If Lang is not English then the encoding is defined as 2-byte UNICODE,
193     // but we don't support that.
194     if ((lang != 0U) && lang != 25)
195     {
196         std::cerr << "Warning: languages other than English is not "
197                      "supported\n";
198         // Return language flag as non english
199         return false;
200     }
201     return true;
202 }
203 
204 /* This function verifies for other offsets to check if they are not
205  * falling under other field area
206  *
207  * fruBytes:    Start of Fru data
208  * currentArea: Index of current area offset to be compared against all area
209  *              offset and it is a multiple of 8 bytes as per specification
210  * len:         Length of current area space and it is a multiple of 8 bytes
211  *              as per specification
212  */
213 bool verifyOffset(std::span<const uint8_t> fruBytes, fruAreas currentArea,
214                   uint8_t len)
215 {
216     unsigned int fruBytesSize = fruBytes.size();
217 
218     // check if Fru data has at least 8 byte header
219     if (fruBytesSize <= fruBlockSize)
220     {
221         std::cerr << "Error: trying to parse empty FRU\n";
222         return false;
223     }
224 
225     // Check range of passed currentArea value
226     if (currentArea > fruAreas::fruAreaMultirecord)
227     {
228         std::cerr << "Error: Fru area is out of range\n";
229         return false;
230     }
231 
232     unsigned int currentAreaIndex = getHeaderAreaFieldOffset(currentArea);
233     if (currentAreaIndex > fruBytesSize)
234     {
235         std::cerr << "Error: Fru area index is out of range\n";
236         return false;
237     }
238 
239     unsigned int start = fruBytes[currentAreaIndex];
240     unsigned int end = start + len;
241 
242     /* Verify each offset within the range of start and end */
243     for (fruAreas area = fruAreas::fruAreaInternal;
244          area <= fruAreas::fruAreaMultirecord; ++area)
245     {
246         // skip the current offset
247         if (area == currentArea)
248         {
249             continue;
250         }
251 
252         unsigned int areaIndex = getHeaderAreaFieldOffset(area);
253         if (areaIndex > fruBytesSize)
254         {
255             std::cerr << "Error: Fru area index is out of range\n";
256             return false;
257         }
258 
259         unsigned int areaOffset = fruBytes[areaIndex];
260         // if areaOffset is 0 means this area is not available so skip
261         if (areaOffset == 0)
262         {
263             continue;
264         }
265 
266         // check for overlapping of current offset with given areaoffset
267         if (areaOffset == start || (areaOffset > start && areaOffset < end))
268         {
269             std::cerr << getFruAreaName(currentArea)
270                       << " offset is overlapping with " << getFruAreaName(area)
271                       << " offset\n";
272             return false;
273         }
274     }
275     return true;
276 }
277 
278 static void parseMultirecordUUID(
279     std::span<const uint8_t> device,
280     boost::container::flat_map<std::string, std::string>& result)
281 {
282     constexpr size_t uuidDataLen = 16;
283     constexpr size_t multiRecordHeaderLen = 5;
284     /* UUID record data, plus one to skip past the sub-record type byte */
285     constexpr size_t uuidRecordData = multiRecordHeaderLen + 1;
286     constexpr size_t multiRecordEndOfListMask = 0x80;
287     /* The UUID {00112233-4455-6677-8899-AABBCCDDEEFF} would thus be represented
288      * as: 0x33 0x22 0x11 0x00 0x55 0x44 0x77 0x66 0x88 0x99 0xAA 0xBB 0xCC 0xDD
289      * 0xEE 0xFF
290      */
291     const std::array<uint8_t, uuidDataLen> uuidCharOrder = {
292         3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15};
293     size_t offset = getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord);
294     if (offset >= device.size())
295     {
296         throw std::runtime_error("Multirecord UUID offset is out of range");
297     }
298     uint32_t areaOffset = device[offset];
299 
300     if (areaOffset == 0)
301     {
302         return;
303     }
304 
305     areaOffset *= fruBlockSize;
306     std::span<const uint8_t>::const_iterator fruBytesIter =
307         device.begin() + areaOffset;
308 
309     /* Verify area offset */
310     if (!verifyOffset(device, fruAreas::fruAreaMultirecord, *fruBytesIter))
311     {
312         return;
313     }
314     while (areaOffset + uuidRecordData + uuidDataLen <= device.size())
315     {
316         if ((areaOffset < device.size()) &&
317             (device[areaOffset] ==
318              (uint8_t)MultiRecordType::managementAccessRecord))
319         {
320             if ((areaOffset + multiRecordHeaderLen < device.size()) &&
321                 (device[areaOffset + multiRecordHeaderLen] ==
322                  (uint8_t)SubManagementAccessRecord::systemUniqueID))
323             {
324                 /* Layout of UUID:
325                  * source: https://www.ietf.org/rfc/rfc4122.txt
326                  *
327                  * UUID binary format (16 bytes):
328                  * 4B-2B-2B-2B-6B (big endian)
329                  *
330                  * UUID string is 36 length of characters (36 bytes):
331                  * 0        9    14   19   24
332                  * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
333                  *    be     be   be   be       be
334                  * be means it should be converted to big endian.
335                  */
336                 /* Get UUID bytes to UUID string */
337                 std::stringstream tmp;
338                 tmp << std::hex << std::setfill('0');
339                 for (size_t i = 0; i < uuidDataLen; i++)
340                 {
341                     tmp << std::setw(2)
342                         << static_cast<uint16_t>(
343                                device[areaOffset + uuidRecordData +
344                                       uuidCharOrder[i]]);
345                 }
346                 std::string uuidStr = tmp.str();
347                 result["MULTIRECORD_UUID"] =
348                     uuidStr.substr(0, 8) + '-' + uuidStr.substr(8, 4) + '-' +
349                     uuidStr.substr(12, 4) + '-' + uuidStr.substr(16, 4) + '-' +
350                     uuidStr.substr(20, 12);
351                 break;
352             }
353         }
354         if ((device[areaOffset + 1] & multiRecordEndOfListMask) != 0)
355         {
356             break;
357         }
358         areaOffset = areaOffset + device[areaOffset + 2] + multiRecordHeaderLen;
359     }
360 }
361 
362 resCodes decodeField(
363     std::span<const uint8_t>::const_iterator& fruBytesIter,
364     std::span<const uint8_t>::const_iterator& fruBytesIterEndArea,
365     const std::vector<std::string>& fruAreaFieldNames, size_t& fieldIndex,
366     DecodeState& state, bool isLangEng, const fruAreas& area,
367     boost::container::flat_map<std::string, std::string>& result)
368 {
369     auto res = decodeFRUData(fruBytesIter, fruBytesIterEndArea, isLangEng);
370     state = res.first;
371     std::string value = res.second;
372     std::string name;
373     bool isCustomField = false;
374     if (fieldIndex < fruAreaFieldNames.size())
375     {
376         name = std::string(getFruAreaName(area)) + "_" +
377                fruAreaFieldNames.at(fieldIndex);
378     }
379     else
380     {
381         isCustomField = true;
382         name = std::string(getFruAreaName(area)) + "_" + fruCustomFieldName +
383                std::to_string(fieldIndex - fruAreaFieldNames.size() + 1);
384     }
385 
386     if (state == DecodeState::ok)
387     {
388         // Strip non null characters and trailing spaces from the end
389         value.erase(
390             std::find_if(value.rbegin(), value.rend(),
391                          [](char ch) { return ((ch != 0) && (ch != ' ')); })
392                 .base(),
393             value.end());
394         if (isCustomField)
395         {
396             // Some MAC addresses are stored in a custom field, with
397             // "MAC:" prefixed on the value.  If we see that, create a
398             // new field with the decoded data
399             if (value.starts_with("MAC: "))
400             {
401                 result["MAC_" + name] = value.substr(5);
402             }
403         }
404         result[name] = std::move(value);
405         ++fieldIndex;
406     }
407     else if (state == DecodeState::err)
408     {
409         std::cerr << "Error while parsing " << name << "\n";
410 
411         // Cancel decoding if failed to parse any of mandatory
412         // fields
413         if (fieldIndex < fruAreaFieldNames.size())
414         {
415             std::cerr << "Failed to parse mandatory field \n";
416             return resCodes::resErr;
417         }
418         return resCodes::resWarn;
419     }
420     else
421     {
422         if (fieldIndex < fruAreaFieldNames.size())
423         {
424             std::cerr << "Mandatory fields absent in FRU area "
425                       << getFruAreaName(area) << " after " << name << "\n";
426             return resCodes::resWarn;
427         }
428     }
429     return resCodes::resOK;
430 }
431 
432 resCodes formatIPMIFRU(
433     std::span<const uint8_t> fruBytes,
434     boost::container::flat_map<std::string, std::string>& result)
435 {
436     resCodes ret = resCodes::resOK;
437     if (fruBytes.size() <= fruBlockSize)
438     {
439         std::cerr << "Error: trying to parse empty FRU \n";
440         return resCodes::resErr;
441     }
442     result["Common_Format_Version"] =
443         std::to_string(static_cast<int>(*fruBytes.begin()));
444 
445     const std::vector<std::string>* fruAreaFieldNames = nullptr;
446 
447     // Don't parse Internal and Multirecord areas
448     for (fruAreas area = fruAreas::fruAreaChassis;
449          area <= fruAreas::fruAreaProduct; ++area)
450     {
451         size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area));
452         if (offset == 0)
453         {
454             continue;
455         }
456         offset *= fruBlockSize;
457         std::span<const uint8_t>::const_iterator fruBytesIter =
458             fruBytes.begin() + offset;
459         if (fruBytesIter + fruBlockSize >= fruBytes.end())
460         {
461             std::cerr << "Not enough data to parse \n";
462             return resCodes::resErr;
463         }
464         // check for format version 1
465         if (*fruBytesIter != 0x01)
466         {
467             std::cerr << "Unexpected version " << *fruBytesIter << "\n";
468             return resCodes::resErr;
469         }
470         ++fruBytesIter;
471 
472         /* Verify other area offset for overlap with current area by passing
473          * length of current area offset pointed by *fruBytesIter
474          */
475         if (!verifyOffset(fruBytes, area, *fruBytesIter))
476         {
477             return resCodes::resErr;
478         }
479 
480         size_t fruAreaSize = *fruBytesIter * fruBlockSize;
481         std::span<const uint8_t>::const_iterator fruBytesIterEndArea =
482             fruBytes.begin() + offset + fruAreaSize - 1;
483         ++fruBytesIter;
484 
485         uint8_t fruComputedChecksum =
486             calculateChecksum(fruBytes.begin() + offset, fruBytesIterEndArea);
487         if (fruComputedChecksum != *fruBytesIterEndArea)
488         {
489             std::stringstream ss;
490             ss << std::hex << std::setfill('0');
491             ss << "Checksum error in FRU area " << getFruAreaName(area) << "\n";
492             ss << "\tComputed checksum: 0x" << std::setw(2)
493                << static_cast<int>(fruComputedChecksum) << "\n";
494             ss << "\tThe read checksum: 0x" << std::setw(2)
495                << static_cast<int>(*fruBytesIterEndArea) << "\n";
496             std::cerr << ss.str();
497             ret = resCodes::resWarn;
498         }
499 
500         /* Set default language flag to true as Chassis Fru area are always
501          * encoded in English defined in Section 10 of Fru specification
502          */
503 
504         bool isLangEng = true;
505         switch (area)
506         {
507             case fruAreas::fruAreaChassis:
508             {
509                 result["CHASSIS_TYPE"] =
510                     std::to_string(static_cast<int>(*fruBytesIter));
511                 fruBytesIter += 1;
512                 fruAreaFieldNames = &chassisFruAreas;
513                 break;
514             }
515             case fruAreas::fruAreaBoard:
516             {
517                 uint8_t lang = *fruBytesIter;
518                 result["BOARD_LANGUAGE_CODE"] =
519                     std::to_string(static_cast<int>(lang));
520                 isLangEng = checkLangEng(lang);
521                 fruBytesIter += 1;
522 
523                 unsigned int minutes =
524                     *fruBytesIter | *(fruBytesIter + 1) << 8 |
525                     *(fruBytesIter + 2) << 16;
526                 std::tm fruTime = intelEpoch();
527                 std::time_t timeValue = timegm(&fruTime);
528                 timeValue += static_cast<long>(minutes) * 60;
529                 fruTime = *std::gmtime(&timeValue);
530 
531                 // Tue Nov 20 23:08:00 2018
532                 std::array<char, 32> timeString = {};
533                 auto bytes = std::strftime(timeString.data(), timeString.size(),
534                                            "%Y%m%dT%H%M%SZ", &fruTime);
535                 if (bytes == 0)
536                 {
537                     std::cerr << "invalid time string encountered\n";
538                     return resCodes::resErr;
539                 }
540 
541                 result["BOARD_MANUFACTURE_DATE"] =
542                     std::string_view(timeString.data(), bytes);
543                 fruBytesIter += 3;
544                 fruAreaFieldNames = &boardFruAreas;
545                 break;
546             }
547             case fruAreas::fruAreaProduct:
548             {
549                 uint8_t lang = *fruBytesIter;
550                 result["PRODUCT_LANGUAGE_CODE"] =
551                     std::to_string(static_cast<int>(lang));
552                 isLangEng = checkLangEng(lang);
553                 fruBytesIter += 1;
554                 fruAreaFieldNames = &productFruAreas;
555                 break;
556             }
557             default:
558             {
559                 std::cerr << "Internal error: unexpected FRU area index: "
560                           << static_cast<int>(area) << " \n";
561                 return resCodes::resErr;
562             }
563         }
564         size_t fieldIndex = 0;
565         DecodeState state = DecodeState::ok;
566         do
567         {
568             resCodes decodeRet = decodeField(fruBytesIter, fruBytesIterEndArea,
569                                              *fruAreaFieldNames, fieldIndex,
570                                              state, isLangEng, area, result);
571             if (decodeRet == resCodes::resErr)
572             {
573                 return resCodes::resErr;
574             }
575             if (decodeRet == resCodes::resWarn)
576             {
577                 ret = decodeRet;
578             }
579         } while (state == DecodeState::ok);
580         for (; fruBytesIter < fruBytesIterEndArea; fruBytesIter++)
581         {
582             uint8_t c = *fruBytesIter;
583             if (c != 0U)
584             {
585                 std::cerr << "Non-zero byte after EndOfFields in FRU area "
586                           << getFruAreaName(area) << "\n";
587                 ret = resCodes::resWarn;
588                 break;
589             }
590         }
591     }
592 
593     /* Parsing the Multirecord UUID */
594     parseMultirecordUUID(fruBytes, result);
595 
596     return ret;
597 }
598 
599 // Calculate new checksum for fru info area
600 uint8_t calculateChecksum(std::span<const uint8_t>::const_iterator iter,
601                           std::span<const uint8_t>::const_iterator end)
602 {
603     constexpr int checksumMod = 256;
604     uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
605     return (checksumMod - sum) % checksumMod;
606 }
607 
608 uint8_t calculateChecksum(std::span<const uint8_t> fruAreaData)
609 {
610     return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
611 }
612 
613 // Update new fru area length &
614 // Update checksum at new checksum location
615 // Return the offset of the area checksum byte
616 unsigned int updateFRUAreaLenAndChecksum(
617     std::vector<uint8_t>& fruData, size_t fruAreaStart,
618     size_t fruAreaEndOfFieldsOffset, size_t fruAreaEndOffset)
619 {
620     size_t traverseFRUAreaIndex = fruAreaEndOfFieldsOffset - fruAreaStart;
621 
622     // fill zeros for any remaining unused space
623     std::fill(fruData.begin() + fruAreaEndOfFieldsOffset,
624               fruData.begin() + fruAreaEndOffset, 0);
625 
626     size_t mod = traverseFRUAreaIndex % fruBlockSize;
627     size_t checksumLoc = 0;
628     if (mod == 0U)
629     {
630         traverseFRUAreaIndex += (fruBlockSize);
631         checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1);
632     }
633     else
634     {
635         traverseFRUAreaIndex += (fruBlockSize - mod);
636         checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1);
637     }
638 
639     size_t newFRUAreaLen =
640         (traverseFRUAreaIndex / fruBlockSize) +
641         static_cast<unsigned long>((traverseFRUAreaIndex % fruBlockSize) != 0);
642     size_t fruAreaLengthLoc = fruAreaStart + 1;
643     fruData[fruAreaLengthLoc] = static_cast<uint8_t>(newFRUAreaLen);
644 
645     // Calculate new checksum
646     std::vector<uint8_t> finalFRUData;
647     std::copy_n(fruData.begin() + fruAreaStart, checksumLoc - fruAreaStart,
648                 std::back_inserter(finalFRUData));
649 
650     fruData[checksumLoc] = calculateChecksum(finalFRUData);
651     return checksumLoc;
652 }
653 
654 ssize_t getFieldLength(uint8_t fruFieldTypeLenValue)
655 {
656     constexpr uint8_t typeLenMask = 0x3F;
657     constexpr uint8_t endOfFields = 0xC1;
658     if (fruFieldTypeLenValue == endOfFields)
659     {
660         return -1;
661     }
662     return fruFieldTypeLenValue & typeLenMask;
663 }
664 
665 bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
666 {
667     // ipmi spec format version number is currently at 1, verify it
668     if (blockData[0] != fruVersion)
669     {
670         lg2::debug(
671             "FRU spec version {VERSION} not supported. Supported version is {SUPPORTED_VERSION}",
672             "VERSION", lg2::hex, blockData[0], "SUPPORTED_VERSION", lg2::hex,
673             fruVersion);
674         return false;
675     }
676 
677     // verify pad is set to 0
678     if (blockData[6] != 0x0)
679     {
680         lg2::debug("Pad value in header is non zero, value is {VALUE}", "VALUE",
681                    lg2::hex, blockData[6]);
682         return false;
683     }
684 
685     // verify offsets are 0, or don't point to another offset
686     std::set<uint8_t> foundOffsets;
687     for (int ii = 1; ii < 6; ii++)
688     {
689         if (blockData[ii] == 0)
690         {
691             continue;
692         }
693         auto inserted = foundOffsets.insert(blockData[ii]);
694         if (!inserted.second)
695         {
696             return false;
697         }
698     }
699 
700     // validate checksum
701     size_t sum = 0;
702     for (int jj = 0; jj < 7; jj++)
703     {
704         sum += blockData[jj];
705     }
706     sum = (256 - sum) & 0xFF;
707 
708     if (sum != blockData[7])
709     {
710         lg2::debug(
711             "Checksum {CHECKSUM} is invalid. calculated checksum is {CALCULATED_CHECKSUM}",
712             "CHECKSUM", lg2::hex, blockData[7], "CALCULATED_CHECKSUM", lg2::hex,
713             sum);
714         return false;
715     }
716     return true;
717 }
718 
719 bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
720                    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
721                    off_t& baseOffset)
722 {
723     if (reader.read(baseOffset, 0x8, blockData.data()) < 0)
724     {
725         std::cerr << "failed to read " << errorHelp << " base offset "
726                   << baseOffset << "\n";
727         return false;
728     }
729 
730     // check the header checksum
731     if (validateHeader(blockData))
732     {
733         return true;
734     }
735 
736     // only continue the search if we just looked at 0x0.
737     if (baseOffset != 0)
738     {
739         return false;
740     }
741 
742     // now check for special cases where the IPMI data is at an offset
743 
744     // check if blockData starts with tyanHeader
745     const std::vector<uint8_t> tyanHeader = {'$', 'T', 'Y', 'A', 'N', '$'};
746     if (blockData.size() >= tyanHeader.size() &&
747         std::equal(tyanHeader.begin(), tyanHeader.end(), blockData.begin()))
748     {
749         // look for the FRU header at offset 0x6000
750         baseOffset = 0x6000;
751         return findFRUHeader(reader, errorHelp, blockData, baseOffset);
752     }
753 
754     lg2::debug("Illegal header {HEADER} base offset {OFFSET}", "HEADER",
755                errorHelp, "OFFSET", baseOffset);
756 
757     return false;
758 }
759 
760 std::pair<std::vector<uint8_t>, bool> readFRUContents(
761     FRUReader& reader, const std::string& errorHelp)
762 {
763     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
764     off_t baseOffset = 0x0;
765 
766     if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
767     {
768         return {{}, false};
769     }
770 
771     std::vector<uint8_t> device;
772     device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
773 
774     bool hasMultiRecords = false;
775     size_t fruLength = fruBlockSize; // At least FRU header is present
776     unsigned int prevOffset = 0;
777     for (fruAreas area = fruAreas::fruAreaInternal;
778          area <= fruAreas::fruAreaMultirecord; ++area)
779     {
780         // Offset value can be 255.
781         unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
782         if (areaOffset == 0)
783         {
784             continue;
785         }
786 
787         /* Check for offset order, as per Section 17 of FRU specification, FRU
788          * information areas are required to be in order in FRU data layout
789          * which means all offset value should be in increasing order or can be
790          * 0 if that area is not present
791          */
792         if (areaOffset <= prevOffset)
793         {
794             std::cerr << "Fru area offsets are not in required order as per "
795                          "Section 17 of Fru specification\n";
796             return {{}, true};
797         }
798         prevOffset = areaOffset;
799 
800         // MultiRecords are different. area is not tracking section, it's
801         // walking the common header.
802         if (area == fruAreas::fruAreaMultirecord)
803         {
804             hasMultiRecords = true;
805             break;
806         }
807 
808         areaOffset *= fruBlockSize;
809 
810         if (reader.read(baseOffset + areaOffset, 0x2, blockData.data()) < 0)
811         {
812             std::cerr << "failed to read " << errorHelp << " base offset "
813                       << baseOffset << "\n";
814             return {{}, true};
815         }
816 
817         // Ignore data type (blockData is already unsigned).
818         size_t length = blockData[1] * fruBlockSize;
819         areaOffset += length;
820         fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
821     }
822 
823     if (hasMultiRecords)
824     {
825         // device[area count] is the index to the last area because the 0th
826         // entry is not an offset in the common header.
827         unsigned int areaOffset =
828             device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
829         areaOffset *= fruBlockSize;
830 
831         // the multi-area record header is 5 bytes long.
832         constexpr size_t multiRecordHeaderSize = 5;
833         constexpr uint8_t multiRecordEndOfListMask = 0x80;
834 
835         // Sanity hard-limit to 64KB.
836         while (areaOffset < std::numeric_limits<uint16_t>::max())
837         {
838             // In multi-area, the area offset points to the 0th record, each
839             // record has 3 bytes of the header we care about.
840             if (reader.read(baseOffset + areaOffset, 0x3, blockData.data()) < 0)
841             {
842                 std::cerr << "failed to read " << errorHelp << " base offset "
843                           << baseOffset << "\n";
844                 return {{}, true};
845             }
846 
847             // Ok, let's check the record length, which is in bytes (unsigned,
848             // up to 255, so blockData should hold uint8_t not char)
849             size_t recordLength = blockData[2];
850             areaOffset += (recordLength + multiRecordHeaderSize);
851             fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
852 
853             // If this is the end of the list bail.
854             if ((blockData[1] & multiRecordEndOfListMask) != 0)
855             {
856                 break;
857             }
858         }
859     }
860 
861     // You already copied these first 8 bytes (the ipmi fru header size)
862     fruLength -= std::min(fruBlockSize, fruLength);
863 
864     int readOffset = fruBlockSize;
865 
866     while (fruLength > 0)
867     {
868         size_t requestLength =
869             std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
870 
871         if (reader.read(baseOffset + readOffset, requestLength,
872                         blockData.data()) < 0)
873         {
874             std::cerr << "failed to read " << errorHelp << " base offset "
875                       << baseOffset << "\n";
876             return {{}, true};
877         }
878 
879         device.insert(device.end(), blockData.begin(),
880                       blockData.begin() + requestLength);
881 
882         readOffset += requestLength;
883         fruLength -= std::min(requestLength, fruLength);
884     }
885 
886     return {device, true};
887 }
888 
889 unsigned int getHeaderAreaFieldOffset(fruAreas area)
890 {
891     return static_cast<unsigned int>(area) + 1;
892 }
893 
894 std::vector<uint8_t>& getFRUInfo(const uint16_t& bus, const uint8_t& address)
895 {
896     auto deviceMap = busMap.find(bus);
897     if (deviceMap == busMap.end())
898     {
899         throw std::invalid_argument("Invalid Bus.");
900     }
901     auto device = deviceMap->second->find(address);
902     if (device == deviceMap->second->end())
903     {
904         throw std::invalid_argument("Invalid Address.");
905     }
906     std::vector<uint8_t>& ret = device->second;
907 
908     return ret;
909 }
910 
911 // Iterate FruArea Names and find start and size of the fru area that contains
912 // the propertyName and the field start location for the property. fruAreaParams
913 // struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
914 // updated/returned if successful.
915 
916 bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData,
917                                  const std::string& propertyName,
918                                  struct FruArea& fruAreaParams)
919 {
920     const std::vector<std::string>* fruAreaFieldNames = nullptr;
921 
922     uint8_t fruAreaOffsetFieldValue = 0;
923     size_t offset = 0;
924     std::string areaName = propertyName.substr(0, propertyName.find('_'));
925     std::string propertyNamePrefix = areaName + "_";
926     auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
927     if (it == fruAreaNames.end())
928     {
929         std::cerr << "Can't parse area name for property " << propertyName
930                   << " \n";
931         return false;
932     }
933     fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
934     fruAreaOffsetFieldValue =
935         fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
936     switch (fruAreaToUpdate)
937     {
938         case fruAreas::fruAreaChassis:
939             offset = 3; // chassis part number offset. Skip fixed first 3 bytes
940             fruAreaFieldNames = &chassisFruAreas;
941             break;
942         case fruAreas::fruAreaBoard:
943             offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
944             fruAreaFieldNames = &boardFruAreas;
945             break;
946         case fruAreas::fruAreaProduct:
947             // Manufacturer name offset. Skip fixed first 3 product fru bytes
948             // i.e. version, area length and language code
949             offset = 3;
950             fruAreaFieldNames = &productFruAreas;
951             break;
952         default:
953             std::cerr << "Invalid PropertyName " << propertyName << " \n";
954             return false;
955     }
956     if (fruAreaOffsetFieldValue == 0)
957     {
958         std::cerr << "FRU Area for " << propertyName << " not present \n";
959         return false;
960     }
961 
962     fruAreaParams.start = fruAreaOffsetFieldValue * fruBlockSize;
963     fruAreaParams.size = fruData[fruAreaParams.start + 1] * fruBlockSize;
964     fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
965     size_t fruDataIter = fruAreaParams.start + offset;
966     size_t skipToFRUUpdateField = 0;
967     ssize_t fieldLength = 0;
968 
969     bool found = false;
970     for (const auto& field : *fruAreaFieldNames)
971     {
972         skipToFRUUpdateField++;
973         if (propertyName == propertyNamePrefix + field)
974         {
975             found = true;
976             break;
977         }
978     }
979     if (!found)
980     {
981         std::size_t pos = propertyName.find(fruCustomFieldName);
982         if (pos == std::string::npos)
983         {
984             std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
985                       << propertyName << "\n";
986             return false;
987         }
988         std::string fieldNumStr =
989             propertyName.substr(pos + fruCustomFieldName.length());
990         size_t fieldNum = std::stoi(fieldNumStr);
991         if (fieldNum == 0)
992         {
993             std::cerr << "PropertyName not recognized: " << propertyName
994                       << "\n";
995             return false;
996         }
997         skipToFRUUpdateField += fieldNum;
998     }
999 
1000     for (size_t i = 1; i < skipToFRUUpdateField; i++)
1001     {
1002         if (fruDataIter < fruData.size())
1003         {
1004             fieldLength = getFieldLength(fruData[fruDataIter]);
1005 
1006             if (fieldLength < 0)
1007             {
1008                 break;
1009             }
1010             fruDataIter += 1 + fieldLength;
1011         }
1012     }
1013     fruAreaParams.updateFieldLoc = fruDataIter;
1014 
1015     return true;
1016 }
1017 
1018 // Copy the FRU Area fields and properties into restFRUAreaFieldsData vector.
1019 // Return true for success and false for failure.
1020 
1021 bool copyRestFRUArea(std::vector<uint8_t>& fruData,
1022                      const std::string& propertyName,
1023                      struct FruArea& fruAreaParams,
1024                      std::vector<uint8_t>& restFRUAreaFieldsData)
1025 {
1026     size_t fieldLoc = fruAreaParams.updateFieldLoc;
1027     size_t start = fruAreaParams.start;
1028     size_t fruAreaSize = fruAreaParams.size;
1029 
1030     // Push post update fru field bytes to a vector
1031     ssize_t fieldLength = getFieldLength(fruData[fieldLoc]);
1032     if (fieldLength < 0)
1033     {
1034         std::cerr << "Property " << propertyName << " not present \n";
1035         return false;
1036     }
1037 
1038     size_t fruDataIter = 0;
1039     fruDataIter = fieldLoc;
1040     fruDataIter += 1 + fieldLength;
1041     size_t restFRUFieldsLoc = fruDataIter;
1042     size_t endOfFieldsLoc = 0;
1043 
1044     if (fruDataIter < fruData.size())
1045     {
1046         while ((fieldLength = getFieldLength(fruData[fruDataIter])) >= 0)
1047         {
1048             if (fruDataIter >= (start + fruAreaSize))
1049             {
1050                 fruDataIter = start + fruAreaSize;
1051                 break;
1052             }
1053             fruDataIter += 1 + fieldLength;
1054         }
1055         endOfFieldsLoc = fruDataIter;
1056     }
1057 
1058     std::copy_n(fruData.begin() + restFRUFieldsLoc,
1059                 endOfFieldsLoc - restFRUFieldsLoc + 1,
1060                 std::back_inserter(restFRUAreaFieldsData));
1061 
1062     fruAreaParams.restFieldsLoc = restFRUFieldsLoc;
1063     fruAreaParams.restFieldsEnd = endOfFieldsLoc;
1064 
1065     return true;
1066 }
1067 
1068 // Get all device dbus path and match path with product name using
1069 // regular expression and find the device index for all devices.
1070 
1071 std::optional<int> findIndexForFRU(
1072     boost::container::flat_map<
1073         std::pair<size_t, size_t>,
1074         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1075     std::string& productName)
1076 {
1077     int highest = -1;
1078     bool found = false;
1079 
1080     for (const auto& busIface : dbusInterfaceMap)
1081     {
1082         std::string path = busIface.second->get_object_path();
1083         if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
1084         {
1085             // Check if the match named has extra information.
1086             found = true;
1087             std::smatch baseMatch;
1088 
1089             bool match = std::regex_match(path, baseMatch,
1090                                           std::regex(productName + "_(\\d+)$"));
1091             if (match)
1092             {
1093                 if (baseMatch.size() == 2)
1094                 {
1095                     std::ssub_match baseSubMatch = baseMatch[1];
1096                     std::string base = baseSubMatch.str();
1097 
1098                     int value = std::stoi(base);
1099                     highest = (value > highest) ? value : highest;
1100                 }
1101             }
1102         }
1103     } // end searching objects
1104 
1105     if (!found)
1106     {
1107         return std::nullopt;
1108     }
1109     return highest;
1110 }
1111 
1112 // This function does format fru data as per IPMI format and find the
1113 // productName in the formatted fru data, get that productName and return
1114 // productName if found or return NULL.
1115 
1116 std::optional<std::string> getProductName(
1117     std::vector<uint8_t>& device,
1118     boost::container::flat_map<std::string, std::string>& formattedFRU,
1119     uint32_t bus, uint32_t address, size_t& unknownBusObjectCount)
1120 {
1121     std::string productName;
1122 
1123     resCodes res = formatIPMIFRU(device, formattedFRU);
1124     if (res == resCodes::resErr)
1125     {
1126         std::cerr << "failed to parse FRU for device at bus " << bus
1127                   << " address " << address << "\n";
1128         return std::nullopt;
1129     }
1130     if (res == resCodes::resWarn)
1131     {
1132         std::cerr << "Warnings while parsing FRU for device at bus " << bus
1133                   << " address " << address << "\n";
1134     }
1135 
1136     auto productNameFind = formattedFRU.find("BOARD_PRODUCT_NAME");
1137     // Not found under Board section or an empty string.
1138     if (productNameFind == formattedFRU.end() ||
1139         productNameFind->second.empty())
1140     {
1141         productNameFind = formattedFRU.find("PRODUCT_PRODUCT_NAME");
1142     }
1143     // Found under Product section and not an empty string.
1144     if (productNameFind != formattedFRU.end() &&
1145         !productNameFind->second.empty())
1146     {
1147         productName = productNameFind->second;
1148         std::regex illegalObject("[^A-Za-z0-9_]");
1149         productName = std::regex_replace(productName, illegalObject, "_");
1150     }
1151     else
1152     {
1153         productName = "UNKNOWN" + std::to_string(unknownBusObjectCount);
1154         unknownBusObjectCount++;
1155     }
1156     return productName;
1157 }
1158 
1159 bool getFruData(std::vector<uint8_t>& fruData, uint32_t bus, uint32_t address)
1160 {
1161     try
1162     {
1163         fruData = getFRUInfo(static_cast<uint16_t>(bus),
1164                              static_cast<uint8_t>(address));
1165     }
1166     catch (const std::invalid_argument& e)
1167     {
1168         std::cerr << "Failure getting FRU Info" << e.what() << "\n";
1169         return false;
1170     }
1171 
1172     return !fruData.empty();
1173 }
1174