xref: /openbmc/openpower-vpd-parser/impl.cpp (revision c78d887c)
1 #include "impl.hpp"
2 
3 #include "vpdecc/vpdecc.h"
4 
5 #include "const.hpp"
6 #include "defines.hpp"
7 #include "ibm_vpd_utils.hpp"
8 #include "types.hpp"
9 #include "vpd_exceptions.hpp"
10 
11 #include <algorithm>
12 #include <exception>
13 #include <iomanip>
14 #include <iostream>
15 #include <iterator>
16 #include <sstream>
17 #include <tuple>
18 #include <unordered_map>
19 
20 namespace openpower
21 {
22 namespace vpd
23 {
24 namespace parser
25 {
26 using namespace openpower::vpd::constants;
27 using namespace openpower::vpd::exceptions;
28 
29 static const std::unordered_map<std::string, Record> supportedRecords = {
30     {"VINI", Record::VINI}, {"OPFR", Record::OPFR}, {"OSYS", Record::OSYS}};
31 
32 static const std::unordered_map<std::string, internal::KeywordInfo>
33     supportedKeywords = {
34         {"DR", std::make_tuple(record::Keyword::DR, keyword::Encoding::ASCII)},
35         {"PN", std::make_tuple(record::Keyword::PN, keyword::Encoding::ASCII)},
36         {"SN", std::make_tuple(record::Keyword::SN, keyword::Encoding::ASCII)},
37         {"CC", std::make_tuple(record::Keyword::CC, keyword::Encoding::ASCII)},
38         {"HW", std::make_tuple(record::Keyword::HW, keyword::Encoding::RAW)},
39         {"B1", std::make_tuple(record::Keyword::B1, keyword::Encoding::B1)},
40         {"VN", std::make_tuple(record::Keyword::VN, keyword::Encoding::ASCII)},
41         {"MB", std::make_tuple(record::Keyword::MB, keyword::Encoding::MB)},
42         {"MM", std::make_tuple(record::Keyword::MM, keyword::Encoding::ASCII)},
43         {"UD", std::make_tuple(record::Keyword::UD, keyword::Encoding::UD)},
44         {"VP", std::make_tuple(record::Keyword::VP, keyword::Encoding::ASCII)},
45         {"VS", std::make_tuple(record::Keyword::VS, keyword::Encoding::ASCII)},
46 };
47 
48 namespace
49 {
toHex(size_t c)50 constexpr auto toHex(size_t c)
51 {
52     constexpr auto map = "0123456789abcdef";
53     return map[c];
54 }
55 } // namespace
56 
57 /*readUInt16LE: Read 2 bytes LE data*/
readUInt16LE(Binary::const_iterator iterator)58 static LE2ByteData readUInt16LE(Binary::const_iterator iterator)
59 {
60     LE2ByteData lowByte = *iterator;
61     LE2ByteData highByte = *(iterator + 1);
62     lowByte |= (highByte << 8);
63     return lowByte;
64 }
65 
getVtocOffset() const66 RecordOffset Impl::getVtocOffset() const
67 {
68     auto vpdPtr = vpd.cbegin();
69     std::advance(vpdPtr, offsets::VTOC_PTR);
70     // Get VTOC Offset
71     auto vtocOffset = readUInt16LE(vpdPtr);
72 
73     return vtocOffset;
74 }
75 
76 #ifdef IPZ_PARSER
vhdrEccCheck()77 int Impl::vhdrEccCheck()
78 {
79     int rc = eccStatus::SUCCESS;
80     auto vpdPtr = vpd.cbegin();
81 
82     auto l_status =
83         vpdecc_check_data(const_cast<uint8_t*>(&vpdPtr[offsets::VHDR_RECORD]),
84                           lengths::VHDR_RECORD_LENGTH,
85                           const_cast<uint8_t*>(&vpdPtr[offsets::VHDR_ECC]),
86                           lengths::VHDR_ECC_LENGTH);
87     if (l_status == VPD_ECC_CORRECTABLE_DATA)
88     {
89         try
90         {
91             if (vpdFileStream.is_open())
92             {
93                 vpdFileStream.seekp(vpdStartOffset + offsets::VHDR_RECORD,
94                                     std::ios::beg);
95                 vpdFileStream.write(
96                     reinterpret_cast<const char*>(&vpd[offsets::VHDR_RECORD]),
97                     lengths::VHDR_RECORD_LENGTH);
98             }
99             else
100             {
101                 std::cerr << "File not open";
102                 rc = eccStatus::FAILED;
103             }
104         }
105         catch (const std::fstream::failure& e)
106         {
107             std::cout << "Error while operating on file with exception:"
108                       << e.what();
109             rc = eccStatus::FAILED;
110         }
111     }
112     else if (l_status != VPD_ECC_OK)
113     {
114         rc = eccStatus::FAILED;
115     }
116 
117     return rc;
118 }
119 
vtocEccCheck()120 int Impl::vtocEccCheck()
121 {
122     int rc = eccStatus::SUCCESS;
123     // Use another pointer to get ECC information from VHDR,
124     // actual pointer is pointing to VTOC data
125 
126     auto vpdPtr = vpd.cbegin();
127 
128     // Get VTOC Offset
129     auto vtocOffset = getVtocOffset();
130 
131     // Get the VTOC Length
132     std::advance(vpdPtr, offsets::VTOC_PTR + sizeof(RecordOffset));
133     auto vtocLength = readUInt16LE(vpdPtr);
134 
135     // Get the ECC Offset
136     std::advance(vpdPtr, sizeof(RecordLength));
137     auto vtocECCOffset = readUInt16LE(vpdPtr);
138 
139     // Get the ECC length
140     std::advance(vpdPtr, sizeof(ECCOffset));
141     auto vtocECCLength = readUInt16LE(vpdPtr);
142 
143     // Reset pointer to start of the vpd,
144     // so that Offset will point to correct address
145     vpdPtr = vpd.cbegin();
146     auto l_status = vpdecc_check_data(
147         const_cast<uint8_t*>(&vpdPtr[vtocOffset]), vtocLength,
148         const_cast<uint8_t*>(&vpdPtr[vtocECCOffset]), vtocECCLength);
149     if (l_status == VPD_ECC_CORRECTABLE_DATA)
150     {
151         try
152         {
153             if (vpdFileStream.is_open())
154             {
155                 vpdFileStream.seekp(vpdStartOffset + vtocOffset, std::ios::beg);
156                 vpdFileStream.write(
157                     reinterpret_cast<const char*>(&vpdPtr[vtocOffset]),
158                     vtocLength);
159             }
160             else
161             {
162                 std::cerr << "File not open";
163                 rc = eccStatus::FAILED;
164             }
165         }
166         catch (const std::fstream::failure& e)
167         {
168             std::cout << "Error while operating on file with exception "
169                       << e.what();
170             rc = eccStatus::FAILED;
171         }
172     }
173     else if (l_status != VPD_ECC_OK)
174     {
175         rc = eccStatus::FAILED;
176     }
177 
178     return rc;
179 }
180 
recordEccCheck(Binary::const_iterator iterator)181 int Impl::recordEccCheck(Binary::const_iterator iterator)
182 {
183     int rc = eccStatus::SUCCESS;
184 
185     auto recordOffset = readUInt16LE(iterator);
186 
187     std::advance(iterator, sizeof(RecordOffset));
188     auto recordLength = readUInt16LE(iterator);
189 
190     std::advance(iterator, sizeof(RecordLength));
191     auto eccOffset = readUInt16LE(iterator);
192 
193     std::advance(iterator, sizeof(ECCOffset));
194     auto eccLength = readUInt16LE(iterator);
195 
196     if (eccLength == 0 || eccOffset == 0)
197     {
198         throw(VpdEccException(
199             "Could not find ECC's offset or Length for Record:"));
200     }
201 
202     if (recordOffset == 0 || recordLength == 0)
203     {
204         throw(VpdDataException("Could not find VPD record offset or VPD record "
205                                "length for Record:"));
206     }
207 
208     auto vpdPtr = vpd.cbegin();
209 
210     auto l_status = vpdecc_check_data(
211         const_cast<uint8_t*>(&vpdPtr[recordOffset]), recordLength,
212         const_cast<uint8_t*>(&vpdPtr[eccOffset]), eccLength);
213     if (l_status == VPD_ECC_CORRECTABLE_DATA)
214     {
215         try
216         {
217             if (vpdFileStream.is_open())
218             {
219                 vpdFileStream.seekp(vpdStartOffset + recordOffset,
220                                     std::ios::beg);
221                 vpdFileStream.write(
222                     reinterpret_cast<const char*>(&vpdPtr[recordOffset]),
223                     recordLength);
224             }
225             else
226             {
227                 std::cerr << "File not open";
228                 rc = eccStatus::FAILED;
229             }
230         }
231         catch (const std::fstream::failure& e)
232         {
233             std::cout << "Error while operating on file with exception "
234                       << e.what();
235             rc = eccStatus::FAILED;
236         }
237     }
238     else if (l_status != VPD_ECC_OK)
239     {
240         rc = eccStatus::FAILED;
241     }
242 
243     return rc;
244 }
245 #endif
246 
checkHeader()247 void Impl::checkHeader()
248 {
249     if (vpd.empty() || (lengths::RECORD_MIN > vpd.size()))
250     {
251         throw(VpdDataException("Malformed VPD"));
252     }
253     else
254     {
255         auto iterator = vpd.cbegin();
256         std::advance(iterator, offsets::VHDR);
257         auto stop = std::next(iterator, lengths::RECORD_NAME);
258         std::string record(iterator, stop);
259         if ("VHDR" != record)
260         {
261             throw(VpdDataException("VHDR record not found"));
262         }
263 
264 #ifdef IPZ_PARSER
265         // Check ECC
266         int rc = eccStatus::FAILED;
267         rc = vhdrEccCheck();
268         if (rc != eccStatus::SUCCESS)
269         {
270             throw(VpdEccException("ERROR: VHDR ECC check Failed"));
271         }
272 #endif
273     }
274 }
275 
readTOC(Binary::const_iterator & iterator)276 std::size_t Impl::readTOC(Binary::const_iterator& iterator)
277 {
278     // The offset to VTOC could be 1 or 2 bytes long
279     RecordOffset vtocOffset = getVtocOffset();
280 
281     // Got the offset to VTOC, skip past record header and keyword header
282     // to get to the record name.
283     std::advance(iterator, vtocOffset + sizeof(RecordId) + sizeof(RecordSize) +
284                                // Skip past the RT keyword, which contains
285                                // the record name.
286                                lengths::KW_NAME + sizeof(KwSize));
287 
288     auto stop = std::next(iterator, lengths::RECORD_NAME);
289     std::string record(iterator, stop);
290     if ("VTOC" != record)
291     {
292         throw(VpdDataException("VTOC record not found"));
293     }
294 
295 #ifdef IPZ_PARSER
296     // Check ECC
297     int rc = eccStatus::FAILED;
298     rc = vtocEccCheck();
299     if (rc != eccStatus::SUCCESS)
300     {
301         throw(VpdEccException("ERROR: VTOC ECC check Failed"));
302     }
303 #endif
304     // VTOC record name is good, now read through the TOC, stored in the PT
305     // PT keyword; vpdBuffer is now pointing at the first character of the
306     // name 'VTOC', jump to PT data.
307     // Skip past record name and KW name, 'PT'
308     std::advance(iterator, lengths::RECORD_NAME + lengths::KW_NAME);
309     // Note size of PT
310     std::size_t ptLen = *iterator;
311     // Skip past PT size
312     std::advance(iterator, sizeof(KwSize));
313 
314     // length of PT keyword
315     return ptLen;
316 }
317 
readPT(Binary::const_iterator iterator,std::size_t ptLength)318 internal::OffsetList Impl::readPT(Binary::const_iterator iterator,
319                                   std::size_t ptLength)
320 {
321     internal::OffsetList offsets{};
322 
323     auto end = iterator;
324     std::advance(end, ptLength);
325 
326     // Look at each entry in the PT keyword. In the entry,
327     // we care only about the record offset information.
328     while (iterator < end)
329     {
330 #ifdef IPZ_PARSER
331         auto iteratorToRecName = iterator;
332 #endif
333         // Skip record name and record type
334         std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType));
335 
336         // Get record offset
337         auto offset = readUInt16LE(iterator);
338         offsets.push_back(offset);
339 
340 #ifdef IPZ_PARSER
341         std::string recordName(iteratorToRecName,
342                                iteratorToRecName + lengths::RECORD_NAME);
343 
344         try
345         {
346             // Verify the ECC for this Record
347             int rc = recordEccCheck(iterator);
348 
349             if (rc != eccStatus::SUCCESS)
350             {
351                 std::string errorMsg = std::string(
352                     "ERROR: ECC check did not pass for the "
353                     "Record:");
354                 throw(VpdEccException(errorMsg));
355             }
356         }
357         catch (const VpdEccException& ex)
358         {
359             inventory::PelAdditionalData additionalData{};
360             additionalData.emplace("DESCRIPTION",
361                                    std::string{ex.what()} + recordName);
362             additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
363             createPEL(additionalData, PelSeverity::WARNING,
364                       errIntfForEccCheckFail, nullptr);
365         }
366         catch (const VpdDataException& ex)
367         {
368             inventory::PelAdditionalData additionalData{};
369             additionalData.emplace("DESCRIPTION",
370                                    std::string{ex.what()} + recordName);
371             additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
372             createPEL(additionalData, PelSeverity::WARNING,
373                       errIntfForInvalidVPD, nullptr);
374         }
375 
376 #endif
377 
378         // Jump record size, record length, ECC offset and ECC length
379         std::advance(iterator, sizeof(RecordOffset) + sizeof(RecordLength) +
380                                    sizeof(ECCOffset) + sizeof(ECCLength));
381     }
382 
383     return offsets;
384 }
385 
processRecord(std::size_t recordOffset)386 void Impl::processRecord(std::size_t recordOffset)
387 {
388     // Jump to record name
389     auto nameOffset = recordOffset + sizeof(RecordId) + sizeof(RecordSize) +
390                       // Skip past the RT keyword, which contains
391                       // the record name.
392                       lengths::KW_NAME + sizeof(KwSize);
393     // Get record name
394     auto iterator = vpd.cbegin();
395     std::advance(iterator, nameOffset);
396 
397     std::string name(iterator, iterator + lengths::RECORD_NAME);
398 
399 #ifndef IPZ_PARSER
400     if (supportedRecords.end() != supportedRecords.find(name))
401     {
402 #endif
403         // If it's a record we're interested in, proceed to find
404         // contained keywords and their values.
405         std::advance(iterator, lengths::RECORD_NAME);
406 
407 #ifdef IPZ_PARSER
408 
409         // Reverse back to RT Kw, in ipz vpd, to Read RT KW & value
410         std::advance(iterator, -(lengths::KW_NAME + sizeof(KwSize) +
411                                  lengths::RECORD_NAME));
412 #endif
413         auto kwMap = readKeywords(iterator);
414         // Add entry for this record (and contained keyword:value pairs)
415         // to the parsed vpd output.
416         out.emplace(std::move(name), std::move(kwMap));
417 
418 #ifndef IPZ_PARSER
419     }
420 #endif
421 }
422 
readKwData(const internal::KeywordInfo & keyword,std::size_t dataLength,Binary::const_iterator iterator)423 std::string Impl::readKwData(const internal::KeywordInfo& keyword,
424                              std::size_t dataLength,
425                              Binary::const_iterator iterator)
426 {
427     using namespace openpower::vpd;
428     switch (std::get<keyword::Encoding>(keyword))
429     {
430         case keyword::Encoding::ASCII:
431         {
432             auto stop = std::next(iterator, dataLength);
433             return std::string(iterator, stop);
434         }
435 
436         case keyword::Encoding::RAW:
437         {
438             auto stop = std::next(iterator, dataLength);
439             std::string data(iterator, stop);
440             std::string result{};
441             std::for_each(data.cbegin(), data.cend(), [&result](size_t c) {
442                 result += toHex(c >> 4);
443                 result += toHex(c & 0x0F);
444             });
445             return result;
446         }
447 
448         case keyword::Encoding::MB:
449         {
450             // MB is BuildDate, represent as
451             // 1997-01-01-08:30:00
452             // <year>-<month>-<day>-<hour>:<min>:<sec>
453             auto stop = std::next(iterator, MB_LEN_BYTES);
454             std::string data(iterator, stop);
455             std::string result;
456             result.reserve(MB_LEN_BYTES);
457             auto strItr = data.cbegin();
458             std::advance(strItr, 1);
459             std::for_each(strItr, data.cend(), [&result](size_t c) {
460                 result += toHex(c >> 4);
461                 result += toHex(c & 0x0F);
462             });
463 
464             result.insert(MB_YEAR_END, 1, '-');
465             result.insert(MB_MONTH_END, 1, '-');
466             result.insert(MB_DAY_END, 1, '-');
467             result.insert(MB_HOUR_END, 1, ':');
468             result.insert(MB_MIN_END, 1, ':');
469 
470             return result;
471         }
472 
473         case keyword::Encoding::B1:
474         {
475             // B1 is MAC address, represent as AA:BB:CC:DD:EE:FF
476             auto stop = std::next(iterator, MAC_ADDRESS_LEN_BYTES);
477             std::string data(iterator, stop);
478             std::string result{};
479             auto strItr = data.cbegin();
480             size_t firstDigit = *strItr;
481             result += toHex(firstDigit >> 4);
482             result += toHex(firstDigit & 0x0F);
483             std::advance(strItr, 1);
484             std::for_each(strItr, data.cend(), [&result](size_t c) {
485                 result += ":";
486                 result += toHex(c >> 4);
487                 result += toHex(c & 0x0F);
488             });
489             return result;
490         }
491 
492         case keyword::Encoding::UD:
493         {
494             // UD, the UUID info, represented as
495             // 123e4567-e89b-12d3-a456-426655440000
496             //<time_low>-<time_mid>-<time hi and version>
497             //-<clock_seq_hi_and_res clock_seq_low>-<48 bits node id>
498             auto stop = std::next(iterator, UUID_LEN_BYTES);
499             std::string data(iterator, stop);
500             std::string result{};
501             std::for_each(data.cbegin(), data.cend(), [&result](size_t c) {
502                 result += toHex(c >> 4);
503                 result += toHex(c & 0x0F);
504             });
505             result.insert(UUID_TIME_LOW_END, 1, '-');
506             result.insert(UUID_TIME_MID_END, 1, '-');
507             result.insert(UUID_TIME_HIGH_END, 1, '-');
508             result.insert(UUID_CLK_SEQ_END, 1, '-');
509 
510             return result;
511         }
512         default:
513             break;
514     }
515 
516     return {};
517 }
518 
readKeywords(Binary::const_iterator iterator)519 internal::KeywordMap Impl::readKeywords(Binary::const_iterator iterator)
520 {
521     internal::KeywordMap map{};
522     while (true)
523     {
524         // Note keyword name
525         std::string kw(iterator, iterator + lengths::KW_NAME);
526         if (LAST_KW == kw)
527         {
528             // We're done
529             break;
530         }
531         // Check if the Keyword is '#kw'
532         char kwNameStart = *iterator;
533 
534         // Jump past keyword name
535         std::advance(iterator, lengths::KW_NAME);
536 
537         std::size_t length;
538         std::size_t lengthHighByte;
539         if (POUND_KW == kwNameStart)
540         {
541             // Note keyword data length
542             length = *iterator;
543             lengthHighByte = *(iterator + 1);
544             length |= (lengthHighByte << 8);
545 
546             // Jump past 2Byte keyword length
547             std::advance(iterator, sizeof(PoundKwSize));
548         }
549         else
550         {
551             // Note keyword data length
552             length = *iterator;
553 
554             // Jump past keyword length
555             std::advance(iterator, sizeof(KwSize));
556         }
557 
558         // Pointing to keyword data now
559 #ifndef IPZ_PARSER
560         if (supportedKeywords.end() != supportedKeywords.find(kw))
561         {
562             // Keyword is of interest to us
563             std::string data = readKwData((supportedKeywords.find(kw))->second,
564                                           length, iterator);
565             map.emplace(std::move(kw), std::move(data));
566         }
567 
568 #else
569         // support all the Keywords
570         auto stop = std::next(iterator, length);
571         std::string kwdata(iterator, stop);
572         map.emplace(std::move(kw), std::move(kwdata));
573 
574 #endif
575         // Jump past keyword data length
576         std::advance(iterator, length);
577     }
578 
579     return map;
580 }
581 
run()582 Store Impl::run()
583 {
584     // Check if the VHDR record is present
585     checkHeader();
586 
587     auto iterator = vpd.cbegin();
588 
589     // Read the table of contents record
590     std::size_t ptLen = readTOC(iterator);
591 
592     // Read the table of contents record, to get offsets
593     // to other records.
594     auto offsets = readPT(iterator, ptLen);
595     for (const auto& offset : offsets)
596     {
597         processRecord(offset);
598     }
599     // Return a Store object, which has interfaces to
600     // access parsed VPD by record:keyword
601     return Store(std::move(out));
602 }
603 
checkVPDHeader()604 void Impl::checkVPDHeader()
605 {
606     // Check if the VHDR record is present and is valid
607     checkHeader();
608 }
609 
readKwFromHw(const std::string & record,const std::string & keyword)610 std::string Impl::readKwFromHw(const std::string& record,
611                                const std::string& keyword)
612 {
613     // Check if the VHDR record is present
614     checkHeader();
615 
616     auto iterator = vpd.cbegin();
617 
618     // Read the table of contents record
619     std::size_t ptLen = readTOC(iterator);
620 
621     // Read the table of contents record, to get offsets
622     // to other records.
623     auto offsets = readPT(iterator, ptLen);
624     for (const auto& offset : offsets)
625     {
626         // Jump to record name
627         auto nameOffset = offset + sizeof(RecordId) + sizeof(RecordSize) +
628                           // Skip past the RT keyword, which contains
629                           // the record name.
630                           lengths::KW_NAME + sizeof(KwSize);
631         // Get record name
632         auto iterator = vpd.cbegin();
633         std::advance(iterator, nameOffset);
634 
635         std::string name(iterator, iterator + lengths::RECORD_NAME);
636         if (name != record)
637         {
638             continue;
639         }
640         else
641         {
642             processRecord(offset);
643             const auto& itr = out.find(record);
644             if (itr != out.end())
645             {
646                 const auto& kwValItr = (itr->second).find(keyword);
647                 if (kwValItr != (itr->second).end())
648                 {
649                     return kwValItr->second;
650                 }
651                 else
652                 {
653                     return "";
654                 }
655             }
656         }
657     }
658     return "";
659 }
660 
661 } // namespace parser
662 } // namespace vpd
663 } // namespace openpower
664