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