1//===-- Transport.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 "Transport.h"
10#include "DAPLog.h"
11#include "Protocol/ProtocolBase.h"
12#include "lldb/Utility/IOObject.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;
27using namespace lldb_dap;
28using namespace lldb_dap::protocol;
29
30/// ReadFull attempts to read the specified number of bytes. If EOF is
31/// encountered, an empty string is returned.
32static Expected<std::string>
33ReadFull(IOObject &descriptor, size_t length,
34 std::optional<std::chrono::microseconds> timeout = std::nullopt) {
35 if (!descriptor.IsValid())
36 return createStringError(Fmt: "transport output is closed");
37
38 bool timeout_supported = true;
39 // FIXME: SelectHelper does not work with NativeFile on Win32.
40#if _WIN32
41 timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket;
42#endif
43
44 if (timeout && timeout_supported) {
45 SelectHelper sh;
46 sh.SetTimeout(*timeout);
47 sh.FDSetRead(fd: descriptor.GetWaitableHandle());
48 Status status = sh.Select();
49 if (status.Fail()) {
50 // Convert timeouts into a specific error.
51 if (status.GetType() == lldb::eErrorTypePOSIX &&
52 status.GetError() == ETIMEDOUT)
53 return make_error<TimeoutError>();
54 return status.takeError();
55 }
56 }
57
58 std::string data;
59 data.resize(n: length);
60 Status status = descriptor.Read(buf: data.data(), num_bytes&: length);
61 if (status.Fail())
62 return status.takeError();
63
64 // Read returns '' on EOF.
65 if (length == 0)
66 return make_error<EndOfFileError>();
67
68 // Return the actual number of bytes read.
69 return data.substr(pos: 0, n: length);
70}
71
72static Expected<std::string>
73ReadUntil(IOObject &descriptor, StringRef delimiter,
74 std::optional<std::chrono::microseconds> timeout = std::nullopt) {
75 std::string buffer;
76 buffer.reserve(res: delimiter.size() + 1);
77 while (!llvm::StringRef(buffer).ends_with(Suffix: delimiter)) {
78 Expected<std::string> next =
79 ReadFull(descriptor, length: buffer.empty() ? delimiter.size() : 1, timeout);
80 if (auto Err = next.takeError())
81 return std::move(Err);
82 buffer += *next;
83 }
84 return buffer.substr(pos: 0, n: buffer.size() - delimiter.size());
85}
86
87/// DAP message format
88/// ```
89/// Content-Length: (?<length>\d+)\r\n\r\n(?<content>.{\k<length>})
90/// ```
91static constexpr StringLiteral kHeaderContentLength = "Content-Length: ";
92static constexpr StringLiteral kHeaderSeparator = "\r\n\r\n";
93
94namespace lldb_dap {
95
96char EndOfFileError::ID;
97char TimeoutError::ID;
98
99Transport::Transport(StringRef client_name, Log *log, IOObjectSP input,
100 IOObjectSP output)
101 : m_client_name(client_name), m_log(log), m_input(std::move(input)),
102 m_output(std::move(output)) {}
103
104Expected<Message> Transport::Read(const std::chrono::microseconds &timeout) {
105 if (!m_input || !m_input->IsValid())
106 return createStringError(Fmt: "transport output is closed");
107
108 IOObject *input = m_input.get();
109 Expected<std::string> message_header =
110 ReadFull(descriptor&: *input, length: kHeaderContentLength.size(), timeout);
111 if (!message_header)
112 return message_header.takeError();
113 if (*message_header != kHeaderContentLength)
114 return createStringError(S: formatv(Fmt: "expected '{0}' and got '{1}'",
115 Vals: kHeaderContentLength, Vals&: *message_header)
116 .str());
117
118 Expected<std::string> raw_length = ReadUntil(descriptor&: *input, delimiter: kHeaderSeparator);
119 if (!raw_length)
120 return handleErrors(E: raw_length.takeError(),
121 Hs: [&](const EndOfFileError &E) -> llvm::Error {
122 return createStringError(
123 Fmt: "unexpected EOF while reading header separator");
124 });
125
126 size_t length;
127 if (!to_integer(S: *raw_length, Num&: length))
128 return createStringError(
129 S: formatv(Fmt: "invalid content length {0}", Vals&: *raw_length).str());
130
131 Expected<std::string> raw_json = ReadFull(descriptor&: *input, length);
132 if (!raw_json)
133 return handleErrors(
134 E: raw_json.takeError(), Hs: [&](const EndOfFileError &E) -> llvm::Error {
135 return createStringError(Fmt: "unexpected EOF while reading JSON");
136 });
137
138 DAP_LOG(m_log, "--> ({0}) {1}", m_client_name, *raw_json);
139
140 return json::parse<Message>(/*JSON=*/*raw_json,
141 /*RootName=*/"protocol_message");
142}
143
144Error Transport::Write(const Message &message) {
145 if (!m_output || !m_output->IsValid())
146 return createStringError(Fmt: "transport output is closed");
147
148 std::string json = formatv(Fmt: "{0}", Vals: toJSON(message)).str();
149
150 DAP_LOG(m_log, "<-- ({0}) {1}", m_client_name, json);
151
152 std::string Output;
153 raw_string_ostream OS(Output);
154 OS << kHeaderContentLength << json.length() << kHeaderSeparator << json;
155 size_t num_bytes = Output.size();
156 return m_output->Write(buf: Output.data(), num_bytes).takeError();
157}
158
159} // end namespace lldb_dap
160

source code of lldb/tools/lldb-dap/Transport.cpp