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 <cxxabi.h>
18
19 #include <boost/asio/spawn.hpp>
20 #include <boost/callable_traits.hpp>
21 #include <ipmid/api-types.hpp>
22 #include <ipmid/message.hpp>
23 #include <phosphor-logging/lg2.hpp>
24 #include <phosphor-logging/log.hpp>
25 #include <user_channel/channel_layer.hpp>
26
27 #include <algorithm>
28 #include <cstdint>
29 #include <exception>
30 #include <memory>
31 #include <optional>
32 #include <stdexcept>
33 #include <tuple>
34 #include <utility>
35
36 #ifdef ALLOW_DEPRECATED_API
37 #include <ipmid/api.h>
38
39 #include <ipmid/oemrouter.hpp>
40 #endif /* ALLOW_DEPRECATED_API */
41
42 namespace ipmi
43 {
44
45 template <typename... Args>
46 static inline message::Response::ptr
errorResponse(message::Request::ptr request,ipmi::Cc cc,Args &&...args)47 errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
48 {
49 message::Response::ptr response = request->makeResponse();
50 response->cc = cc;
51 response->pack(args...);
52 return response;
53 }
54 static inline message::Response::ptr
errorResponse(message::Request::ptr request,ipmi::Cc cc)55 errorResponse(message::Request::ptr request, ipmi::Cc cc)
56 {
57 message::Response::ptr response = request->makeResponse();
58 response->cc = cc;
59 return response;
60 }
61
62 /** @brief Exception extension that allows setting an IPMI return code */
63 class HandlerCompletion
64 {
65 public:
HandlerCompletion(Cc cc)66 HandlerCompletion(Cc cc) noexcept : cc(cc) {}
67
code() const68 Cc code() const noexcept
69 {
70 return cc;
71 }
72
73 private:
74 Cc cc;
75 };
76
77 /** @brief Exception extension that allows setting an IPMI return code and
78 * printing out a logged error */
79 class HandlerException : public HandlerCompletion, public std::runtime_error
80 {
81 public:
HandlerException(Cc cc,const char * what)82 HandlerException(Cc cc, const char* what) :
83 HandlerCompletion(cc), std::runtime_error(what)
84 {}
HandlerException(Cc cc,const std::string & what)85 HandlerException(Cc cc, const std::string& what) :
86 HandlerException(cc, what.c_str())
87 {}
88 };
89
currentExceptionType()90 static inline const char* currentExceptionType()
91 {
92 int status;
93 return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0,
94 0, &status);
95 }
96
97 /**
98 * @brief Handler base class for dealing with IPMI request/response
99 *
100 * The subclasses are all templated so they can provide access to any type
101 * of command callback functions.
102 */
103 class HandlerBase
104 {
105 public:
106 using ptr = std::shared_ptr<HandlerBase>;
107
108 virtual ~HandlerBase() = default;
109
110 /** @brief wrap the call to the registered handler with the request
111 *
112 * This is called from the running queue context after it has already
113 * created a request object that contains all the information required to
114 * execute the ipmi command. This function will return the response object
115 * pointer that owns the response object that will ultimately get sent back
116 * to the requester.
117 *
118 * This is a non-virtual function wrapper to the virtualized executeCallback
119 * function that actually does the work. This is required because of how
120 * templates and virtualization work together.
121 *
122 * @param request a shared_ptr to a Request object
123 *
124 * @return a shared_ptr to a Response object
125 */
call(message::Request::ptr request)126 message::Response::ptr call(message::Request::ptr request)
127 {
128 return executeCallback(request);
129 }
130
131 private:
132 /** @brief call the registered handler with the request
133 *
134 * This is called from the running queue context after it has already
135 * created a request object that contains all the information required to
136 * execute the ipmi command. This function will return the response object
137 * pointer that owns the response object that will ultimately get sent back
138 * to the requester.
139 *
140 * @param request a shared_ptr to a Request object
141 *
142 * @return a shared_ptr to a Response object
143 */
144 virtual message::Response::ptr
145 executeCallback(message::Request::ptr request) = 0;
146 };
147
148 /**
149 * @brief Main IPMI handler class
150 *
151 * New IPMI handlers will resolve into this class, which will read the signature
152 * of the registering function, attempt to extract the appropriate arguments
153 * from a request, pass the arguments to the function, and then pack the
154 * response of the function back into an IPMI response.
155 */
156 template <typename Handler>
157 class IpmiHandler final : public HandlerBase
158 {
159 public:
IpmiHandler(Handler && handler)160 explicit IpmiHandler(Handler&& handler) :
161 handler_(std::forward<Handler>(handler))
162 {}
163
164 private:
165 Handler handler_;
166
167 /** @brief call the registered handler with the request
168 *
169 * This is called from the running queue context after it has already
170 * created a request object that contains all the information required to
171 * execute the ipmi command. This function will return the response object
172 * pointer that owns the response object that will ultimately get sent back
173 * to the requester.
174 *
175 * Because this is the new variety of IPMI handler, this is the function
176 * that attempts to extract the requested parameters in order to pass them
177 * onto the callback function and then packages up the response into a plain
178 * old vector to pass back to the caller.
179 *
180 * @param request a shared_ptr to a Request object
181 *
182 * @return a shared_ptr to a Response object
183 */
184 message::Response::ptr
executeCallback(message::Request::ptr request)185 executeCallback(message::Request::ptr request) override
186 {
187 message::Response::ptr response = request->makeResponse();
188
189 using CallbackSig = boost::callable_traits::args_t<Handler>;
190 using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
191 using UnpackArgsType = typename utility::StripFirstArgs<
192 utility::NonIpmiArgsCount<InputArgsType>::size(),
193 InputArgsType>::type;
194 using ResultType = boost::callable_traits::return_type_t<Handler>;
195
196 UnpackArgsType unpackArgs{};
197 request->payload.trailingOk = false;
198 ipmi::Cc unpackError = request->unpack(unpackArgs);
199 if (unpackError != ipmi::ccSuccess)
200 {
201 response->cc = unpackError;
202 return response;
203 }
204 /* callbacks can contain an optional first argument of one of:
205 * 1) boost::asio::yield_context
206 * 2) ipmi::Context::ptr
207 * 3) ipmi::message::Request::ptr
208 *
209 * If any of those is part of the callback signature as the first
210 * argument, it will automatically get packed into the parameter pack
211 * here.
212 *
213 * One more special optional argument is an ipmi::message::Payload.
214 * This argument can be in any position, though logically it makes the
215 * most sense if it is the last. If this class is included in the
216 * handler signature, it will allow for the handler to unpack optional
217 * parameters. For example, the Set LAN Configuration Parameters
218 * command takes variable length (and type) values for each of the LAN
219 * parameters. This means that the only fixed data is the channel and
220 * parameter selector. All the remaining data can be extracted using
221 * the Payload class and the unpack API available to the Payload class.
222 */
223 ResultType result;
224 try
225 {
226 std::optional<InputArgsType> inputArgs;
227 if constexpr (std::tuple_size<InputArgsType>::value > 0)
228 {
229 if constexpr (std::is_same<
230 std::tuple_element_t<0, InputArgsType>,
231 boost::asio::yield_context>::value)
232 {
233 inputArgs.emplace(std::tuple_cat(
234 std::forward_as_tuple(request->ctx->yield),
235 std::move(unpackArgs)));
236 }
237 else if constexpr (std::is_same<
238 std::tuple_element_t<0, InputArgsType>,
239 ipmi::Context::ptr>::value)
240 {
241 inputArgs.emplace(
242 std::tuple_cat(std::forward_as_tuple(request->ctx),
243 std::move(unpackArgs)));
244 }
245 else if constexpr (std::is_same<
246 std::tuple_element_t<0, InputArgsType>,
247 ipmi::message::Request::ptr>::value)
248 {
249 inputArgs.emplace(std::tuple_cat(
250 std::forward_as_tuple(request), std::move(unpackArgs)));
251 }
252 else
253 {
254 // no special parameters were requested (but others were)
255 inputArgs.emplace(std::move(unpackArgs));
256 }
257 }
258 else
259 {
260 // no parameters were requested
261 inputArgs = std::move(unpackArgs);
262 }
263
264 // execute the registered callback function and get the
265 // ipmi::RspType<>
266 result = std::apply(handler_, *inputArgs);
267 }
268 catch (const HandlerException& e)
269 {
270 lg2::info("Handler produced exception, NetFn: {NETFN}, "
271 "Cmd: {CMD}: {ERROR}",
272 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
273 request->ctx->cmd, "ERROR", e);
274 return errorResponse(request, e.code());
275 }
276 catch (const std::exception& e)
277 {
278 lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
279 "Cmd: {CMD}: {ERROR}",
280 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
281 request->ctx->cmd, "ERROR", e);
282 return errorResponse(request, ccUnspecifiedError);
283 }
284 catch (const HandlerCompletion& c)
285 {
286 return errorResponse(request, c.code());
287 }
288 catch (...)
289 {
290 const char* what = currentExceptionType();
291 lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
292 "Cmd: {CMD}: {ERROR}",
293 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
294 request->ctx->cmd, "ERROR", what);
295 return errorResponse(request, ccUnspecifiedError);
296 }
297
298 response->cc = std::get<0>(result);
299 auto payload = std::get<1>(result);
300 // check for optional payload
301 if (payload)
302 {
303 response->pack(*payload);
304 }
305 return response;
306 }
307 };
308
309 #ifdef ALLOW_DEPRECATED_API
310 static constexpr size_t maxLegacyBufferSize = 64 * 1024;
311 /**
312 * @brief Legacy IPMI handler class
313 *
314 * Legacy IPMI handlers will resolve into this class, which will behave the same
315 * way as the legacy IPMI queue, passing in a big buffer for the request and a
316 * big buffer for the response.
317 *
318 * As soon as all the handlers have been rewritten, this class will be marked as
319 * deprecated and eventually removed.
320 */
321 template <>
322 class IpmiHandler<ipmid_callback_t> final : public HandlerBase
323 {
324 public:
IpmiHandler(const ipmid_callback_t & handler,void * ctx=nullptr)325 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) :
326 handler_(handler), handlerCtx(ctx)
327 {}
328
329 private:
330 ipmid_callback_t handler_;
331 void* handlerCtx;
332
333 /** @brief call the registered handler with the request
334 *
335 * This is called from the running queue context after it has already
336 * created a request object that contains all the information required to
337 * execute the ipmi command. This function will return the response object
338 * pointer that owns the response object that will ultimately get sent back
339 * to the requester.
340 *
341 * Because this is the legacy variety of IPMI handler, this function does
342 * not really have to do much other than pass the payload to the callback
343 * and return response to the caller.
344 *
345 * @param request a shared_ptr to a Request object
346 *
347 * @return a shared_ptr to a Response object
348 */
349 message::Response::ptr
executeCallback(message::Request::ptr request)350 executeCallback(message::Request::ptr request) override
351 {
352 message::Response::ptr response = request->makeResponse();
353 // allocate a big response buffer here
354 response->payload.resize(maxLegacyBufferSize);
355
356 size_t len = request->payload.size() - request->payload.rawIndex;
357 Cc ccRet{ccSuccess};
358 try
359 {
360 ccRet =
361 handler_(request->ctx->netFn, request->ctx->cmd,
362 request->payload.data() + request->payload.rawIndex,
363 response->payload.data(), &len, handlerCtx);
364 }
365 catch (const HandlerException& e)
366 {
367 lg2::info("Legacy Handler produced exception, NetFn: {NETFN}, "
368 "Cmd: {CMD}: {ERROR}",
369 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
370 request->ctx->cmd, "ERROR", e);
371 return errorResponse(request, e.code());
372 }
373 catch (const std::exception& e)
374 {
375 lg2::error("Legacy Handler failed to catch exception, "
376 "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
377 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
378 request->ctx->cmd, "ERROR", e);
379 return errorResponse(request, ccUnspecifiedError);
380 }
381 catch (const HandlerCompletion& c)
382 {
383 return errorResponse(request, c.code());
384 }
385 catch (...)
386 {
387 const char* what = currentExceptionType();
388 lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
389 "Cmd: {CMD}: {ERROR}",
390 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
391 request->ctx->cmd, "ERROR", what);
392 return errorResponse(request, ccUnspecifiedError);
393 }
394 response->cc = ccRet;
395 response->payload.resize(len);
396 return response;
397 }
398 };
399
400 /**
401 * @brief Legacy IPMI OEM handler class
402 *
403 * Legacy IPMI OEM handlers will resolve into this class, which will behave the
404 * same way as the legacy IPMI queue, passing in a big buffer for the request
405 * and a big buffer for the response.
406 *
407 * As soon as all the handlers have been rewritten, this class will be marked as
408 * deprecated and eventually removed.
409 */
410 template <>
411 class IpmiHandler<oem::Handler> final : public HandlerBase
412 {
413 public:
IpmiHandler(const oem::Handler & handler)414 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {}
415
416 private:
417 oem::Handler handler_;
418
419 /** @brief call the registered handler with the request
420 *
421 * This is called from the running queue context after it has already
422 * created a request object that contains all the information required to
423 * execute the ipmi command. This function will return the response object
424 * pointer that owns the response object that will ultimately get sent back
425 * to the requester.
426 *
427 * Because this is the legacy variety of IPMI handler, this function does
428 * not really have to do much other than pass the payload to the callback
429 * and return response to the caller.
430 *
431 * @param request a shared_ptr to a Request object
432 *
433 * @return a shared_ptr to a Response object
434 */
435 message::Response::ptr
executeCallback(message::Request::ptr request)436 executeCallback(message::Request::ptr request) override
437 {
438 message::Response::ptr response = request->makeResponse();
439 // allocate a big response buffer here
440 response->payload.resize(maxLegacyBufferSize);
441
442 size_t len = request->payload.size() - request->payload.rawIndex;
443 Cc ccRet{ccSuccess};
444 try
445 {
446 ccRet =
447 handler_(request->ctx->cmd,
448 request->payload.data() + request->payload.rawIndex,
449 response->payload.data(), &len);
450 }
451 catch (const HandlerException& e)
452 {
453 lg2::info("Legacy OEM Handler produced exception, NetFn: {NETFN}, "
454 "Cmd: {CMD}: {ERROR}",
455 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
456 request->ctx->cmd, "ERROR", e);
457 return errorResponse(request, e.code());
458 }
459 catch (const std::exception& e)
460 {
461 lg2::error("Legacy OEM Handler failed to catch exception, "
462 "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
463 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
464 request->ctx->cmd, "ERROR", e);
465 return errorResponse(request, ccUnspecifiedError);
466 }
467 catch (const HandlerCompletion& c)
468 {
469 return errorResponse(request, c.code());
470 }
471 catch (...)
472 {
473 const char* what = currentExceptionType();
474 lg2::error("Legacy failed to catch exception, NetFn: {NETFN}, "
475 "Cmd: {CMD}: {ERROR}",
476 "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
477 request->ctx->cmd, "ERROR", what);
478 return errorResponse(request, ccUnspecifiedError);
479 }
480 response->cc = ccRet;
481 response->payload.resize(len);
482 return response;
483 }
484 };
485
486 /**
487 * @brief create a legacy IPMI handler class and return a shared_ptr
488 *
489 * The queue uses a map of pointers to do the lookup. This function returns the
490 * shared_ptr that owns the Handler object.
491 *
492 * This is called internally via the ipmi_register_callback function.
493 *
494 * @param handler the function pointer to the callback
495 *
496 * @return A shared_ptr to the created handler object
497 */
makeLegacyHandler(const ipmid_callback_t & handler,void * ctx=nullptr)498 inline auto makeLegacyHandler(const ipmid_callback_t& handler,
499 void* ctx = nullptr)
500 {
501 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
502 return ptr;
503 }
504
505 /**
506 * @brief create a legacy IPMI OEM handler class and return a shared_ptr
507 *
508 * The queue uses a map of pointers to do the lookup. This function returns the
509 * shared_ptr that owns the Handler object.
510 *
511 * This is called internally via the Router::registerHandler method.
512 *
513 * @param handler the function pointer to the callback
514 *
515 * @return A shared_ptr to the created handler object
516 */
makeLegacyHandler(oem::Handler && handler)517 inline auto makeLegacyHandler(oem::Handler&& handler)
518 {
519 HandlerBase::ptr ptr(
520 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
521 return ptr;
522 }
523 #endif // ALLOW_DEPRECATED_API
524
525 /**
526 * @brief create an IPMI handler class and return a shared_ptr
527 *
528 * The queue uses a map of pointers to do the lookup. This function returns the
529 * shared_ptr that owns the Handler object.
530 *
531 * This is called internally via the ipmi::registerHandler function.
532 *
533 * @param handler the function pointer to the callback
534 *
535 * @return A shared_ptr to the created handler object
536 */
537 template <typename Handler>
makeHandler(Handler && handler)538 inline auto makeHandler(Handler&& handler)
539 {
540 HandlerBase::ptr ptr(
541 new IpmiHandler<Handler>(std::forward<Handler>(handler)));
542 return ptr;
543 }
544
545 namespace impl
546 {
547
548 // IPMI command handler registration implementation
549 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
550 ::ipmi::HandlerBase::ptr handler);
551 bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
552 ::ipmi::HandlerBase::ptr handler);
553 bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
554 ::ipmi::HandlerBase::ptr handler);
555
556 } // namespace impl
557
558 /**
559 * @brief main IPMI handler registration function
560 *
561 * This function should be used to register all new-style IPMI handler
562 * functions. This function just passes the callback to makeHandler, which
563 * creates a new wrapper object that will automatically extract the appropriate
564 * parameters for the callback function as well as pack up the response.
565 *
566 * @param prio - priority at which to register; see api.hpp
567 * @param netFn - the IPMI net function number to register
568 * @param cmd - the IPMI command number to register
569 * @param priv - the IPMI user privilige required for this command
570 * @param handler - the callback function that will handle this request
571 *
572 * @return bool - success of registering the handler
573 */
574 template <typename Handler>
registerHandler(int prio,NetFn netFn,Cmd cmd,Privilege priv,Handler && handler)575 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
576 Handler&& handler)
577 {
578 auto h = ipmi::makeHandler(std::forward<Handler>(handler));
579 return impl::registerHandler(prio, netFn, cmd, priv, h);
580 }
581
582 /**
583 * @brief register a IPMI OEM group handler
584 *
585 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
586 * The first data byte position in requests and responses under this network
587 * function identifies the defining body that specifies command functionality.
588 * Software assumes that the command and completion code field positions will
589 * hold command and completion code values.
590 *
591 * The following values are used to identify the defining body:
592 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com)
593 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
594 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
595 * 03h VITA Standards Organization (VSO) (www.vita.com)
596 * DCh DCMI Specifications (www.intel.com/go/dcmi)
597 * all other Reserved
598 *
599 * When this network function is used, the ID for the defining body occupies
600 * the first data byte in a request, and the second data byte (following the
601 * completion code) in a 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>
registerGroupHandler(int prio,Group group,Cmd cmd,Privilege priv,Handler && handler)614 void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
615 Handler&& handler)
616 {
617 auto h = ipmi::makeHandler(handler);
618 impl::registerGroupHandler(prio, group, cmd, priv, h);
619 }
620
621 /**
622 * @brief register a IPMI OEM IANA handler
623 *
624 * From IPMI spec Network Function Codes Table (Row 2Eh):
625 * The first three data bytes of requests and responses under this network
626 * function explicitly identify the OEM or non-IPMI group that specifies the
627 * command functionality. While the OEM or non-IPMI group defines the
628 * functional semantics for the cmd and remaining data fields, the cmd field
629 * is required to hold the same value in requests and responses for a given
630 * operation in order to be supported under the IPMI message handling and
631 * transport mechanisms.
632 *
633 * When this network function is used, the IANA Enterprise Number for the
634 * defining body occupies the first three data bytes in a request, and the
635 * first three data bytes following the completion code position in a
636 * response.
637 *
638 * @tparam Handler - implicitly specified callback function type
639 * @param prio - priority at which to register; see api.hpp
640 * @param netFn - the IPMI net function number to register
641 * @param cmd - the IPMI command number to register
642 * @param priv - the IPMI user privilige required for this command
643 * @param handler - the callback function that will handle this request
644 *
645 * @return bool - success of registering the handler
646 *
647 */
648 template <typename Handler>
registerOemHandler(int prio,Iana iana,Cmd cmd,Privilege priv,Handler && handler)649 void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
650 Handler&& handler)
651 {
652 auto h = ipmi::makeHandler(handler);
653 impl::registerOemHandler(prio, iana, cmd, priv, h);
654 }
655
656 } // namespace ipmi
657
658 #ifdef ALLOW_DEPRECATED_API
659 /**
660 * @brief legacy IPMI handler registration function
661 *
662 * This function should be used to register all legacy IPMI handler
663 * functions. This function just behaves just as the legacy registration
664 * mechanism did, silently replacing any existing handler with a new one.
665 *
666 * @param netFn - the IPMI net function number to register
667 * @param cmd - the IPMI command number to register
668 * @param context - ignored
669 * @param handler - the callback function that will handle this request
670 * @param priv - the IPMI user privilige required for this command
671 */
672 // [[deprecated("Use ipmi::registerHandler() instead")]]
673 void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
674 ipmi_context_t context, ipmid_callback_t handler,
675 ipmi_cmd_privilege_t priv);
676
677 #endif /* ALLOW_DEPRECATED_API */
678