Skip to content

Commit 6a846c2

Browse files
committed
[lldb] Refactoring JSONTransport into an abstract RPC Message Handler and transport layer.
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.
1 parent 45d4e84 commit 6a846c2

File tree

21 files changed

+947
-777
lines changed

21 files changed

+947
-777
lines changed

lldb/include/lldb/Host/JSONTransport.h

Lines changed: 221 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,219 @@ 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+
// Called by transport to send outgoing messages.
66+
virtual void Event(const Evt &) = 0;
67+
virtual void Request(const Req &) = 0;
68+
virtual void Response(const Resp &) = 0;
69+
70+
/// Implemented to handle incoming messages. (See Run() below).
71+
class MessageHandler {
72+
public:
73+
virtual ~MessageHandler() = default;
74+
virtual void OnEvent(const Evt &) = 0;
75+
virtual void OnRequest(const Req &) = 0;
76+
virtual void OnResponse(const Resp &) = 0;
77+
};
78+
79+
/// Called by server or client to receive messages from the connection.
80+
/// The transport should in turn invoke the handler to process messages.
81+
/// The MainLoop is used to handle reading from the incoming connection and
82+
/// will run until the loop is terminated.
83+
virtual llvm::Error Run(MainLoop &, MessageHandler &) = 0;
6284

63-
void log(llvm::raw_ostream &OS) const override;
64-
std::error_code convertToErrorCode() const override;
85+
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
86+
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
87+
}
88+
virtual void Log(llvm::StringRef message) = 0;
6589
};
6690

67-
/// A transport class that uses JSON for communication.
68-
class JSONTransport {
91+
/// A JSONTransport will encode and decode messages using JSON.
92+
template <typename Req, typename Resp, typename Evt>
93+
class JSONTransport : public Transport<Req, Resp, Evt> {
6994
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);
95+
using Transport<Req, Resp, Evt>::Transport;
96+
97+
JSONTransport(lldb::IOObjectSP in, lldb::IOObjectSP out)
98+
: m_in(in), m_out(out) {}
99+
100+
void Event(const Evt &evt) override { Write(evt); }
101+
void Request(const Req &req) override { Write(req); }
102+
void Response(const Resp &resp) override { Write(resp); }
103+
104+
/// Run registers the transport with the given MainLoop and handles any
105+
/// incoming messages using the given MessageHandler.
106+
llvm::Error
107+
Run(MainLoop &loop,
108+
typename Transport<Req, Resp, Evt>::MessageHandler &handler) override {
109+
llvm::Error error = llvm::Error::success();
110+
Status status;
111+
auto read_handle = loop.RegisterReadObject(
112+
m_in,
113+
std::bind(&JSONTransport::OnRead, this, &error, std::placeholders::_1,
114+
std::ref(handler)),
115+
status);
116+
if (status.Fail()) {
117+
// This error is only set if the read object handler is invoked, mark it
118+
// as consumed if registration of the handler failed.
119+
llvm::consumeError(std::move(error));
120+
return status.takeError();
121+
}
122+
123+
status = loop.Run();
124+
if (status.Fail())
125+
return status.takeError();
126+
return error;
87127
}
88128

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-
}
129+
/// Public for testing purposes, otherwise this should be an implementation
130+
/// detail.
131+
static constexpr size_t kReadBufferSize = 1024;
136132

137133
protected:
138-
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
139-
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
134+
virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
135+
virtual std::string Encode(const llvm::json::Value &message) = 0;
136+
void Write(const llvm::json::Value &message) {
137+
this->Logv("<-- {0}", message);
138+
std::string output = Encode(message);
139+
size_t bytes_written = output.size();
140+
Status status = m_out->Write(output.data(), bytes_written);
141+
if (status.Fail()) {
142+
this->Logv("writing failed: {0}", status.AsCString());
143+
}
140144
}
141-
virtual void Log(llvm::StringRef message);
142145

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

146-
static constexpr size_t kReadBufferSize = 1024;
148+
private:
149+
void OnRead(llvm::Error *err, MainLoopBase &loop,
150+
typename Transport<Req, Resp, Evt>::MessageHandler &handler) {
151+
llvm::ErrorAsOutParameter ErrAsOutParam(err);
152+
char buf[kReadBufferSize];
153+
size_t num_bytes = sizeof(buf);
154+
if (Status status = m_in->Read(buf, num_bytes); status.Fail()) {
155+
*err = status.takeError();
156+
loop.RequestTermination();
157+
return;
158+
}
159+
160+
if (num_bytes)
161+
m_buffer.append(llvm::StringRef(buf, num_bytes));
162+
163+
// If the buffer has contents, try parsing any pending messages.
164+
if (!m_buffer.empty()) {
165+
llvm::Expected<std::vector<std::string>> raw_messages = Parse();
166+
if (llvm::Error error = raw_messages.takeError()) {
167+
*err = std::move(error);
168+
loop.RequestTermination();
169+
return;
170+
}
171+
172+
for (const auto &raw_message : *raw_messages) {
173+
auto message =
174+
llvm::json::parse<typename Transport<Req, Resp, Evt>::Message>(
175+
raw_message);
176+
if (!message) {
177+
*err = message.takeError();
178+
loop.RequestTermination();
179+
return;
180+
}
181+
182+
if (Evt *evt = std::get_if<Evt>(&*message)) {
183+
handler.OnEvent(*evt);
184+
} else if (Req *req = std::get_if<Req>(&*message)) {
185+
handler.OnRequest(*req);
186+
} else if (Resp *resp = std::get_if<Resp>(&*message)) {
187+
handler.OnResponse(*resp);
188+
} else {
189+
llvm_unreachable("unknown message type");
190+
}
191+
}
192+
}
193+
194+
if (num_bytes == 0) {
195+
// If we're at EOF and we have unhandled contents in the buffer, return an
196+
// error for the partial message.
197+
if (m_buffer.empty())
198+
*err = llvm::Error::success();
199+
else
200+
*err = llvm::make_error<TransportUnhandledContentsError>(
201+
std::string(m_buffer));
202+
loop.RequestTermination();
203+
}
204+
}
147205

148-
lldb::IOObjectSP m_input;
149-
lldb::IOObjectSP m_output;
150-
llvm::SmallString<kReadBufferSize> m_buffer;
206+
lldb::IOObjectSP m_in;
207+
lldb::IOObjectSP m_out;
151208
};
152209

153210
/// A transport class for JSON with a HTTP header.
154-
class HTTPDelimitedJSONTransport : public JSONTransport {
211+
template <typename Req, typename Resp, typename Evt>
212+
class HTTPDelimitedJSONTransport : public JSONTransport<Req, Resp, Evt> {
155213
public:
156-
HTTPDelimitedJSONTransport(lldb::IOObjectSP input, lldb::IOObjectSP output)
157-
: JSONTransport(input, output) {}
158-
virtual ~HTTPDelimitedJSONTransport() = default;
214+
using JSONTransport<Req, Resp, Evt>::JSONTransport;
159215

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

164267
static constexpr llvm::StringLiteral kHeaderContentLength = "Content-Length";
165268
static constexpr llvm::StringLiteral kHeaderFieldSeparator = ":";
@@ -168,15 +271,31 @@ class HTTPDelimitedJSONTransport : public JSONTransport {
168271
};
169272

170273
/// A transport class for JSON RPC.
171-
class JSONRPCTransport : public JSONTransport {
274+
template <typename Req, typename Resp, typename Evt>
275+
class JSONRPCTransport : public JSONTransport<Req, Resp, Evt> {
172276
public:
173-
JSONRPCTransport(lldb::IOObjectSP input, lldb::IOObjectSP output)
174-
: JSONTransport(input, output) {}
175-
virtual ~JSONRPCTransport() = default;
277+
using JSONTransport<Req, Resp, Evt>::JSONTransport;
176278

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

181300
static constexpr llvm::StringLiteral kMessageSeparator = "\n";
182301
};

lldb/include/lldb/Protocol/MCP/MCPError.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
2626

2727
const std::string &getMessage() const { return m_message; }
2828

29-
lldb_protocol::mcp::Error toProtcolError() const;
29+
lldb_protocol::mcp::Error toProtocolError() const;
3030

3131
static constexpr int64_t kResourceNotFound = -32002;
3232
static constexpr int64_t kInternalError = -32603;

0 commit comments

Comments
 (0)