/* Osmocom VTY interface functions in TTCN-3
 * (C) 2017-2018 Harald Welte <laforge@gnumonks.org>
 * contributions by sysmocom - s.f.m.c. GmbH
 * All rights reserved.
 *
 * Released under the terms of GNU General Public License, Version 2 or
 * (at your option) any later version.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

module Osmocom_VTY_Functions {
	import from TELNETasp_PortType all;
	import from Osmocom_Types all;

	modulepar {
		charstring mp_prompt_prefix := "OpenBSC";
	}

	const charstring VTY_VIEW_SUFFIX := "> ";
	const charstring VTY_ENABLE_SUFFIX := "# ";
	const charstring VTY_CFG_SUFFIX := "(*)";

	template charstring t_vty_unknown := pattern "*% Unknown command.";

	/* configure prompts in TELNETasp module */
	function f_vty_set_prompts(TELNETasp_PT pt, charstring prompt_prefix := mp_prompt_prefix) {
		var ASP_TelnetDynamicConfig vty_prompt[3] := {
			{
				prompt := {
					id := 1,
					prompt := prompt_prefix & VTY_VIEW_SUFFIX,
					has_wildcards := false
				}
			}, {
				prompt := {
					id := 2,
					prompt := prompt_prefix & VTY_ENABLE_SUFFIX,
					has_wildcards := false
				}
			}, {
				prompt := {
					id := 3,
					prompt := prompt_prefix & VTY_CFG_SUFFIX,
					has_wildcards := true
				}
			}
		};

		/* set some configuration that isn't possible to express
		 * in the config file due to syntactic restrictions (Who invents config
		 * files that don't permit regular expressions? */
		for (var integer i := 0; i < sizeof(vty_prompt); i:= i + 1) {
			pt.send(vty_prompt[i]);
		}
	}

	/* wait for any of the permitted prompts; buffer + return all intermediate output */
	function f_vty_wait_for_prompt(TELNETasp_PT pt, boolean strict := true) return charstring {
		var charstring rx, buf := "";
		var integer fd;
		timer T := 2.0;

		T.start;
		alt {
			[] pt.receive(pattern "[\w-]+" & VTY_VIEW_SUFFIX) { };
			[] pt.receive(pattern "[\w-]+\# ") { };
			[] pt.receive(pattern "[\w-]+\(*\)\# ") { };
			[] pt.receive(t_vty_unknown) -> value rx {
				if (strict) {
					setverdict(fail, "VTY: Unknown Command");
					mtc.stop;
				} else {
					log("VTY: Unknown Command (ignored)");
					buf := buf & rx;
					repeat;
				}
				};
			[] pt.receive(charstring:?) -> value rx { buf := buf & rx; repeat };
			[] pt.receive(integer:?) -> value fd {
				if (fd == -1) {
					setverdict(fail, "VTY Telnet Connection Failure");
					mtc.stop;
				} else {
					repeat; /* telnet connection succeeded */
				}
			}
			[] T.timeout {
				setverdict(fail, "VTY Timeout for prompt");
				mtc.stop;
				};
		}
		T.stop;
		return buf;
	}

	/* send a VTY command and obtain response until prompt is received */
	function f_vty_transceive_ret(TELNETasp_PT pt, charstring tx, boolean strict := true) return charstring {
		pt.send(tx);
		return f_vty_wait_for_prompt(pt, strict);
	}

	/* send a VTY command and obtain response until prompt is received */
	function f_vty_transceive(TELNETasp_PT pt, charstring tx, boolean strict := true) {
		var charstring unused := f_vty_transceive_ret(pt, tx, strict);
	}

	type integer BtsNr (0..255);
	type integer BtsTrxNr (0..255);
	type integer BtsTimeslotNr (0..7);
	type integer MscNr (0..255);
	type integer Cs7Nr (0..255);

	type charstring BtsGprsMode ("none", "gprs", "egrps");

	/* enter the'confiugration' mode of the VTY */
	function f_vty_enter_config(TELNETasp_PT pt) {
		f_vty_transceive(pt, "configure terminal")
	}

	function f_vty_enter_cfg_network(TELNETasp_PT pt) {
		f_vty_enter_config(pt);
		f_vty_transceive(pt, "network")
	}

	function f_vty_enter_cfg_bts(TELNETasp_PT pt, BtsNr bts := 0) {
		f_vty_enter_cfg_network(pt);
		f_vty_transceive(pt, "bts " & int2str(bts));
	}

	function f_vty_enter_cfg_trx(TELNETasp_PT pt, BtsNr bts := 0, BtsTrxNr trx := 0) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "trx " & int2str(trx));
	}

	function f_vty_enter_cfg_ts(TELNETasp_PT pt, BtsNr bts := 0, BtsTrxNr trx := 0, BtsTimeslotNr ts) {
		f_vty_enter_cfg_trx(pt, bts, trx);
		f_vty_transceive(pt, "timeslot " & int2str(ts));
	}

	function f_vty_enter_cfg_msc(TELNETasp_PT pt, MscNr msc := 0) {
		f_vty_enter_config(pt);
		f_vty_transceive(pt, "msc " & int2str(msc));
	}

	function f_vty_enter_cfg_cs7_inst(TELNETasp_PT pt, Cs7Nr cs7_inst := 0) {
		f_vty_enter_config(pt);
		f_vty_transceive(pt, "cs7 instance " & int2str(cs7_inst));
	}

type record of charstring rof_charstring;
function f_vty_config2(TELNETasp_PT pt, rof_charstring config_nodes, charstring cmd)
{
	/* enter config mode; enter node */
	f_vty_enter_config(pt);
	for (var integer i := 0; i < sizeof(config_nodes); i := i+1) {
		f_vty_transceive(pt, config_nodes[i]);
	}
	/* execute command */
	f_vty_transceive(pt, cmd);
	/* leave config mode */
	f_vty_transceive(pt, "end");
}


function f_vty_config(TELNETasp_PT pt, charstring config_node, charstring cmd)
{
	f_vty_config2(pt, {config_node}, cmd);
}

function f_vty_transceive_match(TELNETasp_PT pt, charstring cmd, template charstring exp_ret) {
	var charstring ret := f_vty_transceive_ret(pt, cmd);
	if (not match(ret, exp_ret)) {
		setverdict(fail, "Non-matching VTY response: ", ret);
		mtc.stop;
	}
}

function f_vty_transceive_not_match(TELNETasp_PT pt, charstring cmd, template charstring exp_ret) {
	var charstring ret := f_vty_transceive_ret(pt, cmd);
	if (match(ret, exp_ret)) {
		setverdict(fail, "Unexpected matching VTY response: ", ret);
		mtc.stop;
	}
}

function f_vty_transceive_match_regex(TELNETasp_PT pt, charstring cmd, charstring regex, integer groupno) return charstring
{
	var charstring resp := f_vty_transceive_ret(pt, cmd);
	return regexp(resp, regex, groupno);
}

function f_vty_transceive_match_regexp_retry(TELNETasp_PT pt, charstring cmd, charstring regex,
					     integer groupno, integer num_attempts, float retry_delay) return charstring
{
	 while (num_attempts > 0) {
		var charstring ret := f_vty_transceive_match_regex(pt, cmd, regex, groupno);
		if (ret != "") {
			return ret;
		}
		f_sleep(retry_delay);
		num_attempts := num_attempts - 1;
	}

	setverdict(fail, "No matching VTY response for regular expression '", regex,
		   "' after ", num_attempts, " attempts." );
	mtc.stop;
}

}
