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