diff options
Diffstat (limited to 'asterisk/AMI_Functions.ttcn')
-rw-r--r-- | asterisk/AMI_Functions.ttcn | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/asterisk/AMI_Functions.ttcn b/asterisk/AMI_Functions.ttcn new file mode 100644 index 00000000..2acb8768 --- /dev/null +++ b/asterisk/AMI_Functions.ttcn @@ -0,0 +1,793 @@ +/* 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_CHANNEL := "Channel"; +const charstring AMI_FIELD_CHAN_TYPE := "ChannelType"; +const charstring AMI_FIELD_CONTEXT := "Context"; +const charstring AMI_FIELD_DOMAIN := "Domain"; +const charstring AMI_FIELD_EVENT := "Event"; +const charstring AMI_FIELD_INFO := "Info"; +const charstring AMI_FIELD_RESPONSE := "Response"; +const charstring AMI_FIELD_SECRET := "Secret"; +const charstring AMI_FIELD_STATUS := "Status"; +const charstring AMI_FIELD_USERNAME := "Username"; + +/* 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_Channel(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CHANNEL, val); +template (value) AMI_Field +ts_AMI_Field_ChannelType(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CHAN_TYPE, val); +template (value) AMI_Field +ts_AMI_Field_Context(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CONTEXT, val); +template (value) AMI_Field +ts_AMI_Field_Domain(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_DOMAIN, 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_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val); +template (value) AMI_Field +ts_AMI_Field_Status(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_STATUS, val); +template (value) AMI_Field +ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, 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_Channel(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CHANNEL, val); +template (present) AMI_Field +tr_AMI_Field_ChannelType(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CHAN_TYPE, val); +template (present) AMI_Field +tr_AMI_Field_Context(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CONTEXT, val); +template (present) AMI_Field +tr_AMI_Field_Domain(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_DOMAIN, 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_Response(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RESPONSE, 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_Status(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_STATUS, val); +template (present) AMI_Field +tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_USERNAME, 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: DedicatedBearerStatus + * ActionID: <value> + * Channel: <value> + * Status: <value> + */ +template (value) AMI_Msg +ts_AMI_Action_DedicatedBearerStatus(template (value) charstring channel, + template (value) charstring status, + template (value) charstring action_id := "0001") := { + ts_AMI_Field_Action("DedicatedBearerStatus"), + ts_AMI_Field_ActionId(action_id), + ts_AMI_Field_Channel(channel), + ts_AMI_Field_Status(status) +}; + +/* 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) +}; + +/* Action: PJSIPUnregister + * ActionID: <value> + * Registration: volte_ims + */ +template (value) AMI_Msg +ts_AMI_Action_PJSIPUnregister(template (value) charstring registration := "volte_ims", + template (value) charstring action_id := "0001") := { + ts_AMI_Field_Action("PJSIPUnregister"), + ts_AMI_Field_ActionId(action_id), + ts_AMI_Field_Registration(registration) +}; +template (present) AMI_Msg +tr_AMI_Action_PJSIPUnregister(template (present) charstring registration := ?, + template (present) charstring action_id := ?) := { + tr_AMI_Field_Action("PJSIPUnregister"), + 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) +); + +/* Event: Registry + * Privilege: system,all + * ChannelType: PJSIP + * Username: sip:238010000090828@172.18.155.103 + * Domain: sip:172.18.155.103 + * Status: Registered + */ +template (present) AMI_Msg +tr_AMI_Event_Registry(template (present) charstring username := ?, + template (present) charstring domain := ?, + template (present) charstring status := ?, + template (present) charstring chan_type := "PJSIP") := superset( + tr_AMI_Field_Event("Registry"), + tr_AMI_Field_ChannelType(chan_type), + tr_AMI_Field_Username(username), + tr_AMI_Field_Domain(domain), + tr_AMI_Field_Status(status) +); + +/* Event: Newchannel + * Privilege: call,all + * Channel: PJSIP/volte_ims-00000001 + * ChannelState: 0 + * ChannelStateDesc: Down + * CallerIDNum: <unknown> + * CallerIDName: <unknown> + * ConnectedLineNum: <unknown> + * ConnectedLineName: <unknown> + * Language: en + * AccountCode: + * Context: volte_ims + * Exten: s + * Priority: 1 + * Uniqueid: 1718732522.1 + * LinkEvent: Newstate + * Privilege: call,all + * Channel: PJSIP/volte_ims-00000001 + * ChannelState: 5 + * ChannelStateDesc: Ringing + * CallerIDNum: 90829 + * CallerIDName: <unknown> + * ConnectedLineNum: 0501 + * ConnectedLineName: <unknown> + * Language: en + * AccountCode: + * Context: volte_ims + * Exten: 90829 + * Priority: 1 + * Uniqueid: 1718732522.1 + * Linkedid: 1718732522.0 + */ +template (present) AMI_Msg +tr_AMI_Event_Newchannel(template (present) charstring context := ?) := superset( + tr_AMI_Field_Event("Newchannel"), + tr_AMI_Field_Context(context) +); + +/*********************** + * 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 Misc_Helpers.ro_charstring fields := { "AccountCode", "AccountID", "Challenge", "DestExten", "Exten", "Extension", "Hint", "Value" }; + var charstring patched_txt := txt; + for (var integer i := 0; i < lengthof(fields); i := i +1) { + patched_txt := f_str_replace(patched_txt, fields[i] & ": \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_wait_rx_msg(AMI_Msg_PT pt, + template (present) AMI_Msg msg_expect := ?, + boolean fail_others := true, + float rx_timeout := 10.0) return AMI_Msg { + var AMI_Msg ami_msg; + timer tAMI; + + tAMI.start(rx_timeout); + alt { + [] pt.receive(msg_expect) -> value ami_msg; + [not fail_others] as_ami_rx_ignore(pt); + [fail_others] as_ami_rx_fail(pt, msg_expect); + [] tAMI.timeout { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("AMI msg timeout: ", msg_expect)); + } + } + tAMI.stop; + return ami_msg; +} + +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 { + pt.send(tx_msg); + return f_ami_wait_rx_msg(pt, exp_ret, fail_others, rx_timeout); +} + +function f_ami_transceive_match_response_success(AMI_Msg_PT pt, + template (value) AMI_Msg tx_msg, + boolean fail_others := true) { + 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, fail_others := fail_others); +} + +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_DedicatedBearerStatus(AMI_Msg_PT pt, + template (value) charstring channel, + template (value) charstring status, + boolean fail_others := true) { + var charstring reg_action_id := f_gen_action_id(); + f_ami_transceive_match_response_success(pt, ts_AMI_Action_DedicatedBearerStatus(channel, status, reg_action_id), + fail_others := fail_others); +} + +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_PJSIPUnregister(AMI_Msg_PT pt, + charstring register, + boolean fail_others := true) { + var charstring reg_action_id := f_gen_action_id(); + f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPUnregister(register, reg_action_id), + fail_others := fail_others); +} + +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"); +} + +} |