package FreeSWITCH::Client; $|=1; use IO::Socket::INET; use IO::Select; use Data::Dumper; $VERSION = "1.0"; sub init($;$) { my $proto = shift; my $args = shift; my $class = ref($proto) || $proto; $self->{_host} = $args->{-host} || "localhost"; $self->{_port} = $args->{-port} || 8021; $self->{_password} = $args->{-password} || undef; $self->{_tolerant} = $args->{-tolerant} || undef; $self->{events} = []; my $me = bless $self,$class; if (!$self->{_password}) { return $me; } if ($me->connect()) { return $me; } else { return undef; } } sub readhash($;$) { my ($self,$to) = @_; my ($can_read) = IO::Select::select($self->{_sel}, undef, undef, $to); my $s = shift @{$can_read}; my @r = (); my $crc = 0; my $h; if ($s) { for (;;) { my $line; for (;;) { my $i = 0; recv $s, $i, 1, 0; if ($i eq "") { $h->{socketerror} = "yes"; return $h; } elsif ($i eq "\n") { $crc++; last; } else { $crc = 0; } $line .= $i; } if (!$line) { last; } push @r, $line; } if (!@r) { return undef; } foreach(@r) { my ($var, $val) = /^([^:]+):[\s\t]*(.*)$/; $h->{lc $var} = $val; } if ($h->{'content-length'}) { if(! defined $h->{body}) { $h->{body} = ""; } while(length($h->{body}) < $h->{'content-length'}) { my $buf; recv $s, $buf, $h->{'content-length'} - length($h->{body}), 0; if ($buf eq '') { $h->{socketerror} = "yes"; return $h; } $h->{body} .= $buf; } } if ($h->{'content-type'} eq "text/event-plain") { my $e = $self->extract_event($h); $h->{has_event} = 1; $h->{event} = $e; } } return $h; } sub error($$) { my($self,$error) = @_; if ($self->{"_tolerant"}) { print "[DIE CROAKED] $error\n"; return 0; } else { die $error; } } sub output($$) { my ($self,$data) = @_; my $s = $self->{_sock}; print $s $data ; } sub get_events($) { my $self = shift; my $e = $self->{events}; $self->{events} = []; return $e; } sub sendmsg($$$) { my $self = shift; my $sendmsg = shift; my $to = shift; my $e; for(;;) { $e = $self->readhash(.1); if ($e && !$e->{socketerror}) { push @{$self->{events}}, $e; } else { last; } } $self->output($sendmsg->{command} . "\n"); foreach(keys %{$sendmsg}) { next if ($_ eq "command"); $self->output("$_" . ": " . $sendmsg->{$_} . "\n"); } $self->output("\n"); for(;;) { $e = $self->readhash(undef); last if $e->{socketerror} or $e->{'content-type'} eq 'command/reply'; push @{$self->{events}}, $e; } return $e; } sub command($$) { my $self = shift; my $reply; my $r = $self->sendmsg({ 'command' => "api " . shift }); if ($r->{body} ne '') { $reply = $r->{body}; } elsif ($r->{'reply-text'} ne '') { $reply = $r->{'reply-text'}; } else { $reply = "socketerror"; } return $reply; } sub disconnect($) { my $self = shift; if ($self->{_sock}) { $self->{_sock}->shutdown(2); $self->{_sock}->close(); } undef $self->{_sock}; delete $self->{_sock}; } sub raw_command($) { my $self = shift; return $self->sendmsg({ 'command' => shift }); } sub htdecode($;$) { my $urlin = shift; my $url = (ref $urlin) ? \$$urlin : \$urlin; $$url =~ s/%([0-9A-Z]{2})/chr hex $1/ieg; $$url; } sub extract_event($$) { my $self = shift; my $r = shift; my %h = $r->{body} =~ /^([^:]+)\s*:\s*([^\n]*)/mg; foreach (keys %h) { my $new = lc $_; if (!($new eq $_)) { # do not delete keys that were already lowercase $h{$new} = $h{$_}; delete $h{$_}; } } foreach(keys %h) { htdecode(\$h{$_}); } return \%h; } sub call_command($$$) { my $self = shift; my $app = shift; my $arg = shift; my $hash = { 'command' => "sendmsg", 'call-command' => "execute", 'execute-app-name' => $app, 'execute-app-arg' => $arg }; return $self->sendmsg($hash); } sub unicast($$$$$$) { my $self = shift; my $hash = { 'command' => "sendmsg", 'call-command' => "unicast", 'local_ip' => $_[0], 'local_port' => $_[1], 'remote_ip' => $_[2], 'remote_port' => $_[3], 'transport' => $_[4] }; return $self->sendmsg($hash); } sub call_data($) { my $self = shift; return $self->{call_data}; } sub accept($;$$) { my $self = shift; my $ip = shift; my $port = shift || 8084; if (!$self->{_lsock}) { $self->{_lsock} = IO::Socket::INET->new(Listen => 10000, LocalAddr => $ip, LocalPort => $port, Reuse => 1, Proto => "tcp") or return $self->error("Cannot listen"); } $self->{_sock} = $self->{_lsock}->accept(); $self->{_sock}->autoflush(1); $self->{_sel} = new IO::Select( $self->{_sock} ); $self->{call_data} = $self->sendmsg({ 'command' => "connect"}); foreach(keys %{$self->{call_data}}) { htdecode(\$self->{call_data}->{$_}); } if ($self->{call_data} =~ /socketerror/) { return 0; } return 1; }; sub connect($) { my $self = shift; $self->{_sock} = new IO::Socket::INET( Proto => 'tcp', PeerAddr => $self->{_host}, PeerPort => $self->{_port} ) or return $self->error("Connection refused $self->{_host} port $self->{_port}"); $self->{_sock}->autoflush(1); #$self->{_sock}->blocking(0); $self->{_sel} = new IO::Select( $self->{_sock} ); my $h = $self->readhash(undef); if ($h->{"content-type"} eq "auth/request") { my $pass = $self->{"_password"}; $h = $self->sendmsg({command => "auth $pass"}); } if ($h->{'reply-text'} =~ "OK") { return 1; } return 0; } 1;