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