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 auto payload = std::make_tuple(cc, args...); 46 response->pack(payload); 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->pack(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) : handler_(handler) 282 { 283 } 284 285 private: 286 ipmid_callback_t handler_; 287 288 /** @brief call the registered handler with the request 289 * 290 * This is called from the running queue context after it has already 291 * created a request object that contains all the information required to 292 * execute the ipmi command. This function will return the response object 293 * pointer that owns the response object that will ultimately get sent back 294 * to the requester. 295 * 296 * Because this is the legacy variety of IPMI handler, this function does 297 * not really have to do much other than pass the payload to the callback 298 * and return response to the caller. 299 * 300 * @param request a shared_ptr to a Request object 301 * 302 * @return a shared_ptr to a Response object 303 */ 304 message::Response::ptr 305 executeCallback(message::Request::ptr request) override 306 { 307 message::Response::ptr response = request->makeResponse(); 308 size_t len = request->payload.size(); 309 // allocate a big response buffer here 310 response->payload.resize( 311 getChannelMaxTransferSize(request->ctx->channel)); 312 313 Cc ccRet{ccSuccess}; 314 try 315 { 316 ccRet = handler_(request->ctx->netFn, request->ctx->cmd, 317 request->payload.data(), response->payload.data(), 318 &len, nullptr); 319 } 320 catch (const std::exception& e) 321 { 322 phosphor::logging::log<phosphor::logging::level::ERR>( 323 "Legacy Handler failed to catch exception", 324 phosphor::logging::entry("EXCEPTION=%s", e.what()), 325 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 326 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 327 return errorResponse(request, ccUnspecifiedError); 328 } 329 catch (...) 330 { 331 std::exception_ptr eptr; 332 try 333 { 334 eptr = std::current_exception(); 335 if (eptr) 336 { 337 std::rethrow_exception(eptr); 338 } 339 } 340 catch (const std::exception& e) 341 { 342 phosphor::logging::log<phosphor::logging::level::ERR>( 343 "Handler failed to catch exception", 344 phosphor::logging::entry("EXCEPTION=%s", e.what()), 345 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 346 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 347 return errorResponse(request, ccUnspecifiedError); 348 } 349 } 350 response->cc = ccRet; 351 response->payload.resize(len); 352 return response; 353 } 354 }; 355 356 /** 357 * @brief create a legacy IPMI handler class and return a shared_ptr 358 * 359 * The queue uses a map of pointers to do the lookup. This function returns the 360 * shared_ptr that owns the Handler object. 361 * 362 * This is called internally via the ipmi_register_callback function. 363 * 364 * @param handler the function pointer to the callback 365 * 366 * @return A shared_ptr to the created handler object 367 */ 368 inline auto makeLegacyHandler(const ipmid_callback_t& handler) 369 { 370 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler)); 371 return ptr; 372 } 373 374 #endif // ALLOW_DEPRECATED_API 375 376 /** 377 * @brief create an IPMI handler class and return a shared_ptr 378 * 379 * The queue uses a map of pointers to do the lookup. This function returns the 380 * shared_ptr that owns the Handler object. 381 * 382 * This is called internally via the ipmi::registerHandler function. 383 * 384 * @param handler the function pointer to the callback 385 * 386 * @return A shared_ptr to the created handler object 387 */ 388 template <typename Handler> 389 inline auto makeHandler(Handler&& handler) 390 { 391 HandlerBase::ptr ptr( 392 new IpmiHandler<Handler>(std::forward<Handler>(handler))); 393 return ptr; 394 } 395 396 } // namespace ipmi 397