utils: Greatly improve performance of osmo_hexdump routines

In the osmo-bts and libosmo-abis code the hexdump routine is used
for every incoming/outgoing packet (including voice frames) and the
usage of snprintf showed up inside profiles.

There is a semantic change when more than 4096 characters are used.
The code will now truncate at byte boundaries (and not nibbles).

Code:
 static const int lengths[] = { 23, 1000, 52 };
 char buf[4096];
 int i;

 for (i = 0; i < 30000; ++i)
     char *res = osmo_hexdump(buf, lengths[i & 3]);

Results:

before:					after:
real    0m3.233s			real    0m0.085s
user    0m3.212s			user    0m0.084s
sys     0m0.000s			sys     0m0.000s
diff --git a/src/utils.c b/src/utils.c
index 5874077..cc33994 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -107,6 +107,7 @@
 }
 
 static char hexd_buff[4096];
+static const char hex_chars[] = "0123456789abcdef";
 
 static char *_osmo_hexdump(const unsigned char *buf, int len, char *delim)
 {
@@ -115,13 +116,20 @@
 
 	hexd_buff[0] = 0;
 	for (i = 0; i < len; i++) {
+		const char *delimp = delim;
 		int len_remain = sizeof(hexd_buff) - (cur - hexd_buff);
-		if (len_remain <= 0)
+		if (len_remain < 3)
 			break;
-		int rc = snprintf(cur, len_remain, "%02x%s", buf[i], delim);
-		if (rc <= 0)
-			break;
-		cur += rc;
+
+		*cur++ = hex_chars[buf[i] >> 4];
+		*cur++ = hex_chars[buf[i] & 0xf];
+
+		while (len_remain > 1 && *delimp) {
+			*cur++ = *delimp++;
+			len_remain--;
+		}
+
+		*cur = 0;
 	}
 	hexd_buff[sizeof(hexd_buff)-1] = 0;
 	return hexd_buff;