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