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/include/openbsc/gprs_utils.h b/openbsc/include/openbsc/gprs_utils.h
index 60b55a1..6880e05 100644
--- a/openbsc/include/openbsc/gprs_utils.h
+++ b/openbsc/include/openbsc/gprs_utils.h
@@ -31,6 +31,11 @@
 			    size_t old_size, size_t new_size);
 char *gprs_apn_to_str(char *out_str, const uint8_t *apn_enc, size_t rest_chars);
 int gprs_str_to_apn(uint8_t *apn_enc, size_t max_len, const char *str);
+
+/* GSM 04.08, 10.5.7.3 GPRS Timer */
+int gprs_tmr_to_secs(uint8_t tmr);
+uint8_t gprs_secs_to_tmr_floor(int secs);
+
 int gprs_is_mi_tmsi(const uint8_t *value, size_t value_len);
 int gprs_is_mi_imsi(const uint8_t *value, size_t value_len);
 int gprs_parse_mi_tmsi(const uint8_t *value, size_t value_len, uint32_t *tmsi);
diff --git a/openbsc/include/openbsc/gsm_04_08_gprs.h b/openbsc/include/openbsc/gsm_04_08_gprs.h
index fb30dff..3eec983 100644
--- a/openbsc/include/openbsc/gsm_04_08_gprs.h
+++ b/openbsc/include/openbsc/gsm_04_08_gprs.h
@@ -116,9 +116,12 @@
 	GPRS_TMR_2SECONDS	= 0 << 5,
 	GPRS_TMR_MINUTE		= 1 << 5,
 	GPRS_TMR_6MINUTE	= 2 << 5,
-	GPRS_TMR_DEACTIVATED	= 3 << 5,
+	GPRS_TMR_DEACTIVATED	= 7 << 5,
 };
 
+#define GPRS_TMR_UNIT_MASK (7 << 5)
+#define GPRS_TMR_FACT_MASK ((1 << 5)-1)
+
 /* Chapter 9.4.2 / Table 9.4.2 */
 struct gsm48_attach_ack {
 	uint8_t att_result:4,	/* 10.5.5.7 */
diff --git a/openbsc/src/gprs/gprs_utils.c b/openbsc/src/gprs/gprs_utils.c
index 55bc629..2293f02 100644
--- a/openbsc/src/gprs/gprs_utils.c
+++ b/openbsc/src/gprs/gprs_utils.c
@@ -20,6 +20,7 @@
  *
  */
 #include <openbsc/gprs_utils.h>
+#include <openbsc/gsm_04_08_gprs.h>
 
 #include <osmocom/core/msgb.h>
 #include <osmocom/gprs/gprs_ns.h>
@@ -172,6 +173,50 @@
 	return len;
 }
 
+/* GSM 04.08, 10.5.7.3 GPRS Timer */
+int gprs_tmr_to_secs(uint8_t tmr)
+{
+	switch (tmr & GPRS_TMR_UNIT_MASK) {
+	case GPRS_TMR_2SECONDS:
+		return 2 * (tmr & GPRS_TMR_FACT_MASK);
+	default:
+	case GPRS_TMR_MINUTE:
+		return 60 * (tmr & GPRS_TMR_FACT_MASK);
+	case GPRS_TMR_6MINUTE:
+		return 360 * (tmr & GPRS_TMR_FACT_MASK);
+	case GPRS_TMR_DEACTIVATED:
+		return -1;
+	}
+}
+
+/* This functions returns a tmr value such that
+ *   - f is monotonic
+ *   - f(s) <= s
+ *   - f(s) == s if a tmr exists with s = gprs_tmr_to_secs(tmr)
+ *   - the best possible resolution is used
+ * where
+ *   f(s) = gprs_tmr_to_secs(gprs_secs_to_tmr_floor(s))
+ */
+uint8_t gprs_secs_to_tmr_floor(int secs)
+{
+	if (secs < 0)
+		return GPRS_TMR_DEACTIVATED;
+	if (secs < 2 * 32)
+		return GPRS_TMR_2SECONDS | (secs / 2);
+	if (secs < 60 * 2)
+		/* Ensure monotonicity */
+		return GPRS_TMR_2SECONDS | GPRS_TMR_FACT_MASK;
+	if (secs < 60 * 32)
+		return GPRS_TMR_MINUTE | (secs / 60);
+	if (secs < 360 * 6)
+		/* Ensure monotonicity */
+		return GPRS_TMR_MINUTE | GPRS_TMR_FACT_MASK;
+	if (secs < 360 * 32)
+		return GPRS_TMR_6MINUTE | (secs / 360);
+
+	return GPRS_TMR_6MINUTE | GPRS_TMR_FACT_MASK;
+}
+
 /* GSM 04.08, 10.5.1.4 */
 int gprs_is_mi_tmsi(const uint8_t *value, size_t value_len)
 {
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;
diff --git a/openbsc/tests/gprs/gprs_test.ok b/openbsc/tests/gprs/gprs_test.ok
index 4825b67..cf71076 100644
--- a/openbsc/tests/gprs/gprs_test.ok
+++ b/openbsc/tests/gprs/gprs_test.ok
@@ -27,4 +27,5 @@
   Testing Purge MS Request
   Testing Purge MS Error
   Testing Purge MS Result
+Test GPRS timer decoding/encoding
 Done.