#!/usr/bin/perl

#
# mgcpgen
# MGCP message generator
# Copyright (C) 2000 Vikram Visweswaraiah <vikram@acc.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# $Id: mgcpgen,v 1.2 2001/06/04 21:28:22 vikram Exp $

=head1 NAME

mgcpgen - MGCP message generator

=head1 SYNOPSIS

B<mgcpgen>
I<command-file>
I<IP-addr>
I<Port>
[I<timeout>]
[I<retrans>]

=head1 DESCRIPTION

B<mgcpgen> generates MGCP messages from a text file and sends them to 
a specified MG. It is possible to have multiple messages in a single
text file by separating each message by a line with a single '%' 
character. This termination character is required even if a single
message is present. 

B<mgcpgen> prints out the messages it sends and the responses it receives.

The command file can have comments. Any line that starts with a '#'
character is not part of the message sent. Any line that starts with
the sequence '#:' is printed out but is not included in the message.

An arbitrary embedded perl command can be executed by starting a line
with the sequence '#exec:' followed by the command. Such sequences
are useful to introduce delays between sending messages by making
calls to perl sleep(). 

In certain cases setting up calls require a connection identifier
sent by the MG. This would require dynamic generation of the command
file. To support this, if a line begins with the sequence 'I:sub',
B<mgcpgen> will substitute the connection id just received from
the MG. 

B<mgcpgen> understands provisional responses and will wait for
a final response with the same timeout restrictions.

Bulk calls are easily generated if you can write simple shell
scripts and use your imagination with B<mgcpgen>. 

The following example shows a valid command file and is one example
of setting up a voice call. 


 CRCX 26 J6.1/3@192.168.55.14 MGCP 1.0
 C:9
 M:inactive
 L:a:PCMA,e:off,t:00
 %
 # introduce a wait of 3 seconds
 #exec:sleep 3
 MDCX 16 J6.1/3@192.168.55.14 MGCP 1.0
 C:9
 # substitute connection id received in response to CRCX.
 I:sub
 M:recvonly
 X:4D4743506
 S:G/rt
 %
 #exec:sleep 3
 MDCX 25 J6.1/3@192.168.55.14 MGCP 1.0
 C:9
 I:sub
 M:sendrecv
 X:4D4743506
 S:
 
 #: Send a RCD - this comment is printed out.
 v=0
 c=IN IP4 19.12.9.8
 m=audio 20000 RTP/AVP 8
 %
 #exec:sleep 25
 DLCX 19 J6.1/3@192.168.55.14 MGCP 1.0
 I:sub
 %

=head1 OPTIONS

=over 8

=item B<command-file>

The text file from which B<mgcpgen> reads commands. See the DESCRIPTION
above and EXAMPLES below.

=item B<IP-addr>

The IP address of the MG receiving the commands.

=item B<Port>

The UDP port to send the commands to.

=item B<timeout>

An optional timeout. The simulator will move on if it doesn't receive
a response within I<timeout> seconds. If not specified, the wait is
indefinite.

=item B<retrans>

An optional retransmission count. When the simulator times out and this
parameter is specified, the simulator will retransmit I<retrans> times
before giving up. The simulator will not retransmit if it has already
received a provisional response.

=back


=head1 EXAMPLES

mgcpgen voice_cmd 129.192.105.33 2427 

    will read commands from the text file named 'voice_cmd'
    and send them to an MG stationed at 129.192.105.33 on port
    2427.

mgcpgen voice_cmd 129.192.105.33 2427 3

    will do the same but will wait a maximum of 3 seconds for
    a response before quitting or sending the next message if
    'voice_cmd' contains multiple messages.

mgcpgen voice_cmd 129.192.105.33 2427 3  3

    will do the same as above but will attempt to retransmit 3
    times on a timeout before continuing.

cat voice_cmd | mgcpgen - 129.192.105.33 2427

    will allow the command file to be specified via STDIN.


=head1 ENVIRONMENT

No special environment variables are used.

=head1 SEE ALSO

mgcsim(1), rfc2705

=head1 BUGS

Bug me

=head1 AUTHOR

Vikram Visweswaraiah (vikram@acc.com)

=head1 SCRIPT CATEGORIES

Networking

=head1 PREREQUISITES

This script requires C<strict> and C<IO::Socket>.

=head1 README

mgcpgen is a barebones MGCP message generation tool.

This script, though standalone, is intended to be used with `mgcsim' also 
available at CPAN scripts. These scripts were mainly developed to test
parsing on a Media Gateway supporting MGCP 1.0. They have been used 
extensively in real test environments for call setup and debugging. Given 
their simple functionality, bugs should be minimal - please let me
<vikram@acc.com> know if you find bugs and/or if you find these 
scripts useful. 

mgcpgen is really minimally `protocol dependent' because the commands
come from a text file. It should easily be ported to other text-based
protocols such as H.248 (the author feels that the bigger effort in the
port is rewriting the pod).

=cut

require 5.001;
use strict;
use IO::Socket;

sub usage {
    die<<END;
Usage: $0 <command_file> <IP addr> <Port> [timeout] [retx]

Send MGCP messages from <command_file> to <IP addr>:<Port>
Wait for a response between sending each message.
If <command file> is "-", then use stdin for command file.
Multiple MGCP messages can be present in a single file. In any
case, each MGCP message is terminated by a line with a single
'%' character.
An optional timeout specifies the time in secs to wait 
between sending messages. If no timeout is specified, it 
implies an infinite wait. If retx is specified, the command
is retransmitted retx times (after a timeout) before giving up.
Refer to the man page for full details.
END
}

&usage if ($#ARGV < 2);

my $cmd_file = $ARGV[0];
my $rem_addr = $ARGV[1];
my $rem_port = $ARGV[2];
my $timeout =  (defined $ARGV[3] ? $ARGV[3] : undef);
my $n_retx =   (defined $ARGV[4] ? $ARGV[4] : 0);
my $sep = '-'x50;
my $max_msglen = 600;

my $mgc = IO::Socket::INET->new(
				Proto    => "udp",
				Type => SOCK_DGRAM,
				PeerAddr => $rem_addr,
				PeerPort => $rem_port,
				)
    or die "cannot start up MGC\n";

my $lcladdr = "localhost";
my $lclport = $mgc->sockport();
my $fh = undef;
my $connid = undef;
my $retx_ctr = $n_retx;
my $termination = 0;

sub main {

    if ($cmd_file ne "-")
    {
	open(*CFILE, "<$cmd_file") || 
	    die "Cannot open command file $cmd_file for reading\n";
	$fh = *CFILE;
    }
    else
    {
	$fh = *STDIN;
    }

    my $respbuf="";
    while (<$fh>)
    {
	if (/^\#:/) # comments to be printed out.
	{
	    print;
	    next;
	}
	if (/^\#exec:(.*)$/) # perl cmd to be executed.
	{
	    eval $1;
	    next;
	}
	next if (/^\#/); # ignore other comments
	if (/^\%/) # end of a command list 
	{
	    $termination = 1;
	    my ($rin, $nfound, $t, $buf);
	    $nfound = 0;
	    # reset retransmit count.
	    $retx_ctr = $n_retx;
send_cmd:
	    cmdfile_send($respbuf, $rem_addr, $rem_port);
	    # wait for resp.
	    vec($rin, fileno($mgc),1) = 1;
	    ($nfound, $t) = select($rin, undef, undef, $timeout);
	    if ($nfound)
	    {
		$mgc->recv($buf, $max_msglen);
		# Extract a connId if present.
		if ($buf =~ /\nI:\s*([\da-fA-F]+)\r?\n/)
		{
		    $connid = $1;
		}
		print "-------------------\n";
		printf "%s", $buf;
		print "-------------------\n";
		if ($buf =~ /^100/)
		{
		    # Provisional response; wait for the actual resp
		    ($nfound, $t) = select($rin, undef, undef, $timeout);
		    if ($nfound)
		    {
			$mgc->recv($buf, $max_msglen);
			# Extract a connId if present.
			if ($buf =~ /\nI:\s*([\da-fA-F]+)\r?\n/)
			{
			    $connid = $1;
			}
			print "-------------------\n";
			printf "%s", $buf;
			print "-------------------\n";
		    }
		    else
		    {
			print "-------------------\n";
			print "TIMED OUT - no response\n";
			print "-------------------\n";
		    }
		}
	    }
	    else
	    {
		print "-------------------\n";
		print "TIMED OUT - no response\n";
		if ($retx_ctr)
		{
		    # Attempt retransmitting.
		    print "Retransmit count: $retx_ctr, retransmitting..\n";
		    print "-------------------\n\n";
		    $retx_ctr--;
		    goto send_cmd;
		}
		print "-------------------\n";
	    }
	    $respbuf="";
	    next;
	}
	#
	# Check for a connid substitution required.
	#
	if ( (/^I:sub/) && defined($connid) )
	{
	    $respbuf = $respbuf."I:$connid\n";
	}
	else
	{
	    $respbuf = $respbuf.$_;
	}
    }
    close CFILE;
}


sub cmdfile_send {

    my ($respbuf, $rem_addr, $port) = @_;
    my (@t, $buf);

    # Send command from command file.
    print "$sep\n";
    @t = localtime;
    print "$t[2]:$t[1]:$t[0] $lcladdr:$lclport ---> $rem_addr:$port\n";
    print "$sep\n";
    print $respbuf;
    $mgc->send($respbuf);

}

&main;
if (!$termination) # couldn't find termination character at all
{
    print STDERR "Cannot find termination character '%'\n";
    print STDERR "No message sent\n";
    exit(1);
}
exit(0);


