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