/* radare - LGPL - Copyright 2009-2025 - pancake */

#include <r_core.h>
#include <r_vec.h>
#include <r_util/r_json.h>

R_LIB_VERSION (r_sign);

#define SIGN_DIFF_MATCH_BYTES_THRESHOLD 1.0
#define SIGN_DIFF_MATCH_GRAPH_THRESHOLD 1.0

R_VEC_TYPE (RVecRSignItemPtr, RSignItem *);

static inline const char *get_xrefname(RCore *core, ut64 addr) {
	RAnalFunction *f = r_anal_get_fcn_in (core->anal, addr, 0);
	return f? f->name: NULL;
}

static const char *get_refname(RCore *core, ut64 addr) {
	const RVecFlagItemPtr *list = r_flag_get_vec (core->flags, addr);
	RFlagItem **iter;
	RFlagItem *item;
	r_flag_item_vec_foreach (list, iter, item) {
		if (!item->name || !r_str_startswith (item->name, "sym.")) {
			continue;
		}
		return item->name;
	}
	return NULL;
}

static int list_str_cmp(const void *a, const void *b) {
	// prevent silent failure if RListComparator changes
	return strcmp ((const char *)a, (const char *)b);
}

static ut64 valstr(const void *_a) {
	const char *a = _a;
	return r_str_hash64 (a);
}

R_API RList *r_sign_fcn_xrefs(RAnal *a, RAnalFunction *fcn) {
	R_RETURN_VAL_IF_FAIL (a && fcn, NULL);

	RCore *core = a->coreb.core;
	if (!core) {
		return NULL;
	}
	RList *ret = r_list_newf ((RListFree) free);
	RVecAnalRef *xrefs = r_anal_xrefs_get (a, fcn->addr);
	if (!xrefs) {
		return ret;
	}

	RAnalRef *refi;
	R_VEC_FOREACH (xrefs, refi) {
		const char *flag = get_xrefname (core, refi->addr);
		if (flag) {
			r_list_append (ret, strdup (flag));
		}
	}

	r_list_uniq (ret, valstr);
	RVecAnalRef_free (xrefs);
	return ret;
}

static RFlagItem *get_sym_flag_at(RCore *core, ut64 addr) {
	if (!core || !core->flags) {
		return NULL;
	}
	const RVecFlagItemPtr *list = r_flag_get_vec (core->flags, addr);
	RFlagItem **it;
	RFlagItem *fi;
	r_flag_item_vec_foreach (list, it, fi) {
		if (fi && fi->name && r_str_startswith (fi->name, "sym.")) {
			return fi;
		}
	}
	return NULL;
}

R_API RList *r_sign_fcn_refs(RAnal *a, RAnalFunction *fcn) {
	R_RETURN_VAL_IF_FAIL (a && fcn, NULL);
	RCore *core = a->coreb.core;
	if (!core) {
		return NULL;
	}

	RList *ret = r_list_newf ((RListFree) free);
	RVecAnalRef *refs = r_anal_function_get_refs (fcn);
	if (!refs) {
		return ret;
	}

	RAnalRef *refi;
	R_VEC_FOREACH (refs, refi) {
		const char *flag = get_refname (core, refi->addr);
		if (flag) {
			r_list_append (ret, strdup (flag));
		}
	}
	RVecAnalRef_free (refs);
	return ret;
}

static RSignBytes *des_bytes_norm(const char *in) {
	// "444444:ffffff" or "44444444"
	RSignBytes *b = R_NEW0 (RSignBytes);
	if (b && (b->size = r_hex_str2bin_until_new (in, &b->bytes)) > 0) {
		in += 2 * b->size;
		if (!*in && (b->mask = malloc (b->size))) {
			// no mask, set it to f's
			memset (b->mask, 0xff, b->size);
			return b;
		}
		if (*in++ == ':') {
			// get mask
			int size = r_hex_str2bin_until_new (in, &b->mask);
			in += 2 * size;
			if (size == b->size && !*in) {
				return b;
			}
		}
	}
	r_sign_bytes_free (b);
	return NULL;
}

static RSignBytes *deserialize_bytes(const char *in) {
	RSignBytes *b = des_bytes_norm (in);
	if (b) {
		return b;
	}

	// "44..44.."
	b = R_NEW0 (RSignBytes);
	size_t len = strlen (in) + 3;
	b->bytes = malloc (len);
	b->mask = malloc (len);
	if (b && b->bytes && b->mask) {
		b->size = r_hex_str2binmask (in, b->bytes, b->mask);
		if (b->size > 0) {
			return b;
		}
	}
	r_sign_bytes_free (b);
	return NULL;
}

static RSignBytes *deserialize_anal(RAnal *a, const char *in) {
	RSignBytes *b = R_NEW0 (RSignBytes);
	if (b && (b->size = r_hex_str2bin_until_new (in, &b->bytes)) > 0) {
		in += 2 * b->size;
		if (!*in && (b->mask = r_anal_mask (a, b->size, b->bytes, 0))) {
			return b;
		}
	}
	r_sign_bytes_free (b);
	return NULL;
}

static inline RList *sign_vars(RAnalFunction *fcn) {
	RList *l = r_anal_var_get_prots (fcn);
	if (l && r_list_empty (l)) {
		r_list_free (l);
		l = NULL;
	}
	return l;
}

// TODO: use primitives from r_types
#define ALPH(x) (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z')
#define VALID_TOKEN_CHR(x) (ALPH (x) || isdigit (x) || x == '_' || x == '*' || x == ' ' || x == '.')
static bool types_sig_valid(const char *types) {
	// quick state machine parser to validate types being sent to tcc_compile
	int state = 0; // before, inside, or after ()
	char ch;
	const char *t = types;
	int paren_cnt = 0;
	while ((ch = *(t++)) && state >= 0) {
		switch (state) {
		case 0:
			if (ch == '(') {
				state++;
			} else if (!VALID_TOKEN_CHR (ch) && ch != '.') {
				state = -1;
			}
			break;
		case 1:
			if (ch == '(') {
				paren_cnt++;
			} else if (ch == ')') {
				if (paren_cnt == 0) {
					state++;
				} else if (paren_cnt > 0) {
					paren_cnt--;
				} else {
					state = -1;
				}
			} else if (!VALID_TOKEN_CHR (ch) && ch != ',') {
				state = -1;
			}
			break;
		case 2:
			state = -1;
			break;
		}
	}
	return state == 2;
}
#undef ALPH
#undef VALID_TOKEN_CHR

#define DBL_VAL_FAIL(x,y) \
	if (x) { \
		R_LOG_WARN ("Skipping signature with multiple %c signatures (%s)", y, k); \
		success = false; \
		goto out; \
	}
R_API bool r_sign_deserialize(RAnal *a, RSignItem *it, const char *k, const char *v) {
	R_RETURN_VAL_IF_FAIL (a && it && k && v, false);

	bool success = true;
	char *k2 = strdup (k);
	char *v2 = strdup (v);
	if (!k2 || !v2) {
		success = false;
		goto out;
	}

	// Deserialize key: zign|space|name
	int n = r_str_split (k2, '|');
	if (n != 3) {
		R_LOG_WARN ("Skipping signature with invalid key (%s)", k);
		success = false;
		goto out;
	}
	if (strcmp (r_str_word_get0 (k2, 0), "zign")) {
		R_LOG_WARN ("Skipping signature with invalid value (%s)", k);
		success = false;
		goto out;
	}

	it->space = r_spaces_add (&a->zign_spaces, r_str_word_get0 (k2, 1));
	it->name = strdup (r_str_word_get0 (k2, 2));

	// remove newline at end
	char *save_ptr = NULL;
	r_str_tok_r (v2, "\n", &save_ptr);
	// Deserialize value: |k:v|k:v|k:v|...
	n = r_str_split (v2, '|');
	const char *token = NULL;
	int w;
	for (w = 0; w < n; w++) {
		const char *word = r_str_word_get0 (v2, w);
		if (!word) {
			break;
		}
		if (!*word) {
			continue;
		}
		token = word + 2;
		if (!strcmp (word, "*")) {
			continue;
		}
		if (strlen (word) < 3 || word[1] != ':') {
			R_LOG_WARN ("Skipping signature with corrupted serialization (%s:%s)", k, word);
			success = false;
			goto out;
		}
		RSignType st = (RSignType)*word;
		switch (st) {
		case R_SIGN_RAWNAME: {
			DBL_VAL_FAIL (it->rawname, R_SIGN_RAWNAME);
			char *dec = (char *)r_base64_decode_dyn (token, -1, NULL, false);
			it->rawname = dec? dec: strdup (token);
			break;
		}
		case R_SIGN_DEMANGLED: {
			DBL_VAL_FAIL (it->demangled, R_SIGN_DEMANGLED);
			char *dec = (char *)r_base64_decode_dyn (token, -1, NULL, false);
			it->demangled = dec? dec: strdup (token);
			break;
		}
		case R_SIGN_NAME:
			DBL_VAL_FAIL (it->realname, R_SIGN_NAME);
			it->realname = strdup (token);
			break;
		case R_SIGN_COMMENT:
			DBL_VAL_FAIL (it->comment, R_SIGN_COMMENT);
			it->comment = strdup (token);
			break;
		case R_SIGN_NEXT:
			DBL_VAL_FAIL (it->next, R_SIGN_NEXT);
			it->next = strdup (token);
			break;
		case R_SIGN_TYPES:
			DBL_VAL_FAIL (it->types, R_SIGN_TYPES);
			if (!types_sig_valid (token)) {
				R_LOG_ERROR ("Invalid types: ```%s``` in signature for %s", token, k);
				success = false;
				goto out;
			}
			it->types = strdup (token);
			break;
		case R_SIGN_GRAPH:
			DBL_VAL_FAIL (it->graph, R_SIGN_GRAPH);
			if (strlen (token) == 2 * sizeof (RSignGraph)) {
				it->graph = R_NEW0 (RSignGraph);
				if (it->graph) {
					r_hex_str2bin (token, (ut8 *)it->graph);
				}
			}
			break;
		case R_SIGN_OFFSET:
			DBL_VAL_FAIL ((it->addr != UT64_MAX), R_SIGN_OFFSET);
			it->addr = atoll (token);
			break;
		case R_SIGN_REFS:
			DBL_VAL_FAIL (it->refs, R_SIGN_REFS);
			if (!(it->refs = r_str_split_duplist (token, ",", true))) {
				success = false;
				goto out;
			}
			break;
		case R_SIGN_XREFS:
			DBL_VAL_FAIL (it->xrefs, R_SIGN_XREFS);
			if (!(it->xrefs = r_str_split_duplist (token, ",", true))) {
				success = false;
				goto out;
			}
			break;
		case R_SIGN_VARS:
			DBL_VAL_FAIL (it->vars, R_SIGN_VARS);
			if (!(it->vars = r_anal_var_deserialize (token))) {
				success = false;
				goto out;
			}
			break;
		case R_SIGN_COLLISIONS:
			DBL_VAL_FAIL (it->collisions, R_SIGN_COLLISIONS);
			if (!(it->collisions = strdup (token))) {
				success = false;
				goto out;
			}
			break;
		case R_SIGN_BBHASH:
			DBL_VAL_FAIL (it->hash, R_SIGN_BBHASH);
			if (token[0] != 0) {
				it->hash = R_NEW0 (RSignHash);
				if (it->hash) {
					it->hash->bbhash = strdup (token);
				}
			}
			break;
		case R_SIGN_BYTES:
			DBL_VAL_FAIL (it->bytes, R_SIGN_BYTES);
			if (!(it->bytes = des_bytes_norm (token))) {
				success = false;
				goto out;
			}
			break;
		default:
			R_LOG_ERROR ("Unsupported (%c)", st);
			break;
		}
	}

	// Legacy mapping: if only legacy realname is present, assume
	// name=mangled and realname=demangled (as previously produced
	// when zign.mangled=false)
	if (!it->rawname && !it->demangled && it->realname) {
		it->rawname = strdup (it->name);
		it->demangled = strdup (it->realname);
	}
out:
	free (k2);
	free (v2);
	return success;
}
#undef DBL_VAL_FAIL

static inline char *str_serialize_key(const char *sp, const char *name) {
	if (!sp || !name) {
		return NULL;
	}
	return r_str_newf ("zign|%s|%s", sp, name);
}

static inline char *space_serialize_key(const RSpace *space, const char *name) {
	const char *sp = space? space->name: "*";
	return str_serialize_key (sp, name);
}

static inline char *item_serialize_key(RSignItem *it) {
	return space_serialize_key (it->space, it->name);
}

static bool serialize_str_list(RList *l, RStrBuf *sb, RSignType t) {
	if (!l || l->length == 0) {
		return true;
	}
	if (!r_strbuf_appendf (sb, "|%c:", t)) {
		return false;
	}
	char *e, *c = "";
	RListIter *iter;
	r_list_foreach (l, iter, e) {
		if (strchr (e, ',')) {
			return false;
		}
		if (!r_strbuf_append (sb, c) || !r_strbuf_append (sb, e)) {
			return false;
		}
		if (!*c) {
			c = ",";
		}
	}
	return true;
}

static inline size_t serial_val_reserv(RSignItem *it) {
	// does not have to be exact, just save time on re-alloc+copy
	size_t reserve = 32;
	if (it->bytes) {
		reserve += it->bytes->size * 4 + 10;
	}
	if (it->graph) {
		reserve += sizeof (RSignGraph) * 2 + 1;
	}
	if (it->realname) {
		reserve += 0x10;
	}
	if (it->rawname) {
		reserve += strlen (it->rawname) * 2 + 0x10; // base64
	}
	if (it->demangled) {
		reserve += strlen (it->demangled) * 2 + 0x10; // base64
	}
	if (it->next) {
		reserve += 0x20;
	}
	if (it->types) {
		reserve += 0x20;
	}
	if (it->hash && it->hash->bbhash) {
		reserve += 64;
	}
	int mul = 10;
	if (it->refs) {
		reserve += mul * r_list_length (it->refs);
	}
	if (it->xrefs) {
		reserve += mul * r_list_length (it->xrefs);
	}
	if (it->vars) {
		reserve += mul * r_list_length (it->vars);
	}
	if (it->collisions) {
		reserve += strlen (it->collisions) + 3;
	}
	return reserve;
}

#define FreeRet_on_fail(exp, buf) \
	if (!exp) { \
		r_strbuf_free (buf); \
		return NULL; \
	}

static char *serialize_value(RSignItem *it) {
	if (!it) {
		return NULL;
	}

	RStrBuf *sb = r_strbuf_new ("");
	r_strbuf_reserve (sb, serial_val_reserv (it));

	RSignBytes *bytes = it->bytes;
	if (bytes && bytes->bytes && bytes->mask) {
		RSignBytes *bytes = it->bytes;
		size_t len = bytes->size * 2 + 1;
		char *hexbytes = calloc (1, len);
		char *hexmask = calloc (1, len);
		bool success = false;
		if (hexbytes && hexmask) {
			r_hex_bin2str (bytes->bytes, bytes->size, hexbytes);
			r_hex_bin2str (bytes->mask, bytes->size, hexmask);
			success = r_strbuf_appendf (sb, "|%c:%s:%s", R_SIGN_BYTES,
				hexbytes, hexmask);
		}
		free (hexbytes);
		free (hexmask);
		FreeRet_on_fail (success, sb);
	}

	if (it->addr != UT64_MAX) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%" PFMT64d, R_SIGN_OFFSET, it->addr), sb);
	}

	if (it->graph) {
		char *hexgraph = calloc (1, sizeof (RSignGraph) * 2 + 1);
		bool success = false;
		if (hexgraph) {
			r_hex_bin2str ((ut8 *)it->graph, sizeof (RSignGraph), hexgraph);
			success = r_strbuf_appendf (sb, "|%c:%s", R_SIGN_GRAPH, hexgraph);
			free (hexgraph);
		}
		FreeRet_on_fail (success, sb);
	}

	FreeRet_on_fail (serialize_str_list (it->refs, sb, R_SIGN_REFS), sb);
	FreeRet_on_fail (serialize_str_list (it->xrefs, sb, R_SIGN_XREFS), sb);
	if (it->collisions) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:", R_SIGN_COLLISIONS), sb);
		FreeRet_on_fail (r_strbuf_append (sb, it->collisions), sb);
	}

	if (it->vars && !r_list_empty (it->vars)) {
		char *vrs = r_anal_var_prot_serialize (it->vars, false);
		bool vars_good = false;
		if (vrs) {
			vars_good = r_strbuf_appendf (sb, "|%c:%s", R_SIGN_VARS, vrs);
			free (vrs);
		}
		FreeRet_on_fail (vars_good, sb);
	}

	if (it->comment && !strchr (it->comment, '|')) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_COMMENT, it->comment), sb);
	}

	if (it->rawname) {
		char *b64 = r_base64_encode_dyn ((const ut8 *)it->rawname, -1);
		bool ok = b64 && r_strbuf_appendf (sb, "|%c:%s", R_SIGN_RAWNAME, b64);
		free (b64);
		FreeRet_on_fail (ok, sb);
	}

	/* Only serialize demangled if it provides additional information
	 * different from the rawname. Avoids emitting redundant demangled
	 * entries when the value equals the rawname (e.g. non-mangled names). */
	if (it->demangled) {
		bool emit_demangled = true;
		if (it->rawname && it->demangled && !strcmp (it->demangled, it->rawname)) {
			emit_demangled = false;
		}
		if (emit_demangled) {
			char *b64 = r_base64_encode_dyn ((const ut8 *)it->demangled, -1);
			bool ok = b64 && r_strbuf_appendf (sb, "|%c:%s", R_SIGN_DEMANGLED, b64);
			free (b64);
			FreeRet_on_fail (ok, sb);
		}
	}

	if (it->realname && !strchr (it->realname, '|')) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_NAME, it->realname), sb);
	}

	if (it->types && !strchr (it->types, '|')) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_TYPES, it->types), sb);
	}

	if (it->next && !strchr (it->next, '|')) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_NEXT, it->next), sb);
	}

	if (it->hash && it->hash->bbhash) {
		FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_BBHASH, it->hash->bbhash), sb);
	}

	return r_strbuf_drain (sb);
}

static RList *deserialize_sign_space(RAnal *a, const RSpace *space) {
	R_RETURN_VAL_IF_FAIL (a && space, NULL);

	char *key = space_serialize_key (space, "");
	if (!key) {
		return NULL;
	}
	SdbList *zigns = sdb_foreach_match (a->sdb_zigns, key, false);
	free (key);

	SdbListIter *iter;
	SdbKv *kv;
	RList *ret = r_list_newf ((RListFree)r_sign_item_free);
	if (ret) {
		ls_foreach (zigns, iter, kv) {
			RSignItem *it = r_sign_item_new ();
			if (!it) {
				break;
			}
			if (r_sign_deserialize (a, it, kv->base.key, kv->base.value)) {
				r_list_append (ret, it);
			} else {
				r_sign_item_free (it);
			}
		}
	}
	ls_free (zigns);
	return ret;
}

#define quick_merge(x, freefunc) \
	if (src->x) { \
		freefunc (dst->x); \
		dst->x = src->x; \
		src->x = NULL; \
	}

// clobbers src for speed but also guarantee success
static inline void merge_item_clobber(RSignItem *dst, RSignItem *src) {
	dst->space = src->space;
	if (src->addr != UT64_MAX) {
		dst->addr = src->addr;
	}

	// uniquee free for each
	quick_merge (bytes, r_sign_bytes_free);
	quick_merge (graph, r_sign_graph_free);
	quick_merge (hash, r_sign_hash_free);

	// strings
	quick_merge (comment, free);
	quick_merge (realname, free);
	quick_merge (rawname, free);
	quick_merge (demangled, free);
	quick_merge (next, free);
	quick_merge (types, free);

	// lists
	quick_merge (refs, r_list_free);
	quick_merge (xrefs, r_list_free);
	quick_merge (vars, r_list_free);
	quick_merge (collisions, free);
}
#undef quick_merge

static RSignItem *sign_get_sdb_item(RAnal *a, const char *key) {
	RSignItem *it = NULL;
	const char *value = sdb_const_get (a->sdb_zigns, key, 0);
	if (value && (it = r_sign_item_new ())) {
		if (r_sign_deserialize (a, it, key, value)) {
			return it;
		}
		r_sign_item_free (it);
	}
	return NULL;
}

static bool r_sign_set_item(Sdb *sdb, RSignItem *it, char *key_optional) {
	bool retval = false;
	char *key = NULL, *mykey = key_optional;
	if (!mykey) {
		key = mykey = item_serialize_key (it);
	}
	char *value = serialize_value (it);
	if (mykey && value) {
		sdb_set (sdb, mykey, value, 0);
		retval = true;
	}
	free (key);
	free (value);
	return retval;
}

R_API RSignItem *r_sign_get_item(RAnal *a, const char *name) {
	R_RETURN_VAL_IF_FAIL (a && name, NULL);
	char *k = space_serialize_key (r_spaces_current (&a->zign_spaces), name);
	if (k) {
		RSignItem *it = sign_get_sdb_item (a, k);
		free (k);
		return it;
	}
	return NULL;
}

static bool validate_item(RSignItem *it) {
	// TODO: validate more
	if (!r_name_check (it->name)) {
		R_LOG_ERROR ("Bad name in signature: %s", it->name);
		return false;
	}

	if (it->space && it->space->name && !r_name_check (it->space->name)) {
		R_LOG_ERROR ("Bad space name in signature: %s", it->space->name);
		return false;
	}

	if (it->bytes) {
		RSignBytes *b = it->bytes;
		if (!b->mask || !b->bytes || b->size <= 0) {
			R_LOG_ERROR ("Signature '%s' has empty byte field", it->name);
			return false;
		}
		if (b->mask[0] == '\0') {
			R_LOG_ERROR ("Signature '%s' mask starts empty", it->name);
			return false;
		}
	}
	return true;
}

R_API bool r_sign_add_item(RAnal *a, RSignItem *it) {
	R_RETURN_VAL_IF_FAIL (a && it, false);
	r_name_filter (it->name, -1);
	if (!validate_item (it)) {
		return false;
	}
	char *key = item_serialize_key (it);
	if (!key) {
		return false;
	}
	RSignItem *current = sign_get_sdb_item (a, key);

	bool retval = false;
	if (current) {
		merge_item_clobber (current, it);
		retval = r_sign_set_item (a->sdb_zigns, current, key);
		r_sign_item_free (current);
	} else {
		retval = r_sign_set_item (a->sdb_zigns, it, key);
	}
	free (key);
	return retval;
}

static RSignItem *item_new_named(RAnal *a, const char *n) {
	RSignItem *it = r_sign_item_new ();
	if (it && (it->name = strdup (n))) {
		it->space = r_spaces_current (&a->zign_spaces);
		return it;
	}
	r_sign_item_free (it);
	return NULL;
}

static bool addHash(RAnal *a, const char *name, int type, const char *val) {
	RSignItem *it = item_new_named (a, name);
	if (!it) {
		return false;
	}
	it->hash = R_NEW0 (RSignHash);
	if (!it->hash) {
		r_sign_item_free (it);
		return false;
	}
	it->space = r_spaces_current (&a->zign_spaces);

	bool retval = false;
	switch (type) {
	case R_SIGN_BBHASH:
		it->hash->bbhash = strdup (val);
		retval = r_sign_add_item (a, it);
		r_sign_item_free (it);
		break;
	}

	return retval;
}

static bool addBBHash(RAnal *a, RAnalFunction *fcn, const char *name) {
	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && r_sign_addto_item (a, it, fcn, R_SIGN_BBHASH)) {
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_hash(RAnal *a, const char *name, int type, const char *val, int len) {
	R_RETURN_VAL_IF_FAIL (a && name && type && val && len > 0, false);
	if (type != R_SIGN_BBHASH) {
		R_LOG_ERROR ("hash type unknown");
		return false;
	}
	int digestsize = 32 * 2; // SHA256 digest is 32 bytes, hex is 64 chars
	if (len != digestsize) {
		R_LOG_ERROR ("invalid hash size: %d (SHA256 digest size is %d)", len, digestsize);
		return false;
	}
	return addHash (a, name, type, val);
}

R_API bool r_sign_add_bb_hash(RAnal *a, RAnalFunction *fcn, const char *name) {
	R_RETURN_VAL_IF_FAIL (a && fcn && name, false);
	return addBBHash (a, fcn, name);
}

R_API bool r_sign_add_bytes(RAnal *a, const char *name, const char *val) {
	R_RETURN_VAL_IF_FAIL (a && name && val, false);
	bool ret = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->bytes = deserialize_bytes (val))) {
		ret = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return ret;
}

R_API bool r_sign_add_anal(RAnal *a, const char *name, const char *val) {
	R_RETURN_VAL_IF_FAIL (a && name && val, false);
	bool ret = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->bytes = deserialize_anal (a, val))) {
		ret = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return ret;
}

static RSignGraph *r_sign_fcn_graph(RAnalFunction *fcn) {
	RSignGraph *graph = R_NEW0 (RSignGraph);
	if (graph) {
		graph->cc = r_anal_function_complexity (fcn),
		graph->nbbs = r_list_length (fcn->bbs);
		graph->edges = r_anal_function_count_edges (fcn, &graph->ebbs);
		graph->bbsum = r_anal_function_realsize (fcn);
	}
	return graph;
}

static int cmpaddr(const void *_a, const void *_b);

static RSignBytes *r_sign_func_empty_mask(RAnal *a, RAnalFunction *fcn) {
	if (!fcn->bbs || !fcn->bbs->head) {
		return NULL;
	}
	RCore *core = a->coreb.core;
	int maxsz = a->coreb.cfgGetI (core, "zign.maxsz");
	r_list_sort (fcn->bbs, &cmpaddr);
	ut64 ea = fcn->addr;
	RAnalBlock *bb = (RAnalBlock *)fcn->bbs->tail->data;
	int size = R_MIN (bb->addr + bb->size - ea, maxsz);

	// alloc space for signature
	RSignBytes *sig = R_NEW0 (RSignBytes);
	if (sig) {
		sig->bytes = malloc (size);
		sig->mask = R_NEWS0 (ut8, size);
		sig->size = size;
		if (sig->bytes && sig->mask && a->iob.read_at (a->iob.io, ea, sig->bytes, size) == size) {
			return sig;
		}
	}
	r_sign_bytes_free (sig);
	return NULL;
}

static RSignBytes *r_sign_fcn_bytes(RAnal *a, RAnalFunction *fcn) {
	RSignBytes *sig = r_sign_func_empty_mask (a, fcn);
	if (!sig) {
		return NULL;
	}

	ut64 ea = fcn->addr;
	int size = sig->size;
	ut8 *tmpmask = NULL;
	RAnalBlock *bb;
	RListIter *iter;
	r_list_foreach (fcn->bbs, iter, bb) {
		if (bb->addr >= ea) {
			size_t delta = bb->addr - ea;
			size_t rsize = bb->size;

			// bounds check
			if (delta >= size) {
				break;
			}
			if (size - delta < rsize) {
				rsize = size - delta;
			}

			// get mask for block
			if (!(tmpmask = r_anal_mask (a, rsize, sig->bytes + delta, ea))) {
				r_sign_bytes_free (sig);
				return NULL;
			}
			if (rsize > 0) {
				memcpy (sig->mask + delta, tmpmask, rsize);
			}
			free (tmpmask);
		}
	}
	return sig;
}

static RSignHash *r_sign_fcn_bbhash(RAnal *a, RAnalFunction *fcn) {
	R_RETURN_VAL_IF_FAIL (a && fcn, NULL);
	RSignHash *hash = R_NEW0 (RSignHash);
	if (!hash) {
		return NULL;
	}

	char *digest_hex = r_sign_calc_bbhash (a, fcn);
	if (!digest_hex) {
		free (hash);
		return NULL;
	}
	hash->bbhash = digest_hex;
	return hash;
}

static char *real_function_name(RAnal *a, RAnalFunction *fcn);

static RSignItem *item_from_func(RAnal *a, RAnalFunction *fcn, const char *name) {
	if (r_list_empty (fcn->bbs)) {
		R_LOG_WARN ("Function with no basic blocks at 0x%08"PFMT64x, fcn->addr);
		return NULL;
	}
	RSignItem *it = item_new_named (a, name? name: fcn->name);
	if (it) {
		// Prefer names from flag item if available
		RFlagItem *fi = get_sym_flag_at (a->coreb.core, fcn->addr);
		if (fi) {
			/* Prefer the original binary/symbol name as rawname, not the flag
			 * name. Flags use `fi->name` (e.g. "sym.main") which is a
			 * namespaced/escaped representation; the original symbol text is in
			 * `fi->rawname` (from RBinName->oname). Use that when available so
			 * we don't mix flag names into the signature's rawname. */
			it->rawname = fi->rawname? strdup (fi->rawname): strdup (fcn->name);
			/* For demangled prefer the flag's realname (if set), otherwise
			 * fall back to the analysis-provided real name. */
			if (fi->realname) {
				it->demangled = strdup (fi->realname);
			} else {
				it->demangled = real_function_name (a, fcn);
			}
		} else {
			/* Fallback to function/analysis names */
			it->rawname = strdup (fcn->name);
			it->demangled = real_function_name (a, fcn);
		}
		if (name && strcmp (name, fcn->name)) {
			it->realname = strdup (name);
		}
		r_sign_addto_item (a, it, fcn, R_SIGN_GRAPH);
		r_sign_addto_item (a, it, fcn, R_SIGN_BYTES);
		r_sign_addto_item (a, it, fcn, R_SIGN_XREFS);
		r_sign_addto_item (a, it, fcn, R_SIGN_REFS);
		r_sign_addto_item (a, it, fcn, R_SIGN_VARS);
		r_sign_addto_item (a, it, fcn, R_SIGN_TYPES);
		r_sign_addto_item (a, it, fcn, R_SIGN_BBHASH);
		r_sign_addto_item (a, it, fcn, R_SIGN_OFFSET);
		r_sign_addto_item (a, it, fcn, R_SIGN_NAME);
	}
	return it;
}

static int fcn_sort(const void *va, const void *vb) {
	ut64 a = ((const RAnalFunction *)va)->addr;
	ut64 b = ((const RAnalFunction *)vb)->addr;
	if (a < b) {
		return -1;
	} else if (a > b) {
		return 1;
	}
	return 0;
}

static bool name_exists(Sdb *sdb, const char *n, const RSpace *sp) {
	char *key = space_serialize_key (sp, n);
	bool exist = false;
	if (key) {
		exist = sdb_exists (sdb, key);
		free (key);
	}
	return exist;
}

static char *get_unique_name(Sdb *sdb, const char *name, const RSpace *sp) {
	ut32 i;
	for (i = 2; i < UT32_MAX; i++) {
		char *unique = r_str_newf ("%s_%d", name, i);
		if (!name_exists (sdb, unique, sp)) {
			return unique;
		}
		free (unique);
	}
	return NULL;
}

static char *real_function_name(RAnal *a, RAnalFunction *fcn) {
	RCore *core = a->coreb.core;
	if (!core || !fcn || !fcn->name) {
		return NULL;
	}
	// Prefer demangled name stored in flags
	RFlagItem *fi = get_sym_flag_at (core, fcn->addr);
	if (fi && fi->realname) {
		return strdup (fi->realname);
	}
	// Fallback to rbin demangler honoring settings
	bool demangle = a->coreb.cfgGetB (core, "bin.demangle");
	const char *lang = demangle? a->coreb.cfgGet (core, "bin.lang"): NULL;
	bool keep_lib = a->coreb.cfgGetB (core, "bin.demangle.pfxlib");
	char *name = strdup (fcn->name);
	if (demangle && a->binb.demangle) {
		char *tmp = a->binb.demangle (NULL, lang, name, fcn->addr, keep_lib);
		if (tmp) {
			free (name);
			name = tmp;
		}
	}
	return name;
}

R_API int r_sign_all_functions(RAnal *a, bool merge) {
	R_RETURN_VAL_IF_FAIL (a, 0);
	RCore *core = a->coreb.core;
	RCons *cons = core->cons;
	RAnalFunction *fcn = NULL;
	RListIter *iter = NULL;
	int count = 0;
	r_list_sort (a->fcns, fcn_sort);
	const RSpace *sp = r_spaces_current (&a->zign_spaces);
	char *prev_name = NULL;
	r_cons_break_push (cons, NULL, NULL);
	RCoreBind cb = a->coreb;
	bool do_mangled = cb.cfgGetI (core, "zign.mangled");
	bool zign_dups = a->opt.zigndups;
	r_list_foreach_prev (a->fcns, iter, fcn) {
		if (r_cons_is_breaked (core->cons)) {
			break;
		}
		char *realname = do_mangled? strdup (fcn->name): real_function_name (a, fcn);
		RSignItem *it = NULL;
		if (merge || !name_exists (a->sdb_zigns, realname, sp)) {
			it = item_from_func (a, fcn, realname);
		} else if (zign_dups) {
			char *name = get_unique_name (a->sdb_zigns, fcn->name, sp);
			if (name) {
				it = item_from_func (a, fcn, name);
			}
			free (name);
		}
		free (realname);
		if (it) {
			if (prev_name) {
				it->next = prev_name;
			}
			prev_name = strdup (it->name);
			r_sign_add_item (a, it);
			r_sign_item_free (it);
			count++;
		} else {
			free (prev_name);
			prev_name = NULL;
		}
	}
	r_cons_break_pop (cons);
	free (prev_name);
	return count;
}

R_API bool r_sign_add_func(RAnal *a, RAnalFunction *fcn, const char *name) {
	R_RETURN_VAL_IF_FAIL (a && fcn, false);
	RSignItem *it = item_from_func (a, fcn, name);
	if (it) {
		r_sign_add_item (a, it);
		r_sign_item_free (it);
		return true;
	}
	return false;
}

R_API bool r_sign_addto_item(RAnal *a, RSignItem *it, RAnalFunction *fcn, RSignType type) {
	R_RETURN_VAL_IF_FAIL (a && it && fcn, false);
	switch (type) {
	case R_SIGN_GRAPH:
		return !it->graph && (it->graph = r_sign_fcn_graph (fcn));
	case R_SIGN_BYTES:
		return !it->bytes && (it->bytes = r_sign_fcn_bytes (a, fcn));
	case R_SIGN_XREFS:
		return !it->xrefs && (it->xrefs = r_sign_fcn_xrefs (a, fcn));
	case R_SIGN_REFS:
		return !it->refs && (it->refs = r_sign_fcn_refs (a, fcn));
	case R_SIGN_VARS:
		return !it->vars && (it->vars = sign_vars (fcn));
	case R_SIGN_TYPES:
		if (!it->types) {
			it->types = r_anal_function_get_signature_string (fcn);
			if (it->types) {
				size_t l = strlen (it->types) - 1;
				if (l > 0 && it->types[l] == ';') {
					it->types[l] = '\0';
					return true;
				} else {
					free (it->types);
				}
			}
		}
		return false;
	case R_SIGN_BBHASH:
		return !it->hash && (it->hash = r_sign_fcn_bbhash (a, fcn));
	case R_SIGN_OFFSET:
		it->addr = fcn->addr;
		return true;
	case R_SIGN_NAME:
		if (!it->realname && it->name) {
			if (strcmp (it->name, fcn->name)) {
				it->realname = strdup (fcn->name);
			}
			return true;
		}
		break;
	default:
		R_LOG_ERROR ("%s Can not handle type %c", __FUNCTION__, type);
	}

	return false;
}

R_API bool r_sign_add_graph(RAnal *a, const char *name, RSignGraph graph) {
	R_RETURN_VAL_IF_FAIL (a && !R_STR_ISEMPTY (name), false);
	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->graph = R_NEW0 (RSignGraph))) {
		*it->graph = graph;
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_comment(RAnal *a, const char *name, const char *comment) {
	R_RETURN_VAL_IF_FAIL (a && name && comment, false);

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->comment = strdup (comment))) {
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_name(RAnal *a, const char *name, const char *realname) {
	R_RETURN_VAL_IF_FAIL (a && name && realname, false);

	if (strchr (realname, ' ')) {
		R_LOG_ERROR ("Realname sig can't contain spaces");
		return false;
	}

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->realname = strdup (realname))) {
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_types(RAnal *a, const char *name, const char *types) {
	R_RETURN_VAL_IF_FAIL (a && name && types, false);
	if (!types_sig_valid (types)) {
		R_LOG_ERROR ("Invalid types signature: %s", types);
		return false;
	}

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it) {
		it->types = strdup (types);
		retval = r_sign_add_item (a, it);
		r_sign_item_free (it);
	}
	return retval;
}

R_API bool r_sign_add_addr(RAnal *a, const char *name, ut64 addr) {
	R_RETURN_VAL_IF_FAIL (a && name && addr != UT64_MAX, false);

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it) {
		it->addr = addr;
		retval = r_sign_add_item (a, it);
		r_sign_item_free (it);
	}
	return retval;
}

R_API bool r_sign_add_vars(RAnal *a, const char *name, const char *vars) {
	R_RETURN_VAL_IF_FAIL (a && name && vars, false);

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->vars = r_anal_var_deserialize (vars))) {
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_refs(RAnal *a, const char *name, RList *refs) {
	R_RETURN_VAL_IF_FAIL (a && name && refs, false);

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->refs = r_list_newf ((RListFree)free))) {
		RListIter *iter;
		char *ref;
		r_list_foreach (refs, iter, ref) {
			r_list_append (it->refs, strdup (ref));
		}
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

R_API bool r_sign_add_xrefs(RAnal *a, const char *name, RList *xrefs) {
	R_RETURN_VAL_IF_FAIL (a && name && xrefs, false);

	bool retval = false;
	RSignItem *it = item_new_named (a, name);
	if (it && (it->xrefs = r_list_newf ((RListFree)free))) {
		RListIter *iter;
		char *xref;
		r_list_foreach (xrefs, iter, xref) {
			r_list_append (it->xrefs, strdup (xref));
		}
		retval = r_sign_add_item (a, it);
	}
	r_sign_item_free (it);
	return retval;
}

struct ctxDeleteCB {
	RAnal *anal;
	char *key;
	size_t len;
};

static bool deleteBySpaceCB(void *user, const char *k, const char *v) {
	struct ctxDeleteCB *ctx = (struct ctxDeleteCB *) user;
	if (!strncmp (k, ctx->key, ctx->len)) {
		sdb_unset (ctx->anal->sdb_zigns, k, 0);
	}
	return true;
}

R_API bool r_sign_delete(RAnal *a, const char *name) {
	R_RETURN_VAL_IF_FAIL (a && name, false);
	bool retval = false;

	// Remove all zigns
	if (*name == '*') {
		if (!r_spaces_current (&a->zign_spaces)) {
			sdb_reset (a->sdb_zigns);
			return true;
		}
		struct ctxDeleteCB ctx = { .anal = a };
		ctx.key = space_serialize_key (r_spaces_current (&a->zign_spaces), "");
		if (ctx.key) {
			ctx.len = strlen (ctx.key);
			retval = sdb_foreach (a->sdb_zigns, deleteBySpaceCB, &ctx);
			free (ctx.key);
		}
	} else {
		// Remove specific zign
		char *key = space_serialize_key (r_spaces_current (&a->zign_spaces), name);
		if (key) {
			retval = sdb_unset (a->sdb_zigns, key, 0);
			free (key);
		}
	}
	return retval;
}

static ut8 *build_combined_bytes(RSignBytes *bsig) {
	ut8 *buf = (ut8 *)malloc (bsig->size);
	if (buf) {
		size_t i;
		for (i = 0; i < bsig->size; i++) {
			buf[i] = bsig->bytes[i] & bsig->mask[i];
		}
	}
	return buf;
}

static double cmp_bytesig_to_buff(RSignBytes *sig, ut8 *buf, int len) {
	ut8 *sigbuf = build_combined_bytes (sig);
	double sim = -1.0;
	if (sigbuf) {
		r_diff_buffers_distance (NULL, sigbuf, sig->size, buf, len, NULL, &sim);
		free (sigbuf);
	}
	return sim;
}

static double matchBytes(RSignItem *a, RSignItem *b) {
	double result = 0.0;

	if (!a->bytes || !b->bytes) {
		return result;
	}

	size_t min_size = R_MIN ((size_t)a->bytes->size, (size_t)b->bytes->size);
	if (!min_size) {
		return result;
	}

	ut8 *combined_mask = NULL;
	if (a->bytes->mask || b->bytes->mask) {
		combined_mask = (ut8*)malloc (min_size);
		if (!combined_mask) {
			return result;
		}
		if (a->bytes->mask) {
			memcpy (combined_mask, a->bytes->mask, min_size);
		} else {
			memset (combined_mask, 0xff, min_size);
		}
		if (b->bytes->mask) {
			int i;
			for (i = 0; i != min_size; i++) {
				combined_mask[i] &= b->bytes->mask[i];
			}
		}
	}

	if ((combined_mask && !r_mem_cmp_mask (a->bytes->bytes, b->bytes->bytes, combined_mask, min_size)) ||
		(!combined_mask && !memcmp (a->bytes->bytes, b->bytes->bytes, min_size))) {
		result = (double)min_size / (double)R_MAX (a->bytes->size, b->bytes->size);
	}

	free (combined_mask);

	return result;
}

#define SIMILARITY(a, b) \
	((a) == (b)? 1.0: (R_MAX ((a), (b)) == 0.0? 0.0: (double)R_MIN ((a), (b)) / (double)R_MAX ((a), (b))))

static double matchGraph(RSignItem *a, RSignItem *b) {
	if (!a->graph || !b->graph) {
		return 0.0;
	}

	double total = 0.0;

	total += SIMILARITY (a->graph->cc, b->graph->cc);
	total += SIMILARITY (a->graph->nbbs, b->graph->nbbs);
	total += SIMILARITY (a->graph->ebbs, b->graph->ebbs);
	total += SIMILARITY (a->graph->edges, b->graph->edges);
	total += SIMILARITY (a->graph->bbsum, b->graph->bbsum);

	return total / 5.0;
}

static int score_cmpr(const void *a, const void *b) {
	double sa = ((RSignCloseMatch *)a)->score;
	double sb = ((RSignCloseMatch *)b)->score;

	if (sa < sb) {
		return 1;
	}
	if (sa > sb) {
		return -1;
	}
	return 0;
}

typedef struct {
	RSignItem *test;
	RList *output;
	size_t count;
	double score_threshold;
	ut8 *bytes_combined;

	// greatest lower bound. Thanks lattice theory for helping name variables
	double infimum;
} ClosestMatchData;

static bool closest_match_update(RSignItem *it, ClosestMatchData *data) {
	// quantify how close the signature matches
	int div = 0;
	double score = 0.0;
	double gscore = -1.0;
	if (it->graph && data->test->graph) {
		gscore = matchGraph (it, data->test);
		score += gscore;
		div++;
	}
	double bscore = -1.0;
	bool list_full = (r_list_length (data->output) == data->count);

	// value to beat to enter the list
	double pivot = data->score_threshold;
	if (list_full) {
		pivot = R_MAX (pivot, data->infimum);
	}

	if (it->bytes && data->bytes_combined) {
		int sizea = it->bytes->size;
		int sizeb = data->test->bytes->size;
		if (pivot > 0.0) {
			// bytes distance is slow. To avoid it, we can do quick maths to
			// see if the highest possible score would be good enough to change
			// results
			double maxscore = R_MIN (sizea, sizeb) / R_MAX (sizea, sizeb);
			if (div > 0) {
				maxscore = (maxscore + score) / div;
			}
			if (maxscore < pivot) {
				r_sign_item_free (it);
				return true;
			}
		}

		// get true byte score
		bscore = cmp_bytesig_to_buff (it->bytes, data->bytes_combined, sizeb);
		score += bscore;
		div++;
	}
	if (div == 0) {
		r_sign_item_free (it);
		return true;
	}
	score /= div;

	// score is too low, don't bother doing any more work
	if (score < pivot) {
		r_sign_item_free (it);
		return true;
	}

	// add new element
	RSignCloseMatch *row = R_NEW (RSignCloseMatch);
	if (!row) {
		r_sign_item_free (it);
		return false;
	}
	row->score = score;
	row->gscore = gscore;
	row->bscore = bscore;
	row->item = it;
	r_list_add_sorted (data->output, (void *)row, &score_cmpr);

	if (list_full) {
		// remove smallest element
		r_sign_close_match_free (r_list_pop (data->output));

		// get new infimum
		row = r_list_last (data->output);
		data->infimum = row->score;
	}
	return true;
}

R_API void r_sign_close_match_free(RSignCloseMatch *match) {
	if (match) {
		r_sign_item_free (match->item);
		free (match);
	}
}

static bool _closest_match_cb(RSignItem *it, void *user) {
	return closest_match_update (it, (ClosestMatchData *)user);
}

R_API RList *r_sign_find_closest_fcn(RAnal *a, RSignItem *it, int count, double score_threshold) {
	R_RETURN_VAL_IF_FAIL (a && it && count > 0 && score_threshold >= 0 && score_threshold <= 1, NULL);
	R_RETURN_VAL_IF_FAIL (it->bytes || it->graph, NULL);

	RList *output = r_list_newf ((RListFree)r_sign_close_match_free);
	if (!output) {
		return NULL;
	}

	ClosestMatchData data;
	data.output = output;
	data.count = count;
	data.score_threshold = score_threshold;
	data.infimum = 0.0;
	data.test = it;
	if (it->bytes) {
		data.bytes_combined = build_combined_bytes (it->bytes);
	} else {
		data.bytes_combined = NULL;
	}

	RAnalFunction *fcn;
	RListIter *iter;
	r_list_foreach (a->fcns, iter, fcn) {
		// turn function into signature item
		RSignItem *fsig = r_sign_item_new ();
		if (!fsig) {
			free (data.bytes_combined);
			r_list_free (output);
			return NULL;
		}
		if (data.bytes_combined) {
			r_sign_addto_item (a, fsig, fcn, R_SIGN_BYTES);
		}
		if (it->graph) {
			r_sign_addto_item (a, fsig, fcn, R_SIGN_GRAPH);
		}
		r_sign_addto_item (a, fsig, fcn, R_SIGN_OFFSET);
		fsig->name = strdup (fcn->name);

		// maybe add signature item to output list
		closest_match_update (fsig, &data);
	}
	free (data.bytes_combined);
	return output;
}

R_API char *r_sign_diff(RAnal *a, RSignOptions *options, const char *other_space_name) {
	R_RETURN_VAL_IF_FAIL (a && other_space_name, NULL);

	RSpace *current_space = r_spaces_current (&a->zign_spaces);
	if (!current_space) {
		return NULL;
	}
	RSpace *other_space = r_spaces_get (&a->zign_spaces, other_space_name);
	if (!other_space) {
		return NULL;
	}

	RList *la = deserialize_sign_space (a, current_space);
	if (!la) {
		return NULL;
	}
	RList *lb = deserialize_sign_space (a, other_space);
	if (!lb) {
		r_list_free (la);
		return NULL;
	}

	R_LOG_INFO ("Diff %d %d", (int)ls_length (la), (int)ls_length (lb));

	RStrBuf *sb = r_strbuf_new ("");
	RListIter *itr;
	RListIter *itr2;
	RSignItem *si;
	RSignItem *si2;

	// do the sign diff here
	r_list_foreach (la, itr, si) {
		if (strstr (si->name, "imp.")) {
			continue;
		}
		r_list_foreach (lb, itr2, si2) {
			if (strstr (si2->name, "imp.")) {
				continue;
			}
			double bytesScore = matchBytes (si, si2);
			double graphScore = matchGraph (si, si2);
			bool bytesMatch = bytesScore >= (options ? options->bytes_diff_threshold : SIGN_DIFF_MATCH_BYTES_THRESHOLD);
			bool graphMatch = graphScore >= (options ? options->graph_diff_threshold : SIGN_DIFF_MATCH_GRAPH_THRESHOLD);

			if (bytesMatch) {
				r_strbuf_appendf (sb, "0x%08" PFMT64x " 0x%08"PFMT64x " %02.5lf B %s\n", si->addr, si2->addr, bytesScore, si->name);
			}

			if (graphMatch) {
				r_strbuf_appendf (sb, "0x%08" PFMT64x " 0x%08"PFMT64x" %02.5lf G %s\n", si->addr, si2->addr, graphScore, si->name);
			}
		}
	}

	r_list_free (la);
	r_list_free (lb);
	return r_strbuf_drain (sb);
}

R_API char *r_sign_diff_by_name(RAnal *a, RSignOptions *options, const char *other_space_name, bool not_matching) {
	R_RETURN_VAL_IF_FAIL (a && other_space_name, NULL);

	RSpace *current_space = r_spaces_current (&a->zign_spaces);
	if (!current_space) {
		return NULL;
	}
	RSpace *other_space = r_spaces_get (&a->zign_spaces, other_space_name);
	if (!other_space) {
		return NULL;
	}

	RList *la = deserialize_sign_space (a, current_space);
	if (!la) {
		return NULL;
	}
	RList *lb = deserialize_sign_space (a, other_space);
	if (!lb) {
		r_list_free (la);
		return NULL;
	}

	R_LOG_INFO ("Diff by name %d %d (%s)", (int)ls_length (la), (int)ls_length (lb), not_matching? "not matching" : "matching");

	RStrBuf *sb = r_strbuf_new ("");
	RListIter *itr;
	RListIter *itr2;
	RSignItem *si;
	RSignItem *si2;

	r_list_foreach (la, itr, si) {
		if (strstr (si->name, "imp.")) {
			continue;
		}
		r_list_foreach (lb, itr2, si2) {
			if (strcmp (si->name, si2->name)) {
				continue;
			}
			// TODO: add config variable for threshold
			double bytesScore = matchBytes (si, si2);
			double graphScore = matchGraph (si, si2);
			bool bytesMatch = bytesScore >= (options ? options->bytes_diff_threshold : SIGN_DIFF_MATCH_BYTES_THRESHOLD);
			bool graphMatch = graphScore >= (options ? options->graph_diff_threshold : SIGN_DIFF_MATCH_GRAPH_THRESHOLD);
			if ((bytesMatch && !not_matching) || (!bytesMatch && not_matching)) {
				r_strbuf_appendf (sb, "0x%08"PFMT64x" 0x%08"PFMT64x" %02.5f B %s\n", si->addr, si2->addr, bytesScore, si->name);
			}
			if ((graphMatch && !not_matching) || (!graphMatch && not_matching)) {
				r_strbuf_appendf (sb, "0x%08"PFMT64x" 0x%08"PFMT64x" %02.5f G %s\n", si->addr, si2->addr, graphScore, si->name);
			}
		}
	}

	r_list_free (la);
	r_list_free (lb);
	return r_strbuf_drain (sb);
}

struct ctxListCB {
	RAnal *anal;
	int idx;
	int format;
	PJ *pj;
	ut64 addr;
	RStrBuf *sb;
};


static void listBytes(RStrBuf *sb, RSignItem *it, PJ *pj, int format) {
	RSignBytes *bytes = it->bytes;

	if (!bytes->bytes) {
		return;
	}

	int masked = 0, i = 0;
	for (i = 0; i < bytes->size; i++) {
		masked += bytes->mask[i] == 0xff;
	}

	char *strbytes = r_hex_bin2strdup (bytes->bytes, bytes->size);
	if (!strbytes) {
		return;
	}
	char *strmask = r_hex_bin2strdup (bytes->mask, bytes->size);
	if (!strmask) {
		free (strbytes);
		return;
	}

	r_str_sanitize (strbytes);
	r_str_sanitize (strmask);
	if (format == '*') {
		if (masked == bytes->size) {
			r_strbuf_appendf (sb, "za %s b %s\n", it->name, strbytes);
		} else {
			r_strbuf_appendf (sb, "za %s b %s:%s\n", it->name, strbytes, strmask);
		}
	} else if (format == 'q') {
		r_strbuf_appendf (sb, " b(%d/%d)", masked, bytes->size);
	} else if (format == 'j') {
		pj_ks (pj, "bytes", strbytes);
		pj_ks (pj, "mask", strmask);
	} else {
		r_strbuf_appendf (sb, "  bytes: %s\n", strbytes);
		r_strbuf_appendf (sb, "  mask: %s\n", strmask);
	}

	free (strbytes);
	free (strmask);
}

static void listGraph(RStrBuf *sb, RSignItem *it, PJ *pj, int format) {
	RSignGraph *graph = it->graph;

	if (format == 'q') {
		r_strbuf_appendf (sb, " g(cc=%d,nb=%d,e=%d,eb=%d,h=%d)",
			graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum);
	} else if (format == '*') {
		r_strbuf_appendf (sb, "za %s g cc=%d nbbs=%d edges=%d ebbs=%d bbsum=%d\n",
			it->name, graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum);
	} else if (format == 'j') {
		pj_ko (pj, "graph");
		pj_kN (pj, "cc", graph->cc);
		pj_kN (pj, "nbbs", graph->nbbs);
		pj_kN (pj, "edges", graph->edges);
		pj_kN (pj, "ebbs", graph->ebbs);
		pj_kN (pj, "bbsum", graph->bbsum);
		pj_end (pj);
	} else {
		r_strbuf_appendf (sb, "  graph: cc=%d nbbs=%d edges=%d ebbs=%d bbsum=%d\n",
			graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum);
	}
}

static void liststring(RStrBuf *sb, RSignType t, char *value, PJ *pj, int format, const char *name) {
	if (!value) {
		return;
	}
	if (format == 'j') {
		pj_ks (pj, r_sign_type_to_name (t), value);
	} else {
		const char *type = r_sign_type_to_name (t);
		char *print_value = strdup (value);
		if (format != 'j' && (format == '*' || t != R_SIGN_TYPES)) {
			r_str_sanitize (print_value);
		}
		if (format == 'q') {
			r_strbuf_appendf (sb, "\n ; %s\n", print_value);
		} else if (format == '*') {
			// comment injection via CCu..
			if (t == R_SIGN_RAWNAME || t == R_SIGN_DEMANGLED || t == R_SIGN_NAME) {
				char *b64 = r_base64_encode_dyn ((const ut8 *)print_value, -1);
				if (b64) {
					r_strbuf_appendf (sb, "za %s %c %s\n", name, t, b64);
					free (b64);
				}
			} else {
				r_strbuf_appendf (sb, "za %s %c %s\n", name, t, print_value);
			}
		} else {
			r_strbuf_appendf (sb, "  %s: %s\n", type, print_value);
		}
		free (print_value);
	}
}

static void listOffset(RStrBuf *sb, RSignItem *it, PJ *pj, int format) {
	if (format == 'q') {
		// nop
	} else if (format == '*') {
		r_strbuf_appendf (sb, "za %s o 0x%08"PFMT64x"\n", it->name, it->addr);
	} else if (format == 'j') {
		pj_kN (pj, "addr", it->addr);
	} else {
		r_strbuf_appendf (sb, "  addr: 0x%08"PFMT64x"\n", it->addr);
	}
}

static void inline list_vars_abs(RStrBuf *sb, RSignItem *it, bool rad) {
	if (it->vars && !r_list_empty (it->vars)) {
		char *ser = r_anal_var_prot_serialize (it->vars, true);
		if (ser) {
			r_str_sanitize (ser);
			if (rad) {
				r_strbuf_appendf (sb, "za %s %c %s\n", it->name, R_SIGN_VARS, ser);
			} else {
				r_strbuf_appendf (sb, "  vars: %s\n", ser);
			}
		}
		free (ser);
	}
}

static void inline list_vars_json(RSignItem *it, PJ *pj) {
	pj_ka (pj, "vars");
	if (it->vars) {
		RAnalVarProt *v;
		RListIter *iter;
		r_list_foreach (it->vars, iter, v) {
			char kind[] = { v->kind, '\0' };
			pj_o (pj);
			pj_ks (pj, "name", v->name);
			pj_ks (pj, "type", v->type);
			pj_ks (pj, "kind", kind);
			pj_kN (pj, "delta", v->delta);
			pj_kb (pj, "isarg", v->isarg);
			pj_end (pj);
		}
	}
	pj_end (pj);
}

static void inline list_vars(RStrBuf *sb, RSignItem *it, PJ *pj, int fmt) {
	switch (fmt) {
	case '*':
		list_vars_abs (sb, it, true);
		break;
	case 'q':
		if (it->vars) {
			r_strbuf_appendf (sb, " vars[%d]", r_list_length (it->vars));
		}
		break;
	case 'j':
		list_vars_json (it, pj);
		break;
	default:
		list_vars_abs (sb, it, false);
		break;
	}
}

static bool list_sign_begin(RStrBuf *sb, PJ *pj, int fmt, int type, const char *name, int count) {
	const char *tname = r_sign_type_to_name (type);
	switch (fmt) {
	case '*':
		r_strbuf_appendf (sb, "za %s %c ", name, type);
		break;
	case 'q':
		r_strbuf_appendf (sb, " %s[%d]", tname, count);
		return false;
	case 'j':
		pj_ka (pj, tname);
		break;
	default:
		if (count > 0) {
			r_strbuf_appendf (sb, "  %s: ", tname);
		}
	}
	return true;
}

static void list_sign_entry(RStrBuf *sb, PJ *pj, int fmt, char *ref, bool first) {
	if (fmt == '*') {
		r_str_sanitize (ref);
	}
	if (!first) {
		if (fmt == '*') {
			r_strbuf_append (sb, " ");
		} else if (fmt != 'j') {
			r_strbuf_append (sb, ", ");
		}
	}
	if (fmt == 'j') {
		pj_s (pj, ref);
	} else {
		r_strbuf_append (sb, ref);
	}
}

static void list_sign_end(RStrBuf *sb, PJ *pj, int fmt) {
	if (fmt == 'j') {
		pj_end (pj);
	} else {
		r_strbuf_append (sb, "\n");
	}
}

static void list_sign_list(RStrBuf *sb, RList *l, PJ *pj, int fmt, int type, const char *name) {
	if (!list_sign_begin (sb, pj, fmt, type, name, r_list_length (l))) {
		return;
	}
	char *ref;
	RListIter *iter;
	r_list_foreach (l, iter, ref) {
		list_sign_entry (sb, pj, fmt, ref, iter == l->head);
	}
	list_sign_end (sb, pj, fmt);
}

static void list_sign_collisions(RStrBuf *sb, const char *cols, PJ *pj, int fmt, const char *name) {
	int count = !cols? 0: fmt == 'q'? r_str_char_count (cols, ',') + 1: 1;
	if (!list_sign_begin (sb, pj, fmt, R_SIGN_COLLISIONS, name, count)) {
		return;
	}
	char *dup = cols? strdup (cols): NULL;
	char *ref = dup;
	while (ref) {
		char *next = strchr (ref, ',');
		if (next) {
			*next++ = 0;
		}
		r_str_trim (ref);
		list_sign_entry (sb, pj, fmt, ref, ref == dup);
		ref = next;
	}
	free (dup);
	list_sign_end (sb, pj, fmt);
}

static void listHash(RStrBuf *sb, RSignItem *it, PJ *pj, int format) {
	if (!it->hash) {
		return;
	}
	switch (format) {
	case 'q':
		if (it->hash->bbhash) {
			r_strbuf_appendf (sb, " h(%08x)", r_str_hash (it->hash->bbhash));
		}
		break;
	case '*':
		if (it->hash->bbhash) {
			r_str_sanitize (it->hash->bbhash);
			r_strbuf_appendf (sb, "za %s h %s\n", it->name, it->hash->bbhash);
		}
		break;
	case 'j':
		pj_ko (pj, "hash");
		if (it->hash->bbhash) {
			pj_ks (pj, "bbhash", it->hash->bbhash);
		}
		pj_end (pj);
		break;
	default:
		if (it->hash->bbhash) {
			r_str_sanitize (it->hash->bbhash);
			r_strbuf_appendf (sb, "  bbhash: %s\n", it->hash->bbhash);
		}
		break;
	}
}

static bool listCB(RSignItem *it, void *user) {
	struct ctxListCB *ctx = (struct ctxListCB *)user;
	RAnal *a = ctx->anal;
	if (!validate_item (it)) {
		return true;
	}

	if (ctx->addr != UT64_MAX) {
		if (it->addr != ctx->addr) {
			return true;
		}
	}

	// Start item
	if (ctx->format == 'j') {
		pj_o (ctx->pj);
	}

	char padstr[32];
	// Zignspace and name (except for radare format)
	switch (ctx->format) {
	case '*':
		if (it->space) {
			r_strbuf_appendf (ctx->sb, "zs %s\n", it->space->name);
		} else {
			r_strbuf_append (ctx->sb, "zs *\n");
		}
		break;
	case 'q':
		r_strbuf_appendf (ctx->sb, "0x%08" PFMT64x " ", it->addr);
		const char *pad = r_str_pad (padstr, sizeof (padstr), ' ', 30 - strlen (it->name));
		r_strbuf_appendf (ctx->sb, "%s:%s", it->name, pad);
		break;
	case 'j':
		if (it->space) {
			pj_ks (ctx->pj, "zignspace", it->space->name);
		}
		pj_ks (ctx->pj, "name", it->name);
		break;
	default:
		if (!r_spaces_current (&a->zign_spaces) && it->space) {
			r_strbuf_appendf (ctx->sb, "(%s) ", it->space->name);
		}
		r_strbuf_appendf (ctx->sb, "%s:\n", it->name);
		break;
	}

	// Bytes pattern
	if (it->bytes) {
		listBytes (ctx->sb, it, ctx->pj, ctx->format);
	} else if (ctx->format == 'j') {
		pj_ks (ctx->pj, "bytes", "");
	}
	// Graph metrics
	if (it->graph) {
		listGraph (ctx->sb, it, ctx->pj, ctx->format);
	} else if (ctx->format == 'j') {
		pj_ko (ctx->pj, "graph");
		pj_end (ctx->pj);
	}
	// Offset
	if (it->addr != UT64_MAX) {
		listOffset (ctx->sb, it, ctx->pj, ctx->format);
	} else if (ctx->format == 'j') {
		pj_kN (ctx->pj, "addr", -1);
	}

	liststring (ctx->sb, R_SIGN_NAME, it->realname, ctx->pj, ctx->format, it->name);
	liststring (ctx->sb, R_SIGN_RAWNAME, it->rawname, ctx->pj, ctx->format, it->name);
	liststring (ctx->sb, R_SIGN_DEMANGLED, it->demangled, ctx->pj, ctx->format, it->name);
	liststring (ctx->sb, R_SIGN_COMMENT, it->comment, ctx->pj, ctx->format, it->name);
	liststring (ctx->sb, R_SIGN_NEXT, it->next, ctx->pj, ctx->format, it->name);
	liststring (ctx->sb, R_SIGN_TYPES, it->types, ctx->pj, ctx->format, it->name);

	// References
	if (it->refs) {
		list_sign_list (ctx->sb, it->refs, ctx->pj, ctx->format, R_SIGN_REFS, it->name);
	} else if (ctx->format == 'j') {
		pj_ka (ctx->pj, "refs");
		pj_end (ctx->pj);
	}
	// XReferences
	if (it->xrefs) {
		list_sign_list (ctx->sb, it->xrefs, ctx->pj, ctx->format, R_SIGN_XREFS, it->name);
	} else if (ctx->format == 'j') {
		pj_ka (ctx->pj, "xrefs");
		pj_end (ctx->pj);
	}
	// Collisions
	if (it->collisions || ctx->format == 'j') {
		list_sign_collisions (ctx->sb, it->collisions, ctx->pj, ctx->format, it->name);
	}

	// Vars
	list_vars (ctx->sb, it, ctx->pj, ctx->format);

	// Hash
	if (it->hash) {
		listHash (ctx->sb, it, ctx->pj, ctx->format);
	} else if (ctx->format == 'j') {
		pj_ko (ctx->pj, "hash");
		pj_end (ctx->pj);
	}

	// End item
	if (ctx->format == 'j') {
		pj_end (ctx->pj);
	}
	if (ctx->format == 'q') {
		r_strbuf_append (ctx->sb, "\n");
	}

	ctx->idx++;
	return true;
}

R_API char *r_sign_list(RAnal *a, int format) {
	R_RETURN_VAL_IF_FAIL (a, NULL);
	PJ *pj = NULL;
	RStrBuf *sb = r_strbuf_new ("");

	if (format == 'j') {
		pj = a->coreb.pjWithEncoding (a->coreb.core);
		pj_a (pj);
	}

	{ // R2_600 - we need to pass addr as argument
		RCore *core = a->coreb.core;
		ut64 addr = core? core->addr: UT64_MAX;
		struct ctxListCB ctx = { a, 0, format, pj, addr, sb };
		r_sign_foreach (a, listCB, &ctx);
	}

	if (format == 'j') {
		pj_end (pj);
		r_strbuf_appendf (sb, "%s\n", pj_string (pj));
		pj_free (pj);
	}
	return r_strbuf_drain (sb);
}

static bool listGetCB(RSignItem *it, void *user) {
	r_list_append ((RList *)user, it);
	return true;
}

R_API const char *r_sign_type_to_name(int type) {
	switch (type) {
	case R_SIGN_BYTES:
		return "bytes";
	case R_SIGN_COMMENT:
		return "comment";
	case R_SIGN_GRAPH:
		return "graph";
	case R_SIGN_OFFSET:
		return "addr";
	case R_SIGN_NAME:
		return "realname";
	case R_SIGN_RAWNAME:
		return "rawname";
	case R_SIGN_DEMANGLED:
		return "demangled";
	case R_SIGN_REFS:
		return "refs";
	case R_SIGN_XREFS:
		return "xrefs";
	case R_SIGN_VARS:
		return "vars";
	case R_SIGN_TYPES:
		return "types";
	case R_SIGN_COLLISIONS:
		return "collisions";
	case R_SIGN_NEXT:
		return "next";
	case R_SIGN_BBHASH:
		return "bbhash";
	default:
		R_WARN_IF_REACHED ();
		return "UnknownType";
	}
}

static int cmpaddr(const void *_a, const void *_b) {
	const RAnalBlock *a = _a, *b = _b;
	return (a->addr > b->addr)? 1: (a->addr < b->addr)? -1: 0;
}

// Include SHA256 implementation for standalone use
#define R_CRYPTO_INTERNAL 1
#include "../muta/hash/sha2.c"

R_API char *r_sign_calc_bbhash(RAnal *a, RAnalFunction *fcn) {
	RListIter *iter = NULL;
	RAnalBlock *bbi = NULL;

	RSha256Context ctx;
	R_SHA2_API (r_sha256_init) (&ctx);

	r_list_sort (fcn->bbs, &cmpaddr);
	r_list_foreach (fcn->bbs, iter, bbi) {
		ut8 *buf = malloc (bbi->size);
		if (!buf) {
			return NULL;
		}
		if (a->iob.read_at (a->iob.io, bbi->addr, buf, bbi->size) != bbi->size) {
			free (buf);
			return NULL;
		}
		R_SHA2_API (r_sha256_update) (&ctx, buf, bbi->size);
		free (buf);
	}

	char textdigest[R_SHA256_DIGEST_STRING_LENGTH] = {0};
	R_SHA2_API (r_sha256_end) (&ctx, textdigest);
	return strdup (textdigest);
}

static bool countForCB(RSignItem *it, void *user) {
	(*(int *)user)++;
	return true;
}

R_API void r_sign_space_rename_for(RAnal *a, const RSpace *space, const char *oname, const char *nname) {
	R_RETURN_IF_FAIL (a && space && oname && nname);
	char *oprefix = str_serialize_key (oname, "");
	char *nprefix = str_serialize_key (nname, "");
	if (oprefix && nprefix) {
		sdb_rename_prefix (a->sdb_zigns, oprefix, nprefix);
	}
	free (nprefix);
	free (oprefix);
}

struct ctxForeachCB {
	RAnal *anal;
	RSignForeachCallback cb;
	const RSpace *space;
	bool freeit;
	void *user;
};

static bool foreachCB(void *user, const char *k, const char *v) {
	struct ctxForeachCB *ctx = (struct ctxForeachCB *) user;
	RSignItem *it = r_sign_item_new ();
	RAnal *a = ctx->anal;

	bool keep_going = true;
	if (it && r_sign_deserialize (a, it, k, v)) {
		if (!ctx->space || ctx->space == it->space) {
			keep_going = ctx->cb (it, ctx->user);
		}
	} else {
		R_LOG_ERROR ("cannot deserialize zign");
	}
	if (ctx->freeit) {
		r_sign_item_free (it);
	}
	return keep_going;
}

static inline bool local_foreach_item(RAnal *a, RSignForeachCallback cb, const RSpace *sp, bool freeit, void *user) {
	R_RETURN_VAL_IF_FAIL (a && cb, false);
	struct ctxForeachCB ctx = { a, cb, sp, freeit, user };
	return sdb_foreach (a->sdb_zigns, foreachCB, &ctx);
}

static bool r_sign_foreach_nofree(RAnal *a, RSignForeachCallback cb, void *user) {
	return local_foreach_item (a, cb, r_spaces_current (&a->zign_spaces), false, user);
}

R_API int r_sign_space_count_for(RAnal *a, const RSpace *space) {
	int count = 0;
	local_foreach_item (a, countForCB, space, true, &count);
	return count;
}

R_API void r_sign_space_unset_for(RAnal *a, const RSpace *space) {
	char *oprefix = space_serialize_key (space, "");
	char *nprefix = space_serialize_key (NULL, "");
	if (oprefix && nprefix) {
		sdb_rename_prefix (a->sdb_zigns, oprefix, nprefix);
	}
	free (nprefix);
	free (oprefix);
}

R_API bool r_sign_foreach(RAnal *a, RSignForeachCallback cb, void *user) {
	return local_foreach_item (a, cb, r_spaces_current (&a->zign_spaces), true, user);
}

R_API RList *r_sign_get_list(RAnal *a) {
	R_RETURN_VAL_IF_FAIL (a, NULL);
	RList *l = r_list_newf ((RListFree)r_sign_item_free);
	if (l) {
		local_foreach_item (a, listGetCB, NULL, false, (void *)l);
	}
	return l;
}

R_API RSignSearch *r_sign_search_new(void) {
	RSignSearch *ret = R_NEW0 (RSignSearch);
	if (ret) {
		ret->search = r_search_new (R_SEARCH_KEYWORD);
		ret->items = r_list_newf ((RListFree) r_sign_item_free);
	}
	return ret;
}

R_API void r_sign_search_free(RSignSearch *ss) {
	if (!ss) {
		return;
	}
	r_search_free (ss->search);
	r_list_free (ss->items);
	free (ss);
}

R_API RList *r_sign_find_closest_sig(RAnal *a, RSignItem *it, int count, double score_threshold) {
	R_RETURN_VAL_IF_FAIL (a && it && count > 0 && score_threshold >= 0 && score_threshold <= 1, NULL);

	// need at least one acceptable signature type
	R_RETURN_VAL_IF_FAIL (it->bytes || it->graph, NULL);

	ClosestMatchData data;
	RList *output = r_list_newf ((RListFree)r_sign_close_match_free);
	if (!output) {
		return NULL;
	}

	data.output = output;
	data.count = count;
	data.score_threshold = score_threshold;
	data.infimum = 0.0;
	data.test = it;
	if (it->bytes) {
		data.bytes_combined = build_combined_bytes (it->bytes);
	} else {
		data.bytes_combined = NULL;
	}

	if (!r_sign_foreach_nofree (a, _closest_match_cb, &data)) {
		r_list_free (output);
		output = NULL;
	}

	free (data.bytes_combined);
	return output;
}

static int searchHitCB(RSearchKeyword *kw, void *user, ut64 addr) {
	RSignSearch *ss = (RSignSearch *) user;
	return ss->cb? ss->cb ((RSignItem *) kw->data, kw, addr, ss->user): 1;
}

struct ctxAddSearchKwCB {
	RSignSearch *ss;
	int minsz;
};

static bool addSearchKwCB(RSignItem *it, void *user) {
	struct ctxAddSearchKwCB *ctx = (struct ctxAddSearchKwCB *) user;
	RSignSearch *ss = ctx->ss;
	RSignBytes *bytes = it->bytes;

	if (!bytes) {
		R_LOG_ERROR ("Cannot find bytes for this signature: %s", it->name);
		r_sign_item_free (it);
		return true;
	}

	if (ctx->minsz && bytes->size < ctx->minsz) {
		r_sign_item_free (it);
		return true;
	}
	r_list_append (ss->items, it);
	// TODO(nibble): change arg data in r_search_keyword_new to void*
	RSearchKeyword *kw = r_search_keyword_new (bytes->bytes, bytes->size, bytes->mask, bytes->size, (const char *)it);
	r_search_kw_add (ss->search, kw);
	return true;
}

R_API void r_sign_search_init(RAnal *a, RSignSearch *ss, int minsz, RSignSearchCallback cb, void *user) {
	struct ctxAddSearchKwCB ctx = { ss, minsz };
	R_RETURN_IF_FAIL (a && ss && cb);
	ss->cb = cb;
	ss->user = user;
	r_list_purge (ss->items);
	r_search_reset (ss->search, R_SEARCH_KEYWORD);
	r_sign_foreach_nofree (a, addSearchKwCB, &ctx);
	r_search_begin (ss->search);
	r_search_set_callback (ss->search, searchHitCB, ss);
}

R_API int r_sign_search_update(RAnal *a, RSignSearch *ss, ut64 *at, const ut8 *buf, int len) {
	R_RETURN_VAL_IF_FAIL (a && ss && buf && len > 0, 0);
	return r_search_update (ss->search, *at, buf, len);
}

// allow ~10% of margin error
static int matchCount(int a, int b) {
	int m = R_MAX (a, b);
	if (m > 100) {
		return R_ABS (a - b) < m / 10;
	}
	return a == b;
}

static int sig_graph_diff(RSignItem *ia, RSignItem *ib) {
	RSignGraph *a = ia->graph;
	RSignGraph *b = ib->graph;
	if (!a || !b) {
		return 1;
	}
	if (a->cc != -1 && a->cc != b->cc) {
		return 1;
	}
	if (a->nbbs != -1 && a->nbbs != b->nbbs) {
		return 1;
	}
	if (a->edges != -1 && a->edges != b->edges) {
		return 1;
	}
	if (a->ebbs != -1 && a->ebbs != b->ebbs) {
		return 1;
	}
	if (a->bbsum > 0 && !matchCount (a->bbsum, b->bbsum)) {
		return 1;
	}
	return 0;
}

#define SORT_EMPY_LAST(x, y) \
	if (!x) { \
		return !y? 0: 1; \
	} \
	if (!y) { \
		return -1; \
	}

static int sig_graph_cmp(RSignItem *ia, RSignItem *ib) {
	RSignGraph *a = ia->graph;
	RSignGraph *b = ib->graph;

	SORT_EMPY_LAST (a, b);

	int diff = a->bbsum - b->bbsum;
	if (diff) {
		return diff;
	}

	diff = a->cc - b->cc;
	if (diff) {
		return diff;
	}

	diff = a->nbbs - b->nbbs;
	if (diff) {
		return diff;
	}

	diff = a->edges - b->edges;
	if (diff) {
		return diff;
	}

	diff = a->ebbs - b->ebbs;
	if (diff) {
		return diff;
	}
	return 0;
}

// this is to compare a byte signature to a function, that math is slightly
// different
static int sig_bytes_diff(RSignItem *isig, RSignItem *ifunc) {
	RSignBytes *sig = isig->bytes;
	RSignBytes *func = ifunc->bytes;
	if (!sig || !func) {
		return 1;
	}

	if (sig->size != func->size) {
		return 1;
	}
	int i;
	for (i = 0; i < sig->size; i++) {
		char m = sig->mask[i];
		if (m && (sig->bytes[i] & m) != (func->bytes[i] & m)) {
			return 1;
		}
	}
	return 0;
}

static int sig_bytes_cmp(RSignItem *ia, RSignItem *ib) {
	RSignBytes *a = ia->bytes;
	RSignBytes *b = ib->bytes;
	SORT_EMPY_LAST (a, b);
	int i = a->size - b->size;
	if (i) {
		return i;
	}
	for (i = 0; i < a->size; i++) {
		int cmp = a->bytes[i] & a->mask[i];
		cmp -= b->bytes[i] & b->mask[i];
		if (cmp) {
			return cmp;
		}
	}
	return 0;
}

static int sig_addr_cmp(ut64 a, ut64 b) {
	if (a < b) {
		return 1;
	}
	return a > b? -1: 0;
}

static int sig_hash_cmp(RSignHash *a, RSignHash *b) {
	if (!a) {
		return b? -1: 0;
	}
	if (!b) {
		return 1;
	}
	return strcmp (a->bbhash, b->bbhash);
}

static int sig_var_diff(RList *la, RList *lb) {
	if (!la) {
		return lb? -1: 0;
	}
	if (!lb) {
		return 1;
	}
	int len = r_list_length (la);
	int dif = len - r_list_length (lb);
	if (dif) {
		return dif;
	}
	size_t i;
	for (i = 0; i < len; i++) {
		const RAnalVarProt *a = r_list_get_n (la, i);
		const RAnalVarProt *b = r_list_get_n (lb, i);

		if (!a) {
			return -1;
		}
		if (!b) {
			return 1;
		}

		dif = a->delta - b->delta;
		if (dif) {
			return dif;
		}
		if (a->isarg != b->isarg) {
			return a->isarg? 1: -1;
		}
		dif = a->kind - b->kind;
		if (dif) {
			return dif;
		}
	}
	return 0;
}

static int str_list_cmp(RList *la, RList *lb) {
	if (!la) {
		return lb? -1: 0;
	}
	if (!lb) {
		return 1;
	}

	int len = r_list_length (la);
	int dif = len - r_list_length (lb);
	if (dif) {
		return dif;
	}

	size_t i;
	for (i = 0; i < len; i++) {
		const char *a = r_list_get_n (la, i);
		const char *b = r_list_get_n (lb, i);
		if ((dif = strcmp (a, b))) {
			return dif;
		}
	}
	return 0;
}

static inline bool collision_is(RListIter *iter, RSignType type) {
	return iter && *(const char *)iter->data == type;
}

static RListIter *collision_group(RList *collisions, RSignType type) {
	RListIter *iter = r_list_head (collisions);
	while (iter && !collision_is (iter, type)) {
		iter = iter->n;
	}
	return iter;
}

// names colliding under every matched bytes/graph type; NULL means unknown
static RList *check_collisions(RList *collisions, RSignType *types) {
	if (r_list_empty (collisions)) {
		return r_list_new ();
	}
	bool bytes = false;
	bool graph = false;
	for (; *types != R_SIGN_END; types++) {
		bytes |= *types == R_SIGN_BYTES;
		graph |= *types == R_SIGN_GRAPH;
	}
	if (!bytes && !graph) {
		return NULL;
	}
	RListIter *iter;
	char *col;
	r_list_foreach (collisions, iter, col) {
		if (R_STR_ISEMPTY (col) || col[1] != R_SIGN_COL_DELEM) {
			return NULL;
		}
	}
	RList *set = r_list_new ();
	if (!bytes || !graph) {
		RSignType type = bytes? R_SIGN_BYTES: R_SIGN_GRAPH;
		r_list_foreach (collisions, iter, col) {
			if (*col == type) {
				r_list_append (set, col + 2);
			}
		}
		return set;
	}
	r_list_sort (collisions, list_str_cmp);
	RListIter *g = collision_group (collisions, R_SIGN_GRAPH);
	for (iter = collision_group (collisions, R_SIGN_BYTES); collision_is (iter, R_SIGN_BYTES); iter = iter->n) {
		char *name = (char *)iter->data + 2;
		int cmp = -1;
		while (collision_is (g, R_SIGN_GRAPH) && (cmp = strcmp ((char *)g->data + 2, name)) < 0) {
			g = g->n;
		}
		if (!cmp) {
			r_list_append (set, name);
		}
	}
	return set;
}

struct metric_ctx {
	int matched;
	RSignItem *it;
	RSignSearchMetrics *sm;
	RAnalFunction *fcn;
	char *suggest; // holds suggestion for next function match, must be freed
};

static bool match_metrics(RSignItem *it, struct metric_ctx *ctx) {
	RSignSearchMetrics *sm = ctx->sm;
	RSignItem *fit = ctx->it;
	RSignType types[R_SIGN_TYPEMAX];
	int count = 0;

	if (it->bytes && (it->bytes->size >= sm->minsz || ctx->suggest) && !sig_bytes_diff (it, fit)) {
		types[count++] = R_SIGN_BYTES;
	}
	if (it->graph && it->graph->cc >= sm->mincc && !sig_graph_diff (it, fit)) {
		types[count++] = R_SIGN_GRAPH;
	}
	if (fit->addr != UT64_MAX && !sig_addr_cmp (it->addr, fit->addr)) {
		types[count++] = R_SIGN_OFFSET;
	}
	if (fit->hash && it->hash && !sig_hash_cmp (it->hash, fit->hash)) {
		types[count++] = R_SIGN_BBHASH;
	}
	if (fit->refs && it->refs && !str_list_cmp (it->refs, fit->refs)) {
		types[count++] = R_SIGN_REFS;
	}
	if (fit->vars && it->vars && !sig_var_diff (it->vars, fit->vars)) {
		types[count++] = R_SIGN_VARS;
	}
	if (fit->types && it->types && !strcmp (it->types, fit->types)) {
		types[count++] = R_SIGN_TYPES;
	}

	bool keep_searching = true;
	if (count) {
		RList *col = NULL;
		char *coldup = NULL;
		RList *cols = NULL;
		if (ctx->suggest) {
			// Collisions are not possible here, assuming collisions are being
			// used. This is b/c we would not recieve a suggestion unless the
			// previous match lacked a collision.
			types[count++] = R_SIGN_NEXT;
			free (ctx->suggest);
			ctx->suggest = NULL;
			col = r_list_new ();
			types[count] = R_SIGN_END;
		} else {
			types[count] = R_SIGN_END;
			if (it->collisions && (coldup = strdup (it->collisions))) {
				cols = r_str_split_list (coldup, ",", 0);
			}
			// a NULL col counts as colliding, so a failed split cannot look unique
			col = (it->collisions && !cols)? NULL: check_collisions (cols, types);
		}

		ctx->matched += count;
		types[count] = R_SIGN_END;
		sm->cb (it, ctx->fcn, types, sm->user, col);

		// is match unique?
		if (col && r_list_length (col) == 0) {
			keep_searching = false;
			// suggest next signature from this match
			ctx->suggest = it->next;
			it->next = NULL;
		}
		r_list_free (col);
		r_list_free (cols);
		free (coldup);
	} else {
		free (ctx->suggest);
		ctx->suggest = NULL;
	}
	return keep_searching;
}

static bool _sig_to_vec_cb(RSignItem *it, void *user) {
	RSignItem **slot = RVecRSignItemPtr_emplace_back ((RVecRSignItemPtr *)user);
	*slot = it;
	return true;
}

static int sign_name_cmp(const void *a, const void *b) {
	const RSignItem *ia = *(RSignItem *const *)a;
	const RSignItem *ib = *(RSignItem *const *)b;
	return strcmp (ia->name, ib->name);
}

static bool append_collide(RSignItem *it, const char *names, size_t len, size_t skip, size_t skip_end) {
	size_t old = it->collisions? strlen (it->collisions) + 1: 0;
	size_t add = skip + len - skip_end - 1;
	char *s = malloc (old + add + 1);
	if (!s) {
		return false;
	}
	if (old) {
		memcpy (s, it->collisions, old - 1);
		s[old - 1] = ',';
	}
	memcpy (s + old, names, skip);
	memcpy (s + old + skip, names + skip_end, len - skip_end);
	s[old + add] = 0;
	free (it->collisions);
	it->collisions = s;
	return true;
}

static bool update_collide(RVecRSignItemPtr *sigs, int start, int end, int type) {
	RSignItem **group = RVecRSignItemPtr_at (sigs, start);
	const int n = end - start + 1;
	qsort (group, n, sizeof (RSignItem *), sign_name_cmp);
	size_t *beg = R_NEWS (size_t, n);
	RStrBuf *sb = r_strbuf_new (NULL);
	bool ok = beg && sb;
	int i;
	// a name shared with another zignspace is listed once, and kept by both
	for (i = 0; ok && i < n; i++) {
		if (i && !strcmp (group[i]->name, group[i - 1]->name)) {
			beg[i] = beg[i - 1];
		} else {
			beg[i] = r_strbuf_length (sb);
			ok = r_strbuf_appendf (sb, "%c:%s,", type, group[i]->name);
		}
	}
	const char *names = ok? r_strbuf_get (sb): NULL;
	const size_t len = ok? r_strbuf_length (sb): 0;
	for (i = 0; ok && i < n; i++) {
		const bool shared = (i && beg[i - 1] == beg[i]) || (i + 1 < n && beg[i + 1] == beg[i]);
		const size_t skip = shared? len: beg[i];
		const size_t skip_end = shared? len: beg[i] + strlen (group[i]->name) + 3;
		ok = append_collide (group[i], names, len, skip, skip_end);
	}
	free (beg);
	r_strbuf_free (sb);
	return ok;
}

static bool item_has_type(RSignItem *it, RSignType t) {
	switch (t) {
	case R_SIGN_BYTES:
		return it->bytes != NULL;
	case R_SIGN_COMMENT:
		return it->comment != NULL;
	case R_SIGN_GRAPH:
		return it->graph != NULL;
	case R_SIGN_OFFSET:
		return it->addr != UT64_MAX;
	case R_SIGN_NAME:
		return it->realname != NULL;
	case R_SIGN_REFS:
		return it->refs != NULL;
	case R_SIGN_XREFS:
		return it->xrefs != NULL;
	case R_SIGN_VARS:
		return it->vars != NULL;
	case R_SIGN_TYPES:
		return it->types != NULL;
	case R_SIGN_COLLISIONS:
		return it->collisions != NULL;
	case R_SIGN_BBHASH:
		return it->hash && it->hash->bbhash;
	default:
		return false;
	}
}

typedef int (*RSignVecCmp) (RSignItem *const *, RSignItem *const *);

static int sign_graph_cmp_wrap(RSignItem *const *a, RSignItem *const *b) {
	return sig_graph_cmp (*a, *b);
}

static int sign_graph_diff_wrap(RSignItem *const *a, RSignItem *const *b) {
	return sig_graph_diff (*a, *b);
}

static int sign_bytes_cmp_wrap(RSignItem *const *a, RSignItem *const *b) {
	return sig_bytes_cmp (*a, *b);
}

static RSignVecCmp type_to_cmp(int type, bool exact) {
	switch (type) {
	case R_SIGN_GRAPH:
		if (exact) {
			return sign_graph_diff_wrap;
		}
		return sign_graph_cmp_wrap;
	case R_SIGN_BYTES:
		return sign_bytes_cmp_wrap;
	default:
		return NULL;
	}
}

static inline bool sign_collide_by(RVecRSignItemPtr *sigs, RSignType type) {
	RSignVecCmp cmp = type_to_cmp (type, false);
	if (!cmp) {
		return false;
	}
	RVecRSignItemPtr_sort (sigs, cmp);
	// sorting and matching can be slightly different
	cmp = type_to_cmp (type, true);

	int i, start, end;
	RSignItem *old = NULL;
	i = 0;
	start = end = -1;
	RSignItem **p;
	R_VEC_FOREACH (sigs, p) {
		RSignItem *it = *p;
		if (!item_has_type (it, type)) {
			// sort algs should put NULL at bottom
			break;
		}
		if (old) {
			if (!cmp (&old, &it)) {
				// signature collisions
				if (start < 0) {
					start = i - 1;
					end = i;
				} else {
					end++;
				}
			} else if (start >= 0) {
				// not a collision, but had a collision before
				if (!update_collide (sigs, start, end, type)) {
					return false;
				}
				start = end = -1;
			}
		}
		old = it;
		i++;
	}
	if (start >= 0 && !update_collide (sigs, start, end, type)) {
		return false;
	}
	return true;
}

static inline RSignItem *metric_build_item(RSignSearchMetrics *sm, RAnalFunction *fcn) {
	RSignItem *it = r_sign_item_new ();
	if (it) {
		RSignType *t = sm->stypes;
		while (*t != R_SIGN_END) {
			if (*t == R_SIGN_BYTES) {
				// no need for mask
				it->bytes = r_sign_func_empty_mask (sm->anal, fcn);
			} else {
				r_sign_addto_item (sm->anal, it, fcn, *t);
			}
			t++;
		}

		if (it->graph && it->graph->cc < sm->mincc) {
			r_sign_graph_free (it->graph);
			it->graph = NULL;
		}
	}
	return it;
}

R_API bool r_sign_resolve_collisions(RAnal *a) {
	R_RETURN_VAL_IF_FAIL (a, false);
	RVecRSignItemPtr *sigs = RVecRSignItemPtr_new ();
	if (!sigs) {
		return false;
	}

	if (!r_sign_foreach_nofree (a, _sig_to_vec_cb, (void *)sigs)) {
		RVecRSignItemPtr_free (sigs);
		return false;
	}
	// sign_collide_by sorts sigs, so keep each item next to its old list
	const size_t n = RVecRSignItemPtr_length (sigs);
	RSignItem **items = R_NEWS (RSignItem *, n);
	char **old = R_NEWS0 (char *, n);
	bool success = !n || (items && old);
	size_t i;
	for (i = 0; success && i < n; i++) {
		items[i] = *RVecRSignItemPtr_at (sigs, i);
		old[i] = items[i]->collisions;
		items[i]->collisions = NULL;
	}
	RSignType types[] = { R_SIGN_BYTES, R_SIGN_GRAPH, R_SIGN_END };
	int t;
	for (t = 0; success && types[t] != R_SIGN_END; t++) {
		success = sign_collide_by (sigs, types[t]);
	}
	// an item that stopped colliding is rewritten too, without its old list
	for (i = 0; success && i < n; i++) {
		const char *cur = items[i]->collisions;
		if (old[i]? !cur || strcmp (old[i], cur): cur != NULL) {
			success = r_sign_set_item (a->sdb_zigns, items[i], NULL);
		}
	}
	for (i = 0; old && i < n; i++) {
		free (old[i]);
	}
	RSignItem **p;
	R_VEC_FOREACH (sigs, p) {
		r_sign_item_free (*p);
	}
	free (items);
	free (old);
	RVecRSignItemPtr_free (sigs);
	return success;
}

// returns true if you should keep searching
static inline bool suggest_check(RAnal *a, struct metric_ctx *ctx) {
	int ret = true;
	if (ctx && ctx->suggest) {
		RSignItem *it = r_sign_get_item (a, ctx->suggest);
		if (it) {
			ret = match_metrics (it, ctx);
			r_sign_item_free (it);
		}
	}
	return ret;
}

R_API int r_sign_metric_search(RAnal *a, RSignSearchMetrics *sm) {
	R_RETURN_VAL_IF_FAIL (a && sm, -1);
	RListIter *iter;
	RCore *core = a->coreb.core;
	RCons *cons = core->cons;
	r_list_sort (a->fcns, fcn_sort);
	r_cons_break_push (cons, NULL, NULL);
	struct metric_ctx ctx = { 0, NULL, sm, NULL, NULL };
	r_list_foreach (a->fcns, iter, ctx.fcn) {
		if (r_cons_is_breaked (cons)) {
			break;
		}
		ctx.it = metric_build_item (sm, ctx.fcn);
		if (ctx.it && suggest_check (sm->anal, &ctx)) {
			r_sign_foreach (sm->anal, (RSignForeachCallback)match_metrics, (void *)&ctx);
		}
		r_sign_item_free (ctx.it);
	}
	r_cons_break_pop (core->cons);
	free (ctx.suggest);
	return ctx.matched;
}

R_API int r_sign_fcn_match_metrics(RSignSearchMetrics *sm, RAnalFunction *fcn) {
	R_RETURN_VAL_IF_FAIL (sm && sm->mincc >= 0 && sm->anal && fcn, -1);
	struct metric_ctx ctx = { 0, metric_build_item (sm, fcn), sm, fcn, NULL };
	if (ctx.it) {
		r_sign_foreach (sm->anal, (RSignForeachCallback)match_metrics, (void *)&ctx);
		r_sign_item_free (ctx.it);
		free (ctx.suggest);
	}
	return ctx.matched;
}

R_API RSignItem *r_sign_item_new(void) {
	RSignItem *ret = R_NEW0 (RSignItem);
	if (ret) {
		ret->addr = UT64_MAX;
	}
	return ret;
}

R_API void r_sign_item_free(RSignItem *item) {
	if (item) {
		free (item->name);
		free (item->rawname);
		free (item->demangled);
		free (item->next);
		r_sign_bytes_free (item->bytes);
		r_sign_hash_free (item->hash);
		r_sign_graph_free (item->graph);
		free (item->comment);
		free (item->realname);
		free (item->types);
		r_list_free (item->refs);
		r_list_free (item->vars);
		r_list_free (item->xrefs);
		free (item->collisions);
		free (item);
	}
}

R_API void r_sign_graph_free(RSignGraph *graph) {
	free (graph);
}

R_API void r_sign_bytes_free(RSignBytes *bytes) {
	if (bytes) {
		free (bytes->bytes);
		free (bytes->mask);
		free (bytes);
	}
}

R_API void r_sign_hash_free(RSignHash *hash) {
	if (hash) {
		free (hash->bbhash);
		free (hash);
	}
}

struct load_sign_data {
	RAnal *anal;
	bool merge;
};

static bool loadCB(void *user, const char *k, const char *v) {
	struct load_sign_data *u = (struct load_sign_data *)user;
	RAnal *a = u->anal;
	RSignItem *it = r_sign_item_new ();
	if (it && r_sign_deserialize (a, it, k, v)) {
		if (u->merge || !name_exists (a->sdb_zigns, it->name, it->space)) {
			sdb_set (a->sdb_zigns, k, v, 0);
		} else {
			char *name = get_unique_name (a->sdb_zigns, it->name, it->space);
			if (name) {
				if (!it->realname) {
					it->realname = it->name;
				} else {
					free (it->name);
				}
				it->name = name;
				r_sign_set_item (a->sdb_zigns, it, NULL);
			}
		}
	} else {
		R_LOG_ERROR ("cannot deserialize zign");
	}
	r_sign_item_free (it);
	return true;
}

R_API char *r_sign_path(RAnal *a, const char *file) {
	char *abs = r_file_abspath (file);
	if (abs) {
		if (r_file_is_regular (abs)) {
			return abs;
		}
		free (abs);
	}

	if (a->zign_path) {
		char *path = r_str_newf ("%s%s%s", a->zign_path, R_SYS_DIR, file);
		abs = r_file_abspath (path);
		free (path);
		if (r_file_is_regular (abs)) {
			return abs;
		}
		free (abs);
	} else {
		char *home = r_xdg_datadir ("zigns");
		abs = r_file_new (home, file, NULL);
		free (home);
		if (r_file_is_regular (abs)) {
			return abs;
		}
		free (abs);
	}

	// abs = r_str_newf (R_JOIN_3_PATHS ("%s", R2_ZIGNS, "%s"), r_sys_prefix (NULL), file);
	char *pfx = r_sys_prefix (NULL);
	abs = r_file_new (pfx, R2_ZIGNS, file, NULL);
	free (pfx);
	if (r_file_is_regular (abs)) {
		return abs;
	}
	free (abs);

	return NULL;
}

enum {
	SIGNDB_TYPE_SDB,
	SIGNDB_TYPE_KV,
	SIGNDB_TYPE_JSON,
	SIGNDB_TYPE_R2,
	SIGNDB_TYPE_INVALID = -1,
};

static int signdb_type(const char *file) {
	if (r_str_endswith (file, ".sdb")) {
		return SIGNDB_TYPE_SDB;
	}
	if (r_str_endswith (file, ".sdb.txt")) {
		return SIGNDB_TYPE_KV;
	}
	if (r_str_endswith (file, ".json")) {
		return SIGNDB_TYPE_JSON;
	}
	if (r_str_endswith (file, ".r2")) {
		return SIGNDB_TYPE_R2;
	}
	int i, sz = 0;
	char *data = r_file_slurp_range (file, 0, 0x200, &sz);
	if (!data) {
		return SIGNDB_TYPE_INVALID;
	}
	if (sz < 1) {
		free (data);
		return SIGNDB_TYPE_INVALID;
	}
	data[sz - 1] = 0;
	sz = R_MIN (sz, 0x200);
	int is_sdb = 16;
	int is_kv = 4;
	int is_r2 = 2;
	int t = SIGNDB_TYPE_INVALID;
	if (r_str_startswith (data, "[{\"name\":")) {
		t = SIGNDB_TYPE_JSON;
	} else {
		for (i = 0; i < sz; i++) {
			if (!strncmp (data + i, "\nza ", 4)) {
				is_r2--;
				i += 3;
				continue;
			}
			if ((i & 3) == 3 && data[i] == 0) {
				is_sdb--;
				continue;
			}
			if (data[i] == '=' || data[i] == '\n') {
				is_kv--;
				continue;
			}
		}
		if (is_sdb < 0) {
			t = SIGNDB_TYPE_SDB;
		} else if (is_r2 < 0) {
			t = SIGNDB_TYPE_R2;
		} else if (is_kv < 0) {
			t = SIGNDB_TYPE_KV;
		}
	}
#if defined(__GNUC__)
	// XXX looks like a false positive bug in gcc 9.4 (debian CI)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
	free (data);
#pragma GCC diagnostic pop
#else
	free (data);
#endif
	return t;
}

static bool sign_load_r2(RAnal *a, const char *path) {
	char *cmd = r_str_newf ("'. %s", path);
	a->coreb.cmd (a->coreb.core, cmd);
	free (cmd);
	return true;
}

static RList *json_strlist(const RJson *array) {
	if (!array || array->type != R_JSON_ARRAY) {
		return NULL;
	}
	RList *list = r_list_newf (free);
	const RJson *p;
	for (p = array->children.first; p; p = p->next) {
		if (p->type == R_JSON_STRING) {
			r_list_append (list, strdup (p->str_value));
		}
	}
	return list;
}

static bool load_json_signature(RAnal *a, const RJson *child) {
	const char *n = r_json_type (child);
	if (strcmp (n, "object")) {
		return false;
	}
	RSignItem *it = r_sign_item_new ();

	const RJson *name = r_json_get (child, "name");
	if (name && name->type == R_JSON_STRING) {
		it->name = strdup (name->str_value);
	}
	const RJson *rname = r_json_get (child, "rawname");
	if (!rname) {
		rname = r_json_get (child, "realname");
	}
	if (rname && rname->type == R_JSON_STRING) {
		it->realname = strdup (rname->str_value);
	}
	const RJson *jm = r_json_get (child, "rawname");
	if (!jm) {
		jm = r_json_get (child, "mangled");
	}
	if (jm && jm->type == R_JSON_STRING) {
		it->rawname = strdup (jm->str_value);
	}
	const RJson *jd = r_json_get (child, "demangled");
	if (jd && jd->type == R_JSON_STRING) {
		it->demangled = strdup (jd->str_value);
	}
	const char *bytes = r_json_get_str (child, "bytes");
	const char *mask = r_json_get_str (child, "mask");
	// the mask is applied to every byte, it cannot be shorter
	if (bytes && mask && strlen (bytes) == strlen (mask)) {
		it->bytes = R_NEW0 (RSignBytes);
		it->bytes->bytes = (ut8*)strdup (bytes);
		it->bytes->size = r_hex_str2bin (bytes, it->bytes->bytes);
		it->bytes->mask = (ut8*)strdup (mask);
		(void)r_hex_str2bin (mask, it->bytes->mask);
	}

	const RJson *types = r_json_get (child, "types");
	if (types && types->type == R_JSON_STRING) {
		it->types = strdup (types->str_value);
	}

	const RJson *next = r_json_get (child, "next");
	if (next && next->type == R_JSON_STRING) {
		it->next = strdup (next->str_value);
	}
	if (r_json_get (child, "addr")) {
		it->addr = r_json_get_num (child, "addr");
	}
	const RJson *graph = r_json_get (child, "graph");
	if (graph && graph->type == R_JSON_OBJECT) {
		it->graph = R_NEW0 (RSignGraph);
		it->graph->cc = r_json_get_num (graph, "cc");
		it->graph->nbbs = r_json_get_num (graph, "nbbs");
		it->graph->edges = r_json_get_num (graph, "edges");
		it->graph->ebbs = r_json_get_num (graph, "ebbs");
		it->graph->bbsum = r_json_get_num (graph, "bbsum");
	}
	it->refs = json_strlist (r_json_get (child, "refs"));
	it->xrefs = json_strlist (r_json_get (child, "xrefs"));
	const RJson *hash = r_json_get (child, "hash");
	const char *bbhash = (hash && hash->type == R_JSON_OBJECT)? r_json_get_str (hash, "bbhash"): NULL;
	if (bbhash) {
		it->hash = R_NEW0 (RSignHash);
		it->hash->bbhash = strdup (bbhash);
	}

	r_sign_set_item (a->sdb_zigns, it, NULL);
	r_sign_item_free (it);
	return true;
}

static bool sign_load_json(RAnal *a, const char *path) {
	size_t sz;
	char *text = r_file_slurp (path, &sz);
	if (!text) {
		return false;
	}
	bool res = false;
	RJson *rj = r_json_parse (text);
	if (!rj || rj->type != R_JSON_ARRAY) {
		R_LOG_ERROR ("Invalid json");
	} else {
		res = true;
		// walk the array
		int i = 0;
		for (i = 0;; i++) {
			const RJson *child = r_json_item (rj, i);
			if (!child) {
				break;
			}
			if (!load_json_signature (a, child)) {
				R_LOG_WARN ("invalid json");
				res = false;
				break;
			}
		}
	}
	r_json_free (rj);
	free (text);
	return res;
}

static bool sign_load_sdb(RAnal *a, const char *path, bool merge) {
	Sdb *db = sdb_new (NULL, path, 0);
	if (db) {
		struct load_sign_data u = {
			.anal = a,
			.merge = merge
		};
		sdb_foreach (db, loadCB, &u);
		sdb_close (db);
		sdb_free (db);
		return true;
	}
	return false;
}

R_API bool r_sign_load(RAnal *a, const char *file, bool merge) {
	R_RETURN_VAL_IF_FAIL (a && file, false);
	char *path = r_sign_path (a, file);
	if (!path) {
		R_LOG_ERROR ("file %s not found in sign path", file);
		return false;
	}
	if (!r_file_exists (path)) {
		R_LOG_ERROR ("file %s does not exist", file);
		free (path);
		return false;
	}
	bool res = false;
	const int type = signdb_type (path);
	switch (type) {
	case SIGNDB_TYPE_R2:
		res = sign_load_r2 (a, path);
		break;
	case SIGNDB_TYPE_JSON:
		res = sign_load_json (a, path);
		break;
	case SIGNDB_TYPE_KV:
	case SIGNDB_TYPE_SDB:
		res = sign_load_sdb (a, path, merge);
		break;
	default:
		R_LOG_ERROR ("Unsupported signature file format");
		break;
	}
	free (path);
	return res;
}

R_API bool r_sign_load_gz(RAnal *a, const char *filename, bool merge) {
	R_RETURN_VAL_IF_FAIL (a && filename, false);
	ut8 *buf = NULL;
	int size = 0;
	char *tmpfile = NULL;
	bool retval = true;
	char *path = r_sign_path (a, filename);
	if (!path) {
		R_LOG_ERROR ("file %s not found in sign path", filename);
		return false;
	}
	if (!r_file_exists (path)) {
		R_LOG_ERROR ("file %s does not exist", filename);
		retval = false;
		goto out;
	}
	if (!(buf = r_file_gzslurp (path, &size, 0))) {
		R_LOG_ERROR ("cannot decompress file");
		retval = false;
		goto out;
	}
	if (!(tmpfile = r_file_temp ("r2zign"))) {
		R_LOG_ERROR ("cannot create temp file");
		retval = false;
		goto out;
	}
	if (!r_file_dump (tmpfile, buf, size, 0)) {
		R_LOG_ERROR ("cannot dump file");
		retval = false;
		goto out;
	}
	if (!r_sign_load (a, tmpfile, merge)) {
		R_LOG_ERROR ("cannot load file");
		retval = false;
		goto out;
	}
	if (!r_file_rm (tmpfile)) {
		R_LOG_ERROR ("cannot delete temp file");
		retval = false;
		goto out;
	}
out:
	free (buf);
	free (tmpfile);
	free (path);

	return retval;
}

R_API bool r_sign_save(RAnal *a, const char *file) {
	R_RETURN_VAL_IF_FAIL (a && file, false);
	// sdb creates the file and its directories by itself
	if (r_sandbox_enable (false) && !r_sandbox_check_writepath (file)) {
		R_LOG_ERROR ("Cannot save zignatures to %s in sandbox mode", file);
		return false;
	}

	if (sdb_isempty (a->sdb_zigns)) {
		R_LOG_WARN ("no zignatures to save");
		return false;
	}

	Sdb *db = sdb_new (NULL, file, 0);
	if (!db) {
		return false;
	}
	sdb_merge (db, a->sdb_zigns);
	bool retval = sdb_sync (db);
	sdb_close (db);
	sdb_free (db);

	return retval;
}

R_API RSignOptions *r_sign_options_new(const char *bytes_thresh, const char *graph_thresh) {
	R_RETURN_VAL_IF_FAIL (bytes_thresh && graph_thresh, NULL);
	RSignOptions *options = R_NEW0 (RSignOptions);
	if (!options) {
		return NULL;
	}

	options->bytes_diff_threshold = r_num_get_double (NULL, bytes_thresh);
	options->graph_diff_threshold = r_num_get_double (NULL, graph_thresh);

	if (options->bytes_diff_threshold > 1.0) {
		options->bytes_diff_threshold = 1.0;
	}
	if (options->bytes_diff_threshold < 0) {
		options->bytes_diff_threshold = 0.0;
	}
	if (options->graph_diff_threshold > 1.0) {
		options->graph_diff_threshold = 1.0;
	}
	if (options->graph_diff_threshold < 0) {
		options->graph_diff_threshold = 0.0;
	}

	return options;
}

R_API void r_sign_options_free(RSignOptions *options) {
	free (options);
}
