add gsm23236: MSC pooling: TMSI and NRI utility functions

These utilities will be used by osmo-bsc to determine the Network Resource
Indicator seen in the TMSI, and (potentially) by osmo-msc to compose a TMSI
with a specific NRI, for osmo-bsc's load balancing between several MSCs.

Add utility functions to:
- extract an NRI value from a TMSI.
- overwrite the NRI value in a TMSI.
- limit an NRI in a (random) TMSI to a given list of ranges.
- add NRI value ranges to a list.
- remove them from a list.
- match NRI value (range) to a list.
- parse NRI values from string, for VTY.
- common VTY functionality of adding/removing NRI values from argv.

Add C tests for the above.

Why we need public API for NRI ranges: In osmo-bsc alone, we need the same NRI
API twice, 1: to manage/list NRI value ranges per-MSC, and 2: to manage/list
NULL-NRI values. If we also consider (potentially) adding NRI support to
osmo-msc, we need the same API twice again there. Hence it is useful to define
re-used API up here in libosmocore.

Related: OS#3682
Change-Id: Icb57a2dd9323c7ea11b34003eccc7e68a0247bf5
diff --git a/tests/gsm23236/gsm23236_test.c b/tests/gsm23236/gsm23236_test.c
new file mode 100644
index 0000000..14c5ce3
--- /dev/null
+++ b/tests/gsm23236/gsm23236_test.c
@@ -0,0 +1,620 @@
+/*
+ * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <strings.h>
+#include <string.h>
+
+#include <osmocom/gsm/gsm23236.h>
+#include <osmocom/core/utils.h>
+
+void *ctx;
+bool ok = true;
+
+void bitdump(uint8_t count, uint32_t val)
+{
+	uint32_t bit;
+	if (count < 1)
+		return;
+	for (bit = ((uint32_t)1) << (count - 1); bit; bit >>= 1)
+		printf("%c", (val & bit)? '1' : '0');
+}
+
+struct nri_v_get_set_test {
+	uint32_t tmsi;
+	uint8_t nri_bitlen;
+	int16_t expect_get_nri;
+	int expect_get_rc;
+	int16_t set_nri_v;
+	uint32_t expect_tmsi;
+	int expect_set_rc;
+};
+
+struct nri_v_get_set_test nri_v_get_set_tests[] = {
+	{
+		.tmsi = 0,
+		.nri_bitlen = 10,
+		.expect_get_nri = 0,
+		.set_nri_v = 0,
+		.expect_tmsi = 0,
+	},
+	{
+		.tmsi = 0,
+		.nri_bitlen = 10,
+		.expect_get_nri = 0,
+		.set_nri_v = 0x7fff,
+		.expect_tmsi = 0x00ffc000
+	},
+	{
+		.tmsi = 0xffffffff,
+		.nri_bitlen = 10,
+		.expect_get_nri = 0x3ff,
+		.set_nri_v = 0,
+		.expect_tmsi = 0xff003fff
+	},
+	{
+		.tmsi = 0xffffffff,
+		.nri_bitlen = 10,
+		.expect_get_nri = 0x3ff,
+		.set_nri_v = 0x7fff,
+		.expect_tmsi = 0xffffffff
+	},
+	{
+		.tmsi = 0,
+		.nri_bitlen = 5,
+		.expect_get_nri = 0,
+		.set_nri_v = 0,
+		.expect_tmsi = 0,
+	},
+	{
+		.tmsi = 0,
+		.nri_bitlen = 5,
+		.expect_get_nri = 0,
+		.set_nri_v = 0x7fff,
+		.expect_tmsi = 0x00f80000
+	},
+	{
+		.tmsi = 0xffffffff,
+		.nri_bitlen = 5,
+		.expect_get_nri = 0x1f,
+		.set_nri_v = 0,
+		.expect_tmsi = 0xff07ffff
+	},
+	{
+		.tmsi = 0xffffffff,
+		.nri_bitlen = 5,
+		.expect_get_nri = 0x1f,
+		.set_nri_v = 0x7fff,
+		.expect_tmsi = 0xffffffff
+	},
+	{
+		.tmsi = 0x01234567,
+		.nri_bitlen = 8,
+		.expect_get_nri = 0x23,
+		.set_nri_v = 0x42,
+		.expect_tmsi = 0x01424567
+	},
+	{
+		.tmsi = 0x01234567,
+		.nri_bitlen = 15,
+		.expect_get_nri = 0x2345 >> 1,
+		.set_nri_v = 0x7fff,
+		.expect_tmsi = 0x01ffff67
+	},
+	{
+		.tmsi = 0x01234567,
+		.nri_bitlen = 16,
+		.expect_get_rc = -1,
+		.expect_get_nri = -1,
+		.set_nri_v = 0x7fff,
+		.expect_set_rc = -1,
+		.expect_tmsi = 0x01234567,
+	},
+	{
+		.tmsi = 0x01234567,
+		.nri_bitlen = 0,
+		.expect_get_rc = -1,
+		.expect_get_nri = -1,
+		.set_nri_v = 0x7fff,
+		.expect_set_rc = -1,
+		.expect_tmsi = 0x01234567,
+	},
+};
+
+void test_nri_v_get_set()
+{
+	struct nri_v_get_set_test *t;
+
+	for (t = nri_v_get_set_tests; t < &nri_v_get_set_tests[ARRAY_SIZE(nri_v_get_set_tests)]; t++) {
+		int16_t nri_v = 0;
+		uint32_t tmsi2;
+		int rc;
+
+		rc = osmo_tmsi_nri_v_get(&nri_v, t->tmsi, t->nri_bitlen);
+		printf("\nosmo_tmsi_nri_v_get(0x%08x, %u) -> nri_v=0x%x rc=%d\n", t->tmsi, t->nri_bitlen, nri_v, rc);
+		if (!rc) {
+			printf("........|NRI->..................\n");
+			bitdump(32, t->tmsi);
+			printf(" tmsi  nri_bitlen=%u\n", t->nri_bitlen);
+			printf("        ");
+			bitdump(t->nri_bitlen, nri_v);
+			printf(" = 0x%x", nri_v);
+		}
+		if (nri_v == t->expect_get_nri && rc == t->expect_get_rc) {
+			printf(" ok\n");
+		} else {
+			printf(" ERROR: expected nri_v=0x%x rc=%d\n", t->expect_get_nri, t->expect_get_rc);
+			ok = false;
+		}
+
+		tmsi2 = t->tmsi;
+		rc = osmo_tmsi_nri_v_set(&tmsi2, t->set_nri_v, t->nri_bitlen);
+		printf("osmo_tmsi_nri_v_set(0x%08x, 0x%x, %u) -> tmsi=0x%08x rc=%d\n", t->tmsi, t->set_nri_v, t->nri_bitlen,
+		       tmsi2, rc);
+		if (!rc) {
+			printf("        ");
+			bitdump(t->nri_bitlen, t->set_nri_v);
+			printf("\n");
+			bitdump(32, tmsi2);
+		}
+		if (tmsi2 == t->expect_tmsi && rc == t->expect_set_rc) {
+			printf(" ok\n");
+		} else {
+			printf(" ERROR: expected tmsi=0x%08x rc=%d\n", t->expect_tmsi, t->expect_set_rc);
+			ok = false;
+		}
+	}
+}
+
+struct nri_validate_tc {
+	int16_t nri;
+	uint8_t nri_bitlen;
+	int expect_rc;
+};
+
+struct nri_validate_tc nri_validate_tests[] = {
+	{ .nri = INT16_MIN, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .nri = -23, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .nri = -1, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .nri = 0, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .nri = (1 << 10) - 1, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .nri = (1 << 10), .nri_bitlen = 10, .expect_rc = 1 },
+	{ .nri = INT16_MAX, .nri_bitlen = 10, .expect_rc = 1 },
+
+	{ .nri = INT16_MIN, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .nri = -23, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .nri = -1, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .nri = 0, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .nri = (1 << 5) - 1, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .nri = (1 << 5), .nri_bitlen = 5, .expect_rc = 1 },
+	{ .nri = INT16_MAX, .nri_bitlen = 5, .expect_rc = 1 },
+
+	{ .nri = INT16_MIN, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .nri = -23, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .nri = -1, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .nri = 0, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .nri = 1, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .nri = 2, .nri_bitlen = 1, .expect_rc = 1 },
+	{ .nri = INT16_MAX, .nri_bitlen = 1, .expect_rc = 1 },
+
+	{ .nri = INT16_MIN, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .nri = -23, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .nri = -1, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .nri = 0, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .nri = 1, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .nri = INT16_MAX, .nri_bitlen = 0, .expect_rc = 1 },
+};
+
+void test_nri_validate()
+{
+	struct nri_validate_tc *t;
+	printf("\n%s()\n", __func__);
+	for (t = nri_validate_tests; (t - nri_validate_tests) < ARRAY_SIZE(nri_validate_tests); t++) {
+		int rc = osmo_nri_v_validate(t->nri, t->nri_bitlen);
+		printf("osmo_nri_v_validate(%d, %u) = %d ", t->nri, t->nri_bitlen, rc);
+		if (rc == t->expect_rc) {
+			printf("ok\n");
+		} else {
+			printf("ERROR, expected rc = %d\n", t->expect_rc);
+			ok = false;
+		}
+	}
+}
+
+struct nri_range_validate_tc {
+	struct osmo_nri_range range;
+	uint8_t nri_bitlen;
+	int expect_rc;
+};
+
+struct nri_range_validate_tc nri_range_validate_tests[] = {
+	{ .range = { .first = INT16_MIN, .last = INT16_MIN }, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .range = { .first = -23, .last = -23 }, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .range = { .first = -1, .last = -1 }, .nri_bitlen = 10, .expect_rc = -1 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = (1 << 10) - 1, .last = (1 << 10) - 1 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = (1 << 10), .last = (1 << 10) }, .nri_bitlen = 10, .expect_rc = 1 },
+	{ .range = { .first = INT16_MAX, .last = INT16_MAX }, .nri_bitlen = 10, .expect_rc = 1 },
+
+	{ .range = { .first = INT16_MIN, .last = INT16_MIN }, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .range = { .first = -23, .last = -23 }, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .range = { .first = -1, .last = -1 }, .nri_bitlen = 5, .expect_rc = -1 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = (1 << 5) - 1, .last = (1 << 5) - 1 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = (1 << 5), .last = (1 << 5) }, .nri_bitlen = 5, .expect_rc = 1 },
+	{ .range = { .first = INT16_MAX, .last = INT16_MAX }, .nri_bitlen = 5, .expect_rc = 1 },
+
+	{ .range = { .first = INT16_MIN, .last = INT16_MIN }, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .range = { .first = -23, .last = -23 }, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .range = { .first = -1, .last = -1 }, .nri_bitlen = 1, .expect_rc = -1 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 1, .last = 1 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 2, .last = 2 }, .nri_bitlen = 1, .expect_rc = 1 },
+	{ .range = { .first = INT16_MAX, .last = INT16_MAX }, .nri_bitlen = 1, .expect_rc = 1 },
+
+	{ .range = { .first = INT16_MIN, .last = INT16_MIN }, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .range = { .first = -23, .last = -23 }, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .range = { .first = -1, .last = -1 }, .nri_bitlen = 0, .expect_rc = -1 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 1, .last = 1 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = INT16_MAX, .last = INT16_MAX }, .nri_bitlen = 0, .expect_rc = 1 },
+
+
+	{ .range = { .first = 0, .last = INT16_MIN }, .nri_bitlen = 10, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -23 }, .nri_bitlen = 10, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -1 }, .nri_bitlen = 10, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = (1 << 10) - 1 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = (1 << 10) }, .nri_bitlen = 10, .expect_rc = 2 },
+	{ .range = { .first = 0, .last = INT16_MAX }, .nri_bitlen = 10, .expect_rc = 2 },
+
+	{ .range = { .first = 0, .last = INT16_MIN }, .nri_bitlen = 5, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -23 }, .nri_bitlen = 5, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -1 }, .nri_bitlen = 5, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = (1 << 5) - 1 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = (1 << 5) }, .nri_bitlen = 5, .expect_rc = 2 },
+	{ .range = { .first = 0, .last = INT16_MAX }, .nri_bitlen = 5, .expect_rc = 2 },
+
+	{ .range = { .first = 0, .last = INT16_MIN }, .nri_bitlen = 1, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -23 }, .nri_bitlen = 1, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = -1 }, .nri_bitlen = 1, .expect_rc = -2 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = 1 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 0, .last = 2 }, .nri_bitlen = 1, .expect_rc = 2 },
+	{ .range = { .first = 0, .last = INT16_MAX }, .nri_bitlen = 1, .expect_rc = 2 },
+
+	{ .range = { .first = 0, .last = INT16_MIN }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 0, .last = -23 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 0, .last = -1 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 0, .last = 1 }, .nri_bitlen = 0, .expect_rc = 1 },
+	{ .range = { .first = 0, .last = INT16_MAX }, .nri_bitlen = 0, .expect_rc = 1 },
+
+
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = 1, .last = 0 }, .nri_bitlen = 10, .expect_rc = -3 },
+	{ .range = { .first = (1 << 10) - 1, .last = (1 << 10) - 1 }, .nri_bitlen = 10, .expect_rc = 0 },
+	{ .range = { .first = (1 << 10) - 1, .last = (1 << 10) - 2 }, .nri_bitlen = 10, .expect_rc = -3 },
+	{ .range = { .first = (1 << 10) - 1, .last = 0 }, .nri_bitlen = 10, .expect_rc = -3 },
+
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = 1, .last = 0 }, .nri_bitlen = 5, .expect_rc = -3 },
+	{ .range = { .first = (1 << 5) - 1, .last = (1 << 5) - 1 }, .nri_bitlen = 5, .expect_rc = 0 },
+	{ .range = { .first = (1 << 5) - 1, .last = (1 << 5) - 2 }, .nri_bitlen = 5, .expect_rc = -3 },
+	{ .range = { .first = (1 << 5) - 1, .last = 0 }, .nri_bitlen = 5, .expect_rc = -3 },
+
+	{ .range = { .first = 0, .last = 0 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 1, .last = 1 }, .nri_bitlen = 1, .expect_rc = 0 },
+	{ .range = { .first = 1, .last = 0 }, .nri_bitlen = 1, .expect_rc = -3 },
+
+};
+
+void test_nri_range_validate()
+{
+	struct nri_range_validate_tc *t;
+	printf("\n%s()\n", __func__);
+	for (t = nri_range_validate_tests; (t - nri_range_validate_tests) < ARRAY_SIZE(nri_range_validate_tests); t++) {
+		int rc = osmo_nri_range_validate(&t->range, t->nri_bitlen);
+		printf("osmo_nri_range_validate({%d,%d}, %u) = %d ", t->range.first, t->range.last, t->nri_bitlen, rc);
+		if (rc == t->expect_rc) {
+			printf("ok\n");
+		} else {
+			printf("ERROR, expected rc = %d\n", t->expect_rc);
+			ok = false;
+		}
+	}
+}
+
+void dump_list(const struct osmo_nri_ranges *nri_ranges)
+{
+	struct osmo_nri_range *r;
+	printf("nri_ranges = {\n");
+	llist_for_each_entry(r, &nri_ranges->entries, entry) {
+		printf("  { %d, %d },\n", r->first, r->last);
+		if (osmo_nri_range_validate(r, 255)) {
+			ok = false;
+			printf("    ^^^^^ ERROR: invalid range\n");
+		}
+	}
+	printf("};\n");
+}
+
+void test_nri_list()
+{
+	struct osmo_nri_ranges *nri_ranges = osmo_nri_ranges_alloc(ctx);
+	printf("\n%s()\n", __func__);
+
+#define ADD(FIRST, LAST) do { \
+		struct osmo_nri_range r = { .first = FIRST, .last = LAST }; \
+		int rc; \
+		rc = osmo_nri_ranges_add(nri_ranges, &r); \
+		printf("osmo_nri_ranges_add(%d, %d) -> %d\n", r.first, r.last, rc); \
+		dump_list(nri_ranges); \
+	} while(0)
+
+#define DEL(FIRST, LAST) do { \
+		struct osmo_nri_range r = { .first = FIRST, .last = LAST }; \
+		int rc; \
+		rc = osmo_nri_ranges_del(nri_ranges, &r); \
+		printf("osmo_nri_ranges_del(%d, %d) -> %d\n", r.first, r.last, rc); \
+		dump_list(nri_ranges); \
+	} while(0)
+
+#define MATCHES(NRI, EXPECT_MATCH) do { \
+		bool matches = osmo_nri_v_matches_ranges(NRI, nri_ranges); \
+		printf("osmo_nri_v_matches_ranges(%d) -> %s\n", NRI, matches ? "true" : "false"); \
+		if (matches != EXPECT_MATCH) { \
+			ok = false; \
+			printf("  ^ ERROR: expected " #EXPECT_MATCH "\n"); \
+		} \
+	} while(0)
+
+#define OVERLAPS(FIRST, LAST, EXPECT_OVERLAP) do { \
+		struct osmo_nri_range r = { .first = FIRST, .last = LAST }; \
+		bool overlaps = osmo_nri_range_overlaps_ranges(&r, nri_ranges); \
+		printf("osmo_nri_range_overlaps_ranges(%d, %d) -> %s\n", r.first, r.last, overlaps ? "true" : "false"); \
+		if (overlaps != EXPECT_OVERLAP) { \
+			ok = false; \
+			printf("  ^ ERROR: expected " #EXPECT_OVERLAP "\n"); \
+		} \
+	} while(0)
+
+	dump_list(nri_ranges);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, false);
+	MATCHES(INT16_MAX, false);
+	MATCHES(100, false);
+	OVERLAPS(INT16_MIN, -1, false);
+	OVERLAPS(-100, 100, false);
+	OVERLAPS(10, 20, false);
+
+	ADD(100, 200);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, false);
+	MATCHES(INT16_MAX, false);
+	MATCHES(99, false);
+	MATCHES(100, true);
+	MATCHES(101, true);
+	MATCHES(199, true);
+	MATCHES(200, true);
+	MATCHES(201, false);
+	OVERLAPS(INT16_MIN, -1, false);
+	OVERLAPS(-100, 100, true);
+	OVERLAPS(10, 20, false);
+	OVERLAPS(10, 99, false);
+	OVERLAPS(10, 100, true);
+	OVERLAPS(10, 150, true);
+	OVERLAPS(99, 99, false);
+	OVERLAPS(100, 100, true);
+	OVERLAPS(150, 300, true);
+	OVERLAPS(200, 300, true);
+	OVERLAPS(201, 300, false);
+
+	printf("\ndel from start:\n");
+	DEL(0, 110);
+	DEL(111, 111);
+	DEL(112, 199);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, false);
+	MATCHES(INT16_MAX, false);
+	MATCHES(199, false);
+	MATCHES(200, true);
+	MATCHES(201, false);
+	OVERLAPS(INT16_MIN, -1, false);
+	OVERLAPS(-1000, 1000, true);
+	OVERLAPS(0, 199, false);
+	OVERLAPS(0, 200, true);
+	OVERLAPS(0, 201, true);
+	OVERLAPS(0, 1000, true);
+	OVERLAPS(199, 199, false);
+	OVERLAPS(200, 200, true);
+	OVERLAPS(201, 201, false);
+
+	printf("\ndel from end:\n");
+	ADD(100, 200);
+	DEL(190, INT16_MAX);
+	DEL(189, 189);
+	DEL(101, 188);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, false);
+	MATCHES(INT16_MAX, false);
+	MATCHES(99, false);
+	MATCHES(100, true);
+	MATCHES(101, false);
+
+	printf("\ndel from middle:\n");
+	ADD(100, 200);
+	DEL(150, 160);
+	DEL(110, 120);
+	DEL(130, 130);
+	DEL(180, 190);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, false);
+	MATCHES(INT16_MAX, false);
+	MATCHES(99, false);
+	MATCHES(100, true);
+	MATCHES(109, true);
+	MATCHES(110, false);
+	MATCHES(120, false);
+	MATCHES(121, true);
+	MATCHES(129, true);
+	MATCHES(130, false);
+	MATCHES(131, true);
+	MATCHES(148, true);
+	MATCHES(149, true);
+	MATCHES(150, false);
+	MATCHES(160, false);
+	MATCHES(161, true);
+	MATCHES(170, true);
+	MATCHES(179, true);
+	MATCHES(180, false);
+	MATCHES(185, false);
+	MATCHES(190, false);
+	MATCHES(191, true);
+	MATCHES(195, true);
+	MATCHES(200, true);
+	MATCHES(201, false);
+	MATCHES(1000, false);
+	OVERLAPS(110, 120, false);
+	OVERLAPS(110, 130, true);
+	OVERLAPS(100, 200, true);
+
+	printf("\ndel across whole chunks:\n");
+	DEL(115, 185);
+	DEL(105, 195);
+	DEL(0, 1000);
+
+	printf("\nadd to join chunks:\n");
+	ADD(0, 100);
+	DEL(11, 19);
+	DEL(23, 23);
+	DEL(30, 41);
+	ADD(23, 23);
+	ADD(11, 41);
+	MATCHES(0, true);
+	MATCHES(10, true);
+	MATCHES(11, true);
+	MATCHES(24, true);
+	MATCHES(41, true);
+	MATCHES(42, true);
+	MATCHES(100, true);
+	MATCHES(101, false);
+
+	printf("\nborder cases:\n");
+	ADD(0, 0);
+	ADD(INT16_MAX, INT16_MAX);
+	ADD(1, INT16_MAX - 1);
+	MATCHES(INT16_MIN, false);
+	MATCHES(-1, false);
+	MATCHES(0, true);
+	MATCHES(INT16_MAX, true);
+	DEL(0, 0);
+	DEL(INT16_MAX, INT16_MAX);
+	DEL(1, INT16_MAX - 1);
+
+	printf("\nrange errors:\n");
+	ADD(-1, -1);
+	ADD(-20, -10);
+	ADD(100, 1);
+	ADD(0, INT16_MAX);
+	DEL(-1, -1);
+	DEL(-20, -10);
+	DEL(100, 1);
+}
+
+void test_nri_limit_by_ranges()
+{
+	const uint8_t nri_bitlen = 8;
+	const int16_t expect_nri_vals[] = { 10, 20, 21, 30, 31, 32 };
+	int i;
+	struct osmo_nri_ranges *nri_ranges = osmo_nri_ranges_alloc(ctx);
+	printf("\n%s()\n", __func__);
+
+	ADD(10, 10);
+	ADD(20, 21);
+	ADD(30, 32);
+
+	for (i = 0; i < 19; i++) {
+		int rc;
+		int16_t nri_v;
+		int16_t expect_nri_v = expect_nri_vals[i % ARRAY_SIZE(expect_nri_vals)];
+
+		nri_v = i;
+		rc = osmo_nri_v_limit_by_ranges(&nri_v, nri_ranges, nri_bitlen);
+		printf("osmo_nri_v_limit_by_ranges(%d) -> nri_v=%d rc=%d", i, nri_v, rc);
+		if (!rc && nri_v == expect_nri_v) {
+			printf(" ok\n");
+		} else {
+			printf(" ERROR: expected nri_v=%d rc=0\n", expect_nri_v);
+			ok = false;
+		}
+	}
+	for (i = 0; i < 19; i++) {
+		int rc;
+		int16_t nri_v;
+		uint32_t tmsi, tmsi2;
+		int16_t expect_nri_v = expect_nri_vals[i % ARRAY_SIZE(expect_nri_vals)];
+
+		tmsi = 0;
+		osmo_tmsi_nri_v_set(&tmsi, i, nri_bitlen);
+		tmsi2 = tmsi;
+		rc = osmo_tmsi_nri_v_limit_by_ranges(&tmsi2, nri_ranges, nri_bitlen);
+		osmo_tmsi_nri_v_get(&nri_v, tmsi2, nri_bitlen);
+		printf("osmo_tmsi_nri_v_limit_by_ranges(0x%08x, %u) -> tmsi=0x%08x nri_v=%d rc=%d",
+		       tmsi, nri_bitlen, tmsi2, nri_v, rc);
+		if (!rc && nri_v == expect_nri_v) {
+			printf(" ok\n");
+		} else {
+			printf(" ERROR: expected nri_v=%d rc=0\n", expect_nri_v);
+			ok = false;
+		}
+	}
+}
+
+int main()
+{
+	ctx = talloc_named_const(NULL, 0, "nri_test");
+
+	test_nri_v_get_set();
+	test_nri_validate();
+	test_nri_range_validate();
+	test_nri_list();
+	test_nri_limit_by_ranges();
+
+	talloc_free(ctx);
+	if (!ok) {
+		printf("\nFAIL\n");
+		return -1;
+	}
+
+	printf("\npass\n");
+	return 0;
+}
+