asterisk: Introduce AMI_Adapter_CT

This allows to keep string handling totally internal to the AMI_Adapter
component, which also means now the CLIENT port acts asynchronously on
full AMI messages.
This allows for instance using activated altsteps to ignore events or
answer to them.

Change-Id: Ibf230d2302fecf443f34e1c4d4acfd4802f4cc79
This commit is contained in:
Pau Espin 2024-04-26 20:16:47 +02:00
parent 9c83471a5b
commit bcb4e82d16
2 changed files with 111 additions and 48 deletions

View File

@ -167,6 +167,58 @@ tr_AMI_Response_Success_ActionId(template (present) charstring action_id := ?) :
tr_AMI_Field_ActionId(action_id)
);
/***********************
* Adapter:
***********************/
type port AMI_Msg_PT message {
inout AMI_Msg;
} with { extension "internal" };
type component AMI_Adapter_CT {
port TELNETasp_PT AMI;
port AMI_Msg_PT CLIENT;
}
function f_AMI_Adapter_main() runs on AMI_Adapter_CT {
var AMI_Msg msg;
var charstring rx, buf := "";
var integer fd;
map(self:AMI, system:AMI);
while (true) {
alt {
[] AMI.receive(pattern "\n") {
buf := buf & "\n";
msg := dec_AMI_Msg(buf);
buf := "";
CLIENT.send(msg);
};
[] AMI.receive(charstring:?) -> value rx {
buf := buf & rx;
};
[] AMI.receive(integer:?) -> value fd {
if (fd == -1) {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
"AMI Telnet Connection Failure: " & int2str(fd));
} else {
/* telnet connection succeeded */
}
}
[] CLIENT.receive(AMI_Msg:?) -> value msg {
/* TODO: in the future, queue Action if there's already one Action in transit, to fullfill AMI requirements. */
var charstring tx_txt := enc_AMI_Msg(msg);
AMI.send(tx_txt);
}
}
}
}
/*
* Functions:
*/
@ -223,62 +275,66 @@ return template charstring {
return field.val;
}
private function f_ami_wait_for_prompt_str(TELNETasp_PT pt, charstring log_label := "(?)")
return charstring {
var charstring rx, buf := "";
var integer fd;
function f_ami_transceive_ret(AMI_Msg_PT pt, template (value) AMI_Msg tx_msg, float rx_timeout := 10.0) return AMI_Msg {
var AMI_Msg rx_msg;
timer T;
T.start(mp_ami_prompt_timeout);
T.start(rx_timeout);
pt.send(tx_msg);
alt {
[] pt.receive(pattern "\n") { buf := buf & "\n" };
[] pt.receive(charstring:?) -> value rx { buf := buf & rx; repeat };
[] pt.receive(integer:?) -> value fd {
if (fd == -1) {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
"AMI Telnet Connection Failure: " & log_label);
} else {
repeat; /* telnet connection succeeded */
}
}
[] pt.receive(AMI_Msg:?) -> value rx_msg;
[] T.timeout {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
"AMI Timeout for prompt: " & log_label);
};
log2str("AMI Response timeout: ", tx_msg));
}
}
T.stop;
return buf;
return rx_msg;
}
function f_ami_wait_for_prompt(TELNETasp_PT pt, charstring log_label := "(?)") return AMI_Msg {
var charstring buf := f_ami_wait_for_prompt_str(pt, log_label);
var AMI_Msg msg := dec_AMI_Msg(buf);
return msg;
}
/* send a AMI command and obtain response until prompt is received */
private function f_ami_transceive_ret_str(TELNETasp_PT pt, charstring tx) return charstring {
pt.send(tx);
return f_ami_wait_for_prompt_str(pt, tx);
}
function f_ami_transceive_ret(TELNETasp_PT pt, template (value) AMI_Msg tx_msg) return AMI_Msg {
var charstring tx_txt := enc_AMI_Msg(valueof(tx_msg));
var charstring resp_txt := f_ami_transceive_ret_str(pt, tx_txt);
return dec_AMI_Msg(resp_txt);
}
function f_ami_transceive_match(TELNETasp_PT pt,
template (value) AMI_Msg tx_msg,
template (present) AMI_Msg exp_ret := ?) {
var AMI_Msg ret := f_ami_transceive_ret(pt, tx_msg);
if (not match(ret, exp_ret)) {
private altstep as_ami_rx_fail(AMI_Msg_PT pt, template AMI_Msg exp_msg := *)
{
var AMI_Msg msg;
[] pt.receive(AMI_Msg:?) -> value msg {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("Non-matching AMI response: ", ret, " vs exp: ", exp_ret));
log2str("Received unexpected AMI message := ", msg, "\nvs exp := ", exp_msg));
}
}
function f_ami_transceive_match_response_success(TELNETasp_PT pt,
altstep as_ami_expect_msg(AMI_Msg_PT pt, template (present) AMI_Msg msg_expect, boolean fail_others := true)
{
[] pt.receive(msg_expect);
[fail_others] as_ami_rx_fail(pt, msg_expect);
}
function f_ami_transceive_match(AMI_Msg_PT pt,
template (value) AMI_Msg tx_msg,
template (present) AMI_Msg exp_ret := ?,
boolean fail_others := true,
float rx_timeout := 10.0) return AMI_Msg {
var AMI_Msg rx_msg;
timer T;
T.start(rx_timeout);
pt.send(tx_msg);
alt {
[] pt.receive(exp_ret) -> value rx_msg;
[not fail_others] pt.receive(AMI_Msg:?) -> value rx_msg {
log("AMI: Ignoring Rx msg ", rx_msg);
repeat;
}
[fail_others] as_ami_rx_fail(pt, exp_ret);
[] T.timeout {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("AMI Response timeout: ", tx_msg));
}
}
T.stop;
return rx_msg;
}
function f_ami_transceive_match_response_success(AMI_Msg_PT pt,
template (value) AMI_Msg tx_msg) {
var template (present) AMI_Msg exp_resp;
var template (omit) charstring action_id := f_ami_msg_get_value(valueof(tx_msg), AMI_FIELD_ACTION_ID);
@ -290,11 +346,11 @@ function f_ami_transceive_match_response_success(TELNETasp_PT pt,
f_ami_transceive_match(pt, tx_msg, exp_resp);
}
function f_ami_action_login(TELNETasp_PT pt, charstring username, charstring secret) {
function f_ami_action_login(AMI_Msg_PT pt, charstring username, charstring secret) {
f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret));
}
function f_ami_action_PJSIPRegister(TELNETasp_PT pt, charstring register) {
function f_ami_action_PJSIPRegister(AMI_Msg_PT pt, charstring register) {
var charstring reg_action_id := f_gen_action_id();
f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPRegister(register, reg_action_id));
}

View File

@ -49,7 +49,9 @@ type component test_CT {
/* Manages the IMS server Asterisk connects to: */
var SIP_Emulation_CT vc_IMS;
port TELNETasp_PT AMI;
/* Connection towards Asterisk AMI iface: */
var AMI_Adapter_CT vc_AMI;
port AMI_Msg_PT AMI_CLIENT;
port Coord_PT COORD;
port IMSCoord_PT IMS_COORD;
@ -70,8 +72,13 @@ function f_init_ConnHdlrPars(integer idx := 1) runs on test_CT return SIPConnHdl
/* Initialize connection towards Asterisk AMI */
private function f_init_ami() runs on test_CT {
map(self:AMI, system:AMI);
f_ami_action_login(AMI, mp_ami_user, mp_ami_secret);
var charstring id := "Asterisk_Tests_AMI_EMU";
vc_AMI := AMI_Adapter_CT.create(id);
connect(self:AMI_CLIENT, vc_AMI:CLIENT);
vc_AMI.start(f_AMI_Adapter_main());
f_ami_action_login(AMI_CLIENT, mp_ami_user, mp_ami_secret);
}
/* Local SIP UAs */