#include "ppsspp_config.h" #include #include #include #include "Common/Net/SocketCompat.h" #include "Common/Net/Sinks.h" #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/File/FileDescriptor.h" #ifndef MSG_NOSIGNAL // Default value to 0x00 (do nothing) in systems where it's not supported #define MSG_NOSIGNAL 0 #endif namespace net { InputSink::InputSink(size_t fd) : fd_(fd), read_(0), write_(0), valid_(0) { fd_util::SetNonBlocking((int)fd_, true); } bool InputSink::ReadLineWithEnding(std::string &s) { size_t newline = FindNewline(); if (newline == BUFFER_SIZE) { Block(); newline = FindNewline(); } if (newline == BUFFER_SIZE) { // Timed out. return false; } s.resize(newline + 1); if (read_ + newline + 1 > BUFFER_SIZE) { // Need to do two reads. size_t chunk1 = BUFFER_SIZE - read_; size_t chunk2 = read_ + newline + 1 - BUFFER_SIZE; memcpy(&s[0], buf_ + read_, chunk1); memcpy(&s[chunk1], buf_, chunk2); } else { memcpy(&s[0], buf_ + read_, newline + 1); } AccountDrain(newline + 1); return true; } std::pair InputSink::BufferParts() const { if (read_ + valid_ <= BUFFER_SIZE) { return {std::string_view(buf_ + read_, valid_), std::string_view()}; } else { size_t firstPartSize = BUFFER_SIZE - read_; size_t secondPartSize = valid_ - firstPartSize; return {std::string_view(buf_ + read_, firstPartSize), std::string_view(buf_, secondPartSize)}; } } size_t InputSink::ReadBinaryUntilTerminator(char *dest, size_t bufSize, std::string_view terminator, bool *didReadTerminator) { Fill(); auto [part1, part2] = BufferParts(); size_t offset = SplitSearch(terminator, part1, part2); if (offset == std::string_view::npos) { *didReadTerminator = false; // Not found, read as much as we can - but leave space for the terminator const s64 toRead = std::min((s64)valid_, (s64)bufSize - (s64)terminator.length()); TakeExact(dest, toRead); return toRead; } else { // Terminator found! Read right up to it, and then skip it. *didReadTerminator = true; _dbg_assert_(offset < valid_); TakeExact(dest, offset); Skip(terminator.size()); _dbg_assert_(valid_ >= 0); return offset; } } std::string InputSink::ReadLineWithEnding() { std::string s; ReadLineWithEnding(s); return s; } bool InputSink::ReadLine(std::string &s) { bool result = ReadLineWithEnding(s); if (result) { size_t l = s.length(); if (l >= 2 && s[l - 2] == '\r' && s[l - 1] == '\n') { s.resize(l - 2); } else if (l >= 1 && s[l - 1] == '\n') { s.resize(l - 1); } } return result; } std::string InputSink::ReadLine() { std::string s; ReadLine(s); return s; } size_t InputSink::FindNewline() const { // Technically, \r\n, but most parsers are lax... let's follow suit. size_t until_end = std::min(valid_, BUFFER_SIZE - read_); for (const char *p = buf_ + read_, *end = buf_ + read_ + until_end; p < end; ++p) { if (*p == '\n') { return p - (buf_ + read_); } } // Were there more bytes wrapped around? if (read_ + valid_ > BUFFER_SIZE) { size_t wrapped = read_ + valid_ - BUFFER_SIZE; for (const char *p = buf_, *end = buf_ + wrapped; p < end; ++p) { if (*p == '\n') { // Offset by the skipped portion before wrapping. return (p - buf_) + until_end; } } } // Never found, return an invalid position to indicate. return BUFFER_SIZE; } bool InputSink::TakeExact(char *buf, size_t bytes) { while (bytes > 0) { size_t drained = TakeAtMost(buf, bytes); buf += drained; bytes -= drained; if (drained == 0) { if (!Block()) { // Timed out reading more bytes. return false; } } } return true; } size_t InputSink::TakeAtMost(char *buf, size_t bytes) { Fill(); // The least of: contiguous to read, actually populated in buffer, and wanted. size_t avail = std::min(std::min(BUFFER_SIZE - read_, valid_), bytes); if (avail != 0) { memcpy(buf, buf_ + read_, avail); AccountDrain(avail); } return avail; } bool InputSink::Skip(size_t bytes) { while (bytes > 0) { size_t drained = std::min(valid_, bytes); AccountDrain(drained); bytes -= drained; // Nothing left to read? Get more. if (drained == 0) { if (!Block()) { // Timed out reading more bytes. return false; } } } return true; } void InputSink::Discard() { read_ = 0; write_ = 0; valid_ = 0; } void InputSink::Fill() { // Avoid small reads if possible. if (BUFFER_SIZE - valid_ > PRESSURE) { // Whatever isn't valid and follows write_ is what's available. size_t avail = BUFFER_SIZE - std::max(write_, valid_); int bytes = recv(fd_, buf_ + write_, avail, MSG_NOSIGNAL); AccountFill(bytes); } } bool InputSink::Block() { if (!fd_util::WaitUntilReady((int)fd_, 5.0)) { return false; } Fill(); return true; } void InputSink::AccountFill(int bytes) { if (bytes < 0) { int err = socket_errno; if (err == EWOULDBLOCK || err == EAGAIN) return; ERROR_LOG(Log::Net, "Error reading from socket: %d", err); return; } // Okay, move forward (might be by zero.) valid_ += bytes; write_ += bytes; if (write_ >= BUFFER_SIZE) { write_ -= BUFFER_SIZE; } } void InputSink::AccountDrain(size_t bytes) { valid_ -= bytes; read_ += bytes; if (read_ >= BUFFER_SIZE) { read_ -= BUFFER_SIZE; } } bool InputSink::Empty() const { return valid_ == 0; } bool InputSink::TryFill() { Fill(); return !Empty(); } OutputSink::OutputSink(size_t fd) : fd_(fd), read_(0), write_(0), valid_(0) { fd_util::SetNonBlocking((int)fd_, true); } bool OutputSink::Push(const std::string &s) { return Push(&s[0], s.length()); } bool OutputSink::Push(const char *buf, size_t bytes) { while (bytes > 0) { size_t pushed = PushAtMost(buf, bytes); buf += pushed; bytes -= pushed; if (pushed == 0) { if (!Block()) { // We couldn't write all the bytes. return false; } } } return true; } bool OutputSink::PushCRLF(const std::string &s) { if (Push(s)) { return Push("r\n", 2); } return false; } size_t OutputSink::PushAtMost(const char *buf, size_t bytes) { Drain(); if (valid_ == 0 && bytes > PRESSURE) { // Special case for pushing larger buffers: let's try to send directly. int sentBytes = send(fd_, buf, bytes, MSG_NOSIGNAL); // If it was 0 or EWOULDBLOCK, that's fine, we'll enqueue as we can. if (sentBytes > 0) { return sentBytes; } } // Look for contiguous free space after write_ that's valid. size_t avail = std::min(BUFFER_SIZE - std::max(write_, valid_), bytes); if (avail != 0) { memcpy(buf_ + write_, buf, avail); AccountPush(avail); } return avail; } bool OutputSink::Printf(const char *fmt, ...) { // Let's start by checking how much space we have. size_t avail = BUFFER_SIZE - std::max(write_, valid_); va_list args; va_start(args, fmt); // Make a backup in case we don't have sufficient space. va_list backup; va_copy(backup, args); bool success = true; int result = vsnprintf(buf_ + write_, avail, fmt, args); if (result >= (int)avail) { // There wasn't enough space. Let's use a buffer instead. // This could be caused by wraparound. char temp[4096]; result = vsnprintf(temp, sizeof(temp), fmt, args); if ((size_t)result < sizeof(temp) && result > 0) { // In case it did return the null terminator. if (temp[result - 1] == '\0') { result--; } success = Push(temp, result); // We've written so there's nothing more. result = 0; } } va_end(args); va_end(backup); // Okay, did we actually write? if (result >= (int)avail) { // This means the result string was too big for the buffer. ERROR_LOG(Log::IO, "Not enough space to format output."); return false; } else if (result < 0) { ERROR_LOG(Log::IO, "vsnprintf failed."); return false; } if (result > 0) { AccountPush(result); } return success; } bool OutputSink::Block() { if (!fd_util::WaitUntilReady((int)fd_, 5.0, true)) { return false; } Drain(); return true; } bool OutputSink::Flush(bool allowBlock) { while (valid_ > 0) { size_t avail = std::min(BUFFER_SIZE - read_, valid_); int bytes = send(fd_, buf_ + read_, avail, MSG_NOSIGNAL); #if !PPSSPP_PLATFORM(WINDOWS) if (bytes == -1 && (socket_errno == EAGAIN || socket_errno == EWOULDBLOCK)) bytes = 0; #endif AccountDrain(bytes); if (bytes == 0) { // This may also drain. Either way, keep looping. if (!allowBlock || !Block()) { return false; } } else if (bytes < 0) { return false; } } return true; } void OutputSink::Discard() { read_ = 0; write_ = 0; valid_ = 0; } void OutputSink::Drain() { // Avoid small reads if possible. if (valid_ > PRESSURE) { // Let's just do contiguous valid. size_t avail = std::min(BUFFER_SIZE - read_, valid_); int bytes = send(fd_, buf_ + read_, avail, MSG_NOSIGNAL); #if !PPSSPP_PLATFORM(WINDOWS) if (bytes == -1 && (socket_errno == EAGAIN || socket_errno == EWOULDBLOCK)) bytes = 0; #endif AccountDrain(bytes); } } void OutputSink::AccountPush(size_t bytes) { valid_ += bytes; write_ += bytes; if (write_ >= BUFFER_SIZE) { write_ -= BUFFER_SIZE; } } void OutputSink::AccountDrain(int bytes) { if (bytes < 0) { int err = socket_errno; if (err == EWOULDBLOCK || err == EAGAIN) return; ERROR_LOG(Log::IO, "Error writing to socket: %d", err); return; } valid_ -= bytes; read_ += bytes; if (read_ >= BUFFER_SIZE) { read_ -= BUFFER_SIZE; } } bool OutputSink::Empty() const { return valid_ == 0; } size_t OutputSink::BytesRemaining() const { return valid_; } } // namespace net