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