blob: 6e0b8d0cfbf1479ba1d814bd5fb5d3174b6f454d [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";
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020028const charstring AMI_FIELD_ACTION_ID := "ActionId";
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020029const charstring AMI_FIELD_USERNAME := "Username";
30const charstring AMI_FIELD_SECRET := "Secret";
31const charstring AMI_FIELD_RESPONSE := "Response";
32
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020033/* Extensions: */
34const charstring AMI_FIELD_REGISTRATION := "Registration";
35
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020036type record AMI_Field {
37 charstring key,
38 charstring val
Pau Espin Pedrolde7a4852024-04-19 16:20:45 +020039} with {
40 encode "TEXT"
41 variant "SEPARATOR(': ', ':\s+')"
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020042};
Pau Espin Pedrolde7a4852024-04-19 16:20:45 +020043
44type set of AMI_Field AMI_Msg with {
45 encode "TEXT"
46 variant "SEPARATOR('\r\n', '(\r\n)|[\n]')"
47 variant "END('\r\n', '(\r\n)|[\n]')"
48};
49
50external function enc_AMI_Msg(in AMI_Msg msg) return charstring
51 with { extension "prototype(convert) encode(TEXT)" }
52external function dec_AMI_Msg(in charstring stream) return AMI_Msg
53 with { extension "prototype(convert) decode(TEXT)" }
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020054
55template (value) AMI_Field
56ts_AMI_Field(template (value) charstring key,
57 template (value) charstring val) := {
58 key := key,
59 val := val
60};
61
62template (present) AMI_Field
63tr_AMI_Field(template (present) charstring key := ?,
64 template (present) charstring val := ?) := {
65 key := key,
66 val := val
67};
68
69/*
70 * Field Templates:
71 */
72
73template (value) AMI_Field
74ts_AMI_Field_Action(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION, val);
75template (value) AMI_Field
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020076ts_AMI_Field_ActionId(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION_ID, val);
77template (value) AMI_Field
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020078ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, val);
79template (value) AMI_Field
80ts_AMI_Field_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val);
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020081/* Extensions: */
82template (value) AMI_Field
83ts_AMI_Field_Registration(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_REGISTRATION, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020084
85template (present) AMI_Field
86tr_AMI_Field_Action(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_ACTION, val);
87template (present) AMI_Field
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020088tr_AMI_Field_ActionId(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_ACTION_ID, val);
89template (present) AMI_Field
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020090tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_USERNAME, val);
91template (present) AMI_Field
92tr_AMI_Field_Secret(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_SECRET, val);
93template (present) AMI_Field
94tr_AMI_Field_Response(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_RESPONSE, val);
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020095/* Extensions: */
96template (present) AMI_Field
97tr_AMI_Field_Registration(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_REGISTRATION, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020098
99
100template (present) AMI_Field
101tr_AMI_Field_ResponseSuccess := tr_AMI_Field(AMI_FIELD_RESPONSE, "Success");
102
103
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200104/***********************
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200105 * Message Templates:
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200106 ***********************/
107
108/*
109 * ACTIONS
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200110 */
111
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200112/* Action: Login
113 * Username: <value>
114 * Secret: <value>
115 */
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200116template (value) AMI_Msg
117ts_AMI_Action_Login(charstring username, charstring secret) := {
118 ts_AMI_Field_Action("Login"),
119 ts_AMI_Field_Username(username),
120 ts_AMI_Field_Secret(secret)
121};
122
123template (present) AMI_Msg
124tr_AMI_Action_Login(template(present) charstring username := ?,
125 template(present) charstring secret := ?) := superset(
126 tr_AMI_Field_Action("Login"),
127 tr_AMI_Field_Username(username),
128 tr_AMI_Field_Secret(secret)
129);
130
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200131/* Action: PJSIPRegister
132 * ActionID: <value>
133 * Registration: volte_ims
134 */
135template (value) AMI_Msg
136ts_AMI_Action_PJSIPRegister(template (value) charstring registration := "volte_ims",
137 template (value) charstring action_id := "0001") := {
138 ts_AMI_Field_Action("PJSIPRegister"),
139 ts_AMI_Field_ActionId(action_id),
140 ts_AMI_Field_Registration(registration)
141};
142template (present) AMI_Msg
143tr_AMI_Action_PJSIPRegister(template (present) charstring registration := ?,
144 template (present) charstring action_id := ?) := {
145 tr_AMI_Field_Action("PJSIPRegister"),
146 tr_AMI_Field_ActionId(action_id),
147 tr_AMI_Field_Registration(registration)
148};
149
150/*
151 * RESPONSES
152 */
153
154/* Response: Success
155 */
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200156template (present) AMI_Msg
157tr_AMI_Response_Success := superset(
158 tr_AMI_Field_ResponseSuccess
159);
160
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200161/* Response: Success
162 * ActionId: <value>
163 */
164template (present) AMI_Msg
165tr_AMI_Response_Success_ActionId(template (present) charstring action_id := ?) := superset(
166 tr_AMI_Field_ResponseSuccess,
167 tr_AMI_Field_ActionId(action_id)
168);
169
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200170
171/***********************
172 * Adapter:
173 ***********************/
174
175type port AMI_Msg_PT message {
176 inout AMI_Msg;
177} with { extension "internal" };
178
179type component AMI_Adapter_CT {
180 port TELNETasp_PT AMI;
181 port AMI_Msg_PT CLIENT;
182}
183
184function f_AMI_Adapter_main() runs on AMI_Adapter_CT {
185 var AMI_Msg msg;
186
187 var charstring rx, buf := "";
188 var integer fd;
189
190 map(self:AMI, system:AMI);
191
192 while (true) {
193
194 alt {
195 [] AMI.receive(pattern "\n") {
196 buf := buf & "\n";
197 msg := dec_AMI_Msg(buf);
198 buf := "";
199 CLIENT.send(msg);
200 };
201 [] AMI.receive(charstring:?) -> value rx {
202 buf := buf & rx;
203 };
204 [] AMI.receive(integer:?) -> value fd {
205 if (fd == -1) {
206 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
207 "AMI Telnet Connection Failure: " & int2str(fd));
208 } else {
209 /* telnet connection succeeded */
210 }
211 }
212 [] CLIENT.receive(AMI_Msg:?) -> value msg {
213 /* TODO: in the future, queue Action if there's already one Action in transit, to fullfill AMI requirements. */
214 var charstring tx_txt := enc_AMI_Msg(msg);
215 AMI.send(tx_txt);
216 }
217 }
218 }
219}
220
221
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200222/*
223 * Functions:
224 */
225
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200226/* Generate a random "ActionId" value: */
227function f_gen_action_id() return charstring {
228 return hex2str(f_rnd_hexstring(16));
229}
230
231function f_ami_msg_find(AMI_Msg msg,
232 template (present) charstring key := ?)
233return template (omit) AMI_Field {
234 var integer i;
235
236 for (i := 0; i < lengthof(msg); i := i + 1) {
237 if (not ispresent(msg[i])) {
238 continue;
239 }
240 if (match(msg[i].key, key)) {
241 return msg[i];
242 }
243 }
244 return omit;
245}
246
247function f_ami_msg_find_or_fail(AMI_Msg msg,
248 template (present) charstring key := ?)
249return AMI_Field {
250 var template (omit) AMI_Field field;
251 field := f_ami_msg_find(msg, key);
252 if (istemplatekind(field, "omit")) {
253 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
254 log2str("Key ", key, " not found in ", msg));
255 }
256 return valueof(field);
257}
258
259function f_ami_msg_get_value(AMI_Msg msg,
260 template (present) charstring key := ?)
261return template (omit) charstring {
262 var template (omit) AMI_Field field;
263 field := f_ami_msg_find(msg, key);
264 if (istemplatekind(field, "omit")) {
265 return omit;
266 }
267 return field.val;
268}
269
270function f_ami_msg_get_value_or_fail(AMI_Msg msg,
271 template (present) charstring key := ?)
272return template charstring {
273 var AMI_Field field;
274 field := f_ami_msg_find_or_fail(msg, key);
275 return field.val;
276}
277
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200278function f_ami_transceive_ret(AMI_Msg_PT pt, template (value) AMI_Msg tx_msg, float rx_timeout := 10.0) return AMI_Msg {
279 var AMI_Msg rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200280 timer T;
281
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200282 T.start(rx_timeout);
283 pt.send(tx_msg);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200284 alt {
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200285 [] pt.receive(AMI_Msg:?) -> value rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200286 [] T.timeout {
287 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200288 log2str("AMI Response timeout: ", tx_msg));
289 }
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200290 }
291 T.stop;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200292 return rx_msg;
293
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200294}
295
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200296private altstep as_ami_rx_fail(AMI_Msg_PT pt, template AMI_Msg exp_msg := *)
297{
298 var AMI_Msg msg;
299 [] pt.receive(AMI_Msg:?) -> value msg {
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200300 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200301 log2str("Received unexpected AMI message := ", msg, "\nvs exp := ", exp_msg));
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200302 }
303}
304
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200305altstep as_ami_expect_msg(AMI_Msg_PT pt, template (present) AMI_Msg msg_expect, boolean fail_others := true)
306{
307 [] pt.receive(msg_expect);
308 [fail_others] as_ami_rx_fail(pt, msg_expect);
309}
310
311function f_ami_transceive_match(AMI_Msg_PT pt,
312 template (value) AMI_Msg tx_msg,
313 template (present) AMI_Msg exp_ret := ?,
314 boolean fail_others := true,
315 float rx_timeout := 10.0) return AMI_Msg {
316 var AMI_Msg rx_msg;
317 timer T;
318
319 T.start(rx_timeout);
320 pt.send(tx_msg);
321 alt {
322 [] pt.receive(exp_ret) -> value rx_msg;
323 [not fail_others] pt.receive(AMI_Msg:?) -> value rx_msg {
324 log("AMI: Ignoring Rx msg ", rx_msg);
325 repeat;
326 }
327 [fail_others] as_ami_rx_fail(pt, exp_ret);
328 [] T.timeout {
329 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
330 log2str("AMI Response timeout: ", tx_msg));
331 }
332 }
333 T.stop;
334 return rx_msg;
335}
336
337function f_ami_transceive_match_response_success(AMI_Msg_PT pt,
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200338 template (value) AMI_Msg tx_msg) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200339 var template (present) AMI_Msg exp_resp;
340 var template (omit) charstring action_id := f_ami_msg_get_value(valueof(tx_msg), AMI_FIELD_ACTION_ID);
341 if (isvalue(action_id)) {
342 exp_resp := tr_AMI_Response_Success_ActionId(action_id);
343 } else {
344 exp_resp := tr_AMI_Response_Success;
345 }
346 f_ami_transceive_match(pt, tx_msg, exp_resp);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200347}
348
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200349function f_ami_action_login(AMI_Msg_PT pt, charstring username, charstring secret) {
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200350 f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret));
351}
352
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200353function f_ami_action_PJSIPRegister(AMI_Msg_PT pt, charstring register) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200354 var charstring reg_action_id := f_gen_action_id();
355 f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPRegister(register, reg_action_id));
356}
357
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200358}