gsm0808: add BSSMAP Cell Identifier matching API

Add
* osmo_lai_cmp() (to use in gsm0808_cell_id_u_matches())
* osmo_cgi_cmp() (to use in gsm0808_cell_id_u_matches())
* gsm0808_cell_id_u_match() (to re-use for single IDs and lists)
* gsm0808_cell_ids_match()
* gsm0808_cell_id_matches_list()
* Unit tests in gsm0808_test.c

Rationale:

For inter-BSC handover, it is interesting to find matches between *differing*
Cell Identity kinds. For example, if a cell as CGI 23-42-3-5, and a HO for
LAC-CI 3-5 should be handled, we need to see the match.

This is most interesting for osmo-msc, i.e. to direct the BSSMAP Handover
Request towards the correct BSC or MSC.

It is also interesting for osmo-bsc's VTY interface, to be able to manage
cells' neighbors and to trigger manual handovers by various Cell Identity
handles, as the user would expect them.

Change-Id: I5535f0d149c2173294538df75764dd181b023312
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 9fcccae..54ec19c 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -1391,6 +1391,151 @@
 	}
 }
 
+/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
+ * This is useful to supplement one CGI with information from more than one Cell Identifier,
+ * which in turn is useful to match Cell Identifiers of differing kinds to each other.
+ * Before first invocation, clear the *dst struct externally, this function does only write those members
+ * that are present in parameter u.
+ */
+static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
+			   enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
+{
+	switch (discr) {
+	case CELL_IDENT_WHOLE_GLOBAL:
+		*dst = u->global;
+		return;
+
+	case CELL_IDENT_LAC_AND_CI:
+		dst->lai.lac = u->lac_and_ci.lac;
+		dst->cell_identity = u->lac_and_ci.ci;
+		return;
+
+	case CELL_IDENT_CI:
+		dst->cell_identity = u->ci;
+		return;
+
+	case CELL_IDENT_LAI_AND_LAC:
+		dst->lai = u->lai_and_lac;
+		return;
+
+	case CELL_IDENT_LAC:
+		dst->lai.lac = u->lac;
+		return;
+
+	case CELL_IDENT_NO_CELL:
+	case CELL_IDENT_BSS:
+	case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+	case CELL_IDENT_UTRAN_RNC:
+	case CELL_IDENT_UTRAN_LAC_RNC:
+		/* No values to set. */
+		return;
+	}
+}
+
+/*! Return true if the common information between the two Cell Identifiers match.
+ * For example, if a LAC+CI is compared to LAC, return true if the LAC are the same.
+ * Note that CELL_IDENT_NO_CELL will always return false.
+ * Also CELL_IDENT_BSS will always return false, since this function cannot possibly
+ * know the bounds of the BSS, so the caller must handle CELL_IDENT_BSS specially.
+ * \param[in] discr1  Cell Identifier type.
+ * \param[in] u1  Cell Identifier value.
+ * \param[in] discr2  Other Cell Identifier type.
+ * \param[in] u2  Other Cell Identifier value.
+ * \param[in] exact_match  If true, return true only if the CELL_IDENT types and all values are identical.
+ * \returns True if the common fields of the above match.
+ */
+static bool gsm0808_cell_id_u_match(enum CELL_IDENT discr1, const union gsm0808_cell_id_u *u1,
+				    enum CELL_IDENT discr2, const union gsm0808_cell_id_u *u2,
+				    bool exact_match)
+{
+	struct osmo_cell_global_id a = {};
+	struct osmo_cell_global_id b = {};
+
+	if (exact_match && discr1 != discr2)
+		return false;
+
+	/* First handle the odd wildcard like CELL_IDENT kinds. We can't really match any of these. */
+	switch (discr1) {
+	case CELL_IDENT_NO_CELL:
+	case CELL_IDENT_BSS:
+		return discr1 == discr2;
+	case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+	case CELL_IDENT_UTRAN_RNC:
+	case CELL_IDENT_UTRAN_LAC_RNC:
+		return false;
+	default:
+		break;
+	}
+	switch (discr2) {
+	case CELL_IDENT_NO_CELL:
+	case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+	case CELL_IDENT_UTRAN_RNC:
+	case CELL_IDENT_UTRAN_LAC_RNC:
+	case CELL_IDENT_BSS:
+		return false;
+	default:
+		break;
+	}
+
+	/* Enrich both sides to full CGI, then compare those. First set the *other* ID's values in case
+	 * they assign more items. For example:
+	 * u1 = LAC:42
+	 * u2 = LAC+CI:23+5
+	 * 1) a <- LAC+CI:23+5
+	 * 2) a <- LAC:42 so that a = LAC+CI:42+5
+	 * Now we can compare those two and find a mismatch. If the LAC were the same, we would get
+	 * identical LAC+CI and hence a match. */
+
+	cell_id_to_cgi(&a, discr2, u2);
+	cell_id_to_cgi(&a, discr1, u1);
+
+	cell_id_to_cgi(&b, discr1, u1);
+	cell_id_to_cgi(&b, discr2, u2);
+
+	return osmo_cgi_cmp(&a, &b) == 0;
+}
+
+/*! Return true if the common information between the two Cell Identifiers match.
+ * For example, if a LAC+CI is compared to LAC, return true if the LAC are the same.
+ * Note that CELL_IDENT_NO_CELL will always return false.
+ * Also CELL_IDENT_BSS will always return false, since this function cannot possibly
+ * know the bounds of the BSS, so the caller must handle CELL_IDENT_BSS specially.
+ * \param[in] id1  Cell Identifier.
+ * \param[in] id2  Other Cell Identifier.
+ * \param[in] exact_match  If true, return true only if the CELL_IDENT types and all values are identical.
+ * \returns True if the common fields of the above match.
+ */
+bool gsm0808_cell_ids_match(const struct gsm0808_cell_id *id1, const struct gsm0808_cell_id *id2, bool exact_match)
+{
+	return gsm0808_cell_id_u_match(id1->id_discr, &id1->id, id2->id_discr, &id2->id, exact_match);
+}
+
+/*! Find an index in a Cell Identifier list that matches a given single Cell Identifer.
+ * Compare \a id against each entry in \a list using gsm0808_cell_ids_match(), and return the list index
+ * if a match is found. \a match_nr allows iterating all matches in the list. A match_nr <= 0 returns the
+ * first match in the list, match_nr == 1 the second match, etc., and if match_nr exceeds the available
+ * matches in the list, -1 is returned.
+ * \param[in] id  Cell Identifier to match.
+ * \param[in] list  Cell Identifier list to search in.
+ * \param[in] match_nr  Ignore this many matches.
+ * \param[in] exact_match  If true, consider as match only if the CELL_IDENT types and all values are identical.
+ * \returns -1 if no match is found, list index if a match is found.
+ */
+int gsm0808_cell_id_matches_list(const struct gsm0808_cell_id *id, const struct gsm0808_cell_id_list2 *list,
+				 unsigned int match_nr, bool exact_match)
+{
+	int i;
+	for (i = 0; i < list->id_list_len; i++) {
+		if (gsm0808_cell_id_u_match(id->id_discr, &id->id, list->id_discr, &list->id_list[i], exact_match)) {
+			if (match_nr)
+				match_nr--;
+			else
+				return i;
+		}
+	}
+	return -1;
+}
+
 /*! value_string[] for enum CELL_IDENT. */
 const struct value_string gsm0808_cell_id_discr_names[] = {
 	{ CELL_IDENT_WHOLE_GLOBAL, "CGI" },
diff --git a/src/gsm/gsm23003.c b/src/gsm/gsm23003.c
index 1d9cefe..95fca91 100644
--- a/src/gsm/gsm23003.c
+++ b/src/gsm/gsm23003.c
@@ -324,6 +324,40 @@
 	return osmo_mnc_cmp(a->mnc, a->mnc_3_digits, b->mnc, b->mnc_3_digits);
 }
 
+/* Compare two LAI.
+ * The order of comparison is MCC, MNC, LAC. See also osmo_plmn_cmp().
+ * \param a[in]  "Left" side LAI.
+ * \param b[in]  "Right" side LAI.
+ * \returns 0 if the LAI are equal, -1 if a < b, 1 if a > b. */
+int osmo_lai_cmp(const struct osmo_location_area_id *a, const struct osmo_location_area_id *b)
+{
+	int rc = osmo_plmn_cmp(&a->plmn, &b->plmn);
+	if (rc)
+		return rc;
+	if (a->lac < b->lac)
+		return -1;
+	if (a->lac > b->lac)
+		return 1;
+	return 0;
+}
+
+/* Compare two CGI.
+ * The order of comparison is MCC, MNC, LAC, CI. See also osmo_lai_cmp().
+ * \param a[in]  "Left" side CGI.
+ * \param b[in]  "Right" side CGI.
+ * \returns 0 if the CGI are equal, -1 if a < b, 1 if a > b. */
+int osmo_cgi_cmp(const struct osmo_cell_global_id *a, const struct osmo_cell_global_id *b)
+{
+	int rc = osmo_lai_cmp(&a->lai, &b->lai);
+	if (rc)
+		return rc;
+	if (a->cell_identity < b->cell_identity)
+		return -1;
+	if (a->cell_identity > b->cell_identity)
+		return 1;
+	return 0;
+}
+
 /*! Generate TS 23.003 Section 19.2 Home Network Realm/Domain (text form)
  *  \param out[out] caller-provided output buffer, at least 33 bytes long
  *  \param plmn[in] Osmocom representation of PLMN ID (MCC + MNC)
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index c440a79..ea8c9be 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -210,6 +210,8 @@
 gsm0808_cell_id_list_name_buf;
 gsm0808_cell_id_discr_names;
 gsm0808_cell_id_u_name;
+gsm0808_cell_ids_match;
+gsm0808_cell_id_matches_list;
 gsm0808_chan_type_to_speech_codec;
 gsm0808_speech_codec_from_chan_type;
 gsm0808_sc_cfg_from_gsm48_mr_cfg;