File Coverage

blib/lib/POE/Filter/FSSocket.pm
Criterion Covered Total %
statement 18 104 17.3
branch 0 54 0.0
condition 0 9 0.0
subroutine 6 12 50.0
pod 6 6 100.0
total 30 185 16.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             POE::Filter::FSSocket - a POE filter that parses FreeSWITCH events into hashes
4              
5             =head1 SYNOPSIS
6              
7             #!/usr/bin/perl
8            
9             use warnings;
10             use strict;
11            
12             use POE qw(Component::Client::TCP Filter::FSSocket);
13             use Data::Dumper;
14            
15             POE::Component::Client::TCP->new(
16             'RemoteAddress' => '127.0.0.1',
17             'RemotePort' => '8021',
18             'ServerInput' => \&handle_server_input,
19             'Filter' => 'POE::Filter::FSSocket',
20             );
21            
22             POE::Kernel->run();
23             exit;
24            
25             my $auth_sent = 0;
26             my $password = "ClueCon";
27            
28             sub handle_server_input {
29             my ($heap,$input) = @_[HEAP,ARG0];
30            
31             print Dumper $input;
32            
33            
34             if($input->{'Content-Type'} eq "auth/request") {
35             $auth_sent = 1;
36             $heap->{'server'}->put("auth $password");
37             } elsif ($input->{'Content-Type'} eq "command/reply") {
38             if($auth_sent == 1) {
39             $auth_sent = -1;
40            
41             #do post auth stuff
42             $heap->{'server'}->put("events plain all");
43             }
44             }
45             }
46              
47             =head1 DESCRIPTION
48              
49             POE::Filter::FSSocket parses output from FreeSWITCH into hashes. FreeSWITCH
50             events have a very wide range of keys, the only consistant one being
51             Content-Type. The keys are dependant on the type of events. You must use the
52             plain event type as that is what the filter knows how to parse. You can ask for
53             as many event types as you like or all for everything. You specify a list of
54             event types by putting spaces between them ex: "events plain api log talk"
55              
56             Currently known event types (Event-Name):
57              
58             CUSTOM
59             CHANNEL_CREATE
60             CHANNEL_DESTROY
61             CHANNEL_STATE
62             CHANNEL_ANSWER
63             CHANNEL_HANGUP
64             CHANNEL_EXECUTE
65             CHANNEL_BRIDGE
66             CHANNEL_UNBRIDGE
67             CHANNEL_PROGRESS
68             CHANNEL_OUTGOING
69             CHANNEL_PARK
70             CHANNEL_UNPARK
71             API
72             LOG
73             INBOUND_CHAN
74             OUTBOUND_CHAN
75             STARTUP
76             SHUTDOWN
77             PUBLISH
78             UNPUBLISH
79             TALK
80             NOTALK
81             SESSION_CRASH
82             MODULE_LOAD
83             DTMF
84             MESSAGE
85             CODEC
86             BACKGROUND_JOB
87             ALL
88              
89             Currently handled FreeSWITCH messages (Content-Type):
90              
91             auth/request
92             command/response
93             text/event-plain
94             api/response (data in __DATA__ variable)
95             log/data (data in __DATA__ variable)
96              
97             =cut
98              
99              
100             package POE::Filter::FSSocket;
101              
102 1     1   997 use warnings;
  1         2  
  1         41  
103 1     1   5 use strict;
  1         2  
  1         35  
104              
105 1     1   16 use Carp qw(carp croak);
  1         1  
  1         64  
106 1     1   5 use vars qw($VERSION);
  1         2  
  1         57  
107 1     1   5 use base qw(POE::Filter);
  1         2  
  1         908  
108              
109             $VERSION = '0.07';
110              
111 1     1   1635 use Data::Dumper;
  1         6494  
  1         1251  
112              
113             #self array
114             sub FRAMING_BUFFER() {0}
115             sub PARSER_STATE() {1}
116             sub PARSER_STATENEXT() {2}
117             sub PARSED_RECORD() {3}
118             sub CURRENT_LENGTH() {4}
119             sub STRICT_PARSE() {5}
120             sub DEBUG_LEVEL() {6}
121              
122             #states of the parser
123             sub STATE_WAITING() {1} #looking for new input
124             sub STATE_CLEANUP() {2} #wipe out record separators
125             sub STATE_GETDATA() {3} #have header, get data
126             sub STATE_FLUSH() {4} #puts us back in wait state and tells us to kill the parsed_record
127             sub STATE_TEXTRESPONSE() {5} #used for api output
128              
129             sub new {
130 0     0 1   my $class = shift;
131 0           my %args = @_;
132              
133 0           my $strict = 0;
134 0           my $debug = 0;
135              
136 0 0         if(defined($args{'debug'})) {
137 0           $debug = $args{'debug'};
138             }
139              
140 0 0 0       if(defined($args{'strict'}) && $args{'strict'} == 1) {
141 0           $strict = $args{'strict'};
142             }
143              
144 0   0       my $self = bless [
145             "", #framing buffer
146             STATE_WAITING, #PARSER_STATE
147             undef, #PARSER_STATE
148             {}, #PARSED_RECORD
149             0, #length tracking (for Content-Length when needed)
150             $strict, #whether we should bail on a bad parse or try and save the session
151             $debug, #debug level
152             ], ref($class) || $class;
153              
154 0           return $self;
155             }
156              
157              
158             sub get_one_start {
159 0     0 1   my ($self, $stream) = @_;
160              
161             #take all the chunks and put them in the buffer
162 0           $self->[FRAMING_BUFFER] .= join('', @{$stream});
  0            
163             }
164              
165             sub get_one {
166 0     0 1   my $self = shift;
167              
168 0           my $line;
169              
170 0           while(1) {
171 0           $line = "";
172              
173             #see if we are in line based or length based mode
174 0 0         if($self->[PARSER_STATE] == STATE_TEXTRESPONSE) {
175 0           my $length = $self->[PARSED_RECORD]{'Content-Length'};
176              
177 0 0         if($self->[FRAMING_BUFFER] =~ s/^(.{$length})//s) {
178 0           $self->[PARSER_STATE] = STATE_FLUSH;
179 0           $self->[PARSED_RECORD]->{'__DATA__'} = $1;
180 0           return [ $self->[PARSED_RECORD] ];
181             } else {
182             #not engough in the buffer yet, come back later
183 0           return;
184             }
185             } else { #we are in normal line based mode
186 0 0         if($self->[FRAMING_BUFFER] =~ s/^(.*?)(\x0D\x0A?|\x0A\x0D?)//) {
187 0           $line = $1;
188             } else {
189             #not enough off of the socket yet, come back later
190 0           return [];
191             }
192             }
193            
194 0 0 0       if(($self->[PARSER_STATE] == STATE_WAITING) || ($self->[PARSER_STATE] == STATE_FLUSH)) {
    0          
    0          
195             #see if we need to wipe out the parsed_record info
196 0 0         if($self->[PARSER_STATE] == STATE_FLUSH) {
197 0           delete $self->[PARSED_RECORD];
198 0           $self->[CURRENT_LENGTH] = 0;
199              
200 0           $self->[PARSER_STATE] = STATE_WAITING;
201             }
202              
203 0 0         if($line =~ /Content-Length:\ (\d+)$/) {
    0          
204             #store the length
205 0           $self->[PARSED_RECORD]{'Content-Length'} = $1;
206              
207             #see if we had a place to go from here (we should)
208 0 0         if(defined($self->[PARSER_STATENEXT])) {
209 0           $self->[PARSER_STATE] = $self->[PARSER_STATENEXT];
210 0           $self->[PARSER_STATENEXT] = undef;
211             }
212             } elsif($line =~ /Content-Type:\ (.*)$/) {
213             #store the type of request
214 0           $self->[PARSED_RECORD]{'Content-Type'} = $1;
215              
216 0 0         if($1 eq "auth/request") {
    0          
    0          
    0          
    0          
217 0           $self->[PARSER_STATE] = STATE_CLEANUP;
218 0           $self->[PARSER_STATENEXT] = STATE_FLUSH;
219 0           return [ $self->[PARSED_RECORD] ];
220             } elsif ($1 eq "command/reply") { #do something with this later
221 0           $self->[PARSER_STATE] = STATE_GETDATA;
222             } elsif ($1 eq "text/event-plain") {
223 0           $self->[PARSER_STATE] = STATE_CLEANUP;
224 0           $self->[PARSER_STATENEXT] = STATE_GETDATA;
225             } elsif ($1 eq "api/response") {
226 0           $self->[PARSER_STATENEXT] = STATE_TEXTRESPONSE;
227             } elsif ($1 eq "log/data") {
228 0           $self->[PARSER_STATENEXT] = STATE_TEXTRESPONSE;
229             } else { #unexpected input
230 0           croak ref($self) . " unknown input [" . $self->[PARSER_STATE] . "] (" . $line . ")";
231             }
232             } else {
233             #already in wait state, if we are not in strict, keep going
234 0 0         if($self->[STRICT_PARSE]) {
235 0           croak ref($self) . " unknown input [STATE_WAITING] (" . $line . ")";
236             }
237             }
238             } elsif ($self->[PARSER_STATE] == STATE_CLEANUP) {
239 0 0         if($line eq "") {
240 0 0         if(defined($self->[PARSER_STATENEXT])) {
241 0           $self->[PARSER_STATE] = $self->[PARSER_STATENEXT];
242 0           $self->[PARSER_STATENEXT] = undef;
243             } else {
244 0           $self->[PARSER_STATE] = STATE_WAITING;
245             }
246             } else {
247             #see if we should bail
248 0 0         if($self->[STRICT_PARSE]) {
249 0           croak ref($self) . " unknown input [STATE_CLEANUP] (" . $line . ")";
250             } else {
251             #we are not supposed to bail so try and save our session...
252             #since we are think we should be cleaning up, flush it all away
253 0           $self->[PARSER_STATE] = STATE_FLUSH;
254              
255             #parser fail should be considered critical, if any debug at all, print dump
256 0 0         if($self->[DEBUG_LEVEL]) {
257 0           print STDERR "Parse failed on ($line) in STATE_CLEANUP:\n";
258 0           print STDERR Dumper $self->[PARSED_RECORD];
259             }
260             }
261             }
262             } elsif ($self->[PARSER_STATE] == STATE_GETDATA) {
263 0 0         if($line =~ /^([^:]+):\ (.*)$/) {
    0          
264 0           $self->[PARSED_RECORD]{$1} = $2;
265             } elsif ($line eq "") { #end of event
266 0           $self->[PARSER_STATE] = STATE_FLUSH;
267              
268 0           return [ $self->[PARSED_RECORD] ];
269             } else {
270 0 0         if($self->[STRICT_PARSE]) {
271 0           croak ref($self) . " unknown input [STATE_GETDATA] (" . $line . ")";
272             } else {
273             #flush and run
274 0           $self->[PARSER_STATE] = STATE_FLUSH;
275              
276             #parser fail should be considered critical, if any debug at all, print dump
277 0 0         if($self->[DEBUG_LEVEL]) {
278 0           print STDERR "Parse failed on ($line) in STATE_GETDATA:\n";
279 0           print STDERR Dumper $self->[PARSED_RECORD];
280             }
281             }
282             }
283             }
284             }
285             }
286              
287             sub put {
288 0     0 1   my ($self, $lines) = @_;
289              
290 0           my @row;
291 0           foreach my $line (@$lines) {
292 0           push @row, $line . "\n\n";
293             }
294              
295 0           return \@row;
296            
297             }
298              
299             sub get_pending {
300 0     0 1   my $self = shift;
301 0           return $self->[FRAMING_BUFFER];
302             }
303              
304             sub get {
305 0     0 1   my ($self, $stream) = @_;
306 0           my @return;
307              
308 0           $self->get_one_start($stream);
309 0           while(1) {
310 0           my $next = $self->get_one();
311 0 0         last unless @$next;
312 0           push @return, @$next;
313             }
314              
315 0           return \@return;
316             }
317              
318             1;
319              
320             =head1 SEE ALSO
321              
322             FreeSWITCH - http://www.freeswitch.org/
323              
324             =head1 AUTHORS
325              
326             POE::Filter::FSSocket is written by Paul Tinsley. You can reach him by e-mail
327             at pdt@jackhammer.org.
328              
329             =head1 COPYRIGHT
330              
331             Copyright 2006, Paul Tinsley. All rights are reserved.
332              
333             POE::Filter::FSSocket is free software; it is currently licensed under the MPL
334             license version 1.1.
335              
336             =cut