Skip to content

Commit 538bd83

Browse files
authored
[lldb] Refactoring JSONTransport into an abstract RPC Message Handler and transport layer. (llvm#153121)
This abstracts the base Transport handler to have a MessageHandler component and allows us to generalize both JSON-RPC 2.0 for MCP (or an LSP) and DAP format. This should allow us to create clearly defined clients and servers for protocols, both for testing and for RPC between the lldb instances and an lldb-mcp multiplexer. This basic model is inspiried by the clangd/Transport.h file and the mlir/lsp-server-support/Transport.h that are both used for LSP servers within the llvm project. Additionally, this helps with testing by subclassing `Transport` to allow us to simplify sending/receiving messages without needing to use a toJSON/fromJSON and a pair of pipes, see `TestTransport` in DAP/TestBase.h.
1 parent 1d1b60c commit 538bd83

File tree

17 files changed

+973
-679
lines changed

17 files changed

+973
-679
lines changed

lldb/include/lldb/Host/JSONTransport.h

Lines changed: 216 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,25 @@
1313
#ifndef LLDB_HOST_JSONTRANSPORT_H
1414
#define LLDB_HOST_JSONTRANSPORT_H
1515

16+
#include "lldb/Host/MainLoop.h"
1617
#include "lldb/Host/MainLoopBase.h"
1718
#include "lldb/Utility/IOObject.h"
1819
#include "lldb/Utility/Status.h"
1920
#include "lldb/lldb-forward.h"
21+
#include "llvm/ADT/StringExtras.h"
2022
#include "llvm/ADT/StringRef.h"
2123
#include "llvm/Support/Error.h"
24+
#include "llvm/Support/ErrorHandling.h"
2225
#include "llvm/Support/FormatVariadic.h"
2326
#include "llvm/Support/JSON.h"
27+
#include "llvm/Support/raw_ostream.h"
2428
#include <string>
2529
#include <system_error>
30+
#include <variant>
2631
#include <vector>
2732

2833
namespace lldb_private {
2934

30-
class TransportEOFError : public llvm::ErrorInfo<TransportEOFError> {
31-
public:
32-
static char ID;
33-
34-
TransportEOFError() = default;
35-
void log(llvm::raw_ostream &OS) const override;
36-
std::error_code convertToErrorCode() const override;
37-
};
38-
3935
class TransportUnhandledContentsError
4036
: public llvm::ErrorInfo<TransportUnhandledContentsError> {
4137
public:
@@ -54,112 +50,214 @@ class TransportUnhandledContentsError
5450
std::string m_unhandled_contents;
5551
};
5652

57-
class TransportInvalidError : public llvm::ErrorInfo<TransportInvalidError> {
53+
/// A transport is responsible for maintaining the connection to a client
54+
/// application, and reading/writing structured messages to it.
55+
///
56+
/// Transports have limited thread safety requirements:
57+
/// - Messages will not be sent concurrently.
58+
/// - Messages MAY be sent while Run() is reading, or its callback is active.
59+
template <typename Req, typename Resp, typename Evt> class Transport {
5860
public:
59-
static char ID;
60-
61-
TransportInvalidError() = default;
61+
using Message = std::variant<Req, Resp, Evt>;
62+
63+
virtual ~Transport() = default;
64+
65+
/// Sends an event, a message that does not require a response.
66+
virtual llvm::Error Send(const Evt &) = 0;
67+
/// Sends a request, a message that expects a response.
68+
virtual llvm::Error Send(const Req &) = 0;
69+
/// Sends a response to a specific request.
70+
virtual llvm::Error Send(const Resp &) = 0;
71+
72+
/// Implemented to handle incoming messages. (See Run() below).
73+
class MessageHandler {
74+
public:
75+
virtual ~MessageHandler() = default;
76+
/// Called when an event is received.
77+
virtual void Received(const Evt &) = 0;
78+
/// Called when a request is received.
79+
virtual void Received(const Req &) = 0;
80+
/// Called when a response is received.
81+
virtual void Received(const Resp &) = 0;
82+
83+
/// Called when an error occurs while reading from the transport.
84+
///
85+
/// NOTE: This does *NOT* indicate that a specific request failed, but that
86+
/// there was an error in the underlying transport.
87+
virtual void OnError(llvm::Error) = 0;
88+
89+
/// Called on EOF or client disconnect.
90+
virtual void OnClosed() = 0;
91+
};
92+
93+
using MessageHandlerSP = std::shared_ptr<MessageHandler>;
94+
95+
/// RegisterMessageHandler registers the Transport with the given MainLoop and
96+
/// handles any incoming messages using the given MessageHandler.
97+
///
98+
/// If an unexpected error occurs, the MainLoop will be terminated and a log
99+
/// message will include additional information about the termination reason.
100+
virtual llvm::Expected<MainLoop::ReadHandleUP>
101+
RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) = 0;
62102

63-
void log(llvm::raw_ostream &OS) const override;
64-
std::error_code convertToErrorCode() const override;
103+
protected:
104+
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
105+
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
106+
}
107+
virtual void Log(llvm::StringRef message) = 0;
65108
};
66109

67-
/// A transport class that uses JSON for communication.
68-
class JSONTransport {
110+
/// A JSONTransport will encode and decode messages using JSON.
111+
template <typename Req, typename Resp, typename Evt>
112+
class JSONTransport : public Transport<Req, Resp, Evt> {
69113
public:
70-
using ReadHandleUP = MainLoopBase::ReadHandleUP;
71-
template <typename T>
72-
using Callback = std::function<void(MainLoopBase &, const llvm::Expected<T>)>;
73-
74-
JSONTransport(lldb::IOObjectSP input, lldb::IOObjectSP output);
75-
virtual ~JSONTransport() = default;
76-
77-
/// Transport is not copyable.
78-
/// @{
79-
JSONTransport(const JSONTransport &rhs) = delete;
80-
void operator=(const JSONTransport &rhs) = delete;
81-
/// @}
82-
83-
/// Writes a message to the output stream.
84-
template <typename T> llvm::Error Write(const T &t) {
85-
const std::string message = llvm::formatv("{0}", toJSON(t)).str();
86-
return WriteImpl(message);
114+
using Transport<Req, Resp, Evt>::Transport;
115+
using MessageHandler = typename Transport<Req, Resp, Evt>::MessageHandler;
116+
117+
JSONTransport(lldb::IOObjectSP in, lldb::IOObjectSP out)
118+
: m_in(in), m_out(out) {}
119+
120+
llvm::Error Send(const Evt &evt) override { return Write(evt); }
121+
llvm::Error Send(const Req &req) override { return Write(req); }
122+
llvm::Error Send(const Resp &resp) override { return Write(resp); }
123+
124+
llvm::Expected<MainLoop::ReadHandleUP>
125+
RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) override {
126+
Status status;
127+
MainLoop::ReadHandleUP read_handle = loop.RegisterReadObject(
128+
m_in,
129+
std::bind(&JSONTransport::OnRead, this, std::placeholders::_1,
130+
std::ref(handler)),
131+
status);
132+
if (status.Fail()) {
133+
return status.takeError();
134+
}
135+
return read_handle;
87136
}
88137

89-
/// Registers the transport with the MainLoop.
90-
template <typename T>
91-
llvm::Expected<ReadHandleUP> RegisterReadObject(MainLoopBase &loop,
92-
Callback<T> read_cb) {
93-
Status error;
94-
ReadHandleUP handle = loop.RegisterReadObject(
95-
m_input,
96-
[read_cb, this](MainLoopBase &loop) {
97-
char buf[kReadBufferSize];
98-
size_t num_bytes = sizeof(buf);
99-
if (llvm::Error error = m_input->Read(buf, num_bytes).takeError()) {
100-
read_cb(loop, std::move(error));
101-
return;
102-
}
103-
if (num_bytes)
104-
m_buffer.append(std::string(buf, num_bytes));
105-
106-
// If the buffer has contents, try parsing any pending messages.
107-
if (!m_buffer.empty()) {
108-
llvm::Expected<std::vector<std::string>> messages = Parse();
109-
if (llvm::Error error = messages.takeError()) {
110-
read_cb(loop, std::move(error));
111-
return;
112-
}
113-
114-
for (const auto &message : *messages)
115-
if constexpr (std::is_same<T, std::string>::value)
116-
read_cb(loop, message);
117-
else
118-
read_cb(loop, llvm::json::parse<T>(message));
119-
}
120-
121-
// On EOF, notify the callback after the remaining messages were
122-
// handled.
123-
if (num_bytes == 0) {
124-
if (m_buffer.empty())
125-
read_cb(loop, llvm::make_error<TransportEOFError>());
126-
else
127-
read_cb(loop, llvm::make_error<TransportUnhandledContentsError>(
128-
std::string(m_buffer)));
129-
}
130-
},
131-
error);
132-
if (error.Fail())
133-
return error.takeError();
134-
return handle;
135-
}
138+
/// Public for testing purposes, otherwise this should be an implementation
139+
/// detail.
140+
static constexpr size_t kReadBufferSize = 1024;
136141

137142
protected:
138-
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
139-
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
143+
virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
144+
virtual std::string Encode(const llvm::json::Value &message) = 0;
145+
llvm::Error Write(const llvm::json::Value &message) {
146+
this->Logv("<-- {0}", message);
147+
std::string output = Encode(message);
148+
size_t bytes_written = output.size();
149+
return m_out->Write(output.data(), bytes_written).takeError();
140150
}
141-
virtual void Log(llvm::StringRef message);
142151

143-
virtual llvm::Error WriteImpl(const std::string &message) = 0;
144-
virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
152+
llvm::SmallString<kReadBufferSize> m_buffer;
145153

146-
static constexpr size_t kReadBufferSize = 1024;
154+
private:
155+
void OnRead(MainLoopBase &loop, MessageHandler &handler) {
156+
char buf[kReadBufferSize];
157+
size_t num_bytes = sizeof(buf);
158+
if (Status status = m_in->Read(buf, num_bytes); status.Fail()) {
159+
handler.OnError(status.takeError());
160+
return;
161+
}
162+
163+
if (num_bytes)
164+
m_buffer.append(llvm::StringRef(buf, num_bytes));
165+
166+
// If the buffer has contents, try parsing any pending messages.
167+
if (!m_buffer.empty()) {
168+
llvm::Expected<std::vector<std::string>> raw_messages = Parse();
169+
if (llvm::Error error = raw_messages.takeError()) {
170+
handler.OnError(std::move(error));
171+
return;
172+
}
173+
174+
for (const std::string &raw_message : *raw_messages) {
175+
llvm::Expected<typename Transport<Req, Resp, Evt>::Message> message =
176+
llvm::json::parse<typename Transport<Req, Resp, Evt>::Message>(
177+
raw_message);
178+
if (!message) {
179+
handler.OnError(message.takeError());
180+
return;
181+
}
182+
183+
std::visit([&handler](auto &&msg) { handler.Received(msg); }, *message);
184+
}
185+
}
186+
187+
// Check if we reached EOF.
188+
if (num_bytes == 0) {
189+
// EOF reached, but there may still be unhandled contents in the buffer.
190+
if (!m_buffer.empty())
191+
handler.OnError(llvm::make_error<TransportUnhandledContentsError>(
192+
std::string(m_buffer.str())));
193+
handler.OnClosed();
194+
}
195+
}
147196

148-
lldb::IOObjectSP m_input;
149-
lldb::IOObjectSP m_output;
150-
llvm::SmallString<kReadBufferSize> m_buffer;
197+
lldb::IOObjectSP m_in;
198+
lldb::IOObjectSP m_out;
151199
};
152200

153201
/// A transport class for JSON with a HTTP header.
154-
class HTTPDelimitedJSONTransport : public JSONTransport {
202+
template <typename Req, typename Resp, typename Evt>
203+
class HTTPDelimitedJSONTransport : public JSONTransport<Req, Resp, Evt> {
155204
public:
156-
HTTPDelimitedJSONTransport(lldb::IOObjectSP input, lldb::IOObjectSP output)
157-
: JSONTransport(input, output) {}
158-
virtual ~HTTPDelimitedJSONTransport() = default;
205+
using JSONTransport<Req, Resp, Evt>::JSONTransport;
159206

160207
protected:
161-
llvm::Error WriteImpl(const std::string &message) override;
162-
llvm::Expected<std::vector<std::string>> Parse() override;
208+
/// Encodes messages based on
209+
/// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
210+
std::string Encode(const llvm::json::Value &message) override {
211+
std::string output;
212+
std::string raw_message = llvm::formatv("{0}", message).str();
213+
llvm::raw_string_ostream OS(output);
214+
OS << kHeaderContentLength << kHeaderFieldSeparator << ' '
215+
<< std::to_string(raw_message.size()) << kEndOfHeader << raw_message;
216+
return output;
217+
}
218+
219+
/// Parses messages based on
220+
/// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
221+
llvm::Expected<std::vector<std::string>> Parse() override {
222+
std::vector<std::string> messages;
223+
llvm::StringRef buffer = this->m_buffer;
224+
while (buffer.contains(kEndOfHeader)) {
225+
auto [headers, rest] = buffer.split(kEndOfHeader);
226+
size_t content_length = 0;
227+
// HTTP Headers are formatted like `<field-name> ':' [<field-value>]`.
228+
for (const llvm::StringRef &header :
229+
llvm::split(headers, kHeaderSeparator)) {
230+
auto [key, value] = header.split(kHeaderFieldSeparator);
231+
// 'Content-Length' is the only meaningful key at the moment. Others are
232+
// ignored.
233+
if (!key.equals_insensitive(kHeaderContentLength))
234+
continue;
235+
236+
value = value.trim();
237+
if (!llvm::to_integer(value, content_length, 10)) {
238+
// Clear the buffer to avoid re-parsing this malformed message.
239+
this->m_buffer.clear();
240+
return llvm::createStringError(std::errc::invalid_argument,
241+
"invalid content length: %s",
242+
value.str().c_str());
243+
}
244+
}
245+
246+
// Check if we have enough data.
247+
if (content_length > rest.size())
248+
break;
249+
250+
llvm::StringRef body = rest.take_front(content_length);
251+
buffer = rest.drop_front(content_length);
252+
messages.emplace_back(body.str());
253+
this->Logv("--> {0}", body);
254+
}
255+
256+
// Store the remainder of the buffer for the next read callback.
257+
this->m_buffer = buffer.str();
258+
259+
return std::move(messages);
260+
}
163261

164262
static constexpr llvm::StringLiteral kHeaderContentLength = "Content-Length";
165263
static constexpr llvm::StringLiteral kHeaderFieldSeparator = ":";
@@ -168,15 +266,31 @@ class HTTPDelimitedJSONTransport : public JSONTransport {
168266
};
169267

170268
/// A transport class for JSON RPC.
171-
class JSONRPCTransport : public JSONTransport {
269+
template <typename Req, typename Resp, typename Evt>
270+
class JSONRPCTransport : public JSONTransport<Req, Resp, Evt> {
172271
public:
173-
JSONRPCTransport(lldb::IOObjectSP input, lldb::IOObjectSP output)
174-
: JSONTransport(input, output) {}
175-
virtual ~JSONRPCTransport() = default;
272+
using JSONTransport<Req, Resp, Evt>::JSONTransport;
176273

177274
protected:
178-
llvm::Error WriteImpl(const std::string &message) override;
179-
llvm::Expected<std::vector<std::string>> Parse() override;
275+
std::string Encode(const llvm::json::Value &message) override {
276+
return llvm::formatv("{0}{1}", message, kMessageSeparator).str();
277+
}
278+
279+
llvm::Expected<std::vector<std::string>> Parse() override {
280+
std::vector<std::string> messages;
281+
llvm::StringRef buf = this->m_buffer;
282+
while (buf.contains(kMessageSeparator)) {
283+
auto [raw_json, rest] = buf.split(kMessageSeparator);
284+
buf = rest;
285+
messages.emplace_back(raw_json.str());
286+
this->Logv("--> {0}", raw_json);
287+
}
288+
289+
// Store the remainder of the buffer for the next read callback.
290+
this->m_buffer = buf.str();
291+
292+
return messages;
293+
}
180294

181295
static constexpr llvm::StringLiteral kMessageSeparator = "\n";
182296
};

0 commit comments

Comments
 (0)