diff options
| -rw-r--r-- | include/Makefile.am | 2 | ||||
| -rw-r--r-- | include/osmocom/core/tdef.h | 172 | ||||
| -rw-r--r-- | include/osmocom/vty/tdef_vty.h | 67 | ||||
| -rw-r--r-- | src/Makefile.am | 3 | ||||
| -rw-r--r-- | src/fsm.c | 4 | ||||
| -rw-r--r-- | src/tdef.c | 282 | ||||
| -rw-r--r-- | src/vty/Makefile.am | 3 | ||||
| -rw-r--r-- | src/vty/tdef_vty.c | 372 | ||||
| -rw-r--r-- | tests/Makefile.am | 36 | ||||
| -rw-r--r-- | tests/tdef/tdef_test.c | 445 | ||||
| -rw-r--r-- | tests/tdef/tdef_test.ok | 184 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_config_root.c | 292 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_config_root.vty | 292 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_config_subnode.c | 288 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_config_subnode.vty | 107 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_dynamic.c | 362 | ||||
| -rw-r--r-- | tests/tdef/tdef_vty_test_dynamic.vty | 83 | ||||
| -rw-r--r-- | tests/testsuite.at | 6 | 
18 files changed, 2998 insertions, 2 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index 25a6d75f..17f7d1ce 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -47,6 +47,7 @@ nobase_include_HEADERS = \                         osmocom/core/statistics.h \                         osmocom/core/strrb.h \                         osmocom/core/talloc.h \ +                       osmocom/core/tdef.h \                         osmocom/core/timer.h \                         osmocom/core/timer_compat.h \                         osmocom/core/utils.h \ @@ -154,6 +155,7 @@ nobase_include_HEADERS += \                            osmocom/vty/vector.h \                            osmocom/vty/vty.h \                            osmocom/vty/ports.h \ +                          osmocom/vty/tdef_vty.h \                            osmocom/ctrl/control_vty.h  endif diff --git a/include/osmocom/core/tdef.h b/include/osmocom/core/tdef.h new file mode 100644 index 00000000..92b71597 --- /dev/null +++ b/include/osmocom/core/tdef.h @@ -0,0 +1,172 @@ +/*! \file tdef.h + * API to define Tnnn timers globally and use for FSM state changes. + */ +/* + * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stdint.h> +#include <osmocom/core/utils.h> + +struct osmo_fsm_inst; + +/*! \defgroup Tdef  Tnnn timer configuration + * @{ + * \file tdef.h + */ + +enum osmo_tdef_unit { +	OSMO_TDEF_S = 0,	/*!< most T are in seconds, keep 0 as default. */ +	OSMO_TDEF_MS,		/*!< milliseconds */ +	OSMO_TDEF_M,		/*!< minutes */ +	OSMO_TDEF_CUSTOM,	/*!< unspecified unit, explained in osmo_tdef.desc. */ +}; + +extern const struct value_string osmo_tdef_unit_names[]; +/*! \return enum osmo_tdef_unit value as human readable unit letter, or "custom-unit". */ +static inline const char *osmo_tdef_unit_name(enum osmo_tdef_unit val) +{ return get_value_string(osmo_tdef_unit_names, val); } + +/*! Define a GSM timer of the form Tnnn, with unit, default value and doc string. + * Typically used as an array with the last entry being left zero-initialized, e.g.: + * + *         struct osmo_tdef tdefs[] = { + *                 { .T=10, .default_val=6, .desc="RR Assignment" }, + *                 { .T=101, .default_val=10, .desc="inter-BSC Handover MT, HO Request to HO Accept" }, + *                 { .T=3101, .default_val=3, .desc="RR Immediate Assignment" }, + *                 {} + *         }; + * + * Program initialization should call osmo_tdefs_reset() so that all timers return the default_val, until e.g. the VTY + * configuration sets user-defined values (see osmo_tdef_vty_init()). + */ +struct osmo_tdef { +	/*! T1234 number; type corresponds to struct osmo_fsm_inst.T. Negative and zero T numbers are actually possible, +	 * but be aware that osmo_tdef_fsm_inst_state_chg() interprets T == 0 as "no timer". */ +	const int T; +	/*! Timeout duration (according to unit), default value; type corresponds to osmo_fsm_inst_state_chg()'s +	 * timeout_secs argument. Note that osmo_fsm_inst_state_chg() clamps the range. */ +	const unsigned long default_val; +	const enum osmo_tdef_unit unit; +	/*! Human readable description. For unit == OSMO_TDEF_CUSTOM, this should include an explanation of the value's +	 * unit. Best keep this a short one-liner (e.g. for VTY output). */ +	const char *desc; +	/*! Currently active timeout value, e.g. set by user config. This is the only mutable member: a user may +	 * configure the timeout value, but neither unit nor any other field. */ +	unsigned long val; +}; + +/*! Iterate an array of struct osmo_tdef, the last item should be fully zero, i.e. "{}". + * Example: + * + *     struct osmo_tdef *t; + *     osmo_tdef_for_each(t, tdefs) { + *             printf("%lu %s %s\n", t->val, osmo_tdef_unit_name(t->unit), t->desc); + *     } + * + * \param[inout] t  A struct osmo_tdef *t used for iteration, will point at the current entry inside the loop scope. + * \param[in] tdefs  Array of struct osmo_tdef to iterate, zero-terminated. + */ +#define osmo_tdef_for_each(t, tdefs) \ +	for (t = tdefs; t && (t->T || t->default_val || t->desc); t++) + +void osmo_tdefs_reset(struct osmo_tdef *tdefs); +unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit, +			    unsigned long val_if_not_present); +struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T); + +/*! Using osmo_tdef for osmo_fsm_inst: array entry for a mapping of state numbers to timeout definitions. + * For a usage example, see osmo_tdef_get_state_timeout() and test_tdef_state_timeout() in tdef_test.c. */ +struct osmo_tdef_state_timeout { +	/*! Timer number to match struct osmo_tdef.T, and to pass to osmo_fsm_inst_state_chg(). */ +	int T; +	/*! If true, call osmo_fsm_inst_state_chg_keep_timer(). +	 * If T == 0, keep previous T number, otherwise also set fi->T. */ +	bool keep_timer; +}; + +const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state, +								  const struct osmo_tdef_state_timeout *timeouts_array); + +/*! Call osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer(), depending on the timeouts_array, tdefs and + * default_timeout. + * + * A T timer configured in sub-second precision is rounded up to the next full second. A timer in unit = + * OSMO_TDEF_CUSTOM is applied as if the unit is in seconds (i.e. this macro does not make sense for custom units!). + * + * See osmo_tdef_get_state_timeout() and osmo_tdef_get(). + * + * If no T timer is defined for the given state (T == 0), invoke the state change without a timeout. + * + * Should a T number be defined in timeouts_array that is not defined in tdefs, use default_timeout (in seconds). If + * default_timeout is negative, a missing T definition in tdefs instead causes a program abort. + * + * This is best used by wrapping this function call in a macro suitable for a specific FSM implementation, which can + * become as short as: my_fsm_state_chg(fi, NEXT_STATE): + * + *     #define my_fsm_state_chg(fi, NEXT_STATE) \ + *             osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, my_fsm_timeouts, global_T_defs, 5) + * + *     my_fsm_state_chg(fi, MY_FSM_STATE_1); + *     // -> No timeout configured, will enter state without timeout. + * + *     my_fsm_state_chg(fi, MY_FSM_STATE_3); + *     // T423 configured for this state, will look up T423 in tdefs, or use 5 seconds if unset. + * + *     my_fsm_state_chg(fi, MY_FSM_STATE_8); + *     // keep_timer == true for this state, will invoke osmo_fsm_inst_state_chg_keep_timer(). + * + * \param[inout] fi  osmo_fsm_inst to transition to another state. + * \param[in] state  State number to transition to. + * \param[in] timeouts_array  Array of struct osmo_tdef_state_timeout[32] to look up state in. + * \param[in] tdefs  Array of struct osmo_tdef (last entry zero initialized) to look up T in. + * \param[in] default_timeout  If a T is set in timeouts_array, but no timeout value is configured for T, then use this + *                             default timeout value as fallback, or pass -1 to abort the program. + * \return Return value from osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer(). + */ +#define osmo_tdef_fsm_inst_state_chg(fi, state, timeouts_array, tdefs, default_timeout) \ +	_osmo_tdef_fsm_inst_state_chg(fi, state, timeouts_array, tdefs, default_timeout, \ +				      __FILE__, __LINE__) +int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state, +				  const struct osmo_tdef_state_timeout *timeouts_array, +				  const struct osmo_tdef *tdefs, unsigned long default_timeout, +				  const char *file, int line); + +/*! Manage timer definitions in named groups. + * This should be defined as an array with the final element kept fully zero-initialized, + * to be compatible with osmo_tdef_vty* API. There must not be any tdefs == NULL entries except on the final + * zero-initialized entry. */ +struct osmo_tdef_group { +	const char *name; +	const char *desc; +	struct osmo_tdef *tdefs; +}; + +/*! Iterate an array of struct osmo_tdef_group, the last item should be fully zero, i.e. "{}". + * \param[inout] g  A struct osmo_tdef_group *g used for iteration, will point at the current entry inside the loop scope. + * \param[in] tdefs  Array of struct osmo_tdef_group to iterate, zero-terminated. + */ +#define osmo_tdef_groups_for_each(g, tdef_groups) \ +	for (g = tdef_groups; g && g->tdefs; g++) + +/*! @} */ diff --git a/include/osmocom/vty/tdef_vty.h b/include/osmocom/vty/tdef_vty.h new file mode 100644 index 00000000..f55239ad --- /dev/null +++ b/include/osmocom/vty/tdef_vty.h @@ -0,0 +1,67 @@ +/*! \file tdef_vty.h + * API to configure osmo_tdef Tnnn timers from VTY configuration. + */ +/* (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stdint.h> + +struct vty; + +/*! \defgroup Tdef_VTY  Tnnn timer VTY configuration + * @{ + * \file tdef_vty.h + */ + +struct osmo_tdef; +struct osmo_tdef_group; + +#define OSMO_TDEF_VTY_ARG_T "TNNNN" +#define OSMO_TDEF_VTY_DOC_T "T-number, optionally preceded by 't' or 'T'.\n" +#define OSMO_TDEF_VTY_ARG_T_OPTIONAL "[" OSMO_TDEF_VTY_ARG_T "]" + +#define OSMO_TDEF_VTY_ARG_VAL "(<0-2147483647>|default)" +#define OSMO_TDEF_VTY_DOC_VAL "New timer value\n" "Set to default timer value\n" +#define OSMO_TDEF_VTY_ARG_VAL_OPTIONAL "[" OSMO_TDEF_VTY_ARG_VAL "]" + +#define OSMO_TDEF_VTY_ARG_SET	OSMO_TDEF_VTY_ARG_T " " OSMO_TDEF_VTY_ARG_VAL +#define OSMO_TDEF_VTY_DOC_SET	OSMO_TDEF_VTY_DOC_T OSMO_TDEF_VTY_DOC_VAL +#define OSMO_TDEF_VTY_ARG_SET_OPTIONAL	OSMO_TDEF_VTY_ARG_T_OPTIONAL " " OSMO_TDEF_VTY_ARG_VAL_OPTIONAL + +int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args); +int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char *T_arg, +			   const char *prefix_fmt, ...); +void osmo_tdef_vty_write(struct vty *vty, struct osmo_tdef *tdefs, +			 const char *prefix_fmt, ...); + +void osmo_tdef_vty_out_one(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, ...); +void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...); + +void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va); +void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, va_list va); + +struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *osmo_tdef_str); +unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val); + +void osmo_tdef_vty_groups_init(enum node_type parent_node, struct osmo_tdef_group *groups); +void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent); + +/*! @} */ diff --git a/src/Makefile.am b/src/Makefile.am index 6840f798..27ab7026 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,8 @@ libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \  			 loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \  			 macaddr.c stat_item.c stats.c stats_statsd.c prim.c \  			 conv_acc.c conv_acc_generic.c sercomm.c prbs.c \ -			 isdnhdlc.c +			 isdnhdlc.c \ +			 tdef.c  if HAVE_SSSE3  libosmocore_la_SOURCES += conv_acc_sse.c @@ -498,6 +498,10 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,   *  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.   * + *  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. + *   *  Range: since time_t's maximum value is not well defined in a cross platform   *  way, clamp timeout_secs to the maximum of the signed 32bit range, or roughly   *  68 years (float(0x7fffffff) / (60. * 60 * 24 * 365.25) = 68.0497). Thus diff --git a/src/tdef.c b/src/tdef.c new file mode 100644 index 00000000..7e79d680 --- /dev/null +++ b/src/tdef.c @@ -0,0 +1,282 @@ +/*! \file tdef.c + * Implementation to define Tnnn timers globally and use for FSM state changes. + */ +/* + * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include <limits.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> + +/*! \addtogroup Tdef + * + * Implementation to define Tnnn timers globally and use for FSM state changes. + * + * See also \ref Tdef_VTY + * + * osmo_tdef provides: + * + * - a list of Tnnnn (GSM) timers with description, unit and default value. + * - vty UI to allow users to configure non-default timeouts. + * - API to tie T timers to osmo_fsm states and set them on state transitions. + * + * - a few standard units (minute, second, millisecond) as well as a custom unit + *   (which relies on the timer's human readable description to indicate the + *   meaning of the value). + * - conversion for standard units: for example, some GSM timers are defined in + *   minutes, while our FSM definitions need timeouts in seconds. Conversion is + *   for convenience only and can be easily avoided via the custom unit. + * + * By keeping separate osmo_tdef arrays, several groups of timers can be kept + * separately. The VTY tests in tests/tdef/ showcase different schemes: + * + * - \ref tests/vty/tdef_vty_test_config_root.c: + *   Keep several timer definitions in separately named groups: showcase the + *   osmo_tdef_vty_groups*() API. Each timer group exists exactly once. + * + * - \ref tests/vty/tdef_vty_test_config_subnode.c: + *   Keep a single list of timers without separate grouping. + *   Put this list on a specific subnode below the CONFIG_NODE. + *   There could be several separate subnodes with timers like this, i.e. + *   continuing from this example, sets of timers could be separated by placing + *   timers in specific config subnodes instead of using the global group name. + * + * - \ref tests/vty/tdef_vty_test_dynamic.c: + *   Dynamically allocate timer definitions per each new created object. + *   Thus there can be an arbitrary number of independent timer definitions, one + *   per allocated object. + * + * osmo_tdef was introduced because: + * + * - without osmo_tdef, each invocation of osmo_fsm_inst_state_chg() needs to be + *   programmed with the right timeout value, for all code paths that invoke this + *   state change. It is a likely source of errors to get one of them wrong.  By + *   defining a T timer exactly for an FSM state, the caller can merely invoke the + *   state change and trust on the original state definition to apply the correct + *   timeout. + * + * - it is helpful to have a standardized config file UI to provide user + *   configurable timeouts, instead of inventing new VTY commands for each + *   separate application of T timer numbers. See \ref tdef_vty.h. + * + * @{ + * \file tdef.c + */ + +/*! a = return_val * b. \return 0 if factor is below 1. */ +static unsigned long osmo_tdef_factor(enum osmo_tdef_unit a, enum osmo_tdef_unit b) +{ +	if (b == a +	    || b == OSMO_TDEF_CUSTOM || a == OSMO_TDEF_CUSTOM) +		return 1; + +	switch (b) { +	case OSMO_TDEF_MS: +		switch (a) { +		case OSMO_TDEF_S: +			return 1000; +		case OSMO_TDEF_M: +			return 60*1000; +		default: +			return 0; +		} +	case OSMO_TDEF_S: +		switch (a) { +		case OSMO_TDEF_M: +			return 60; +		default: +			return 0; +		} +	default: +		return 0; +	} +} + +/*! \return val in unit to_unit, rounded up to the next integer value and clamped to ULONG_MAX, or 0 if val == 0. */ +static unsigned long osmo_tdef_round(unsigned long val, enum osmo_tdef_unit from_unit, enum osmo_tdef_unit to_unit) +{ +	unsigned long f; +	if (!val) +		return 0; + +	f = osmo_tdef_factor(from_unit, to_unit); +	if (f == 1) +		return val; +	if (f < 1) { +		f = osmo_tdef_factor(to_unit, from_unit); +		return (val / f) + (val % f? 1 : 0); +	} +	/* range checking */ +	if (f > (ULONG_MAX / val)) +		return ULONG_MAX; +	return val * f; +} + +/*! 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()) + * \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) +		t->val = t->default_val; +} + +/*! Return the value of a T timer from a list of osmo_tdef, in the given unit. + * If no such timer is defined, return the default value passed, or abort the program if default < 0. + * + * Round up any value match as_unit: 1100 ms as OSMO_TDEF_S becomes 2 seconds, as OSMO_TDEF_M becomes one minute. + * However, always return a value of zero as zero (0 ms as OSMO_TDEF_M still is 0 m). + * + * Range: even though the value range is unsigned long here, in practice, using ULONG_MAX as value for a timeout in + * seconds may actually wrap to negative or low timeout values (e.g. in struct timeval). It is recommended to stay below + * INT_MAX seconds. See also osmo_fsm_inst_state_chg(). + * + * Usage example: + * + * 	struct osmo_tdef global_T_defs[] = { + * 		{ .T=7, .default_val=50, .desc="Water Boiling Timeout" },  // default is .unit=OSMO_TDEF_S == 0 + * 		{ .T=8, .default_val=300, .desc="Tea brewing" }, + * 		{ .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" }, + * 		{ .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" }, + * 		{}  //  <-- important! last entry shall be zero + * 	}; + * 	osmo_tdefs_reset(global_T_defs); // make all values the default + * 	osmo_tdef_vty_init(global_T_defs, CONFIG_NODE); + * + * 	val = osmo_tdef_get(global_T_defs, 7, OSMO_TDEF_S, -1); // -> 50 + * 	sleep(val); + * + * 	val = osmo_tdef_get(global_T_defs, 7, OSMO_TDEF_M, -1); // 50 seconds becomes 1 minute -> 1 + * 	sleep_minutes(val); + * + * 	val = osmo_tdef_get(global_T_defs, 99, OSMO_TDEF_S, 3); // not defined, returns 3 + * + * 	val = osmo_tdef_get(global_T_defs, 99, OSMO_TDEF_S, -1); // not defined, program aborts! + * + * \param[in] tdefs  Array of timer definitions, last entry must be fully zero initialized. + * \param[in] T  Timer number to get the value for. + * \param[in] as_unit  Return timeout value in this unit. + * \param[in] val_if_not_present  Fallback value to return if no timeout is defined. + * \return Timeout value in the unit given by as_unit, rounded up if necessary, or val_if_not_present. + */ +unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit, unsigned long val_if_not_present) +{ +	const struct osmo_tdef *t = osmo_tdef_get_entry((struct osmo_tdef*)tdefs, T); +	if (!t) { +		OSMO_ASSERT(val_if_not_present >= 0); +		return val_if_not_present; +	} +	return osmo_tdef_round(t->val, t->unit, as_unit); +} + +/*! Find tdef entry matching T. + * This is useful for manipulation, which is usually limited to the VTY configuration. To retrieve a timeout value, + * most callers probably should use osmo_tdef_get() instead. + * \param[in] tdefs  Array of timer definitions, last entry being fully zero. + * \param[in] T  Timer number to get the entry for. + * \return osmo_tdef entry matching T in given array, or NULL if no match is found. + */ +struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T) +{ +	struct osmo_tdef *t; +	osmo_tdef_for_each(t, tdefs) { +		if (t->T == T) +			return t; +	} +	return NULL; +} + +/*! 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 + * with nonzero values. 32 corresponds to the number of states allowed by the osmo_fsm_* API. Lookup is by array index. + * Not populated entries imply a state change invocation without timeout. + * + * For example: + * + * 	struct osmo_tdef_state_timeout my_fsm_timeouts[32] = { + * 		[MY_FSM_STATE_3] = { .T = 423 }, // look up timeout configured for T423 + * 		[MY_FSM_STATE_7] = { .T = 235 }, + * 		[MY_FSM_STATE_8] = { .keep_timer = true }, // keep previous state's T number, continue timeout. + * 		// any state that is omitted will remain zero == no timeout + *	}; + *	osmo_tdef_get_state_timeout(MY_FSM_STATE_0, &my_fsm_timeouts) -> NULL, + *	osmo_tdef_get_state_timeout(MY_FSM_STATE_7, &my_fsm_timeouts) -> { .T = 235 } + * + * The intention is then to obtain the timer like osmo_tdef_get(global_T_defs, T=235); see also + * fsm_inst_state_chg_T() below. + * + * \param[in] state  State constant to look up. + * \param[in] timeouts_array  Array[32] of struct osmo_tdef_state_timeout defining which timer number to use per state. + * \return A struct osmo_tdef_state_timeout entry, or NULL if that entry is zero initialized. + */ +const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state, const struct osmo_tdef_state_timeout *timeouts_array) +{ +	const struct osmo_tdef_state_timeout *t; +	OSMO_ASSERT(state < 32); +	t = &timeouts_array[state]; +	if (!t->keep_timer && !t->T) +		return NULL; +	return t; +} + +/*! See invocation macro osmo_tdef_fsm_inst_state_chg() instead. + * \param[in] file  Source file name, like __FILE__. + * \param[in] line  Source file line number, like __LINE__. + */ +int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state, +				  const struct osmo_tdef_state_timeout *timeouts_array, +				  const struct osmo_tdef *tdefs, unsigned long default_timeout, +				  const char *file, int line) +{ +	const struct osmo_tdef_state_timeout *t = osmo_tdef_get_state_timeout(state, timeouts_array); +	unsigned long val; + +	/* No timeout defined for this state? */ +	if (!t) +		return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line); + +	if (t->keep_timer) { +		int rc = _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line); +		if (t->T && !rc) +			fi->T = t->T; +		return rc; +	} + +	val = osmo_tdef_get(tdefs, t->T, OSMO_TDEF_S, default_timeout); +	return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line); +} + +const struct value_string osmo_tdef_unit_names[] = { +	{ OSMO_TDEF_S, "s" }, +	{ OSMO_TDEF_MS, "ms" }, +	{ OSMO_TDEF_M, "m" }, +	{ OSMO_TDEF_CUSTOM, "custom-unit" }, +	{} +}; + +/*! @} */ diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am index 2e494982..cdde0fa5 100644 --- a/src/vty/Makefile.am +++ b/src/vty/Makefile.am @@ -11,7 +11,8 @@ lib_LTLIBRARIES = libosmovty.la  libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \  			telnet_interface.c logging_vty.c stats_vty.c \ -			fsm_vty.c talloc_ctx_vty.c +			fsm_vty.c talloc_ctx_vty.c \ +			tdef_vty.c  libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined  libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)  endif diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c new file mode 100644 index 00000000..1c6af70a --- /dev/null +++ b/src/vty/tdef_vty.c @@ -0,0 +1,372 @@ +/*! \file tdef_vty.c + * Implementation to configure osmo_tdef Tnnn timers from VTY configuration. + */ +/* (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <limits.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/tdef_vty.h> +#include <osmocom/core/tdef.h> + +/*! \addtogroup Tdef_VTY + * + * VTY API for \ref Tdef. + * + * @{ + * \file tdef_vty.c + */ + +/*! Parse an argument like "T1234", "t1234" or "1234", 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. + * \return the corresponding osmo_tdef entry from the tdefs array, or NULL if no such entry exists. + */ +struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str) +{ +	long l; +	int T; +	struct osmo_tdef *t; +	char *endptr; +	const char *T_nr_str; + +	if (!tdefs) { +		vty_out(vty, "%% Error: no timers found%s", VTY_NEWLINE); +		return NULL; +	} + +	T_nr_str = T_str; +	if (T_nr_str[0] == 't' || T_nr_str[0] == 'T') +		T_nr_str++; + +	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); +		return NULL; +	} +	T = l; + +	t = osmo_tdef_get_entry(tdefs, T); +	if (!t) +		vty_out(vty, "%% No such timer: T%d%s", T, VTY_NEWLINE); +	return t; +} + +/*! Parse an argument of the form "(0-2147483647|default)", as from OSMO_TDEF_VTY_ARG_VAL. + * \param[in] val_arg  Argument string (not format checked). + * \param[in] default_val  Value to return in case of val_arg being "default". + * \return Parsed value or default_val. + */ +unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val) +{ +        if (!strcmp(val_arg, "default")) +                return default_val; +	return atoll(val_arg); +} + +/*! Apply a timer configuration from VTY argument strings. + * Employ both osmo_tdef_vty_parse_T_arg() and osmo_tdef_vty_parse_val_arg() to configure a T timer in an array of + * tdefs. Evaluate two arguments, a "T1234" argument and a "(0-2147483647|default)" argument, as from + * OSMO_TDEF_VTY_ARGS.  If the T timer given in the first argument is found in tdefs, set it to the value given in the + * second argument. + * \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] args  Array of string arguments like { "T1234", "23" }. + * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. + */ +int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args) +{ +	const char *T_arg = args[0]; +	const char *val_arg = args[1]; +	struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); +	if (!t) +		return CMD_WARNING; +	t->val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val); +	return CMD_SUCCESS; +} + +/*! Output one or all timers to the VTY, as for a VTY command like 'show timer [TNNNN]'. + * If T_arg is NULL, print all timers in tdefs to the VTY. + * If T_arg is not NULL, employ osmo_tdef_vty_parse_T_arg() to select one timer from tdefs and print only that to the + * VTY. + * \param[in] vty  VTY context for vty_out() of error messages. + * \param[in] tdefs  Array of timer definitions. + * \param[in] T_arg  Argument string like "T1234", or NULL. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable printf like arguments. + * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. + */ +int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char *T_arg, +			   const char *prefix_fmt, ...) +{ +	va_list va; +	if (T_arg) { +		struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); +		if (!t) +			return CMD_WARNING; +		va_start(va, prefix_fmt); +		osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); +		va_end(va); +	} else { +		va_start(va, prefix_fmt); +		osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); +		va_end(va); +	} +	return CMD_SUCCESS; +} + +/*! Write to VTY the current status of one timer. + * \param[in] vty  VTY context for vty_out(). + * \param[in] t  The timer to print. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable vprintf like arguments. + * \param[in] va  va_list instance. As always, call va_start() before, and va_end() after this call. + */ +void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va) +{ +	if (!t) { +		vty_out(vty, "%% Error: no such timer%s", VTY_NEWLINE); +		return; +	} +	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, +		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), +		VTY_NEWLINE); +} + +/*! Write to VTY the current status of one timer. + * \param[in] vty  VTY context for vty_out(). + * \param[in] t  The timer to print. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable printf like arguments. + */ +void osmo_tdef_vty_out_one(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, ...) +{ +	va_list va; +	va_start(va, prefix_fmt); +	osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); +	va_end(va); +} + +/*! Write to VTY the current status of all given timers. + * \param[in] vty  VTY context for vty_out(). + * \param[in] tdefs  Array of timers to print, ended with a fully zero-initialized entry. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable vprintf like arguments. + * \param[in] va  va_list instance. As always, call va_start() before, and va_end() after this call. + */ +void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, va_list va) +{ +	struct osmo_tdef *t; +	if (!tdefs) { +		vty_out(vty, "%% Error: no such timers%s", VTY_NEWLINE); +		return; +	} +	osmo_tdef_for_each(t, tdefs) { +		va_list va2; +		va_copy(va2, va); +		osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); +		va_end(va2); +	} +} + +/*! Write to VTY the current status of all given timers. + * \param[in] vty  VTY context for vty_out(). + * \param[in] tdefs  Array of timers to print, ended with a fully zero-initialized entry. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable printf like arguments. + */ +void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) +{ +	va_list va; +	va_start(va, prefix_fmt); +	osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); +	va_end(va); +} + +/*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value. + * The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using. + * See tdef_vty_test_config_subnode.c and tdef_vty_test_dynamic.c for examples. + * \param[in] vty  VTY context. + * \param[in] tdefs  Array of timers to print, ended with a fully zero-initialized entry. + * \param[in] prefix_fmt  Arbitrary string to start each line with, with variable printf like arguments. + */ +void osmo_tdef_vty_write(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) +{ +	va_list va; +	struct osmo_tdef *t; +	osmo_tdef_for_each(t, tdefs) { +		if (t->val == t->default_val) +			continue; +		if (prefix_fmt && *prefix_fmt) { +			va_start(va, prefix_fmt); +			vty_out_va(vty, prefix_fmt, va);  | 
