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