blob: ffd0c49f1770b3e248bcf6cec70b9a96369711bc [file] [log] [blame]
//===-- Transport.cpp -----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Transport.h"
#include "DAPLog.h"
#include "Protocol/ProtocolBase.h"
#include "lldb/Utility/IOObject.h"
#include "lldb/Utility/SelectHelper.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <string>
#include <utility>
using namespace llvm;
using namespace lldb;
using namespace lldb_private;
using namespace lldb_dap;
using namespace lldb_dap::protocol;
/// ReadFull attempts to read the specified number of bytes. If EOF is
/// encountered, an empty string is returned.
static Expected<std::string>
ReadFull(IOObject &descriptor, size_t length,
std::optional<std::chrono::microseconds> timeout = std::nullopt) {
if (!descriptor.IsValid())
return createStringError("transport output is closed");
bool timeout_supported = true;
// FIXME: SelectHelper does not work with NativeFile on Win32.
#if _WIN32
timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket;
#endif
if (timeout && timeout_supported) {
SelectHelper sh;
sh.SetTimeout(*timeout);
sh.FDSetRead(descriptor.GetWaitableHandle());
Status status = sh.Select();
if (status.Fail()) {
// Convert timeouts into a specific error.
if (status.GetType() == lldb::eErrorTypePOSIX &&
status.GetError() == ETIMEDOUT)
return make_error<TimeoutError>();
return status.takeError();
}
}
std::string data;
data.resize(length);
Status status = descriptor.Read(data.data(), length);
if (status.Fail())
return status.takeError();
// Read returns '' on EOF.
if (length == 0)
return make_error<EndOfFileError>();
// Return the actual number of bytes read.
return data.substr(0, length);
}
static Expected<std::string>
ReadUntil(IOObject &descriptor, StringRef delimiter,
std::optional<std::chrono::microseconds> timeout = std::nullopt) {
std::string buffer;
buffer.reserve(delimiter.size() + 1);
while (!llvm::StringRef(buffer).ends_with(delimiter)) {
Expected<std::string> next =
ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1, timeout);
if (auto Err = next.takeError())
return std::move(Err);
buffer += *next;
}
return buffer.substr(0, buffer.size() - delimiter.size());
}
/// DAP message format
/// ```
/// Content-Length: (?<length>\d+)\r\n\r\n(?<content>.{\k<length>})
/// ```
static constexpr StringLiteral kHeaderContentLength = "Content-Length: ";
static constexpr StringLiteral kHeaderSeparator = "\r\n\r\n";
namespace lldb_dap {
char EndOfFileError::ID;
char TimeoutError::ID;
Transport::Transport(StringRef client_name, Log *log, IOObjectSP input,
IOObjectSP output)
: m_client_name(client_name), m_log(log), m_input(std::move(input)),
m_output(std::move(output)) {}
Expected<Message> Transport::Read(const std::chrono::microseconds &timeout) {
if (!m_input || !m_input->IsValid())
return createStringError("transport output is closed");
IOObject *input = m_input.get();
Expected<std::string> message_header =
ReadFull(*input, kHeaderContentLength.size(), timeout);
if (!message_header)
return message_header.takeError();
if (*message_header != kHeaderContentLength)
return createStringError(formatv("expected '{0}' and got '{1}'",
kHeaderContentLength, *message_header)
.str());
Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator);
if (!raw_length)
return handleErrors(raw_length.takeError(),
[&](const EndOfFileError &E) -> llvm::Error {
return createStringError(
"unexpected EOF while reading header separator");
});
size_t length;
if (!to_integer(*raw_length, length))
return createStringError(
formatv("invalid content length {0}", *raw_length).str());
Expected<std::string> raw_json = ReadFull(*input, length);
if (!raw_json)
return handleErrors(
raw_json.takeError(), [&](const EndOfFileError &E) -> llvm::Error {
return createStringError("unexpected EOF while reading JSON");
});
DAP_LOG(m_log, "--> ({0}) {1}", m_client_name, *raw_json);
return json::parse<Message>(*raw_json);
}
Error Transport::Write(const Message &message) {
if (!m_output || !m_output->IsValid())
return createStringError("transport output is closed");
std::string json = formatv("{0}", toJSON(message)).str();
DAP_LOG(m_log, "<-- ({0}) {1}", m_client_name, json);
std::string Output;
raw_string_ostream OS(Output);
OS << kHeaderContentLength << json.length() << kHeaderSeparator << json;
size_t num_bytes = Output.size();
return m_output->Write(Output.data(), num_bytes).takeError();
}
} // end namespace lldb_dap