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