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
24using namespace llvm;
25using namespace lldb;
26using namespace lldb_private;
27
28/// ReadFull attempts to read the specified number of bytes. If EOF is
29/// encountered, an empty string is returned.
30static Expected<std::string>
31ReadFull(IOObject &descriptor, size_t length,
32 std::optional<std::chrono::microseconds> timeout = std::nullopt) {
33 if (!descriptor.IsValid())
34 return llvm::make_error<TransportInvalidError>();
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(
46 fd: reinterpret_cast<lldb::socket_t>(descriptor.GetWaitableHandle()));
47 Status status = sh.Select();
48 if (status.Fail()) {
49 // Convert timeouts into a specific error.
50 if (status.GetType() == lldb::eErrorTypePOSIX &&
51 status.GetError() == ETIMEDOUT)
52 return make_error<TransportTimeoutError>();
53 return status.takeError();
54 }
55 }
56
57 std::string data;
58 data.resize(n: length);
59 Status status = descriptor.Read(buf: data.data(), num_bytes&: length);
60 if (status.Fail())
61 return status.takeError();
62
63 // Read returns '' on EOF.
64 if (length == 0)
65 return make_error<TransportEOFError>();
66
67 // Return the actual number of bytes read.
68 return data.substr(pos: 0, n: length);
69}
70
71static Expected<std::string>
72ReadUntil(IOObject &descriptor, StringRef delimiter,
73 std::optional<std::chrono::microseconds> timeout = std::nullopt) {
74 std::string buffer;
75 buffer.reserve(res_arg: delimiter.size() + 1);
76 while (!llvm::StringRef(buffer).ends_with(Suffix: delimiter)) {
77 Expected<std::string> next =
78 ReadFull(descriptor, length: buffer.empty() ? delimiter.size() : 1, timeout);
79 if (auto Err = next.takeError())
80 return std::move(Err);
81 buffer += *next;
82 }
83 return buffer.substr(pos: 0, n: buffer.size() - delimiter.size());
84}
85
86JSONTransport::JSONTransport(IOObjectSP input, IOObjectSP output)
87 : m_input(std::move(input)), m_output(std::move(output)) {}
88
89void JSONTransport::Log(llvm::StringRef message) {
90 LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
91}
92
93Expected<std::string>
94HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) {
95 if (!m_input || !m_input->IsValid())
96 return llvm::make_error<TransportInvalidError>();
97
98 IOObject *input = m_input.get();
99 Expected<std::string> message_header =
100 ReadFull(descriptor&: *input, length: kHeaderContentLength.size(), timeout);
101 if (!message_header)
102 return message_header.takeError();
103 if (*message_header != kHeaderContentLength)
104 return createStringError(S: formatv(Fmt: "expected '{0}' and got '{1}'",
105 Vals: kHeaderContentLength, Vals&: *message_header)
106 .str());
107
108 Expected<std::string> raw_length = ReadUntil(descriptor&: *input, delimiter: kHeaderSeparator);
109 if (!raw_length)
110 return handleErrors(E: raw_length.takeError(),
111 Hs: [&](const TransportEOFError &E) -> llvm::Error {
112 return createStringError(
113 Fmt: "unexpected EOF while reading header separator");
114 });
115
116 size_t length;
117 if (!to_integer(S: *raw_length, Num&: length))
118 return createStringError(
119 S: formatv(Fmt: "invalid content length {0}", Vals&: *raw_length).str());
120
121 Expected<std::string> raw_json = ReadFull(descriptor&: *input, length);
122 if (!raw_json)
123 return handleErrors(
124 E: raw_json.takeError(), Hs: [&](const TransportEOFError &E) -> llvm::Error {
125 return createStringError(Fmt: "unexpected EOF while reading JSON");
126 });
127
128 Log(message: llvm::formatv(Fmt: "--> {0}", Vals&: *raw_json).str());
129
130 return raw_json;
131}
132
133Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) {
134 if (!m_output || !m_output->IsValid())
135 return llvm::make_error<TransportInvalidError>();
136
137 Log(message: llvm::formatv(Fmt: "<-- {0}", Vals: message).str());
138
139 std::string Output;
140 raw_string_ostream OS(Output);
141 OS << kHeaderContentLength << message.length() << kHeaderSeparator << message;
142 size_t num_bytes = Output.size();
143 return m_output->Write(buf: Output.data(), num_bytes).takeError();
144}
145
146Expected<std::string>
147JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) {
148 if (!m_input || !m_input->IsValid())
149 return make_error<TransportInvalidError>();
150
151 IOObject *input = m_input.get();
152 Expected<std::string> raw_json =
153 ReadUntil(descriptor&: *input, delimiter: kMessageSeparator, timeout);
154 if (!raw_json)
155 return raw_json.takeError();
156
157 Log(message: llvm::formatv(Fmt: "--> {0}", Vals&: *raw_json).str());
158
159 return *raw_json;
160}
161
162Error JSONRPCTransport::WriteImpl(const std::string &message) {
163 if (!m_output || !m_output->IsValid())
164 return llvm::make_error<TransportInvalidError>();
165
166 Log(message: llvm::formatv(Fmt: "<-- {0}", Vals: message).str());
167
168 std::string Output;
169 llvm::raw_string_ostream OS(Output);
170 OS << message << kMessageSeparator;
171 size_t num_bytes = Output.size();
172 return m_output->Write(buf: Output.data(), num_bytes).takeError();
173}
174
175char TransportEOFError::ID;
176char TransportTimeoutError::ID;
177char TransportInvalidError::ID;
178

source code of lldb/source/Host/common/JSONTransport.cpp