tdef: Introduce min_val and max_val fields

This is useful for timers expected to have a range of valid or expected
values.

Validation is done at runtime when timer values are set by the app or by
the user through the VTY.

Related: OS#4190
Change-Id: I4661ac41c29a009a1d5fc57d87aaee6041c7d1b2
diff --git a/src/tdef.c b/src/tdef.c
index ab6a51b..94d987f 100644
--- a/src/tdef.c
+++ b/src/tdef.c
@@ -136,14 +136,22 @@
 
 /*! Set all osmo_tdef values to the default_val.
  * It is convenient to define a tdefs array by setting only the default_val, and calling osmo_tdefs_reset() once for
- * program startup. (See also osmo_tdef_vty_init())
+ * program startup. (See also osmo_tdef_vty_init()).
+ * During call to this function, default values are verified to be inside valid range; process is aborted otherwise.
  * \param[in] tdefs  Array of timer definitions, last entry being fully zero.
  */
 void osmo_tdefs_reset(struct osmo_tdef *tdefs)
 {
 	struct osmo_tdef *t;
-	osmo_tdef_for_each(t, tdefs)
+	osmo_tdef_for_each(t, tdefs) {
+		if (!osmo_tdef_val_in_range(t, t->default_val)) {
+			char range_str[64];
+			osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
+			osmo_panic("%s:%d Timer " OSMO_T_FMT " contains default value %lu not in range %s\n",
+				   __FILE__, __LINE__, OSMO_T_FMT_ARGS(t->T), t->default_val, range_str);
+		}
 		t->val = t->default_val;
+	}
 }
 
 /*! Return the value of a T timer from a list of osmo_tdef, in the given unit.
@@ -221,13 +229,55 @@
  */
 int osmo_tdef_set(struct osmo_tdef *tdefs, int T, unsigned long val, enum osmo_tdef_unit val_unit)
 {
+	unsigned long new_val;
 	struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T);
 	if (!t)
 		return -EEXIST;
-	t->val = osmo_tdef_round(val, val_unit, t->unit);
+
+	new_val = osmo_tdef_round(val, val_unit, t->unit);
+	if (!osmo_tdef_val_in_range(t, new_val))
+		return -ERANGE;
+
+	t->val = new_val;
 	return 0;
 }
 
+/*! Check if value new_val is in range of valid possible values for timer entry tdef.
+ * \param[in] tdef  Timer entry from a timer definition table.
+ * \param[in] new_val  The value whose validity to check, in units as per this timer entry.
+ * \return true if inside range, false otherwise.
+ */
+bool osmo_tdef_val_in_range(struct osmo_tdef *tdef, unsigned long new_val)
+{
+	return new_val >= tdef->min_val && (!tdef->max_val || new_val <= tdef->max_val);
+}
+
+/*! Write string representation of osmo_tdef range into buf.
+ * \param[in] buf  The buffer where the string representation is stored.
+ * \param[in] buf_len  Length of buffer in bytes.
+ * \param[in] tdef  Timer entry from a timer definition table.
+ * \return The number of characters printed on success, negative on error. See snprintf().
+ */
+int osmo_tdef_range_str_buf(char *buf, size_t buf_len, struct osmo_tdef *t)
+{
+	int ret, len = 0, offset = 0, rem = buf_len;
+
+	buf[0] = '\0';
+	ret = snprintf(buf + offset, rem, "[%lu .. ", t->min_val);
+	if (ret < 0)
+		return ret;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	if (t->max_val)
+		ret = snprintf(buf + offset, rem, "%lu]", t->max_val);
+	else
+		ret = snprintf(buf + offset, rem, "inf]");
+	if (ret < 0)
+		return ret;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	return ret;
+}
+
 /*! Using osmo_tdef for osmo_fsm_inst: find a given state's osmo_tdef_state_timeout entry.
  *
  * The timeouts_array shall contain exactly 32 elements, regardless whether only some of them are actually populated