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