================ @@ -7,163 +7,122 @@ //===----------------------------------------------------------------------===// #include "lldb/Host/JSONTransport.h" -#include "lldb/Utility/IOObject.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" -#include "lldb/Utility/SelectHelper.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/raw_ostream.h" -#include <optional> #include <string> #include <utility> using namespace llvm; using namespace lldb; using namespace lldb_private; -/// ReadFull attempts to read the specified number of bytes. If EOF is -/// encountered, an empty string is returned. -static Expected<std::string> -ReadFull(IOObject &descriptor, size_t length, - std::optional<std::chrono::microseconds> timeout = std::nullopt) { - if (!descriptor.IsValid()) - return llvm::make_error<TransportInvalidError>(); - - bool timeout_supported = true; - // FIXME: SelectHelper does not work with NativeFile on Win32. -#if _WIN32 - timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket; -#endif - - if (timeout && timeout_supported) { - SelectHelper sh; - sh.SetTimeout(*timeout); - sh.FDSetRead( - reinterpret_cast<lldb::socket_t>(descriptor.GetWaitableHandle())); - Status status = sh.Select(); - if (status.Fail()) { - // Convert timeouts into a specific error. - if (status.GetType() == lldb::eErrorTypePOSIX && - status.GetError() == ETIMEDOUT) - return make_error<TransportTimeoutError>(); - return status.takeError(); - } - } - - std::string data; - data.resize(length); - Status status = descriptor.Read(data.data(), length); - if (status.Fail()) - return status.takeError(); - - // Read returns '' on EOF. - if (length == 0) - return make_error<TransportEOFError>(); - - // Return the actual number of bytes read. - return data.substr(0, length); -} - -static Expected<std::string> -ReadUntil(IOObject &descriptor, StringRef delimiter, - std::optional<std::chrono::microseconds> timeout = std::nullopt) { - std::string buffer; - buffer.reserve(delimiter.size() + 1); - while (!llvm::StringRef(buffer).ends_with(delimiter)) { - Expected<std::string> next = - ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1, timeout); - if (auto Err = next.takeError()) - return std::move(Err); - buffer += *next; - } - return buffer.substr(0, buffer.size() - delimiter.size()); -} - JSONTransport::JSONTransport(IOObjectSP input, IOObjectSP output) : m_input(std::move(input)), m_output(std::move(output)) {} void JSONTransport::Log(llvm::StringRef message) { LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message); } -Expected<std::string> -HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) { - if (!m_input || !m_input->IsValid()) - return llvm::make_error<TransportInvalidError>(); +// Parses messages based on +// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol +Expected<std::vector<std::string>> HTTPDelimitedJSONTransport::Parse() { + if (m_buffer.empty()) + return std::vector<std::string>{}; + + std::vector<std::string> messages; + StringRef buffer = m_buffer; + size_t content_length = 0, end_of_last_message = 0, cursor = 0; + do { + auto idx = buffer.find(kHeaderSeparator, cursor); + // Separator not found, we need more data. + if (idx == StringRef::npos) + break; + + auto header = buffer.slice(cursor, idx); + cursor = idx + kHeaderSeparator.size(); + + // An empty line separates the headers from the message body. + if (header.empty()) { + // Check if we have enough data or wait for the next chunk to arrive. + if (content_length + cursor > buffer.size()) + break; + + std::string body = buffer.substr(cursor, content_length).str(); + end_of_last_message = cursor + content_length; + cursor += content_length; + Logv("--> {0}", body); + messages.emplace_back(std::move(body)); + content_length = 0; + continue; + } + + // HTTP Headers are formatted like `<field-name> ':' [<field-value>]`. + if (!header.contains(kHeaderFieldSeparator)) + return createStringError("malformed content header"); + + auto [name, value] = header.split(kHeaderFieldSeparator); + + // Handle known headers, at the moment only "Content-Length" is specified, + // other headers are ignored. + if (name.lower() == kHeaderContentLength.lower()) { + value = value.trim(); + if (value.trim().consumeInteger(10, content_length)) + return createStringError(std::errc::invalid_argument, + "invalid content length: %s", + value.str().c_str()); + } + } while (cursor < buffer.size()); + + // Store the remainder of the buffer for the next read callback. + m_buffer = buffer.substr(end_of_last_message); - IOObject *input = m_input.get(); - Expected<std::string> message_header = - ReadFull(*input, kHeaderContentLength.size(), timeout); - if (!message_header) - return message_header.takeError(); - if (*message_header != kHeaderContentLength) - return createStringError(formatv("expected '{0}' and got '{1}'", - kHeaderContentLength, *message_header) - .str()); - - Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator); - if (!raw_length) - return handleErrors(raw_length.takeError(), - [&](const TransportEOFError &E) -> llvm::Error { - return createStringError( - "unexpected EOF while reading header separator"); - }); - - size_t length; - if (!to_integer(*raw_length, length)) - return createStringError( - formatv("invalid content length {0}", *raw_length).str()); - - Expected<std::string> raw_json = ReadFull(*input, length); - if (!raw_json) - return handleErrors( - raw_json.takeError(), [&](const TransportEOFError &E) -> llvm::Error { - return createStringError("unexpected EOF while reading JSON"); - }); - - Log(llvm::formatv("--> {0}", *raw_json).str()); - - return raw_json; + return messages; } Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) { if (!m_output || !m_output->IsValid()) return llvm::make_error<TransportInvalidError>(); - Log(llvm::formatv("<-- {0}", message).str()); + Logv("<-- {0}", message); std::string Output; raw_string_ostream OS(Output); - OS << kHeaderContentLength << message.length() << kHeaderSeparator << message; + OS << kHeaderContentLength << kHeaderFieldSeparator << ' ' << message.length() + << kHeaderSeparator << kHeaderSeparator << message; size_t num_bytes = Output.size(); return m_output->Write(Output.data(), num_bytes).takeError(); } -Expected<std::string> -JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) { - if (!m_input || !m_input->IsValid()) - return make_error<TransportInvalidError>(); - - IOObject *input = m_input.get(); - Expected<std::string> raw_json = - ReadUntil(*input, kMessageSeparator, timeout); - if (!raw_json) - return raw_json.takeError(); - - Log(llvm::formatv("--> {0}", *raw_json).str()); - - return *raw_json; +Expected<std::vector<std::string>> JSONRPCTransport::Parse() { + std::vector<std::string> messages; + StringRef buf = m_buffer; + do { + size_t idx = buf.find(kMessageSeparator); + if (idx == StringRef::npos) + break; + std::string raw_json = buf.substr(0, idx).str(); + buf = buf.substr(idx + 1); ---------------- labath wrote:
The +1 only works because the strlen(separator) is 1, which also leads me to question its usefulness as a constant. https://github.com/llvm/llvm-project/pull/148300 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits