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