blob: 53a79642944b9e9449a42c83c396b2b14f653520 [file] [log] [blame]
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +02001/* Asterisk's AMI interface functions in TTCN-3
2 * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
3 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
4 * All rights reserved.
5 *
6 * Released under the terms of GNU General Public License, Version 2 or
7 * (at your option) any later version.
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12/*
13 * https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/AMI-v2-Specification/
14 */
15module AMI_Functions {
16
17import from Misc_Helpers all;
18import from TELNETasp_PortType all;
19import from Osmocom_Types all;
20import from TCCConversion_Functions all;
21import from Socket_API_Definitions all;
22
23modulepar {
24 float mp_ami_prompt_timeout := 10.0;
25}
26
27const charstring AMI_FIELD_ACTION := "Action";
28const charstring AMI_FIELD_USERNAME := "Username";
29const charstring AMI_FIELD_SECRET := "Secret";
30const charstring AMI_FIELD_RESPONSE := "Response";
31
32type record AMI_Field {
33 charstring key,
34 charstring val
35};
36type set of AMI_Field AMI_Msg;
37
38template (value) AMI_Field
39ts_AMI_Field(template (value) charstring key,
40 template (value) charstring val) := {
41 key := key,
42 val := val
43};
44
45template (present) AMI_Field
46tr_AMI_Field(template (present) charstring key := ?,
47 template (present) charstring val := ?) := {
48 key := key,
49 val := val
50};
51
52/*
53 * Field Templates:
54 */
55
56template (value) AMI_Field
57ts_AMI_Field_Action(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION, val);
58template (value) AMI_Field
59ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, val);
60template (value) AMI_Field
61ts_AMI_Field_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val);
62
63template (present) AMI_Field
64tr_AMI_Field_Action(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_ACTION, val);
65template (present) AMI_Field
66tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_USERNAME, val);
67template (present) AMI_Field
68tr_AMI_Field_Secret(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_SECRET, val);
69template (present) AMI_Field
70tr_AMI_Field_Response(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_RESPONSE, val);
71
72
73template (present) AMI_Field
74tr_AMI_Field_ResponseSuccess := tr_AMI_Field(AMI_FIELD_RESPONSE, "Success");
75
76
77/*
78 * Message Templates:
79 */
80
81template (value) AMI_Msg
82ts_AMI_Action_Login(charstring username, charstring secret) := {
83 ts_AMI_Field_Action("Login"),
84 ts_AMI_Field_Username(username),
85 ts_AMI_Field_Secret(secret)
86};
87
88template (present) AMI_Msg
89tr_AMI_Action_Login(template(present) charstring username := ?,
90 template(present) charstring secret := ?) := superset(
91 tr_AMI_Field_Action("Login"),
92 tr_AMI_Field_Username(username),
93 tr_AMI_Field_Secret(secret)
94);
95
96template (present) AMI_Msg
97tr_AMI_Response_Success := superset(
98 tr_AMI_Field_ResponseSuccess
99);
100
101/*
102 * Functions:
103 */
104
105function f_AMI_Field_from_str(charstring str) return AMI_Field {
106 var AMI_Field field;
107 /* "each field is a key value pair delineated by a ':'.
108 * A single space MUST follow the ':' and precede the value. "*/
109 var integer pos := f_strstr(str, ": ", 0);
110 if (pos < 0) {
111 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
112 log2str("Failed parsing AMI_Field: ", str));
113 }
114 field.key := substr(str, 0, pos);
115 /* skip ": " */
116 pos := pos + 2;
117 field.val := substr(str, pos, lengthof(str) - pos);
118 return field;
119}
120
121function f_AMI_Msg_from_str(charstring str) return AMI_Msg {
122 var AMI_Msg msg := {};
123 var Misc_Helpers.ro_charstring lines := f_str_split(str, "\n");
124
125 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
126 var charstring line := lines[i];
127 var AMI_Field field := f_AMI_Field_from_str(lines[i]);
128 msg := msg & { field };
129 }
130 return msg;
131}
132
133function f_AMI_Field_to_str(AMI_Field field) return charstring {
134 return field.key & ": " & field.val;
135}
136
137function f_AMI_Msg_to_str(AMI_Msg msg) return charstring {
138 var charstring str := "";
139
140 for (var integer i := 0; i < lengthof(msg); i := i + 1) {
141 str := str & f_AMI_Field_to_str(msg[i]) & "\r\n";
142 }
143
144 str := str & "\r\n";
145 return str;
146}
147
148private function f_ami_wait_for_prompt_str(TELNETasp_PT pt, charstring log_label := "(?)")
149return charstring {
150 var charstring rx, buf := "";
151 var integer fd;
152 timer T;
153
154 T.start(mp_ami_prompt_timeout);
155 alt {
156 [] pt.receive(pattern "\n") { };
157 [] pt.receive(charstring:?) -> value rx { buf := buf & rx; repeat };
158 [] pt.receive(integer:?) -> value fd {
159 if (fd == -1) {
160 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
161 "AMI Telnet Connection Failure: " & log_label);
162 } else {
163 repeat; /* telnet connection succeeded */
164 }
165 }
166 [] T.timeout {
167 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
168 "AMI Timeout for prompt: " & log_label);
169 };
170 }
171 T.stop;
172 return buf;
173}
174
175function f_ami_wait_for_prompt(TELNETasp_PT pt, charstring log_label := "(?)") return AMI_Msg {
176 var charstring buf := f_ami_wait_for_prompt_str(pt, log_label);
177 var AMI_Msg msg := f_AMI_Msg_from_str(buf);
178 return msg;
179}
180
181/* send a AMI command and obtain response until prompt is received */
182private function f_ami_transceive_ret_str(TELNETasp_PT pt, charstring tx) return charstring {
183 pt.send(tx);
184 return f_ami_wait_for_prompt_str(pt, tx);
185}
186
187function f_ami_transceive_ret(TELNETasp_PT pt, template (value) AMI_Msg tx_msg) return AMI_Msg {
188 var charstring tx_txt := f_AMI_Msg_to_str(valueof(tx_msg));
189 var charstring resp_txt := f_ami_transceive_ret_str(pt, tx_txt);
190 return f_AMI_Msg_from_str(resp_txt);
191}
192
193function f_ami_transceive_match(TELNETasp_PT pt,
194 template (value) AMI_Msg tx_msg,
195 template (present) AMI_Msg exp_ret := ?) {
196 var AMI_Msg ret := f_ami_transceive_ret(pt, tx_msg);
197 if (not match(ret, exp_ret)) {
198 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
199 log2str("Non-matching AMI response: ", ret, " vs exp: ", exp_ret));
200 }
201}
202
203function f_ami_transceive_match_response_success(TELNETasp_PT pt,
204 template (value) AMI_Msg tx_msg) {
205 f_ami_transceive_match(pt, tx_msg, tr_AMI_Response_Success);
206}
207
208function f_ami_action_login(TELNETasp_PT pt, charstring username, charstring secret) {
209 f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret));
210}
211
212}