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
103protected:
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;
108};
109
110/// An IOTransport sends and receives messages using an IOObject.
111template <typename Req, typename Resp, typename Evt>
112class IOTransport : public Transport<Req, Resp, Evt> {
113public:
114 using Transport<Req, Resp, Evt>::Transport;
116
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>
126 Status status;
128 m_in,
129 std::bind(&IOTransport::OnRead, this, std::placeholders::_1,
130 std::ref(handler)),
131 status);
132 if (status.Fail()) {
133 return status.takeError();
134 }
135 return read_handle;
136 }
137
138 /// Public for testing purposes, otherwise this should be an implementation
139 /// detail.
140 static constexpr size_t kReadBufferSize = 1024;
141
142 // FIXME: Write should be protected.
143 llvm::Error Write(const llvm::json::Value &message) {
144 this->Logv("<-- {0}", message);
145 std::string output = Encode(message);
146 size_t bytes_written = output.size();
147 return m_out->Write(output.data(), bytes_written).takeError();
148 }
149
150protected:
151 virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
152 virtual std::string Encode(const llvm::json::Value &message) = 0;
153
154 llvm::SmallString<kReadBufferSize> m_buffer;
155
156private:
157 void OnRead(MainLoopBase &loop, MessageHandler &handler) {
158 char buf[kReadBufferSize];
159 size_t num_bytes = sizeof(buf);
160 if (Status status = m_in->Read(buf, num_bytes); status.Fail()) {
161 handler.OnError(status.takeError());
162 return;
163 }
164
165 if (num_bytes)
166 m_buffer.append(llvm::StringRef(buf, num_bytes));
167
168 // If the buffer has contents, try parsing any pending messages.
169 if (!m_buffer.empty()) {
170 llvm::Expected<std::vector<std::string>> raw_messages = Parse();
171 if (llvm::Error error = raw_messages.takeError()) {
172 handler.OnError(std::move(error));
173 return;
174 }
175
176 for (const std::string &raw_message : *raw_messages) {
177 llvm::Expected<typename Transport<Req, Resp, Evt>::Message> message =
178 llvm::json::parse<typename Transport<Req, Resp, Evt>::Message>(
179 raw_message);
180 if (!message) {
181 handler.OnError(message.takeError());
182 return;
183 }
184
185 std::visit([&handler](auto &&msg) { handler.Received(msg); }, *message);
186 }
187 }
188
189 // Check if we reached EOF.
190 if (num_bytes == 0) {
191 // EOF reached, but there may still be unhandled contents in the buffer.
192 if (!m_buffer.empty())
193 handler.OnError(llvm::make_error<TransportUnhandledContentsError>(
194 std::string(m_buffer.str())));
195 handler.OnClosed();
196 }
197 }
198
201};
202
203/// A transport class for JSON with a HTTP header.
204template <typename Req, typename Resp, typename Evt>
205class HTTPDelimitedJSONTransport : public IOTransport<Req, Resp, Evt> {
206public:
207 using IOTransport<Req, Resp, Evt>::IOTransport;
208
209protected:
210 /// Encodes messages based on
211 /// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
212 std::string Encode(const llvm::json::Value &message) override {
213 std::string output;
214 std::string raw_message = llvm::formatv("{0}", message).str();
215 llvm::raw_string_ostream OS(output);
217 << std::to_string(raw_message.size()) << kEndOfHeader << raw_message;
218 return output;
219 }
220
221 /// Parses messages based on
222 /// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
223 llvm::Expected<std::vector<std::string>> Parse() override {
224 std::vector<std::string> messages;
225 llvm::StringRef buffer = this->m_buffer;
226 while (buffer.contains(kEndOfHeader)) {
227 auto [headers, rest] = buffer.split(kEndOfHeader);
228 size_t content_length = 0;
229 // HTTP Headers are formatted like `<field-name> ':' [<field-value>]`.
230 for (const llvm::StringRef &header :
231 llvm::split(headers, kHeaderSeparator)) {
232 auto [key, value] = header.split(kHeaderFieldSeparator);
233 // 'Content-Length' is the only meaningful key at the moment. Others are
234 // ignored.
235 if (!key.equals_insensitive(kHeaderContentLength))
236 continue;
237
238 value = value.trim();
239 if (!llvm::to_integer(value, content_length, 10)) {
240 // Clear the buffer to avoid re-parsing this malformed message.
241 this->m_buffer.clear();
242 return llvm::createStringError(std::errc::invalid_argument,
243 "invalid content length: %s",
244 value.str().c_str());
245 }
246 }
247
248 // Check if we have enough data.
249 if (content_length > rest.size())
250 break;
251
252 llvm::StringRef body = rest.take_front(content_length);
253 buffer = rest.drop_front(content_length);
254 messages.emplace_back(body.str());
255 this->Logv("--> {0}", body);
256 }
257
258 // Store the remainder of the buffer for the next read callback.
259 this->m_buffer = buffer.str();
260
261 return std::move(messages);
262 }
263
264 static constexpr llvm::StringLiteral kHeaderContentLength = "Content-Length";
265 static constexpr llvm::StringLiteral kHeaderFieldSeparator = ":";
266 static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n";
267 static constexpr llvm::StringLiteral kEndOfHeader = "\r\n\r\n";
268};
269
270/// A transport class for JSON RPC.
271template <typename Req, typename Resp, typename Evt>
272class JSONRPCTransport : public IOTransport<Req, Resp, Evt> {
273public:
274 using IOTransport<Req, Resp, Evt>::IOTransport;
275
276protected:
277 std::string Encode(const llvm::json::Value &message) override {
278 return llvm::formatv("{0}{1}", message, kMessageSeparator).str();
279 }
280
281 llvm::Expected<std::vector<std::string>> Parse() override {
282 std::vector<std::string> messages;
283 llvm::StringRef buf = this->m_buffer;
284 while (buf.contains(kMessageSeparator)) {
285 auto [raw_json, rest] = buf.split(kMessageSeparator);
286 buf = rest;
287 messages.emplace_back(raw_json.str());
288 this->Logv("--> {0}", raw_json);
289 }
290
291 // Store the remainder of the buffer for the next read callback.
292 this->m_buffer = buf.str();
293
294 return messages;
295 }
296
297 static constexpr llvm::StringLiteral kMessageSeparator = "\n";
298};
299
300} // namespace lldb_private
301
302#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
virtual std::string Encode(const llvm::json::Value &message)=0
llvm::Error Write(const llvm::json::Value &message)
llvm::Expected< MainLoop::ReadHandleUP > RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) override
RegisterMessageHandler registers the Transport with the given MainLoop and handles any incoming messa...
virtual llvm::Expected< std::vector< std::string > > Parse()=0
llvm::Error Send(const Resp &resp) override
Sends a response to a specific request.
typename Transport< Req, Resp, Evt >::MessageHandler MessageHandler
void OnRead(MainLoopBase &loop, MessageHandler &handler)
llvm::SmallString< kReadBufferSize > m_buffer
llvm::Error Send(const Req &req) override
Sends a request, a message that expects a response.
llvm::Error Send(const Evt &evt) override
Sends an event, a message that does not require a response.
IOTransport(lldb::IOObjectSP in, lldb::IOObjectSP out)
static constexpr size_t kReadBufferSize
Public for testing purposes, otherwise this should be an implementation detail.
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
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