xref: /openbmc/ipmi-fru-parser/frup.cpp (revision cfa96afa)
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 struct ipmi_fru_field
85 {
86     uint8_t type_length_field[IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX];
87     /* store length of data stored in buffer */
88     unsigned int type_length_field_length;
89 };
90 
91 typedef struct ipmi_fru_field ipmi_fru_field_t;
92 /*
93  * FRU Parser
94  */
95 
96 typedef struct ipmi_fru_area_info
97 {
98     uint8_t off;
99     uint8_t len;
100 } ipmi_fru_area_info_t;
101 
102 typedef struct ipmi_fru_common_hdr
103 {
104     uint8_t fmtver;
105     uint8_t internal;
106     uint8_t chassis;
107     uint8_t board;
108     uint8_t product;
109     uint8_t multirec;
110 } __attribute__((packed)) ipmi_fru_common_hdr_t;
111 
112 const char* vpd_key_names[] = {
113     "Key Names Table Start",
114     "Type",           /*OPENBMC_VPD_KEY_CHASSIS_TYPE*/
115     "Part Number",    /*OPENBMC_VPD_KEY_CHASSIS_PART_NUM,*/
116     "Serial Number",  /*OPENBMC_VPD_KEY_CHASSIS_SERIAL_NUM,*/
117     "Custom Field 1", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM1,*/
118     "Custom Field 2", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM2,*/
119     "Custom Field 3", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM3,*/
120     "Custom Field 4", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM4,*/
121     "Custom Field 5", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM5,*/
122     "Custom Field 6", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM6,*/
123     "Custom Field 7", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM7,*/
124     "Custom Field 8", /*OPENBMC_VPD_KEY_CHASSIS_CUSTOM8,*/
125 
126     "Mfg Date",
127     /* OPENBMC_VPD_KEY_BOARD_MFG_DATE, */ /* not a type/len */
128     "Manufacturer",                       /* OPENBMC_VPD_KEY_BOARD_MFR, */
129     "Name",                               /* OPENBMC_VPD_KEY_BOARD_NAME, */
130     "Serial Number",      /* OPENBMC_VPD_KEY_BOARD_SERIAL_NUM, */
131     "Part Number",        /* OPENBMC_VPD_KEY_BOARD_PART_NUM, */
132     "FRU File ID",        /* OPENBMC_VPD_KEY_BOARD_FRU_FILE_ID, */
133     "Custom Field 1",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM1,*/
134     "Custom Field 2",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM2,*/
135     "Custom Field 3",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM3,*/
136     "Custom Field 4",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM4,*/
137     "Custom Field 5",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM5,*/
138     "Custom Field 6",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM6,*/
139     "Custom Field 7",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM7,*/
140     "Custom Field 8",     /*OPENBMC_VPD_KEY_BOARD_CUSTOM8,*/
141 
142     "Manufacturer",       /* OPENBMC_VPD_KEY_PRODUCT_MFR, */
143     "Name",               /* OPENBMC_VPD_KEY_PRODUCT_NAME, */
144     "Model Number",       /* OPENBMC_VPD_KEY_PRODUCT_PART_MODEL_NUM, */
145     "Version",            /* OPENBMC_VPD_KEY_PRODUCT_VER, */
146     "Serial Number",      /* OPENBMC_VPD_KEY_PRODUCT_SERIAL_NUM, */
147     "Asset Tag",          /* OPENBMC_VPD_KEY_PRODUCT_ASSET_TAG, */
148     "FRU File ID",        /* OPENBMC_VPD_KEY_PRODUCT_FRU_FILE_ID, */
149     "Custom Field 1",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM1,*/
150     "Custom Field 2",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM2,*/
151     "Custom Field 3",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM3,*/
152     "Custom Field 4",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM4,*/
153     "Custom Field 5",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM5,*/
154     "Custom Field 6",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM6,*/
155     "Custom Field 7",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM7,*/
156     "Custom Field 8",     /*OPENBMC_VPD_KEY_PRODUCT_CUSTOM8,*/
157 
158     "Key Names Table End" /*OPENBMC_VPD_KEY_MAX,*/
159 };
160 
161 /*
162  * --------------------------------------------------------------------
163  *
164  * --------------------------------------------------------------------
165  */
166 
167 static size_t _to_time_str(uint32_t mfg_date_time, char* timestr, uint32_t len)
168 {
169     struct tm tm;
170     time_t t;
171     size_t s;
172 
173     ASSERT(timestr);
174     ASSERT(len);
175 
176     memset(&tm, '\0', sizeof(struct tm));
177 
178     t = mfg_date_time;
179     gmtime_r(&t, &tm);
180     s = strftime(timestr, len, "%F - %H:%M:%S", &tm);
181 
182     return s;
183 }
184 
185 /* private method to parse type/length */
186 static int _parse_type_length(const void* areabuf, unsigned int areabuflen,
187                               unsigned int current_area_offset,
188                               uint8_t* number_of_data_bytes,
189                               ipmi_fru_field_t* field)
190 {
191     const uint8_t* areabufptr = (const uint8_t*)areabuf;
192     uint8_t type_length;
193     uint8_t type_code;
194 
195     ASSERT(areabuf);
196     ASSERT(areabuflen);
197     ASSERT(number_of_data_bytes);
198 
199     type_length = areabufptr[current_area_offset];
200 
201     /* ipmi workaround
202      *
203      * dell p weredge r610
204      *
205      * my reading of the fru spec is that all non-custom fields are
206      * required to be listed by the vendor.  however, on this
207      * motherboard, some areas list this, indicating that there is
208      * no more data to be parsed.  so now, for "required" fields, i
209      * check to see if the type-length field is a sentinel before
210      * calling this function.
211      */
212 
213     ASSERT(type_length != IPMI_FRU_SENTINEL_VALUE);
214 
215     type_code = (type_length & IPMI_FRU_TYPE_LENGTH_TYPE_CODE_MASK) >>
216                 IPMI_FRU_TYPE_LENGTH_TYPE_CODE_SHIFT;
217     (*number_of_data_bytes) = type_length &
218                               IPMI_FRU_TYPE_LENGTH_NUMBER_OF_DATA_BYTES_MASK;
219 
220     /* special case: this shouldn't be a length of 0x01 (see type/length
221      * byte format in fru information storage definition).
222      */
223     if (type_code == IPMI_FRU_TYPE_LENGTH_TYPE_CODE_LANGUAGE_CODE &&
224         (*number_of_data_bytes) == 0x01)
225     {
226         return (-1);
227     }
228 
229     if ((current_area_offset + 1 + (*number_of_data_bytes)) > areabuflen)
230     {
231         return (-1);
232     }
233 
234     if (field)
235     {
236         memset(field->type_length_field, '\0',
237                IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX);
238         memcpy(field->type_length_field, &areabufptr[current_area_offset],
239                1 + (*number_of_data_bytes));
240         field->type_length_field_length = 1 + (*number_of_data_bytes);
241     }
242 
243     return (0);
244 }
245 
246 int ipmi_fru_chassis_info_area(const void* areabuf, unsigned int areabuflen,
247                                uint8_t* chassis_type,
248                                ipmi_fru_field_t* chassis_part_number,
249                                ipmi_fru_field_t* chassis_serial_number,
250                                ipmi_fru_field_t* chassis_custom_fields,
251                                unsigned int chassis_custom_fields_len)
252 {
253     const uint8_t* areabufptr = (const uint8_t*)areabuf;
254     unsigned int area_offset = 0;
255     unsigned int custom_fields_index = 0;
256     uint8_t number_of_data_bytes;
257     int rv = -1;
258 
259     if (!areabuf || !areabuflen)
260     {
261         return (-1);
262     }
263 
264     if (chassis_part_number)
265         memset(chassis_part_number, '\0', sizeof(ipmi_fru_field_t));
266     if (chassis_serial_number)
267         memset(chassis_serial_number, '\0', sizeof(ipmi_fru_field_t));
268     if (chassis_custom_fields && chassis_custom_fields_len)
269         memset(chassis_custom_fields, '\0',
270                sizeof(ipmi_fru_field_t) * chassis_custom_fields_len);
271 
272     if (chassis_type)
273         (*chassis_type) = areabufptr[area_offset];
274     area_offset++;
275 
276     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
277         goto out;
278 
279     if (_parse_type_length(areabufptr, areabuflen, area_offset,
280                            &number_of_data_bytes, chassis_part_number) < 0)
281         goto cleanup;
282     area_offset += 1; /* type/length byte */
283     area_offset += number_of_data_bytes;
284 
285     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
286         goto out;
287 
288     if (_parse_type_length(areabufptr, areabuflen, area_offset,
289                            &number_of_data_bytes, chassis_serial_number) < 0)
290         goto cleanup;
291     area_offset += 1; /* type/length byte */
292     area_offset += number_of_data_bytes;
293 
294     while (area_offset < areabuflen &&
295            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
296     {
297         ipmi_fru_field_t* field_ptr = NULL;
298 
299         if (chassis_custom_fields && chassis_custom_fields_len)
300         {
301             if (custom_fields_index < chassis_custom_fields_len)
302                 field_ptr = &chassis_custom_fields[custom_fields_index];
303             else
304             {
305                 goto cleanup;
306             }
307         }
308 
309         if (_parse_type_length(areabufptr, areabuflen, area_offset,
310                                &number_of_data_bytes, field_ptr) < 0)
311             goto cleanup;
312 
313         area_offset += 1; /* type/length byte */
314         area_offset += number_of_data_bytes;
315         custom_fields_index++;
316     }
317 
318 out:
319     rv = 0;
320 cleanup:
321     return (rv);
322 }
323 
324 int ipmi_fru_board_info_area(
325     const void* areabuf, unsigned int areabuflen, uint8_t* language_code,
326     uint32_t* mfg_date_time, ipmi_fru_field_t* board_manufacturer,
327     ipmi_fru_field_t* board_product_name, ipmi_fru_field_t* board_serial_number,
328     ipmi_fru_field_t* board_part_number, ipmi_fru_field_t* board_fru_file_id,
329     ipmi_fru_field_t* board_custom_fields, unsigned int board_custom_fields_len)
330 {
331     const uint8_t* areabufptr = (const uint8_t*)areabuf;
332     uint32_t mfg_date_time_tmp = 0;
333     unsigned int area_offset = 0;
334     unsigned int custom_fields_index = 0;
335     uint8_t number_of_data_bytes;
336     int rv = -1;
337 
338     if (!areabuf || !areabuflen)
339     {
340         return (-1);
341     }
342 
343     if (board_manufacturer)
344         memset(board_manufacturer, '\0', sizeof(ipmi_fru_field_t));
345     if (board_product_name)
346         memset(board_product_name, '\0', sizeof(ipmi_fru_field_t));
347     if (board_serial_number)
348         memset(board_serial_number, '\0', sizeof(ipmi_fru_field_t));
349     if (board_part_number)
350         memset(board_part_number, '\0', sizeof(ipmi_fru_field_t));
351     if (board_fru_file_id)
352         memset(board_fru_file_id, '\0', sizeof(ipmi_fru_field_t));
353     if (board_custom_fields && board_custom_fields_len)
354         memset(board_custom_fields, '\0',
355                sizeof(ipmi_fru_field_t) * board_custom_fields_len);
356 
357     if (language_code)
358         (*language_code) = areabufptr[area_offset];
359     area_offset++;
360 
361     if (mfg_date_time)
362     {
363         struct tm tm;
364         time_t t;
365 
366         /* mfg_date_time is little endian - see spec */
367         mfg_date_time_tmp |= areabufptr[area_offset];
368         area_offset++;
369         mfg_date_time_tmp |= (areabufptr[area_offset] << 8);
370         area_offset++;
371         mfg_date_time_tmp |= (areabufptr[area_offset] << 16);
372         area_offset++;
373 
374         /* mfg_date_time is in minutes, so multiple by 60 to get seconds */
375         mfg_date_time_tmp *= 60;
376 
377         /* posix says individual calls need not clear/set all portions of
378          * 'struct tm', thus passing 'struct tm' between functions could
379          * have issues.  so we need to memset.
380          */
381         memset(&tm, '\0', sizeof(struct tm));
382 
383         /* in fru, epoch is 0:00 hrs 1/1/96
384          *
385          * so convert into ansi epoch
386          */
387 
388         tm.tm_year = 96; /* years since 1900 */
389         tm.tm_mon = 0;   /* months since january */
390         tm.tm_mday = 1;  /* 1-31 */
391         tm.tm_hour = 0;
392         tm.tm_min = 0;
393         tm.tm_sec = 0;
394         tm.tm_isdst = -1;
395 
396         if ((t = mktime(&tm)) == (time_t)-1)
397         {
398             goto cleanup;
399         }
400 
401         mfg_date_time_tmp += (uint32_t)t;
402         (*mfg_date_time) = mfg_date_time_tmp;
403     }
404     else
405         area_offset += 3;
406 
407     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
408         goto out;
409 
410     if (_parse_type_length(areabufptr, areabuflen, area_offset,
411                            &number_of_data_bytes, board_manufacturer) < 0)
412         goto cleanup;
413     area_offset += 1; /* type/length byte */
414     area_offset += number_of_data_bytes;
415 
416     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
417         goto out;
418 
419     if (_parse_type_length(areabufptr, areabuflen, area_offset,
420                            &number_of_data_bytes, board_product_name) < 0)
421         goto cleanup;
422     area_offset += 1; /* type/length byte */
423     area_offset += number_of_data_bytes;
424 
425     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
426         goto out;
427 
428     if (_parse_type_length(areabufptr, areabuflen, area_offset,
429                            &number_of_data_bytes, board_serial_number) < 0)
430         goto cleanup;
431     area_offset += 1; /* type/length byte */
432     area_offset += number_of_data_bytes;
433 
434     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
435         goto out;
436 
437     if (_parse_type_length(areabufptr, areabuflen, area_offset,
438                            &number_of_data_bytes, board_part_number) < 0)
439         goto cleanup;
440     area_offset += 1; /* type/length byte */
441     area_offset += number_of_data_bytes;
442 
443     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
444         goto out;
445 
446     if (_parse_type_length(areabufptr, areabuflen, area_offset,
447                            &number_of_data_bytes, board_fru_file_id) < 0)
448         goto cleanup;
449     area_offset += 1; /* type/length byte */
450     area_offset += number_of_data_bytes;
451 
452     while (area_offset < areabuflen &&
453            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
454     {
455         ipmi_fru_field_t* field_ptr = NULL;
456 
457         if (board_custom_fields && board_custom_fields_len)
458         {
459             if (custom_fields_index < board_custom_fields_len)
460                 field_ptr = &board_custom_fields[custom_fields_index];
461             else
462             {
463                 goto cleanup;
464             }
465         }
466 
467         if (_parse_type_length(areabufptr, areabuflen, area_offset,
468                                &number_of_data_bytes, field_ptr) < 0)
469             goto cleanup;
470 
471         area_offset += 1; /* type/length byte */
472         area_offset += number_of_data_bytes;
473         custom_fields_index++;
474     }
475 
476 out:
477     rv = 0;
478 cleanup:
479     return (rv);
480 }
481 
482 int ipmi_fru_product_info_area(
483     const void* areabuf, unsigned int areabuflen, uint8_t* language_code,
484     ipmi_fru_field_t* product_manufacturer_name, ipmi_fru_field_t* product_name,
485     ipmi_fru_field_t* product_part_model_number,
486     ipmi_fru_field_t* product_version, ipmi_fru_field_t* product_serial_number,
487     ipmi_fru_field_t* product_asset_tag, ipmi_fru_field_t* product_fru_file_id,
488     ipmi_fru_field_t* product_custom_fields,
489     unsigned int product_custom_fields_len)
490 {
491     const uint8_t* areabufptr = (const uint8_t*)areabuf;
492     unsigned int area_offset = 0;
493     unsigned int custom_fields_index = 0;
494     uint8_t number_of_data_bytes;
495     int rv = -1;
496 
497     if (!areabuf || !areabuflen)
498     {
499         return (-1);
500     }
501 
502     if (product_manufacturer_name)
503         memset(product_manufacturer_name, '\0', sizeof(ipmi_fru_field_t));
504     if (product_name)
505         memset(product_name, '\0', sizeof(ipmi_fru_field_t));
506     if (product_part_model_number)
507         memset(product_part_model_number, '\0', sizeof(ipmi_fru_field_t));
508     if (product_version)
509         memset(product_version, '\0', sizeof(ipmi_fru_field_t));
510     if (product_serial_number)
511         memset(product_serial_number, '\0', sizeof(ipmi_fru_field_t));
512     if (product_asset_tag)
513         memset(product_asset_tag, '\0', sizeof(ipmi_fru_field_t));
514     if (product_fru_file_id)
515         memset(product_fru_file_id, '\0', sizeof(ipmi_fru_field_t));
516     if (product_custom_fields && product_custom_fields_len)
517         memset(product_custom_fields, '\0',
518                sizeof(ipmi_fru_field_t) * product_custom_fields_len);
519 
520     if (language_code)
521         (*language_code) = areabufptr[area_offset];
522     area_offset++;
523 
524     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
525         goto out;
526 
527     if (_parse_type_length(areabufptr, areabuflen, area_offset,
528                            &number_of_data_bytes,
529                            product_manufacturer_name) < 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_name) < 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,
548                            product_part_model_number) < 0)
549         goto cleanup;
550     area_offset += 1; /* type/length byte */
551     area_offset += number_of_data_bytes;
552 
553     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
554         goto out;
555 
556     if (_parse_type_length(areabufptr, areabuflen, area_offset,
557                            &number_of_data_bytes, product_version) < 0)
558         goto cleanup;
559     area_offset += 1; /* type/length byte */
560     area_offset += number_of_data_bytes;
561 
562     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
563         goto out;
564 
565     if (_parse_type_length(areabufptr, areabuflen, area_offset,
566                            &number_of_data_bytes, product_serial_number) < 0)
567         goto cleanup;
568     area_offset += 1; /* type/length byte */
569     area_offset += number_of_data_bytes;
570 
571     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
572         goto out;
573 
574     if (_parse_type_length(areabufptr, areabuflen, area_offset,
575                            &number_of_data_bytes, product_asset_tag) < 0)
576         goto cleanup;
577     area_offset += 1; /* type/length byte */
578     area_offset += number_of_data_bytes;
579 
580     if (areabufptr[area_offset] == IPMI_FRU_SENTINEL_VALUE)
581         goto out;
582 
583     if (_parse_type_length(areabufptr, areabuflen, area_offset,
584                            &number_of_data_bytes, product_fru_file_id) < 0)
585         goto cleanup;
586 
587     area_offset += 1; /* type/length byte */
588     area_offset += number_of_data_bytes;
589 
590     while (area_offset < areabuflen &&
591            areabufptr[area_offset] != IPMI_FRU_SENTINEL_VALUE)
592     {
593         ipmi_fru_field_t* field_ptr = NULL;
594 
595         if (product_custom_fields && product_custom_fields_len)
596         {
597             if (custom_fields_index < product_custom_fields_len)
598                 field_ptr = &product_custom_fields[custom_fields_index];
599             else
600             {
601                 goto cleanup;
602             }
603         }
604 
605         if (_parse_type_length(areabufptr, areabuflen, area_offset,
606                                &number_of_data_bytes, field_ptr) < 0)
607             goto cleanup;
608 
609         area_offset += 1; /* type/length byte */
610         area_offset += number_of_data_bytes;
611         custom_fields_index++;
612     }
613 
614 out:
615     rv = 0;
616 cleanup:
617     return (rv);
618 }
619 
620 void _append_to_dict(uint8_t vpd_key_id, uint8_t* vpd_key_val,
621                      IPMIFruInfo& info)
622 {
623     int type_length = vpd_key_val[0];
624     int type_code = (type_length & IPMI_FRU_TYPE_LENGTH_TYPE_CODE_MASK) >>
625                     IPMI_FRU_TYPE_LENGTH_TYPE_CODE_SHIFT;
626     int vpd_val_len = type_length &
627                       IPMI_FRU_TYPE_LENGTH_NUMBER_OF_DATA_BYTES_MASK;
628 
629     /* Needed to convert each uint8_t byte to a ascii */
630     char bin_byte[3] = {0};
631 
632     /*
633      * Max number of characters needed to represent 1 unsigned byte in string
634      * is number of bytes multiplied by 2. Extra 3 for 0x and a ending '\0';
635      */
636     char bin_in_ascii_len = vpd_val_len * 2 + 3;
637 
638     /* Binary converted to ascii in array */
639     char* bin_in_ascii = (char*)malloc(bin_in_ascii_len);
640 
641     /* For reading byte from the area */
642     int val = 0;
643 
644     char* bin_copy = &((char*)bin_in_ascii)[2];
645 
646     switch (type_code)
647     {
648         case 0:
649             memset(bin_in_ascii, 0x0, bin_in_ascii_len);
650 
651             /* Offset 1 is where actual data starts */
652             for (val = 1; val <= vpd_val_len; val++)
653             {
654                 /* 2 bytes for data and 1 for terminating '\0' */
655                 snprintf(bin_byte, 3, "%02x", vpd_key_val[val]);
656 
657                 /* Its a running string so strip off the '\0' */
658                 strncat(bin_copy, bin_byte, 2);
659             }
660 
661             /* We need the data represented as 0x...... */
662             if (vpd_val_len > 0)
663             {
664                 memcpy(bin_in_ascii, "0x", 2);
665             }
666 #if IPMI_FRU_PARSER_DEBUG
667             printf("_append_to_dict: VPD Key = [%s] : Type Code = [BINARY] :"
668                    " Len = [%d] : Val = [%s]\n",
669                    vpd_key_names[vpd_key_id], vpd_val_len, bin_in_ascii);
670 #endif
671             info[vpd_key_id] = std::make_pair(vpd_key_names[vpd_key_id],
672                                               bin_in_ascii);
673             break;
674 
675         case 3:
676 #if IPMI_FRU_PARSER_DEBUG
677             printf("_append_to_dict: VPD Key = [%s] : Type Code=[ASCII+Latin]"
678                    " : Len = [%d] : Val = [%s]\n",
679                    vpd_key_names[vpd_key_id], vpd_val_len, &vpd_key_val[1]);
680 #endif
681             info[vpd_key_id] = std::make_pair(
682                 vpd_key_names[vpd_key_id],
683                 std::string(vpd_key_val + 1, vpd_key_val + 1 + type_length));
684             break;
685     }
686 
687     if (bin_in_ascii)
688     {
689         free(bin_in_ascii);
690         bin_in_ascii = NULL;
691     }
692 }
693 
694 int parse_fru_area(const uint8_t area, const void* msgbuf, const size_t len,
695                    IPMIFruInfo& info)
696 {
697     int rv = -1;
698     int i = 0;
699 
700     /* Chassis */
701     uint8_t chassis_type;
702     /* Board */
703     uint32_t mfg_date_time;
704     /* Product */
705     // unsigned int product_custom_fields_len;
706 
707     // ipmi_fru_area_info_t fru_area_info [ IPMI_FRU_AREA_TYPE_MAX ];
708     ipmi_fru_field_t vpd_info[OPENBMC_VPD_KEY_MAX];
709     char timestr[OPENBMC_VPD_VAL_LEN];
710 
711     // uint8_t* ipmi_fru_field_str=NULL;
712     // ipmi_fru_common_hdr_t* chdr = NULL;
713     // uint8_t* hdr = NULL;
714 
715     ASSERT(msgbuf);
716 
717     for (i = 0; i < OPENBMC_VPD_KEY_MAX; i++)
718     {
719         memset(vpd_info[i].type_length_field, '\0',
720                IPMI_FRU_AREA_TYPE_LENGTH_FIELD_MAX);
721         vpd_info[i].type_length_field_length = 0;
722     }
723 
724     switch (area)
725     {
726         case IPMI_FRU_AREA_CHASSIS_INFO:
727 #if IPMI_FRU_PARSER_DEBUG
728             printf("Chassis : Buf len = [%d]\n", len);
729 #endif
730             ipmi_fru_chassis_info_area(
731                 (uint8_t*)msgbuf + 2, len, &chassis_type,
732                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_PART_NUM],
733                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_SERIAL_NUM],
734                 &vpd_info[OPENBMC_VPD_KEY_CHASSIS_CUSTOM1],
735                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
736 
737             /* Populate VPD Table */
738             for (i = 1; i <= OPENBMC_VPD_KEY_CHASSIS_MAX; i++)
739             {
740                 if (i == OPENBMC_VPD_KEY_CHASSIS_TYPE)
741                 {
742 #if IPMI_FRU_PARSER_DEBUG
743                     printf("Chassis : Appending [%s] = [%d]\n",
744                            vpd_key_names[i], chassis_type);
745 #endif
746                     info[i] = std::make_pair(vpd_key_names[i],
747                                              std::to_string(chassis_type));
748                     continue;
749                 }
750                 _append_to_dict(i, vpd_info[i].type_length_field, info);
751             }
752             break;
753         case IPMI_FRU_AREA_BOARD_INFO:
754 #if IPMI_FRU_PARSER_DEBUG
755             printf("Board : Buf len = [%d]\n", len);
756 #endif
757             ipmi_fru_board_info_area(
758                 (uint8_t*)msgbuf + 2, len, NULL, &mfg_date_time,
759                 &vpd_info[OPENBMC_VPD_KEY_BOARD_MFR],
760                 &vpd_info[OPENBMC_VPD_KEY_BOARD_NAME],
761                 &vpd_info[OPENBMC_VPD_KEY_BOARD_SERIAL_NUM],
762                 &vpd_info[OPENBMC_VPD_KEY_BOARD_PART_NUM],
763                 &vpd_info[OPENBMC_VPD_KEY_BOARD_FRU_FILE_ID],
764                 &vpd_info[OPENBMC_VPD_KEY_BOARD_CUSTOM1],
765                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
766 
767             /* Populate VPD Table */
768             for (i = OPENBMC_VPD_KEY_BOARD_MFG_DATE;
769                  i <= OPENBMC_VPD_KEY_BOARD_MAX; i++)
770             {
771                 if (i == OPENBMC_VPD_KEY_BOARD_MFG_DATE)
772                 {
773                     _to_time_str(mfg_date_time, timestr, OPENBMC_VPD_VAL_LEN);
774 #if IPMI_FRU_PARSER_DEBUG
775                     printf("Board : Appending [%s] = [%s]\n", vpd_key_names[i],
776                            timestr);
777 #endif
778                     info[i] = std::make_pair(vpd_key_names[i],
779                                              std::string(timestr));
780                     continue;
781                 }
782                 _append_to_dict(i, vpd_info[i].type_length_field, info);
783             }
784             break;
785         case IPMI_FRU_AREA_PRODUCT_INFO:
786 #if IPMI_FRU_PARSER_DEBUG
787             printf("Product : Buf len = [%d]\n", len);
788 #endif
789             ipmi_fru_product_info_area(
790                 (uint8_t*)msgbuf + 2, len, NULL,
791                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_MFR],
792                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_NAME],
793                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_PART_MODEL_NUM],
794                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_VER],
795                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_SERIAL_NUM],
796                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_ASSET_TAG],
797                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_FRU_FILE_ID],
798                 &vpd_info[OPENBMC_VPD_KEY_PRODUCT_CUSTOM1],
799                 OPENBMC_VPD_KEY_CUSTOM_FIELDS_MAX);
800 
801             for (i = OPENBMC_VPD_KEY_PRODUCT_MFR;
802                  i <= OPENBMC_VPD_KEY_PRODUCT_MAX; ++i)
803             {
804                 _append_to_dict(i, vpd_info[i].type_length_field, info);
805             }
806             break;
807         default:
808             /* TODO: Parse Multi Rec / Internal use area */
809             break;
810     }
811 
812 #if IPMI_FRU_PARSER_DEBUG
813     printf("parse_fru_area : Dictionary Packing Complete\n");
814 #endif
815     rv = 0;
816     return (rv);
817 }
818