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