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