#!/usr/bin/perl -w

# The Vovida Software License, Version 1.0 
# 
# Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. The names "VOCAL", "Vovida Open Communication Application Library",
#    and "Vovida Open Communication Application Library (VOCAL)" must
#    not be used to endorse or promote products derived from this
#    software without prior written permission. For written
#    permission, please contact vocal\@vovida.org.
# 
# 4. Products derived from this software may not be called "VOCAL", nor
#    may "VOCAL" appear in their name, without prior written
#    permission of Vovida Networks, Inc.
# 
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
# NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
# NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
# IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES LOSS OF USE, DATA, OR
# PROFITS OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
# 
# ====================================================================
# 
# This software consists of voluntary contributions made by Vovida
# Networks, Inc. and many individuals on behalf of Vovida Networks,
# Inc.  For more information on Vovida Networks, Inc., please see
# <http://www.vovida.org/>.
#

# File: parseEndpoint
# usage:  parseEndpoint LOGFILE_NAME OUTPUT_DIRECTORY
#
# This program takes in a siph323csgw logfile as an input file.  It then
# searches for selected blocks of messages related to endpoint
# registrations.  Each individual blocks of message will then be put into
# a separate file. The name of each output file is made up of the timestamp,
# message type keyword (RRQ, RCF, etc), and the direction of the message.
# Here's an example of the name of a file contain the message RCF going out
# to an endpoint: 000922-172708.566.RCF.out
#
# 


# **************************************************************************
# Begin Main() 
#
#
# hash maps and lists:
# @msgToIpAddressMap      - a map of message type to ip address
# @callIdToRASaddressMap  - a map of call ID to ip address
# @ipAddressToEndPointMap - a map of ip address to endpoint name


# check input arguments and setup output directory
#
if (!defined($ARGV[0]))
{
    print "usage: parseEndpoints LOGFILE_NAME OUTPUT_DIRECTORY";
    print "[default to current directory]\n";
    exit;
}

$infileName  = $ARGV[0];

if (defined($outputdir = $ARGV[1]))
{
    if ( $outputdir =~ /(\/$)/ )
    {
        $outputdir = $ARGV[1];  # ends with a slash
    }
    else
    {
        $outputdir = $ARGV[1]."/";  # add a slash to end
    }
    mkdir($outputdir,0777) unless (-d $outputdir);
}
else
{
   $outputdir = "";  # default to current directory
}

$errLog = $outputdir."parseEndpoints.errorLog";

# remove existing errLog
#
unlink($errLog);

open(ERRLOG, ">$errLog") || die("cannot open $errLog: $!");
print ERRLOG "*** Session for parsing $infileName ***\n";


# initialize global variables:
#
$endPointId = "";
$lastMsg    = "";
$msgKey     = "";

$leftBracelet  = 0;  # counter
$rightBracelet = 0;  # counter

$logTimeStamp  = 0;  # false

open(IN,$infileName) || die("cannot open $infileName for reading: $!");
 
while ($line = <IN>)
{
    if ( $line =~ /CALLTRACE/ )
    {
        # RRQ
        if ( $line =~ / Message: RRQ/ )
        {
            $clientAddress = GetTargetAddress();
            $msgToIpAddressMap{"RRQ"} = $clientAddress;

            @retList = ExtractMsg("RRQ");

            # Set $direction, $timeStamp, $callId (maybe null)
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            @RRQretList   = @retList;
            $RRQtimeStamp = $timeStamp;
            $RRQdirection = $direction;

            $RRQdumped = 0;  # false
            $lastMsg   = "RRQ";
        }
        # REGISTER
        elsif ( $line =~ / Message: REGISTER/ )
        {
            if ($lastMsg eq "RRQ")
	    {
                $msgKey = "RRQ";
	    }
            elsif ($lastMsg eq "URQ")
            {
                $msgKey = "URQ";
            }
            else
            {
                print ERRLOG "Uncorrelated REGISTER message.\n";
                $logTimeStamp = 1;  # true
            }
            $clientAddress = $msgToIpAddressMap{$msgKey};

            @retList = ExtractMsg("REGISTER");

            # Set $direction, $timeStamp, $callId (maybe null)
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            if ($logTimeStamp)
	    {
                print ERRLOG "Time Stamp: $timeStamp\n\n";
                $logTimeStamp = 0;  # reset
                die("No preceding RAS message for REGISTER\n");
	    }

            $callIdToRASaddressMap{$callId} = $clientAddress;

            @REGISTERretList   = @retList;
            $REGISTERcallId    = $callId;
            $REGISTERtimeStamp = $timeStamp;
            $REGISTERdirection = $direction;

            $REGISTERdumped = 0;  # false
            $lastMsg = "REGISTER";
	}
        # REGISTER STATUS
        elsif ( $line =~ / Message: STATUS/ )
        {
            @retList = ExtractMsg("REGISTER_STATUS");

            # Set $direction, $timeStamp, $callId (maybe null)
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();   
                  
            # leave out REGISTER STATUS
            next unless ( $cSeq eq "REGISTER");

            if ( defined(@retList) )
	    {
                @REG_STATUSretList   = @retList;
                $REG_STATUScallId    = $callId;
                $REG_STATUStimeStamp = $timeStamp;
                $REG_STATUSdirection = $direction;
                $REG_STATUSinfo      = $statusInfo;

                $REG_STATUSdumped = 0;  # false
                $lastMsg = "REGISTER_STATUS";
	    }
            else
            {
                next;
            }
	}
        # RCF
        elsif ( $line =~ / Message: RCF/ )
        {
            $clientAddress = GetTargetAddress();

            @retList = ExtractMsg("RCF");

            # Set $direction, $timeStamp, $callId
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            $endPointId = GetEndpointId();     # from the next line
            $ipAddressToEndpointMap{$clientAddress} = $endPointId;

            # dump RCF message
            #
            $fileName = CreateFile($outputdir, 
                                   $endPointId,
                                   $timeStamp, 
                                   ".RCF",
                                   $direction.".H323");

            DumpMsgToFile($fileName, @retList);
            $lastMsg   = "RCF";

            # dump RRQ message
            #
            unless ($RRQdumped)
	    {
                $fileName = CreateFile($outputdir, 
                                       $endPointId,
                                       $RRQtimeStamp, 
                                       ".RRQ",
                                       $RRQdirection.".H323");

                DumpMsgToFile($fileName, @RRQretList);
                $RRQdumped = 1;  # true
	    }

            # dump REGISTER message
            #
            unless ($REGISTERdumped)
	    {
                $rasAddress = $callIdToRASaddressMap{$REGISTERcallId};
                $endPointId = $ipAddressToEndpointMap{$rasAddress};

                $fileName = CreateFile($outputdir, 
                                       $endPointId,
                                       $REGISTERtimeStamp, 
                                       ".REGISTER",
                                       $REGISTERdirection.".SIP");

                DumpMsgToFile($fileName, @REGISTERretList);
                $REGISTERdumped = 1;  # true
	    }

            # dump REGISTER_STATUS message
            #
            unless ($REG_STATUSdumped)
	    {
                $rasAddress = $callIdToRASaddressMap{$REG_STATUScallId};
                $endPointId = $ipAddressToEndpointMap{$rasAddress};

                $fileName = CreateFile($outputdir, 
                                       $endPointId,
                                       $REG_STATUStimeStamp, 
                                       ".STATUS.$REG_STATUSinfo",
                                       $REG_STATUSdirection.".SIP");

                DumpMsgToFile($fileName, @REG_STATUSretList);
                $REG_STATUSdumped = 1;  # true
	    }
        }
        # URQ
        elsif ( $line =~ / Message: URQ/ )
        {
            $clientAddress = GetTargetAddress();
            $msgToIpAddressMap{"URQ"} = $clientAddress;

            @retList = ExtractMsg("URQ");

            # Set $direction, $timeStamp, $callId (maybe null)
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            $endPointId = $ipAddressToEndpointMap{$clientAddress};

            # dump URQ message
            #
            $fileName = CreateFile($outputdir, 
                                   $endPointId,
                                   $timeStamp, 
                                   ".URQ",
                                   $direction.".H323");

            DumpMsgToFile($fileName, @retList);
            $lastMsg = "URQ";
        }
        # URJ
        elsif ( $line =~ / Message: URJ/ )
        {
            $clientAddress = GetTargetAddress();

            @retList = ExtractMsg("URJ");

            # Set $direction, $timeStamp, $callId (maybe null)
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            $endPointId = $ipAddressToEndpointMap{$clientAddress};

            # dump URQ message
            #
            $fileName = CreateFile($outputdir, 
                                   $endPointId,
                                   $timeStamp, 
                                   ".URJ",
                                   $direction.".H323");

            DumpMsgToFile($fileName, @retList);
            $lastMsg = "URJ";
        }
        # UCF
        elsif ( $line =~ / Message: UCF/ )
        {
            $clientAddress = GetTargetAddress();

            @retList = ExtractMsg("UCF");

            # Set $direction, $timeStamp, $callId
            # @retlist will get truncated as a result
            #
            ExtractReturnValuesFromRetList();

            $endPointId = GetEndpointId();     # from the next line

            # dump UCF message
            #
            $fileName = CreateFile($outputdir, 
                                   $endPointId,
                                   $timeStamp, 
                                   ".UCF",
                                   $direction.".H323");

            DumpMsgToFile($fileName, @retList);
            $lastMsg   = "UCF";

        # dump unregistered related messages:

            # dump REGISTER message
            #
            unless ($REGISTERdumped)
	    {
                $rasAddress = $callIdToRASaddressMap{$REGISTERcallId};
                $endPointId = $ipAddressToEndpointMap{$rasAddress};

                $fileName = CreateFile($outputdir, 
                                       $endPointId,
                                       $REGISTERtimeStamp, 
                                       ".REGISTER",
                                       $REGISTERdirection.".SIP");

                DumpMsgToFile($fileName, @REGISTERretList);
                $REGISTERdumped = 1;  # true
	    }

            # dump REGISTER_STATUS message
            #
            unless ($REG_STATUSdumped)
	    {
                $rasAddress = $callIdToRASaddressMap{$REG_STATUScallId};
                $endPointId = $ipAddressToEndpointMap{$rasAddress};

                $fileName = CreateFile($outputdir, 
                                       $endPointId,
                                       $REG_STATUStimeStamp, 
                                       ".STATUS.$REG_STATUSinfo",
                                       $REG_STATUSdirection.".SIP");

                DumpMsgToFile($fileName, @REG_STATUSretList);
                $REG_STATUSdumped = 1;  # true
	    }
        }
    }
}

# dumping any late arriving REGISTER STATUS message
#
unless ($REG_STATUSdumped)
{
    print "late register message timestamp: $REG_STATUStimeStamp\n";

    $rasAddress = $callIdToRASaddressMap{$REG_STATUScallId};
    $endPointId = $ipAddressToEndpointMap{$rasAddress};

    $fileName = CreateFile($outputdir, 
                           $endPointId,
                           $REG_STATUStimeStamp, 
                           ".STATUS.$REG_STATUSinfo",
                           $REG_STATUSdirection.".SIP");

    DumpMsgToFile($fileName, @REG_STATUSretList);
    $REG_STATUSdumped = 1;  # true
}

close(IN)  || die("cannot close $infileName: $!");

print ERRLOG "*** End of Session ***\n";
close(ERRLOG) || die("cannot close $errLog: $!");

#
# End Main() 
# **************************************************************************


# **************************************************************************
# function: CountBracelets()
sub CountBracelets
{
        $leftBracelet++  if ( $line =~ /{$/ );
	$rightBracelet++ if ( $line =~ /}$/ );
} # end CountBracelets()
#
# **************************************************************************


# **************************************************************************
# function resetBraceletCounters()
#
sub ResetBraceletCounters
{
   $leftBracelet  = 0;
   $rightBracelet = 0;
}
#
# **************************************************************************


# **************************************************************************
# function GetTargetAddress()
#
sub GetTargetAddress
{
    if ( $line =~ /Address: AF_INET:(\w+.)(\w+.)(\w+.)(\w+:)(\w+)/ )
    {
        return ($1.$2.$3.$4.$5);
    }
    else
    {
        die("cannot find client address.");
    }
} # end GetTargetAddress()
#
# **************************************************************************


# **************************************************************************
# function GetEndpointName()
#
sub GetEndpointId
{
    $line = <IN>;  # next line
    if ( $line =~ /endpointIdentifier=\"(.*?)\"/ )
    {
        return $1;
    }
    else
    {
        die("cannot find endpoint ID");
    }
} # end GetEndpointId()
#
# **************************************************************************


# **************************************************************************
# function: DumpMsgToFile()
# This function writes to the given file with the content of the message 
# buffer.
#
# input arguments as ordered:
# $_[0] - output file name.
# $_[1] - a list of messages.
#
sub DumpMsgToFile
{
    my($fileName, @msgBuffer) = @_;

    open(OUT, ">$fileName") || die("cannot open $fileName: $!");

    foreach $bufline (@msgBuffer)
    {
        print OUT $bufline;
    }
} # end DumpMsgToFile()
#
# **************************************************************************


# **************************************************************************
# function: CreateFile()
# This function finds the message time stamp from the current line, 
# and form a file name by concatenating with the input arguments.
#
# input arguments as ordered:
# $_[0] - name of output directory.
# $_[1] - endpoint id.
# $_[2] - message type (such as INVITE, setup, ACK, alerting, etc).
# $_[3] - message direction (example: "from.SIP", or "to.SIP", etc.
#
sub CreateFile
{
    # retrieve input parameters
    #
    my($outputDir, $endpt, $timeStamp, $msgType, $direction) = @_;

    # create endpoint directory unless it exists
    unless ( defined($endpt) )
    {
        $endpt = "unknownEndpoint";
    }
    $endptDir = $outputdir.$endpt."/";
    mkdir($endptDir,0777) unless (-d $endptDir);

    # create calls directory unless it exists
    $regDir = $endptDir."registrations/";
    mkdir($regDir,0777) unless (-d $regDir);

    my($outfileName) = $regDir.$timeStamp.$msgType.$direction;

    return $outfileName;
} # end CreateFile()
#
# **************************************************************************


# **************************************************************************
# function: ExtractReturnValuesFromRetList()
# This function extracts return parameters from global list @retList, which
# is returned by ExtractMsg().
#
sub ExtractReturnValuesFromRetList
{
    # reset
    #
    $callId = "";

    # Order is VERY IMPORTANT---last in first out
    #
    $cSeq       = pop(@retList);
    $statusInfo = pop(@retList);
    $direction  = pop(@retList);
    $timeStamp  = pop(@retList);
    $callId     = pop(@retList);

} # end ExtractReturnValuesFromRetList()
#
# **************************************************************************


# **************************************************************************
# function: ExtractMsg()
# This function extracts text that are by the outermost {} for a given
# message.  Each message is unique and is written to a separate file.
#
# input arguments as order:
# $_[0] - type of message (such as RRQ, RCF, etc).
#
# list of output items as ordered:
# $msgBuffer - a buffer contains the message body
# $callId    - the call identifier of the message if encountered.
# $timeStamp - time tagged by the gatekeeper for this message
# $direction - transmission direction (to/from sip/h323).
# $cseq      - the Cseq parameter from a SIP message.
#

sub ExtractMsg
{
    my($cSeq)        = "";
    my($callId)      = "";
    my($timeStamp)   = "";
    my($direction)   = "";
    my($statusInfo)  = "";
    my($callIdFound) = 0;  # false

    local(@msgBuffer) = "";

    my($msgType) = $_[0];

    # extract timestamp, we're in first line and should have the timeStamp
    if ( $line =~ /(\b0\w+.)(\w+.)(\w+)/ )
    {
        $timeStamp = $1.$2.$3;  # add them up
    }
    else
    {
       die("cannot match time stamp pattern");
    }

    # determine direction of message
    if ( $line =~ /Outbound/ )
    {
        $direction = ".to";
    }
    elsif ( $line =~ /Inbound/ )
    {
        $direction = ".from";
    }

    # find the first left bracelet
    while ( !($line =~ /{/) )
    {
        push(@msgBuffer,$line);
        $line = <IN>;
    }
    CountBracelets();
    push(@msgBuffer,$line);

    # extract body of message
    #
    while ($leftBracelet != $rightBracelet)
    {
	$line = <IN>;      # IN filehandle from main
        CountBracelets();  # after every new line

        if ( ($msgType eq "REGISTER" ) && (!$callIdFound) )
	{
            if ( $line =~ /Call-ID: (.*)@/ )
            {
                $callIdFound = 1;  # true
                $callId      = $1;
            }
        }
        elsif ( ($msgType eq "REGISTER_STATUS") ) 
	{

            # get the status info from the SIP line
            #
            if ($line =~ /^SIP/)
            {
                $statusInfo = $line;
                $statusInfo =~ s/\s//g;
                $statusInfo =~ s/SIP\/\d\.\d//;
            }
            elsif ( $line =~ /^CSeq:\s\d\s(\w+)/ )
	    {
                $cSeq = $1;
	    }

            if (!$callIdFound)
	    {
                if ( $line =~ /Call-ID: (.*)@/ )
                {
                    $callIdFound = 1;  # true
                    $callId      = $1;
                }
	    }

            # This block allows us to back out of a non-register status
            # message.  We are only interested in a register status.
            #
            if ( ($line =~ /CSeq:/) && !($line =~ /REGISTER/) )
            {
                @msgBuffer = "";  # reset buffer
                last;  # leave loop
            } 
        }       push(@msgBuffer,$line);
    }
    ResetBraceletCounters();

    return (@msgBuffer, $callId, $timeStamp, $direction, $statusInfo, $cSeq);
} # end ExtractMsg
#
# **************************************************************************

