| /* Asterisk's AMI interface functions in TTCN-3 |
| * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| * Author: Pau Espin Pedrol <pespin@sysmocom.de> |
| * 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 |
| } with { |
| encode "TEXT" |
| variant "SEPARATOR(': ', ':\s+')" |
| }; |
| |
| type set of AMI_Field AMI_Msg with { |
| encode "TEXT" |
| variant "SEPARATOR('\r\n', '(\r\n)|[\n]')" |
| variant "END('\r\n', '(\r\n)|[\n]')" |
| }; |
| |
| external function enc_AMI_Msg(in AMI_Msg msg) return charstring |
| with { extension "prototype(convert) encode(TEXT)" } |
| external function dec_AMI_Msg(in charstring stream) return AMI_Msg |
| with { extension "prototype(convert) decode(TEXT)" } |
| |
| 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: |
| */ |
| |
| 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") { buf := buf & "\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 := dec_AMI_Msg(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 := enc_AMI_Msg(valueof(tx_msg)); |
| var charstring resp_txt := f_ami_transceive_ret_str(pt, tx_txt); |
| return dec_AMI_Msg(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)); |
| } |
| |
| } |