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