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