/* libgdbr - LGPL - Copyright 2014 - defragger */

#include "arch.h"
#include "gdbclient/responses.h"
#include "gdbr_common.h"
#include "utils.h"
#include "r_util/r_str.h"
#include "r_util/r_log.h"

int handle_g(libgdbr_t *g) {
	if (unpack_hex (g->data, g->data_len, g->data) < 0) {
		return -1;
	}
	g->data_len = g->data_len / 2;
	return send_ack (g);
}

int handle_G(libgdbr_t *g) {
	return send_ack (g);
}

int handle_M(libgdbr_t *g) {
	return send_ack (g);
}

int handle_P(libgdbr_t *g) {
	g->last_code = g->data_len? MSG_OK: MSG_NOT_SUPPORTED;
	return send_ack (g);
}

int handle_m(libgdbr_t *g) {
	if (g->data_len == 3 && g->data[0] == 'E') {
		// TODO: figure out if this is a problem
		send_ack (g);
		return -1;
	}
	int len = strlen (g->data);
	g->data_len = len / 2;
	unpack_hex (g->data, len, g->data);
	return send_ack (g);
}

int handle_qStatus(libgdbr_t *g) {
	if (!g || !g->data || !*g->data) {
		return -1;
	}
	char *data = strdup (g->data);
	char *save_ptr = NULL;
	char *tok = r_str_tok_r (data, ";", &save_ptr);
	if (!tok) {
		free (data);
		return -1;
	}
	// TODO: We do not yet handle the case where a trace is already running
	if (!r_str_startswith (tok, "T0")) {
		send_ack (g);
		free (data);
		return -1;
	}
	// Ensure that trace was never run
	while (tok) {
		if (r_str_startswith (tok, "tnotrun:0")) {
			free (data);
			return send_ack (g);
		}
		tok = r_str_tok_r (NULL, ";", &save_ptr);
	}
	send_ack (g);
	free (data);
	return -1;
}

int handle_qC(libgdbr_t *g) {
	// We get process and thread ID
	if (strncmp (g->data, "QC", 2)) {
		send_ack (g);
		return -1;
	}
	g->data[g->data_len] = '\0';
	if (read_thread_id (g->data + 2, &g->pid, &g->tid, g->stub_features.multiprocess) < 0) {
		send_ack (g);
		return -1;
	}
	return send_ack (g);
}

int handle_setbp(libgdbr_t *g) {
	return send_ack (g);
}

int handle_removebp(libgdbr_t *g) {
	return send_ack (g);
}

int handle_attach(libgdbr_t *g) {
	if (g->data_len == 3 && g->data[0] == 'E') {
		send_ack (g);
		return -1;
	}
	return send_ack (g);
}

int handle_vFile_open(libgdbr_t *g) {
	if (g->data_len < 2 || g->data[0] != 'F' || g->data[1] == '-'
	    || !isxdigit ((unsigned char)g->data[1])) {
		send_ack (g);
		return -1;
	}
	g->data[g->data_len] = '\0';
	if ((g->remote_file_fd = strtol (g->data + 1, NULL, 16)) <= 0) {
		send_ack (g);
		return -1;
	}
	return send_ack (g);
}

int handle_vFile_pread(libgdbr_t *g, ut8 *buf, size_t max_len) {
	send_ack (g);
	char *ptr;
	int len;
	if (g->data_len < 3 || g->data[0] != 'F') {
		return -1;
	}
	// F-1 is an error, yes, but it probably should not be fatal, since it might
	// mean we're reading beyond file end. So this is handled in gdbr_read_file
	if (g->data[1] == '-') {
		return 0;
	}
	if (!isxdigit ((unsigned char)g->data[1])) {
		return -1;
	}
	if (sscanf (g->data, "F%x;", &len) != 1) {
		return -1;
	}
	// Again, this is probably the end of file
	if (len == 0) {
		return 0;
	}
	if (!(ptr = strchr (g->data, ';')) || ptr >= g->data + g->data_len) {
		return -1;
	}
	ptr++;
	if (len > 0) {
		len = R_MIN (len, max_len);
		memcpy (buf, ptr, len);
	}
	return len;
}

int handle_vFile_close(libgdbr_t *g) {
	if (g->data_len < 2 || g->data[0] != 'F' || g->data[1] == '-'
	    || !isxdigit ((unsigned char)g->data[1])) {
		send_ack (g);
		return -1;
	}
	return send_ack (g);
}

#include <r_debug.h>
#include <gdbclient/commands.h>

static bool parse_thread_stop(const char *data, const char *prefix, int *pid, int *tid, bool multiprocess, bool *present) {
	if (*present || !r_str_startswith (data, prefix)) {
		return false;
	}
	const char *colon = strchr (data, ':');
	if (!colon || read_thread_id (colon + 1, pid, tid, multiprocess) < 0) {
		return false;
	}
	*present = true;
	return true;
}

static int stop_reason_death(libgdbr_t *g, bool is_signal) {
	int value = 0, pid = g->pid;
	bool multi = g->stub_features.multiprocess && g->data_len > 3;
	if (multi) {
		if (sscanf (g->data + 1, "%x;process:%x", &value, &pid) != 2) {
			R_LOG_DEBUG ("Message from remote: %s", g->data);
			return -1;
		}
	} else if (!isxdigit ((unsigned char)g->data[1])) {
		R_LOG_DEBUG ("Message from remote: %s", g->data);
		return -1;
	} else {
		value = (int) strtol (g->data + 1, NULL, 16);
	}
	R_LOG_DEBUG ("Process %d %s %d", pid,
		is_signal? "terminated with signal": "exited with status", value);
	gdbr_stop_reason_reset (&g->stop_reason);
	g->stop_reason.reason = R_DEBUG_REASON_DEAD;
	g->stop_reason.thread.pid = pid;
	g->stop_reason.thread.tid = pid;
	if (is_signal) {
		g->stop_reason.signum = value;
	}
	g->stop_reason.is_valid = true;
	return multi? 0: gdbr_disconnect (g);
}

int handle_stop_reason(libgdbr_t *g) {
	send_ack (g);
	if (g->data_len < 3) {
		return -1;
	}
	switch (g->data[0]) {
	case 'O':
		unpack_hex (g->data + 1, g->data_len - 1, g->data + 1);
		R_LOG_DEBUG ("%s: %s", __func__, g->data + 1);
		if (send_ack (g) < 0) {
			return -1;
		}
		gdbr_stop_reason_reset (&g->stop_reason);
		g->stop_reason.signum = -1;
		g->stop_reason.reason = R_DEBUG_REASON_NONE;
		return 0;
	case 'W':
		return stop_reason_death (g, false);
	case 'X':
		return stop_reason_death (g, true);
	}
	if (g->data[0] != 'T') {
		return -1;
	}
	char *ptr1, *ptr2;
	char *save_ptr = NULL;
	g->data[g->data_len] = '\0';
	gdbr_stop_reason_reset (&g->stop_reason);
	g->stop_reason.core = -1;
	if (sscanf (g->data + 1, "%02x", &g->stop_reason.signum) != 1) {
		return -1;
	}
	g->stop_reason.is_valid = true;
	g->stop_reason.reason = R_DEBUG_REASON_SIGNAL;
	for (ptr1 = r_str_tok_r (g->data + 3, ";", &save_ptr); ptr1; ptr1 = r_str_tok_r (NULL, ";", &save_ptr)) {
		if (parse_thread_stop (ptr1, "thread", &g->stop_reason.thread.pid,
				&g->stop_reason.thread.tid, g->stub_features.multiprocess,
				&g->stop_reason.thread.present)) {
			continue;
		}
		if (r_str_startswith (ptr1, "core")) {
			if (!(ptr2 = strchr (ptr1, ':'))) {
				continue;
			}
			ptr2++;
			if (!isxdigit ((unsigned char)*ptr2)) {
				continue;
			}
			g->stop_reason.core = (int) strtol (ptr2, NULL, 16);
			continue;
		}
		if (g->stop_reason.signum == 5) {
			if (r_str_startswith (ptr1, "watch")
			    || r_str_startswith (ptr1, "rwatch")
			    || r_str_startswith (ptr1, "awatch")) {
				if (!(ptr2 = strchr (ptr1, ':'))) {
					continue;
				}
				ptr2++;
				if (!isxdigit ((ut8)*ptr2)) {
					continue;
				}
				g->stop_reason.watchpoint.addr = strtoll (ptr2, NULL, 16);
				g->stop_reason.watchpoint.present = true;
				continue;
			}
			if (r_str_startswith (ptr1, "exec") && !g->stop_reason.exec.present) {
				if (!(ptr2 = strchr (ptr1, ':'))) {
					continue;
				}
				ptr2++;
				if (!(g->stop_reason.exec.path = calloc (strlen (ptr1) / 2, 1))) {
					continue;
				}
				unpack_hex (ptr2, strlen (ptr2), g->stop_reason.exec.path);
				g->stop_reason.exec.present = true;
				continue;
			}
			if (parse_thread_stop (ptr1, "fork", &g->stop_reason.fork.pid,
					&g->stop_reason.fork.tid, g->stub_features.multiprocess,
					&g->stop_reason.fork.present)) {
				continue;
			}
			if (parse_thread_stop (ptr1, "vfork", &g->stop_reason.vfork.pid,
					&g->stop_reason.vfork.tid, g->stub_features.multiprocess,
					&g->stop_reason.vfork.present)) {
				continue;
			}
			if (r_str_startswith (ptr1, "vforkdone")) {
				g->stop_reason.vforkdone = true;
				continue;
			}
			if (r_str_startswith (ptr1, "library")) {
				g->stop_reason.library = true;
				continue;
			}
			if (r_str_startswith (ptr1, "swbreak")) {
				g->stop_reason.swbreak = true;
				continue;
			}
			if (r_str_startswith (ptr1, "hwbreak")) {
				g->stop_reason.hwbreak = true;
				continue;
			}
			if (r_str_startswith (ptr1, "create")) {
				g->stop_reason.create = true;
				continue;
			}
		}
	}
	if (g->stop_reason.signum == 5) {
		g->stop_reason.reason = R_DEBUG_REASON_BREAKPOINT;
	}
	return 0;
}

int handle_cont(libgdbr_t *g) {
	return handle_stop_reason (g);
}

int handle_lldb_read_reg(libgdbr_t *g) {
	if (send_ack (g) < 0) {
		return -1;
	}
	char *ptr, *ptr2, *buf;
	char *save_ptr = NULL;
	size_t regnum, tot_regs, buflen = 0;

	// Get maximum register number
	for (regnum = 0; *g->registers[regnum].name; regnum++) {
		if (g->registers[regnum].offset + g->registers[regnum].size > buflen) {
			buflen = g->registers[regnum].offset + g->registers[regnum].size;
		}
	}
	tot_regs = regnum;
	if (buflen > (size_t)g->read_max || buflen > (size_t)g->data_max) {
		R_LOG_ERROR ("%s: register buffer %zu exceeds io buffers", __func__, buflen);
		return -1;
	}

	// We're not using the receive buffer till next packet anyway. Better use it
	buf = g->read_buff;
	memset (buf, 0, buflen);

	if (!(ptr = r_str_tok_r (g->data, ";", &save_ptr))) {
		return -1;
	}
	while (ptr) {
		if (isxdigit ((unsigned char)*ptr)) {
			regnum = (int) strtoul (ptr, NULL, 16);
			if (regnum < tot_regs && (ptr2 = strchr (ptr, ':'))) {
				const size_t roff = g->registers[regnum].offset;
				const size_t rsz = g->registers[regnum].size;
				if (roff < buflen && rsz <= buflen - roff) {
					size_t hexlen = strlen (ptr2 + 1);
					if (hexlen / 2 > rsz) {
						hexlen = rsz * 2;
					}
					unpack_hex (ptr2 + 1, hexlen, buf + roff);
				}
			}
		}
		ptr = r_str_tok_r (NULL, ";", &save_ptr);
	}
	memcpy (g->data, buf, buflen);
	g->data_len = buflen;
	return 0;
}
