utils: add osmo_is_hexstr(), add unit test

Will be used by OsmoHLR to validate VTY and CTRL input.

Change-Id: Idf75946eb0a84e145adad13fc7c78bb7a267aa0a
diff --git a/src/utils.c b/src/utils.c
index 1c176f8..b4345c7 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -375,4 +375,37 @@
 	return ret;
 }
 
+/*! Validate that a given string is a hex string within given size limits.
+ * Note that each hex digit amounts to a nibble, so if checking for a hex
+ * string to result in N bytes, pass amount of digits as 2*N.
+ * \param str  A nul-terminated string to validate, or NULL.
+ * \param min_digits  least permitted amount of digits.
+ * \param max_digits  most permitted amount of digits.
+ * \param require_even  if true, require an even amount of digits.
+ * \returns true when the hex_str contains only hexadecimal digits (no
+ *          whitespace) and matches the requested length; also true
+ *          when min_digits <= 0 and str is NULL.
+ */
+bool osmo_is_hexstr(const char *str, int min_digits, int max_digits,
+		    bool require_even)
+{
+	int len;
+	/* Use unsigned char * to avoid a compiler warning of
+	 * "error: array subscript has type 'char' [-Werror=char-subscripts]" */
+	const unsigned char *pos = (const unsigned char*)str;
+	if (!pos)
+		return min_digits < 1;
+	for (len = 0; *pos && len < max_digits; len++, pos++)
+		if (!isxdigit(*pos))
+			return false;
+	if (len < min_digits)
+		return false;
+	/* With not too many digits, we should have reached *str == nul */
+	if (*pos)
+		return false;
+	if (require_even && (len & 1))
+		return false;
+	return true;
+}
+
 /*! @} */