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/Makefile.am b/tests/Makefile.am
index 5e810e6..2a364d5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -23,6 +23,7 @@
 		 coding/coding_test conv/conv_gsm0503_test		\
 		 abis/abis_test endian/endian_test sercomm/sercomm_test	\
 		 prbs/prbs_test gsm23003/gsm23003_test 			\
+		 gsm23236/gsm23236_test                                 \
 		 codec/codec_ecu_fr_test timer/clk_override_test	\
 		 oap/oap_client_test gsm29205/gsm29205_test		\
 		 logging/logging_vty_test				\
@@ -246,6 +247,9 @@
 gsm23003_gsm23003_test_SOURCES = gsm23003/gsm23003_test.c
 gsm23003_gsm23003_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
 
+gsm23236_gsm23236_test_SOURCES = gsm23236/gsm23236_test.c
+gsm23236_gsm23236_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
 tdef_tdef_test_SOURCES = tdef/tdef_test.c
 tdef_tdef_test_LDADD = $(LDADD)
 
@@ -336,6 +340,7 @@
 	     conv/conv_gsm0503_test.ok endian/endian_test.ok 		\
 	     sercomm/sercomm_test.ok prbs/prbs_test.ok			\
 	     gsm29205/gsm29205_test.ok gsm23003/gsm23003_test.ok        \
+	     gsm23236/gsm23236_test.ok                                  \
 	     timer/clk_override_test.ok					\
 	     oap/oap_client_test.ok oap/oap_client_test.err		\
 	     vty/vty_transcript_test.vty				\
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;
+}
+
diff --git a/tests/gsm23236/gsm23236_test.ok b/tests/gsm23236/gsm23236_test.ok
new file mode 100644
index 0000000..22630bf
--- /dev/null
+++ b/tests/gsm23236/gsm23236_test.ok
@@ -0,0 +1,514 @@
+
+osmo_tmsi_nri_v_get(0x00000000, 10) -> nri_v=0x0 rc=0
+........|NRI->..................
+00000000000000000000000000000000 tmsi  nri_bitlen=10
+        0000000000 = 0x0 ok
+osmo_tmsi_nri_v_set(0x00000000, 0x0, 10) -> tmsi=0x00000000 rc=0
+        0000000000
+00000000000000000000000000000000 ok
+
+osmo_tmsi_nri_v_get(0x00000000, 10) -> nri_v=0x0 rc=0
+........|NRI->..................
+00000000000000000000000000000000 tmsi  nri_bitlen=10
+        0000000000 = 0x0 ok
+osmo_tmsi_nri_v_set(0x00000000, 0x7fff, 10) -> tmsi=0x00ffc000 rc=0
+        1111111111
+00000000111111111100000000000000 ok
+
+osmo_tmsi_nri_v_get(0xffffffff, 10) -> nri_v=0x3ff rc=0
+........|NRI->..................
+11111111111111111111111111111111 tmsi  nri_bitlen=10
+        1111111111 = 0x3ff ok
+osmo_tmsi_nri_v_set(0xffffffff, 0x0, 10) -> tmsi=0xff003fff rc=0
+        0000000000
+11111111000000000011111111111111 ok
+
+osmo_tmsi_nri_v_get(0xffffffff, 10) -> nri_v=0x3ff rc=0
+........|NRI->..................
+11111111111111111111111111111111 tmsi  nri_bitlen=10
+        1111111111 = 0x3ff ok
+osmo_tmsi_nri_v_set(0xffffffff, 0x7fff, 10) -> tmsi=0xffffffff rc=0
+        1111111111
+11111111111111111111111111111111 ok
+
+osmo_tmsi_nri_v_get(0x00000000, 5) -> nri_v=0x0 rc=0
+........|NRI->..................
+00000000000000000000000000000000 tmsi  nri_bitlen=5
+        00000 = 0x0 ok
+osmo_tmsi_nri_v_set(0x00000000, 0x0, 5) -> tmsi=0x00000000 rc=0
+        00000
+00000000000000000000000000000000 ok
+
+osmo_tmsi_nri_v_get(0x00000000, 5) -> nri_v=0x0 rc=0
+........|NRI->..................
+00000000000000000000000000000000 tmsi  nri_bitlen=5
+        00000 = 0x0 ok
+osmo_tmsi_nri_v_set(0x00000000, 0x7fff, 5) -> tmsi=0x00f80000 rc=0
+        11111
+00000000111110000000000000000000 ok
+
+osmo_tmsi_nri_v_get(0xffffffff, 5) -> nri_v=0x1f rc=0
+........|NRI->..................
+11111111111111111111111111111111 tmsi  nri_bitlen=5
+        11111 = 0x1f ok
+osmo_tmsi_nri_v_set(0xffffffff, 0x0, 5) -> tmsi=0xff07ffff rc=0
+        00000
+11111111000001111111111111111111 ok
+
+osmo_tmsi_nri_v_get(0xffffffff, 5) -> nri_v=0x1f rc=0
+........|NRI->..................
+11111111111111111111111111111111 tmsi  nri_bitlen=5
+        11111 = 0x1f ok
+osmo_tmsi_nri_v_set(0xffffffff, 0x7fff, 5) -> tmsi=0xffffffff rc=0
+        11111
+11111111111111111111111111111111 ok
+
+osmo_tmsi_nri_v_get(0x01234567, 8) -> nri_v=0x23 rc=0
+........|NRI->..................
+00000001001000110100010101100111 tmsi  nri_bitlen=8
+        00100011 = 0x23 ok
+osmo_tmsi_nri_v_set(0x01234567, 0x42, 8) -> tmsi=0x01424567 rc=0
+        01000010
+00000001010000100100010101100111 ok
+
+osmo_tmsi_nri_v_get(0x01234567, 15) -> nri_v=0x11a2 rc=0
+........|NRI->..................
+00000001001000110100010101100111 tmsi  nri_bitlen=15
+        001000110100010 = 0x11a2 ok
+osmo_tmsi_nri_v_set(0x01234567, 0x7fff, 15) -> tmsi=0x01ffff67 rc=0
+        111111111111111
+00000001111111111111111101100111 ok
+
+osmo_tmsi_nri_v_get(0x01234567, 16) -> nri_v=0xffffffff rc=-1
+ ok
+osmo_tmsi_nri_v_set(0x01234567, 0x7fff, 16) -> tmsi=0x01234567 rc=-1
+ ok
+
+osmo_tmsi_nri_v_get(0x01234567, 0) -> nri_v=0xffffffff rc=-1
+ ok
+osmo_tmsi_nri_v_set(0x01234567, 0x7fff, 0) -> tmsi=0x01234567 rc=-1
+ ok
+
+test_nri_validate()
+osmo_nri_v_validate(-32768, 10) = -1 ok
+osmo_nri_v_validate(-23, 10) = -1 ok
+osmo_nri_v_validate(-1, 10) = -1 ok
+osmo_nri_v_validate(0, 10) = 0 ok
+osmo_nri_v_validate(1023, 10) = 0 ok
+osmo_nri_v_validate(1024, 10) = 1 ok
+osmo_nri_v_validate(32767, 10) = 1 ok
+osmo_nri_v_validate(-32768, 5) = -1 ok
+osmo_nri_v_validate(-23, 5) = -1 ok
+osmo_nri_v_validate(-1, 5) = -1 ok
+osmo_nri_v_validate(0, 5) = 0 ok
+osmo_nri_v_validate(31, 5) = 0 ok
+osmo_nri_v_validate(32, 5) = 1 ok
+osmo_nri_v_validate(32767, 5) = 1 ok
+osmo_nri_v_validate(-32768, 1) = -1 ok
+osmo_nri_v_validate(-23, 1) = -1 ok
+osmo_nri_v_validate(-1, 1) = -1 ok
+osmo_nri_v_validate(0, 1) = 0 ok
+osmo_nri_v_validate(1, 1) = 0 ok
+osmo_nri_v_validate(2, 1) = 1 ok
+osmo_nri_v_validate(32767, 1) = 1 ok
+osmo_nri_v_validate(-32768, 0) = -1 ok
+osmo_nri_v_validate(-23, 0) = -1 ok
+osmo_nri_v_validate(-1, 0) = -1 ok
+osmo_nri_v_validate(0, 0) = 1 ok
+osmo_nri_v_validate(1, 0) = 1 ok
+osmo_nri_v_validate(32767, 0) = 1 ok
+
+test_nri_range_validate()
+osmo_nri_range_validate({-32768,-32768}, 10) = -1 ok
+osmo_nri_range_validate({-23,-23}, 10) = -1 ok
+osmo_nri_range_validate({-1,-1}, 10) = -1 ok
+osmo_nri_range_validate({0,0}, 10) = 0 ok
+osmo_nri_range_validate({1023,1023}, 10) = 0 ok
+osmo_nri_range_validate({1024,1024}, 10) = 1 ok
+osmo_nri_range_validate({32767,32767}, 10) = 1 ok
+osmo_nri_range_validate({-32768,-32768}, 5) = -1 ok
+osmo_nri_range_validate({-23,-23}, 5) = -1 ok
+osmo_nri_range_validate({-1,-1}, 5) = -1 ok
+osmo_nri_range_validate({0,0}, 5) = 0 ok
+osmo_nri_range_validate({31,31}, 5) = 0 ok
+osmo_nri_range_validate({32,32}, 5) = 1 ok
+osmo_nri_range_validate({32767,32767}, 5) = 1 ok
+osmo_nri_range_validate({-32768,-32768}, 1) = -1 ok
+osmo_nri_range_validate({-23,-23}, 1) = -1 ok
+osmo_nri_range_validate({-1,-1}, 1) = -1 ok
+osmo_nri_range_validate({0,0}, 1) = 0 ok
+osmo_nri_range_validate({1,1}, 1) = 0 ok
+osmo_nri_range_validate({2,2}, 1) = 1 ok
+osmo_nri_range_validate({32767,32767}, 1) = 1 ok
+osmo_nri_range_validate({-32768,-32768}, 0) = -1 ok
+osmo_nri_range_validate({-23,-23}, 0) = -1 ok
+osmo_nri_range_validate({-1,-1}, 0) = -1 ok
+osmo_nri_range_validate({0,0}, 0) = 1 ok
+osmo_nri_range_validate({1,1}, 0) = 1 ok
+osmo_nri_range_validate({32767,32767}, 0) = 1 ok
+osmo_nri_range_validate({0,-32768}, 10) = -2 ok
+osmo_nri_range_validate({0,-23}, 10) = -2 ok
+osmo_nri_range_validate({0,-1}, 10) = -2 ok
+osmo_nri_range_validate({0,0}, 10) = 0 ok
+osmo_nri_range_validate({0,1023}, 10) = 0 ok
+osmo_nri_range_validate({0,1024}, 10) = 2 ok
+osmo_nri_range_validate({0,32767}, 10) = 2 ok
+osmo_nri_range_validate({0,-32768}, 5) = -2 ok
+osmo_nri_range_validate({0,-23}, 5) = -2 ok
+osmo_nri_range_validate({0,-1}, 5) = -2 ok
+osmo_nri_range_validate({0,0}, 5) = 0 ok
+osmo_nri_range_validate({0,31}, 5) = 0 ok
+osmo_nri_range_validate({0,32}, 5) = 2 ok
+osmo_nri_range_validate({0,32767}, 5) = 2 ok
+osmo_nri_range_validate({0,-32768}, 1) = -2 ok
+osmo_nri_range_validate({0,-23}, 1) = -2 ok
+osmo_nri_range_validate({0,-1}, 1) = -2 ok
+osmo_nri_range_validate({0,0}, 1) = 0 ok
+osmo_nri_range_validate({0,1}, 1) = 0 ok
+osmo_nri_range_validate({0,2}, 1) = 2 ok
+osmo_nri_range_validate({0,32767}, 1) = 2 ok
+osmo_nri_range_validate({0,-32768}, 0) = 1 ok
+osmo_nri_range_validate({0,-23}, 0) = 1 ok
+osmo_nri_range_validate({0,-1}, 0) = 1 ok
+osmo_nri_range_validate({0,0}, 0) = 1 ok
+osmo_nri_range_validate({0,1}, 0) = 1 ok
+osmo_nri_range_validate({0,32767}, 0) = 1 ok
+osmo_nri_range_validate({0,0}, 10) = 0 ok
+osmo_nri_range_validate({1,0}, 10) = -3 ok
+osmo_nri_range_validate({1023,1023}, 10) = 0 ok
+osmo_nri_range_validate({1023,1022}, 10) = -3 ok
+osmo_nri_range_validate({1023,0}, 10) = -3 ok
+osmo_nri_range_validate({0,0}, 5) = 0 ok
+osmo_nri_range_validate({1,0}, 5) = -3 ok
+osmo_nri_range_validate({31,31}, 5) = 0 ok
+osmo_nri_range_validate({31,30}, 5) = -3 ok
+osmo_nri_range_validate({31,0}, 5) = -3 ok
+osmo_nri_range_validate({0,0}, 1) = 0 ok
+osmo_nri_range_validate({1,1}, 1) = 0 ok
+osmo_nri_range_validate({1,0}, 1) = -3 ok
+
+test_nri_list()
+nri_ranges = {
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> false
+osmo_nri_v_matches_ranges(32767) -> false
+osmo_nri_v_matches_ranges(100) -> false
+osmo_nri_range_overlaps_ranges(-32768, -1) -> false
+osmo_nri_range_overlaps_ranges(-100, 100) -> false
+osmo_nri_range_overlaps_ranges(10, 20) -> false
+osmo_nri_ranges_add(100, 200) -> 0
+nri_ranges = {
+  { 100, 200 },
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> false
+osmo_nri_v_matches_ranges(32767) -> false
+osmo_nri_v_matches_ranges(99) -> false
+osmo_nri_v_matches_ranges(100) -> true
+osmo_nri_v_matches_ranges(101) -> true
+osmo_nri_v_matches_ranges(199) -> true
+osmo_nri_v_matches_ranges(200) -> true
+osmo_nri_v_matches_ranges(201) -> false
+osmo_nri_range_overlaps_ranges(-32768, -1) -> false
+osmo_nri_range_overlaps_ranges(-100, 100) -> true
+osmo_nri_range_overlaps_ranges(10, 20) -> false
+osmo_nri_range_overlaps_ranges(10, 99) -> false
+osmo_nri_range_overlaps_ranges(10, 100) -> true
+osmo_nri_range_overlaps_ranges(10, 150) -> true
+osmo_nri_range_overlaps_ranges(99, 99) -> false
+osmo_nri_range_overlaps_ranges(100, 100) -> true
+osmo_nri_range_overlaps_ranges(150, 300) -> true
+osmo_nri_range_overlaps_ranges(200, 300) -> true
+osmo_nri_range_overlaps_ranges(201, 300) -> false
+
+del from start:
+osmo_nri_ranges_del(0, 110) -> 0
+nri_ranges = {
+  { 111, 200 },
+};
+osmo_nri_ranges_del(111, 111) -> 0
+nri_ranges = {
+  { 112, 200 },
+};
+osmo_nri_ranges_del(112, 199) -> 0
+nri_ranges = {
+  { 200, 200 },
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> false
+osmo_nri_v_matches_ranges(32767) -> false
+osmo_nri_v_matches_ranges(199) -> false
+osmo_nri_v_matches_ranges(200) -> true
+osmo_nri_v_matches_ranges(201) -> false
+osmo_nri_range_overlaps_ranges(-32768, -1) -> false
+osmo_nri_range_overlaps_ranges(-1000, 1000) -> true
+osmo_nri_range_overlaps_ranges(0, 199) -> false
+osmo_nri_range_overlaps_ranges(0, 200) -> true
+osmo_nri_range_overlaps_ranges(0, 201) -> true
+osmo_nri_range_overlaps_ranges(0, 1000) -> true
+osmo_nri_range_overlaps_ranges(199, 199) -> false
+osmo_nri_range_overlaps_ranges(200, 200) -> true
+osmo_nri_range_overlaps_ranges(201, 201) -> false
+
+del from end:
+osmo_nri_ranges_add(100, 200) -> 0
+nri_ranges = {
+  { 100, 200 },
+};
+osmo_nri_ranges_del(190, 32767) -> 0
+nri_ranges = {
+  { 100, 189 },
+};
+osmo_nri_ranges_del(189, 189) -> 0
+nri_ranges = {
+  { 100, 188 },
+};
+osmo_nri_ranges_del(101, 188) -> 0
+nri_ranges = {
+  { 100, 100 },
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> false
+osmo_nri_v_matches_ranges(32767) -> false
+osmo_nri_v_matches_ranges(99) -> false
+osmo_nri_v_matches_ranges(100) -> true
+osmo_nri_v_matches_ranges(101) -> false
+
+del from middle:
+osmo_nri_ranges_add(100, 200) -> 0
+nri_ranges = {
+  { 100, 200 },
+};
+osmo_nri_ranges_del(150, 160) -> 0
+nri_ranges = {
+  { 100, 149 },
+  { 161, 200 },
+};
+osmo_nri_ranges_del(110, 120) -> 0
+nri_ranges = {
+  { 100, 109 },
+  { 121, 149 },
+  { 161, 200 },
+};
+osmo_nri_ranges_del(130, 130) -> 0
+nri_ranges = {
+  { 100, 109 },
+  { 121, 129 },
+  { 131, 149 },
+  { 161, 200 },
+};
+osmo_nri_ranges_del(180, 190) -> 0
+nri_ranges = {
+  { 100, 109 },
+  { 121, 129 },
+  { 131, 149 },
+  { 161, 179 },
+  { 191, 200 },
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> false
+osmo_nri_v_matches_ranges(32767) -> false
+osmo_nri_v_matches_ranges(99) -> false
+osmo_nri_v_matches_ranges(100) -> true
+osmo_nri_v_matches_ranges(109) -> true
+osmo_nri_v_matches_ranges(110) -> false
+osmo_nri_v_matches_ranges(120) -> false
+osmo_nri_v_matches_ranges(121) -> true
+osmo_nri_v_matches_ranges(129) -> true
+osmo_nri_v_matches_ranges(130) -> false
+osmo_nri_v_matches_ranges(131) -> true
+osmo_nri_v_matches_ranges(148) -> true
+osmo_nri_v_matches_ranges(149) -> true
+osmo_nri_v_matches_ranges(150) -> false
+osmo_nri_v_matches_ranges(160) -> false
+osmo_nri_v_matches_ranges(161) -> true
+osmo_nri_v_matches_ranges(170) -> true
+osmo_nri_v_matches_ranges(179) -> true
+osmo_nri_v_matches_ranges(180) -> false
+osmo_nri_v_matches_ranges(185) -> false
+osmo_nri_v_matches_ranges(190) -> false
+osmo_nri_v_matches_ranges(191) -> true
+osmo_nri_v_matches_ranges(195) -> true
+osmo_nri_v_matches_ranges(200) -> true
+osmo_nri_v_matches_ranges(201) -> false
+osmo_nri_v_matches_ranges(1000) -> false
+osmo_nri_range_overlaps_ranges(110, 120) -> false
+osmo_nri_range_overlaps_ranges(110, 130) -> true
+osmo_nri_range_overlaps_ranges(100, 200) -> true
+
+del across whole chunks:
+osmo_nri_ranges_del(115, 185) -> 0
+nri_ranges = {
+  { 100, 109 },
+  { 191, 200 },
+};
+osmo_nri_ranges_del(105, 195) -> 0
+nri_ranges = {
+  { 100, 104 },
+  { 196, 200 },
+};
+osmo_nri_ranges_del(0, 1000) -> 0
+nri_ranges = {
+};
+
+add to join chunks:
+osmo_nri_ranges_add(0, 100) -> 0
+nri_ranges = {
+  { 0, 100 },
+};
+osmo_nri_ranges_del(11, 19) -> 0
+nri_ranges = {
+  { 0, 10 },
+  { 20, 100 },
+};
+osmo_nri_ranges_del(23, 23) -> 0
+nri_ranges = {
+  { 0, 10 },
+  { 20, 22 },
+  { 24, 100 },
+};
+osmo_nri_ranges_del(30, 41) -> 0
+nri_ranges = {
+  { 0, 10 },
+  { 20, 22 },
+  { 24, 29 },
+  { 42, 100 },
+};
+osmo_nri_ranges_add(23, 23) -> 0
+nri_ranges = {
+  { 0, 10 },
+  { 20, 29 },
+  { 42, 100 },
+};
+osmo_nri_ranges_add(11, 41) -> 0
+nri_ranges = {
+  { 0, 100 },
+};
+osmo_nri_v_matches_ranges(0) -> true
+osmo_nri_v_matches_ranges(10) -> true
+osmo_nri_v_matches_ranges(11) -> true
+osmo_nri_v_matches_ranges(24) -> true
+osmo_nri_v_matches_ranges(41) -> true
+osmo_nri_v_matches_ranges(42) -> true
+osmo_nri_v_matches_ranges(100) -> true
+osmo_nri_v_matches_ranges(101) -> false
+
+border cases:
+osmo_nri_ranges_add(0, 0) -> 0
+nri_ranges = {
+  { 0, 100 },
+};
+osmo_nri_ranges_add(32767, 32767) -> 0
+nri_ranges = {
+  { 0, 100 },
+  { 32767, 32767 },
+};
+osmo_nri_ranges_add(1, 32766) -> 0
+nri_ranges = {
+  { 0, 32767 },
+};
+osmo_nri_v_matches_ranges(-32768) -> false
+osmo_nri_v_matches_ranges(-1) -> false
+osmo_nri_v_matches_ranges(0) -> true
+osmo_nri_v_matches_ranges(32767) -> true
+osmo_nri_ranges_del(0, 0) -> 0
+nri_ranges = {
+  { 1, 32767 },
+};
+osmo_nri_ranges_del(32767, 32767) -> 0
+nri_ranges = {
+  { 1, 32766 },
+};
+osmo_nri_ranges_del(1, 32766) -> 0
+nri_ranges = {
+};
+
+range errors:
+osmo_nri_ranges_add(-1, -1) -> -1
+nri_ranges = {
+};
+osmo_nri_ranges_add(-20, -10) -> -1
+nri_ranges = {
+};
+osmo_nri_ranges_add(100, 1) -> -1
+nri_ranges = {
+};
+osmo_nri_ranges_add(0, 32767) -> 0
+nri_ranges = {
+  { 0, 32767 },
+};
+osmo_nri_ranges_del(-1, -1) -> -1
+nri_ranges = {
+  { 0, 32767 },
+};
+osmo_nri_ranges_del(-20, -10) -> -1
+nri_ranges = {
+  { 0, 32767 },
+};
+osmo_nri_ranges_del(100, 1) -> -1
+nri_ranges = {
+  { 0, 32767 },
+};
+
+test_nri_limit_by_ranges()
+osmo_nri_ranges_add(10, 10) -> 0
+nri_ranges = {
+  { 10, 10 },
+};
+osmo_nri_ranges_add(20, 21) -> 0
+nri_ranges = {
+  { 10, 10 },
+  { 20, 21 },
+};
+osmo_nri_ranges_add(30, 32) -> 0
+nri_ranges = {
+  { 10, 10 },
+  { 20, 21 },
+  { 30, 32 },
+};
+osmo_nri_v_limit_by_ranges(0) -> nri_v=10 rc=0 ok
+osmo_nri_v_limit_by_ranges(1) -> nri_v=20 rc=0 ok
+osmo_nri_v_limit_by_ranges(2) -> nri_v=21 rc=0 ok
+osmo_nri_v_limit_by_ranges(3) -> nri_v=30 rc=0 ok
+osmo_nri_v_limit_by_ranges(4) -> nri_v=31 rc=0 ok
+osmo_nri_v_limit_by_ranges(5) -> nri_v=32 rc=0 ok
+osmo_nri_v_limit_by_ranges(6) -> nri_v=10 rc=0 ok
+osmo_nri_v_limit_by_ranges(7) -> nri_v=20 rc=0 ok
+osmo_nri_v_limit_by_ranges(8) -> nri_v=21 rc=0 ok
+osmo_nri_v_limit_by_ranges(9) -> nri_v=30 rc=0 ok
+osmo_nri_v_limit_by_ranges(10) -> nri_v=31 rc=0 ok
+osmo_nri_v_limit_by_ranges(11) -> nri_v=32 rc=0 ok
+osmo_nri_v_limit_by_ranges(12) -> nri_v=10 rc=0 ok
+osmo_nri_v_limit_by_ranges(13) -> nri_v=20 rc=0 ok
+osmo_nri_v_limit_by_ranges(14) -> nri_v=21 rc=0 ok
+osmo_nri_v_limit_by_ranges(15) -> nri_v=30 rc=0 ok
+osmo_nri_v_limit_by_ranges(16) -> nri_v=31 rc=0 ok
+osmo_nri_v_limit_by_ranges(17) -> nri_v=32 rc=0 ok
+osmo_nri_v_limit_by_ranges(18) -> nri_v=10 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00000000, 8) -> tmsi=0x000a0000 nri_v=10 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00010000, 8) -> tmsi=0x00140000 nri_v=20 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00020000, 8) -> tmsi=0x00150000 nri_v=21 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00030000, 8) -> tmsi=0x001e0000 nri_v=30 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00040000, 8) -> tmsi=0x001f0000 nri_v=31 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00050000, 8) -> tmsi=0x00200000 nri_v=32 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00060000, 8) -> tmsi=0x000a0000 nri_v=10 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00070000, 8) -> tmsi=0x00140000 nri_v=20 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00080000, 8) -> tmsi=0x00150000 nri_v=21 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00090000, 8) -> tmsi=0x001e0000 nri_v=30 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000a0000, 8) -> tmsi=0x001f0000 nri_v=31 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000b0000, 8) -> tmsi=0x00200000 nri_v=32 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000c0000, 8) -> tmsi=0x000a0000 nri_v=10 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000d0000, 8) -> tmsi=0x00140000 nri_v=20 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000e0000, 8) -> tmsi=0x00150000 nri_v=21 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x000f0000, 8) -> tmsi=0x001e0000 nri_v=30 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00100000, 8) -> tmsi=0x001f0000 nri_v=31 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00110000, 8) -> tmsi=0x00200000 nri_v=32 rc=0 ok
+osmo_tmsi_nri_v_limit_by_ranges(0x00120000, 8) -> tmsi=0x000a0000 nri_v=10 rc=0 ok
+
+pass
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 4ff6671..a4c28f9 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -344,6 +344,12 @@
 AT_CHECK([$abs_top_builddir/tests/gsm23003/gsm23003_test], [0], [expout], [ignore])
 AT_CLEANUP
 
+AT_SETUP([gsm23236])
+AT_KEYWORDS([gsm23236])
+cat $abs_srcdir/gsm23236/gsm23236_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm23236/gsm23236_test], [0], [expout], [ignore])
+AT_CLEANUP
+
 AT_SETUP([tdef])
 AT_KEYWORDS([tdef])
 cat $abs_srcdir/tdef/tdef_test.ok > expout