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 */
crc8bit(const uint8_t * ptr,const size_t len)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
Messagemessage::Message123 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 */
Messagemessage::Message139 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 */
getCommandmessage::Message169 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 */
createResponsemessage::Message195 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