add osmo_tdef API, originally adopted from osmo-bsc T_def

Move T_def from osmo-bsc to libosmocore as osmo_tdef. Adjust naming to be more
consistent. Upgrade to first class API:
- add timer grouping
- add generic vty support
- add mising API doc
- add C test
- add VTY transcript tests, also as examples for using the API

From osmo_fsm_inst_state_chg() API doc, cross reference to osmo_tdef API.

The root reason for moving to libosmocore is that I want to use the
mgw_endpoint_fsm in osmo-msc for inter-MSC handover, and hence want to move the
FSM to libosmo-mgcp-client. This FSM uses the T_def from osmo-bsc. Though the
mgw_endpoint_fsm's use of T_def is minimal, I intend to use the osmo_tdef API
in osmo-msc (and probably elsewhere) as well. libosmocore is the most sensible
place for this.

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:

- 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.

- 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 timers could be separated by placing
  timers in specific config subnodes instead of using the global group name.

- 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.

T_def was introduced during the recent osmo-bsc refactoring for inter-BSC
handover, and has proven useful:

- 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.

Change-Id: Ibd6b1ed7f1bd6e1f2e0fde53352055a4468f23e5
This commit is contained in:
Neels Hofmeyr 2019-01-26 20:36:12 +01:00
parent 44c15b7a8f
commit 0fd615fd7b
18 changed files with 2998 additions and 2 deletions

View File

@ -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

172
include/osmocom/core/tdef.h Normal file
View File

@ -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++)
/*! @} */

View File

@ -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);
/*! @} */

View File

@ -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

View File

@ -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

282
src/tdef.c Normal file
View File

@ -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" },
{}
};
/*! @} */

View File

@ -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

372
src/vty/tdef_vty.c Normal file
View File

@ -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);
va_end(va);
}
vty_out(vty, "T%d %lu%s", t->T, t->val, VTY_NEWLINE);
}
}
/*! Singleton Tnnn groups definition as set by osmo_tdef_vty_groups_init(). */
static struct osmo_tdef_group *global_tdef_groups;
/*! \return true iff the first characters of str fully match startswith_str or both are empty. */
static bool startswith(const char *str, const char *startswith_str)
{
if (!startswith_str)
return true;
if (!str)
return false;
return strncmp(str, startswith_str, strlen(startswith_str)) == 0;
}
DEFUN(show_timer, show_timer_cmd, "DYNAMIC", "DYNAMIC")
/* show timer [(alpha|beta|gamma)] [TNNNN] */
{
const char *group_arg = argc > 0 ? argv[0] : NULL;
const char *T_arg = argc > 1 ? argv[1] : NULL;
struct osmo_tdef_group *g;
/* The argument should be either "tea" or "software", but the VTY also allows partial arguments
* like "softw" or "t" (which can also be ambiguous). */
osmo_tdef_groups_for_each(g, global_tdef_groups) {
if (!group_arg || startswith(g->name, group_arg))
osmo_tdef_vty_show_cmd(vty, g->tdefs, T_arg, "%s: ", g->name);
}
return CMD_SUCCESS;
}
DEFUN(cfg_timer, cfg_timer_cmd, "DYNAMIC", "DYNAMIC")
/* show timer [(alpha|beta|gamma)] [TNNNN] [(<0-2147483647>|default)] */
{
const char *group_arg;
const char **timer_args;
struct osmo_tdef *tdefs = NULL;
struct osmo_tdef_group *g = NULL;
/* If any arguments are missing, redirect to 'show' */
if (argc < 3)
return show_timer(self, vty, argc, argv);
/* If all arguments are passed, this is configuring a timer. */
group_arg = argc > 0 ? argv[0] : NULL;
timer_args = argv + 1;
osmo_tdef_groups_for_each(g, global_tdef_groups) {
if (strcmp(g->name, group_arg))
continue;
if (tdefs) {
vty_out(vty, "%% Error: ambiguous timer group match%s", VTY_NEWLINE);
return CMD_WARNING;
}
tdefs = g->tdefs;
}
return osmo_tdef_vty_set_cmd(vty, tdefs, timer_args);
}
static char *add_group_args(void *talloc_ctx, char *dest)
{
struct osmo_tdef_group *g;
osmo_talloc_asprintf(talloc_ctx, dest, "[(");
osmo_tdef_groups_for_each(g, global_tdef_groups) {
osmo_talloc_asprintf(talloc_ctx, dest, "%s%s",
(g == global_tdef_groups) ? "" : "|",
g->name);
}
osmo_talloc_asprintf(talloc_ctx, dest, ")]");
return dest;
}
static char *add_group_docs(void *talloc_ctx, char *dest)
{
struct osmo_tdef_group *g;
osmo_tdef_groups_for_each(g, global_tdef_groups) {
osmo_talloc_asprintf(talloc_ctx, dest, "%s\n", g->desc);
}
return dest;
}
static char *timer_command_string(const char *prefix, const char *suffix)
{
char *dest = NULL;
osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix);
dest = add_group_args(tall_vty_cmd_ctx, dest);
osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix);
return dest;
}
static char *timer_doc_string(const char *prefix, const char *suffix)
{
char *dest = NULL;
osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix);
dest = add_group_docs(tall_vty_cmd_ctx, dest);
osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix);
return dest;
}
/*! Convenience implementation for keeping a fixed set of timer groups in a program.
* Install a 'timer [(group|names|...)] [TNNN] [(<val>|default)]' command under the given parent_node,
* and install a 'show timer...' command on VIEW_NODE and ENABLE_NODE.
* For a usage example, see \ref tdef_test_config_root.c.
* The given timer definitions group is stored in a global pointer, so this can be done only once per main() scope.
* It would also be possible to have distinct timer groups on separate VTY subnodes, with a "manual" implementation, but
* not with this API.
* \param[in] parent_node VTY node id at which to add the timer group commands, e.g. CONFIG_NODE.
* \param[in] groups Global timer groups definition.
*/
void osmo_tdef_vty_groups_init(enum node_type parent_node, struct osmo_tdef_group *groups)
{
struct osmo_tdef_group *g;
OSMO_ASSERT(!global_tdef_groups);
global_tdef_groups = groups;
osmo_tdef_groups_for_each(g, global_tdef_groups)
osmo_tdefs_reset(g->tdefs);
show_timer_cmd.string = timer_command_string("show timer", OSMO_TDEF_VTY_ARG_T_OPTIONAL);
show_timer_cmd.doc = timer_doc_string(SHOW_STR "Show timers\n", OSMO_TDEF_VTY_DOC_T);
cfg_timer_cmd.string = timer_command_string("timer", OSMO_TDEF_VTY_ARG_SET_OPTIONAL);
cfg_timer_cmd.doc = timer_doc_string("Configure or show timers\n", OSMO_TDEF_VTY_DOC_SET);
install_element_ve(&show_timer_cmd);
install_element(parent_node, &cfg_timer_cmd);
}
/*! Write the global osmo_tdef_group configuration to VTY, as previously passed to osmo_tdef_vty_groups_init().
* \param[in] vty VTY context.
* \param[in] indent String to print before each line.
*/
void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent)
{
struct osmo_tdef_group *g;
osmo_tdef_groups_for_each(g, global_tdef_groups)
osmo_tdef_vty_write(vty, g->tdefs, "%stimer %s ", indent ? : "", g->name);
}
/*! @} */

View File

@ -27,6 +27,9 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
oap/oap_client_test gsm29205/gsm29205_test \
logging/logging_vty_test \
vty/vty_transcript_test \
tdef/tdef_test tdef/tdef_vty_test_config_root \
tdef/tdef_vty_test_config_subnode \
tdef/tdef_vty_test_dynamic \
$(NULL)
if ENABLE_MSGFILE
@ -221,6 +224,18 @@ prbs_prbs_test_SOURCES = prbs/prbs_test.c
gsm23003_gsm23003_test_SOURCES = gsm23003/gsm23003_test.c
gsm23003_gsm23003_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
tdef_tdef_test_SOURCES = tdef/tdef_test.c
tdef_tdef_test_LDADD = $(LDADD)
tdef_tdef_vty_test_config_root_SOURCES = tdef/tdef_vty_test_config_root.c
tdef_tdef_vty_test_config_root_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
tdef_tdef_vty_test_config_subnode_SOURCES = tdef/tdef_vty_test_config_subnode.c
tdef_tdef_vty_test_config_subnode_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
tdef_tdef_vty_test_dynamic_SOURCES = tdef/tdef_vty_test_dynamic.c
tdef_tdef_vty_test_dynamic_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
:;{ \
@ -284,6 +299,10 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
timer/clk_override_test.ok \
oap/oap_client_test.ok oap/oap_client_test.err \
vty/vty_transcript_test.vty \
tdef/tdef_test.ok \
tdef/tdef_vty_test_config_root.vty \
tdef/tdef_vty_test_config_subnode.vty \
tdef/tdef_vty_test_dynamic.vty \
$(NULL)
DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c
@ -328,6 +347,7 @@ endif
# To update the VTY script from current application behavior,
# pass -u to osmo_verify_transcript_vty.py by doing:
# make vty-test U=-u
vty-test-logging:
osmo_verify_transcript_vty.py -v \
-p 42042 \
@ -340,9 +360,25 @@ vty-test-vty:
-r "$(top_builddir)/tests/vty/vty_transcript_test" \
$(U) $(srcdir)/vty/*.vty
vty-test-tdef:
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/tdef/tdef_vty_test_config_root" \
$(U) $(srcdir)/tdef/tdef_vty_test_config_root.vty
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/tdef/tdef_vty_test_config_subnode" \
$(U) $(srcdir)/tdef/tdef_vty_test_config_subnode.vty
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/tdef/tdef_vty_test_dynamic" \
$(U) $(srcdir)/tdef/tdef_vty_test_dynamic.vty
# don't run vty tests concurrently so that the ports don't conflict
vty-test:
$(MAKE) vty-test-logging
$(MAKE) vty-test-vty
$(MAKE) vty-test-tdef
ctrl-test:
echo "No CTRL tests exist currently"

445
tests/tdef/tdef_test.c Normal file
View File

@ -0,0 +1,445 @@
/* Test implementation for osmo_tdef API. */
/*
* (C) 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 General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
static void *ctx = NULL;
static struct osmo_tdef tdefs[] = {
{ .T=1, .default_val=100, .desc="100s" },
{ .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="100ms" },
{ .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" },
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" },
{ .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" },
/* test conversions */
{ .T=1000, .default_val=2*1000, .unit=OSMO_TDEF_MS, .desc="two seconds from ms" },
{ .T=1001, .default_val=60*1000, .unit=OSMO_TDEF_MS, .desc="one minute from ms" },
{ .T=1002, .default_val=(ULONG_MAX/60), .unit=OSMO_TDEF_M, .desc="almost too many seconds" },
{ .T=1003, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="too many seconds" },
{ .T=1004, .default_val=1, .unit=OSMO_TDEF_MS, .desc="one ms" },
{ .T=1005, .default_val=0, .unit=OSMO_TDEF_MS, .desc="zero ms" },
{ .T=1006, .default_val=0, .unit=OSMO_TDEF_S, .desc="zero s" },
{ .T=1007, .default_val=0, .unit=OSMO_TDEF_M, .desc="zero m" },
{ .T=1008, .default_val=0, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
/* test range */
{ .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" },
{ .T=INT_MAX-1, .default_val=ULONG_MAX-1, .unit=OSMO_TDEF_S, .desc="very large" },
{ .T=INT_MAX-2, .default_val=LONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" },
{ .T=INT_MAX-3, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="very large in minutes" },
{ .T=INT_MIN, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="negative" },
{ .T=0, .default_val=1, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
/* no desc */
{ .T=123, .default_val=1 },
{} // <-- important! last entry shall be zero
};
#define print_tdef_get(T, AS_UNIT) do { \
unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \
printf("osmo_tdef_get(tdefs, %d, %s, 999)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \
} while (0)
#define print_tdef_get_short(T, AS_UNIT) do { \
unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \
printf("osmo_tdef_get(%d, %s)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \
} while (0)
void print_tdef_info(unsigned int T)
{
const struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T);
if (!t) {
printf("T%d=NULL", T);
return;
}
printf("T%d=%lu%s", T, t->val, osmo_tdef_unit_name(t->unit));
if (t->val != t->default_val)
printf("(def=%lu)", t->default_val);
printf("\n");
}
static void test_tdef_get()
{
int i;
enum osmo_tdef_unit as_unit;
printf("\n%s()\n", __func__);
osmo_tdefs_reset(tdefs); // make all values the default
for (i = 0; i < ARRAY_SIZE(tdefs)-1; i++) {
unsigned int T = tdefs[i].T;
print_tdef_info(T);
for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) {
print_tdef_get_short(T, as_unit);
}
}
}
static void test_tdef_get_nonexisting()
{
printf("\n%s()\n", __func__);
print_tdef_get(5, OSMO_TDEF_S);
print_tdef_get(5, OSMO_TDEF_MS);
print_tdef_get(5, OSMO_TDEF_M);
print_tdef_get(5, OSMO_TDEF_CUSTOM);
}
static void test_tdef_set_and_get()
{
struct osmo_tdef *t;
printf("\n%s()\n", __func__);
t = osmo_tdef_get_entry(tdefs, 7);
printf("setting 7 = 42\n");
t->val = 42;
print_tdef_info(7);
print_tdef_get_short(7, OSMO_TDEF_MS);
print_tdef_get_short(7, OSMO_TDEF_S);
print_tdef_get_short(7, OSMO_TDEF_M);
print_tdef_get_short(7, OSMO_TDEF_CUSTOM);
printf("setting 7 = 420\n");
t->val = 420;
print_tdef_info(7);
print_tdef_get_short(7, OSMO_TDEF_MS);
print_tdef_get_short(7, OSMO_TDEF_S);
print_tdef_get_short(7, OSMO_TDEF_M);
print_tdef_get_short(7, OSMO_TDEF_CUSTOM);
printf("resetting\n");
osmo_tdefs_reset(tdefs);
print_tdef_info(7);
print_tdef_get_short(7, OSMO_TDEF_S);
}
enum test_tdef_fsm_states {
S_A = 0,
S_B,
S_C,
S_D,
S_G,
S_H,
S_I,
S_J,
S_K,
S_L,
S_M,
S_N,
S_O,
S_X,
S_Y,
S_Z,
};
static const struct osmo_tdef_state_timeout test_tdef_state_timeouts[32] = {
[S_A] = { .T = 1 },
[S_B] = { .T = 2 },
[S_C] = { .T = 3 },
[S_D] = { .T = 4 },
[S_G] = { .T = 7 },
[S_H] = { .T = 8 },
[S_I] = { .T = 9 },
[S_J] = { .T = 10 },
/* keep_timer: adopt whichever T was running before and continue the timeout. */
[S_K] = { .keep_timer = true },
/* S_F defines an undefined T, but should continue previous state's timeout. */
[S_L] = { .T = 123, .keep_timer = true },
/* range */
[S_M] = { .T = INT_MAX },
[S_N] = { .T = INT_MIN },
/* T0 is not addressable from osmo_tdef_state_timeout, since it is indistinguishable from an unset entry. Even
* though a timeout value is set for T=0, the transition to state S_O will show "no timer configured". */
[S_O] = { .T = 0 },
/* S_X undefined on purpose */
/* S_Y defines a T that does not exist */
[S_Y] = { .T = 666 },
/* S_Z undefined on purpose */
};
#define S(x) (1 << (x))
static const struct osmo_fsm_state test_tdef_fsm_states[] = {
#define DEF_STATE(NAME) \
[S_##NAME] = { \
.name = #NAME, \
.out_state_mask = 0 \
| S(S_A) \
| S(S_B) \
| S(S_C) \
| S(S_D) \
| S(S_G) \
| S(S_H) \
| S(S_I) \
| S(S_J) \
| S(S_K) \
| S(S_L) \
| S(S_M) \
| S(S_N) \
| S(S_O) \
| S(S_X) \
| S(S_Y) \
| S(S_Z) \
, \
}
DEF_STATE(A),
DEF_STATE(B),
DEF_STATE(C),
DEF_STATE(D),
DEF_STATE(G),
DEF_STATE(H),
DEF_STATE(I),
DEF_STATE(J),
DEF_STATE(K),
DEF_STATE(L),
DEF_STATE(M),
DEF_STATE(N),
DEF_STATE(O),
DEF_STATE(X),
DEF_STATE(Y),
/* Z: test not being allowed to transition to other states. */
[S_Z] = {
.name = "Z",
.out_state_mask = 0
| S(S_A)
,
},
};
static const struct value_string test_tdef_fsm_event_names[] = { {} };
static struct osmo_fsm test_tdef_fsm = {
.name = "tdef_test",
.states = test_tdef_fsm_states,
.event_names = test_tdef_fsm_event_names,
.num_states = ARRAY_SIZE(test_tdef_fsm_states),
.log_subsys = DLGLOBAL,
};
const struct timeval fake_time_start_time = { 123, 456 };
#define fake_time_passes(secs, usecs) do \
{ \
struct timeval diff; \
osmo_gettimeofday_override_add(secs, usecs); \
osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \
timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \
printf("Total time passed: %ld.%06ld s\n", diff.tv_sec, diff.tv_usec); \
osmo_timers_prepare(); \
osmo_timers_update(); \
} while (0)
void fake_time_start()
{
struct timespec *clock_override;
osmo_gettimeofday_override_time = fake_time_start_time;
osmo_gettimeofday_override = true;
clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
OSMO_ASSERT(clock_override);
clock_override->tv_sec = fake_time_start_time.tv_sec;
clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000;
osmo_clock_override_enable(CLOCK_MONOTONIC, true);
fake_time_passes(0, 0);
}
static void print_fsm_state(struct osmo_fsm_inst *fi)
{
struct timeval remaining;
printf("state=%s T=%d", osmo_fsm_inst_state_name(fi), fi->T);
if (!osmo_timer_pending(&fi->timer)) {
printf(", no timeout\n");
return;
}
osmo_timer_remaining(&fi->timer, &osmo_gettimeofday_override_time, &remaining);
printf(", %lu.%06lu s remaining\n", remaining.tv_sec, remaining.tv_usec);
}
#define test_tdef_fsm_state_chg(NEXT_STATE) do { \
const struct osmo_tdef_state_timeout *st = osmo_tdef_get_state_timeout(NEXT_STATE, \
test_tdef_state_timeouts); \
if (!st) { \
printf(" --> %s (no timer configured for this state)\n", \
osmo_fsm_state_name(&test_tdef_fsm, NEXT_STATE)); \
} else { \
struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, st->T); \
int rc = osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, test_tdef_state_timeouts, tdefs, 999); \
printf(" --> %s (configured as T%d%s %lu %s) rc=%d;\t", osmo_fsm_state_name(&test_tdef_fsm, \
NEXT_STATE), \
st->T, st->keep_timer ? "(keep_timer)" : "", \
t? t->val : -1, t? osmo_tdef_unit_name(t->unit) : "-", \
rc); \
print_fsm_state(fi); \
} \
} while(0)
static void test_tdef_state_timeout(bool test_range)
{
struct osmo_fsm_inst *fi;
struct osmo_tdef *m = osmo_tdef_get_entry(tdefs, INT_MAX);
unsigned long m_secs;
printf("\n%s()\n", __func__);
osmo_tdefs_reset(tdefs);
fake_time_start();
fi = osmo_fsm_inst_alloc(&test_tdef_fsm, ctx, NULL, LOGL_DEBUG, __func__);
OSMO_ASSERT(fi);
print_fsm_state(fi);
test_tdef_fsm_state_chg(S_A);
test_tdef_fsm_state_chg(S_B);
test_tdef_fsm_state_chg(S_C);
test_tdef_fsm_state_chg(S_D);
test_tdef_fsm_state_chg(S_G);
test_tdef_fsm_state_chg(S_H);
test_tdef_fsm_state_chg(S_I);
test_tdef_fsm_state_chg(S_J);
printf("- test keep_timer:\n");
fake_time_passes(123, 45678);
print_fsm_state(fi);
test_tdef_fsm_state_chg(S_K);
test_tdef_fsm_state_chg(S_A);
fake_time_passes(23, 45678);
print_fsm_state(fi);
test_tdef_fsm_state_chg(S_K);
test_tdef_fsm_state_chg(S_A);
fake_time_passes(23, 45678);
print_fsm_state(fi);
test_tdef_fsm_state_chg(S_L);
printf("- test large T:\n");
test_tdef_fsm_state_chg(S_M);
printf("- test T<0:\n");
test_tdef_fsm_state_chg(S_N);
printf("- test T=0:\n");
test_tdef_fsm_state_chg(S_O);
printf("- test no timer:\n");
test_tdef_fsm_state_chg(S_X);
printf("- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999:\n");
test_tdef_fsm_state_chg(S_Y);
/* the range of unsigned long is architecture dependent. This test can be invoked manually to see whether
* clamping the timeout values works, but the output will be of varying lengths depending on the system's
* unsigned long range, and would cause differences in expected output. */
if (test_range) {
printf("- test range:\n");
test_tdef_fsm_state_chg(S_M);
/* sweep through all the bits, shifting in 0xfffff.. from the right. */
m_secs = 0;
do {
m_secs = (m_secs << 1) + 1;
switch (m_secs) {
case 0x7fff:
printf("--- int32_t max ---\n");
break;
case 0xffff:
printf("--- uint32_t max ---\n");
break;
case 0x7fffffff:
printf("--- int64_t max ---\n");
break;
case 0xffffffff:
printf("--- uint64_t max ---\n");
break;
default:
break;
}
m->val = m_secs - 1;
test_tdef_fsm_state_chg(S_M);
m->val = m_secs;
test_tdef_fsm_state_chg(S_M);
m->val = m_secs + 1;
test_tdef_fsm_state_chg(S_M);
} while (m_secs < ULONG_MAX);
}
printf("- test disallowed transition:\n");
test_tdef_fsm_state_chg(S_Z);
test_tdef_fsm_state_chg(S_B);
test_tdef_fsm_state_chg(S_C);
test_tdef_fsm_state_chg(S_D);
}
int main(int argc, char **argv)
{
ctx = talloc_named_const(NULL, 0, "tdef_test.c");
osmo_init_logging2(ctx, NULL);
log_set_print_filename(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
log_set_use_color(osmo_stderr_target, 0);
osmo_fsm_register(&test_tdef_fsm);
test_tdef_get();
test_tdef_get_nonexisting();
test_tdef_set_and_get();
/* Run range test iff any argument is passed on the cmdline. For the rationale, see the comment in
* test_tdef_state_timeout(). */
test_tdef_state_timeout(argc > 1);
return EXIT_SUCCESS;
}

184
tests/tdef/tdef_test.ok Normal file
View File

@ -0,0 +1,184 @@
test_tdef_get()
T1=100s
osmo_tdef_get(1, s) = 100
osmo_tdef_get(1, ms) = 100000
osmo_tdef_get(1, m) = 2
osmo_tdef_get(1, custom-unit) = 100
T2=100ms
osmo_tdef_get(2, s) = 1
osmo_tdef_get(2, ms) = 100
osmo_tdef_get(2, m) = 1
osmo_tdef_get(2, custom-unit) = 100
T3=100m
osmo_tdef_get(3, s) = 6000
osmo_tdef_get(3, ms) = 6000000
osmo_tdef_get(3, m) = 100
osmo_tdef_get(3, custom-unit) = 100
T4=100custom-unit
osmo_tdef_get(4, s) = 100
osmo_tdef_get(4, ms) = 100
osmo_tdef_get(4, m) = 100
osmo_tdef_get(4, custom-unit) = 100
T7=50s
osmo_tdef_get(7, s) = 50
osmo_tdef_get(7, ms) = 50000
osmo_tdef_get(7, m) = 1
osmo_tdef_get(7, custom-unit) = 50
T8=300s
osmo_tdef_get(8, s) = 300
osmo_tdef_get(8, ms) = 300000
osmo_tdef_get(8, m) = 5
osmo_tdef_get(8, custom-unit) = 300
T9=5m
osmo_tdef_get(9, s) = 300
osmo_tdef_get(9, ms) = 300000
osmo_tdef_get(9, m) = 5
osmo_tdef_get(9, custom-unit) = 5
T10=20m
osmo_tdef_get(10, s) = 1200
osmo_tdef_get(10, ms) = 1200000
osmo_tdef_get(10, m) = 20
osmo_tdef_get(10, custom-unit) = 20
T1000=2000ms
osmo_tdef_get(1000, s) = 2
osmo_tdef_get(1000, ms) = 2000
osmo_tdef_get(1000, m) = 1
osmo_tdef_get(1000, custom-unit) = 2000
T1001=60000ms
osmo_tdef_get(1001, s) = 60
osmo_tdef_get(1001, ms) = 60000
osmo_tdef_get(1001, m) = 1
osmo_tdef_get(1001, custom-unit) = 60000
T1002=307445734561825860m
osmo_tdef_get(1002, s) = 18446744073709551600
osmo_tdef_get(1002, ms) = 18446744073709551615
osmo_tdef_get(1002, m) = 307445734561825860
osmo_tdef_get(1002, custom-unit) = 307445734561825860
T1003=18446744073709551615m
osmo_tdef_get(1003, s) = 18446744073709551615
osmo_tdef_get(1003, ms) = 18446744073709551615
osmo_tdef_get(1003, m) = 18446744073709551615
osmo_tdef_get(1003, custom-unit) = 18446744073709551615
T1004=1ms
osmo_tdef_get(1004, s) = 1
osmo_tdef_get(1004, ms) = 1
osmo_tdef_get(1004, m) = 1
osmo_tdef_get(1004, custom-unit) = 1
T1005=0ms
osmo_tdef_get(1005, s) = 0
osmo_tdef_get(1005, ms) = 0
osmo_tdef_get(1005, m) = 0
osmo_tdef_get(1005, custom-unit) = 0
T1006=0s
osmo_tdef_get(1006, s) = 0
osmo_tdef_get(1006, ms) = 0
osmo_tdef_get(1006, m) = 0
osmo_tdef_get(1006, custom-unit) = 0
T1007=0m
osmo_tdef_get(1007, s) = 0
osmo_tdef_get(1007, ms) = 0
osmo_tdef_get(1007, m) = 0
osmo_tdef_get(1007, custom-unit) = 0
T1008=0custom-unit
osmo_tdef_get(1008, s) = 0
osmo_tdef_get(1008, ms) = 0
osmo_tdef_get(1008, m) = 0
osmo_tdef_get(1008, custom-unit) = 0
T2147483647=18446744073709551615s
osmo_tdef_get(2147483647, s) = 18446744073709551615
osmo_tdef_get(2147483647, ms) = 18446744073709551615
osmo_tdef_get(2147483647, m) = 307445734561825861
osmo_tdef_get(2147483647, custom-unit) = 18446744073709551615
T2147483646=18446744073709551614s
osmo_tdef_get(2147483646, s) = 18446744073709551614
osmo_tdef_get(2147483646, ms) = 18446744073709551615
osmo_tdef_get(2147483646, m) = 307445734561825861
osmo_tdef_get(2147483646, custom-unit) = 18446744073709551614
T2147483645=9223372036854775807s
osmo_tdef_get(2147483645, s) = 9223372036854775807
osmo_tdef_get(2147483645, ms) = 18446744073709551615
osmo_tdef_get(2147483645, m) = 153722867280912931
osmo_tdef_get(2147483645, custom-unit) = 9223372036854775807
T2147483644=18446744073709551615m
osmo_tdef_get(2147483644, s) = 18446744073709551615
osmo_tdef_get(2147483644, ms) = 18446744073709551615
osmo_tdef_get(2147483644, m) = 18446744073709551615
osmo_tdef_get(2147483644, custom-unit) = 18446744073709551615
T-2147483648=18446744073709551615s
osmo_tdef_get(-2147483648, s) = 18446744073709551615
osmo_tdef_get(-2147483648, ms) = 18446744073709551615
osmo_tdef_get(-2147483648, m) = 307445734561825861
osmo_tdef_get(-2147483648, custom-unit) = 18446744073709551615
T0=1custom-unit
osmo_tdef_get(0, s) = 1
osmo_tdef_get(0, ms) = 1
osmo_tdef_get(0, m) = 1
osmo_tdef_get(0, custom-unit) = 1
T123=1s
osmo_tdef_get(123, s) = 1
osmo_tdef_get(123, ms) = 1000
osmo_tdef_get(123, m) = 1
osmo_tdef_get(123, custom-unit) = 1
test_tdef_get_nonexisting()
osmo_tdef_get(tdefs, 5, s, 999) = 999
osmo_tdef_get(tdefs, 5, ms, 999) = 999
osmo_tdef_get(tdefs, 5, m, 999) = 999
osmo_tdef_get(tdefs, 5, custom-unit, 999) = 999
test_tdef_set_and_get()
setting 7 = 42
T7=42s(def=50)
osmo_tdef_get(7, ms) = 42000
osmo_tdef_get(7, s) = 42
osmo_tdef_get(7, m) = 1
osmo_tdef_get(7, custom-unit) = 42
setting 7 = 420
T7=420s(def=50)
osmo_tdef_get(7, ms) = 420000
osmo_tdef_get(7, s) = 420
osmo_tdef_get(7, m) = 7
osmo_tdef_get(7, custom-unit) = 420
resetting
T7=50s
osmo_tdef_get(7, s) = 50
test_tdef_state_timeout()
Total time passed: 0.000000 s
state=A T=0, no timeout
--> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining
--> B (configured as T2 100 ms) rc=0; state=B T=2, 1.000000 s remaining
--> C (configured as T3 100 m) rc=0; state=C T=3, 6000.000000 s remaining
--> D (configured as T4 100 custom-unit) rc=0; state=D T=4, 100.000000 s remaining
--> G (configured as T7 50 s) rc=0; state=G T=7, 50.000000 s remaining
--> H (configured as T8 300 s) rc=0; state=H T=8, 300.000000 s remaining
--> I (configured as T9 5 m) rc=0; state=I T=9, 300.000000 s remaining
--> J (configured as T10 20 m) rc=0; state=J T=10, 1200.000000 s remaining
- test keep_timer:
Total time passed: 123.045678 s
state=J T=10, 1076.954322 s remaining
--> K (configured as T0(keep_timer) 1 custom-unit) rc=0; state=K T=10, 1076.954322 s remaining
--> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining
Total time passed: 146.091356 s
state=A T=1, 76.954322 s remaining
--> K (configured as T0(keep_timer) 1 custom-unit) rc=0; state=K T=1, 76.954322 s remaining
--> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining
Total time passed: 169.137034 s
state=A T=1, 76.954322 s remaining
--> L (configured as T123(keep_timer) 1 s) rc=0; state=L T=123, 76.954322 s remaining
- test large T:
--> M (configured as T2147483647 18446744073709551615 s) rc=0; state=M T=2147483647, 2147483647.000000 s remaining
- test T<0:
--> N (configured as T-2147483648 18446744073709551615 s) rc=0; state=N T=-2147483648, 2147483647.000000 s remaining
- test T=0:
--> O (no timer configured for this state)
- test no timer:
--> X (no timer configured for this state)
- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999:
--> Y (configured as T666 18446744073709551615 -) rc=0; state=Y T=666, 999.000000 s remaining
- test disallowed transition:
--> Z (no timer configured for this state)
--> B (configured as T2 100 ms) rc=0; state=B T=2, 1.000000 s remaining
--> C (configured as T3 100 m) rc=0; state=C T=3, 6000.000000 s remaining
--> D (configured as T4 100 custom-unit) rc=0; state=D T=4, 100.000000 s remaining

View File

@ -0,0 +1,292 @@
/* Test implementation for osmo_tdef VTY configuration API. */
/*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <getopt.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/core/tdef.h>
#include <osmocom/vty/tdef_vty.h>
#include <stdlib.h>
#include "config.h"
/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */
/* This example keeps several separate timer groups and offers 'timer' VTY commands at the root of the config node. See
* the tdef_vty_test_config_root.vty transcript test.
*/
static struct osmo_tdef tdefs_test[] = {
{ .T=1, .default_val=100, .desc="Testing a hundred seconds" }, // default is .unit=OSMO_TDEF_S == 0
{ .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Testing a hundred milliseconds" },
{ .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="Testing a hundred minutes" },
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="Testing a hundred potatoes" },
{ .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="Very large" },
{ .T=-23, .default_val=-15, .desc="Negative T number" },
{} // <-- important! last entry shall be zero
};
static struct osmo_tdef tdefs_tea[] = {
{ .T=1, .default_val=50, .desc="Water Boiling Timeout" },
{ .T=2, .default_val=300, .desc="Tea brewing" },
{ .T=3, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" },
{ .T=4, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" },
{}
};
static struct osmo_tdef tdefs_software[] = {
{ .T=1, .default_val=30, .unit=OSMO_TDEF_M, .desc="Write code" },
{ .T=2, .default_val=20, .unit=OSMO_TDEF_MS, .desc="Hit segfault" },
{ .T=3, .default_val=480, .unit=OSMO_TDEF_M, .desc="Fix bugs" },
{}
};
static struct osmo_tdef_group tdef_groups[] = {
{
.name = "tea",
.desc = "Tea time",
.tdefs = tdefs_tea,
},
{
.name = "test",
.desc = "Test timers",
.tdefs = tdefs_test,
},
{
.name = "software",
.desc = "Typical software development cycle",
.tdefs = tdefs_software,
},
{}
};
enum tdef_vty_test_nodes {
TIMER_NODE = _LAST_OSMOVTY_NODE + 1,
};
/* This example puts 'timer' configuration commands directly at the root of the CONFIG_NODE.
* This TIMER_NODE is merely needed as a hook for the vty_write() command, but becomes an empty node in the VTY docs.
* It is possible to cheat around needing this if you choose to config_write_timer() in another root nodes' write cb.
* Another example using a 'network' subnode is \ref tdef_vty_test_config_subnode.c */
static struct cmd_node timer_node = {
TIMER_NODE,
"%s(config-timer)# ",
1,
};
static int config_write_timer(struct vty *vty)
{
osmo_tdef_vty_groups_write(vty, "");
return CMD_SUCCESS;
}
static void timer_init_vty()
{
/* Again, this is merely to get a vty write hook, see above. */
install_node(&timer_node, config_write_timer);
osmo_tdef_vty_groups_init(CONFIG_NODE, tdef_groups);
}
/* ------------------- THE REST is just boilerplate osmo main() ------------------- */
void *root_ctx = NULL;
static void print_help()
{
printf( "options:\n"
" -h --help this text\n"
" -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
" -D --daemonize For the process into a background daemon\n"
" -c --config-file Specify the filename of the config file\n"
" -s --disable-color Don't use colors in stderr log output\n"
" -T --timestamp Prefix every log line with a timestamp\n"
" -V --version Print version information and exit\n"
" -e --log-level Set a global log-level\n"
);
}
static struct {
const char *config_file;
int daemonize;
} cmdline_config = {};
static void handle_options(int argc, char **argv)
{
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "debug", 1, 0, 'd' },
{ "daemonize", 0, 0, 'D' },
{ "config-file", 1, 0, 'c' },
{ "disable-color", 0, 0, 's' },
{ "timestamp", 0, 0, 'T' },
{ "version", 0, 0, 'V' },
{ "log-level", 1, 0, 'e' },
{}
};
c = getopt_long(argc, argv, "hc:d:Dc:sTVe:",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
cmdline_config.daemonize = 1;
break;
case 'c':
cmdline_config.config_file = optarg;
break;
case 'T':
log_set_print_timestamp(osmo_stderr_target, 1);
break;
case 'e':
log_set_log_level(osmo_stderr_target, atoi(optarg));
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
}
}
}
static int quit = 0;
static void signal_handler(int signal)
{
fprintf(stdout, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
case SIGTERM:
quit++;
break;
case SIGABRT:
osmo_generate_backtrace();
/* in case of abort, we want to obtain a talloc report
* and then return to the caller, who will abort the process */
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(root_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(tall_vty_ctx, stderr);
break;
default:
break;
}
}
static struct vty_app_info vty_info = {
.name = "tdef_vty_test",
.version = PACKAGE_VERSION,
};
static const struct log_info_cat default_categories[] = {};
const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
int main(int argc, char **argv)
{
int rc;
root_ctx = talloc_named_const(NULL, 0, "tdef_vty_test");
osmo_init_logging2(root_ctx, &log_info);
vty_info.tall_ctx = root_ctx;
vty_init(&vty_info);
osmo_talloc_vty_add_cmds();
timer_init_vty(); /* <---- the only tdef relevant init */
handle_options(argc, argv);
if (cmdline_config.config_file) {
rc = vty_read_config_file(cmdline_config.config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n", cmdline_config.config_file);
return 1;
}
}
rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
if (rc < 0)
return 2;
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
if (cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
return 6;
}
}
while (!quit) {
log_reset_context();
osmo_select_main(0);
}
talloc_free(root_ctx);
talloc_free(tall_vty_ctx);
return 0;
}

View File

@ -0,0 +1,292 @@
tdef_vty_test> list
...
show timer [(tea|test|software)] [TNNNN]
...
tdef_vty_test> show timer ?
[tea] Tea time
[test] Test timers
[software] Typical software development cycle
tdef_vty_test> show timer test ?
[TNNNN] T-number, optionally preceded by 't' or 'T'.
tdef_vty_test> show timer
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test> enable
tdef_vty_test# show timer
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test# configure terminal
tdef_vty_test(config)# show running-config
... !timer
tdef_vty_test(config)# list
...
timer [(tea|test|software)] [TNNNN] [(<0-2147483647>|default)]
...
tdef_vty_test(config)# timer ?
[tea] Tea time
[test] Test timers
[software] Typical software development cycle
tdef_vty_test(config)# timer sof T123 ?
[<0-2147483647>] New timer value
[default] Set to default timer value
tdef_vty_test(config)# timer sof T123 ?
[<0-2147483647>] New timer value
[default] Set to default timer value
tdef_vty_test(config)# timer test ?
[TNNNN] T-number, optionally preceded by 't' or 'T'.
tdef_vty_test(config)# timer test t2 ?
[<0-2147483647>] New timer value
[default] Set to default timer value
tdef_vty_test(config)# do show timer
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# do show timer tea
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
tdef_vty_test(config)# do show timer tea 2
tea: T2 = 300 s Tea brewing (default: 300 s)
tdef_vty_test(config)# do show timer tea t2
tea: T2 = 300 s Tea brewing (default: 300 s)
tdef_vty_test(config)# do show timer tea T2
tea: T2 = 300 s Tea brewing (default: 300 s)
tdef_vty_test(config)# do show timer tea T5
% No such timer: T5
tdef_vty_test(config)# do show timer tea T0
% No such timer: T0
tdef_vty_test(config)# do show timer tea T-123
% No such timer: T-123
tdef_vty_test(config)# do show timer t
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config)# do show timer te
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 5 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config)# do show timer te T2
tea: T2 = 300 s Tea brewing (default: 300 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
tdef_vty_test(config)# timer tea 3 30
tdef_vty_test(config)# timer tea T3
tea: T3 = 30 m Let tea cool down before drinking (default: 5 m)
tdef_vty_test(config)# timer tea t3 31
tdef_vty_test(config)# timer tea T3
tea: T3 = 31 m Let tea cool down before drinking (default: 5 m)
tdef_vty_test(config)# timer tea T3 32
tdef_vty_test(config)# timer tea T3
tea: T3 = 32 m Let tea cool down before drinking (default: 5 m)
tdef_vty_test(config)# timer tea T-123 99
% No such timer: T-123
tdef_vty_test(config)# timer tea T0 0
% No such timer: T0
tdef_vty_test(config)# timer tea T123 default
% No such timer: T123
tdef_vty_test(config)# timer tea
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 32 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
tdef_vty_test(config)# timer t
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 32 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config)# timer te T2
tea: T2 = 300 s Tea brewing (default: 300 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
tdef_vty_test(config)# timer test T2 100
tdef_vty_test(config)# timer tes T2 100
% Error: no timers found
tdef_vty_test(config)# timer te T2 100
% Error: no timers found
tdef_vty_test(config)# do show timer software
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# do show timer software 1
software: T1 = 30 m Write code (default: 30 m)
tdef_vty_test(config)# do show timer software t1
software: T1 = 30 m Write code (default: 30 m)
tdef_vty_test(config)# do show timer software T1
software: T1 = 30 m Write code (default: 30 m)
tdef_vty_test(config)# do show timer software T99
% No such timer: T99
tdef_vty_test(config)# do show timer software T-123123
% No such timer: T-123123
tdef_vty_test(config)# do show timer software T0
% No such timer: T0
tdef_vty_test(config)# timer software 1 11
tdef_vty_test(config)# timer software T1
software: T1 = 11 m Write code (default: 30 m)
tdef_vty_test(config)# timer software t1 12
tdef_vty_test(config)# timer software T1
software: T1 = 12 m Write code (default: 30 m)
tdef_vty_test(config)# timer software T1 13
tdef_vty_test(config)# timer software T2 0
tdef_vty_test(config)# timer software
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# timer softw
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# timer softw T3
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# timer softw T3 23
% Error: no timers found
tdef_vty_test(config)# timer
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 32 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# do show timer
tea: T1 = 50 s Water Boiling Timeout (default: 50 s)
tea: T2 = 300 s Tea brewing (default: 300 s)
tea: T3 = 32 m Let tea cool down before drinking (default: 5 m)
tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m)
test: T1 = 100 s Testing a hundred seconds (default: 100 s)
test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
tdef_vty_test(config)# show running-config
... !timer
timer tea T3 32
timer software T1 13
timer software T2 0
... !timer
tdef_vty_test(config)# timer tea T3 default
tdef_vty_test(config)# timer software T1 default
tdef_vty_test(config)# show running-config
... !timer
timer software T2 0
... !timer
tdef_vty_test(config)# timer softw 2 default
% Error: no timers found
tdef_vty_test(config)# timer software 2 default
tdef_vty_test(config)# show running-config
... !timer

View File

@ -0,0 +1,288 @@
/* Test implementation for osmo_tdef VTY configuration API. */
/*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <getopt.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/core/tdef.h>
#include <osmocom/vty/tdef_vty.h>
#include <stdlib.h>
#include "config.h"
/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */
/* This example keeps a single global timer group and offers a custom 'timer' VTY command in a 'network' subnode below
* the CONFIG_NODE.
* the tdef_vty_test_config_subnode.vty transcript test.
*/
static struct osmo_tdef global_tdefs[] = {
{ .T=1, .default_val=100, .desc="Testing a hundred seconds" }, // default is .unit=OSMO_TDEF_S == 0
{ .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Testing a hundred milliseconds" },
{ .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="Testing a hundred minutes" },
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="Testing a hundred potatoes" },
{ .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="Very large" },
{ .T=-23, .default_val=-15, .desc="Negative T number" },
{} // <-- important! last entry shall be zero
};
enum tdef_vty_test_nodes {
GSMNET_NODE = _LAST_OSMOVTY_NODE + 1,
};
/* This example offers 'timer T123' commands within an "unrelated" already existing subnode. */
static struct cmd_node gsmnet_node = {
GSMNET_NODE,
"%s(config-net)# ",
1,
};
DEFUN(show_timer, show_timer_cmd,
"show timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL,
SHOW_STR "Show timers\n"
OSMO_TDEF_VTY_DOC_T)
{
const char *T_arg = argc > 0 ? argv[0] : NULL;
return osmo_tdef_vty_show_cmd(vty, global_tdefs, T_arg, NULL);
}
DEFUN(cfg_net_timer, cfg_net_timer_cmd,
"timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL,
"Configure or show timers\n"
OSMO_TDEF_VTY_DOC_SET)
{
/* If any arguments are missing, redirect to 'show' */
if (argc < 2)
return show_timer(self, vty, argc, argv);
return osmo_tdef_vty_set_cmd(vty, global_tdefs, argv);
}
DEFUN(cfg_net, cfg_net_cmd,
"network", "Enter network node\n")
{
vty->node = GSMNET_NODE;
return CMD_SUCCESS;
}
static int config_write_gsmnet(struct vty *vty)
{
vty_out(vty, "net%s", VTY_NEWLINE);
/* usually, here would be the output of any other 'net' config items... */
osmo_tdef_vty_write(vty, global_tdefs, " timer ");
return CMD_SUCCESS;
}
static void gsmnet_init_vty()
{
install_node(&gsmnet_node, config_write_gsmnet);
install_element(CONFIG_NODE, &cfg_net_cmd);
osmo_tdefs_reset(global_tdefs);
install_element_ve(&show_timer_cmd);
install_element(GSMNET_NODE, &cfg_net_timer_cmd);
}
/* ------------------- THE REST is just boilerplate osmo main() ------------------- */
void *root_ctx = NULL;
static void print_help()
{
printf( "options:\n"
" -h --help this text\n"
" -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
" -D --daemonize For the process into a background daemon\n"
" -c --config-file Specify the filename of the config file\n"
" -s --disable-color Don't use colors in stderr log output\n"
" -T --timestamp Prefix every log line with a timestamp\n"
" -V --version Print version information and exit\n"
" -e --log-level Set a global log-level\n"
);
}
static struct {
const char *config_file;
int daemonize;
} cmdline_config = {};
static void handle_options(int argc, char **argv)
{
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "debug", 1, 0, 'd' },
{ "daemonize", 0, 0, 'D' },
{ "config-file", 1, 0, 'c' },
{ "disable-color", 0, 0, 's' },
{ "timestamp", 0, 0, 'T' },
{ "version", 0, 0, 'V' },
{ "log-level", 1, 0, 'e' },
{}
};
c = getopt_long(argc, argv, "hc:d:Dc:sTVe:",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
cmdline_config.daemonize = 1;
break;
case 'c':
cmdline_config.config_file = optarg;
break;
case 'T':
log_set_print_timestamp(osmo_stderr_target, 1);
break;
case 'e':
log_set_log_level(osmo_stderr_target, atoi(optarg));
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
}
}
}
static int quit = 0;
static void signal_handler(int signal)
{
fprintf(stdout, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
case SIGTERM:
quit++;
break;
case SIGABRT:
osmo_generate_backtrace();
/* in case of abort, we want to obtain a talloc report
* and then return to the caller, who will abort the process */
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(root_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(tall_vty_ctx, stderr);
break;
default:
break;
}
}
static struct vty_app_info vty_info = {
.name = "tdef_vty_test",
.version = PACKAGE_VERSION,
};
static const struct log_info_cat default_categories[] = {};
const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
int main(int argc, char **argv)
{
int rc;
root_ctx = talloc_named_const(NULL, 0, "tdef_vty_test");
osmo_init_logging2(root_ctx, &log_info);
vty_info.tall_ctx = root_ctx;
vty_init(&vty_info);
osmo_talloc_vty_add_cmds();
gsmnet_init_vty(); /* <--- relevant init for this example */
handle_options(argc, argv);
if (cmdline_config.config_file) {
rc = vty_read_config_file(cmdline_config.config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n", cmdline_config.config_file);
return 1;
}
}
rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
if (rc < 0)
return 2;
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
if (cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
return 6;
}
}
while (!quit) {
log_reset_context();
osmo_select_main(0);
}
talloc_free(root_ctx);
talloc_free(tall_vty_ctx);
return 0;
}

View File

@ -0,0 +1,107 @@
tdef_vty_test> list
... !timer
show timer [TNNNN]
... !timer
tdef_vty_test> show timer ?
[TNNNN] T-number, optionally preceded by 't' or 'T'.
tdef_vty_test> show timer
T1 = 100 s Testing a hundred seconds (default: 100 s)
T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
T3 = 100 m Testing a hundred minutes (default: 100 m)
T4 = 100 Testing a hundred potatoes (default: 100)
T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test> enable
tdef_vty_test# show timer
T1 = 100 s Testing a hundred seconds (default: 100 s)
T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
T3 = 100 m Testing a hundred minutes (default: 100 m)
T4 = 100 Testing a hundred potatoes (default: 100)
T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test# configure terminal
tdef_vty_test(config)# show running-config
... !timer
tdef_vty_test(config)# network
tdef_vty_test(config-net)# do show timer
T1 = 100 s Testing a hundred seconds (default: 100 s)
T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
T3 = 100 m Testing a hundred minutes (default: 100 m)
T4 = 100 Testing a hundred potatoes (default: 100)
T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config-net)# do show timer T3
T3 = 100 m Testing a hundred minutes (default: 100 m)
tdef_vty_test(config-net)# do show timer 3
T3 = 100 m Testing a hundred minutes (default: 100 m)
tdef_vty_test(config-net)# do show timer t3
T3 = 100 m Testing a hundred minutes (default: 100 m)
tdef_vty_test(config-net)# timer T1 5
tdef_vty_test(config-net)# timer T1
T1 = 5 s Testing a hundred seconds (default: 100 s)
tdef_vty_test(config-net)# timer t1 678
tdef_vty_test(config-net)# timer T1
T1 = 678 s Testing a hundred seconds (default: 100 s)
tdef_vty_test(config-net)# timer 1 9012345
tdef_vty_test(config-net)# timer T1
T1 = 9012345 s Testing a hundred seconds (default: 100 s)
tdef_vty_test(config-net)# do show timer T666
% No such timer: T666
tdef_vty_test(config-net)# do show timer t666
% No such timer: T666
tdef_vty_test(config-net)# do show timer 666
% No such timer: T666
tdef_vty_test(config-net)# timer T666
% No such timer: T666
tdef_vty_test(config-net)# timer t666
% No such timer: T666
tdef_vty_test(config-net)# timer 666
% No such timer: T666
tdef_vty_test(config-net)# timer T666 5
% No such timer: T666
tdef_vty_test(config-net)# timer T-23 42
tdef_vty_test(config-net)# timer T-23
T-23 = 42 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config-net)# timer t-23 43
tdef_vty_test(config-net)# timer T-23
T-23 = 43 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config-net)# timer -23 44
tdef_vty_test(config-net)# timer T-23
T-23 = 44 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config-net)# do show timer
T1 = 9012345 s Testing a hundred seconds (default: 100 s)
T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
T3 = 100 m Testing a hundred minutes (default: 100 m)
T4 = 100 Testing a hundred potatoes (default: 100)
T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m)
T-23 = 44 s Negative T number (default: 18446744073709551601 s)
tdef_vty_test(config-net)# show running-config
... !timer
net
timer T1 9012345
timer T-23 44
... !timer
tdef_vty_test(config-net)# timer T1 default
tdef_vty_test(config-net)# timer T-23 default
tdef_vty_test(config-net)# show running-config
... !timer

View File

@ -0,0 +1,362 @@
/* Test implementation for osmo_tdef VTY configuration API. */
/*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <getopt.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/core/tdef.h>
#include <osmocom/vty/tdef_vty.h>
#include <stdlib.h>
#include "config.h"
void *root_ctx = NULL;
/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */
/* This example keeps a separate list of timers for each instance of a dynamically allocated instance of a VTY node,
* for example of keeping separate timers for each BTS in a BSC.
*/
static const struct osmo_tdef bts_default_tdefs[] = {
{ .T=1111, .default_val=2, .desc="Dynamic Duo" },
{ .T=2222, .default_val=1, .desc="BATMAN" },
{ .T=3333, .default_val=12, .desc="Dadadadadadadadadadadada" },
{ .T=4444, .default_val=500, .unit=OSMO_TDEF_MS, .desc="POW!" },
{}
};
/* Boilerplate dynamic VTY node ... */
enum tdef_vty_test_nodes {
MEMBER_NODE = _LAST_OSMOVTY_NODE + 1,
};
static struct cmd_node member_node = {
MEMBER_NODE,
"%s(config-member)# ",
1,
};
struct member {
struct llist_head entry;
char name[23];
struct osmo_tdef *tdefs;
};
LLIST_HEAD(all_members);
struct member *member_alloc(const char *name)
{
struct member *m = talloc_zero(root_ctx, struct member);
osmo_strlcpy(m->name, name, sizeof(m->name));
/* DYNAMIC TDEF COPIES */
m->tdefs = (struct osmo_tdef*)talloc_size(m, sizeof(bts_default_tdefs));
memcpy((char*)m->tdefs, (char*)&bts_default_tdefs, sizeof(bts_default_tdefs));
osmo_tdefs_reset(m->tdefs);
llist_add_tail(&m->entry, &all_members);
return m;
}
struct member *member_find(const char *name)
{
struct member *m;
llist_for_each_entry(m, &all_members, entry) {
if (!strcmp(m->name, name))
return m;
}
return NULL;
}
DEFUN(cfg_member, cfg_member_cmd,
"member NAME",
"Enter member node\n" "Existing or new member node name\n")
{
const char *name = argv[0];
struct member *m = member_find(name);
if (!m)
m = member_alloc(name);
vty->index = m;
vty->node = MEMBER_NODE;
return CMD_SUCCESS;
}
/* TDEF SPECIFIC VTY */
static bool startswith(const char *str, const char *startswith_str)
{
if (!startswith_str)
return true;
if (!str)
return false;
return strncmp(str, startswith_str, strlen(startswith_str)) == 0;
}
DEFUN(show_timer, show_member_timer_cmd,
"show member-timer [NAME] " OSMO_TDEF_VTY_ARG_T_OPTIONAL,
SHOW_STR "Show timers for a specific member" "member name\n"
OSMO_TDEF_VTY_DOC_T)
{
const char *name = argc > 0 ? argv[0] : NULL;
struct member *m;
const char *T_arg = argc > 1 ? argv[1] : NULL;
int shown = 0;
llist_for_each_entry(m, &all_members, entry) {
if (!name || startswith(m->name, name)) {
osmo_tdef_vty_show_cmd(vty, m->tdefs, T_arg, "%11s: ", m->name);
shown ++;
}
}
if (!shown) {
vty_out(vty, "%% No such member: %s%s", name ? : "(none)", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(cfg_member_timer, cfg_member_timer_cmd,
"timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL,
"Configure or show timers for this member\n"
OSMO_TDEF_VTY_DOC_SET)
{
struct member *m = vty->index;
if (!m || !m->tdefs) {
vty_out(vty, "%% No timers here%s", VTY_NEWLINE);
return CMD_WARNING;
}
/* If any arguments are missing, redirect to 'show' */
if (argc < 2) {
const char *T_arg = argc > 0 ? argv[0] : NULL;
return osmo_tdef_vty_show_cmd(vty, m->tdefs, T_arg, "%11s: ", m->name);
}
return osmo_tdef_vty_set_cmd(vty, m->tdefs, argv);
}
static int config_write_member(struct vty *vty)
{
struct member *m;
llist_for_each_entry(m, &all_members, entry) {
vty_out(vty, "member %s%s", m->name, VTY_NEWLINE);
osmo_tdef_vty_write(vty, m->tdefs, " timer ");
}
return CMD_SUCCESS;
}
static void member_init_vty()
{
install_node(&member_node, config_write_member);
install_element(CONFIG_NODE, &cfg_member_cmd);
install_element_ve(&show_member_timer_cmd);
install_element(MEMBER_NODE, &cfg_member_timer_cmd);
}
/* ------------------- THE REST is just boilerplate osmo main() ------------------- */
static void print_help()
{
printf( "options:\n"
" -h --help this text\n"
" -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
" -D --daemonize For the process into a background daemon\n"
" -c --config-file Specify the filename of the config file\n"
" -s --disable-color Don't use colors in stderr log output\n"
" -T --timestamp Prefix every log line with a timestamp\n"
" -V --version Print version information and exit\n"
" -e --log-level Set a global log-level\n"
);
}
static struct {
const char *config_file;
int daemonize;
} cmdline_config = {};
static void handle_options(int argc, char **argv)
{
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "debug", 1, 0, 'd' },
{ "daemonize", 0, 0, 'D' },
{ "config-file", 1, 0, 'c' },
{ "disable-color", 0, 0, 's' },
{ "timestamp", 0, 0, 'T' },
{ "version", 0, 0, 'V' },
{ "log-level", 1, 0, 'e' },
{}
};
c = getopt_long(argc, argv, "hc:d:Dc:sTVe:",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
cmdline_config.daemonize = 1;
break;
case 'c':
cmdline_config.config_file = optarg;
break;
case 'T':
log_set_print_timestamp(osmo_stderr_target, 1);
break;
case 'e':
log_set_log_level(osmo_stderr_target, atoi(optarg));
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
}
}
}
static int quit = 0;
static void signal_handler(int signal)
{
fprintf(stdout, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
case SIGTERM:
quit++;
break;
case SIGABRT:
osmo_generate_backtrace();
/* in case of abort, we want to obtain a talloc report
* and then return to the caller, who will abort the process */
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(root_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(tall_vty_ctx, stderr);
break;
default:
break;
}
}
static struct vty_app_info vty_info = {
.name = "tdef_vty_test",
.version = PACKAGE_VERSION,
};
static const struct log_info_cat default_categories[] = {};
const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
int main(int argc, char **argv)
{
int rc;
root_ctx = talloc_named_const(NULL, 0, "tdef_vty_test");
osmo_init_logging2(root_ctx, &log_info);
vty_info.tall_ctx = root_ctx;
vty_init(&vty_info);
osmo_talloc_vty_add_cmds();
member_init_vty(); /* <--- relevant init for this example */
handle_options(argc, argv);
if (cmdline_config.config_file) {
rc = vty_read_config_file(cmdline_config.config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n", cmdline_config.config_file);
return 1;
}
}
rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
if (rc < 0)
return 2;
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
if (cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
return 6;
}
}
while (!quit) {
log_reset_context();
osmo_select_main(0);
}
talloc_free(root_ctx);
talloc_free(tall_vty_ctx);
return 0;
}

View File

@ -0,0 +1,83 @@
tdef_vty_test> list
...
show member-timer [NAME] [TNNNN]
...
tdef_vty_test> enable
tdef_vty_test# configure terminal
tdef_vty_test(config)# member robin
tdef_vty_test(config-member)# timer
robin: T1111 = 2 s Dynamic Duo (default: 2 s)
robin: T2222 = 1 s BATMAN (default: 1 s)
robin: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
robin: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test(config-member)# timer T2222 423
tdef_vty_test(config-member)# timer T2222
robin: T2222 = 423 s BATMAN (default: 1 s)
tdef_vty_test(config-member)# timer
robin: T1111 = 2 s Dynamic Duo (default: 2 s)
robin: T2222 = 423 s BATMAN (default: 1 s)
robin: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
robin: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test(config-member)# do show member-timer
robin: T1111 = 2 s Dynamic Duo (default: 2 s)
robin: T2222 = 423 s BATMAN (default: 1 s)
robin: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
robin: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test(config-member)# exit
tdef_vty_test(config)# member batman
tdef_vty_test(config-member)# timer 3333 17
tdef_vty_test(config-member)# timer 3333
batman: T3333 = 17 s Dadadadadadadadadadadada (default: 12 s)
tdef_vty_test(config-member)# show running-config
Current configuration:
...
member robin
timer T2222 423
member batman
timer T3333 17
...
tdef_vty_test(config-member)# timer 3333 default
tdef_vty_test(config-member)# show running-config
...
member robin
timer T2222 423
member batman
... !timer
tdef_vty_test(config-member)# exit
tdef_vty_test(config)# exit
tdef_vty_test# show member-timer
robin: T1111 = 2 s Dynamic Duo (default: 2 s)
robin: T2222 = 423 s BATMAN (default: 1 s)
robin: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
robin: T4444 = 500 ms POW! (default: 500 ms)
batman: T1111 = 2 s Dynamic Duo (default: 2 s)
batman: T2222 = 1 s BATMAN (default: 1 s)
batman: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
batman: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test# show member-timer batman
batman: T1111 = 2 s Dynamic Duo (default: 2 s)
batman: T2222 = 1 s BATMAN (default: 1 s)
batman: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
batman: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test# show member-timer robin
robin: T1111 = 2 s Dynamic Duo (default: 2 s)
robin: T2222 = 423 s BATMAN (default: 1 s)
robin: T3333 = 12 s Dadadadadadadadadadadada (default: 12 s)
robin: T4444 = 500 ms POW! (default: 500 ms)
tdef_vty_test# show member-timer joker
% No such member: joker

View File

@ -325,3 +325,9 @@ AT_KEYWORDS([gsm23003])
cat $abs_srcdir/gsm23003/gsm23003_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/gsm23003/gsm23003_test], [0], [expout], [ignore])
AT_CLEANUP
AT_SETUP([tdef])
AT_KEYWORDS([tdef])
cat $abs_srcdir/tdef/tdef_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/tdef/tdef_test], [0], [expout], [ignore])
AT_CLEANUP