1 #include "apphandler.hpp"
2 
3 #include "app/channel.hpp"
4 #include "app/watchdog.hpp"
5 #include "ipmid.hpp"
6 #include "sys_info_param.hpp"
7 #include "transporthandler.hpp"
8 #include "types.hpp"
9 #include "utils.hpp"
10 
11 #include <arpa/inet.h>
12 #include <host-ipmid/ipmid-api.h>
13 #include <limits.h>
14 #include <mapper.h>
15 #include <systemd/sd-bus.h>
16 #include <unistd.h>
17 
18 #include <algorithm>
19 #include <array>
20 #include <cstddef>
21 #include <fstream>
22 #include <memory>
23 #include <nlohmann/json.hpp>
24 #include <phosphor-logging/elog-errors.hpp>
25 #include <phosphor-logging/log.hpp>
26 #include <string>
27 #include <tuple>
28 #include <vector>
29 #include <xyz/openbmc_project/Common/error.hpp>
30 #include <xyz/openbmc_project/Software/Activation/server.hpp>
31 #include <xyz/openbmc_project/Software/Version/server.hpp>
32 #include <xyz/openbmc_project/State/BMC/server.hpp>
33 
34 #if __has_include(<filesystem>)
35 #include <filesystem>
36 #elif __has_include(<experimental/filesystem>)
37 #include <experimental/filesystem>
38 namespace std
39 {
40 // splice experimental::filesystem into std
41 namespace filesystem = std::experimental::filesystem;
42 } // namespace std
43 #else
44 #error filesystem not available
45 #endif
46 
47 extern sd_bus* bus;
48 
49 constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
50 constexpr auto bmc_state_property = "CurrentBMCState";
51 constexpr auto bmc_interface = "xyz.openbmc_project.Inventory.Item.Bmc";
52 constexpr auto bmc_guid_interface = "xyz.openbmc_project.Common.UUID";
53 constexpr auto bmc_guid_property = "UUID";
54 constexpr auto bmc_guid_len = 16;
55 
56 static constexpr auto redundancyIntf =
57     "xyz.openbmc_project.Software.RedundancyPriority";
58 static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
59 static constexpr auto activationIntf =
60     "xyz.openbmc_project.Software.Activation";
61 static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
62 
63 void register_netfn_app_functions() __attribute__((constructor));
64 
65 using namespace phosphor::logging;
66 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
67 using Version = sdbusplus::xyz::openbmc_project::Software::server::Version;
68 using Activation =
69     sdbusplus::xyz::openbmc_project::Software::server::Activation;
70 using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC;
71 namespace fs = std::filesystem;
72 
73 // Offset in get device id command.
74 typedef struct
75 {
76     uint8_t id;
77     uint8_t revision;
78     uint8_t fw[2];
79     uint8_t ipmi_ver;
80     uint8_t addn_dev_support;
81     uint8_t manuf_id[3];
82     uint8_t prod_id[2];
83     uint8_t aux[4];
84 } __attribute__((packed)) ipmi_device_id_t;
85 
86 /**
87  * @brief Returns the Version info from primary s/w object
88  *
89  * Get the Version info from the active s/w object which is having high
90  * "Priority" value(a smaller number is a higher priority) and "Purpose"
91  * is "BMC" from the list of all s/w objects those are implementing
92  * RedundancyPriority interface from the given softwareRoot path.
93  *
94  * @return On success returns the Version info from primary s/w object.
95  *
96  */
97 std::string getActiveSoftwareVersionInfo()
98 {
99     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
100 
101     std::string revision{};
102     auto objectTree =
103         ipmi::getAllDbusObjects(bus, softwareRoot, redundancyIntf, "");
104     if (objectTree.empty())
105     {
106         log<level::ERR>("No Obj has implemented the s/w redundancy interface",
107                         entry("INTERFACE=%s", redundancyIntf));
108         elog<InternalFailure>();
109     }
110 
111     auto objectFound = false;
112     for (auto& softObject : objectTree)
113     {
114         auto service = ipmi::getService(bus, redundancyIntf, softObject.first);
115         auto objValueTree = ipmi::getManagedObjects(bus, service, softwareRoot);
116 
117         auto minPriority = 0xFF;
118         for (const auto& objIter : objValueTree)
119         {
120             try
121             {
122                 auto& intfMap = objIter.second;
123                 auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
124                 auto& versionProps = intfMap.at(versionIntf);
125                 auto& activationProps = intfMap.at(activationIntf);
126                 auto priority =
127                     redundancyPriorityProps.at("Priority").get<uint8_t>();
128                 auto purpose = versionProps.at("Purpose").get<std::string>();
129                 auto activation =
130                     activationProps.at("Activation").get<std::string>();
131                 auto version = versionProps.at("Version").get<std::string>();
132                 if ((Version::convertVersionPurposeFromString(purpose) ==
133                      Version::VersionPurpose::BMC) &&
134                     (Activation::convertActivationsFromString(activation) ==
135                      Activation::Activations::Active))
136                 {
137                     if (priority < minPriority)
138                     {
139                         minPriority = priority;
140                         objectFound = true;
141                         revision = std::move(version);
142                     }
143                 }
144             }
145             catch (const std::exception& e)
146             {
147                 log<level::ERR>(e.what());
148             }
149         }
150     }
151 
152     if (!objectFound)
153     {
154         log<level::ERR>("Could not found an BMC software Object");
155         elog<InternalFailure>();
156     }
157 
158     return revision;
159 }
160 
161 bool getCurrentBmcState()
162 {
163     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
164 
165     // Get the Inventory object implementing the BMC interface
166     ipmi::DbusObjectInfo bmcObject =
167         ipmi::getDbusObject(bus, bmc_state_interface);
168     auto variant =
169         ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
170                               bmc_state_interface, bmc_state_property);
171 
172     return variant.is<std::string>() &&
173            BMC::convertBMCStateFromString(variant.get<std::string>()) ==
174                BMC::BMCState::Ready;
175 }
176 
177 ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
178                                          ipmi_request_t request,
179                                          ipmi_response_t response,
180                                          ipmi_data_len_t data_len,
181                                          ipmi_context_t context)
182 {
183     ipmi_ret_t rc = IPMI_CC_OK;
184     *data_len = 0;
185 
186     log<level::DEBUG>("IPMI SET ACPI STATE Ignoring for now\n");
187     return rc;
188 }
189 
190 typedef struct
191 {
192     char major;
193     char minor;
194     uint16_t d[2];
195 } rev_t;
196 
197 /* Currently supports the vx.x-x-[-x] and v1.x.x-x-[-x] format. It will     */
198 /* return -1 if not in those formats, this routine knows how to parse       */
199 /* version = v0.6-19-gf363f61-dirty                                         */
200 /*            ^ ^ ^^          ^                                             */
201 /*            | |  |----------|-- additional details                        */
202 /*            | |---------------- Minor                                     */
203 /*            |------------------ Major                                     */
204 /* and version = v1.99.10-113-g65edf7d-r3-0-g9e4f715                        */
205 /*                ^ ^  ^^ ^                                                 */
206 /*                | |  |--|---------- additional details                    */
207 /*                | |---------------- Minor                                 */
208 /*                |------------------ Major                                 */
209 /* Additional details : If the option group exists it will force Auxiliary  */
210 /* Firmware Revision Information 4th byte to 1 indicating the build was     */
211 /* derived with additional edits                                            */
212 int convert_version(const char* p, rev_t* rev)
213 {
214     std::string s(p);
215     std::string token;
216     uint16_t commits;
217 
218     auto location = s.find_first_of('v');
219     if (location != std::string::npos)
220     {
221         s = s.substr(location + 1);
222     }
223 
224     if (!s.empty())
225     {
226         location = s.find_first_of(".");
227         if (location != std::string::npos)
228         {
229             rev->major =
230                 static_cast<char>(std::stoi(s.substr(0, location), 0, 16));
231             token = s.substr(location + 1);
232         }
233 
234         if (!token.empty())
235         {
236             location = token.find_first_of(".-");
237             if (location != std::string::npos)
238             {
239                 rev->minor = static_cast<char>(
240                     std::stoi(token.substr(0, location), 0, 16));
241                 token = token.substr(location + 1);
242             }
243         }
244 
245         // Capture the number of commits on top of the minor tag.
246         // I'm using BE format like the ipmi spec asked for
247         location = token.find_first_of(".-");
248         if (!token.empty())
249         {
250             commits = std::stoi(token.substr(0, location), 0, 16);
251             rev->d[0] = (commits >> 8) | (commits << 8);
252 
253             // commit number we skip
254             location = token.find_first_of(".-");
255             if (location != std::string::npos)
256             {
257                 token = token.substr(location + 1);
258             }
259         }
260         else
261         {
262             rev->d[0] = 0;
263         }
264 
265         if (location != std::string::npos)
266         {
267             token = token.substr(location + 1);
268         }
269 
270         // Any value of the optional parameter forces it to 1
271         location = token.find_first_of(".-");
272         if (location != std::string::npos)
273         {
274             token = token.substr(location + 1);
275         }
276         commits = (!token.empty()) ? 1 : 0;
277 
278         // We do this operation to get this displayed in least significant bytes
279         // of ipmitool device id command.
280         rev->d[1] = (commits >> 8) | (commits << 8);
281     }
282 
283     return 0;
284 }
285 
286 ipmi_ret_t ipmi_app_get_device_id(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
287                                   ipmi_request_t request,
288                                   ipmi_response_t response,
289                                   ipmi_data_len_t data_len,
290                                   ipmi_context_t context)
291 {
292     ipmi_ret_t rc = IPMI_CC_OK;
293     int r = -1;
294     rev_t rev = {0};
295     static ipmi_device_id_t dev_id{};
296     static bool dev_id_initialized = false;
297     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
298     constexpr auto ipmiDevIdStateShift = 7;
299     constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
300 
301     // Data length
302     *data_len = sizeof(dev_id);
303 
304     if (!dev_id_initialized)
305     {
306         try
307         {
308             auto version = getActiveSoftwareVersionInfo();
309             r = convert_version(version.c_str(), &rev);
310         }
311         catch (const std::exception& e)
312         {
313             log<level::ERR>(e.what());
314         }
315 
316         if (r >= 0)
317         {
318             // bit7 identifies if the device is available
319             // 0=normal operation
320             // 1=device firmware, SDR update,
321             // or self-initialization in progress.
322             // The availability may change in run time, so mask here
323             // and initialize later.
324             dev_id.fw[0] = rev.major & ipmiDevIdFw1Mask;
325 
326             rev.minor = (rev.minor > 99 ? 99 : rev.minor);
327             dev_id.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
328             std::memcpy(&dev_id.aux, rev.d, 4);
329         }
330 
331         // IPMI Spec version 2.0
332         dev_id.ipmi_ver = 2;
333 
334         std::ifstream dev_id_file(filename);
335         if (dev_id_file.is_open())
336         {
337             auto data = nlohmann::json::parse(dev_id_file, nullptr, false);
338             if (!data.is_discarded())
339             {
340                 dev_id.id = data.value("id", 0);
341                 dev_id.revision = data.value("revision", 0);
342                 dev_id.addn_dev_support = data.value("addn_dev_support", 0);
343                 dev_id.manuf_id[2] = data.value("manuf_id", 0) >> 16;
344                 dev_id.manuf_id[1] = data.value("manuf_id", 0) >> 8;
345                 dev_id.manuf_id[0] = data.value("manuf_id", 0);
346                 dev_id.prod_id[1] = data.value("prod_id", 0) >> 8;
347                 dev_id.prod_id[0] = data.value("prod_id", 0);
348                 dev_id.aux[3] = data.value("aux", 0);
349                 dev_id.aux[2] = data.value("aux", 0) >> 8;
350                 dev_id.aux[1] = data.value("aux", 0) >> 16;
351                 dev_id.aux[0] = data.value("aux", 0) >> 24;
352 
353                 // Don't read the file every time if successful
354                 dev_id_initialized = true;
355             }
356             else
357             {
358                 log<level::ERR>("Device ID JSON parser failure");
359                 rc = IPMI_CC_UNSPECIFIED_ERROR;
360             }
361         }
362         else
363         {
364             log<level::ERR>("Device ID file not found");
365             rc = IPMI_CC_UNSPECIFIED_ERROR;
366         }
367     }
368 
369     // Set availability to the actual current BMC state
370     dev_id.fw[0] &= ipmiDevIdFw1Mask;
371     if (!getCurrentBmcState())
372     {
373         dev_id.fw[0] |= (1 << ipmiDevIdStateShift);
374     }
375 
376     // Pack the actual response
377     std::memcpy(response, &dev_id, *data_len);
378 
379     return rc;
380 }
381 
382 ipmi_ret_t ipmi_app_get_self_test_results(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
383                                           ipmi_request_t request,
384                                           ipmi_response_t response,
385                                           ipmi_data_len_t data_len,
386                                           ipmi_context_t context)
387 {
388     ipmi_ret_t rc = IPMI_CC_OK;
389 
390     // Byte 2:
391     //  55h - No error.
392     //  56h - Self Test function not implemented in this controller.
393     //  57h - Corrupted or inaccesssible data or devices.
394     //  58h - Fatal hardware error.
395     //  FFh - reserved.
396     //  all other: Device-specific 'internal failure'.
397     //  Byte 3:
398     //      For byte 2 = 55h, 56h, FFh:     00h
399     //      For byte 2 = 58h, all other:    Device-specific
400     //      For byte 2 = 57h:   self-test error bitfield.
401     //      Note: returning 57h does not imply that all test were run.
402     //      [7] 1b = Cannot access SEL device.
403     //      [6] 1b = Cannot access SDR Repository.
404     //      [5] 1b = Cannot access BMC FRU device.
405     //      [4] 1b = IPMB signal lines do not respond.
406     //      [3] 1b = SDR Repository empty.
407     //      [2] 1b = Internal Use Area of BMC FRU corrupted.
408     //      [1] 1b = controller update 'boot block' firmware corrupted.
409     //      [0] 1b = controller operational firmware corrupted.
410 
411     char selftestresults[2] = {0};
412 
413     *data_len = 2;
414 
415     selftestresults[0] = 0x56;
416     selftestresults[1] = 0;
417 
418     std::memcpy(response, selftestresults, *data_len);
419 
420     return rc;
421 }
422 
423 ipmi_ret_t ipmi_app_get_device_guid(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
424                                     ipmi_request_t request,
425                                     ipmi_response_t response,
426                                     ipmi_data_len_t data_len,
427                                     ipmi_context_t context)
428 {
429     const char* objname = "/org/openbmc/control/chassis0";
430     const char* iface = "org.freedesktop.DBus.Properties";
431     const char* chassis_iface = "org.openbmc.control.Chassis";
432     sd_bus_message* reply = NULL;
433     sd_bus_error error = SD_BUS_ERROR_NULL;
434     int r = 0;
435     char* uuid = NULL;
436     char* busname = NULL;
437 
438     // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
439     // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte
440     // order
441     // Ex: 0x2332fc2c40e66298e511f2782395a361
442 
443     const int resp_size = 16;     // Response is 16 hex bytes per IPMI Spec
444     uint8_t resp_uuid[resp_size]; // Array to hold the formatted response
445     // Point resp end of array to save in reverse order
446     int resp_loc = resp_size - 1;
447     int i = 0;
448     char* tokptr = NULL;
449     char* id_octet = NULL;
450     size_t total_uuid_size = 0;
451     // 1 byte of resp is built from 2 chars of uuid.
452     constexpr size_t max_uuid_size = 2 * resp_size;
453 
454     // Status code.
455     ipmi_ret_t rc = IPMI_CC_OK;
456     *data_len = 0;
457 
458     // Call Get properties method with the interface and property name
459     r = mapper_get_service(bus, objname, &busname);
460     if (r < 0)
461     {
462         log<level::ERR>("Failed to get bus name", entry("BUS=%s", objname),
463                         entry("ERRNO=0x%X", -r));
464         goto finish;
465     }
466     r = sd_bus_call_method(bus, busname, objname, iface, "Get", &error, &reply,
467                            "ss", chassis_iface, "uuid");
468     if (r < 0)
469     {
470         log<level::ERR>("Failed to call Get Method", entry("ERRNO=0x%X", -r));
471         rc = IPMI_CC_UNSPECIFIED_ERROR;
472         goto finish;
473     }
474 
475     r = sd_bus_message_read(reply, "v", "s", &uuid);
476     if (r < 0 || uuid == NULL)
477     {
478         log<level::ERR>("Failed to get a response", entry("ERRNO=0x%X", -r));
479         rc = IPMI_CC_RESPONSE_ERROR;
480         goto finish;
481     }
482 
483     // Traverse the UUID
484     // Get the UUID octects separated by dash
485     id_octet = strtok_r(uuid, "-", &tokptr);
486 
487     if (id_octet == NULL)
488     {
489         // Error
490         log<level::ERR>("Unexpected UUID format", entry("UUID=%s", uuid));
491         rc = IPMI_CC_RESPONSE_ERROR;
492         goto finish;
493     }
494 
495     while (id_octet != NULL)
496     {
497         // Calculate the octet string size since it varies
498         // Divide it by 2 for the array size since 1 byte is built from 2 chars
499         int tmp_size = strlen(id_octet) / 2;
500 
501         // Check if total UUID size has been exceeded
502         if ((total_uuid_size += strlen(id_octet)) > max_uuid_size)
503         {
504             // Error - UUID too long to store
505             log<level::ERR>("UUID too long", entry("UUID=%s", uuid));
506             rc = IPMI_CC_RESPONSE_ERROR;
507             goto finish;
508         }
509 
510         for (i = 0; i < tmp_size; i++)
511         {
512             // Holder of the 2 chars that will become a byte
513             char tmp_array[3] = {0};
514             strncpy(tmp_array, id_octet, 2); // 2 chars at a time
515 
516             int resp_byte = strtoul(tmp_array, NULL, 16); // Convert to hex byte
517             // Copy end to first
518             std::memcpy((void*)&resp_uuid[resp_loc], &resp_byte, 1);
519             resp_loc--;
520             id_octet += 2; // Finished with the 2 chars, advance
521         }
522         id_octet = strtok_r(NULL, "-", &tokptr); // Get next octet
523     }
524 
525     // Data length
526     *data_len = resp_size;
527 
528     // Pack the actual response
529     std::memcpy(response, &resp_uuid, *data_len);
530 
531 finish:
532     sd_bus_error_free(&error);
533     reply = sd_bus_message_unref(reply);
534     free(busname);
535 
536     return rc;
537 }
538 
539 ipmi_ret_t ipmi_app_get_bt_capabilities(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
540                                         ipmi_request_t request,
541                                         ipmi_response_t response,
542                                         ipmi_data_len_t data_len,
543                                         ipmi_context_t context)
544 {
545 
546     // Status code.
547     ipmi_ret_t rc = IPMI_CC_OK;
548 
549     // Per IPMI 2.0 spec, the input and output buffer size must be the max
550     // buffer size minus one byte to allocate space for the length byte.
551     uint8_t str[] = {0x01, MAX_IPMI_BUFFER - 1, MAX_IPMI_BUFFER - 1, 0x0A,
552                      0x01};
553 
554     // Data length
555     *data_len = sizeof(str);
556 
557     // Pack the actual response
558     std::memcpy(response, &str, *data_len);
559 
560     return rc;
561 }
562 
563 ipmi_ret_t ipmi_app_wildcard_handler(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
564                                      ipmi_request_t request,
565                                      ipmi_response_t response,
566                                      ipmi_data_len_t data_len,
567                                      ipmi_context_t context)
568 {
569     // Status code.
570     ipmi_ret_t rc = IPMI_CC_INVALID;
571 
572     *data_len = strlen("THIS IS WILDCARD");
573 
574     // Now pack actual response
575     std::memcpy(response, "THIS IS WILDCARD", *data_len);
576 
577     return rc;
578 }
579 
580 ipmi_ret_t ipmi_app_get_sys_guid(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
581                                  ipmi_request_t request,
582                                  ipmi_response_t response,
583                                  ipmi_data_len_t data_len,
584                                  ipmi_context_t context)
585 
586 {
587     ipmi_ret_t rc = IPMI_CC_OK;
588     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
589 
590     try
591     {
592         // Get the Inventory object implementing BMC interface
593         ipmi::DbusObjectInfo bmcObject =
594             ipmi::getDbusObject(bus, bmc_interface);
595 
596         // Read UUID property value from bmcObject
597         // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
598         auto variant =
599             ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
600                                   bmc_guid_interface, bmc_guid_property);
601         std::string guidProp = variant.get<std::string>();
602 
603         // Erase "-" characters from the property value
604         guidProp.erase(std::remove(guidProp.begin(), guidProp.end(), '-'),
605                        guidProp.end());
606 
607         auto guidPropLen = guidProp.length();
608         // Validate UUID data
609         // Divide by 2 as 1 byte is built from 2 chars
610         if ((guidPropLen <= 0) || ((guidPropLen / 2) != bmc_guid_len))
611 
612         {
613             log<level::ERR>("Invalid UUID property value",
614                             entry("UUID_LENGTH=%d", guidPropLen));
615             return IPMI_CC_RESPONSE_ERROR;
616         }
617 
618         // Convert data in RFC4122(MSB) format to LSB format
619         // Get 2 characters at a time as 1 byte is built from 2 chars and
620         // convert to hex byte
621         // TODO: Data printed for GUID command is not as per the
622         // GUID format defined in IPMI specification 2.0 section 20.8
623         // Ticket raised: https://sourceforge.net/p/ipmitool/bugs/501/
624         uint8_t respGuid[bmc_guid_len];
625         for (size_t i = 0, respLoc = (bmc_guid_len - 1);
626              i < guidPropLen && respLoc >= 0; i += 2, respLoc--)
627         {
628             auto value = static_cast<uint8_t>(
629                 std::stoi(guidProp.substr(i, 2).c_str(), NULL, 16));
630             respGuid[respLoc] = value;
631         }
632 
633         *data_len = bmc_guid_len;
634         std::memcpy(response, &respGuid, bmc_guid_len);
635     }
636     catch (const InternalFailure& e)
637     {
638         log<level::ERR>("Failed in reading BMC UUID property",
639                         entry("INTERFACE=%s", bmc_interface),
640                         entry("PROPERTY_INTERFACE=%s", bmc_guid_interface),
641                         entry("PROPERTY=%s", bmc_guid_property));
642         return IPMI_CC_UNSPECIFIED_ERROR;
643     }
644     return rc;
645 }
646 
647 static std::unique_ptr<SysInfoParamStore> sysInfoParamStore;
648 
649 static std::string sysInfoReadSystemName()
650 {
651     // Use the BMC hostname as the "System Name."
652     char hostname[HOST_NAME_MAX + 1] = {};
653     if (gethostname(hostname, HOST_NAME_MAX) != 0)
654     {
655         perror("System info parameter: system name");
656     }
657     return hostname;
658 }
659 
660 struct IpmiSysInfoResp
661 {
662     uint8_t paramRevision;
663     uint8_t setSelector;
664     union
665     {
666         struct
667         {
668             uint8_t encoding;
669             uint8_t stringLen;
670             uint8_t stringData0[14];
671         } __attribute__((packed));
672         uint8_t stringDataN[16];
673         uint8_t byteData;
674     };
675 } __attribute__((packed));
676 
677 /**
678  * Split a string into (up to) 16-byte chunks as expected in response for get
679  * system info parameter.
680  *
681  * @param[in] fullString: Input string to be split
682  * @param[in] chunkIndex: Index of the chunk to be written out
683  * @param[in,out] chunk: Output data buffer; must have 14 byte capacity if
684  *          chunk_index = 0 and 16-byte capacity otherwise
685  * @return the number of bytes written into the output buffer, or -EINVAL for
686  * invalid arguments.
687  */
688 static int splitStringParam(const std::string& fullString, int chunkIndex,
689                             uint8_t* chunk)
690 {
691     constexpr int maxChunk = 255;
692     constexpr int smallChunk = 14;
693     constexpr int chunkSize = 16;
694     if (chunkIndex > maxChunk || chunk == nullptr)
695     {
696         return -EINVAL;
697     }
698     try
699     {
700         std::string output;
701         if (chunkIndex == 0)
702         {
703             // Output must have 14 byte capacity.
704             output = fullString.substr(0, smallChunk);
705         }
706         else
707         {
708             // Output must have 16 byte capacity.
709             output = fullString.substr((chunkIndex * chunkSize) - 2, chunkSize);
710         }
711 
712         std::memcpy(chunk, output.c_str(), output.length());
713         return output.length();
714     }
715     catch (const std::out_of_range& e)
716     {
717         // The position was beyond the end.
718         return -EINVAL;
719     }
720 }
721 
722 /**
723  * Packs the Get Sys Info Request Item into the response.
724  *
725  * @param[in] paramString - the parameter.
726  * @param[in] setSelector - the selector
727  * @param[in,out] resp - the System info response.
728  * @return The number of bytes packed or failure from splitStringParam().
729  */
730 static int packGetSysInfoResp(const std::string& paramString,
731                               uint8_t setSelector, IpmiSysInfoResp* resp)
732 {
733     uint8_t* dataBuffer = resp->stringDataN;
734     resp->setSelector = setSelector;
735     if (resp->setSelector == 0) // First chunk has only 14 bytes.
736     {
737         resp->encoding = 0;
738         resp->stringLen = paramString.length();
739         dataBuffer = resp->stringData0;
740     }
741     return splitStringParam(paramString, resp->setSelector, dataBuffer);
742 }
743 
744 ipmi_ret_t ipmi_app_get_system_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
745                                     ipmi_request_t request,
746                                     ipmi_response_t response,
747                                     ipmi_data_len_t dataLen,
748                                     ipmi_context_t context)
749 {
750     IpmiSysInfoResp resp = {};
751     size_t respLen = 0;
752     uint8_t* const reqData = static_cast<uint8_t*>(request);
753     std::string paramString;
754     bool found;
755     std::tuple<bool, std::string> ret;
756     constexpr int minRequestSize = 4;
757     constexpr int paramSelector = 1;
758     constexpr uint8_t revisionOnly = 0x80;
759     const uint8_t paramRequested = reqData[paramSelector];
760     int rc;
761 
762     if (*dataLen < minRequestSize)
763     {
764         return IPMI_CC_REQ_DATA_LEN_INVALID;
765     }
766 
767     *dataLen = 0; // default to 0.
768 
769     // Parameters revision as of IPMI spec v2.0 rev. 1.1 (Feb 11, 2014 E6)
770     resp.paramRevision = 0x11;
771     if (reqData[0] & revisionOnly) // Get parameter revision only
772     {
773         respLen = 1;
774         goto writeResponse;
775     }
776 
777     // The "Set In Progress" parameter can be used for rollback of parameter
778     // data and is not implemented.
779     if (paramRequested == 0)
780     {
781         resp.byteData = 0;
782         respLen = 2;
783         goto writeResponse;
784     }
785 
786     if (sysInfoParamStore == nullptr)
787     {
788         sysInfoParamStore = std::make_unique<SysInfoParamStore>();
789         sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
790                                   sysInfoReadSystemName);
791     }
792 
793     // Parameters other than Set In Progress are assumed to be strings.
794     ret = sysInfoParamStore->lookup(paramRequested);
795     found = std::get<0>(ret);
796     paramString = std::get<1>(ret);
797     if (!found)
798     {
799         return IPMI_CC_SYSTEM_INFO_PARAMETER_NOT_SUPPORTED;
800     }
801     // TODO: Cache each parameter across multiple calls, until the whole string
802     // has been read out. Otherwise, it's possible for a parameter to change
803     // between requests for its chunks, returning chunks incoherent with each
804     // other. For now, the parameter store is simply required to have only
805     // idempotent callbacks.
806     rc = packGetSysInfoResp(paramString, reqData[2], &resp);
807     if (rc == -EINVAL)
808     {
809         return IPMI_CC_RESPONSE_ERROR;
810     }
811 
812     respLen = sizeof(resp); // Write entire string data chunk in response.
813 
814 writeResponse:
815     std::memcpy(response, &resp, sizeof(resp));
816     *dataLen = respLen;
817     return IPMI_CC_OK;
818 }
819 
820 void register_netfn_app_functions()
821 {
822     // <Get BT Interface Capabilities>
823     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CAP_BIT, NULL,
824                            ipmi_app_get_bt_capabilities, PRIVILEGE_USER);
825 
826     // <Wildcard Command>
827     ipmi_register_callback(NETFUN_APP, IPMI_CMD_WILDCARD, NULL,
828                            ipmi_app_wildcard_handler, PRIVILEGE_USER);
829 
830     // <Reset Watchdog Timer>
831     ipmi_register_callback(NETFUN_APP, IPMI_CMD_RESET_WD, NULL,
832                            ipmi_app_watchdog_reset, PRIVILEGE_OPERATOR);
833 
834     // <Set Watchdog Timer>
835     ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_WD, NULL,
836                            ipmi_app_watchdog_set, PRIVILEGE_OPERATOR);
837 
838     // <Get Watchdog Timer>
839     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_WD, NULL,
840                            ipmi_app_watchdog_get, PRIVILEGE_OPERATOR);
841 
842     // <Get Device ID>
843     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_DEVICE_ID, NULL,
844                            ipmi_app_get_device_id, PRIVILEGE_USER);
845 
846     // <Get Self Test Results>
847     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SELF_TEST_RESULTS, NULL,
848                            ipmi_app_get_self_test_results, PRIVILEGE_USER);
849 
850     // <Get Device GUID>
851     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_DEVICE_GUID, NULL,
852                            ipmi_app_get_device_guid, PRIVILEGE_USER);
853 
854     // <Set ACPI Power State>
855     ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_ACPI, NULL,
856                            ipmi_app_set_acpi_power_state, PRIVILEGE_ADMIN);
857 
858     // <Get Channel Access>
859     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHANNEL_ACCESS, NULL,
860                            ipmi_get_channel_access, PRIVILEGE_USER);
861 
862     // <Get Channel Info Command>
863     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHAN_INFO, NULL,
864                            ipmi_app_channel_info, PRIVILEGE_USER);
865 
866     // <Get System GUID Command>
867     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYS_GUID, NULL,
868                            ipmi_app_get_sys_guid, PRIVILEGE_USER);
869 
870     // <Get Channel Cipher Suites Command>
871     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHAN_CIPHER_SUITES, NULL,
872                            getChannelCipherSuites, PRIVILEGE_CALLBACK);
873 
874     // <Set Channel Access Command>
875     ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_CHAN_ACCESS, NULL,
876                            ipmi_set_channel_access, PRIVILEGE_ADMIN);
877 
878     // <Get System Info Command>
879     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYSTEM_INFO, NULL,
880                            ipmi_app_get_system_info, PRIVILEGE_USER);
881     return;
882 }
883