1 #pragma once 2 3 #include <cstddef> 4 #include <memory> 5 #include <numeric> 6 #include <vector> 7 8 namespace message 9 { 10 11 enum class PayloadType : uint8_t 12 { 13 IPMI = 0x00, 14 SOL = 0x01, 15 OPEN_SESSION_REQUEST = 0x10, 16 OPEN_SESSION_RESPONSE = 0x11, 17 RAKP1 = 0x12, 18 RAKP2 = 0x13, 19 RAKP3 = 0x14, 20 RAKP4 = 0x15, 21 INVALID = 0xFF, 22 }; 23 24 namespace LAN 25 { 26 27 constexpr uint8_t requesterBMCAddress = 0x20; 28 constexpr uint8_t responderBMCAddress = 0x81; 29 30 namespace header 31 { 32 33 /** 34 * @struct IPMI LAN Message Request Header 35 */ 36 struct Request 37 { 38 uint8_t rsaddr; 39 uint8_t netfn; 40 uint8_t cs; 41 uint8_t rqaddr; 42 uint8_t rqseq; 43 uint8_t cmd; 44 } __attribute__((packed)); 45 46 /** 47 * @struct IPMI LAN Message Response Header 48 */ 49 struct Response 50 { 51 uint8_t rqaddr; 52 uint8_t netfn; 53 uint8_t cs; 54 uint8_t rsaddr; 55 uint8_t rqseq; 56 uint8_t cmd; 57 } __attribute__((packed)); 58 59 } // namespace header 60 61 namespace trailer 62 { 63 64 /** 65 * @struct IPMI LAN Message Trailer 66 */ 67 struct Request 68 { 69 uint8_t checksum; 70 } __attribute__((packed)); 71 72 using Response = Request; 73 74 } // namespace trailer 75 76 } // namespace LAN 77 78 /** 79 * @brief Calculate 8 bit 2's complement checksum 80 * 81 * Initialize checksum to 0. For each byte, checksum = (checksum + byte) 82 * modulo 256. Then checksum = - checksum. When the checksum and the 83 * bytes are added together, modulo 256, the result should be 0. 84 */ 85 static inline uint8_t crc8bit(const uint8_t* ptr, const size_t len) 86 { 87 return (0x100 - std::accumulate(ptr, ptr + len, 0)); 88 } 89 90 /** 91 * @struct Message 92 * 93 * IPMI message is data encapsulated in an IPMI Session packet. The IPMI 94 * Session packets are encapsulated in RMCP packets, which are encapsulated in 95 * UDP datagrams. Refer Section 13.5 of IPMI specification(IPMI Messages 96 * Encapsulation Under RMCP). IPMI payload is a special class of data 97 * encapsulated in an IPMI session packet. 98 */ 99 struct Message 100 { 101 static constexpr uint32_t MESSAGE_INVALID_SESSION_ID = 0xBADBADFF; 102 103 Message() : 104 payloadType(PayloadType::INVALID), 105 rcSessionID(Message::MESSAGE_INVALID_SESSION_ID), 106 bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID) 107 { 108 } 109 110 /** 111 * @brief Special behavior for copy constructor 112 * 113 * Based on incoming message state, the resulting message will have a 114 * pre-baked state. This is used to simplify the flows for creating a 115 * response message. For each pre-session state, the response message is 116 * actually a different type of message. Once the session has been 117 * established, the response type is the same as the request type. 118 */ 119 Message(const Message& other) : 120 isPacketEncrypted(other.isPacketEncrypted), 121 isPacketAuthenticated(other.isPacketAuthenticated), 122 payloadType(other.payloadType), rcSessionID(other.rcSessionID), 123 bmcSessionID(other.bmcSessionID) 124 { 125 // special behavior for rmcp+ session creation 126 if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType) 127 { 128 payloadType = PayloadType::OPEN_SESSION_RESPONSE; 129 } 130 else if (PayloadType::RAKP1 == other.payloadType) 131 { 132 payloadType = PayloadType::RAKP2; 133 } 134 else if (PayloadType::RAKP3 == other.payloadType) 135 { 136 payloadType = PayloadType::RAKP4; 137 } 138 } 139 Message& operator=(const Message&) = default; 140 Message(Message&&) = default; 141 Message& operator=(Message&&) = default; 142 ~Message() = default; 143 144 /** 145 * @brief Extract the command from the IPMI payload 146 * 147 * @return Command ID in the incoming message 148 */ 149 uint32_t getCommand() 150 { 151 uint32_t command = 0; 152 153 command |= (static_cast<uint8_t>(payloadType) << 16); 154 if (payloadType == PayloadType::IPMI) 155 { 156 auto request = 157 reinterpret_cast<LAN::header::Request*>(payload.data()); 158 command |= request->netfn << 8; 159 command |= request->cmd; 160 } 161 return command; 162 } 163 164 /** 165 * @brief Create the response IPMI message 166 * 167 * The IPMI outgoing message is constructed out of payload and the 168 * corresponding fields are populated. For the payload type IPMI, the 169 * LAN message header and trailer are added. 170 * 171 * @param[in] output - Payload for outgoing message 172 * 173 * @return Outgoing message on success and nullptr on failure 174 */ 175 std::shared_ptr<Message> createResponse(std::vector<uint8_t>& output) 176 { 177 // SOL packets don't reply; return NULL 178 if (payloadType == PayloadType::SOL) 179 { 180 return nullptr; 181 } 182 auto outMessage = std::make_shared<Message>(*this); 183 184 if (payloadType == PayloadType::IPMI) 185 { 186 outMessage->payloadType = PayloadType::IPMI; 187 188 outMessage->payload.resize(sizeof(LAN::header::Response) + 189 output.size() + 190 sizeof(LAN::trailer::Response)); 191 192 auto reqHeader = 193 reinterpret_cast<LAN::header::Request*>(payload.data()); 194 auto respHeader = reinterpret_cast<LAN::header::Response*>( 195 outMessage->payload.data()); 196 197 // Add IPMI LAN Message Response Header 198 respHeader->rqaddr = reqHeader->rqaddr; 199 respHeader->netfn = reqHeader->netfn | 0x04; 200 respHeader->cs = crc8bit(&(respHeader->rqaddr), 2); 201 respHeader->rsaddr = reqHeader->rsaddr; 202 respHeader->rqseq = reqHeader->rqseq; 203 respHeader->cmd = reqHeader->cmd; 204 205 auto assembledSize = sizeof(LAN::header::Response); 206 207 // Copy the output by the execution of the command 208 std::copy(output.begin(), output.end(), 209 outMessage->payload.begin() + assembledSize); 210 assembledSize += output.size(); 211 212 // Add the IPMI LAN Message Trailer 213 auto trailer = reinterpret_cast<LAN::trailer::Response*>( 214 outMessage->payload.data() + assembledSize); 215 trailer->checksum = crc8bit(&respHeader->rsaddr, assembledSize - 3); 216 } 217 else 218 { 219 outMessage->payload = output; 220 } 221 return outMessage; 222 } 223 224 bool isPacketEncrypted; // Message's Encryption Status 225 bool isPacketAuthenticated; // Message's Authentication Status 226 PayloadType payloadType; // Type of message payload (IPMI,SOL ..etc) 227 uint32_t rcSessionID; // Remote Client's Session ID 228 uint32_t bmcSessionID; // BMC's session ID 229 uint32_t sessionSeqNum; // Session Sequence Number 230 231 /** @brief Message payload 232 * 233 * “Payloads” are a capability specified for RMCP+ that enable an IPMI 234 * session to carry types of traffic that are in addition to IPMI Messages. 235 * Payloads can be ‘standard’ or ‘OEM’.Standard payload types include IPMI 236 * Messages, messages for session setup under RMCP+, and the payload for 237 * the “Serial Over LAN” capability introduced in IPMI v2.0. 238 */ 239 std::vector<uint8_t> payload; 240 }; 241 242 } // namespace message 243