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 */
crc8bit(const uint8_t * ptr,const size_t len)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
Messagemessage::Message121 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 * @brief Special behavior for copy constructor
130 *
131 * Based on incoming message state, the resulting message will have a
132 * pre-baked state. This is used to simplify the flows for creating a
133 * response message. For each pre-session state, the response message is
134 * actually a different type of message. Once the session has been
135 * established, the response type is the same as the request type.
136 */
Messagemessage::Message137 Message(const Message& other) :
138 isPacketEncrypted(other.isPacketEncrypted),
139 isPacketAuthenticated(other.isPacketAuthenticated),
140 payloadType(other.payloadType), rcSessionID(other.rcSessionID),
141 bmcSessionID(other.bmcSessionID), rmcpMsgClass(other.rmcpMsgClass)
142 {
143 // special behavior for rmcp+ session creation
144 if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType)
145 {
146 payloadType = PayloadType::OPEN_SESSION_RESPONSE;
147 }
148 else if (PayloadType::RAKP1 == other.payloadType)
149 {
150 payloadType = PayloadType::RAKP2;
151 }
152 else if (PayloadType::RAKP3 == other.payloadType)
153 {
154 payloadType = PayloadType::RAKP4;
155 }
156 }
157 Message& operator=(const Message&) = default;
158 Message(Message&&) = default;
159 Message& operator=(Message&&) = default;
160 ~Message() = default;
161
162 /**
163 * @brief Extract the command from the IPMI payload
164 *
165 * @return Command ID in the incoming message
166 */
getCommandmessage::Message167 uint32_t getCommand()
168 {
169 uint32_t command = 0;
170
171 command |= (static_cast<uint32_t>(payloadType) << 16);
172 if (payloadType == PayloadType::IPMI)
173 {
174 auto request =
175 reinterpret_cast<LAN::header::Request*>(payload.data());
176 command |= request->netfn << 8;
177 command |= static_cast<uint32_t>(request->cmd);
178 }
179 return command;
180 }
181
182 /**
183 * @brief Create the response IPMI message
184 *
185 * The IPMI outgoing message is constructed out of payload and the
186 * corresponding fields are populated. For the payload type IPMI, the
187 * LAN message header and trailer are added.
188 *
189 * @param[in] output - Payload for outgoing message
190 *
191 * @return Outgoing message on success and nullptr on failure
192 */
createResponsemessage::Message193 std::shared_ptr<Message> createResponse(std::vector<uint8_t>& output)
194 {
195 // SOL packets don't reply; return NULL
196 if (payloadType == PayloadType::SOL)
197 {
198 return nullptr;
199 }
200 auto outMessage = std::make_shared<Message>(*this);
201
202 if (payloadType == PayloadType::IPMI)
203 {
204 outMessage->payloadType = PayloadType::IPMI;
205
206 outMessage->payload.resize(sizeof(LAN::header::Response) +
207 output.size() +
208 sizeof(LAN::trailer::Response));
209
210 auto reqHeader =
211 reinterpret_cast<LAN::header::Request*>(payload.data());
212 auto respHeader = reinterpret_cast<LAN::header::Response*>(
213 outMessage->payload.data());
214
215 // Add IPMI LAN Message Response Header
216 respHeader->rqaddr = reqHeader->rqaddr;
217 respHeader->netfn = reqHeader->netfn | 0x04;
218 respHeader->cs = crc8bit(&(respHeader->rqaddr), 2);
219 respHeader->rsaddr = reqHeader->rsaddr;
220 respHeader->rqseq = reqHeader->rqseq;
221 respHeader->cmd = reqHeader->cmd;
222
223 auto assembledSize = sizeof(LAN::header::Response);
224
225 // Copy the output by the execution of the command
226 std::copy(output.begin(), output.end(),
227 outMessage->payload.begin() + assembledSize);
228 assembledSize += output.size();
229
230 // Add the IPMI LAN Message Trailer
231 auto trailer = reinterpret_cast<LAN::trailer::Response*>(
232 outMessage->payload.data() + assembledSize);
233 trailer->checksum = crc8bit(&respHeader->rsaddr, assembledSize - 3);
234 }
235 else
236 {
237 outMessage->payload = output;
238 }
239 return outMessage;
240 }
241
242 bool isPacketEncrypted; // Message's Encryption Status
243 bool isPacketAuthenticated; // Message's Authentication Status
244 PayloadType payloadType; // Type of message payload (IPMI,SOL ..etc)
245 uint32_t rcSessionID; // Remote Client's Session ID
246 uint32_t bmcSessionID; // BMC's session ID
247 uint32_t sessionSeqNum; // Session Sequence Number
248 ClassOfMsg rmcpMsgClass; // Class of Message
249 #ifdef RMCP_PING
250 uint8_t asfMsgTag; // ASF Message Tag
251 #endif // RMCP_PING
252
253 /** @brief Message payload
254 *
255 * “Payloads” are a capability specified for RMCP+ that enable an IPMI
256 * session to carry types of traffic that are in addition to IPMI Messages.
257 * Payloads can be ‘standard’ or ‘OEM’.Standard payload types include IPMI
258 * Messages, messages for session setup under RMCP+, and the payload for
259 * the “Serial Over LAN” capability introduced in IPMI v2.0.
260 */
261 std::vector<uint8_t> payload;
262 };
263
264 } // namespace message
265