#include "sol_context.hpp" #include "main.hpp" #include "message_handler.hpp" #include "sd_event_loop.hpp" #include "sessions_manager.hpp" #include "sol_manager.hpp" #include #include namespace sol { using namespace phosphor::logging; Context::Context(std::shared_ptr io, uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance, session::SessionID sessionID) : accumulateTimer(*io), retryTimer(*io), maxRetryCount(maxRetryCount), retryCounter(maxRetryCount), sendThreshold(sendThreshold), payloadInstance(instance), sessionID(sessionID) { session = session::Manager::get().getSession(sessionID); } std::shared_ptr Context::makeContext(std::shared_ptr io, uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance, session::SessionID sessionID) { auto ctx = std::make_shared(io, maxRetryCount, sendThreshold, instance, sessionID); ctx->enableAccumulateTimer(true); return ctx; } void Context::enableAccumulateTimer(bool enable) { // fetch the timeout from the SOL manager std::chrono::microseconds interval = sol::Manager::get().accumulateInterval; if (enable) { auto bufferSize = sol::Manager::get().dataBuffer.size(); if (bufferSize > sendThreshold) { charAccTimerHandler(); return; } accumulateTimer.expires_after(interval); std::weak_ptr weakRef = weak_from_this(); accumulateTimer.async_wait( [weakRef](const boost::system::error_code& ec) { std::shared_ptr self = weakRef.lock(); if (!ec && self) { self->charAccTimerHandler(); } }); } else { accumulateTimer.cancel(); } } void Context::enableRetryTimer(bool enable) { if (enable) { // fetch the timeout from the SOL manager std::chrono::microseconds interval = sol::Manager::get().retryInterval; retryTimer.expires_after(interval); std::weak_ptr weakRef = weak_from_this(); retryTimer.async_wait([weakRef](const boost::system::error_code& ec) { std::shared_ptr self = weakRef.lock(); if (!ec && self) { self->retryTimerHandler(); } }); } else { retryTimer.cancel(); } } void Context::processInboundPayload(uint8_t seqNum, uint8_t ackSeqNum, uint8_t count, bool status, bool isBreak, const std::vector& input) { uint8_t respAckSeqNum = 0; uint8_t acceptedCount = 0; auto ack = false; /* * Check if the Inbound sequence number is same as the expected one. * If the Packet Sequence Number is 0, it is an ACK-Only packet. Multiple * outstanding sequence numbers are not supported in this version of the SOL * specification. Retried packets use the same sequence number as the first * packet. */ if (seqNum && (seqNum != seqNums.get(true))) { lg2::info("Out of sequence SOL packet - packet is dropped"); return; } /* * Check if the expected ACK/NACK sequence number is same as the * ACK/NACK sequence number in the packet. If packet ACK/NACK sequence * number is 0, then it is an informational packet. No request packet being * ACK'd or NACK'd. */ if (ackSeqNum && (ackSeqNum != seqNums.get(false))) { lg2::info("Out of sequence ack number - SOL packet is dropped"); return; } /* * Retry the SOL payload packet in the following conditions: * * a) NACK in Operation/Status * b) Accepted Character Count does not match with the sent out SOL payload * c) Non-zero Packet ACK/NACK Sequence Number */ if (status || ((count != expectedCharCount) && ackSeqNum)) { resendPayload(noClear); enableRetryTimer(false); enableRetryTimer(true); return; } /* * Clear the sent data once the acknowledgment sequence number matches * and the expected character count matches. */ else if ((count == expectedCharCount) && ackSeqNum) { // Clear the Host Console Buffer sol::Manager::get().dataBuffer.erase(count); // Once it is acknowledged stop the retry interval timer enableRetryTimer(false); retryCounter = maxRetryCount; expectedCharCount = 0; payloadCache.clear(); } if (isBreak && seqNum) { lg2::info("Writing break to console socket descriptor"); constexpr uint8_t sysrqValue = 72; // use this to notify sol server const std::vector test{sysrqValue}; auto ret = sol::Manager::get().writeConsoleSocket(test, isBreak); if (ret) { lg2::error("Writing to console socket descriptor failed: {ERROR}", "ERROR", strerror(errno)); } } isBreak = false; // Write character data to the Host Console if (!input.empty() && seqNum) { auto rc = sol::Manager::get().writeConsoleSocket(input, isBreak); if (rc) { lg2::error("Writing to console socket descriptor failed: {ERROR}", "ERROR", strerror(errno)); ack = true; } else { respAckSeqNum = seqNum; ack = false; acceptedCount = input.size(); } } /* * SOL payload with no character data and valid sequence number can be used * as method to keep the SOL session active. */ else if (input.empty() && seqNum) { respAckSeqNum = seqNum; } if (seqNum != 0) { seqNums.incInboundSeqNum(); prepareResponse(respAckSeqNum, acceptedCount, ack); } else { enableAccumulateTimer(true); } } void Context::prepareResponse(uint8_t ackSeqNum, uint8_t count, bool ack) { auto bufferSize = sol::Manager::get().dataBuffer.size(); /* Sent a ACK only response */ if (payloadCache.size() != 0 || (bufferSize < sendThreshold)) { enableAccumulateTimer(true); std::vector outPayload(sizeof(Payload)); auto response = reinterpret_cast(outPayload.data()); response->packetSeqNum = 0; response->packetAckSeqNum = ackSeqNum; response->acceptedCharCount = count; response->outOperation.ack = ack; sendPayload(outPayload); return; } auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE); payloadCache.resize(sizeof(Payload) + readSize); auto response = reinterpret_cast(payloadCache.data()); response->packetAckSeqNum = ackSeqNum; response->acceptedCharCount = count; response->outOperation.ack = ack; response->packetSeqNum = seqNums.incOutboundSeqNum(); auto handle = sol::Manager::get().dataBuffer.read(); std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload)); expectedCharCount = readSize; enableRetryTimer(true); enableAccumulateTimer(false); sendPayload(payloadCache); } int Context::sendOutboundPayload() { if (payloadCache.size() != 0) { enableAccumulateTimer(true); return -1; } auto bufferSize = sol::Manager::get().dataBuffer.size(); auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE); payloadCache.resize(sizeof(Payload) + readSize); auto response = reinterpret_cast(payloadCache.data()); response->packetAckSeqNum = 0; response->acceptedCharCount = 0; response->outOperation.ack = false; response->packetSeqNum = seqNums.incOutboundSeqNum(); auto handle = sol::Manager::get().dataBuffer.read(); std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload)); expectedCharCount = readSize; enableRetryTimer(true); enableAccumulateTimer(false); sendPayload(payloadCache); return 0; } void Context::resendPayload(bool clear) { sendPayload(payloadCache); if (clear) { payloadCache.clear(); expectedCharCount = 0; sol::Manager::get().dataBuffer.erase(expectedCharCount); } } void Context::sendPayload(const std::vector& out) const { message::Handler msgHandler(session->channelPtr, sessionID); msgHandler.sendSOLPayload(out); } void Context::charAccTimerHandler() { auto bufferSize = sol::Manager::get().dataBuffer.size(); try { if (bufferSize > 0) { int rc = sendOutboundPayload(); if (rc == 0) { return; } } enableAccumulateTimer(true); } catch (const std::exception& e) { lg2::error("Failed to call the sendOutboundPayload method: {ERROR}", "ERROR", e); } } void Context::retryTimerHandler() { try { if (retryCounter) { --retryCounter; enableRetryTimer(true); resendPayload(sol::Context::noClear); } else { retryCounter = maxRetryCount; resendPayload(sol::Context::clear); enableRetryTimer(false); enableAccumulateTimer(true); } } catch (const std::exception& e) { lg2::error("Failed to retry timer: {ERROR}", "ERROR", e); } } } // namespace sol