pespin has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/36594?usp=email )


Change subject: Asterisk: Initial AMI support
......................................................................

Asterisk: Initial AMI support

Introduce config and functions around TELNETasp_PT to implement an AMI
interface client to interact with Asterisk.

So far only the "Action: Login" case is implemented.

Change-Id: I2c570e4d04e7ab8c44962cf484e4bbc946209aee
---
A asterisk/AMI_Functions.ttcn
M asterisk/Asterisk_Tests.default
M asterisk/Asterisk_Tests.ttcn
M asterisk/gen_links.sh
M asterisk/regen_makefile.sh
5 files changed, 257 insertions(+), 1 deletion(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks 
refs/changes/94/36594/1

diff --git a/asterisk/AMI_Functions.ttcn b/asterisk/AMI_Functions.ttcn
new file mode 100644
index 0000000..53a7964
--- /dev/null
+++ b/asterisk/AMI_Functions.ttcn
@@ -0,0 +1,212 @@
+/* Asterisk's AMI interface functions in TTCN-3
+ * (C) 2024 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ * Author: Pau Espin Pedrol <[email protected]>
+ * 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 TELNETasp_PortType all;
+import from Osmocom_Types all;
+import from TCCConversion_Functions all;
+import from Socket_API_Definitions all;
+
+modulepar {
+       float mp_ami_prompt_timeout := 10.0;
+}
+
+const charstring AMI_FIELD_ACTION := "Action";
+const charstring AMI_FIELD_USERNAME := "Username";
+const charstring AMI_FIELD_SECRET := "Secret";
+const charstring AMI_FIELD_RESPONSE := "Response";
+
+type record AMI_Field {
+       charstring      key,
+       charstring      val
+};
+type set of AMI_Field AMI_Msg;
+
+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_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);
+
+template (present) AMI_Field
+tr_AMI_Field_Action(template (present) charstring val := ?) := 
tr_AMI_Field(AMI_FIELD_ACTION, val);
+template (present) AMI_Field
+tr_AMI_Field_Username(template (present) charstring val := ?) := 
tr_AMI_Field(AMI_FIELD_USERNAME, val);
+template (present) AMI_Field
+tr_AMI_Field_Secret(template (present) charstring val := ?) := 
tr_AMI_Field(AMI_FIELD_SECRET, val);
+template (present) AMI_Field
+tr_AMI_Field_Response(template (present) charstring val := ?) := 
tr_AMI_Field(AMI_FIELD_RESPONSE, val);
+
+
+template (present) AMI_Field
+tr_AMI_Field_ResponseSuccess := tr_AMI_Field(AMI_FIELD_RESPONSE, "Success");
+
+
+/*
+ * Message Templates:
+ */
+
+template (value) AMI_Msg
+ts_AMI_Action_Login(charstring username, charstring secret) := {
+       ts_AMI_Field_Action("Login"),
+       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 := ?) := superset(
+       tr_AMI_Field_Action("Login"),
+       tr_AMI_Field_Username(username),
+       tr_AMI_Field_Secret(secret)
+);
+
+template (present) AMI_Msg
+tr_AMI_Response_Success := superset(
+       tr_AMI_Field_ResponseSuccess
+);
+
+/*
+ * Functions:
+ */
+
+function f_AMI_Field_from_str(charstring str) return AMI_Field {
+       var AMI_Field field;
+       /* "each field is a key value pair delineated by a ':'.
+        * A single space MUST follow the ':' and precede the value. "*/
+       var integer pos := f_strstr(str, ": ", 0);
+       if (pos < 0) {
+               Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+                                       log2str("Failed parsing AMI_Field: ", 
str));
+       }
+       field.key := substr(str, 0, pos);
+       /* skip ": " */
+       pos := pos + 2;
+       field.val := substr(str, pos, lengthof(str) - pos);
+       return field;
+}
+
+function f_AMI_Msg_from_str(charstring str) return AMI_Msg {
+       var AMI_Msg msg := {};
+       var Misc_Helpers.ro_charstring lines := f_str_split(str, "\n");
+
+       for (var integer i := 0; i < lengthof(lines); i := i + 1) {
+               var charstring line := lines[i];
+               var AMI_Field field := f_AMI_Field_from_str(lines[i]);
+               msg := msg & { field };
+       }
+       return msg;
+}
+
+function f_AMI_Field_to_str(AMI_Field field) return charstring {
+       return field.key & ": " & field.val;
+}
+
+function f_AMI_Msg_to_str(AMI_Msg msg) return charstring {
+       var charstring str := "";
+
+       for (var integer i := 0; i < lengthof(msg); i := i + 1) {
+               str := str & f_AMI_Field_to_str(msg[i]) & "\r\n";
+       }
+
+       str := str & "\r\n";
+       return str;
+}
+
+private function f_ami_wait_for_prompt_str(TELNETasp_PT pt, charstring 
log_label := "(?)")
+return charstring {
+       var charstring rx, buf := "";
+       var integer fd;
+       timer T;
+
+       T.start(mp_ami_prompt_timeout);
+       alt {
+       [] pt.receive(pattern "\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 */
+               }
+       }
+       [] T.timeout {
+               Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+                                       "AMI Timeout for prompt: " & log_label);
+               };
+       }
+       T.stop;
+       return buf;
+}
+
+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 := f_AMI_Msg_from_str(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 := f_AMI_Msg_to_str(valueof(tx_msg));
+       var charstring resp_txt := f_ami_transceive_ret_str(pt, tx_txt);
+       return f_AMI_Msg_from_str(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)) {
+               Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+                       log2str("Non-matching AMI response: ", ret, " vs exp: 
", exp_ret));
+       }
+}
+
+function f_ami_transceive_match_response_success(TELNETasp_PT pt,
+                                                template (value) AMI_Msg 
tx_msg) {
+       f_ami_transceive_match(pt, tx_msg, tr_AMI_Response_Success);
+}
+
+function f_ami_action_login(TELNETasp_PT pt, charstring username, charstring 
secret) {
+       f_ami_transceive_match_response_success(pt, 
ts_AMI_Action_Login(username, secret));
+}
+
+}
diff --git a/asterisk/Asterisk_Tests.default b/asterisk/Asterisk_Tests.default
index a2fda0f..0996545 100644
--- a/asterisk/Asterisk_Tests.default
+++ b/asterisk/Asterisk_Tests.default
@@ -4,13 +4,24 @@
 mtc.FileMask := ERROR | WARNING | PARALLEL | VERDICTOP;

 [TESTPORT_PARAMETERS]
+*.*.DEBUG := "yes"
+*.AMI.PROMPT1 := "Asterisk Call Manager/9.0.0\n"
+*.AMI.PROMPT2 := "\n"
+#*.AMI.REGEX_PROMPT1 := "^Asterisk Call Manager.*$"
+*.AMI.CTRL_MODE := "client"
+*.AMI.CTRL_HOSTNAME := "127.0.0.1"
+*.AMI.CTRL_PORTNUM := "5038"
+*.AMI.CTRL_LOGIN_SKIPPED := "yes"
+*.AMI.CTRL_DETECT_SERVER_DISCONNECTED := "yes"
+*.AMI.CTRL_READMODE := "buffered"
+*.AMI.CTRL_CLIENT_CLEANUP_LINEFEED := "yes"
+*.AMI.CTRL_CRLF := "yes"
 *.SIP.local_sip_port := "5060"
 *.SIP.default_local_address := "127.0.0.2"
 *.SIP.default_sip_protocol := "UDP"
 *.SIP.default_dest_port := "5060"
 *.SIP.default_dest_address := "127.0.0.1"

-
 [MODULE_PARAMETERS]

 [MAIN_CONTROLLER]
diff --git a/asterisk/Asterisk_Tests.ttcn b/asterisk/Asterisk_Tests.ttcn
index 151041d..c883bac 100644
--- a/asterisk/Asterisk_Tests.ttcn
+++ b/asterisk/Asterisk_Tests.ttcn
@@ -16,6 +16,8 @@
 import from Osmocom_Types all;
 import from Native_Functions all;
 import from Misc_Helpers all;
+import from TELNETasp_PortType all;
+import from AMI_Functions all;

 import from SDP_Types all;
 import from SDP_Templates all;
@@ -29,6 +31,10 @@
        integer mp_local_sip_port := 5060;
        charstring mp_remote_sip_host := "127.0.0.1";
        integer mp_remote_sip_port := 5060;
+
+       /* Asterisk AMI: */
+       charstring mp_ami_user := "test_user";
+       charstring mp_ami_secret := "1234";
 }

 type port Coord_PT message
@@ -44,6 +50,7 @@

 type component test_CT {
        var SIP_Emulation_CT vc_SIP;
+       port TELNETasp_PT AMI;
        port Coord_PT COORD;
 }

@@ -159,7 +166,14 @@
        mt := t_CallParsMT
 }

+/* 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);
+}
+
 function f_init() runs on test_CT {
+       f_init_ami();
        f_init_sip(vc_SIP, "Asterisk_Test");
        log("end of f_init");
 }
diff --git a/asterisk/gen_links.sh b/asterisk/gen_links.sh
index 7394b64..97df3a2 100755
--- a/asterisk/gen_links.sh
+++ b/asterisk/gen_links.sh
@@ -18,6 +18,10 @@
 FILES="IPL4asp_Functions.ttcn  IPL4asp_PT.cc  IPL4asp_PT.hh 
IPL4asp_PortType.ttcn  IPL4asp_Types.ttcn  IPL4asp_discovery.cc 
IPL4asp_protocol_L234.hh"
 gen_links $DIR $FILES

+DIR=$BASEDIR/titan.TestPorts.TELNETasp/src
+FILES="TELNETasp_PT.cc  TELNETasp_PT.hh  TELNETasp_PortType.ttcn"
+gen_links $DIR $FILES
+
 DIR=$BASEDIR/titan.ProtocolModules.SDP/src
 FILES="SDP_EncDec.cc SDP_Types.ttcn SDP_parse_.tab.c SDP_parse_.tab.h 
SDP_parse_parser.h SDP_parser.l
 SDP_parser.y lex.SDP_parse_.c"
diff --git a/asterisk/regen_makefile.sh b/asterisk/regen_makefile.sh
index 3cd0a66..1aa18ae 100755
--- a/asterisk/regen_makefile.sh
+++ b/asterisk/regen_makefile.sh
@@ -15,6 +15,7 @@
        TCCConversion.cc
        TCCInterface.cc
        TCCOpenSecurity.cc
+       TELNETasp_PT.cc
 "

 ../regen-makefile.sh -e $NAME $FILES

--
To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/36594?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I2c570e4d04e7ab8c44962cf484e4bbc946209aee
Gerrit-Change-Number: 36594
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <[email protected]>
Gerrit-MessageType: newchange

Reply via email to