xref: /openbmc/ipmi-fru-parser/frup.cpp (revision 545a5d33)
1 /*
2  * Copyright (C) 2003-2014 FreeIPMI Core Team
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18 /*****************************************************************************\
19  *  Copyright (C) 2007-2014 Lawrence Livermore National Security, LLC.
20  *  Copyright (C) 2007 The Regents of the University of California.
21  *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
22  *  Written by Albert Chu <chu11@llnl.gov>
23  *  UCRL-CODE-232183
24  *
25  *  This file is part of Ipmi-fru, a tool used for retrieving
26  *  motherboard field replaceable unit (FRU) information. For details,
27  *  see http://www.llnl.gov/linux/.
28  *
29  *  Ipmi-fru is free software; you can redistribute it and/or modify
30  *  it under the terms of the GNU General Public License as published by the
31  *  Free Software Foundation; either version 3 of the License, or (at your
32  *  option) any later version.
33  *
34  *  Ipmi-fru is distributed in the hope that it will be useful, but
35  *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
36  *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
37  *  for more details.
38  *
39  *  You should have received a copy of the GNU General Public License along
40  *  with Ipmi-fru.  If not, see <http://www.gnu.org/licenses/>.
41 \*****************************************************************************/
42 #include "frup.hpp"
43 
44 #include <ctype.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <systemd/sd-bus.h>
49 #include <time.h>
50 #include <unistd.h>
51 
52 #define TEXTSTR(a) #a
53 #define ASSERT(x)                                                              \
54     do                                                                         \
55     {                                                                          \
56         if (0 == (x))                                                          \
57         {                                                                      \
58             fprintf(stderr,                                                    \
59                     "Assertion failed: %s, "                                   \
60                     "%d at \'%s\'\n",                                          \
61                     __FILE__, __LINE__, TEXTSTR(a));                           \
62             return -1;                                                         \
63         }                                                                      \
64     } while (0)
65 
66 #define IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX 512
67 #define IPMI_FRU_SENTINEL_VALUE 0xC1
68 #define IPMI_FRU_TYPE_LENGTH_TYPE_CODE_MASK 0xC0
69 #define IPMI_FRU_TYPE_LENGTH_TYPE_CODE_SHIFT 0x06
70 #define IPMI_FRU_TYPE_LENGTH_NUMBER_OF_DATA_BYTES_MASK 0x3F
71 #define IPMI_FRU_TYPE_LENGTH_TYPE_CODE_LANGUAGE_CODE 0x03
72 
73 /* OpenBMC defines for Parser */
74 #define IPMI_FRU_AREA_INTERNAL_USE 0x00
75 #define IPMI_FRU_AREA_CHASSIS_INFO 0x01
76 #define IPMI_FRU_AREA_BOARD_INFO 0x02
77 #define IPMI_FRU_AREA_PRODUCT_INFO 0x03
78 #define IPMI_FRU_AREA_MULTI_RECORD 0x04
79 #define IPMI_FRU_AREA_TYPE_MAX 0x05
80 
81 #define OPENBMC_VPD_KEY_LEN 64
82 #define OPENBMC_VPD_VAL_LEN 512
83 
84 constexpr long fruEpochMinutes = 820454400;
85 
86 struct ipmi_fru_field
87 {
88     uint8_t type_length_field[IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX];
89     /* store length of data stored in buffer */
90     unsigned int type_length_field_length;
91 };
92 
93 typedef struct ipmi_fru_field ipmi_fru_field_t;
94 /*
95  * FRU Parser
96  */
97 
98 typedef struct ipmi_fru_area_info
99 {
100     uint8_t off;
101     uint8_t len;
102 } ipmi_fru_area_info_t;
103 
104 typedef struct ipmi_fru_common_hdr
105 {
106     uint8_t fmtver;
107     uint8_t internal;
108     uint8_t chassis;
109     uint8_t board;
110     uint8_t product;
111     uint8_t multirec;
112 } __attribute__((packed)) ipmi_fru_common_hdr_t;
113 
114 const char* vpd_key_names[] = {
115     "Key Names Table Start",
116     "Type",           /*OPENBMC_VPD_KEY_CHASSIS_TYPE*/
117     "Part Number",    /*OPENBMC_VPD_KEY_CHASSIS_PART_NUM,*/
118     "Serial Number",  /*OPENBMC_VPD_KEY_CHASSIS_SERIAL_NUM,*/
119     "Custom Field 1", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM1,*/
120     "Custom Field 2", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM2,*/
121     "Custom Field 3", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM3,*/
122     "Custom Field 4", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM4,*/
123     "Custom Field 5", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM5,*/
124     "Custom Field 6", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM6,*/
125     "Custom Field 7", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM7,*/
126     "Custom Field 8", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM8,*/
127 
128     "Mfg Date",
129     /* OPENBMC_VPD_KEY_BOARD_MFG_DATE, */ /* not a type/len */
130     "Manufacturer",                       /* OPENBMC_VPD_KEY_BOARD_MFR, */
131     "Name",                               /* OPENBMC_VPD_KEY_BOARD_NAME, */
132     "Serial Number",      /* OPENBMC_VPD_KEY_BOARD_SERIAL_NUM, */
133     "Part Number",        /* OPENBMC_VPD_KEY_BOARD_PART_NUM, */
134     "FRU File ID",        /* OPENBMC_VPD_KEY_BOARD_FRU_FILE_ID, */
135     "Custom Field 1",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM1,*/
136     "Custom Field 2",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM2,*/
137     "Custom Field 3",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM3,*/
138     "Custom Field 4",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM4,*/
139     "Custom Field 5",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM5,*/
140     "Custom Field 6",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM6,*/
141     "Custom Field 7",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM7,*/
142     "Custom Field 8",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM8,*/
143 
144     "Manufacturer",       /* OPENBMC_VPD_KEY_PRODUCT_MFR, */
145     "Name",               /* OPENBMC_VPD_KEY_PRODUCT_NAME, */
146     "Model Number",       /* OPENBMC_VPD_KEY_PRODUCT_PART_MODEL_NUM, */
147     "Version",            /* OPENBMC_VPD_KEY_PRODUCT_VER, */
148     "Serial Number",      /* OPENBMC_VPD_KEY_PRODUCT_SERIAL_NUM, */
149     "Asset Tag",          /* OPENBMC_VPD_KEY_PRODUCT_ASSET_TAG, */
150     "FRU File ID",        /* OPENBMC_VPD_KEY_PRODUCT_FRU_FILE_ID, */
151     "Custom Field 1",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM1,*/
152     "Custom Field 2",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM2,*/
153     "Custom Field 3",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM3,*/
154     "Custom Field 4",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM4,*/
155     "Custom Field 5",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM5,*/
156     "Custom Field 6",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM6,*/
157     "Custom Field 7",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM7,*/
158     "Custom Field 8",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM8,*/
159 
160     "Key Names Table End" /*OPENBMC_VPD_KEY_MAX,*/
161 };
162 
163 /*
164  * --------------------------------------------------------------------
165  *
166  * --------------------------------------------------------------------
167  */
168 
169 static size_t _to_time_str(uint32_t mfg_date_time, char* timestr, uint32_t len)
170 {
171     struct tm tm;
172     time_t t;
173     size_t s;
174 
175     ASSERT(timestr);
176     ASSERT(len);
177 
178     memset(&tm, '\0', sizeof(struct tm));
179 
180     t = mfg_date_time;
181     gmtime_r(&t, &tm);
182     s = strftime(timestr, len, "%F - %H:%M:%S UTC", &tm);
183 
184     return s;
185 }
186 
187 /* private method to parse type/length */
188 static int _parse_type_length(const void* areabuf, unsigned int areabuflen,
189                               unsigned int current_area_offset,
190                               uint8_t* number_of_data_bytes,
191                               ipmi_fru_field_t* field)
192 {
193     const uint8_t* areabufptr = (const uint8_t*)areabuf;
194     uint8_t type_length;
195     uint8_t type_code;
196 
197     ASSERT(areabuf);
198     ASSERT(areabuflen);
199     ASSERT(number_of_data_bytes);
200 
201     type_length = areabufptr[current_area_offset];
202 
203     /* ipmi workaround
204      *
205      * dell p weredge r610
206      *
207      * my reading of the fru spec is that all non-custom fields are
208      * required to be listed by the vendor.  however, on this
209      * motherboard, some areas list this, indicating that there is
210      * no more data to be parsed.  so now, for "required" fields, i
211      * check to see if the type-length field is a sentinel before
212      * calling this function.
213      */
214 
215     ASSERT(type_length != IPMI_FRU_SENTINEL_VALUE);
216 
217     type_code = (type_length & IPMI_FRU_TYPE_LENGTH_TYPE_CODE_MASK) >>
218                 IPMI_FRU_TYPE_LENGTH_TYPE_CODE_SHIFT;
219     (*number_of_data_bytes) = type_length &
220                               IPMI_FRU_TYPE_LENGTH_NUMBER_OF_DATA_BYTES_MASK;
221 
222     /* special case: this shouldn't be a length of 0x01 (see type/length
223      * byte format in fru information storage definition).
224      */
225     if (type_code == IPMI_FRU_TYPE_LENGTH_TYPE_CODE_LANGUAGE_CODE &&
226         (*number_of_data_bytes) == 0x01)
227     {
228         return (-1);
229     }
230 
231     if ((current_area_offset + 1 + (*number_of_data_bytes)) > areabuflen)
232     {
233         return (-1);
234     }
235 
236     if (field)
237     {
238         memset(field->type_length_field, '\0',
239                IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX);
240         memcpy(field->type_length_field, &areabufptr[current_area_offset],
241                1 + (*number_of_data_bytes));
242         field->type_length_field_length = 1 + (*number_of_data_bytes);
243     }
244 
245     return (0);
246 }
247 
248 int ipmi_fru_chassis_info_area(const void* areabuf, unsigned int areabuflen,
249                                uint8_t* chassis_type,
250                                ipmi_fru_field_t* chassis_part_number,
251                                ipmi_fru_field_t* chassis_serial_number,
252                                ipmi_fru_field_t* chassis_custom_fields,
253                                unsigned int chassis_custom_fields_len)
254 {
255     const uint8_t* areabufptr = (const uint8_t*)areabuf;
256     unsigned int area_offset = 0;
257     unsigned int custom_fields_index = 0;
258     uint8_t number_of_data_bytes;
259     int rv = -1;
260 
261     if (!areabuf || !areabuflen)
262     {
263         return (-1);
264     }
265 
266     if (chassis_part_number)
267         memset(chassis_part_number, '\0', sizeof(ipmi_fru_field_t));
268     if (chassis_serial_number)
269         memset(chassis_serial_number, '\0', sizeof(ipmi_fru_field_t));
270     if (chassis_custom_fields && chassis_custom_fields_len)
271         memset(chassis_custom_fields, '\0',
272                sizeof(ipmi_fru_field_t) * chassis_custom_fields_len);
273 
274     if (chassis_type)
275         (*chassis_type) = areabufptr[area_offset];
276     area_offset++;
277 
278     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
279         goto out;
280 
281     if (_parse_type_length(areabufptr, areabuflen, area_offset,
282                            &number_of_data_bytes, chassis_part_number) < 0)
283         goto cleanup;
284     area_offset += 1; /* type/length byte */
285     area_offset += number_of_data_bytes;
286 
287     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
288         goto out;
289 
290     if (_parse_type_length(areabufptr, areabuflen, area_offset,
291                            &number_of_data_bytes, chassis_serial_number) < 0)
292         goto cleanup;
293     area_offset += 1; /* type/length byte */
294     area_offset += number_of_data_bytes;
295 
296     while (area_offset < areabuflen &&
297            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
298     {
299         ipmi_fru_field_t* field_ptr = NULL;
300 
301         if (chassis_custom_fields && chassis_custom_fields_len)
302         {
303             if (custom_fields_index < chassis_custom_fields_len)
304                 field_ptr = &chassis_custom_fields[custom_fields_index];
305             else
306             {
307                 goto cleanup;
308             }
309         }
310 
311         if (_parse_type_length(areabufptr, areabuflen, area_offset,
312                                &number_of_data_bytes, field_ptr) < 0)
313             goto cleanup;
314 
315         area_offset += 1; /* type/length byte */
316         area_offset += number_of_data_bytes;
317         custom_fields_index++;
318     }
319 
320 out:
321     rv = 0;
322 cleanup:
323     return (rv);
324 }
325 
326 int ipmi_fru_board_info_area(
327     const void* areabuf, unsigned int areabuflen, uint8_t* language_code,
328     uint32_t* mfg_date_time, ipmi_fru_field_t* board_manufacturer,
329     ipmi_fru_field_t* board_product_name, ipmi_fru_field_t* board_serial_number,
330     ipmi_fru_field_t* board_part_number, ipmi_fru_field_t* board_fru_file_id,
331     ipmi_fru_field_t* board_custom_fields, unsigned int board_custom_fields_len)
332 {
333     const uint8_t* areabufptr = (const uint8_t*)areabuf;
334     unsigned int area_offset = 0;
335     unsigned int custom_fields_index = 0;
336     uint8_t number_of_data_bytes;
337     int rv = -1;
338 
339     if (!areabuf || !areabuflen)
340     {
341         return (-1);
342     }
343 
344     if (board_manufacturer)
345         memset(board_manufacturer, '\0', sizeof(ipmi_fru_field_t));
346     if (board_product_name)
347         memset(board_product_name, '\0', sizeof(ipmi_fru_field_t));
348     if (board_serial_number)
349         memset(board_serial_number, '\0', sizeof(ipmi_fru_field_t));
350     if (board_part_number)
351         memset(board_part_number, '\0', sizeof(ipmi_fru_field_t));
352     if (board_fru_file_id)
353         memset(board_fru_file_id, '\0', sizeof(ipmi_fru_field_t));
354     if (board_custom_fields && board_custom_fields_len)
355         memset(board_custom_fields, '\0',
356                sizeof(ipmi_fru_field_t) * board_custom_fields_len);
357 
358     if (language_code)
359         (*language_code) = areabufptr[area_offset];
360     area_offset++;
361 
362     if (mfg_date_time)
363     {
364         unsigned int minutes = areabufptr[area_offset];
365         area_offset++;
366         minutes |= (areabufptr[area_offset] << 8);
367         area_offset++;
368         minutes |= (areabufptr[area_offset] << 16);
369         area_offset++;
370 
371         /* In fru, epoch is 0:00 hrs 1/1/96 == 820454400
372          * Remove it directly and remove the time conversion.
373          */
374         (*mfg_date_time) = fruEpochMinutes + static_cast<long>(minutes) * 60;
375     }
376     else
377         area_offset += 3;
378 
379     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
380         goto out;
381 
382     if (_parse_type_length(areabufptr, areabuflen, area_offset,
383                            &number_of_data_bytes, board_manufacturer) < 0)
384         goto cleanup;
385     area_offset += 1; /* type/length byte */
386     area_offset += number_of_data_bytes;
387 
388     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
389         goto out;
390 
391     if (_parse_type_length(areabufptr, areabuflen, area_offset,
392                            &number_of_data_bytes, board_product_name) < 0)
393         goto cleanup;
394     area_offset += 1; /* type/length byte */
395     area_offset += number_of_data_bytes;
396 
397     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
398         goto out;
399 
400     if (_parse_type_length(areabufptr, areabuflen, area_offset,
401                            &number_of_data_bytes, board_serial_number) < 0)
402         goto cleanup;
403     area_offset += 1; /* type/length byte */
404     area_offset += number_of_data_bytes;
405 
406     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
407         goto out;
408 
409     if (_parse_type_length(areabufptr, areabuflen, area_offset,
410                            &number_of_data_bytes, board_part_number) < 0)
411         goto cleanup;
412     area_offset += 1; /* type/length byte */
413     area_offset += number_of_data_bytes;
414 
415     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
416         goto out;
417 
418     if (_parse_type_length(areabufptr, areabuflen, area_offset,
419                            &number_of_data_bytes, board_fru_file_id) < 0)
420         goto cleanup;
421     area_offset += 1; /* type/length byte */
422     area_offset += number_of_data_bytes;
423 
424     while (area_offset < areabuflen &&
425            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
426     {
427         ipmi_fru_field_t* field_ptr = NULL;
428 
429         if (board_custom_fields && board_custom_fields_len)
430         {
431             if (custom_fields_index < board_custom_fields_len)
432                 field_ptr = &board_custom_fields[custom_fields_index];
433             else
434             {
435                 goto cleanup;
436             }
437         }
438 
439         if (_parse_type_length(areabufptr, areabuflen, area_offset,
440                                &number_of_data_bytes, field_ptr) < 0)
441             goto cleanup;
442 
443         area_offset += 1; /* type/length byte */
444         area_offset += number_of_data_bytes;
445         custom_fields_index++;
446     }
447 
448 out:
449     rv = 0;
450 cleanup:
451     return (rv);
452 }
453 
454 int ipmi_fru_product_info_area(
455     const void* areabuf, unsigned int areabuflen, uint8_t* language_code,
456     ipmi_fru_field_t* product_manufacturer_name, ipmi_fru_field_t* product_name,
457     ipmi_fru_field_t* product_part_model_number,
458     ipmi_fru_field_t* product_version, ipmi_fru_field_t* product_serial_number,
459     ipmi_fru_field_t* product_asset_tag, ipmi_fru_field_t* product_fru_file_id,
460     ipmi_fru_field_t* product_custom_fields,
461     unsigned int product_custom_fields_len)
462 {
463     const uint8_t* areabufptr = (const uint8_t*)areabuf;
464     unsigned int area_offset = 0;
465     unsigned int custom_fields_index = 0;
466     uint8_t number_of_data_bytes;
467     int rv = -1;
468 
469     if (!areabuf || !areabuflen)
470     {
471         return (-1);
472     }
473 
474     if (product_manufacturer_name)
475         memset(product_manufacturer_name, '\0', sizeof(ipmi_fru_field_t));
476     if (product_name)
477         memset(product_name, '\0', sizeof(ipmi_fru_field_t));
478     if (product_part_model_number)
479         memset(product_part_model_number, '\0', sizeof(ipmi_fru_field_t));
480     if (product_version)
481         memset(product_version, '\0', sizeof(ipmi_fru_field_t));
482     if (product_serial_number)
483         memset(product_serial_number, '\0', sizeof(ipmi_fru_field_t));
484     if (product_asset_tag)
485         memset(product_asset_tag, '\0', sizeof(ipmi_fru_field_t));
486     if (product_fru_file_id)
487         memset(product_fru_file_id, '\0', sizeof(ipmi_fru_field_t));
488     if (product_custom_fields && product_custom_fields_len)
489         memset(product_custom_fields, '\0',
490                sizeof(ipmi_fru_field_t) * product_custom_fields_len);
491 
492     if (language_code)
493         (*language_code) = areabufptr[area_offset];
494     area_offset++;
495 
496     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
497         goto out;
498 
499     if (_parse_type_length(areabufptr, areabuflen, area_offset,
500                            &number_of_data_bytes,
501                            product_manufacturer_name) < 0)
502         goto cleanup;
503     area_offset += 1; /* type/length byte */
504     area_offset += number_of_data_bytes;
505 
506     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
507         goto out;
508 
509     if (_parse_type_length(areabufptr, areabuflen, area_offset,
510                            &number_of_data_bytes, product_name) < 0)
511         goto cleanup;
512     area_offset += 1; /* type/length byte */
513     area_offset += number_of_data_bytes;
514 
515     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
516         goto out;
517 
518     if (_parse_type_length(areabufptr, areabuflen, area_offset,
519                            &number_of_data_bytes,
520                            product_part_model_number) < 0)
521         goto cleanup;
522     area_offset += 1; /* type/length byte */
523     area_offset += number_of_data_bytes;
524 
525     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
526         goto out;
527 
528     if (_parse_type_length(areabufptr, areabuflen, area_offset,
529                            &number_of_data_bytes, product_version) < 0)
530         goto cleanup;
531     area_offset += 1; /* type/length byte */
532     area_offset += number_of_data_bytes;
533 
534     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
535         goto out;
536 
537     if (_parse_type_length(areabufptr, areabuflen, area_offset,
538                            &number_of_data_bytes, product_serial_number) < 0)
539         goto cleanup;
540     area_offset += 1; /* type/length byte */
541     area_offset += number_of_data_bytes;
542 
543     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
544         goto out;
545 
546     if (_parse_type_length(areabufptr, areabuflen, area_offset,
547                            &number_of_data_bytes, product_asset_tag) < 0)
548         goto cleanup;
549     area_offset += 1; /* type/length byte */
550     area_offset += number_of_data_bytes;
551 
552     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
553         goto out;
554 
555     if (_parse_type_length(areabufptr, areabuflen, area_offset,
556                            &number_of_data_bytes, product_fru_file_id) < 0)
557         goto cleanup;
558 
559     area_offset += 1; /* type/length byte */
560     area_offset += number_of_data_bytes;
561 
562     while (area_offset < areabuflen &&
563            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
564     {
565         ipmi_fru_field_t* field_ptr = NULL;
566 
567         if (product_custom_fields && product_custom_fields_len)
568         {
569             if (custom_fields_index < product_custom_fields_len)
570                 field_ptr = &product_custom_fields[custom_fields_index];
571             else
572             {
573                 goto cleanup;
574             }
575         }
576 
577         if (_parse_type_length(areabufptr, areabuflen, area_offset,
578                                &number_of_data_bytes, field_ptr) < 0)
579             goto cleanup;
580 
581         area_offset += 1; /* type/length byte */
582         area_offset += number_of_data_bytes;
583         custom_fields_index++;
584     }
585 
586 out:
587     rv = 0;
588 cleanup:
589     return (rv);
590 }
591 
592 void _append_to_dict(uint8_t vpd_key_id, uint8_t* vpd_key_val,
593                      IPMIFruInfo& info)
594 {
595     int type_length = vpd_key_val[0];
596     int type_code = (type_length & IPMI_FRU_TYPE_LENGTH_TYPE_CODE_MASK) >>
597                     IPMI_FRU_TYPE_LENGTH_TYPE_CODE_SHIFT;
598     int vpd_val_len = type_length &
599                       IPMI_FRU_TYPE_LENGTH_NUMBER_OF_DATA_BYTES_MASK;
600 
601     /* Needed to convert each uint8_t byte to a ascii */
602     char bin_byte[3] = {0};
603 
604     /*
605      * Max number of characters needed to represent 1 unsigned byte in string
606      * is number of bytes multiplied by 2. Extra 3 for 0x and a ending '\0';
607      */
608     char bin_in_ascii_len = vpd_val_len * 2 + 3;
609 
610     /* Binary converted to ascii in array */
611     char* bin_in_ascii = (char*)malloc(bin_in_ascii_len);
612 
613     /* For reading byte from the area */
614     int val = 0;
615 
616     char* bin_copy = &((char*)bin_in_ascii)[2];
617 
618     switch (type_code)
619     {
620         case 0:
621             memset(bin_in_ascii, 0x0, bin_in_ascii_len);
622 
623             /* Offset 1 is where actual data starts */
624             for (val = 1; val <= vpd_val_len; val++)
625             {
626                 /* 2 bytes for data and 1 for terminating '\0' */
627                 snprintf(bin_byte, 3, "%02x", vpd_key_val[val]);
628 
629 #pragma GCC diagnostic push
630 #pragma GCC diagnostic ignored "-Wstringop-truncation"
631                 /* Its a running string so strip off the '\0' */
632                 strncat(bin_copy, bin_byte, 2);
633 #pragma GCC diagnostic pop
634             }
635 
636             /* We need the data represented as 0x...... */
637             if (vpd_val_len > 0)
638             {
639                 memcpy(bin_in_ascii, "0x", 2);
640             }
641 #if IPMI_FRU_PARSER_DEBUG
642             printf("_append_to_dict: VPD Key = [%s] : Type Code = [BINARY] :"
643                    " Len = [%d] : Val = [%s]\n",
644                    vpd_key_names[vpd_key_id], vpd_val_len, bin_in_ascii);
645 #endif
646             info[vpd_key_id] = std::make_pair(vpd_key_names[vpd_key_id],
647                                               bin_in_ascii);
648             break;
649 
650         case 3:
651 #if IPMI_FRU_PARSER_DEBUG
652             printf("_append_to_dict: VPD Key = [%s] : Type Code=[ASCII+Latin]"
653                    " : Len = [%d] : Val = [%s]\n",
654                    vpd_key_names[vpd_key_id], vpd_val_len, &vpd_key_val[1]);
655 #endif
656             info[vpd_key_id] = std::make_pair(
657                 vpd_key_names[vpd_key_id],
658                 std::string(vpd_key_val + 1, vpd_key_val + 1 + type_length));
659             break;
660     }
661 
662     if (bin_in_ascii)
663     {
664         free(bin_in_ascii);
665         bin_in_ascii = NULL;
666     }
667 }
668 
669 int parse_fru_area(const uint8_t area, const void* msgbuf, const size_t len,
670                    IPMIFruInfo& info)
671 {
672     int rv = -1;
673     int i = 0;
674 
675     /* Chassis */
676     uint8_t chassis_type;
677     /* Board */
678     uint32_t mfg_date_time;
679     /* Product */
680     // unsigned int product_custom_fields_len;
681 
682     // ipmi_fru_area_info_t fru_area_info [ IPMI_FRU_AREA_TYPE_MAX ];
683     ipmi_fru_field_t vpd_info[OPENBMC_VPD_KEY_MAX];
684     char timestr[OPENBMC_VPD_VAL_LEN];
685 
686     // uint8_t* ipmi_fru_field_str=NULL;
687     // ipmi_fru_common_hdr_t* chdr = NULL;
688     // uint8_t* hdr = NULL;
689 
690     ASSERT(msgbuf);
691 
692     for (i = 0; i < OPENBMC_VPD_KEY_MAX; i++)
693     {
694         memset(vpd_info[i].type_length_field, '\0',
695                IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX);
696         vpd_info[i].type_length_field_length = 0;
697     }
698 
699     switch (area)
700     {
701         case IPMI_FRU_AREA_CHASSIS_INFO:
702 #if IPMI_FRU_PARSER_DEBUG
703             printf("Chassis : Buf len = [%d]\n", len);
704 #endif
705             ipmi_fru_chassis_info_area(
706                 (uint8_t*)msgbuf + 2, len, &chassis_type,
707                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_PART_NUM],
708                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_SERIAL_NUM],
709                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_CUSTOM1],
710                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
711 
712             /* Populate VPD Table */
713             for (i = 1; i <= OPENBMC_VPD_KEY_CHASSIS_MAX; i++)
714             {
715                 if (i == OPENBMC_VPD_KEY_CHASSIS_TYPE)
716                 {
717 #if IPMI_FRU_PARSER_DEBUG
718                     printf("Chassis : Appending [%s] = [%d]\n",
719                            vpd_key_names[i], chassis_type);
720 #endif
721                     info[i] = std::make_pair(vpd_key_names[i],
722                                              std::to_string(chassis_type));
723                     continue;
724                 }
725                 _append_to_dict(i, vpd_info[i].type_length_field, info);
726             }
727             break;
728         case IPMI_FRU_AREA_BOARD_INFO:
729 #if IPMI_FRU_PARSER_DEBUG
730             printf("Board : Buf len = [%d]\n", len);
731 #endif
732             ipmi_fru_board_info_area(
733                 (uint8_t*)msgbuf + 2, len, NULL, &mfg_date_time,
734                 &vpd_info[OPENBMC_VPD_KEY_BOARD_MFR],
735                 &vpd_info[OPENBMC_VPD_KEY_BOARD_NAME],
736                 &vpd_info[OPENBMC_VPD_KEY_BOARD_SERIAL_NUM],
737                 &vpd_info[OPENBMC_VPD_KEY_BOARD_PART_NUM],
738                 &vpd_info[OPENBMC_VPD_KEY_BOARD_FRU_FILE_ID],
739                 &vpd_info[OPENBMC_VPD_KEY_BOARD_CUSTOM1],
740                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
741 
742             /* Populate VPD Table */
743             for (i = OPENBMC_VPD_KEY_BOARD_MFG_DATE;
744                  i <= OPENBMC_VPD_KEY_BOARD_MAX; i++)
745             {
746                 if (i == OPENBMC_VPD_KEY_BOARD_MFG_DATE)
747                 {
748                     _to_time_str(mfg_date_time, timestr, OPENBMC_VPD_VAL_LEN);
749 #if IPMI_FRU_PARSER_DEBUG
750                     printf("Board : Appending [%s] = [%s]\n", vpd_key_names[i],
751                            timestr);
752 #endif
753                     info[i] = std::make_pair(vpd_key_names[i],
754                                              std::string(timestr));
755                     continue;
756                 }
757                 _append_to_dict(i, vpd_info[i].type_length_field, info);
758             }
759             break;
760         case IPMI_FRU_AREA_PRODUCT_INFO:
761 #if IPMI_FRU_PARSER_DEBUG
762             printf("Product : Buf len = [%d]\n", len);
763 #endif
764             ipmi_fru_product_info_area(
765                 (uint8_t*)msgbuf + 2, len, NULL,
766                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_MFR],
767                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_NAME],
768                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_PART_MODEL_NUM],
769                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_VER],
770                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_SERIAL_NUM],
771                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_ASSET_TAG],
772                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_FRU_FILE_ID],
773                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_CUSTOM1],
774                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
775 
776             for (i = OPENBMC_VPD_KEY_PRODUCT_MFR;
777                  i <= OPENBMC_VPD_KEY_PRODUCT_MAX; ++i)
778             {
779                 _append_to_dict(i, vpd_info[i].type_length_field, info);
780             }
781             break;
782         default:
783             /* TODO: Parse Multi Rec / Internal use area */
784             break;
785     }
786 
787 #if IPMI_FRU_PARSER_DEBUG
788     printf("parse_fru_area : Dictionary Packing Complete\n");
789 #endif
790     rv = 0;
791     return (rv);
792 }
793