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