blob: a82af97a50e23e2f36b08e5558a3a9ff20c201fb [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: */
422 var charstring patched_txt := f_str_replace(txt, "Challenge: \r\n", "");
423 patched_txt := f_str_replace(patched_txt, "AccountCode: \r\n", "");
424 patched_txt := f_str_replace(patched_txt, "Value: \r\n", "");
425 patched_txt := f_str_replace(patched_txt, "DestExten: \r\n", "");
426 patched_txt := f_str_replace(patched_txt, "Exten: \r\n", "");
427 patched_txt := f_str_replace(patched_txt, "Extension: \r\n", "");
Pau Espin Pedrolbf0e14d2024-05-16 18:34:13 +0200428 patched_txt := f_str_replace(patched_txt, "Hint: \r\n", "");
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200429
430 /* "AppData" field sometimes has a value containing separator ": ", which makes
431 * TEXT dec not happy. Workaround it for now by removing the whole field line:
432 * "AppData: 5,0502: Call pjsip endpoint from 0501\r\n"
433 */
434 var integer pos := f_strstr(patched_txt, "AppData: ", 0);
435 if (pos >= 0) {
436 var integer pos_end := f_strstr(patched_txt, "\r\n", pos) + lengthof("\r\n");
437 var charstring to_remove := substr(patched_txt, pos, pos_end - pos);
438 patched_txt := f_str_replace(patched_txt, to_remove, "");
439 }
440
441 log("AMI patched dec: '", patched_txt, "'");
442 return dec_AMI_Msg(patched_txt);
443}
444
445function f_AMI_Adapter_main(AMI_Adapter_Parameters pars := c_default_AMI_Adapter_pars)
446 runs on AMI_Adapter_CT {
447 var AMI_Msg msg;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200448 var charstring rx, buf := "";
449 var integer fd;
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200450 var ASP_RecvFrom rx_rf;
451 var ASP_Event rx_ev;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200452
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200453 g_pars := pars;
454
455 f_AMI_Adapter_connect();
456
457 f_AMI_Adapter_wait_rx_welcome_str();
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200458
459 while (true) {
460
461 alt {
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200462 [] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf {
463 var charstring rx_str := f_ASP_RecvFrom_msg_to_charstring(rx_rf);
464 log("AMI rx: '", rx_str, "'");
465 buf := buf & rx_str;
466 log("AMI buf: '", buf, "'");
467
468 /* If several messages come together */
469 var boolean last_is_complete := f_str_endswith(buf, "\r\n\r\n");
470 var Misc_Helpers.ro_charstring msgs := f_str_split(buf, "\r\n\r\n");
471 log("AMI split: ", msgs);
472 if (lengthof(msgs) > 0) {
473 for (var integer i := 0; i < lengthof(msgs) - 1; i := i + 1) {
474 var charstring txt := msgs[i] & "\r\n";
475 msg := dec_AMI_Msg_ext(txt);
476 CLIENT.send(msg);
477 }
478 if (last_is_complete) {
479 var charstring txt := msgs[lengthof(msgs) - 1] & "\r\n";
480 msg := dec_AMI_Msg_ext(txt);
481 CLIENT.send(msg);
482 buf := "";
483 } else {
484 buf := msgs[lengthof(msgs) - 1];
485 }
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200486 }
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200487 log("AMI remain buf: '", buf, "'");
488 }
489 [] IPL4.receive(ASP_ConnId_ReadyToRelease:?) {
490 }
491
492 [] IPL4.receive(ASP_Event:?) -> value rx_ev {
493 log("Rx AMI ASP_Event: ", rx_ev);
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200494 }
495 [] CLIENT.receive(AMI_Msg:?) -> value msg {
496 /* 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 +0200497 var charstring tx_txt := enc_AMI_Msg(msg) & "\r\n";
498
499 var ASP_SendTo tx := {
500 connId := g_self_conn_id,
501 remName := g_pars.remote_host,
502 remPort := g_pars.remote_port,
503 proto := { tcp := {} },
504 msg := char2oct(tx_txt)
505 };
506 IPL4.send(tx);
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200507 }
508 }
509 }
510}
511
512
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200513/*
514 * Functions:
515 */
516
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200517/* Generate a random "ActionId" value: */
518function f_gen_action_id() return charstring {
519 return hex2str(f_rnd_hexstring(16));
520}
521
522function f_ami_msg_find(AMI_Msg msg,
523 template (present) charstring key := ?)
524return template (omit) AMI_Field {
525 var integer i;
526
527 for (i := 0; i < lengthof(msg); i := i + 1) {
528 if (not ispresent(msg[i])) {
529 continue;
530 }
531 if (match(msg[i].key, key)) {
532 return msg[i];
533 }
534 }
535 return omit;
536}
537
538function f_ami_msg_find_or_fail(AMI_Msg msg,
539 template (present) charstring key := ?)
540return AMI_Field {
541 var template (omit) AMI_Field field;
542 field := f_ami_msg_find(msg, key);
543 if (istemplatekind(field, "omit")) {
544 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
545 log2str("Key ", key, " not found in ", msg));
546 }
547 return valueof(field);
548}
549
550function f_ami_msg_get_value(AMI_Msg msg,
551 template (present) charstring key := ?)
552return template (omit) charstring {
553 var template (omit) AMI_Field field;
554 field := f_ami_msg_find(msg, key);
555 if (istemplatekind(field, "omit")) {
556 return omit;
557 }
558 return field.val;
559}
560
561function f_ami_msg_get_value_or_fail(AMI_Msg msg,
562 template (present) charstring key := ?)
563return template charstring {
564 var AMI_Field field;
565 field := f_ami_msg_find_or_fail(msg, key);
566 return field.val;
567}
568
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200569function f_ami_transceive_ret(AMI_Msg_PT pt, template (value) AMI_Msg tx_msg, float rx_timeout := 10.0) return AMI_Msg {
570 var AMI_Msg rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200571 timer T;
572
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200573 T.start(rx_timeout);
574 pt.send(tx_msg);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200575 alt {
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200576 [] pt.receive(AMI_Msg:?) -> value rx_msg;
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200577 [] T.timeout {
578 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200579 log2str("AMI Response timeout: ", tx_msg));
580 }
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200581 }
582 T.stop;
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200583 return rx_msg;
584
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200585}
586
Pau Espin Pedrole98f4412024-05-17 19:33:46 +0200587altstep as_ami_rx_ignore(AMI_Msg_PT pt)
588{
589 var AMI_Msg msg;
590 [] pt.receive(AMI_Msg:?) -> value msg {
591 log("Ignoring AMI message := ", msg);
592 repeat;
593 }
594}
595
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200596private altstep as_ami_rx_fail(AMI_Msg_PT pt, template AMI_Msg exp_msg := *)
597{
598 var AMI_Msg msg;
599 [] pt.receive(AMI_Msg:?) -> value msg {
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200600 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200601 log2str("Received unexpected AMI message := ", msg, "\nvs exp := ", exp_msg));
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200602 }
603}
604
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200605altstep as_ami_expect_msg(AMI_Msg_PT pt, template (present) AMI_Msg msg_expect, boolean fail_others := true)
606{
607 [] pt.receive(msg_expect);
608 [fail_others] as_ami_rx_fail(pt, msg_expect);
609}
610
611function f_ami_transceive_match(AMI_Msg_PT pt,
612 template (value) AMI_Msg tx_msg,
613 template (present) AMI_Msg exp_ret := ?,
614 boolean fail_others := true,
615 float rx_timeout := 10.0) return AMI_Msg {
616 var AMI_Msg rx_msg;
617 timer T;
618
619 T.start(rx_timeout);
620 pt.send(tx_msg);
621 alt {
622 [] pt.receive(exp_ret) -> value rx_msg;
623 [not fail_others] pt.receive(AMI_Msg:?) -> value rx_msg {
624 log("AMI: Ignoring Rx msg ", rx_msg);
625 repeat;
626 }
627 [fail_others] as_ami_rx_fail(pt, exp_ret);
628 [] T.timeout {
629 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
630 log2str("AMI Response timeout: ", tx_msg));
631 }
632 }
633 T.stop;
634 return rx_msg;
635}
636
637function f_ami_transceive_match_response_success(AMI_Msg_PT pt,
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200638 template (value) AMI_Msg tx_msg) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200639 var template (present) AMI_Msg exp_resp;
640 var template (omit) charstring action_id := f_ami_msg_get_value(valueof(tx_msg), AMI_FIELD_ACTION_ID);
641 if (isvalue(action_id)) {
642 exp_resp := tr_AMI_Response_Success_ActionId(action_id);
643 } else {
644 exp_resp := tr_AMI_Response_Success;
645 }
646 f_ami_transceive_match(pt, tx_msg, exp_resp);
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200647}
648
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200649function f_ami_action_login(AMI_Msg_PT pt, charstring username, charstring secret) {
Pau Espin Pedrol77976a62024-05-08 21:17:38 +0200650 var charstring reg_action_id := f_gen_action_id();
651 f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret, reg_action_id));
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200652}
653
Pau Espin Pedrol0c5c6472024-05-21 13:13:49 +0200654function f_ami_action_PJSIPAccessNetworkInfo(AMI_Msg_PT pt,
655 template (value) charstring registration,
656 template (value) charstring info) {
657 var charstring reg_action_id := f_gen_action_id();
658 f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPAccessNetworkInfo(registration, info, reg_action_id));
659}
660
Pau Espin Pedrolbcb4e822024-04-26 20:16:47 +0200661function f_ami_action_PJSIPRegister(AMI_Msg_PT pt, charstring register) {
Pau Espin Pedrol4362dbd2024-04-26 19:31:27 +0200662 var charstring reg_action_id := f_gen_action_id();
663 f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPRegister(register, reg_action_id));
664}
665
Pau Espin Pedrolddc4c702024-05-22 17:02:57 +0200666function f_ami_action_AuthResponse_AUTS(AMI_Msg_PT pt,
667 template (value) charstring registration,
668 template (value) charstring auts) {
669 var charstring reg_action_id := f_gen_action_id();
670 f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_AUTS(registration, auts, reg_action_id));
671}
672function f_ami_action_AuthResponse_RES(AMI_Msg_PT pt,
673 template (value) charstring registration,
674 template (value) charstring res,
675 template (value) charstring ck,
676 template (value) charstring ik) {
677 var charstring reg_action_id := f_gen_action_id();
678 f_ami_transceive_match_response_success(pt, ts_AMI_Action_AuthResponse_RES(registration, res, ck, ik, reg_action_id));
679}
680
Pau Espin Pedrol01f1df82024-05-08 16:55:55 +0200681private function f_ami_selftest_decode(charstring txt) {
682 log("Text to decode: '", txt, "'");
683 var AMI_Msg msg := dec_AMI_Msg(txt);
684 log("AMI_Msg decoded: ", msg);
685}
686
687function f_ami_selftest() {
688 f_ami_selftest_decode("AppData: 5,0502: Call pjsip endpoint from 0501\r\n");
689}
690
Pau Espin Pedrol54b614a2024-04-17 18:58:36 +0200691}