| #pragma once |
| |
| #include <cstddef> |
| #include <memory> |
| #include <numeric> |
| #include <vector> |
| |
| namespace message |
| { |
| |
| enum class PayloadType : uint8_t |
| { |
| IPMI = 0x00, |
| SOL = 0x01, |
| OPEN_SESSION_REQUEST = 0x10, |
| OPEN_SESSION_RESPONSE = 0x11, |
| RAKP1 = 0x12, |
| RAKP2 = 0x13, |
| RAKP3 = 0x14, |
| RAKP4 = 0x15, |
| INVALID = 0xFF, |
| }; |
| |
| // RMCP Classes of Message as per section 13.1.3. |
| enum class ClassOfMsg : uint8_t |
| { |
| RESERVED = 0x05, |
| ASF = 0x06, |
| IPMI = 0x07, |
| OEM = 0x08, |
| }; |
| |
| #ifdef RMCP_PING |
| // RMCP Message Type as per section 13.1.3. |
| enum class RmcpMsgType : uint8_t |
| { |
| PING = 0x80, |
| PONG = 0x40, |
| }; |
| #endif // RMCP_PING |
| |
| namespace LAN |
| { |
| |
| constexpr uint8_t requesterBMCAddress = 0x20; |
| constexpr uint8_t responderBMCAddress = 0x81; |
| |
| namespace header |
| { |
| |
| /** |
| * @struct IPMI LAN Message Request Header |
| */ |
| struct Request |
| { |
| uint8_t rsaddr; |
| uint8_t netfn; |
| uint8_t cs; |
| uint8_t rqaddr; |
| uint8_t rqseq; |
| uint8_t cmd; |
| } __attribute__((packed)); |
| |
| /** |
| * @struct IPMI LAN Message Response Header |
| */ |
| struct Response |
| { |
| uint8_t rqaddr; |
| uint8_t netfn; |
| uint8_t cs; |
| uint8_t rsaddr; |
| uint8_t rqseq; |
| uint8_t cmd; |
| } __attribute__((packed)); |
| |
| } // namespace header |
| |
| namespace trailer |
| { |
| |
| /** |
| * @struct IPMI LAN Message Trailer |
| */ |
| struct Request |
| { |
| uint8_t checksum; |
| } __attribute__((packed)); |
| |
| using Response = Request; |
| |
| } // namespace trailer |
| |
| } // namespace LAN |
| |
| /** |
| * @brief Calculate 8 bit 2's complement checksum |
| * |
| * Initialize checksum to 0. For each byte, checksum = (checksum + byte) |
| * modulo 256. Then checksum = - checksum. When the checksum and the |
| * bytes are added together, modulo 256, the result should be 0. |
| */ |
| static inline uint8_t crc8bit(const uint8_t* ptr, const size_t len) |
| { |
| return (0x100 - std::accumulate(ptr, ptr + len, 0)); |
| } |
| |
| /** |
| * @struct Message |
| * |
| * IPMI message is data encapsulated in an IPMI Session packet. The IPMI |
| * Session packets are encapsulated in RMCP packets, which are encapsulated in |
| * UDP datagrams. Refer Section 13.5 of IPMI specification(IPMI Messages |
| * Encapsulation Under RMCP). IPMI payload is a special class of data |
| * encapsulated in an IPMI session packet. |
| */ |
| struct Message |
| { |
| static constexpr uint32_t MESSAGE_INVALID_SESSION_ID = 0xBADBADFF; |
| |
| Message() : |
| payloadType(PayloadType::INVALID), |
| rcSessionID(Message::MESSAGE_INVALID_SESSION_ID), |
| bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID), |
| rmcpMsgClass(ClassOfMsg::RESERVED) |
| {} |
| |
| /** |
| * @brief Special behavior for copy constructor |
| * |
| * Based on incoming message state, the resulting message will have a |
| * pre-baked state. This is used to simplify the flows for creating a |
| * response message. For each pre-session state, the response message is |
| * actually a different type of message. Once the session has been |
| * established, the response type is the same as the request type. |
| */ |
| Message(const Message& other) : |
| isPacketEncrypted(other.isPacketEncrypted), |
| isPacketAuthenticated(other.isPacketAuthenticated), |
| payloadType(other.payloadType), rcSessionID(other.rcSessionID), |
| bmcSessionID(other.bmcSessionID), rmcpMsgClass(other.rmcpMsgClass) |
| { |
| // special behavior for rmcp+ session creation |
| if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType) |
| { |
| payloadType = PayloadType::OPEN_SESSION_RESPONSE; |
| } |
| else if (PayloadType::RAKP1 == other.payloadType) |
| { |
| payloadType = PayloadType::RAKP2; |
| } |
| else if (PayloadType::RAKP3 == other.payloadType) |
| { |
| payloadType = PayloadType::RAKP4; |
| } |
| } |
| Message& operator=(const Message&) = default; |
| Message(Message&&) = default; |
| Message& operator=(Message&&) = default; |
| ~Message() = default; |
| |
| /** |
| * @brief Extract the command from the IPMI payload |
| * |
| * @return Command ID in the incoming message |
| */ |
| uint32_t getCommand() |
| { |
| uint32_t command = 0; |
| |
| command |= (static_cast<uint32_t>(payloadType) << 16); |
| if (payloadType == PayloadType::IPMI) |
| { |
| auto request = |
| reinterpret_cast<LAN::header::Request*>(payload.data()); |
| command |= request->netfn << 8; |
| command |= static_cast<uint32_t>(request->cmd); |
| } |
| return command; |
| } |
| |
| /** |
| * @brief Create the response IPMI message |
| * |
| * The IPMI outgoing message is constructed out of payload and the |
| * corresponding fields are populated. For the payload type IPMI, the |
| * LAN message header and trailer are added. |
| * |
| * @param[in] output - Payload for outgoing message |
| * |
| * @return Outgoing message on success and nullptr on failure |
| */ |
| std::shared_ptr<Message> createResponse(std::vector<uint8_t>& output) |
| { |
| // SOL packets don't reply; return NULL |
| if (payloadType == PayloadType::SOL) |
| { |
| return nullptr; |
| } |
| auto outMessage = std::make_shared<Message>(*this); |
| |
| if (payloadType == PayloadType::IPMI) |
| { |
| outMessage->payloadType = PayloadType::IPMI; |
| |
| outMessage->payload.resize(sizeof(LAN::header::Response) + |
| output.size() + |
| sizeof(LAN::trailer::Response)); |
| |
| auto reqHeader = |
| reinterpret_cast<LAN::header::Request*>(payload.data()); |
| auto respHeader = reinterpret_cast<LAN::header::Response*>( |
| outMessage->payload.data()); |
| |
| // Add IPMI LAN Message Response Header |
| respHeader->rqaddr = reqHeader->rqaddr; |
| respHeader->netfn = reqHeader->netfn | 0x04; |
| respHeader->cs = crc8bit(&(respHeader->rqaddr), 2); |
| respHeader->rsaddr = reqHeader->rsaddr; |
| respHeader->rqseq = reqHeader->rqseq; |
| respHeader->cmd = reqHeader->cmd; |
| |
| auto assembledSize = sizeof(LAN::header::Response); |
| |
| // Copy the output by the execution of the command |
| std::copy(output.begin(), output.end(), |
| outMessage->payload.begin() + assembledSize); |
| assembledSize += output.size(); |
| |
| // Add the IPMI LAN Message Trailer |
| auto trailer = reinterpret_cast<LAN::trailer::Response*>( |
| outMessage->payload.data() + assembledSize); |
| trailer->checksum = crc8bit(&respHeader->rsaddr, assembledSize - 3); |
| } |
| else |
| { |
| outMessage->payload = output; |
| } |
| return outMessage; |
| } |
| |
| bool isPacketEncrypted; // Message's Encryption Status |
| bool isPacketAuthenticated; // Message's Authentication Status |
| PayloadType payloadType; // Type of message payload (IPMI,SOL ..etc) |
| uint32_t rcSessionID; // Remote Client's Session ID |
| uint32_t bmcSessionID; // BMC's session ID |
| uint32_t sessionSeqNum; // Session Sequence Number |
| ClassOfMsg rmcpMsgClass; // Class of Message |
| #ifdef RMCP_PING |
| uint8_t asfMsgTag; // ASF Message Tag |
| #endif // RMCP_PING |
| |
| /** @brief Message payload |
| * |
| * “Payloads” are a capability specified for RMCP+ that enable an IPMI |
| * session to carry types of traffic that are in addition to IPMI Messages. |
| * Payloads can be ‘standard’ or ‘OEM’.Standard payload types include IPMI |
| * Messages, messages for session setup under RMCP+, and the payload for |
| * the “Serial Over LAN” capability introduced in IPMI v2.0. |
| */ |
| std::vector<uint8_t> payload; |
| }; |
| |
| } // namespace message |