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