1 /** 2 * Copyright © 2018 Intel Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #pragma once 17 #include <cxxabi.h> 18 19 #include <boost/asio/spawn.hpp> 20 #include <boost/callable_traits.hpp> 21 #include <ipmid/api-types.hpp> 22 #include <ipmid/message.hpp> 23 #include <phosphor-logging/log.hpp> 24 #include <user_channel/channel_layer.hpp> 25 26 #include <algorithm> 27 #include <cstdint> 28 #include <exception> 29 #include <memory> 30 #include <optional> 31 #include <stdexcept> 32 #include <tuple> 33 #include <utility> 34 35 #ifdef ALLOW_DEPRECATED_API 36 #include <ipmid/api.h> 37 38 #include <ipmid/oemrouter.hpp> 39 #endif /* ALLOW_DEPRECATED_API */ 40 41 namespace ipmi 42 { 43 44 template <typename... Args> 45 static inline message::Response::ptr 46 errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args) 47 { 48 message::Response::ptr response = request->makeResponse(); 49 response->cc = cc; 50 response->pack(args...); 51 return response; 52 } 53 static inline message::Response::ptr 54 errorResponse(message::Request::ptr request, ipmi::Cc cc) 55 { 56 message::Response::ptr response = request->makeResponse(); 57 response->cc = cc; 58 return response; 59 } 60 61 /** @brief Exception extension that allows setting an IPMI return code */ 62 class HandlerCompletion 63 { 64 public: 65 HandlerCompletion(Cc cc) noexcept : cc(cc) {} 66 67 Cc code() const noexcept 68 { 69 return cc; 70 } 71 72 private: 73 Cc cc; 74 }; 75 76 /** @brief Exception extension that allows setting an IPMI return code and 77 * printing out a logged error */ 78 class HandlerException : public HandlerCompletion, public std::runtime_error 79 { 80 public: 81 HandlerException(Cc cc, const char* what) : 82 HandlerCompletion(cc), std::runtime_error(what) 83 {} 84 HandlerException(Cc cc, const std::string& what) : 85 HandlerException(cc, what.c_str()) 86 {} 87 }; 88 89 static inline const char* currentExceptionType() 90 { 91 int status; 92 return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 93 0, &status); 94 } 95 96 /** 97 * @brief Handler base class for dealing with IPMI request/response 98 * 99 * The subclasses are all templated so they can provide access to any type 100 * of command callback functions. 101 */ 102 class HandlerBase 103 { 104 public: 105 using ptr = std::shared_ptr<HandlerBase>; 106 107 virtual ~HandlerBase() = default; 108 109 /** @brief wrap the call to the registered handler with the request 110 * 111 * This is called from the running queue context after it has already 112 * created a request object that contains all the information required to 113 * execute the ipmi command. This function will return the response object 114 * pointer that owns the response object that will ultimately get sent back 115 * to the requester. 116 * 117 * This is a non-virtual function wrapper to the virtualized executeCallback 118 * function that actually does the work. This is required because of how 119 * templates and virtualization work together. 120 * 121 * @param request a shared_ptr to a Request object 122 * 123 * @return a shared_ptr to a Response object 124 */ 125 message::Response::ptr call(message::Request::ptr request) 126 { 127 return executeCallback(request); 128 } 129 130 private: 131 /** @brief call the registered handler with the request 132 * 133 * This is called from the running queue context after it has already 134 * created a request object that contains all the information required to 135 * execute the ipmi command. This function will return the response object 136 * pointer that owns the response object that will ultimately get sent back 137 * to the requester. 138 * 139 * @param request a shared_ptr to a Request object 140 * 141 * @return a shared_ptr to a Response object 142 */ 143 virtual message::Response::ptr 144 executeCallback(message::Request::ptr request) = 0; 145 }; 146 147 /** 148 * @brief Main IPMI handler class 149 * 150 * New IPMI handlers will resolve into this class, which will read the signature 151 * of the registering function, attempt to extract the appropriate arguments 152 * from a request, pass the arguments to the function, and then pack the 153 * response of the function back into an IPMI response. 154 */ 155 template <typename Handler> 156 class IpmiHandler final : public HandlerBase 157 { 158 public: 159 explicit IpmiHandler(Handler&& handler) : 160 handler_(std::forward<Handler>(handler)) 161 {} 162 163 private: 164 Handler handler_; 165 166 /** @brief call the registered handler with the request 167 * 168 * This is called from the running queue context after it has already 169 * created a request object that contains all the information required to 170 * execute the ipmi command. This function will return the response object 171 * pointer that owns the response object that will ultimately get sent back 172 * to the requester. 173 * 174 * Because this is the new variety of IPMI handler, this is the function 175 * that attempts to extract the requested parameters in order to pass them 176 * onto the callback function and then packages up the response into a plain 177 * old vector to pass back to the caller. 178 * 179 * @param request a shared_ptr to a Request object 180 * 181 * @return a shared_ptr to a Response object 182 */ 183 message::Response::ptr 184 executeCallback(message::Request::ptr request) override 185 { 186 message::Response::ptr response = request->makeResponse(); 187 188 using CallbackSig = boost::callable_traits::args_t<Handler>; 189 using InputArgsType = typename utility::DecayTuple<CallbackSig>::type; 190 using UnpackArgsType = typename utility::StripFirstArgs< 191 utility::NonIpmiArgsCount<InputArgsType>::size(), 192 InputArgsType>::type; 193 using ResultType = boost::callable_traits::return_type_t<Handler>; 194 195 UnpackArgsType unpackArgs; 196 request->payload.trailingOk = false; 197 ipmi::Cc unpackError = request->unpack(unpackArgs); 198 if (unpackError != ipmi::ccSuccess) 199 { 200 response->cc = unpackError; 201 return response; 202 } 203 /* callbacks can contain an optional first argument of one of: 204 * 1) boost::asio::yield_context 205 * 2) ipmi::Context::ptr 206 * 3) ipmi::message::Request::ptr 207 * 208 * If any of those is part of the callback signature as the first 209 * argument, it will automatically get packed into the parameter pack 210 * here. 211 * 212 * One more special optional argument is an ipmi::message::Payload. 213 * This argument can be in any position, though logically it makes the 214 * most sense if it is the last. If this class is included in the 215 * handler signature, it will allow for the handler to unpack optional 216 * parameters. For example, the Set LAN Configuration Parameters 217 * command takes variable length (and type) values for each of the LAN 218 * parameters. This means that the only fixed data is the channel and 219 * parameter selector. All the remaining data can be extracted using 220 * the Payload class and the unpack API available to the Payload class. 221 */ 222 ResultType result; 223 try 224 { 225 std::optional<InputArgsType> inputArgs; 226 if constexpr (std::tuple_size<InputArgsType>::value > 0) 227 { 228 if constexpr (std::is_same< 229 std::tuple_element_t<0, InputArgsType>, 230 boost::asio::yield_context>::value) 231 { 232 inputArgs.emplace(std::tuple_cat( 233 std::forward_as_tuple(request->ctx->yield), 234 std::move(unpackArgs))); 235 } 236 else if constexpr (std::is_same< 237 std::tuple_element_t<0, InputArgsType>, 238 ipmi::Context::ptr>::value) 239 { 240 inputArgs.emplace( 241 std::tuple_cat(std::forward_as_tuple(request->ctx), 242 std::move(unpackArgs))); 243 } 244 else if constexpr (std::is_same< 245 std::tuple_element_t<0, InputArgsType>, 246 ipmi::message::Request::ptr>::value) 247 { 248 inputArgs.emplace(std::tuple_cat( 249 std::forward_as_tuple(request), std::move(unpackArgs))); 250 } 251 else 252 { 253 // no special parameters were requested (but others were) 254 inputArgs.emplace(std::move(unpackArgs)); 255 } 256 } 257 else 258 { 259 // no parameters were requested 260 inputArgs = std::move(unpackArgs); 261 } 262 263 // execute the registered callback function and get the 264 // ipmi::RspType<> 265 result = std::apply(handler_, *inputArgs); 266 } 267 catch (const HandlerException& e) 268 { 269 phosphor::logging::log<phosphor::logging::level::INFO>( 270 "Handler produced exception", 271 phosphor::logging::entry("CC=%x", e.code()), 272 phosphor::logging::entry("EXCEPTION=%s", e.what()), 273 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 274 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 275 return errorResponse(request, e.code()); 276 } 277 catch (const std::exception& e) 278 { 279 phosphor::logging::log<phosphor::logging::level::ERR>( 280 "Handler failed to catch exception", 281 phosphor::logging::entry("EXCEPTION=%s", e.what()), 282 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 283 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 284 return errorResponse(request, ccUnspecifiedError); 285 } 286 catch (const HandlerCompletion& c) 287 { 288 return errorResponse(request, c.code()); 289 } 290 catch (...) 291 { 292 const char* what = currentExceptionType(); 293 phosphor::logging::log<phosphor::logging::level::ERR>( 294 "Handler failed to catch exception", 295 phosphor::logging::entry("EXCEPTION=%s", what), 296 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 297 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 298 return errorResponse(request, ccUnspecifiedError); 299 } 300 301 response->cc = std::get<0>(result); 302 auto payload = std::get<1>(result); 303 // check for optional payload 304 if (payload) 305 { 306 response->pack(*payload); 307 } 308 return response; 309 } 310 }; 311 312 #ifdef ALLOW_DEPRECATED_API 313 static constexpr size_t maxLegacyBufferSize = 64 * 1024; 314 /** 315 * @brief Legacy IPMI handler class 316 * 317 * Legacy IPMI handlers will resolve into this class, which will behave the same 318 * way as the legacy IPMI queue, passing in a big buffer for the request and a 319 * big buffer for the response. 320 * 321 * As soon as all the handlers have been rewritten, this class will be marked as 322 * deprecated and eventually removed. 323 */ 324 template <> 325 class IpmiHandler<ipmid_callback_t> final : public HandlerBase 326 { 327 public: 328 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) : 329 handler_(handler), handlerCtx(ctx) 330 {} 331 332 private: 333 ipmid_callback_t handler_; 334 void* handlerCtx; 335 336 /** @brief call the registered handler with the request 337 * 338 * This is called from the running queue context after it has already 339 * created a request object that contains all the information required to 340 * execute the ipmi command. This function will return the response object 341 * pointer that owns the response object that will ultimately get sent back 342 * to the requester. 343 * 344 * Because this is the legacy variety of IPMI handler, this function does 345 * not really have to do much other than pass the payload to the callback 346 * and return response to the caller. 347 * 348 * @param request a shared_ptr to a Request object 349 * 350 * @return a shared_ptr to a Response object 351 */ 352 message::Response::ptr 353 executeCallback(message::Request::ptr request) override 354 { 355 message::Response::ptr response = request->makeResponse(); 356 // allocate a big response buffer here 357 response->payload.resize(maxLegacyBufferSize); 358 359 size_t len = request->payload.size() - request->payload.rawIndex; 360 Cc ccRet{ccSuccess}; 361 try 362 { 363 ccRet = 364 handler_(request->ctx->netFn, request->ctx->cmd, 365 request->payload.data() + request->payload.rawIndex, 366 response->payload.data(), &len, handlerCtx); 367 } 368 catch (const HandlerException& e) 369 { 370 phosphor::logging::log<phosphor::logging::level::INFO>( 371 "Legacy Handler produced exception", 372 phosphor::logging::entry("CC=%x", e.code()), 373 phosphor::logging::entry("EXCEPTION=%s", e.what()), 374 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 375 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 376 return errorResponse(request, e.code()); 377 } 378 catch (const std::exception& e) 379 { 380 phosphor::logging::log<phosphor::logging::level::ERR>( 381 "Legacy Handler failed to catch exception", 382 phosphor::logging::entry("EXCEPTION=%s", e.what()), 383 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 384 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 385 return errorResponse(request, ccUnspecifiedError); 386 } 387 catch (const HandlerCompletion& c) 388 { 389 return errorResponse(request, c.code()); 390 } 391 catch (...) 392 { 393 const char* what = currentExceptionType(); 394 phosphor::logging::log<phosphor::logging::level::ERR>( 395 "Handler failed to catch exception", 396 phosphor::logging::entry("EXCEPTION=%s", what), 397 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 398 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 399 return errorResponse(request, ccUnspecifiedError); 400 } 401 response->cc = ccRet; 402 response->payload.resize(len); 403 return response; 404 } 405 }; 406 407 /** 408 * @brief Legacy IPMI OEM handler class 409 * 410 * Legacy IPMI OEM handlers will resolve into this class, which will behave the 411 * same way as the legacy IPMI queue, passing in a big buffer for the request 412 * and a big buffer for the response. 413 * 414 * As soon as all the handlers have been rewritten, this class will be marked as 415 * deprecated and eventually removed. 416 */ 417 template <> 418 class IpmiHandler<oem::Handler> final : public HandlerBase 419 { 420 public: 421 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {} 422 423 private: 424 oem::Handler handler_; 425 426 /** @brief call the registered handler with the request 427 * 428 * This is called from the running queue context after it has already 429 * created a request object that contains all the information required to 430 * execute the ipmi command. This function will return the response object 431 * pointer that owns the response object that will ultimately get sent back 432 * to the requester. 433 * 434 * Because this is the legacy variety of IPMI handler, this function does 435 * not really have to do much other than pass the payload to the callback 436 * and return response to the caller. 437 * 438 * @param request a shared_ptr to a Request object 439 * 440 * @return a shared_ptr to a Response object 441 */ 442 message::Response::ptr 443 executeCallback(message::Request::ptr request) override 444 { 445 message::Response::ptr response = request->makeResponse(); 446 // allocate a big response buffer here 447 response->payload.resize(maxLegacyBufferSize); 448 449 size_t len = request->payload.size() - request->payload.rawIndex; 450 Cc ccRet{ccSuccess}; 451 try 452 { 453 ccRet = 454 handler_(request->ctx->cmd, 455 request->payload.data() + request->payload.rawIndex, 456 response->payload.data(), &len); 457 } 458 catch (const HandlerException& e) 459 { 460 phosphor::logging::log<phosphor::logging::level::INFO>( 461 "Legacy OEM Handler produced exception", 462 phosphor::logging::entry("CC=%x", e.code()), 463 phosphor::logging::entry("EXCEPTION=%s", e.what()), 464 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 465 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 466 return errorResponse(request, e.code()); 467 } 468 catch (const std::exception& e) 469 { 470 phosphor::logging::log<phosphor::logging::level::ERR>( 471 "Legacy OEM Handler failed to catch exception", 472 phosphor::logging::entry("EXCEPTION=%s", e.what()), 473 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 474 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 475 return errorResponse(request, ccUnspecifiedError); 476 } 477 catch (const HandlerCompletion& c) 478 { 479 return errorResponse(request, c.code()); 480 } 481 catch (...) 482 { 483 const char* what = currentExceptionType(); 484 phosphor::logging::log<phosphor::logging::level::ERR>( 485 "Handler failed to catch exception", 486 phosphor::logging::entry("EXCEPTION=%s", what), 487 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 488 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 489 return errorResponse(request, ccUnspecifiedError); 490 } 491 response->cc = ccRet; 492 response->payload.resize(len); 493 return response; 494 } 495 }; 496 497 /** 498 * @brief create a legacy IPMI handler class and return a shared_ptr 499 * 500 * The queue uses a map of pointers to do the lookup. This function returns the 501 * shared_ptr that owns the Handler object. 502 * 503 * This is called internally via the ipmi_register_callback function. 504 * 505 * @param handler the function pointer to the callback 506 * 507 * @return A shared_ptr to the created handler object 508 */ 509 inline auto makeLegacyHandler(const ipmid_callback_t& handler, 510 void* ctx = nullptr) 511 { 512 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx)); 513 return ptr; 514 } 515 516 /** 517 * @brief create a legacy IPMI OEM handler class and return a shared_ptr 518 * 519 * The queue uses a map of pointers to do the lookup. This function returns the 520 * shared_ptr that owns the Handler object. 521 * 522 * This is called internally via the Router::registerHandler method. 523 * 524 * @param handler the function pointer to the callback 525 * 526 * @return A shared_ptr to the created handler object 527 */ 528 inline auto makeLegacyHandler(oem::Handler&& handler) 529 { 530 HandlerBase::ptr ptr( 531 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler))); 532 return ptr; 533 } 534 #endif // ALLOW_DEPRECATED_API 535 536 /** 537 * @brief create an IPMI handler class and return a shared_ptr 538 * 539 * The queue uses a map of pointers to do the lookup. This function returns the 540 * shared_ptr that owns the Handler object. 541 * 542 * This is called internally via the ipmi::registerHandler function. 543 * 544 * @param handler the function pointer to the callback 545 * 546 * @return A shared_ptr to the created handler object 547 */ 548 template <typename Handler> 549 inline auto makeHandler(Handler&& handler) 550 { 551 HandlerBase::ptr ptr( 552 new IpmiHandler<Handler>(std::forward<Handler>(handler))); 553 return ptr; 554 } 555 556 namespace impl 557 { 558 559 // IPMI command handler registration implementation 560 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 561 ::ipmi::HandlerBase::ptr handler); 562 bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 563 ::ipmi::HandlerBase::ptr handler); 564 bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 565 ::ipmi::HandlerBase::ptr handler); 566 567 } // namespace impl 568 569 /** 570 * @brief main IPMI handler registration function 571 * 572 * This function should be used to register all new-style IPMI handler 573 * functions. This function just passes the callback to makeHandler, which 574 * creates a new wrapper object that will automatically extract the appropriate 575 * parameters for the callback function as well as pack up the response. 576 * 577 * @param prio - priority at which to register; see api.hpp 578 * @param netFn - the IPMI net function number to register 579 * @param cmd - the IPMI command number to register 580 * @param priv - the IPMI user privilige required for this command 581 * @param handler - the callback function that will handle this request 582 * 583 * @return bool - success of registering the handler 584 */ 585 template <typename Handler> 586 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 587 Handler&& handler) 588 { 589 auto h = ipmi::makeHandler(std::forward<Handler>(handler)); 590 return impl::registerHandler(prio, netFn, cmd, priv, h); 591 } 592 593 /** 594 * @brief register a IPMI OEM group handler 595 * 596 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch): 597 * The first data byte position in requests and responses under this network 598 * function identifies the defining body that specifies command functionality. 599 * Software assumes that the command and completion code field positions will 600 * hold command and completion code values. 601 * 602 * The following values are used to identify the defining body: 603 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com) 604 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org) 605 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org) 606 * 03h VITA Standards Organization (VSO) (www.vita.com) 607 * DCh DCMI Specifications (www.intel.com/go/dcmi) 608 * all other Reserved 609 * 610 * When this network function is used, the ID for the defining body occupies 611 * the first data byte in a request, and the second data byte (following the 612 * completion code) in a response. 613 * 614 * @tparam Handler - implicitly specified callback function type 615 * @param prio - priority at which to register; see api.hpp 616 * @param netFn - the IPMI net function number to register 617 * @param cmd - the IPMI command number to register 618 * @param priv - the IPMI user privilige required for this command 619 * @param handler - the callback function that will handle this request 620 * 621 * @return bool - success of registering the handler 622 * 623 */ 624 template <typename Handler> 625 void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 626 Handler&& handler) 627 { 628 auto h = ipmi::makeHandler(handler); 629 impl::registerGroupHandler(prio, group, cmd, priv, h); 630 } 631 632 /** 633 * @brief register a IPMI OEM IANA handler 634 * 635 * From IPMI spec Network Function Codes Table (Row 2Eh): 636 * The first three data bytes of requests and responses under this network 637 * function explicitly identify the OEM or non-IPMI group that specifies the 638 * command functionality. While the OEM or non-IPMI group defines the 639 * functional semantics for the cmd and remaining data fields, the cmd field 640 * is required to hold the same value in requests and responses for a given 641 * operation in order to be supported under the IPMI message handling and 642 * transport mechanisms. 643 * 644 * When this network function is used, the IANA Enterprise Number for the 645 * defining body occupies the first three data bytes in a request, and the 646 * first three data bytes following the completion code position in a 647 * response. 648 * 649 * @tparam Handler - implicitly specified callback function type 650 * @param prio - priority at which to register; see api.hpp 651 * @param netFn - the IPMI net function number to register 652 * @param cmd - the IPMI command number to register 653 * @param priv - the IPMI user privilige required for this command 654 * @param handler - the callback function that will handle this request 655 * 656 * @return bool - success of registering the handler 657 * 658 */ 659 template <typename Handler> 660 void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 661 Handler&& handler) 662 { 663 auto h = ipmi::makeHandler(handler); 664 impl::registerOemHandler(prio, iana, cmd, priv, h); 665 } 666 667 } // namespace ipmi 668 669 #ifdef ALLOW_DEPRECATED_API 670 /** 671 * @brief legacy IPMI handler registration function 672 * 673 * This function should be used to register all legacy IPMI handler 674 * functions. This function just behaves just as the legacy registration 675 * mechanism did, silently replacing any existing handler with a new one. 676 * 677 * @param netFn - the IPMI net function number to register 678 * @param cmd - the IPMI command number to register 679 * @param context - ignored 680 * @param handler - the callback function that will handle this request 681 * @param priv - the IPMI user privilige required for this command 682 */ 683 // [[deprecated("Use ipmi::registerHandler() instead")]] 684 void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, 685 ipmi_context_t context, ipmid_callback_t handler, 686 ipmi_cmd_privilege_t priv); 687 688 #endif /* ALLOW_DEPRECATED_API */ 689