File Coverage

blib/lib/Telephony/Asterisk/AMI.pm
Criterion Covered Total %
statement 84 113 74.3
branch 31 48 64.5
condition 6 12 50.0
subroutine 13 13 100.0
pod 7 7 100.0
total 141 193 73.0


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package Telephony::Asterisk::AMI;
3             #
4             # Copyright 2015 Christopher J. Madsen
5             #
6             # Author: Christopher J. Madsen
7             # Created: 31 Oct 2015
8             #
9             # This program is free software; you can redistribute it and/or modify
10             # it under the same terms as Perl itself.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
15             # GNU General Public License or the Artistic License for more details.
16             #
17             # ABSTRACT: Simple Asterisk Manager Interface client
18             #---------------------------------------------------------------------
19              
20 3     3   2723901 use 5.008;
  3         12  
21 3     3   16 use strict;
  3         4  
  3         61  
22 3     3   13 use warnings;
  3         6  
  3         73  
23              
24              
25 3     3   14 use Carp ();
  3         6  
  3         51  
26 3     3   1106 use IO::Socket::IP ();
  3         42954  
  3         2023  
27              
28             our $VERSION = '0.006';
29             # This file is part of Telephony-Asterisk-AMI 0.006 (December 26, 2015)
30              
31             my $EOL = "\r\n";
32              
33             #=====================================================================
34              
35              
36             sub new {
37 2     2 1 322 my $class = shift;
38 2 50       17 my $args = (@_ == 1) ? shift : { @_ };
39              
40             my $self = bless {
41             Debug_FH => ($args->{Debug_FH} || ($args->{Debug} ? *STDERR : undef)),
42             Event_Callback => $args->{Event_Callback},
43             Host => $args->{Host} || 'localhost',
44             Port => $args->{Port} || 5038,
45 2   33     57 ActionID => $args->{ActionID} || 1,
      50        
      50        
      50        
46             }, $class;
47              
48 2         6 for my $key (qw(Username Secret)) {
49 4 50       26 defined( $self->{$key} = $args->{$key} )
50             or Carp::croak("Required parameter '$key' not defined");
51             }
52              
53 2         9 $self;
54             } # end new
55             #---------------------------------------------------------------------
56              
57              
58             sub connect {
59 2     2 1 1580 my $self = shift;
60              
61             # Open a socket to Asterisk.
62             # IO::Socket::IP->new reports error in $@
63 2         3 local $@;
64              
65             $self->{socket} = IO::Socket::IP->new(
66             Type => IO::Socket::IP::SOCK_STREAM(),
67             PeerHost => $self->{Host},
68             PeerService => $self->{Port},
69 2         27 );
70              
71 2 50       73 unless ($self->{socket}) {
72 0         0 $self->{error} = "Connection failed: $@";
73 0         0 return undef;
74             }
75              
76             # Verify that we've connected to Asterisk Call Manager
77 2         14 my $id = readline($self->{socket});
78              
79 2 50       14 unless (defined $id) {
80 0         0 $self->{error} = "Connection closed without input: $!";
81 0         0 undef $self->{socket};
82 0         0 return undef;
83             }
84              
85 2         8 chomp $id;
86 2 50       7 print { $self->{Debug_FH} } "<< $id\n" if $self->{Debug_FH};
  0         0  
87              
88 2 50       13 if ($id =~ m!^Asterisk Call Manager/(.+)!) {
89 2         10 $self->{protocol} = $1;
90             } else {
91 0         0 $self->{error} = "Unknown Protocol";
92 0         0 undef $self->{socket};
93 0         0 return undef;
94             }
95              
96             # Automatically log in using Username/Secret
97             my $response = $self->action({
98             Action => 'Login',
99             Username => $self->{Username},
100             Secret => $self->{Secret},
101 2         15 });
102              
103             # If login failed, set error
104 2 50       10 unless ($response->{Response} eq 'Success') {
105 0         0 $self->{error} = "Login failed: $response->{Message}";
106 0         0 undef $self->{socket};
107 0         0 return undef;
108             }
109              
110             # Login successful
111 2         13 1;
112             } # end connect
113             #---------------------------------------------------------------------
114              
115              
116             sub disconnect {
117 2     2 1 714 my $self = shift;
118              
119 2         10 my $response = $self->action({Action => 'Logoff'});
120              
121             # If logoff failed, set error
122 2 50       16 unless ($response->{Response} eq 'Goodbye') {
123 0         0 $self->{error} = "Logoff failed: $response->{Message}";
124 0         0 undef $self->{socket};
125 0         0 return undef;
126             }
127              
128 2 50       26 unless ($self->{socket}->close) {
129 0         0 $self->{error} = "Closing socket failed: $!";
130 0         0 undef $self->{socket};
131 0         0 return undef;
132             }
133              
134 2         29 undef $self->{socket};
135              
136             # Logoff successful
137 2         10 1;
138             } # end disconnect
139             #---------------------------------------------------------------------
140              
141              
142             sub action {
143 14     14 1 3704 my $self = shift;
144              
145             # Send the request to Asterisk
146             my $id = $self->send_action(@_) or return {
147             Response => 'Error',
148             Message => $self->{error},
149 14 50       35 };
150              
151             # Read responses until we get the response to this action
152 14         21 while (1) {
153 22         60 my $response = $self->read_response;
154              
155             # If this is the response to the action we just sent,
156             # or there was an error, return it.
157 3     3   19 no warnings 'uninitialized';
  3         5  
  3         1971  
158 22 100 66     101 if (($response->{ActionID} eq $id) ||
159             ($response->{Response} eq 'Error')) {
160 14         85 return $response;
161             }
162              
163             # If there is an event callback, send it this event
164 8 100       29 if ($self->{Event_Callback}) {
165 4         12 $self->{Event_Callback}->($response);
166             }
167             } # end infinite loop waiting for response
168             } # end action
169             #---------------------------------------------------------------------
170              
171              
172             sub send_action {
173 14     14 1 18 my $self = shift;
174 14 100       42 my $act = (@_ == 1) ? shift : { @_ };
175              
176 14 50       41 Carp::croak("Required parameter 'Action' not defined") unless $act->{Action};
177              
178             # Check that the connection is open
179 14 50       32 unless ($self->{socket}) {
180 0         0 $self->{error} = "Not connected to Asterisk!";
181 0         0 return undef;
182             }
183              
184             # Assemble the message to send to Asterisk
185 14         21 my $id = $self->{ActionID}++;
186 14         33 my $message = "ActionID: $id$EOL";
187              
188 14         59 for my $key (sort keys %$act) {
189 28 100       59 if (ref $act->{$key}) {
190 2         4 $message .= "$key: $_$EOL" for @{ $act->{$key} };
  2         14  
191             } else {
192 26         61 $message .= "$key: $act->{$key}$EOL";
193             }
194             }
195              
196 14         27 $message .= $EOL; # Message ends with blank line
197              
198             # If debugging, print out the message before sending it
199 14 50       32 if ($self->{Debug_FH}) {
200 0         0 my $debug = $message;
201 0         0 $debug =~ s/\r//g;
202 0         0 $debug =~ s/^/>> /mg;
203 0         0 print { $self->{Debug_FH} } $debug;
  0         0  
204             }
205              
206             # Send the request to Asterisk
207 14 50       21 unless (print { $self->{socket} } $message) {
  14         77  
208 0         0 $self->{error} = "Writing to socket failed: $!";
209 0         0 return undef;
210             }
211              
212 14         283 $id;
213             } # end send_action
214             #---------------------------------------------------------------------
215              
216              
217             sub read_response {
218 22     22 1 26 my $self = shift;
219              
220             # Check that the connection is open
221 22         42 my $socket = $self->{socket};
222 22 50       78 unless ($socket) {
223             return {
224             Response => 'Error',
225 0         0 Message => $self->{error} = "Not connected to Asterisk!",
226             };
227             }
228              
229             # Read a response terminated by a blank line
230 22         68 local $/ = $EOL;
231 22         33 my $debug_fh = $self->{Debug_FH};
232 22         26 my ($line, %response);
233 22         36 undef $!;
234              
235 22         59 while ($line = <$socket>) {
236 152         642 chomp $line;
237 152 50       272 print $debug_fh "<< $line\n" if $debug_fh;
238              
239 152 100       335 return \%response unless length $line;
240              
241             # Remove the key from the "Key: Value" line
242             # If the line is not in that format, ignore it.
243 130 50       523 $line =~ s/^([^:]+): // or next;
244              
245 130 100       341 if (not exists $response{$1}) {
    100          
246             # First occurrence of this key, save as string
247 106         391 $response{$1} = $line;
248             } elsif (ref $response{$1}) {
249             # Third or more occurrence of this key, append to arrayref
250 8         13 push @{ $response{$1} }, $line;
  8         35  
251             } else {
252             # Second occurrence of this key, convert to arrayref
253 16         70 $response{$1} = [ $response{$1}, $line ];
254             }
255             } # end while reading from $socket
256              
257             # There was a communication failure; return an error.
258             return {
259             Response => 'Error',
260 0         0 Message => $self->{error} = "Reading from socket failed: $!",
261             };
262             } # end read_response
263              
264             #---------------------------------------------------------------------
265              
266              
267 14     14 1 1159 sub error { shift->{error} }
268              
269             #=====================================================================
270             # Package Return Value:
271              
272             1;
273              
274             __END__