xref: /openbmc/entity-manager/src/fru_device/fru_utils.cpp (revision f850ecad00900a9d338950e28506c04af42b8883)
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     // check if blockData starts with gigabyteHeader
755     const std::vector<uint8_t> gigabyteHeader = {'G', 'I', 'G', 'A',
756                                                  'B', 'Y', 'T', 'E'};
757     if (blockData.size() >= gigabyteHeader.size() &&
758         std::equal(gigabyteHeader.begin(), gigabyteHeader.end(),
759                    blockData.begin()))
760     {
761         // look for the FRU header at offset 0x4000
762         baseOffset = 0x4000;
763         return findFRUHeader(reader, errorHelp, blockData, baseOffset);
764     }
765 
766     lg2::debug("Illegal header {HEADER} base offset {OFFSET}", "HEADER",
767                errorHelp, "OFFSET", baseOffset);
768 
769     return false;
770 }
771 
772 std::pair<std::vector<uint8_t>, bool> readFRUContents(
773     FRUReader& reader, const std::string& errorHelp)
774 {
775     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
776     off_t baseOffset = 0x0;
777 
778     if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
779     {
780         return {{}, false};
781     }
782 
783     std::vector<uint8_t> device;
784     device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
785 
786     bool hasMultiRecords = false;
787     size_t fruLength = fruBlockSize; // At least FRU header is present
788     unsigned int prevOffset = 0;
789     for (fruAreas area = fruAreas::fruAreaInternal;
790          area <= fruAreas::fruAreaMultirecord; ++area)
791     {
792         // Offset value can be 255.
793         unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
794         if (areaOffset == 0)
795         {
796             continue;
797         }
798 
799         /* Check for offset order, as per Section 17 of FRU specification, FRU
800          * information areas are required to be in order in FRU data layout
801          * which means all offset value should be in increasing order or can be
802          * 0 if that area is not present
803          */
804         if (areaOffset <= prevOffset)
805         {
806             std::cerr << "Fru area offsets are not in required order as per "
807                          "Section 17 of Fru specification\n";
808             return {{}, true};
809         }
810         prevOffset = areaOffset;
811 
812         // MultiRecords are different. area is not tracking section, it's
813         // walking the common header.
814         if (area == fruAreas::fruAreaMultirecord)
815         {
816             hasMultiRecords = true;
817             break;
818         }
819 
820         areaOffset *= fruBlockSize;
821 
822         if (reader.read(baseOffset + areaOffset, 0x2, blockData.data()) < 0)
823         {
824             std::cerr << "failed to read " << errorHelp << " base offset "
825                       << baseOffset << "\n";
826             return {{}, true};
827         }
828 
829         // Ignore data type (blockData is already unsigned).
830         size_t length = blockData[1] * fruBlockSize;
831         areaOffset += length;
832         fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
833     }
834 
835     if (hasMultiRecords)
836     {
837         // device[area count] is the index to the last area because the 0th
838         // entry is not an offset in the common header.
839         unsigned int areaOffset =
840             device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
841         areaOffset *= fruBlockSize;
842 
843         // the multi-area record header is 5 bytes long.
844         constexpr size_t multiRecordHeaderSize = 5;
845         constexpr uint8_t multiRecordEndOfListMask = 0x80;
846 
847         // Sanity hard-limit to 64KB.
848         while (areaOffset < std::numeric_limits<uint16_t>::max())
849         {
850             // In multi-area, the area offset points to the 0th record, each
851             // record has 3 bytes of the header we care about.
852             if (reader.read(baseOffset + areaOffset, 0x3, blockData.data()) < 0)
853             {
854                 std::cerr << "failed to read " << errorHelp << " base offset "
855                           << baseOffset << "\n";
856                 return {{}, true};
857             }
858 
859             // Ok, let's check the record length, which is in bytes (unsigned,
860             // up to 255, so blockData should hold uint8_t not char)
861             size_t recordLength = blockData[2];
862             areaOffset += (recordLength + multiRecordHeaderSize);
863             fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
864 
865             // If this is the end of the list bail.
866             if ((blockData[1] & multiRecordEndOfListMask) != 0)
867             {
868                 break;
869             }
870         }
871     }
872 
873     // You already copied these first 8 bytes (the ipmi fru header size)
874     fruLength -= std::min(fruBlockSize, fruLength);
875 
876     int readOffset = fruBlockSize;
877 
878     while (fruLength > 0)
879     {
880         size_t requestLength =
881             std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
882 
883         if (reader.read(baseOffset + readOffset, requestLength,
884                         blockData.data()) < 0)
885         {
886             std::cerr << "failed to read " << errorHelp << " base offset "
887                       << baseOffset << "\n";
888             return {{}, true};
889         }
890 
891         device.insert(device.end(), blockData.begin(),
892                       blockData.begin() + requestLength);
893 
894         readOffset += requestLength;
895         fruLength -= std::min(requestLength, fruLength);
896     }
897 
898     return {device, true};
899 }
900 
901 unsigned int getHeaderAreaFieldOffset(fruAreas area)
902 {
903     return static_cast<unsigned int>(area) + 1;
904 }
905 
906 std::vector<uint8_t>& getFRUInfo(const uint16_t& bus, const uint8_t& address)
907 {
908     auto deviceMap = busMap.find(bus);
909     if (deviceMap == busMap.end())
910     {
911         throw std::invalid_argument("Invalid Bus.");
912     }
913     auto device = deviceMap->second->find(address);
914     if (device == deviceMap->second->end())
915     {
916         throw std::invalid_argument("Invalid Address.");
917     }
918     std::vector<uint8_t>& ret = device->second;
919 
920     return ret;
921 }
922 
923 // Iterate FruArea Names and find start and size of the fru area that contains
924 // the propertyName and the field start location for the property. fruAreaParams
925 // struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
926 // updated/returned if successful.
927 
928 bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData,
929                                  const std::string& propertyName,
930                                  struct FruArea& fruAreaParams)
931 {
932     const std::vector<std::string>* fruAreaFieldNames = nullptr;
933 
934     uint8_t fruAreaOffsetFieldValue = 0;
935     size_t offset = 0;
936     std::string areaName = propertyName.substr(0, propertyName.find('_'));
937     std::string propertyNamePrefix = areaName + "_";
938     auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
939     if (it == fruAreaNames.end())
940     {
941         std::cerr << "Can't parse area name for property " << propertyName
942                   << " \n";
943         return false;
944     }
945     fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
946     fruAreaOffsetFieldValue =
947         fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
948     switch (fruAreaToUpdate)
949     {
950         case fruAreas::fruAreaChassis:
951             offset = 3; // chassis part number offset. Skip fixed first 3 bytes
952             fruAreaFieldNames = &chassisFruAreas;
953             break;
954         case fruAreas::fruAreaBoard:
955             offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
956             fruAreaFieldNames = &boardFruAreas;
957             break;
958         case fruAreas::fruAreaProduct:
959             // Manufacturer name offset. Skip fixed first 3 product fru bytes
960             // i.e. version, area length and language code
961             offset = 3;
962             fruAreaFieldNames = &productFruAreas;
963             break;
964         default:
965             std::cerr << "Invalid PropertyName " << propertyName << " \n";
966             return false;
967     }
968     if (fruAreaOffsetFieldValue == 0)
969     {
970         std::cerr << "FRU Area for " << propertyName << " not present \n";
971         return false;
972     }
973 
974     fruAreaParams.start = fruAreaOffsetFieldValue * fruBlockSize;
975     fruAreaParams.size = fruData[fruAreaParams.start + 1] * fruBlockSize;
976     fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
977     size_t fruDataIter = fruAreaParams.start + offset;
978     size_t skipToFRUUpdateField = 0;
979     ssize_t fieldLength = 0;
980 
981     bool found = false;
982     for (const auto& field : *fruAreaFieldNames)
983     {
984         skipToFRUUpdateField++;
985         if (propertyName == propertyNamePrefix + field)
986         {
987             found = true;
988             break;
989         }
990     }
991     if (!found)
992     {
993         std::size_t pos = propertyName.find(fruCustomFieldName);
994         if (pos == std::string::npos)
995         {
996             std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
997                       << propertyName << "\n";
998             return false;
999         }
1000         std::string fieldNumStr =
1001             propertyName.substr(pos + fruCustomFieldName.length());
1002         size_t fieldNum = std::stoi(fieldNumStr);
1003         if (fieldNum == 0)
1004         {
1005             std::cerr << "PropertyName not recognized: " << propertyName
1006                       << "\n";
1007             return false;
1008         }
1009         skipToFRUUpdateField += fieldNum;
1010     }
1011 
1012     for (size_t i = 1; i < skipToFRUUpdateField; i++)
1013     {
1014         if (fruDataIter < fruData.size())
1015         {
1016             fieldLength = getFieldLength(fruData[fruDataIter]);
1017 
1018             if (fieldLength < 0)
1019             {
1020                 break;
1021             }
1022             fruDataIter += 1 + fieldLength;
1023         }
1024     }
1025     fruAreaParams.updateFieldLoc = fruDataIter;
1026 
1027     return true;
1028 }
1029 
1030 // Copy the FRU Area fields and properties into restFRUAreaFieldsData vector.
1031 // Return true for success and false for failure.
1032 
1033 bool copyRestFRUArea(std::vector<uint8_t>& fruData,
1034                      const std::string& propertyName,
1035                      struct FruArea& fruAreaParams,
1036                      std::vector<uint8_t>& restFRUAreaFieldsData)
1037 {
1038     size_t fieldLoc = fruAreaParams.updateFieldLoc;
1039     size_t start = fruAreaParams.start;
1040     size_t fruAreaSize = fruAreaParams.size;
1041 
1042     // Push post update fru field bytes to a vector
1043     ssize_t fieldLength = getFieldLength(fruData[fieldLoc]);
1044     if (fieldLength < 0)
1045     {
1046         std::cerr << "Property " << propertyName << " not present \n";
1047         return false;
1048     }
1049 
1050     size_t fruDataIter = 0;
1051     fruDataIter = fieldLoc;
1052     fruDataIter += 1 + fieldLength;
1053     size_t restFRUFieldsLoc = fruDataIter;
1054     size_t endOfFieldsLoc = 0;
1055 
1056     if (fruDataIter < fruData.size())
1057     {
1058         while ((fieldLength = getFieldLength(fruData[fruDataIter])) >= 0)
1059         {
1060             if (fruDataIter >= (start + fruAreaSize))
1061             {
1062                 fruDataIter = start + fruAreaSize;
1063                 break;
1064             }
1065             fruDataIter += 1 + fieldLength;
1066         }
1067         endOfFieldsLoc = fruDataIter;
1068     }
1069 
1070     std::copy_n(fruData.begin() + restFRUFieldsLoc,
1071                 endOfFieldsLoc - restFRUFieldsLoc + 1,
1072                 std::back_inserter(restFRUAreaFieldsData));
1073 
1074     fruAreaParams.restFieldsLoc = restFRUFieldsLoc;
1075     fruAreaParams.restFieldsEnd = endOfFieldsLoc;
1076 
1077     return true;
1078 }
1079 
1080 // Get all device dbus path and match path with product name using
1081 // regular expression and find the device index for all devices.
1082 
1083 std::optional<int> findIndexForFRU(
1084     boost::container::flat_map<
1085         std::pair<size_t, size_t>,
1086         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1087     std::string& productName)
1088 {
1089     int highest = -1;
1090     bool found = false;
1091 
1092     for (const auto& busIface : dbusInterfaceMap)
1093     {
1094         std::string path = busIface.second->get_object_path();
1095         if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
1096         {
1097             // Check if the match named has extra information.
1098             found = true;
1099             std::smatch baseMatch;
1100 
1101             bool match = std::regex_match(path, baseMatch,
1102                                           std::regex(productName + "_(\\d+)$"));
1103             if (match)
1104             {
1105                 if (baseMatch.size() == 2)
1106                 {
1107                     std::ssub_match baseSubMatch = baseMatch[1];
1108                     std::string base = baseSubMatch.str();
1109 
1110                     int value = std::stoi(base);
1111                     highest = (value > highest) ? value : highest;
1112                 }
1113             }
1114         }
1115     } // end searching objects
1116 
1117     if (!found)
1118     {
1119         return std::nullopt;
1120     }
1121     return highest;
1122 }
1123 
1124 // This function does format fru data as per IPMI format and find the
1125 // productName in the formatted fru data, get that productName and return
1126 // productName if found or return NULL.
1127 
1128 std::optional<std::string> getProductName(
1129     std::vector<uint8_t>& device,
1130     boost::container::flat_map<std::string, std::string>& formattedFRU,
1131     uint32_t bus, uint32_t address, size_t& unknownBusObjectCount)
1132 {
1133     std::string productName;
1134 
1135     resCodes res = formatIPMIFRU(device, formattedFRU);
1136     if (res == resCodes::resErr)
1137     {
1138         std::cerr << "failed to parse FRU for device at bus " << bus
1139                   << " address " << address << "\n";
1140         return std::nullopt;
1141     }
1142     if (res == resCodes::resWarn)
1143     {
1144         std::cerr << "Warnings while parsing FRU for device at bus " << bus
1145                   << " address " << address << "\n";
1146     }
1147 
1148     auto productNameFind = formattedFRU.find("BOARD_PRODUCT_NAME");
1149     // Not found under Board section or an empty string.
1150     if (productNameFind == formattedFRU.end() ||
1151         productNameFind->second.empty())
1152     {
1153         productNameFind = formattedFRU.find("PRODUCT_PRODUCT_NAME");
1154     }
1155     // Found under Product section and not an empty string.
1156     if (productNameFind != formattedFRU.end() &&
1157         !productNameFind->second.empty())
1158     {
1159         productName = productNameFind->second;
1160         std::regex illegalObject("[^A-Za-z0-9_]");
1161         productName = std::regex_replace(productName, illegalObject, "_");
1162     }
1163     else
1164     {
1165         productName = "UNKNOWN" + std::to_string(unknownBusObjectCount);
1166         unknownBusObjectCount++;
1167     }
1168     return productName;
1169 }
1170 
1171 bool getFruData(std::vector<uint8_t>& fruData, uint32_t bus, uint32_t address)
1172 {
1173     try
1174     {
1175         fruData = getFRUInfo(static_cast<uint16_t>(bus),
1176                              static_cast<uint8_t>(address));
1177     }
1178     catch (const std::invalid_argument& e)
1179     {
1180         std::cerr << "Failure getting FRU Info" << e.what() << "\n";
1181         return false;
1182     }
1183 
1184     return !fruData.empty();
1185 }
1186 
1187 bool isFieldEditable(std::string_view fieldName)
1188 {
1189     if (fieldName == "PRODUCT_ASSET_TAG")
1190     {
1191         return true; // PRODUCT_ASSET_TAG is always editable.
1192     }
1193 
1194     if (!ENABLE_FRU_UPDATE_PROPERTY)
1195     {
1196         return false; // If FRU update is disabled, no fields are editable.
1197     }
1198 
1199     // Editable fields
1200     constexpr std::array<std::string_view, 8> editableFields = {
1201         "MANUFACTURER",  "PRODUCT_NAME", "PART_NUMBER",    "VERSION",
1202         "SERIAL_NUMBER", "ASSET_TAG",    "FRU_VERSION_ID", "INFO_AM"};
1203 
1204     // Find position of first underscore
1205     std::size_t pos = fieldName.find('_');
1206     if (pos == std::string_view::npos || pos + 1 >= fieldName.size())
1207     {
1208         return false;
1209     }
1210 
1211     // Extract substring after the underscore
1212     std::string_view subField = fieldName.substr(pos + 1);
1213 
1214     // Trim trailing digits
1215     while (!subField.empty() && (std::isdigit(subField.back()) != 0))
1216     {
1217         subField.remove_suffix(1);
1218     }
1219 
1220     // Match against editable fields
1221     return std::ranges::contains(editableFields, subField);
1222 }
1223