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