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