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