add osmo_mobile_identity API

Implement better API around 3GPP TS 24.008 Mobile Identity coding.

struct osmo_mobile_identity is a decoded representation of the raw Mobile
Identity, with a string representation as well as dedicated raw uint32_t TMSI.
The aim is to remove all uncertainty about decoded buffer sizes / data types.

I have patches ready for current osmo CNI programs, replacing the Mobile
Identity coding with this new API. Deprecate the old MI API.
osmo-bsc: I71c3b4c65dbfdfa51409e09d4868aea83225338a
osmo-msc: Ic3f969e739654c1e8c387aedeeba5cce07fe2307
osmo-sgsn: I4cacb10bac419633ca0c14f244f9903f7f517b49
Note that some GPRS and SGs related coding is done here in libosmocore and
hence currently remains using the old implementation (see previous version of
this patch: Ic3f969e739654c1e8c387aedeeba5cce07fe2307).

New API functions provide properly size-checking implementations of:
- decoding a raw MI from a bunch of MI octets;
- locating and decoding MI from a full 3GPP TS 24.008 Complete Layer 3 msgb;
- encoding to a buffer;
- encoding to the end of a msgb.

Other than the old gsm48_generate_mid(), omit a TLV tag and length from
encoding. Many callers manually stripped the tag and value after calling
gsm48_generate_mid(). The aim is to leave writing a TL to the caller entirely,
especially since some callers need to use a TvL, i.e. support a variable-size
length of 8 or 16 bit.

New validity checks so far not implemented anywhere else:
- stricter validation of number of digits of IMSI, IMEI, IMEI-SV MI.
- stricter on filler nibbles to be 0xf.
As a result, applications using osmo_mobile_identity will be stricter in
rejecting coding mistakes (some of which we currently have in our test suites,
and which we'll need to fix).

Rationale:

While implementing osmo-bsc's MSC pooling feature in osmo-bsc, this API will be
used to reduce the number of times a Mobile Identity is extracted from a raw
RSL message.

Extracting the Mobile Identity from messages has numerous duplicate
implementations across our code with various levels of specialization.
https://xkcd.com/927/

To name a few:
- libosmocore: gsm48_mi_to_string(), osmo_mi_name_buf()
- osmo-bsc: extract_sub()
- osmo-msc: mm_rx_loc_upd_req(), cm_serv_reuse_conn(), gsm48_rx_mm_serv_req(),
  vlr_proc_acc_req()

We have existing functions to produce a human readable string from a Mobile
Identity, more or less awkward:
- gsm48_mi_to_string() decodes a TMSI as a decimal number. These days we use
  hexadecimal TMSI everywhere.
- osmo_mi_name_buf() decodes the BCD digits from a raw MI every time, so we'd
  need to pass around the raw message bytes. Also, osmo_mi_name_buf() has the
  wrong signature, it should return a length like snprintf().
- osmo-bsc's extract_sub() first uses gsm48_mi_to_string() which encodes the
  raw uint32_t TMSI to a string, and then calls strtoul() via
  tmsi_from_string() to code those back to a raw uint32_t.

Each of the above implementations employ their own size overflow checks, each
invoke osmo_bcd2str() and implement their own TMSI osmo_load32be() handling.
Too much code dup, let's hope that each and every one is correct.

In osmo-bsc, I am now implementing MSC pooling, and need to extract NRI bits
from a TMSI Mobile Identity. Since none of the above functions are general
enough to be re-used, I found myself again copy-pasting Mobile Identity code:
locating the MI in a 24.008 message with proper size checks, decoding MI
octets.

This time I would like it to become a generally re-usable API.

This patch was first merged as Ic3f969e739654c1e8c387aedeeba5cce07fe2307 and
caused test fallout, because it re-implemented old API with the new stricter
decoding. In this patch version, old API remains 1:1 unchanged to avoid such
fallout. Applications will soon switch to the new osmo_mobile_identity API and
become stricter on MI coding when that happens, not implicitly by a new
libosmocore version.

Change-Id: If4f7be606e54cfa1c59084cf169785b1cbda5cf5
diff --git a/src/utils.c b/src/utils.c
index 18e105f..3c4a8c9 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -175,6 +175,65 @@
 	return OSMO_MAX(0, end_nibble - start_nibble);
 }
 
+/*! Convert string to BCD.
+ * The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0x0f, nibble 1 is bcd[0] & 0xf0, nibble
+ * 3 is bcd[1] & 0x0f, etc..
+ *  \param[out] dst  Output BCD buffer.
+ *  \param[in] dst_size  sizeof() the output string buffer.
+ *  \param[in] digits  String containing decimal or hexadecimal digits in upper or lower case.
+ *  \param[in] start_nibble  Offset to start from, in nibbles, typically 1 to skip the first (MI type) nibble.
+ *  \param[in] end_nibble  Negative to write all digits found in str, followed by 0xf nibbles to fill any started octet.
+ *                         If >= 0, stop before this offset in nibbles, e.g. to get default behavior, pass
+ *                         start_nibble + strlen(str) + ((start_nibble + strlen(str)) & 1? 1 : 0) + 1.
+ *  \param[in] allow_hex  If false, return error if there are hexadecimal digits (A-F). If true, write those to
+ *                        BCD.
+ *  \returns The buffer size in octets that is used to place all bcd digits (including the skipped nibbles
+ *           from 'start_nibble' and rounded up to full octets); -EINVAL on invalid digits;
+ *           -ENOMEM if dst is NULL, if dst_size is too small to contain all nibbles, or if start_nibble is negative.
+ */
+int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex)
+{
+	const char *digit = digits;
+	int nibble_i;
+
+	if (!dst || !dst_size || start_nibble < 0)
+		return -ENOMEM;
+
+	if (end_nibble < 0) {
+		end_nibble = start_nibble + strlen(digits);
+		/* If the last octet is not complete, add another filler nibble */
+		if (end_nibble & 1)
+			end_nibble++;
+	}
+	if ((end_nibble / 2) > dst_size)
+		return -ENOMEM;
+
+	for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) {
+		uint8_t nibble = 0xf;
+		int octet = nibble_i >> 1;
+		if (*digit) {
+			char c = *digit;
+			digit++;
+			if (c >= '0' && c <= '9')
+				nibble = c - '0';
+			else if (allow_hex && c >= 'A' && c <= 'F')
+				nibble = 0xa + (c - 'A');
+			else if (allow_hex && c >= 'a' && c <= 'f')
+				nibble = 0xa + (c - 'a');
+			else
+				return -EINVAL;
+		}
+		nibble &= 0xf;
+		if ((nibble_i & 1))
+			dst[octet] = (nibble << 4) | (dst[octet] & 0x0f);
+		else
+			dst[octet] = (dst[octet] & 0xf0) | nibble;
+	}
+
+	/* floor(float(end_nibble) / 2) */
+	return end_nibble / 2;
+}
+
 /*! Parse a string containing hexadecimal digits
  *  \param[in] str string containing ASCII encoded hexadecimal digits
  *  \param[out] b output buffer