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