/* radare2 - LGPL - Copyright 2009-2026 - pancake */

#include <r_bp.h>
#include <config.h>

R_LIB_VERSION (r_bp);

static RBreakpointPlugin *bp_static_plugins[] = { R_BP_STATIC_PLUGINS };

static void r_bp_item_free(RBreakpointItem *b) {
	if (b) {
		free (b->name);
		free (b->bbytes);
		free (b->obytes);
		free (b->module_name);
		free (b->data);
		free (b->cond);
		free (b->expr);
		free (b);
	}
}

R_API RBreakpoint *r_bp_new(void) {
	RBreakpoint *bp = R_NEW0 (RBreakpoint);
	bp->bps_idx_count = 16;
	bp->bps_idx = R_NEWS0 (RBreakpointItem*, bp->bps_idx_count);
	bp->stepcont = R_BP_CONT_NORMAL;
	bp->traces = r_bp_traptrace_new ();
	bp->bps = r_list_newf ((RListFree)r_bp_item_free);
	r_libstore_new (&bp->libstore, bp, bp_static_plugins, NULL, NULL, NULL, (RLibPluginAddCb)r_bp_plugin_remove);
	memset (&bp->iob, 0, sizeof (bp->iob));
	return bp;
}

R_API void r_bp_free(RBreakpoint *bp) {
	if (bp) {
		r_list_free (bp->bps);
		r_libstore_free (bp->libstore);
		r_list_free (bp->traces);
		free (bp->bps_idx);
		free (bp);
	}
}

R_API int r_bp_get_bytes(RBreakpoint *bp, ut8 *buf, int len, int endian, int idx) {
	int i;
	RBreakpointArch *b;
	if (bp->cur) {
		// find matching size breakpoint, fall back to len=4
		bool retried = false;
		for (;;) {
			for (i = 0; i < bp->cur->nbps; i++) {
				b = &bp->cur->bps[i];
				if (bp->cur->bps[i].bits) {
					if (bp->bits != bp->cur->bps[i].bits) {
						continue;
					}
				}
				if (bp->cur->bps[i].length == len && bp->cur->bps[i].endian == endian) {
					memcpy (buf, b->bytes, b->length);
					return b->length;
				}
			}
			if (!retried && len != 4) {
				len = 4;
				retried = true;
				continue;
			}
			break;
		}
		/* if not found try to pad with the first one */
		b = &bp->cur->bps[0];
		if (len % b->length) {
			R_LOG_WARN ("No matching bpsize");
			return 0;
		}
		for (i = 0; i < len; i++) {
			memcpy (buf + i, b->bytes, b->length);
		}
		return b->length;
	}
	return 0;
}

R_API RBreakpointItem *r_bp_get_at(RBreakpoint *bp, ut64 addr) {
	R_RETURN_VAL_IF_FAIL (bp, NULL);
	RListIter *iter;
	RBreakpointItem *b;
	r_list_foreach (bp->bps, iter, b) {
		if (b->addr == addr) {
			return b;
		}
	}
	return NULL;
}

static inline bool inRange(RBreakpointItem *b, ut64 addr) {
	return (addr >= b->addr && addr < (b->addr + b->size));
}

static inline bool matchProt(RBreakpointItem *b, int perm) {
	return (!perm || (perm && b->perm));
}

R_API RBreakpointItem *r_bp_get_in(RBreakpoint *bp, ut64 addr, int perm) {
	R_RETURN_VAL_IF_FAIL (bp, NULL);
	RBreakpointItem *b;
	RListIter *iter;
	r_list_foreach (bp->bps, iter, b) {
		// Check addr within range and provided perm matches (or null)
		if (inRange (b, addr) && matchProt (b, perm)) {
			return b;
		}
	}
	return NULL;
}

R_API RBreakpointItem *r_bp_enable(RBreakpoint *bp, ut64 addr, int set, int count) {
	R_RETURN_VAL_IF_FAIL (bp, NULL);
	RBreakpointItem *b = r_bp_get_in (bp, addr, 0);
	if (b) {
		b->enabled = set;
		b->togglehits = count;
		return b;
	}
	return NULL;
}

R_API void r_bp_enable_all(RBreakpoint *bp, int set) {
	R_RETURN_IF_FAIL (bp);
	RListIter *iter;
	RBreakpointItem *b;
	r_list_foreach (bp->bps, iter, b) {
		b->enabled = set;
	}
}

R_API int r_bp_stepy_continuation(RBreakpoint *bp) {
	R_RETURN_VAL_IF_FAIL (bp, 0);
	/* Return current step continuation mode */
	return bp->stepcont;
}

static void unlinkBreakpoint(RBreakpoint *bp, RBreakpointItem *b) {
	int i;
	for (i = 0; i < bp->bps_idx_count; i++) {
		if (bp->bps_idx[i] == b) {
			bp->bps_idx[i] = NULL;
		}
	}
	r_list_delete_data (bp->bps, b);
}

static RBreakpointItem *r_bp_add(RBreakpoint *bp, const ut8 * R_NULLABLE obytes, ut64 addr, int size, int hw, int perm) {
	R_RETURN_VAL_IF_FAIL (bp, NULL);
	if (addr == UT64_MAX || size < 1) {
		return NULL;
	}
	/* Detect and (only warn) when setting breakpoints that overlap */
	{
		RListIter *iter;
		RBreakpointItem *other;
		r_list_foreach (bp->bps, iter, other) {
			// Check for address space overflow in either breakpoint
			bool other_overflow = (other->size > 0 && other->addr > UT64_MAX - other->size);
			bool this_overflow = (size > 0 && addr > UT64_MAX - size);
			if (other_overflow || this_overflow) {
				R_LOG_WARN ("Breakpoint range overflow detected at 0x%08"PFMT64x, other->addr);
				continue;
			}
			// Check if ranges overlap
			ut64 other_end = other->addr + other->size;
			ut64 this_end = addr + size;
			if (addr < other_end && other->addr < this_end) {
				R_LOG_WARN ("Breakpoint overlaps with existing breakpoint at 0x%08"PFMT64x, other->addr);
			}
		}
	}
	if (r_bp_get_in (bp, addr, perm)) {
		R_LOG_WARN ("Breakpoint already set at this address");
		return NULL;
	}
	RBreakpointItem *b = r_bp_item_new (bp);
	if (!b) {
		return NULL;
	}
	b->addr = addr + bp->delta;
	if (bp->baddr > addr) {
		R_LOG_WARN ("base addr should not be larger than the breakpoint address");
	}
	if (bp->bpinmaps && !r_bp_is_valid (bp, b)) {
		R_LOG_WARN ("Cannot set breakpoint outside maps. Use dbg.bpinmaps to false");
	}
	b->delta = addr - bp->baddr;
	b->size = size;
	b->enabled = true;
	b->perm = perm;
	b->hw = hw;
	// NOTE: for hw breakpoints there are no bytes to save/restore
	if (!hw) {
		b->bbytes = calloc (size + 16, 1);
		if (!b->bbytes) {
			unlinkBreakpoint (bp, b);
			r_bp_item_free (b);
			return NULL;
		}
		if (obytes) {
			b->obytes = malloc (size);
			if (!b->obytes) {
				unlinkBreakpoint (bp, b);
				r_bp_item_free (b);
				return NULL;
			}
			memcpy (b->obytes, obytes, size);
		} else {
			b->obytes = NULL;
		}
		int ret = r_bp_get_bytes (bp, b->bbytes, size, bp->endian, 0);
		if (ret != size) {
			R_LOG_WARN ("Cannot get breakpoint bytes. No architecture selected?");
		}
	}
	r_list_append (bp->bps, b);
	return b;
}

R_API void r_bp_add_fault(RBreakpoint *bp, ut64 addr, int size, int perm) {
	R_RETURN_IF_FAIL (bp);
	/* Add a fault-type breakpoint (no original bytes to preserve) */
	r_bp_add (bp, NULL, addr, size, R_BP_TYPE_FAULT, perm);
}

R_API RBreakpointItem* r_bp_add_sw(RBreakpoint *bp, ut64 addr, int size, int perm) {
	R_RETURN_VAL_IF_FAIL (bp && bp->iob.read_at, NULL);
	if (size < 1) {
		size = 1;
	}
	ut8 *bytes = calloc (1, size);
	RBreakpointItem *item = NULL;
	if (bytes) {
		if (bp->iob.read_at (bp->iob.io, addr, bytes, size) == size) {
			item = r_bp_add (bp, bytes, addr, size, R_BP_TYPE_SW, perm);
		}
		free (bytes);
	}
	return item;
}

R_API RBreakpointItem* r_bp_add_hw(RBreakpoint *bp, ut64 addr, int size, int perm) {
	return r_bp_add (bp, NULL, addr, size, R_BP_TYPE_HW, perm);
}

R_API bool r_bp_del_all(RBreakpoint *bp) {
	int i;
	if (!r_list_empty (bp->bps)) {
		r_list_purge (bp->bps);
		for (i = 0; i < bp->bps_idx_count; i++) {
			bp->bps_idx[i] = NULL;
		}
		return true;
	}
	return false;
}

R_API bool r_bp_del(RBreakpoint *bp, ut64 addr) {
	RListIter *iter;
	RBreakpointItem *b;
	/* No _safe loop necessary because we return immediately after the delete. */
	r_list_foreach (bp->bps, iter, b) {
		if (b->addr == addr) {
			unlinkBreakpoint (bp, b);
			return true;
		}
	}
	return false;
}

R_API bool r_bp_set_trace(RBreakpoint *bp, ut64 addr, int set) {
	R_RETURN_VAL_IF_FAIL (bp, false);
	RBreakpointItem *b = r_bp_get_in (bp, addr, 0);
	if (b) {
		b->trace = set;
		return true;
	}
	return false;
}

R_API void r_bp_set_trace_all(RBreakpoint *bp, int set) {
	R_RETURN_IF_FAIL (bp);
	RListIter *iter;
	RBreakpointItem *b;
	r_list_foreach (bp->bps, iter, b) {
		b->trace = set;
	}
}

R_API char *r_bp_list(RBreakpoint *bp, int rad) {
	RBreakpointItem *b;
	RListIter *iter;
	PJ *pj = NULL;
	RStrBuf *sb = NULL;
	if (rad == 'j') {
		pj = pj_new ();
		pj_a (pj);
	} else {
		sb = r_strbuf_new ("");
	}
	r_list_foreach (bp->bps, iter, b) {
		if (pj) {
			pj_o (pj);
			pj_kN (pj, "addr", b->addr);
			pj_ki (pj, "size", b->size);
			pj_ks (pj, "perm", r_str_rwx_i (b->perm & 7)); /* filter out R_BP_PROT_ACCESS */
			pj_kb (pj, "hw", b->hw);
			pj_kb (pj, "trace", b->trace);
			pj_kb (pj, "enabled", b->enabled);
			pj_kb (pj, "valid", r_bp_is_valid (bp, b));
			pj_ks (pj, "data", r_str_get (b->data));
			pj_ks (pj, "cond", r_str_get (b->cond));
			pj_ks (pj, "name", r_str_get (b->name));
			pj_end (pj);
		} else if (rad) {
			if (b->module_name) {
				char *mf = r_name_filter_dup (b->module_name);
				r_strbuf_appendf (sb, "dbm %s %"PFMT64d"\n", mf? mf: "", b->module_delta);
				free (mf);
			} else {
				r_strbuf_appendf (sb, "db 0x%08"PFMT64x"\n", b->addr);
			}
		} else {
			r_strbuf_appendf (sb, "0x%08"PFMT64x" - 0x%08"PFMT64x \
				" %d %c%c%c %s %s %s %s",
				b->addr, b->addr + b->size, b->size,
				((b->perm & R_BP_PROT_READ) | (b->perm & R_BP_PROT_ACCESS)) ? 'r' : '-',
				((b->perm & R_BP_PROT_WRITE)| (b->perm & R_BP_PROT_ACCESS)) ? 'w' : '-',
				(b->perm & R_BP_PROT_EXEC) ? 'x' : '-',
				b->hw ? "hw": "sw",
				b->trace ? "trace" : "break",
				b->enabled ? "enabled" : "disabled",
				r_bp_is_valid (bp, b) ? "valid" : "invalid");
			char *f;
			if (b->data && (f = r_name_filter_dup (b->data))) {
				r_strbuf_appendf (sb, " cmd=\"%s\"", f);
				free (f);
			}
			if (b->cond && (f = r_name_filter_dup (b->cond))) {
				r_strbuf_appendf (sb, " cond=\"%s\"", f);
				free (f);
			}
			if (b->name && (f = r_name_filter_dup (b->name))) {
				r_strbuf_appendf (sb, " name=\"%s\"", f);
				free (f);
			}
			if (b->module_name && (f = r_name_filter_dup (b->module_name))) {
				r_strbuf_appendf (sb, " module=\"%s\"", f);
				free (f);
			}
			r_strbuf_append (sb, "\n");
		}
	}
	if (pj) {
		pj_end (pj);
		return pj_drain (pj);
	}
	return r_strbuf_drain (sb);
}

R_API RBreakpointItem *r_bp_item_new(RBreakpoint *bp) {
	int i;
	/* find empty slot */
	for (i = 0; i < bp->bps_idx_count; i++) {
		if (!bp->bps_idx[i]) {
			return (bp->bps_idx[i] = R_NEW0 (RBreakpointItem));
		}
	}
	/* allocate new slot */
	bp->bps_idx_count += 16;
	RBreakpointItem **newbps = realloc (bp->bps_idx, bp->bps_idx_count * sizeof (RBreakpointItem*));
	if (newbps) {
		bp->bps_idx = newbps;
	} else {
		bp->bps_idx_count -= 16;
		return NULL;
	}
	int j;
	for (j = i; j < bp->bps_idx_count; j++) {
		bp->bps_idx[j] = NULL;
	}
	return (bp->bps_idx[i] = R_NEW0 (RBreakpointItem));
}

R_API RBreakpointItem *r_bp_get_index(RBreakpoint *bp, int idx) {
	if (idx >= 0 && idx < bp->bps_idx_count) {
		return bp->bps_idx[idx];
	}
	return NULL;
}

R_API int r_bp_get_index_at(RBreakpoint *bp, ut64 addr) {
	int i;
	for (i = 0; i < bp->bps_idx_count; i++) {
		if (bp->bps_idx[i] && bp->bps_idx[i]->addr == addr) {
			return i;
		}
	}
	return -1;
}

R_API bool r_bp_del_index(RBreakpoint *bp, int idx) {
	if (idx >= 0 && idx < bp->bps_idx_count) {
		r_list_delete_data (bp->bps, bp->bps_idx[idx]);
		bp->bps_idx[idx] = 0;
		return true;
	}
	return false;
}

R_API int r_bp_size(RBreakpoint *bp) {
	int i, bpsize = 8;
	if (!bp || !bp->cur) {
		return 0;
	}
	for (i = 0; bp->cur->bps[i].bytes; i++) {
		RBreakpointArch *bpa = &bp->cur->bps[i];
		if (bpa->bits && bpa->bits != bp->bits) {
			continue;
		}
		if (bpa->length < bpsize) {
			bpsize = bpa->length;
		}
	}
	return bpsize;
}

// Check if the breakpoint is in a valid map
R_API bool r_bp_is_valid(RBreakpoint *bp, RBreakpointItem *b) {
	R_RETURN_VAL_IF_FAIL (bp && b, false);
	if (bp->bpinmaps) {
		return bp->coreb.isMapped (bp->coreb.core, b->addr, b->perm);
	}
	return true;
}
