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