The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package BGPmon::Fetch;
our $VERSION = '1.09';

use 5.006;
use strict;
use warnings;
use BGPmon::Fetch::Client;
use BGPmon::Fetch::File;
use BGPmon::Fetch::Archive;

require Exporter;
our $AUTOLOAD;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(connect_bgpdata read_xml_message close_connection is_connected messages_read uptime connection_endtime get_error_code get_error_message get_error_msg) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

# Flag to determine type of data source we're connecting to
# 0 for live data, 1 for online archive, 2 for local file
our $use_offline_data = -1;

#error code/message
my %error_code;
my %error_msg;

#Error codes and messages
use constant NO_ERROR_CODE => 0;
use constant NO_ERROR_MSG => 'No Error. Life is good.';
use constant ARGUMENT_ERROR_CODE => 101;
use constant ARGUMENT_ERROR_MSG => 'Invalid number of arguments';
use constant UNCONNECTED_CODE => 102;
use constant UNCONNECTED_MSG => 'Not connected to a data source';
use constant INVALID_FUNCTION_SPECIFIED_CODE => 103;
use constant INVALID_FUNCTION_SPECIFIED_MSG => 'Invalid function name given';

my @function_names = ('init_bgpdata', 'connect_bgpdata', 'read_xml_message',
'close_connection', 'is_connected','uptime','connection_endtime');

for my $function_name (@function_names) {
    $error_code{$function_name} = NO_ERROR_CODE;
    $error_msg{$function_name} = NO_ERROR_MSG;
}

=head1 NAME

BGPmon::Fetch.pm

The BGPmon Fetch module, to connect to a live BGPmon stream,
an online archive of XFB data, or a single XML file.  The interface then
supports "streaming" of XML messages from the given data source.

=head1 SYNOPSIS

The BGPmon::Fetch module provides functionality to connect to one of the
following data sources:
    1) a live BGPmon instance
    2) an archive of XFB data
    3) an individual XML file
The user is then able to read XML messages in sequence from the appropriate
data source and get information about the currently-running connection.

    use BGPmon::Fetch;
    my $ret = init_bgpdata();
    my $ret = connect_bgpdata();
    my $xml_msg = read_xml_message();
    if ( !defined($xml_msg) ){
        print get_error_code() . ": " . get_error_msg();
    }
    my $ret = is_connected();
    my $num_read = messages_read();
    my $uptime = uptime();
    my $ret = close_connection();
    my $downtime = connection_endtime();

=head1 EXPORT

init_bgpdata
connect_bgpdata
read_xml_message
close_connection
is_connected
messages_read
uptime
connection_endtime
get_error_code
get_error_message
get_error_msg

=cut

=head1 SUBROUTINES/METHODS

=head2 init_bgpdata

Initialize parameters for any/all of the Fetch submodules.
File and Archive take the same three optional parameters:

    scratch_dir - a filesystem location to put a scratch directory in
        (default is /tmp)

    ignore_incomplete_data - a flag to turn off checking for possible gaps
        in the data "stream" (default is to check)

    ignore_data_errors - a flag to turn off checking for any other errors
        in the data. Using this flag requires setting ignore_incomplete_data.
        (default is to check)

Client also accepts the same scratch directory argument described above,
but will ignore the data-integrity flags, as a live stream is, well, live.

Usage:      my $ret = init_bgpdata('scratch_dir' => '/tmp',
                                    'ignore_incomplete_data' => 1,
                                    'ignore_data_errors' => 0);
            my $ret = init_bgpdata();
            my $ret = init_bgpdata('scratch_dir' => "~/");

=cut

sub init_bgpdata{
    my @args = @_;
    BGPmon::Fetch::File::init_bgpdata(@args);
    BGPmon::Fetch::Archive::init_bgpdata(@args);
    BGPmon::Fetch::Client::init_bgpdata(@args);

    return 0;
}

=head2 connect_bgpdata

This is a function which connects to the appropriate data source.
If a live data source is specified, the function opens a TCP connection to the
source. If an archived data source is specified, the function processes data
files from the given URL.  If a local file is specified, it will read data
from the file.

Input:  One of the following sets of arguments:
        [server_address, listening port]    -> this will connect to live data
        [URL,start time, end time]          -> this will connect to an archive
        [filename]                          -> this will connect to a file

Output: 1 on failure

=cut

sub connect_bgpdata {
    my $fname = 'connect_bgpdata';
    if (scalar(@_) == 2) { # Live data stream.
        my ($server, $port) = @_;
        $use_offline_data = 0;

        if( BGPmon::Fetch::Client::connect_bgpmon($server,$port) ){
            $error_code{$fname} = BGPmon::Fetch::Client::get_error_code('connect_bgpmon');
            $error_msg{$fname} = BGPmon::Fetch::Client::get_error_message('connect_bgpmon');
            return 1;
        }
    } elsif (scalar(@_) == 3) { # Archived data.
        my ($url, $start, $end) = @_;
        $use_offline_data = 1;

        if( BGPmon::Fetch::Archive::connect_archive($url, $start, $end) ){
            $error_code{$fname} = BGPmon::Fetch::Archive::get_error_code('connect_archive');
            $error_msg{$fname} = BGPmon::Fetch::Archive::get_error_message('connect_archive');
            return 1;
        }
    } elsif (scalar(@_) == 1){
        my $filename = shift;
        $use_offline_data = 2;

        if( BGPmon::Fetch::File::connect_file($filename) ){
            $error_code{$fname} = BGPmon::Fetch::File::get_error_code('connect_file');
            $error_msg{$fname} = BGPmon::Fetch::File::get_error_message('connect_file');
            return 1;
        }
    } else {
        $error_code{$fname} = ARGUMENT_ERROR_CODE;
        $error_msg{$fname} = ARGUMENT_ERROR_MSG;
        return 1;
    }
}

=head2 read_xml_message

This function reads and returns the next XML message from the currently
connected data source, or undef if there is an error OR there are no
more messages available (in File or Archive).

Usage:  my $msg = read_xml_message();

=cut

sub read_xml_message {
    my $fname = 'read_xml_message';
    if ($use_offline_data == 0) {
        my $msg = BGPmon::Fetch::Client::read_xml_message();
        if( !defined($msg) ){
            $error_code{$fname} = BGPmon::Fetch::Client::get_error_code('read_xml_message');
            $error_msg{$fname} = BGPmon::Fetch::Client::get_error_message('read_xml_message');
            return undef;
        }
        $error_code{$fname} = NO_ERROR_CODE;
        $error_msg{$fname} = NO_ERROR_MSG;
        return $msg;
    } elsif ($use_offline_data == 1) {
        my $msg = BGPmon::Fetch::Archive::read_xml_message();
        if( !defined($msg) ){
            $error_code{$fname} = BGPmon::Fetch::Archive::get_error_code('read_xml_message');
            $error_msg{$fname} = BGPmon::Fetch::Archive::get_error_message('read_xml_message');
            return undef;
        }
        else {
            $error_code{$fname} = NO_ERROR_CODE;
            $error_msg{$fname} = NO_ERROR_MSG;
            return $msg;
        }
    } elsif ($use_offline_data == 2) {
        my $msg = BGPmon::Fetch::File::read_xml_message();
        if( !defined($msg) ){
            $error_code{$fname} = BGPmon::Fetch::File::get_error_code('read_xml_message');
            $error_msg{$fname} = BGPmon::Fetch::File::get_error_message('read_xml_message');
            return undef;
        }
        else {
            $error_code{$fname} = NO_ERROR_CODE;
            $error_msg{$fname} = NO_ERROR_MSG;
            return $msg;
        }
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return undef;
    }
}

=head2 close_connection

This subroutine closes an active connection.  For File and Archive, this will
delete the scratch directory and close any open files.  For a live stream,
the TCP connection will be terminated.  The function returns 0 on success,
1 on failure

Usage: close_connection();

=cut

sub close_connection {
    my $fname = 'close_connection';
    if ($use_offline_data == 0) {
        if( BGPmon::Fetch::Client::close_connection() ){
            $error_code{$fname} = BGPmon::Fetch::Client::get_error_code("$fname");
            $error_msg{$fname} = BGPmon::Fetch::Client::get_error_message("$fname");
            return 1;
        }
    } elsif ($use_offline_data == 1) {
        if( BGPmon::Fetch::Archive::close_connection() ){
            $error_code{$fname} = BGPmon::Fetch::Archive::get_error_code("$fname");
            $error_msg{$fname} = BGPmon::Fetch::Archive::get_error_message("$fname");
            return 1;
        }
    } elsif ($use_offline_data == 2) {
        if( BGPmon::Fetch::File::close_connection() ){
            $error_code{$fname} = BGPmon::Fetch::File::get_error_code("$fname");
            $error_msg{$fname} = BGPmon::Fetch::File::get_error_message("$fname");
            return 1;
        }
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return undef;
    }
    return 0;
}

=head2 is_connected

Subroutine that simply returns whether or not there is a currently-active
connection.  If no connection has been initialized, returns undef.

Usage:  if( is_connected ) { #Do stuff }

=cut

sub is_connected {
    my $fname = 'is_connected';
    if ($use_offline_data == 0) {
        return BGPmon::Fetch::Client::is_connected();
    } elsif ($use_offline_data == 1) {
        return BGPmon::Fetch::Archive::is_connected();
    } elsif ($use_offline_data == 2) {
        return BGPmon::Fetch::File::is_connected();
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return undef;
    }
}

=head2 messages_read

Returns the number of messages read from the data source.  This also includes
any additional metadata messages that may be in the stream or within files.

Usage:  my $num_msgs = messages_read();

=cut

sub messages_read {
    my $fname = 'messages_read';
    if ($use_offline_data == 0) {
        return BGPmon::Fetch::Client::messages_read();
    } elsif ($use_offline_data == 1) {
        return BGPmon::Fetch::Archive::messages_read();
    } elsif ($use_offline_data == 2) {
        return BGPmon::Fetch::File::messages_read();
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return undef;
    }
}


=head2 uptime

Returns number of seconds the connection has been up.
If the connection is down or there has never been a connection, return 0.

Usage:  my $up = uptime();

=cut

sub uptime {
    my $fname = 'uptime';
    if ($use_offline_data == 0) {
        return BGPmon::Fetch::Client::uptime();
    } elsif ($use_offline_data == 1) {
        return BGPmon::Fetch::Archive::uptime();
    } elsif ($use_offline_data == 2) {
        return BGPmon::Fetch::File::uptime();
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return 0;
    }
}

=head2 connection_endtime

Returns the time the connection ended.
If the connection is up, return 0.
If there has been no connection, returns -1.

Usage:  my $e_time = connection_endtime();

=cut

sub connection_endtime {
    my $fname = 'connection_endtime';
    if ($use_offline_data == 0) {
        return BGPmon::Fetch::Client::connection_endtime();
    } elsif ($use_offline_data == 1) {
        return BGPmon::Fetch::Archive::connection_endtime();
    } elsif ($use_offline_data == 2) {
        return BGPmon::Fetch::File::connection_endtime();
    } else {
        $error_code{$fname} = UNCONNECTED_CODE;
        $error_msg{$fname} = UNCONNECTED_MSG;
        return -1;
    }
}

=head2 get_error_code

Get the error code for a given function
Input : the name of the function whose error code we should report
Output: the function's error code
        or ARGUMENT_ERROR if the user did not supply a function
        or INVALID_FUNCTION_SPECIFIED if the user provided an invalid function
Usage:  my $err_code = get_error_code("connect_archive");

=cut

sub get_error_code {
    my $function = shift;

    # check we got a function name
    if (!defined($function)) {
        return ARGUMENT_ERROR_CODE;
    }

    return $error_code{$function} if defined($error_code{$function});
    return INVALID_FUNCTION_SPECIFIED_CODE;
}

=head2 get_error_message

Get the error message of a given function
Input : the name of the function whose error message we should report
Output: the function's error message
        or ARGUMENT_ERROR if the user did not supply a function
        or INVALID_FUNCTION_SPECIFIED if the user provided an invalid function
Usage:  my $err_msg = get_error_message("read_xml_message");

=cut

sub get_error_message {
    my $function = shift;

    # check we got a function name
    if (!defined($function)) {
        return ARGUMENT_ERROR_MSG;
    }

    return $error_msg{$function} if defined($error_msg{$function});
    return INVALID_FUNCTION_SPECIFIED_MSG."$function";
}

=head2 get_error_msg

Shorthand call for get_error_message

=cut

sub get_error_msg{
    my $fname = shift;
    return get_error_message($fname);
}

=head1 ERROR CODES AND MESSAGES

The following error codes and messages are defined for the Fetch module.
Additional error codes and messages are defined within Client, File,
and Archive, and can be inspected by running perldoc on each of them.

File uses Error codes 300-399
Archive uses Error codes 400-499
Client uses Error codes 500-599

    0:      No Error
            'No Error. Life is good.'

    101:    Too many or too few arguments were passed to connect_bgpdata or
                get_error_[code/message/msg]
            'Invalid number of arguments'

    102:    There is no connection (via connect_bgpdata) to a data source.
            'Not connected to a data source'

    103:    An invalid function name was passed to get_error_[code/message/msg]
            'Invalid function name specified'

=head1 AUTHOR

Kaustubh Gadkari, C<< <kaustubh at cs.colostate.edu> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bgpmon at netsec.colostate.edu>, or through
the web interface at L<http://bgpmon.netsec.colostate.edu>.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc BGPmon::Fetch

=cut

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2012 Colorado State University

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or
    sell copies of the Software, and to permit persons to whom
    the Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.\

    File: Fetch.pm

    Authors: Kaustubh Gadkari, Dan Massey, Cathie Olschanowsky, Jason Bartlett
    Date: May 21, 2012

=cut

1; # End of BGPmon::Fetch