blob: 1643e5807fd51a66ee5f64fe823c9f85725894b5 [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:
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/**
Vernon Maueryf984a012018-10-08 12:05:18 -0700357 * @brief Legacy IPMI OEM handler class
358 *
359 * Legacy IPMI OEM handlers will resolve into this class, which will behave the
360 * same way as the legacy IPMI queue, passing in a big buffer for the request
361 * and a big buffer for the response.
362 *
363 * As soon as all the handlers have been rewritten, this class will be marked as
364 * deprecated and eventually removed.
365 */
366template <>
367class IpmiHandler<oem::Handler> final : public HandlerBase
368{
369 public:
370 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler)
371 {
372 }
373
374 private:
375 oem::Handler handler_;
376
377 /** @brief call the registered handler with the request
378 *
379 * This is called from the running queue context after it has already
380 * created a request object that contains all the information required to
381 * execute the ipmi command. This function will return the response object
382 * pointer that owns the response object that will ultimately get sent back
383 * to the requester.
384 *
385 * Because this is the legacy variety of IPMI handler, this function does
386 * not really have to do much other than pass the payload to the callback
387 * and return response to the caller.
388 *
389 * @param request a shared_ptr to a Request object
390 *
391 * @return a shared_ptr to a Response object
392 */
393 message::Response::ptr
394 executeCallback(message::Request::ptr request) override
395 {
396 message::Response::ptr response = request->makeResponse();
397 size_t len = request->payload.size();
398 // allocate a big response buffer here
399 response->payload.resize(
400 getChannelMaxTransferSize(request->ctx->channel));
401
402 Cc ccRet{ccSuccess};
403 try
404 {
405 ccRet = handler_(request->ctx->cmd, request->payload.data(),
406 response->payload.data(), &len);
407 }
408 catch (const std::exception& e)
409 {
410 phosphor::logging::log<phosphor::logging::level::ERR>(
411 "Legacy OEM Handler failed to catch exception",
412 phosphor::logging::entry("EXCEPTION=%s", e.what()),
413 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
414 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
415 return errorResponse(request, ccUnspecifiedError);
416 }
417 catch (...)
418 {
419 std::exception_ptr eptr;
420 try
421 {
422 eptr = std::current_exception();
423 if (eptr)
424 {
425 std::rethrow_exception(eptr);
426 }
427 }
428 catch (const std::exception& e)
429 {
430 phosphor::logging::log<phosphor::logging::level::ERR>(
431 "Handler failed to catch exception",
432 phosphor::logging::entry("EXCEPTION=%s", e.what()),
433 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
434 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
435 return errorResponse(request, ccUnspecifiedError);
436 }
437 }
438 response->cc = ccRet;
439 response->payload.resize(len);
440 return response;
441 }
442};
443
444/**
Vernon Mauerye7329c72018-10-08 12:05:16 -0700445 * @brief create a legacy IPMI handler class and return a shared_ptr
446 *
447 * The queue uses a map of pointers to do the lookup. This function returns the
448 * shared_ptr that owns the Handler object.
449 *
450 * This is called internally via the ipmi_register_callback function.
451 *
452 * @param handler the function pointer to the callback
453 *
454 * @return A shared_ptr to the created handler object
455 */
456inline auto makeLegacyHandler(const ipmid_callback_t& handler)
457{
458 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler));
459 return ptr;
460}
461
Vernon Maueryf984a012018-10-08 12:05:18 -0700462/**
463 * @brief create a legacy IPMI OEM handler class and return a shared_ptr
464 *
465 * The queue uses a map of pointers to do the lookup. This function returns the
466 * shared_ptr that owns the Handler object.
467 *
468 * This is called internally via the Router::registerHandler method.
469 *
470 * @param handler the function pointer to the callback
471 *
472 * @return A shared_ptr to the created handler object
473 */
474inline auto makeLegacyHandler(oem::Handler&& handler)
475{
476 HandlerBase::ptr ptr(
477 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
478 return ptr;
479}
Vernon Mauerye7329c72018-10-08 12:05:16 -0700480#endif // ALLOW_DEPRECATED_API
481
482/**
483 * @brief create an IPMI handler class and return a shared_ptr
484 *
485 * The queue uses a map of pointers to do the lookup. This function returns the
486 * shared_ptr that owns the Handler object.
487 *
488 * This is called internally via the ipmi::registerHandler function.
489 *
490 * @param handler the function pointer to the callback
491 *
492 * @return A shared_ptr to the created handler object
493 */
494template <typename Handler>
495inline auto makeHandler(Handler&& handler)
496{
497 HandlerBase::ptr ptr(
498 new IpmiHandler<Handler>(std::forward<Handler>(handler)));
499 return ptr;
500}
501
502} // namespace ipmi