#include "tb_gdb.h"
#include "tb_constants.h"

#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>

static bool send_all(int fd, const uint8_t *buf, size_t len) {
	size_t sent = 0;
	while (sent < len) {
#ifdef MSG_NOSIGNAL
		ssize_t n = send(fd, buf + sent, len - sent, MSG_NOSIGNAL);
#else
		ssize_t n = send(fd, buf + sent, len - sent, 0);
#endif
		if (n < 0) {
			if (errno == EINTR)
				continue;
			return false;
		}
		sent += (size_t)n;
	}
	return true;
}

tb_gdb_server::tb_gdb_server(uint16_t port, tb_gdb_target &target)
	: port_{port}, target_{target} {
	target_xml_ =
		"<?xml version=\"1.0\"?>\n"
		"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
		"<target>\n"
		"  <architecture>riscv:rv32</architecture>\n"
		"  <feature name=\"org.gnu.gdb.riscv.cpu\">\n"
		"    <reg name=\"x0\"  bitsize=\"32\" type=\"int\" regnum=\"0\"/>\n"
		"    <reg name=\"x1\"  bitsize=\"32\" type=\"int\" regnum=\"1\"/>\n"
		"    <reg name=\"x2\"  bitsize=\"32\" type=\"int\" regnum=\"2\"/>\n"
		"    <reg name=\"x3\"  bitsize=\"32\" type=\"int\" regnum=\"3\"/>\n"
		"    <reg name=\"x4\"  bitsize=\"32\" type=\"int\" regnum=\"4\"/>\n"
		"    <reg name=\"x5\"  bitsize=\"32\" type=\"int\" regnum=\"5\"/>\n"
		"    <reg name=\"x6\"  bitsize=\"32\" type=\"int\" regnum=\"6\"/>\n"
		"    <reg name=\"x7\"  bitsize=\"32\" type=\"int\" regnum=\"7\"/>\n"
		"    <reg name=\"x8\"  bitsize=\"32\" type=\"int\" regnum=\"8\"/>\n"
		"    <reg name=\"x9\"  bitsize=\"32\" type=\"int\" regnum=\"9\"/>\n"
		"    <reg name=\"x10\" bitsize=\"32\" type=\"int\" regnum=\"10\"/>\n"
		"    <reg name=\"x11\" bitsize=\"32\" type=\"int\" regnum=\"11\"/>\n"
		"    <reg name=\"x12\" bitsize=\"32\" type=\"int\" regnum=\"12\"/>\n"
		"    <reg name=\"x13\" bitsize=\"32\" type=\"int\" regnum=\"13\"/>\n"
		"    <reg name=\"x14\" bitsize=\"32\" type=\"int\" regnum=\"14\"/>\n"
		"    <reg name=\"x15\" bitsize=\"32\" type=\"int\" regnum=\"15\"/>\n"
		"    <reg name=\"x16\" bitsize=\"32\" type=\"int\" regnum=\"16\"/>\n"
		"    <reg name=\"x17\" bitsize=\"32\" type=\"int\" regnum=\"17\"/>\n"
		"    <reg name=\"x18\" bitsize=\"32\" type=\"int\" regnum=\"18\"/>\n"
		"    <reg name=\"x19\" bitsize=\"32\" type=\"int\" regnum=\"19\"/>\n"
		"    <reg name=\"x20\" bitsize=\"32\" type=\"int\" regnum=\"20\"/>\n"
		"    <reg name=\"x21\" bitsize=\"32\" type=\"int\" regnum=\"21\"/>\n"
		"    <reg name=\"x22\" bitsize=\"32\" type=\"int\" regnum=\"22\"/>\n"
		"    <reg name=\"x23\" bitsize=\"32\" type=\"int\" regnum=\"23\"/>\n"
		"    <reg name=\"x24\" bitsize=\"32\" type=\"int\" regnum=\"24\"/>\n"
		"    <reg name=\"x25\" bitsize=\"32\" type=\"int\" regnum=\"25\"/>\n"
		"    <reg name=\"x26\" bitsize=\"32\" type=\"int\" regnum=\"26\"/>\n"
		"    <reg name=\"x27\" bitsize=\"32\" type=\"int\" regnum=\"27\"/>\n"
		"    <reg name=\"x28\" bitsize=\"32\" type=\"int\" regnum=\"28\"/>\n"
		"    <reg name=\"x29\" bitsize=\"32\" type=\"int\" regnum=\"29\"/>\n"
		"    <reg name=\"x30\" bitsize=\"32\" type=\"int\" regnum=\"30\"/>\n"
		"    <reg name=\"x31\" bitsize=\"32\" type=\"int\" regnum=\"31\"/>\n"
		"    <reg name=\"pc\"  bitsize=\"32\" type=\"code_ptr\" regnum=\"32\"/>\n"
		"  </feature>\n"
		"</target>\n";

	char mm_line[128];
	snprintf(
		mm_line,
		sizeof(mm_line),
		"  <memory type=\"ram\" start=\"0x%08x\" length=\"0x%08x\"/>\n",
		(unsigned)MEM_BASE,
		(unsigned)MEM_SIZE
	);

	memory_map_xml_ =
		"<?xml version=\"1.0\"?>\n"
		"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" "
		"\"http://sourceware.org/gdb/gdb-memory-map.dtd\">\n"
		"<memory-map>\n" +
		std::string(mm_line) +
		"</memory-map>\n";
}

tb_gdb_server::~tb_gdb_server() {
	close_client_();
	if (server_fd_ >= 0) {
		::close(server_fd_);
		server_fd_ = -1;
	}
}

bool tb_gdb_server::open_listen_socket_() {
	server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
	if (server_fd_ < 0) {
		fprintf(stderr, "tb_gdb: socket() failed: %s\n", strerror(errno));
		return false;
	}

	int opt = 1;
	if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
		fprintf(stderr, "tb_gdb: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
		return false;
	}
#ifdef SO_REUSEPORT
	(void)setsockopt(server_fd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
#endif

	sockaddr_in addr{};
	addr.sin_family = AF_INET;
	// Debug services are local by default. Remote access belongs behind an
	// explicit SSH tunnel, never an unauthenticated wildcard listener.
	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	addr.sin_port = htons(port_);
	if (bind(server_fd_, (sockaddr *)&addr, sizeof(addr)) < 0) {
		fprintf(stderr, "tb_gdb: bind(127.0.0.1:%u) failed: %s\n", (unsigned)port_, strerror(errno));
		return false;
	}
	if (listen(server_fd_, 1) < 0) {
		fprintf(stderr, "tb_gdb: listen() failed: %s\n", strerror(errno));
		return false;
	}
	return true;
}

bool tb_gdb_server::accept_client_() {
	sockaddr_in addr{};
	socklen_t addr_len = sizeof(addr);
	client_fd_ = accept(server_fd_, (sockaddr *)&addr, &addr_len);
	if (client_fd_ < 0) {
		fprintf(stderr, "tb_gdb: accept() failed: %s\n", strerror(errno));
		return false;
	}

	int flag = 1;
	(void)setsockopt(client_fd_, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
	return true;
}

void tb_gdb_server::close_client_() {
	if (client_fd_ >= 0) {
		::close(client_fd_);
		client_fd_ = -1;
	}
	no_ack_mode_ = false;
	breakpoints_.clear();
	ignore_breakpoint_pc_ = 0xffffffffu;
}

uint8_t tb_gdb_server::checksum_(const std::string &s) {
	uint8_t sum = 0;
	for (unsigned char c : s)
		sum = (uint8_t)(sum + c);
	return sum;
}

int tb_gdb_server::hex_val_(char c) {
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'a' && c <= 'f')
		return 10 + (c - 'a');
	if (c >= 'A' && c <= 'F')
		return 10 + (c - 'A');
	return -1;
}

std::string tb_gdb_server::to_hex_bytes_(const uint8_t *data, size_t len) {
	static const char *hex = "0123456789abcdef";
	std::string out;
	out.reserve(len * 2);
	for (size_t i = 0; i < len; ++i) {
		out.push_back(hex[(data[i] >> 4) & 0xf]);
		out.push_back(hex[data[i] & 0xf]);
	}
	return out;
}

bool tb_gdb_server::from_hex_bytes_(const std::string &hex, std::string &out_bytes) {
	out_bytes.clear();
	if (hex.size() % 2 != 0)
		return false;
	out_bytes.reserve(hex.size() / 2);
	for (size_t i = 0; i < hex.size(); i += 2) {
		int hi = hex_val_(hex[i]);
		int lo = hex_val_(hex[i + 1]);
		if (hi < 0 || lo < 0)
			return false;
		out_bytes.push_back((char)((hi << 4) | lo));
	}
	return true;
}

void tb_gdb_server::append_u32_le_hex_(std::string &out, uint32_t v) {
	for (int i = 0; i < 4; ++i) {
		const uint8_t b = (uint8_t)((v >> (8 * i)) & 0xffu);
		static const char *hex = "0123456789abcdef";
		out.push_back(hex[(b >> 4) & 0xf]);
		out.push_back(hex[b & 0xf]);
	}
}

bool tb_gdb_server::parse_u32_hex_(const std::string &s, uint32_t &out) {
	if (s.empty())
		return false;
	uint32_t v = 0;
	for (char c : s) {
		int h = hex_val_(c);
		if (h < 0)
			return false;
		v = (v << 4) | (uint32_t)h;
	}
	out = v;
	return true;
}

bool tb_gdb_server::maybe_send_ack_(bool ok) {
	if (no_ack_mode_)
		return true;
	const char c = ok ? '+' : '-';
	return send_all(client_fd_, (const uint8_t *)&c, 1);
}

bool tb_gdb_server::send_packet_(const std::string &payload) {
	std::string pkt;
	pkt.reserve(payload.size() + 4);
	pkt.push_back('$');
	pkt += payload;
	pkt.push_back('#');
	uint8_t cksum = checksum_(payload);
	static const char *hex = "0123456789abcdef";
	pkt.push_back(hex[(cksum >> 4) & 0xf]);
	pkt.push_back(hex[cksum & 0xf]);
	return send_all(client_fd_, (const uint8_t *)pkt.data(), pkt.size());
}

bool tb_gdb_server::check_interrupt_() {
	uint8_t c = 0;
	ssize_t n = recv(client_fd_, &c, 1, MSG_DONTWAIT | MSG_PEEK);
	if (n < 0) {
		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
			return false;
		return true;
	}
	if (n == 0)
		return true;
	if (c != 0x03)
		return false;
	(void)recv(client_fd_, &c, 1, MSG_DONTWAIT);
	return true;
}

bool tb_gdb_server::recv_packet_(std::string &out_payload, bool &got_interrupt) {
	out_payload.clear();
	got_interrupt = false;
	while (true) {
		uint8_t c = 0;
		ssize_t n = recv(client_fd_, &c, 1, 0);
		if (n == 0)
			return false;
		if (n < 0) {
			if (errno == EINTR)
				continue;
			return false;
		}

		if (c == 0x03) {
			got_interrupt = true;
			return true;
		}
		if (c == '+' || c == '-') {
			continue;
		}
		if (c != '$') {
			continue;
		}

		std::string payload;
		while (true) {
			n = recv(client_fd_, &c, 1, 0);
			if (n == 0)
				return false;
			if (n < 0) {
				if (errno == EINTR)
					continue;
				return false;
			}
			if (c == '#')
				break;
			payload.push_back((char)c);
		}

		char ck[2];
		for (int i = 0; i < 2; ++i) {
			n = recv(client_fd_, &c, 1, 0);
			if (n == 0)
				return false;
			if (n < 0) {
				if (errno == EINTR) {
					--i;
					continue;
				}
				return false;
			}
			ck[i] = (char)c;
		}

		bool ok = true;
		const int hi = hex_val_(ck[0]);
		const int lo = hex_val_(ck[1]);
		if (hi < 0 || lo < 0) {
			ok = false;
		} else {
			const uint8_t expected = (uint8_t)((hi << 4) | lo);
			ok = expected == checksum_(payload);
		}

		if (!maybe_send_ack_(ok))
			return false;
		if (!ok)
			continue;

		out_payload = std::move(payload);
		return true;
	}
}

std::string tb_gdb_server::stop_reply_(int signo) const {
	char buf[8];
	snprintf(buf, sizeof(buf), "S%02x", signo & 0xff);
	return std::string(buf);
}

std::string tb_gdb_server::stop_reply_exit_() const {
	char buf[8];
	snprintf(buf, sizeof(buf), "W%02x", (unsigned)(target_.exit_code() & 0xffu));
	return std::string(buf);
}

std::string tb_gdb_server::handle_command_(const std::string &cmd, bool &should_close) {
	should_close = false;

	if (cmd.empty())
		return "";

	// General queries
	if (cmd == "?")
		return stop_reply_(5); // SIGTRAP

	if (cmd.rfind("qSupported", 0) == 0) {
		return "PacketSize=4000;qXfer:features:read+;qXfer:memory-map:read+;QStartNoAckMode+;swbreak+;hwbreak+;vContSupported+;qRcmd+";
	}
	if (cmd == "QStartNoAckMode") {
		no_ack_mode_ = true;
		return "OK";
	}
	if (cmd == "qAttached")
		return "1";
	if (cmd == "qC")
		return "QC1";
	if (cmd == "qfThreadInfo")
		return "m1";
	if (cmd == "qsThreadInfo")
		return "l";
	if (cmd.rfind("H", 0) == 0)
		return "OK";
	if (cmd.rfind("qThreadExtraInfo", 0) == 0) {
		const std::string s = "hart0";
		return to_hex_bytes_((const uint8_t *)s.data(), s.size());
	}
	if (cmd.rfind("qSymbol", 0) == 0)
		return "OK";
	if (cmd == "qTStatus")
		return "";

	// GDB "monitor" commands (OpenOCD-style). Payload is hex-encoded bytes.
	if (cmd.rfind("qRcmd,", 0) == 0) {
		const std::string hex = cmd.substr(strlen("qRcmd,"));
		std::string decoded;
		if (!from_hex_bytes_(hex, decoded))
			return "E01";

		std::string console;
		if (!target_.monitor_cmd(decoded, console))
			return "";

		if (!console.empty()) {
			const std::string payload = "O" + to_hex_bytes_((const uint8_t *)console.data(), console.size());
			(void)send_packet_(payload);
		}
		return "OK";
	}

	// Extended-remote mode request
	if (cmd == "!")
		return "OK";

	// target.xml for riscv32 (x0..x31 + pc)
	if (cmd.rfind("qXfer:features:read:target.xml:", 0) == 0) {
		const std::string args = cmd.substr(strlen("qXfer:features:read:target.xml:"));
		const size_t comma = args.find(',');
		if (comma == std::string::npos)
			return "E01";
		uint32_t off = 0, len = 0;
		if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
			return "E01";
		if (off >= target_xml_.size())
			return "l";
		const size_t end = std::min<size_t>(target_xml_.size(), (size_t)off + (size_t)len);
		const bool more = end < target_xml_.size();
		std::string out;
		out.reserve(1 + (end - off));
		out.push_back(more ? 'm' : 'l');
		out.append(target_xml_, off, end - off);
		return out;
	}

	// memory-map.xml (RAM region for disassembly / memory accessibility)
	if (cmd.rfind("qXfer:memory-map:read::", 0) == 0) {
		const std::string args = cmd.substr(strlen("qXfer:memory-map:read::"));
		const size_t comma = args.find(',');
		if (comma == std::string::npos)
			return "E01";
		uint32_t off = 0, len = 0;
		if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
			return "E01";
		if (off >= memory_map_xml_.size())
			return "l";
		const size_t end = std::min<size_t>(memory_map_xml_.size(), (size_t)off + (size_t)len);
		const bool more = end < memory_map_xml_.size();
		std::string out;
		out.reserve(1 + (end - off));
		out.push_back(more ? 'm' : 'l');
		out.append(memory_map_xml_, off, end - off);
		return out;
	}

	// Registers
	if (cmd == "g") {
		std::string out;
		out.reserve(33 * 8);
		for (uint32_t r = 0; r < 33; ++r)
			append_u32_le_hex_(out, target_.read_reg(r));
		return out;
	}
	if (cmd.size() >= 2 && cmd[0] == 'p') {
		uint32_t regno = 0;
		if (!parse_u32_hex_(cmd.substr(1), regno))
			return "E01";
		std::string out;
		out.reserve(8);
		append_u32_le_hex_(out, target_.read_reg(regno));
		return out;
	}
	if (cmd.size() >= 3 && cmd[0] == 'P') {
		const size_t eq = cmd.find('=');
		if (eq == std::string::npos)
			return "E01";
		uint32_t regno = 0;
		if (!parse_u32_hex_(cmd.substr(1, eq - 1), regno))
			return "E01";
		std::string bytes;
		if (!from_hex_bytes_(cmd.substr(eq + 1), bytes))
			return "E01";
		if (bytes.size() < 4)
			return "E01";
		uint32_t v = (uint8_t)bytes[0] | ((uint32_t)(uint8_t)bytes[1] << 8) | ((uint32_t)(uint8_t)bytes[2] << 16) |
			((uint32_t)(uint8_t)bytes[3] << 24);
		target_.write_reg(regno, v);
		return "OK";
	}
	if (cmd.size() >= 1 && cmd[0] == 'G') {
		std::string bytes;
		if (!from_hex_bytes_(cmd.substr(1), bytes))
			return "E01";
		if (bytes.size() < 33 * 4)
			return "E01";
		for (uint32_t r = 0; r < 33; ++r) {
			const size_t i = r * 4;
			uint32_t v = (uint8_t)bytes[i + 0] | ((uint32_t)(uint8_t)bytes[i + 1] << 8) |
				((uint32_t)(uint8_t)bytes[i + 2] << 16) | ((uint32_t)(uint8_t)bytes[i + 3] << 24);
			target_.write_reg(r, v);
		}
		return "OK";
	}

	// Memory
	if (cmd.size() >= 2 && cmd[0] == 'm') {
		const size_t comma = cmd.find(',');
		if (comma == std::string::npos)
			return "E01";
		uint32_t addr = 0, len = 0;
		if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1), len))
			return "E01";
		if (len == 0)
			return "";
		std::string buf(len, '\0');
		if (!target_.read_mem(addr, (uint8_t *)&buf[0], (size_t)len))
			return "E01";
		return to_hex_bytes_((const uint8_t *)buf.data(), buf.size());
	}
	if (cmd.size() >= 2 && cmd[0] == 'M') {
		const size_t comma = cmd.find(',');
		const size_t colon = cmd.find(':');
		if (comma == std::string::npos || colon == std::string::npos || comma > colon)
			return "E01";
		uint32_t addr = 0, len = 0;
		if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1, colon - comma - 1), len))
			return "E01";
		if (len == 0)
			return "OK";
		std::string bytes;
		if (!from_hex_bytes_(cmd.substr(colon + 1), bytes))
			return "E01";
		if (bytes.size() < len)
			return "E01";
		if (!target_.write_mem(addr, (const uint8_t *)bytes.data(), len))
			return "E01";
		return "OK";
	}

	// Execute breakpoints.
	//
	// GDB's stock stepi implementation may place a temporary software
	// breakpoint (Z0) or hardware breakpoint (Z1) at the predicted next PC,
	// for example when stepping across an indirect jalr into a read-only text
	// section. This testbench doesn't patch target memory, so both packet types
	// are handled identically as execute breakpoints checked in the run loop.
	if (cmd.rfind("Z0,", 0) == 0 || cmd.rfind("z0,", 0) == 0 ||
		cmd.rfind("Z1,", 0) == 0 || cmd.rfind("z1,", 0) == 0) {
		const bool set = cmd[0] == 'Z';
		const size_t comma1 = cmd.find(',');
		const size_t comma2 = cmd.find(',', comma1 + 1);
		if (comma1 == std::string::npos || comma2 == std::string::npos)
			return "E01";
		uint32_t addr = 0;
		if (!parse_u32_hex_(cmd.substr(comma1 + 1, comma2 - comma1 - 1), addr))
			return "E01";
		if (set)
			breakpoints_.insert(addr);
		else
			breakpoints_.erase(addr);
		return "OK";
	}

	// vCont
	if (cmd == "vCont?")
		return "vCont;c;s";

	auto do_step = [&](bool single_step) -> std::string {
		const uint32_t start_pc = target_.read_reg(32);
		const bool ignore_start_break = single_step || start_pc == ignore_breakpoint_pc_;
		const bool consume_ignore_pc = start_pc == ignore_breakpoint_pc_;
		if (consume_ignore_pc)
			ignore_breakpoint_pc_ = 0xffffffffu;

		if (single_step) {
			const tb_gdb_run_result r = target_.step_instruction();
			if (r == tb_gdb_run_result::exited) {
				// In extended-remote mode keep the connection alive after target exit,
				// so the user can issue monitor commands such as "reset halt".
				return stop_reply_exit_();
			}
			if (r == tb_gdb_run_result::timed_out) {
				should_close = true;
				return stop_reply_(14); // SIGALRM
			}
			ignore_breakpoint_pc_ = 0xffffffffu;
			return stop_reply_(5); // SIGTRAP
		}

		bool first_iter = true;
		while (true) {
			if (check_interrupt_()) {
				ignore_breakpoint_pc_ = 0xffffffffu;
				return stop_reply_(2); // SIGINT
			}

			const uint32_t pc = target_.read_reg(32);
			if (breakpoints_.count(pc) && !(first_iter && ignore_start_break && pc == start_pc)) {
				ignore_breakpoint_pc_ = pc;
				return stop_reply_(5); // SIGTRAP
			}

			const tb_gdb_run_result r = target_.step_instruction();
			ignore_breakpoint_pc_ = 0xffffffffu;
			if (r == tb_gdb_run_result::exited) {
				// In extended-remote mode keep the connection alive after target exit,
				// so the user can issue monitor commands such as "reset halt".
				return stop_reply_exit_();
			}
			if (r == tb_gdb_run_result::timed_out) {
				should_close = true;
				return stop_reply_(14); // SIGALRM
			}
			first_iter = false;
		}
	};

	if (cmd.size() >= 1 && (cmd[0] == 'c' || cmd[0] == 's')) {
		// Optional resume address
		if (cmd.size() > 1) {
			uint32_t addr = 0;
			if (!parse_u32_hex_(cmd.substr(1), addr))
				return "E01";
			target_.write_reg(32, addr);
		}
		return do_step(cmd[0] == 's');
	}
	if (cmd.rfind("vCont;", 0) == 0) {
		if (cmd == "vCont;c")
			return do_step(false);
		if (cmd == "vCont;s")
			return do_step(true);
		return "";
	}

	// Detach / kill
	if (cmd == "D") {
		should_close = true;
		return "OK";
	}
	if (cmd == "k") {
		should_close = true;
		return "OK";
	}

	// Unknown/unsupported -> empty response
	return "";
}

tb_gdb_run_result tb_gdb_server::serve() {
	if (!open_listen_socket_())
		return tb_gdb_run_result::error;

	fprintf(stderr, "tb_gdb: listening on 127.0.0.1:%u\n", (unsigned)port_);
	if (!accept_client_())
		return tb_gdb_run_result::error;

	fprintf(stderr, "tb_gdb: client connected\n");
	if (!send_packet_(stop_reply_(5)))
		return tb_gdb_run_result::error;

	while (true) {
		std::string cmd;
		bool got_interrupt = false;
		if (!recv_packet_(cmd, got_interrupt))
			break;
		if (got_interrupt) {
			if (!send_packet_(stop_reply_(2)))
				break;
			continue;
		}

		bool should_close = false;
		std::string reply = handle_command_(cmd, should_close);
		if (!send_packet_(reply))
			break;

		if (should_close) {
			if (!reply.empty() && reply[0] == 'W')
				return tb_gdb_run_result::exited;
			break;
		}
	}

	close_client_();
	return tb_gdb_run_result::ok;
}
