blob: 17beb42e03f16c8920326c3c24986d589ef2b086 [file] [log] [blame]
Vernon Mauerye7329c72018-10-08 12:05:16 -07001/**
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
37namespace ipmi
38{
39
40template <typename... Args>
41static inline message::Response::ptr
42 errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
43{
44 message::Response::ptr response = request->makeResponse();
Vernon Mauerybfe55a12019-03-08 13:48:40 -080045 response->cc = cc;
46 response->pack(args...);
Vernon Mauerye7329c72018-10-08 12:05:16 -070047 return response;
48}
49static inline message::Response::ptr
50 errorResponse(message::Request::ptr request, ipmi::Cc cc)
51{
52 message::Response::ptr response = request->makeResponse();
Vernon Mauerybfe55a12019-03-08 13:48:40 -080053 response->cc = cc;
Vernon Mauerye7329c72018-10-08 12:05:16 -070054 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 */
63class 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 */
114template <typename Handler>
115class 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 */
277template <>
278class IpmiHandler<ipmid_callback_t> final : public HandlerBase
279{
280 public:
Vernon Mauerybe376302019-03-21 13:02:05 -0700281 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) :
282 handler_(handler), handlerCtx(ctx)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700283 {
284 }
285
286 private:
287 ipmid_callback_t handler_;
Vernon Mauerybe376302019-03-21 13:02:05 -0700288 void* handlerCtx;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700289
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(),
Vernon Mauerybe376302019-03-21 13:02:05 -0700320 &len, handlerCtx);
Vernon Mauerye7329c72018-10-08 12:05:16 -0700321 }
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/**
Vernon Maueryf984a012018-10-08 12:05:18 -0700359 * @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 */
368template <>
369class 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/**
Vernon Mauerye7329c72018-10-08 12:05:16 -0700447 * @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 */
Vernon Mauerybe376302019-03-21 13:02:05 -0700458inline auto makeLegacyHandler(const ipmid_callback_t& handler,
459 void* ctx = nullptr)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700460{
Vernon Mauerybe376302019-03-21 13:02:05 -0700461 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
Vernon Mauerye7329c72018-10-08 12:05:16 -0700462 return ptr;
463}
464
Vernon Maueryf984a012018-10-08 12:05:18 -0700465/**
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 */
477inline auto makeLegacyHandler(oem::Handler&& handler)
478{
479 HandlerBase::ptr ptr(
480 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
481 return ptr;
482}
Vernon Mauerye7329c72018-10-08 12:05:16 -0700483#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 */
497template <typename Handler>
498inline 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