aboutsummaryrefslogtreecommitdiffstats
path: root/asterisk/AMI_Functions.ttcn
diff options
context:
space:
mode:
Diffstat (limited to 'asterisk/AMI_Functions.ttcn')
-rw-r--r--asterisk/AMI_Functions.ttcn793
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");
+}
+
+}