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