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