LLDB mainline
JSONTransport.h
Go to the documentation of this file.
1//===-- JSONTransport.h ---------------------------------------------------===//
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// Transport layer for encoding and decoding JSON protocol messages.
10//
11//===----------------------------------------------------------------------===//
12
13#ifndef LLDB_HOST_JSONTRANSPORT_H
14#define LLDB_HOST_JSONTRANSPORT_H
15
16#include "lldb/Host/MainLoop.h"
19#include "lldb/Utility/Status.h"
20#include "lldb/lldb-forward.h"
21#include "llvm/ADT/StringExtras.h"
22#include "llvm/ADT/StringRef.h"
23#include "llvm/Support/Error.h"
24#include "llvm/Support/ErrorHandling.h"
25#include "llvm/Support/FormatVariadic.h"
26#include "llvm/Support/JSON.h"
27#include "llvm/Support/raw_ostream.h"
28#include <string>
29#include <system_error>
30#include <variant>
31#include <vector>
32
33namespace lldb_private {
34
36 : public llvm::ErrorInfo<TransportUnhandledContentsError> {
37public:
38 static char ID;
39
40 explicit TransportUnhandledContentsError(std::string unhandled_contents);
41
42 void log(llvm::raw_ostream &OS) const override;
43 std::error_code convertToErrorCode() const override;
44
45 const std::string &getUnhandledContents() const {
47 }
48
49private:
51};
52
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.
59template <typename Req, typename Resp, typename Evt> class Transport {
60public:
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).
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;
102
103 // FIXME: Refactor mcp::Server to not directly access log on the transport.
104 // protected:
105 template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
106 Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
107 }
108 virtual void Log(llvm::StringRef message) = 0;
109};
110
111/// A JSONTransport will encode and decode messages using JSON.
112template <typename Req, typename Resp, typename Evt>
113class JSONTransport : public Transport<Req, Resp, Evt> {
114public:
115 using Transport<Req, Resp, Evt>::Transport;
117
120
121 llvm::Error Send(const Evt &evt) override { return Write(evt); }
122 llvm::Error Send(const Req &req) override { return Write(req); }
123 llvm::Error Send(const Resp &resp) override { return Write(resp); }
124
125 llvm::Expected<MainLoop::ReadHandleUP>
127 Status status;
129 m_in,
130 std::bind(&JSONTransport::OnRead, this, std::placeholders::_1,
131 std::ref(handler)),
132 status);
133 if (status.Fail()) {
134 return status.takeError();
135 }
136 return read_handle;
137 }
138
139 /// Public for testing purposes, otherwise this should be an implementation
140 /// detail.
141 static constexpr size_t kReadBufferSize = 1024;
142
143 // FIXME: Write should be protected.
144 llvm::Error Write(const llvm::json::Value &message) {
145 this->Logv("<-- {0}", message);
146 std::string output = Encode(message);
147 size_t bytes_written = output.size();
148 return m_out->Write(output.data(), bytes_written).takeError();
149 }
150
151protected:
152 virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
153 virtual std::string Encode(const llvm::json::Value &message) = 0;
154
155 llvm::SmallString<kReadBufferSize> m_buffer;
156
157private:
158 void OnRead(MainLoopBase &loop, MessageHandler &handler) {
159 char buf[kReadBufferSize];
160 size_t num_bytes = sizeof(buf);
161 if (Status status = m_in->Read(buf, num_bytes); status.Fail()) {
162 handler.OnError(status.takeError());
163 return;
164 }
165
166 if (num_bytes)
167 m_buffer.append(llvm::StringRef(buf, num_bytes));
168
169 // If the buffer has contents, try parsing any pending messages.
170 if (!m_buffer.empty()) {
171 llvm::Expected<std::vector<std::string>> raw_messages = Parse();
172 if (llvm::Error error = raw_messages.takeError()) {
173 handler.OnError(std::move(error));
174 return;
175 }
176
177 for (const std::string &raw_message : *raw_messages) {
178 llvm::Expected<typename Transport<Req, Resp, Evt>::Message> message =
179 llvm::json::parse<typename Transport<Req, Resp, Evt>::Message>(
180 raw_message);
181 if (!message) {
182 handler.OnError(message.takeError());
183 return;
184 }
185
186 std::visit([&handler](auto &&msg) { handler.Received(msg); }, *message);
187 }
188 }
189
190 // Check if we reached EOF.
191 if (num_bytes == 0) {
192 // EOF reached, but there may still be unhandled contents in the buffer.
193 if (!m_buffer.empty())
194 handler.OnError(llvm::make_error<TransportUnhandledContentsError>(
195 std::string(m_buffer.str())));
196 handler.OnClosed();
197 }
198 }
199
202};
203
204/// A transport class for JSON with a HTTP header.
205template <typename Req, typename Resp, typename Evt>
206class HTTPDelimitedJSONTransport : public JSONTransport<Req, Resp, Evt> {
207public:
208 using JSONTransport<Req, Resp, Evt>::JSONTransport;
209
210protected:
211 /// Encodes messages based on
212 /// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
213 std::string Encode(const llvm::json::Value &message) override {
214 std::string output;
215 std::string raw_message = llvm::formatv("{0}", message).str();
216 llvm::raw_string_ostream OS(output);
218 << std::to_string(raw_message.size()) << kEndOfHeader << raw_message;
219 return output;
220 }
221
222 /// Parses messages based on
223 /// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
224 llvm::Expected<std::vector<std::string>> Parse() override {
225 std::vector<std::string> messages;
226 llvm::StringRef buffer = this->m_buffer;
227 while (buffer.contains(kEndOfHeader)) {
228 auto [headers, rest] = buffer.split(kEndOfHeader);
229 size_t content_length = 0;
230 // HTTP Headers are formatted like `<field-name> ':' [<field-value>]`.
231 for (const llvm::StringRef &header :
232 llvm::split(headers, kHeaderSeparator)) {
233 auto [key, value] = header.split(kHeaderFieldSeparator);
234 // 'Content-Length' is the only meaningful key at the moment. Others are
235 // ignored.
236 if (!key.equals_insensitive(kHeaderContentLength))
237 continue;
238
239 value = value.trim();
240 if (!llvm::to_integer(value, content_length, 10)) {
241 // Clear the buffer to avoid re-parsing this malformed message.
242 this->m_buffer.clear();
243 return llvm::createStringError(std::errc::invalid_argument,
244 "invalid content length: %s",
245 value.str().c_str());
246 }
247 }
248
249 // Check if we have enough data.
250 if (content_length > rest.size())
251 break;
252
253 llvm::StringRef body = rest.take_front(content_length);
254 buffer = rest.drop_front(content_length);
255 messages.emplace_back(body.str());
256 this->Logv("--> {0}", body);
257 }
258
259 // Store the remainder of the buffer for the next read callback.
260 this->m_buffer = buffer.str();
261
262 return std::move(messages);
263 }
264
265 static constexpr llvm::StringLiteral kHeaderContentLength = "Content-Length";
266 static constexpr llvm::StringLiteral kHeaderFieldSeparator = ":";
267 static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n";
268 static constexpr llvm::StringLiteral kEndOfHeader = "\r\n\r\n";
269};
270
271/// A transport class for JSON RPC.
272template <typename Req, typename Resp, typename Evt>
273class JSONRPCTransport : public JSONTransport<Req, Resp, Evt> {
274public:
275 using JSONTransport<Req, Resp, Evt>::JSONTransport;
276
277protected:
278 std::string Encode(const llvm::json::Value &message) override {
279 return llvm::formatv("{0}{1}", message, kMessageSeparator).str();
280 }
281
282 llvm::Expected<std::vector<std::string>> Parse() override {
283 std::vector<std::string> messages;
284 llvm::StringRef buf = this->m_buffer;
285 while (buf.contains(kMessageSeparator)) {
286 auto [raw_json, rest] = buf.split(kMessageSeparator);
287 buf = rest;
288 messages.emplace_back(raw_json.str());
289 this->Logv("--> {0}", raw_json);
290 }
291
292 // Store the remainder of the buffer for the next read callback.
293 this->m_buffer = buf.str();
294
295 return messages;
296 }
297
298 static constexpr llvm::StringLiteral kMessageSeparator = "\n";
299};
300
301} // namespace lldb_private
302
303#endif
static llvm::raw_ostream & error(Stream &strm)
A transport class for JSON with a HTTP header.
static constexpr llvm::StringLiteral kEndOfHeader
static constexpr llvm::StringLiteral kHeaderFieldSeparator
static constexpr llvm::StringLiteral kHeaderContentLength
std::string Encode(const llvm::json::Value &message) override
Encodes messages based on https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol.
llvm::Expected< std::vector< std::string > > Parse() override
Parses messages based on https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol.
static constexpr llvm::StringLiteral kHeaderSeparator
A transport class for JSON RPC.
std::string Encode(const llvm::json::Value &message) override
static constexpr llvm::StringLiteral kMessageSeparator
llvm::Expected< std::vector< std::string > > Parse() override
llvm::Error Send(const Evt &evt) override
Sends an event, a message that does not require a response.
virtual llvm::Expected< std::vector< std::string > > Parse()=0
llvm::SmallString< kReadBufferSize > m_buffer
llvm::Error Send(const Req &req) override
Sends a request, a message that expects a response.
void OnRead(MainLoopBase &loop, MessageHandler &handler)
static constexpr size_t kReadBufferSize
Public for testing purposes, otherwise this should be an implementation detail.
virtual std::string Encode(const llvm::json::Value &message)=0
typename Transport< Req, Resp, Evt >::MessageHandler MessageHandler
llvm::Error Write(const llvm::json::Value &message)
llvm::Error Send(const Resp &resp) override
Sends a response to a specific request.
llvm::Expected< MainLoop::ReadHandleUP > RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) override
RegisterMessageHandler registers the Transport with the given MainLoop and handles any incoming messa...
JSONTransport(lldb::IOObjectSP in, lldb::IOObjectSP out)
std::unique_ptr< ReadHandle > ReadHandleUP
ReadHandleUP RegisterReadObject(const lldb::IOObjectSP &object_sp, const Callback &callback, Status &error) override
An error handling class.
Definition Status.h:118
llvm::Error takeError()
Definition Status.h:170
bool Fail() const
Test for error condition.
Definition Status.cpp:294
void log(llvm::raw_ostream &OS) const override
TransportUnhandledContentsError(std::string unhandled_contents)
std::error_code convertToErrorCode() const override
const std::string & getUnhandledContents() const
Implemented to handle incoming messages. (See Run() below).
virtual void Received(const Evt &)=0
Called when an event is received.
virtual void OnError(llvm::Error)=0
Called when an error occurs while reading from the transport.
virtual void OnClosed()=0
Called on EOF or client disconnect.
virtual void Received(const Resp &)=0
Called when a response is received.
virtual void Received(const Req &)=0
Called when a request is received.
A transport is responsible for maintaining the connection to a client application,...
virtual llvm::Expected< MainLoop::ReadHandleUP > RegisterMessageHandler(MainLoop &loop, MessageHandler &handler)=0
RegisterMessageHandler registers the Transport with the given MainLoop and handles any incoming messa...
std::variant< Req, Resp, Evt > Message
virtual llvm::Error Send(const Evt &)=0
Sends an event, a message that does not require a response.
virtual llvm::Error Send(const Resp &)=0
Sends a response to a specific request.
virtual void Log(llvm::StringRef message)=0
std::shared_ptr< MessageHandler > MessageHandlerSP
auto Logv(const char *Fmt, Ts &&...Vals)
virtual llvm::Error Send(const Req &)=0
Sends a request, a message that expects a response.
virtual ~Transport()=default
A class that represents a running process on the host machine.
MainLoopPosix MainLoop
Definition MainLoop.h:20
std::shared_ptr< lldb_private::IOObject > IOObjectSP