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