Files
BloomPatched/src/DebugServer/Gdb/Connection.cpp

276 lines
8.8 KiB
C++
Raw Normal View History

2021-10-02 17:39:27 +01:00
#include "Connection.hpp"
#include <arpa/inet.h>
2022-04-05 22:37:00 +01:00
#include <unistd.h>
#include <cerrno>
2021-04-04 21:04:12 +01:00
#include <fcntl.h>
#include <algorithm>
2021-04-04 21:04:12 +01:00
#include "Exceptions/ClientDisconnected.hpp"
#include "Exceptions/ClientCommunicationError.hpp"
2021-10-02 17:39:27 +01:00
#include "src/Exceptions/Exception.hpp"
2021-10-02 17:39:27 +01:00
#include "src/Logger/Logger.hpp"
2021-04-04 21:04:12 +01:00
namespace Bloom::DebugServer::Gdb
{
using namespace Exceptions;
using namespace Bloom::Exceptions;
using ResponsePackets::ResponsePacket;
Connection::Connection(int serverSocketFileDescriptor, EventFdNotifier& interruptEventNotifier)
: interruptEventNotifier(interruptEventNotifier)
{
this->accept(serverSocketFileDescriptor);
2021-04-04 21:04:12 +01:00
::fcntl(
this->socketFileDescriptor.value(),
F_SETFL,
::fcntl(this->socketFileDescriptor.value(), F_GETFL, 0) | O_NONBLOCK
);
this->epollInstance.addEntry(
this->socketFileDescriptor.value(),
static_cast<std::uint16_t>(::EPOLL_EVENTS::EPOLLIN)
);
this->enableReadInterrupts();
}
2021-04-04 21:04:12 +01:00
Connection::~Connection() {
this->close();
}
std::string Connection::getIpAddress() const {
std::array<char, INET_ADDRSTRLEN> ipAddress = {};
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
throw Exception("Failed to convert client IP address to text form.");
}
return std::string(ipAddress.data());
2021-04-04 21:04:12 +01:00
}
2022-10-01 21:01:37 +01:00
std::vector<RawPacket> Connection::readRawPackets() {
std::vector<RawPacket> output;
const auto bytes = this->read();
std::size_t bufferSize = bytes.size();
for (std::size_t byteIndex = 0; byteIndex < bufferSize; byteIndex++) {
auto byte = bytes[byteIndex];
if (byte == 0x03) {
/*
* This is an interrupt packet - it doesn't carry any of the usual packet frame bytes, so we'll just
* add them here, in order to keep things consistent.
*
* Because we're effectively faking the packet frame, we can use any value for the checksum.
*/
output.push_back({'$', byte, '#', 'F', 'F'});
} else if (byte == '$') {
// Beginning of packet
2022-10-01 21:01:37 +01:00
RawPacket rawPacket;
rawPacket.push_back('$');
auto packetIndex = byteIndex;
bool validPacket = false;
bool isByteEscaped = false;
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
byte = bytes[packetIndex];
if (byte == '}' && !isByteEscaped) {
isByteEscaped = true;
continue;
}
if (!isByteEscaped) {
if (byte == '$') {
// Unexpected end of packet
validPacket = false;
break;
}
if (byte == '#') {
// End of packet data
if ((bufferSize - 1) < (packetIndex + 2)) {
// There should be at least two more bytes in the buffer, for the checksum.
break;
}
rawPacket.push_back(byte);
// Add the checksum bytes and break the loop
rawPacket.push_back(bytes[++packetIndex]);
rawPacket.push_back(bytes[++packetIndex]);
validPacket = true;
break;
}
} else {
// Escaped bytes are XOR'd with a 0x20 mask.
byte ^= 0x20;
isByteEscaped = false;
}
rawPacket.push_back(byte);
}
if (validPacket) {
// Acknowledge receipt
this->write({'+'});
2022-04-08 22:17:46 +01:00
Logger::debug("Read GDB packet: " + std::string(rawPacket.begin(), rawPacket.end()));
output.emplace_back(std::move(rawPacket));
byteIndex = packetIndex;
}
}
2021-04-04 21:04:12 +01:00
}
return output;
2021-04-04 21:04:12 +01:00
}
void Connection::writePacket(const ResponsePacket& packet) {
// Write the packet repeatedly until the GDB client acknowledges it.
int attempts = 0;
2022-04-08 22:17:46 +01:00
const auto rawPacket = packet.toRawPacket();
Logger::debug("Writing GDB packet: " + std::string(rawPacket.begin(), rawPacket.end()));
2021-04-04 21:04:12 +01:00
do {
if (attempts > 10) {
throw ClientCommunicationError(
"Failed to write GDB response packet - client failed to acknowledge receipt - retry limit reached"
);
}
2021-04-04 21:04:12 +01:00
this->write(rawPacket);
attempts++;
} while (this->readSingleByte(false).value_or(0) != '+');
}
2021-04-04 21:04:12 +01:00
void Connection::accept(int serverSocketFileDescriptor) {
int socketAddressLength = sizeof(this->socketAddress);
const auto socketFileDescriptor = ::accept(
serverSocketFileDescriptor,
reinterpret_cast<sockaddr*>(&(this->socketAddress)),
reinterpret_cast<socklen_t*>(&socketAddressLength)
);
if (socketFileDescriptor < 0) {
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
}
this->socketFileDescriptor = socketFileDescriptor;
}
void Connection::close() noexcept {
if (this->socketFileDescriptor.value_or(-1) >= 0) {
::close(this->socketFileDescriptor.value());
this->socketFileDescriptor = std::nullopt;
}
}
std::vector<unsigned char> Connection::read(
std::optional<std::size_t> bytes,
bool interruptible,
std::optional<std::chrono::milliseconds> timeout
) {
auto output = std::vector<unsigned char>();
2021-04-04 21:04:12 +01:00
2022-04-16 21:23:03 +01:00
if (this->readInterruptEnabled != interruptible) {
if (interruptible) {
this->enableReadInterrupts();
2021-04-04 21:04:12 +01:00
} else {
2022-04-16 21:23:03 +01:00
this->disableReadInterrupts();
}
2021-04-04 21:04:12 +01:00
}
2022-04-16 21:23:03 +01:00
// Clear any previous interrupts that are still hanging around
this->interruptEventNotifier.clear();
2021-04-04 21:04:12 +01:00
const auto eventFileDescriptor = this->epollInstance.waitForEvent(timeout);
2021-04-04 21:04:12 +01:00
if (!eventFileDescriptor.has_value()) {
// Timed out
return output;
}
if (eventFileDescriptor.value() == this->interruptEventNotifier.getFileDescriptor()) {
// Interrupted
this->interruptEventNotifier.clear();
return output;
}
2021-04-04 21:04:12 +01:00
const auto bytesToRead = bytes.value_or(Connection::ABSOLUTE_MAXIMUM_PACKET_READ_SIZE);
output.resize(bytesToRead, 0x00);
2021-04-04 21:04:12 +01:00
const auto bytesRead = ::read(
this->socketFileDescriptor.value(),
output.data(),
bytesToRead
);
if (bytesRead < 0) {
throw ClientCommunicationError(
"Failed to read data from GDB client - error code: " + std::to_string(errno)
);
}
2021-04-04 21:04:12 +01:00
if (bytesRead == 0) {
// Client has disconnected
throw ClientDisconnected();
2021-04-04 21:04:12 +01:00
}
if (bytesRead != output.size()) {
output.resize(static_cast<unsigned long>(std::max(ssize_t{0}, bytesRead)));
}
return output;
2021-04-04 21:04:12 +01:00
}
std::optional<unsigned char> Connection::readSingleByte(bool interruptible) {
auto bytes = this->read(1, interruptible, std::chrono::milliseconds(300));
2021-04-04 21:04:12 +01:00
if (!bytes.empty()) {
return bytes.front();
}
2021-04-04 21:04:12 +01:00
return std::nullopt;
2021-04-04 21:04:12 +01:00
}
void Connection::write(const std::vector<unsigned char>& buffer) {
if (::write(this->socketFileDescriptor.value(), buffer.data(), buffer.size()) == -1) {
if (errno == EPIPE || errno == ECONNRESET) {
// Connection was closed
throw ClientDisconnected();
}
2021-04-04 21:04:12 +01:00
throw ClientCommunicationError(
"Failed to write " + std::to_string(buffer.size()) + " bytes to GDP client socket - error no: "
+ std::to_string(errno)
);
}
}
2021-04-04 21:04:12 +01:00
void Connection::disableReadInterrupts() {
this->epollInstance.removeEntry(this->interruptEventNotifier.getFileDescriptor());
this->readInterruptEnabled = false;
}
2021-04-04 21:04:12 +01:00
void Connection::enableReadInterrupts() {
this->epollInstance.addEntry(
this->interruptEventNotifier.getFileDescriptor(),
static_cast<std::uint16_t>(::EPOLL_EVENTS::EPOLLIN)
);
this->readInterruptEnabled = true;
2021-04-04 21:04:12 +01:00
}
}