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