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