blob: be1be7015cfc7e27dca9ae26f6739720daff49be [file] [log] [blame]
/* Asterisk's AMI interface functions in TTCN-3
* (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
* 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
*/
/*
* https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/AMI-v2-Specification/
*/
module AMI_Functions {
import from Misc_Helpers all;
import from Osmocom_Types all;
import from IPL4asp_Types all;
import from IPL4asp_PortType all;
import from Socket_API_Definitions all;
import from TCCConversion_Functions all;
modulepar {
float mp_ami_prompt_timeout := 10.0;
}
const charstring AMI_FIELD_ACTION := "Action";
const charstring AMI_FIELD_ACTION_ID := "ActionID";
const charstring AMI_FIELD_EVENT := "Event";
const charstring AMI_FIELD_INFO := "Info";
const charstring AMI_FIELD_USERNAME := "Username";
const charstring AMI_FIELD_SECRET := "Secret";
const charstring AMI_FIELD_RESPONSE := "Response";
/* Extensions: */
const charstring AMI_FIELD_ALGORITHM := "Algorithm";
const charstring AMI_FIELD_AUTN := "AUTN";
const charstring AMI_FIELD_AUTS := "AUTS";
const charstring AMI_FIELD_CK := "CK";
const charstring AMI_FIELD_IK := "IK";
const charstring AMI_FIELD_RAND := "RAND";
const charstring AMI_FIELD_RES := "RES";
const charstring AMI_FIELD_REGISTRATION := "Registration";
type record AMI_Field {
charstring key,
charstring val
} with {
encode "TEXT"
variant "SEPARATOR(': ', ':\s+')"
};
type set of AMI_Field AMI_Msg with {
encode "TEXT"
variant "SEPARATOR('\r\n', '(\r\n)|[\n]')"
variant "END('\r\n', '(\r\n)|[\n]')"
};
external function enc_AMI_Msg(in AMI_Msg msg) return charstring
with { extension "prototype(convert) encode(TEXT)" }
external function dec_AMI_Msg(in charstring stream) return AMI_Msg
with { extension "prototype(convert) decode(TEXT)" }
template (value) AMI_Field
ts_AMI_Field(template (value) charstring key,
template (value) charstring val) := {
key := key,
val := val
};
template (present) AMI_Field
tr_AMI_Field(template (present) charstring key := ?,
template (present) charstring val := ?) := {
key := key,
val := val
};
/*
* Field Templates:
*/
template (value) AMI_Field
ts_AMI_Field_Action(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION, val);
template (value) AMI_Field
ts_AMI_Field_ActionId(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION_ID, val);
template (value) AMI_Field
ts_AMI_Field_Event(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_EVENT, val);
template (value) AMI_Field
ts_AMI_Field_Info(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_INFO, val);
template (value) AMI_Field
ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, val);
template (value) AMI_Field
ts_AMI_Field_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val);
/* Extensions: */
template (value) AMI_Field
ts_AMI_Field_Algorithm(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ALGORITHM, val);
template (value) AMI_Field
ts_AMI_Field_AUTN(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_AUTN, val);
template (value) AMI_Field
ts_AMI_Field_AUTS(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_AUTS, val);
template (value) AMI_Field
ts_AMI_Field_CK(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CK, val);
template (value) AMI_Field
ts_AMI_Field_IK(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_IK, val);
template (value) AMI_Field
ts_AMI_Field_RAND(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_RAND, val);
template (value) AMI_Field
ts_AMI_Field_RES(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_RES, val);
template (value) AMI_Field
ts_AMI_Field_Registration(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_REGISTRATION, val);
template (present) AMI_Field
tr_AMI_Field_Action(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ACTION, val);
template (present) AMI_Field
tr_AMI_Field_ActionId(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ACTION_ID, val);
template (present) AMI_Field
tr_AMI_Field_Event(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_EVENT, val);
template (present) AMI_Field
tr_AMI_Field_Info(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_INFO, val);
template (present) AMI_Field
tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_USERNAME, val);
template (present) AMI_Field
tr_AMI_Field_Secret(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_SECRET, val);
template (present) AMI_Field
tr_AMI_Field_Response(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RESPONSE, val);
/* Extensions: */
template (present) AMI_Field
tr_AMI_Field_Algorithm(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ALGORITHM, val);
template (present) AMI_Field
tr_AMI_Field_AUTN(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_AUTN, val);
template (present) AMI_Field
tr_AMI_Field_AUTS(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_AUTS, val);
template (present) AMI_Field
tr_AMI_Field_CK(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CK, val);
template (present) AMI_Field
tr_AMI_Field_IK(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_IK, val);
template (present) AMI_Field
tr_AMI_Field_RAND(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RAND, val);
template (present) AMI_Field
tr_AMI_Field_RES(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RES, val);
template (present) AMI_Field
tr_AMI_Field_Registration(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_REGISTRATION, val);
template (present) AMI_Field
tr_AMI_Field_ResponseSuccess := tr_AMI_Field(pattern @nocase AMI_FIELD_RESPONSE, "Success");
/***********************
* Message Templates:
***********************/
/*
* ACTIONS
*/
/* Action: AuthResponse
* Registration: volte_ims
* AUTS: <value>
*/
template (value) AMI_Msg
ts_AMI_Action_AuthResponse_AUTS(template (value) charstring registration,
template (value) charstring auts,
template (value) charstring action_id := "0001") := {
ts_AMI_Field_Action("AuthResponse"),
ts_AMI_Field_ActionId(action_id),
ts_AMI_Field_Registration(registration),
ts_AMI_Field_AUTS(auts)
};
/* Action: AuthResponse
* Registration: volte_ims
* RES: <value>
* CK: <value>
* IK: <value>
*/
template (value) AMI_Msg
ts_AMI_Action_AuthResponse_RES(template (value) charstring registration,
template (value) charstring res,
template (value) charstring ck,
template (value) charstring ik,
template (value) charstring action_id := "0001") := {
ts_AMI_Field_Action("AuthResponse"),
ts_AMI_Field_ActionId(action_id),
ts_AMI_Field_Registration(registration),
ts_AMI_Field_RES(res),
ts_AMI_Field_CK(ck),
ts_AMI_Field_IK(ik)
};
/* Action: Login
* Username: <value>
* Secret: <value>
*/
template (value) AMI_Msg
ts_AMI_Action_Login(charstring username,
charstring secret,
template (value) charstring action_id := "0001") := {
ts_AMI_Field_Action("Login"),
ts_AMI_Field_ActionId(action_id),
ts_AMI_Field_Username(username),
ts_AMI_Field_Secret(secret)
};
template (present) AMI_Msg
tr_AMI_Action_Login(template(present) charstring username := ?,
template(present) charstring secret := ?,
template (present) charstring action_id := ?) := superset(
tr_AMI_Field_Action("Login"),
tr_AMI_Field_ActionId(action_id),
tr_AMI_Field_Username(username),
tr_AMI_Field_Secret(secret)
);
/* Action: PJSIPAccessNetworkInfo
* Registration: volte_ims
* Info: 3GPP-E-UTRAN-FDD; utran-cell-id-3gpp=2380100010000101
*/
template (value) AMI_Msg
ts_AMI_Action_PJSIPAccessNetworkInfo(template (value) charstring registration := "volte_ims",
template (value) charstring info := "",
template (value) charstring action_id := "0001") := {
ts_AMI_Field_Action("PJSIPAccessNetworkInfo"),
ts_AMI_Field_ActionId(action_id),
ts_AMI_Field_Registration(registration),
ts_AMI_Field_Info(info)
};
function f_ami_gen_PJSIPAccessNetworkInfo_Info_EUTRAN(charstring uli_str) return charstring {
return "3GPP-E-UTRAN-FDD; utran-cell-id-3gpp=" & uli_str;
}
/* Action: PJSIPRegister
* ActionID: <value>
* Registration: volte_ims
*/
template (value) AMI_Msg
ts_AMI_Action_PJSIPRegister(template (value) charstring registration := "volte_ims",
template (value) charstring action_id := "0001") := {
ts_AMI_Field_Action("PJSIPRegister"),
ts_AMI_Field_ActionId(action_id),
ts_AMI_Field_Registration(registration)
};
template (present) AMI_Msg
tr_AMI_Action_PJSIPRegister(template (present) charstring registration := ?,
template (present) charstring action_id := ?) := {
tr_AMI_Field_Action("PJSIPRegister"),
tr_AMI_Field_ActionId(action_id),
tr_AMI_Field_Registration(registration)
};
/*
* RESPONSES
*/
/* Response: Success
*/
template (present) AMI_Msg
tr_AMI_Response_Success := superset(
tr_AMI_Field_ResponseSuccess
);
/* Response: Success
* ActionId: <value>
*/
template (present) AMI_Msg
tr_AMI_Response_Success_ActionId(template (present) charstring action_id := ?) := superset(
tr_AMI_Field_ResponseSuccess,
tr_AMI_Field_ActionId(action_id)
);
/*
* EVENTS
*/
template (present) AMI_Msg
tr_AMI_Event(template (present) charstring ev_name := ?) := superset(
tr_AMI_Field_Event(ev_name)
);
/* Event: FullyBooted
* Privilege: system,all
* Status: Fully Booted
* Uptime: 4
* LastReload: 4 *
*/
template (present) AMI_Msg
tr_AMI_Event_FullyBooted := tr_AMI_Event("FullyBooted");
/* Event: AuthRequest
* Privilege: <none>
* Registration: volte_ims
* Algorithm: AKAv1-MD5
* RAND: 14987631f65f8e3788a0798b6ebcd08e
* AUTN: f6e19a7ccb028000a06b19c9544516e5
*/
template (present) AMI_Msg
tr_AMI_Event_AuthRequest(template (present) charstring registration := ?,
template (present) charstring algorithm := "AKAv1-MD5",
template (present) charstring rand := ?,
template (present) charstring autn := ?) := superset(
tr_AMI_Field_Event("AuthRequest"),
tr_AMI_Field_Registration(registration),
tr_AMI_Field_Algorithm(algorithm),
tr_AMI_Field_RAND(rand),
tr_AMI_Field_AUTN(autn)
);
/***********************
* Adapter:
***********************/
type record AMI_Adapter_Parameters {
charstring remote_host,
IPL4asp_Types.PortNumber remote_port,
charstring local_host,
IPL4asp_Types.PortNumber local_port,
charstring welcome_str
}
const AMI_Adapter_Parameters c_default_AMI_Adapter_pars := {
remote_host := "127.0.0.1",
remote_port := 5038,
local_host := "0.0.0.0",
local_port := 0,
welcome_str := "Asterisk Call Manager/9.0.0\r\n"
};
type port AMI_Msg_PT message {
inout AMI_Msg;
} with { extension "internal" };
type component AMI_Adapter_CT {
port IPL4asp_PT IPL4;
port AMI_Msg_PT CLIENT;
var AMI_Adapter_Parameters g_pars;
/* Connection identifier of the client itself */
var IPL4asp_Types.ConnectionId g_self_conn_id := -1;
}
/* Function to use to connect as client to a remote IPA Server */
private function f_AMI_Adapter_connect() runs on AMI_Adapter_CT {
var IPL4asp_Types.Result res;
map(self:IPL4, system:IPL4);
res := IPL4asp_PortType.f_IPL4_connect(IPL4, g_pars.remote_host, g_pars.remote_port,
g_pars.local_host, g_pars.local_port, 0, { tcp:={} });
if (not ispresent(res.connId)) {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("Could not connect AMI socket from ", g_pars.local_host, " port ",
g_pars.local_port, " to ", g_pars.remote_host, " port ", g_pars.remote_port,
"; check your configuration"));
}
g_self_conn_id := res.connId;
log("AMI connected, ConnId=", g_self_conn_id)
}
private function f_ASP_RecvFrom_msg_to_charstring(ASP_RecvFrom rx_rf) return charstring {
return oct2char(rx_rf.msg);
}
/* Function to use to connect as client to a remote IPA Server */
private function f_AMI_Adapter_wait_rx_welcome_str() runs on AMI_Adapter_CT {
var ASP_RecvFrom rx_rf;
var charstring rx_str;
timer Twelcome := 3.0;
Twelcome.start;
alt {
[] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf {
rx_str := f_ASP_RecvFrom_msg_to_charstring(rx_rf);
if (g_pars.welcome_str != rx_str) {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("AMI Welcome message mismatch: '", rx_str,
"' vs exp '", g_pars.welcome_str, "'"));
}
}
[] Twelcome.timeout {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("AMI Welcome timeout"));
}
}
Twelcome.stop;
log("AMI Welcome message received: '", rx_str, "'");
}
private function dec_AMI_Msg_ext(charstring txt) return AMI_Msg {
log("AMI dec: '", txt, "'");
/* TEXT Enc/dec is not happy with empty values, workaround it: */
var charstring patched_txt := f_str_replace(txt, "Challenge: \r\n", "");
patched_txt := f_str_replace(patched_txt, "AccountCode: \r\n", "");
patched_txt := f_str_replace(patched_txt, "Value: \r\n", "");
patched_txt := f_str_replace(patched_txt, "DestExten: \r\n", "");
patched_txt := f_str_replace(patched_txt, "Exten: \r\n", "");
patched_txt := f_str_replace(patched_txt, "Extension: \r\n", "");
patched_txt := f_str_replace(patched_txt, "Hint: \r\n", "");
/* "AppData" field sometimes has a value containing separator ": ", which makes
* TEXT dec not happy. Workaround it for now by removing the whole field line:
* "AppData: 5,0502: Call pjsip endpoint from 0501\r\n"
*/
var integer pos := f_strstr(patched_txt, "AppData: ", 0);
if (pos >= 0) {
var integer pos_end := f_strstr(patched_txt, "\r\n", pos) + lengthof("\r\n");
var charstring to_remove := substr(patched_txt, pos, pos_end - pos);
patched_txt := f_str_replace(patched_txt, to_remove, "");
}
log("AMI patched dec: '", patched_txt, "'");
return dec_AMI_Msg(patched_txt);
}
function f_AMI_Adapter_main(AMI_Adapter_Parameters pars := c_default_AMI_Adapter_pars)
runs on AMI_Adapter_CT {
var AMI_Msg msg;
var charstring rx, buf := "";
var integer fd;
var ASP_RecvFrom rx_rf;
var ASP_Event rx_ev;
g_pars := pars;
f_AMI_Adapter_connect();
f_AMI_Adapter_wait_rx_welcome_str();
while (true) {
alt {
[] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf {
var charstring rx_str := f_ASP_RecvFrom_msg_to_charstring(rx_rf);
log("AMI rx: '", rx_str, "'");
buf := buf & rx_str;
log("AMI buf: '", buf, "'");
/* If several messages come together */
var boolean last_is_complete := f_str_endswith(buf, "\r\n\r\n");
var Misc_Helpers.ro_charstring msgs := f_str_split(buf, "\r\n\r\n");
log("AMI split: ", msgs);
if (lengthof(msgs) > 0) {
for (var integer i := 0; i < lengthof(msgs) - 1; i := i + 1) {
var charstring txt := msgs[i] & "\r\n";
msg := dec_AMI_Msg_ext(txt);
CLIENT.send(msg);
}
if (last_is_complete) {
var charstring txt := msgs[lengthof(msgs) - 1] & "\r\n";
msg := dec_AMI_Msg_ext(txt);
CLIENT.send(msg);
buf := "";
} else {
buf := msgs[lengthof(msgs) - 1];
}
}
log("AMI remain buf: '", buf, "'");
}
[] IPL4.receive(ASP_ConnId_ReadyToRelease:?) {
}
[] IPL4.receive(ASP_Event:?) -> value rx_ev {
log("Rx AMI ASP_Event: ", rx_ev);
}
[] 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) & "\r\n";
var ASP_SendTo tx := {
connId := g_self_conn_id,
remName := g_pars.remote_host,
remPort := g_pars.remote_port,
proto := { tcp := {} },
msg := char2oct(tx_txt)
};
IPL4.send(tx);
}
}
}
}
/*
* Functions:
*/
/* Generate a random "ActionId" value: */
function f_gen_action_id() return charstring {
return hex2str(f_rnd_hexstring(16));
}
function f_ami_msg_find(AMI_Msg msg,
template (present) charstring key := ?)
return template (omit) AMI_Field {
var integer i;
for (i := 0; i < lengthof(msg); i := i + 1) {
if (not ispresent(msg[i])) {
continue;
}
if (match(msg[i].key, key)) {
return msg[i];
}
}
return omit;
}
function f_ami_msg_find_or_fail(AMI_Msg msg,
template (present) charstring key := ?)
return AMI_Field {
var template (omit) AMI_Field field;
field := f_ami_msg_find(msg, key);
if (istemplatekind(field, "omit")) {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("Key ", key, " not found in ", msg));
}
return valueof(field);
}
function f_ami_msg_get_value(AMI_Msg msg,
template (present) charstring key := ?)
return template (omit) charstring {
var template (omit) AMI_Field field;
field := f_ami_msg_find(msg, key);
if (istemplatekind(field, "omit")) {
return omit;
}
return field.val;
}
function f_ami_msg_get_value_or_fail(AMI_Msg msg,
template (present) charstring key := ?)
return template charstring {
var AMI_Field field;
field := f_ami_msg_find_or_fail(msg, key);
return field.val;
}
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(rx_timeout);
pt.send(tx_msg);
alt {
[] pt.receive(AMI_Msg:?) -> value rx_msg;
[] T.timeout {
Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
log2str("AMI Response timeout: ", tx_msg));
}
}
T.stop;
return rx_msg;
}
altstep as_ami_rx_ignore(AMI_Msg_PT pt)
{
var AMI_Msg msg;
[] pt.receive(AMI_Msg:?) -> value msg {
log("Ignoring AMI message := ", msg);
repeat;
}
}
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("Received unexpected AMI message := ", msg, "\nvs exp := ", exp_msg));
}
}
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);
if (isvalue(action_id)) {
exp_resp := tr_AMI_Response_Success_ActionId(action_id);
} else {
exp_resp := tr_AMI_Response_Success;
}
f_ami_transceive_match(pt, tx_msg, exp_resp);
}
function f_ami_action_login(AMI_Msg_PT pt, charstring username, charstring secret) {
var charstring reg_action_id := f_gen_action_id();
f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret, reg_action_id));
}
function f_ami_action_PJSIPAccessNetworkInfo(AMI_Msg_PT pt,
template (value) charstring registration,
template (value) charstring info) {
var charstring reg_action_id := f_gen_action_id();
f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPAccessNetworkInfo(registration, info, reg_action_id));
}
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));
}
function f_ami_action_AuthResponse_AUTS(AMI_Msg_PT pt,
template (value) charstring registration,
template (value) charstring auts) {
var charstring reg_action_id := f_gen_action_id();
f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_AUTS(registration, auts, reg_action_id));
}
function f_ami_action_AuthResponse_RES(AMI_Msg_PT pt,
template (value) charstring registration,
template (value) charstring res,
template (value) charstring ck,
template (value) charstring ik) {
var charstring reg_action_id := f_gen_action_id();
f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_RES(registration, res, ck, ik, reg_action_id));
}
private function f_ami_selftest_decode(charstring txt) {
log("Text to decode: '", txt, "'");
var AMI_Msg msg := dec_AMI_Msg(txt);
log("AMI_Msg decoded: ", msg);
}
function f_ami_selftest() {
f_ami_selftest_decode("AppData: 5,0502: Call pjsip endpoint from 0501\r\n");
}
}