blob: 4864f4ec037d4a2d7a24a9341effde0d94866f58 [file] [log] [blame]
Vernon Mauerye7329c72018-10-08 12:05:16 -07001/**
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
18#include <algorithm>
19#include <boost/asio/spawn.hpp>
20#include <cstdint>
William A. Kennington IIIf2fd17a2019-04-24 01:53:52 -070021#include <exception>
Vernon Mauerye08fbff2019-04-03 09:19:34 -070022#include <ipmid/api-types.hpp>
Vernon Mauerye7329c72018-10-08 12:05:16 -070023#include <ipmid/message/types.hpp>
24#include <memory>
25#include <phosphor-logging/log.hpp>
Vernon Mauery33298af2019-05-13 15:32:37 -070026#include <sdbusplus/asio/connection.hpp>
Vernon Mauerye7329c72018-10-08 12:05:16 -070027#include <tuple>
28#include <utility>
29#include <vector>
30
31namespace ipmi
32{
33
34struct Context
35{
36 using ptr = std::shared_ptr<Context>;
37
Vernon Mauery33298af2019-05-13 15:32:37 -070038 Context() = delete;
39 Context(const Context&) = default;
40 Context& operator=(const Context&) = default;
41 Context(Context&&) = delete;
42 Context& operator=(Context&&) = delete;
Vernon Mauerye7329c72018-10-08 12:05:16 -070043
Vernon Mauery33298af2019-05-13 15:32:37 -070044 Context(std::shared_ptr<sdbusplus::asio::connection> bus, NetFn netFn,
45 Cmd cmd, int channel, int userId, Privilege priv, int rqSA,
46 boost::asio::yield_context* yield) :
47 bus(bus),
48 netFn(netFn), cmd(cmd), channel(channel), userId(userId), priv(priv),
49 rqSA(rqSA), yield(yield)
Vernon Mauerye7329c72018-10-08 12:05:16 -070050 {
51 }
52
Vernon Mauery33298af2019-05-13 15:32:37 -070053 std::shared_ptr<sdbusplus::asio::connection> bus;
Vernon Mauerye7329c72018-10-08 12:05:16 -070054 // normal IPMI context (what call is this, from whence it came...)
Vernon Mauery33298af2019-05-13 15:32:37 -070055 NetFn netFn;
56 Cmd cmd;
57 int channel;
58 int userId;
59 Privilege priv;
Vernon Maueryd6a2da02019-04-09 16:00:46 -070060 // srcAddr is only set on IPMB requests because
61 // Platform Event Message needs it to determine the incoming format
Vernon Mauery33298af2019-05-13 15:32:37 -070062 int rqSA;
63 boost::asio::yield_context* yield;
Vernon Mauerye7329c72018-10-08 12:05:16 -070064};
65
66namespace message
67{
68
69namespace details
70{
71
72template <typename A>
73struct UnpackSingle;
74
75template <typename T>
76using UnpackSingle_t = UnpackSingle<utility::TypeIdDowncast_t<T>>;
77
78template <typename A>
79struct PackSingle;
80
81template <typename T>
82using PackSingle_t = PackSingle<utility::TypeIdDowncast_t<T>>;
83
84// size to hold 64 bits plus one (possibly-)partial byte
85static constexpr size_t bitStreamSize = ((sizeof(uint64_t) + 1) * CHAR_BIT);
86
87} // namespace details
88
89/**
90 * @brief a payload class that provides a mechanism to pack and unpack data
91 *
92 * When a new request is being executed, the Payload class is responsible for
93 * attempting to unpack all the required arguments from the incoming blob. For
94 * variable-length functions, it is possible to have function signature have a
95 * Payload object, which will then allow the remaining data to be extracted as
96 * needed.
97 *
98 * When creating a response, the parameters returned from the callback use a
99 * newly created payload object to pack all the parameters into a buffer that is
100 * then returned to the requester.
101 *
102 * These interfaces make calls into the message/pack.hpp and message/unpack.hpp
103 * functions.
104 */
105struct Payload
106{
107 Payload() = default;
108 Payload(const Payload&) = default;
109 Payload& operator=(const Payload&) = default;
110 Payload(Payload&&) = default;
111 Payload& operator=(Payload&&) = default;
112
William A. Kennington III51694c22019-04-24 01:44:44 -0700113 explicit Payload(std::vector<uint8_t>&& data) : raw(std::move(data))
Vernon Mauerye7329c72018-10-08 12:05:16 -0700114 {
115 }
116
117 ~Payload()
118 {
119 using namespace phosphor::logging;
William A. Kennington IIIf2fd17a2019-04-24 01:53:52 -0700120 if (raw.size() != 0 && std::uncaught_exceptions() == 0 && !trailingOk &&
121 !unpackCheck && !unpackError)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700122 {
123 log<level::ERR>("Failed to check request for full unpack");
124 }
125 }
126
127 /******************************************************************
128 * raw vector access
129 *****************************************************************/
130 /**
131 * @brief return the size of the underlying raw buffer
132 */
133 size_t size() const
134 {
135 return raw.size();
136 }
137 /**
138 * @brief resize the underlying raw buffer to a new size
139 *
140 * @param sz - new size for the buffer
141 */
142 void resize(size_t sz)
143 {
144 raw.resize(sz);
145 }
146 /**
147 * @brief return a pointer to the underlying raw buffer
148 */
149 uint8_t* data()
150 {
151 return raw.data();
152 }
153 /**
154 * @brief return a const pointer to the underlying raw buffer
155 */
156 const uint8_t* data() const
157 {
158 return raw.data();
159 }
160
161 /******************************************************************
162 * Response operations
163 *****************************************************************/
164 /**
165 * @brief append a series of bytes to the buffer
166 *
167 * @tparam T - the type pointer to return; must be compatible to a byte
168 *
169 * @param begin - a pointer to the beginning of the series
170 * @param end - a pointer to the end of the series
171 */
172 template <typename T>
173 void append(T* begin, T* end)
174 {
175 static_assert(
176 std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
177 std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
178 std::is_same_v<utility::TypeIdDowncast_t<T>, char>,
179 "begin and end must be signed or unsigned byte pointers");
180 // this interface only allows full-byte access; pack in partial bytes
181 drain();
182 raw.insert(raw.end(), reinterpret_cast<const uint8_t*>(begin),
183 reinterpret_cast<const uint8_t*>(end));
184 }
185
186 /**
187 * @brief append a series of bits to the buffer
188 *
189 * Only the lowest @count order of bits will be appended, with the most
190 * significant of those bits getting appended first.
191 *
192 * @param count - number of bits to append
193 * @param bits - a byte with count significant bits to append
194 */
195 void appendBits(size_t count, uint8_t bits)
196 {
197 // drain whole bytes out
198 drain(true);
199
200 // add in the new bits as the higher-order bits, filling LSBit first
201 fixed_uint_t<details::bitStreamSize> tmp = bits;
202 tmp <<= bitCount;
203 bitStream |= tmp;
204 bitCount += count;
205
206 // drain any whole bytes we have appended
207 drain(true);
208 }
209
210 /**
211 * @brief empty out the bucket and pack it as bytes LSB-first
212 *
213 * @param wholeBytesOnly - if true, only the whole bytes will be drained
214 */
215 void drain(bool wholeBytesOnly = false)
216 {
217 while (bitCount > 0)
218 {
219 uint8_t retVal;
220 if (bitCount < CHAR_BIT)
221 {
222 if (wholeBytesOnly)
223 {
224 break;
225 }
226 }
227 size_t bitsOut = std::min(static_cast<size_t>(CHAR_BIT), bitCount);
228 retVal = static_cast<uint8_t>(bitStream);
229 raw.push_back(retVal);
230 bitStream >>= bitsOut;
231 bitCount -= bitsOut;
232 }
233 }
234
235 // base empty pack
236 int pack()
237 {
238 return 0;
239 }
240
241 /**
242 * @brief pack arbitrary values (of any supported type) into the buffer
243 *
244 * @tparam Arg - the type of the first argument
245 * @tparam Args - the type of the optional remaining arguments
246 *
247 * @param arg - the first argument to pack
248 * @param args... - the optional remaining arguments to pack
249 *
250 * @return int - non-zero on pack errors
251 */
252 template <typename Arg, typename... Args>
253 int pack(Arg&& arg, Args&&... args)
254 {
255 int packRet =
256 details::PackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
257 if (packRet)
258 {
259 return packRet;
260 }
261 packRet = pack(std::forward<Args>(args)...);
262 drain();
263 return packRet;
264 }
265
William A. Kennington III92476a82019-04-25 01:32:30 -0700266 /**
267 * @brief Prepends another payload to this one
268 *
269 * Avoid using this unless absolutely required since it inserts into the
270 * front of the response payload.
271 *
272 * @param p - The payload to prepend
273 *
274 * @retunr int - non-zero on prepend errors
275 */
276 int prepend(const ipmi::message::Payload& p)
277 {
278 if (bitCount != 0 || p.bitCount != 0)
279 {
280 return 1;
281 }
282 raw.reserve(raw.size() + p.raw.size());
283 raw.insert(raw.begin(), p.raw.begin(), p.raw.end());
284 return 0;
285 }
286
Vernon Mauerye7329c72018-10-08 12:05:16 -0700287 /******************************************************************
288 * Request operations
289 *****************************************************************/
290 /**
291 * @brief pop a series of bytes from the raw buffer
292 *
293 * @tparam T - the type pointer to return; must be compatible to a byte
294 *
295 * @param count - the number of bytes to return
296 *
297 * @return - a tuple of pointers (begin,begin+count)
298 */
299 template <typename T>
300 auto pop(size_t count)
301 {
302 static_assert(
303 std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
304 std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
305 std::is_same_v<utility::TypeIdDowncast_t<T>, char>,
306 "T* must be signed or unsigned byte pointers");
307 // this interface only allows full-byte access; skip partial bits
308 if (bitCount)
309 {
310 // WARN on unused bits?
311 discardBits();
312 }
313 if (count <= (raw.size() - rawIndex))
314 {
315 auto range = std::make_tuple(
316 reinterpret_cast<T*>(raw.data() + rawIndex),
317 reinterpret_cast<T*>(raw.data() + rawIndex + count));
318 rawIndex += count;
319 return range;
320 }
321 unpackError = true;
322 return std::make_tuple(reinterpret_cast<T*>(NULL),
323 reinterpret_cast<T*>(NULL));
324 }
325
326 /**
327 * @brief fill bit stream with at least count bits for consumption
328 *
329 * @param count - number of bit needed
330 *
331 * @return - unpackError
332 */
333 bool fillBits(size_t count)
334 {
335 // add more bits to the top end of the bitstream
336 // so we consume bits least-significant first
337 if (count > (details::bitStreamSize - CHAR_BIT))
338 {
339 unpackError = true;
340 return unpackError;
341 }
342 while (bitCount < count)
343 {
344 if (rawIndex < raw.size())
345 {
346 fixed_uint_t<details::bitStreamSize> tmp = raw[rawIndex++];
347 tmp <<= bitCount;
348 bitStream |= tmp;
349 bitCount += CHAR_BIT;
350 }
351 else
352 {
353 // raw has run out of bytes to pop
354 unpackError = true;
355 return unpackError;
356 }
357 }
358 return false;
359 }
360
361 /**
362 * @brief consume count bits from bitstream (must call fillBits first)
363 *
364 * @param count - number of bit needed
365 *
366 * @return - count bits from stream
367 */
368 uint8_t popBits(size_t count)
369 {
370 if (bitCount < count)
371 {
372 unpackError = true;
373 return 0;
374 }
375 // consume bits low-order bits first
376 auto bits = bitStream.convert_to<uint8_t>();
377 bits &= ((1 << count) - 1);
378 bitStream >>= count;
379 bitCount -= count;
380 return bits;
381 }
382
383 /**
384 * @brief discard all partial bits
385 */
386 void discardBits()
387 {
388 bitStream = 0;
389 bitCount = 0;
390 }
391
392 /**
393 * @brief fully reset the unpack stream
394 */
395 void reset()
396 {
397 discardBits();
398 rawIndex = 0;
399 unpackError = false;
400 }
401
402 /**
403 * @brief check to see if the stream has been fully unpacked
404 *
405 * @return bool - true if the stream has been unpacked and has no errors
406 */
407 bool fullyUnpacked()
408 {
409 unpackCheck = true;
410 return raw.size() == rawIndex && bitCount == 0 && !unpackError;
411 }
412
413 // base empty unpack
414 int unpack()
415 {
416 return 0;
417 }
418
419 /**
420 * @brief unpack arbitrary values (of any supported type) from the buffer
421 *
422 * @tparam Arg - the type of the first argument
423 * @tparam Args - the type of the optional remaining arguments
424 *
425 * @param arg - the first argument to unpack
426 * @param args... - the optional remaining arguments to unpack
427 *
428 * @return int - non-zero for unpack error
429 */
430 template <typename Arg, typename... Args>
431 int unpack(Arg&& arg, Args&&... args)
432 {
433 int unpackRet =
434 details::UnpackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
435 if (unpackRet)
436 {
437 unpackError = true;
438 return unpackRet;
439 }
440 return unpack(std::forward<Args>(args)...);
441 }
442
443 /**
444 * @brief unpack a tuple of values (of any supported type) from the buffer
445 *
446 * This will unpack the elements of the tuple as if each one was passed in
447 * individually, as if passed into the above variadic function.
448 *
449 * @tparam Types - the implicitly declared list of the tuple element types
450 *
451 * @param t - the tuple of values to unpack
452 *
453 * @return int - non-zero on unpack error
454 */
455 template <typename... Types>
456 int unpack(std::tuple<Types...>& t)
457 {
458 // roll back checkpoint so that unpacking a tuple is atomic
459 size_t priorBitCount = bitCount;
460 size_t priorIndex = rawIndex;
461 fixed_uint_t<details::bitStreamSize> priorBits = bitStream;
462
463 int ret =
464 std::apply([this](Types&... args) { return unpack(args...); }, t);
465 if (ret)
466 {
467 bitCount = priorBitCount;
468 bitStream = priorBits;
469 rawIndex = priorIndex;
470 }
471
472 return ret;
473 }
474
475 // partial bytes in the form of bits
476 fixed_uint_t<details::bitStreamSize> bitStream;
477 size_t bitCount = 0;
478 std::vector<uint8_t> raw;
479 size_t rawIndex = 0;
William A. Kennington III51694c22019-04-24 01:44:44 -0700480 bool trailingOk = true;
481 bool unpackCheck = false;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700482 bool unpackError = false;
483};
484
485/**
486 * @brief high-level interface to an IPMI response
487 *
488 * Make it easy to just pack in the response args from the callback into a
489 * buffer that goes back to the requester.
490 */
491struct Response
492{
493 /* Define all of the basic class operations:
494 * Not allowed:
495 * - Default constructor to avoid nullptrs.
496 * Allowed:
497 * - Copy operations.
498 * - Move operations.
499 * - Destructor.
500 */
501 Response() = delete;
502 Response(const Response&) = default;
503 Response& operator=(const Response&) = default;
504 Response(Response&&) = default;
505 Response& operator=(Response&&) = default;
506 ~Response() = default;
507
508 using ptr = std::shared_ptr<Response>;
509
510 explicit Response(Context::ptr& context) :
511 payload(), ctx(context), cc(ccSuccess)
512 {
513 }
514
515 /**
516 * @brief pack arbitrary values (of any supported type) into the payload
517 *
518 * @tparam Args - the type of the optional arguments
519 *
520 * @param args... - the optional arguments to pack
521 *
522 * @return int - non-zero on pack errors
523 */
524 template <typename... Args>
525 int pack(Args&&... args)
526 {
527 return payload.pack(std::forward<Args>(args)...);
528 }
529
530 /**
531 * @brief pack a tuple of values (of any supported type) into the payload
532 *
533 * This will pack the elements of the tuple as if each one was passed in
534 * individually, as if passed into the above variadic function.
535 *
536 * @tparam Types - the implicitly declared list of the tuple element types
537 *
538 * @param t - the tuple of values to pack
539 *
540 * @return int - non-zero on pack errors
541 */
542 template <typename... Types>
543 int pack(std::tuple<Types...>& t)
544 {
545 return payload.pack(t);
546 }
547
William A. Kennington III92476a82019-04-25 01:32:30 -0700548 /**
549 * @brief Prepends another payload to this one
550 *
551 * Avoid using this unless absolutely required since it inserts into the
552 * front of the response payload.
553 *
554 * @param p - The payload to prepend
555 *
556 * @retunr int - non-zero on prepend errors
557 */
558 int prepend(const ipmi::message::Payload& p)
559 {
560 return payload.prepend(p);
561 }
562
Vernon Mauerye7329c72018-10-08 12:05:16 -0700563 Payload payload;
564 Context::ptr ctx;
565 Cc cc;
566};
567
568/**
569 * @brief high-level interface to an IPMI request
570 *
571 * Make it easy to unpack the buffer into the request args for the callback.
572 */
573struct Request
574{
575 /* Define all of the basic class operations:
576 * Not allowed:
577 * - Default constructor to avoid nullptrs.
578 * Allowed:
579 * - Copy operations.
580 * - Move operations.
581 * - Destructor.
582 */
583 Request() = delete;
584 Request(const Request&) = default;
585 Request& operator=(const Request&) = default;
586 Request(Request&&) = default;
587 Request& operator=(Request&&) = default;
588 ~Request() = default;
589
590 using ptr = std::shared_ptr<Request>;
591
592 explicit Request(Context::ptr context, std::vector<uint8_t>&& d) :
593 payload(std::forward<std::vector<uint8_t>>(d)), ctx(context)
594 {
595 }
596
597 /**
598 * @brief unpack arbitrary values (of any supported type) from the payload
599 *
600 * @tparam Args - the type of the optional arguments
601 *
602 * @param args... - the optional arguments to unpack
603 *
604 * @return int - non-zero for unpack error
605 */
606 template <typename... Args>
607 int unpack(Args&&... args)
608 {
609 int unpackRet = payload.unpack(std::forward<Args>(args)...);
Vernon Maueryf865dea2019-05-03 13:29:40 -0700610 if (unpackRet != ipmi::ccSuccess)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700611 {
Vernon Maueryf865dea2019-05-03 13:29:40 -0700612 // not all bits were consumed by requested parameters
613 return ipmi::ccReqDataLenInvalid;
614 }
615 if (!payload.trailingOk)
616 {
617 if (!payload.fullyUnpacked())
Vernon Mauerye7329c72018-10-08 12:05:16 -0700618 {
Vernon Maueryf865dea2019-05-03 13:29:40 -0700619 // not all bits were consumed by requested parameters
620 return ipmi::ccReqDataLenInvalid;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700621 }
622 }
Vernon Maueryf865dea2019-05-03 13:29:40 -0700623 return ipmi::ccSuccess;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700624 }
625
626 /**
627 * @brief unpack a tuple of values (of any supported type) from the payload
628 *
629 * This will unpack the elements of the tuple as if each one was passed in
630 * individually, as if passed into the above variadic function.
631 *
632 * @tparam Types - the implicitly declared list of the tuple element types
633 *
634 * @param t - the tuple of values to unpack
635 *
636 * @return int - non-zero on unpack error
637 */
638 template <typename... Types>
639 int unpack(std::tuple<Types...>& t)
640 {
641 return std::apply([this](Types&... args) { return unpack(args...); },
642 t);
643 }
644
645 /** @brief Create a response message that corresponds to this request
646 *
647 * @return A shared_ptr to the response message created
648 */
649 Response::ptr makeResponse()
650 {
651 return std::make_shared<Response>(ctx);
652 }
653
654 Payload payload;
655 Context::ptr ctx;
656};
657
658} // namespace message
659
660} // namespace ipmi
661
662// include packing and unpacking of types
663#include <ipmid/message/pack.hpp>
664#include <ipmid/message/unpack.hpp>