gprs: Add GPRS timer conversion functions

Currently, all GPRS timer values are hard-coded. To make these values
configurable in seconds and to show them, conversion functions from
and to seconds are needed.

This patch adds gprs_tmr_to_secs and gprs_secs_to_tmr_floor. Due to
the limited number of bits used to encode GPRS timer values, only a
few durations can be represented. gprs_secs_to_tmr_floor therefore
always returns the timer value that represents either the exact
number (if an exact representation exists) or the next lower number
for that an exact representation exists.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/tests/gprs/gprs_test.c b/openbsc/tests/gprs/gprs_test.c
index 37dc832..e445ae7 100644
--- a/openbsc/tests/gprs/gprs_test.c
+++ b/openbsc/tests/gprs/gprs_test.c
@@ -614,6 +614,73 @@
 	}
 }
 
+static void test_gprs_timer_enc_dec(void)
+{
+	int i, u, secs, tmr;
+	const int upper_secs_test_limit = 12000;
+	int dec_secs, last_dec_secs = -1;
+
+	printf("Test GPRS timer decoding/encoding\n");
+
+	/* Check gprs_tmr_to_secs with all 256 encoded values */
+	for (u = 0; u <= GPRS_TMR_DEACTIVATED; u += 32) {
+		fprintf(stderr, "Testing decoding with timer value unit %u\n",
+			u / 32);
+		for (i = 0; i < 32; i++) {
+			switch (u) {
+			case GPRS_TMR_2SECONDS:
+				OSMO_ASSERT(gprs_tmr_to_secs(u + i) == 2 * i);
+				break;
+
+			default:
+			case GPRS_TMR_MINUTE:
+				OSMO_ASSERT(gprs_tmr_to_secs(u + i) == 60 * i);
+				break;
+
+			case GPRS_TMR_6MINUTE:
+				OSMO_ASSERT(gprs_tmr_to_secs(u + i) == 360 * i);
+				break;
+
+			case GPRS_TMR_DEACTIVATED:
+				OSMO_ASSERT(gprs_tmr_to_secs(u + i) == -1);
+				break;
+			}
+
+			OSMO_ASSERT(gprs_tmr_to_secs(u + i) < upper_secs_test_limit);
+		}
+	}
+
+	/* Check gprs_secs_to_tmr_floor for secs that can exactly be
+	 * represented as GPRS timer values */
+	for (i = 0; i < GPRS_TMR_DEACTIVATED; i++) {
+		int j;
+		secs = gprs_tmr_to_secs(i);
+		tmr = gprs_secs_to_tmr_floor(secs);
+		OSMO_ASSERT(secs == gprs_tmr_to_secs(tmr));
+
+		/* Check that the highest resolution is used */
+		for (j = 0; j < tmr; j++)
+			OSMO_ASSERT(secs != gprs_tmr_to_secs(j));
+	}
+	OSMO_ASSERT(GPRS_TMR_DEACTIVATED == gprs_secs_to_tmr_floor(-1));
+
+	/* Check properties of gprs_secs_to_tmr_floor */
+	for (secs = 0; secs <= upper_secs_test_limit; secs++) {
+		int tmr = gprs_secs_to_tmr_floor(secs);
+		int delta_secs = gprs_tmr_to_secs((tmr & ~0x1f) | 1);
+		dec_secs = gprs_tmr_to_secs(tmr);
+
+		/* Check floor */
+		OSMO_ASSERT(dec_secs <= secs);
+		/* Check monotonicity */
+		OSMO_ASSERT(dec_secs >= last_dec_secs);
+		/* Check max distance (<= resolution) */
+		OSMO_ASSERT(dec_secs - last_dec_secs <= delta_secs);
+
+		last_dec_secs = dec_secs;
+	}
+}
+
 const struct log_info_cat default_categories[] = {
 	[DGPRS] = {
 		.name = "DGPRS",
@@ -635,6 +702,7 @@
 	test_gsm_03_03_apn();
 	test_tlv_shift_functions();
 	test_gsup_messages_dec_enc();
+	test_gprs_timer_enc_dec();
 
 	printf("Done.\n");
 	return EXIT_SUCCESS;