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