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 std::optional<InputArgsType> inputArgs; 183 if constexpr (std::tuple_size<InputArgsType>::value > 0) 184 { 185 if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>, 186 boost::asio::yield_context>::value) 187 { 188 inputArgs.emplace(std::tuple_cat( 189 std::forward_as_tuple(*(request->ctx->yield)), 190 std::move(unpackArgs))); 191 } 192 else if constexpr (std::is_same< 193 std::tuple_element_t<0, InputArgsType>, 194 ipmi::Context::ptr>::value) 195 { 196 inputArgs.emplace( 197 std::tuple_cat(std::forward_as_tuple(request->ctx), 198 std::move(unpackArgs))); 199 } 200 else if constexpr (std::is_same< 201 std::tuple_element_t<0, InputArgsType>, 202 ipmi::message::Request::ptr>::value) 203 { 204 inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request), 205 std::move(unpackArgs))); 206 } 207 else 208 { 209 // no special parameters were requested (but others were) 210 inputArgs.emplace(std::move(unpackArgs)); 211 } 212 } 213 else 214 { 215 // no parameters were requested 216 inputArgs = std::move(unpackArgs); 217 } 218 ResultType result; 219 try 220 { 221 // execute the registered callback function and get the 222 // ipmi::RspType<> 223 result = std::apply(handler_, *inputArgs); 224 } 225 catch (const std::exception& e) 226 { 227 phosphor::logging::log<phosphor::logging::level::ERR>( 228 "Handler failed to catch exception", 229 phosphor::logging::entry("EXCEPTION=%s", e.what()), 230 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 231 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 232 return errorResponse(request, ccUnspecifiedError); 233 } 234 catch (...) 235 { 236 std::exception_ptr eptr; 237 try 238 { 239 eptr = std::current_exception(); 240 if (eptr) 241 { 242 std::rethrow_exception(eptr); 243 } 244 } 245 catch (const std::exception& e) 246 { 247 phosphor::logging::log<phosphor::logging::level::ERR>( 248 "Handler failed to catch exception", 249 phosphor::logging::entry("EXCEPTION=%s", e.what()), 250 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 251 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 252 return errorResponse(request, ccUnspecifiedError); 253 } 254 } 255 256 response->cc = std::get<0>(result); 257 auto payload = std::get<1>(result); 258 // check for optional payload 259 if (payload) 260 { 261 response->pack(*payload); 262 } 263 return response; 264 } 265 }; 266 267 #ifdef ALLOW_DEPRECATED_API 268 /** 269 * @brief Legacy IPMI handler class 270 * 271 * Legacy IPMI handlers will resolve into this class, which will behave the same 272 * way as the legacy IPMI queue, passing in a big buffer for the request and a 273 * big buffer for the response. 274 * 275 * As soon as all the handlers have been rewritten, this class will be marked as 276 * deprecated and eventually removed. 277 */ 278 template <> 279 class IpmiHandler<ipmid_callback_t> final : public HandlerBase 280 { 281 public: 282 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) : 283 handler_(handler), handlerCtx(ctx) 284 { 285 } 286 287 private: 288 ipmid_callback_t handler_; 289 void* handlerCtx; 290 291 /** @brief call the registered handler with the request 292 * 293 * This is called from the running queue context after it has already 294 * created a request object that contains all the information required to 295 * execute the ipmi command. This function will return the response object 296 * pointer that owns the response object that will ultimately get sent back 297 * to the requester. 298 * 299 * Because this is the legacy variety of IPMI handler, this function does 300 * not really have to do much other than pass the payload to the callback 301 * and return response to the caller. 302 * 303 * @param request a shared_ptr to a Request object 304 * 305 * @return a shared_ptr to a Response object 306 */ 307 message::Response::ptr 308 executeCallback(message::Request::ptr request) override 309 { 310 message::Response::ptr response = request->makeResponse(); 311 size_t len = request->payload.size(); 312 // allocate a big response buffer here 313 response->payload.resize( 314 getChannelMaxTransferSize(request->ctx->channel)); 315 316 Cc ccRet{ccSuccess}; 317 try 318 { 319 ccRet = handler_(request->ctx->netFn, request->ctx->cmd, 320 request->payload.data(), response->payload.data(), 321 &len, handlerCtx); 322 } 323 catch (const std::exception& e) 324 { 325 phosphor::logging::log<phosphor::logging::level::ERR>( 326 "Legacy Handler failed to catch exception", 327 phosphor::logging::entry("EXCEPTION=%s", e.what()), 328 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 329 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 330 return errorResponse(request, ccUnspecifiedError); 331 } 332 catch (...) 333 { 334 std::exception_ptr eptr; 335 try 336 { 337 eptr = std::current_exception(); 338 if (eptr) 339 { 340 std::rethrow_exception(eptr); 341 } 342 } 343 catch (const std::exception& e) 344 { 345 phosphor::logging::log<phosphor::logging::level::ERR>( 346 "Handler failed to catch exception", 347 phosphor::logging::entry("EXCEPTION=%s", e.what()), 348 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 349 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 350 return errorResponse(request, ccUnspecifiedError); 351 } 352 } 353 response->cc = ccRet; 354 response->payload.resize(len); 355 return response; 356 } 357 }; 358 359 /** 360 * @brief Legacy IPMI OEM handler class 361 * 362 * Legacy IPMI OEM handlers will resolve into this class, which will behave the 363 * same way as the legacy IPMI queue, passing in a big buffer for the request 364 * and a big buffer for the response. 365 * 366 * As soon as all the handlers have been rewritten, this class will be marked as 367 * deprecated and eventually removed. 368 */ 369 template <> 370 class IpmiHandler<oem::Handler> final : public HandlerBase 371 { 372 public: 373 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) 374 { 375 } 376 377 private: 378 oem::Handler handler_; 379 380 /** @brief call the registered handler with the request 381 * 382 * This is called from the running queue context after it has already 383 * created a request object that contains all the information required to 384 * execute the ipmi command. This function will return the response object 385 * pointer that owns the response object that will ultimately get sent back 386 * to the requester. 387 * 388 * Because this is the legacy variety of IPMI handler, this function does 389 * not really have to do much other than pass the payload to the callback 390 * and return response to the caller. 391 * 392 * @param request a shared_ptr to a Request object 393 * 394 * @return a shared_ptr to a Response object 395 */ 396 message::Response::ptr 397 executeCallback(message::Request::ptr request) override 398 { 399 message::Response::ptr response = request->makeResponse(); 400 size_t len = request->payload.size(); 401 // allocate a big response buffer here 402 response->payload.resize( 403 getChannelMaxTransferSize(request->ctx->channel)); 404 405 Cc ccRet{ccSuccess}; 406 try 407 { 408 ccRet = handler_(request->ctx->cmd, request->payload.data(), 409 response->payload.data(), &len); 410 } 411 catch (const std::exception& e) 412 { 413 phosphor::logging::log<phosphor::logging::level::ERR>( 414 "Legacy OEM Handler failed to catch exception", 415 phosphor::logging::entry("EXCEPTION=%s", e.what()), 416 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 417 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 418 return errorResponse(request, ccUnspecifiedError); 419 } 420 catch (...) 421 { 422 std::exception_ptr eptr; 423 try 424 { 425 eptr = std::current_exception(); 426 if (eptr) 427 { 428 std::rethrow_exception(eptr); 429 } 430 } 431 catch (const std::exception& e) 432 { 433 phosphor::logging::log<phosphor::logging::level::ERR>( 434 "Handler failed to catch exception", 435 phosphor::logging::entry("EXCEPTION=%s", e.what()), 436 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 437 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 438 return errorResponse(request, ccUnspecifiedError); 439 } 440 } 441 response->cc = ccRet; 442 response->payload.resize(len); 443 return response; 444 } 445 }; 446 447 /** 448 * @brief create a legacy IPMI handler class and return a shared_ptr 449 * 450 * The queue uses a map of pointers to do the lookup. This function returns the 451 * shared_ptr that owns the Handler object. 452 * 453 * This is called internally via the ipmi_register_callback function. 454 * 455 * @param handler the function pointer to the callback 456 * 457 * @return A shared_ptr to the created handler object 458 */ 459 inline auto makeLegacyHandler(const ipmid_callback_t& handler, 460 void* ctx = nullptr) 461 { 462 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx)); 463 return ptr; 464 } 465 466 /** 467 * @brief create a legacy IPMI OEM handler class and return a shared_ptr 468 * 469 * The queue uses a map of pointers to do the lookup. This function returns the 470 * shared_ptr that owns the Handler object. 471 * 472 * This is called internally via the Router::registerHandler method. 473 * 474 * @param handler the function pointer to the callback 475 * 476 * @return A shared_ptr to the created handler object 477 */ 478 inline auto makeLegacyHandler(oem::Handler&& handler) 479 { 480 HandlerBase::ptr ptr( 481 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler))); 482 return ptr; 483 } 484 #endif // ALLOW_DEPRECATED_API 485 486 /** 487 * @brief create an IPMI handler class and return a shared_ptr 488 * 489 * The queue uses a map of pointers to do the lookup. This function returns the 490 * shared_ptr that owns the Handler object. 491 * 492 * This is called internally via the ipmi::registerHandler function. 493 * 494 * @param handler the function pointer to the callback 495 * 496 * @return A shared_ptr to the created handler object 497 */ 498 template <typename Handler> 499 inline auto makeHandler(Handler&& handler) 500 { 501 HandlerBase::ptr ptr( 502 new IpmiHandler<Handler>(std::forward<Handler>(handler))); 503 return ptr; 504 } 505 506 namespace impl 507 { 508 509 // IPMI command handler registration implementation 510 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 511 ::ipmi::HandlerBase::ptr handler); 512 bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 513 ::ipmi::HandlerBase::ptr handler); 514 bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 515 ::ipmi::HandlerBase::ptr handler); 516 517 } // namespace impl 518 519 /** 520 * @brief main IPMI handler registration function 521 * 522 * This function should be used to register all new-style IPMI handler 523 * functions. This function just passes the callback to makeHandler, which 524 * creates a new wrapper object that will automatically extract the appropriate 525 * parameters for the callback function as well as pack up the response. 526 * 527 * @param prio - priority at which to register; see api.hpp 528 * @param netFn - the IPMI net function number to register 529 * @param cmd - the IPMI command number to register 530 * @param priv - the IPMI user privilige required for this command 531 * @param handler - the callback function that will handle this request 532 * 533 * @return bool - success of registering the handler 534 */ 535 template <typename Handler> 536 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 537 Handler&& handler) 538 { 539 auto h = ipmi::makeHandler(std::forward<Handler>(handler)); 540 return impl::registerHandler(prio, netFn, cmd, priv, h); 541 } 542 543 /** 544 * @brief register a IPMI OEM group handler 545 * 546 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch): 547 * The first data byte position in requests and responses under this network 548 * function identifies the defining body that specifies command functionality. 549 * Software assumes that the command and completion code field positions will 550 * hold command and completion code values. 551 * 552 * The following values are used to identify the defining body: 553 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com) 554 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org) 555 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org) 556 * 03h VITA Standards Organization (VSO) (www.vita.com) 557 * DCh DCMI Specifications (www.intel.com/go/dcmi) 558 * all other Reserved 559 * 560 * When this network function is used, the ID for the defining body occupies 561 * the first data byte in a request, and the second data byte (following the 562 * completion code) in a response. 563 * 564 * @tparam Handler - implicitly specified callback function type 565 * @param prio - priority at which to register; see api.hpp 566 * @param netFn - the IPMI net function number to register 567 * @param cmd - the IPMI command number to register 568 * @param priv - the IPMI user privilige required for this command 569 * @param handler - the callback function that will handle this request 570 * 571 * @return bool - success of registering the handler 572 * 573 */ 574 template <typename Handler> 575 void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 576 Handler&& handler) 577 { 578 auto h = ipmi::makeHandler(handler); 579 impl::registerGroupHandler(prio, group, cmd, priv, h); 580 } 581 582 /** 583 * @brief register a IPMI OEM IANA handler 584 * 585 * From IPMI spec Network Function Codes Table (Row 2Eh): 586 * The first three data bytes of requests and responses under this network 587 * function explicitly identify the OEM or non-IPMI group that specifies the 588 * command functionality. While the OEM or non-IPMI group defines the 589 * functional semantics for the cmd and remaining data fields, the cmd field 590 * is required to hold the same value in requests and responses for a given 591 * operation in order to be supported under the IPMI message handling and 592 * transport mechanisms. 593 * 594 * When this network function is used, the IANA Enterprise Number for the 595 * defining body occupies the first three data bytes in a request, and the 596 * first three data bytes following the completion code position in a 597 * response. 598 * 599 * @tparam Handler - implicitly specified callback function type 600 * @param prio - priority at which to register; see api.hpp 601 * @param netFn - the IPMI net function number to register 602 * @param cmd - the IPMI command number to register 603 * @param priv - the IPMI user privilige required for this command 604 * @param handler - the callback function that will handle this request 605 * 606 * @return bool - success of registering the handler 607 * 608 */ 609 template <typename Handler> 610 void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 611 Handler&& handler) 612 { 613 auto h = ipmi::makeHandler(handler); 614 impl::registerOemHandler(prio, iana, cmd, priv, h); 615 } 616 617 } // namespace ipmi 618 619 #ifdef ALLOW_DEPRECATED_API 620 /** 621 * @brief legacy IPMI handler registration function 622 * 623 * This function should be used to register all legacy IPMI handler 624 * functions. This function just behaves just as the legacy registration 625 * mechanism did, silently replacing any existing handler with a new one. 626 * 627 * @param netFn - the IPMI net function number to register 628 * @param cmd - the IPMI command number to register 629 * @param context - ignored 630 * @param handler - the callback function that will handle this request 631 * @param priv - the IPMI user privilige required for this command 632 */ 633 // [[deprecated("Use ipmi::registerHandler() instead")]] 634 void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, 635 ipmi_context_t context, ipmid_callback_t handler, 636 ipmi_cmd_privilege_t priv); 637 638 #endif /* ALLOW_DEPRECATED_API */ 639