blob: 9c84d0098421f061c3ca0706a5fec5505ee37f96 [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;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020018import from Osmocom_Types all;
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +020019import from IPL4asp_Types all;
20import from IPL4asp_PortType all;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020021import from Socket_API_Definitions all;
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +020022import from TCCConversion_Functions all;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020023
24modulepar {
25 float mp_ami_prompt_timeout := 10.0;
26}
27
28const charstring AMI_FIELD_ACTION := "Action";
Pau Espin Pedrol941ca3c2024-05-09 16:46:58 +020029const charstring AMI_FIELD_ACTION_ID := "ActionID";
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +020030const charstring AMI_FIELD_CHAN_TYPE := "ChannelType";
31const charstring AMI_FIELD_DOMAIN := "Domain";
Pau Espin Pedrole98f4412024-05-17 19:33:46 +020032const charstring AMI_FIELD_EVENT := "Event";
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +020033const charstring AMI_FIELD_INFO := "Info";
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020034const charstring AMI_FIELD_RESPONSE := "Response";
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +020035const charstring AMI_FIELD_SECRET := "Secret";
36const charstring AMI_FIELD_STATUS := "Status";
37const charstring AMI_FIELD_USERNAME := "Username";
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020038
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020039/* Extensions: */
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +020040const charstring AMI_FIELD_ALGORITHM := "Algorithm";
41const charstring AMI_FIELD_AUTN := "AUTN";
42const charstring AMI_FIELD_AUTS := "AUTS";
43const charstring AMI_FIELD_CK := "CK";
44const charstring AMI_FIELD_IK := "IK";
45const charstring AMI_FIELD_RAND := "RAND";
46const charstring AMI_FIELD_RES := "RES";
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020047const charstring AMI_FIELD_REGISTRATION := "Registration";
48
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020049type record AMI_Field {
50 charstring key,
51 charstring val
Pau Espin Pedrolde7a4852024-04-19 16:20:45 +020052} with {
53 encode "TEXT"
54 variant "SEPARATOR(': ', ':\s+')"
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020055};
Pau Espin Pedrolde7a4852024-04-19 16:20:45 +020056
57type set of AMI_Field AMI_Msg with {
58 encode "TEXT"
59 variant "SEPARATOR('\r\n', '(\r\n)|[\n]')"
60 variant "END('\r\n', '(\r\n)|[\n]')"
61};
62
63external function enc_AMI_Msg(in AMI_Msg msg) return charstring
64 with { extension "prototype(convert) encode(TEXT)" }
65external function dec_AMI_Msg(in charstring stream) return AMI_Msg
66 with { extension "prototype(convert) decode(TEXT)" }
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020067
68template (value) AMI_Field
69ts_AMI_Field(template (value) charstring key,
70 template (value) charstring val) := {
71 key := key,
72 val := val
73};
74
75template (present) AMI_Field
76tr_AMI_Field(template (present) charstring key := ?,
77 template (present) charstring val := ?) := {
78 key := key,
79 val := val
80};
81
82/*
83 * Field Templates:
84 */
85
86template (value) AMI_Field
87ts_AMI_Field_Action(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION, val);
88template (value) AMI_Field
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +020089ts_AMI_Field_ActionId(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION_ID, val);
90template (value) AMI_Field
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +020091ts_AMI_Field_ChannelType(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CHAN_TYPE, val);
92template (value) AMI_Field
93ts_AMI_Field_Domain(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_DOMAIN, val);
94template (value) AMI_Field
Pau Espin Pedrole98f4412024-05-17 19:33:46 +020095ts_AMI_Field_Event(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_EVENT, val);
96template (value) AMI_Field
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +020097ts_AMI_Field_Info(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_INFO, val);
98template (value) AMI_Field
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +020099ts_AMI_Field_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val);
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +0200100template (value) AMI_Field
101ts_AMI_Field_Status(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_STATUS, val);
102template (value) AMI_Field
103ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, val);
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200104/* Extensions: */
105template (value) AMI_Field
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200106ts_AMI_Field_Algorithm(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ALGORITHM, val);
107template (value) AMI_Field
108ts_AMI_Field_AUTN(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_AUTN, val);
109template (value) AMI_Field
110ts_AMI_Field_AUTS(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_AUTS, val);
111template (value) AMI_Field
112ts_AMI_Field_CK(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_CK, val);
113template (value) AMI_Field
114ts_AMI_Field_IK(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_IK, val);
115template (value) AMI_Field
116ts_AMI_Field_RAND(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_RAND, val);
117template (value) AMI_Field
118ts_AMI_Field_RES(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_RES, val);
119template (value) AMI_Field
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200120ts_AMI_Field_Registration(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_REGISTRATION, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200121
122template (present) AMI_Field
Pau Espin Pedrol21c084f2024-05-09 16:42:16 +0200123tr_AMI_Field_Action(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ACTION, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200124template (present) AMI_Field
Pau Espin Pedrol21c084f2024-05-09 16:42:16 +0200125tr_AMI_Field_ActionId(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ACTION_ID, val);
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200126template (present) AMI_Field
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +0200127tr_AMI_Field_ChannelType(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CHAN_TYPE, val);
128template (present) AMI_Field
129tr_AMI_Field_Domain(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_DOMAIN, val);
130template (present) AMI_Field
Pau Espin Pedrole98f4412024-05-17 19:33:46 +0200131tr_AMI_Field_Event(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_EVENT, val);
132template (present) AMI_Field
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +0200133tr_AMI_Field_Info(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_INFO, val);
134template (present) AMI_Field
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +0200135tr_AMI_Field_Response(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RESPONSE, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200136template (present) AMI_Field
Pau Espin Pedrol21c084f2024-05-09 16:42:16 +0200137tr_AMI_Field_Secret(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_SECRET, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200138template (present) AMI_Field
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +0200139tr_AMI_Field_Status(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_STATUS, val);
140template (present) AMI_Field
141tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_USERNAME, val);
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200142/* Extensions: */
143template (present) AMI_Field
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200144tr_AMI_Field_Algorithm(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_ALGORITHM, val);
145template (present) AMI_Field
146tr_AMI_Field_AUTN(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_AUTN, val);
147template (present) AMI_Field
148tr_AMI_Field_AUTS(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_AUTS, val);
149template (present) AMI_Field
150tr_AMI_Field_CK(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_CK, val);
151template (present) AMI_Field
152tr_AMI_Field_IK(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_IK, val);
153template (present) AMI_Field
154tr_AMI_Field_RAND(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RAND, val);
155template (present) AMI_Field
156tr_AMI_Field_RES(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_RES, val);
157template (present) AMI_Field
Pau Espin Pedrol21c084f2024-05-09 16:42:16 +0200158tr_AMI_Field_Registration(template (present) charstring val := ?) := tr_AMI_Field(pattern @nocase AMI_FIELD_REGISTRATION, val);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200159
160
161template (present) AMI_Field
Pau Espin Pedrol21c084f2024-05-09 16:42:16 +0200162tr_AMI_Field_ResponseSuccess := tr_AMI_Field(pattern @nocase AMI_FIELD_RESPONSE, "Success");
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200163
164
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200165/***********************
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200166 * Message Templates:
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200167 ***********************/
168
169/*
170 * ACTIONS
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200171 */
172
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200173/* Action: AuthResponse
174 * Registration: volte_ims
175 * AUTS: <value>
176 */
177template (value) AMI_Msg
178ts_AMI_Action_AuthResponse_AUTS(template (value) charstring registration,
179 template (value) charstring auts,
180 template (value) charstring action_id := "0001") := {
181 ts_AMI_Field_Action("AuthResponse"),
182 ts_AMI_Field_ActionId(action_id),
183 ts_AMI_Field_Registration(registration),
184 ts_AMI_Field_AUTS(auts)
185};
186/* Action: AuthResponse
187 * Registration: volte_ims
188 * RES: <value>
189 * CK: <value>
190 * IK: <value>
191 */
192template (value) AMI_Msg
193ts_AMI_Action_AuthResponse_RES(template (value) charstring registration,
194 template (value) charstring res,
195 template (value) charstring ck,
196 template (value) charstring ik,
197 template (value) charstring action_id := "0001") := {
198 ts_AMI_Field_Action("AuthResponse"),
199 ts_AMI_Field_ActionId(action_id),
200 ts_AMI_Field_Registration(registration),
201 ts_AMI_Field_RES(res),
202 ts_AMI_Field_CK(ck),
203 ts_AMI_Field_IK(ik)
204};
205
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200206/* Action: Login
207 * Username: <value>
208 * Secret: <value>
209 */
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200210template (value) AMI_Msg
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200211ts_AMI_Action_Login(charstring username,
212 charstring secret,
213 template (value) charstring action_id := "0001") := {
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200214 ts_AMI_Field_Action("Login"),
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200215 ts_AMI_Field_ActionId(action_id),
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200216 ts_AMI_Field_Username(username),
217 ts_AMI_Field_Secret(secret)
218};
219
220template (present) AMI_Msg
221tr_AMI_Action_Login(template(present) charstring username := ?,
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200222 template(present) charstring secret := ?,
223 template (present) charstring action_id := ?) := superset(
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200224 tr_AMI_Field_Action("Login"),
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200225 tr_AMI_Field_ActionId(action_id),
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200226 tr_AMI_Field_Username(username),
227 tr_AMI_Field_Secret(secret)
228);
229
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +0200230/* Action: PJSIPAccessNetworkInfo
231 * Registration: volte_ims
232 * Info: 3GPP-E-UTRAN-FDD; utran-cell-id-3gpp=2380100010000101
233 */
234template (value) AMI_Msg
235ts_AMI_Action_PJSIPAccessNetworkInfo(template (value) charstring registration := "volte_ims",
236 template (value) charstring info := "",
237 template (value) charstring action_id := "0001") := {
238 ts_AMI_Field_Action("PJSIPAccessNetworkInfo"),
239 ts_AMI_Field_ActionId(action_id),
240 ts_AMI_Field_Registration(registration),
241 ts_AMI_Field_Info(info)
242};
243function f_ami_gen_PJSIPAccessNetworkInfo_Info_EUTRAN(charstring uli_str) return charstring {
244 return "3GPP-E-UTRAN-FDD; utran-cell-id-3gpp=" & uli_str;
245}
246
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200247/* Action: PJSIPRegister
248 * ActionID: <value>
249 * Registration: volte_ims
250 */
251template (value) AMI_Msg
252ts_AMI_Action_PJSIPRegister(template (value) charstring registration := "volte_ims",
253 template (value) charstring action_id := "0001") := {
254 ts_AMI_Field_Action("PJSIPRegister"),
255 ts_AMI_Field_ActionId(action_id),
256 ts_AMI_Field_Registration(registration)
257};
258template (present) AMI_Msg
259tr_AMI_Action_PJSIPRegister(template (present) charstring registration := ?,
260 template (present) charstring action_id := ?) := {
261 tr_AMI_Field_Action("PJSIPRegister"),
262 tr_AMI_Field_ActionId(action_id),
263 tr_AMI_Field_Registration(registration)
264};
265
266/*
267 * RESPONSES
268 */
269
270/* Response: Success
271 */
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200272template (present) AMI_Msg
273tr_AMI_Response_Success := superset(
274 tr_AMI_Field_ResponseSuccess
275);
276
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200277/* Response: Success
278 * ActionId: <value>
279 */
280template (present) AMI_Msg
281tr_AMI_Response_Success_ActionId(template (present) charstring action_id := ?) := superset(
282 tr_AMI_Field_ResponseSuccess,
283 tr_AMI_Field_ActionId(action_id)
284);
285
Pau Espin Pedrole98f4412024-05-17 19:33:46 +0200286/*
287 * EVENTS
288 */
289template (present) AMI_Msg
290tr_AMI_Event(template (present) charstring ev_name := ?) := superset(
291 tr_AMI_Field_Event(ev_name)
292);
293
294/* Event: FullyBooted
295 * Privilege: system,all
296 * Status: Fully Booted
297 * Uptime: 4
298 * LastReload: 4 *
299 */
300template (present) AMI_Msg
301tr_AMI_Event_FullyBooted := tr_AMI_Event("FullyBooted");
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200302
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200303/* Event: AuthRequest
304 * Privilege: <none>
305 * Registration: volte_ims
306 * Algorithm: AKAv1-MD5
307 * RAND: 14987631f65f8e3788a0798b6ebcd08e
308 * AUTN: f6e19a7ccb028000a06b19c9544516e5
309 */
310template (present) AMI_Msg
311tr_AMI_Event_AuthRequest(template (present) charstring registration := ?,
312 template (present) charstring algorithm := "AKAv1-MD5",
313 template (present) charstring rand := ?,
314 template (present) charstring autn := ?) := superset(
315 tr_AMI_Field_Event("AuthRequest"),
316 tr_AMI_Field_Registration(registration),
317 tr_AMI_Field_Algorithm(algorithm),
318 tr_AMI_Field_RAND(rand),
319 tr_AMI_Field_AUTN(autn)
320);
321
Pau Espin Pedrol469c2f62024-05-22 17:48:17 +0200322/* Event: Registry
323 * Privilege: system,all
324 * ChannelType: PJSIP
325 * Username: sip:238010000090828@172.18.155.103
326 * Domain: sip:172.18.155.103
327 * Status: Registered
328 */
329template (present) AMI_Msg
330tr_AMI_Event_Registry(template (present) charstring username := ?,
331 template (present) charstring domain := ?,
332 template (present) charstring status := ?,
333 template (present) charstring chan_type := "PJSIP") := superset(
334 tr_AMI_Field_Event("Registry"),
335 tr_AMI_Field_ChannelType(chan_type),
336 tr_AMI_Field_Username(username),
337 tr_AMI_Field_Domain(domain),
338 tr_AMI_Field_Status(status)
339);
340
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200341/***********************
342 * Adapter:
343 ***********************/
344
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200345type record AMI_Adapter_Parameters {
346 charstring remote_host,
347 IPL4asp_Types.PortNumber remote_port,
348 charstring local_host,
349 IPL4asp_Types.PortNumber local_port,
350 charstring welcome_str
351}
352
353const AMI_Adapter_Parameters c_default_AMI_Adapter_pars := {
354 remote_host := "127.0.0.1",
355 remote_port := 5038,
356 local_host := "0.0.0.0",
357 local_port := 0,
358 welcome_str := "Asterisk Call Manager/9.0.0\r\n"
359};
360
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200361type port AMI_Msg_PT message {
362 inout AMI_Msg;
363} with { extension "internal" };
364
365type component AMI_Adapter_CT {
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200366 port IPL4asp_PT IPL4;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200367 port AMI_Msg_PT CLIENT;
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200368 var AMI_Adapter_Parameters g_pars;
369
370 /* Connection identifier of the client itself */
371 var IPL4asp_Types.ConnectionId g_self_conn_id := -1;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200372}
373
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200374/* Function to use to connect as client to a remote IPA Server */
375private function f_AMI_Adapter_connect() runs on AMI_Adapter_CT {
376 var IPL4asp_Types.Result res;
377 map(self:IPL4, system:IPL4);
378 res := IPL4asp_PortType.f_IPL4_connect(IPL4, g_pars.remote_host, g_pars.remote_port,
379 g_pars.local_host, g_pars.local_port, 0, { tcp:={} });
380 if (not ispresent(res.connId)) {
381 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
382 log2str("Could not connect AMI socket from ", g_pars.local_host, " port ",
383 g_pars.local_port, " to ", g_pars.remote_host, " port ", g_pars.remote_port,
384 "; check your configuration"));
385 }
386 g_self_conn_id := res.connId;
387 log("AMI connected, ConnId=", g_self_conn_id)
388}
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200389
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200390private function f_ASP_RecvFrom_msg_to_charstring(ASP_RecvFrom rx_rf) return charstring {
391 return oct2char(rx_rf.msg);
392}
393
394/* Function to use to connect as client to a remote IPA Server */
395private function f_AMI_Adapter_wait_rx_welcome_str() runs on AMI_Adapter_CT {
396 var ASP_RecvFrom rx_rf;
397 var charstring rx_str;
398 timer Twelcome := 3.0;
399
400 Twelcome.start;
401 alt {
402 [] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf {
403 rx_str := f_ASP_RecvFrom_msg_to_charstring(rx_rf);
404 if (g_pars.welcome_str != rx_str) {
405 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
406 log2str("AMI Welcome message mismatch: '", rx_str,
407 "' vs exp '", g_pars.welcome_str, "'"));
408 }
409 }
410 [] Twelcome.timeout {
411 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
412 log2str("AMI Welcome timeout"));
413 }
414 }
415 Twelcome.stop;
416 log("AMI Welcome message received: '", rx_str, "'");
417}
418
419private function dec_AMI_Msg_ext(charstring txt) return AMI_Msg {
420 log("AMI dec: '", txt, "'");
421 /* TEXT Enc/dec is not happy with empty values, workaround it: */
Pau Espin Pedrolcd20bbf2024-05-28 18:29:34 +0200422 var Misc_Helpers.ro_charstring fields := { "AccountCode", "AccountID", "Challenge", "DestExten", "Exten", "Extension", "Hint", "Value" };
423 var charstring patched_txt := txt;
424 for (var integer i := 0; i < lengthof(fields); i := i +1) {
425 patched_txt := f_str_replace(patched_txt, fields[i] & ": \r\n", "");
426 }
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200427
428 /* "AppData" field sometimes has a value containing separator ": ", which makes
429 * TEXT dec not happy. Workaround it for now by removing the whole field line:
430 * "AppData: 5,0502: Call pjsip endpoint from 0501\r\n"
431 */
432 var integer pos := f_strstr(patched_txt, "AppData: ", 0);
433 if (pos >= 0) {
434 var integer pos_end := f_strstr(patched_txt, "\r\n", pos) + lengthof("\r\n");
435 var charstring to_remove := substr(patched_txt, pos, pos_end - pos);
436 patched_txt := f_str_replace(patched_txt, to_remove, "");
437 }
438
439 log("AMI patched dec: '", patched_txt, "'");
440 return dec_AMI_Msg(patched_txt);
441}
442
443function f_AMI_Adapter_main(AMI_Adapter_Parameters pars := c_default_AMI_Adapter_pars)
444 runs on AMI_Adapter_CT {
445 var AMI_Msg msg;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200446 var charstring rx, buf := "";
447 var integer fd;
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200448 var ASP_RecvFrom rx_rf;
449 var ASP_Event rx_ev;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200450
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200451 g_pars := pars;
452
453 f_AMI_Adapter_connect();
454
455 f_AMI_Adapter_wait_rx_welcome_str();
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200456
457 while (true) {
458
459 alt {
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200460 [] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf {
461 var charstring rx_str := f_ASP_RecvFrom_msg_to_charstring(rx_rf);
462 log("AMI rx: '", rx_str, "'");
463 buf := buf & rx_str;
464 log("AMI buf: '", buf, "'");
465
466 /* If several messages come together */
467 var boolean last_is_complete := f_str_endswith(buf, "\r\n\r\n");
468 var Misc_Helpers.ro_charstring msgs := f_str_split(buf, "\r\n\r\n");
469 log("AMI split: ", msgs);
470 if (lengthof(msgs) > 0) {
471 for (var integer i := 0; i < lengthof(msgs) - 1; i := i + 1) {
472 var charstring txt := msgs[i] & "\r\n";
473 msg := dec_AMI_Msg_ext(txt);
474 CLIENT.send(msg);
475 }
476 if (last_is_complete) {
477 var charstring txt := msgs[lengthof(msgs) - 1] & "\r\n";
478 msg := dec_AMI_Msg_ext(txt);
479 CLIENT.send(msg);
480 buf := "";
481 } else {
482 buf := msgs[lengthof(msgs) - 1];
483 }
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200484 }
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200485 log("AMI remain buf: '", buf, "'");
486 }
487 [] IPL4.receive(ASP_ConnId_ReadyToRelease:?) {
488 }
489
490 [] IPL4.receive(ASP_Event:?) -> value rx_ev {
491 log("Rx AMI ASP_Event: ", rx_ev);
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200492 }
493 [] CLIENT.receive(AMI_Msg:?) -> value msg {
494 /* TODO: in the future, queue Action if there's already one Action in transit, to fullfill AMI requirements. */
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200495 var charstring tx_txt := enc_AMI_Msg(msg) & "\r\n";
496
497 var ASP_SendTo tx := {
498 connId := g_self_conn_id,
499 remName := g_pars.remote_host,
500 remPort := g_pars.remote_port,
501 proto := { tcp := {} },
502 msg := char2oct(tx_txt)
503 };
504 IPL4.send(tx);
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200505 }
506 }
507 }
508}
509
510
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200511/*
512 * Functions:
513 */
514
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200515/* Generate a random "ActionId" value: */
516function f_gen_action_id() return charstring {
517 return hex2str(f_rnd_hexstring(16));
518}
519
520function f_ami_msg_find(AMI_Msg msg,
521 template (present) charstring key := ?)
522return template (omit) AMI_Field {
523 var integer i;
524
525 for (i := 0; i < lengthof(msg); i := i + 1) {
526 if (not ispresent(msg[i])) {
527 continue;
528 }
529 if (match(msg[i].key, key)) {
530 return msg[i];
531 }
532 }
533 return omit;
534}
535
536function f_ami_msg_find_or_fail(AMI_Msg msg,
537 template (present) charstring key := ?)
538return AMI_Field {
539 var template (omit) AMI_Field field;
540 field := f_ami_msg_find(msg, key);
541 if (istemplatekind(field, "omit")) {
542 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
543 log2str("Key ", key, " not found in ", msg));
544 }
545 return valueof(field);
546}
547
548function f_ami_msg_get_value(AMI_Msg msg,
549 template (present) charstring key := ?)
550return template (omit) charstring {
551 var template (omit) AMI_Field field;
552 field := f_ami_msg_find(msg, key);
553 if (istemplatekind(field, "omit")) {
554 return omit;
555 }
556 return field.val;
557}
558
559function f_ami_msg_get_value_or_fail(AMI_Msg msg,
560 template (present) charstring key := ?)
561return template charstring {
562 var AMI_Field field;
563 field := f_ami_msg_find_or_fail(msg, key);
564 return field.val;
565}
566
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200567function f_ami_transceive_ret(AMI_Msg_PT pt, template (value) AMI_Msg tx_msg, float rx_timeout := 10.0) return AMI_Msg {
568 var AMI_Msg rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200569 timer T;
570
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200571 T.start(rx_timeout);
572 pt.send(tx_msg);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200573 alt {
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200574 [] pt.receive(AMI_Msg:?) -> value rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200575 [] T.timeout {
576 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200577 log2str("AMI Response timeout: ", tx_msg));
578 }
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200579 }
580 T.stop;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200581 return rx_msg;
582
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200583}
584
Pau Espin Pedrole98f4412024-05-17 19:33:46 +0200585altstep as_ami_rx_ignore(AMI_Msg_PT pt)
586{
587 var AMI_Msg msg;
588 [] pt.receive(AMI_Msg:?) -> value msg {
589 log("Ignoring AMI message := ", msg);
590 repeat;
591 }
592}
593
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200594private altstep as_ami_rx_fail(AMI_Msg_PT pt, template AMI_Msg exp_msg := *)
595{
596 var AMI_Msg msg;
597 [] pt.receive(AMI_Msg:?) -> value msg {
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200598 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200599 log2str("Received unexpected AMI message := ", msg, "\nvs exp := ", exp_msg));
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200600 }
601}
602
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200603altstep as_ami_expect_msg(AMI_Msg_PT pt, template (present) AMI_Msg msg_expect, boolean fail_others := true)
604{
605 [] pt.receive(msg_expect);
606 [fail_others] as_ami_rx_fail(pt, msg_expect);
607}
608
609function f_ami_transceive_match(AMI_Msg_PT pt,
610 template (value) AMI_Msg tx_msg,
611 template (present) AMI_Msg exp_ret := ?,
612 boolean fail_others := true,
613 float rx_timeout := 10.0) return AMI_Msg {
614 var AMI_Msg rx_msg;
615 timer T;
616
617 T.start(rx_timeout);
618 pt.send(tx_msg);
619 alt {
620 [] pt.receive(exp_ret) -> value rx_msg;
621 [not fail_others] pt.receive(AMI_Msg:?) -> value rx_msg {
622 log("AMI: Ignoring Rx msg ", rx_msg);
623 repeat;
624 }
625 [fail_others] as_ami_rx_fail(pt, exp_ret);
626 [] T.timeout {
627 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
628 log2str("AMI Response timeout: ", tx_msg));
629 }
630 }
631 T.stop;
632 return rx_msg;
633}
634
635function f_ami_transceive_match_response_success(AMI_Msg_PT pt,
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200636 template (value) AMI_Msg tx_msg) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200637 var template (present) AMI_Msg exp_resp;
638 var template (omit) charstring action_id := f_ami_msg_get_value(valueof(tx_msg), AMI_FIELD_ACTION_ID);
639 if (isvalue(action_id)) {
640 exp_resp := tr_AMI_Response_Success_ActionId(action_id);
641 } else {
642 exp_resp := tr_AMI_Response_Success;
643 }
644 f_ami_transceive_match(pt, tx_msg, exp_resp);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200645}
646
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200647function f_ami_action_login(AMI_Msg_PT pt, charstring username, charstring secret) {
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200648 var charstring reg_action_id := f_gen_action_id();
649 f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret, reg_action_id));
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200650}
651
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +0200652function f_ami_action_PJSIPAccessNetworkInfo(AMI_Msg_PT pt,
653 template (value) charstring registration,
654 template (value) charstring info) {
655 var charstring reg_action_id := f_gen_action_id();
656 f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPAccessNetworkInfo(registration, info, reg_action_id));
657}
658
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200659function f_ami_action_PJSIPRegister(AMI_Msg_PT pt, charstring register) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200660 var charstring reg_action_id := f_gen_action_id();
661 f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPRegister(register, reg_action_id));
662}
663
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200664function f_ami_action_AuthResponse_AUTS(AMI_Msg_PT pt,
665 template (value) charstring registration,
666 template (value) charstring auts) {
667 var charstring reg_action_id := f_gen_action_id();
668 f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_AUTS(registration, auts, reg_action_id));
669}
670function f_ami_action_AuthResponse_RES(AMI_Msg_PT pt,
671 template (value) charstring registration,
672 template (value) charstring res,
673 template (value) charstring ck,
674 template (value) charstring ik) {
675 var charstring reg_action_id := f_gen_action_id();
676 f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_RES(registration, res, ck, ik, reg_action_id));
677}
678
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200679private function f_ami_selftest_decode(charstring txt) {
680 log("Text to decode: '", txt, "'");
681 var AMI_Msg msg := dec_AMI_Msg(txt);
682 log("AMI_Msg decoded: ", msg);
683}
684
685function f_ami_selftest() {
686 f_ami_selftest_decode("AppData: 5,0502: Call pjsip endpoint from 0501\r\n");
687}
688
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200689}