|  | 
|  | 1 | +//===-- JSONTransport.cpp -------------------------------------------------===// | 
|  | 2 | +// | 
|  | 3 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | 4 | +// See https://llvm.org/LICENSE.txt for license information. | 
|  | 5 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | 6 | +// | 
|  | 7 | +//===----------------------------------------------------------------------===// | 
|  | 8 | + | 
|  | 9 | +#include "lldb/Host/JSONTransport.h" | 
|  | 10 | +#include "lldb/Utility/IOObject.h" | 
|  | 11 | +#include "lldb/Utility/LLDBLog.h" | 
|  | 12 | +#include "lldb/Utility/Log.h" | 
|  | 13 | +#include "lldb/Utility/SelectHelper.h" | 
|  | 14 | +#include "lldb/Utility/Status.h" | 
|  | 15 | +#include "lldb/lldb-forward.h" | 
|  | 16 | +#include "llvm/ADT/StringExtras.h" | 
|  | 17 | +#include "llvm/ADT/StringRef.h" | 
|  | 18 | +#include "llvm/Support/Error.h" | 
|  | 19 | +#include "llvm/Support/raw_ostream.h" | 
|  | 20 | +#include <optional> | 
|  | 21 | +#include <string> | 
|  | 22 | +#include <utility> | 
|  | 23 | + | 
|  | 24 | +using namespace llvm; | 
|  | 25 | +using namespace lldb; | 
|  | 26 | +using namespace lldb_private; | 
|  | 27 | + | 
|  | 28 | +/// ReadFull attempts to read the specified number of bytes. If EOF is | 
|  | 29 | +/// encountered, an empty string is returned. | 
|  | 30 | +static Expected<std::string> | 
|  | 31 | +ReadFull(IOObject &descriptor, size_t length, | 
|  | 32 | +         std::optional<std::chrono::microseconds> timeout = std::nullopt) { | 
|  | 33 | +  if (!descriptor.IsValid()) | 
|  | 34 | +    return llvm::make_error<TransportClosedError>(); | 
|  | 35 | + | 
|  | 36 | +  bool timeout_supported = true; | 
|  | 37 | +  // FIXME: SelectHelper does not work with NativeFile on Win32. | 
|  | 38 | +#if _WIN32 | 
|  | 39 | +  timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket; | 
|  | 40 | +#endif | 
|  | 41 | + | 
|  | 42 | +  if (timeout && timeout_supported) { | 
|  | 43 | +    SelectHelper sh; | 
|  | 44 | +    sh.SetTimeout(*timeout); | 
|  | 45 | +    sh.FDSetRead(descriptor.GetWaitableHandle()); | 
|  | 46 | +    Status status = sh.Select(); | 
|  | 47 | +    if (status.Fail()) { | 
|  | 48 | +      // Convert timeouts into a specific error. | 
|  | 49 | +      if (status.GetType() == lldb::eErrorTypePOSIX && | 
|  | 50 | +          status.GetError() == ETIMEDOUT) | 
|  | 51 | +        return make_error<TransportTimeoutError>(); | 
|  | 52 | +      return status.takeError(); | 
|  | 53 | +    } | 
|  | 54 | +  } | 
|  | 55 | + | 
|  | 56 | +  std::string data; | 
|  | 57 | +  data.resize(length); | 
|  | 58 | +  Status status = descriptor.Read(data.data(), length); | 
|  | 59 | +  if (status.Fail()) | 
|  | 60 | +    return status.takeError(); | 
|  | 61 | + | 
|  | 62 | +  // Read returns '' on EOF. | 
|  | 63 | +  if (length == 0) | 
|  | 64 | +    return make_error<TransportEOFError>(); | 
|  | 65 | + | 
|  | 66 | +  // Return the actual number of bytes read. | 
|  | 67 | +  return data.substr(0, length); | 
|  | 68 | +} | 
|  | 69 | + | 
|  | 70 | +static Expected<std::string> | 
|  | 71 | +ReadUntil(IOObject &descriptor, StringRef delimiter, | 
|  | 72 | +          std::optional<std::chrono::microseconds> timeout = std::nullopt) { | 
|  | 73 | +  std::string buffer; | 
|  | 74 | +  buffer.reserve(delimiter.size() + 1); | 
|  | 75 | +  while (!llvm::StringRef(buffer).ends_with(delimiter)) { | 
|  | 76 | +    Expected<std::string> next = | 
|  | 77 | +        ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1, timeout); | 
|  | 78 | +    if (auto Err = next.takeError()) | 
|  | 79 | +      return std::move(Err); | 
|  | 80 | +    buffer += *next; | 
|  | 81 | +  } | 
|  | 82 | +  return buffer.substr(0, buffer.size() - delimiter.size()); | 
|  | 83 | +} | 
|  | 84 | + | 
|  | 85 | +JSONTransport::JSONTransport(IOObjectSP input, IOObjectSP output) | 
|  | 86 | +    : m_input(std::move(input)), m_output(std::move(output)) {} | 
|  | 87 | + | 
|  | 88 | +void JSONTransport::Log(llvm::StringRef message) { | 
|  | 89 | +  LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message); | 
|  | 90 | +} | 
|  | 91 | + | 
|  | 92 | +Expected<std::string> | 
|  | 93 | +HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) { | 
|  | 94 | +  if (!m_input || !m_input->IsValid()) | 
|  | 95 | +    return createStringError("transport output is closed"); | 
|  | 96 | + | 
|  | 97 | +  IOObject *input = m_input.get(); | 
|  | 98 | +  Expected<std::string> message_header = | 
|  | 99 | +      ReadFull(*input, kHeaderContentLength.size(), timeout); | 
|  | 100 | +  if (!message_header) | 
|  | 101 | +    return message_header.takeError(); | 
|  | 102 | +  if (*message_header != kHeaderContentLength) | 
|  | 103 | +    return createStringError(formatv("expected '{0}' and got '{1}'", | 
|  | 104 | +                                     kHeaderContentLength, *message_header) | 
|  | 105 | +                                 .str()); | 
|  | 106 | + | 
|  | 107 | +  Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator); | 
|  | 108 | +  if (!raw_length) | 
|  | 109 | +    return handleErrors(raw_length.takeError(), | 
|  | 110 | +                        [&](const TransportEOFError &E) -> llvm::Error { | 
|  | 111 | +                          return createStringError( | 
|  | 112 | +                              "unexpected EOF while reading header separator"); | 
|  | 113 | +                        }); | 
|  | 114 | + | 
|  | 115 | +  size_t length; | 
|  | 116 | +  if (!to_integer(*raw_length, length)) | 
|  | 117 | +    return createStringError( | 
|  | 118 | +        formatv("invalid content length {0}", *raw_length).str()); | 
|  | 119 | + | 
|  | 120 | +  Expected<std::string> raw_json = ReadFull(*input, length); | 
|  | 121 | +  if (!raw_json) | 
|  | 122 | +    return handleErrors( | 
|  | 123 | +        raw_json.takeError(), [&](const TransportEOFError &E) -> llvm::Error { | 
|  | 124 | +          return createStringError("unexpected EOF while reading JSON"); | 
|  | 125 | +        }); | 
|  | 126 | + | 
|  | 127 | +  Log(llvm::formatv("--> {0}", *raw_json).str()); | 
|  | 128 | + | 
|  | 129 | +  return raw_json; | 
|  | 130 | +} | 
|  | 131 | + | 
|  | 132 | +Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) { | 
|  | 133 | +  if (!m_output || !m_output->IsValid()) | 
|  | 134 | +    return llvm::make_error<TransportClosedError>(); | 
|  | 135 | + | 
|  | 136 | +  Log(llvm::formatv("<-- {0}", message).str()); | 
|  | 137 | + | 
|  | 138 | +  std::string Output; | 
|  | 139 | +  raw_string_ostream OS(Output); | 
|  | 140 | +  OS << kHeaderContentLength << message.length() << kHeaderSeparator << message; | 
|  | 141 | +  size_t num_bytes = Output.size(); | 
|  | 142 | +  return m_output->Write(Output.data(), num_bytes).takeError(); | 
|  | 143 | +} | 
|  | 144 | + | 
|  | 145 | +char TransportEOFError::ID; | 
|  | 146 | +char TransportTimeoutError::ID; | 
|  | 147 | +char TransportClosedError::ID; | 
0 commit comments