xref: /openbmc/phosphor-net-ipmid/message.hpp (revision ddba9d15)
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