1 #include "sol_context.hpp"
2 
3 #include "main.hpp"
4 #include "message_handler.hpp"
5 #include "sd_event_loop.hpp"
6 #include "sessions_manager.hpp"
7 #include "sol_manager.hpp"
8 
9 #include <errno.h>
10 
11 #include <phosphor-logging/lg2.hpp>
12 
13 namespace sol
14 {
15 using namespace phosphor::logging;
16 
17 Context::Context(std::shared_ptr<boost::asio::io_context> io,
18                  uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance,
19                  session::SessionID sessionID) :
20     accumulateTimer(*io),
21     retryTimer(*io), maxRetryCount(maxRetryCount), retryCounter(maxRetryCount),
22     sendThreshold(sendThreshold), payloadInstance(instance),
23     sessionID(sessionID)
24 {
25     session = session::Manager::get().getSession(sessionID);
26 }
27 
28 std::shared_ptr<Context>
29     Context::makeContext(std::shared_ptr<boost::asio::io_context> io,
30                          uint8_t maxRetryCount, uint8_t sendThreshold,
31                          uint8_t instance, session::SessionID sessionID)
32 {
33     auto ctx = std::make_shared<Context>(io, maxRetryCount, sendThreshold,
34                                          instance, sessionID);
35     ctx->enableAccumulateTimer(true);
36     return ctx;
37 }
38 
39 void Context::enableAccumulateTimer(bool enable)
40 {
41     // fetch the timeout from the SOL manager
42     std::chrono::microseconds interval = sol::Manager::get().accumulateInterval;
43 
44     if (enable)
45     {
46         auto bufferSize = sol::Manager::get().dataBuffer.size();
47         std::weak_ptr<Context> weakRef = weak_from_this();
48         if (bufferSize > sendThreshold)
49         {
50             getIo()->post([weakRef]() {
51                 std::shared_ptr<Context> self = weakRef.lock();
52                 if (self)
53                 {
54                     self->charAccTimerHandler();
55                 }
56             });
57             return;
58         }
59         accumulateTimer.expires_after(interval);
60         accumulateTimer.async_wait(
61             [weakRef](const boost::system::error_code& ec) {
62             std::shared_ptr<Context> self = weakRef.lock();
63             if (!ec && self)
64             {
65                 self->charAccTimerHandler();
66             }
67         });
68     }
69     else
70     {
71         accumulateTimer.cancel();
72     }
73 }
74 
75 void Context::enableRetryTimer(bool enable)
76 {
77     if (enable)
78     {
79         // fetch the timeout from the SOL manager
80         std::chrono::microseconds interval = sol::Manager::get().retryInterval;
81         retryTimer.expires_after(interval);
82         std::weak_ptr<Context> weakRef = weak_from_this();
83         retryTimer.async_wait([weakRef](const boost::system::error_code& ec) {
84             std::shared_ptr<Context> self = weakRef.lock();
85             if (!ec && self)
86             {
87                 self->retryTimerHandler();
88             }
89         });
90     }
91     else
92     {
93         retryTimer.cancel();
94     }
95 }
96 
97 void Context::processInboundPayload(uint8_t seqNum, uint8_t ackSeqNum,
98                                     uint8_t count, bool status, bool isBreak,
99                                     const std::vector<uint8_t>& input)
100 {
101     uint8_t respAckSeqNum = 0;
102     uint8_t acceptedCount = 0;
103     auto ack = false;
104 
105     /*
106      * Check if the Inbound sequence number is same as the expected one.
107      * If the Packet Sequence Number is 0, it is an ACK-Only packet. Multiple
108      * outstanding sequence numbers are not supported in this version of the SOL
109      * specification. Retried packets use the same sequence number as the first
110      * packet.
111      */
112     if (seqNum && (seqNum != seqNums.get(true)))
113     {
114         lg2::info("Out of sequence SOL packet - packet is dropped");
115         return;
116     }
117 
118     /*
119      * Check if the expected ACK/NACK sequence number is same as the
120      * ACK/NACK sequence number in the packet. If packet ACK/NACK sequence
121      * number is 0, then it is an informational packet. No request packet being
122      * ACK'd or NACK'd.
123      */
124     if (ackSeqNum && (ackSeqNum != seqNums.get(false)))
125     {
126         lg2::info("Out of sequence ack number - SOL packet is dropped");
127         return;
128     }
129 
130     /*
131      * Retry the SOL payload packet in the following conditions:
132      *
133      * a) NACK in Operation/Status
134      * b) Accepted Character Count does not match with the sent out SOL payload
135      * c) Non-zero Packet ACK/NACK Sequence Number
136      */
137     if (status || ((count != expectedCharCount) && ackSeqNum))
138     {
139         resendPayload(noClear);
140         enableRetryTimer(false);
141         enableRetryTimer(true);
142         return;
143     }
144     /*
145      * Clear the sent data once the acknowledgment sequence number matches
146      * and the expected character count matches.
147      */
148     else if ((count == expectedCharCount) && ackSeqNum)
149     {
150         // Clear the Host Console Buffer
151         sol::Manager::get().dataBuffer.erase(count);
152 
153         // Once it is acknowledged stop the retry interval timer
154         enableRetryTimer(false);
155 
156         retryCounter = maxRetryCount;
157         expectedCharCount = 0;
158         payloadCache.clear();
159     }
160 
161     if (isBreak && seqNum)
162     {
163         lg2::info("Writing break to console socket descriptor");
164         constexpr uint8_t sysrqValue = 72; // use this to notify sol server
165         const std::vector<uint8_t> test{sysrqValue};
166         auto ret = sol::Manager::get().writeConsoleSocket(test, isBreak);
167         if (ret)
168         {
169             lg2::error("Writing to console socket descriptor failed: {ERROR}",
170                        "ERROR", strerror(errno));
171         }
172     }
173 
174     isBreak = false;
175     // Write character data to the Host Console
176     if (!input.empty() && seqNum)
177     {
178         auto rc = sol::Manager::get().writeConsoleSocket(input, isBreak);
179         if (rc)
180         {
181             lg2::error("Writing to console socket descriptor failed: {ERROR}",
182                        "ERROR", strerror(errno));
183             ack = true;
184         }
185         else
186         {
187             respAckSeqNum = seqNum;
188             ack = false;
189             acceptedCount = input.size();
190         }
191     }
192     /*
193      * SOL payload with no character data and valid sequence number can be used
194      * as method to keep the SOL session active.
195      */
196     else if (input.empty() && seqNum)
197     {
198         respAckSeqNum = seqNum;
199     }
200 
201     if (seqNum != 0)
202     {
203         seqNums.incInboundSeqNum();
204         prepareResponse(respAckSeqNum, acceptedCount, ack);
205     }
206     else
207     {
208         enableAccumulateTimer(true);
209     }
210 }
211 
212 void Context::prepareResponse(uint8_t ackSeqNum, uint8_t count, bool ack)
213 {
214     auto bufferSize = sol::Manager::get().dataBuffer.size();
215 
216     /* Sent a ACK only response */
217     if (payloadCache.size() != 0 || (bufferSize < sendThreshold))
218     {
219         enableAccumulateTimer(true);
220 
221         std::vector<uint8_t> outPayload(sizeof(Payload));
222         auto response = reinterpret_cast<Payload*>(outPayload.data());
223         response->packetSeqNum = 0;
224         response->packetAckSeqNum = ackSeqNum;
225         response->acceptedCharCount = count;
226         response->outOperation.ack = ack;
227         sendPayload(outPayload);
228         return;
229     }
230 
231     auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE);
232     payloadCache.resize(sizeof(Payload) + readSize);
233     auto response = reinterpret_cast<Payload*>(payloadCache.data());
234     response->packetAckSeqNum = ackSeqNum;
235     response->acceptedCharCount = count;
236     response->outOperation.ack = ack;
237     response->packetSeqNum = seqNums.incOutboundSeqNum();
238 
239     auto handle = sol::Manager::get().dataBuffer.read();
240     std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
241     expectedCharCount = readSize;
242 
243     enableRetryTimer(true);
244     enableAccumulateTimer(false);
245 
246     sendPayload(payloadCache);
247 }
248 
249 int Context::sendOutboundPayload()
250 {
251     if (payloadCache.size() != 0)
252     {
253         enableAccumulateTimer(true);
254         return -1;
255     }
256 
257     auto bufferSize = sol::Manager::get().dataBuffer.size();
258     auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE);
259 
260     payloadCache.resize(sizeof(Payload) + readSize);
261     auto response = reinterpret_cast<Payload*>(payloadCache.data());
262     response->packetAckSeqNum = 0;
263     response->acceptedCharCount = 0;
264     response->outOperation.ack = false;
265     response->packetSeqNum = seqNums.incOutboundSeqNum();
266 
267     auto handle = sol::Manager::get().dataBuffer.read();
268     std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
269     expectedCharCount = readSize;
270 
271     enableRetryTimer(true);
272     enableAccumulateTimer(false);
273 
274     sendPayload(payloadCache);
275 
276     return 0;
277 }
278 
279 void Context::resendPayload(bool clear)
280 {
281     sendPayload(payloadCache);
282 
283     if (clear)
284     {
285         payloadCache.clear();
286         expectedCharCount = 0;
287         sol::Manager::get().dataBuffer.erase(expectedCharCount);
288     }
289 }
290 
291 void Context::sendPayload(const std::vector<uint8_t>& out) const
292 {
293     message::Handler msgHandler(session->channelPtr, sessionID);
294 
295     msgHandler.sendSOLPayload(out);
296 }
297 
298 void Context::charAccTimerHandler()
299 {
300     auto bufferSize = sol::Manager::get().dataBuffer.size();
301 
302     try
303     {
304         if (bufferSize > 0)
305         {
306             int rc = sendOutboundPayload();
307             if (rc == 0)
308             {
309                 return;
310             }
311         }
312         enableAccumulateTimer(true);
313     }
314     catch (const std::exception& e)
315     {
316         lg2::error("Failed to call the sendOutboundPayload method: {ERROR}",
317                    "ERROR", e);
318     }
319 }
320 
321 void Context::retryTimerHandler()
322 {
323     try
324     {
325         if (retryCounter)
326         {
327             --retryCounter;
328             enableRetryTimer(true);
329             resendPayload(sol::Context::noClear);
330         }
331         else
332         {
333             retryCounter = maxRetryCount;
334             resendPayload(sol::Context::clear);
335             enableRetryTimer(false);
336             enableAccumulateTimer(true);
337         }
338     }
339     catch (const std::exception& e)
340     {
341         lg2::error("Failed to retry timer: {ERROR}", "ERROR", e);
342     }
343 }
344 } // namespace sol
345