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