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.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 ipmi::Cc unpackError = request->unpack(unpackArgs); 157 if (unpackError != ipmi::ccSuccess) 158 { 159 response->cc = unpackError; 160 return response; 161 } 162 /* callbacks can contain an optional first argument of one of: 163 * 1) boost::asio::yield_context 164 * 2) ipmi::Context::ptr 165 * 3) ipmi::message::Request::ptr 166 * 167 * If any of those is part of the callback signature as the first 168 * argument, it will automatically get packed into the parameter pack 169 * here. 170 * 171 * One more special optional argument is an ipmi::message::Payload. 172 * This argument can be in any position, though logically it makes the 173 * most sense if it is the last. If this class is included in the 174 * handler signature, it will allow for the handler to unpack optional 175 * parameters. For example, the Set LAN Configuration Parameters 176 * command takes variable length (and type) values for each of the LAN 177 * parameters. This means that the only fixed data is the channel and 178 * parameter selector. All the remaining data can be extracted using 179 * the Payload class and the unpack API available to the Payload class. 180 */ 181 std::optional<InputArgsType> inputArgs; 182 if constexpr (std::tuple_size<InputArgsType>::value > 0) 183 { 184 if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>, 185 boost::asio::yield_context>::value) 186 { 187 inputArgs.emplace(std::tuple_cat( 188 std::forward_as_tuple(*(request->ctx->yield)), 189 std::move(unpackArgs))); 190 } 191 else if constexpr (std::is_same< 192 std::tuple_element_t<0, InputArgsType>, 193 ipmi::Context::ptr>::value) 194 { 195 inputArgs.emplace( 196 std::tuple_cat(std::forward_as_tuple(request->ctx), 197 std::move(unpackArgs))); 198 } 199 else if constexpr (std::is_same< 200 std::tuple_element_t<0, InputArgsType>, 201 ipmi::message::Request::ptr>::value) 202 { 203 inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request), 204 std::move(unpackArgs))); 205 } 206 else 207 { 208 // no special parameters were requested (but others were) 209 inputArgs.emplace(std::move(unpackArgs)); 210 } 211 } 212 else 213 { 214 // no parameters were requested 215 inputArgs = std::move(unpackArgs); 216 } 217 ResultType result; 218 try 219 { 220 // execute the registered callback function and get the 221 // ipmi::RspType<> 222 result = std::apply(handler_, *inputArgs); 223 } 224 catch (const std::exception& e) 225 { 226 phosphor::logging::log<phosphor::logging::level::ERR>( 227 "Handler failed to catch exception", 228 phosphor::logging::entry("EXCEPTION=%s", e.what()), 229 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 230 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 231 return errorResponse(request, ccUnspecifiedError); 232 } 233 catch (...) 234 { 235 std::exception_ptr eptr; 236 try 237 { 238 eptr = std::current_exception(); 239 if (eptr) 240 { 241 std::rethrow_exception(eptr); 242 } 243 } 244 catch (const std::exception& e) 245 { 246 phosphor::logging::log<phosphor::logging::level::ERR>( 247 "Handler failed to catch exception", 248 phosphor::logging::entry("EXCEPTION=%s", e.what()), 249 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 250 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 251 return errorResponse(request, ccUnspecifiedError); 252 } 253 } 254 255 response->cc = std::get<0>(result); 256 auto payload = std::get<1>(result); 257 // check for optional payload 258 if (payload) 259 { 260 response->pack(*payload); 261 } 262 return response; 263 } 264 }; 265 266 #ifdef ALLOW_DEPRECATED_API 267 /** 268 * @brief Legacy IPMI handler class 269 * 270 * Legacy IPMI handlers will resolve into this class, which will behave the same 271 * way as the legacy IPMI queue, passing in a big buffer for the request and a 272 * big buffer for the response. 273 * 274 * As soon as all the handlers have been rewritten, this class will be marked as 275 * deprecated and eventually removed. 276 */ 277 template <> 278 class IpmiHandler<ipmid_callback_t> final : public HandlerBase 279 { 280 public: 281 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) : 282 handler_(handler), handlerCtx(ctx) 283 { 284 } 285 286 private: 287 ipmid_callback_t handler_; 288 void* handlerCtx; 289 290 /** @brief call the registered handler with the request 291 * 292 * This is called from the running queue context after it has already 293 * created a request object that contains all the information required to 294 * execute the ipmi command. This function will return the response object 295 * pointer that owns the response object that will ultimately get sent back 296 * to the requester. 297 * 298 * Because this is the legacy variety of IPMI handler, this function does 299 * not really have to do much other than pass the payload to the callback 300 * and return response to the caller. 301 * 302 * @param request a shared_ptr to a Request object 303 * 304 * @return a shared_ptr to a Response object 305 */ 306 message::Response::ptr 307 executeCallback(message::Request::ptr request) override 308 { 309 message::Response::ptr response = request->makeResponse(); 310 size_t len = request->payload.size(); 311 // allocate a big response buffer here 312 response->payload.resize( 313 getChannelMaxTransferSize(request->ctx->channel)); 314 315 Cc ccRet{ccSuccess}; 316 try 317 { 318 ccRet = handler_(request->ctx->netFn, request->ctx->cmd, 319 request->payload.data(), response->payload.data(), 320 &len, handlerCtx); 321 } 322 catch (const std::exception& e) 323 { 324 phosphor::logging::log<phosphor::logging::level::ERR>( 325 "Legacy Handler failed to catch exception", 326 phosphor::logging::entry("EXCEPTION=%s", e.what()), 327 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 328 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 329 return errorResponse(request, ccUnspecifiedError); 330 } 331 catch (...) 332 { 333 std::exception_ptr eptr; 334 try 335 { 336 eptr = std::current_exception(); 337 if (eptr) 338 { 339 std::rethrow_exception(eptr); 340 } 341 } 342 catch (const std::exception& e) 343 { 344 phosphor::logging::log<phosphor::logging::level::ERR>( 345 "Handler failed to catch exception", 346 phosphor::logging::entry("EXCEPTION=%s", e.what()), 347 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 348 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 349 return errorResponse(request, ccUnspecifiedError); 350 } 351 } 352 response->cc = ccRet; 353 response->payload.resize(len); 354 return response; 355 } 356 }; 357 358 /** 359 * @brief Legacy IPMI OEM handler class 360 * 361 * Legacy IPMI OEM handlers will resolve into this class, which will behave the 362 * same way as the legacy IPMI queue, passing in a big buffer for the request 363 * and a big buffer for the response. 364 * 365 * As soon as all the handlers have been rewritten, this class will be marked as 366 * deprecated and eventually removed. 367 */ 368 template <> 369 class IpmiHandler<oem::Handler> final : public HandlerBase 370 { 371 public: 372 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) 373 { 374 } 375 376 private: 377 oem::Handler handler_; 378 379 /** @brief call the registered handler with the request 380 * 381 * This is called from the running queue context after it has already 382 * created a request object that contains all the information required to 383 * execute the ipmi command. This function will return the response object 384 * pointer that owns the response object that will ultimately get sent back 385 * to the requester. 386 * 387 * Because this is the legacy variety of IPMI handler, this function does 388 * not really have to do much other than pass the payload to the callback 389 * and return response to the caller. 390 * 391 * @param request a shared_ptr to a Request object 392 * 393 * @return a shared_ptr to a Response object 394 */ 395 message::Response::ptr 396 executeCallback(message::Request::ptr request) override 397 { 398 message::Response::ptr response = request->makeResponse(); 399 size_t len = request->payload.size(); 400 // allocate a big response buffer here 401 response->payload.resize( 402 getChannelMaxTransferSize(request->ctx->channel)); 403 404 Cc ccRet{ccSuccess}; 405 try 406 { 407 ccRet = handler_(request->ctx->cmd, request->payload.data(), 408 response->payload.data(), &len); 409 } 410 catch (const std::exception& e) 411 { 412 phosphor::logging::log<phosphor::logging::level::ERR>( 413 "Legacy OEM Handler failed to catch exception", 414 phosphor::logging::entry("EXCEPTION=%s", e.what()), 415 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 416 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 417 return errorResponse(request, ccUnspecifiedError); 418 } 419 catch (...) 420 { 421 std::exception_ptr eptr; 422 try 423 { 424 eptr = std::current_exception(); 425 if (eptr) 426 { 427 std::rethrow_exception(eptr); 428 } 429 } 430 catch (const std::exception& e) 431 { 432 phosphor::logging::log<phosphor::logging::level::ERR>( 433 "Handler failed to catch exception", 434 phosphor::logging::entry("EXCEPTION=%s", e.what()), 435 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 436 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 437 return errorResponse(request, ccUnspecifiedError); 438 } 439 } 440 response->cc = ccRet; 441 response->payload.resize(len); 442 return response; 443 } 444 }; 445 446 /** 447 * @brief create a legacy IPMI handler class and return a shared_ptr 448 * 449 * The queue uses a map of pointers to do the lookup. This function returns the 450 * shared_ptr that owns the Handler object. 451 * 452 * This is called internally via the ipmi_register_callback function. 453 * 454 * @param handler the function pointer to the callback 455 * 456 * @return A shared_ptr to the created handler object 457 */ 458 inline auto makeLegacyHandler(const ipmid_callback_t& handler, 459 void* ctx = nullptr) 460 { 461 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx)); 462 return ptr; 463 } 464 465 /** 466 * @brief create a legacy IPMI OEM handler class and return a shared_ptr 467 * 468 * The queue uses a map of pointers to do the lookup. This function returns the 469 * shared_ptr that owns the Handler object. 470 * 471 * This is called internally via the Router::registerHandler method. 472 * 473 * @param handler the function pointer to the callback 474 * 475 * @return A shared_ptr to the created handler object 476 */ 477 inline auto makeLegacyHandler(oem::Handler&& handler) 478 { 479 HandlerBase::ptr ptr( 480 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler))); 481 return ptr; 482 } 483 #endif // ALLOW_DEPRECATED_API 484 485 /** 486 * @brief create an IPMI handler class and return a shared_ptr 487 * 488 * The queue uses a map of pointers to do the lookup. This function returns the 489 * shared_ptr that owns the Handler object. 490 * 491 * This is called internally via the ipmi::registerHandler function. 492 * 493 * @param handler the function pointer to the callback 494 * 495 * @return A shared_ptr to the created handler object 496 */ 497 template <typename Handler> 498 inline auto makeHandler(Handler&& handler) 499 { 500 HandlerBase::ptr ptr( 501 new IpmiHandler<Handler>(std::forward<Handler>(handler))); 502 return ptr; 503 } 504 505 } // namespace ipmi 506