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