represent negative T-timers as Osmocom-specific X-timers

fi->T values are int, i.e. can be negative. Do not log them as unsigned, but
define a distinct timer class "Xnnnn" for negative T values: i.e. for T == -1,
print "Timeout of X1" instead of "Timeout of T4294967295".

The negative T timer number space is useful to distinguish freely invented
timers from proper 3GPP defined T numbers. So far I was using numbers like
T993210 or T9999 for invented T, but X1, X2 etc. is a better solution. This way
we can make sure to not accidentally define an invented timer number that
actually collides with a proper 3GPP specified timer number that the author was
not aware of at the time of writing.

Add OSMO_T_FMT and OSMO_T_FMT_ARGS() macros as standardized timer number print
format. Use that in fsm.c, tdef_vty.c, and adjust vty tests accordingly.

Mention the two timer classes in various API docs and VTY online-docs.

Change-Id: I3a59457623da9309fbbda235fe18fadd1636bff6
diff --git a/src/fsm.c b/src/fsm.c
index eb457a1..4876c04 100644
--- a/src/fsm.c
+++ b/src/fsm.c
@@ -108,8 +108,8 @@
 /*! Enable or disable logging of timeout values for FSM instance state changes.
  *
  * By default, state changes are logged by state name only, omitting the timeout. When passing true, each state change
- * will also log the T number and the chosen timeout in seconds. osmo_fsm_inst_state_chg_keep_timer() will log remaining
- * timeout in millisecond precision.
+ * will also log the T number (or Osmocom-specific X number) and the chosen timeout in seconds.
+ * osmo_fsm_inst_state_chg_keep_timer() will log remaining timeout in millisecond precision.
  *
  * The default for this is false to reflect legacy behavior. Since various C tests that verify logging output already
  * existed prior to this option, keeping timeout logging off makes sure that they continue to pass. Particularly,
@@ -205,9 +205,9 @@
 {
 	struct osmo_fsm_inst *fi = data;
 	struct osmo_fsm *fsm = fi->fsm;
-	uint32_t T = fi->T;
+	int32_t T = fi->T;
 
-	LOGPFSM(fi, "Timeout of T%u\n", fi->T);
+	LOGPFSM(fi, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T));
 
 	if (fsm->timer_cb) {
 		int rc = fsm->timer_cb(fi);
@@ -482,13 +482,13 @@
 
 	if (fsm_log_timeouts) {
 		if (keep_timer && fi->timer.active && (osmo_timer_remaining(&fi->timer, NULL, &remaining) == 0))
-			LOGPFSMSRC(fi, file, line, "State change to %s (keeping T%d, %ld.%03lds remaining)\n",
+			LOGPFSMSRC(fi, file, line, "State change to %s (keeping " OSMO_T_FMT ", %ld.%03lds remaining)\n",
 				   osmo_fsm_state_name(fsm, new_state),
-				   fi->T, remaining.tv_sec, remaining.tv_usec / 1000);
+				   OSMO_T_FMT_ARGS(fi->T), remaining.tv_sec, remaining.tv_usec / 1000);
 		else if (timeout_secs && !keep_timer)
-			LOGPFSMSRC(fi, file, line, "State change to %s (T%d, %lus)\n",
+			LOGPFSMSRC(fi, file, line, "State change to %s (" OSMO_T_FMT ", %lus)\n",
 				   osmo_fsm_state_name(fsm, new_state),
-				   T, timeout_secs);
+				   OSMO_T_FMT_ARGS(T), timeout_secs);
 		else
 			LOGPFSMSRC(fi, file, line, "State change to %s (no timeout)\n",
 				   osmo_fsm_state_name(fsm, new_state));
@@ -535,6 +535,10 @@
  *  timer_cb. If passing timeout_secs == 0, it is recommended to also pass T ==
  *  0, so that fi->T is reset to 0 when no timeout is invoked.
  *
+ *  Positive values for T are considered to be 3GPP spec compliant and appear in
+ *  logging and VTY as "T1234", while negative values are considered to be
+ *  Osmocom specific timers, represented in logging and VTY as "X1234".
+ *
  *  See also osmo_tdef_fsm_inst_state_chg() from the osmo_tdef API, which
  *  provides a unified way to configure and apply GSM style Tnnnn timers to FSM
  *  state transitions.
@@ -549,7 +553,9 @@
  *  \param[in] fi FSM instance whose state is to change
  *  \param[in] new_state The new state into which we should change
  *  \param[in] timeout_secs Timeout in seconds (if !=0), maximum-clamped to 2147483647 seconds.
- *  \param[in] T Timer number (if \ref timeout_secs != 0)
+ *  \param[in] T Timer number, where positive numbers are considered to be 3GPP spec compliant timer numbers and are
+ *               logged as "T1234", while negative numbers are considered Osmocom specific timer numbers logged as
+ *               "X1234".
  *  \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
  *  \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
  *  \returns 0 on success; negative on error
diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c
index 1c6af70..04c14b9 100644
--- a/src/vty/tdef_vty.c
+++ b/src/vty/tdef_vty.c
@@ -31,6 +31,7 @@
 #include <osmocom/vty/command.h>
 #include <osmocom/vty/tdef_vty.h>
 #include <osmocom/core/tdef.h>
+#include <osmocom/core/fsm.h>
 
 /*! \addtogroup Tdef_VTY
  *
@@ -40,7 +41,7 @@
  * \file tdef_vty.c
  */
 
-/*! Parse an argument like "T1234", "t1234" or "1234", as from OSMO_TDEF_VTY_ARG_T.
+/*! Parse an argument like "1234", "T1234", "t1234", or "X1234", "x1234", as from OSMO_TDEF_VTY_ARG_T.
  * \param[in] vty  VTY context for vty_out() of error messages.
  * \param[in] tdefs  Array of timer definitions to look up T timer.
  * \param[in] T_str  Argument string. It is not validated, expected to be checked by VTY input.
@@ -53,6 +54,7 @@
 	struct osmo_tdef *t;
 	char *endptr;
 	const char *T_nr_str;
+	int sign = 1;
 
 	if (!tdefs) {
 		vty_out(vty, "%% Error: no timers found%s", VTY_NEWLINE);
@@ -60,20 +62,31 @@
 	}
 
 	T_nr_str = T_str;
-	if (T_nr_str[0] == 't' || T_nr_str[0] == 'T')
+	if (T_nr_str[0] == 't' || T_nr_str[0] == 'T') {
+		sign = 1;
 		T_nr_str++;
+	} else if (T_nr_str[0] == 'x' || T_nr_str[0] == 'X') {
+		T_nr_str++;
+		sign = -1;
+	}
+
+	/* Make sure to disallow any characters changing the signedness of the parsed int */
+	if (T_nr_str[0] < '0' || T_nr_str[0] > '9') {
+		vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE);
+		return NULL;
+	}
 
 	errno = 0;
 	l = strtol(T_nr_str, &endptr, 10);
-	if (errno || *endptr || l > INT_MAX) {
-		vty_out(vty, "%% No such timer: '%s'%s", T_str, VTY_NEWLINE);
+	if (errno || *endptr || l > INT_MAX || l < 0) {
+		vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE);
 		return NULL;
 	}
-	T = l;
+	T = l * sign;
 
 	t = osmo_tdef_get_entry(tdefs, T);
 	if (!t)
-		vty_out(vty, "%% No such timer: T%d%s", T, VTY_NEWLINE);
+		vty_out(vty, "%% No such timer: " OSMO_T_FMT "%s", OSMO_T_FMT_ARGS(T), VTY_NEWLINE);
 	return t;
 }
 
@@ -153,8 +166,8 @@
 	}
 	if (prefix_fmt)
 		vty_out_va(vty, prefix_fmt, va);
-	vty_out(vty, "T%d = %lu%s%s\t%s (default: %lu%s%s)%s",
-		t->T, t->val,
+	vty_out(vty, OSMO_T_FMT " = %lu%s%s\t%s (default: %lu%s%s)%s",
+		OSMO_T_FMT_ARGS(t->T), t->val,
 		t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit),
 		t->desc, t->default_val,
 		t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit),
@@ -227,7 +240,7 @@
 			vty_out_va(vty, prefix_fmt, va);
 			va_end(va);
 		}
-		vty_out(vty, "T%d %lu%s", t->T, t->val, VTY_NEWLINE);
+		vty_out(vty, OSMO_T_FMT " %lu%s", OSMO_T_FMT_ARGS(t->T), t->val, VTY_NEWLINE);
 	}
 }