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