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