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 <algorithm>
18 #include <boost/asio/spawn.hpp>
19 #include <boost/callable_traits.hpp>
20 #include <cstdint>
21 #include <exception>
22 #include <ipmid/api.hpp>
23 #include <ipmid/message.hpp>
24 #include <memory>
25 #include <optional>
26 #include <phosphor-logging/log.hpp>
27 #include <tuple>
28 #include <user_channel/channel_layer.hpp>
29 #include <utility>
30 
31 #ifdef ALLOW_DEPRECATED_API
32 #include <ipmid/api.h>
33 
34 #include <ipmid/oemrouter.hpp>
35 #endif /* ALLOW_DEPRECATED_API */
36 
37 namespace ipmi
38 {
39 
40 template <typename... Args>
41 static inline message::Response::ptr
42     errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
43 {
44     message::Response::ptr response = request->makeResponse();
45     auto payload = std::make_tuple(cc, args...);
46     response->pack(payload);
47     return response;
48 }
49 static inline message::Response::ptr
50     errorResponse(message::Request::ptr request, ipmi::Cc cc)
51 {
52     message::Response::ptr response = request->makeResponse();
53     response->pack(cc);
54     return response;
55 }
56 
57 /**
58  * @brief Handler base class for dealing with IPMI request/response
59  *
60  * The subclasses are all templated so they can provide access to any type
61  * of command callback functions.
62  */
63 class HandlerBase
64 {
65   public:
66     using ptr = std::shared_ptr<HandlerBase>;
67 
68     /** @brief wrap the call to the registered handler with the request
69      *
70      * This is called from the running queue context after it has already
71      * created a request object that contains all the information required to
72      * execute the ipmi command. This function will return the response object
73      * pointer that owns the response object that will ultimately get sent back
74      * to the requester.
75      *
76      * This is a non-virtual function wrapper to the virtualized executeCallback
77      * function that actually does the work. This is required because of how
78      * templates and virtualization work together.
79      *
80      * @param request a shared_ptr to a Request object
81      *
82      * @return a shared_ptr to a Response object
83      */
84     message::Response::ptr call(message::Request::ptr request)
85     {
86         return executeCallback(request);
87     }
88 
89   private:
90     /** @brief call the registered handler with the request
91      *
92      * This is called from the running queue context after it has already
93      * created a request object that contains all the information required to
94      * execute the ipmi command. This function will return the response object
95      * pointer that owns the response object that will ultimately get sent back
96      * to the requester.
97      *
98      * @param request a shared_ptr to a Request object
99      *
100      * @return a shared_ptr to a Response object
101      */
102     virtual message::Response::ptr
103         executeCallback(message::Request::ptr request) = 0;
104 };
105 
106 /**
107  * @brief Main IPMI handler class
108  *
109  * New IPMI handlers will resolve into this class, which will read the signature
110  * of the registering function, attempt to extract the appropriate arguments
111  * from a request, pass the arguments to the function, and then pack the
112  * response of the function back into an IPMI response.
113  */
114 template <typename Handler>
115 class IpmiHandler final : public HandlerBase
116 {
117   public:
118     explicit IpmiHandler(Handler&& handler) :
119         handler_(std::forward<Handler>(handler))
120     {
121     }
122 
123   private:
124     Handler handler_;
125 
126     /** @brief call the registered handler with the request
127      *
128      * This is called from the running queue context after it has already
129      * created a request object that contains all the information required to
130      * execute the ipmi command. This function will return the response object
131      * pointer that owns the response object that will ultimately get sent back
132      * to the requester.
133      *
134      * Because this is the new variety of IPMI handler, this is the function
135      * that attempts to extract the requested parameters in order to pass them
136      * onto the callback function and then packages up the response into a plain
137      * old vector to pass back to the caller.
138      *
139      * @param request a shared_ptr to a Request object
140      *
141      * @return a shared_ptr to a Response object
142      */
143     message::Response::ptr
144         executeCallback(message::Request::ptr request) override
145     {
146         message::Response::ptr response = request->makeResponse();
147 
148         using CallbackSig = boost::callable_traits::args_t<Handler>;
149         using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
150         using UnpackArgsType = typename utility::StripFirstArgs<
151             utility::NonIpmiArgsCount<InputArgsType>::size(),
152             InputArgsType>::type;
153         using ResultType = boost::callable_traits::return_type_t<Handler>;
154 
155         UnpackArgsType unpackArgs;
156         ipmi::Cc unpackError = request->unpack(unpackArgs);
157         if (unpackError != ipmi::ccSuccess)
158         {
159             response->cc = unpackError;
160             return response;
161         }
162         /* callbacks can contain an optional first argument of one of:
163          * 1) boost::asio::yield_context
164          * 2) ipmi::Context::ptr
165          * 3) ipmi::message::Request::ptr
166          *
167          * If any of those is part of the callback signature as the first
168          * argument, it will automatically get packed into the parameter pack
169          * here.
170          *
171          * One more special optional argument is an ipmi::message::Payload.
172          * This argument can be in any position, though logically it makes the
173          * most sense if it is the last. If this class is included in the
174          * handler signature, it will allow for the handler to unpack optional
175          * parameters. For example, the Set LAN Configuration Parameters
176          * command takes variable length (and type) values for each of the LAN
177          * parameters. This means that the  only fixed data is the channel and
178          * parameter selector. All the remaining data can be extracted using
179          * the Payload class and the unpack API available to the Payload class.
180          */
181         std::optional<InputArgsType> inputArgs;
182         if constexpr (std::tuple_size<InputArgsType>::value > 0)
183         {
184             if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>,
185                                        boost::asio::yield_context>::value)
186             {
187                 inputArgs.emplace(std::tuple_cat(
188                     std::forward_as_tuple(*(request->ctx->yield)),
189                     std::move(unpackArgs)));
190             }
191             else if constexpr (std::is_same<
192                                    std::tuple_element_t<0, InputArgsType>,
193                                    ipmi::Context::ptr>::value)
194             {
195                 inputArgs.emplace(
196                     std::tuple_cat(std::forward_as_tuple(request->ctx),
197                                    std::move(unpackArgs)));
198             }
199             else if constexpr (std::is_same<
200                                    std::tuple_element_t<0, InputArgsType>,
201                                    ipmi::message::Request::ptr>::value)
202             {
203                 inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request),
204                                                  std::move(unpackArgs)));
205             }
206             else
207             {
208                 // no special parameters were requested (but others were)
209                 inputArgs.emplace(std::move(unpackArgs));
210             }
211         }
212         else
213         {
214             // no parameters were requested
215             inputArgs = std::move(unpackArgs);
216         }
217         ResultType result;
218         try
219         {
220             // execute the registered callback function and get the
221             // ipmi::RspType<>
222             result = std::apply(handler_, *inputArgs);
223         }
224         catch (const std::exception& e)
225         {
226             phosphor::logging::log<phosphor::logging::level::ERR>(
227                 "Handler failed to catch exception",
228                 phosphor::logging::entry("EXCEPTION=%s", e.what()),
229                 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
230                 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
231             return errorResponse(request, ccUnspecifiedError);
232         }
233         catch (...)
234         {
235             std::exception_ptr eptr;
236             try
237             {
238                 eptr = std::current_exception();
239                 if (eptr)
240                 {
241                     std::rethrow_exception(eptr);
242                 }
243             }
244             catch (const std::exception& e)
245             {
246                 phosphor::logging::log<phosphor::logging::level::ERR>(
247                     "Handler failed to catch exception",
248                     phosphor::logging::entry("EXCEPTION=%s", e.what()),
249                     phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
250                     phosphor::logging::entry("CMD=%x", request->ctx->cmd));
251                 return errorResponse(request, ccUnspecifiedError);
252             }
253         }
254 
255         response->cc = std::get<0>(result);
256         auto payload = std::get<1>(result);
257         // check for optional payload
258         if (payload)
259         {
260             response->pack(*payload);
261         }
262         return response;
263     }
264 };
265 
266 #ifdef ALLOW_DEPRECATED_API
267 /**
268  * @brief Legacy IPMI handler class
269  *
270  * Legacy IPMI handlers will resolve into this class, which will behave the same
271  * way as the legacy IPMI queue, passing in a big buffer for the request and a
272  * big buffer for the response.
273  *
274  * As soon as all the handlers have been rewritten, this class will be marked as
275  * deprecated and eventually removed.
276  */
277 template <>
278 class IpmiHandler<ipmid_callback_t> final : public HandlerBase
279 {
280   public:
281     explicit IpmiHandler(const ipmid_callback_t& handler) : handler_(handler)
282     {
283     }
284 
285   private:
286     ipmid_callback_t handler_;
287 
288     /** @brief call the registered handler with the request
289      *
290      * This is called from the running queue context after it has already
291      * created a request object that contains all the information required to
292      * execute the ipmi command. This function will return the response object
293      * pointer that owns the response object that will ultimately get sent back
294      * to the requester.
295      *
296      * Because this is the legacy variety of IPMI handler, this function does
297      * not really have to do much other than pass the payload to the callback
298      * and return response to the caller.
299      *
300      * @param request a shared_ptr to a Request object
301      *
302      * @return a shared_ptr to a Response object
303      */
304     message::Response::ptr
305         executeCallback(message::Request::ptr request) override
306     {
307         message::Response::ptr response = request->makeResponse();
308         size_t len = request->payload.size();
309         // allocate a big response buffer here
310         response->payload.resize(
311             getChannelMaxTransferSize(request->ctx->channel));
312 
313         Cc ccRet{ccSuccess};
314         try
315         {
316             ccRet = handler_(request->ctx->netFn, request->ctx->cmd,
317                              request->payload.data(), response->payload.data(),
318                              &len, nullptr);
319         }
320         catch (const std::exception& e)
321         {
322             phosphor::logging::log<phosphor::logging::level::ERR>(
323                 "Legacy Handler failed to catch exception",
324                 phosphor::logging::entry("EXCEPTION=%s", e.what()),
325                 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
326                 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
327             return errorResponse(request, ccUnspecifiedError);
328         }
329         catch (...)
330         {
331             std::exception_ptr eptr;
332             try
333             {
334                 eptr = std::current_exception();
335                 if (eptr)
336                 {
337                     std::rethrow_exception(eptr);
338                 }
339             }
340             catch (const std::exception& e)
341             {
342                 phosphor::logging::log<phosphor::logging::level::ERR>(
343                     "Handler failed to catch exception",
344                     phosphor::logging::entry("EXCEPTION=%s", e.what()),
345                     phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
346                     phosphor::logging::entry("CMD=%x", request->ctx->cmd));
347                 return errorResponse(request, ccUnspecifiedError);
348             }
349         }
350         response->cc = ccRet;
351         response->payload.resize(len);
352         return response;
353     }
354 };
355 
356 /**
357  * @brief Legacy IPMI OEM handler class
358  *
359  * Legacy IPMI OEM handlers will resolve into this class, which will behave the
360  * same way as the legacy IPMI queue, passing in a big buffer for the request
361  * and a big buffer for the response.
362  *
363  * As soon as all the handlers have been rewritten, this class will be marked as
364  * deprecated and eventually removed.
365  */
366 template <>
367 class IpmiHandler<oem::Handler> final : public HandlerBase
368 {
369   public:
370     explicit IpmiHandler(const oem::Handler& handler) : handler_(handler)
371     {
372     }
373 
374   private:
375     oem::Handler handler_;
376 
377     /** @brief call the registered handler with the request
378      *
379      * This is called from the running queue context after it has already
380      * created a request object that contains all the information required to
381      * execute the ipmi command. This function will return the response object
382      * pointer that owns the response object that will ultimately get sent back
383      * to the requester.
384      *
385      * Because this is the legacy variety of IPMI handler, this function does
386      * not really have to do much other than pass the payload to the callback
387      * and return response to the caller.
388      *
389      * @param request a shared_ptr to a Request object
390      *
391      * @return a shared_ptr to a Response object
392      */
393     message::Response::ptr
394         executeCallback(message::Request::ptr request) override
395     {
396         message::Response::ptr response = request->makeResponse();
397         size_t len = request->payload.size();
398         // allocate a big response buffer here
399         response->payload.resize(
400             getChannelMaxTransferSize(request->ctx->channel));
401 
402         Cc ccRet{ccSuccess};
403         try
404         {
405             ccRet = handler_(request->ctx->cmd, request->payload.data(),
406                              response->payload.data(), &len);
407         }
408         catch (const std::exception& e)
409         {
410             phosphor::logging::log<phosphor::logging::level::ERR>(
411                 "Legacy OEM Handler failed to catch exception",
412                 phosphor::logging::entry("EXCEPTION=%s", e.what()),
413                 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
414                 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
415             return errorResponse(request, ccUnspecifiedError);
416         }
417         catch (...)
418         {
419             std::exception_ptr eptr;
420             try
421             {
422                 eptr = std::current_exception();
423                 if (eptr)
424                 {
425                     std::rethrow_exception(eptr);
426                 }
427             }
428             catch (const std::exception& e)
429             {
430                 phosphor::logging::log<phosphor::logging::level::ERR>(
431                     "Handler failed to catch exception",
432                     phosphor::logging::entry("EXCEPTION=%s", e.what()),
433                     phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
434                     phosphor::logging::entry("CMD=%x", request->ctx->cmd));
435                 return errorResponse(request, ccUnspecifiedError);
436             }
437         }
438         response->cc = ccRet;
439         response->payload.resize(len);
440         return response;
441     }
442 };
443 
444 /**
445  * @brief create a legacy IPMI handler class and return a shared_ptr
446  *
447  * The queue uses a map of pointers to do the lookup. This function returns the
448  * shared_ptr that owns the Handler object.
449  *
450  * This is called internally via the ipmi_register_callback function.
451  *
452  * @param handler the function pointer to the callback
453  *
454  * @return A shared_ptr to the created handler object
455  */
456 inline auto makeLegacyHandler(const ipmid_callback_t& handler)
457 {
458     HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler));
459     return ptr;
460 }
461 
462 /**
463  * @brief create a legacy IPMI OEM handler class and return a shared_ptr
464  *
465  * The queue uses a map of pointers to do the lookup. This function returns the
466  * shared_ptr that owns the Handler object.
467  *
468  * This is called internally via the Router::registerHandler method.
469  *
470  * @param handler the function pointer to the callback
471  *
472  * @return A shared_ptr to the created handler object
473  */
474 inline auto makeLegacyHandler(oem::Handler&& handler)
475 {
476     HandlerBase::ptr ptr(
477         new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
478     return ptr;
479 }
480 #endif // ALLOW_DEPRECATED_API
481 
482 /**
483  * @brief create an IPMI handler class and return a shared_ptr
484  *
485  * The queue uses a map of pointers to do the lookup. This function returns the
486  * shared_ptr that owns the Handler object.
487  *
488  * This is called internally via the ipmi::registerHandler function.
489  *
490  * @param handler the function pointer to the callback
491  *
492  * @return A shared_ptr to the created handler object
493  */
494 template <typename Handler>
495 inline auto makeHandler(Handler&& handler)
496 {
497     HandlerBase::ptr ptr(
498         new IpmiHandler<Handler>(std::forward<Handler>(handler)));
499     return ptr;
500 }
501 
502 } // namespace ipmi
503